pax_global_header00006660000000000000000000000064150476370500014521gustar00rootroot0000000000000052 comment=6a8e26d69fb32d61efd398dc1ad9a77122400034 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/000077500000000000000000000000001504763705000205415ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/.cirrus.yml000066400000000000000000000160021504763705000226500ustar00rootroot00000000000000freebsd_task: freebsd_instance: image_family: freebsd-14-2 cpu: 4 memory: 4G install_script: - pkg update - pkg install -y pkgconf cmake gmake capstone4 GraphicsMagick png devel/sdl20 devel/libedit script: - ./configure --enable-debug || { cat config.log; exit 1; } - gmake -j4 - gmake test || { cat Testing/Temporary/LastTest.log; exit 1; } xcode_task: osx_instance: image: ghcr.io/cirruslabs/macos-runner:sonoma env: SDLVERSION: 2.26.5 PNGVERSION: 1.6.48 download_cache: folder: dl_cache populate_script: - if [ ! -d dl_cache ]; then mkdir dl_cache ; fi install_script: # Download and install precompiled SDL2 Framework - if [ ! -f dl_cache/SDL2-$SDLVERSION.dmg ]; then wget -O dl_cache/SDL2-$SDLVERSION.dmg https://github.com/libsdl-org/SDL/releases/download/release-$SDLVERSION/SDL2-$SDLVERSION.dmg ; fi - hdiutil attach dl_cache/SDL2-$SDLVERSION.dmg - sudo cp -a /Volumes/SDL2/SDL2.framework /Library/Frameworks/ # Download, compile and install libpng Framework - if [ ! -e dl_cache/png.framework ]; then wget -O dl_cache/libpng-$PNGVERSION.tar.xz "http://prdownloads.sourceforge.net/libpng/libpng-$PNGVERSION.tar.xz?download" ; tar -xJf dl_cache/libpng-$PNGVERSION.tar.xz ; cd libpng-$PNGVERSION ; cmake -DPNG_FRAMEWORK=ON -DPNG_HARDWARE_OPTIMIZATIONS=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING="10.13" -DCMAKE_OSX_ARCHITECTURES:STRING="arm64;x86_64" . ; cmake --build . --verbose --config Release -j$(sysctl -n hw.ncpu) ; codesign --force -s - png.framework ; cd .. ; mv libpng-$PNGVERSION/png.framework dl_cache/ ; rm -rf libpng-$PNGVERSION ; fi - sudo cp -a dl_cache/png.framework /Library/Frameworks/ # Download and install precompiled portmidi Framework - if [ ! -e dl_cache/portmidi.framework ]; then cd dl_cache ; wget "https://hatari.tuxfamily.org/ci/portmidi.framework.zip" ; unzip portmidi.framework.zip ; mv license.txt portmidi-license.txt ; cd .. ; fi - sudo cp -a dl_cache/portmidi.framework /Library/Frameworks/ # Download and install precompiled capsimage Framework - if [ ! -e dl_cache/capsimage_5.1_macos-x86_64-arm64 ]; then cd dl_cache ; wget "https://hatari.tuxfamily.org/ci/capsimage_5.1_macos-x86_64-arm64_selfsigned.zip" ; unzip capsimage_5.1_macos-x86_64-arm64_selfsigned.zip ; cd capsimage_5.1_macos-x86_64-arm64 ; mv LICENCE.txt DONATIONS.txt README.txt HISTORY.txt CAPSImage.framework ; cd ../.. ; fi - sudo cp -a dl_cache/capsimage_5.1_macos-x86_64-arm64/CAPSImage.framework /Library/Frameworks/ script: - export PATH=/usr/local/bin:$PATH - mkdir build - cd build - cmake -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING="10.13" -DCMAKE_OSX_ARCHITECTURES:STRING="arm64;x86_64" .. || { cat config.log; exit 1; } - cmake --build . --verbose --config Release -j$(sysctl -n hw.ncpu) -t Hatari - cp -a ../dl_cache/portmidi.framework src/hatari.app/Contents/Frameworks/ - cp -a ../dl_cache/capsimage_5.1_macos-x86_64-arm64/CAPSImage.framework src/hatari.app/Contents/Frameworks/ - codesign --force -s - --entitlements ../src/gui-osx/hatari.app.xcent src/Hatari.app - cd .. - mkdir hatari-snapshot - echo $(git rev-parse HEAD) > hatari-snapshot/version.txt - date >> hatari-snapshot/version.txt - cp -a build/src/Hatari.app hatari-snapshot/ - cp -a doc gpl.txt readme.txt hatari-snapshot/ - cp dl_cache/portmidi-license.txt hatari-snapshot/ - zip -r hatari-snapshot.zip hatari-snapshot hatari_artifacts: path: "hatari-snapshot.zip" macos14_task: osx_instance: image: ghcr.io/cirruslabs/macos-runner:sonoma install_script: - brew update - brew install sdl2 libpng make tidy-html5 imagemagick capstone script: - export PATH=/usr/local/bin:$PATH - ./configure --disable-osx-bundle --enable-debug || { cat config.log; exit 1; } - gmake -j$(sysctl -n hw.ncpu) - gmake test || { cat Testing/Temporary/LastTest.log; exit 1; } cygwin_task: windows_container: image: cirrusci/windowsservercore:2019 os_version: 2019 cpu: 4 memory: 4G env: BE: cygwin-gcc install_script: - choco install -y --no-progress cygwin - C:\tools\cygwin\cygwinsetup.exe -q -P make,cmake,gcc-core,pkg-config,zlib-devel,libSDL2-devel,libpng-devel script: - C:\tools\cygwin\bin\bash.exe -lc "cd '%cd%' ; CFLAGS='-Werror -Wno-error=char-subscripts' cmake -G 'Unix Makefiles' ." - C:\tools\cygwin\bin\bash.exe -lc "cd '%cd%' ; make -j4" test_script: - C:\tools\cygwin\bin\bash.exe -lc "cd '%cd%' ; ctest -j4" msys2_task: windows_container: image: cirrusci/windowsservercore:2019 cpu: 4 memory: 4G env: MSYS: winsymlinks:nativestrict MSYSTEM: MINGW64 CHERE_INVOKING: 1 choco_cache: folder: '%temp%\chocolatey' install_script: - choco install -y --no-progress msys2 # Keep the log and temporary files out of the cache: - del %temp%\chocolatey\*.log - del %temp%\chocolatey\*log.txt - del %temp%\chocolatey\*.tmp # Install the required libraries: - C:\tools\msys64\usr\bin\bash -lc "pacman --noconfirm -S --needed make pkg-config diffutils mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2 mingw-w64-x86_64-libpng mingw-w64-x86_64-portmidi" script: - C:\tools\msys64\usr\bin\bash -lc "cmake -G 'MSYS Makefiles' ." - C:\tools\msys64\usr\bin\bash -lc "make -j4" test_script: - C:\tools\msys64\usr\bin\bash -lc "ctest" visualstudio_task: windows_container: image: cirrusci/windowsservercore:2019 cpu: 4 memory: 4G choco_cache: folder: '%temp%\chocolatey' install_script: - choco install -y --no-progress cmake --install-arguments="ADD_CMAKE_TO_PATH=System" - choco install -y --no-progress visualstudio2019community visualstudio2019-workload-vctools # Keep the log and temporary files out of the cache: - del %temp%\chocolatey\*.log - del %temp%\chocolatey\*log.txt - del %temp%\chocolatey\*.tmp - del %temp%\chocolatey\windowssdk\*.log - del %temp%\chocolatey\VSLogs\*.svclog # Install the required libraries and headers: - curl -LO "https://github.com/libsdl-org/SDL/releases/download/release-2.32.2/SDL2-devel-2.32.2-VC.zip" - powershell -command "Expand-Archive -Force '%cd%\SDL2-devel-2.32.2-VC.zip' '%cd%'" - curl -O https://raw.githubusercontent.com/barrysteyn/scrypt-windows/master/win/include/unistd.h - curl -O https://raw.githubusercontent.com/tronkko/dirent/master/include/dirent.h - echo // > getopt.h script: - refreshenv - cmake -G "Visual Studio 16 2019" -A X64 -DCMAKE_BUILD_TYPE="Release" -DSDL2_DIR:PATH=SDL2-2.32.2\cmake . - cmake --build . --verbose --target ALL_BUILD --config Release -j4 test_script: - refreshenv - ctest -C Release hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/.gitignore000066400000000000000000000011301504763705000225240ustar00rootroot00000000000000*.1.gz *.a *.o *.pyc config.h CMakeCache.txt cmake_install.cmake CTestTestfile.cmake CMakeFiles install_manifest.txt Makefile Testing src/hatari src/tos.img src/cpu/build68k src/cpu/cpudefs.c src/cpu/cpuemu_*.c src/cpu/cpustbl.c src/cpu/cputbl.h src/cpu/gencpu tests/debugger/test-breakcond tests/debugger/test-evaluate tests/debugger/test-symbols tests/unit/test-file tools/hmsa/hmsa tools/debugger/gst2ascii python-ui/conftypes.py doc/doxygen/html # These are for XCode on Mac OS X: build/* Hatari.build/* Release/* Debug/* # Created by the Eclipse IDE (CDT) .cproject .project .settings/ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/.gitlab-ci.yml000066400000000000000000000125421504763705000232010ustar00rootroot00000000000000before_script: - apt-get update -qq build-minimal: script: - apt-get install -y -qq cmake libsdl2-dev tidy - rm -rf /usr/include/zlib.h /usr/include/png.h /usr/include/readline* - CFLAGS="-D_FORTIFY_SOURCE=3" ./configure --disable-dsp --enable-debug --disable-tracing --enable-werror - make -j$(nproc) - ctest -j$(nproc) build-32bit: script: - dpkg --add-architecture i386 - apt-get update -qq - apt-get install -y cmake clang file gcc-multilib libgcc1:i386 libportmidi-dev:i386 libpng-dev:i386 libglib2.0-dev:i386 zlib1g-dev:i386 libsdl2-dev:i386 libudev-dev:i386 libreadline-dev:i386 - CC="clang" CFLAGS="-m32 -O3 -Werror -D_FORTIFY_SOURCE=3" ./configure --enable-debug - make -j$(nproc) - file src/hatari | grep 32.bit - ctest -j$(nproc) build-mingw: image: fedora:41 variables: GIT_DEPTH: 1000 cache: paths: - libs before_script: - dnf update -y - dnf install -y autoconf cmake git make gawk gcc mingw64-gcc mingw64-gcc-c++ mingw64-libstdc++ mingw64-SDL2 mingw64-zlib mingw64-libpng script: - if [ ! -d libs ]; then mkdir libs ; fi - cd libs # Prepare CAPS library - MINGW_PREFIX=/usr/x86_64-w64-mingw32/sys-root/mingw - if [ ! -d capsimg ]; then ../ci/build-mingw-capsimg.sh; fi - cp capsimg/CAPSImg/capsimg.dll $MINGW_PREFIX/bin/ - cp capsimg/CAPSImg/capsimg.dll.a $MINGW_PREFIX/lib/ - mkdir -p $MINGW_PREFIX/include/caps - cp capsimg/LibIPF/* capsimg/Core/CommonTypes.h $MINGW_PREFIX/include/caps # Prepare PortMidi library - if [ ! -d portmidi ]; then git clone https://github.com/PortMidi/portmidi.git --shallow-since=2025-01-01 -c advice.detachedHead=false && cd portmidi && git checkout 806aa16c7d3c && cmake -DCMAKE_TOOLCHAIN_FILE=../../cmake/Toolchain-mingw32-win64_64.cmake . && make -j$(nproc) && cd .. ; fi - cp portmidi/libportmidi.dll $MINGW_PREFIX/bin/ - cp portmidi/libportmidi.dll.a $MINGW_PREFIX/lib/ - cp -v portmidi/pm_common/*.h $MINGW_PREFIX/include/ # Now build Hatari - mkdir ../build - cd ../build - VERSION=$(git describe) - cmake -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-mingw32-win64_64.cmake -DENABLE_WERROR:BOOL=1 .. - make -j$(nproc) - cd .. # And finally create the package - mkdir hatari-$VERSION - mv build/src/hatari.exe gpl.txt readme.txt hatari-$VERSION/ - cp -r doc hatari-$VERSION/ - cd hatari-$VERSION/doc - rm CMakeLists.txt hatari.1 hatari-ui.html release-checklist.txt - rm -r de fr doxygen - cd ../.. - DLLS=(SDL2.dll capsimg.dll zlib1.dll libpng16-16.dll libwinpthread-1.dll libportmidi.dll libstdc++-6.dll libgcc_s_seh-1.dll) - cp ${DLLS[@]/#/\/usr\/x86_64-w64-mingw32\/sys-root\/mingw\/bin\//} hatari-$VERSION/ - cp libs/capsimg/LICENCE.txt hatari-$VERSION/capsimg-license.txt - cp libs/portmidi/license.txt hatari-$VERSION/portmidi-license.txt - zip -r hatari-$VERSION-win64.zip hatari-$VERSION/ artifacts: paths: - ./*.zip build-fedora: image: fedora:latest variables: GIT_DEPTH: 1000 before_script: - dnf update -y - dnf install -y cmake make gcc diffutils python-unversioned-command capstone-devel GraphicsMagick SDL2-devel libpng-devel zlib-devel tidy python3-gobject gtk3 readline-devel man git rpmdevtools script: - rpmdev-setuptree - VERSION=$(git describe | sed -e s,-,^, -e s,-,.,) - sed -i s/^\#define\ PROG_NAME\ .*/\#define\ PROG_NAME\ \"Hatari\ $VERSION\"/ src/includes/version.h - git diff > ~/rpmbuild/SOURCES/hatari-version.patch - echo Patch1\:\ hatari-version.patch > ~/rpmbuild/SPECS/hatari.spec - sed s/^Version:.*/Version:\ $VERSION/ < hatari.spec >> ~/rpmbuild/SPECS/hatari.spec - git archive --prefix=hatari-$VERSION/ -o ~/rpmbuild/SOURCES/hatari-$VERSION.tar.bz2 HEAD - rpmbuild -ba ~/rpmbuild/SPECS/hatari.spec - cp ~/rpmbuild/SRPMS/hatari-v*.rpm ~/rpmbuild/RPMS/x86_64/hatari-v*.rpm . artifacts: paths: - ./*.rpm build-emscripten: image: emscripten/emsdk:4.0.4 cache: paths: - files script: - if [ ! -d files ]; then wget -O emutos.zip https://sourceforge.net/projects/emutos/files/emutos/1.3/emutos-256k-1.3.zip/download ; unzip emutos.zip ; mkdir -p files/fs ; mv emutos-256k-1.3/etos256us.img files/tos.img ; cd files/fs ; wget https://framagit.org/hatari/releases/-/raw/main/v1.9/hatari19.prg ; wget https://framagit.org/hatari/releases/-/raw/main/v2.1/hatari21.prg ; wget https://framagit.org/hatari/releases/-/raw/main/v2.2/hatari22.prg ; cd ../.. ; fi - emcmake cmake . - cmake --build . -j $(nproc) artifacts: when: on_success expire_in: 2 days paths: - src/hatari.* pages: stage: deploy needs: - job: build-emscripten artifacts: true script: - mkdir -p public/doc - cp doc/*.html doc/*.css doc/*.js doc/*.txt public/doc/ - cp -r doc/images public/doc/ - apt-get install -y -qq doxygen - cd doc/doxygen/ ; doxygen ; cd ../.. - cp -r doc/doxygen/html public/doxygen/ - mkdir public/online - cp src/hatari.* public/online/ artifacts: paths: - public rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/.travis.yml000066400000000000000000000007661504763705000226630ustar00rootroot00000000000000dist: jammy language: c compiler: - gcc addons: apt: packages: - libcapstone-dev - libpng-dev - libreadline-dev - libsdl2-dev - imagemagick - tidy before_script: - ./configure ${CONFIG} script: - make -j3 && ctest -j2 matrix: include: - env: - CONFIG="--enable-werror --enable-debug" arch: ppc64le script: - make -j4 && ctest -j$(nproc) - env: - CONFIG="--enable-werror --enable-debug" arch: s390x hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/CMakeLists.txt000066400000000000000000000344111504763705000233040ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12 FATAL_ERROR) # Set build type to "Release" if user did not specify any build type yet # Other possible values: Debug, Release, RelWithDebInfo and MinSizeRel if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif(NOT CMAKE_BUILD_TYPE) if(APPLE AND CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Target architectures") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Target minimum macOS version") endif(APPLE AND CMAKE_BUILD_TYPE STREQUAL "Release") project(Hatari C) enable_testing() set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") include(CheckIncludeFiles) include(CheckFunctionExists) include(CheckStructHasMember) include(CheckSymbolExists) include(CheckCCompilerFlag) include(DistClean) include(CheckTypeSize) # set(CMAKE_VERBOSE_MAKEFILE 1) find_package(PkgConfig) ###################################################################### # Check if Large File Support is available # We test if the size of the type 'off_t' is at least 8 bytes ###################################################################### function (check_large_file) set (CMAKE_REQUIRED_FLAGS ${HATARI_LFS_FLAGS} ) check_type_size("off_t" SIZEOF_OFF_T ) if ( SIZEOF_OFF_T GREATER 7 ) set ( HATARI_HAVE_LFS 1 CACHE INTERNAL "Large file Support" ) else() set ( HATARI_HAVE_LFS 0 CACHE INTERNAL "Large file Support" ) endif() endfunction (check_large_file) # ########################## # Conditional build features # ########################## set(ENABLE_DSP_EMU 1 CACHE BOOL "Enable DSP 56k emulator for Falcon mode") set(ENABLE_TRACING 1 CACHE BOOL "Enable tracing messages for debugging") # Run-time checks with GCC / LLVM (Clang) AddressSanitizer: # - stack protection # - checking of pointer accesses # # Configure Hatari with AddressSanitizer: # cd build; make clean; cmake -D ENABLE_ASAN:BOOL=1 -D .. # If everything's fine, CMake output should include: # Performing Test ASAN_AVAILABLE - Success" # # After (re-)building Hatari, run it as following to see # available AddressSanitizer options (and that it works): # ASAN_OPTIONS=help=true src/hatari # # For more info: # https://github.com/google/sanitizers/wiki/AddressSanitizer # CHECK_C_COMPILER_FLAG("-fsanitize=address" ASAN_AVAILABLE) if(ASAN_AVAILABLE) set(ENABLE_ASAN 0 CACHE BOOL "Enable GCC/LLVM run-time stack/pointer debugging (~2x slowdown)") endif(ASAN_AVAILABLE) CHECK_C_COMPILER_FLAG("-fsanitize=undefined" UBSAN_AVAILABLE) if(UBSAN_AVAILABLE) set(ENABLE_UBSAN 0 CACHE BOOL "Enable GCC/LLVM run-time undefined behavior debugging") endif(UBSAN_AVAILABLE) find_program(GZIP gzip) if(UNIX AND GZIP) set(ENABLE_MAN_PAGES 1 CACHE BOOL "Built and install man pages") else() set(ENABLE_MAN_PAGES 0 CACHE BOOL "Built and install man pages") endif() if(APPLE) set(ENABLE_OSX_BUNDLE 1 CACHE BOOL "Built Hatari as macOS application bundle") set(CMAKE_XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@loader_path/../Frameworks") else() set(ENABLE_OSX_BUNDLE 0 CACHE BOOL "Built Hatari as macOS application bundle") endif(APPLE) if(ENABLE_OSX_BUNDLE) set(APP_NAME "Hatari") else() set(APP_NAME "hatari") endif(ENABLE_OSX_BUNDLE) # #################### # Check for libraries: # #################### find_package(SDL2) if(NOT SDL2_FOUND) message(FATAL_ERROR "SDL2 library not found!") endif(NOT SDL2_FOUND) find_package(Math) find_package(Readline) if(Readline_FOUND) set(HAVE_LIBREADLINE 1) endif(Readline_FOUND) find_package(ZLIB) if(ZLIB_FOUND) set(HAVE_LIBZ 1) set(HAVE_ZLIB_H 1) endif(ZLIB_FOUND) find_package(PNG) if(PNG_FOUND) set(HAVE_LIBPNG 1) endif(PNG_FOUND) if (NOT ENABLE_OSX_BUNDLE) find_package(X11) if(X11_FOUND) set(HAVE_X11 1) endif(X11_FOUND) endif() find_package(PortMidi) if(PortMidi_FOUND) set(HAVE_PORTMIDI 1) endif(PortMidi_FOUND) find_package(CapsImage) if(CapsImage_FOUND) set(HAVE_CAPSIMAGE 1) endif(CapsImage_FOUND) find_package(Udev) if(Udev_FOUND) set(HAVE_UDEV 1) endif(Udev_FOUND) find_package(Capstone) if(Capstone_FOUND) set(CMAKE_REQUIRED_INCLUDES ${CAPSTONE_INCLUDE_DIR}) CHECK_INCLUDE_FILE("m68k.h" HAVE_CAPSTONE_M68K) unset(CMAKE_REQUIRED_INCLUDES) if(NOT HAVE_CAPSTONE_M68K) unset(Capstone_FOUND) endif() endif(Capstone_FOUND) # Check if Large File Support is available with the standard POSIX flags set (HATARI_LFS_FLAGS "-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64") check_large_file() # ################ # CPP Definitions: # ################ # Test for large file support: execute_process(COMMAND getconf LFS_CFLAGS OUTPUT_VARIABLE DETECTED_LFS_CFLAGS ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(DETECTED_LFS_CFLAGS) add_definitions(${DETECTED_LFS_CFLAGS}) # message(STATUS "Large filesystem flags: ${DETECTED_LFS_CFLAGS}") endif(DETECTED_LFS_CFLAGS) # Additional CFLAGS suggested by the SDL library: if(PKG_CONFIG_FOUND) execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --cflags-only-other sdl2 OUTPUT_VARIABLE SDL2_OTHER_CFLAGS ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) endif(PKG_CONFIG_FOUND) if(ENABLE_OSX_BUNDLE) # Use OSX native alert windows add_definitions(-DALERT_HOOKS=1) # We still want to use our SDLMain.m with SDL2 add_definitions(-DSDL_MAIN_NEEDED=1) endif(ENABLE_OSX_BUNDLE) if(MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif(MSVC) # ########################### # Check for optional headers: # ########################### check_include_files(byteswap.h HAVE_BYTESWAP_H) check_include_files(termios.h HAVE_TERMIOS_H) check_include_files(sys/ioctl.h HAVE_SYS_IOCTL_H) check_include_files(strings.h HAVE_STRINGS_H) check_include_files(sys/time.h HAVE_SYS_TIME_H) check_include_files(sys/times.h HAVE_SYS_TIMES_H) check_include_files(utime.h HAVE_UTIME_H) check_include_files(sys/utime.h HAVE_SYS_UTIME_H) check_include_files("sys/socket.h;sys/un.h" HAVE_UNIX_DOMAIN_SOCKETS) # ############################# # Check for optional functions: # ############################# check_symbol_exists(bswap_16 "byteswap.h" HAVE_BSWAP_16) check_symbol_exists(bswap_32 "byteswap.h" HAVE_BSWAP_32) check_symbol_exists(cfmakeraw "termios.h" HAVE_CFMAKERAW) check_symbol_exists(tcsetattr "termios.h" HAVE_TCSETATTR) check_symbol_exists(setenv "stdlib.h" HAVE_SETENV) check_symbol_exists(select "sys/select.h" HAVE_SELECT) check_symbol_exists(gettimeofday "sys/time.h" HAVE_GETTIMEOFDAY) check_symbol_exists(nanosleep "time.h" HAVE_NANOSLEEP) check_symbol_exists(alphasort "dirent.h" HAVE_ALPHASORT) check_symbol_exists(scandir "dirent.h" HAVE_SCANDIR) check_symbol_exists(statvfs "sys/statvfs.h" HAVE_STATVFS) check_symbol_exists(fseeko "stdio.h" HAVE_FSEEKO) check_symbol_exists(ftello "stdio.h" HAVE_FTELLO) check_symbol_exists(flock "sys/file.h" HAVE_FLOCK) check_struct_has_member("struct dirent" d_type dirent.h HAVE_DIRENT_D_TYPE) # ############# # Other CFLAGS: # ############# # GCC/Clang stack/pointer debugging (~2x slowdown) if(ENABLE_ASAN) # better backtraces set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -fno-omit-frame-pointer") # enable stack protection set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-all") # enable AddressSanitizer with global variable tracking set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-common") endif(ENABLE_ASAN) # GCC/Clang undefined behavior debugging if(ENABLE_UBSAN) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") add_definitions(-DENABLE_UBSAN=1) endif(ENABLE_UBSAN) # GCC/Clang specific flags: if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") # We want to allow ‘for’-loop initial declarations a la for(int i=0; ...) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") # Warning flags: set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wcast-qual -Wbad-function-cast -Wpointer-arith") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-prototypes -Wstrict-prototypes") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wwrite-strings -Wsign-compare") #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wno-unused-parameter -Wno-empty-body") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wformat-security") endif() if(APPLE AND CMAKE_C_COMPILER_ID MATCHES "Clang") # Silence linker warning with AppleClang 17 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-common") endif() CHECK_C_COMPILER_FLAG("-Wimplicit-fallthrough=2" WARN_FALLTRHOUGH_AVAILABLE) if(WARN_FALLTRHOUGH_AVAILABLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wimplicit-fallthrough=2") endif(WARN_FALLTRHOUGH_AVAILABLE) CHECK_C_COMPILER_FLAG("-Wshadow=local" WARN_SHADOW_LOCAL_AVAILABLE) if(WARN_SHADOW_LOCAL_AVAILABLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow=local") endif(WARN_SHADOW_LOCAL_AVAILABLE) CHECK_C_COMPILER_FLAG("-Wvla" WARN_VARIABLE_LENGTH_ARRAY_AVAILABLE) if(WARN_VARIABLE_LENGTH_ARRAY_AVAILABLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wvla") endif(WARN_VARIABLE_LENGTH_ARRAY_AVAILABLE) if(EMSCRIPTEN) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-rtti -fno-exceptions -O3 -s USE_SDL=2 -s USE_ZLIB=1") set(CMAKE_EXECUTABLE_SUFFIX ".html") endif() if(ENABLE_WERROR) if (ENABLE_TRACING) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wno-error=unused-function") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=unused-but-set-variable") endif() endif(ENABLE_WERROR) # Building Hatari w/o optimization is no fun... IF (CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_C_FLAGS "-O ${CMAKE_C_FLAGS}") ENDIF (CMAKE_BUILD_TYPE STREQUAL "Debug") # Always add the Large File Support flags in case they are supported set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${HATARI_LFS_FLAGS}") # #################### # Paths configuration: # #################### if(NOT BINDIR) set(BINDIR bin) endif() if(NOT DATADIR) set(DATADIR share/hatari) endif() if(NOT BIN2DATADIR) if(WIN32) set(BIN2DATADIR "." CACHE STRING "Relative path from bindir to datadir") elseif(ENABLE_OSX_BUNDLE) set(BIN2DATADIR "../Resources" CACHE STRING "Relative path from bindir to datadir") else() set(BIN2DATADIR "../share/hatari" CACHE STRING "Relative path from bindir to datadir") endif(WIN32) mark_as_advanced(BIN2DATADIR) endif() if(NOT MANDIR) set(MANDIR share/man/man1) endif() if(NOT DOCDIR) set(DOCDIR share/doc/hatari) endif() if(NOT ETCDIR) if(WIN32) set(ETCDIR .) else() set(ETCDIR /etc) endif() endif() if(NOT ICONDIR) set(ICONDIR share/icons/hicolor) endif() if(ENABLE_OSX_BUNDLE) # put the config files in the app's bundle add_definitions(-DCONFDIR=\"../Resources\") else() add_definitions(-DCONFDIR=\"${ETCDIR}\") endif() # ######################################### # Create config.h and recurse into subdirs: # ######################################### configure_file(${CMAKE_SOURCE_DIR}/cmake/config-cmake.h ${CMAKE_BINARY_DIR}/config.h) add_subdirectory(src) add_subdirectory(doc) add_subdirectory(tools) if(NOT CMAKE_CROSSCOMPILING) add_subdirectory(tests) endif(NOT CMAKE_CROSSCOMPILING) include(FindPython) if(Python_Interpreter_FOUND AND Python_VERSION_MAJOR LESS 3) message("Note: Hatari needs at least Python 3 ... ignoring older version") unset(Python_Interpreter_FOUND) endif() set(PYTHON_GTK_FOUND 0) if(Python_Interpreter_FOUND) execute_process(COMMAND ${Python_EXECUTABLE} -c "\ import gi\n\ gi.require_version('Gtk', '3.0')\n\ from gi.repository import Gtk\n\ from gi.repository import Gdk\n\ from gi.repository import GdkPixbuf\n\ from gi.repository import GLib" RESULT_VARIABLE PYTHON_GTK_RESULT OUTPUT_QUIET ERROR_QUIET) if(${PYTHON_GTK_RESULT} EQUAL 0) set(PYTHON_GTK_FOUND 1) add_subdirectory(python-ui) endif() endif(Python_Interpreter_FOUND) if(UNIX AND NOT ENABLE_OSX_BUNDLE) add_subdirectory(share) endif() add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake/Uninstall.cmake) # ################################################################### # Print a summary of the optional libraries with a short explanation: # ################################################################### message( " Libraries summary : ------------------- ") message(" - sdl :\tusing SDL2 ${SDL2_VERSION}") if(Readline_FOUND) message( " - readline :\tfound, enables history/completion in the debugger" ) else() message( " - readline :\tnot found, install it to enable debugger history/completion" ) endif(Readline_FOUND) if(ZLIB_FOUND) message( " - zlib :\tfound, allows to use zip/gz files directly" ) else() message( " - zlib :\tnot found, install it to use zip/gz files" ) endif(ZLIB_FOUND) if(PNG_FOUND) message( " - png :\tfound, allows to compress screenshot/avi files using png" ) else() message( " - png :\tnot found, install it to compress screenshot/avi files using png" ) endif(PNG_FOUND) if(PortMidi_FOUND) message( " - portmidi :\tfound, required for (non-Linux) MIDI support" ) else() message( " - portmidi :\tnot found, install it for MIDI support on Windows / OSX" ) endif(PortMidi_FOUND) if(CapsImage_FOUND) message( " - capsimage :\tv5 found, allow to use .IPF, .RAW and .CTR disk images" ) else() message( " - capsimage :\tv5 not found, install it to use .IPF, .RAW and .CTR disk images" ) endif(CapsImage_FOUND) if(PYTHON_GTK_FOUND) message( " - python Gtk:\tfound, python-ui can be used" ) else() message( " - python Gtk:\tnot found, install it to enable the python-ui" ) endif(PYTHON_GTK_FOUND) if(Udev_FOUND) message( " - udev :\tfound, required for media change detection in NatFeats SCSI" ) message( " \tdevices on udev-based systems (Linux)" ) else() if(UNIX AND NOT APPLE AND NOT CYGWIN) message( " - udev :\tnot found, install it to enable media change detection in" ) message( " \tNatFeats SCSI devices on udev-based systems (Linux)" ) endif() endif(Udev_FOUND) if(Capstone_FOUND) message( " - capstone :\tfound, allows nice disassembly with --disasm ext" ) else() message( " - capstone :\tnot found, install it to use extend disassembly options" ) endif(Capstone_FOUND) if (HATARI_HAVE_LFS) message(" - LFS :\tLarge File Support is available (size of off_t = ${SIZEOF_OFF_T}),") message(" \tAVI recording and HD image files can be bigger than 2 GB") else() message(" - LFS :\tLarge File Support is NOT available (size of off_t = ${SIZEOF_OFF_T}),") message(" \tAVI recording and HD image files will be limited to 2 GB") endif() if(NOT HAVE_SYS_TIMES_H) message("\n Note: times() function is missing (sys/times.h is not available)") message(" ==> using inaccurate SDL_GetTicks() instead") endif() message( "" ) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/ci/000077500000000000000000000000001504763705000211345ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/ci/build-mingw-capsimg.sh000077500000000000000000000006751504763705000253420ustar00rootroot00000000000000#!/bin/bash set -e TARGET=x86_64-w64-mingw32 git clone https://github.com/FrodeSolheim/capsimg.git --depth=1 -b v5.1.2 \ -c advice.detachedHead=false cd capsimg ./bootstrap ./configure --host=${TARGET} sed -i 's/\-g /-pipe -fPIC /' CAPSImg/Makefile make -C CAPSImg -j$(nproc) find . -name '*.dll' | cut -f 1 -d : | xargs ${TARGET}-strip -s find . -name '*.a' | xargs ${TARGET}-strip -g sed -i -e '/Linux change/,+5d' Core/CommonTypes.h hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/000077500000000000000000000000001504763705000216215ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/DistClean.cmake000066400000000000000000000016131504763705000244720ustar00rootroot00000000000000# # "distclean" target for removing the generated files from CMake # if(UNIX) add_custom_target(distclean COMMENT "Cleaning up for distribution") if (CMAKE_GENERATOR STREQUAL "Unix Makefiles") add_custom_command(TARGET distclean POST_BUILD COMMAND make clean) endif() # Clean up Hatari specific files: foreach(CLEAN_FILE config.h install_manifest.txt python-ui/conftypes.py src/*cpu/cpudefs.c src/*cpu/cpuemu*.c src/*cpu/cpustbl.c src/*cpu/cputbl.h) add_custom_command(TARGET distclean POST_BUILD COMMAND rm -f ${CLEAN_FILE}) endforeach(CLEAN_FILE) # Clean up files that can appear at multiple places: foreach(CLEAN_FILE CMakeFiles CMakeCache.txt cmake_install.cmake CTestTestfile.cmake Makefile Testing '*.a' '*.1.gz' '*.pyc') add_custom_command(TARGET distclean POST_BUILD COMMAND find . -depth -name ${CLEAN_FILE} | xargs rm -rf) endforeach(CLEAN_FILE) endif(UNIX) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindCapsImage.cmake000066400000000000000000000011701504763705000252540ustar00rootroot00000000000000 IF (CAPSIMAGE_INCLUDE_DIR) # Already in cache, be silent SET(CAPSIMAGE_FIND_QUIETLY TRUE) ENDIF (CAPSIMAGE_INCLUDE_DIR) SET(CAPSIMAGE_DIR caps) FIND_PATH(CAPSIMAGE_INCLUDE_DIR ${CAPSIMAGE_DIR}/CapsAPI.h) if(WIN32) FIND_LIBRARY(CAPSIMAGE_LIBRARY NAMES capsimg PATH_SUFFIXES ${CAPSIMAGE_DIR} ) else() FIND_LIBRARY(CAPSIMAGE_LIBRARY NAMES capsimage PATH_SUFFIXES ${CAPSIMAGE_DIR} ) endif(WIN32) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(CapsImage DEFAULT_MSG CAPSIMAGE_LIBRARY CAPSIMAGE_INCLUDE_DIR) MARK_AS_ADVANCED(CAPSIMAGE_LIBRARY CAPSIMAGE_INCLUDE_DIR) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindCapstone.cmake000066400000000000000000000013371504763705000252040ustar00rootroot00000000000000# Locate the Capstone library # # This module defines: # Capstone_FOUND - system has CAPSTONE # CAPSTONE_INCLUDE_DIR - the CAPSTONE include directory # CAPSTONE_LIBRARY - link these to use CAPSTONE find_package(PkgConfig QUIET) pkg_check_modules(PC_CAPSTONE capstone) find_path(CAPSTONE_INCLUDE_DIR capstone.h HINTS ${PC_CAPSTONE_INCLUDEDIR} ${PC_CAPSTONE_INCLUDE_DIRS} PATH_SUFFIXES capstone ) find_library(CAPSTONE_LIBRARY NAMES capstone HINTS ${PC_CAPSTONE_LIBDIR} ${PC_CAPSTONE_LIBRARY_DIRS} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Capstone DEFAULT_MSG CAPSTONE_LIBRARY CAPSTONE_INCLUDE_DIR) mark_as_advanced(CAPSTONE_INCLUDE_DIR CAPSTONE_LIBRARY) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindMath.cmake000066400000000000000000000006011504763705000243120ustar00rootroot00000000000000 if(MATH_INCLUDE_DIR) # Already in cache, be silent set(MATH_FIND_QUIETLY TRUE) endif(MATH_INCLUDE_DIR) find_path(MATH_INCLUDE_DIR math.h) find_library(MATH_LIBRARY NAMES m) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Math DEFAULT_MSG MATH_LIBRARY MATH_INCLUDE_DIR) mark_as_advanced(MATH_LIBRARY MATH_INCLUDE_DIR) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindPortMidi.cmake000066400000000000000000000023021504763705000251500ustar00rootroot00000000000000# # Find the native PortMidi includes and library # # PORTMIDI_INCLUDE_DIR - where to find portmidi.h, etc. # PORTMIDI_LIBRARY - List of libraries when using portmidi. # PortMidi_FOUND - True if portmidi found. include(FindPackageHandleStandardArgs) include(CheckSymbolExists) if(PORTMIDI_INCLUDE_DIR) # Already in cache, be silent set(PORTMIDI_FIND_QUIETLY TRUE) endif(PORTMIDI_INCLUDE_DIR) find_path(PORTMIDI_INCLUDE_DIR portmidi.h) find_library(PORTMIDI_LIBRARY NAMES portmidi) # handle the QUIETLY and REQUIRED arguments and set PortMidi_FOUND to TRUE if # all listed variables are TRUE find_package_handle_standard_args(PortMidi DEFAULT_MSG PORTMIDI_LIBRARY PORTMIDI_INCLUDE_DIR) # Check if it's really a portmidi installation... if(PortMidi_FOUND) set(CMAKE_REQUIRED_LIBRARIES ${PORTMIDI_LIBRARY}) set(CMAKE_REQUIRED_INCLUDES ${PORTMIDI_INCLUDE_DIR}) check_symbol_exists(Pm_Initialize "portmidi.h" HAVE_PM_INITIALIZE) if (NOT HAVE_PM_INITIALIZE) unset (PortMidi_FOUND) endif(NOT HAVE_PM_INITIALIZE) set(CMAKE_REQUIRED_LIBRARIES "") set(CMAKE_REQUIRED_INCLUDES "") endif(PortMidi_FOUND) mark_as_advanced(PORTMIDI_LIBRARY PORTMIDI_INCLUDE_DIR) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindReadline.cmake000066400000000000000000000042441504763705000251530ustar00rootroot00000000000000 IF (READLINE_INCLUDE_DIR) # Already in cache, be silent SET(READLINE_FIND_QUIETLY TRUE) ENDIF (READLINE_INCLUDE_DIR) FIND_PATH(READLINE_INCLUDE_DIR readline.h PATH_SUFFIXES readline) FIND_LIBRARY(READLINE_LIBRARY NAMES readline) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG READLINE_LIBRARY READLINE_INCLUDE_DIR) MARK_AS_ADVANCED(READLINE_LIBRARY READLINE_INCLUDE_DIR) INCLUDE(CheckSymbolExists) if(Readline_FOUND) set(CMAKE_REQUIRED_LIBRARIES ${READLINE_LIBRARY}) set(CMAKE_REQUIRED_INCLUDES ${READLINE_INCLUDE_DIR}) check_symbol_exists(rl_filename_completion_function "stdio.h;readline.h" HAVE_RL_COMPLETION_FUNCTION) # If linking did not work, we might have to link # explicitly against libtermcap or libncurses if(NOT HAVE_RL_COMPLETION_FUNCTION) unset(Readline_FOUND) find_package(Termcap) if(Termcap_FOUND) set(CMAKE_REQUIRED_LIBRARIES "readline" "termcap") check_symbol_exists(rl_filename_completion_function "termcap.h" HAVE_RL_COMPLETION_FUNCTION_TERMCAP) endif(Termcap_FOUND) if(HAVE_RL_COMPLETION_FUNCTION_TERMCAP) set(READLINE_LIBRARY ${READLINE_LIBRARY} ${TERMCAP_LIBRARY}) set(Readline_FOUND TRUE) else(HAVE_RL_COMPLETION_FUNCTION_TERMCAP) find_package(Curses) if(Curses_FOUND) if(CURSES_NCURSES_LIBRARY) set(CMAKE_REQUIRED_LIBRARIES "readline" "ncurses") check_symbol_exists(rl_filename_completion_function "ncurses.h" HAVE_RL_COMPLETION_FUNCTION_CURSES) else() set(CMAKE_REQUIRED_LIBRARIES "readline" "curses") check_symbol_exists(rl_filename_completion_function "curses.h" HAVE_RL_COMPLETION_FUNCTION_CURSES) endif() if(HAVE_RL_COMPLETION_FUNCTION_CURSES) set(READLINE_LIBRARY ${READLINE_LIBRARY} ${CURSES_LIBRARIES}) set(Readline_FOUND TRUE) endif(HAVE_RL_COMPLETION_FUNCTION_CURSES) endif(Curses_FOUND) endif(HAVE_RL_COMPLETION_FUNCTION_TERMCAP) endif(NOT HAVE_RL_COMPLETION_FUNCTION) set(CMAKE_REQUIRED_LIBRARIES "") set(CMAKE_REQUIRED_INCLUDES "") endif(Readline_FOUND) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindTermcap.cmake000066400000000000000000000006471504763705000250260ustar00rootroot00000000000000 if(TERMCAP_INCLUDE_DIR) # Already in cache, be silent set(TERMCAP_FIND_QUIETLY TRUE) endif(TERMCAP_INCLUDE_DIR) find_path(TERMCAP_INCLUDE_DIR termcap.h) find_library(TERMCAP_LIBRARY NAMES termcap) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Termcap DEFAULT_MSG TERMCAP_LIBRARY TERMCAP_INCLUDE_DIR) mark_as_advanced(TERMCAP_LIBRARY TERMCAP_INCLUDE_DIR) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/FindUdev.cmake000066400000000000000000000006071504763705000243320ustar00rootroot00000000000000 if(UDEV_INCLUDE_DIR) # Already in cache, be silent set(UDEV_FIND_QUIETLY TRUE) endif(UDEV_INCLUDE_DIR) find_path(UDEV_INCLUDE_DIR libudev.h) find_library(UDEV_LIBRARY NAMES udev) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Udev DEFAULT_MSG UDEV_LIBRARY UDEV_INCLUDE_DIR) mark_as_advanced(UDEV_LIBRARY UDEV_INCLUDE_DIR) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/Toolchain-mingw32-win64_32.cmake000066400000000000000000000034741504763705000273100ustar00rootroot00000000000000# This Toolchain file is used to cross compile the Windows 32 bit # version of Hatari under linux using mingw32 # use : cmake -DCMAKE_TOOLCHAIN_FILE=Toolchain-mingw32-win64_32.cmake # mingw32 versions of the different tools # (change these depending on your system settings) set (MINGW_EXE_PREFIX "i686-w64-mingw32") set (MINGW_ROOT_PATH "mingw") #-- Changes should not be required below this point # The name of the target operating system SET(CMAKE_SYSTEM_NAME Windows) # Use the value provided to set mingw's tools SET(CMAKE_C_COMPILER ${MINGW_EXE_PREFIX}-gcc) SET(CMAKE_CXX_COMPILER ${MINGW_EXE_PREFIX}-g++) SET(CMAKE_RC_COMPILER ${MINGW_EXE_PREFIX}-windres) SET(ENV{PKG_CONFIG} ${MINGW_EXE_PREFIX}-pkg-config) # Base directory for the target environment # We use the output from '-print-sysroot' EXECUTE_PROCESS( COMMAND ${CMAKE_C_COMPILER} -print-sysroot OUTPUT_VARIABLE CMAKE_FIND_ROOT_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ) # bin/, include/, lib/ and share/ are often in "mingw/" # You might need to adjust the path for your system SET(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH}/${MINGW_ROOT_PATH}) # Make the path absolute, a relative path could confuse some systems get_filename_component ( CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ABSOLUTE ) #message ( "MINGW_ROOT_PATH ${MINGW_ROOT_PATH} MINGW_EXE_PREFIX ${MINGW_EXE_PREFIX} CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH}" ) # FindSDL.cmake doesn't search correctly in CMAKE_FIND_ROOT_PATH # so we force SDLDIR here set(ENV{SDL2DIR} ${CMAKE_FIND_ROOT_PATH}/include/SDL2) # Adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/Toolchain-mingw32-win64_64.cmake000066400000000000000000000034761504763705000273170ustar00rootroot00000000000000# This Toolchain file is used to cross compile the Windows 64 bit # version of Hatari under linux using mingw32 # use : cmake -DCMAKE_TOOLCHAIN_FILE=Toolchain-mingw32-win64_64.cmake # mingw32 versions of the different tools # (change these depending on your system settings) set (MINGW_EXE_PREFIX "x86_64-w64-mingw32") set (MINGW_ROOT_PATH "mingw") #-- Changes should not be required below this point # The name of the target operating system SET(CMAKE_SYSTEM_NAME Windows) # Use the value provided to set mingw's tools SET(CMAKE_C_COMPILER ${MINGW_EXE_PREFIX}-gcc) SET(CMAKE_CXX_COMPILER ${MINGW_EXE_PREFIX}-g++) SET(CMAKE_RC_COMPILER ${MINGW_EXE_PREFIX}-windres) SET(ENV{PKG_CONFIG} ${MINGW_EXE_PREFIX}-pkg-config) # Base directory for the target environment # We use the output from '-print-sysroot' EXECUTE_PROCESS( COMMAND ${CMAKE_C_COMPILER} -print-sysroot OUTPUT_VARIABLE CMAKE_FIND_ROOT_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ) # bin/, include/, lib/ and share/ are often in "mingw/" # You might need to adjust the path for your system SET(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH}/${MINGW_ROOT_PATH}) # Make the path absolute, a relative path could confuse some systems get_filename_component ( CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ABSOLUTE ) #message ( "MINGW_ROOT_PATH ${MINGW_ROOT_PATH} MINGW_EXE_PREFIX ${MINGW_EXE_PREFIX} CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH}" ) # FindSDL.cmake doesn't search correctly in CMAKE_FIND_ROOT_PATH # so we force SDLDIR here set(ENV{SDL2DIR} ${CMAKE_FIND_ROOT_PATH}/include/SDL2) # Adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/Uninstall.cmake000066400000000000000000000022011504763705000245670ustar00rootroot00000000000000# # "uninstall" target for reverting "make install" # # cmake_policy(SET CMP0007 NEW) if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\"") endif() file(READ "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") # list(REVERSE files) foreach (file ${files}) message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") if (EXISTS "$ENV{DESTDIR}${file}") execute_process( COMMAND ${CMAKE_COMMAND} -E remove "$ENV{DESTDIR}${file}" OUTPUT_VARIABLE rm_out RESULT_VARIABLE rm_retval ) if(NOT ${rm_retval} EQUAL 0) message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") endif (NOT ${rm_retval} EQUAL 0) else (EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") endif (EXISTS "$ENV{DESTDIR}${file}") endforeach(file) execute_process( COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt ) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/cmake/config-cmake.h000066400000000000000000000062501504763705000243200ustar00rootroot00000000000000/* CMake config.h for Hatari */ /* Define if you have a PNG compatible library */ #cmakedefine HAVE_LIBPNG 1 /* Define if you have a readline compatible library */ #cmakedefine HAVE_LIBREADLINE 1 /* Define if you have the PortMidi library */ #cmakedefine HAVE_PORTMIDI 1 /* Define if you have the capsimage library */ #cmakedefine HAVE_CAPSIMAGE 1 /* Define if you have a X11 environment */ #cmakedefine HAVE_X11 1 /* Define to 1 if you have the `z' library (-lz). */ #cmakedefine HAVE_LIBZ 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ZLIB_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_TERMIOS_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_IOCTL_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_GLOB_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TIME_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TIMES_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UTIME_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_UTIME_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_BYTESWAP_H 1 /* Define to 1 if you have the 'bswap_16' function. */ #cmakedefine HAVE_BSWAP_16 1 /* Define to 1 if you have the 'bswap_32' function. */ #cmakedefine HAVE_BSWAP_32 1 /* Define to 1 if you have the `cfmakeraw' function. */ #cmakedefine HAVE_CFMAKERAW 1 /* Define to 1 if you have the `tcsetattr' function. */ #cmakedefine HAVE_TCSETATTR 1 /* Define to 1 if you have the 'setenv' function. */ #cmakedefine HAVE_SETENV 1 /* Define to 1 if you have the `select' function. */ #cmakedefine HAVE_SELECT 1 /* Define to 1 if you have unix domain sockets */ #cmakedefine HAVE_UNIX_DOMAIN_SOCKETS 1 /* Define to 1 if you have the 'gettimeofday' function. */ #cmakedefine HAVE_GETTIMEOFDAY 1 /* Define to 1 if you have the 'nanosleep' function. */ #cmakedefine HAVE_NANOSLEEP 1 /* Define to 1 if you have the 'alphasort' function. */ #cmakedefine HAVE_ALPHASORT 1 /* Define to 1 if you have the 'scandir' function. */ #cmakedefine HAVE_SCANDIR 1 /* Define to 1 if you have the 'statvfs' function. */ #cmakedefine HAVE_STATVFS 1 /* Define to 1 if you have the 'fseeko' function. */ #cmakedefine HAVE_FSEEKO 1 /* Define to 1 if you have the 'ftello' function. */ #cmakedefine HAVE_FTELLO 1 /* Define to 1 if you have the 'flock' function. */ #cmakedefine HAVE_FLOCK 1 /* Define to 1 if you have the 'd_type' member in the 'dirent' struct */ #cmakedefine HAVE_DIRENT_D_TYPE 1 /* Relative path from bindir to datadir */ #define BIN2DATADIR "@BIN2DATADIR@" /* Define to 1 to enable DSP 56k emulation for Falcon mode */ #cmakedefine ENABLE_DSP_EMU 1 /* Define to 1 to enable trace logs - undefine to slightly increase speed */ #cmakedefine ENABLE_TRACING 1 /* Define to 1 if udev support is available */ #cmakedefine HAVE_UDEV 1 /* Define to 1 if the capstone library with m68k support is available */ #cmakedefine HAVE_CAPSTONE_M68K 1 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/configure000077500000000000000000000070521504763705000224540ustar00rootroot00000000000000#!/bin/sh # NOTE: this is a simple script wrapper around the cmake command line tools, # for those used to the autotools configure script conventions if ! command -v cmake > /dev/null 2>&1; then echo "ERROR: You need the 'cmake' program to configure the Hatari build process." echo "Please install 'cmake' first, then try again." exit 1 fi print_help() { echo "This is a simple configure script wrapper around cmake build system." echo "Parameters are:" echo " --prefix= Set the install prefix to path" echo " --enable-asan Enable address sanitizer debug build" echo " --enable-debug Enable debug (non-optimized) build" echo " --disable-dsp Disable DSP emulation for Falcon mode." echo " --disable-tracing Disable tracing messages for debugging" echo " --disable-osx-bundle Disable application bundling on macOS" echo " --enable-ubsan Enable undefined behavior sanitizer debug build" echo " --enable-werror Use -Werror flag to stop on compiler warnings" echo " --cross-compile-win64_32 Build the 32 bit Windows version using mingw-w64" echo " --cross-compile-win64_64 Build the 64 bit Windows version using mingw-w64" echo echo "Please run cmake directly for full control over the build." echo } cmake_args="" build_type="Release" while [ $# -gt 0 ] do preq=${1%=*} # get part before = case $preq in --help) print_help exit 0 ;; --prefix) prefix=${1##*=} # get part after = cmake_args="$cmake_args -DCMAKE_INSTALL_PREFIX:PATH=$prefix" ;; --enable-asan) cmake_args="$cmake_args -DENABLE_ASAN:BOOL=1" ;; --disable-asan) cmake_args="$cmake_args -DENABLE_ASAN:BOOL=0" ;; --enable-debug) build_type="Debug" cmake_args="$cmake_args -DCMAKE_BUILD_TYPE:STRING=Debug" ;; --disable-debug) build_type="Release" cmake_args="$cmake_args -DCMAKE_BUILD_TYPE:STRING=Release" ;; --enable-dsp) cmake_args="$cmake_args -DENABLE_DSP_EMU:BOOL=1" ;; --disable-dsp) cmake_args="$cmake_args -DENABLE_DSP_EMU:BOOL=0" ;; --enable-tracing) cmake_args="$cmake_args -DENABLE_TRACING:BOOL=1" ;; --disable-tracing) cmake_args="$cmake_args -DENABLE_TRACING:BOOL=0" ;; --enable-osx-bundle) cmake_args="$cmake_args -DENABLE_OSX_BUNDLE:BOOL=1" ;; --disable-osx-bundle) cmake_args="$cmake_args -DENABLE_OSX_BUNDLE:BOOL=0" ;; --enable-ubsan) cmake_args="$cmake_args -DENABLE_UBSAN:BOOL=1" ;; --disable-ubsan) cmake_args="$cmake_args -DENABLE_UBSAN:BOOL=0" ;; --enable-werror) cmake_args="$cmake_args -DENABLE_WERROR:BOOL=1" ;; --disable-werror) cmake_args="$cmake_args -DENABLE_WERROR:BOOL=0" ;; --cross-compile-win64_32) cmake_args="$cmake_args -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-mingw32-win64_32.cmake" ;; --cross-compile-win64_64) cmake_args="$cmake_args -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-mingw32-win64_64.cmake" ;; *) echo "Invalid argument: $preq" echo "Run $0 --help for a list of valid parameters." exit 2 ;; esac shift 1 done # remove previous cmake's cache rm -rf CMakeCache.txt CMakeFiles/ if [ -e `dirname $0`/CMakeCache.txt ]; then echo "Source directory is not clean, please run 'make distclean' there first." exit 1 fi cmake `dirname $0` $cmake_args || exit 1 echo echo "Now you must type: make; make install" echo "to actually build and install the software" echo hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/000077500000000000000000000000001504763705000213065ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/CMakeLists.txt000066400000000000000000000023341504763705000240500ustar00rootroot00000000000000 INSTALL(FILES authors.txt bugs.txt emutos.txt keymap-sample.txt m68k-linux.txt memory-usage.txt midi-linux.txt release-notes.txt scsi-driver.txt thanks.txt todo.txt video-recording.txt DESTINATION ${DOCDIR}) INSTALL(FILES compatibility.html manual.html debugger.html hatari-ui.html manual.css toc.js DESTINATION ${DOCDIR}) INSTALL(DIRECTORY images DESTINATION ${DOCDIR}) if(ENABLE_MAN_PAGES) add_custom_target(manpages ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hatari.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hatari.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/hatari.1 > ${CMAKE_CURRENT_BINARY_DIR}/hatari.1.gz DEPENDS hatari.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hatari.1.gz DESTINATION ${MANDIR}) endif(ENABLE_MAN_PAGES) find_program(TIDY tidy) if(TIDY) add_test(NAME tidy-manual COMMAND tidy -q -e ${CMAKE_CURRENT_SOURCE_DIR}/manual.html) add_test(NAME tidy-compatibility COMMAND tidy -q -e ${CMAKE_CURRENT_SOURCE_DIR}/compatibility.html) add_test(NAME tidy-debugger COMMAND tidy -q -e ${CMAKE_CURRENT_SOURCE_DIR}/debugger.html) add_test(NAME tidy-hatariui COMMAND tidy -q -e ${CMAKE_CURRENT_SOURCE_DIR}/hatari-ui.html) endif() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/authors.txt000066400000000000000000000231161504763705000235370ustar00rootroot00000000000000Contents: - Active Hatari developers - Contributors - Code from other projects Active Hatari developers: ------------------------- - Nicolas Pomarede : Project admin, improving CPU, video, sound, blitter, IKBD and floppy emulation. - Thomas Huth : Project initiator and backup admin. - Eero Tamminen : Speedups, small parts of the STE, TT & Falcon emulation, Python CLI & GUI, TOS tester + Hatari window embedding & remote control API, pause & auto frameskip support, statusbar & overlay led, profiling and conditional breakpoint support and other debugger features, GEMDOS HD emulation improvements, PNG saving. - Laurent Sallafranque: Many fixes and speedups to DSP emulation, DSP debugging support, crossbar emulation, falcon microphone emulation, STE LMC1992/microwire emulation, Videl emulation. Contributors: ------------- Following people contributed code or patches to this projects (listed in random order - and if someone is missing here, please remind us!): - Jean-Baptiste Berlioz : Cycle accurate Blitter emulation (although most of the code was rewritten since Hatari 2.3) - David Savinkoff : More accurate printer emulation, LMC1992 emulation patches, IIR/Low Pass filters and many improvements to the YM2149 model to get a close emulation of the circuit used to merge and filter the output of the 3 YM2149 voices. Great work to enhance the sound quality. - Matthias Arndt : Wrote the original version of the Hatari user manual, fixed the printer emulation functions. - Sébastien Molines : Wrote the main part of the macOS GUI of Hatari. - Marco Herrn : Wrote the initial version of the "man" page of Hatari and maintained the Hatari Debian packages until Hatari was included into Debian. - Sven de Marothy : Screenshot functions, the initial CLI debugger, the ACSI emulation and added support for ZIPed and GZIPed disk images. - Emmanuel Anne : Contributed lots of patches, RTC emulation. - Tuduri Benoît : French man-page, support for Doxygen, MacOS bug reports. - Markus Oberhumer : fixed a problem with ZIPed disk images, routine for loading the configuration file from the $HOME directory. - Philippe Gerin : Fixed a bug in the CPU core (bus errors problem). - Patrice Mandin : Some improvements of the autoconf build system files, original author of the DSP emulation core. - Martin Doering : Code for compiling the font data into the executable and some other ideas for cleaning up the source code. - Ventzislav Tzvetkov : Joystick closing patch, Hatari for AmigaOS. - Stefan Berndtsson : Patches to get Hatari running on big endian machines. - Anatol Paruntik (?) : Patches for compiling Hatari on QNX. - Claus Windeler : BeOS adaption. - James Lampard : Adapted Hatari to Acorn RISC OS machines. - Mark Keates : Patches for compiling Hatari with MinGW. - Volker Seebode: Fix to ASCI emulation to get other than AHDI drivers working. - Cyprian Konador: Found some bugs in the blitter cycles emulation, provided patches for duochrome and samplehold modes for TT video emulation, reported some infos about alt-RAM & DMA ranges and DIP switch register. Also provided some test programs to show cpu/blitter bus sharing and parallel execution. - Jerome Vernet: Some updates to the macOS Xcode project file and macOS GUI, supplied a french keymapping file for macOS. - Kenneth Kaufman: MS VC6 & C++ compiler and multiple GEMDOS HD partition support patches. - Uwe Seimet: IDE and ACSI/SCSI emulation improvements, GEMDOS HD emulation improvement suggestions and NatFeats SCSI Driver code for Linux. - Markus Fritze: New m68k disassembler with more Motorola like syntax and options for controlling how the output looks. - Deniz Turkoglu: Patches for the Max macOS GUI. - Markus Heiden: SCSI class 1 (ICD) command support for drives > 1 GB - Gilles Fetis: fixes to MMU emulation (from NeXT emulator project using Hatari code). - Christer Solskogen: Set up an automatic build script on his site, providing up to date Hatari binaries for Linux and Windows in 32 and 64 bit mode. Very useful for end users wishing to try the devel versions of Hatari, and lots of interesting build logs too for various cpu architectures. See http://antarctica.no/~hatari/latest He also provided a patch for fixing compilation problems on macOS. - Max Böhm: host <-> Atari filename encoding conversion routines and related changes needed to gemdos.c. - Troed Sångberg (Troed of Sync): accurate description of the GLUE state machine for STF/STE in all wakestates (display signal, sync signal, ...) and the resulting lines' length used in overscan / hardscroll. Coder of the '{Closure}' demo which makes huge use of these techniques. He also provided patches for Audio Sculpture's custom IKBD program and MacOS screenshot dir handling. - Thorsten Otto: improvements to the code that patches the TOS ROMs, support for Pure-C long symbol names, a.out symbol table, MINT+ELF symbols, and absolute symbols, few debugger & tracing usability improvements, fixed (sin, mulaw & a-law) DSP ROM tables, fmovem test program, key mapping improvement. - Andreas Grabher : FPU improvements using the SoftFloat library, most of the 030 MMU implementation and few bug reports & patches for the DSP emulation - Miguel Saro (Cocoa Pod) : Many updates to the macOS GUI. - Jari Kleimola : PortMidi support which allows MIDI usage on Windows & OSX + required SDL & OSX GUI changes - Federico Ulivi : tracked down and provided fixes for the invalid PortMidi data issues, finally making PortMidi support stable. Helped determine remaining Notator sysex issue to be softsynth bug, not Hatari one - Mark Fechtner : patch to not use tmpfile() directly under Windows - Miro Kropáček : patch for correct V flag when using BCD instructions on 68020/68030 CPU, running tests on real Falcon - Steven Noonan : patch to prevent a crash if Halt dialog was called before SDLGui_SetScreen() - Christophe de Dinechin : patch to improve Spec512_StoreCyclePalette when using 16 or 32 MHz - Fredrik Noring (Noring of NoCrew) : Tracked down a bug in the blitter emulation and a bug in the PSG shadow register emulation. Patch to improve clearing borders in fullscreen mode. Patch to fix PNG header alignment when recording AVI files. - Jens Guenther : extra info on Python UI embedding issue and fixes for PyGtk deprecations warnings in Python UI - Václav Lipert : Ability to change Windows drive in file select dialog - Chris Jenkins : patch to compile the macOS version when PortMidi is not available. - Robin Sergeant : support for remapping joystick buttons - Brad Smith : Ported Hatari 2.4 to the Libretro emulation system as 'HatariB' and contributed several patches to Hatari : missing variables in memory snapshot, keyboard/joystick fixes, saving screen content as .neo and .ximg file. - Alex Hornby : Patch to add analog/paddle support to the joypad emulation - Frederic Poeydomenge : add support to specify a directory to save screenshots and AVI files See also "thanks.txt" for people who've helped Hatari development in other ways than code contributions. Code from other projects ------------------------ As a true open source project, Hatari also uses some code from other projects which we would like to acknowledge here: - Hatari binary releases include latest version of EmuTOS: https://emutos.sourceforge.io/ - Most of the original ST hardware emulation came from the WinSTon source code which has been written by Paul Bates. (http://www.sourceforge.net/projects/winston/) - The original CPU core has been taken from UAE which has been written by Bernd Schmidt and others. (http://uae.coresystems.de/) - The current CPU & FPU cores have been taken from WinUAE which is maintained by Toni Wilen. Huge thanks to Toni for accepting ideas and patches not specific to Amiga emulation, as well as keeping on improving the accuracy of 68000/20/30 CPU and FPUs. (http://www.winuae.net/) - Parts of the NCR 5380 SCSI emulation are based on code from WinUAE, too. - Some parts have been taken from the emulator STonX that has been written by Marinos Yannikos and Martin Griffiths. (http://stonx.sourceforge.net/) - A lot of code (e.g. the scancode keyboard mapping, Videl, NVRAM, SCC and DSP emulation) has been adapted from the sources of the emulator Aranym. (http://aranym.atari.org/) - The code for decompressing ZIP files (unzip.c) has been taken from Gilles Vollant's miniunzip program. (http://www.winimage.com/zLibDll/unzip.html) - The routines for saving and loading the ASCII configuration file (cfgopts.c) have originally been written by Jeffry J. Brickley. - The new sound core uses (or used) some code/ideas from the following GPL projects : * 5 bits volume table and 16*16*16 combinations of all volume are from Sc68 by Benjamin Gerard. * 4 bits to 5 bits volume interpolation from 16*16*16 to 32*32*32 are from YM blep synthesis by Antti Lankila. * Since Hatari 1.7, volume table based on measures by Paulo Simoes - The IDE hard disk emulation is based on code from QEMU. (http://www.qemu.org/) - The MMU emulation for the 68030 has been taken from the NeXT emulator Previous (thanks to Andreas Grabher!). Since Hatari 1.9, this is now taken from WinUAE which uses the same code base. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/bugs.txt000066400000000000000000000067651504763705000230250ustar00rootroot00000000000000 Hatari bugs =========== Below are listed known failures with Hatari. Contents: - Related documents - Emulator bugs - Known issues with workarounds - External bug reports Related documents ----------------- Missing features are listed in the "todo.txt" file. Emulation option compatibility issues with specific games and demos are documented in the "compatibility.html" file. Additional emulation issues specific to running Linux kernel are listed in the "m68k-linux.txt" file. Compatibility issues with EmuTOS (TOS version distributed with Hatari binaries), are documented in the "emutos.txt" file. They're related to missing EmuTOS functionality and byte-exactness with old TOS versions, not to Hatari. Known issues / workarounds -------------------------- * Printing doesn't work in auto-started programs with TOS v1.02 - v2.06: Why: program startup is so fast that TOS hasn't had time to start 200hz tick used for printing. WA: put tests/autostart/4_secs.prg to AUTO/-folder to slow down program startup enough for printing to work * PortMidi device disconnects: Playback of Notator 3.x "DEMO8.SON" song (which has very large amounts of sysex data) can trigger "Operation not permitted" PortMidi "host error" around 7:00 mark, resulting in MIDI output being disabled (until MIDI device is changed or Hatari restarted). This is triggered completely randomly, but seems to be issue on the receiving end as it happens only when connected to Qsynth / FluidSynth, not when using just MIDI through as output. WA: Avoid non-working MIDI data / softsynth combinations. * Mac GUI support only PortMidi devices, not raw MIDI devices: Direct file access is required to use MIDI for debugging output or networking (up to 16 machines) over MIDI. Mac users have not yet contributed a support for that to Mac GUI. WA: configure that with SDL GUI for now * Neither RS-232 nor raw MIDI device file input is supported on Windows: Input for both RS-232 and raw MIDI device files is checked with File_InputAvailable() function which uses select() POSIX functionality to do it. Windows users have not yet contributed corresponding Windows functionality to it. WA: Use Linux version of Hatari under Windows: https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps External bug reports -------------------- * Problem: On real Falcon there is a minor feature in VIDEL. You can simply fade whole screen to black, if you just clear the Horizontal Border End ($ffff8286) and start fading colors from 0 to 255 to $ffff9800 or vice versa: test move.w #255-1,d7 moveq #0,d6 .loop clr.w $ffff8286.w move.l d6,$ffff9800.w addq.l #1,d6 dbf d7,.loop Now it does this exactly opposite direction, it fades bgcolor from 0 to 255. * To compare my real Falcon with Hatari on my Windows 7 PC, I make some short benchmarks with Kronos. The disk benchmark in Kronos runs in 1-2 minutes on my real Falcon. The same disk benchmark in Kronos under Hatari 1.5 runs longer than 20 minutes with GEMDOS emulation. Only when I use the AltGr + X option, before the benchmark runs, then the disk benchmark in kronos is comparable fast as my 16MHz Falcon. -> Hatari GEMDOS emulation needs to do a lot of extra kernel file & directory accesses which with current code seem unavoidable. They're a problem only with test programs like Kronos, for those one should consider using harddisk image files instead. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/coding.txt000066400000000000000000000035441504763705000233200ustar00rootroot00000000000000 Hatari coding guidelines ======================== Before writing new code or changing existing files, please read through these coding guidelines to make sure that your changes are in harmony with the existing source code. - all source text files have to use Unix Line ending convention (line-feed only) (exception: Code like cart_asm.s which has to be compiled from the Atari TOS side should have DOS line endings (CR-LF) instead). - Avoid non-ASCII characters in source code. Use UTF-8 encoding for non-english documentation files if necessary. - Use TABs for indentation at the beginning of a line. Default TAB width is 8, but your source code should also look fine with other TAB width (e.g. 4). - Use doxygen-style comments to document what each function is doing. - No "magic" variable values. Use either defines or enums to name the values and document what they mean if it's not obvious. - Hatari uses code from many projects, e.g. the UAE CPU files, which use another coding style than the main source code. We keep the original coding style there for compatibility with the origin. So always try to adapt to the coding style of the file that you're currently editing. - For new files, use the main Hatari coding style (see the various *.c file in the main src/ folder) for files that are specific to Hatari. Don't add a new one coding style, unless the new files are shared with other projects and need to be synchronized from time to time. - Try to avoid including a header file from within another header file. Include all necessary header files in the right order in the *.c files instead. Including a header file from within another header file easily leads to a dependency hell which can become very painful when one of the header files clashes with a system header for example and must only be included in certain .c files only. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/compatibility.html000066400000000000000000004772171504763705000250670ustar00rootroot00000000000000 Hatari Atari Software Compatibility List

Hatari Atari Software Compatibility List

Index

Introduction

Here are lists of Atari software that have been tested with different Hatari versions and configurations.

First is listed software (games, demos and applications) for normal STs, after them is listed STE software. Then there are a few TT-only games and demos, and finally a list of somewhat working Falcon games, demos and applications.

Technical details and any known issues in the software are described in the "Notes" column of the lists. Software having issues has different "Hatari version" column background color; orange for minor and red for major issues. The first known/tested Hatari version with which a program worked (perfectly) or with which it was last tested still to have issues, is given in the version field.

Reporting compatibility issues

If something that works on the real hardware doesn't work properly in Hatari (or something has started working in latest Hatari version) and this information is missing from these lists, please mail hatari-devel mailing list so that we can update the list.

Before reporting something not working as expected, please check few things:

  • That you have enabled the "Slower but more compatible CPU" Hatari system option and that the machine type (ST/STe/TT/Falcon) is correct.
  • Amount of memory. Some games and demos may not work if you have specified too little or too much memory. For ST, 1MB memory size is usually fairly safe.
  • Some games/demos can refuse to run when GEMDOS HD emulation is enabled (due to presence of Hatari cartridge image needed for that). In STF/STE mode, it is also better to disable the MegaST's real time clock.
  • TOS version: ST demos and games may require a specific TOS version and fail to work with all other versions. If you notice that this information is missing for some listed game or demo, please send mail to the hatari-devel mailing list.

Note that some demos and games (especially floppy-only ones done with STOS), don't work with the free EmuTOS ROM. To make sure they have enough RAM available, you should use the most appropriate EmuTOS version: 192K one for ST, 256K one for STE, and 512K one (included with the Hatari binary downloads as it has widest HW support) for TT/Falcon games and demos. For more information, see emutos.txt.

Also note that since Hatari 1.8, support is included also for STX, IPF and CTR floppy image formats. They allow storing disk contents in an unaltered form, without requiring a crack to remove the copy-protection. Some complex protections rely on precise CPU/FDC timings provided with these formats, and a specific TOS version.

  • For STF protected games, it is recommended to use TOS 1.02 or 1.04 and 1MB RAM, as well as turning HD emulation OFF for maximum compatibility (but most games will run fine with different settings).

(More information on games compatibility on Atari forum.)

ST software compatibility list

These aren't comprehensive lists. The lists concentrate mainly on software that has (historically had) problems working in Hatari or provide good regression test-cases for some specific technical emulation issues, but some well known other programs are also listed.

By default everything that works on a real ST should work also in Hatari.

ST games

Tested ST games
Game Hatari version Notes
4 Wheel Drive compilation 1.8 Requires TOS 1.04
A320 Airbus 1.8 Does not work with cartridge / HD emulation
Alien World before 1.0  
Antago 1.8 Write protection should be OFF
Arkanoid 1.8 Requires TOS 1.02 for mouse input
Armalyte before 1.0  
Atari Dash 2,6 Requires improved 4-pixel hardscroll suppport in v2.6
Baal 1.8 Does not work with cartridge / HD emulation
Bio Challenge 1.8 Does not work with cartridge / HD emulation
Blood Money - Superior 65 1.9 The file version of this game has some serious bugs and will crash with TOS 1.04 or 1.62, and maybe in other conditions too. Avoid it, use another version of this game
Bolo 1.7 This game works in medium resolution and in monochrome
Bubble Bobble before 1.0  
Captain Blood 1.7 Uses the IKBD set-clock command to set a custom time for game-internal time measurement
Defenders Of The Earth before 1.0  
Doom8088ST 2.4 Doom port for ST with reduced functionality. Dithered monochrome graphics in ST-mid rez. Very slow. Supports only optimized Doom 1 Episode 1 WAD
Dragon Ninja 1.8 Requires TOS ≤ 1.04. Does not work with cartridge / HD emulation
Eco 1.8 Write protection should be OFF
Eliminator before 1.0  
Exterminator 1.2 The "Patch Timer-D" option must be disabled to avoid color raster problems
Enchanted Land 1.3 This game uses heavily sync-scrolling techniques!
F15 Strike Eagle 2 1.3  
F29 Retaliator 1.6 Under some badly cracked versions (Pompey Pirate 38), joystick doesn't work
Fire And Forget II 1.8 Requires UK or US TOS
Fuzion CD 161 1.6 Menu's intro is buggy, top border is not correctly removed even on a real STF, due to timer C not correctly initialized to play digidrums. Use STE mode instead.
G-Loc 2.3.1 Incompatible with IMP chipset used for MegaST emulation. Set machine type to ST or STE instead.
Garfield Winter's Tail 1.8 Requires TOS ≤ 1.04 and ≤ 1MB RAM. Does not work with cartridge / HD emulation
Grimblood 1.8 Requires TOS ≤ 1.02
Hover Sprint 1.8 Write protection should be OFF
Jaguar XJ220 (development demo) 1.2 Only loads correctly when "Fast floppy" option is disabled
Knightmare 1.8 Does not work with cartridge / HD emulation
Leisure Suit Larry 2 before 1.0 Everything works, even Roland-MT32 MIDI music
Leander before 1.0  
Lethal Xcess Beta 1.8 The STX version requires Hatari ≥ 1.8 to handle the protection
Lethal Xcess 1.0 Uses sync-scrolling in ST mode and STE HW scrolling in STE mode.
Metal Mutant 1.0  
Microprose Golf 1.7 Requires Hatari ≥ 1.7 to avoid a crash during the game's intro
Midi Maze (v1.0) 2.5 With Hatari v2.4.1 or earlier, multiplayer connection failed with "MIDI ring boo-boo" error dialog when trying to start a game
Midi Maze II (v1.5) 2.5 With Hatari v2.4.1 or earlier, after playing for a while, game ends to "The MIDI ring has timed out" error message
Midi Maze 3 (v0.9) 2.5 Multiplayer game ends right after maze has been drawn once
MidiWiz v1.1i 2.5 Neither MIDI nor RS-232 multiplayer connection works, data sync at game start never completes
Monster Business 1.8 Does not work with cartridge / HD emulation
Mr. Heli 1.8 Does not work with cartridge / HD emulation
Navy Seals before 1.0  
Nebulus 1.1 Disappearing platform tiles sound dodgy
No Buddies Land 1.3 Uses "0-byte" vertical sync-scrolling
Oids 1.8 Write protection should be OFF
Pirates! before 1.0 Game also supports MIDI music
Populous   US version of the game requires US TOS to work
Populous 1.8 Requires TOS ≤ 1.04. Does not work with cartridge / HD emulation
Prehistoric Tale 1.8 Does not work with cartridge / HD emulation
R.B.I. Baseball 2 1.6 On STE, the game will play sounds with the DMA audio, but with a different freq than the STF mode (6258 vs 7675 Hz). On STF, the sound fx don't sound correct because of that, but that's not an emulation bug
Rick Dangerous before 1.0  
Robocop II 1.1  
Running Man 1.6 When running on Hatari versions before 1.6, the RS232 emulation must be enabled for the game to work (since it writes some debug information to the serial port)
Seven Gates Of Jambala 1.3 Uses a very rare mode for timer B to count start of line. Disable the "Patch Timer-D" option to avoid color raster problems.
Skweek before 1.0 Does not work with cartridge / HD emulation
Slayer 1.3 Uses color raster FX at highscore and main screen
Speedball before 1.0  
Spherical 1.8 Requires ≤ 1MB RAM
Spidertronic 1.7 Crashed before the 50/60 Hz selection screen with Hatari < 1.7
Squixx 1.4 Requires TOS 1.00 for input to work
Star Goose 1.1  
Subbuteo 1.7  
Super Hang-On 1.7 Requires Hatari ≥ 1.7 to avoid flickering rasters
Super Monaco GP 1.7 Requires Hatari ≥ 1.6 for correct FDC emulation and Hatari ≥ 1.7 to avoid flickering rasters
Super Sprint 1.3 The "Patch Timer-D" must be disabled or the music will play too fast
Super Skweek 1.3 Disable the "Fast floppy" option if your version fails to load
Team Suzuki 2.3.1 Incompatible with IMP chipset used for MegaST emulation. Set machine type to ST or STE instead.
The Deep 1.1 When running on Hatari versions before 1.6, the RS232 emulation must be enabled for the game to work (since it writes some debug information to the serial port)
The Final Conflict 1.7 Buggy samples replay during the intro, requires Hatari ≥ 1.7
The Sentinel 1.7 Mouse moved too slowly before Hatari 1.7
The Teller 1.9 Does not work with cartridge / HD emulation
The Ultimate Ride   The game has a bug and will crash if drive B is empty. Either disable drive B or insert disk 2 in drive B to make it work
Tiles Osmosis 1.8 Requires disk in drive A: to start
To Be On Top 1.8 Does not work with cartridge / HD emulation. Although it works on real ST with 512 kB of RAM, current emulation requires to use ≥ 1MB RAM
Toki before 1.0  
Total Eclipse 1.8 Does not work with cartridge / HD emulation
Treasure Trap 1.8 When running on Hatari versions before 1.6, the RS232 emulation must be enabled for the game to work (since it writes some debug information to the serial port)
Warlock's Quest 1.7 The game has some IKBD bugs, it doesn't work with TOS 1.04, but works fine with TOS 1.02 or 1.62. The keyboard mode is also less responsive than the joystick mode, but that's the same on a real ST
Wings Of Death 1.0 works in STE mode too
Xenon 1.1  
Xenon 2 - Megablast before 1.0  
Yolanda 1.6 This game expects random data from the TOS memory test in low RAM, so the emulator has to be started with "fast boot" disabled or the game will freeze after a few seconds
Zombi 1.6.2 Uses the IKBD set-clock command to set a custom time for game-internal time measurement

ST demos

Some (very) rare demos try to install IKBD code. Hatari doesn't have full 6301 emulator, but has specific code to handle these particular demos. They are marked with '*'. Few remaining screen sync issues are related to hi/lo frequency switching or switching to 60 Hz for too long (used for border removal).

Tested ST demos
Demo Hatari version Notes
1984: No Cooper 1.0 Many very good plasma effects, mid res overscan, ... The loader's protection requires exact cpu/shifter sync, as well as cpu pairing
ACF: Just Buggin' 1.6 Requires at least Hatari 1.6 for correct FDC emulation
Acid Team: Dragonnels* 1.1 Uses its own IKBD 6301 routine. The "Unlimited Bobs" screen is very sensitive to proper IKBD timings.
Aggression: Overdose before 1.0 Does not work with cartridge / HD emulation
Big Alec: Audio Artistic Demo 1.7  
BushWacKers: Transbeauce II* 1.1 Uses its own IKBD 6301 routine. Does not work with HD emulation. Due to a bug in the demo when detecting RAM size, it will crash in STF mode when using more than 1 MB (because MMU is forced to 512 KB)
Chaos: Pandemonium 2.3
Checkpoint: Posh before 1.0  
Checkpoint: Suretrip 49% 2.0 Requires Hatari ≥ 2.0 to work, else the fullscreen tunnel part will crash after a few seconds (due to complex VBL/HBL exception timing)
Dead Braincells: Monoscreen 2.4 (Partial) monochrome overscan isn't supported by Hatari. Demo requires manual monitor timings adjustments to work (which may be SM124 specific)
Delta Force: Punish Your Machine 1.5 Sync-scroll, overscan... ST Connexion's screen features 4 pixels hardware scrolling on STF. "Best Part Of The Creation" uses spec512 mode in med res
Delta Force: Syntax Terror 1.3 Planes are shifted in TCB screen (missing stabilizer). Tex screen works since 1.3 (mixes 50/60 HZ lines)
DHS: Sweety before 1.0 Does not work with HD emulation
Diamond Design: Brace 1.5 Use spec512 mode in med res in the mixed low/med res horizontal scrolltext
Dune: Illusion 1.0 Very nice demo. Some rasters are sometimes flickering, this is a bug in the demo, not in Hatari. Using --fastfdc can cause problems with some parts of this demo
Dune/Sector One: Fantasia before 1.0  
Dune/Sector One: Odd Stuff before 1.0 In STF mode, some screens tries to simulate more colors by changing the palette on each VBL, which creates an unpleasant flickering effect (this is not a bug in Hatari). Use STE mode for better results
Eagles / EKO / Impact: Place To Be Again devel Although the demo should work in STF mode, the colors will be wrong in the Menu part, unless you use STE mode (this is a bug in the demo which disables timer B when enabling timer A to play samples)
Equinox: Virtual Escape before 1.0  
Equinox: Vodka Demo 1.6.1 The 'Kill The Beast 2' part requires Hatari 1.6.1 to work. Also due to a bug in the demo loader that forces MMU to 512 KB, the demo will crash in STF mode with more than 1MB RAM (even on a real STF)
Fraggle's: Bird Mad Girl Show 1.5  
Futur Minds: Snork 2.4 DNT screen 1 is mixing low/med res on the same line, which is not supported by Hatari at the moment
Inner Circle: Decade Demo 1.7 Very good demo. Reset part requires Hatari ≥ 1.7 to avoid flickering bottom border
Lynx: BBS Intro 3 1.5 Good 3D (voluntarily uses some address errors in the 3D routines)
M.C.S: Delirious Demos IV 2.4 Requires at least Hatari 1.6 for correct FDC emulation and for some of the overscan screens. Some screens are not working or not correctly emulated yet (Vodka, Fulltrax, Tekila, La Miga Demo)
MJJ Prod: Anomaly 1.7 Requires Hatari ≥ 1.7 to avoid glitches in the main menu when playing digidrums
Newline: Ika I Compofylla 2.5 Uses "bus display mode" (data bus read "noise" instead of screen writes) to provide data for Shifter. Not displayed correctly
Next: Phaleon Giga Demo 1.0 4 disks. Some overscan screens have small sync issues (in Future Minds screen, planes are shifted in the lower part)
Offbeat: The Musical Wonders 1990 1.7 This demo requires STE mode. With STF mode, bottom border is not correctly removed (this is the same on a real STF)
Omega: Omega 2.4 In full overscan screen, planes are shifted due to missing hi/lo stabilizer
Omega: RGB Plasma 1.5 Boot sector doesn't match the physical disk layout, which caused bus error in Hatari < 1.5
Overlanders: European Demos 1.1 Does not work with cartridge / HD emulation
Overlanders: Gen4 Demo 1.5 Does not work with cartridge / HD emulation. Uses illegal/address error exceptions to decode the program
Overlanders: Ventura 1.1 In Ultimate Dist screen planes are shifted in the upper logo
Oxygene: Flip-o-demo before 1.0  
Oxygene: ST-NICCC 2000 Demo 1.7 Due to a bug in the FDC's emulation, the demo ran slower than expected with Hatari < 1.7
Paulo Simoes (ljkb): The Overscan Demos 1.0 All demos are running in overscan, including some spectrum 512 pictures.
Paulo Simoes (ljkb): shforstv 1.3 Small technical demo posted on www.atari-forum.com. It features the most advanced hardscroll on STF, using only 4 raster lines and all possible borders's removal (including 162 bytes 50 Hz line).
Phalanx: Overdrive 1.1 In Dragon screen planes are shifted in the upper part
PHF: UMD 8730 1.8 The Ultimate Music Disc for Atari. This demo doesn't work in STF mode with Hatari < 1.8, due to the non standard fading colors routine used at the start of the demo
Reservoir Gods: Hallucinations before 1.0  
Sector One: Oh no!! More Froggies 1.0 Works only with <4MB of RAM. It doesn't support SID sound with timers during the overscan parts, so it stops music (it is not a bug).
ST Connexion/Overlanders/Legacy: Froggies over the Fence* 1.1 Uses its own IKBD 6301 routine
Sync: {Closure} 2.0 Very technical demo requiring accurate emulation of many items : 2 cycle precision, wakeup states emulation for STF, new overscan line with no stabiliser on the right. A real benchmark for emulators !
Sync: Monism 2.4 Monochrome demo that requires starting with color monitor and switching to mono one, to get 1280x200 monochrome mode. Can be viewed using 1280x200@2 VDI mode
Sync: The LoSTE screens 2.0 Uses 2 cycle precision to create 0 byte lines on STF/STE and requires wakeup states emulation for STF
The CareBears: SoWatt 1.0 Sync-scroll, overscan...
The CareBears: Swedish New Year 1.3 Fullscreen in the first TCB screen is mixing 50/60 HZ lines
The Exceptions: B.I.G. Demo 1.0  
The Lost Boys: Mindbomb 1.3 "DI No Shit" is mixing 50/60 Hz lines. Does not work with cartridge / HD emulation
The Marvellous V8: V8 Music System 1.1 The keyboard detection at the beginning is buggy and it is sometimes necessary to retry several times (this is the same on a real STF)
The Syndicate: If Pigs Could Fly before 1.0  
The Union: The Union Demo 1.8 One of the most famous ST demos. Does not work with cartridge / HD emulation. Although it works on real ST with 512 kB of RAM, current emulation requires to use ≥ 1MB RAM
TNT Crew: Death of the left border 2.4 Planes are shifted, but Hatari ≥ 2.0 includes a temporary fix for this
TOS Crew: 4-pixel plasma screen 1.8 This demo features some 4 pixels color changes on STF, instead of the usual 8 pixels minimum width
Unlimited Matricks: Dark Side Of The Spoon 1.0 Lots of fullscreens and sync-scrolling
X-Troll: Long Screen before 1.0  
Ym Rockerz: Popstars before 1.0  

ST applications

Note that some applications can even crash at startup if their configuration or data files are not writable.

Tested applications
Application Hatari version Notes
Programming
AHCC 1.3 Open Source Pure-C compatible C-compiler and IDE
Pure-C 1.4 Debugger had issues with GEMDOS HD emulation before Hatari v1.4
Devpac 2.2 1.2  
Devpac 3 1.7 Earlier GEMDOS HD emu flushed writes only when file was closed or its metadata was changed. Before Hatari v1.7 tools run by Devpac could get incomplete file contents
asm56000.ttp 2.1 Works correctly only when Fopen() returns very small file handle numbers. Doesn't work with GEMDOS HD emulation as that returns high file handle numbers to avoid conflicting with TOS file handles. Use disk images for compiling
Turbo Assembler before 1.0 Due to a bug you need to have a proper disk image inserted into drive A.
GFABASIC 3.6TT before 1.0 MENU.PRG does not like harddisks. Use disk images for compiling.
ST Basic before 1.0  
Gulam shell before 1.0  
Orcs Resource editor before 1.0  
Graphics
Crackart before 1.0 Harddisk access does not work with GEMDOS emulation - fine with disk images
ImCon before 1.0  
Mgif 1.4 Convolutions can use Falcon DSP and JPEG loading Brainstorm DSP decoder
Neochrome 1.3  
Neochrome Master 2.28 2.1 Use bottom border removal on 50 Hz and 60 Hz screen
OCR before 1.0 Didn't try scanning
Spectrum 512 1.7 Mouse moved too slowly before Hatari 1.7
Speed Of Light before 1.0  
Sound
Hextracker 2.4 Uses non-standard mouse event scaling factor. Mouse cannot (vertically) traverse whole Hatari window unless mouse grab or fullscreen mode is used
M 1.9 MIDI program, use a weird way to trigger interrupt by changing MFP's AER
MusicMon 2 before 1.0  
Noise Tracker 1.1  
Notator 1.9 MIDI program, very sensitive to the timings when writing to the MIDI's ACIA
Protracker ST/STE 2.1 2.1 Use bottom border removal on 50 Hz and 60 Hz screen
Quartet before 1.0  
Realtime 1.9 MIDI program, use a weird way to trigger interrupt by changing MFP's AER
SidSound Designer before 1.0  
Accompanist
(Henry Cosh Sequencer)
1.2 MIDI input/output works
Cubase Lite 1.2 MIDI input/output works
Sequencer One 1.2 Both MIDI input/output and sampled sound work
Text Editors
Tempus before 1.0  
Qed editor before 1.0  
Everest editor before 1.0  
Business applications
Opus spreadsheet before 1.0  
SBase database before 1.0  
Sheet spreadsheet before 1.0  
Calamus v1.0.9 before 1.0  
Outline Art 3 (demo) before 1.0  
Communications
CAB browser before 1.0  
Connect before 1.0  
Kivi QWK Reader before 1.0  
Desktops
Teradesk desktop before 1.0  
Thing desktop before 1.0  
Science
Euler before 1.0  
Minidraft before 1.0  
Molsys before 1.0  
Benchmarks/statistics
Kronos before 1.6 Disk speed check is really slow under GEMDOS HD emulation, use disk images instead
Statistician 1.8 Program crashes if disk info is requested for GEMDOS HD emulated drive, or a floppy drive without a disk, because program doesn't check BIOS GetBPB() function return value
Utilities
Revenge Document Displayer before 1.0  
ST-Guide before 1.0  
ST-Zip before 1.0  
STCat before 1.0  
Lharc Shell before 1.0  
TwoInOne before 1.0  
Sagrotan viruskiller before 1.0  
Others
ZX Spectrum emulator before 1.0 Installer needs to be run from a floppy image and (HD) install directory needs to be already present. After installing, use unzip to get zero byte sized files from the original self-extracting zip files. Both 68000 and 68030 versions work both in color and mono.

Midi setup

If you don't have a MIDI sequencer, on Linux you can use a softsynth like Timidity or FluidSynth instead (if your distribution supports ALSA). For instructions, see the midi-linux.txt file.

STE software compatibility list

By default everything should work with the STE emulation. The lists below contain nearly all the STE specific software that the developers could get their hands on...

STE games

First are some STE games that do work with the STE features in Hatari. Most of these games work only on STE, but here are also some games which work (or have a version that works) also on ST, i.e. they are just STE enhanced. These are marked with '*' (for more info, see here).

Tested STE games
Title Hatari version Notes
4K Pacman 1.4  
Adam is Me 2.4 Works also on ST, enhanced sound on STe/TT/Falcon
Aerial Combat 3 1.4  
Alien Blast before 1.0  
Alien Thing 1.2 "Director's cut" preview. Needs to be run from atdcdemo/-folder in root
AttackWave* 1.3 Uses blitter and DMA sound on STE
Astrodia* before 1.0 Blitter
Battletris+ before 1.0  
BeGEMeD 1.8  
Blat! before 1.0  
Bombaman before 1.0  
Breakdance before 1.0 THE ultimate scene game
Chronicles of Omega* before 1.0  
Chaos Engine (demo) before 1.0  
Chroma Grid 2.4 Sources available on GitHub
ChuChu Rocket* before 1.0 needs to be run from disk image
Crash Time Plumber 1.8  
Defender* 2.4 Atari port of 6809E Williams Defender sources. Overscan, DMA sound, joypad support. Works also on ST + Falcon
Detonator 2.4  
Dread* 2.3 Wolfenstein clone with Doom style, ported from Amiga. Uses DMA sound on STE
Dynabusters+ 2.4 Hi-score ("Table") screen flickers. Supports joypad.
FASTER 2.5 Great racing game
Frantick 1.0  
H.E.R.O. 2 before 1.0 Requires 4MB RAM
Giana Sisters STE rewrite before 1.6.2  
Leavin' Teramis*   Loader text was not correctly displayed with Hatari < 1.8
Manga Puzzle before 1.0  
Nano Cave 2.4  
No Limit II before 1.0  
Obsession (demo) before 1.0 Opens top, left (with STE shifter bug) and bottom borders. Supports joypad.
Operation Garfield before 1.0  
Pacman on E before 1.0  
Pacmania STE rewrite 1.6.2  
Penta* before 1.0 Blitter, DMA audio and STE fadings do work properly
Pole Position (arcade conversion) 1.8 Preview version
Pooz* before 1.0 Uses STE DMA, blitter and palette
Power Up* 1.5 Very weird sound routine in STE mode : play a 2 bytes sample in a loop with DMA sound and update the content of this sample with an MFP timer
R-Type Deluxe (arcade conversion) 1.8 STNICCC preview
R0X 1.3  
R0X Zero 2.1 Needs Joypad. Credits screen overscan needs 50Hz
Ready Steady Bang! 2.6 GEMDOS HD Fattrib() calls on directories do not work on Hatari 2.5 or earlier
Rodney vs. KFC 2.4 Works also on ST, but has in-game music only on STe
Roger 1.1 Needs to be run from a disk image
Shotgun 2.5 Works also on ST, enhanced for STe/TT/Falcon
Skulls before 1.0 Opens right border
Space Zot 2.4  
Starball* before 1.0 Uses DMA sound
Stardust
(tunnel demo)
before 1.0  
STrEet Fighter 2 before 1.0 Uses blitter, HW scrolling and DMA sound
Stupid Balloon game before 1.0  
Substation (demo) before 1.0 Supports joypad
Team 1.0 HW scrolling, DMA sound, overscan. Supports joypad
TomTar before 1.0  
Trapped II* 2.0  
Utopos 1.4 Screen was flickering with previous version of Hatari
Ultimate Arena before 1.0  
Whitewater Madness 2.3.1
Wolfenstein 3D* 1.2 Uses DMA sound on STE
Zool (demo) 1.0  
Zero-5
(ST Review -demo)
2.3.1 STE DMA sound, Blitter and palette. STE joypad / mouse control. Mouse cursor flickers in menus

STE demos

Blitter emulation cycle-accuracy is an issue for some demos.

Tested STE demos
Demo Hatari version Notes
Aggression: !cube vs. YM2149 2.4 2025 musicdisk
Aggression: Armada is dead 1.0  
Aggression: Braindamage 1.3  
Aggression: RGBeast 1.9 Overscan + blitter. Requires Hatari ≥ 1.9
Anatomica: Extreme Rage 2.0 3D screen bottom border looks different in Youtube video, but that is assumed to be from an older emulator version. Earlier bad blitter timings were causing timer B to occur at wrong time
Atari scene collective: 20 years* 1.1 You may need to reset between screens
Avena + RG: Short and Curly 2.4
Brainless Institute: Bad Taste 2.4 Demo crashes during the vector balls at the beginning. This is due to the blitter overwriting some code (due to a bug in the sprite routine) but the reason why it happens only under Hatari was not found yet
Cerebral Vortex: MonogAtari 2.3 Monochrome megademo. First half works also with megaST (blitter)
Checkpoint: ATM 10 Years 1.8 Streamed music, voxels
Checkpoint: Yanartas 1.9 ST demo that works only STE (due to top border removal having too short timings and freezing the demo)
Cream: Madness 1.2 Music demo with Amiga Paula soundchip emulation + tunes
Cybernetics: New Stream 1.8 WRITER.PRG doesn't work due to FDC "write track" command, but demos work fine if you convert IMG files to ST floppy images by removing their 8 byte header.
Cybernetics: Relapse 1.9 WRITER.PRG doesn't work due to FDC "write track" command (same as 'New Stream'). The Graphix Sound 2 part uses blitter with overscan and requires Hatari ≥ 1.9.
Defence Force: Save The Earth 1.5 Right border switches, blitter, extended palette, HW scrolling/screen splitting, DMA music + microwire/lmc control.
Defence Force: Time Slices 2.4 Monochrome. Vertical STNICCC2015 scroll text is missing and some parts of the screen have inverted colors: https://www.youtube.com/watch?v=RlpP7JVImGA
Defence Force: VibeCoding 2.6 Overscan with both horizontal + vertical resolution splitting. Content on left side of screen completely missing, and glitches also elsewhere on screen compared to real HW: https://www.youtube.com/watch?v=ePhfz3L7GmA
DHS: Cernit Trandafir 1.5  
DHS: Double Rez Trouble 2.6.1 Screens with horizontal + vertical resolution splitting (low/med res mix). Screen with horizontal split does not work correctly
DHS: Just Musix 2 1.0  
DHS: More or Less Zero 1.5  
DHS/SMFX: MPC in Dalarna 2.3  
DHS: Revise and Revisit 2.1  
DHS: Tyranny & Massacre before 1.0  
Dune/Sector One: Antiques 1.6.2 The Greeting Parts requires Hatari 1.6.2 to work, it will flicker sometimes but that's a bug in the demo, not in Hatari
Dune/Sector One: UFO 1.7 Requires --fastfdc off
Effect: A Letter to Sommarhack 2.6.1 Mixed low/med res, needs Hatari v2.6.1
Electronic Images: Music Dream II 1.5 This demo requires accurate values when reading dma sound address ($ff8909/0b/0d) and won't work correctly with Hatari < 1.5
Ephidrena: YMSE 2.4 Musicdisk with dentro
Extream: Vulgar Display of Power 2.1 Filled vectors with effects (z-buffering, polygon clipping, plane projection)
Fenarinarsa: Bad Apple!! 2.3 Delta-packed video + blitter commands streamed with GEMDOS. Separate color and mono versions
GGN: The Sierpinski overdose 4K 1.0 Uses blitter, requires med-rez
Hemoroids: Heal Old Geek 2.1 Only endpart.prg needs STE
Hemoroids: Square v2 2.4.1 See BALLSHMD for a version working with TOS2 / EmuTOS
ICE: Ecstacy A before 1.0  
ICE: Jam-Cols 2.1 Changes background color using blitter
ICE: Kryos & Intruding 2.4 Planes are shifted
ICE: Space Tale 2.0  
ICE: The Wave Of The Future 1.6.2
Imagina: Xmas demo 92 before 1.0 Flickers without frameskip
Imagina: Systematic error 1.4
Light: E605 before 1.0  
Light: Power Rise 1.1  
Light: VGA slideshow 2.4 Some vertical bad pixels stripes and one of the images broken, otherwise fine
Masters of Electric City: Unbeatable 1.8 Monochrome. Claims to work in ST, but due to bug in demo, does only on STE
MJJ-Prod: 1st Step before 1.0  
New Core: Beyond Deadline before 1.0  
New Core: Coreflakes 1.1  
Next: Illusion 1.2 Part of the Phaleon Gigademo
N.L.C: Techno Drugs before 1.0  
No Extra: 0OMPA 1.8  
No Extra: Infinite Live of the Blitter 1.8+ Demo crashes in Hatari v1.7/v1.8 due to Blitter / MFP timings issue. Needs floppy in drive A
Omega: Grotesque before 1.0  
OUCH: Songs Of The Unexpected 1.5  
Oxygene: Amiga Demo 2 1.4 50kHz DMA sound
Oxygene: We Were @ 2.0 Blitter + overscan. 4MB
Paradox: 20 years Atari STE megademo 2.4 Sound is broken in Paradox color zoomer screen. Reset screen has a bug and will issue lots of bus errors if RAM is less than 4 MB.
Paradox: X-mas 2004 demo 2.4 The last (4th) screen horizontal scroll texts don't look right and are jerky
Paradox: Pacemaker 2.3 There was some garbage in the end screen when the demo had been started from GEMDOS hard disk with Hatari versions previous to 2.3. The demo should run fine in Hatari 2.3 and newer.
Paradox: HighResMode 1.5 Use spec512 mode in med res. Requires Hatari ≥ 1.8 for correct alignment between bitmaps and color changes
POV: POV 136 2.4 Hatari doesn't support modifying $ff8205 and reading it back while display is enabled. Without VBL sync, there's a only 10% chance of STE HW detection working
Psycho Hacking Force: Ambermoon music demo 2.2
Reservoir Gods: Mind Rewind before 1.0 Opens all borders, heavily abuses horizontal STE HW scrolling
Reservoir Gods: Grimey before 1.0  
Sedma: Blitter Mania 1.1  
Sedma etc: Out A Time 2.4 4MB, 18MB HDD, 1st @ Silly Venture 2024
SMFX: Motus 2.2  
SMFX & DHS: The Art of Profanity 2.4.1  
SMFX & KÜA: We Accidentally Released 2.2  
Syntax: Reanimation before 1.0  
Stax: Compact Disk #65 Requires TOS ≥ 1.06. MegaST's real time clock should be disabled
T. Barker: Fantasia before 1.0 For any other than TOS 1.06, you need to use fantfix.prg first
The Arctic Land: The Star Wars Demo v2.3
The Estate: Speechless v2.3
The Pixels Twins: Mental Hangover 1.5 Does not work with cartridge / HD emulation. Real Time Clock should also be disabled to prevent a bug in the boot sector's loader (depends on the TOS version used). Sound requires Hatari ≥ 1.5 to work (the routines write in the sample buffer while it is played by the dma)
Tobe and 505: Tribute to Rainbow TOS 1.1  
Unit 17: Dynamite 1.2  
Unit 17: E.P.S.S. Demo 1.4 Video counter is modified while display is ON
Ym-Rockerz: Tyme Warp 2.0 The demo crashes on a real STF (write to ff8939), although pouet.net wrongly lists it as STF compatible
Zeal: Birdie 2 & Lethal Trash 1.0  

STE applications

Finally some STE only or STE enhanced applications.

tested STE applications
Application Hatari version Notes
Hextracker (0.849b) 2.4 Uses non-standard mouse event scaling factor. Mouse cannot (vertically) traverse whole Hatari window unless mouse grab or fullscreen mode is used
Jam (1.1b) 2.4 Version with CPU plugins. Supports all machines with sound-DMA
MaxYMiser DMA before 1.0  
Octalyser STE 1.0 Opens left and bottom border, screen flickers when playing mods if Octalyser isn't configured correctly for the machine
Protracker STE 1.4 Opens bottom border, 50kHz DMA replay
Shifter Inside STE 2.4 Extends GEM desktop to top + bottom borders in color modes. Disable TOS blitter option to avoid flickering
Stretch before 1.0 Screen extender (like the famous Bigscreen). One of the few programs which uses STE hardware scrolling also in monochrome mode

TT software compatibility list

Most programs that work on TT work also in the Falcon emulation. As all 1-8 bit GEM programs work without problems under TT emulation this section lists only programs that have TT-specific features.

TT applications and games

TT applications and games
Program Hatari version Notes
Atari800 2.3 Requires TT-RAM and 256-color VGA mode. Really slow. Sound works on Falcon as-is, on TT it requires XBIOS sound emulator
Capy 2.0 VGA/TT-medium only. Some version(s) bus error due to DSP register check (although they should support TT with chip music)
CrY image viewer 2.4 Needs at least some TT-RAM. Either the 8-bit (grayscale) luminance screen, or Cyan-red color screen is shown, not the correct interlaced "16bpp CrY mode" result (by tSCc)
Frontier: Elite 2 2.4 Palette issues (also present in Falcon emulation): Intro sequence partly plays in grayscale, in-game colors are completely wrong. Workaround: Play in ST/E emulation mode; preferrably with 16 or 32 MHz CPU clock to achieve a usable frame rate
HHeretic and HHexen 2.4 "Hacked" (fixed) SDL versions of original open source versions
M_PLAYER 2.3 Movie player and creator. v4.02 added TT microwire support for audio. Whether M_PLAYER actually uses sound, depends on there being enough RAM for it to cache video audio
Milanopoly 1.0 GEM monopoly
OpenTTD 1.9 "hatari-prg-args --tos-res ttlow --machine tt --tos etos1024k.img -s 4 --ttram 256 --addr24 off -- ./openttd-m68020.gtp -m null -s null -b 8bpp-optimized", then select "original" as "Land generator" at game start. Game startup takes minutes with nothing happening on screen. Works also on Falcons with FPU and no DSP, as long as it's started from 256-color resolution
Oxyd 2 TT 2.5 Oxyd 2 in color. 2-player mode via MIDI does not work in Hatari v2.4.1 or earlier
PM Doom, Heretic and Hexen 1.0 Heretic v0.24 is playable, Doom and Hexen v0.55 are very slow
ScummVm (2.8, 2.9) 2.5 Official (lite) 030-only version. Needs at least 4 + 32 MB RAM
STDOOM 2.5 020+ version of STDOOM. Runs in ST-low rez, and reasonably fast with default settings + "-nosound" option (audio quality is not that great)
TUM 1.0 "The Ultimate Minesweeper", GEM, sound effects

TT demos

TT demos
Demo Hatari version Notes
Brainstorm: Adebug 3DTT demo 1.2 (Works also on Falcon)
Daroou: Daroou's GEM Demos 2.0 Need TT-low mode. Work also on Falcon (with music). Boing demo bus errors on startup on both
Escape: Monscape 2.5 Voxels in TT-mono resolution
Evolution: Elegant Machinery 2.0 Nice 3D demo, need 32 MB TT RAM to load the music
Gildor: Yeti3D, Spear of Destiny 2.4.1 Atari ports of Yeti3D engine demo and ID's Spear of Destiny, both unusably slow under TOS. They work also on Falcon with FPU
gwEm: 4getful 2.1 TT-version of gwEm's 4K demo
Moving Pixels: TT-Wars before 1.0  
Omega: Swing, Vinjett, Xitkul 1.2 TT demos included to XiTEC presentation. Xitkul colors flicker
tSCc: 256mbrot, Bragg256, Glasstro, Orion-B, Yabt 1.8 TT/Falcon intros
tSCc: Beams 2.1 TT-version. At least 8MB RAM
tSCc: TC fish / blobs 2.4 Does not interlace properly, but is viewable
tSCc: TT-hires 2.4 Needs 8MB. Slideshow flickers badly + graphics and colors are wrong
Xanth FX: Shiny Bubbles TT 1.2  

Falcon software compatibility list

You can select between three different DSP emulation modes with the Hatari "--dsp" option:

  • none: No emulation. If program doesn't need/use DSP, this is the best option.
  • emu: DSP emulation. Needed if program requires DSP. On by default for Falcon. Slow.
  • dummy: Faked DSP response to DSP commands. Some rare programs that need DSP may also work with faked DSP. This is much faster than the real DSP emulation.

The "dsp" column in tables below tells which DSP emulation mode needs to be used for the given program to work. If it doesn't matter, the column contains "-". The column for whether the program works and for whether it has (working) sound have "yes" when this is true and otherwise "-".

If program requires FPU or MMU to be enabled, or does not work with MMU, that is indicated in the program description.

Due to incomplete VIDEL emulation and some remaining emulation cycle accuracy issues, some Falcon programs aren't usable yet. Most games and demos work with RGB monitor, those requiring VGA one are indicated separately.

Falcon games

Falcon games
Title Hatari version Sound DSP Comment
Aazohm Krypth 1.3 - emu Use joypad. 8MB RAM. Black screen without DSP emu. Auto-starting works from Hatari v2.1 onward
Aces High preview 1.3 - -
Addsub 2.3 - - Player 1 keys are Z + C, Player 2 keys are up + down. Space key removes the focused piece for both players. Joystick up or ESC exits
Ausbruch 1.0 yes - Use STE joypad. Background music if DSP emu enabled
Bad Mood 2.5 yes emu Enhanced Doom I/II game port and engine rewrite (v0.35-v0.37 from 2017 + v1.0 from 2022):
  • Requires DSP and 14 MB RAM
  • Supports joypad
  • Hatari v2.4.1 and earlier trigger random IKBD input events when game plays MIDI music
  • WAD cache creation crashes/freezes, so one needs to get pre-built WAD cache
  • Uses MMU if it is available, but rendering freezes with Hatari 030 MMU emulation
  • 32-bit addressing / TT-RAM works only with Hatari specific "bmhat.ttp" version
  • Game statusbar shows only in Hatari specific version due to Videl emulation omissions
  • Some parts have clear speed differences to real Falcon due to CPU, FPU (instruction too fast) and Videl emulation (screen updated only at VBL) inaccuracies
Bad Mood (demo) 2.5 - emu Old Doom level viewer (from 90's). All tested versions (v1.9a, v2.14a, v3.03a, v307a) work with Doom1 WAD. Heretic shareware WAD looks fine with older versions, (as long as one is not inside a wall), and broken with v3.x. "bm307.ttp" shows just black with MMU, but "bm307fh.ttp" and older versions work fine with MMU
Beats of Rage 2.3.1 yes - Music with DSP. Supports joypad. Due to game bug (data file corruption), it can freeze if user doesn't start game before it shows hall-of-fame screen
Blackhole 1.0 yes - 640x400@256 mode only
Block Blizzard (preview) 1.0 - -
Blum 1.4 yes - Background music if DSP emu enabled
Bomb Squad 1.0 yes - Supports joypad
Boom preview 2.1 yes emu
Bugger 1.4 yes emu If DSP emulation is disabled, game play pauses randomly
Bunion 1.4 yes emu If DSP emulation is disabled, game play pauses randomly
Capy 2.3.1 yes emu VGA/TT-medium (640x480x16) only
Cavemania (demo) 2.3 yes - Some menus are black (space gets through them). Moving player character to bottom of game area shows garbage. Background music with DSP emu
ChainZ 2.0 yes - Background music with DSP emu (keep space pressed while game is starting to disable that). VGA only
Chorensha 1.8 yes - Needs 14MB
Color runner 2.5 yes - 14MB + VGA, joypad only. Music with DSP. Game proceeds to main menu only if MMU is enabled or DSP disabled. Unable to start the game itself (only joypad fire works, not directions)
Columns by Deadheart (demo) 1.0 yes - Nice. Background music if DSP emu enabled
Command & Conquer 2.5 - - 32Mhz Falcon, FPU, 32MB TT-RAM
Confusion (demo) 2.3.1 yes - Background music if DSP emu enabled. Demo 1 works with 4MB, demo 2 needs 8MB
Conquest of Elysium II 2.3 yes - Both v2.0 (demo) and v2.3 work. Preview version freezes with more than 4MB of RAM
Corsair 1.0 yes - Background music if DSP emu enabled. Multiplayer
Crown of Creation 3D 1.3 yes emu Non-interactive pre-preview
DangerZone 2.4 yes - Needs 8MB RAM, VGA and 640x480@16-color mode
DB 4K 1.0 - - DB in 4KB
Double Bobble 2000 1.9 yes emu Supports joypad
DownFall 2.3 yes - Music with DSP
Dry Egg 2.3.1 yes - Music with DSP. Use joypad for control. Works only with EmuTOS (bus Error writing at address $e00000 with TOS v4)
DuSau 1.9 yes - Music with DSP. Use "--gemdos-drive e", expects to be run from E:
Epi-Lepsie 1.4 yes emu
Evolution: Dino Dudes
(aka. Humans)
2.5 yes - One of the Falcon launch games. Requires joypad emulation (including the 1-9 keypad buttons). Sound with DSP. Intro and menu work, but graphics are distorted in game itself (writes twice to the $ff820e register during one VBL!)
Falcon Fighter II 1.0 yes - ST low version has problems with scrolling (which doesn't happen with STe emulation). In TC version parts of sprites get stuck to top of screen
FalcTron 1.3 yes -
Ganymed 1.3 yes - Use F1 to start game. With DSP emu there's bg music
Galaga 88 1.9 - - Needs 14MB RAM
Golden Island (v1.24 demo) 2.3 yes - VGA only
Gravon (demo) 2.5 yes emu 3D hovercraft
Great 1.3 yes - Atoms clone
Grenzüberschreitung 2.3 yes - Tron, using Lua-interpreter. Use joypad
H2O 2.3.1 yes emu Nice. Music didn't work in Hatari v1.6 - v2.3
Hexogan (v0.6) 1.4 yes - Use number keypad to play
Impulse 1.3 yes - Nice breakout clone. Background music if DSP emu enabled
Ishar 1, 2 2.1 yes - Work only from a floppy
Ishar 3 2.1 yes -
Jewelz 2.0 yes - Background music with DSP emu (game F1 / Falcon option). VGA only
"K" 1.6 yes emu Preview for a Karting game by EXA. First entry in Training menu allows playing. Opens its AVR files as write-only
Killing Impact 2.5 yes - Modern version of Joust, up to 4 players. Sound with DSP. Supports joypad. Game screen colors etc are all wrong
King Solitaire 1.9 yes - Nice gfx
Kwiks.pd 1.9 yes - Nice Quix clone
Lamemine 1.0 yes dummy emu Background music if DSP emu enabled
Lasers and Hommes (DLDH2) 2.3 yes emu
L'Abbaye Des Morts 2.3 yes - Needs FPU. Slower than it should, because Hatari doesn't support Videl 60Hz Falcon emulation yet, only 50Hz. DMA sounds effects are too low volume compared to YM
Les Dinosaures (demo) 1.6 yes emu
Llamazap 2.5 - - One of the Falcon launch games. Linked version does not have (DSP) music, some others have. Needs STE joypad emulation, including the 1-9 keypad buttons (to select power-ups, ships and other functions)
Madtris 1.0 yes -
Masters of Chaos 1.1 - - 2-4 player "Dungeon Master". 4 players needs MIDI
Men at War (preview) 2.3.1 yes emu Supports joypad. Separate intro program requires 14MB, and double bus errors at end with TOS4. Bullets show only close to opponent
Mini F1 (demo) 2.5 - - Freezes when the game would start (until VideoAddressCounter == $2A?). Hatari takes all CPU with constant "crossbar DMA Play: Illegal buffer size 0" messages in console and sound is just noise
Moongame 2.5 - emu Only STE Joypad button 3 starts the game. After selecting speeder + track, game freezes when training would actually start. Under TOS4, freeze is accompanied with inifinite illegal DSP instruction warnings with "classic Falcon" option. Double bus error at game start with MMU
Moonspeeder (preview 2) 2.5 Yes emu Supports joypad. lst*.* files need to be renamed with GEMDOS HD emulation as game searches them with different name (space before dot)
Mouse trap 2.5 yes - Initial game screen looks OK, then suddenly screen mode gets wrong
Multi Briques 2.3.1 yes emu v1.50 and v1.00 (preview) versions of the game work
Neurobot.108 2.3.1 yes - Game versions from 1.06 onwards suppport also Joypad
Nibe2 2.0 - - Videl emu doesn't support rasters in title screen
Ninja Torappu 2.3 yes - Some extra sounds with DSP enabled
Offworld Security 1.9 yes -
Operation Skuum 1.3 yes -
Pac Them 1.0 yes - Nice
Pacmania X68000 2.3.1 - - Needs 14MB RAM. Vertical game scrolling can be jerky. Demo mode double bus errors after it has gone through all levels
Painium Disaster 1.0 yes - Some garbage temporarily visible in screen changes
Pinball Dreams 2.5 yes - Actual game screen is scrambled / doesn't work
Pingo98 2.5 yes emu Colors wrong in classic version. TC version just exits, but had worked in some very early Hatari version with DSP disabled
Poker 1.3 yes - Works only in VGA
Pong 2K (demo) 1.3 yes - Background music with DSP. A bit unstable
Pouspous 1.0 - - Image puzzle. HiColor only
Push It 1.4 yes - Background music if DSP emu enabled. Auto-starting works from Hatari v2.1 onward
Q-Blue 1.0 - - Hicolor / VGA only
Racer 2.3 yes - Music with DSP
Racer 2 2.3 yes - Music with DSP
Radical Race (demo) 1.0 - - Supports joypad. Nice
Raiden 2.5 yes - One of the Falcon launch games. Needs over 4MB and joypad emulation (including the 1-9 keypad buttons)
Rave 1.0 yes dummy emu Background music with DSP
Reeking Rubber 1.8 yes - (Required proper emulation of Videl palette registers at startup)
Risk 030 1.3.1 - - VGA (640x480) only. Auto-starting works from Hatari v2.1 onward
Road Riot 4WD ? ? ? TODO. One of the Falcon launch games. Supports joypad
Robinson's Requiem ? ? ? TODO. Supports joypad
Running 2.3.1 yes emu A Doom like game. Both the 1996 preview and 1997 shareware versions work fine. Auto-starting works from Hatari v2.1 onward. Main menu music didn't work in Hatari v1.6 - v2.3
SBM 1.6 - - Nice bomberman clone. v0.8 needs at least 2MB RAM. v1.03 needs at least 8MB to start reliably
Shangai (demo) 1.3 - -
Sheer Agony 1.3 yes -
Snatch by FUN 1.3 yes - VGA 640x480x16 mode only. Background music with DSP
Sky Fall 1.9 yes emu
Slippery Sam 2.4 yes - Background music with DSP
Sokoban by FUN 2.4 yes - VGA 640x480x16 only. Background music with DSP, occasional screen glitches without it
Space Taxi 1.0 - -
Spice 1.0 yes - Nice Defender clone
Spout 2.5 yes - Atari port of the game
Static 1.0 - - Play solitaire against computer
Steel Talons ? ? ? TODO. One of the Falcon launch games. Requires joypad
Sweety Thingies 1.6 yes - Background music if DSP emu enabled
Switch 1.0 yes emu Background music if DSP emu enabled. Joystick input and game timings don't work properly unless DSP is enabled
Sworm 1.9 yes emu
Tank Blaster 2.1 yes -
Tautology 1.9 yes -
Tautology II 1.9 yes - Nice Mahjongg variant. Supports Joypad (configurable). Background music with DSP emu enabled
TeknoBalls 1.4 yes emu Breakout clone
Tetrhex 1.0 - -
The 8 runes of Aerillion 2.3 yes emu Requires 14MB + FPU. DSP used for intro music + game rendering. Playable demo
Towers 2 1.3 yes emu
ToyMan 1.0 - - Pacman
Trinoids 1.9 yes - Columns clone, sound with DSP
Tron 2 2.0 yes dummy emu VGA only. Background music if DSP emu enabled
TsccTron (preview) 2.5 yes - Screen bottom is just black (palette tricks?). Background music if DSP emu enabled
Ufo War 2.5 yes - 256-color, 80-column, no interlace video mode only. Screen scrambled. With GEMDOS HD, after a while does Fread() to invalid RAM range, and terminates with error code. With disk image, lot of "pBank flags mismatch" and FDC DMA warnings. Background music with DSP emu
Ultimate Arena (demo) 1.5 yes - Falcon version of the game
Vertical Mayhem(+) 1.0 yes - Works perfectly, nice columns clone
Watership 2.0 yes emu Use VGA display, RGB has glitches at screen bottom
Willie Adventurer 1.0 yes - Both 1st and 2nd preview versions work, but 1st one has scrolling/display issues. Background music if DSP emu enabled
Wotanoid 1.4 yes emu Asteroids clone
X-moon 1.7 yes - Needs Joypad. Background music if DSP emu enabled
Zodiax (demo) 1.0 yes - Nice R-type game clone

Falcon demos

Some demos require FPU, although normal Falcon's don't have a one. You need to enable (68882) FPU separately if demo is stated to require FPU.

Falcon demos
Title Hatari version Sound DSP Comment
Abstract: X-perience 2.6 yes emu v2.5: Dots do not show in the "4096 dots" screen, and demo does not progress further from it, music just plays. Supposed to be for VGA, but screen is proper size only with RGB
v2.6: Freezes right at start
Aggression: Aggressive II party info 2.3 yes emu
Aggression: Motion 2.6 - emu Unfinished Aggression demo with 030 and DSP source code. All screens work fine except for "Susie" which shows broken graphics due to cycle-exactness issues (at end it waits for ESC to exit, it's not frozen). "Torus" screen freezes if MMU is enabled
An cool: The Keff demo 2.3.1 yes -
Archangel: 1600 x 600 1.6 - - Widest known Videl mode
Aura: E.X.Illusion 2.3 yes emu
Avena: Binliner preview 2.0 - emu Land & Worship screens die if started with space key, use something else. Background color is correct from Hatari v2.0 onwards
Avena: DSP fishes 2.0 - emu 3K
Avena & Digital Chaos: Enraged 2.5 yes emu Display update is wrong in first and many following screens (updated only in narrow strips). There's also a long period of white between the 2 plasma screens between the 3D screens
Avena: Sonolumineszenz 2.5 yes emu There are a lot of "Dsp: Modulo addressing result unpredictable" warnings in the "whale" screen. Start runs too fast, and white period after whale screen takes too long
Avena: Weltschmerz 1.3 yes emu
Black Scorpion Software: Atari UK demos 1.3 yes emu plasma50.prg (50Hz) has screen update issues
Bohemian Grove: At Night They Come 2.3 yes - Slideshow of AI generated graphics. MP2 music with DSP, occasionally with very high Hatari CPU usage
Bohemian Grove: Back To The ST 2.4 yes emu MP2/DSP music disk with AI generated content
Brainstorm: Mouse 1.4 yes - AVR animation+music with Brainstorm player
Cerebral Vortex / Dune / Sector One: Bird to be alive 2.1 yes emu
Cerebral Vortex / Dune / Sector One: Tere Ra'I 1.8 yes - Music with DSP
Cobra / Mystic Bytes: Revertant 1.2 2.2 yes - 14MB RAM. Music with DSP
Collapze: Sidetracked 2.3.1 yes emu VGA-only GFA musicdemo with DSP playback and GT2 songs
Crac: Bound 2.1 yes - Sound with DSP. Use Enter instead of Space key to proceed to the actual demo, otherwise it (often) freezes
Crac: Bound 2 2.3 yes emu
Crac: Bound 3 2.1 yes emu
Crac: Bound 4 2.5 yes emu Freezes with music playing, waiting on DSP interrupt (or Video Address Counter?)
Crac: Bound 5 2.5 yes emu Freezes in tiger screen, with music playing
Cruor: 96ktro 2.3 yes emu
Cruor: The mountains flight 2.3 yes emu
Cybernetics: Geotech 2.3 - emu Multitech program requires HiColor + DSP, Geotech one does not
D-Bug / Excellence In Art: Who OKayed This? 2.3.1 yes emu Run zikdisk.tos to see fixed version of EKO's original Epidemic music disk, and epidemic.prg to see the new and the original intros for it. Music in Epidemic didn't work in Hatari v1.6 - v2.3. From Hatari v2.3 onwards, demo doesn't start unless DSP is enabled
Dekadence: Ath/0 2.2 yes - Music with DSP
Dekadence: Kusipäät Kaljanpöllijät 2.4 yes - Music with DSP
Dekadence: Payback 2015 2.3 yes - Needs FPU and TT-RAM. Startup takes several minutes and demo is slow, so it is better just to disable DSP + sound to make fast forward faster: --machine falcon --cpulevel 6 --cpuclock 32 --fpu internal --dsp none --sound off --fast-forward yes -s 14 --ttram 64 --addr24 off --cpu-exact on
DHS: 128dist 1.7 - - Requires FPU
DHS: 128 krawl 1.7 - - Requires FPU
DHS: 4ever 1.7 - - Requires FPU
DHS: 4orce 1.7 yes - Requires FPU
DHS: Amanita 2.0 yes emu Music disk, DSP playback
DHS: A.T.S 1.0 yes - Fast. Needs FPU. Music with DSP
DHS: Don't Break The Oath 1.5 yes - Needs 14MB RAM + FPU, music with DSP (NoCrew DSP MP2-player)
DHS: Dream Dimension 2.1 yes - Music with DSP. Requires FPU
DHS / Evolution / New Beat: Echos 2.1 yes - 96k. Requires FPU. (ACE) music with DSP
DHS: GemDemo 1.3 - - Needs to be started in HiColor mode to work properly
DHS: Outline 2006 invite 1.5 yes - Needs 14MB RAM + FPU, music with DSP (NoCrew DSP MP2-player)
DHS: Xmas 1997 2.3 yes - Music with DSP
DHS / New Beat / SMFX: The Ultimate Seduction 2.3 yes - Sommarhack 2020 invite, music with DSP, 14MB
Digital Chaos: Built-in Obsolescence 1.6.2 yes emu Gives DSP stack overflows
DNT Crew: Agony 2.5 yes emu Type "spread.ntk" to prompt. After bouncing ball fades to black, gfx aren't quite right due to incomplete VFC emulation
DNT Crew: Chaos A.D.* 2.5 yes emu Uses its own IKBD 6301 routine to decode the demo's protection. First plasma is half the correct height. Picture before dot tunnel (on white background) is not visible
DrTypo: Voxel 1.9 - emu At least 4MB RAM, FPU and VGA monitor. Rendering looks correct only with the cycle exact mode
Dune: Electric Night 2.0 yes emu Requires very powerful machine to run at full speed
Dune / Sector One: 4musiK 2.2 yes emu Silly Venture 2018 Falcon 4K softsynth intro. 14MB RAM. Graphics won't show if TT-RAM is used
Dune / Sector One: Silly Venture 2k14 invitro 2.0 yes - Music with DSP
Dune / Mystic Bytes: Silly Venture 2k16 invitro 2.0 yes - Music with DSP
EKO: Are You Experienced? 2.1 yes emu
EKO: E.K.O System 2.3 yes emu "Betty Boo" picture is missing split screen transition to it and from it (shown in Youtube video). Music issues in some screens
EKO: Geranium 1.3 yes emu
EKO: Mars DSP 2.5 - emu Freezes at start waiting DSP (btst #1,$ffffa202.w)
EKO: Papa was a Blade Runner 2.5 yes emu Texture mapping uses DSP, but otherwise works fine also without DSP. From Hatari v2.3 onwards, freezes in "flat 3D" screen if MMU is enabled
Escape: _ 2.5 yes emu Freezes after few screens to illegal DSP instructions
Escape: Hmmm... 2.3 yes emu Classic from 2001
Escape: Illness 1.3 yes emu
EXA: Entracte 2.5 - - Counts CPU cycles at start and exits because Hatari is not accurate enough
EXA: Evolution 2.5 - - Counts CPU cycles at start and exits because Hatari is not accurate enough
Extream: Old Stuff 1.8 yes - Music with DSP
Extream: Tiny Things 2.0 yes - Music with DSP
Extream: Whirlpool 128k 2.3 yes emu Uses strange resolution / screen transitions
Extream: Why 2.2 yes emu 3D with DSP, music with CPU. Julia screen needs 14MB RAM
Extream: ZygyFem6 2.3 - - Sommarhack 2022 / 256b
Fit: Mahabharata 2.1 yes emu Bombay-style... With some HW / 3D drivers, SDL2 fails to create wide enough screen texture, workaround added to 2.1
Fun Industries: Alive 1.3 yes - Background music with DSP emu
Fun Industries: Fungle Beats 1.8 yes - Background music with DSP emu
Gaston: Earth 1.0 - - Texturemapped ball
Hydroxid: Symposium'96 2.5 yes emu Double bus error (NULL pointer) when fullscreen face texture (sin wave) bender screen ends
ICE: Cooler than ever 1.0 yes - Background music with DSP emu
ICE: Tron2001 2.4 yes - Non-interactive preview
Impulse: Bugs from Outer Space 1.1 yes -
KÜA Software ¨ Pixel Twins: I Want You To Remember 2.0 yes - Monochrome, sound with DSP
Lamers: Polygon discount 2.1 yes - 14MB, RGB. Music with DSP
Lamers: Too Silly 2 2.3 yes - MP2 music with DSP
Lamers ¨ Mystic Bytes: SillyVenture 2k13 Invitro 2.0 yes - MP2 music with DSP
Lazer: Autowaschen Verboten 2.1 yes emu
Lazer: Dan's Lustiges Kinderfest 2.3 yes emu
Lazer: Gurkensalad 1.3 - emu 4K. Ends in static starfield
Lazer: Lost Blubb 1.6 yes emu Nice
Lazer: Oergs 2.5 yes emu
Lazer: Warum 2.3 yes emu
Level 42: Apollo 13 2.0 - emu
Light: 680xx 2.0 yes emu
Lineout: Delta 1.3 yes emu Nice (+ almost half an hour long + includes sources)
Lineout: Hurry 1.3 yes emu
Lineout: Out 1.3 yes emu
Mind Design: 4some 2.0 - - Requires FPU + 8MB RAM
Miro Kropáček: Led Blur 2.5 yes - Atari port of demo. Nice but too slow even for 32Mhz. Works also on TT, but is even slower on 8-bit
MJJ Prod: Bound 42 2.3 yes emu
Mugwumps: Cycedelic knockout 2.3 yes emu Needs to be started from Falcon video mode
Mystic bytes: Chosneck Supplement (e5) 2.0 yes emu 14MB RAM. GEM shell requires starting from desktop. Intro requires FPU. Main program doesn't work with RGB 50Hz mode, only other ones
Mystic bytes: Lockup 2.5 - emu 14MB RAM. Requires at least Hatari v2.3.1. Music missing
Mystic bytes: Moai 96kB 1.4 yes emu 14MB RAM + FPU
Mystic bytes: Silly Venture 2k19 invitro 2.5 yes emu 14MB RAM. Plasma screen doesn't look correct at all
Mystic bytes: Time Out 1.6 yes emu Ends in black screen after plasma tunnel
Mystic bytes: Virtual Dream 2.0 - - 4K. Requires FPU. Needs to be started from HiColor mode
New Beat: Blue 1.1 - - Nice 4K demo from New Beat (Flu and Blue are emulation-wise both very lightweight)
New Beat: Flu 1.1 yes - Nice 4K demo from New Beat. Flu requires FPU
New Beat: Joy 2.2 yes - DSP MP2 music for most of demo, endpart music with YM
New Beat: Maggie 24 intro 1.3 - emu
New Beat: More 2.1 yes - Music with DSP
NoCrew: Aggressive Party 2 4k 1.4 - emu Uses DSP play / DMA record in handshake mode. Auto-starting works from Hatari v2.1 onward
NoCrew: Stocasto 2.3.1 yes emu Background color black, not blue like in Youtube video. Music didn't work before Hatari v2.3.1
Opium: Chrome Dragon 2.6 yes - Requires RGB. At least since v1.8, music is missing. Freezes when textured donut zooms out (in 2.5 demo restarted after that)
Opium: Falcon Flight 1.0 yes -
Orion_: Unexpected Fun 2.5 yes - needs 8 MB TT ram, music with DSP
Paradox: 2x1287 2.1 yes - Scenes from the demo scene. Music with DSP
Paranoia: Illusion 64 1.3 yes - Background music with DSP emu
Paranoia: Rock Solid + 2.0 yes - Background music with DSP emu
POV: JESTERday 1.4 yes emu 256 colors DSP+DMA MOD music demo
Psychosis: FOG-intro 1.3 yes emu
Rabenauge: Chipo Django 1 2.4 yes - STE mod chip music disk that supports also Falcon (and Amiga). Videl colors are wrong due to (timer-B) palette changes
Reservoir Gods: Are you sitting comfortably? 1.3 yes emu Needs 14MB to work properly (otherwise bombs or gets Dsp overflow)
Reservoir Gods: ASCII Art Intro, SnowStorm, Snowtro 1.0 yes emu Intros by RG. Require DSP (for music) with newer Hatari versions
Reservoir Gods: Cider, CryWolf, Grapes, Hobnobs, HongTron, Hot, Ketchup, Lettuce, Pasta, Wormhole 2.4 - - 128-Bytros. Start from TC mode. Grapes, Hobnobs, Ketchup and Lettuce need also FPU
Shadows: Firestarter 1.0 yes - Background music with DSP emu, slow otherwise. Auto-starting works from Hatari v2.1 onward
STAX: Doomino 1.4 - emu Doesn't work with 020 CPU
The Pixel Twins: Wee Gift 2.0 yes emu Needs FPU. Real time ray-tracing. After about ten minutes, animation starts to slow down a lot
The Pixel Twins: Zero Three Zero 2.5 yes emu Needs FPU. Real time ray-tracing with DSP
The Respectables: Cebit 93 demo 1.9 yes -
Thothy: Burnin Bee 2.4 - - 128-Bytro
Trio: 124 beers later 1.6 yes - Textures visible in fighter and last screen only with DSP
Trio: X-tasie 2.5 yes emu Requires MMU. There's a Videl emulation problem left when user moves mouse (before clicking) in the first screen
tSCc: 30L Coke 1.3 - emu 4K demo
tSCc: A Rh positive 2.3 - emu 4K demo
tSCc: Beams (Falcon version) 1.5 yes - Nice demo, NoCrew DSP MP2-music (Hatari v2.1 adds support for undocumented FPU constants needed by the first tunnel section)
tSCc: Laser Days E.P. 2.1 yes emu "80's" DSP MP2-music "disk"
tSCc: Six Sievert 1.3 yes emu
tSCc: Terrorize your soul 2.5 yes emu Works correctly only with MMU (without it, music is missing from last screens and there are illegal DSP instruction warnings). Z-buffer screen (before Rubik's cube) uses 68-pixel width, but Videl should repeat the pattern instead of stretching (same as when it is later roto-zoomed). Note: last ("firecube") screen before greets, is missing from demo's Youtube video
Tony Benett: Virtual City 2.1 - emu HiColor, drawing the city requires DSP emulation
Toons: Yepyha 2.0 yes emu Strange resolutions / screen sizes. At end of the demo, those are used to "roll-up" the screen content away before credits
T.O.Y.S: wait 1.6 yes emu Uses NoCrew DSP MP2-player. 60Hz RGB mode shows garbage, other mode selections work
Wildfire: 4kbtro 1.3 - emu Julia fractals
Wildfire: 4tune 1.7 - emu Requires FPU and DSP
Wildfire: ALT-party invtro 2.0 yes - Music with DSP
Unknown: Birdshow 1.0 - - A simple FLE animation player
Unknown: ROT3DBMP 1.9 - emu Streams data to DSP without synchronization

More Falcon demos can be found e.g. from the Atari.Org Falcon demos archive and demo tools from the dhs.nu demoscene site.

060 Falcon games and demos

Here are Falcon programs documented to be for a 060 CPU. Issue with several of them is them relying on CT TOS and its emulation for the 030/040 CPU/FPU instructions missing in 060. Some of the programs may even have worked with the oldUAE CPU core included to earlier Hatari versions, because it did not limit its instruction emulation only to instructions available on the actual selected CPU and FPU HW type.

FPSP060 is standalone Atari program providing emulation of the instructions missing on 060. Just have it in AUTO folder for demos listed as requiring it. See Thorsten Otto's page for FPSP binaries and sources.

060 Falcon games

060 Falcon games
Title Hatari version Sound DSP Comment
tSCc: OpenJazz, OpenTyrian 2.5 yes - 8MB + (060 internal) FPU + FPSP060. Best with 32Mhz CPU
tSCc: Worms 2.5 yes - 8MB + (060 internal) FPU + FPSP060. Sound only with DSP. Best with 32Mhz CPU. Needs original data files

060 Falcon demos

060 Falcon demos
Title Hatari version Sound DSP Comment
DHS: Circuitry of Desire, Codein, Derealization 2.4 yes - 4MB + 32MB TT-RAM (less for Codein) + FPU. Start with 030, or 060 + FPSP060. Music with DSP
Evolution: Chiaroscuro 2.4 yes - 14MB + 060 + FPU. Requires FPSP060. Music with DSP
Fit: Hex Pistols 2.4 yes emu 14MB + 060 + FPU, .tSCc port from Amiga. Requires FPSP060 and full software FPU emulation (--fpu-softfloat on)
Fit: Stercus Accidit 2.4 yes emu 8MB + 060 + FPU, .tSCc port from Amiga. Requires FPSP060 and full software FPU emulation (--fpu-softfloat on)
Lamers: F22.0 2.4 yes - 060 + 64MB TT-RAM + FPU + FPSP060. Music with DSP, but emulation too slow for it to sound decent, so best to disable that + cycle-exactness for speed. Works only with EmuTOS: --tos etos1024k.img --machine falcon --dsp none --sound off --fpu internal --cpulevel 6 --cpuclock 32 --cpu-exact off --addr24 off --ttram 64 -s 4 --fast-forward yes
Lamers: Lamer than Lamers 2.4 yes - Requires 32MB TT-RAM + FPU. Starts with 030, or 060 + FPSP060. Music with DSP
Lamers: LCF 2.4 yes - Requires 14MB + 060 / FPU + FPSP060 + VGA. Music with DSP. With 32Hmz, first + last scene render at 60 FPS, middle one at 21 FPS
Lamers: Own 2.4 yes - Requires 8MB + 060 / FPU + FPSP060. Music with DSP
MadWizards: Kioea 2.0 "yes" - 060 + 14MB + 64MB TT-RAM + FPU. Startup takes over 10 minutes and demo is also otherwise so slow that it is better to disable sound and DSP to speed up fast forwarding: --machine falcon --dsp none --cpulevel 6 --cpuclock 32 --fpu internal --sound off --fast-forward yes --addr24 off --ttram 64 -s 14
Orion_: We Reached Stars 2.3 yes - Music with DSP. Requires 060 + 32Mhz + 16 MB TT ram: --fast-boot on --machine falcon --dsp none --mmu off --cpulevel 6 --cpuclock 32 --fpu internal -s 4 --addr24 off --ttram 16 --sound off
TBL: Ocean Machine, Rift, Silkcut, Startstruck 1.9 yes - Require 060 + FPU + 4MB / 64MB TT-RAM. Music with DSP (DSP + sound can be disabled to speed up emulation a lot)
XI / Satantronic: Imperfect 2.5 yes - 512b intro, requires 060 + MMU + FPU + FPSP060

Falcon applications

Here are listed some Falcon specific applications:

Falcon applications
Title Hatari version DSP Comment
AceMidi (demo) 1.6 emu DSP generated softsynth sounds need Hatari v2.0+ to sound good
AceTracker v2 2.0 emu Sounds good, but needs beefy machine for fast enough DSP emulation
AFM (audio fun machine) v1.03 2.5 emu Popups work only when invoked with quick click. In Hatari v2.4.x and earlier, sound was fine only when equaliser was disabled
Animator (v0.20.4) 2.3 - Replacement for Aviplayer. Doesn't play all the files that M_PLAYER does
Aniplayer v2.22 2.5 emu On first MP2/MP3 file play, complains about DSP sync. After that, MP2/MP3 files play, but not correctly, bits of songs are repeated many times when they should not. Does not handle colors properly for MPG files either. Rest seems to work OK
Apex Alpha (demo) 2.5 - Requires MMU + FPU. Screen isn't drawn properly, but dragging scorpion image over top + bottom left screen area reveals GUI buttons that can be moved around the screen. Doesn't seem to do much else
Apex demo v2.10 2.5 emu apex210d.prg crashes on startup. Separate image viewer programs work fine. On real Falcon, application (its bus error detection for Expose digitizer) crashes under debugger, but works when run normally (details)
Apex Media v2.41 2.3 - The latest v2.41 works fine, as do the separate viewers. Decoding of several image formats uses DSP, but otherwise works also fine without DSP
AVI 030 2.3.1 - Very picky about which AVI files it supports
Centview 1.3 emu Remember to run included JPEGD.PRG first (or put it to AUTO-folder)
Centurbo bench 2.6 emu v2.4-v2.6, MMU enabled: FPU 274 Mhz, DSP 65 Mhz, CPU 116 Mhz
v1.8, WinUAE CPU core: FPU 221 Mhz, DSP 32 Mhz, CPU 15 Mhz
Without MMU, FPU numbers are 2-3x and moving mouse can drop FPU + CPU numbers by ~20%
Chronos 3D player 2.0 - Requires 15-bit display (e.g. NOVA)
Delmpaint 2.3.1 - Program errors at exit if started in other than 256-color low rez
Escapepaint 1.1 - Very nice
FalcAMP 2.5 emu MP3s have no sound / complain about DSP timeout (v1.20b, v1.09 just does not do anything with MP3 files), but MOD playing works fine, unless MP3 is tried first
Falcon Sound CPX 2.3 - CPX to view + set Falcon's sound system state
FlaySID (v3.01) 2.5 emu (SID playback with DSP engine froze app in Hatari v2.4.x)
FlexTrax 2.5 emu Heavy to emulate, sound is very noisy. Startup graphics look OK only in VGA mode
Fractals 2.2b 2.4 emu 030 + DSP used for fractal calculations
GemPLAY 2.0 emu Requires beefy machine for AM playback. Unless user manually adds (e.g. "C:") drive prefix to file mask in file selector, v1.92 and newer prefix file paths with "*", which is not accepted by TOS4 (but works with EmuTOS)
Godpaint 1.0 -
Graoumf Tracker (v0.8890) 2.3.1 emu 32-voice DSP soundtracker
Hextracker (v0.849) 2.4 - Uses non-standard mouse event scaling factor. Mouse cannot (vertically) traverse whole Hatari window unless mouse grab or fullscreen mode is used
Indypaint 1.0 - Errors at exit if started in other than HiColor mode
Jam (1.1b) 2.6 emu Version with DSP plugins
MegaPlayer v1.15/v1.16 2.3.1 emu Plays several tracker formats using DSP with nice GEM UI. Unless user manually adds (e.g. "C:") drive prefix to file mask in file selector, it prefixes file paths with "*", which is not accepted by TOS4 (but works with EmuTOS). Plays Octalyzer MODs badly
MP2 player 1.5 emu NoCrew MP2 sound player
Quaderno 2.3.1 - MIDI auto-composer / sequencer. Expects 640x400@256. Does not work when run from the drive root; it either needs to be run from a subdirectory, or one needs to symlink quaderno.sys directory under itself to work around this (which is required when using auto-start as that runs program with its directory being the drive root)
Rainbow II multimedia 1.5 emu Application scrolls screen from the edges, so mouse cannot easily be used to interact with items on the sides, unless Hatari mouse grab or fullscreen mode is used
ScummVm (2.8, 2.9) 2.5 - Official full Atari m68k version, needs FPU and 4 + 64 MB RAM
Voxx 2.6 emu Requires VGA with 640x480@16 resolution at minimum. "About Voxx..." dialog closes immediately on opening. In Hatari v2.5 and older, freezes due to incorrect DSP interrupt JMP instruction handling
Whip 1.4 emu Effects are working
WinRec 1.4 emu Direct-to-disk recording with DSP sound effects processing

TT/Falcon utilities

Here are some utilities that either have specific TT and/or Falcon support or work only on them.

TT/Falcon utilities
Utility Hatari version Notes
Adebug (reloaded) 1.7 Debugger supporting 68000 - 68030
Calamus 2.3
Calamus SL, 1991 demo:
  • Nice with TT-med or TT-high, and larger VDI modes
  • Requires at minimum 2MB ST and 640x400 resolution
Calamus SL2000, SL2003 R3, L2006 R7, LE2015 R1 (lite versions):
  • Require either GDOS to be installed, or ancient EmuTOS version (v0.9.5-v0.9.12) which did not have GDOS stubs
  • Require at minimum 8MB RAM and 800x600 resolution (e.g. TT-high or 800x600@16 VDI mode)
Cécile v2.22 2.3 Hard disk driver. Supports only TT and Falcon. When using Falcon as machine, you either need to use an IDE disk, or disable the "fast boot" option.
FreeMiNT / XaAES 2.5 Needs to be installed/booted from a hard disk image as MiNT overrides GEMDOS (i.e. GEMDOS HD cannot do its interception).
FreeMiNT v1.19, bootable snapshot from summer 2022:
  • With 030 MMU + FPU + cycle-exact (cache) emulation enabled, switching to XaAES, clicking on "Quit all Apps" and then "Quit XaAES" (in "Process" menu) freezes with EmuTOS v1.1.x (but works OK with newer EmuTOS versions)
VanillaMiNT-030-1.19, from March 2017:
  • Under TT emulation, if either MMU or 32-bit addressing is enabled, MiNT crashes at startup to "Bus Error writing at address $fffffff". Under Falcon emulation, both work
HD Driver 1.9 Hard disk driver. Works with native ACSI, IDE and SCSI on machines supporting those interfaces + NF_SCSI driver. Native SCSI bus support requires at least 020 CPU, rest work with any 680x0 CPU
ROMSPEED 1.9 Utility to copy and run ROM from Fast-RAM to accelerate its working. Requires TT-RAM. Different memory timings on different memory areas aren't emulated yet, so no speed changes are visible

Software sites

In case you are new to the Atari scene or an old-timer who's forgotten where all the freely available software is, these file archive sites still retain their Atari sections:

Demos (for many platforms) can be found from pouet.net and besides Atari scene news, Dead Hackers Society provides descriptions, screenshots and files for tools to create music, graphics and code on Atari:

There are also couple of sites dedicated to STE and Falcon specific software:


hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/de/000077500000000000000000000000001504763705000216765ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/de/tastatur-windows.txt000066400000000000000000000003341504763705000257760ustar00rootroot00000000000000# Below is a keymap file for Hatari running on Windows, which maps the SDL # keys of a German PC keyboard to the corresponding Atari ST scancodes. 45,12 # ß 91,26 # Ü 93,27 # + 92,41 # # 96,43 # ~ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/debugger.html000066400000000000000000002347361504763705000237770ustar00rootroot00000000000000 Hatari Debugger User's Manual

Hatari Debugger User's Manual

Index

The debugger

Hatari has a built-in debugging interface which can be used for analyzing code that runs in the emulated system.

On Unix (Linux / macOS) debugger uses Hatari's parent console window, so make sure you run Hatari from the command line when you want to use the debugger. On Windows you need to use "-W" option to get console window. You can add an icon to your desktop that does it. On Linux it should do something like this (replace "xterm" with your favorite terminal program):

xterm -T "Hatari debug window" -e hatari

To run debugger commands from a file at Hatari startup, one can use the "--parse <file>" command line option. This is useful e.g. for debugging TOS or some demo startup code, or if you always want to use some specific debugger setup (breakpoints etc).

Note that when debugger scripts are run, current directory is set to the currently running script's directory i.e. all file operations are relative to it. After script finishes, earlier current directory is restored. To set current directory from a setup script, e.g. for scripts run at breakpoints, you need to give '-f' option for the 'cd' command.

Invoking the debugger

You can invoke the debugger manually by pressing the AltGr + Pause key combination.

With the "-D" command line option, you can toggle whether m68k exceptions will also invoke the debugger. Which exceptions cause this, can be controlled with the "--debug-except" option.

Giving "-D" option at Hatari startup is not advised because TOS HW checks generate some exceptions at every TOS boot. It is better to toggle exception catching later from the debugger with the "setopt -D" command.

Alternatively, you can give "--debug-except" option "autostart" flag (e.g. "--debug-except all,autostart"). This will enable catching of (specified) exceptions after TOS boot, when Atari program given on Hatari command line is autostarted.

Debugger configuration options

When you save Hatari configuration, your current debugger settings are also saved to the Hatari configuration file.

Settings are following:

nNumberBase
Debugger number base. Set with the debugger "setopt [bin|dec|hex]" command
nSymbolLines
Number of lines to show when listing debug symbols
nMemdumpLines
Number of memory dump lines to show
nFindLines
Number of find (search) lines to show
nDisasmLines
Number of disassembly lines to show
nBacktraceLines
Number of items to show in stack/bactraces
nExceptionDebugMask
Mask of exceptions which invoke debugger when exceptions catching is enabled (-D). Set with the "--debug-except" option
nDisasmOptions
Disassembler output options, set with the "--disasm" option
bDisasmUAE
Whether disassembly uses CPU core internal disassembler ("uae"), or the external disassembler ("ext" one with output options). Set with the "--disasm" option
bSymbolsAutoLoad
Whether debug symbols are automatically loaded when debugger is invoked, and freed when program exits, for Atari programs run though GEMDOS HD. Set with the "symbols autoload [on|off]" command
bMatchAllSymbols
Whether symbol name TAB-completion matches just symbols from a relevant code section, or all of them. Toggled with the "symbols match" command

These are their defaults:

[Debugger]
nNumberBase = 10
nSymbolLines = -1
nMemdumpLines = -1
nFindLines = -1
nDisasmLines = -1
nBacktraceLines = 0
nExceptionDebugMask = 515
nDisasmOptions = 15
bDisasmUAE = TRUE
bSymbolsAutoLoad = TRUE
bMatchAllSymbols = FALSE

Settings on how many lines are shown can be changed only from the configuration file. You should need to change/set them only if you have built debugger without readline support, as it is then unable to determine terminal size ("-1" = use terminal height).

Note that "--debug-except" and "--disasm" options can be given either on Hatari command line or, like all other Hatari command line options, with the debugger "setopt" command.

General debugger use

At the debugger prompt, type "help" to get a list of all the available commands and their shortcuts:

Generic commands:
           cd (  ) : change directory
         echo (  ) : output given string(s)
     evaluate ( e) : evaluate an expression
         help ( h) : print help
      history (hi) : show last CPU and/or DSP PC values + instructions
         info ( i) : show machine/OS information
         lock (  ) : specify information to show on entering the debugger
      logfile ( f) : open or close log file
        parse ( p) : get debugger commands from file
       rename (  ) : rename given file
        reset (  ) : reset emulation
   screenshot (  ) : save screenshot to given file
       setopt ( o) : set Hatari command line and debugger options
    stateload (  ) : restore emulation state
    statesave (  ) : save emulation state
        trace ( t) : select Hatari tracing settings
    variables ( v) : List builtin symbols / variables
         quit ( q) : quit emulator

CPU commands:
      address ( a) : set CPU PC address breakpoints
   breakpoint ( b) : set/remove/list conditional CPU breakpoints
       disasm ( d) : disassemble from PC, or given address
         find (  ) : find given value sequence from memory
      profile (  ) : profile CPU code
       cpureg ( r) : dump register values or set register to value
      memdump ( m) : dump memory
       struct (  ) : structured memory output, e.g. for breakpoints
     memwrite ( w) : write bytes to memory
      loadbin ( l) : load a file into memory
      savebin (  ) : save memory to a file
      symbols (  ) : load CPU symbols & their addresses
         step ( s) : single-step CPU
         next ( n) : step CPU through subroutine calls / to given instruction type
         cont ( c) : continue emulation / CPU single-stepping

DSP commands:
   dspaddress (da) : set DSP PC address breakpoints
     dspbreak (db) : set/remove/list conditional DSP breakpoints
    dspdisasm (dd) : disassemble DSP code
   dspmemdump (dm) : dump DSP memory
   dspsymbols (  ) : load DSP symbols & their addresses
   dspprofile (dp) : profile DSP code
       dspreg (dr) : read/write DSP registers
      dspstep (ds) : single-step DSP
      dspnext (dn) : step DSP through subroutine calls / to given instruction type
      dspcont (dc) : continue emulation / DSP single-stepping

Entering arguments to debugger commands

After writing (with TAB completion) one of the above command names, pressing TAB will (for most commands) show all the available subcommands.

If you want to give numbers in other number bases than the default/selected one, they need to be prefixed with a character indicating this. For decimals this prefix is "#" (#15), for hexadecimals "$" ($F), and for binary values it is "%" (%1111).

By default debugger expects all numbers without a prefix to be decimals, but you can change the default number base with the "setopt" command, just give it the desired default number base (bin/dec/hex). When using the hexadecimal number base, remember still to prefix hexadecimal numbers with '$' if they could be confused with register names (a0-7, d0-7)! Otherwise results from expressions and conditional breakpoints can be unexpected.

Calculations and immediate evaluation

Instead of a number, you can also use an arithmetic expression, by surrounding it with quotes (""). An expression can contain calculations with CPU and DSP registers, symbols and Hatari variables in addition to numbers. For example to give a sum of A0 and D0 register values to a command, use "a0+d0".

Also within arithmetic expressions, parenthesis are used to indicate indirect addressing, not to change the order of precedence. Unlike with conditional breakpoint expressions (explained below), you cannot give size for the indirect addressing, a long value is always read from the RAM address given within parenthesis. For example to get a long value pointed by stack pointer + 2, use "(a7+2)".

Values of arithmetic expressions are always evaluated before being given to a command. Except for "evaluate" and "address" commands, they always need to be marked with quotes (""). Besides arithmetic, this can be used also to give symbol/address/register/variable values to commands that don't otherwise interpret them. If command complains that it didn't recognize e.g. a register name, just put it to quotes and it will be "evaluated" before being given to the command.

Virtual V0-V7 "registers" can be used to store intermediate results for calculations. For example, to get a sum of "_counter" symbol address contents one could use following in suitable breakpoint:

# store counter sum to V0 virtual register
r v0=(_counter)
# store count of how many values are added
r v1="v1+1"

And then later on, calculate the average:

# round the counter sum (add half count to sum)
r v2="v0 + v1/2"
# and calculate the rounded average (rounded sum / count)
e v2/v1

(Another virtual register was used for rounding here, in case one wants to continue summing the _counter values with the original value.)

With command argument completion (see readline), result from the last "evaluate" command can be inserted by typing '$' and pressing TAB.

Hatari outputs and their controls

When debugging something, there can be so much output that you want to store emulator output for more detailed inspection later.

Hatari has 5 different kinds of outputs and their controls:

  • Tracing, controlled by "--trace" and "--trace-file" options
  • Logging, controlled by "--log-level" and "--log-file" options
  • Debugger CPU+DSP disassembly / memdump / register commands output, controlled by "logfile" command
  • Xconout redirection [1] + Hatari command line help, hard-coded to "stdout"
  • Rest of debugger (breakpoints etc) + Hatari output, hard-coded to "stderr"

With 3 first options defaulting to "stderr".

If you want to catch all of them, it is better just to redirect all "stdout" and "stderr" output from Hatari to a file:

hatari --parse debugger.ini --trace os_base 2>&1 | tee out.log

("2>&1" redirects stderr to stdout for piping, and "tee" command both saves and shows the output, so that you can still see the saved output in more or less real-time.)

[1] E.g. VT52 console redirection to Hatari standard output is enabled with "os_base" and "os_all" trace settings and "--conout 2" option.

Inspecting emulation state

In the beginning, probably the most interesting commands are "m" and "d" for dumping and disassembling memory regions. You can use "dm" and "dd" commands to do the same for the DSP.

> help memdump
'memdump' or 'm' - dump memory
Usage:  m [b|w|l] [start address-[end address| count]]
	dump memory at address or continue dump from previous address.
	By default memory output is done as bytes, with 'w' or 'l'
	option, it will be done as words/longs instead.  Output amount
	can be given either as a count or an address range.
> help disasm
'disasm' or 'd' - disassemble from PC, or given address
Usage:  d [start address-[end address]]
        If no address is given, this command disassembles from the last
        position or from current PC if no last position is available.
> disasm pc
(PC)
$00aa6e : 2f08                                 move.l    a0,-(sp)
$00aa70 : 0241 0fff                            andi.w    #$fff,d1
$00aa74 : 207c 00fe 78c0                       movea.l   #$fe78c0,a0
$00aa7a : 2070 1000                            movea.l   (a0,d1.w),a0
$00aa7e : 4ed0                                 jmp       (a0)

Both commands accept in addition to numeric addresses also register and symbol names, like in above example. If you don't specify an address, the commands continue showing from an address that comes after the previously shown data.

"disasm" command default address will be reset to program counter (PC) address every time you re-enter the debugger. If history is enabled and it includes addresses just before PC, disassembly will instead start from a slightly earlier address to give more context.

Use "setopt --disasm help" if you want to set options controlling the disassembly output.

You can use the "info" command to see state of specific sets of HW registers (e.g. "info videl") and Atari OS structures (e.g. "info gemdos").

One can also show contents of arbitrary program structs with the "struct" command. Parts of the structure can be skipped (with 's') and they can be shown in different number bases:

> help struct
'struct' - structured memory output, e.g. for breakpoints
Usage:  struct <name> <address>[name]:<type>[base][:<count>[/<split>]] ...]

        Show <name>d structure content at given <address>, with each
	[name]:<type>[base][:<count>] arg output on its own line, prefixed
	with offset from struct start address, if [name] is not given.
	Output uses multiple lines when type count <split> is given.
	Supported <type>s are 'b|c|w|l|s' (byte|char|word|long|skip).
	Optional [base] can be 'b|o|d|h' (bin|oct|dec|hex).
	Defaults are hex [base], and [count] of 1.

For example:

> struct TOS 0xe00000 :s:2 version:wh:1 :s:20 os_date:lh os_conf:wb :s:14 :a:4
TOS: $e00000
+ version : 0206
+ os_date : 03172024
+ os_conf : 0000000011111111
+ $2c     : ETOS

By saving such command to a file, it can be used with breakpoint ":file" option, to show contents of a given structure, whenever that breakpoint matches.

Prefixing "info" and "struct" commands with "echo \ec" command would clear screen before their output. This could help noticing changes in real-time output.

Selecting what information is shown on entering the debugger

By using the "lock" command, you can ask Hatari to show specific information whenever you enter the debugger, e.g. due to a breakpoint. For example to see disassembly from current PC address, use "lock disasm".

With the "regaddr" subcommand, you see disassembly or memory dump of an address pointed by a given register ("lock regaddr disasm a0"). Of the DSP registers, only Rx ones are valid for this subcommand.

"file" subcommand can be used to get (arbitrary number of) commands parsed and executed from a given debugger input file whenever debugger is entered. With this you can output any information you need:

lock file debugger.ini

To disable showing of this extra information, use "lock default". Without arguments "lock" command will show the available options (like the "info" command does).

Debug symbols

You can load debugging symbols to the debugger with the "symbols" command (and with "dspsymbols" for DSP). These symbolic names can be used in arithmetic expressions and conditional breakpoint expressions. They also show up in the "disasm" command output and you can trace calls to them with "trace cpu_symbols" (and DSP symbols with "trace dsp_symbols").

Demangling (C++) symbols

C++ (and other similar languages) store symbols in binaries in a "mangled" format used by tools like linkers, and symbol names need to be "demangled" (expanded) to a more readable / user-friendly format. This can be done using a tool coming with the C++ compiler (in "binutils-m68k-atari-mint" package). ScummVM example:

$ gst2ascii scummvm.ttp | m68k-atari-mint-c++filt > scummvm.sym

(When symbols are in a file named as "<prgname>.sym", Hatari debugger will load symbols from it, not from the program file.)

Note: many demangled C++ symbols contain special characters which prevent them from being given as arguments to breakpoints and other debugger commands. One should use (resolved) symbol address instead for those commands.

Addresses for symbols that match user specified substring, can be listed like this:

symbols name XMLParser::parse

Symbols for a program under GEMDOS HD emulation

If currently running program contains debug symbol table, and it is started from GEMDOS HD emulated drive, its symbol names / addresses are automatically loaded when debugger is invoked, and removed when that program terminates.

Above happens only if there are no symbols loaded when the program starts. If there are, you can load program symbol data manually with the following command, after program has been loaded to the memory by TOS (see setting breakpoint at program startup):

symbols prg

The options you need to add suitable symbol table to your programs, depend on which toolchain you use to build it:

Devpac:
"OPT D+,X+"
AHCC:
"-g" and "-l" options for linking
GCC:
"-g" for compilation, and no strip option for linking (with older Hatari versions that did not support "a.out" format, also "-Wl,--traditional-format" option was needed for linking)
VBCC:
"-g" (can only be used at linking phase), when VBCC configuration file uses "-bataritos" option for the linker

You can view the generated symbols (and convert them to debugger ASCII format) with tool installed with Hatari:

$ gst2ascii program.tos > program.sym

(By default "gst2ascii" filters out same symbols as Hatari debugger does.)

For C++ programs, pipe the that output through m68k-atari-mint-c++filt demangler (see above) before directing it to a file.

Overriding program symbols

Symbols in a program can be overridden by providing similarly named ".sym" file in the same directory. If for example there is a file called "program.sym", debugger will try to load that instead of "program.prg" (when debugger is first invoked after that program started).

There are few reasons why one might want to do that:

  • Provide debugger demangled versions of the C++ program symbols
  • Stripped binary + ".sym" file take less space than providing also a non-stripped binary
  • Add (function) or remove (loop) symbols to improve profiling results

For a program on a (disk) image

If the program isn't run from a GEMDOS HD emulated drive, but from a cartridge, floppy or HD image, you need to have the corresponding program also as a normal host file which location you can give to the debugger:

symbols /path/to/the/program.tos

ASCII debug symbol files

If Hatari complains that your program doesn't have debug symbol table, or its symbols are in some unsupported format, you have two options:

  • Convert the symbols to ASCII format understood by the Hatari debugger. Writing converters for other ASCII formats is easy, and Hatari already contains converters for DSP LOD files, nm output, and a few other formats.
  • Create the ASCII symbols file by hand while you're debugging a program.

NOTE: nm output for GCC generated a.out binaries includes labels also for loops, not just functions. While loop labels are fine for debugging, they should be removed before profiling. Besides causing misleading profile results, loop labels can seriously slow down profiling (call graph tracking is automatically enabled for profiling when debug symbols are loaded, and operations done on each matched symbol address cause huge overhead if that match is for something happening every few instructions).

ASCII symbols file format is following:

e01034 T random
e01076 T kbdvbase
e0107e T supexec

Where 'T' means text (code), 'D' means data and 'B' means BSS section type of address. The hexadecimal address, address type letter and the symbol name are separated by white space. Empty lines and lines starting with '#' (comments) are ignored.

Debugger will automatically "relocate" the symbol addresses when it loads them from a program binary, but with ASCII symbol files you need to give the relocation offset(s) separately, unless the symbol names are for fixed addresses (like is the case e.g. with EmuTOS):

symbols program.sym TEXT DATA BSS

If you're interested only about code symbols, you can leave DATA and BSS offsets out (the values of the above virtual debugger variables like TEXT come from the currently loaded program's basepage, they're set after the program is loaded by TOS, see "info basepage" output).

Debugging resident programs

When debugging resident (TSR) programs (terminated with a Ptermres() GEMDOS call), you'll probably have a 'trigger' program that invokes some functionality in the TSR you want to debug. Loading your 'trigger' program from a Hatari GEMDOS emulated drive will autoload its symbols, thus replacing the symbols of your TSR (which you are really interested in) you loaded previously.

Symbol replacement can be avoided in two ways:

  • Using "symbols autoload off" debugger command, or
  • Loading TSR symbols from an ASCII file (see above). This is the way to go if your TSR is not a normal Atari program, or you want to filter or add some symbols (e.g. for profiling)

Breakpoints

There are two ways to specify breakpoints for Hatari. First, there are the simple address breakpoints which trigger when the CPU (or DSP) program counter hits a given address. Use "a" (or "da" for the DSP) to create them, for example:

a $e01034
a some_symbol

Note that address breakpoints are just wrappers for conditional breakpoints so you need to use "b" command to remove or list them.

Then there are the conditional breakpoints which can handle much more complex break condition expressions; they can track changes to register and memory values with bitmasks, include multiple conditions for triggering a breakpoint and so on. Use "b" (or "db" for the DSP) to manage them.

Help explains the general syntax:

> help b
'breakpoint' or 'b' - set/remove/list conditional CPU breakpoints
Usage:  b <condition> [&& <condition> ...] [:<option>] | <index> | help | all

Set breakpoint with given <conditions>, remove breakpoint with
given <index>, remove all breakpoints with 'all' or output
breakpoint condition syntax with 'help'.  Without arguments,
lists currently active breakpoints.

Unless you give breakpoint one of the pre-defined subcommands ('all', 'help'), index for a breakpoint to remove or no arguments (to list breakpoints), the arguments are interpreted as a new breakpoint definition.

Each conditional breakpoint can have (currently up to 4) conditions which are separated by "&&". All of the breakpoint's conditions need to be true for a breakpoint to trigger.

Breakpoint options

Normally when a breakpoint is triggered, emulation is stopped and you get to the debugger. Breakpoint options can be used to affect what happens when a breakpoint is triggered. These options are given after the conditions, and are prefixed with a (space and) ':' character.

<count>
Break only on every <count> hit. For example, to stop on every other time PC is at given address, use:
a $1234 :2
once
Delete the breakpoint when it is hit, i.e. trigger it only once. It may be useful if you just want to get a specific address. Or if you're on an instruction that jumps back to a start of the loop and you want to finish the loop, you could use:
b pc > "pc" :once
continue
trace
Continue emulation without stopping after printing the value that triggered the breakpoint and doing other possible option actions. This is most useful when investigating memory or register value changes (explained below).
lock
Show the same information on breakpoint hit as you see when entering the debugger (see the "lock" command in Inspecting emulation state above). This enables also trace option as you would anyway see this information if debugger would be entered.
info <name>
Show on breakpoint hits the same information as "info" command would show (see Inspecting emulation state above). With "lock" option and command there is more control over what information is shown, whereas with "info" option, every breakpoint can show different information, and one doesn't need to change what's shown on entering the debugger. This option also enables trace option.
file <file>
Execute debugger commands from given <file> when this breakpoint is hit. With this you have complete control over what information is show when the debugger is hit, you can even chain breakpoints (as explained in Chaining breakpoints later on). Use this when "info" and "lock" options are not enough.
noinit
Avoid debugger initialization (profiling data reset and disassembly address being set to current PC) on breakpoint hit. This enables trace option as entering debugger would anyway re-initialize debugger state. This option is mainly intended for breakpoints that use either ":file" or ":lock" option to show backtraces with "profile stack" command during profiling. See Usage examples section for an example.
quiet
Inhibit showing of extra information when breakpoint is either set or hit i.e. show only the information that breakpoint itself outputs.

Note: you can give multiple options for conditional breakpoints, but for address breakpoints you can give only one these options. "file" and "info" options are supported only for conditional breakpoints.

Breakpoint conditions

"b help" explains very briefly the breakpoint condition syntax:

> b help
condition = <value>[.mode] [& <mask>] <comparison> <value>[.mode]

where:
        value = [(] <register/symbol/variable name | number> [)]
        number/mask = [#|$|%]<digits>
        comparison = '<' | '>' | '=' | '!'
        addressing mode (width) = 'b' | 'w' | 'l'
        addressing mode (space) = 'p' | 'x' | 'y'

For CPU breakpoints, mode is the address width; it can be byte ("b"), word ("w") or long ("l", default). For DSP breakpoints, mode specifies the address space: "P", "X" or "Y". Note that on DSP only R0-R7 registers can be used for memory addressing. For example;

db (r0).x = 1 && (r0).y = 2

If the value is in parenthesis like in '($ff820)' or '(a0)', then the used value will be read from the memory address pointed by it. Note that this conditional breakpoint expression value is checked at run-time whereas quoted arithmetic expressions (mentioned in Entering arguments to debugger commands above) are evaluated already when adding a breakpoint. For example, to break when a value in an address (later) pointed by A0 matches the value currently in D0, one would use:

b (a0) = "d0"

If you're interested only on certain bits in the value, you can use '&' and a numeric mask on either side of comparison operator to mask the corresponding value, like this:

b ($ff820).w & 3 = (a0)  &&  (a1) = d0 & %1100

Comparison operators should be familiar and obvious, except for '!' which indicates inequality ("is not") comparison. For example:

b d0 > $20  &&  d0 < $40  &&  d0 ! $30
Tracking breakpoint conditions

As a convenience, if the both sides of the comparison are exactly the same (i.e. condition is redundant as it is always either true or false), the right side of the comparison is replaced with its current value. This way you can give something like this:

b pc > "pc"

As:

b pc > pc

That in itself isn't so useful, but for inequality ('!') comparison, conditional breakpoint will additionally track and output all further changes for the given address/register expression. This can be used for example to find out all value changes in a given memory address, like this:

b ($ffff9202).w ! ($ffff9202).w :trace

Because tracking breakpoint conditions will print the evaluated value when it changes, they're typically used with the trace option to track changes e.g. to some I/O register.

Breakpoint condition notes
  • Any '!' condition should be given as the first condition. Because breakpoint evaluation is stopped ("short-circuited") when any of the conditions fails, the tracked value would not be updated correctly unless tracking condition is given as the first one.
  • Hatari will internally update some register values without immediately updating the corresponding I/O address range memory addresses. For example the Busy bit for the internal Blitter control register is (internally) cleared when Blitter activity stops, but the actual I/O address for that control register gets updated only when something actually writes or reads that I/O address. Many HW registers behave like this (status registers in FDC, ACIA, MFP, Blitter...).
    For breakpoints that track just a single I/O register memory address, or multiple ones of which none are modified by Hatari, only by emulated code, this is not a problem, they get triggered as expected.
    However, if you have a breakpoint that tracks multiple I/O registers where some of them are updated by Hatari, for example to check that other Blitter registers aren't updated while control register indicates Blitter to be active (busy), things don't work as expected!

Breakpoint variables

In addition to loaded symbols, the debugger supports also setting conditional breakpoints on values of some "virtual" variables listed by "variables" (v) command. For example:

  • Aes/Bios/Gemdos/LineA/LineF/Vdi/XbiosOpcode variables can be used to catch AES, BIOS, GEMDOS, Line-A, Line-F, VDI and XBIOS OS-calls. By default they contain the 0xffff value, so to trace e.g. all AES calls (instead of a specific one) one needs to use something like this:
    b  AesOpcode ! AesOpcode  &&  AesOpcode < 0xffff  :trace
    
  • To stop when TOS starts loading next program, set a breakpoint for Pexec(0,...) OS call:
    b GemdosOpcode = 0x4B && OsCallParam = 0x0
    
  • To stop emulation after program has been loaded, but before it runs, set next breakpoint on its first instruction i.e. when program counter matches the TEXT (code) segment address (taken from program basepage):
    b  pc = TEXT :once
    
    Note1: It is better to trigger this breakpoint only once, because if you would leave it on, during (re)boot you would get a warning for every instruction (until TOS sets a valid basepage).
    Note2: you cannot use an address breakpoint for this because variable values are evaluated at run-time only for conditional breakpoints.
  • To view current program DATA and BSS segment contents, use the corresponding variables:
    m  DATA
    m  BSS
    
  • If you want to stop at a specific cycle within a frame (that is, PC relative to the current VBL/HBL in cycles), set breakpoints to specific "VBL", "FrameCycles", "HBL" and "LineCycles" variable values. If you want, for example, to break after 20 HBLs, use:
    b  HBL = "HBL+20"
    
  • To stop on every symbol, break on:
    b  PConSymbol = 1
    

Hint: "info" command "aes", "bios", "gemdos", "vdi" and "xbios" subcommands can be used to list the corresponding OS-call opcodes. For example, to see the GEMDOS opcodes, use:

info gemdos 1

Chaining breakpoints and other actions

As the file pointed by the breakpoint ":file" option (see Breakpoint options) can contain any debugger commands, it can also be used to do automatic "chaining" of debugger and breakpoint actions so that after one breakpoint is hit, another one is set.

For example if you have these input files:

  • "pexec.ini":
    # continue to "program.ini" on Pexec(0, ....)
    b GemdosOpcode = 0x4B && OsCallParam = 0x0 :trace :once :file program.ini
    
  • "program.ini":
    # continue to "trace.ini" when program execution starts
    b pc = TEXT :trace :once :file trace.ini
    
  • "trace.ini":
    # load symbols, trace gemdos & program function calls
    symbols prg
    trace gemdos,cpu_symbols
    # continue to "disable.ini" after 4 VBLs
    b VBL = "VBL+4" :trace :once :file disable.ini
    
  • "disable.ini":
    # stop tracing and remove breakpoints
    trace none
    b all
    

And then start Hatari with the first debugger input file:

hatari --parse pexec.ini /path/to/your/program.tos
  1. "pexec.ini" sets a breakpoint to parse debugger commands from "program.ini" when TOS starts loading the given program (it is first Pexec(0) after boot)
  2. "program.ini" sets a breakpoint to parse debugger commands from "trace.ini" when program code begins executing. These two steps are needed because TEXT variable isn't valid until TOS has booted
  3. "trace.ini" input file loads symbols for the run program, sets Hatari to trace several things (see Tracing section below) in the emulated system for few VBLs until breakpoint runs commands from the "disable.ini" file
  4. "disable.ini" input file will disable tracing and remove all (remaining) breakpoints

Note:

  • Because debugger input files cannot "continue" emulation, ":trace" option needs to be used for the breakpoint(s) if you want emulation to continue after the breakpoint action(s).
  • In simpler breakpoint chain (like above), where new breakpoint just replaces the previous one, ":once" option tells debugger that breakpoint isn't needed after it is hit.

Hint: It is better to test each input file separately before testing the whole chain. Besides the ":file" breakpoint option, you can test these debugger input files also with the debugger "file" command, "file" option for the "lock" command, and with the Hatari "--parse" command line option.

Stepping through code

After analyzing the emulation state and/or setting new breakpoints, you can continue the emulation with the "c" command. You can continue for a given number of CPU instructions (or DSP instructions when "dc" is used), or you can continue forever (until a non-tracing breakpoint triggers) if you omit the instruction count.

If you want to continue just to the next instruction, use "s" (step) command to continue for exactly one instruction, or "n" (next), if you want to skip subroutine + exception calls and DBCC branching backwards (i.e. loops). "ds" and "dn" commands do the same for DSP (except that "dn" doesn't skip loops).

You can also continue with the "n" until instruction of certain type is encountered, by giving it the instruction type:

  • "branch" matches branch instructions:
    CPU: BCC, BRA, DBCC, JMP
    DSP: DO/ENDO JCC, JCLR, JMP, JSET, REP
  • "subcall" matches subroutine calls:
    CPU: BSR, JSR
    DSP: JSCC, JSCLR, JSSET, JSR
  • "subreturn" matches return from subroutine:
    CPU: RTD, RTR, RTS
    DSP: RTS
  • "exception" matches exceptions:
    CPU: BKPT, ILLG, STOP, TRAP, TRAPV
  • "exreturn" matches return from exception:
    CPU: RTE
    DSP: RTI
  • "return" matches both subroutine and exception returns

"subreturn" differs from others by running until current subroutine ends, even if other subroutines are called before that. This is particularly useful for profiling more complex functions; set breakpoint on function start, enable profiling and run until that functions returns, to get its full profile. Example: "n subreturn", or "dn subreturn".

Notes:

  • "next subreturn" works only for real subroutines i.e. code called with BSR/JSR and returning with RT[DRS]. In (GCC) optimized compiled C-code, calls to functions in same C / object file may get inlined or just called with JMP. If that's the case, and making the function non-static doesn't help, move it to another C-file
  • "exreturn" and "return" run only until first instruction of given type is encountered. They cannot implement similar functionality as "subreturn", because it's not possible for "next" command to track CPU / DSP exception invocations, and therefore it cannot do call depth tracking required for this either
  • Tracking CPU CHK2 and FPU FBCC, FDBCC & FTRAPCC exception / branch instructions isn't supported currently

Tracing

(Hatari needs to be built with ENABLE_TRACING define set for tracing to work. By default it is.)

If you want e.g. to continue with real-time disassembling, you can enable it with "trace cpu_disasm" (or "trace dsp_disasm" for DSP) at the debugger prompt before continuing.

Disable tracing with "trace none" when you enter the debugger again. "trace help" (or TAB) can be used to list all the (over 40) supported traceable things, from HW events to OS functions.

At run-time you can enable and disable trace flags individually by starting the trace flags with -/+, like this:

trace gemdos,aes,vdi   # trace just these
trace +xbios,bios      # trace additionally these
trace -aes,-vdi        # remove tracing of these

('+' is optional for addition except at start of the trace flags list.)

Notes:

  • If GEMDOS HD emulation isn't enabled, GEMDOS call tracing needs to be enabled at Hatari command line, it is not possible to enable it after TOS has initialized GEMDOS.
  • AES, BIOS, GEMDOS and XBIOS traces show arguments for (most of) the calls, VDI trace shows only function calls (parsing the arguments would be too complicated).
  • Tracing options can be set even from a program within the emulation, if you enable the (deprecated) "--bios-intercept" option and call XBios 255 from the program with a suitable trace options string.
  • Note that the trace output file can be set only when Hatari starts, it cannot be changed from within the debugger (or emulation).

If there isn't a trace option for something you would like to track, you may be able to use tracing breakpoints, explained above. For example, following tracks Line-A calls:

b  LineAOpcode ! LineAOpcode  &&  LineAOpcode < 0xffff  :trace

Profiling

Profiling tells where the emulated code spends most of its (emulated) time. It can be used to find out where a program is (apparently) stuck, or what are the largest performance bottlenecks for a program.

Collecting the profile data

Profiling is used by first enabling the profiler (use "dp" for DSP):

> profile on
Profiling enabled.

And profiling will start once you continue the emulation:

> c
Returning to emulation...
Allocated CPU profile buffer (27 MB).

When you get back to the debugger, the collected profiling information is processed and a summary of in which parts of memory the execution happened, and how long it took, is shown:

Allocated CPU profile address buffer (57 KB).
ROM TOS (0xE00000-0xE80000):
- active address range:
  0xe00030-0xe611a4
- active instruction addresses:
  14240 (100.00% of all)
- executed instructions:
  4589668 (100.00% of all)
- used cycles:
  56898472 (100.00% of all)
  = 7.09347s
Cartridge ROM (0xFA0000-0xFC0000):
  - no activity

= 7.09347s

(DSP RAM will be shown only as single area in profile information.)

Investigating the profile data

When you are back in debugger, you can inspect the collected profile data:

> h profile
'profile' - profile CPU code
Usage:  profile <subcommand> [parameter]

	Subcommands:
		- on
		- off
		- counts [count]
		- cycles [count]
		- i-misses [count]
		- d-hits [count]
		- symbols [count]
		- addresses [address]
		- callers
		- caches
		- stack
		- stats
		- save <file>
		- loops <file> [CPU limit] [DSP limit]

	'on' ¨ 'off' enable and disable profiling.  Data is collected
	until debugger is entered again at which point you get profiling
	statistics ('stats') summary.

	Then you can ask for list of the PC addresses, sorted either by
	execution 'counts', used 'cycles', i-cache misses or d-cache hits.
	First can be limited just to named addresses with 'symbols'.
	Optional count will limit how many items will be shown.

	'caches' shows histogram of CPU cache usage.

	'addresses' lists the profiled addresses in order, with the
	instructions (currently) residing at them.  By default this
	starts from the first executed instruction, or you can
	specify the starting address.

	'callers' shows (raw) caller information for addresses which
	had symbol(s) associated with them.  'stack' shows the current
	profile stack (this is useful only with :noinit breakpoints).

	Profile address and callers information can be saved with
	'save' command.

	Detailed (spin) looping information can be collected by
	specifying to which file it should be saved, with optional
	limit(s) on how many bytes first and last instruction
	address of the loop can differ (0 = no limit).

For example, to see which memory addresses were executed most and what instructions those have at the end of profiling, use:

> profile counts 8
addr:           count:
0xe06f10        12.11%  555724  move.l    $4ba,d1
0xe06f16        12.11%  555724  cmp.l     d1,d0
0xe06f18        12.11%  555724  bgt.s     $e06f06
0xe06f06        12.11%  555708  move.b    $fffffa01.w,d1
0xe06f0a        12.11%  555708  btst      #5,d1
0xe06f0e        12.11%  555708  beq.s     $e06f1e
0xe00ed8         1.66%  76001   subq.l    #1,d0
0xe00eda         1.66%  76001   bpl.s     $e00ed8
8 CPU addresses listed.

Then, to see what the executed code and its costs look like around top addresses:

> profile addresses 0xe06f04
# disassembly with profile data:
# <instructions percentage>% (<sum of instructions>, <sum of cycles>, <sum of i-cache misses>, <sum of d-cache hits>)

$e06f04 :             bra.s     $e06f10                    0.00% (48, 576, 0, 0)
$e06f06 :             move.b    $fffffa01.w,d1            12.11% (555708, 8902068, 0, 0)
$e06f0a :             btst      #5,d1                     12.11% (555708, 6685268, 0, 0)
$e06f0e :             beq.s     $e06f1e                   12.11% (555708, 4457312, 0, 0)
$e06f10 :             move.l    $4ba,d1                   12.11% (555724, 11125668, 0, 0)
$e06f16 :             cmp.l     d1,d0                     12.11% (555724, 4461708, 0, 0)
$e06f18 :             bgt.s     $e06f06                   12.11% (555724, 4455040, 0, 0)
$e06f1a :             moveq     #1,d0                      0.00% (16, 64, 0, 0)
Disassembled 8 (of active 14240) CPU addresses.

Unlike normal disassembly, "profile addresses" command shows only memory addresses which instructions were executed during profiling. You get cache hit/miss information only when using cycle-accurate 680x0 emulation.

If you have loaded symbol information, symbol names are shown above the corresponding addresses. With the "profile symbols" command you get a list of how many times the code execution passed through the defined symbol addresses.

Profile data accuracy

Profile data accuracy depends on Hatari emulation accuracy. Profile data accuracy, from most to least accurate, with default Hatari emulation options, is following:

  • CPU and DSP instruction counts: accurate.
  • 68000 cycle counts: tested to be accurate to within 1%.
  • MegaSTE 16Mhz 68000 (16KB) cache: hit/miss/cycle counts should also be accurate.
  • DSP cycle counts (and the variance information): should be accurate.
  • 030 (256B+256B) cache: hit/miss counts are assumed to be accurate.
  • 030 cycle counts: can be off by tens of percents.
  • FPU cycle counts: can be off by 2x.
  • TT-RAM access: perf improvement over ST-RAM is not emulated.
  • 040/060 cycle counts: reported, but not accurate, nor really emulated - ignore.

Caller information

If you have loaded symbols (see Debug symbols) before continuing emulation/profiling, additional caller information will be collected for all the code symbol addresses which are called as subroutines. This information includes callstack, call counts, calling instruction type (subroutine call, branch, return etc), and costs for those calls, both including costs for further subroutine calls and without them.

When debugger is re-entered, current callstack is output before profiling information:

> a _P_LineAttack
CPU condition breakpoint 1 with 1 condition(s) added:
        pc = $30f44
$030f44 : 48e7 3820                            movem.l   d2-d4/a2,-(sp)
> c
...
CPU breakpoint condition(s) matched 1 times.
        pc = $30f44
Finalizing costs for 12 non-returned functions:
- 0x32a3c: _P_GunShot (return = 0x32b7e)
- 0x32b18: _A_FireShotgun (return = 0x3229a)
- 0x3223a: _P_SetPsprite (return = 0x32e86)
- 0x32e4e: _P_MovePsprites (return = 0x38070)
- 0x37f44: _P_PlayerThink (return = 0x36ea0)
- 0x36e44: _P_Ticker (return = 0x260e0)
- 0x25dcc: _G_Ticker (return = 0x1e4c6)
- 0x1e29e: _TryRunTics (return = 0x239fa)
- 0x238e8: _D_DoomLoop (return = 0x2556a)
- 0x24d7a: _D_DoomMain (return = 0x44346)
...

("profile stack" command can be used in breakpoints with :noinit option to show backtraces during caller profiling.)

Note: rest of this subsection is about caller information format which is mainly of interest for people writing profiling post-processing tools. Come back here if you think there's some problem with callgraphs produced by those tools.

Other information collected during profiling is shown with following command:

> profile callers
# <callee>: <caller1> = <calls> <types>[ <inclusive/totals>[ <exclusive/totals>]], <caller2> ..., <callee name>
# types: s = subroutine call, r = return from subroutine, e = exception, x = return from exception,
#        b = branch/jump, n = PC moved to next instruction, u = unknown PC change
# totals: calls/instructions/cycles/misses
0xe00030: 0xffffff = 1 e, _main
0xe000fe: 0xe00a0c = 1 b, memdone
0xe0010a: 0xe04e34 = 1 s 1/5/72 1/5/72, _run_cartridge_applications
0xe00144: 0xe04dbe = 1 s 4/118/1512 1/27/444, _init_acia_vecs
0xe001ea: 0xe00ec6 = 1 b, _int_acia
0xe0038c: 0xe04c28 = 1 s 1/191/2052 1/191/2052, _init_exc_vec
0xe003a6: 0xe04c2e = 1 s 1/388/4656 1/388/4656, _init_user_vec
...

For example, if you don't know all the places from which a certain function is called, or in what context a certain interrupt handler can be called during the period you are profiling, profile caller information will tell you:

callee: caller: calls: calltype:
  |       |       |   /
0x379:  0x155 = 144 r, 0x283 = 112 b, 0x2ef = 112 b, 0x378 = 72 s
583236/359708265/1631189180 72/4419020/19123430, dsp_interrupt
           |                       |                 |
    inclusive costs         exclusive costs     callee name
  (of calls from 0x378)

Calltypes:
- b: jump/branch
- n: PC  just moved to next address
- r: subroutine return
- s: subroutine call

(Most "calls" to "dsp_interrupt" were subroutine call returns (=r) to it from address 0x155.)

With the execution counts in normal profiling data, caller information can actually be used to have complete picture of what exactly the code did during profiling. Main/overview work for this analysis is best done automatically, by the profiler data post-processor (documented below).

Caller data accuracy

Everything about profile data accuracy applies also to caller costs, but there are additional things to take into account, mainly because profiler cannot determine when exceptions are being handled:

  • If there are exception(s) during a subroutine call, costs for the exception handling will also be accounted for that subroutine. This shouldn't be a problem unless those costs are very large, i.e. check how much CPU your exception handlers take.
  • Indicated exception handler call type can be incorrect.
  • Profiled code doing return address related stack manipulations confuses call tracking and produces incorrect results (profiler has special code to handle EmuTOS AES switcher because of this). Typically this produces large list of functions that are finalized at profile end, so it should be easy to detect.
  • Complicated recursive calls seem to sometimes cause inclusive costs (ones including costs of further subroutine calls) to be incorrect, e.g. >100%.
  • On DSP, profiler heuristics assume (for speed reasons) that conditional subroutine calls never call the very next instruction (as that would be very bad/inefficient code).

Saving profile data to a file

It is useful to save the profile data to a file:

> profile save program-profile.txt

With the saved profile disassembly (and optional caller information) you can more easily investigate what your program did during profiling, search symbols & addresses in it, and compare the results to profiles you have saved from earlier versions of your code.

You may even create your own post-processing tools for investigating the profiling data more closely, e.g. to find CPU/DSP communication bottlenecks.

Profile data post-processing

Saved profile data can be post-processed with (Python) script installed by Hatari, to:

  • Get lists of functions/symbols with highest costs.
  • Get callgraphs of what functions/symbols cause those costs and what kind of call hierarchy the profiled code has.
  • Export profile data in Valgrind's Callgrind format for viewing it in Kcachegrind GUI.

Providing symbols for the post-processor

When the data is post-processed, you should always provide the post-processor symbols for the profile code! Relying just on the symbol in the profile data can cause costs to be assigned to wrong symbol, if symbol's code wasn't called through symbol's own address, but by jumping inside its code.

If your code is in fixed location, you should tell post-processor to handle symbol addresses as absolute (-a):

$ hatari_profile.py -a etos1024k.sym emutos-profile.txt

Normal programs are relocated and you should instead give the symbols as TEXT (code) section relative ones (-r):

$ hatari_profile.py -r program.sym program-profile.txt

If symbols are included to your binary, first they need to be extracted to the ASCII format understood by the post-processor:

$ gst2ascii -b -a -d program.prg > program.sym

(Options given to "gst2ascii" filter out symbols for other things than what are in the program code section.)

If there are some extra symbols that you don't want to see separately in profiles, because they aren't real functions, but e.g. loop labels, you can either remove them manually from the ASCII *.sym file, or filter them out with grep:

$ gst2ascii -b -a -d program.prg | grep -v -e useless1 -e useless2 > program.sym

For C++ programs, see earlier section(s) on how to best provide symbols for them.

Post-processor provided statistics

Above post-processor examples just parse + verify the given data and produce output like this:

Hatari profile data processor

Parsing TEXT relative symbol address information from program.sym...
[...]
3237 lines with 1550 code symbols/addresses parsed, 0 unknown.

Parsing profile information from program-profile.txt...
[...]
9575 lines processed with 368 functions.

CPU profile information from 'program-profile.txt':
- Hatari v1.6.2+ (May  4 2013), WinUAE CPU core

To get statistics (-s) and list of top (-t) CPU users in profile, add "-st" option:

$ hatari_profile.py -st -r program.sym program-profile.txt
[...]
CPU profile information from 'program-profile.txt':
- Hatari v1.6.2+ (May  4 2013), WinUAE CPU core

Time spent in profile = 34.49539s.

Calls:
- max = 187738, in __toupper at 0x52b88, on line 8286
- 1585901 in total
Executed instructions:
- max = 1900544, in flat_remap_mips+14 at 0x47654, on line 7020
- 64499351 in total
Used cycles:
- max = 15224620, in flat_remap_mips+18 at 0x47658, on line 7022
- 553392132 in total
Instruction cache misses:
- max = 184308, in _BM_T_GetTicks at 0x43b90, on line 4772
- 4941307 in total

Calls:
  11.84%      187698  __toupper
  11.48%      182105  _BM_T_GetTicks
  11.48%      182019  _I_GetTime
[...]
Executed instructions:
  34.83%    22462729  flat_generate_mips
  14.08%     9080215  flat_remap_mips
   8.55%     5515945  render_patch_direct
   5.09%     3283328  _TryRunTics
[...]
Used cycles:
  23.62%   130702768  flat_generate_mips
  12.42%    68735832  flat_remap_mips
   9.77%    54041148  _TryRunTics
   5.80%    32111536  correct_element
[...]
Instruction cache misses:
  37.03%     1829764  _TryRunTics
  11.20%      553314  _BM_T_GetTicks
   9.44%      466319  _NetUpdate
   9.27%      457899  _HGetPacket
[...]

If you want to see also symbol addresses and what is per call cost, add -i option:

$ hatari_profile.py -st -i -r program.sym program-profile.txt
[...]
Executed instructions:
  34.83%    22462729  flat_generate_mips   (0x04778a, 774576 / call)
  14.08%     9080215  flat_remap_mips      (0x047646, 313110 / call)
   8.55%     5515945  render_patch_direct  (0x047382, 29977 / call)
   5.09%     3283328  _TryRunTics          (0x042356, 19660 / call)
[...]
Used cycles:
  23.62%   8.14728s  130702768  flat_generate_mips  (0x04778a, 0.28094s / call)
  12.42%   4.28461s   68735832  flat_remap_mips     (0x047646, 0.14775s / call)
   9.77%   3.36863s   54041148  _TryRunTics         (0x042356, 0.02017s / call)
   5.80%   2.00165s   32111536  correct_element     (0x04a658, 0.00001s / call)
[...]
Instruction cache misses:
  37.03%     1829764  _TryRunTics          (0x042356, 10956 / call)
  11.20%      553314  _BM_T_GetTicks       (0x043b90, 3 / call)
   9.44%      466319  _NetUpdate           (0x041bcc, 5 / call)
   9.27%      457899  _HGetPacket          (0x041754, 5 / call)
[...]

(For cycles the "per call" information is in seconds, not as a cost count.)

If your profile file contains caller information, you should add -p option to see it, as that will also help in detecting symbol issues (see Interpreting the numbers):

$ hatari_profile.py -st -p -r program.sym program-profile.txt
[...]
9575 lines processed with 368 functions.
[...]
Of all 1570498 switches, ignored 581 for type(s) ['r', 'u', 'x'].

CPU profile information from 'badmood-level-load-CPU.txt':
- Hatari v1.6.2+ (May  4 2013), WinUAE CPU core
[...]
Calls:
  11.84%  11.84%      187698    187698  __toupper
  11.48%  11.48%      182105    182105  _BM_T_GetTicks
  11.48%  22.95%      182019    364038  _I_GetTime
[...]
Executed instructions:
  34.83%  34.86%  34.86%    22462729  22484024  22484024  flat_generate_mips
  14.08%  14.10%  14.10%     9080215   9091270   9091676  flat_remap_mips
   8.55%                     5515945                      render_patch_direct
   5.09%   5.11%  94.96%     3283328   3294022  61247717  _TryRunTics
[...]
Used cycles:
  23.62%  23.69%  23.69%   130702768 131100604 131100604  flat_generate_mips
  12.42%  12.46%  12.46%    68735832  68928816  68930904  flat_remap_mips
   9.77%   9.80%  95.66%    54041148  54238744 529368824  _TryRunTics
   5.80%   5.82%   5.82%    32111536  32193664  32193664  correct_element
[...]
Instruction cache misses:
  37.03%  37.14%  98.57%     1829764   1835261   4870573  _TryRunTics
  11.20%  11.24%  11.24%      553314    555191    555191  _BM_T_GetTicks
   9.44%   9.49%  29.13%      466319    468782   1439340  _NetUpdate
   9.27%   9.29%   9.37%      457899    459197    463217  _HGetPacket
[...]

Now there's a message telling that some of the calls were ignored because according to their "call type", they were actually returns from exceptions, not real calls (this is mainly important for callgraph generation, discussed below).

Interpreting the results

In addition to accuracy issues mentioned in previous Profiling sections, function/symbol level costs have gotchas of their own.

The first cost percentage and count column are sums for costs accounted for all the addresses that were in profile data file between the indicated symbol's address and the address of the next symbol (= "between-symbols" cost).

NOTE: If your symbols file does not contain addresses for all the relevant symbols, results from this can be misleading; instruction costs get assigned to whatever symbol's address happened to precede those instructions. And you do not see which caller is causing it from caller info or callgraphs either, as entry point for that time sink missing a symbol means calls to it had not been tracked by profiler...

The next two percentages (and cost counts) are total cost of calls to given subroutine, based on profiler runtime branch tracking (see caller information documented above). First value is ("exclusive") cost for just that subroutine (from its entry, until execution returns to where it was called from), without costs for branches to further subroutines. Latter value is ("inclusive") cost covering also costs for all the subroutines it calls.

Reasons why "between-symbols" costs, and subroutine call costs can differ, are following:

  • Subroutine terminates before next symbol address: "exclusive" cost is smaller than "between-symbols" cost because of missing symbol information (these are indicated with '*' in statistics).
  • Subroutine is called more through jumps/branches than through subroutine calls (JSR/BSR): "inclusive" call count may be smaller than "between-symbols" call count which includes costs also for function entries through branches/jumps.
  • Subroutine jumps/branches to another function instead of using subroutine call, or function contains additional (non-function) labels: "exclusive" cost is larger than "between-symbols" cost.
  • Exception happening during subroutine call: "exclusive" cost is (slightly) larger than "between-symbols" cost.

In the first case, you should check saved profile disassembly to find out whether there are missing symbols for executed function entry points. You can notice function entry points as address gaps and/or instructions retrieving arguments from stack. Exit points can be seen from RTS instructions.

Second case can also be seen from the profile disassembly. Call count is same as count for how many times first instruction is executed (worst case: large loop on subroutine's first instruction).

While subroutine costs should be more accurate and relevant, due to code optimizations, many of the functions are not called as subroutines (on m68k, using JSR/BSR), but just jumped or branched to. Because of this, it is useful to compare both subroutine and "between-symbols" costs. One should be able to see from the profile disassembly which of the above cases is cause for the discrepancy in the values.

NOTE: Before starting to do any serious optimizations based on profile information, you should always verify from profile disassembly where exactly the costs are in a function, to make sure your optimization efforts can actually have an impact on performance.

Generating and viewing callgraphs

Callgraphs require that saved profile data contains caller function address information, i.e. symbols for the code should be loaded before starting profiling it (see loading symbol data).

Separate callgraphs will be created for each of the costs (0=calls, 1=instructions, 2=cycles) with the -g option:

$ hatari_profile.py -p -g -r program.sym program-profile.txt
[...]
Generating 'program-profile-0.dot' DOT callgraph file...

Generating 'program-profile-1.dot' DOT callgraph file...

Generating 'program-profile-2.dot' DOT callgraph file...
[...]

Callgraphs are saved in GraphViz "dot" format. Dot files can be viewed:

  • With "dotty" program included with GraphViz
  • With XDot Python GUI (best option on Linux), or some platform specific viewer
  • By converting dot file to PostScript or SVG format before viewing it with viewers for those:
    $ dot -Tsvg program-profile-1.dot > program-profile-1.svg
    
    (problem with most PS/PDF and SVG viewers is that either they don't allow zooming large callgraphs enough or they use huge amounts of memory and get very slow)

Produced callgraph will look like this:

Interpreting the callgraph:

  • Diamond shaped nodes are symbols called as subroutines. Values listed in them are subroutine call costs; inclusive (total) cost with exclusive (own) cost in parenthesis, followed by inclusive cost count. Exclusive cost is shown only if it differs from inclusive one.
  • Ellipse shaped nodes are for other symbols (functions called using jumps/branches, loop labels etc). Values listed in them are between-symbols costs, i.e. normally they are included to inclusive (total) costs shown in subroutine call node somewhere higher in call hierarchy.
  • Nodes which exclusive (own) or between-symbols costs exceed default or explicitly given threshold value, have gray background.
  • Both nodes, which inclusive or between-symbols cost exceeds the threshold value, and the arrows to & from them, are marked red.
  • Arrow types indicate call types; normal arrows subroutine calls, circles branches/jumps, backarrows returns. Exception calls and returns are indicated with dashed lines, unknown calls with dotted lines.
  • Arrow text tells from which address (within the caller) the call originated. If symbol had multiple callers, text includes count of calls from that particular address, and its percentage is of all calls done to that symbol.

Making large callgraphs readable

If profile is for larger and more varied amount of code (e.g. program startup), the resulting callgraph can be so huge that it not really readable anymore.

If your code has interrupt handlers, they can get called at any point, which can show in callgraph as "explicit" calls from the interrupted functions. To get rid of such incorrect calls, give interrupt handler names to --ignore-to option:

$ hatari_profile.py -p -g --ignore-to handler1,handler2 -r program.sym program-profile.txt

In large callgraph most of the functions aren't really interesting, because their contribution to the cost is insignificant. You can remove large number of them with --no-leafs and --no-intermediate options, those options act only on nodes which costs are below given threshold. Leaf nodes are ones which don't have any parents and/or children. Intermediate ones have only single parent and children (node calling itself is not taken into account).

Threshold for this is given with the --limit (-l) option. With that it typically makes also sense to change the node emphasis threshold with --emph-limit (-e) option:

$ hatari_profile.py -p -g -l 0.5 -e 2.0 -r program.sym program-profile.txt

If you are not interested in from how many different addresses a given function calls another function, use --compact option. If you still see multiple calls between two nodes with it, the reason is that they happened through different call paths which were removed from the callgraph after --compact option was applied:

$ hatari_profile.py -p -g -l 1.0 -e 2.0 --no-leafs --no-intermediate --compact -r program.sym program-profile.txt

If even this doesn't help, you can remove all nodes below the given cost threshold limit with --no-limited option, but this often doesn't leave much of a call hierarchy. Instead you may consider removing all nodes except for subroutine call ones, with the --only-subroutines option.

If you have trouble locating nodes you are specially interested about, you can either color them differently with the --mark option, or exclude everything else from the callgraph except those nodes and their immediate callers & callees, with the --only option:

$ hatari_profile.py -p -g --only func1,func2 -r program.sym program-profile.txt

Last option for reading the callgraph is using -k option to export the data for use in (Linux) Kcachegrind UI. Kcachegrind generates callgraphs on the fly, and just for the area around the function you selected, so navigating in callgraph may be easier. It also shows the related profile disassembly, which can make verifying matters easier:

$ hatari_profile.py -p -k -r program.sym program-profile.txt
[...]
Generating callgrind file 'program-profile.cg'...
[...]
$ kcachegrind program-profile.cg
Kcachegrind screenshot

Usage examples

Here's a list of some common debugging tasks and how to do them with the Hatari debugger:

Stopping on program startup and examining its data
Please see Breakpoint variables and Inspecting emulation state sections.
Tracing specific things in the system
To trace e.g. all GEMDOS calls and I/O operations, use:
trace  gemdos,io_all
Please see Tracing section for more information on tracing, what's possible with it and what are its limitations.
Stopping when certain PC address is passed Nth time
To stop e.g. after function/subroutine at $12345 is called for the 6th time:
a  $12345 :6
Stopping when specific exception happens
If one wants a specific breakpoint to trigger on a specific exception, one can check when its handler address is called by the CPU. At the start of memory is the CPU exception table for exception vectors. So, to stop e.g. at bus error with some extra information, one can use following:
history  on
b  pc=($8)
After bus error invokes debugger, 'history' command can then be used to see (executed memory addresses with their current) instructions leading to the error. The most interesting vector addresses are: $8 (Bus error), $C (Address error), $10 (Illegal instruction), $14 (Division by zero).
NOTE: Normally, to invoke debugger for larger set of exceptions, one would use Hatari's "--debug-except" option to specify on which exceptions debugger is invoked, and "setopt -D" to enable that on run-time.
Stopping when register has a specific value
To stop when e.g. D1 register contains value 5, set a breakpoint on:
b  d1 = 5
Stopping when a register value changes
To stop when e.g. D1 register value changes, set a breakpoint on:
b  d1 ! d1
Stopping when part of register value changes
To stop when e.g. D1 register lowest byte changes, set a breakpoint on masked lowest 8 bits:
b  d1 & 0xff ! d1 & 0xff
Stopping when register value is within some range
To stop when e.g. D1 register value is within range of 10-30, set a breakpoint on:
b  d1 > 9  &&  d1 < 31
Stopping when memory location has a specific value
To stop when e.g. bit 1 of the Video Shifter Sync Mode byte at I/O address $ff820a is set i.e. video frequency is 60Hz, set a breakpoint on:
b  ($ff820a).b & 2 = 2
Stopping when a memory value changes
To stop when above bit changes, set a breakpoint on its value being different from the current value ('!' compares for inequality):
b  ($ff820a).b & 2 ! ($ff820a).b & 2
Tracing all changes in specific memory location
To see the new values and continue without stopping, add the ":trace" breakpoint option:
b  ($ff820a).b & 2 ! ($ff820a).b & 2  :trace
Finding specific data from memory
Find "ETOS" character string matches from ROM:
find c 0xe00000 E T O S
Find all potential (word aligned) RTS instructions from RAM:
find w 0x0 0x4e75
Values in different number base
To see values in different number bases, use "evaluate" command. It can be used also with more complex expressions:
> e ($ff8802).b & 0x7
  value at ($ff8802).b = $27
= %111 (bin), #7 (dec), $7 (hex)
Viewing OS structure and IO register values
To see e.g. basepage for currently running program:
info  basepage
To see e.g. all Falcon Videl register values, use:
info  videl
Showing OS attribute information for specific OS calls
To see e.g. VDI attributes for all v_gtext VDI calls:
b  VdiOpcode = $8  :quiet :info vdi
Stopping at specific screen position
To stop e.g. when VBL is 100, HBL is 40 and line cycles is 5, use the corresponding debugger variables:
b  VBL = 100  &&  HBL = 40  &&  LineCycles = 5
Stopping after value increases/decreases by certain amount
To stop e.g. after D0 value has increased by 10, set breakpoint on:
b  d0 = "d0 + 10"
Examining specific system call return value
To check e.g. what's the Fopen() GEMDOS call return value, check with "info gemdos 1" its opcode, set a breakpoint for that and step to next (n) instruction from the trap call when breakpoint is hit. GEMDOS call return value is then in register D0:
> trace  gemdos
> b  GemdosOpcode = $3D
> c
[...continue until breakpoint...]
1. CPU breakpoint condition(s) matched 1 times.
        GemdosOpcode = $3D
> n
GEMDOS 0x3D Fopen("TEST.TXT", read-only)
> e  d0
= %1000000 (bin), #64 (dec), $40 (hex)
Seeing code leading to a breakpoint
To see CPU instructions executed before debugger was entered, you need to enable history tracking before it. Whenever debugger is entered, you can then request given number (here 16) of past PC addresses and their (current) instructions to be shown:
history  cpu
c
[breakpoint is hit and debugger entered]
history  16
Getting instruction execution history for every breakpoint
To see last 16 instructions for both CPU and DSP whenever (a normal or tracing) breakpoint is hit:
history  on
lock  history 16
c
Single stepping so that new register values are shown after each step
lock  registers
s
[new register values]
s
[new register values]
...
Showing current stack contents
To see first 64 bytes on top of the stack, use:
m  "a7-64"-a7
Seeing specific information each time debugger is entered
To see above information whenever some breakpoint is hit, you enter debugger manually etc, write that command to e.g. stack.ini file and then use:
lock  file stack.ini
Please see also Chaining breakpoints section for more examples on what you can do with the debugger input files.
Adding cycle information to disassembly
Profiling collects cycle usage information for all executed instructions:
profile  on
c
[after a while, use AltGr+Pause to get back to debugger]
d
[or if you want to see just the executed instructions]
profile addresses
Please see Profiling section for more info.
Finding where a program or the OS is stuck
Profiling tells from which addresses CPU is executing the instructions:
profile  on
c
[after a while, use AltGr+Pause to get back to debugger]
profile  addresses
Please see Profiling section for more info.
Profiling specific function and everything it calls
Set breakpoint for the function entrypoint and when it is hit, enable profiling and continue to the end of the function:
address myfunction
c
[back in debugger at myfunction]
profile on
next subreturn
[back in debugger when function returns]
Please see Profiling section on and how to save and analyze profiling information after that, and Stepping section for some "next subreturn" command limitations.
Seeing program callstack when breakpoint is hit
Profiler caller data includes callstack information (with some limitations).
Seeing call backtraces whenever given function is called
Enable profiling, load symbols for the program and set breakpoint for the function you are interested about, in the following way:
profile  on
symbols  prg
b  pc = _my_function  :quiet :noinit :file showstack.ini
I.e. whenever 'my_function' address is called, quietly trigger a breakpoint without resetting profiling (callstack) information and run debugger command(s) from the 'showstack.ini' debugger script file, which contains following command:
profile  stack
Seeing how program functions/symbols call each other
Profile data post-processing can provide execution callgraphs.

Hint: for most of the above commands, one just needs to prefix them with "d" (or "dsp" when using full command names) to do similar operation on the DSP.

Command line editing and libreadline

Hatari debugger is much nicer to use with the command line history, editing and especially the completion support for the command, command argument and symbol names. Like many other programs (Bash etc), debugger uses libreadline to handle this.

Hatari building

If you are building Hatari yourself, please make sure that you have the GNU readline development files installed (on Debian / Ubuntu these come from the "libreadline*-dev" packages). Otherwise the name completion and other features don't get enabled when you configure Hatari.

Keyboard shortcuts

In addition to the normal line editing keys, libreadline supports several keyboard shortcuts ('^' indicates Control-key):

  • Cursor movement (sometimes preferable over using arrow keys):
    • ^B - backward
    • ^F - forward
    • ^arrow - move by word
    • ^A - start of line
    • ^E - end of line
    • ^P - previous line
    • ^N - next line
  • Cut / paste (relative to cursor position):
    • ^W - cut previous word (space separated)
    • ^K - cut ("kill") to end of line
    • ^U - cut to start of line
    • ^Y - paste ("yank") deleted content
  • Other text changes:
    • ^D - delete character
    • ^T - transpose (switch) last 2 characters
    • ^_ - undo last change
  • Miscanellous:
    • ^R - history ("reverse") search
    • ^L - clear screen and redraw line

(Above shortcuts are inherited from Emacs text editor, and work also in all shells using readline. See "man readline" on how to configure them.)

hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/doxygen/000077500000000000000000000000001504763705000227635ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/doxygen/Doxyfile000066400000000000000000000207641504763705000245020ustar00rootroot00000000000000# Doxyfile 1.5.1-p1 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- PROJECT_NAME = Hatari PROJECT_NUMBER = 2.6.1 OUTPUT_DIRECTORY = . CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO BUILTIN_STL_SUPPORT = NO DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = NO HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = ../../src FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = NO REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = NO IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4 EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = MAX_DOT_GRAPH_DEPTH = 1000 DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = NO hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/emutos.txt000066400000000000000000001711011504763705000233640ustar00rootroot00000000000000 Contents: - Hatari and EmuTOS - What is EmuTOS - EmuTOS usage - EmuTOS compatibility notes - Debugging issues with EmuTOS - EmuTOS debug symbols - EmuTOS debug output - Other EmuTOS debug output options - Debugging OS calls, Line-A usage and panics - Debugging freezes - EmuTOS free/shareware compatibility lists - Working Atari ST demos and games - Color ST demos and games - GEM / monochrome ST demos and games - Broken Atari ST demos and games - Broken (floppy) demo classics - ST games with broken input handling - Atari ST applications (including commercial) - Atari STE programs - Working STE programs - Non-working STE programs - Atari TT programs - Falcon programs - Falcon demos - Falcon games - Falcon applications Hatari and EmuTOS ================= To run the emulator, a TOS ROM image is needed. EmuTOS, a free implementation of TOS, is shipped with the official binary releases of Hatari. This "tos.img" ROM image is identical to the official binary package of EmuTOS, which is available at the SourceForge website of EmuTOS. For downloading other versions or the source code of EmuTOS, please visit the EmuTOS website. Starting from Hatari 2.5.0, the "tos.img" file included with the given Hatari release is the etos1024k.img[1] from a preceding EmuTOS release: - Hatari v2.6.0, v2.6.1: https://sourceforge.net/projects/emutos/files/emutos/1.4/ - Hatari v2.5.0: https://sourceforge.net/projects/emutos/files/emutos/1.3/ Before Hatari 2.5.0, the "tos.img" file included with the given Hatari release was the etos512k.img[1] from a preceding EmuTOS release: - Hatari v2.4.0, v2.4.1: https://sourceforge.net/projects/emutos/files/emutos/1.1.1/ - Hatari v2.3.0, v2.3.1: https://sourceforge.net/projects/emutos/files/emutos/1.0/ - Hatari v2.2.0, v2.2.1: https://sourceforge.net/projects/emutos/files/emutos/0.9.10/ - Hatari v2.1.0: https://sourceforge.net/projects/emutos/files/emutos/0.9.9.1/ - Hatari v2.0.0: http://sourceforge.net/projects/emutos/files/emutos/0.9.6/ - Hatari v1.9.0: http://sourceforge.net/projects/emutos/files/emutos/0.9.4/ - Hatari v1.8.0: http://sourceforge.net/projects/emutos/files/emutos/0.9.3/ - Hatari v1.7.0: http://sourceforge.net/projects/emutos/files/emutos/0.9.0/ - Hatari v1.6.2: http://sourceforge.net/projects/emutos/files/emutos/0.8.7/ - Hatari v1.6.0: http://sourceforge.net/projects/emutos/files/emutos/0.8.6/ - Hatari v1.4.0, v1.5.0: http://sourceforge.net/projects/emutos/files/emutos/0.8.5/ - Hatari v1.1.0, v1.2.0, v1.3.0, v1.3.1: http://sourceforge.net/projects/emutos/files/emutos/0.8.4/ - Hatari v1.0.0, v1.0.1: http://sourceforge.net/projects/emutos/files/emutos/0.8.3/ - Hatari v0.95: http://sourceforge.net/projects/emutos/files/emutos/0.8.2/ [1] 1024k EmuTOS image is the "all languages" version, intended mainly for TT & Falcon. Older 512k EmuTOS variants support also all the EmuTOS languages, but nowadays only the new EmuTOS 1024k variant has space for all of them. While 192k and 256k EmuTOS variants work better for some ST and STE programs, only the larger image variants contain support for all the hardware emulated by Hatari. Notes for older EmuTOS versions: * EmuTOS <= v0.9.6: EmuTOS changed its INF file format in v0.9.7. Hatari's Atari program autostarting and TOS resolution setting functionality supports only EmuTOS >= v0.9.7 INF file format * EmuTOS <= v0.8.5 with Hatari <= v2.0: In order to work with Hatari, EmuTOS v0.8.5 and earlier require the FastBoot option to be enabled. This is used to skip some memory tests (that Hatari v2.0 and earlier do not support) when EmuTOS tries to determine the available RAM size. What is EmuTOS ============== EmuTOS is a single-user single-tasking operating system for the 32-bit Atari computers and emulators. It can be used as a replacement for the TOS images needed for using the emulators, and its latest versions support also wide variety of real hardware. All the source code is open and free, licensed under the GNU General Public License (GPL). A copy of the GNU GPL can be found in the gpl.txt file. EmuTOS project has home page at SourceForge: https://emutos.sourceforge.io/ With sources at GitHub: https://github.com/emutos/emutos EmuTOS usage ============ There are various reasons for using EmuTOS instead of the original TOS ROM images: * Legal. Users who don't have Atari HW or other legal means to obtain the original Atari TOS ROMs (for all of the Hatari supported Atari versions: ST/STE/TT/Falcon), but would like to try out Hatari and see what kind of software the Atari machines had. EmuTOS gives them a legal way to test Hatari with free demos, games etc. * Performance. EmuTOS boots faster to desktop than Atari TOS versions. * Convenience. EmuTOS does not need a HD driver to access hard disk images, and same 1024k (or 512k) image supports all the hardware that different Atari TOS versions support. * Continuity. Users who want to continue using Atari compatible operating systems and need an open source replacement of TOS for modifications. Beside EmuTOS, they typically also use FreeMiNT, XaAES and fVDI as operating system replacements/improvements. This includes users of Atari FPGA clones, such as MiST & MiSTer: https://github.com/mist-devel/mist-board/wiki https://github.com/MiSTer-devel/Main_MiSTer/wiki users of new "Atari" machines, like Atari ColdFire Project: http://acp.atari.org/ and even Amiga (WinUAE) emulator and real Amiga hardware. EmuTOS compatibility notes ========================== These comments apply to EmuTOS v1.x. Many commercial games (e.g. Bombjack, Nebulus, Oids, Super Hang-On, Turrican, Xenon...) and almost all ST/STe/TT applications work fine with EmuTOS. Because (even 192k variant of) EmuTOS uses a bit more memory than normal TOS, badly written (typically floppy-only) games & demos may blindly overwrite EmuTOS memory. They also often rely on (undocumented) TOS internal variables & functions being at specific memory locations, and will crash when they call these bogus addresses, use bogus values, or call OS functions which internal state they had overwritten with garbage. For example (older) Omikron Basic and many programs done with it have this problem. As a special case of this problem, input (e.g. joystick) does not work in unpatched STOS Basic games as they access it through other means than XBIOS function Kbdvbase() or by hooking into the IKBD interrupt vector. They use unofficial system variables which location depends on the TOS version. However, ST/STE games that work from HD and/or have been fixed to work with newer TOS versions, should usually be fine. Additionally, very old EmuTOS versions (up to v0.9.1) lacked support for the (deprecated) line-A graphics functions which are used in many free/shareware games (ones done with GFA basic etc). This isn't an issue with newer EmuTOS versions. Lists of known issues and incompatible applications are listed in incompatible.txt file coming with EmuTOS: https://raw.githubusercontent.com/emutos/emutos/master/doc/incompatible.txt Debugging issues with EmuTOS ============================ EmuTOS debug symbols -------------------- After loading EmuTOS debug symbols from the file coming with the 1024k (or 512k) EmuTOS image, using debugger "symbols etos1024k.sym" command, you can ask debugger to "trace cpu_symbols", to get trace of all the (non-static) function calls EmuTOS does. For smaller EmuTOS variants, there's a script[1] you can use to convert the emutos2.map file (produced during EmuTOS build) to a format you can load to Hatari debugger with its "symbols" command[2]. [1] http://sourceforge.net/mailarchive/message.php?msg_id=29381777 [2] https://www.hatari-emu.org/doc/debugger.html#Debug_symbols After loading the symbols to the Hatari debugger, disassembling the problematic address should provide more clues for the problem. Or you can just look up the program counter value given in the EmuTOS panic message from the map file. EmuTOS debug output ------------------- Start Hatari with "--natfeats on --trace os_base" options to get EmuTOS debug output (with 512k or 1024k images), and to redirect EmuTOS panic output and other console messages to Hatari's console. For extra debug output and to add your own debug statements, you need to uncomment following line from suitable EmuTOS source file(s): #define ENABLE_KDEBUG 1 And recompile EmuTOS image. Note that you can't do that for whole EmuTOS as resulting image would be too large. Other EmuTOS debug output options --------------------------------- There are debug output options for different HW in EmuTOS "config.h" header file, but they aren't necessary with NatFeats and console output redirection. If you want to test them anyway, below is listed each of them with Hatari command line options to save their output (to "debug.txt"), and shortcomings of each option are following: * MIDI_DEBUG_PRINT: "--midi-in /dev/null --midi-out debug.txt" - Hatari needs to be compiled with PortMidi support disabled, as PortMidi interprets only real MIDI events * RS232_DEBUG_PRINT: "--rs232-in /dev/null --rs232-out debug.txt" * SCC_DEBUG_PRINT: "--scc-b-out debug.txt" - SCC port is available only on MegaSTE/TT/Falcon Notes: * Don't use Hatari stdout/stderr ("/dev/stdout", "/dev/stderr") as debug output files, unless you absolutely need to get ordering right between the extra debug messages and other output, as that results in messed up console and output from Hatari * Both input & output files need to be specified for RS232 & MIDI, otherwise Hatari does not enable them Debugging OS calls, Line-A usage and panics ------------------------------------------- Ask Hatari debugger to stop when the program is started: ----------- > b pc=TEXT CPU condition breakpoint 1 with 1 condition(s) added: pc = TEXT > c Returning to emulation... ----------- (In above example '>' indicates what you type to debugger.) Then when the debugger is invoked at program startup, you can ask it to: - collect CPU instruction history - break on exceptions that trigger the panic - load symbols + profile what happens, as these will provide EmuTOS callstack when debugger is entered - trace all OS calls and line-A opcodes leading to the panic with: ----------- > history on > setopt -D > symbols etos1024k.sym > profile on > trace os_all > b LineAOpcode ! LineAOpcode && lineAopcode ! 0xffff :trace > c Returning to emulation... ----------- With above, when the exception causing the panic triggers the debugger, you see EmuTOS callstack and the OS calls & line-A opcodes leading to it, and have a panic message on console from which you can copy & paste relevant parts. You can then ask what the register values are at that point and what are the CPU instructions leading to it with the "r" and "history" commands. Memory state and addresses accessed by the instructions can be inspected with the "m" command. For more info on Hatari debugger usage, see the Hatari manual section on it: https://www.hatari-emu.org/doc/debugger.html Debugging freezes ----------------- Hatari profiling support can be used to debug OS freezes. If EmuTOS gets stuck, just profile it for a while and then ask debugger for a profile that tells where it's looping. Then disassemble that part with EmuTOS symbols directly in Hatari. EmuTOS free/shareware compatibility lists ========================================= The compatibility lists below cater mainly for users who don't have Atari HW and therefore won't have (legal) version of commercial Atari software and most likely lack motivation to investigate applications very deeply (Linux offers better alternatives nowadays for most of that). I.e. the lists skip (most) commercial software and concentrate on EmuTOS compatibility of freely available games and demos (including commercial games that have been released later on for free distribution). Games that are considered good quality & fun, are marked with "*". All the listed demos are quite OK. Most demos can be downloaded from: http://pouet.net/ http://demozoo.org/ Many of the other programs can be found from Atari Mania: http://www.atarimania.com/atari-st-tt-falcon.html And the remaining Atari FTP sites. Below are first listed ST demos, games, graphics & MIDI applications, then STE demos, games and music applications, then TT programs and last Falcon demos, games and applications. Working Atari ST demos and games -------------------------------- For STs, 192k EmuTOS variant is best as it leaves most RAM free. If ST has more memory, *and* OS speed is important (i.e. app uses OS drawing functions), 512k (or 1024k) variant could be used instead, as that is compiled with higher optimizations than 192k one. Some old, floppy only games expect certain memory areas occupied by EmuTOS, to be over-writable without issues. These need 192k image. Some of them can be gotten to work by using EmuTOS cartridge image (it uses less memory than normal EmuTOS), like this: hatari --tos etos192uk.img --cartridge etoscart.img floppy.img Mostly the non-working games "Panic" EmuTOS during their startup, but it can happen also later on. Color ST demos and games ........................ Color demos: - 0 Pixels 0 Regrets (0-bitplane by DHS) - 2011 (by Positivity & Sector One, 4kB intro) - 300 days to go (by SMFX) - 3D screen 3.5k (by Overlanders) - 44 (by Avena, 4KB intro) - 4getful (by gwEm, 4KB intro) - 4kker (by Checkpoint, 4KB intro) - A Series of Raster Effects (0-bitplane by SMFX) - Across the Borders (by Effect) - Algorift 256b (with MIDI output, by Marquee Design) - Ambience (by Chaos) - Anomaly (by MJJ Prod) - April Fools (by DNT Crew) - Arsenite (by Dekadence) - Atom (128b by Acid Team) - Audiopacdemo (by X-Troll) - Bad Remix (by Dekadence) - Beast Scroll (by Joefish) - Beefect (by Effect) - Beyond Imagination (by NLC), in GFA basic - Bp0d (0-bitplane by Extream) - Bread and Circuses (by Effect) - Breath (by Mystic Bytes) - Calimer-o-demo (by Oxygene) - Charts Compilation (by Next) - Closure (by Sync) - Coast II Coast (by Sector One, 4kB intro) - Coolism (music disk, by Effect) - Cracking On (by Exocet & Tom & Adam Gilmore) - Crisis (by Joska) - Cross the Deserts (by Chronicle) - one screen gfx is partly corrupted - Crowd Teaser (by SMFX) - Cuddly Demos (by CareBears) - Darktrip (by Legend) - does not work properly with >2MB of RAM - Dark Side of the Spoon (by ULM) - Darwin's Dilemma (by Cerebral Vortex, DHS and SMFX) - DataSTorm Invite (by Genesis Project) - Death of the Clock Cycles (by Aggression) - Definitely Number One (by Spice Boys) - Delusion (by MJJ Prod) - Dimensio (by Condemned) - Done & Dusted (by Genesis Project) - DragonST (256-Bytro by atariBDSM) - Eat my bollocks (by Equinox) - Execution (by SMFX) - Fantasia (by Dune & Sector One) - FF8240.w (0-bitplane by Defence Force) - Flashback (by Carebears) - Flexiscroll 2021 (64k by Effect) - Flipo (by Oxygene) - Flux-AI slides (Spec512/50Hz slideshow by Anima) - Forever (great demo by 505 & Tom & modMate) - Frenchies (by Frenchies, 96KB intro) - Fruit is Great! (256-Bytro 50Hz overscan by Effect) - Fuckings To Nightbours (by Donald Trump Cracking Crew) - Glokzilla (by MJJ Prod) - Gnoël (by Flush) - Going (by Spice Boys) - Grand Theft Auto VI Cracktro (by Vectronix) - Grafik -und Sounddemo (by Eckhard Kruse) - Hallucinations (by Reservoir Gods) - Harstad Vito (by Dekadence) - Helvetin Hyttysiä (by Spice Boys) - Hiatus (by SMFX) - Hatari 2.2 intro (by Mr.Styckx) - If pigs could fly (by Syndicate) - Ika I Compofylla (by Newline) - Illusion (by Dune) - In Waves (by Exocet & Tomchi & Tom) - Inbjudan (by Helsingfors Svenska Demoscensklubben rf) - Infinity/Madstars (by Holocaust) - Just Jack (by Joker) - Let Go (by Dekadence) - Lichthaus (by Newline) - Lostmax (by Psycho Hacking Force) - Lots of Dots (by Acid Maker & Friends) - Make More Short Demos (by Dekadence) - MMoDA (Mini Museum of Digitized Arts) exhibitions (by Pepe) - MonaST (by GGN/tln, 256-Bytro) - Motus (by SMFX) - Music Box 128k (by Michael Lunn, in STOS) - My Galaxy (by Dune & Sector One & MJJ Prod) - Music Disc 1 (by G. Gaubatz) - New Year intro 1990 (by The Replicants) - No Cooper (by 1984) - No Grtz To SPKR (by Spice Boys) - No more froggies (by Sector1) - NYAN zeST (by DHS) - O-demo (by Oxygene) - Octogenarian (by Vlad) - Odd Stuff (by Dune & Sector One) - Oh no!! more froggies (by Sector One) - does not work with >2MB of RAM - Old (by Tom & Dubmood) - Oldskool Rebirth (by MJJ Prod) - Omega boot (bootsector by Omega) - Ooh Crikey (by Lost Boys) - OVeRclocked Remix (by Overlanders) - Outline 2008 invite (by DHS) - Outline 2010 invite (by Checkpoint) - OutRun Music demo (by FedePede) - Overdose (by Aggression) - Overdrive (by Phalanx) - Pandemonium (by Chaos) - Panic (Paolo Simoes) - Paradise (by Dune Design) - Pause (by Cerebral Vortex / Dekadence / SMFX) - Phaleon Gigademo (by Next) - select "fast loader" - PONG_CRK (fake cracktro by Overlanders) - Posh (by Checkpoint) - POV disk 165 (except for EiL 99 invite) - Princess of Doom (by Brainheadsinteractive) - Prix (by Ephidrena) - Punish your machine (by Delta Force) - Recycles (by SMFX) - Remote Entry #2 - 11110 Edition (by Spice Boys) - Remote Entry #2 - Delta Quadrant Edition (by Spice Boys) - Renegade Breakdown - Sommarhack 2021 invitro (by Spkr / SMFX) - Replay (by SMFX, Sommarhack 2023 winner) - RGB plasma (by Omega) - Santa Claus Barbie Was Drunk As A Cochon (by MJJ Prod) - Save The Earth (by Defence Force) - Second Reality 2013 (by Checkpoint) - Serial (by Dekadence) - Shiraz 256b (with MIDI output, by Marquee Design) - SillyVenture 2k10 & 2k11 (invitros by Mystic Bytes & Aggression) - SillyVenture 2020 (invitro by Agenda, Hemoroids & Mystic Bytes) - Silly Invaders (158-Bytro by Sedma) - Smag-FX 3 (by SMFX) - SNDH v4.8 update demo (PHF & KUA & DHS) - Somebody Has Owned Someone (by Spice Boys) - Sommar TTRAK (TTRAK v1.00 intro) - Sommarhack 1991 Best Of (by Pepe) - Glitches are intentional, not EmuTOS issue - Sommarhack 2011 invite (by DHS) - Sommarhack 2022 reminder (by DHS) - Sommarhack 2024 reminder (by DHS & others)* - Soundtracker (by Equinox) - Sowatt sprite record (rec16e, by Oxygene) - ST NICCC 2000 (by Oxygene) - ST NICCC 2015 (by Checkpoint) - ST soccer (intro by Exceptions) - Starfield Humppa (by Dekadence) - STay4Evr (by Cream) - Stone tower (by FUN) - STresSTeST (by Cream) - Superstar! (intro by Positivity/Dma-Sc/Ukko) - Suretrip 49% (by Checkpoint) - Sweety (by DHS) - Synergy megademo (by Synergy) - Syntax Terror (by Delta Force) - Temptation aka Project "A" (by NoExtra) - The Puppet Show (by Fantasy & Dune) - The Rise of Love (by Hemoroids) - The Snowman 2009 (by Checkpoint) - The Stupendous Demo (by Pixel Twins) - has couple of STE screen - The Union demo (by The Union) - The Truth (by Dekadence) - The twist (by Team "The twist") - The World According to Nordlicht - The XMAS '88 Demo (by Kenrick Productions) - TomTechTronic (by Effect) - Transbeauce II (by BushWacKers) - Tut! (by Wildfire) - Twelve (by Paulo Simoes) - TwiSTy (256-Bytro by Ultra / Cream) - Two in One (by DHS) - Unbelievable (by Masters Of Electric City) - Unextreme (by Masters Of Electric City) - Unreachable (by MoEC & Sector One) - Unstatic (by Cyprian, Dma-Sc & bob_er) - Virtual (by Equinox) - Visualize (by Checkpoint) - Vitsit Dottuun (by Extream, 50Hz only) - Vodka demo (by Equinox) - does not work with >1MB of RAM - We Accidentally Your Oculus (by Sector One) - We do not rip or recycle anything (by Spice Boys) - Wedtro (by Avena & Effect & D-Bug) - X-files (by Martin Cubitt, in STOS) - Error #046 => run from desktop instead of auto/-folder - Xmas 2000 Intro (by Paranoid) - YmRockerz musicdisks from "Wave upon Wave" to "Seven" - Zero Bitplane Picture (256b by Dyno / Hemoroids) Color toys: - Fplanet - Schnapfl Commercial game demos/previews (many of these are pretty good): - Baby Jo demo (floppy only) - Beast II demo - Bloodwych demo (floppy only) - Bolo demo (floppy only) - Bug-Bash demo - Cadaver demos 1 & 2 - Captive demo - Cartoon Capers (STOS) - Celica GT4 Rally demo - Challenge golf demo (floppy only) - Chips Challenge demo - Civilization demo - Cloud Kingdoms demo - Conqueror demo - Defender II demo - Deflektor (floppy only, Zero magazine disk) - Creatures demo - Fire & Ice demo - Flimbo's Quest demo - Flood demo - Gods demo (floppy only, ST format disk) - Golden Ax demo - GuyFawke demo (use joy in port 0, floppy only) - Helter Skelter demo - Hudson Hawk demo - Hunter demo - Interphase demo (floppy only) - James Pond demo - Jupiter's Masterdrive demo - Killing Game Show demo - Knightmare demo (needs <4MB RAM) - Leander demo - Legends of Valour demo (50Hz, STF47) - LLamasoft games http://minotaurproject.co.uk/lc-16bit.php - Lotus III demo (floppy only, non-interactive) - Mad Professor demo - Magicboy demo (floppy only) - Manix preview - Magic Pockets demo - Megalomania demo - Microprose Golf preview (floppy only) - Mig29 Super Fulcrum demo - Moonshine Racers demo - Nitro demo - Oids demo - Parasol Stars demo - Photon Storm demo - Pipemania demo - Populous II demo - Pushover demo (3 playable levels) - Puzznic demo (needs floppy in drive A:) - Rampage demo - Robin Hood demo - Robokid demo - Rolling Ronny demo - Rotoplex demo - Space Crusade demo - Space Harrier (line-A copy raster & line, in Atari PowerPack) - Stormball demo - Super Stario Land demo - Thunderstrike demo - Tony - Montezuma's Gold demo - Tower of Babel demo - Turrican II demo (bus errors during game) - Up an' Away / Flip & Magnose demo - Venus Flytrap (floppy only, Zero magazine disk) - Videokid demo (floppy only, non-interactive) Free/shareware/PD games: - 2048 (by Paradize) - A Day at the Races - Abombinaball (STF47) - Alterra - Archon 3.5k (by Deltaforce) - Alien Blockade* (quixx) - Atari Dash - Atax - Bellum* - Blaster - Blind labyrinth (line-A bitblits, mouse issues) - Bodyshop (line-A bitblits) - Boinx 3.5k (by Total Vision Inc) - Bold (floppy only) - Bombs Away - Bombwatch - Bombzai - Boom - Bugs* (line-A bitblits) - Candyman (line-A bitblits) - ChuChu Rocket* (RG's STFM Lite version) - Clogged Up - Cops and Robbers Too! (line-A bitblits) - Crapman* - Cybernetix - Dark Fortress* - Dave Munsie games* (shareware released later as freeware) http://www.atarimania.com/list_games_atari-st-munsie-dave_team_950_S_G.html - Donkey (GWBasic GFA port) - Donkey Island (adventure) - Dot 2 Dots (for kids) - Downfall (2-player) - Droid (floppy only, bootsector has 0 for reserved sectors) - Droid 2* (floppy only) - Entombed - Does not show title, copyright, hiscore nor help after EmuTOS v0.8.6 - TOS v2 shows title but not others and sound is wrong - Flip'em* 4KB (puzzle) - Fly Robin Fly - Fokker (2-player biplanes, use joystick 0) - French adaptations of classic games: Boulderdash, Demineur, Jewel, Qbert, Sokoban - Frogger* - Fuzzball* - Galaxian (STF47) - GodPey (floppy only, by Reservoir Gods) - Golgafrincham (mod compile with 2-player minigame) - Grav* - Grav2* - Hackman II* - Hang About 4KB (mountain climbing, use joystick 0) - Hangmad (hangman) - Happy Worm - Hardcore preview - Haywire (use joystick 0) - Hector* - Hero (RPG) - Hexmines* - Iceblox Plus* (works on all Atari machines) - In nihilum reverteris (interactive color novel) - Insectroid* - Japlish (line-A polygon lines) - Jetpac - Jitterbugs (MIDI multiplayer, GFA, slow) - Killing Spree (2014 preview by TLB at Atarimania) - Laserball 2014 - LeMans (line-A get pixel, draw line, bitblit, show mouse) - Manic Miner - Master Breakout - Maze* - Mem* (line-A bitblits) - Merchant (works also in mono) - Midi Maze* (MIDI multiplayer, works also in mono) - Midi Maze II (MIDI multiplayer) - Midi Maze 3 (MIDI multiplayer) - MidiWiz 1.1i (MIDI / RS-232 multiplayer) - Requires EmuTOS >= v1.2 to work also on ST - Miracle Boy in Dragon Land - Missile Alert - Monkeys and Balloons - Monopoly - Nibe 2 - Nightline MC - Nova - Out of this Word* (typing game) - Panic - Paradize games (line-A bitblits): http://paradize.final-memory.org/games.shtml - PHF Rally 2 (0-bitplane / overscan, needs 50Hz) - Picross ST* (floppy only) - Punt II - Rayoid* - Recoil - Revenge of the mutant camels (shareware) - Robert in the Fire Factory - Robotz - Roc'hell* (by Dune) https://github.com/dune-demogroup/roc-hell/ - Rockfall* - Santafly - Shift* - Sideways - Sinister development games (shareware) - Snowball Fight (2-player) - Spacebar - Spacewar - Starball* (shareware) - Stomp* (STF47) - Super Pac-Man* (use joystick 0) - Superfly (floppy only) - Sweeper - Tanx* - Terraboink - Tiles Osmosis - Time Bandit - Teserae* - The errand boy (text adventure) - The lost world (line-A bitblits) - Toogle* (works an any machine) - Toop - Trace - Unheart - Video poker (shareware) - WalZ (breakout) - Warzone - Whip Snapper's race - Wolf3d* (floppy version, Fread()s files opened as write-only) - Xenon 3.5k (by Michael Bittner) - Yak 3.5k (gridrunner by Jeff Minter) - Yoomp* (by Dekadence) - Zap - Zatacka (multiplayer) Med-rez games: - Doom8088ST (dithered BW graphics) - Hangman v2.0 - Poker solitaire GEM / monochrome ST demos and games ................................... Monochrome demos: - Demounstures (by TOS-Crew, works also in color) - Halftron (by Ephidrena) - Hi Alf (by Echo Cray) - Monism (by Sync, requires 1280x200) - Monofillah (by Tom) - Monomental (by Senior Dads, crashes at end) - Monoscreen (by Dead Braincells) - Monotheist (by SMFX) - No rights (by Dekadence) - Roland W-30 Editor Cracktro (by DHS) - The Mono Tale (by The Cyclemasters) GEM games: - 2048 (by Rajah Lone) - 4 Gewinnt* (Connect four) - Abalone - AtariGo - Awele (use v1.02, it fixes bugs in v1.01) - Ballerburg* - Bombs (minesweeper) - Centi - Checkers - Chess* (works only from floppy) - Clicks* (needs wdialog) - Corewar - Daleks* - Dame - DBWH - Drachen - Flipside (fs, othello) - Frix - Gem_mind - GEMcell - Gemamigo* - Gnuchess - Gobang* - Halma - Invers - Isola - Kensington - Magic Stones* (m_stones: tetris, 2x tetris, columns) - Mars (corewars) - Mathmaze - Megaroid* - Minds* (minesweeper) - Mines* - Mosaik - Nanjing - Nethack* - Never Mind - Orb - Pacman - Patience* (card solitaire, use latest v2.25) - Risky* (Risk board game) - Shanghai - Ship Combat (battleships, line-A calls) - Sixteen (puzzle) - Sliders - Snake - Sokoban* (http://peterlane.info/sokoban.html) - Solitair* - Spiegel ("mirrormagic") - Stello* - ST Concentration* - Sudoku* - Tartan* (chess) - Thor (Othello, unstable) - Thrust - Tricky yahoo - Verrueck (labyrinth) - WindowBall (breakout) - Wormania - Yams (minesweeper) - Yukon* (card solitaire) - zSudoku Monochrome games: - Backgammon (line-A bitblits under GEM menu) - Big Blow (line-A bitblit) - Balloons - Bolo (demo levels, floppy only) - Bouncing boubles - Columns* - Criss Cross - CW Puzzle (line-A bitblit + line & point draw in start) - Delta patrol - Diamond miner - Domino (line-A bitblit) - Emperor (v1.0 & v1.5) - Fun Face (playable demo) - Gluckrad (line-A bitblit) - Go UP - HASCS - Lasermon (laser "chess", there's also color version) - Lokomotive (line-A bitblit) - MacPan* (line-A draw/clear sprite) - MineFeld (line-A draw/clear sprite) - Minigolf* (line-A draw/clear sprite) - Monkey Business - Mr Dash - Pacballs (line-A bitblit) - Pipeline* (line-A line and filled rectangle, works also in color) - Pling II (line-A bitblit, draw/clear sprite and filled rectangle) - Poolmono* - Punssi - Puzzlepuzzle - Qix - Roll X (line-A bitblit and set/get pixel) - Super breakout* (by Mark Overmars) - Sherlock* (texts overlap slightly) - Skull diggery - Slither (line-A draw/clear sprite, works also in color) - Space war (bug: score missing) - Take2* (line-A bitblit) - Tenebra-1* - Tenebra-2* - Wallball - Whack a Virus (SillyVenture 2022 edition) Text games: - Advent (Colossal Cave) - Eliza - Hack - Larn - Omega - Quix Mono/GEM toys: - Biglife* - Fishes (line-A line and filled rectangle, works also in color) - Mandala - Planet (celectial body calculations) - Pyro - Robugs* (GEM UI, line-A fillrect + sprite draw) - ST life - ST tour (from Atari corp, works also in color) - Wator (slow) - Worm farm Broken Atari ST demos and games ------------------------------- Below are listed demos and games known not to work with EmuTOS. EmuTOS documents issues with a subset of them in more detail in: https://raw.githubusercontent.com/emutos/emutos/master/doc/incompatible.txt Broken (floppy) demo classics ............................. - Amiga demo (by Exceptions) - bus errors before *any* OS calls - B.I.G demo (by Exceptions) - bus errors before *any* OS calls - Froggies over the Fence (by Legacy, ST Connexion, Overlanders) - Line-F panic, earlier bus error at $400000 after first OS call: Rwabs(0,0x30000,8,1,0) - with high-RAM etos512k, complains about 1/2 MB RAM - Place to be (by EKO) - with 192k, black screen / panic at start - with cart, bus errors after intro screens - Things not to do (by Inner Circle) - NULL pointer access at 0x7d80a - Floprd(0x7d800, 252, 2, 0, 0, 9) - SoWatt (by CareBears) - constant bus errors at same PC address - Worked before EmuTOS v0.9.10 commit 5981ef6, which puts EmuTOS default disk buffer at a low memory location ($1004) - Floprd(0x70000, 0, 1, 81, 1, 6) - Swedish New Year (by CareBears) - panic at 0x7006 after first OS call - Floprd(0x7000, 0, 1, 79, 0, 9) - WhattaHeck (by CareBears) - panic at 0x7000 after first OS call - Floprd(0x7000, 0, 2, 0, 0, 1) ST games with broken input handling ................................... As expected, STOS games which use STOS routines for input do not work as STOS does that in TOS binary version specific manner (see EmuTOS incompatible.txt). Floppy-only (color) STOS games: - Diamond Ice (joystick input doesn't work) - Insecticide (joystick input doesn't work) - Islandstrike (mouse input doesn't work) - Ozone (fire at start doesn't work) - Vidigrid v2 (mouse input doesn't work) Note that their AUTO-folder starter programs need to be moved to floppy root and started from there, see EmuTOS incompatible.txt. GEMDOS HD supporting (color) STOS games: - Balls Up (mouse input doesn't work) - Color Clash* (fire at start doesn't work) - Dice II (mouse doesn't work) - Heartland (fire at start doesn't work) - Mr Dice* (gets spurious joy input) - Nostram (input doesn't work) - Orbit (mouse input doesn't work) - Phantom (joystick input doesn't work) - Pipetris (spurious joystick left input) - Prehistorik3 demo (spurious joystick left input) - Reflection* (keys work, mouse doesn't) - Roadkill 2 (joystick input doesn't work) - Skatetribe (fire at start doesn't work) - Smash hit (input doesn't work) - Sudoku Universe* (mouse input doesn't work) - The Rollercoaster Experience (fire at start doesn't work) - Trackball (mouse doesn't work) Even some commercial games use TOS version specific input handling (see EmuTOS incompatible.txt): - Brain Blasters / The Teller demo (input doesn't work) - Super Cauldron demo (fire doesn't start game) - Striker soccer demo (fire doesn't start game) Free/shareware color games (more input issues): - Douglas Rockmoor (see EmuTOS incompatible.txt) - Space invaders (see EmuTOS incompatible.txt) Mono games (more input issues): - Crystal Cave* (line-A bitblit & draw/clear sprite, see EmuTOS incompatible.txt) - Macro War (line-A bitblit, see EmuTOS incompatible.txt) - Ramses* (panic, see EmuTOS incompatible.txt) GEM games (more input issues): - Anduril (see EmuTOS incompatible.txt) - Invaders* (see EmuTOS incompatible.txt) Atari ST applications (including commercial) -------------------------------------------- Working debuggers (support also e.g. TT): - Adebug reloaded (68000-68030 debugger) - Peacebug v1.19 (supports remote control over MIDI) - Peacebug v1.30 - Templmon 2.01 Working utilities: - Shifer Inside ST (expands GEM desktop to top & bottom borders) Working drawing / image viewing programs: - Degas Elite, see http://www.youtube.com/watch?v=G4st-x4-BJg (at 16:30) - Iffcnv, IFF converter/viewer - ImCon v1.1, image converter/viewer - Neochrome master (line-A bitblit, text, line) - Photochrome v4 (all buttons don't work) - Speed of Light v3.8 - Spslide, Spec512 viewer Not working: - Spectrum512 (see EmuTOS incompatible.txt) Not working video / HD access programs: - STVidPlay (see EmuTOS incompatible.txt) Working MIDI programs: - A.C.T (EmuTOS >= 1.2 for screen update fixes) - AlterTune - Dave's Fabulous MIDI Music Box - Dr T's KCS v4 (EmuTOS >= 1.2 for menu / WF_FIRSTXYWH fix) - Dr T's Tiger Cub 2.0 - EditTrack - EZ-Score Plus - FinalCut Pro demo (needs <4MB RAM) - Fractal Music - Fractal Music Composer 2 - FretCalc - G.I.S.T (sound synthesis with MIDI input) - Henry Cosh's Accompanist - Largo (MIDI playlist manager) - Ludwig - M - Mandala - MIDI Master Drummer 2.5 - MIDI Square - Miditree (floppy/color only, press "P" to play Xmas carrols) - MiniMoo - MusicEditDemo 8.001 (tries to use GDOS + HighWire) - Music Mouse (uses line-A hline for cursor) - Notator 3.0 - Pro Midi Player 8.0 - Realtime 1.25 - Sequencer One - SMF-PLAYer 1.1 - Sweet 16 (Almost all work in monochrome, about half also on TT / mono.) Not working: - Cubase 2.x / lite (see EmuTOS incompatible.txt) - ST versions of Hypnosis / Quaderno (see EmuTOS incompatible.txt) MIDI programs are available from Tim's MIDI world: http://tamw.atari-users.net/ Most of the non-GEM ST tracker & chip music composer programs work also with EmuTOS, most even with the 512k/1024k EmuTOS variants. See STE notes for more info on them. Atari STE programs ------------------ There are not that many STE specific (or even STE enhanced) programs (see the "STE software compatibility list" section in compatibility.html), so this tries to cover status for all the games, and many of the demos. Note: STE emulation needs 256k variant of EmuTOS. With US i.e. 60Hz image (etos256us.img), you may get screen flicker and music playback issues with demos and games. Full STE support requires EmuTOS v0.9.4+. Working STE programs .................... Following STE specific & enhanced demos, games and apps work fine with EmuTOS. Color demos: - !cube vs. YM2149 (by Aggression) - 1st Step (by Tobe/MJJ Prod) - 20 years megademo (by Atari scene collective) - 20 years Atari STE megademo (by Paradox) - "NoExtra" screen exits immediately - 21 bobs (by Anima, needs 50Hz) - 32768 colors showdown (by Tronic of Effect) - 3D full (by Oxygene) - 68k Inside 2025 (by Dekadence) - 96 Shades of Gray (by Paradox, >1MB, panics at end/exit) - A Letter to Sommarhack (by Effect) - Again (by Paradox) - AltParty 2008 intro (by Paradox) - Ambermoon Music demo (by Psycho Hacking Force) - Amiga Demo 2 (by Oxygene), music disk - An Cool on STE - Another kid story (MJJ prod 2009) - Antiques (by Dune & Sector One) - Appendix (by DHS) - Armada is dead (by Aggression) - ATM 10 Years (by Checkpoint) - Azed/Jungle demo (by Atari) - BallsHmd (D-Bug intro + fixed Hemoroids "Square V2" demo) - Beat Demo (by Frontline) - Bird Mad Girl Show (by Fraggle's) - Birdie (by Zeal) - Blitzwav v1 (by Cybernetics) - Blue Period (by Paradox) - BoingSTE - CD-player (by Light) - Cernit Trandafir (by DHS) - Chipo Django 1 (by Rabenauge) - Circus BackSTage (by BlaBLa) - City Frames (by DHS) - Core Flakes (by New Core) - Devotion (by Excellence in Art) - DMA Bliss (by Effect & Ephidrena & SMFX) - Double Rez Trouble (by DHS) - Dynamite (by Unit 17) - E605 (by Light) - Ecstasy part A (by Inner Circuit Explorers) - Ecstasy part B (by Inner Circuit Explorers) - Fluffy Clouds (by Genesis Project) - French Kiss (by DHS) - Gobi Toons (by Dune) - Grimey (by Reservoir Gods) - Hatari 1.9 intro (by Mr. Styckx) - Hatari 2.1 intro (by Mr. Styckx) - Hatari 2.3 intro (by Evil/DHS, spkr/SMFX and Mr.Styckx) - Heal Old Geek (by Hemoroids, only endpart.prg needs STE) - High Fidelity Dreams (by Aura) - HighResMode 1 & 2 (by Paradox) - I got invited to Psycho Hack (by Modula) - Illusion (by Next) - Inscape To Fractalus (4k overscan by Acid Team) - It's A Girl (by Paradox) - It's A Girl 2 (by Paradox) - Jam-Cols (by ICE) - Just Musix 2 (by DHS) - Just another Blitter demo (by Anima) - Kick my ass'embler (cpp_edit by Paradize & Cerebral Vortex) - Lavalamp (by Tobe) - Little Blitter Benchmark Demo (by Frank B) - LovelYM (by KÜA & SMFX) - Lovetro (by Paradize & Sector One) - Madness (by Cream) - Massacre (by DHS) - Mathematica (by Aura) - Maxymizer compo 2006 (by gwEm) - Mea Culpa (by NoExtra) - Meshake (by Spice Boys) - Mind Rewind (by Reservoir Gods) - Monogatari (monochrome, by Cerebral Vortex) - More or Less Zero (by DHS) - MovieSTE (by Tony Barker) - MPC in Dalarna (by DHS + SMFX) - Muda (by Live!) - Necrosys (by Hemoroids) - New Year -94 Intro (by Extream) - No Monkey (by Tomchi) - No Signal (ADPCM musicdisk by DHS) - Noname intro (by Baky, DemoBit 1995) - Out A Time (1st at Silly Venture 2024) - Pacemaker (by Paradox), end part works only from floppy image - Paracon 6 & 7 remindtros (by Paradox) - Parallax Distorter 2 (by Solomon) - Pixel Dreams (by Bohemian Grove) - Power Rise (by Light) - Realtime (by MJJ Prod) - Reanimation (by Syntax) - Relapse HD (by Cybernetics) - Revise and Revisit (by DHS) - Rise of the blackstorm (by Mystic Bytes) - Riverside (by DHS) - RGBeast (by Aggression) - RGB Splitter (by Paradox) - SaboTagE (by Paradox) - Satan stole my Atari (by Lamers) - Save the Earth (by Defence Force) - Scene? Ay Mamba! (by Spice Boys) - Short and Curly (by Avena + RG) - Sinfull Sinuses (by Chronicle) - Sierpinski overdose 4K (by GGN), med-rez+blitter only - Sommarhack 2010 invite (by DHS) - Songs Of The Unexpected (by OUCH) - SpaSTiba 2024 (by SpaSTiba), needs >= 2MB RAM - Speechless (by The Estate) - Steroid (Metroid remake demo) - STE slideshow (by DHS) - STE Wars (by Tony Barker) - STePS (by XiA/CPT) - STrange RoboTS (by Blabla) - STresSTeST by (Cream) - Summer Delights (by DHS) - Sventure intro (by Paradox) - Takeover (by Lamers) - TalkTalk2 (by XiA) - Techno drugs (by NLC) - The AI slide show (by Slime-Soft) - The Art of Profanity (by SMFX & DHS) - The Star Wars Demo (by The Arctic Land) - Tribute to Rainbow TOS (by 505 & Tobe) - Tyranny (by DHS) - UFO (by Dune & Sector1) - Unbeatable (by Masters of Electric City) - VibeCoding (by Defence Force) - Vision (by POV) - Vulgar Display of Power (by Extream) - We Accidentally Released (by SMFX & KÜA) - We Were @ STNICCC 2015 (by Oxygene) - Reset screen scroll text colors are wrong - Wonderful World (by Paradox) - XiTEC Presentation (by Omega) - Xmas 2004 (by Paradox) - Yanartas (by Checkpoint) - ST demo that works only on STE Monochrome demos: - Bad Apple!! (by Fenerinarsa) - MonogAtari (by Cerebral Vortex) - Time Slices (by Defence Force) - Unbeatable (by MEC) Color games: - 4K Pacman - Adam is Me - Aerial Combat 3 - Alien Blast (demo) - Alien Thing preview - Atari Scene Card Battle (line-A bitblit) - Astrodia - Battletris+ - BeGEMeD* (line-A bitblits) - Blat (line-A bitblit) - Bombaman - Boom (screen flashes during scrolling messages) - Breakdance (scene game, STOS) - Catch Me If You Can* - Chroma Grid (including D-BUG intro+trainer) - ChuChu Rocket* (RG's full version) - Crash Time Plumber - Defender* (Williams version port, works also on ST & Falcon) - Detonator - Dread* (playable Wolf3D clone demo v1905) - Droid STE rewrite - Dynabusters+* - Exchange Puzzle (SV20 edition, optional STe palette support) - Faster* - Frantick - Frogs (optional STE audio/blitter support, MIDI multiplayer) - Giana Sisters STE rewrite: http://atari-forum.com/viewtopic.php?f=3&t=26360 - H Mec 2 (line-A bitblit) - Invasion of the Emerald Saucers - Metal Slug STE (mission 1) - Micromachines preview (by .tscc) - Mr Boomer (line-A bitblit) - Nano Cave* - No Limit II (shareware pinball) - Obsession* (demo of commercial pinball) - Operation Garfield - Pacman on E - Pacmania* STE/overscan rewrite: http://atari-forum.com/viewtopic.php?f=3&t=24635 - Pole Position (arcade conversion) http://atari-forum.com/viewtopic.php?f=3&t=26068 - Power Up - R0x (line-A bitblit) - R0x Zero (line-A bitblit, overscan screen needs 50Hz version of Emutos) - Randomazer 1.x & SV2020 editions - Ready Steady Bang! - Rodney vs. KFC - Roger (line-A bitblit) - R-Type Deluxe (arcade conversion) http://atari-forum.com/viewtopic.php?f=28&t=29047 - Sardonic http://atari-forum.com/viewtopic.php?f=3&t=38121 - ScummST (STE enhanced) http://www.happydaze.se/scummvm-lite-atari/ - Shotgun (STE enhanced) - Skulls (shareware "minesweeper") - Space Zot - Sporny's Wrecking Ball - Spy 4k - Stardust* (tunnel sequence demo) - STEtris (STOS) - STrEet Fighter II (incomplete) - Stupid balloon game (joysticks swapped?) - Substation* (demo) - Supa Zazai Da - The chronicles of Omega - Tomtar - Trapped II (DMA audio, line-A functions 1, 2, 3, 5, 7, 9) - Treasure in Cave (by Freedom) - Ultimate Arena v1.3 light (slow with EmuTOS) - Utopos* (demo) - Warriors of Light v0.63e - Wolfenstein3D v0.8a* (a bit unstable) - Zaptastic - Zero-5 (demo) - Zool (demo) Additionally, also all nice STe enhanced Paradize games work (AttackWave, Kolmik, MangaPuzzle, NWDump, Pairs, Penta, PokerSquare, Pooz, Znax, SpaceBattle), and they all use line-A bitblits: http://paradize.final-memory.org/games.shtml Monochrome games: - Whack a Virus (SommarHack 2020 edition) - Does not work on ST with newer EmuTOS versions STE drawing programs: - Art for Kids / demo STE (enhanced) video players: - MP_STE STE (enhanced) music applications: - Blipp Blopper - E.P.S.S (both v0.54b demo & v1.0.1 full version) - DeskTracker MT - Hextracker - Jam (with CPU plugins) - MaxYMizer - MusicPlayer - Octalyzer - Paula - Protracker 2 STE - Sirius Player STE (enhanced) utilities: - Shifer Inside STE (expands GEM desktop to top & bottom borders) Non-working STE programs ........................ Some non-working demos: - Original Beat Maker (OG.PRG, SV2019, by CrazyQ) - STE recognization fails with TOS 2.x / EmuTOS -> bus error - YMSE (by Ephidrena) - Musicdisk dentro works only under Falcon, or original STE TOS - VGA slideshow (by Light) Non-working games and music programs: - Newer Audio Sculpture versions (v1.5beta doesn't handle disk access properly, v1.4 has broken crack, v1.1 works) - Cameleon (see EmuTOS incompatible.txt) - Hero (STOS, see EmuTOS incompatible.txt) - Protracker v2 STE, Equinox version (see EmuTOS incompatible.txt) - http://www.pouet.net/prod.php?which=13381 Atari TT programs ----------------- TT emulation requires 512k (or 1024k) variant of EmuTOS (the one shipped with Hatari). TT-low (256-color) mode and sound support requires EmuTOS v0.9.6+. (If program supports features common to both TT and Falcon, but has no specific Falcon support, it's listed for TT.) Working applications: - Calamus SL demo (1991 demo version) - Calamus SL/L/LE 20xx lite versions - Need either GDOS or older EmuTOS v0.9.5 - v0.9.12 - Hardware 2.0 (works also on Falcon) - Mandelbrot playtime (fracplay, uses FPU) - M_PLAYER v4.02 - https://gtello.pagesperso-orange.fr/mplayere.htm - PixArt4 - http://www.1632systems.co.uk/www/html/Product/pixart.htm - Vision v4.9 (works also in ST, but is much nicer in TT low/med) - http://jlusetti.free.fr/visione.htm - ZXR v0.1m (ZX Spectrum emulator for 020+ machines) - https://www.atari-forum.com/viewtopic.php?t=34048 Working demos: - 256mbrot, Bragg, Glasstro, Orion-B, Yabt (TT/Falcon intros by ray//.tSCc) - 4getful (by gwEm, 4KB intro TT-version) - Adebug 3DTT demo (by Brainstorm) - Cube 256 (030 ST-low 256-Bytro by Thadoss / Dune) - CrY image viewer (by .tSCc) - Elegant Machinery (by Evolution) - Led Blur (by Miro) - Much faster on Falcon/16-bit - Mandelbrot explorer (by .tSCc) - Monscape (by Escape) - TC fish (by .tSCc) - TT highres slideshow (by .tSCc) - Shiny Bubbles TT (by Xanth FX) - Spear of Destiny (port by Gildor) - XiTEC demos (by Omega) - On exiting Swing, EmuTOS panics with privilege violation (TOS bombs and continues) - Yeti3D (port by Gildor) Working games: - Capy (by YesCREW) - DGEM v0.16 (Dungeon Master GEM version) - Start from TT-med (or use 640x400@16 VDI mode on ST/e) - GEM Breakout - GEM Klikomania - GEM Landmine - GEM Magnetic v0.8 (Magnetic Scrolls game runner) - Crashes on resolutions smaller than game window. Setup game colors to black and white, disable graphics and use TT mono monitor (or use 672x672@1 VDI mode on ST/e) - GEM Manga Puzzle (needs 256 color mode) - GEM Milanopoly - GEM NetHack v3.4.3 - GEM Santarun - GEM Shapes - Sounds do not work on ST + needs at least 480x400 mode - GEM Slashem v3.3.1 - "Hacked" Heretic & Hexen (SDL ports by Miro) - OpenDUNE - OpenTTD (start from TT-low with 256MBs of TT-RAM) - Oxyd (Falcon version) - PM Doom, Heretic & Hexen v0.55 (ports by Patrice Mandin) - ScummVM 2.8, 2.9 (lite build by Miro) - Official upstream version (37 engines, 300+ game variants recognized) - STDOOM, first 020+ version - The Ultimate Minesweeper (GEM) Most of above GEM games run even on ST, but only TT (and Falcon) have large enough resolution with enough colors for them to be properly playable. Falcon programs --------------- Falcon emulation requires 512k (or 1024k) variant of EmuTOS (the one shipped with Hatari). Programs using DSP require EmuTOS v1.1.1+, programs using TrueColor (TC) mode, require EmuTOS v1.3+. Following Falcon only demos, games and apps work (mostly) fine though. Falcon demos ............ Working demos (comments are for EmuTOS v1.0 or older): - 1600x600 (by Archangel) - 256Water (256-Bytro by Extream) - 30l coke, needs DSP - 4getful (4k intro by gwEm, Falcon version) - Has way too low volume - 4orce (4k intro by DHS) - A Rh Positive 4k (by tSCc), needs DSP - Ascii (by Reservoir Gods), disable DSP - Ath/0 (by Dekadence) - ATS (by DHS) - Autowachen Verboten (by Lazer) - Binliner (preview by Avena) - Birdshow - Blue 4k (by New Beat) - Ikbdws() stack usage was issue in v0.8.7-v0.9.3 - Bugs from outer space (by Impulse) - Bad sound - Cebit 93 (by Respectables) - Chipo Django 1 (by Rabenauge) - Chrome Dragon (by Opium) - Cooler than ever (by ICE) - Bad sound - DBA Magazine 14 intro (by Lazer) - Earth (by Gaston) - Falcon Flight (by Opium) - Firestarter (by Shadows) - Flu 4k (by New Beat), needs FPU - Fungle beats (by FUN) - Game of Life 4k (by Aggression) - Geotech (by Cybernetics) - Gourand triangles demos (by ray//.tSCc) - These have asm sources! - Gurkensalat (by Lazer) - Illusion 64 (by Paranoia) - Plasma screen has garbage on right side, end screen at top & bottom - ImPerfect (by XI / Satantronic) - The Keff Demo (by An Cool) - More (by New Beat) - Mouse (animation + music) - Oergs (by Lazer, pic + music) - Old Stuff (by Extream) - Payback 2015 (by Dekadence) - 060+FPU demo - Prepare (by Extream) - RGB Reine (by New Beat) - Rock Solid (by Paranoia) - Schlumpf invtro (by Lazer) - Horizontal scrolling is bad with some EmuTOS versions - Six Sievert (by tSCc) - Snowstorm (by Reservoir Gods), disable DSP - Sonolumineszenz (by Avena) - works before v0.9.1, but without sounds - STrEet figher II (by Patrice Mandin) - Terrorize Your Soul (by tSCc) - Fonts and colors are not right on all screens - Tiny Things (no music as it's on DSP) - Videl visions slideshow - Virtual City, needs DSP - Freezes at exit - Warum (by Lazer) - Weltchmerz (by Avena) - Why (by Extream) - ZygyFem6 (by Extream) - ZZ 9 Plural Z Alpha (by tSCc) Demos working under EmuTOS >= v1.1 (with DSP support): - 4kbtro (by Wilfire) - 96ktro (by Cruor) - Aggressive Party 2 4k (NoCrew) - Agony (by DNT crew) - At Night They Come (by Bohemian Grove) - Back To The ST (by Bohemian Grove) - Beams (by .tscc) - Bird to be alive (by Dune etc) - Bound 3, Bound 5 (by Crac) - Bound 42 (by MMJ Prod) - Built-in Obsolescence (by Digital Chaos) - Chaos A.D. (by DNT crew) - Chiaroscuro (060, by Evolution) - Circuitry of Desire (060, by DHS) - Codein (060, by DHS) - Cycedelic knockout (by Mugwumps) - Dan's Lustiges Kinderfest (by Lazer) - Derealization (060, by DHS) - Enraged (by Avena) - Ex Illusion (by Aura) - F22.0 (060, by Lamers) - FOG-intro (by Psychosis) - Hex Pistols (060, by Fit) - Hmmm... (by Escape) - I want you to remember (by KÜA Software & Pixel Twins) - Monochrome, with DSP music - Illness (by Escape) - It's that time of the year again (by New Beat) - Works also with v1.0, but MP2 DSP music requires >v1.0 - JESTERday (by POV), musicdemo - Joy (by New Beat) - Works also with v1.0, but MP2 DSP music requires >v1.0 - Kusipäät Kaljanpöllijät (by Dekadence) - Monochrome, with DSP music - Lamer than Lamers (060, by Lamers) - Laser Days E.P. (by .tSCc) - Works also with v1.0, but MP2 DSP music requires >v1.0 - Lockup (by Mystic Bytes) - Motion (unfinished demo by Aggression) - No Signal (MP2 musicdisk by DHS) - Papa was a Blade Runner (by EKO) - Revertant 1.2 (by Cobra / Mystic Bytes) - Works also with v1.0, but MP2 DSP music requires >v1.0 - ROT3DBMP - Shades of Circuits (v1.1 musicdisk by Dry Bone Records) - Works also with v1.0, but startup texts look bad - She danced to Tainted Love (musicdisk by tSCc) - Works also with v1.0, but MP2 DSP music requires >v1.0 - Sidetracked (by Collapze) - Silly Venture 2k14 invitro (by Dune / Sector One) - Silly Venture 2k16 invitro (by Dune / Mystic Bytes) - Silly Venture 2k19 invitro (by Mystic Bytes) - Stercus Accidit (060, by Fit) - Stocasto (by NoCrew) - Works also with v1.0, but then DSP 3D objects are missing - Tere Ra'I (by Cerebral Vortex / Dune / Sector One) - The mountains flight (by Cruor) - The Ultimate Seduction (Sommarhack 2020 invite) - Too Silly 2 (by Lamers) - Works also with v1.0, but MP2 DSP music requires >v1.0 - Tron2001 (by ICE) - Unexpected Fun (by Orion_) - Voxel (by DrTypo) - We Reached Stars (by Orion_) - 060+FPU demo - Whirlpool 128k (by Extream) - Why (by Extream) - X-tasie (by Trio, requires MMU) - Xmas 1997 (by DHS) - Yepyha (by Toons) - Zero Three Zero (by The Pixel Twins) Demos working under EmuTOS >= v1.2: - LCF (by Lamers) - 060+FPU+VGA demo Demos working under EmuTOS >= v1.3 (XBios TC support): - Multitech (by Cybernetics) - Part of Geotech demo, needs DSP + TC mode Demos working under EmuTOS >= v1.4 (full TC support): - 128dist (128-Bytro by DHS) - Needs FPU + started from 320-wide TC mode - Bound & Bound2 (Crac) - Bound 4 (by Crac) - Startup text renders correctly only if started from TC mode - Burnin Bee (128-Bytro by Thothy) - Needs to be started from 320x200 TC mode - Cider, CryWolf, Grapes, Hobnobs, HongTron, Hot, Ketchup, Lettuce, Pasta, Wormhole (128-Bytros by Reservoir Gods) - Need to be started from 320-wide TC mode, some with FPU - Doomino (STAX) - Startup text needs TC terminal output, otherwise OK with v1.1 - GemDemo (by DHS & AssemSoft): - Needs to be started from TC mode - MengerMarcherFC (256-Bytro by atariBDSM) - Needs to be started from 320x200 TC mode - Spin176 (256-Bytro by MKM / Lamers) - Needs FPU + started from 320x200 TC mode Demos working partly under EmuTOS >= v1.4: - 124 beers later (by Trio) - Works already with v1.0, but fighter screen DSP textures need v1.1 - Ground texture missing in last screen Broken demos (with EmuTOS v1.4): - Who Okayed This? - Both the new and (fixed) old intros (epidemic.prg) do work, (fixed) music disk (zikdisk.tos) panics to bus error at -1 Hatari issues (happen also under TOS4): - '_' (by Escape) - Crashes after first few screens - Symposium'96 (by Hydroxid) - Cannot run to end - X-perience (by Abstract) - NULL pointer bus error after while / demo freezes Falcon games ............ Games working under EmuTOS >= v1.0: - Aces High (preview) - Addsub - BeGEMeD* (Falcon version) - Block Blizzard preview (line-A bitblit + hide cursor / show mouse) - Blum - Cavemania (demo version) - Chorensha (old, non-DSP version) - Columns by Deadheart* (Atarimania / 1st demo version) - Corsair - DB 4K - Dry Egg (needs >4MB RAM) - FalcTron - Galaga 88 - Ganymed (start with F1) - Grenzüberschreitung - Heretic (very slow) - Hexogan (use number pad) - Impulse* (Breakout clone) - Jeu de Poker (VGA only) - Needs 640x480@256 mode - King Solitaire - Kniff (VGA only, Omikron Basic v3.60) - needs 640x480@256 mode - Lamemine - L'Abbaye Des Morts - Let's play Shanghai - Needs 640x400@256 mode - LlamaZap (free version) - Requires joypad, not joystick - Madtris - Masters of Chaos (multiplayer "dungeon master") - Nibe - Offworld Security - Pac Them* - Pacmania X68000 - Pingo 98 (classic version) - Running* (demo version) - Radical* Race (demo) - Raiden - Risk 030 demo (VGA only) - Needs 640x480@16 mode - Sokoban By FUN (VGA only, VDI draws + line-A area blits) - Start from 640x480@16 - Spice* (Defender clone) - Spout - Steinbruch - Sweety Things - Switch - Tank Blaster - Tautology (by Reservoir Gods) - Tautology II* (by Reservoir Gods) - Toy Man - Ultimate Arena Falcon edition (demo): - Does not recognize it's on Falcon -> select "Test" - Vertical Mayhem(+)* (Columns clone) - Vroom 030 (too fast) - Willie's Adventure (preview1) - Disable DSP - Willie's Adventure* (preview2) - X-moon preview* (music with DSP) - Zodiax (Falcon "R-type") - Start from 640x400@16 Games working under EmuTOS >= v1.1.1 (DSP support + fixes): - Aazohm Krypth - Bad Mood (90's demo) - Bad Mood* (v0.37 game from 2017) - Beats of Rage* (by Thadoss/Dune) - Boom preview - Chainz (VGA only): - For v1.0, press space at start to disable DSP music - Chorensha (DSP version) - Conquest of Elysium II (preview) - Crown of Creation (preview) - Des Lazers & des Hommes - Double Bobble 2000* (by Reservoir Gods) - For v1.0, disable DSP from Hatari - Epi-Lepsie (by PARX) - Gold island (demo, VGA only, Omikron Basic v3.5x) - H2O* (by EKO) - Jewelz (VGA only) - For v1.0, select hatari mode (F2) to disable DSP music - Kwiks* (by Digital Nightmares) - Les Dinosaures (v1.02 demo by PARX) - Men at War - Multibriques (by PARX) - Ninja Torappu (by Cerebral Vortex) - Some sounds missing with EmuTOS v1.0 - NoMess (GEM, requires color icon support) - OpenJazz (060 Atari port) - OpenTyrian (060 Atari port) - Pong 2000 demo (use joypad) - Push It - Racer (by Thadoss/Dune) - Racer 2* (by Thadoss/Dune) - Running* (shareware version) - Slippery Sam - Static (Patience by Reservoir Gods) - For v1.0, disable DSP from Hatari - TsccTron - TeknoBalls (Arkanoid clone) - The 8 runes of Aerillion (demo version) - Towers II - Trionoids* (Columns clone) - Worms* (Team17 game 060 Atari port) - Wotanoid (Asteroids clone) Games working under EmuTOS >= v1.2: - Tetrhex (1.41) Games working under EmuTOS >= v1.3 (XBios TC support): - Confusion demo 1 & 2 - Gravon (demo + full version) - Moonspeeder (preview 2) - Rave* - Conquest of Elysium II v2.0 & 2.3 (uses VDI calls) - Tron 2 (VGA only) Games working under EmuTOS >= v1.4 (full TC support): - Boulder Smash Falcon (Omikron Basic) - Needs to be started from 480x400 or larger TC mode - DangerZone - Needs 8MB + started from 640x480@16-color VGA mode - Without palette<->VDI index compatibility fix, errors on start - Hong Tron (128b game by Reservoir Gods) - Needs to be started from 320x400 TC mode - Pous-Pous (GEM puzzle) - Needs to be started from TC mode - Q-Blue (VGA only, Omikron Basic) Needs to be started from 320x240 TC mode Seem to work except for Hatari Videl emulation issues breaking graphics (which happens also with TOS4): - Killing Impact - Ufo War Games working with extra hacks: - Bugger, Bunion, SkyFall, Sworm games by Resorvoir Gods: - use an undocumented TOS4 vector for keyboard input instead of accessing kbdvec correctly which use causes EmuTOS to panic. This can be worked around with the following hack.prg: http://sourceforge.net/mailarchive/message.php?msg_id=26841274 - with EmuTOS <=1.0, enabling DSP freezes the games right at startup (with normal TOS4, DSP is used for background music) Partly working games: - Bomb Squad - Game is 4x slower than with TOS4 (see EmuTOS incompatible.txt) - Color Runner - Game cannot be started from main menu (same as with TOS v4) - Columns by Deadheart* (demo, 2nd version) - Unlike with TOS4, broken gfx if started under RGB instead of VGA - Does not use Vsetmode() for OS version <= $0402 - EmuTOS >= v1.0 - Mouse Trap - Unlike with TOS4, freezes if started under RGB instead of VGA - Requires TC XBios support (EmuTOS v1.3) - Painium - Fire button works only until game itself starts - Works only with 8MB RAM Broken games (with EmuTOS v1.4): - Downfall - Dialog complains "Error while loading BLACKWHT.BGR". It is already opened, so issue may be Setscreen() following it - It's Great (start from st-med rez) - Mouse doesn't work - "K" preview* (by EXA) - Bus error panic when actual game would start - Moongame - "Classic Falcon" (A) option bus errors to $59f1fffe read, when the actual game would start - "fast Falcon" (B) option gets infinite illegal DSP instruction errors after pressing joypad button 3 in main menu - Neurobot - Both intro & game just exit immediately (TOS version?) - Reeking Rubber - Broken graphics & pterm0() on game start, uses VDI draw calls - SBM v0.8 & v1.03* (Bomberman clone) - Bus error panic with 8MB, illegal instruction one with 14MB - Watership (VGA only) - Bus error reading $fffffffe on startup -> panic Falcon applications ................... Applications working under EmuTOS >= v1.0: - Aniplayer v2.22 - Can decode data faster with DSP - BIQ codec / player (by .tscc, has sources) - Cecile hard disk (image) driver - Godpaint (by Reservoir Gods) - ICDraw, .ICO and .IB3 GEM viewer - Jam (with DSP music playback plugins) - Kronos v1.91 benchmark - Rainbow 2 multimedia, needs MMU - Smurf (GEM image viewer) - Videlity v1.00r (resolution enhancer) Applications working under EmuTOS >= v1.1 (with DSP support): - AceMidi demo (v1.01) - AceTracker v1 & v2 - AFM (audio fun machine) - UI needs color mode - Atari800 v4.2 emulator - Centurbo benchmark - DSP speed 2x, and bogus speeds for FPU & CPU as with normal TOS - Delmpaint (GFA) - Due to program bugs, start from 320x200@256 resolution - DSPDebug (by Brainstorm) - DSPdit (by .tscc) - Falcon Sound CPX - FlaySID - Flextracker (use VGA) - Fractals v2.2b (030 + DSP) - GemGT2 v1.1, GemMOD v0.85, and GemPLAY v1.95 - http://yescrew.atari.org/eng/products.htm - Graoumf Tracker (v0.8890) - Start from 16-color >=640x200 resolution - MegaPlayer v1.15 (MegaTracker Player v1.1) - Iphigenie v1.4b (sound system monitor and manager / DSP LOD loader) - Voxx (vocoder), demo and free version - Needs at minimum 640x480x16 color VGA resolution - Whip! (virtual light machine) - WinRec (direct to disk GEM audio recorder/effects) - Oscilloscope & spectralizer show only in VGA resolution, although they work fine also in RBG with TOS v4 Applications working under EmuTOS >= v1.2: - Escape paint (GEM, requires color icons support) - EmuTOS v1.2 provides correct window management - FalcAMP (GEM, requires DSP & color icons support) - 3D look in icons & text in some dialogs - MP2AUDIO player - 3D look in icons - Needs to be started from 640x400 or larger resolution, otherwise it positions itself completely outside the screen (also with TOS v4) - Quaderno - Falcon version, expects 640x400@256 - EmuTOS v1.2 provides TOS v4 compatibility for doing wind_set() for a window that's created, but not yet opened - Videl Inside (v1.2) - EmuTOS v1.2 provides Srealloc() support needed to avoid desktop crash with modes larger than earlier static screen alloc - M_PLAYER v4.x - Sound did not work with EmuTOS v1.1 - https://gtello.pagesperso-orange.fr/mplayere.htm Applications working under EmuTOS >= v1.3 (XBios TC support): - Apex Alpha (demo) - Requires MMU + FPU, works both under VGA & RGB - Apex Animator 2.41 - Both the app itself and separate viewers - Apex Media v2.41 - Both the app itself and all viewer TTPs work - ScummVm for Falcon + clones - Older TC-only versions from OL & Miro - ScummVM 2.8, 2.9 (full build by Miro) - Official upstream version (56 engines, 390+ game variants recognized) - Centview (GEM image viewer) - After showing an image, GUI is not always refreshed Applications working under EmuTOS >= v1.4 (full TC support): - Indypaint - Crashes if not started from TC mode (line-A init, hide/show mouse) - Interact with mouse & keys; F1-F5: popup menus, Space: palette Applications working with extra hacks: - Fractal Playground, Godleness, GodBoyX by Reservoir Gods - Like RG games, these require "hack.prg", otherwise pressing any key crashes them Partly working applications: - Animator v0.20.4 - Works as well as with TOS v4 (refuses to run with many videos or crashes on exit leaving desktop with broken graphics) - AVI030 - Works as well as with TOS v4 (= has issues) - Eero hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/fr/000077500000000000000000000000001504763705000217155ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/fr/clavier-exemple.txt000066400000000000000000000047251504763705000255500ustar00rootroot00000000000000# This file must be UTF-8 coded # Mai 2017, Cocoa Pod. # FRENCH ------------------------------------------------ # Mappage du clavier AZERTY-fr d'un Macbook Pro # # Tests sur Macbook Pro (MacBookPro5,3), MacOS 10.12 fr # Tests sur Macbook Air, MacOS 10.11 fr # Tests sur émulateur Hatari 2.0 _ ST, MegaST, STE, MegaSTE, tout TOS fr # Apple: Dans "Préférences Système / clavier", Valider l'option : # "Utiliser les touches F1,F2,.... Comme des touches de fonction standard" # F1-F8 presser la touche F, F9-F12 presser les touches cmd et F. # # Les commentaires commencent par un # ou un ; # Chaque ligne active commence par deux chiffres séparés par une virgule. # le premier est la valeur du symbole de la touche de votre clavier et le # deuxième est le scancode de la touche équivalente du clavier Atari. # Le premier chiffre peut être remplacé par un symbol. # ENGLISH ------------------------------------------------ # Mapping for Macbook Pro keyboard AZERTY-fr # # Tested on Macbook Pro (MacBookPro5,3), MacOS 10.12 fr # Tested on Macbook Air, MacOS 10.11 fr # Tested on emulator Hatari 2.0 _ ST, MegaST, STE, MegaSTE, all TOS Fr # On Apple: in "System Preferences / Keyboard", Validate the option : # "Use all F1,F2,.... as standard function keys " # F1-F8 press F key, F9-F12 press cmd and F keys # # 'keymap-sample.txt' file explains the syntax and values that can # be used for keyboard mapping. # function keys line # Escape,1 F1,59 F2,60 F3,61 F4,62 F5,63 F6,64 F7,65 F8,66 F9,67 # hit cmd-F9 F10,68 # hit cmd-F10 # F11, # cmd-F11 Does not exist in ATARI # F12, # cmd-F12 Does not exist in ATARI # first line: @ é " ' ( § è ! ç à ) - # <,43 &,2 é,3 ",4 ',5 (,6 §,7 è,8 !,9 ç,10 à,11 ),12 -,13 Backspace,14 Delete,83 # fn + Backspace # second line: a z e r t y u i o p ^ $ # Tab,15 a,16 z,17 e,18 r,19 t,20 y,21 u,22 i,23 o,24 p,25 ^,26 $,27 # third line: q s d f g h j k l m ù ` # q,30 s,31 d,32 f,33 g,34 h,35 j,36 k,37 l,38 m,39 ù,40 `,41 # forth line: < w x c v b n , ; : = # @,96 w,44 x,45 c,46 v,47 b,48 n,49 44,50 # comma 59,51 # semicolon :,52 =,53 Space,57 CapsLock,58 Return,28 Left,75 Right,77 Down,80 Up,72 Keypad Enter,114 # fn + Return Home,71 # fn + Left End,97 # fn + Right PageDown,100 # fn + Down PageUp,99 # fn + Up Left Ctrl,29 Left Shift,42 # Left Alt,56 # fail, but not need to remap !!!!! Right Shift,54 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/fr/hatari.1000066400000000000000000000167011504763705000232540ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "HATARI" "1" "2008-03-14" "Hatari" "" .\" Please adjust this date whenever revising the manpage. .SH "NAME" Hatari \- emulateur Atari ST(e) .SH "SYNOPSIS" .B hatari .RI [options] .RI [diskimage] .SH "DESCRIPTION" Hatari est un emulateur d'Atari ST(e) pour Linux, FreeBSD, BeOS ainsi que tout systemes d'exploitation supportant la bibliotheque SDL. .PP Avec Hatari vous pouvez lancer des jeux, demos ou des applications ecrites pour l'Atari ST ou STE. Il supporte egalement les images de disquettes les plus couramment utilisees a savoir *.st et *.msa. Il permet aussi l'emulation de disque dur. .PP Pour lancer l'emulateur une image ROM du TOS est requise. EmuTOS est une implementation libre du TOS fournie avec Hatari. Malheureusement cette derniere n'est pas entierement compatible avec le TOS original donc certains programmes risquent de ne pas fonctionner correctement. Pour cela, il est recommande d'utiliser une ROM TOS issue d'un veritable Atari. .SH "OPTIONS" Les options sont classées en sous catégories: .SH "Options générale" .TP .B \-h, \-\-help affiche les options de la ligne de commande .TP .B \-v, \-\-version affiche les informations sur la version .TP .B \-\-confirm-quit Whether Hatari confirms quitting .TP .B \-c, \-\-configfile utilise un fichier nomme explicitement comme fichier de configuration en lieu et place du ~/.config/hatari/hatari.cfg .SH "Options d'écran .TP .B \-\-monitor selectionne le type de moniteur (x = mono/rgb/vga/tv) .TP .B \-f, \-\-fullscreen demarre l'emulateur en mode plein ecran .TP .B \-w, \-\-window demarre l'emulateur en mode fenetre .TP .B \-z, \-\-zoom agrandir les petites résolutions (1=non, 2=oui) .TP .B \-\-frameskips Saute images apres chaque image affichees pour accelerer l'emulation (0 <= x <= 8) .TP .B \-\-borders Show screen borders (for overscan demos etc) .TP .B \-\-spec512 Hatari uses this threshold to decide when to render a screen with the slower but more accurate Spectrum512 screen conversion functions (0 <= x <= 512, 0=disable) .TP .B \-\-bpp Force given internal bitdepth (x=8/16/32, x=0 for autodetection). 8-bit color depth may be useful for older host computers .SH "Options de VDI" .TP .B \-\-vdi\-planes utilise une resolution VDI etendue avec une profondeur de bit de (x = 1, 2 or 4) .TP .B \-\-vdi\-width utilise une resolution VDI etendue avec une largeur de (384 <= w <= 1024) .TP .B \-\-vdi\-height utilise une resolution VDI etendue avec une hauteur de (200 < h <= 768) .SH "Options d'interface" .TP .B \-j, \-\-joystick emule le joystick ST sur le 0 ou 1 avec le clavier .TP .B \-\-printer active le support de l'imprimante et ecrit les donnees dans le fichier .TP .B \-\-midi active la sortie MIDI experimentale vers un fichier mentionne par un nom .TP .B \-\-rs232 active l'emulation experimentale du RS232 via un fichier/device nomme explicitement .SH "Options des disques" .TP .B \-d, \-\-harddrive utilise comme un disque dur emule .TP .B \-\-acsi emule un disque dur ACSI avec un fichier image .TP .B \-\-fastfdc accelere l'emulation du FDC (peut entrainer des erreurs dans certains jeux ou demos) .SH "Options de mémoire" .TP .B \-s, \-\-memsize fixe une quantite de memoire pour la RAM emulee, x = 1 a 14 MiB, ou 0 pour 512 Ko .TP .B \-t, \-\-tos specifie une image ROM TOS a utiliser .TP .B \-\-cartridge utilise une image ROM de cartouche (fonctionne uniquement si l'emulation de disque dur et VDI etendue sont desactivee) .TP .B \-\-memstate Load memory snap-shot .SH "Option du processeur" .TP .B \-\-cpulevel specifie un CPU (680x0) a utiliser (TOS 2.06 uniquement!!) .TP .B \-\-cpuclock Set the CPU clock (8, 16 or 32 Mhz) .TP .B \-\-compatible utilise le mode de compatibilite plus fidele mais plus lent que le mode 68000 .SH "Options diverses du système" .TP .B \-\-machine selectionne un type de machine (x = st, ste, tt ou falcon) .TP .B \-\-blitter active l'emulation du blitter .TP .B \-\-dsp emulation du DSP du Falcon (x = aucune, factice ou emulee) .TP .B \-\-sound Régle le son (x=off/low/med/hi) .TP .B \-\-keymap charge un fichier de refinition du clavier de .SH "Options de déboguer" .TP .B \-D, \-\-debug active le deboggueur integre .TP .B \-\-log Sauvegarde le rapport vers le fichier (peut aussi etre "stdout" ou "stderr") .TP .B \-\-trace Activate debug traces, see \-\-trace help for tracing options .SH "COMMANDS" Les touches de raccourcis peuvent etre parametrees dans le fichier de configurations. Par defaut, les parametres sont: .TP .B AltGr + a enregistre l'animation .TP .B AltGr + g fait une capture d'ecran .TP .B AltGr + i touche patron: quitte le mode plein ecran et met la fenetre en icone .TP .B AltGr + j active l'emulation joystick via les touches de directions .TP .B AltGr + m (active/desactive) la souris dans la fenetre .TP .B AltGr + r eteint le ST (a chaud) .TP .B AltGr + c eteint le ST a froid (comme le bouton original d'allumage) .TP .B AltGr + s active/desactive le son .TP .B AltGr + q quitte l'emulateur .TP .B AltGr + x change la vitesse normale/maximum .TP .B AltGr + y active/desactive l'enregistrement du son .TP .B AltGr + k sauvegarde l'etat de la memoire .TP .B AltGr + l restaure l'etat de la memoire .TP .B F11 change le mode entre plein ecran et fenetre .TP .B F12 active les commandes GUI de Hatari .br Vous pouvez avoir besoin de tenir la touche SHIFT en mode fenetre .TP .B Pause Ouvrira le deboggueur, s'il etait active avec l'option \-\-debug .SH Clavier d'Atari ST emule Toutes les autres touches du clavier PC agissent comme celles de Atari ST donc si vous appuyez sur ESPACE sur votre PC il en resultera sur le clavier d'Atari ST un appuis sur la touche ESPACE. Les touches suivantes ont une signification speciales : .TP .B Alt Agira comme la touche ALTERNATE du clavier ST .TP .B left Ctrl Agira comme la touche CONTROL du clavier ST .TP .B Page Up Emulera la touche HELP du clavier ST .TP .B Page Down Emulera la touche UNDO du clavier ST .PP .B AltGr Agira comme .B Alternate tel sauf si vous appuyez sur les touches speciales d'Hatari. La touche .B right Ctrl est utilisee comme le bouton feu d'un joystick emule que vous aurez active precedement par l'emulation du joystick via les touches du clavier. Le touche de directions agiront comme les touches de directions sur l'Atari ST tant que l'emulation du joystick par le clavier est inactive. .SH "VOIR AUSSI" La documentation originale du programme, habituellement en /usr/share/doc/. .PP La page d'accueil d'Hatari : https://www.hatari-emu.org/ .SH "FICHIERS" .TP /etc/hatari.cfg (ou /usr/local/etc/hatari.cfg) le fichier de configuration global d'Hatari .TP ~/.hatari/hatari.cfg Le fichier de configuration de l'utilisateur personnel d'Hatari .TP tos.img L'image ROM du TOS qui sera charge a partir du repertoire de donnees d'Hatari si aucun argument n'est specifie sur la ligne de commande ou dans le fichier de configuration. .SH "AUTEURS" Cette page du manuel a ete ecrite par Marco Herrn , pour le projet Debian et modifiee par la suite par Thomas Huth pour les versions plus recentes d'Hatari .SH "TRADUCTEUR" Benoît TUDURI hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/hatari-ui.html000066400000000000000000000303211504763705000240560ustar00rootroot00000000000000 Hatari Python / Gtk UI

Hatari Python / Gtk UI

Contents

Introduction

Hatari UI is an out-of-process user interface for the Hatari emulator and its built-in debugger which can (optionally) embed the Hatari emulator window. Having the UI in another process allows doing it with a higher level language and avoiding adding GUI toolkit dependencies to Hatari itself.

The UI is done with PyGtk i.e. in Python language, using the Gtk widget set. it is an additional UI, the built-in Hatari SDL UI is not being replaced or going anywhere!

Why another UI?

While the built-in SDL UI for Hatari has its good points, it has also many minor shortcomings:

  • There is no UI for the debugger or for viewing trace/debug logs.
  • The configuration UI itself is not configurable and although its quite nice for an UI done with C and libSDL, it is still very hard to modify (e.g. its layout).
  • It does not currently support UTF-8 or internationalization and is not scalable enough to support localization well (translations need up to 1/3 more space).
  • The file selector does not support unicode (all file systems use nowadays Unicode file names) nor bookmarks or other goodies in modern file selectors.
  • Invoking the UI stops the emulation.
  • It does not allow configuring all the things that one can configure e.g. from command line (Spec512, fast forward, Falcon DSP settings, trace settings and bios-tracing, log files and levels).

As you can see from the screenshots, the features offered by the new UI are nice also on desktops, but the configurability is especially important for devices that have constrained input methods; limited set of keys and e.g. high DPI touchscreen. Something like this UI is really a must on devices with no right or middlebutton, where use of stylus or finger makes double clicks hard because the pixels are so small that hitting the same place twice is not easy and which lacks standard Atari keys.

One of the targets for the UI configurability and features was Nokia N8x0 (Linux) devices with 226 DPI 800x480 touchscreen and limited number of keys. This reflects slightly in the UI design too. For example text pasting is done in separate dialog instead of in an inline control and one can easily add buttons for synthetizing specific key presses.

Current features

  • Besides the UI menu, user can specify whether there are toolbars, where they are (on left, right, top or bottom, or in a separate panel window) and what controls the toolbars have (from a predefined set).
  • Optional support for embedding the Hatari window (UI keyboard shortcuts would prevent those keys from being used with Hatari so keyboard shortcuts are disabled when Hatari window is embedded).
  • User can specify buttons which synthetize specified text strings or Atari keycodes to the emulator. There is also a dialog for pasting text (e.g. from other programs) to Hatari.
  • An UI for the Hatari debugger and dialog for changing the emulation tracing points at run time.
  • An ability to pause Hatari completely.
    (SDL UI cannot do this because SDL does constant polling even when Hatari is completely idle; on OSX this prevents screensaver from starting and on laptops it drains the battery.)
  • Support all main options that can affect Hatari speed (frameskip, fastforward, spec512 and sound) without a need to reset the emulation and dialogs for configuring the other main Hatari options (which require reset).
  • Gtk file selector used for floppies, harddisks, ROMs etc.
  • Saving and loading Hatari configurations (user can have multiple setups between which he can switch such as STE+color for demos vs. TT+mono for applications).

See screenshots for more details.

Potential future features

There are several other features that would be nice in the new UI:

  • Support for less often used Hatari options.
  • Multiple views to registers, emulated memory areas or disassembly which are refreshed by the debugger UI each time the emulator is stopped.
  • Debugger breakpoint support.
  • Trace/debug log view window with log level controls.
  • Saving/loading the configuration for Hatari UI itself.
  • Saving Hatari configuration changes to separate file from configuration file Hatari normally reads.
  • UI internationalization and localization (after somebody asks for it).

Known issues

There are some issues which cannot be handled in an external UI (without additional support from Hatari):

  • Hatari window embedding to the Hatari UI window is done by telling Hatari to reparent its SDL framebuffer X11 window under (given) Hatari UI window XID. Therefore embedding will only work under X11, not under Wayland (even Xwayland), Mac or Windows.
  • Hatari UI does not notice nor adapt if user changes Hatari configuration options from within Hatari itself (as Hatari does not currently tell about them). This is not very serious issue as with Hatari UI, the configuration changes are supposed to be done through it, not by using the internal Hatari UI.
  • To take Machine setup "Use harddisk" option disabling into use, it is not enough to reboot the emulation. You need to re-run Hatari as Hatari does not support doing that externally at run-time.

Getting Hatari UI

Hatari is available from major Linux distributions and Hatari UI is included with it. The latest version can be found from the Hatari Git repository: https://framagit.org/hatari/hatari/-/tree/main/python-ui

Portability

Besides Linux, the UI could eventually be used even on Windows by first installing the PyGtk dependencies listed on (otherwise unrelated) Gramps application Windows dependencies page. Additionally the Hatari control socket code needs to be ported to Windows (i.e. use WinSock or have proper Cygwin build of Hatari with unix domain socket support).

Mac should not be a problem, under its UI glitz it is mostly BSD unix.

Screenshots

Current UI screenshot

Current Hatari UI uses standard Gtk menu and toolbars instead of the button boxes used in older versions. You can still configure the buttons included to the top / bottom / left / right of the Hatari screen though.

Hatari UI with the About dialog open:

Hatari UI

Options:

hatari-ui.py --embed --right "about,|,run,pause,forward,|,reset,|,quit"

Screenshots for older versions

While the older UI is bit different, these option examples and their screenshots show how to specify elements and their layout for the Hatari Python UI.

Hatari UI with the About dialog open:

Hatari UI

Options:

hatari-ui.py --embed
--top "about,run,pause,reset,screenshot,setup,quit"
--panel "Input,..."
--panel "Speed settings,..."
--bottom "debug,trace,peripherals,Speed settings,Input"

An example UI configuration without the embedding option:

No embedding

Options:

hatari-ui.py
--top "about,run,pause,quit"
--panel "Function keys,..."
--panel "Other keys,..."
--right "setup,debug,trace,Function keys,Other keys"
--bottom "sound,spec512,|,fastforward,|,frameskip"

A panel with controls for configuring performance:

Speed panel example

Options:

--panel "Speed settings,frameskip,>,fastforward,|,spec512,|,sound,>,close"

Another panel, with buttons for keys and mouse button events:

Input panel example

Peripherals settings dialog:

Peripherals settings

Machine settings dialog:

Machine setup

Debug UI:

Debug UI

Trace settings dialog:

Trace settings

Quitting with unsaved options:

Unsaved options

Command line usage


Hatari UI v1.4
==============

Usage: hatariui.py [options] [directory|disk image|Atari program]

Options:
	-h, --help		this help
	-n, --nomenu		omit menus
	-e, --embed		embed Hatari window in middle of controls (X11 only)
	-f, --fullscreen	start in fullscreen
	-l, --left <controls>	toolbar at left
	-r, --right <controls>	toolbar at right
	-t, --top <controls>	toolbar at top
	-b, --bottom <controls>	toolbar at bottom
	-p, --panel <name>,<controls>
				separate window with given name and controls

Available (panel/toolbar) controls:
	|		Separator between controls
	>		Start next toolbar row in panel windows
	changes		Latest Hatari changes
	pause		Pause Hatari to save battery
	bugs		Hatari bugs
	path		Device & save file paths
	recanim		Record animation
	sound		Sound settings
	forward		Whether to fast forward Hatari (needs fast machine)
	joystick	Joystick settings
	uireadme	Hatari UI README
	todo		Hatari TODO
	trace		Hatari tracing setup
	input		Simulate text input and mouse clicks
	hatariui	Hatari UI home page
	full		Toggle whether Hatari is fullscreen
	harddisk	Hard disk images and directories
	uirelease	Hatari UI release notes
	authors		Hatari authors
	shot		Grab a screenshot
	lconfig		Load configuration
	release		Hatari release notes
	floppy		Floppy images
	hatari		Hatari home page
	recsound	Record YM/Wav
	mails		Hatari mailing lists
	debug		Activate Hatari debugger
	reset		Warm or cold reset Hatari
	display		Display settings
	machine		Hatari st/e/tt/falcon configuration
	sconfig		Save configuration
	device		Toggle Midi, Printer, RS232 peripherals
	load		Load emulation snapshot
	save		Save emulation snapshot
	about		Hatari UI information
	quit		Quit Hatari UI
	manual		Hatari manual
	run		(Re-)run Hatari
	compatibility	Hatari compatibility list
	<panel name>	Button for the specified panel window
	<name>=<string/code>
			Synthetize string or single key <code>

You can have as many panels as you wish.  For each panel you need to add
a control with the name of the panel (see "MyPanel" below).

For example:
	hatariui.py --embed \
	--top "about,run,pause,quit" \
	--panel "MyPanel,Macro=Test,Undo=97,Help=98,>,F1=59,F2=60,F3=61,F4=62,>,close" \
	--right "MyPanel,debug,trace,machine" \
	--bottom "sound,|,forward,|,full,|,quit"

if no options are given, the UI uses basic controls.

Keycodes

To configure the keys, you need Atari keycode map matching your TOS language / version. Thorsten Otto has documented them here: http://tho-otto.de/keyboards/ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/hatari.1000066400000000000000000000715651504763705000226560ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "HATARI" "1" "2020-11-27" "Hatari" "" .\" Please adjust this date whenever revising the manpage. .SH "NAME" hatari \- Atari ST/STE/TT/Falcon emulator .SH "SYNOPSIS" .B hatari .RI [options] .RI [directory|diskimage|program] .SH "DESCRIPTION" Hatari is an Atari ST/STE/TT/Falcon emulator for Linux and other Systems which are supported by the SDL (cross-platform graphics, input and sound) library. .PP With Hatari one can run games, demos or applications written for Atari ST, STE or Falcon. Atari TT support is experimental. Hatari supports the commonly used *.st, *.msa and *.stx disk images, and hard disk emulation. .PP To run the emulator a TOS ROM image is needed. EmuTOS, a free implementation of TOS is shipped with Hatari. It boots faster than original TOS versions and doesn't need separate HD drivers, but some buggy (typically floppy only) programs won't work correctly with it. For best compatibility, it is recommended to use a TOS ROM from a real Atari. .PP As an argument, one can give either a name of a directory that should be emulated as a virtual GEMDOS hard disk, a floppy disk image or an Atari program that should be autostarted. In the last case the program's directory will be used as the C: drive from where this program will be started. These shortcuts correspond to "-d

", "--disk-a " and "-d --auto C:\\" options. .PP Booting will be done from the disk image or directory that's given last on the command line, either as an option or an argument (and which corresponds to A: or C:). .SH "OPTIONS" Hatari options are split into several categories: .SH "General options" .TP .B \-h, \-\-help Print command line options and terminate .TP .B \-v, \-\-version Print version information and terminate .TP .B \-\-confirm\-quit Whether Hatari confirms quitting .TP .B \-c, \-\-configfile Read additional configuration values from , these override values read from the global and user configuration files .TP .B \-k, \-\-keymap Load keyboard mapping from . "Symbolic" mapping will be used as fallback for keys not defined there .TP .B \-\-country Set EmuTOS ROM country code on Mega/ST/STe machines lacking NVRAM, when EmuTOS indicates supporting multiple ones. In 512k EmuTOS images, country code selects the TOS keyboard layout and screen refresh (US = 60Hz NTSC, 50Hz PAL otherwise). In 1024k EmuTOS images (coming with Hatari binaries and supporting multiple languages), country code selects also TOS language. Alternatively, one can use "tos-lang-change" tool from EmuTOS project to modify country code in the ROM image file itself. That works also for TOS v4 .TP .B \-\-layout Set NVRAM keyboard layout value. While both TT and Falcon machines have NVRAM, only TOS v4 and EmuTOS 512k / 1024k ROM versions support multiple layouts. Regardless of whether keyboard layout change is done through the ROM country code or NVRAM setting, it may impact your key mappings in Hatari key mapping files, Hatari Python UI arguments, or key injection in your automation scripts for Hatari debugger, command FIFO or hconsole tool .TP .B \-\-language Set NVRAM language value. While both TT and Falcon machines have NVRAM, only TOS v4 and EmuTOS 1024k ROM versions support multiple languages. Default is taken from the LANG environment variable .TP .B \-\-fast\-forward Fast-forward through the boring parts by running emulator at maximum speed. Done by skipping frame update VBL waits. Upper limit for frame skipping is given with the --frameskips option and shown in statusbar "FS" field .TP .B \-\-auto Autostarts given program, if TOS finds it. Program needs to be given with full path it will have under emulation, for example "C:\\DIR\\PROGRAM.PRG". This is implemented by providing TOS a virtual INF file for the boot drive (A: or C:), which tells TOS to start the given program .B \-\-fast-forward-key-repeat Use keyboard auto repeat when using fast forward mode (default true) .TP .SH "Common display options" .TP .B \-m, \-\-mono Start in monochrome mode instead of color .TP .B \-\-monitor Select monitor type (x = mono/rgb/vga/tv) .TP .B \-\-tos-res Select TOS resolution for color monitors (x = low/med/high/ttlow/ttmed) .TP .B \-f, \-\-fullscreen Start the emulator in fullscreen mode .TP .B \-w, \-\-window Start the emulator in windowed mode .TP .B \-\-grab Grab mouse (also) in windowed mode .TP .B \-\-resizable Allow window resizing .TP .B \-\-borders Show ST/STE/Falcon screen borders (for low/med resolution overscan demos) .TP .B \-\-frameskips Skip frames after each displayed frame to accelerate emulation (0=disabled, >4 uses automatic frameskip with given value as maximum) .TP .B \-\-slowdown Slow down emulation by factor of x (used as multiplier for VBL wait time) .TP .B \-\-mousewarp To keep host mouse better in sync with Atari mouse pointer, center it to Hatari window on cold reset and resolution changes .TP .B \-\-statusbar Show statusbar (with floppy leds etc etc) .TP .B \-\-drive\-led Show overlay drive led when statusbar isn't shown .TP .B \-\-max\-width Preferred / maximum Hatari screen width .TP .B \-\-max\-height Preferred / maximum Hatari screen height. Maximum width and height options are part of Hatari's Atari monitor emulation. They limit the size Hatari should aim for its internal SDL framebuffer, and how much of the Atari screen borders are visible. The framebuffer is then scaled to the Hatari output window based on the specified Hatari zoom factor (see below). Aim of this is to have all resolutions show up in approximately same size, like on a real Atari monitor. Hatari's internal integer scaling support sets some limits on this, so it's an expert option. Note: Only reason to change the defaults, should be limiting this to a smaller resolution for performance reasons, e.g. for video recording, or on really underpowered systems, to make monitor do all of the ST-low resolution scaling by forcing Hatari to ask SDL for CGA / QVGA resolution. .TP .B \-z, \-\-zoom This option overrides max width/height options so that e.g. ST-low resolution gets always doubled, and all resolutions (except TT-high) have approximately the same size, like on a real CRT monitor. Zoom factor is then used to scale that up (or down) to the Hatari output window. This way scaling results always in approximately same sized Hatari window. With non-integer zoom factors, linear scaling is used to smooth out the output, with integer zoom factors, scaling is done using nearest neighboring pixels for sharper output. This applies also to window resizes. To avoid zooming of low resolutions, use "--zoom 1 --max-width 416 --max-height 276" (if you don't need borders, 320x200 size is enough). Disabling low resolution doubling like this is not recommended for Falcon emulation because TOS v4 bootup and some demos switch resolutions frequently. .TP .B \-\-bpp Force internal bitdepth (x = 8/15/16/32, 0=disable) .TP .B \-\-disable\-video Run emulation without displaying video (audio only) .SH "ST/STE specific display options" .TP .B \-\-spec512 Hatari uses this threshold to decide when to render a screen with the slower but more accurate Spectrum512 screen conversion functions (0 <= x <= 512, 0=disable) .TP .B \-\-video-timing Wakeup State for MMU/GLUE (x=ws1/ws2/ws3/ws4/random, default ws3). When powering on, the STF will randomly choose one of these wake up states. The state will then affect the timings where border removals and other video tricks should be made, which can give different results on screen. For example, WS3 is known to be compatible with many demos, while WS1 can show more problems. .SH "TT/Falcon specific display options" Zooming to sizes specified below is internally done using integer scaling factors. This means that different Atari resolutions may show up with different sizes, but they are never blurry. .TP .B \-\-desktop Whether to use desktop resolution on fullscreen to avoid issues related to resolution switching. Otherwise fullscreen will use a resolution that is closest to the Hatari window size. (enabled by default) .TP .B \-\-force\-max Hatari window size is forced to specified maximum size and black borders used when Atari resolution doesn't scale evenly to it. This is most useful when recording videos of Falcon demos that change their resolution. (disabled by default) .TP .B \-\-aspect Whether to do monitor aspect ratio correction (enabled by default) .SH "VDI options" .TP .B \-\-vdi Whether to use VDI screen mode. Doesn't work with TOS v4. TOS v3 memory detection isn't compatible with larger VDI modes (i.e. you need to skip the detection at boot). Original TOS desktops use wrong window size in 2-plane (4 color) VDI mode when screen height >= 400 pixels. Because of these issues, using EmuTOS is recommended for VDI mode .TP .B \-\-vdi\-planes Use extended VDI resolution with bit depth (x = 1, 2 or 4) .TP .B \-\-vdi\-width Use extended VDI resolution with width (320 < w <= 2048) .TP .B \-\-vdi\-height Use extended VDI resolution with height (160 < h <= 1280) .PP Because TOS and popular GEM programs have problems with certain screen sizes, Hatari enforces restrictions on VDI screen size. In total VDI screen size is limited to 32-300kB, width to multiple of 16/planes, and height to multiple of 8 pixels (smaller system font height). That translates to following maximum standard resolutions for the VDI mode: .TP .B monochrome FullHD (1920x1080), WUXGA (1920x1200) and QWXGA (2048x1152) .TP .B 2 plane mode (4 colors) HD (1280x720), WXGA (1280x768) and XGA+ (1152x864) .TP .B 4 plane mode (16-colors) qHD (960x540), DVGA (960x640) and WSVGA (1024x600) .SH "Screen capture options" .TP .B \-\-crop Remove statusbar from the screen captures .TP .B \-\-avirecord Start AVI recording. Note: recording will automatically stop when emulation resolution changes. .TP .B \-\-avi\-vcodec Select AVI video codec (x = bmp/png). PNG compression can be \fImuch\fP slower than using the uncompressed BMP format, but uncompressed video content takes huge amount of space. .TP .B \-\-png\-level Select PNG compression level for AVI video (x = 0-9). Both compression efficiency and speed depend on the compressed screen content. Highest compression level (9) can be \fIreally\fP slow with some content. Levels 3-6 should compress nearly as well with clearly smaller CPU overhead. .TP .B \-\-avi\-fps Force AVI frame rate (x = 50/60/71/...) .TP .B \-\-avi\-file Use to record AVI .TP .B \-\-screenshot\-dir Directory for saving screenshots (default Hatari directory for them is OS specific). .SH "Devices options" .TP .B \-j, \-\-joystick Emulate joystick with cursor keys in given port (0-5) .TP .B \-\-joy Set joystick type (none/keys/real) for given port .TP .B \-\-printer Enable printer support and write data to .TP .B \-\-midi Whether to enable MIDI device support (when Hatari is built with PortMidi support) .TP .B \-\-midi\-in Enable MIDI support and write raw MIDI data to (when not built with PortMidi support) .TP .B \-\-midi\-out Enable MIDI support and read raw MIDI data from (when not built with PortMidi support) .TP .B \-\-rs232\-in Enable MFP serial port support and use as the input device .TP .B \-\-rs232\-out Enable MFP serial port support and use as the output device .TP .B \-\-scc\-a\-in Enable SCC channel A serial port support and use for the input (only for Mega-STE, TT and Falcon) .TP .B \-\-scc\-a\-out Enable SCC channel A serial port support and use for the output (only for Mega-STE, TT and Falcon) .TP .B \-\-scc\-a\-lan\-in Enable SCC channel A LAN port support and use for the input (only for Mega-STE and TT) .TP .B \-\-scc\-a\-lan\-out Enable SCC channel A LAN port support and use for the output (only for Mega-STE and TT) .TP .B \-\-scc\-b\-in Enable SCC channel B serial port support and use for the input (only for Mega-STE, TT and Falcon) .TP .B \-\-scc\-b\-out Enable SCC channel B serial port support and use for the output (only for Mega-STE, TT and Falcon) .SH "Floppy drive options" .TP .B \-\-drive\-a Enable/disable drive A (default is on) .TP .B \-\-drive\-b Enable/disable drive B (default is on) .TP .B \-\-drive\-a\-heads Set number of heads for drive A (1=single sided, 2=double sided) .TP .B \-\-drive\-b\-heads Set number of heads for drive B (1=single sided, 2=double sided) .TP .B \-\-disk\-a Set disk image for floppy drive A .TP .B \-\-disk\-b Set disk image for floppy drive B .TP .B \-\-fastfdc speed up FDC emulation (can cause incompatibilities) .TP .B \-\-protect\-floppy Write protect floppy image contents (on/off/auto). With "auto" option write protection is according to the disk image file attributes .SH "Hard drive options" .TP .B \-d, \-\-harddrive GEMDOS HD emulation. Emulate harddrive partition(s) with contents. If directory contains only single letter (C-Z) subdirectories, each of these subdirectories will be treated as a separate partition, otherwise the given directory itself will be assigned to drive "C:". In the multiple partition case, the letters used as the subdirectory names will determine to which drives/partitions they are assigned. If is an empty string, then harddrive's emulation is disabled .TP .B \-\-protect\-hd Write protect harddrive contents (on/off/auto). With "auto" option the protection can be controlled by setting individual files attributes as it disables the file attribute modifications for the GEMDOS hard disk emulation .TP .B \-\-gemdos\-case Specify whether new dir/filenames are forced to be in upper or lower case with the GEMDOS HD emulation. Off/upper/lower, off by default .TP .B \-\-gemdos\-time Specify what file modification timestamps should be used, emulation internal (atari) ones, or ones from the machine (host) on which the machine is running. While Atari emulation and host clocks are in sync at Hatari startup, they will diverge while emulation is running, especially if you use fast forward. Default is "atari". If you modify files accessed by the Atari side, directly from the host side while Hatari is already running, you may want to use "host" option .TP .B \-\-gemdos\-conv Whether GEMDOS file names with 8-bit (non-ASCII) characters are converted between Atari and host character sets. On Linux, host file name character set is assumed to be UTF-8. This option is disabled by default, in case you have transferred files from Atari machine without proper file name conversion (e.g. by zipping them on Atari and unzipping on PC) .TP .B \-\-gemdos\-drive Assign (separately specified) GEMDOS HD to given drive letter (C-Z) instead of default C:, or use "skip" to specify that Hatari should add GEMDOS HD after IDE and ACSI drives (assumes Hatari and native HD driver parse same number of partitions from the partition tables in HD images) .TP .B \-\-acsi = Emulate an ACSI hard disk with given BUS ID (0-7) using image . If just a filename is given, it is assigned to BUS ID 0 .TP .B \-\-scsi = Emulate a SCSI hard disk with given BUS ID (0-7) using image . If just a filename is given, it is assigned to BUS ID 0 .TP .B \-\-scsi\-ver = Emulate specified SCSI version (1-2) for given BUS ID (0-7). If just a version is given, it is applied to BUS ID 0 .TP .B \-\-ide\-master Emulate an IDE 0 (master) hard disk with an image .TP .B \-\-ide\-slave Emulate an IDE 1 (slave) hard disk with an image .TP .B \-\-ide\-swap = Set byte-swap option (off/on/auto) for given IDE (0/1). If just option is given, it is applied to IDE 0 .SH "Memory options" .TP .B \-\-memstate Load memory snap-shot .TP .B \-s, \-\-memsize Set amount of emulated ST RAM, x = 1 to 14 MiB, or 0 for 512 KiB. Other values are considered as a size in KiB. While Hatari allows 14 MiB for all machine types, on real HW, ST/STE can have up to 4 MiB, MegaSTE/TT up to 10 MiB, and Falcon up to 14 MiB RAM. .TP .B \-\-ttram Set amount of emulated TT RAM, x = 0 to 1024 MiB (in 4MiB steps) .SH "ROM options" .TP .B \-t, \-\-tos Specify TOS ROM image to use .TP .B \-\-patch\-tos Use this option to enable/disable TOS ROM patching. Experts only! Leave this enabled unless you know what you are doing! .TP .B \-\-cartridge Use ROM cartridge image (only works if GEMDOS HD emulation and extended VDI resolution are disabled) .SH "CPU/FPU/bus options" .TP .B \-\-cpulevel Specify CPU (680x0) to use (use x >= 1 with EmuTOS or TOS >= 2.06 only!) .TP .B \-\-cpuclock Set the CPU clock (8, 16 or 32 Mhz) .TP .B \-\-compatible Use a more compatible 68000 CPU mode with better prefetch accuracy and cycle counting .TP .B \-\-cpu\-exact Use cycle exact CPU emulation .TP .B \-\-data\-cache Emulate >= 030 CPU data cache .TP .B \-\-addr24 Use 24-bit instead of 32-bit addressing mode (24-bit is enabled by default) .TP .B \-\-fpu FPU type (x=none/68881/68882/internal) .TP .B \-\-fpu-softfloat Use full software FPU emulation (Softfloat library) .TP .B \-\-mmu Use MMU emulation .SH "Misc system options" .TP .B \-\-machine Select machine type (x = st, megast, ste, megaste, tt or falcon) .TP .B \-\-blitter Enable blitter emulation (ST only) .TP .B \-\-dsp Falcon DSP emulation (x = none, dummy or emu, Falcon only) .TP .B \-\-rtc\-year With the default value 0, RTC date and time are taken from the host. If application does not handle current dates, this can be used to change RTC year to a more compatible one. See also "--gemdos-time" option. .TP .B \-\-timer\-d Patch redundantly high Timer-D frequency set by TOS. This can increase Hatari speed significantly (especially for ST/e emulation) as the original Timer-D frequency causes large amount of extra interrupts to emulate. .TP .B \-\-fast\-boot Patch TOS and initialize the so-called "memvalid" system variables to by-pass the memory test of TOS, so that the system boots faster. .SH "Sound options" .TP .B \-\-mic Enable/disable (Falcon only) microphone .TP .B \-\-sound Sound frequency: 6000-50066. "off" disables the sound and speeds up the emulation. To prevent extra sound artifacts, the frequency should be selected so that it either matches evenly with the STE/TT/Falcon sound DMA (6258, 12517, 250033, 50066 Hz) or your sound card frequencies (11025, 22050, 44100 or 6000...48000 Hz). Check what your sound card supports. .TP .B \-\-sound\-buffer\-size SDL's sound buffer size: 10-100, or 0 to use default buffer size. By default Hatari uses an SDL buffer size of 1024 samples, which gives approximately 20-30 ms of sound depending on the chosen sound frequency. Under some OS or with not fully supported sound card, this default setting can cause a bigger delay at lower frequency (nearly 0.5 sec). In that case, you can use this option to force the size of the sound buffer to a fixed number of milliseconds of sound (using 20 is often a good choice if you have such problems). Most users will not need this option. .TP .B \-\-sound\-sync The emulation rate is nudged by +100 or 0 or \-100 micro-seconds on occasion. This prevents the sound buffer from overflowing (long latency and lost samples) or underflowing (short latency and repeated samples). The emulation rate smoothly deviates by a maximum of 0.58% until synchronized, while the emulator continuously generates every sound sample and the crystal controlled sound system consumes every sample. .br (on|off, off=default) .TP .B \-\-ym\-mixing Select a method for mixing the three YM2149 voice volumes together. "model" uses a mathematical model of the YM voices, "table" uses a lookup table of audio output voltage values measured on STF and "linear" just averages the 3 YM voices. .SH "Debug options" .TP .B \-W, \-\-wincon Open console window (Windows only) .TP .B \-D, \-\-debug Toggle whether CPU exceptions invoke the debugger .TP .B \-\-debug\-except Specify which exceptions invoke debugger, see .B \-\-debug\-except help for available (comma separated) exception flags. .TP .B \-\-lilo Boot m68k Linux using kernel, ramdisk, and kernel arguments specified in the Hatari configuration file [LILO] section. Hatari documentation folder contains an example "lilo.cfg" config file for this. String given to the \-\-lilo option is appended to the kernel command line. .br NOTE: This is Hatari (and Linux kernel) developer option to test Linux booting. Unless you know how your kernel is configured, and the state of specific kernel and Hatari features, don't expect m68k Linux to boot up successfully. .TP .B \-\-bios\-intercept Enable/Disable XBios command parsing. XBios(11) Dbmsg call can be used to invoke Hatari debugger. XBios(20) printscreen calls produce also Hatari screenshots. XBios(255) allows Atari programs to use Hatari debugger functionality, which allows e.g. invoking shortcuts and Hatari command line options. Last one is deprecated as it gives too much control to emulated program, please use NatFeats and remote control APIs (--natfeats, --cmd-fifo, hconsole) instead of XBios 11 and 255. .TP .B \-\-conout Enable console (xconout vector functions) output redirection for given to host terminal. Device 2 is for the (CON:) VT52 console, which vector function catches also EmuTOS panic messages and MiNT console output, not just normal BIOS console output. .TP .B \-\-disasm Set disassembly options. 'uae' and 'ext' select the disassembly engine to use, bitmask sets disassembly output options and 'help' lists them. .TP .B \-\-natfeats Enable/disable (basic) Native Features support. EmuTOS uses it for debug output, and it's supported also by the Aranym emulator. For more info, see example code and readme.txt in tests/natfeats/ coming with Hatari sources. .TP .B \-\-trace Activate debug traces, see .B \-\-trace help for available (comma separated) tracing flags .TP .B \-\-trace\-file Save trace output to (default=stderr) .TP .B \-\-msg\-repeat Toggle whether successive repeats of identical log or trace messages will be suppressed, so that only their count is shown (default=suppress). Disassembly, register and (multi-line) AES traces bypass this feature .TP .B \-\-parse Parse/execute debugger commands from .TP .B \-\-saveconfig Save Hatari configuration and exit. Hatari UI needs Hatari configuration file to start, this can be used to create it automatically. .TP .B \-\-control\-socket Hatari connects to given local socket file and reads commands from it. Use when the control process life-time is longer than Hatari's, or control process needs response from Hatari .TP .B \-\-cmd\-fifo Hatari creates the indicated FIFO file and reads commands from it. Commands can be echoed to FIFO file, and are same as with the control socket. Hatari outputs help for unrecognized commands and subcommands .TP .B \-\-log\-file Save log output to (default=stderr) .TP .B \-\-log\-level Log output level (x=debug/todo/info/warn/error/fatal) .TP .B \-\-alert\-level Show dialog for log messages above given level .TP .B \-\-run\-vbls Exit after X VBLs. Used with --benchmark mode .TP .B \-\-benchmark Start in benchmark mode (use with --run-vbls). Same as --fast-forward mode, except it cannot be disabled at run-time, and FPS will be printed on emulation exit (and pausing) regardless of log level. Allows better measuring of emulation speed, in frames per second. Unless you're specifically measuring emulator audio and screen processing speed, disable them (--sound off/--disable-video on) to have as little OS overhead as possible .SH "INPUT HANDLING" Hatari provides special input handling for different purposes. .SH "Emulated Atari ST joystick" Joystick can be emulated either with keyboard or any real joystick supported by your kernel / SDL library. First joystick button acts as FIRE, second as SPACE key. .SH "Emulated Atari ST mouse" Middle button mouse click is interpreted as double click, this is especially useful in Fast Forward mode. .PP Mouse scrollwheel will act as cursor up and down keys. .SH "Emulated Atari ST keyboard" Keys on the keyboard act as the normal Atari ST keys so pressing SPACE on your PC will result in an emulated press of the SPACE key on the ST. How the PC keys are mapped to Atari key codes, can be changed with keyboard config file (-k option). .PP The following keys have special meanings: .TP .B Alt will act as the ST's ALTERNATE key .TP .B left Ctrl will act as the ST's CONTROL key .TP .B Print will emulate the ST's HELP key .TP .B Scroll lock will emulate the ST's UNDO key .PP .B AltGr will act as .B Alternate as well as long as you do not press it together with a Hatari hotkey combination. .PP The .B right Ctrl key is used as the fire button of the emulated joystick when you turn on joystick emulation via keyboard. .PP The cursor keys will act as the cursor keys on the Atari ST as long as joystick emulation via keyboard has been turned off. .SH "Keyboard shortcuts during emulation" The shortcut keys can be configured in the configuration file. The default settings are: .TP .B AltGr + a record animation .TP .B AltGr + g grab a screenshot .TP .B AltGr + i boss key: leave full screen mode and iconify window .TP .B AltGr + m (un-)lock the mouse into the window .TP .B AltGr + r warm reset the ST (same as the reset button) .TP .B AltGr + c cold reset the ST (same as the power switch) .TP .B AltGr + d open dialog to select/change disk A .TP .B AltGr + s enable/disable sound .TP .B AltGr + q quit the emulator .TP .B AltGr + x toggle normal/max speed .TP .B AltGr + y enable/disable sound recording .TP .B AltGr + k save memory snapshot .TP .B AltGr + l load memory snapshot .TP .B AltGr + j toggle joystick emulation via cursor keys .TP .B AltGr + F1 switch joystick type on joy port 0 .TP .B AltGr + F2 switch joystick type on joy port 1 .TP .B AltGr + F3 switch joystick type for joypad A .TP .B AltGr + F4 switch joystick type for joypad B .TP .B AltGr + b toggle borders on/off .TP .B AltGr + f or F11 toggle between fullscreen and windowed mode .TP .B AltGr + o or F12 activate the Hatari options GUI .br You may need to hold SHIFT down while in windowed mode. .TP .B Pause Pauses the emulation .TP .B AltGr + Pause Invokes the internal Hatari debugger .SH "Keyboard shortcuts for the SDL GUI" There are multiple ways to interact with the SDL GUI. .PP TAB and cursor keys change the focus between UI elements. Home key moves focus to the first dialog item, End key to the last one. Initially focus is on the default UI element, but focus changes are remembered between dialog invocations. .PP Enter and Space invoke the focused item, ESC key invokes the dialog cancel option (if there is one). .PP UI element which name has an underlined character can be invoked directly by pressing Alt + key with that character. Alt + arrow keys will act on dialog arrow buttons. .PP Main interactions: .TP .B Options GUI main view Enter accepts configuration, ESC cancels it. .TP .B Options GUI dialogs Enter (or End + Enter if focus was moved), returns back to main view. .TP .B Fileselector Page up and down keys move the file list by one page, mouse wheel and Alt + cursor keys scroll it by one item. Enter on the focused file name selects it. Enter on the OK button accepts the selected file. ESC cancels the dialog/selection. .TP .B Alert dialogs Enter accepts and ESC cancels the dialog. .SH "SEE ALSO" The main program documentation, usually in /usr/share/doc/. Among other things it contains an extensive usage manual, software compatibility list and release notes. .PP The homepage of Hatari: https://www.hatari-emu.org/ .PP Other Hatari programs and utilities: .br .IR hmsa (1), .IR zip2st (1), .IR atari\-convert\-dir (1), .IR atari\-hd\-image (1), .IR hatariui (1), .IR hconsole (1), .IR gst2ascii (1), .IR hatari_profile (1) .SH "FILES AND DIRECTORIES" .TP /etc/hatari.cfg (or /usr/local/etc/hatari.cfg) The global configuration file of Hatari. .TP ~/.config/hatari/ The (default) directory for user's personal Hatari files; .B hatari.cfg (configuration file), .B hatari.nvram (NVRAM content file), .B hatari.sav (Hatari memory state snapshot file which Hatari can load/save automatically when it starts/exits), .B hatari.prn (printer output file), .TP /usr/share/hatari/ (or /usr/local/share/hatari/) The global data directory of Hatari. .TP tos.img The TOS ROM image will be loaded from the data directory of Hatari unless it is specified on the command line or the configuration file. .SH "AUTHOR" This manual page was written by Marco Herrn for the Debian project and later modified by Thomas Huth and Eero Tamminen to suit the latest version of Hatari. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/000077500000000000000000000000001504763705000225535ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/callgraph.png000066400000000000000000000516701504763705000252270ustar00rootroot00000000000000PNG  IHDRMHsRGB0PLTE-/,IIF00iheOOwv.^f9 pHYs+tIME (#tEXtCommentCreated with GIMPW IDATxhɚ/Z&׫@e  c2bBx`0y!!-!71\g&1a +0d8;&ogs#cB `ps;i4`fY8nc˖#իnuWwUu[NEe鮪_?U ك6] @ŖݶBwӄwE:ΨCסl wh=@XO{.TD+z6FCnրpԺb"}. "unwzb#{;te]4#FTK/Cݨ!יձwҠ~П 'Tzx+ѣTzi'b%}tB6h6"V~-?7 mx,R~}JU'V7ߡkW¾H!plCצPTuq}RK5q1({E2P6"V"M"t6#r_ہX D@#~D)U+4t;`eJ6J0ł($J6J ut5pA6ʔ]@t+:#TmBsi7"OD+ct?"zw]>{RM8a@%?ޡ ]kO.%`雇?û&Vѭ\S!o7-ibݕ|"qzibyRcu"ه" vEw¹䦈03󡅗ab eB{.6Ewud1j' FV\?R=JJ,eϔC*GwJ§z)UEGA(`M7UY:R$x<p+{ =T<&ĊyZ5zJ% Ub+^tԠMR5[]϶E=䵣TMªڃXq[fq)Iͬ $V6! g%'M^;/#EجF8i_Fy7*<|@8sHݴ\ȴ li+>tOۢ;L"!T e@ײVD8òy[GеnJ"!VϏt-?L8K g(DR]DXkt7 &)VJL5QϖfboWl+i PcłL<&mw!A}&8J%i J:q_ +&^-J (S\(„ -o]١ ׏9<{@xm L5]ʶFXqˣDYKՅn3VGw[Yxk :Ċ]Afj(bŎ.!S=$ M"7|߮"W5"lS\W$&_2{i׉Čk vTUKA0j*B\DeDBj4<押 *tT/־4GTF*jKjz7?EdBa<Kg zCe#0t/*ljk.Ak)w}gQ^Л BG1A N#qJj&AȤ{3y dm&䪤8&&kj)o\dp0"s^`<R=) r Js)LR5b#Et:߭H)saZB6E1wI_8ROo*@6V@vkɃD2tI3mW%&I!lct*tIYXA'9 (si* Ou,zbo(AuXU.b3RJxH.$ FR˓Z;* sx3#lAlߩ*6YjotȆXaínxrR/͢*"ݦ 83aR%|rH=Sox&d(U)ʹ.RѦTPr޼Am4- AK Wdθ 2i wH"-(Xة<oBc.nP'y׸ċډPگAXȗ+ CS >z1o׍HWX:n69$|ĞEm*.tM$I !6C=%^ތnX 9Uf.g mV$ޭC;(sȢ3W]edcM94D%VF hl;P*(d׬KfzSwזX+L ~nh]&ft2ghOi]x ]ݏVTpOLI{TSY{xV> PLڝ/IJGadڡ;zE`{uiOx+==K]LI7ð{P_c,]*]]D#gUpSJnkPmuj\ʙ1nRغӿ4i!V}JF^gxs_u0ǪF.ẕT-i`0o'MFrv[Bӽ@Xw0lf1x={ⲃܥ.l0y*ZzB.;;1w` AGbT;.t^ ݡYUfft ZUw'}ȓ艕eс+r"oz:'Il хS +o w]ow㰠bwRhap+MtweACTyrijZT6J8<"=٥T..Y;[bP-BiZE.(>CѾCpoκ+\Ã`4K.]r̓sܧÂnq<wS}(pHЭ#.|+r]W!YF.ڀ.am;tP5pw*V Na@w%bgH1r'}$J4 %>J(C ta>2sZk{FP }b3vH-9X $kno>VnA0/S=jOŠsw-gkG4Jur}mfec>}/9y3ϥxCCצdw-Q-J喯e-X㽶@咳7*>ižv@wUpPj/Wx/t -C m/t[lyx, *r00xD|pGZY߽!P{cGl!X.,9U:m|eT㶵ыXݴKAq)rYG%F7& bkjWmhb$8בG-5Q:5yW M8@7 U|.%Hf KIùؚUX$HqK[[Stu@wRAG?Cw[[(2@u9 UR3yϞ%1rV:% {yO"Bwt\=a U(bs׭3RJcQH.8>;;Uz]Rv^ſoqjPA;8/(to Sʄ\">ZR׺R~O-\_X[ZTtO|yt& ~O8\MWF$EuK̡SqM!H 2(/m. aꥬ{ @TWniP㓊UT\X[юW\l7oX80mcK''1N̖/A;suxw\uK{[8=hew:]Sڈ[݆V=I/Fz7= 妹%@u&!C9_Ҫ(t\zd`RV׻6^:cE e11?dLCH%Vvuu %\$GU~Ot29h]icwUH[#u!3i@BYS򶘪6ꥬ">M{8x9fb% 6GarVW7lY1KAw Oz3zOnj y{dU9>TꖱsS `7yD]st #+!  eV*%.'q枣6mT2)kSwY~rKGrB:MEV\ڟJ9,h@P[架E=f%Ru]42JM]sjOEttt7}hJ8mlr$w?p"7tDbzY`BnKd7t#1uEiL5> oz3wj]ռA(B6޹5ABЪxgƼȦcFכ@hE,w=قEd|tpErS V[;(īݻ<'l llU)K/+7oWt*N}JR"~(|PlsK57iU6uߤϝY/Zx!F׃7ҹ./N)pN3z ~"e_ /Q~i/6D$ jtc5Z<_[}v/;^aB~ ^~}aE5MH]1gRǍ.p!Eܝ9Ujn v1$b&uScC g FEWݿ?~5 |U#ڌ&kTމ*8 [Qi16 ]qn/4y{5aG ә{-Gܭ5 |+AtW }ɏutim]fq0hڇnW,]NF}!NoH"ӜNl2A>\^eHklc{BӚ'|q+.W6c4yw\h)k"pGp٥ZZdK6c]כr薹ݝ/leCā xgUlh;ӐhxkǶ]0)^_^/ LqP{q:EF'@t{Ֆ:(kc&B.6B>.K0z_؋A7e?ww f|%|4 G0x%&15?IU:ŦeKʘP+l6qv.XG@W]T}-㝇l ߘY6^6'Uݬ8"R .A{4a^;gM,sw̭kjx!Y=P\h^O#zlbX]йk>MzU3]"YmᆮhB#.|Gw39͜gSw00NJkM1 wSG[bw׿sQ3ʨD2߹>3NrOBw*'>K *uޙծ{+4[Ees~[`2ZE fN n>2T<'ʥ|wƋ'~х{cVPղvhnEՠGrªfW8J3oj;Zti̦t&I[O )4}YQ)ф?G9(D-uIJwf50oi]sЭh4R gOxCtDfyANMۭJ(+ȬFh,0rMšCwsuj~wgTޘՃ^}!iǷ] g^;bT/ⴢB (jLY}0E"ҫAfL#L) ̰<*aʼTo%( <ҩĬC\`EΩ84z,|SVԤtV?x2*EFfe%$j8~VD OqrZQNvڧْ*P'q<{3FfeV @?䜼 p;`@™4ymd?5(+ K;XE⌉Yٲ0D@#J̰gÝ /9-H25e3uJ{Qq0+{jb ]^P֐zM딊gTz&OM RؠKoqv =u (R0kd0l.P/DU㊮ ~$: gK9}rmVҵ2ijJ'-B83HgF2If&OrQiM)t i95n}Eo"$&+ 3(=8^FĬf#3 9`UD9CS^ 7tM{R,A(h~O)DF1r4wm9=ZT>mCSH^( IDAT 7tMT].JݓMvQy, Kܙ{C9z14 .j8l0k¬&T. 83~Ź'VE<ۣ33/mf)L zhJ\ǂB2.G }Ϛ%F(au%u⹸0+ %bW & ]z{:GePedZUbbVV͂rKo* n݌8pm<toӣkQ$ׄd (rggC5 V^qbVM ,gӎ)ц=Agڠ6Y\nnA.E %:n$9ɢ^q`V6у`kކR _(bKW]6iיִ2H%Ǽa$=?Hn9gs免/܆F8$Mx-\2ힾf _;G %\j.VED0*7fely)Z6}682颵>@&RPzeC7\V(ǽF̬ls65+ֆF8Į GmRq]4ב@YYλ8]UΨRP>f5C[COyIYK(Z(ŜxEꮌΤh(Q!#i$ŬjD;bEhS)\ȾXѵݰTLU?M䏢j?8FǬjK ~/I-Q@)Ȋ̜Î.bZPEO 8$r`+j;Z(}iݫⰣ;l#-c<1wcʬv?Uf˝>S7_ *?^@jc.j7ӻ<U׻XY) ]d|Q]>tGbVqXG,lj&Ff-!0 gN+Gtlڬ 6?;O$pd(D}b*?Нõc6|Q,)$px( 5'j? pu#8ڊGG2ژ:!)J )DȄ4y5ΒPt7iDs E~}zko,a-)=-Wyn]t0SQF &;e :t9lL٭\]՚p3@Ό%@^ſd&pY9@)cB˙`ڿsτW\aC7.7!wM2Ka|UL5e :y@A2*92@-%@ˇ H+~*Bj#(ĔX !#YknJӉt ~\ T0ULU${9ӄ >eLR9%雌Eh?&dyJigBoN|Nct%U_f2z9,1"%:)%Ƽ׏;DʍQ#j#MB^}}(zJzpw'pYcD s"2a꽢-F CX%2 Kɹ1㻍v$9d-NE9svNnBӻ ldAv+ĒaGnO!/J93" ]q3n!7ю k VhzCcs{(@̫:(sfΨhmvnWʉTO r͉@9'R[cp)cȉDsN$ ^ NL3`-[KNz3H2XlA1A27#96" B,v-Rxe(qP<^@/ǤRBzH&4,BS ' kĮ#Rt?u 9]a/ۈ#Űf [KkB]2c#5Rc5xbF`Bn]`3wöP2 S4w}tX!^hХe;@ŶPe.һƃε$~ DH~X]&ⴄq=MHL7AѭL?6b,OwnÊ}=U&]:ZCjuʐ褭ZbdT `byZ{:FFC7(.XҶC1levmlJ8 $0ft׺K4ZoFaA ׽)A獄G:ڟ ͞7."F[aB} yiFa)Q(ō1WUG~%߯J[aBӔ&5.-\ʀnhPFžK"לoڔf5 #.T.]}"h!aVC‰Y((O$%OdaAXͭ)Jm9Mƶ:E33r(0>c6$euJchniDY숦=^ii=^W4KSlVF]$bw /86;G`VN‚Q5'4M1$ڰKz~_kɘ5'̩`@{(.\6ft<"?DGW+֓,L*Z3MG]ݰGDE#r>îxw;WhD™~ -Yb\q?НFwgxwzt|wn[v>;ݹm}=o9%wDFf5li<⇃"!Jtab(]PySՋ{Dfej.,XӑGBc-0*b{ЅoωS2 ]o[[ZQ. |y>+̊Q1Sfϫ1=.\jڮ:Ӕߦ۰nfɨX[VN `@'3@n,m8_Q1d~ Y*τ.hOn wP QdƠe(Æ.*k dGpw})Nj\ʬ{Hn^aF=nkzD Ut2&1=1zߥM{&t|PaIleC?UR5C*H@S{h}pWGFEM~)\ƏЅmR1JlS|aTp BuHqHUd\;fQQ2K%0vtw~a '<^Rq^0vt>^,*A>1*,a1t9\oGbnt"ڐM~Q(|B>ktZZEz^e(;e?Q]q1P}cACﯲ&GPFՐJ7@x)@ת^h3sv{*zqϏK ?e8\RH(:sIU/3+ь1rbv|QuHh \GnxQ l?ۡ˒el brN/w.cNo9Jti9{ݽ<5XEnQb(5FXF{ yaܖy2|,> 9S[@DP{@ >)^a+,;Dކ]gT-vEN_ 0=TKBzlBxX=˅Tʜvf%N^zW=/BSæ2+]$نK0ˢYL EwoE0=`X1#_X.I?9Nf5cRbх=KWϤq]'ˆ/¥x W=gQoemGuqo; {fIA_<ꡕ啁| eG3Q2^^K6 ڝ/i<b!J GVx~7>?Nӭ>8q;38TWdFtE: eKڭaF7Tt!q4=f&9Q!. 3 g6Mߡٻ_lH6lIa!F7Lt(֋$_9^täxBa?1JxYB;Vv2ʳO)^ЅL.l^}u˸e7Ǚ>NڡV>4jCYh vzu3]wS!UbVٗ DQ{ --v/J']J'9x:r{ 6^D21B='p$U*,(Q[meoOãx}EIwS*t =Y8&)(.Cn&|xw.ܑG7# nW&?8FTD?èxEyCf>i *cy|)R)? x#HTdHʩ ɠH)k!U4Znh6 % K{94ki wIf8,SEMRѕЅt,ͥMZnh6WcBaGM4wutQϟ,㷟1IM.r =Pm]DElf'ZwãxJk+@ӻ*ȂX5`f ] tLoJ6H]&1te-K A zW-uLnt74$HjXo EZl QqQEw2[[-ʻ Z¢x=@ i.7=2$5t\g`h @}LJCnH/IqYO^ ׶*xzzHaEW>a'Uw$ 4S nUˣk##?yū#7ܢх+}lS疊ejIR|  .3{pmˣkr@(^nknf٩B׫Zv^_.GkeIU\*FKGkMz}W_ArD>C9b.Iد? Z&CW1U!2Ң0Ad^ߕ&ѤЧ1TXIb6sfk76JA+퇮psdGOQ -- kAbC**4 jһ õjX2_ R(^i?tU}t2Ң0IiHтn9_)ef_.2 3_wp#tŲTHq(pt>d|){vA[fiBMLW:NmIz*^HaFܥs0J́3;XT7b~̾ˁqdGL*5ۜvmGtZm>REF}&sG$>28.er}ŃVjUs#_ɇ]b<0/mF7i4Zu6.O,MK %a] RˡKVsA@7Hˇ4w'LѢڭяvL:}Wn;'2pˇ%/U'Oxkړߎkt6xyѽ/~d@yt/&q'Jxy]O};! ]Jxkf<{y$D ٴgawm6 R)^nOtaVmWŅ96x}ӌׂU/Sm#ڴ q?̺/ghȌbuc՞snMu{&BWƝ/Ήz@ Z'x~b(^NtڝxNh Vtd$a2.l 嵈ѭ]"x ja9!zQn7ݻחDE46uw qy).BpHGw\\ʅB6W9KXc^{o(sN1r?|N/syݳd5(^ntki4:Vqi\[-jH0DGN癷7 ?Vfg_El>A7s2.DݝcDG[/7iuN-C 2gta?*G,iƹ.vhAb@m%GAwsž|++9ERqԂ7@n[0pO[&tu}綪IzAE \ȺՋdF4 u=ܷN `w e{o.Qoξd t"+ٯAhy[#^:8ջݰ7DxŸ5"Lƺ>6ؼZDtatN)wD>/ж5Bax Nkk񏻿>tZ"+i@wmr!]]{w}rz1!EsϵӾ{ǯf~PcJ9+;T \)zu}~4*Ҩ䄷zMVN0G,YMd^ )t,`Շ7$[ŗK'te[clo^XQZ /~I09$IDAT;"BG|:X1S٥֣䠲/bEЭ쮑)$*:NGٍ uh.Wr׻HgѨ %HNGӘS?0 /]FDDeDj]8"9N ٻMJtyT.Msdg )0NvKP&6c}t)EOgw:CF(90)+K;$ӑGw:Y+f 7Mh.(Ft ;yХ>./hskZ螂||ʒ#Id@)4VikxKFvJ.7t躅BdNлng&a "o[ .V&&0]90œ$VV:8aji8|w㳄!B^,u,}ˑ[R L) A2+}FWyT;Լ%aE]h |J}iSoB8x%y!- U3 ƉvЯ}DWmXPߓZ5٥J]qO-B4N"~ 7ki7t;(]vʇRa)?WJtAUJut+rx]E5ϒOhA7= v)QuRcnU)!9k#﷽w?VKJnBQY8./܅㙬݂fmfNwpoO}L7, U:ۥ!2ޅ؄(iݲ j&=K̶vl6@9=n5ytİ;܄;_r] C&ƪxݖsڻ>ڧN a]Gy*'OYR@2-'#9 ktVOi\ҲR]S~kv86=zE}J?8nS20dhڊOa?=P2g3oPtysUR@»ۊSU#63f\=i ^gRco)W>;a/Vx)ΦMj % )ġtٖثH,taJ#{mmK'`e[ aS JZT6y3c f)o.OEȟšwIf,\-ư]z[(rB86ɤJDVŴmv8{mQ2HVEBFb K B1 f; {l*ɔ :Xo]8z$7=;%q5㆟q$ԛA]7'; ئ+i sMWb&:))IO7JNgV;11H qLɝAOW;\_Khn 9epS^)e59(*R`'ǝG07q rGbcd3K:p%Q&eeUA\)Q|! DÎ8NU))46w+O4Y 1tTePU\Ä8#ɬnuK\iAB06l+a.);%ܕQMէ.u3{W@"!˺PZU<%=G@{”*<uPTlHlFt $% uaSqY7Wz%s=]}oAG7`A+]g>s!qf6w5t\aAs ܪ-t ]¿ 5te'-5nsp,h݂.kZ8wa%+Gx&։}g F&V:A-%m' xHZLaidċ<) $$1j3`EHFJXw$o ͋q90zdA]R:@`6bH6 _{nk%,]C@ALWdmA(%4эCd|U%a2iZ]a;{|[i"hs9[^Q+`3Ҡ+MƁ\ GеscKpi߂t.m J2d@j,7K-!i$sW[i稦wssa*LkkYhB eN)-4wP9Q!}*=̐sZNx 2S4@61T SX핰L An.~7qSm!m>#ޅӜn'.] ]聴ěQa,p.U2h;.́BLwN\ \j \.R,-:Of +w|!m$]D:YVK]})~s=sձ>ēV{>]mS{$?T[w9p+r3OF7^VJ\n|tVrTB}pջ4"]r7b#yş :^jsץ^ %qNrI[.a'yF?z؍3SѼ̨8D_!K#S1W&~kϙѭM]0%}[r>ZgCI BU'N^7u]hFxۑlٙpJEč N%&ur-fuP1qlpFy-12ͳ!gnSv=4E堲]ҬЭ)K'3xV+ ;,&չ3<ԣ tiRyś֢+d Ye^ջ=EzW.[z4"]{OB-T)<~LW{F83O[Y-@֞3-,fb\*uy/a@2hNeⲴkqݟ`z\w<:e󳝷sهln#t=m.w + {9aK a#_e?I}2S|}.o2ݷ"gS>WPg"݆$|y%Ofm{h![`oq߲R%}We0@ty~5Ch$Nb3.;,v);vAi+[NqLx;H7٢Yl%qzm)Mv̓)s 䴤?^u9PtG;UTO&ge[ȱ(ykW>>Du0PtVG:2xe-2TuQMf_~y#`u=p>=νf(.~(EXUO.ΪzXMk;sf*ʿAp[0.ۊATg/_8qgOgRW¿Q{7f ]*x\O0ᄆwcD] Pʃ-@Az9g|躹9r+}CqH:ODT>Y|4agҗW4zuJ ٍ-*TIMNovTsKJ w3*t\Nj}ntÚ]JɑY]q][x8l^}tеJ z]DcdegOM~}l#@i(^q85ep^Xg߂. AE{Vrd 8-j5O ^N\r'ƋAEŰܽ bM 2h"DytJJo)H/?8<3"&ԑz񆁜UbZZl&73gy2N)ڲ(_iYr{+|Ki5̍|mKtozwmO2}t;5Mn* kllѽ >e}F`r{++H4 SWa]{7C70G2{3pxO Αaz+Vbmr. ;n R(._~o+t'PRX(XKw;[ C-vY2 profile Executed instructions for badmood-1-frame-CPU.txt (Hatari v1.6.2+ (May  4 2013), WinUAE CPU core) own cost emphasis (gray bg) & total cost emphasis (red) limit = 2.00% nodes which are subroutines and have accurate total costs, have diamond shape 24 leaf and/or intermediate nodes below 0.20% were removed N_48100 4.30% (own: 0.25%) 5819 visplane_tryflush (15 calls) N_480B2 4.05% (own: 0.05%) 5486 flush_visplanes (7 calls) N_48100->N_480B2 visplane_tryflush+86 ($48156) N_48780 0.98% 1326 R_BSPHyperPlane_RHS (587 calls) N_48780->N_48780 R_BSPHyperPlane_RHS+4 ($48784) 567 calls =96.59% N_48728 0.03% 46 R_PopBSPNode (23 calls) N_48780->N_48728 R_BSPHyperPlane_RHS+18 ($48792) 3 calls =13.04% N_4872E 3.82% 5170 R_BSPHyperPlane (36 calls) N_48780->N_4872E R_BSPHyperPlane_RHS+24 ($48798) 11 calls =30.56% N_481F8 0.06% 77 ssector_node (16 calls) N_48780->N_481F8 R_BSPHyperPlane_RHS+26 ($4879a) 6 calls =37.50% N_48206 0.41% 550 build_ssector (15 calls) N_48206->N_48100 build_ssector+116 ($4827a) N_482A0 2.04% 2766 segment_loop (50 calls) N_48206->N_482A0 build_ssector+150 ($4829c) 15 calls =30.00% N_4A3E8 0.47% 630 process_lighting (15 calls) N_48206->N_4A3E8 build_ssector+112 ($48276) N_48716 0.07% 96 R_RenderBSPNode (16 calls) N_48716->N_48728 R_RenderBSPNode+16 ($48726) 16 calls =69.57% N_4819C 0.01% 20 finish_tree (1 calls) N_48BDC 0.63% (own: 0.05%) 857 get_flat_floor (2 calls) N_4819C->N_48BDC finish_tree+64 ($481dc) 1 calls =50.00% N_48B74 0.35% (own: 0.08%) 470 get_flat_ceiling (3 calls) N_4819C->N_48B74 finish_tree+40 ($481c4) 1 calls =33.33% N_4879E 0.75% 1015 R_BSPHyperPlane_LHS (449 calls) N_4879E->N_4879E R_BSPHyperPlane_LHS+4 ($487a2) 433 calls =96.44% N_4879E->N_48728 R_BSPHyperPlane_LHS+18 ($487b0) 4 calls =17.39% N_4879E->N_4872E R_BSPHyperPlane_LHS+26 ($487b8) 8 calls =22.22% N_4879E->N_481F8 R_BSPHyperPlane_LHS+30 ($487bc) 4 calls =25.00% N_483BA 0.26% 357 seg_prelight_done (21 calls) N_482A0->N_483BA segment_loop+-498 ($480ae) 1 calls =4.76% N_482A0->N_483BA segment_loop+278 ($483b6) 20 calls =95.24% N_486CC 0.03% 36 sector_window (36 calls) N_482A0->N_486CC segment_loop+248 ($48398) 15 calls =41.67% N_486D0 0.20% 270 seg_invisible (50 calls) N_482A0->N_486D0 segment_loop+118 ($48316) 14 calls =28.00% N_48728->N_4872E R_PopBSPNode+2 ($4872a) 17 calls =47.22% N_48728->N_481F8 R_PopBSPNode+2 ($4872a) 6 calls =37.50% N_48C74 2.41% 3264 stack_visplane_area (13 calls) N_48BDC->N_48C74 get_flat_floor+90 ($48c36) 2 calls =15.38% N_49C2C 39.97% 54144 render_wall_1x1 (18 calls) N_4872E->N_48780 R_BSPHyperPlane+80 ($4877e) 20 calls =3.41% N_4872E->N_4879E R_BSPHyperPlane+80 ($4877e) 16 calls =3.56% N_480B2->N_48BDC flush_visplanes+42 ($480dc) 1 calls =50.00% N_480B2->N_48B74 flush_visplanes+62 ($480f0) 2 calls =66.67% N_48B00 3.25% (own: 1.69%) 4401 get_ssector (4 calls) N_480B2->N_48B00 flush_visplanes+10 ($480bc) N_494B4 34.82% (own: 27.62%) 47161 render_flats (1 calls) N_494CC 27.56% 37335 render_flats_1x1 (1 calls) N_494B4->N_494CC render_flats+20 ($494c8) N_47438 0.28% 378 cache_resource (21 calls) N_48B74->N_48C74 get_flat_ceiling+86 ($48bca) 3 calls =23.08% N_483BA->N_486CC seg_prelight_done+456 ($48582) 6 calls =16.67% N_48650 0.33% 450 sector_wall (15 calls) N_483BA->N_48650 seg_prelight_done+64 ($483fa) N_48C44 2.28% 3086 init_stategroups (1 calls) N_486CC->N_486D0 sector_window 36 calls =72.00% N_494CC->N_47438 render_flats_1x1+62 ($4950a) 3 calls =14.29% N_49438 7.16% 9701 stream_texture (3 calls) N_494CC->N_49438 render_flats_1x1+66 ($4950e) N_486D0->N_482A0 seg_invisible+18 ($486e2) 35 calls =70.00% N_486E6 0.21% 278 do_ssector (15 calls) N_486D0->N_486E6 seg_invisible+12 ($486dc) N_4815C 59.72% (own: 9.63%) 80888 descend_bsp (1 calls) N_4815C->N_48716 descend_bsp+60 ($48198) 1 calls =6.25% N_48B00->N_48C74 get_ssector+100 ($48b64) 8 calls =61.54% N_48650->N_486CC sector_wall+120 ($486c8) 15 calls =41.67% N_486E6->N_48716 do_ssector+46 ($48714) 15 calls =93.75% N_49B28 42.19% (own: 1.70%) 57144 add_wall_segment (18 calls) N_486E6->N_49B28 do_ssector+20 ($486fa) N_49ACC 2.84% 3851 add_partition_segment (6 calls) N_486E6->N_49ACC do_ssector+20 ($486fa) N_49BEA 40.49% (own: 40.25%) 54844 render_wall (18 calls) N_49BEA->N_49C2C render_wall+62 ($49c28) N_49BEA->N_47438 render_wall+26 ($49c04) 18 calls =85.71% N_47A6C 100.00% (own: 0.09%) 135449 display_engine (1 calls) N_47A6C->N_494B4 display_engine+302 ($47b9a) N_47A6C->N_48C44 display_engine+286 ($47b8a) N_47A6C->N_4815C display_engine+294 ($47b92) N_491EA 3.04% 4120 initialise_freetable (2 calls) N_47A6C->N_491EA display_engine+6010 ($491e6) N_49B28->N_49BEA add_wall_segment+186 ($49be2) N_481F8->N_48206 ssector_node+12 ($48204) N_481F8->N_4819C ssector_node+2 ($481fa) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/cpu.png000066400000000000000000000154471504763705000240630ustar00rootroot00000000000000PNG  IHDR.ŦbKGD pHYs.#.#x?vtIME;b=IDATxk8ಣ;t]m39Oݚe HE8aV}k#8Ȟ܆a;%r$8MnQVrS4T .i.%Htֽĵ{[̱k#bVI'wǻ\]nos_[n;hR߿2MG2s{-_4Bn(N6.= Ȅr|P)JL#3ɭnR/Pz͍ؖF{;=O ޿#~lS|A,A- @g.IptSp1S`HpHp Hp  Hp pO|kՎ.^ċx!W|;H]axs6Xƿ[[;es%}[YN҇SsW>'ة+/ŋ\+./@S b{s&>s#ݺ$ł'g MtI2wΞh]bs#Fpů[ iҜ^}2)n4l7ځWc/>` ~/-f x)WڍvCz}kn?"/9ϫ)Fx|(Wo/G+~=xnZQ4]v-^ʨ1Gf1[U:88\]L rkW#Z]ɑxI/sl~߯+ʾGUK 50 uMdP/ίx)[o;EK=8$<xAzp˪_Dχzp%GR20>'zpʲ=^&^r/YS (ۉ#ͅ%rJ6:38U[}'</Geݻɱvzp%/a+2P+㪖_rhˑcn6^V]RPCA_2^/Yzp ٿI=7-ߏzpHp9y"ԃ ,7^vϾm}ghhK=yv}4zw$yg%@#+\9 (Y“$l^Z7W&/p9Xtt$6R~߯K"$(ċx/䈗a'8(mHp Hp V`ԕm&;1 CҚp(җ& eZx fi[n#F:aZdy}A6luwHr)dsHp\4\?v6b][nd{{EN;{EeIH̭/{/'GGd.|`G8W>NߋsV/.coODEYvX G\ܖ:۫tK<]tmDg}[qps9ZbLQVԇVW״Kvu?S[+Hp$!1m'풫SU-+:R{tqk Fp$/}`k]ߙuN7DC,mZDv˙8USƩ)~xT/HeH?rr@uK]r*ej@@ $8@  $8@  $8bOgh9Eh*EW4 n-eC O=\,ؾ@?GeDrbܶs \;%vyG8fHpF2r/N2r6u|g;v{E4Y&yn2T\5w54Yfz_h>QM~Ni~}{9zo}ح4ujJMpFN`m}k\$9Ng׷:守ӉGe-<6hQ)UlM Qej>xs|f@˜jjOKΑTIont޷轑[z .%Q㐘#Gosk"OХ޿Ze.&bl{mߖsts\~^$+~D^} PvrKp '0Kɔ"]&H:zD0uB{,UHpY$镶Ρvr9FVsȂЕ܇u; |SS,񦬷xաSKpM,uG߲y;VuswV[W,?v:t9;bSu`35]փ=;AHܟ֮ܣOj7GЭ:tepCST]#Q:f\ΡR)ۚk:t׷F?V[=3Kujj?uՔY9^VҜw8TជB";{K}dn][ri+wIHy'~[r6>M %<)em=8_dm"+p .)vS{vM z`,G48ijZ+%I؊ ׷`sqyVY>EK~Ubxfv!^%nK&=ܓ%9 p9MW&c)̮~CI^vy{lW:o{u{{ߩUj#U9^0;S*Kӫ뵫R_ƉdkΑ-KtV ,գGs[e.RoOۢ/;zE5?o5y:xn[TMVG=av&f;ZV(>Z%ӓ):RuJ+;fGLFmss/J@9g܁EOujY:o|#G[[ڟMG/>ɭ=]Ճ&unʺqKy=ZɄ<պԃwzpWu`)ELpudkgݘ u /EY\5,q} 5ә.:\\cJ==;. tTYuπr܃+uIS 0Jtx]܏)@'g״3@#I.xs;z}[6hgj@j]%蒇L0Kɔ"]&H:zGjyIwS{i"OE+n.KR)F$WkǦ8_.@L Nj fʄy>ڞzkg3TmGt>O'#=BRsZ$B}&rs۳ݭe# ƀC?o݊5\-::=#˭Mt}ręk7Lp9ZSI-""#֋%&p4mg5^HhmqkRpN T2w=sRסɋ^` tDZgznO}B:or{K9I=E|=8(C=807X zphZ>MQLpvJ_l*Fw@$f jz}Y6) ;?O;9V.\#F#H$>lŎG[.zl}nwB[;}9{m]tn޶9Ͻ7pS9;9lwõw昺tl{Ю4sO >^@][# WN6zsz}493##Q\N%F9Gemgwad}.:{/#H%v9z;5==3˕쬎GM̽[-`WqT_>ܙ{9{--8j:g-Fo\󻧮B7պԃ#9saХ $8@  $8@ $8$8@ $8WO|>1 C9RMEQM֟"NJ)*$9(0MC+(HpJ#zdn2g|.6:s9{g{i}y&SKSH"ƭ%{/{w$!,ݿ=֗ǿ{ & n)!M{KVy"9]s]InPV>W9(qϴծ"Zig $a)%>Y_jߚ _{|k(1]q|ZB:hg~߯+ʾ֕zakOQ~oI=ٟTS{ؑiՔOyZ5U=}f]]&8$8o2K]JrCR?U(@ $8@ $8@@}iVcy^A_ing%:f#8x<_~wlWl 0~wKzp.@#ak9I 'S hֹFwt{{h2ڶ#ٷ1E Hp Hp 4? t?ѢwdIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/devices-portmidi.png000066400000000000000000000020601504763705000265260ustar00rootroot00000000000000PNG  IHDRrK~ PLTEfbKGDH pHYs B(xtIME ˄tEXtCommentCreated with GIMPWIDAThK0 PAOate>z 9Q.#ݴ"%۱7$-'E?';/߂#}^Y_GBpoB!$I8ꎲ= ("AaQ [(pM >2; 9"!!;F A?"@>Z}^ i3‡@BlyxV=kaTdܥB AT h}aKBsP;hfG*O}x"iSΎxޏ'k'8 Npv.\@^.n|G C20n;s!P2Dh8pAQA !Т!v#]r % Rw0ČP}b}QKP?!b/*J_&4 X_TI)jy;"ka FJSXo@$ qH0,$m[?B97[BnO B<6('B N/` KCԤ=V:A1A1}ÈhUq+!r}vt'8 k~a>;*?Ro#4Uj!Kj Iz^E/r-W^Jn$Y4A#X`8_{ʵ {"Z(sQµzEA.w:siڌ5~z]Dą1bk~A`.ŹF#!o턶!#Խo!]#tG‘3k~&}ͼ@yA'2}T~!OxOzPwYd$ʈπΎGm}VM{<'8 NpV/w\>8 NX1C_-(IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/devices.png000066400000000000000000000053421504763705000247070ustar00rootroot00000000000000PNG  IHDRQ]sRGB PLTEfbKGDH pHYs  tIME0 OIDATxA&)V.NjV.3aNU*`aH9BR e?ܒTч\5|.# /1_;VAHGEX¡"("("(".}^ %D#$ӜC4S:oF=t lF1@ [cQ_092s@-XT]gI_PF!x`_Y) $bPwж# #\rEЇnF8"$6#ཋF-KUDZEE=;"s%XsD.D㨫gDH_W/8q{pv%_`ډKz;?-Ƌ%q`)ـc9GuQP"+Ŏ|^(ɋa!0&*rp^Q.1,Jڝh>씀 geD(4`]zàĄ i(aL@=),JM͸z1+ ix8ۂ"pNΉ 88mE6OK0JY$ɋ}|P ׯ'>~R/"("("(»w|u#9(dƼn7ꄁY;o$bu܊0nvߑ542q9=%.\˅륰;$nu0;R$ou0;@[Y$_;.,; %d+~zZAAAA[aTEv]h6BxS0#l;`R.T$c .w4\Fk[0hPK|͘bV%вIj '7S)FĈM֌gCp6lmue"GR,S7 ڂ`F~!7tMzV6Dv2C؈F ``[[xwKlC$' IH(ŋ0p%Ca{s|(2L);.Ku |0nNwBĮoీL6:!4#LVdQVx玗꾍"o#LE@o}M-x w;A^n=%u-LNBp-,q}"] ]"ЙC3=wDqPl VR]~NF>cg)t4~Ƕ_)}AmExDqjp2F9]9#?L);{솬ԩGkfò84 P㟅a8"xgrr>},M"L~j'#6@yT/p38sEӸ A(O;6Ǚߑ;F5k"pF@O\bQ^s&Β,a}a_uCp}aܯi;\`8sbK0RVd镽5e)Ld镝5!KקNݯAH_X{JAN_4 e)e#2|q॔+k)EP}AT_P}A>;TEPEPE_ /F/j:ND([pBFy#+-\5)(o'} /1)Awie%m4=rEP&QG[ S~ڒc ! )0CY)")SIܯ9ZÉnHS!Kn+ ~@玊/esXIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/fileselector.png000066400000000000000000000164331504763705000257500ustar00rootroot00000000000000PNG  IHDR!PLTE@@@AAAnVbKGDH pHYs+tIME0  #iTXtCommentCreated with GIMPd.eWIDATx͎H퍽akZYUm 5w00Ҩ#'# e tı"ፈÿY-31M@4m1M@4M@0Z C?|_Cܸee65uOPfefyڧF"YL@TПeT@@Td/4?;Ϧ Hx6rX^R@ vfe WIpp (M  6tj<6E*OEӕ- RԳ?&?(`|_@? a ^a"|`TM~LUEw .#4m49-K+jc_Dx^e..S.E/`h2Ikƫ'8~] ؜Jλ˟|dp& x" UH$`tfS2Li+i2G@͐p4 Pkjza|UQ[G{X&S^TE> FcL.,̐̅,Mt& ̦%PdDb2Mw X8,fO `Lڈ8"k Q oC$v>^D`#vƷ7`tgRwa9i. 1FmBr(]D&]=c ȈT!duE ]DF4M 8_E|0[˸٦DX2HCē۝6xЫ6lM@p-Ad{Eou{W"` RgWGxPf WV? J@bgɦ֯QplD@UOl ݭy}DiF h'<@[RvnJn#>)ohmE۞QxTN ;eU0vgWR/F•ˡm"GSp4`- ފ=зS 7wMnne #>=6Æۑ |z!" A˷f][Aʧ4@:OZmY~>M^f|flHO ɧW†ۙ |l{bDE{R`mz¯d`QoKGm1!`eTx x;8,9߁o%8b{ m}\cBqŤ w$ >@,Cf<q0y=-L`?$\{p" &;ˆ k >{T1p W1ûn`zJw(`;"` L H|f;82+!`'7tS[nf51R Xbyu`z5ll a04M@4M@[L@4m1M@13I-(_& .%=,G}M ^\ h>/0"x1xOk h;z Vp׆@qnc8S@3 3eT#{&B=p1(w-dH:nb_;PMyR,x)QͿ8~sMg_;%aGz(VnR9JOiRv`A T/L@'yPPX?O1^'a `+:`ʆ]DzJ{CQtt9P$m.ڿQAP WW &_0_ƒ𪎚N4+P@*m0y1h >_QH(xxN(YO(`T1[(. bB׌/(. b(uSAL޹0ڇ vޠR( ֵ8`7@(%a#$xJeH~lGc`T\o`MK\e?tĨsi.)Al@Dz*"t\UrqwWQ#YjEaLsCn\:$򂨆<|P@%0#)h/w\t)1 JLH6gua$ l6|B. NI'myr$+o2j&o P|VEH?ҧSR{5i;m~ (ʬ/ 5b}3bP EU JSqtbrF?)&{\@7P@N(_ (."$/'شeP 7tlmcZ&fŴxky rYƠ9fX(_ߩo4u .;Xs(- ND 1isS;WNx(;FUx_sxYt+!7G,C`럮&^^l x 8]<bÇ+W*Rx_qYa' .aě/.[1[88iQ̫o,uG[ݩA*x qN̅BaJD e3#C 99D_#\rap_ Ʈ+Zb5B0%N!YE.p$?kojEqFz771_$* aC]i3_(CE7S8"Å!}-8b(ta7WHJ=_u\!BΞd4sa'[uŔl'Ӯw'`QOpH # MĩyE$`ys LaWFF(c>nWI@/OG#D8GGW?!nWM%MG#`%αOyܨi ,@.ܥV.E)t Tu<~$JF)@X (ѿFi[BPxmO qS5ݍCt23 sݹ:3 W 4ĸ]RDTѿA@<@P]3)Ay c@υl_ydt@wit:T8 ^[0-\g .޴3{ .6hB@]aϠsI |9_6xsmnN ltI ڐuu-bWyo_( 9fI,dgle*r֫]Up"Kx9zupprO@HTt.*CKvb _á ^1yCUbCK0nBWXkKq`v=X@ +Vy.c gM}uiy:Jc:]10o &*(|9+ PO*HH䖛f:a'O X&k9Ұ|bȝ~:a=yReUǭm'`TZx C$^#3.2s"K"،K _]Dx0:H,J >G"kKWr)[%_/[)ˁh!\Zx4}*DV7!!}_Jm(98]ݷu_Va6EG:N٩J%/Ԇp%$ qXau;_3T׹fyEdh `] v3,;2-2Lr?8˭d]Gcy5]Uע- n0&\hڌnx6QQQ0Sf8έuY/TK?~lۓ?FCƬkJm5rܬg̸`2I?oqScX}}pl|[k55۞lֳhF\2Sy'WC9l-Xsj7gW,2mMx5ϮeSfPeqokCS,Vn rD/@JUIG+ȱ؉l: $@ftt1кHd nGږj IDKèv["щ+""I8)5~f,)JQ9AGo6@V#vˡfp=%CcIbՉ)\F>;xϵÞn0 ʵ#.k2xl"LKP\DpQ/]tks J:e-z:t:B5iAE26i&=T&"L!?ݡD,'AW"uKu&z)[ }/=?rtX(7r߳o">BV7c- fr>\^VcW-ʂjN÷@Weo"Gsȶw폊4~:Pߨ \gC/.]zKD+6Yؓ剾7?RP 5)XWoYD$Lag]w 9?|xY70&4K51l=1u%aB|S>_0I 6OmpjGR{q;}ʭ񚋼못tg*ڼCZ~w#&MHCw,lM?=N];XLFmJ8o`UXgq?:hLW|[-?%:u? hvbc#Y~=X+W/Rt όXk ?~FZi[ &{Zc^Lv:+pA8WaOO,-X=偘6H"~ެ:\$;7,4MK+ّ%Zj[pg Z!\`#8yv,eh֛Rmtܓu~wfSjy` .jᮡd&w . 91vU)\WahcCZ+ǣ%@HC[]b P;;-5뎞 Ckt4 z5;u݄ѕ {szmuQ_'Ӷ ca4}EVKǷiSZhC&̄XV-ajj)r/ݍZzKw~X/ p}`ldk .^ b=[`C!6kc!o+hɋ W_c.{ w5O{ {        +~ %z9 /9_-/9_a_ 5/|n O z3*p x\=v궬ka|Gmfug~,Ԙsc ;MOo/tWzL7ncăwe7/k < 8]| 0 JEptIU\N ʼn$ *:B, Q"%,.i^x0⹓L$8{$%1VJ=l(H{0.\/̞2mh^=x$|C./"z[0~Izȃ3.KD vM>wUT/c1}Q125>]` /tyO ?/l.| ?/l.| ?/l.3> 5{     \>ԴxG5o߮~>Kv iMjTT9p}a>k-qjCζeVNw59'މvl^Mڴ, λ̫Az՛!~S?BKtUݰvOz~R-j_tUS{, SY6~N4#ڴ*λ1@?y_qnjY%y&z}`]a,5L#@@@@@@@@\@ye8H6Pi?SnJg̍49T"PCz˟$v/K%:Hjg'TߢWʄC3"b…QQTC98rD2BM̥!@mtA$I%E/^LT4@p2S W8d? J2&҄|"V7'H"URjFJ -|jTCrŕNGʨ(.(NK"T4?@y䐴z!'Ƿ{ɥD RKJYDꅧWd`5uqYӔ(s T"-A4R}FD-*9Kx BP'(J&N%"iHD=,  I?*; N ŃVH2 /0deT")B8mBڏrt /1 Y[MDx>pF*W1&$,3R dQ>9D//P`҇I{ˌz욇h-R?e$&KyIh7O6IbBR66T`:$A&{}4*VD|GXw%{Tzd$[cS6h,N7/ݡ`Iib5h%jU-hA88g k%\ Uraj*D t ΩTpgÅb/H9#|6:Z J"͑Nwa/sh3 */t* sj:M]2-9f/t|û\ DM gt_Iu7U^Krz[ RtЛt0wam<_cX5 E @zk^i ZA`Ud|q{Qj:нU᠗ł/9) U4QEzlL~vU0|* *   Ec eC &)o-z}I Y\ X|q bpb,xc)ls!ĠK%6z5;  !5f_*I 4qc]n".q3hbDާyuG ^6k@B j̾XLZްCUX8/#zq{?/|~/Pj·l7&ΠI*4z56s۷28qgmJA, ^_       ʅF[ڳIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/harddisks.png000066400000000000000000000120471504763705000252410ustar00rootroot00000000000000PNG  IHDRPLTE{bKGDH pHYs  IDATx] U)+@W0]wkz$NǎΡub? Af& ^_3JDv~`\D%^J~]7|oeo}+WM2w`0ODpHlFD o}1׌J-fX_{v[/wwPl -cѶ>hcNrXFԣ}m1_#o>u>Nir8[o}}9052ַ$(&0 :7,/*EM&Ds!xb>zΩU ۩=%r2t3cCRsE#B C#p,I w%H ir3]+#$57ir-F-sיuJrU7Oef b+ 1Od(^IĚAZު Kej:YC_~mP M|@$JpRIfϺ KǾ~AqLi\Fq.YM0IO"`m6Tq[~^Ti1ոOv6z(S#jK1U#)Η.oyW kOW៕QB,Ж4yC7,KWu״ǹsI]W:s JT L Wܨ2'KTI *g2ܗܗ|/w}Y|[?/T ޚ`3͊6ݗZLù"yq}}uQOecWUe@}y9_}}z_F.ܸك m' S`V_,MyXOd͈.]}i_ӣڡ/'; Z]'Wcޅ8ji?Lz6Rg[ˋ.y5_8`iy5kZͣ;] O: ԎU#<^fRXD[ ١dӆVu4͎w^yi r:z'@L/sq\ns J0P b\L! i *9f3;UE-s@%9(d:_60SRuk!d^ \=襖b]v)cy\ࢱY\c@T069 sq[{5Im7?>$hz.Kymp Oi:]v(H_~JPjxi5k=&s:uՆp6MVTN@9g֐uHķgôAh+7ۈBgr{m6PZIt*-i0jraΦVk q =uiDZdYWnqjP d@emI)3 *;M=͡<dU'UM`rx]A~=g`7ʍ5 TPUmު ީ#n5ZQ.z1~@Ų@15+e7EB7qgmBvXU 'eS$a`R^r8ntJUC>ݥe/WhW%p-;F>ū>$;G'  H8592@9 @L?#1X|.Ymn-K#r*NF\CU1A@mPf?(;d;OsR%kqhgNLE{E@j)~oOXJ,Q~Bv=~`>G-d'ˇAU,*&٠}WPٶI vd6R܃sÌPga_M ~&@C:8WAz-zqz1Ћŷ:ɧuI}߁:  B/Noנ!=@ڝz(g+яUb @&u]N"|X^T¤+:"x @.o|<> [2C`[E~" XߪbLSfPEh"hK[^|PÈ]'-X5!mwNy v)b$:  0P?`E[^T%ww>G]IfQߥl>XJ~18[/V5llYųa'q%ěTz&5-]y7Ct\,^ yW(PQ(oU*RF8x `֡Rī2 ECJ=E枔^)J;)ơAuPܹ/0g9[lC0V깩/d0AE)U<X1i$S %Ǒ!j;5['E])(Ǜ|\!d.l3ۛW~k0 y1{ #eoøXOGMf fo[w`9zIj,oMd>@i{% 3&[ïhxuX ~۾EX>B; %Hd:/x3sa^d |9D=F{LzkxI滴c3>$xP? @@  ] 4`󍻢 !C uOY) {6=*EzwRDh#֝27ZA  @$ߚ?H R%X?3g+[M<IAI%9ICWK)'@SǕwWXc~DC>~)cH݀=5TXmqDqrf.6j}Z Z+D[Vih/7E`5ȖzTy}(gEF+H.A3j+:׎H!9 fp߷$1 0WE =љ(ILhꔓnvH^ioasM5y&a&9p)'Z0Mtf~Ɨ8Afo'epZG;̄nޘs{u8÷:Yo 98 sՑFW)1r &A H [UEZGjRwf>|NH l+o%h[ B3ȖmفH@ ,}.W~ lzv)6Wnم=~~qߏdkhZ8: NSUU!r iA H 'H @$ $fjyvK.f= Ä;#0|ȨYۑѺ{/pe:C\0ta5rK3O@@ @A  "W[  .'͇D %bJDXxKd^QΒZepwh+ /t-q97.ZZ-h` -t*-gpwGYB "@ߟJ3 SIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-0.7.png000066400000000000000000000655641504763705000254660ustar00rootroot00000000000000PNG  IHDR,d PLTE@ !1 BB/T,0&gQC( 0@|%)')'+">B mV3,101@a dO./N`4474AJ0%B@azC?.A?Cn XCBDA!` #a PA3AA_jALDIzZ@;-bzt!m9Z$JJM]0@RP3CaROC H?WQ;>?RQS"yOOnVaBlVY2 7L#-xZ\#BZX\(.$_Yc`CaaVD!lbad}"tioUV~raR>aA -Ch|odf\ QT)Lrja ppnpkpxR:tqdnqo_X`@-hPVz=`>}|~ab8bca_mhNvxS@q!'Ăcdw`9Jj>桁1ç0ýеҸ`3 tRNS@fbKGDH pHYs  tIME46_ IDATxŕ]sXIb<3!^FʈZGH4$5fɚ1.ޏ5dQDt5'` xH (QwDK8/e]-kzz>Wի G9N<ȟ9N( r|A/DqGB<ʱ}d1cU1)cU#T9X"cz(x1!ǎeqcݎ[1|P-qN6op6vl׏oߡ][C9EmknsF6t&T?탂?w֊a zLp|\ v%w7dmrH?p2ڭ۶v6w"aMނ36{s.FyicKMBheh}Vb aڭa*{={Yg'?E877v]Ml/\\?}eSݤP8IZ~&ӥ!p*/6=k,І/m 󧷃wnKg-yt_ϭO_Y[p~H?s֚/3YkR;j/=؎Vyo$ _۷vӦ۶o^K];nھ)8_.,CJ|ozmO~??^r}amkW҇Ty }]vd @le4@Y}ƞ`ႵGc۵^_ٴ)o~H???ep-?Xp|v sڱ޾6g9sz{9s־}s#=|)DOYjKqp*aS;=?F R,\ǶIj̸q=UT"X6TrÃdc;xP] R,vݎ9!B<oo>n]ãBx#{w8#n]Ǎ^X?ѣhQ+%Sg쨤= st) gTxcֲ*-"zb__4}S{}jSGnOG|lC7=([ߝLqB.i|g#|~oj{`ʔDNP:pkq;kk3/.*"mw35+}}Ms5 WOj٦n~Pv߷#i?;iG5Zh) 671mf;MG{7gw C@1fR߾o4nwGiʪehSqb}iy>`}AJ|:keA}~LI0lz6o}?~sv&&;F!/76=k\*1⢲* {OK=3k4SV|:c^t̟oye}oߝ4'X8cWot@{%TaS<_i^nt0CKЋkytt̟oŰA|ݎ;AVPb{b8P*sp9w ;+wz~`~ǀ7gw,% |20и;3qh}C;fVxcEeUM75ZpZzqO7\o1~^e} ?mjj* 9c8v.7/msh`zp|S/~Wz UaTS,D/ c#/.*o~"T`wCklY5]ڧs7?ߊA}dੱJ۲ɞ5S OYz@/o(0(b,ʰ"F扑y`d^V(C!f 9YN\~ CaU`lvRb'0< ͏΄{畚8;#Eۗg=/nP`(0í#n8aX)N.xm0,}J; ち]Ogx(O_ +i C`@``8}Ϫ=#G`(0c见V 6f >vZRHCPHe.P`(DP0/DqJ!B   Z+1  911, ZPHǰ ,D`(dc>ӊxBBF&Ta"021"020t6*[Pİb,0"020tnKaM$F .uW"02R0JBa)څQ"0214喂j+Cư܄ +P`(0pe+(WQ`(0<0.Va8A 2D#Y!3f BD; y_!GB@ cSxw>QHu}{{?ա cx,ݱ?=H<{؉'p-(ۏ!w =2paxxzpzrCT';QaPbaz`ZcXz vB1>ց>vaxGp~p)g%ދ{i^gxD[l{pPB(mݶ!jy'O{;X(\Y {K2aMAc7'>`tǰ_=͞={ڬٳ?adm`73g+~ܭT joN767v2NPX1Y`M@Ξ5{h×02'ط/8۶`۶+!..ϟg]EƖQ !}ZPzC]~c`Q긣?oU9kۃ7gn>S_ JP]Q;m~ m۷o NW߿_z Ty }]v(eWwTv;VfX 5=U;¦q`k- .5 /! g=o=Ar}pS^ll8oU{w\nP}}s~q>׼{J_jKuSp*aS;=?F Rl(mzaSai=UT"T U~w"|! sBXx}ѷ6"rd{;w 2d"0"0"CQ2"020fC!C!CPPP`(D`(D`(0"0"D`(D`(DPP',B9ug EثdnAօΡ+31lSEpuYE 0<^>/@U(0&5 usE9pAB.oi#c/},E^ÈC.1T 8aE,oG7?J C| "#S"i``MkQ= yMF[ \)4Gzpy$01Ίxco$Iڠb5;8t0z*!ԅEeY2m_^ ŴvW<W+#a$MAO:W;i/|[$owʹ7p%[q?l\ ">WOIa"mx2EMmns=_|a5¶?l,j=z\1$\.zOҴdGru=1䮇/~xQX5>CNby eꆒi<R%7vQ(UhP>8kn/Xn~0D<! 1|T_QMj͠hCԅ~zs1=Fy꩸Z;2tPsD-;Kss/K-=?gï+1*N*,u ֙x7K( &keF2-UaLbS!ogT~ku3r ~[MExcvb.CE\6yh7Ѐzgr %vzz9}禳~}y˅/V[ϼ_UQNz^@X77$$:، LsB{^p1/% :rt8^C1gI6&Op;Ñ{i]|1]fT,e4ؤ T"\)-a+H?A/1\/Es\ @NH Nؕ$:ɻ)P(! UJeiI6zM|D=Uf!~rꇵ^=e{rZ^Jԋ3Abj RͨC 8؋S6>Vj "o~B&.4c&!/.LRa0"3CYILĮ8^Yw0" *!nv^(Cp/k ˪RwC* Qe=eցsbDv?PUghLzФ|ڜjLuNӀj:MTIOOk"IZӝtqp oבKJL -fZ{ O2B`UjS?XpU 6Y"OOy[dІaa3Y$1\~/΁3r9xSm('1(0D!+$q ]&2[r[FIʒ4aRh$»g{~v|_}9lA 1'5u*,xCK8M<"}ʱ!PӈFa;'UIV$Ub8b( {N**zN]xkv&I`qB D&bܰ.؅vè s4#xUnyժ 0t>el N')^i, G;@6Q~[9;[to= ]CZd2QH]B& Eo7ǰ9N2׃F->0;cbNkʋ/*\~7\ ~we/*O`"68 Ufn*pĘfy5rQ̞ + GU~= yDG4M7Ǒ#Ђq\I,Psцx=PC4Lәy6)' §gr7@]z ʥz*<*{uXL jqvh8q \lU%/p-1ҤUJgfePF;-IҮ$TQ2fKj6cRȓ4 w;› g򃹺\r%pWeo^ܛ#xIA ZqD шlHӰ_gPi'6DӅ4z Ì`C$XSIju ,IJz!!1P%L%º6rx6 8/> s=s*߸vTO`I.B=C~T6vDڰ8K"P.Py F?rCC}ʥ Fjbm̡6As_Y\u!!1 vD;4IRx*iq"10bå2FƁDlyLH N.Pj&Çy 8ɫ\nPg?MNrn7fC7؊mx\"e%tOy139 (hxcԏ7֯65_.cgH:k4:+0Vnk}D B2T(T)HOKV7 g, P!a^^3y|˔ |QUs}˗? s/R M)?ڎc/km8B~Ē7T6jF'@W~ x孁|>*bx>3?]}b"):-8&.7<9_aGF#DT/C4ɫۂZ~wsPE>Ј8;߹}*ps c f9?6hРW:[׫wİ14S6tWP\J߆Š1J`2pHONke j~&ҘI Xad#Y זw'|;"]مξ@5ϗ7,_7. sBuB8,9Fo6F~ąWI0iegM2t{c\#-zڋr9 ƢRyqv?Li +әd< >aTZBd'w5B{@.C{|6 '~7/^v(įྕ7>xxOBX5clG^Od T14S6mʶ[ */rY)_w95Ő$@_$M&8) uC+)SעGMWG`{ ۮMy{OV5A{lsUW?\{S~꥔r-?6%?)oW1uywƛ,hٲ劂DiZy{޿|OC)*Pu`GäU}@;Yx) tXTGI+ZTR5C>Mϗ/-.)Sζ9l O}Y~N .m)?a [S74zPR.rs^04)+Cpe0#e' Kt_u^(sIHYV<&2P`ɧo. D?k=xls~'\] tU.,3W 褌DKz~Ā \0oS6WԿLO]OT&^C I`kBi7PB`qm/ޭCRi"g٨ClKThŘkL%.8񻜍S.}p'bcWy>1TĢsB~Dybl^y|ϛ`0E+5tu taw0˘ff0Y{HρJ,ޏG,ͣQ#3`gxud=?sSL0&ʄ)S>~[{)8.z~B_XAóhs`hrQ//{ezaAbC 2S Yv![!I NۡpC@f :K{ zpno.rFe5 KW7R[]`#Gx ku_5kLIdEbY@s΄ mm*U!CXMV:@qƣg̚9v!rXR ۱A+RY¤b حRt1K7: T#j@10pG^g0 }`X}6{UO|BUuC-6Z =vjh4s1ɵ+hXe(1Ob1l#(A ߄Cr3,09h L4Dg'_(/N(ra0Y +THkqI]OlyՌٞf _9o M0TDqM'[ƮP_3{gj 6Li1Q!慣 I dCR",}[g;!I$0RHi hHQ0܂0;)Rddmm? _0KxV ´6>viBko3a1eO.B !7L cd($h<HTeF&QE #LWgw<8Y)=Xf50|+_ϕm6bW~;?\E+PKaҹ1rբë*1ʱ5z@B.Fa #ϳ 7 ꆣoKP@ I2kY'n&a=/BhX],jvY]!rlvh [n%i+GeЭ L3CDQ3x/Q׀T>~6bȣ_|9zTuz6Fz!ԏ!r"R v(''_ Yce. 6*a7nqMIv͞&g5xl iP`gC={UsÏ|,G (WYk*77#)PlFi ѱ6wRuVy S]&@MG7ڃGjz*"η,D &&ci%eF:P0Ovfmt jJ>wQ>G(lS>0^EQ{Qb͜fxC1?bW{ +h7\d7@CY'bݐ:9qT,mf\M3:>Oc SؔJT ch+ v <ÝFFMrechS1bfMeNcC[Jv@W+eo%9JH$;I(bd0K@;NÉ AZ$!0ƼEyu,@Cb$F0'G04!pgo40m98C y %fi;bZ/ B`u2: 2+![povfÒTr ;9xCKꚘ^)3i `hӭfCxbQnBXryC"IB;BdV%]*3!3d^7e֜;g;SaU:bԗxC1t Vð~l24LFe7-[[efIEo܀C4Lj@SFZq(:8 2qi9a|a̮ͮtp﫤nX7rmYb(NcC۶?A{W=7tцaFcϐI6KV/,K-Np48[!Q6C%C:;3$8 Ea8SÕNfDg y_e.>oJ*hOKNcÒ?R koV7$HJHfr4ʽbXxh?+D:BبA<8+ \::''RҙLHFY s+P74b7 Xo8׶.. 5r0Ih)[VAe-tC@0Gշk@'VΰZCLc̳ʉ1Ξ01̯gv+U6 -*746Avclx*9B;I75H K)IP%Ĉ]s;wja^SHeVoS-`KUm ŃXʵp k M'c3#lP-<$3*-K$B[=ax駟9\sY.Vw brC0uܸq14T +w7`X/aͦhL#(Ï5̵ucJx /rH S[pdT+KN$hYh(ET eä7!RĻQE6vpܸBɧ#jkԝ\%C#5l G+NxAe("gؘ_ٚg4m@0r15 M`[i,FbǮAnh à`KKjz@b r K JtRnYY(^Btø;wC-_!SA~C>b+gL_3ϯl֘uڀ6 Yb9oEؙIZcmHR*7(IiZr\}* cRSv[Qp\K':/M1)U= *rl1R4 gb*oeՊalߧlpg9mU ;å=|%áJ,{"'ԔW8r 1l{"i I)/ hr C:=O<[vgOrJXMB(k7twmt3 IDAT1Y.;:[Y Q o,hP}#$9:pWWO-1i \PyI$EVD$4tmL!a(yB7Ϫu> Ý[[h/ c&m8ho|(LR;>(ŐD#8PG R 7ĿQ>s+R4hO SUF SCFskKcy0uZ] pxڀ ]b9)]qB'aWKB>+U< VP Ҧ*+74ch件`ae_:V Նi/yH놧R69,IF"1I{f1FC맰Ʉl+K ƖRipKMs̫|\n(AsI9?FkÔuFo \CCbwϳ֋bszv0h$O~-:y$ FY]w!*wc(Nʯ%4o:u;~qo SxpR!X{oc 2DRU`cw]`Sve<Zv*6`X c dE:BRHj$*fVUM=uHJ[n؇,0Ffўuf9۫MFpclXuTU`h7…~,olW^~gl4iz Ya.烛:- n NJ,`ݮBt_4Ԃgr\ɳzřiLԘq6qzm2uOͣXy赂+І >Zоl?ϲ>e]#A1Ν:&A CD }LCJ4IܺsJiȘ8@jғHX9_Kzu2m}K+P7LTe94a _}־fy=)v7Xy$ސ $fFTOf4tv&er? e>'")uefCN( .Jv})rŠAxCr)X'9diSB$"JO)0@,fX"o$`#r^9·u045#j&uZm14;S..BRM!S c|~Ny"I$}K,G P/.19$m^%^ꓫW_q%ql^ج774CC>bGO¬Doe5<){w.XN]fy^SUYa 5 HX&qNf=~C$iˇО*jS@1W"kM)[?zkaxChiS9g΃4rUeGlrB `6 j, X ǖt0D-,b$Gdq +=QMD8pRшL0]WS9v0ExC9=Χϣuyo7aHɵ>YC *mm&m0șn CکHد[]DSP8fB^cN207,2iNdSmm eyLP1l˚8\xouKJ\(;d=9y`D{$`2\++ H cҒo 7 xC@/ES~C9bOZޯ*Cf '!7NǺiGH!rk. %4I>= (w+)vaXoNM\p D>"CPz ;Rq\z~857tw) w2FZHd(-Mvw'44mCiSCUМ00uTTSfT.iNdS.9Iwm,} **UxSc2ٍQ>d^K-Ӟ ]09 xXktzar::\=@wBiNdoS.94 }}H_C:'=d 'I`\0r[xI'p3Orʢr7T!w#LGfte0)3nx&yo4'7 );75p Xك#Npj,ʒq;x=LCvi>UDKphZhXBfiFo&0gY<7t~PІ f+|CPN@$0팂3Jdq"x/agCIhH7p-KLa`"ࡤ8:͉ӫS5,>O@yuZ|YS [c28)%֙7.̏8u# H[ kc0ðo"^SqUDNP̚'/k5ۇQx<=fЈcs001զQnMx]qIpy%P'^B!bxf (k~CRISxG W[kI @&l)_Qnc!,ӡDigse3H1y-p3 ?M]ra{nGkاLƱy8-*9N㊒c y̒3 P5Eq(dn0._J SJrƩ (@|0{t(_*9NPՆ.4Mgj,Adc"w`bR^q7Td&`2|%0607G%zf@Mb!;^146=' 0~mǘaSNSȵxm mX C({$8'. qĕPk^rˈpðЋ p6{7tp#\~&++Ы,Y]yІuZWy}#5rbGV2<,3ҸuHdljJd ,ڐ2}ʞ1R^nTzU KƘZ[)76V٢ KCC.J2_TE@~xut-D%oz} t'KD[TT}fuClN^!?K5g(r>"5*.omSiJ(ډ;>Y WdrOBp(*Eq̦ڙ&$MqYi^ 9CXXƱSp WNpӆk ٓW-μ!P!bUg5+хz&?Z7{}̋)3ЉGXS׮ӢCp\S{#ȡ3ń &LU 7[Q׼\yy Lvh3ĺ( rч~`-W;H1/;QB񨌊TÌ-af0<+r - Ô6;hW&lTCkh@/7գ!-~YӅ^nЇmq PSJbf-뷔Ze4.h8$-頊8Ro RS1<)/@Y@ 0~0s5,=_R@2;]MC樄 R Chi1W-`2=x8"avRV:u0<͂kVdWX^+5u2eu^ԣ̊a.,W7tׇ>0$dIl%:/hP餒θh"Ce)Ns1 ㎽}|ӺsCnj [{C >] sP >$aT2F;U|V%k3.A'`O=>ƚpC]]tnHU3D -ظ¬o}CQ&<|^ޝ;&];~]ix)uEQvncb}~y=D^ê}N͇ME9Q1>+|~}*~GPtc 1'2Zkwɠt}_ϺA˽p0>E7tX`xbe6NYwQPJqɆWoi9ހ8^=ܕsՆ ?: 0;*`aaST sB  BBC!C!CPP!C!C!BBZE\4gZDMC`8ufM>x[w?cSn}r=_oCc~H(0apwƶo嶩ӷ5fv tԖoP<-5ml~\m[πMmMw>]]5rhzo7Vgg1ikb9Zg^S@ Gc{`\6OaR|nl>F!>~TUXĵm>_m*ҰRG^&Ykhuà أ._1sІNkõg EP?6:5=o>C`ᖉw<j>C毝iC}Fpo}8nhhCH} U47\뎡q/oo厱Zp[.9qku%ZC =嵧OEFZS?XE/uиL`xNSes{^Ӷl.RPP!C!C!BBB   ,QD ӶeT֊owGpi嫆F6_+°sYK聁7vVn|_;Q:Tq(9/xz2 s{̷gH+pK'!C?_Yc7Xؘ^A5.xJWp{\WǧF8|{ GFF7rכԍK? `ɠ==d*cH~y7 3_H`8V sƘ}K?m:]:yC{zcZ kgJS7L .w$0j Ѧ~lˆp`;)ڰikC/:<'=q1GF"o6MT_;S?[0r?ȹXão93V~6q 7?ü-n  7[ナ/Uڱޡp퍿;g=~{վkhtdgmXBc wm6oy۰kCc`_[ΑCޮr k0{ !a@¼T11?mxsT-O0Jv }eˣ>wv{C}ޝY_?}b5~U yLjب9*٣G3 CMFqwð+N(1?s҉á]y#IIDATs];O6ws[GG_ˁC+SvG6=znؤ^#G? =W0nÏn{}ݦpOw}fM~ƽ{/wƦ fK$/GKąVD|+]x6]Cy/Olh/qz%0dVhO}IځμvSb H^_bA,I]"A- }]w6]{Cj[?ʷzmoJ5fu^=׺ս]$ljܐ}mkվgoPeq#'PF:ZInc܋"ߙ^t.`Z n13q۶sz+cxHhkj/Sz/J!|S@o`r`Isi=,ȏݠ4``8P"\u76%M%iKD`8ZcV_eKN|m0˯㾾Szx#)[oS>xC{7K5(k(94PaX ÷ܯ|~~oj0(3wڡiCF+0FO(|W 5yˣTtzQ zQ210SE`8 6 /^xw+0|_~t~ދ{'(^ezQDǰ2(Ñ0 Dl6+0bpb(˟r˗b(!3,c89<:!3,c8=%$<!3,[`(0 & K Ua^`XѣCɏ@  C Rfz#{ƌ@|?j*C( a*`sMP ːwj'2Bde2(b7x%9ޔ˧?Rտ)êt K:#g{eŊq -:)p'Uuc c ];Uoʾv xI$1;JUJK8bg]7À-^p'QjSm 3TU*#-Xc)C( ad$xCVi%3zI maVF$c 솝Ai G Fx0IxaިYu l t\$y":A}:_Xm\/$&C̍si*&vaJK8d $@()zf%7ssW pFAmD}^nsy3% S%#(to-Q[&sl<7E_?nJa (*/Y8eǪ?ux8B!Zdé-)F@jɽP`^o;eJS.hu$IH'4]s@W:֊oP'A$ LY7$ʳsb?+$bnROC0.b?XQ;7L<\+GU[Ca31SUR/\zYW[NBlY$_^e x,wc`C _`^b`d\dYQT!l>aJ>raR \ )R!wmDa EpUoV~dpzh|QP[qortqcn@rtqhP nynX1-bd_MaH{y}jzr|)pja:`_适l`j{~cYJe@V^nyĂc-qeS@uВ銊lۛ!>֣桁9흜JDZjݿZ¦UúƜʛҵׂە`QaqvtRNS@fbKGDH pHYs  tIME 3jU IDATxչgw0C2naHr|c&ě-倫q _ɩML&#djA0^!h*+$RTr? Um>9==3=3=3灙>c_<9o+S6-L(oᷔ)◕)& ޥLYMsmSmv /=O?~V٦yu&m`m~_ݶm:>(Svl}BX ,mb]<(S6*!^Z_BiPVozm6I`=+}sYK;wCrxv[248e[ڶ7t7?mzYLȺc[ΡyNlMݗ~aL?;r}GۥT_ݥ ~wB?g f|}vkGmG9sؾw;6o/l+S`blKwHKwd!;jut]/O3k¶;;֧O>lav>tc;ӗL;-宄>N!svm… 6UW Z0TOƆmlZneC /=}<~hxyx$loS~eXG!Ļ< vs~k3aC rbwQBMPٟiC/ w u4BX6;-p7 ? VV5U bA/1:. 7GX8A>9}8 i}6o`3v $XY?&ٖjaY$M@K!}Rd:<~/c6>pOn?wl ^Les"jx]qs\Gǻq`vCRwEOQ>:;γrغNKxΗAmΥ`{)s Ƭl8 v6jHQNĶmnb|%qo8J_ ۷=M')`ɒmه'qh1TʔOnK~?AQ2egv.m6į+SvVmד_W+fArPV!2eMeF*))S+S V V+TCzaQ (]?4qJ@'`7N8\S'񈃸O$PD5o{C}7WIA[S09wSy'oQ(Z, aDY/gƌ=KN; ;vE.Ì;=Yueo3y N-(["Ӊ gY/18aϫ V+?b> /Cr?W+}J^^C$ b_~˽C\}ybߏXA lB< =ĵK}rBA V+9}\rd8WodȉBd8XABlCb;Rl>]<XA<:w>ΕB:v bgbeă'}dٲGT+\#;#>P+Xv𵃯/͠XhxdĎ;^CGeJC*; ɰtŰoF'&<()^v$);ر1be beⓏ | !~푓 bewb?8yZB؎*"! q'HM {Ise!)e=& ~'ז)6w(ąǤ9!(3*'j#,#Ա^JT(Ⓩ|L@,7^{줂X(xpk;l׆gu 6?) q be b7 ?dP0\.z!Q|M%)UqfcwX#F}~59|܃!?Ӿ2?}29=iٲ'=7 V6`߱ 7>֗ZM\\0Kՠ !F3}}ǏK (*meΰ?+SvC 2eg؞C2eg؆]ʔ mZkDC~_yg2eCcy!&m1mЦ2eCa zl6WFuL٨xk}R/?P\C|K}W{W\tONwe}/U@]O]`wW/s{vߴ};tKN7lNr3rGmG9sP)Mbl~xM\ÁC x_.ԣ wf^H4>۶xox`ZZǶ;;֧O>m?cсC(>&!~K @|7U eۂ m`M6)ݿ~W m68P0{wKIo\H?X~]7xz$mC,7u8ϸwv ğDoxuu{5k4+ ҆د'7ᎎ= oul;ȮBjyyqC; O?BC? 7]+{^6cf̘__& ~?қo{3}|ح_;osT>N#qG]0%w6a'b2E_!CܶKOoɎ;:Cz #ɻgY/ҡ CȰ|;~KNށ`? F9zOn߶On{n<-%lC{IyqW}A^iP'E`zx(c|Ţcw|_|PlH &u<9,~?AQ2eCo)~rװ@2ef̵]UAl0@TIʔAۮ V V+S+S V V+S+))z{' be#OʙXʔ))S+S+))S+S V V+2t%ڥ%[FkЯ,٢ V6W[آ .nHg=oa$ cqbxQ1XA)>eݼ~EOt!F;^ӴbkC |/s9Q\as˖%İ1 p&Mj!{xp!j(>rDɉұ[L޷l樃Ӂx0[ ~/p|1'M_ěm>:2 vaן(rqFtLJ1x>A4F??kjto!60[(3ݷ Qfm*Dj'\mVo[9ĢV/Av}m [ WNuys-Mhml o>483vqH̘1"n80T@q]CC oCC%#ƁۂÁ!~޶e 畺x0a8 \pĕC\Hq!2vȑ!U]@8=xho!ő6[fSmI-'S\}D~]  j##)M1B) MIBܰ(= ֦Yk_nC=r B,(d<_Dz,a8*;*x{퍯 '@mootiK&ŏkкY ~۴(ms\K'r5oMHTcMSJ0t-JFxqR#/ 1P<%8%K_g,P>ֺ_0P\Z N| hre-Cዩet"W8 CϞ>]=!N(iFFb`<'J烙ǓRdӮv83IVqo'5ء4xp7H} P~@l@셧7G -ʗ%ӻ Nfk,3(5L% LXN#pF 4ɓ$Ll1E䛿!NsQ8 x/gL? B@uXB,Yf l9Ty_k-p4qiOB4eb12W5#-5qQ9rŒ Դù /'n,4U}/@A`8 tЀ8!x툫 z!X&ì;x`5*'^m`h<9vGO6kxhT=B͘4.3n E(n Da8H>xK#-ᄧ7S܊3WQ61zE.Ca bP=`b8`u.Ȍ{A~ŽOvC1GOxb|NG,c, 4%#|~7Mtjqǁ.x$bnLi&v'Fs htjx /Z\q֮]<^$ 'w?o'XnM{ s׈])O| MˡG&Xli`+i$%z~!IGa Fq 2$xl /r"غuk"Z,}w',lL}}v;PiZ4;P.PqmE,T޾ӦY+_`Ɠ k(8X' Vl oMщchg+ιPXN tN}g"@arcn 7脟e{ ɰKpAC'q.Mh#:ho%EJ}>@ A93bap 8fh(!~I`, Ϟ b)QDH>A`,Bj*uGF޺zRKͰXag!X'U5\5M5C|tKNA۴h6S#ӵ(H =JxKWX$؈]dWؒ}b &u ꤨHآvY﷉H 1γ_^Nr 0vsn>x=Zxd c&[T;t؉|3nW.wGBVT$6,jXd8'0z谀8p3 _x甋NXma BuEu !)"/8b~'D!sA8ux_}E'းxD$aq1?E"GI!q> -" ]D'qXyϗxSYBS4_>:!l #9C7o*T"1P\u`5u#@S!ĵ&/|Ǐ0M/䜧_b-'c˃ J{RIx*m3Zt2=Yjaѡa9&D.tbѱ8E hrj x&n Eq"l_ v\Vl| "[bBEϜLJ/3= 3L0ٖR?[PK8ѓ)5pYcM!;c{W&d+w=7`dmbXL[dɉHA 1H YM,}2_^v^zl>| /^Eүү~_4Օ}}_ \ &rsvmh]y'JvW6bJ%_(Q!|ͽ"d+Vx9% suY$3x䘇ݱ{,_luD{ltN|ĉ<U;QpVCEvnEw}趇7΋6g!6þ4l G):r"${9ݘk,ʡyr"|UW38"O%6egGYTLǸqPL@R2$vƍSc R(1yQ\[,!L g=KN): NX @g,w:=q$HzwAJou]DDIavhJåũɇh4RLަ% J?n\1>bvo\\DLl]5b{YD(Hd!n@N`#v~Μ=V3Ĉ@7NR1_~!/S@kgW%cPNPS# ޵=QWrO-IXk 5BX; N]op7܀bwԿUl(ǔNHP]lv+:!# [} TL? @9߇uepl q|`]} Ε"m+gF/8zw Yg5&/evpMlbׁj|[U;ISݘQLxt\ '6DTFj[-)S%bIFց=|{:{o|P*._BSS{+lذ.Yp"5o4qC~c`:!e%ϝ(I YԒvї-$ƀvnˎѺnVq?cTGH8dm6T Np4.LJj-aT#p* h8pf=LpAB5!Crԩ{_,Y(˧ ;K<(>@ < u2VU>'®nZ^ +1bȕ/nӕSM_y^yl>jHQ۬81\^y>$mND*4q~sbC܊DNТD.Qpba4uV 'vM-c ],v̝KG$v[R 6H(83،N0b?C1k~3f{LpnߣKdS/r[>1r'fz@߮b#%v{}5^֝poW}Tw.ߠ7uˡwVayYjUجbW]w½]UaR?z2ڎW=1Z<ѻWH:"S2?2O꥗};G1aC:1@&D9 =!Z=4]tͰfɮ.c(Y-8ksWk)$e:i7/_F\.g .[X< q!~}7L};ox(ꉝY|Yl^zYl>1?Bb?njlOl^[y7 RaT&| L4c8r慄$Q(8r^Xqn(-[yo>ݏcǧ󁬾݇+T{d+ qn"C>< 8o=f qj@&kz~k|lYcvX$P`gf9@ Se:29G ߝcV#eX|}KAo?:b5׹{NV5ğyɣ( d:v&^#)v1lP/}k_֝`a%y'4imxO\dZJ _h9dS&Z(4wP QmU_YOi֖Mu wW1_qHca"W*UŠK$S|V^xN/&щMFzpm26 _4l0Յ)?8+`0e-4-tbR8B4& MߙMj]JC1W~[STbW⤐A pFNB<<vY3<*֥=9QY<ğtŅʣ1cv͊ͰLr6S_*kLTH%R1Kfk1 %s#QÅsiBs\EWSJuG(ԈerIs~B9b|#! GG(ۅc*U5KlB:v^xxvE ;xc@g|< _/J:S|9pFtpY3VtqN{(FxRʵvݽt H Q(O)1Ic2ZV긆ƣr&^gLqAS@>ta ͈I`^8;gt+.>BqÚ̌\N3óɉ"5%ʩn*XˣE™ѣ^_3 IEcH)o0P߶b8+` :y&AmDבi3D):z$T˝7 rs+H^~t?i\ijb's|E孊Bτ}s3Z)*@\-;ơAb:h.] Xד8WP ׽xXsm~sE69wDW;lz N`PN)./:HYA6=1x3j9|> gI^[.g`a /fFՈ>s)O8ՌTW }6C6 χ />c!mAP ClU)xy R=ĥk~]w݀$̦,H84!rϢ( { vnĴ565oB%x|n6f+b $['.VS>!١rĥ͑iYЧ m..:Qږ(K@pt.d!(9bd]HgNHW'jjS!b voE] u^+'.VS z4#ÛO|qFJ䌓;:;[NjBpm8$MǍEE*p q,G:i<ɠ(O3 2{9˅b'8[X WO\@&+Xjl0q|q88_ݳ$oFǺ@J&M[%MM &RDѠ@pܩvnh݌/_Pq|bˬ,XM ;ĵtA<fcO0`֝bSt(ymٳWǼIVa7LpZ\<9-IJ'p1w{Rg22C|룏W|bҶ](,VO\}@-#'2ę!14cdnr84pDDE1 Fl@hk_J5iO! 9!.AlVq,x;q*qkJ=u|OEmwl ',j8ϭӛ418Ic`UdItuQ m5Y@y~l X`j}+0Duď{jSBlbkWOʗkJ8\zϝ(] BA (:ń¿(60- u1k2!Lpk< 5EENO-ǥy``xNx{X v9A)*颕% Oٰ av:b4SI˽GŠww.lјLF칫Ɠ<:WdaNp PO|'HoleVXN|,8wJR6/ zj_VQ#GHLOxN 錃p1 p!qUץYQ G gQq,hG#E.)q`X ':7#['΃ɋy2E3R!͚$51f806SJYQ"MQYK18:]aT4UÙ-$ );Fqc)GƬ0]PYUOY./d$x=͛OgxZ)Kb* c_I29k_ qFp[l)#B:;qmsƹ!G̗nlhl6fɘ|b|`]uĹyC kfC~4[t:`<g\FH'GR8])׉TxrI`53?SqFtfD9Jx)+s(l! p]5 '/b Z<=E{ExhR1 oZW-$։NY° ]Zˆ/!p`j(ÑNV 1څj7R\pG6XՄ?U疮" h. bP{k~o T!x|("X)ހSL O(t@1:YRB[u0od:̞x$[VE]W+Ouak,j!lONd80nU 1; CTZbO+B!m!+6ð2gt!8&*Fs>zEh~GZH}qBH tr9Ju:r2ruם~>@%!ή^jUڵ&6t(r=j2\ [FtLLO`y&f7M#w3\U)In9vq۲}9b Qa/Z;Kg|p=|u:r Ύϭ;gl bl!ήQ l _wx q"R$O| '-Q}mk 'x,KB8z.NibG3c;,twSP{~Xփ( 7]Xb$tUnt@\/ %ܺ(ē ˱D"[MoIu.E/?q )J<-[+~ ڗ%-W2+'r)W٬7 :vu:wu'].dK- ib*'ڋz([~/nvcbx.a8lסcy7Eh 4%Ղb{ W9C&2bsӑbsuu'||0j3A_|x;vE&CEX+k)ŐiFqF\BU\SƌГ)KD\=}U8ԙbSsjW}b@;k, 3a#*;\tIBr]ܥ!:UCl>5m!IcqNxu@2kc)+ƓDWHT:㚸U' I.l"_a3.max -(O h#=ln|֝(Y0,OeWOu A+ƋƠ[ I.O9S8uu:w91 /}s|b` o?ބ+#'|WfcQ'4i@;V0 +%.|Co510 Q%xX 1ؑvQ3]3EMOLb Rp%b!Fqv*7n G|_#| w.DnNE<<'lGs80[=0`K7}Uhq:s%aʢՒ)1-.1DqY1mL$bG;D[WfygPSmEj!avגP#,s!rs*r!vW˝^4C9w~xo0nNT"I 8Jh{9N\D{)gTO{{h7+i=.٢ij'qTX{y(p\|D"qaܰ(Y: QzX.D Hdl~E܉Zep[? "{7SӠ.8b7Oilx e먎K3Ýw 5u &DH2Fe4 -T+8\ĸ"10,?(Ohl6^% vW͝(U"R ||b?r-kD(c\עexR/њ 4Q\?Ӧh+*?Tħ>2xáD|Fٱ˃+8Up|_c$ hI2ک%&J1JD N)9-ӈ`'s1ѩ)(bVĮ|btX.Q>劷 \+N@aأWKQ,.@ѵ`-:C?Ɖ:IM.:AiKR':.Ӂkhv@1ztJR9aNC -[n9=)'anX`e'vbb\  gr0uk68*E; (. ڪ OD0m \0$J $;1F4mvW M8c)j`m ,Ji. t?W9v>LpC(.O[•Σ0w5[w向@Fs-.s 3`s7']._F@P޾%i [IQq;[4w"WS 3%tnk!θ4QMƻRb ,Ȇ1A;*qc(\RH0\I7gb;^O#Q)ĕNM*u?!:Tij[9%IIF"2ƛ;9ٓ`d&Y}88ՈDg%a딧8=4q6xkhLAJO#Q!ĕNc+ZUT7U~#>q)ixDiFpM#diO9(NBg`EbBUk##.f#3ЯK}'͑ Zs'*Ɯ*/T̯t۬D̯yAnNbv&kyLn.5<Ner}s] m&c熵O' BY*NԐQ<ճ_WINVMv wA9xnR7*XχdK mu8# Osxč90?bjr'M[޹y `NY[Chws_16GF1P,(vNb1O.ѱ1F{ؽX3EI\;, O?{ ̽ĠDH0< x[}'v?8뇳NFxO6PKeB08IW'6ȪdWJLP,H&qN}$H̚|'._k|[QpE"f&^=gA,y3,<=ԑ|k8O{ub\s@q8čh.bb5a2 sF!1 ŗ {J̓دyNuTXq?l:&135v (7iQc8d"/xSㆦ((3qFDa'=qvB?H.P. c f玕0!RK ?$-וx?l4?Mjb;V\* CCLb,b?&d6# QrHS rIAE0ZJV.ϛ:%o8O\aQo\WaA W0y歮yrO|QQ_\br6jqm[ŧX \K|UQNp;B1NDqϩ4B7N<\[U1eqš`'('6#c,K.XTR{f bv~Lzt섊ze2\hߑdogqcZ즜s o!B|/͆ @P8ݺ{`xȉJ [V<|-+_K@T3`xKͣF LLxS)qX#ŚH'xd ǚSp34j r wNC݆̆ G ֖2*R1+s\p9M\8m=75@ r MvuxD)+xJ=Nӻ,2ngb;'H />޵]m_.j, C C爇A<0pZD' SA;E^6G%fp;kv6LgF !>}l(:=P+uo: be }s7FħOm(2XX22XXALALXXALA V VLA|6-mTqוJ\Mn:e ! UB|((V 6 ^(hS+G,鱁}i\v|eivK^%iu_$qM?ԥ8Wm T/ _w2sF pW:by< ͖8=;!~ւ@Ysl :"ux b˂3e6NOdBl4.Pzei57?7pnƱ{*)HuÂۚW\w H94Q6kE ['xhb vV jxiC<R_e4|txŇ/3y@FA\DN<5clЁy BLǖHH̚ik b7Ԋi;V7w` q9Y:s!J8Q zsЕ6csw,;l1+͍= ʬ!Vr8&{́::󲌂XA\+ăk&'Z ֢{ Nnדp5|Vuali* be be be b*S+S V V+S+S VF{' be#OʙXʔ)GQlx9p`yh# :!6o>d_Q?겺ȉik=k㉏/s6׷ :s2(P "ujpp -',Ol vnOi)u}|!Ǐ3t߼G. Nz\I 'fəC_*6N =4wXgxŕYV`gXcIk]bYq3Ry9џ 79` băΤq˘kfq`=Ud IDAT ݁˂.1B|0[?Ş=߃xppPF#2H:v9izjukQ}gx>y5}u<lPA\66 YURqo!>CvT bwO|}޼yg&V6 >cby޼|ޱX(x58,Fk$C}*:lyⷎ~ͼe@kձS6 6cO>{KL(lEp8wCxiT+[(g"uFF>eLAoyH0g%tU4.E$K[Df"QkE>e-t?trb UR١r&R$\[²p (3:2(/Xf @ @1#@ w[ Roa!1)4q  5#fM@ @ 1X=}xKb ijHz z|$7_^λJ]rķ} ~a})}*{bq.ܝg6⇋xWQX-5|ʘ;_Y].9`⇋xGUbuȪRNaO|'6ܕ @@}"nJuE6qM=)vBu{1]Y^].9Ayǹ|G,~(8XmJaE[ X!Hκtrw*u޵"#zxolɍ>ލ1 b76 ;a1:>4^odU9{aoEbUK$޳8X(qVgotF}|OzgeVW#تY%2BU"xlֹ-@bS\vO@l;~_qϩͷ~AuНeIDċg Xl% 靕{'vO7e ->{금.CUb gX)rUQdE+0;+7 x^h/]q]K$b gXԚ(rUVi蝕1g>*O:4ڊ]v&vfA.DlUpa >劬%]ĎY"N'=}b}4.Fi:v.)rU.jzXMY"~OGzb V4Bv8E\[Q(W*ri~ӡ?u?L]/6_J;ߏiRi@'Үؼl]^%Iqxf;e 9ɥVsuyչK?̤1ѽqvϻd:b qn o@l:upN ,7Yg݈#41bxRp F b bbbb FA6 kYc@ s/d @,.%gG89VjR"FBGj՚n.Oof]q-ԞxaAl6*tWZ >~ώD07~Z/tq˘ݯI_WU6mͼp: FCvόՙ(5o}7 =10@=Uϓ'{ ֞xf'6ğDNІzߐ@l+{b FN醝ƈiC0VbsBFl()(t}']U<:::ag;76⊸sppowkŝ# F8ClXGH;O0sw;xS ~"^"Bێd>07'\C"刯!w:\1Ĭ1Xsa'vKpEcl]'p"& ʍ6 Ğ-!b=eq`c ;R@L) lx1svaӞQ;K q#vYCNIKFP17EW4H9捥^S#]A8bs0xsb ^{[;:A?1VbK:1IgFnYý >Ϥ:1 )a~31DL|} 8bv{{cB#vԛ] $֌;=ioL/<{pG/)f CrIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-debug.png000066400000000000000000000257101504763705000262350ustar00rootroot00000000000000PNG  IHDRPRW3PLTE" 134GGJI|ijmb5}gЮc~stRNS@fbKGDH pHYs  tIME'` IDATx] }:_X@041rR,*sn_uGFw< 6#tdzրc}vlVKa{i-:8^?|~#AGI$ O!۳vr,pwՀCBtp8  v `J(,V6Dp$ lG".|,.c$^> V4YCGX|B炃3x38 aippqo7r GI|c17F3(\(b| Ťntp [ǼyPb1 e1q}|c',11_&%c)-%ǂYa$Jb’& 8V~O 5<Ԭvpf~^EnD#љg3%.j/ϑ .:8(8r~W'g ^B} 8JlKޗ+,:ɯ<q:6X)@$K{g''cy* 띌$Nr^lCC.MV-4Ҹ׋R*I{׋I< G)EYM1@ae%2^ &:50p.8Ek(kZ(r uPv qu 8F{7p-CB#$!bu:Q&yպtS1 wUbfW9p~R> +BAH};:IRzHp(ɉU:DNB'$/94hu\ׁsxe԰ sTہI69acpQǂcRIDTrS@m\%}}yCw`ut l5/MgeH\(_$2ɒ݋0]Ui"V'˫-G*5  z1r$NG:x$dΌuXNK5%=QryTɍ%4?oW' +jN14TЁە@`"V'eaHfDcv_?? d :w qux50? [!̯k%8U3|Æf]uIvnOs+ }DpaS&?]CC5ٷϻ+5u]Gwupt]Gwuptq% ϻce:S\{q#@1pe|fq[-۪h]1Ty"]H<\'FqrLR2ƳwTްBUL,m҅lZR4t,2j-÷r lpQŇt"⋋"dEUd`:fD-l<-qtv ?!X]B=TegSpYy$48'ɵvC<cɜ%U䚀b`3VJ!CAch<=KXrk'3IxE $X97-e2ū\aB5Aj%v)V4Z9!je̎Vb3kQ&\F.f+'-uJ`+}ܫpoupt#AxhW=,^&.lM)#6e}UFz>͔=B߉wuhʪ6ږdRE1=v>B߅h/\T*߫w(pSNr>Te;lFOI}*kB=ge:߄&_!7b!sLK'/U%P_*Kr( }>):9 4OSRyTIT͔9~')?DEGa (hTs4Sd܉OV+TYZQ}Xfڗݖ}*{V+͔= !k,D'dxʑ2dU,b$ٷ۟? {ϻuptoBZn=>5O)xx=ٞ[u>2^)2/+7;}0DzU^BO71l/[XU\#1[TA}e Ŕ=xQX)~ި~[M}M>a]AUڡ ؊rgjƄw_U̾[0ĊlooRKt?DgScZ418#OP:马G=ɣ%<~U(ayгS!9-*oRBO!ViG[ݠkZ4B eO3ǯU?ev*OfDsGGT*ۿI٧]./'i+5" H({<~Uj%GKĒc,9)S>Bs4Q 8`!99G(exl$sfO3d;َFs2V+eV`U]TuZJ*j:2)>(PX gSp=f<~9;se-x|.yv_tpt]Gw1-{bJ)€1ծ/}}|\7o03+a͆\|-쮖}}|\jѮe78']9ïރDZ5q៪VG3c)t1'mX nAS}}3che]O }}qz͒œd;%9tٓ zy`O˾>~R/1σz=s љ9ĠkDž9G3c)t_\G'xQ?ѫaZU߸Ziײwkϐn O}-֫9R}O:8Now]Gw]S.;._;xY&j"3N=Yd=rIZ<,{ 辭f<O)=.K b~+νsϲ'pӳSCN'' {leqYDnz->r^/9&aױjqYD;'-`NY1]."ۛd~38,{ ֲ%Y&9鏑SAx,{ %Gj$/eoOr1^}:9gSj βVloaZ9,{ W+d@,{%p̐Gzp0`"38,{ 9mϲgwDpt⭻:8{$8~>>TVM>Q;@l`ڏZ;Uٯ~U~͌qI||DlM0'nU)[˗Ump43e-@gm`o>4.sT`-_JUmp4S"܃49e@ 7r`U)YǗRU~m.=B)Ax3{sRX*%떗$o6=aK*sTPX|;u>y[ʞz壍T%k-?y?P[Vٹ-xGӶ æ1 Spr}xnOQ^1^wMN P(&b12W/eUOrg?1;8讃JXE<,zk3ϲz!XCGyFMYT=d|Y rv4-ϲ'zYKgmtpY?G*ꆓ L,{ s"irҎ,Ps>LE{QQnu5ξHeYP C17[QfM-e`~~^+=bׄAnzņ謠SVe_mGpw+?ܜk< (+^MM]hC,{L¿lBKAeu= ƒcTnE8lŬrYk zAv4=ϲ+PO's Ix?0(:/b4';0`$G} ϲ(fV 3<&灷 8TM#Lمd&ܧqbQ]p0gh9J۞e/I-ş  y\ێ = _GHPn8\גsotp\ =O;UO/['Ax(c ҺgPb:8> _q}( L ƗIwd@o8L3ߖ ~^, ٰ(b4Kq2e/O+.@`$/9"& 䘞 S(w-8 |ֻ 52TDt ʱbYmzԂ:h >~ 4װfo#WorToz܂oQ}Yu||#sw$K[!{ub on&e_w}5v>z0{}%&_-y,kβQ(k=wɿe9n-7_lAQ4_seڐ7cw\mzO}Y |l(2 C_'7%N!cV>JX=pDm&nW(u=Mar?@ 2-跢ta|LaќCS1-=7KC )t:}JkAO$mZsFqO Xh]%lF Z`XmQ\79*R4پf}']u1gɄ_ئ÷(upxlitaᣜKǢ9g(u, َ^EΟ'(jB~101˕/>gήs+pg pe@>guaq: fʞΐZ(K>Oun0YF,o0SH˞ΐ혹70&2|:d^Rvij2:r"H-ѮeJXpd;baB K}}78;4B$8){MOv7AJk&yכ}݄alέPw""Sc:4+9G*M~ge~Irؑ|`If^R&gwgq&@s>0~49Ns4SEŚ%v<a\^za|耝ՊF66V){ ksHi\p:ryIHqBxԀx]Ctpt/|CHo p ao}+SN^#&Eऽ~_f~kNHe^&8,:ʴ-p43;||%c| 5j68^KhfGla>>BZ~Ǜ`4}F2 tw.Ey e ZzCzc#8,*6hV[T#Hp`e lxOf-{G 8QHy 8$>Zy{ 1 *Dbe |hֲ?[~hα='ǠCE,B,l$'@9p.2YH˞4p'w^S&/_ݼbvHaF!2Y|{7m5335@ ԲCa5i~I}ƣ‚jiz6p9i4papU;G(Os K-a ,S${ I[a%]d1mfV}$9+r;\ ?(Hǿ[ȵࠝ,Po:8_;+`pMSO灥w}uz48:q=8UvT j>%o:MT^;z'rsAVCܟ}~@o6%Vs&җp6[7u\y;yFvc!J_v}ڬqXɛG;Ohc_r\9Yam%t˝Ȯh{yɜ〒7!9ZZ8>d;$q\;3hv΁M0җp6isWɛ8OCخmppjQODZOa*tptpܕ[-۫dp|p\eYb 񉮡v|}o8]}PG$zQQ[ՈKl}ÈUFw_nJz+qq.XOkk(TeTV]i17zb geBk^XE v IayH oϱ z?EyF]C 1.NEKjd R*ktWp4S$8Nʖ$GiZunnǏliBL䘆_)^*kt[p4ksr H턖} cI.H5r5D(P -)92Ĝ#S IDAT@*VWgI}yOI&]dX2fN,kI ԫ$GFOsPvB˾6xz:$Gq{? 1籉"sVb55zjD{:hl?Z]}POǏoI|LaR$ϝՊm%vW+=jyo&],8mHW?҈#{LnRXspMs2/e+jԷϿ̹aV~]:8Ns!]GwgdLnH&ֈT| 2۫zB m&3Q10êj }`HP(L9 <Mnz ,sti=^:rK|ս:8ฅrn!䑡`ň)J.Y6YMI[C8)%!28276*3gUnl{ p4S?@۬~ (O%\ 8Un$88)Y !}}z(L$"]qhKg_O:G^B()m8F-ÂDٗβlwYxnŜ9/rmF6XQr͔},vȺ$9\Yh!v Vss4S'!kaB-pQԃ=kK0(bh{EHWjd&68){,;U0Gn)!+@O ߄_G+eOe'>k^>8IO!/ lmyt(QYw:bbɱ<*=gC68n5稠c-t7M`p$ZOL~`31(52~_nZ)QYw:CdBP"5{EW+Q&n#S=yx3y9JVQ}d /}Njvptptpt]Gwupt]Gwupt]wupt]GwuptHǗK,[l.q *@,^:8:8:8:8fyʩIX^PwU&oe .bꑬx02M)elb[6ߘ<|m0Xȷ_L88ŐpHfm~2 ÂߒB7%E?fiUKo:{F"'WW6Y '$uzƆF.Kk͗ ?0m!g%gpk8J9-.WkUHl c}VPuW9K 845 蹏W>&i􍩣 .|ֺ҃ObzID<`LB ꪻ‡\s )Be]bΨ`ūG8 ~ުxc sƒhޠ5‰=>CL:p.P+Kx'Ÿ,Ʌp5">qksyq4m='p `{Q}eEK07ۛ&q؈OrM8PٽBs>Cyb$K=Z1uyBjwB}،^D1gmr񹪞TsH>xT z{x>GnΡ 6Sz6sLeb ߿iPӜÕy?Lց㥅~mTDt_B:r8cWQ.cT@ jEnɘAS\Ǻ`o\FCԜ#Zf&;wG*EOh}*I}ep/QRyBZĒÊ6[Zr ,<ѰB5j-Z~}&Z+bކ̀[iAKs@*%ܶ7Na'B"u9D`||˂ $pouВ Lyq+DSs+ yrmbe_2[vVu;8.G;,aݕ]Gw%pt]?4IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-input.png000066400000000000000000000044431504763705000263060ustar00rootroot00000000000000PNG  IHDR@E263PLTE`s ./.CAD&cbc}exh:?e \k5o\&ˢ$ 8z0m?DGǴBo;_`*^u?gOK`'lJt?34{؇H)k1I 0;HTR3T8*f7>RںS7gj5[u$>+:p)(hQة+]Z@u Y`tX]:04ap+?~A>P)i7ږ  51Ԣ~lOӁ]6?SzAz8zY0A͏P4пBT39Dr ~cΧ1!eJol.|٘:q|"'NI3 Tfos>E*f %zs9-JO,}2oTގwiq\n-5zV.N@^Z nb]%uHqcPW]@̪_N7ySӳQ8S{I+:h R?x).T9҉BX p*!*+B%KjPHttZj'(\^_m[Ȭ9)nOeK'wJJUP2 ў?FQ!%1iLTW=uB9G.Odf؎ 5]ǮiBLY2J*9$*д I|4mϤ[tFI@g 5&QQ?'q?HD:sJj;0vS*-퉊&ON-M;`7<m";m"licb:k3owepy+{IOg1`Z,A tqĎ>!SlK WM&4/ CA!2 NLct\"܊/YCLX*1_k;GUv%tsY*s4(NUMOg¶t NY]qTڎg]5}cl#`b%T\' lg<ƺF(Minlk[e,fM%)U`j)l;&O)6*;8vc ǩW0P(<@,&&n${#'˷&KKtJWN62KE%2Hc/ƺ"w#z3K=}l#Iku)vGb"#M\p{d>6xkyC[o-t~@\*j rLlXg@v`,Ig.2 E옮v!?g⇪_z\O &La9|%`Z%,s5u(&L%V1``.A)/ J0WBŽFC``ք/+ $ ir0J0~W dO6Kv*ĆOȴ6iIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-noembed.png000066400000000000000000000072441504763705000265620ustar00rootroot00000000000000PNG  IHDRk?B3PLTE66-./GFI&hgoˎ~ec/tRNS@fbKGDH pHYs  tIMEz IDATx흁z*W[ h?ehL&Qz_ $:G!}}]`'7 mh;mfx+nk^^e֮܊75ͯ1wZ^ƓXߏg33Ys+nk_CL*Tx~̨^^8~h ljޔۢwxgLGgW-s=O1#w3`cԡ1iy}e*ÔVibZSXf~š`ؼҴouquJn)Ƣ41L8F>[YMU8-x@:VE3z[?D#Sf`,̶XjIarKr5_ j}͌ە[T8ڞ6S*_sh/ZVRa# {`vf&(%A^^?]2m.Kث̬ބrV־?4Ŭ]󝞚mo{>o vzLt7ⵖ+;1 X*۲;##|_ƧayG600 M?TGyG6O̮[*77rwc{;wȒ4]grq{ewrӿ&~ Vth=N|lI;Ìogȵ33Zz+1 Zweu_6}/Sazkc*Y[8ewf;eKswZnUuN\;C/Bp˸T)(GY/|^2kS-~1 wV;׬0M0~|:82.vp8Dq7T+v=`Vuv,jfG`M8ʂC0~V VDV+0 {ߪY 5f0{YEK'?GEקa^{.gS$"-.+%lbCyqٹ.vDY.yuǑ(ԽiWw;>"L-¤533n٤bjffbvkG?汫}(7_]f0,y&`&|w^Cyǩ]y\-a-Zv@WKر\-a:rg` ` m=2*Nr*< ۞bZhk 0Ob;=/aGI[<5yX{+NK^<Zc0ARrL<3wy^&wBtťN3t-y;%)K^fnwN0ۄ L`! x5fa L <yy ~yh L`2A iOݬ%k`7 U5 BgaBaF.sB2c=UBCeq:+'XhOТ3.gLhS l=1MUBK'lMB&޸5UBK's$:*`J 3.ϷLl蚹+LsB\8CfLL}(W+OLLۓS0;/B/+?5LQҒOa|d S*s[w, & fla Ssevq RgYտ I;+4$"KٝN56R[W0^Y02fr=h&bCC8$5xߨ%GఔP{4aӮTY[pzZ_3F,|T8Ure:oUEy;}2f!hZU-H\3$Zf<|>E)*PԹe"ۺDӑxIgC*=JSF\ 0)UNӧJ\әaZ*lWt#Cr$9(,perL`~>jskWLYie7X.¬|gX1:t?Ko1Y|.,qNǢQ=5KI'/&Ic:#a7`^rˬ~zes2sTlį]3})̎?ևt> [Lyl?/ì[ߋYYȑr0}o5|ٙeU Z _gWuj%Z&~qY[rbRɾ8?M.19X$~0`BYE069[e5W8Wf%GCRkfnts7h+flK9/InRĜh;u}R%K4"^x`&`:%֥2ŋ0 9+gf^%fL><_48~~oR^kÕXOCM z`4h6u&4;DÔiK [,P,ly~EM?;@Ϋ r8nͮ4IKhAύaL{xxvL1x8 L|z4 x0!OxQ^fu;0ifP&dgތ*5Ij2l &:(p@IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-peripherals.png000066400000000000000000000073021504763705000274620ustar00rootroot00000000000000PNG  IHDR6 ߹3PLTE,-0POR.M&b5g퓖qtRNS@fbKGDH pHYs  tIME"?*IDATx흋*@tnle#Y>eaZS]f''Жj}%6Ms댚x}jc9MRj/4DT%hޅga5/,lR~ilm6sHt {3gExY/.[R7`aqOvkU; o=480S[}6KA(+c.. ܲUne;beɮ5ڍM'i ,l|Ipy"+Bq+bl$b&(_7-?[։ ŀDDL _$d]-}l/іf,0· bQg$HB` (@K Bi d>?[L<1b$,1Hnܣn6KJ5K>95a?@W}'J-s/m|qw]`lHZeGGJ8plLt;{w`3`،M Xlá`p,ڰpNQ+cjO҄?ʭ<͎شqtML1 @s%n(4,C2%l\V Ilͦgl4**6qx4m.6_u -l-D*_HX. YviPso&f㺍E e/tHq-L[0frlm %lh5wM3T5`ӮP { .4T)ɰj-XrݢZmUvm (UPm-;n{REsXņSq4M &olՆBp~#jSm ϫ&LxEaGO&fR sJ} Åv–ւ5[s4l__ t lvGQA0؁9ql$Y΁lfc{bn7FuRkShܞԯ0"MznyPmJga?({[bJ&[_ vcv6˚} |ÆG6RnQqf;IbK G+6v8_M)(?&T<=)v`&h`;'5B{$lGmFq5k2l~NztOj&S` fslGu[lEP“mjXFIjsluQvMK(KhC{x6?*JI(2֓Ff(ti[ Ii=)g%vsl [BRjc;'[x,i!)7[g)ܦҖvzOS:'k =I il4"tړ6dI[8n3;WғGUS6'7VP[LY祭^{Nnչ\Pm>H]1~дno۴+\BٛW׊ʅ̪rr6Qq6ZLv/cHFMާE[Vlm_@m%Wwkqm2'FزKT,-b<޺ yUjw}+_(J yt_>?%K7 6g0WIeIˋgD/Oö^Ά^bw礯B`^~K 6O iѰOe>9%Û;Eۑ2C摸]ͺClC^"{/!epkr7Kdm˖eqwݛ`;^.{Wf\8g'ݩn;^!W/WZ-;;dˢœ 61WE/^Bjrq˙bp=l繉¯ ݹFo"|Ilf€cv!t-l$n"|h':9XdTOD'Wܩ:98g&sX_7NͥV8f~BmOn"ږj=?[ۂcbsuZV=g*yd5pUݰm}6R{\ܰ'}raMP Hfܼ[D/8f޷R;$O+04wU͎.lc~Űd12B:6Mfy|D4i\\{Ǻ$œ 6񤢗E/^lbē^,zYrL^>^>ܽϡws{/^,zyD/?Q⢗M^~veˢE/^,zYxR&zY`O*\^,ؤ/Kg+|ű%F^~6mv1$)X;d֥m\SQ&yKl^/l[_s/}[]焺Eű6=n,~iX 8Iݖg{Ǿ^6\}^~37c,zYeˢœ 6񤢗E/^O*ؤ^>^[ij)(zy^n.?z-zُP/ӠSˋ2X/]jge.e#zYeˢE/^O*zYe&T^|O=U`lM 77ƽqѻcwuтM ͱQ,Oli o<3'=u_HزV_Mn_Z-cfb Xcynگdž$h/U/` ARfuu6_Ⱥf)卻6Go[΍مcuoǝN͇YYp[GމAl[֚c;AEPԹDLl1zɞ]B31DRCQg ?Ux@l]ed.p6l[4Fl{@:cLѷ؀-m^K1zl[lVƮo * Z -/b2lVvugF^]4@-cnݎu[;rSaht܄P|!ܖce00 bp&6+p|6-Bvۯfw rǶyW+GE m-.Aſe ]g`$`lM|lV ^/IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-quit.png000066400000000000000000000062401504763705000261260ustar00rootroot00000000000000PNG  IHDROT3PLTE" %('PPR&a2nT}o9otRNS@fbKGDH pHYs  tIME5f IDATx c* Oٝ S^ gUܒsEG>K6G/aYMorucr=-;/  5\s{ @IPGr)(S;`2[Gtq= #䭻/76^S:dž>B1KeHՌ/?Pk=i j)4I+2 ߹`:z4[KKr,G݁&T}p x544dNj.䳊"xw8k2.u%=} a$OlcPcpYze1}6;f(_|,=;varW8)}^*W[ \/Yzڲ%fإӿYNo.Wc~ĝ+ѪKv¤'ͤ.EDri#\Hu5Ұnq0.q;LC6'ܡEO&-\>eϙ陾Q_ebK|rdK6Vy2RHꕹs.\hW@lr󇪲Tf8os+\4RX*.0[C7_ʥUH!+#[bYYlXL.}/K.]&qÒKmpbv}LJ=D'#d*+3\7tǚbԿ8,(Ty {'W7-&O2#\py."\voʥO3V\?mUwsE7ԗ" \nԯ4?NIˬa)Xqe]`E [w4RVTjZaβ˭\׼+ϫjO^(XjuԳp_q>WҧWmq5,ޱ:XpJG@=Y} Υ(Lj@B8ַp%}}^ HV{\ml>9+\_"\""\~y8X{='Qd\D"\Dpy80,,\|J(K.ːϒddA"\y.gp$ i̟\^7m& pv\KҙPGL1WJ\6uesWD|.3bIU'hTwc8`^qЩXD.XMP2=tMٔ e h7EW+A6u.mӮJi4jCb䥻)X$xFenX(6m*:JlGQ1վw7mqn)zf^K7/MsWԿPenh(e:(Ƒ`\PՖ'/7 =KUmfJ\슀W(d}3[T.{̿nBɗbHHQ`(LMO󲒅-)\rfa=k#.omϕv~[TZ+ +;"wW1.PV&p^=`r^Z}e,GhN 'MbA[))?s CQZ< Jr 1$S(şrYԬ ('joG17/q TC}KIX(TJJ#'po^z1znD0&dI}t ҙ89`grO=a|:ݼ%@0sם{Hxe>y?/}YK<]zE<+!.ςc/\~E%1`ED_D"\Dp."?"2i,IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-setup-edit.png000066400000000000000000000075161504763705000272360ustar00rootroot00000000000000PNG  IHDR PH3PLTE..1POQ.M&b5g퓖ɣFPtRNS@fbKGDH pHYs  tIME %IDATx ߲* tlD6eoj1m.p,~C`@^Ζ%q:֞'0m!8Xz1n׼č[30wТm5 3$x7x&Б6 ffX#N u4 M#`9M9RGfOڴXNt {Ѓoeo;=!b mbjp\HX@hd1^^bע:Vo[Y.eՐ!,[ /(70CU?B }fr\dK c-DT P[%jjܐ!{W~QJgB^I 6@xL+I(8_P6Ӯm·K5~_cs6r`ZAuo}hnXw V%O 6e'H`P161C|ǣڵ۵'=p] kHhzOd6-?υd.㶽9- ̚TY=ajj^j\;Z5mw0䅫 [wzIoQL"UyWA_BBŀ'E~»e3~B^;T$:N$V&cV=0=w;<&ſh=j?$OEB/$óTb>0 %tKeSl/nG#;!!I P!BASgO{R\u$DE O-t|&D&gHc:-Ge{HHLYr+EiE"C =(=:0z'^֗~*? )PCrOv畟L"mlϰ|z"@FhJҫ[H?oP~(RH?{Q~Jn#H,t+?]OW~ӕt+?]ُaajfkGVe2#$ltA"&}tLU# 8TCJ17&aDCe!˃2<=Z]ʥͰn[ Def&Q k&&sUIdSRG_t)a0R w5It}=Wāy:c6Gs'zz@J9 Ba"!c$[O W5O`&c٣E~bF!1UeYR9,^BG$ac 䰜Du+f2&   {*a_c*,#PgkH"FVI%V"BT&4>,ǩl΍5Obp8'+&i1R>U#à2P@|z.2,Eg&yt>^0r81arG5GB|ސiwk4( Rm;sȇфD8ucp8Fʏ#Q<&pcqKLCIj8:IR~k۴$G $H#]9xmg{|84N=D7Ae7=31U&밙Q~b9<wt]ذw4#Ol}Y D΄.)/~p&!&'/1C^Hy o_tQ;'Tl$_t&2&Aœ$m+NyQ .v[[l¦T$q.w3JHLN9Q~g:Ep>'ZZj2]gyT׎\ u>"`kG}c-cj JDAQxola*SP>'! H=lr?:Ӆ$(WCͽ|2] RQc[gT3]vRz~HtO;>tD'5 x_%c^(Oÿfrj4O"gJpGB)f(ɿF2\HB7 "/ W ,rxJnEbI7+ƒ$8N ,ȉ&~Ʀ.l$j)9 $$:M6a'6!hIYYK:\?!X; k~8kXFXl|A9XiQDZIa>X@Q)q)jc0&x ðipߖ.=bFuN& rU&y/iz /14y¼)[(S3 G lI >) ~C H s(0$aHD&XV"Ze ڷ)0:;Ă;H$q[F M:4G/ .kIzJlL W&(Ϻ<6(~$ax0 nAɿAU`imc yyԟLⵧqNb ]j`W;N$:N$ ^b-'/JIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-setup.png000066400000000000000000000075111504763705000263060ustar00rootroot00000000000000PNG  IHDRj3PLTE" -./FEI&hhka2nT}u֠tRNS@fbKGDH pHYs  tIME4"/bIDATx]* :%ĺu#; 8IXsadan E!7fÚM]VkS/Ɇ -$U QKZ}/A pW+ Vj{+Iw߻*%VP%V AzU]]/fQm 4RB  DR\@hqjϏ ^><ϮChZ 6Gy1_~ cx0ԤDb Ǩ2CP>(f a?؋1g_+HyJ&%)2˛T <ޥ v0\yI=*.1.|еpSQ"媓2zRc[:KPl 1m+$( @=!I0,IիP]^,ݫB:q?( ӈ͒JiT--&8 G5 DZU\J YUcc=bŅ6IwKRYɖީXUFv{!$NAJmm) B_nt<J:0J_(GS:B[)ϓ:q#JWR/wzfRul׾l @ v`'{{'3[g÷Nlh\"4{MV;qKPO>O'u4#n ]cȊYoĢ*^$GEђWYp2nRS0{)7@T`UXx/B2c 5F!CE t}R4C+]pnH59?׀2hvHCm xc!gh.?u%3>{ 70kZ_)U]@+ۻΑyf:#M<8tz嶊 ^0O 3|t]f¨d8&Nx0Ybaj(fpf:LBx0aϮ έD8~}%F% 'é:4ʇP %[@AW20H~U4&h> 72hlk(.D%n" -aZL8`/'L1Y /a5Xg‘SgBwOL0@Oցj,G ri8} 2HL9#Ƥ3ּ0e'NL8`i3'J lh8u#Qrbk C6b:O]a}DP2 E3vaM!OҀۙq]41==phδ0D`jF..6Ȱ_H_ zzD8 e'ayzj4JVfvMgi3FnlLR#l jB hA_˽B_-scLmf=)22R:@̞״dCy=р)`AS.C=Wk /9?sWrdӷ@\K&;Aĺm@*mL4wE [Sђy"efI{֍g D熧d 佽,I#C MT':wpg|y|)VY `X! ktD;-s ^"![.v|q5y%i6J/(H9Iduتvr%`.p@BtH9 4%xT$crGU$di::c9Q+UByfZv¿̙;}#,*6$!Ka#$=.N[ueU~IUDf=E*s; YI@l5j2 `0JRGt] !KSܰg$6B(7Rn"P]~)JU Hz|?[qbpJV|q-^'nu\+n4 =ޙq7-}^ޘqc:=~O\ mop;yz|ƃZ S0`cPg?[a6;@9W]>[lj4Wof/qاM Bh&EjC{ja`EH+vȦe;|Ɲ?ocvjxR|E"IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-speed.png000066400000000000000000000040411504763705000262410ustar00rootroot00000000000000PNG  IHDRAt,p3PLTE`K *+,FDH&ggq˒~entRNS@fbKGDH pHYs  tIME4]CgIDATx ( GMm=V3&w@jb9#_IN;* `'8LOO|Iݚ3i r9CxcLoJe|F?#xu:3'(CEeIV~|K.f `oYVტ+6إsb9,IXQ6b1G*t! @}%Bp$a%{`69aE5hvދGQ!tAቜm;e:ό!ڰBVYy_!j~蓭)  A_ 7 &!{sm$cEkt "2TE9 PqDβaSbVZ/!-SYeעe㲼I*"0K)b:̽x`ŏ ^: 3gFOP'LҚ6 e(bH'Y x0l7W T8Z:%͵Ţ=Wm@H_:8/Ν%5A_mnїQq1IOTJP *A%'( bU%t7́TqPLt=xOX^$xN6xtd!H^C{$:'Jf1\3d:a:-DUI 6600<s y,H1>b?A0ydş>8!m("v=€1+TNR=A9TEnV׶%tT~C~M0KGfr;Y֔ @,V6,&_)rW;ĶY W1PCD\1+46hMc %8X_Mtr9>+=8[Br ΅lsXt&Gg༊DЕo稊}ODSm䂪po6\;yd&ˢMdfoxpf!x6xb1\'EkޖXb\CdEOXa/NSt4:` ޏr+L,֫d{`ݖD0RU52A7&ö@pGfrqP*06k_3:48}qP^l+TEjKCn,]r9S}Z{q%^f#3_X.lxpJzPxX|:gy! *BzSY8s'- D0tu׸=CSwu:}dDzLvx_s #n)A}ʯ]W+(A%>$$`o*V3 pZ *OP[oyӢ7+%6M8(ë_ Vi1;%?To CmĴ^#=U(8_8t[\3#$8BĀ %=}…a] >9?ʻ'Owڋw^Pb,tMſT'C߸h* 3y/IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/hatari-ui-trace.png000066400000000000000000000114071504763705000262430ustar00rootroot00000000000000PNG  IHDR()O3PLTE`K ,,/HHL-~gim}h !tRNS@fbKGDH pHYs  tIME34# MIDATx흉b*Iti/ hL;A JL;?P!<*TAꏀ⛩d#>!'F߿/DHPU)(qPb.R(9NC?>6Pbà_1IFB.vGGK.2շW=JBU6 U9܆|fY=ŘQmf\P\z J5( D;&lfX-X17}1^,\-O]%([T{g?U"꫙嶪7Ă~>viJn?'bVvϳxWGP'>8 zޗJS ͆={p%Ԙ=c4/zE?巧wI 7Ƀ2Eg.<gEIGq(&]?_W42rgPK>[%Ux?O$η/%&nw,z`4ĉTgP*Aij3(9-O:7S_`A!AwN;wN> 0㉠Qd/. $`9O;"9јvYHp U>hxԨdHLyHST,gRQ&dRWL$'ü(JD/M$*BH@}'B~ȺWy~7Y>c:}8xo%O =A9f.^ukPJ漪@Y'q29w(zLu.T1g-EEiAV.0W=! =O;>Pg΢(UT]W:@*.z>1PfxTOP3/ S`B@xqK_@!popA>2r鷀ap9U?JO)ld"n ފqj4Ԣc/L2UG}b먦Gi"&Rp;TW:JydO91=vC05Xʽ̫S*@iC;*]> p)ʷ@zhţBP21DP**nuk A d )O,z&jss?W{MTQpg"֮zW=~y=BP=@P AAP|%>+]SϬHy}5J*AybI8r;h= uDDq0H:ȥKl8\[ |d.͏P:.AdjRjVIhT.Nð6H 9P 9 '8_=Ø2U;\WBԣp +I%gQQ3z;j _:uQ̠{TPGdE9u:'uTG^`_$~骗rW=_\nDӫ^GNWDǾp;"j r g#Z6t,BP AaBPtP(.|Z(lqu~qZՇb>bdPSwPyqA\!.&^VJeS=\DT^\PG6PBN{ڿ~bZ1 )/Fi$r1qa6[z Q||7Y&䆃:bBz~&Uh%xs[l STG8ą(B;*(e?I>~P\Hz*uTG >77C84fVkڇKPn8>sxWjj\n(zd}$&7*q>5BU\zRk*D: ũ6YBP A!(L=o80>W~ [ AG]Qe-T{膣XGv ( AYv}REbP-L{bvفy><0ng'Ub8( GW/3vNP: ѯ7č f gL=#V:W{zY>X͓Q:CuꨁZ 1V@eꨭ:RG zQ5jC:qu ljrԞVkEjur (BPθlKBe$@%瀊@.rwYą b[8Ayئk-Ձʉ n]l+/3٠A19E-킪 B܊@Mf5YqX:[U/.ą&]\@-UVYq)P". @폹zN~L\QBPGVNfp QnPۃ`7~sB| Gu-^LkΖGJ‰Y]6 Bptȅ#.`Q A!(wb{ʇ᭠xG[=*r+YM;N(TF,S\*rb qڴ$؍`Dji`CtJqA@8N´ra4P-"!T`mqDm>VYqaC,W x&~ P W \!.Xv*xRG]QE"a+kszNP^µq-=뭋 í@"}GaBP 8EA!(S' L_};MT!M#()2q^ ՒfPUґ:d^#u௟ ,os@}8(.9H)"N|QPpƘA2 ǏeY2%݅Sbk t(RNV叇OQZ_7(C(.Ԝ$(ύz"82<4hjt&Q9;1(B]LȊ@Q j|HR?A+&)7t2(H*ēwi0)y!}/DQK nsM2"SzdoHҗps[3TN6ƻϠ %'Mjryg2=2 &pD}DrSxX(.Az|5ҠH0VIBʒ57%jWB1Šj1'dcZ236}3OB*`)z`uy2UX :GIJ@IR! DvKA""{5yDmU$+tCPof@Pi'43( #(@QR/i (L A!((ƆIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/joystick.png000066400000000000000000000040641504763705000251240ustar00rootroot00000000000000PNG  IHDR@P(O PLTEo pHYs  tIME6NIDATxMn,'#ZǛ@,YzHՓ7OE(鯡?l?%iEß*Ϲ<׀ʔ{GFJ9@g!XeFAkUtz#A{:yŘ`#m@CJK|W|Gdk1D@,7bzO YnCk6όֆDgh2{9~no7>8x>Flށo ^_wF>ZƠYևnp8>T';zoS`#*]br[mB3k4 lNmbvTnfBn3wg `mr?Ӈ7;vmrNNӇS;\|?p6`6`6hNbV`/%@ l,`X" # g٣rTa N Hp21dSƐL5dxڰ ckC;uJY&)F1L@чU@\m>»E,)a3aw$ 4D(*7YDC ]U7.&ka s@IC Ɵ10p&s@"QQ@X$K)\ItKK1pP89` n9mH`Aߛ\.0C2-s&E@7xRb<,\<gK2^X)u$~\k_6I5`6`~Tprͧk:~8z&S ɹ ZCHQjޙƪGoY $ L)&(PV5W[Lf)U*tUR S8bqLI 0C`mXᔑ>$Ah$ /&{jG[o#5WTgcVܑ{5( R$tll ty2q&+S[4Qq$Yg#Na3|&h?t <,ќ=ŐǜB )nͧo +~#p._g 8ߦ*!րrn=|x+^/S}Ϛ D$g 08EbMP|&Mu3r)!j2F'&&z}ֻeڀ> Lr>+ӷ|Іܱ^[Wlʶ׎+pћ]}jypPnq/z .pSM ؀ ~y8}[6`~puzp`Όi.N`ڭQ~:pɲͧh-VW y%t X,vj7Q끔1ށG(Ud!} Ӣ@>ts5PP!nʢYƴUsLsSC%x|\%Ozs z&l`׿VkԖIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/kcachegrind.png000066400000000000000000000677741504763705000255500ustar00rootroot00000000000000PNG  IHDRwOgsRGBPLTE     %% "#!+)%$).%'$`/ /8*,)7?130621>7`=(797.C>=>@=`=$L2%KY8GbFGE'X^I`-TaPLK42[/aO"`5PQO/_>_PGDV``VUVT6`P\WVX`"1jG0o _`6^_\?k|6sIjedegd3n6~%ORlmkAwR U&;UUtpnvqpUYtvsH@*B\\#^ z|y~}i8OG| D/ToEB{W9HjQjl[VL1{TMoSkVmNo`܆YRr;$L+Ut.iiX-?L)X9n=}iTHHqL5M6U}ŸZ90oh{㨪o曆J9q½꽿\ÿ§Ҳ:ϹjGաlx]߶\ pHYs+tIME 2,J:qtEXtCommentCreated with GIMPW IDATx|WbGsHg,,#0s^8{ ؁$k*R+V++.kK{L9U,Yrji0}6,YJMhQƷ9XMf0 o}&BU$a:}4gp馬p (^sKtͪ/JB?M7{t!EөIgCZ %# ΢6< .3p~IK|(0F䢤Jv!.P<$g c> gr:R~Zp³,ɐ bc<)9PdᇀeY?Ni*USb9WZ3h萻~]?ɷلYUY0}b ~՚Bɥ\Wc|>?%“#!tgy@̅h+O>dx8s!/hj:f1aj$lF/S|W2.?݂24H/b.26E'!%ѳE#B͒*přc<)9PdᇀeY?B#S!{|C ߏ[=?^П6~̷لY$Lm1r"(7}{s)OO*՟| +JP$}`<*xH}Jfqyln5r6.|2V蒍磄@>'mgP|t#< cgH &?N._(aEt[B]ON05iQ̉" ?Ȋf!oi,kbC -{ޭ#PdN,¬y&W S3w)w?>*?y'`K. {0%Uؔrğ8 Z@X)K#L)RRG ay ˴=ڭxv5t", ܱEcLvFŧcߊ^);Zsb+,Jc!'qjըBTC[!pbJ,s,3p)Fo~܋(w0o$w 4o45r>.4Os H6 hKF3ԙ`,_0p.g;XDV0 t>Fz?F*)D"Ec,$/s_YFԖE1Tw/ D_U=(3*:?n <'To Eȟ4.⼲</^lr5ʹq;Tra5V|w\~c ,ڙlbYp~b9Y`oXK -#_C(%x;ڙcLcbfQ Iۙe!-1B =o/]h$\ZYqifn)$*?m^ZJK-;%!E 'V;t^.]$1߱xΎ;6{ x9] ,I4>8螻Hnͅ0|_dr۟H %wv kLc HPw?wG/J$whbwR;;+I1I$wRk.U$wR;Z5)w|owϝWvbZ)Sh(w??p'"V U\lKՕM b]qJ3+wxŏ=fYKU_]mIEm,d$ܹٞrseӂj'ʫqܹܽ\/+ŵv^p]thou0 Y$YPnz*AD-sy.4lw"n %@X ME(5 q݋{̝pTsq4PVaJ2FdfD/mCȬ}K-Yn=Զ JB`? HSW?w^s,良.m BYY*Iqb!]A~UeǙ!aΜzǒ1ΗAPj ) bq;pobTSrOUOe;~ !SG`UK#~!E5NPg' wh!o[R^3l0v߼T CF/ӹ M?e+s w,N=櫧{ts^>m~DwIDGR)@`>\@V)dv&*~v/^}9X/f.Ժ gv>\( GH<ʦ1tm*\wv\*wTrVUQo`V Ɲ\c*$Yw3^F$@5߷-jڙ-蜫fa!Ӣp!m?G n :2s:|s;-QӔ8늻FrJSj'^@к*sdjold&4LbN ·Uf. wRf!@{mw6Iʹ-2ܭwNr'NwnLɁn>Âb(K(9]?oyI6zJO1ZuSuE|ȼVmaந$-oЊpUvJ3{񛾯 ^uQ̫A)?HJDs7|ֆ.MݪZ;^Ӟ -nw x'3w.+/u <'8_qF&N̽?;Zu.i]0 M׏@lbbr*Ɲ'&p&?ۼEm>7@ z .  bLbZ ngcֽ,9]H &a˄85x~5͗|$E3c- ߳"lq$KP9 իۊYB'GC;t~?ۯ#Ubͭ ( `Ko# y*-űX?zbwSf~* ]lv#TpU7;9w0|}0DSFItdU! dGB0Jt|& .DR*D#A<fM'O {ې wJ}xbVX9uP +F`n30H*Zc3lR5i5.4şc'ؼh]Vt/ K@rn,&s&ƕ|~TY[CsX%AzOK+۷4\Z^ 9 j XO dq;( 5LhCOt)H%Cm wpLSʂB4jΌM՟?:Tbr.@.aW*^qGU qYѓKs^*4YP5K @G+IxD]a+,(B!* Y ~3hP(kxe!48gMґ/3[1mn7ş0w9÷YB@0h[s?(_^1J ¶σ7QB\V4Yꥂɜ ;Oڙ[ 7W \Q;%(zƣi>y)wK,Y|[fǿ)ĸ-mhg^[dU 4C Qzx_洝9|j^cEh"B;f½TKS81{ ܱ84 ~Y| 'ea[3|[_6*m\0~;K|4 Io.W;lP KĝɜS.<@Bqz:Q; =g'`1#HT|V\,7# ($YhӰm& VOݼ9c(,EѿʊU[cb Wa~(eEOn*%^*4Y͝zR|7UO*/8DH(JNF}eRG ~(eGER/8vc1lyegVd75 8>k?lW'qV0X`ŝMIgQpm)9/Zr$OrZII)ϽV$wRu΍䟡u$II$w;ɝTq~A!kQnC.KݱU__|!(j`O;P Ni$S{Yy ?86+l%R_g4кNXJ;p.w]98H D/K!wَwj<0V߿QaK EIo7'zDL%>B8WA򷒪'XY;&OwܕWW%snR~>)CJi~* )\DwQz{uPקfiS]*^6Y\\r/8SjBW ̰&*ޠ[슻>؏~Uw%4%o@)0;Ӣ &RH^8n[OAMґ \Zlv.w/ۯ9evQ΢EkL7'NI½E:jL;j~;/5[&+:܂˖H7N*wo(W.nA!@GrWhH!yHQ2dTHJl$i㬣 ȝ}+5ʫJ^$V1 ܕ$"a]tf1HbhZ[3{Δ?w sHl=P,A(犃voC"/b3)<s@MC&ۙRNP%8T=NCQUpDg?Vbg4pg6`ifXN͎wq'ۙRUq'**z .CQF'q&;jA}VV E.t`4>ߙ XL!";Z;3$z^ܵw>P18PK0;lh_7 Q *2ihgT?S4`L;vYt/[ϹKZ;vQhBMRR'|7mhȰZ WDRs&;fAE}V*(72pWb\*w( %,M9OL3)w h9t'&%^UMMĂzTZ+H$wrNr'I$w;/ (=IlUUׂͬ2b\rU'b Jl:{#,-{]YJUW1 *bbRGUZ;Rᔺ2V}a՗XѢTDq"xdH BB\f3A #)$>R-wUUW{`fy#.W7Tv]){ׄSSJdl:wBf4NMMG#L4h g.o=x_>Owѱiq1wEV_żڳL9L*'-p$QWJ PhvECHr!;Ai>1g6",ðK,>j,w_`B*x{BW&#W1p[,P~FÙU\i$wKR=K)e!ϰR4(eiJӧ'vDY-{"y/=U$wR;ΔI$wR;ɝNrW/ԜsЃe[35,?ϥ|H ۙhDҞ&\4QBㅬN/0 6|w͟ !6h#V!_ew{jDtU[hW@dOFi|mǡiʈR T`ߦy\g*WXVb jgmR \2쓥TRUnWE?CrOr"9Wa( _ꄓ 99KRAp&43h Mo_ó?q_vn!*WPs `'0NFbg\(XJrnbZ@G݊-,+G2_7;T'oȍE܍jي;lciy;l"zz)wNTܥ,Fzh^O{ 2֨ETt EP[{P?aLx0/-,-#<+v&SˆZZeb1o# ۴EÜ)L3ا9x -ܭ9wA]д2^l-=tewU؟#y(+⮢yB*s=I;h/6puwda5q_ESn XWԾLJr'dac/YJ' aۧ5nŰs﯒ۙ~UY븹iw,7.,FzbLq,sR;|GUt/]h)wHבaܕ`G$s6Z!v0ޢ*% X]_YgMI5;XY`gx%THZBx6ie{BFl+<(W55Tf&U_Wl;voHϪ BzVݿFI9箴i|sx&[lhփYIrGW } 4ifp5$wR5rgʹU[&g]n>̳8Dr'嚻_763-;oJ\@ʯgܜp U;*ڙL[rC?w/zYWw_ ~t {ӻ=+kU~&W1=-ArJؕgx$z3+s/|߄ؽv>p}_֌ ]ч >Qށf,!sbvs; r MޚqqOy ?kwa@ݍ]*:Lŷ1j<>RZe| z?U<w.U窸*6wV՝:,#x6: }M ](0w_s^_񨥄K!+4udD{e^KuWA8U֚{Y(ɝk P \!?`nU9UҢA z`Ά !ЖW_:L@+3Ѐ6w*@ⶄ;jB~m t'='w)ˢQĄf~*$J"ֽkH@߽|;̖6/yDLLX=]vڵ~Ͼv`ʧ;;Yv]ш%+!8w7pW?٪?Xw߁Q&&s2.R,et}OTz~tV&H6]ш%RQ}XJKܽ {ǖo9k owUO|DU' [wve{IԃY촵:#>_sV)wW w:|)ݷ ܱ&eX6/w/,hxn\\ ѭ;fB$7}}mGg23zP7*d.swr woyېwX}zfsWWѓQ|n1r&9RDA%w0g13<~g*e\jK=# ^{v=z3znԲ 竰BL-6?Cԧx;+Q됻u9?sqNr|rR K=Vj{S]ʙJ.,)5m;~O%wjlNrH4_qs| |ˍB)*eQ \wk9OrYe'rɝQS[^󢭸0̋~΋"mu@:(Ν]IƝhLGʛX%a>+N%*֦.AZ#Guݻo\/9fn=r'Pӑ f)VIϊs˿ \^sWN޵3U=]og2zV6K)IbX_/DLQb"P2]ޜl4V:3`a6*4#<ڔa]ŁPP}@'Đ b;ٟ1sxh"rGMQEmЙX#up%6*4#ʝ!Ú;athY/ζ#0kiY ȁY9 YqsZb"rGMQ(ˢ ҄szX!.Q*c]Iwv+v޷칳Ѵz#]ܵ0wY N+$ zD/) Et[)|pfR3w =HNJmC<w߲,P2OU-̝hf)%ILwEP\ w,.Q2%IH5w+<;hkUp}䮅RuZ,4 R5w ` O'`q @ œs{4C+rܕbgȚWlwmvǾ]k`"Z7ey#]ZG*<ڵb ^@f }{՝Gh]K} oW/ _D_{0ٳR n*<{KwoY`'2~\rw#?\}#u M%|3Hr'U}}G#=+xPـG;q?4Rgnb#*$= -,¾v7?=#.iiڀ;o`Sg~Cbuw{县ZW {g^U߉=+҄Wć;cuzUZ;ޛ7?IU_Vᡦ{ɽl~$= h* |Q|*~YUP\G&΄eSuL܁ g`F؄}ݳ;1Ѱ.ye֏ʬY*|`:T;OuX*<4wUYߙZicu(>g) /  ~$ 7Awvhx3I=2%e'~TzϨvZEx]~34 xwF.L\5;;cݟ ߢZF[_,3]*g8:[;d] WzDmځP3{' pH2_NMZU ]W̿{ZH}{Ÿ{[zqpmH(]_ .)ZFrtwE--fon.:? &D/mcRKsk& wpCSsS1p+̤un-3c?XxTGe1 hxMqʲ'&,dRIK kiGm4Vwd(xu Qq U]*w`(&,dR"w(єPᡖN`GpFڐQcb" .mgqO 꼾wwU;j‚7.ŖB껏w hhi+ w/wԇ5F(԰2_EN3(}BL3'v&TJfڙS3 >Yh լ;z32YCm,1 4KȮXvs)EBRG4̎k^޵}~R[q Za*>.mi^a}+X+ܭ6w+qWګ v8;! :pCqb¼X]JoH &uW ܉͛j SV&c: 55uω^(O;::#:'V7o Ȇs'SҖ&[A)cU;Nr'f~fkG <ޡyUqGV32?IT_ߙZ"xh8W-whv;]T73;G< N@U|;C+UIPe9peTU2rI$w;S})pG!wB~ C罘IVa'mcּLηRJژq;N=xp?]~N@onoۀwtc]qJ)v;݆,x!o?qN@|w 7pe7?vΑ!wKOLv6MvN _AOyt !v; YRN@: D䮅c^*X@ ɛ TJ %hR^ԉ\^wV3Wzqg)/Q݉wuy>.<`un䮥c^*lGlR:/Z8L-YDN,,?ۮjrr̙_-wV}x5E.6c?ln%/b5vܵ0w&/)p TJe> -YܴCo%@(]I[%͖Q#wC y F#(?١Y*IZ;Jhb|ZmZpGRk>惔qehjiz{7\,4U 2Tɩ7cْ<<ok~+ ;C( Vꮗî9e˝K%)@TȒd;BNHzW,O18vgKgh?$!3e󨝻JQG}*o gPڈ 4 C})p0hۘIZ;m=.C,X}Aܱ"wa̜%")C:F*!b~b)w,BNY>&Az~Iv{0؜E]+W̕5vKeT+*Lz[`M+n)Dn~)wt"G9X=7SQQz]Y{{ KĎCb}߿EHja'kqJTF5U d!ce~S}چ{80UwԱe!ww|+lAbyAt&N ]ɮgÝ4,rb IDAT.oo܀"rc.fAW,Ԧ Ka\7JDS &oBB$sV!*}BVc <$!4 pz2ٜW s߿@=vΥ0wvGm_ Eg6SCG͸B-YMKeQ'ݪ@} u\A-a;žnNwԱe wU譭۾G:YL͓@ҕ2۱GOԙk1l^+=3] eP6S7@3%v:uPw9?fj:joXwL_}V}"v됻p0[;+%XSu;;yFcnt߬CڕP=|Wrg!]5:;hwԖ;tmr'*p'8~[ *HpHr' qGk)l^ %]m$.%$w2q~Ƹ}']*_ŸԵwvbw=ɝ wFrw_$npWQA9w<֬ ;zpW0w![+CІZD\rUqJ_abpPa;<_cIօ 3N`"8Lb!X.RrjkcJ֍ ]S9LbSj)Ӽhgv(UڰsGsڟ)b'[*8Y0١TgBց;ΑJs<~'b1w sAF_87K}&QnΡJU6,$j=˹s1n^-wb4N5K|k!平CʆD wqwWrU)f)b4IՇ^onϝJu6,8j;-ww%w_`bobկR!'mdR KJwwD{ލ+[o*8Y0n(9[*١TcB֑;"ܹS}@=rH*8Y0%QݖJv(ذul|a& db9o M`WWG;PGWנ5T_ͭc-gڀgG}9Q'RᮄЕ~ + \YUsO[F뗻BW'(>8 Ea #wQ]_V ܍wy\LV [,W)T5wuhrДiDT½&ebB+S9;O: Jp(N,!=~e&+Qe yj}%~[Y/vff#=MBA'w jJn>8uݭs{$Att/A ` ylD^q}m}$TI1w o+ ׮b"Z< )E}2;S;øC_rs#BceW6"wr%@z{{暲D:GDVO*EuFW|0#=60 'gd9ΎS^pwJr'K],L,f?9yԟIMYl3xaaev{龴4YsV54MVJ w+;*O}RҶi'm(m~ 7D;7m2{s#b&,qzn6SCGW N|B^b;.VE5e!w%ܥ`z+vwHm͝N]lUJ^Y)#m; `zp>n}=u "`_FQ< d38FkF1U?#ҽ]t.Xnn_ͬԩOҠo&e:τ8֞,J!htߑv>( ;0w4@ܭ礲rhgN+솼W^y;yxm̈́83]r߭w^yt%ȑG7h[$C;۟hiТsz݉o>k4{ 1Ѝ7,2GI<1;]&y0$I$wT&4LJ$wFj.ur[I$wn+]9]ů#$w;)ɝNr'I$w;ɝ+*֛;N%L1m"6%wX4F,OYBs[1#OJxUzl8L;_%-; 'Sh>fq.PRU;] G˿&ٴ?mUb#_MĩyغprWILXHԒEr璻6;'<ӟz}NaMvң;T% 0h}dws qĝ\|]"rGMXp*isLn]K}q/o޷+a/=ehwܹb-鯒ǙieͼY"ӹ $MS,^qg4W,lg=H); *Ȗ;t\x9aHw~}ttu̝_%N/4nDPk.-e3O1W;j‚SO;;Ж<-Uhei{I2w|ÖW)m}#4nܜDPk.-e3OV1W;f‚RO;;S`Fl€&/3ՒǮ,;,ٷɶ}WpIݷ%aSѾ͒u:H(wvbIplZadOrlB\Yta5$wk(*^lZ@Us7#NrkI$w뉻NrnBIR{JشW1[oPz]>]-Q }g028ӈ^_VI adbb0K)+Jl]*,. x;+]/ÝEC31G?Y^M3]LR]L>`TDs.g=Nkd]u>ւ'3J*#tڬ|Fi;S.rĄgje2ܙUBۮ$B !G3wf744dr"2<#ba;Īc9n yjgUt#2*z@E!ȑ̝_eoN)žѠ- K"3Q>Fϋ\$wjOfC䔎A3Rx'$bcG3wf 4莘\S,h[(;rY"$i$taKU⊪";※:w-vf)ʜq4$gGxχqW0D˫x3;]$ 5̭Hbs(bsT@N"sdR%ܱ#< Ί3ۙ[?5wdILoH,U[%aaYc*wTrGRzCrOrǏϧ,W1ܭ&)sD4}W)ϝU\̑Ş;21s=g?vU bHObrY kAy"T.0S̕ᆁ'̖n(`ᨠ:"RW'%wR;c-4NJr'* 힏 J$wq6Bcl9#(X,ޖvsPL΄ܕ]vҏErWNZ`|9 :!-rܕ35 wlt/RM+D` Kfamh0@C6{0ޞf*Se΂ZrܙQ홚;fs_#r*$x`t)} [߮p3hG:$G5^r9j:zmFjWg* 4߫p8o}dJDU:UJbRVHO{Lܡԯq]Wt˸ö(4Z*A0]\׊Z,u eVfJ9Au:jn*3S}wnF뎆N-d9:SspGmNt}AUHReT4pGO(tkN[qŝ٘Z[G%wGܽW;Fmqx0sI9XtCm-Mv& ܟ)6'XB&UHR`DܭВXO^8۸)w^{َ-'!"Uc_ҟOl-` cJYķlxTrwbk)31[{6`j0k3xzXcO g`JoƦeRKT)೎BYDT΁e.%w%AoK6FUrWI:Oܭïi@55;iCoFgI6"wxzZ:K;ntmNNrMag 0?&ܭ wkɋ7nd#5+lFo\i9%wJCۘExzXtp *;,9&I ؓ\lKj9KiMX1wv*j/y>ye3kݍ3m޾ιRtj_;_e0<pBf{ I,P}*jR g6er ڷEk9wP,E0IĒp[Ë;m/yJAfRۊRIH7I֝0'+(:PjBdpha 7&wJb7`w&)I`թCɀK`Jw0dRi%|W>($+ԁ0K"C ;HOܭ݇^24\8pJܱ$zBSE@S\qS#('zQ@B?l:KLQD,3ihXgAASb}k3KLR*pU SA;RFe^M A3s$6:)VC ?HB7(w[0vi5ݿf)IJXiTCAw(| PH j¹-ln~ ,4ό6gi.*ږ#ٲf)IJyxBirvXAjBmTx;%g5tJa+ LSwqvnP퐺Ěuϝ`!e.$Pny,.NNH2g)E 6Jan+ɭ_5o $+ LS.~C =N!UfA M^U(w ǯd6SCGТs.N"w078B7qWD)d~g ,4ZLC =N;l=quIԫs^_a<֛Fʞ_h"2.kkfV%wU,w?iZ=aݟ6vHUMf+N7jܬzͺQ;\uw]s wiIr's،*?l6I$w{o{ ת׫޶c6Gv ǪUr'wzr|Y8ԶС]QۘI$wX62t̀xtd]s#_x7=Oon8> ?v _<)$ 4X?vz;Զȧ*v@HDxc8 mokJ܉*t6יsM\M 0qRlS`GpfS?|'8xMO<,x#<H2=wuhC?RYXC0wCwVNW!xM͉KxrWPܽ{ڏO7rNqvldc};Ђl w'=304+i${QVkgf)`? M.)n@V%Kۙ+4H*qԀfCg^;>}q#s Gpb2`D텡hnCcehId\ 9IDATwwY /n;Q,twI=;@V ,Rydhbz25`YS57='{%3wTHxرO讇ۿbqQ΢$@'aGd !WGfRPWq] a:mg恠`v&Iրv&BmE2X;sHN;jkq'l$* ;ʈ;kYd6N*wW6Y~MO<o7 *4pgѯrvddc]ۙwڻ1ނd*s;Pcv){~׎V[;,c3LMmz]^!E `nsGZOOq`s\G ;8h$]=Gj珱q္X9<1N4Kה%cI$@ۓs7g+^!E ogx;x#$OwZ0sVjRD_ &N7ItŞ;lBdB0DW)W)nN@lBIWWK|*՜,wf{ġ)w[NKa`U sݒv,S(:pGwkǙq0TqZgzI,ѫ箖<&5Jd0)ֆ*%^N-Qª@Qr'զTs~k55w%䮡U%ۙ;]T<஑;ɝp׸Nr'I$w;:55w%nVjS(wGΡ:c]s&ξɹR|*sV6O4 䮕{U܉f)mI<O[EN1Qh)կKd}Q;('}Gc`g8:[" Rt@Lt`?&un|R2]ޜlGчrGC3"wM1HYKyFRpRQE,B"l5>pKYE;ҹX'?JX>,dUNrW3wKdl]Z" , 1>pK+U2xvZ3zW2ֻJ$w3K<+=%UD9w;b|bRn|({y;}V+J$w 厛_+*jI&*[ ÎJZ5, f)իWU/Pb_|RlR(w( 5X3g;9#n#4 wYJ2__Ec&KdϤ'n);v|X,UqW;7;O˞V"q%w;f|b¸Gyb9}U$wfZU3Y'yVKC;zrn)9|6(wT-,{]sHr'ywwmvB>POqvmĦMsK;~jokuLi;}_]8lG{.9%y5Fr>Ǿ k@o|ǿ@ /~}=B*4|;+{hX?D^p{a[xX9}Nr9w N·{;y$~ꍆpg.)㎿Yk@xnj;vx^r'kJ6oB/:^L&km촕Yg=S;vav۵C}ЗIO?TS׾9E>oßKmm_/:?]?wZ5\;]Ý&A]׶qp8ywx~kݡ;ԇ'zhozH|cw=p*^OOfDXyk|܁/= `wKѯk;n{6י2 S(1@]{` ߳J92֚7 w;{}^|1w5wݻm!cݗmAA;2Szq>2q~_T^ XH=.d⌻"K;rstwO>y<߽q'Oqm:R|vs>x#ڙ%ELNc_ڻ|}Cx+1xڙlM}y\*+eUĸ|PU~[Im@k 3`IEnE4W;0#tnm_xVyh\#F[ W遶6c;7!meCݛ{[;uɀ^vX0 },U#FW mTKtnHnŒ\ wYw ^!wqB DTa>_ȝϧs"w>u6NQƝX B~zǒFE0`v感9%wp\jRH$ƴ&#w jJn>8uݭs{$A\"BԡA0`Yy]:YXLt]39)e2 $A.Z;- f0\, `\|ah#1Q2#/ߍ ؍^{EIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/keyboard.png000066400000000000000000000201111504763705000250540ustar00rootroot00000000000000PNG  IHDRWFb}iCCPICC profile(}=HBQ{5P"rhC5RebZ 5{{8BC?KCKs ATr㨠D-=q|TM?XTI+!ƯNq:Ӷy7"fFAR)h nQUs`NAzV7bf^L΀\ k-&aZȩ:˂BIoSlXK B1< 4J)2B $1m}5(̲Z#wNvl>)T+}y=Õo'*Mm⺩ip OEVkYx?pzns[X,C`(]moO#rz-Oa pHYs  tIME {S_IDATxq6PX5!0&4;s~5`TZhf;#>??/o&V_wеX\LEcokŚu`Ea8q$.Vdz=٠^͜#1ģqlQ1!QJqCq3CwѹW{rO^-uKݏν;ju?z=KݻS-F=apet-dl9km]_: Cs%~?أֿh4=]^D$X޿ΎaI+y;x*Ĥ+9{ݿkϔsʘl}N{g\g'bRLH{{/^wkJ^)N˭dOs}׮W̕w+O^ot?yֱE;Ƥ'xp=ξkDs'5^"W_q}ow`-y=wx4yyvǒ+=>3i3l3̵8gE/}9iGKs G7go=6x-%{,:zuLYN{k#]%ʙK}Vlؚ3LLLL;g 瞹FkL331\ʒӧS֥=g{I͝{L9ӷg>7}Ozܻ/n\sA%g;mu4vasfÅ#3wعϖ{v?6/3~:6gCaL{Qr&v&v&v&foGE~yoWJoLڕvZv^wwtYLL:^ G.f+5滨|T ҒvŹ+kjocԖ."uJ;va6x{%̝/\z@:y]ͺ5d*:kر vX:0%l9`,'geصN6`3qigbgbg;N7Wyxf4IZ?SE"3>u2Yle~t?3f39B.}ovvos6lkE{ԩHC{R }\y(|oV$5ΤȔgkl} | ghγWֶ8;S=Zwc,݉JJL3?&ؙuUWiT j,oWuY99256Βg>ؙ$cYLL;s^;޵M8mKA>z:w[Ʒ.y6h=-F1ݑ-ߏ0H1LLLҺfׯKl߿HPߛ N[q?~'wP9FXoӋn06ɪ9ӖgY@`f:3%]=&E`سj~^9(m霍_w h}};Y%8: 4 i.jZ' &L`^i>V^u^j-Mey>M[M l~)SK2:gyFPn.9Sv(yѧʂ@`Lݪ.1ggځm={!c\['=@KUK]z4i<-}winOU,1sKڥ,6Y=غ rm{vRzNkihzBMmν?rIC;&$혀cM0qWknj}=7yGkRYZہKÉFې7}vXj~m%Hr:R;h֎ 1& 0  0 dL@`]376'{ ф_uWmi+,n_R 0fmw+X+ui֌=ƭ"|>ӟ^|պT84>Y{vj\Xg#cܖOV4ՎyW`$%1ӦmW:3MчoK dÔ>kNm9r\ɯA1dM%5Uc>'KmֿsIh2yݦ^m܇ c.k|mx?uߓ4cjmφݎ,ڧ2:ld Hsg-Q^hK~zV?];f!=GnmOcMpɶͧvLHK S` LK{ dOns+-7eTAE_ڏǒZstGey;8 Ǔj{H{Ƚ%Kos[+PnlEdQZa囚GKaowu阜oɻWmΥ L 0鏙RWRJ___|;Rۚe ___kE>{ ZLgv@`. ΟLXlδY97^[rGHK^RV`.eA`.qi3q_&L@`&L@`if Z LnV yڱg[Fuoёk+7q2ϙutH[~.6:2殦Jrrk,32t\`^W 2]cݾT 0 Kٽ6=&L@`&L@`U,SCp)QD`^ҏ4R_éuZAfso/5(\;V鷥>3.R6 D6내GkOD6h̏ԫY©\{h'UQ L@`&7vo{q3պ̝ѷ.l˷󑏍vzkϔyvAؓVreڌ)ؿ3,kUP_]9~lq-9,#R+wT]||̥e)ս,m 3O//Mztz˽M.xlՕ2-#әc3F3l=E.Kǜ~m0^+opzccɶOgzҡX細Gx)= RZ4y 0  0!y`Ћ{&cb=Y}t7m {֚vIYۭ+Rwߑ~cYk2kUZfˤVwu* |ǼrIHڽOk;|>ׯ5jޥknܱfݾԚ$?ꏉcɛ? 0  L LR:#Cڷ}, 0A`& 0ypYTw%qR@`BZ9n{SĤ+ 1.3TXMkzK_ʦ!MƬ]^C`67R@*(x 0  0S>fi<ʃzx=:wNW~N ީߜQznȸemsM4ܲ櫋Ѯx/vJ}?K+c| rh &^ ?(֌s`N ҧr<$z);#զ7,K{IDzG3.]I撹13 Cr@JL@`& 0A`Bh.im\ 42]oɺ QߞmzFpR[S_:6=;ڕMeY"}Cjzz ڝK{]TFXS"%2<^{LvH2WGp]qo#MdKTέ94zU˹ {j9^"WzK|n8EecU:"'o& 0A`$1;ZYF-hF]8ZݳZ2UlsrYK(Ǚև:ב6Kyw%oL✺S*=&.ç8rߏ\qDgŌ;Lf)–&usCI=ɻQ@zw)SH^9=$0z$L0@ $L0@$L0@ $L0@Lt  &H a%=_hG4Y  fGgHvΨ 0'5_0 fSߋ|wnrk]Ι$ p„YJNQm-!Wf;Mm#$Ja=X׸/sN9gClm[MW{0z[%; $L0@ $L0xfNTr2"P! V˰ ԹC%̣J~*vͫmӺs"e޵D_t;yʱ]}t;e}wXia}^Q \6aFNWSwu<9ӫ{_;YEojYOGvs'9u:DOs" g=z/KӋn2ݟXtEBX?k=r?rs_dbEDnWGhM-ZV+5{ Xa3$/y $m a &H I>>>l(0 &&˿ۆ4=*#YzO|@T=YK2$d9N%w^䳩i㋃9JKKoGXzE7^-a¬;^:ѕ^GuW]p;ף>7&, >-%h6|[$;#lPIa% d7a@IlH+M'i$L0@ $L8_e+ VuCIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/main.png000066400000000000000000000076621504763705000242200ustar00rootroot00000000000000PNG  IHDR0ĆPLTE{ pHYs  tIME 81:6IDATx]#7ŃN:hWgZYAݞ"o?dD"TDKg^kw@`r?gǽ\!#:н]YS  pc 04ڒݟ lG U+Tz!u#T* RC: 9툜{L{@;5ܠ;_@rTve[y]Pg 1o?d" &o,="Y0K/k ; Fgf٪w8QX)ߥ 5 XO9G ~Gae[(DQo?L F31|՟ҡ W'E.y8>HJPӌGj,">+I<ՁT`hNV gQۢkThlpiEl/zi_B4̪tVJ`$^oC՟(?ְ$P `q֌Ӻ\|Ӥ2eКq5t3{1]eq I [1&.8h`)<2㙹}bԅF5GE^r\R>Ho.T s׊$@3BFCtPQ7MYm:^ƞIJ |7cT- 2+8{KtN3.AZ"pQ"=:ߗoy#ԕ= 9,zp̝jSO{;E(P͖-_8q$Fj|u^;UyCo6wc>:R#q=ֽnAG"咉X| l= cy8]չ9t?J^Dcz]cԑnVP䮻X6Qܽ!]]ovD}Z?el|)Amg]+a؈dƮ>'ߍ:T۹^g{xnjJ+L v8t6ql"]]]E?_WW"5:LVv1`l{Nz *E7gB| Vm G]]梷a!ꃂH"UQ#I]GzΝKA v幉*J.Onypl?ж-_U}^]kϿSٷMg1Xz9tSfAw S*?+,Rsoznz; . ؈N}te-6vӑ̥<='sܻqGw;_O۩~Zw2;3{.=2*ekctZIIB] u.ԅP<+P5: l{MϞ}c_MxPS#$c_hvףΕ8xQ7rք՝8YᅺPB] u.ԅPB] u7IWq%on~EWy:K4NIJ*A=g^K]VVH`G(uN .o>WP7w+*hs륌{{W0{2DUAQMui mz=|ϥHJ(j`X.yEQ]41G}ǒW%ڜPB] u^s{=Khl=Selү[δZIԣu#gaS Pnm8UH]m.xnTo;m=]z<7cw::Eԟ?^ b"+ԅPB]uKOUi^Գeaw2j)o }#z}<Ԋ^7aף[`}:ؑ73-nl[6ݡpk]asw-} u떱%KyO{EB] u.om{;@1[onn Pd4[o9L9f{=q)WuoR 4w:^z[Q7kb"+ԅPB] u.ԅPB}w c]ug+qWj:tvJ2P8uޮV:/$!CR(>V~Lۏ2q5Z$Ļ=M{#nxl쏳w ׹̧'{=u'1e:s= ,F,v,TB] u.ԅϼmf}a6EmжZ ҋSY;NˇíaPL=ZJoPǻKMѽZKg[Nl_k`2^ԃ;@0uwMVC)|Cԏz^@W2pzZ4>:S.s 3z'Q_&/rߊ9zw:==mq$u)e=?^0 M1\&k{eѱCyʁs=ցOSQ{SWtPB]  hs.IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/memory.png000066400000000000000000000056131504763705000245760ustar00rootroot00000000000000PNG  IHDRr+PLTE{bKGDH pHYs+ IDATx 8  Z(2_O@ @ Sr  @ dQ?LBl.ZJfyjsBQԌ jYKIYGkQo q:]Ir/I keA6255)D(Ӄ-Dbwg!!Xfl#ۿr)BQEۭ :8j<  ',5$#@ @rTbgE$?GJ2E'dX@AKH=#LB abOde}4ӑဪ5{~ٗ?[TNdU@~QD$(C/RR<Űh((/$c;E˶2ZKaY:oԦ뼶K |=pN r/MiYTɢYۈMGL,tV5﫳%ρ&+Z1e(Qo-DZ2V-$G|cO3t*8sIf)ZNz{l40A]Q5n&;}& FL㡏L-ZպG} (9B/W#@yK-2)U2$:ADwH#R- H dA.h߫ET rhݫ+A~_*ZMeV%ú>d#C94#"a}x=X:ݯQ,d4> "?!J@1}1VM^AJEl HI52>˖Pd3zc-@>}S(1-#@}S(1Sȫ1M#Ʌ qIed6^G4u)%2e GXBɒ6=$>}nB fWb'oc6yCN8ךaO@ / /T)eɗS^3i[y H>m@j$\@JfrE{-]ʟ$\\ ^ HCי Px,kbK9rɂPnO~Mbہ-2y sM7)D b4-4I,H7\!?)K*-Kv D2Ƿoެ(SKRY|&' K{"_ $']i7}eEK)A|E:"-]^T,)/i.Zcgy5sjA @ rثsry~A @Eh;Avt;Xd^RC@buL-{Fwq/B=Vt_#yOJ7(zow"k=LOI GN:q'j쵈{s_;Z6AӀ^^ rN.Pu @L Kd]Y2Gf 4 =}WX Bz8!o,ΣÞEV"m_5ols27fd"c}#C{jveij*֏:IL_?,ϧjIK@lW?7΀؏Y]&ZZL9ȃ WF< @ __r'reIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/monitor.png000066400000000000000000000034101504763705000247460ustar00rootroot00000000000000PNG  IHDRT0ajsRGB PLTE߃r pHYs  tIME/TIDATxMn6 -d5}r CaN!H]foQl_cy>^2-Ll84?/RTތ~87S?<ҷO-eRutP}MA~aEo\-@ػ tAΧD^2Vs X}IMv>} DMfj|< \Zq@JUR+Q& M"LqbG Oyh(ԗ8ʾ+;SSظ B\05k€zaJ8dh_JΤ☚@UTŶ*^N8[ wK.8I456vHm]UmURWM xqψ:.BOeoZ4oc*ij`l9!Jj \9-^ta[j^KT^zkɾ[k#Fm*UJU*o 3D[)uQgT*6j5e@I`W꣔.5VF75&;GsHڤΚzoq={|yig)lm׎(I`Τ@[t S;,RsvdjפֿJc/2;+[l9:W~]Tvr-@-+4 nDJURũO~VRTg/RT- őj.e٦mݑjc6{uEݗ`/Dj֚>ogjjGؘYtZ]ֺRaj`K*I횰J3n֏]p"4Qj.2VZIGM`j:@M֨Y@cͣBʥ^ W\hZWinɣ[ehprZk`MNQ$hVRTljV9)ѬQCX}8KT~k2z k$DUuA d`Hd.<~DD0zOF[]AhfBqzT K&p""Q[eOK8.[{8[c*.Z@r[A- 'bVXV^b$ NXb~Jtʧx8F;[:|k*\FM rP;oP[A +S6?*O1L`J ae%'ED;U񕩐 Im|-T9M8TסܧǁQ_C*UwM}/}+CIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/newfloppy.png000066400000000000000000000024431504763705000253070ustar00rootroot00000000000000PNG  IHDR"ڇ.sRGB PLTE߃r pHYs  tIME$"IDATxM6 -"U&aNahQ 3-|9ݤ$F~oM8PY(duF~NNz/$LT@2 ^"3ɬߺ'3A3iw0V;c>h*'9!: iPEHų:IbRoHxj2[PF"::.g ѧ7$c)8ik);[fZ_5ϦK& PCrprZ$gKt>8l՜$]Br:>$ 6jIċ;HZ@&{" StJ:_;oƇ7wDӯONNIT2 7r0& D$\]mFa&R//ǎS,S3#[DN9ehcQt&J""'G@-1T$+I$H129qDQ:*R2r\5NLe{9YIO$HޞH<8H iez˔Asmkߥ&h^e_DJn(T82>*-MRtCxd ꥳd[H^VG+i9=5kh{nȴv?N$g`'oObcqO&Fw^%|vѫ ٫)~D.N=lNNꤟDk)*O4,R~䑮{; $)5IXY#тt1'Ժ>M勫\K:ծ[K%>-YOM$\_;Kzodv!0V{fm{==g$OklƮ3;::: ~ wOWWF_tݑ\.y'ߚlf)]"#~$NR f4(j>MRQYA$ؐH6qnzezz8I[|/}[6.]"#oE>7w?Nͭ+lN@:/i\pIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/screen.png000066400000000000000000000126631504763705000245500ustar00rootroot00000000000000PNG  IHDRzG\[#iCCPICC profile(JPVRtA"vU!b`tJ1$)7Ma:Yp8s M'Lx&-sF6kt "y^J旎ժ-ţ"R+(Kh싻2`vNlX$ލMl&F?vQzqnm\N8aȔ nrL=e{ "儑j3܈ )"mSUyrJc"-pY5؜gaVEs1}lР7i+Л0RP4oz봨 kyd/)=Evy A )x#qSxKәgi`1#@;ǒSjGXhWu c'˕>zh4H;#9NUg~67%Lħ[,'(]jp E`?iʗae[ŗ%~ʠ58ђd 364@uқ I1_Nf;b:d&>+APmE2=4\OWC>e{'U؂49jp(VJNR(Q!zʠ5Ne؂lY v cLĵe5s-pV* [zDʼRmԻ~Y|8Bj(Axwpgگ]ap=detNH*&2 l Z͵;Oop;FPۀ& 1˯zZ4G},-ҽr4Pf:xm 0y$} ssfB~^ʂэc,i\5eLbΝd easgy ,4f, (g6@A'^G{ ی=%Ix7;1 zӷg5^J:v|5oFWT<_: h(9>$xs5DͬyMD%,eL3@#4 \6)qsdM$ڤxh@r~@f9<chby(޾D,|g\4 jVo7Sfsa< ΢xD7|י.I&^խ<Z ͓xc @&< |@S&"P s_}u:FOIqiHJ՝)b`5H}D(yҏɣOiM`'TܿۡeGMJ©#㯐jLoBx&'(3%frmm_fွ(0@>7"u3X&~$-(3){@ ?=FHAkK=kp_z%C40MQ]3hgk{@vI6?*BQ\=SU\mf?4xX z?9Ri1.ހAAp5豩 4>a:ឞ+'5#MQ#J\; B߇x ƹ NYR]h AqvC`&7nJ_/L4֜_{o moۭk)P Y5 s/%M4Ǟ=<70=)Gn-+ =9sm+tuw)efew.p )~]ڶSA,<d*Ս,V6rΣw.2{7(v5^*%eom:z "RUM/ko16ߣ& rǃ# p/̵ǂl 1)Ի^h`6[ 8@M ZT|Ӑඁ/ډyBrf@|KR݁-'}kyoHv\/`6E76Qa1 1 IfʫJ՗/as^q-=pŖvZkҡvqE7%h ll4 &"_߮k6h,UUJAnAJn"Kf&qR?fK9ywRR}(%^Vݟ5#mved^B8WR #ڶ=oJͩWy!#}3z_%Ħm ۦK2QA(!TJ;u\cnͼO nӹG}\*ɫqu/.4Q@e)婶O侓~qyu yyHesje*U&:Ƴ2~VU 6Vb˪z :.[\_:i-KFq4/LOaj镰-Y3K/D Fv[Nm4@ӿ;@4@4@4@4@4@4 A~J8To%\##qĹlm7x6ZfVwev.@N[^~umJd# aKWJN# 3)@Go/6{ wq,+!3p%0nJI$fk4>SҫSZy ve{݈`^ yںrܼKw7’b]Q2C(|k>[@ 0]ܯokS d-5v|+ ."Z\NJ46"(QԦ783n}ǧ& fi?6:\ ͰL?e4 t]@ P7 P Yb}+B9Vp=(qm@XDn&E$Y3h4duW%-]۠r P1t "K rpv`iȭR 2~N h5xg\jeMTA@ oJ  ʘgnC}oInʗ8ϴebAW<ݜ+VԶċlOSfǪ׬ċlvK)>e0H],, hISWhR%)vnLH{ѪR lnXL< y5uMQl%)WEBuVT.+]d Q=T֕Z{K>s{CŎ]4!Uk݃mBx^wo`xX`78Y\pb\CF!B%Ep;8ˢmB|;p^ ]tV(*k*u]N1WOW<ʪ/\*5B@ /'IJ" 8ׁ۽DC2 vP喝 83Z#V'k^qb _8hr)e)dk۫y *Ub"ox~#''h{ *kϧ>&.{ _f磇<oE3 #ƃ x4@4@4@4@4@4@49a'_@%4`Rc1K~`YV.e4'yE}-Ja@%:?,wX>O(jcNpGJ,1˲jP>e~Pb;b[_YYb_eѵCo9o@)2Y=R4a!|b/48͟͠vWh0[|>Ƙ=QdIkŁr峺 cfj6`g Hs@)'y2IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/sound.png000066400000000000000000000131631504763705000244150ustar00rootroot00000000000000PNG  IHDRݡ pHYs+tIME 8HIDATx[FQrMgfwp HSwa@dJR>߿0 -_&@` X@` X@` X@` XF#\<V+g`@`@`@`@`pILe0^/&bg4!X>41k[Ե6X75.=v.~MY`O@e&[cwS‘KX\;FKsێ&]?C>*鸽X7VQ/qյ]\s5fe> y^y\Z⧘/⿧\:3l.f1^ڦH~n.BvЊ|VC#Ι X{uP`````_pL`5nwKEX7P.#o0@v 1MB %DsQ#`ս/\R`9#l 4I-r pMlC^k7mٵW &;ydJKs85AAboUS/Wg\:ܛMgxĤ8VlxӨzoO&eGdkt#SQ6\a$f~xC&>^|>6U brsWG^lSnͭ6-i=%D~g ֹ)6k3~Ofs^.hەw MN~ d/* }Wv~7-͵]V{Os提XæwK@^شsyH |[׽gI['}7}oyaV[^شs؃zac ##/|>{q\۴}tW_^kăeC-xx< 5/\Oj^)a*ʬiS)׮_*֭xx[ݡ֣ncc}XD%a]= 6֎y[y!!7mBKaeִUL^V{]fYXTaU29xZ'Vb;8PHX=wU%+}0lUf3AMj-%/Y+ \ z]c^£!4isWO="v]ުV(^]Vt+vquUU#SEzXmfOTR@4et;'R/ ao,ae15b||1Z+Ux_UryBaYRcmZyQnks%/L.xo9{uͻνv7>!tI&q[kcp%B@9rw^h_*Y2a3w>酰k 1^ȷ)oy!\^F=K/zX4/UQjxx``````a/ǜ6`Yqy0AXOfk`mq&23U⃬5ij &>Uu9+棹)  tN:;'i%ƌ8dSoy-My3_W.7H V=\iѾțJQJܿ-I[xW ^<+-uhΘ0>O= k>3-8`j)apLFQ/SxήaIo  KOaE`k08ۼ] U| ^M> %2k)!Z,FaH , X, X, X, X, X\B, QC%XKP[ v$s4/OX\5bZ1/Iz9^Z8YpEicArFː'_|¡JQXA)!.1\PֲK`m rkܵ&M@ | ߿DzatوcGK%x i3>a;{aq`+M0[ZJqY ,ՋϼoGp 죀xOJU|֌k'E 9㳸٢|SܤM٪e;-sa5=uNVJ2ăk;}?HC]=0NPw²R5NBwt@{ Fu>9x{q.s2vz_ѹ>JkUvnĿ[o=-四e=s+ tusaӱqf\;xڴUGY{\G|1^h?~/n{>kFS/\ $ |({jӼ WQOȄx᪨`@`@`@`@`@LBRd$\Q3H ` X@` X@` X@, X@܎j 瓍NzU` X@, X@, X@, X@, X@,84Ÿq֜#cy!ºZ^EvcF&X(q׬C6 ;eōAJZC 9 ZsE0kc;r.)0<{60WR U,շ J Q Ye~|M^MR`@u q-j({@tV㼕n} Xc&  k3M]~&P{OjMau?揌t6U>y 1 IV%ev{yZc˦|Jڭx$'P ^^=E.sD _Ekq{m凌j-ԖڻYZmԉ̕cJj1O+V$p+pnP^f!d@     @(/s. k+}m~Cw6+eGDXw|R`Y X.氮Cxa7]\Jzm߽@sZ˼ka mZx}'wW:iҮVE?j:K8☌#ysyo2 -",)!j} M8QdwOww}>c5(@JxPkq(m뮷(ݕ:r8K39,Ȱ&V[w8}waoV%7s_˟]OvK7aCUK^/)!n Xo|`, X, , , $*/ bz RB X, X, X, ,hE4utj~'fl秛 +V띌g۰}}x<>ozc3 (;JJn0h 5, ````@c]?ޝIIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/system.png000066400000000000000000000107661504763705000246170ustar00rootroot00000000000000PNG  IHDR PPLTE{ pHYs  tIME 7(zIDATx] #)U(OP>>3@OGLcN.N/dLۀxA+@A-l(pPF H}@$-/LVuD^P "na> Ma?|:jʑ:ĭ-X[ Y]@Qo'[`Yq5_X^_ X(`gD8s{p͚QWoWW?[Qw`>:g[}7jݬ|:f ]zϨ_dQG_ ou]w߲fϮ@MJ\d֧?or>'~sv ,To>2BiNZl@DܦmgOSßY䰁'cڼYsi]OB@0}2+; ~K<Y1}OUym!}lΟ,1ei~{홿z\s0fVbfw?z+Ov@(#U %}]ܣ]&-PYOw?p |HĊ[X5MSw>YmYSXBpYuҋ2Zӗ$c{DďN.H>ׇXqT޴ID_}' ,mO"(tRo] /4~}aNN|`E^k??;8!?_g3__xSF9-uC%h^'#}&׺?i?sN,R'pi.Qc`u.BM?!@TꑭK6O@(9G0V]dm]a9S/=#md^e_]G-Ue| ŏv>P+&Z ՟IΤ:-X)qE}<{bМVa-7*q vY@B \P S)ѦOBGIɦMƼE6Vc6=̟y؏uyx]@|[OZUzϣM/BKlrV_r:q_͟qˌ.V]ʁ.-줃Q%3JuBG=߄(aq~ D6ST?>ur˷+uIjJO#O[OEɋ~`%m X*΀J|.$w᰺8~P)&C;FSEG=/|2ylNu\V)7[VG}~=,bߩSN;ަ^]Sۧ9Ѫ4r׳*obFN>9mS {:ڡTqE{:é9@^I`Q6rX~>1pɢ r9OӨ#F ƙp"6O'y%UnGoA֚lXնc/nijw^lΠ$hN}L>NW"ӱcE:nR>:"۱"W#2䟪{_Az9tSC#6ɢ<Π>期 ^3OR>5| uW`W#@1Ak<&5D4GA:8][9:c6>wR,}t9Kaz{~eNETSչI9ȑ ck(QԂoA!{V^(Q[DizӨ"B|GSdBf#`39 jҗXuQgi"n_S,^SW#-*5C訃r }c_tIR.VM 9%Zy'I8Nʹ?(װ<'[~(+Ѣn\PoQ类?PhylP˵缶QAyVǗ{%y3s#(IXZD,ݿ&օ r OP -ueo*˛5WqF#w #/y2+ԭ|$0=wLVl9Pד )z!~'6W.//+y yTyn8+d>lp)Ȼ65lAO#I[|^}?ߌ|/vnHuGy/+^Z?^[0"¬~dQzRYPg9M#ey}#}~Og]E޷ϺʢeuDʻ]A߉E='/ {YwP?TEwEE'FJ)d&ړG/npY{x wBXΣ~3OEԵ+YVaϠO%'AS1L6v6b7G^YW׷.Q?eqXԖ&EC$Hs/&[ N%neJ:H+99IÓ}u>KHө} Υ#OOuMy?>+6R;sɹ~n9ЁɻX8MOF(|˺mun(S;݌cIɷ6f k٘:vs3%RU ܢSD ogI/ [dvɻ({PX疦71OzuX+}W 2 X#"/y yC}2<coQ{ =R/UC}P_ӨUȫUuyʢp %/4_\i'Ib/}& }B' }Bл)>zPO8j. <ldܥlnPhp |7V n_[* zu+|OgAv }4>OEA=pDoޭ=gUT6hA{&# D5a]ȬPu:QAWK?ΰu?Q&m@'d oGs8RmTфu]A~ۓ KP8Ucq'aeY7' y:e$\J΢!}NE?7a_#)} )v2 cS/>O> t8/^t\{jKml{iߦG)bwC9|#tm(r7@GaOJ]kmޙQ.gˁUs_=Ꮡh9WS+!'oSz9ܪdxNحם,ϰCz9~"q;a^MN_U%J:?.j`'z-snS誗hlB7oSƠ?;z3^7vMRЃlZ.@f99.U\/ vٯסeN쇾YܒxqНp+zuO 6.eqNҼe vYߨ# ϊ_+ED~!z,O>~V(^;!\'|~~r{k} uq^?F]'N7Ťwާo;__^C/ǹ?z2j(X(DDGO[^MO~*o{& }B' wB"D3h/OpqgO-[m\wXrzA1`;p=HI 'W6F{ BAt@efxҼ:ק^2.ƽϠ]>@?C|m!x*inf/&qx}\M B'Iwק^b=xAŹ!GUs)^U,ݚ@f#萾k[KGc!86 U@_mc iɋY -]&BX"v1[a sݷk1IsУIɝu3ܝ6cz?x/㩻C:rMfB' }BЛ'< e]z(5tEQ f`個(=~Ź.Q?zsYzêXnX\_a.XԏoW> }B' z?i(IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/images/tos.png000066400000000000000000000041321504763705000240660ustar00rootroot00000000000000PNG  IHDRo PLTE߃rbKGDH pHYs  tIME  -J־IDATxM6 +M0j5)I@pH9B aUQ$jK֏X,>~cJ'" Ǚ3w @Fho|ւK΅p.BDѷ,ۓYb`RDxql ?M8!1)1Ɓ|`!1էse gT"\ʛ[ċ^,@ @ 5T±?v~)F}/| .F35ă#;ېlkl`|z'k#$uA( 8]K)A1#rC !WZDF`cy;_.B־j !хl.kp|UGĭ6fM @,gMl]gwC0D {mߵhA#0`!t'؇+HlQȽ:.|&M7x"B0FRŹc6~>mrg%3_/sѮA(i*|H`){{PRR1F-.WEBA C\S B2\]H&z$6!RAikGw]X T>=z1 B:}[rp)7)+Q.ЍL/ |q#L  @!/ K5@ x M9L Z0{BDL/ O6Apx49ox0[mZeMB:N<բMk!HjѦ-ͣX+ڴ⺠z8!/$Zi|ѝZpЉEG' P!*G @ @ aZ)TQz8Rb5v "ܿh꘮@TA_!BGG$M RC “_ T */B joN% ^WM4~)KdeNmysdy]?IPrEHA BX"瘅-xCԄ(GP~JyI2ۂS H] Բٵ,4]é!4鱌nHؠ8A);!<5]!BgA2$ DN |]b" ?︥jLtCЄIJvЎ0B 7_DGBwe՛R [)J6/.QAh\+ Q y!-";>:PWWz[ˁ=-O[D'WƕD:6d9N'G߬ @s˥F%B;ND B8 E iWI?N yζLaiF;z]JI 2Y'%Q4^`'CHڥ1*˙rvpC0nn$+h@ZH;#8 e};P{Me46m#@t6nS&@-@ΉEIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/index.html000066400000000000000000000034641504763705000233120ustar00rootroot00000000000000 Hatari documentation index

Hatari documentation

Documentation index

The following documentation files are available in this folder:

hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/keymap-sample.txt000066400000000000000000000027451504763705000246240ustar00rootroot00000000000000# This is an example for a keyboard mapping file that can be used in Hatari # by loading it from the keyboard setup dialog. # # Lines starting with a '#' or with a ';' are comments. All other lines # should contain exactly one key name and a scancode, separated by a comma. # Comment characters can be quoted with '\#' and '\;'. # # The key name is the libSDL symbolic name of the key, see the following # URL for a list: https://wiki.libsdl.org/SDL_Keycode # You can also use the symbolic keycode value instead of the name, which # you can get with the "--trace keymap" output from Hatari, for example. # # The given host key will be mapped to the ST key which is specified by # second number - the ST scan code of the key. "--trace keymap" output # shows the already mapped scan code. # # All numbers should be given as decimals (not hexadecimals). # # Note that using keyboard mapping file causes Hatari to use symbolic # key mapping. Symbolic key mapping does not work with so called "dead" # keys. # # Scan codes for Atari keyboard key positions in different TOS versions # are documented here: # http://tho-otto.de/keyboards/ # # tests/keymap/ directory contains programs to discover/test the PC SDL # and Atari scan code values. Hatari's default PC key code -> ST scan # code mappings are in src/keymap.c source file. # # Example: If you want to get the 'y' and 'z' keys right with a german TOS # ROM, you can use the following two lines to map the PC keys to the right # ST scan codes: Y,44 Z,21 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/lilo.cfg000066400000000000000000000073511504763705000227340ustar00rootroot00000000000000# These config options are similar to ones used by the Aranym emulator. # Edit them to suit your kernel before using this config!!! # # Documentation on how to build m68k Linux kernel, ramdisk and rootfs # for use with Hatari are in the "m68k-linux.txt" file, along with # notes about suitable Hatari options for them. [LILO] # Kernel file name: # https://en.wikipedia.org/wiki/Vmlinux Kernel = vmlinuz # Kernel symbols file name, to autoload symbols info for debugging Symbols = # Initial ramdisk (initramfs since 2.6 kernels): # https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt # https://en.wikipedia.org/wiki/Initial_ramdisk # https://wiki.debian.org/initramfs # https://wiki.ubuntu.com/Initramfs # Relevant if kernel needs to load modules or do other things # (e.g. uncrypting) to mount actual root file system Ramdisk = # Kernel command line arguments, see: # https://www.kernel.org/doc/Documentation/m68k/kernel-options.txt # # Specifying partition and partition table: # https://www.kernel.org/doc/Documentation/block/cmdline-partition.txt # (CONFIG_BLK_CMDLINE_PARSER, CONFIG_CMDLINE_PARTITION, # CONFIG_ATARI_PARTITION, CONFIG_MSDOS_PARTITION) # # Platform independent arguments: # - console= -- device for interactive console # tty -- onscreen virtual terminal (CONFIG_VT_CONSOLE) # - root=, root file system device: # /dev/ram -- ramdisk (CONFIG_BLK_DEV_RAM) # /dev/fd0 -- "--disk-a " (CONFIG_ATARI_FLOPPY) # /dev/fd1 -- "--disk-b " # /dev/hda -- "--ide-master " (CONFIG_BLK_DEV_FALCON_IDE) # /dev/hdb -- "--ide-slave " # /dev/sda -- "--ide-master " (CONFIG_PATA_FALCON, Linux v5.4+) # "--scsi 0=" (CONFIG_ATARI_SCSI) # /dev/sdb -- "--ide-slave " # "--scsi 1=" # - ro/rw -- whether to mount rootfs read-only or writable # - scsi_mod.scan=sync -- disable async SCSI scanning # (CONFIG_SCSI_SCAN_ASYNC) # - devtmpfs.mount=1 -- mount devtmpfs at /dev after kernel mounts # root filesystem. Does not affect initramfs based mounting # (CONFIG_DEVTMPFS_MOUNT) # - loglevel=<1-15> -- debug output logging: default=7, quiet=4 # - initcall_debug -- lot of debug output from early kernel init # # Atari specific arguments: # - video=atafb: -- Atari framebuffer / VT, with mode one of: # - stlow, stmid, sthigh -- all machines # - ttlow, ttmid, tthigh -- TT-only # - vga2, vga4, vga16, vga256, falh2, falh16 -- Falcon-only # (CONFIG_FB_ATARI) # - external: -- Hatari VDI mode, see m68k/kernel-options.txt # (CONFIG_FB_SIMPLE) # - debug= -- select kernel debug boot messages output: # - par -- parallel port, "--printer /dev/stderr" shows messages # - midi -- MIDI, "--midi-out /dev/stderr" shows messages # (if MIDI-in is also enabled) # - ser1 -- ST-MFP, "--rs232-out serial-out.txt" # (if RS232-in is also enabled) # - ser2 -- SCC, "--scc-b-out serial-out.txt" # - ser -- serial auto-select, "ser2" on Falcon, "ser1" on other machines # - nfcon -- NatFeats STDERR (CONFIG_NFCON), "--natfeats on" shows messages # - stram_pool=, bytes set aside for ST-RAM allocs (default=1MB), # relevant only if kernel is loaded to TT-RAM Args = video=atafb:sthigh console=tty # Whether to load kernel to TT-RAM when there's enough of it # Note: only kernels smaller than 8MB can be run from ST-RAM KernelToFastRam = FALSE # Whether to load ramdisk to TT-RAM when there's enough of it # (most ramdisks are too large to fit into ST-RAM) RamdiskToFastRam = TRUE # Whether to exit emulator when Linux resets on reboots # - automatically disabled unless Hatari "--natfeats on" option is used HaltOnReboot = TRUE hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/m68k-linux.txt000066400000000000000000000630031504763705000237730ustar00rootroot00000000000000Contents: 1. Introduction 1.1 Notation 2. Hatari issues 3. Building m68k kernel 4. Building root file system 4.1 As EXT2 HD image, from BusyBox 4.2 As EXT2 HD image, from klibc utils 5. Building initrd 6. Running Hatari 7. Native bootstrap 8. Debugging and profiling 8.1 Kernel debugging 8.2 User-space debugging 9. Debian m68k install 9.1 Debian installer 9.2 Running initrd as normal disk 9.3 Bootstrapping Debian installation on PC 10. Linux kernel issues 10.1 Known issues with workarounds 10.2 TODO / investigate more 1. Introduction --------------- Here are instructions on building m68k Linux: - kernel - ramdisk - root file system And using them with Hatari. Build instructions are for Debian as that is the only Linux distribution still building latest SW package versions for m68k, in ports: http://ftp.ports.debian.org/debian-ports/pool-m68k/main/ But m68k gcc is available also on other distributions, along with tools for building the images (from packages in Debian ports). 1.1 Notation ------------ In examples, different execution environments are indicated by prompts: $ -- shell prompt # -- root shell prompt > -- Hatari debugger prompt "wget" examples do not give exact package names because versions in them change frequently. Replace "*" in those file names with the version available in the given remote directory! 2. Hatari issues ---------------- - LILO reset code has not yet been updated from Aranym's Falcon AfterBurner behavior to normal Falcon (they use different reset addresses), so "reboot" command fails with LILO - PC address points to ST-RAM in debugger even for emulated Linux code running in TT-RAM, because it is not MMU-translated => workaround: load Linux to ST-RAM when debugging/profiling - There are lot of ("--log-level debug" and profiler) warnings about memory accesses to >2GB range when programs do syscalls. An issue with Hatari high address memory setup, or another symptom of missing MMU-translation within Hatari? DEBUG: Your Atari program just did something terribly stupid: dummy_xlate($c00123ee) - Incomplete SCSI emulation: => workaround: have root file system on IDE master or slave Rootfs mount from SCSI device fails after following debug output: ------------------------------------------------------------ ... sd 0:0:0:0: [sda] Write Protect is off sd 0:0:0:0: [sda] Mode Sense: 2b 00 00 00 DEBUG: raw_scsi: arbitration initiator id 7 (80) DEBUG: raw_scsi: arbitration DEBUG: raw_scsi: selected id 0 DEBUG: raw_scsi_put_data got message c0 (1/1) DEBUG: raw_scsi_put_data got message c0 (1 bytes) DEBUG: raw_scsi: got command byte 1a (1/6) DEBUG: raw_scsi: got command byte 00 (2/6) DEBUG: raw_scsi: got command byte 08 (3/6) DEBUG: raw_scsi: got command byte 00 (4/6) DEBUG: raw_scsi: got command byte 04 (5/6) DEBUG: raw_scsi: got command byte 00 (6/6) TODO : HDC: Unsupported MODE SENSE mode page DEBUG: raw_scsi: no data, status = 2 DEBUG: raw_scsi: status byte read 02. Next=1 DEBUG: raw_scsi: message byte read 00. Next=1 DEBUG: raw_scsi: arbitration initiator id 7 (80) DEBUG: raw_scsi: arbitration DEBUG: raw_scsi: selected id 0 DEBUG: raw_scsi_put_data got message 80 (1/1) DEBUG: raw_scsi_put_data got message 80 (1 bytes) DEBUG: raw_scsi: got command byte 03 (1/6) DEBUG: raw_scsi: got command byte 00 (2/6) DEBUG: raw_scsi: got command byte 00 (3/6) DEBUG: raw_scsi: got command byte 00 (4/6) DEBUG: raw_scsi: got command byte 60 (5/6) DEBUG: raw_scsi: got command byte 00 (6/6) WARN : HDC: *** Strange REQUEST SENSE ***! DEBUG: raw_scsi: data in 22 bytes waiting DEBUG: raw_scsi: data in finished, 22 bytes: status phase DEBUG: DMA initiator recv PC=001c40ee DEBUG: SCSI BUS reset sd 0:0:0:0: Device offlined - not ready after error recovery sd 0:0:0:0: [sda] Asking for cache data failed sd 0:0:0:0: [sda] Assuming drive cache: write through sd 0:0:0:0: rejecting I/O to offline device sd 0:0:0:0: [sda] Attached SCSI disk VFS: Cannot open root device "/dev/sda" or unknown-block(8,0): error -6 ------------------------------------------------------------ 3. Building m68k kernel ----------------------- 0. Install generic build tools: $ sudo apt install bc bison flex 1. Install compiler: $ sudo apt install gcc-m68k-linux-gnu 2. Get latest upstream kernel release sources (without history): $ git clone --depth 1 --branch v6.15 \ git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git $ cd linux 3. Apply Linux issue workaround patches: $ git am /path/to/hatari/tools/linux/000*.patch 4. Get Hatari compatible configuration: $ cp /path/to/hatari/tools/linux/kernel.config .config 5. Compile configured kernel: $ ARCH=m68k CROSS_COMPILE=m68k-linux-gnu- make -j4 vmlinux Notes: - You can fine-tune kernel config for your requirements with: $ ARCH=m68k CROSS_COMPILE=m68k-linux-gnu- make -j4 menuconfig - Use "optimize for size" instead of "optimize for performance". Latter is 1/4th (1MB) larger, and does not currently work properly, although it boots much faster - Normal Linux builds require 030 (or better) with MMU and FPU (only uCLinux would work on 68000 without MMU) - Using included kernel.config file as-is, builds non-modular kernel that includes all relevant features for mounting root fs directly. It saves bootup time compared to using initramfs for loading the needed external modules - Bootup works faster when kernel is uncompressed, as uncompression would happen in emulated code => do not compress kernel 4. Building root file system ---------------------------- This is common part for both examples below (if you already have a directory named disk/, rename it, or use another name). 1. Create root file system directories: $ mkdir disk $ cd disk $ mkdir -p dev bin etc lib mnt proc root run sbin sys tmp usr/bin usr/sbin 2. Add minimal init script for mounting virtual file systems before starting shell: $ cp -a /path/to/hatari/tools/linux/init.sh init 3. Add a user: $ echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd 4. And basic terminal descriptions: $ cp -ar /lib/terminfo/ lib/ 4.1 As EXT2 HD image, from BusyBox ---------------------------------- This builds a minimal hard disk root file system with BusyBox. BusyBox is a multi-call binary, which performs the same job as many separate programs, based on the (symlink) name with which it is invoked. For more info, see "man busybox" (after installing BusyBox to host). 5. Fetch statically linked m68k BusyBox and extract it: $ wget http://ftp.ports.debian.org/debian-ports/pool-m68k/main/b/busybox/busybox-static_*_m68k.deb $ ar x busybox-static_*_m68k.deb $ tar xvf data.tar.xz --wildcards *bin/busybox 6. Install qemu user space emulation to query m68k busybox, so that symlinks can be added for the tools it lists: $ sudo apt install qemu-user $ /path/to/hatari/tools/linux/symlink-busybox.sh 7. Cleanup directory content: $ rm *.tar.* debian-binary 8. Make 4MB EXT2 HD image out of it: $ cd .. $ /sbin/mkfs.ext2 -t ext2 -L BUSYBOX -d disk/ rootfs.img 4M 4.2 As EXT2 HD image, from klibc utils -------------------------------------- This builds a minimal hard disk root file system with kernel "libc" utilities intended for initramfs. These utilities are separate binaries (not symlinks like with BusyBox) and they're much more limited than BusyBox; they lack many of the standard options and shell does not e.g. have file name completion. In total they take only few hundred KB of space though. 5. Fetch klibc library and utility packages: $ wget http://ftp.ports.debian.org/debian-ports/pool-m68k/main/k/klibc/libklibc_*_m68k.deb $ wget http://ftp.ports.debian.org/debian-ports/pool-m68k/main/k/klibc/klibc-utils_*_m68k.deb 6. Extract them: $ ar x libklibc_*_m68k.deb $ tar xvf data.tar.xz $ ar x klibc-utils_*_m68k.deb $ tar xvf data.tar.xz 7. Move utilities to bin/ and clean directory: $ mv usr/lib/klibc/bin/ . $ rm -r *.tar.* debian-binary $ cd .. 8. Make 4MB EXT2 HD image out of it: $ /sbin/mkfs.ext2 -t ext2 -L KLIBC -d disk/ rootfs.img 4M Notes: - these utilities would fit even to a floppy image: $ zip2st disk/ klibc.st But FAT file system cannot be used for Linux root fs because it is lacking security features required from one. 5. Building initrd ------------------ After creating either of above root file systems, you can use the same content also to create an example initrd / initramfs. 9. Package directory content with cpio and compress it with LZO: $ cd disk/ $ find | cpio -R 0:0 -H newc --create | lzop -9 > ../initrd.img Notes: - *Initrd is unnecessary* when kernel includes everything needed for mounting the real root file system, like is the case *with the provided kernel.config*. Initrd is required only if you e.g. need to enter password to decrypt the disk or load additional kernel module(s) before mounting the root file system. - Initrd resides in / uses RAM, so you're better of mounting the real root fs directly. if you're tight on memory, but still need initrd, minimize its size and make sure kernel can free it after pivoting to real root fs. - Kernel will need to extract initrd contents from CPIO archive loaded to RAM. Archive is freed after extraction, but before that a significant amount of extra RAM is needed for it. - Kernel can handle both compressed and uncompressed initrd. Compressed initrd takes less RAM during CIO archive extraction, but its decompression slows boot significantly. - Above used LZO compression provides faster decompression speed, gzip/deflate better compression ratio. Both are enabled in included kernel.config. One can enable from kernel config extra compression algorithms with better compression ratios, but they are significantly slower. 6. Running Hatari ----------------- 1. Set suitable options in lilo.cfg, for example: ------------------------------------- [LILO] Kernel = vmlinux-v6.15 Symbols = System.map-v6.15 Ramdisk = Args = video=atafb:sthigh console=tty KernelToFastRam = FALSE ------------------------------------- (See lilo.cfg in doc/ directory for more info.) 2. Start Hatari: $ hatari --log-level info \ --fast-forward on --fastfdc on --timer-d on \ --machine falcon --dsp off --fpu 68882 --cpuclock 32 \ --mmu on -s 14 --ttram 64 --addr24 off \ -c lilo.cfg --natfeats on \ 2a) with root fs on disk: --ide-master rootfs.img \ --lilo "debug=nfcon root=/dev/sda ro init=/init" 2b) with root fs being initrd specified in lilo.cfg: --lilo "debug=nfcon root=/dev/ram ro init=/init" Options explanation (these can also be set in lilo.cfg): - video=atafb:sthigh -- frame buffer starts in ST-high (mono is fastest mode with large enough resolution) - console=tty -- console output goes to frame buffer - debug=nfcon -- kernel debug messages go to Hatari console - root= -- what device is used for mounting root file system - ro -- mount it read-only - init= -- run given executable / script as first process Other notes: - Keep "init=" last on "--lilo" option kernel command line, otherwise kernel may give arguments after it also to init! - Kernel does not need DSP, and avoiding its emulation with "--dsp off" (about) doubles "--fast-forward on" speed 7. Native bootstrap ------------------- Real HW does not have LILO, so a separate bootstrap program is needed for loading the kernel into RAM and executing it from there. Following shows easiest method to simulate that with Hatari. 1. Get bootstrap program: $ mkdir gemdos-hd $ cd gemdos-hd $ wget https://people.debian.org/~wouter/d-i/images/20130502-06:51/tools/atari/bootstra.tos 2. Tell bootstrap program to load kernel to ST-RAM and whole command line to give to the kernel: $ echo "-s -d -k vmlinux root=/dev/sda ro debug=nfcon video=atafb:sthigh console=tty init=/init" > bootargs $ cd .. 3. Get kernel to bootstrap: $ cp /path/to/m68k-kernel/vmlinux gemdos-hd/ 4. If you do not have a TOS ROM image, download latest EmuTOS release from: https://sourceforge.net/projects/emutos/files/emutos/1.4/emutos-1024k-1.4.zip/download and extract it: $ unzip emutos-1024k-1.4.zip 5. Then run Hatari with them: $ hatari --trace os_base --log-level info \ --tos emutos-1024k-1.4/etos1024k.img \ --fast-forward on --fastfdc on --timer-d on \ --machine falcon --dsp off --fpu 68882 --cpuclock 32 \ --mmu on -s 14 --ttram 64 --addr24 off \ --natfeats on --ide-master rootfs.img \ gemdos-hd/bootstra.tos Hatari notes: - Because GEMDOS HD is specific to TOS, it is not accessible from Linux - Because TOS does not have driver for the Linux disk, and GEMDOS HD is assigned to C:, IDE content is not accessible from TOS On real HW: - Linux rootfs would be put to a partition (e.g. "sda2"), it would not start from IDE drive first sector ("sda") - NatFeats is not available, so debug option can be removed, or debug device set to a more suitable one (e.g. serial) 8. Debugging and profiling -------------------------- 8.1 Kernel debugging -------------------- There are few ways to investigate what happens at boot: - Asking kernel to give debug output for module initializations with "initcall_debug" kernel command line option - Disabling individual init calls with "initcall_blacklist=" to see whether boot proceeds (better) without them - Tracing kernel function calls with Hatari "trace cpu_symbols" debugger command and the "--trace cpu_symbols" option - Profiling what the kernel does Tracing and "initcall_debug" will give a lot of output so save it also to a file: $ hatari ... 2>&1 | tee output.txt Tracing and profiling both require symbols file produced during kernel build. If that file is listed in lilo.cfg, symbols will be automatically loaded before Linux boots. For now, all debugging and profiling should be done with kernel loaded to ST-RAM (see Hatari issues section). For that, its size needs to be <8MB uncompressed, otherwise it wo not work from ST-RAM. Related lilo.cfg options are: KernelToFastRam = FALSE Symbols = Symbols.map To verify that symbols loaded correctly, one can check in debugger e.g. whether nf_call (NatFeats entry point) looks as expected: ---------------- > d nf_call $00008230 : 7301 DC.W $7301 $00008232 : 4e75 rts ---------------- To profile whole bootup, profiling needs to be enabled right from the start, and stopped (e.g. by breakpoint) at appropriate point. This debugger command file does profiling until first brk() syscall (i.e. shortly after kernel starts init and that does its first alloc): --- profile-boot.ini --- profile on b pc = sys_brk ------------------------ Adding that to Hatari options with "--parse profile-boot.ini", will start profiling from the very first executed instruction. After debugger gets invoked on first brk() syscall function call / breakpoint, one can save profile with: > profile save profile.txt Resulting file can be post-processed with: $ hatari_profile -st -a System.map profile.txt To see how much emulated time the profiled part took, and to get lists of functions taking most cycles / time, using most instructions, and being called most. To debug and get back traces to where "die_if_kernel" bug handler gets called, one can use following files: ---- bt-show.ini ---- profile stack ---- bt-init.ini ---- profile on b pc=die_if_kernel :noinit :trace :file bt-show.ini ------------------------ And start hatari with "--parse bt-init.ini" option. Backtraces to any other functions listed in kernel symbol file can caught the same way. Breakpoint options: * :file -- execute debugger commands from given file when breakpoint is hit * :trace -- continue running without dropping to debugger * :noinit -- no reset for profile/caller info when breakpoint is hit (Last option is needed only for the "profile stack" command.) What symbols are in the symbol file dictates what one sees with many of the debugger commands. If one does just tracing and profiling, one can reduce symbol warnings from Hatari by removing unnecessary symbols from the used symbols file: $ grep ' [tT] ' Symbols.map > code-symbols.map If one is interested only of e.g. tracing kernel syscalls, one should load just those to debugger: $ grep ' [tT] sys_' Symbols.map > syscall-symbols.map ... > symbols syscall-symbols.map > trace cpu_symbols (System call info is naturally interesting only after user-space processes are running & calling them.) If you see errors like these on Linux console: hda: possibly failed opcode: 0x39 hda: possibly failed opcode: 0x30 (On Linux v5.4, IDE is on 'sda' instead of 'hda'.) Those are IDE write command opcodes. Make sure your IDE image actually *is* writable and try again. 8.1 User-space debugging ------------------------ Linux user-space is best debugged with Linux user-space tools. Some actions can be done with "Magic SysRq", for help, see: echo h > /proc/sysrq-trigger To see what syscall and their arguments graphical program does, install "strace" and forward its output to Hatari NatFeats output device: $ strace -f -e execve -o /dev/nfcon0 wserver To see what library calls programs dynamically linked to Glibc do, use "latrace". More indepth debugging is likely to require Gdb. Install "gdbserver" to emulated system: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Server.html TODO (test): Tell server to communicate over one of the serial ports supported by Hatari, tell Gdb on host to attach to corresponding Hatari device file, and point it to matching m68k ELF binary and its debug symbols. (There are several UIs that can be used with Gdb.) 9. Debian m68k install ---------------------- 9.1 Debian installer -------------------- These are setup instructions for testing Debian installer in Hatari. 0. Work directory: $ mkdir -p debian/installer $ cd debian/installer 1. Get latest Debian installer (kernel + initrd images): $ wget https://cdimage.debian.org/cdimage/ports/debian-installer/2024-02-25/m68k/debian-installer-images_202306XX_m68k.tar.gz $ tar xvf debian-installer-images*.gz 2. Set up lilo.cfg with a framebuffer mode supported by Debian installer (it needs at least 16 colors to be usable): $ cat > lilo.cfg [LILO] Kernel = installer-m68k/current/images/kernels/vmlinux Args = video=atafb:vga16 console=tty Ramdisk = installer-m68k/current/images/nativehd/initrd.gz KernelToFastRam = FALSE RamdiskToFastRam = TRUE ^D 3. Get ISO image release matching installer & kernel (checksum files are in same directory): $ wget https://cdimage.debian.org/cdimage/ports/snapshots/2024-02-25/debian-12.0.0-m68k-NETINST-1.iso 4. Try running Hatari with them: $ hatari --log-level debug \ --fast-forward on --fastfdc on --timer-d on \ --machine falcon --dsp off --fpu 68882 --cpuclock 32 \ --mmu on -s 14 --ttram 128 --addr24 off \ --monitor vga -c lilo.cfg --natfeats on \ --ide-master debian-12.0.0-m68k-NETINST-1.iso \ --lilo "debug=nfcon root=/dev/ram init=/init" NOTE: Hatari does not support networking so network install obviously does not work. There are no ready made images of pre-installed and up to date m68k Debian installs either. TODO: kernel panics when init crashes: ------------------------ [ 42.310000] Freeing initrd memory: 26768K [ 42.370000] Instruction fault at 0x00222a40 [ 42.380000] BAD KERNEL BUSERR [ 42.390000] Oops: 00000000 [ 42.400000] Modules linked in: [ 42.420000] PC: [<00222a40>] strnlen_user+0x38/0xd0 ... [ 42.590000] Call Trace: [<001623a0>] load_elf_binary+0x9ca/0xb0a [ 42.610000] [<00020000>] _FP_CALL_TOP+0x9596/0xd512 [ 42.620000] [<0005fc02>] module_put+0x0/0x50 [ 42.630000] [<0011f086>] bprm_execve+0x196/0x39c [ 42.640000] [<0011e7fe>] copy_strings_kernel+0x0/0x54 [ 42.660000] [<0011e502>] count_strings_kernel+0x0/0x3c [ 42.670000] [<0011f63a>] kernel_execve+0x148/0x150 [ 42.680000] [<0003920c>] call_usermodehelper_exec_async+0xec/0x100 [ 42.700000] [<00039120>] call_usermodehelper_exec_async+0x0/0x100 [ 42.730000] [<00002568>] ret_from_kernel_thread+0xc/0x14 ------------------------ 9.2 Running initrd as normal disk --------------------------------- If Debian boot has issues already in its initrd, you could try running it as normal disk with monolithic kernel, or using modular kernel with a working initrd. Converting initrd to a normal EXT2 disk partition, and using fakeroot session to retain file properties requiring root rights: $ mkdir initrd $ fakeroot /bin/bash # cd initrd # zcat ../installer-m68k/current/images/nativehd/initrd.gz | cpio -i # cd .. # /sbin/mkfs.ext2 -t ext2 -L INITRD -d initrd initrd.img 32M # exit Notes: * Before creating image image, move "proc" mount before "mount" call in initrd "init" file. Otherwise "mount" warns "no /proc/mounts", returns an error and init script fails, resulting in kernel panic * Use normal monolithic kernel because Debian's modular installer kernel does not have built-in IDE supports TODO: freeze when "init" script calls "/bin/busybox init" at end. 9.3 Bootstrapping Debian installation on PC ------------------------------------------- Doing first stage of setting up minimal m68k Debian system image (without package PGP signing checks), as root: # apt install debootstrap # debootstrap --foreign --arch=m68k --no-check-gpg sid debian-chroot http://ftp.ports.debian.org/debian-ports/ # cp -a /path/to/hatari/tools/linux/init.sh debian-chroot/init # /sbin/mkfs.ext2 -t ext2 -L DEBIAN -d debian-chroot debian.img 512M # chown $USER.$USER debian.img NOTE: debootstrap needs to be done on a partition that is not mounted with "noexec" or "nodev" option, otherwise it fails! To finish bootstrapping, one would need to boot it on m68k system and run "debootstrap --second-stage". TODO: test again whether there are improvements to last status: - Hatari: debootstrap fails to kernel oops - m68k system-qemu: does not support required HW - m68k user-qemu (from Debian Bookworm): does support m68k binaries - Aranym: ------------------------------------------------------------ $ cat > linux.config # NOTE, run with: aranym-mmu -l [GLOBAL] FastRAM = 64 [LILO] Kernel = vmlinux Args = video=atafb:vga16 console=tty debug=nfcon root=/dev/sda ro init=/init # 512MB EXT2 image with mkfs.ext2 (Cylinders = 2 * size in MB) [IDE0] Cylinders = 1024 Heads = 16 SectorsPerTrack = 64 Present = Yes IsCDROM = No ByteSwap = Yes ReadOnly = Yes Path = debian.img ModelName = Debian-m68k ^D $ aranym-mmu -l linux.config ------------------------------------------------------------ 10. Linux kernel issues ----------------------- 10.1 Known issues with workarounds ---------------------------------- - Builds configured for performance (-O2) instead of size (-Os), and built with current Debian stable (cross) m68k GCC (12.2), will Oops once boot reaches user-space Workaround: kernel configured for size (in included kernel.config) - Old (4.x?) Linux versions do not work if kernel is loaded to TT-RAM Workaround: Load kernel to ST-RAM or use more recent kernel version - Hatari debug output shows IDE and DSP reset messages on Falcon at 2Hz (from floppy media change detection) if Linux is started with --lilo, instead of from TOS. This is because TOS initializes PSG port-A register bits to sensible values and Linux does not. Workaround: included kernel patch - Atari TT boot freezes with constant "unexpected interrupt from 112" messages, due to Linux SCU register setup regression. Workaround: use >=6.11 kernel, or kernel patch: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit?id=f70065a9fd988983b2c693631b801f25a615fc04 - Hatari prefetch was earlier working incorrectly on bus errors. This caused user-space program crashes if either (or both) cycle-exact or prefetch emulation is enabled for 030 (with MMU). This problem was very visible with the tools included to statically linked Busybox, but those issues cannot be reproduced any more. Fix: Use latest Hatari Git version, commit 06fc93b021c38 (Oct 20 2024), or newer. 10.2 TODO / investigate more ---------------------------- - Kernel does not recognize ACSI drives at all, although kernel reports finding Atari ACSI HW: ------------------------------------------------------------ Atari hardware found: TT_SHIFTER ST_MFP TT_MFP TT_SCSI_DMA TT_SCSI YM2149 PCM SCC_DMA SCC MICROWIRE TT_CLK FDC_SPEED ACSI ... scsi host0: Atari native SCSI, irq 39, io_port 0x0, base 0x0, can_queue 16, cmd_per_lun 2, sg_tablesize 128, this_id 7, flags { } ... Core driver edition 01.06 : TT driver edition 00.03 Write will use 4 fragments of 32768 bytes as default NET: Registered PF_PACKET protocol family VFS: Cannot open root device "/dev/sda" or unknown-block(0,0): error -6 Please append a correct "root=" boot option; here are the available partitions: 0200 3280 fd0 (driver?) 0201 3280 fd1 (driver?) 0100 4096 ram0 (driver?) Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ------------------------------------------------------------ Only SCSI & IDE drives are recognized - with Hatari 2.5, Linux works again[1] on 040 & 060, when CPU data cache emulation is disabled (--data-cache off). However, when data cache is enabled, boots crash to double bus error. Although screen is black, CPU trace indicates a panic. Note: when booting Linux with "bootstra.prg" instead directly with LILO, kernel panics immediately even with cache emulation disabled. [1] In previous Hatari versions, there was panic on reaching user-space. These did not happen with 2019 v2.3-dev Git version (and Linux version of that time). Linux also boots fine on real 060 Falcon: https://www.youtube.com/watch?v=8Sriz45Z4oM - Busybox "halt" & "reboot" do nothing, although klibc versions work fine hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/m68k-netbsd.txt000066400000000000000000000075051504763705000241200ustar00rootroot00000000000000Contents -------- 0. Status summary 1. NetBSD Atari HW support history 2. Fetching NetBSD binaries 3. Testing NetBSD kernel 4. Kernel issues 5. Installing NetBSD 0. Status summary ----------------- Apparently NetBSD works fine with WinUAE Amiga emulation. However, none of the NetBSD kernel variants or versions boots yet with Hatari. This just documents how to test them. According to NetBSD docs: "A minimal system should have a 68030 CPU, 4MB RAM (of which 2MB can be ST-RAM) and a SCSI or IDE disk." 1. NetBSD Atari HW support history ---------------------------------- Atari HW supported by NetBSD is listed in wiki: http://wiki.netbsd.org/ports/atari/ NetBSD releases of interest: * 1995: v1.1, initial Atari TT/Falcon support * 1996: v1.2, Stable Atari support * 1998: v1.3, Medusa Hades, Riebl ethernet and Falcon IDE support, FAT32 & EXT2 support, XFree86, pkgsource packages system * 1999: v1.4, new virtual memory system & full USB support, m68k Linux compat * 2002: v1.6, last non-SMP release, ELF format binaries, multibyte locales * 2004: v2.0, POSIX threads and SMP support, m68k GCC 3.3.1 * 2005: v2.1, last release including install floppies * 2009: v5.0, 1:1 threading model and many scheduling changes, switch from XFree86 to Xorg, initial support for Clang as system compiler * 2012: v6.0, TLS and Atari EtherNEC support * 2015: v7.0, m68k FPE supports all math functions, kernel Lua scripting * 2018: v8.0, Userland uses PIE by default, support for building without crypto removed, remove XFree86 completely, GCC 5.5 * 2020: v9.0, GCC 7.4, LLVM 7.0, reworked SATA & USB3 support, shadow FB + anti-aliasing support * 2024: v10.0, GCC 10, Linux compat disabled by default, Atari box drawing character support for the framebuffer driver NetBSD v8.x and earlier releases are in archive: https://cdn.netbsd.org/pub/NetBSD/NetBSD-archive/ (Only releases newer than those are still supported.) 2. Fetching NetBSD binaries --------------------------- Fetching latest NetBSD binaries: * Get TOS kernel loader "loadbsd.ttp": wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.0/atari/installation/misc/loadbsd.ttp * Get Hatari compatible kernel: wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.0/atari/binary/kernel/netbsd-SMALL030.gz gunzip -c netbsd-SMALL030.gz > small030.bsd * Get minimal install sets (base & config files, base is tens of MBs): wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.0/atari/binary/sets/base.tgz wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.0/atari/binary/sets/etc.tgz (what the other sets are, is listed in the install manual.) * Get installer floppy image: wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.0/atari/installation/miniroot/sysinst.fs.gz gunzip sysinst.fs.gz 3. Testing NetBSD kernel ------------------------ 1. Start Hatari with the directory having the downloaded files: hatari --machine falcon --dsp none --mmu on -s 14 . 2. Drag "small030.bsd" to "loadbsd.ttp" to run kernel. (You need "-b" option instead of just dropping desired kernel on TTP, if you want kernel to ask for root drive upfront.) 4. Kernel issues ---------------- * 030 (Mega)ST(e) / netbsd-SMALL030 kernel: - double address/bus error immediately on boot => no ST models supported? * Falcon / netbsd-FALCON kernel: - NetBSD v9.x or newer output initially garbage to VGA screen, output will become correctly visible only later in boot. With RGB screen, output is correct from the start. - Startup fails to floppy timeout and kernel not being able to mount "sysinst.fs" contents from Hatari floppy drive. 5. Installing NetBSD -------------------- Building (modified) kernel from sources: http://www.netbsd.org/docs/guide/en/chap-kernel.html Full installation instructions: https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.0/atari/INSTALL.html hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/manual.css000066400000000000000000000034671504763705000233070ustar00rootroot00000000000000body { background: #FFFFFF; color: #000000; margin-left: 10px; margin-right: 10px; font-family: Verdana,Arial,Helvetica,sans-serif; } h2 { border-bottom: solid thin black;} h4.gui { clear: right } h5 { margin-bottom: 2px; margin-left: 1em; } hr { width: 100%; height: 2px; margin-top: 4ex; margin-bottom: 2ex; } pre { color: black; background:#eeeeee; margin: 0px 20px 8px 20px; padding: 2px 8px 1px 8px; border: solid thin #ccaa88; } th { text-align: center; } td { font-family: Verdana,Arial,Helvetica,sans-serif; } a:link { color: #000099; background: #ffffff; text-decoration: none; } a:visited { color: #cc0000; background: #ffffff; text-decoration: none; } a:hover { color: #0000ff; background: #ffffff; text-decoration: none; } a:active { color: #993399; background: #ffffff; text-decoration: none; } .pageheader { text-align: center; } .commandline { font-family: Courier,monospace; font-size: 90% } .file { color: #000088; } .key { color: #550000; font-family: Courier,monospace; font-size:90% } .backdropped { background: #ffffee; } .button { color: #000000; background: #c0c0c0; border: outset thin gray; font-family: Courier,monospace; padding-left: 1em; padding-right: 1em; } .image { margin-left: 5px; margin-right: 5px; border-width: 2px; border-style: solid; border-color: #eeeeff; padding: 1cm; text-align: center; } .floatimage { clear: right; float: right; margin-left: 1.5ex; border-width: 2px; border-style: solid; border-color: #eeeeff; padding: 1.5ex; } .clearboth { clear: both; } table.keytable { border: 1px solid; } table.keytable tbody { text-align: center; } table.keytable th { border: 1px solid; padding: 3px; } table.keytable td { border: 1px solid; padding: 3px; } p.parameter { margin: 1px 1em 1px 11%; font-weight: bold; } p.paramdesc { margin: 1px 1em 1px 22%; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/manual.html000066400000000000000000004134571504763705000234670ustar00rootroot00000000000000 Hatari User's Manual

Hatari User's Manual

Index

Introduction

General description

Hatari is an Atari ST, STE, TT and Falcon emulator for Linux, *BSD, macOS, Windows and other Systems which are supported by the SDL library. The emulator is open source software and is distributed under the terms of the GNU General Public License (GPL).

The Atari ST was a 16/32-bit computer system which was first released by Atari in 1985. Using the Motorola 68000 CPU, it was a very popular computer having quite a lot of CPU power at that time. See Appendix B for details on emulation in general.

Unlike many other Atari emulators which try to give you a good environment for running GEM applications, Hatari tries to emulate the hardware as close as possible so that it is able to run most of the old games and demos. Of course you can run normal GEM applications with Hatari, too.

Atari hardware differences matrix

Table of hardware features belonging to each Atari machine, except for the HW common to all (FDC, IKBD, MFP, MIDI, PSG, RS-232...).

Feature: ST MegaST STE MegaSTE TT Falcon Notes:
Audio HW: X = stock machine configuration
56001 DSP X
Microphone X
Microwire/LMC1992XXX
Stereo DMA XXXX
Graphics HW:
Blitter [x]XXXX [x] = add-on, supported since TOS 1.02
Color grades 512512409640964096262144
Max. palette size 16161616256256 Falcon also has a 16-bit high-color non-indexed mode
Horizontal scrolling XXX Only possible with tricks on the normal ST
Videl X Hatari Videl emulation is not complete
Hard disk interfaces: You need a hard-disk driver for using these with normal TOS
ACSI XXXXX
IDE [1][1][1][1][2]X [1] = add-on, requires EmuTOS or TOS 2.06 for booting
[2] = add-on, requires EmuTOS or patched TOS 3.x for booting
SCSI XX Hatari support is incomplete, defaults to SCSI v1
Other HW features:
Second MFP X A.k.a. TT-MFP
MMU [x][x][x][x]XX [x] = Needs 68030+ CPU, requires 512k/1024k EmuTOS or TOS 2.06
FPU [1][1][1][1]X[2] [1] = Memory mapped FPU add-ons, not supported by Hatari
[2] = 68881/68882 FPU through CPU coprocessor interface
NVRAM XX
Real Time Clock XXXX The Mega RTC is different from the Falcon/TT RTC
SCC A/B serial XXX Full support, including LAN port on MegaSTE/TT
TT-RAM +
32-bit addressing
X[x] [x] = Hatari patches TT-RAM support to TOS v4.04
SCU XX System IRQ are correctly masked and forwarded to the CPU/IPL
VME bus XX Although SCU registers are emulated, this expansion bus is not supported by Hatari
Feature ST MegaST STE MegaSTE TT Falcon Notes

Features

Emulator features

  • scaling any resolution to any size (using the SDL2 scaling)
  • interleaved lines rendering with TV "monitor type" for ST-medium and (scaled) ST-low resolutions
  • joystick and joypad emulation via cursor keys and connected PC joysticks
  • GEMDOS interface driver to mount host directories as hard drives (with optional write-protection)
  • support for auto-starting Atari programs, and installing Hatari as handler for them + floppy disk images
  • support for (MSA, GZip and Zip) compressed floppy disk images (with optional write-protection)
  • support for whole system state snapshot save/restore
  • driver for extended VDI resolutions (this patches TOS, the system variables and the so-called line-A variables for bigger resolutions)
  • sound recording as .WAV or .YM files
  • screenshots in PNG, BMP, NEO or XIMG format
  • AVI video capture with sound
  • emulation speedup, slowdown and pause support
  • Built-in debugger and profiler with readline command completion, and tracing for all system calls and many HW events
  • emulator control from Atari programs through NatFeats API, and from the host through a command FIFO or a socket

CPU / FPU emulation

  • 68000 - 68060 and MMU emulation
  • cycle-accurate 68000 emulation, and several options for disabling CPU accuracy for performance
  • 68881, 68882 and 68040/060 internal FPU emulation, either using host FPU (default, faster), or emulating them more accurately in SW

ST hardware emulation

  • ST RAM from 512kiB up to 14MiB
  • optional cartridge images for the ST ROM port
  • ST Shifter with ST-High, ST-Medium and ST-Low resolutions, overscan effects for all borders in color resolutions
  • 512 color ST palette
  • Spec512 mode support for low and medium resolutions
  • many raster effects
  • Ricoh chipset and add-on Blitter chip emulation
  • PSG YM2149 emulation (soundchip) including STFM samples
  • Printer port emulation on hardware level (print to file)
  • RS232 emulation
  • MIDI input/output/through emulation
  • IKBD emulation (keyboard, mouse and joystick) with custom keyboard mapping
  • FDC (floppy disk controller) emulation using floppy disk images in standard formats (*.ST, *.MSA, *.DIM and *.STX)
  • FDC emulation via the IPF support library for using *.IPF, *.RAW and *.CTR images
  • ACSI emulation for hard drive support (with basic support for extended host adapter protocol to access disks > 1 GB)
  • TOS versions 1.00, 1.02, 1.04 and 2.06 (and EmuTOS) can be used in ST mode.

Note: The real ST/STE hardware only can deal with RAM sizes up to 4 MiB. If you configure more than 4 MiB for your emulated system, Hatari will patch the system variables to make TOS aware of the extended memory, and it will fake DMA support for more than 4 MiB (which does not exist on real hardware).

Mega ST hardware emulation

  • Builtin real time clock (RTC)
  • IMP chipset instead of Ricoh one
  • TOS versions 1.02, 1.04 and 2.06 (and EmuTOS) can be used in Mega ST mode.

STE hardware emulation

There is support for following additional STE features:

  • Blitter chip emulation
  • horizontal and vertical hardware fine scrolling
  • split screen techniques / in-screen video address manipulations
  • (STE specific) left border opening
  • 4096 colors STE palette
  • Stereo DMA sample sound
  • Microwire/LMC1992 emulation
  • STE joypads (a.k.a. JagPad / PowerPad)
  • TOS versions 1.06, 1.62, 2.05 and 2.06 (and EmuTOS) can be used in STE mode.

Mega STE hardware emulation

There is support for following additional features in Mega STE and later HW:

  • Builtin real time clock (RTC)
  • DD/HD mode and DIP switch registers
  • 8/16 MHz CPU speed and cycle accurate external 16 KB cache
  • Full support for SCC (LAN, Serial and Modem)
  • TOS versions 2.05 and 2.06 (and EmuTOS) can be used in Mega STE mode.

TT hardware emulation

There is support for following additional TT features:

  • TT low/med/high resolution support
  • Second (TT) MFP chip
  • ST/TT palette switching and video shifter
  • Up to 1024 MiB of TT-RAM and 32-bit memory addressing
  • Full support for SCC (LAN, Serial and Modem)
  • NVRAM for persistent OS configuration and RTC (real time clock)
  • Only TOS version 3.05 and 3.06 (and 512k/1024k EmuTOS) can be used in TT mode.

Note: Only the 68030 CPU and the 68040 CPU can be used in TT mode when using the original TOS.

Falcon hardware emulation

There is support for following additional Falcon features:

  • Partial Videl and Videl borders emulation for all Falcon screen modes
  • STE/Falcon palette switching and shifter
  • Mono/RGB/VGA/TV monitor types
  • DSP co-processor emulation
  • Up to 14 MiB of ST-RAM and 1024 MiB of TT-RAM
  • Experimental microphone (jack) emulation
  • Experimental Crossbar sound matrix (ADC (mic & PSG), DAC, DMA, DSP) interconnect emulation + support for the additional DMA sound sample rates
  • Experimental IDE master and slave emulation for hard drive support
  • Experimental NCR5380 SCSI emulation for hard drive support (incomplete)
  • Full support for SCC (Lan and Modem)
  • NVRAM for persistent OS configuration and RTC (real time clock)
  • TOS versions 4.00, 4.02, 4.04 and 4.92 (and 512k/1024k EmuTOS) can be used in Falcon mode. There is also experimental support for TOS 2.07 (the "Sparrow"-TOS), and for Linux, but they are not recommended for use yet

Note: For using TT-RAM in Falcon mode, Hatari patches TOS 4.04 with the right "Maddalt()" call and code to add a "_FRB" cookie. It also alters the system variables accordingly, since the original Falcon TOS does not support TT-RAM otherwise. For supporting the 68040 and 68060 CPUs in Falcon mode, Hatari patches TOS with code specific to these CPUs.

See the developers' doc/todo.txt file (included with Hatari sources) for the details on the few remaining emulation gaps and the Hatari Atari Software Compatibility List for which Atari programs are known to be affected by them.

System requirements

Hatari needs a fast machine (1 GHz or more for ST/STE emulation, > 2 GHz for Falcon emulation) which is running a POSIX compatible operating system (preferably GNU/Linux) that supports the SDL library. There are also some ports to other operating systems like macOS or Windows, but they are not used by the developers, so such builds are normally not very well tested.

Compiling and running

Compiling Hatari

Required:

  • A C compiler that supports the "gnu99" C extensions. Thus any recent versions of GCC or clang should be fine.
  • A working CMake installation (version 3.3 or newer). See http://www.cmake.org/ for details.
  • The SDL library v2.0.6 or newer. You can get it from http://www.libsdl.org/.

Optional:

  • The zLib compression library for supporting compressed disk images. You can get it from https://zlib.net/.
  • The PNG image library for PNG format screenshots and to decrease AVI video recording file sizes. You can get it from http://www.libpng.org/.
  • The GNU Readline library for Hatari debugger command line editing.
  • The Xlib library to support Hatari Python UI window embedding on systems with the X window system (Linux and other unixes).
  • The PortMidi library required for MIDI device support on macOS and Windows http://portmedia.sourceforge.net/.
  • The udev library for NatFeats SCSI driver media change detection (Linux).
  • The IPF support library http://www.softpres.org/download.

The versions available in your Linux distribution will be sufficient in most cases, but make sure you have also the header files installed for the libraries as well! Typically they are in a corresponding -dev package.

After you have verified that you have the required libraries and their development files, change to the hatari/ directory. Create a build/ directory under it and configure the build system for your environment:

mkdir -p build
cd build
cmake ..

Then compile Hatari by typing make. If all works fine, you'll get the executable hatari in the src/ subdirectory.

Note: Instead of calling CMake directly, you can also use the supplied configure script to run CMake and to give the arguments (like install prefix) in a format familiar from GNU Autotools using programs. Type "./configure --help" to see all the options supported by this script.

Installation of a TOS ROM

Before you can start Hatari, you have to copy a TOS ROM image to the data directory (<prefix>/share/hatari/, by default /usr/local/share/hatari/) and rename it to tos.img, or use the --tos command line option to tell Hatari where to find a TOS ROM. Hatari needs a TOS ROM image because this contains the operating system of the emulated Atari.

Unfortunately it is not possible to ship an original ROM image with the Hatari package since these images are still copyrighted. But you can easily create an image with a real Atari machine and one of those various ROM-image programs for them (search for "TOSDUMP" with your favourite internet search engine).

Another solution is EmuTOS, which is also shipped with the official release versions of Hatari. EmuTOS is an open-source TOS clone. You can find it at: https://emutos.sourceforge.io/. While it works fine with most Atari software, it is not the best solution for Falcon emulation due to missing TrueColor mode support, or for playing old (disk only) games and other software tied to specific TOS version(s) (see emutos.txt for more details). However, it is free, its 1024k version supports all Atari machines emulated by Hatari, it supports more languages than original TOS, does not require a driver to support harddisk images, and it boots faster than original TOS.

If you do not specify a TOS image on the commandline and Hatari cannot find a suitable TOS image in the default dir, you'll get the chance to select a TOS image file from the GUI.

Installation of the binary

Type make install as "root" user to do a systemwide installation.

Assuming you didn't change the default installation prefix and that /usr/local/bin/ is in your PATH, you should be now able to start the Hatari executable from anywhere.

When you finally have got a TOS image, try starting Hatari with the option --help to find out more about its command line parameters.

Running Hatari for the first time

Now type hatari to run the emulator for the first time. If all goes well, you should now be presented with a window showing you the familiar little green desktop of the Atari ST. Press F12 to turn on the GUI to configure Hatari to suit your needs, press F11 to toggle windowed and fullscreen mode.

Configuration options precedence

Hatari settings can come from several sources, with later ones overriding the earlier given ones:

  • Builtin Hatari default options
  • Global /etc/hatari.cfg (or /usr/local/etc/hatari.cfg) configuration file
  • User specific ~/.config/hatari/hatari.cfg configuration file
  • Command line arguments
  • Option changes done at run-time in Hatari options GUI, with the Hatari debugger "setopt" command, or through the (optionally enabled) Hatari command FIFO or control socket

Some of the run-time changes require emulation to be reset for them to take effect. In most cases, Hatari will do that automatically when needed.

Command line options and arguments

Usage:

 hatari [options] [disk image | directory | Atari program ]

As an argument one can give either a name of:

  • A floppy disk image,
  • A directory that should be emulated as a virtual GEMDOS HD, or
  • An Atari program that should be autostarted. In this case the program's directory will be used as the C: drive from where this program will be started.
(These arguments are shortcuts for "--disk-a", "--harddisk" and "--auto" options listed below.)

Booting will be done from the disk image or directory that's given last on the command line as an option or the argument (and which corresponds to A: or C:).

Hatari command line options are split into several categories:

General options

-h, --help

Print command line options and terminate

-v, --version

Print version information and terminate

--confirm-quit <bool>

Whether Hatari confirms quitting

-c, --configfile <filename>

Read additional configuration values from <file>, these override values read from the global and user configuration files

-k, --keymap <file>

load keyboard mapping from <file>. "Symbolic" mapping will be used as fallback for keys not defined there

--country <x>

Set EmuTOS ROM country code on Mega/ST/STe machines lacking NVRAM, when EmuTOS indicates supporting multiple ones.

In 512k EmuTOS images, country code selects the TOS keyboard layout and screen refresh (US = 60Hz NTSC, 50Hz PAL otherwise). In 1024k EmuTOS images (coming with Hatari binaries and supporting multiple languages), country code selects also TOS language.

Alternatively, one can use "tos-lang-change" tool from EmuTOS project to modify country code in the ROM image file itself. That works also for TOS v4

--layout <x>

Set NVRAM keyboard layout value. Set NVRAM keyboard layout value. While both TT and Falcon machines have NVRAM, only TOS v4 and EmuTOS 512k / 1024k ROM versions support multiple layouts.

Regardless of whether keyboard layout change is done through the ROM country code or NVRAM setting, it may impact your key mappings in Hatari key mapping files, Hatari Python UI arguments, or key injection in your automation scripts for Hatari debugger, command FIFO or hconsole tool

--language <x>

Set NVRAM language value. While both TT and Falcon machines have NVRAM, only TOS v4 and EmuTOS 1024k ROM versions support multiple languages. Default is taken from the LANG environment variable

--fast-forward <bool>

On fast machine helps skipping (fast forwarding) Hatari output

--auto <program>

Autostarts given program, if TOS finds it. Program needs to be given with full path it will have under emulation, for example "C:\DIR\PROGRAM.PRG"

--fast-forward-key-repeat <bool>

Use keyboard auto repeat when using fast forward mode (default true)

Common display options

-m, --mono

Start in monochrome mode instead of color

--monitor <x>

Select monitor type (x = mono/rgb/vga/tv)

--tos-res <x>

Select TOS resolution for color monitors (x = low/med/high/ttlow/ttmed)

-f, --fullscreen

Start the emulator in fullscreen mode

-w, --window

Start the emulator in windowed mode

--grab

Grab mouse (also) in windowed mode

--resizable <bool>

Allow window resizing

NOTE: this is supported only by Hatari SDL2 build

--borders <bool>

Show ST/STE/Falcon screen borders (for low/med resolution overscan demos)

--frameskips <x>

Skip <x> frames after each displayed frame to accelerate emulation (0=disabled, >4 uses automatic frameskip with given value as maximum)

--slowdown <x>

Slow down emulation by factor of x (used as multiplier for VBL wait time)

--statusbar <bool>

Show statusbar (with floppy leds etc etc)

--drive-led <bool>

Show overlay drive led when statusbar isn’t shown

--max-width <x>

Preferred / maximum Hatari screen width

--max-height <x>

Preferred / maximum Hatari screen height.

Maximum width and height options are part of Hatari's Atari monitor emulation. They limit the size Hatari should aim for its internal SDL framebuffer, and how much of the Atari screen borders are visible.

On an SDL2 build, framebuffer is then scaled to the Hatari output window based on the specified Hatari zoom factor (see below).

Aim of this is to have all resolutions show up in approximately same size, like on a real Atari monitor. Hatari's internal integer scaling support sets some limits on this, so it's an expert option.

Note: Only reason to change the defaults, should be limiting this to a smaller resolution for performance reasons, e.g. for video recording, or on really underpowered systems, to make monitor do all of the ST-low resolution scaling by forcing Hatari to ask SDL for CGA / QVGA resolution.

-z, --zoom <x>

This option overrides max width/height options so that e.g. ST-low resolution gets always doubled, and all resolutions (except TT-high) have approximately the same size, like on a real CRT monitor.

Zoom factor is then used to scale that up (or down) to the Hatari output window. This way scaling results always in approximately same sized Hatari window.

With non-integer zoom factors, linear scaling is used to smooth out the output, with integer zoom factors, scaling is done using nearest neighboring pixels for sharper output. This applies also to window resizes.

To avoid zooming for low ST resolutions, use "--zoom 1 --max-width 416 --max-height 276" (if you don't need borders, 320x200 size is enough). Disabling low resolution doubling like this is not recommended for Falcon emulation because TOS v4 bootup and some demos switch resolutions frequently.

--disable-video <bool>

Run emulation without displaying video (audio only)

ST/STE specific display options

--spec512 <x>

Hatari uses this threshold to decide when to render a screen with the slower but more accurate Spectrum512 screen conversion functions (0 <= x <= 512, 0=disable)

--video-timing <x>

Wakeup State for MMU/GLUE (x=ws1/ws2/ws3/ws4/random, default ws3). When powering on, the STF will randomly choose one of these wake up states. The state will then affect the timings where border removals and other video tricks should be made, which can give different results on screen. For example, WS3 is known to be compatible with many demos, while WS1 can show more problems.

TT/Falcon specific display options

Zooming to sizes specified below is internally done using integer scaling factors. This means that different Atari resolutions may show up with different sizes, but they are never blurry.

--desktop <bool>

Whether to use desktop resolution on fullscreen to avoid issues related to resolution switching. Otherwise fullscreen will use a resolution that is closest to the Hatari window size. (enabled by default)

--force-max <bool>

Hatari window size is forced to specified maximum size and black borders used when Atari resolution doesn’t scale evenly to it. This is most useful when recording videos of Falcon demos that change their resolution. (disabled by default)

--aspect <bool>

Whether to do monitor aspect ratio correction (enabled by default)

VDI options

--vdi <bool>

Whether to use VDI screen mode. Doesn't work with TOS v4. TOS v3 memory detection isn't compatible with larger VDI modes (i.e. you need to skip the detection at boot). Original TOS desktops use wrong window size in 2-plane (4 color) VDI mode when screen height >= 400 pixels. Because of these issues, using EmuTOS is recommended for VDI mode

--vdi-planes <x>

Use extended VDI resolution with bit depth <x> (x = 1, 2 or 4)

--vdi-width <w>

Use extended VDI resolution with width <w> (320 < w <= 2048)

--vdi-height <h>

Use extended VDI resolution with height <h> (160 < h <= 1280)

Because TOS and popular GEM programs have problems with certain screen sizes, Hatari enforces restrictions on VDI screen size. In total VDI screen size is limited to 32-300kB, width to multiple of 16 pixels, and height to multiple of 8 pixels (smaller system font height). That translates to following maximum standard resolutions for the VDI mode:

monochrome

FullHD (1920x1080), WUXGA (1920x1200) and QWXGA (2048x1152)

2 plane mode (4 colors)

HD (1280x720), WXGA (1280x768) and XGA+ (1152x864)

4 plane mode (16-colors)

qHD (960x540), DVGA (960x640) and WSVGA (1024x600)

Screen capture options

--crop <bool>

Remove statusbar from the screen captures

--avirecord

Start AVI recording. Note: recording will automatically stop when emulation resolution changes.

--avi-vcodec <x>

Select AVI video codec (x = bmp/png). PNG compression can be much slower than using the uncompressed BMP format, but uncompressed video content takes huge amount of space.

--png-level <x>

Select PNG compression level for AVI video (x = 0-9). Both compression efficiency and speed depend on the compressed screen content. Highest compression level (9) can be really slow with some content. Levels 3-6 should compress nearly as well with clearly smaller CPU overhead.

--avi-fps <x>

Force AVI frame rate (x = 50/60/71/...)

--avi-file <file>

Use <file> to record AVI

--screenshot-dir <dir>

Directory for saving screenshots (default Hatari directory for them is OS specific).

--screenshot-format <x>

Select screenshot file format (x = bmp/png/neo/ximg).

Devices options

-j, --joystick <port>

Emulate joystick with cursor keys in given port (0-5)

--joy<port> <type>

Set joystick type (none/keys/real) for given port

--printer <file>

Enable printer support and write data to <file>

--midi <bool>

Enable MIDI support (when Hatari is built with PortMidi support)

--midi-in <filename>

Enable MIDI support and write MIDI data to <file> (when not built with PortMidi support)

--midi-out <filename>

Enable MIDI support and read MIDI data from <file> (when not built with PortMidi support)

--rs232-in <filename>

Enable MFP serial port support and use <file> as the input device

--rs232-out <filename>

Enable MFP serial port support and use <file> as the output device

--scc-a-in <filename>

Enable SCC channel A serial port support and use <file> for the input (only for Mega-STE, TT and Falcon)

--scc-a-out <filename>

Enable SCC channel A serial port support and use <file> for the output (only for Mega-STE, TT and Falcon)

--scc-a-lan-in <filename>

Enable SCC channel A LAN port support and use <file> for the input (only for Mega-STE and TT)

--scc-a-lan-out <filename>

Enable SCC channel A LAN port support and use <file> for the output (only for Mega-STE and TT)

--scc-b-in <filename>

Enable SCC channel B serial port support and use <file> for the input (only for Mega-STE, TT and Falcon)

--scc-b-out <filename>

Enable SCC channel B serial port support and use <file> for the output (only for Mega-STE, TT and Falcon)

Floppy drive options

--drive-a <bool>

Enable/disable drive A (default is on)

--drive-b <bool>

Enable/disable drive B (default is on)

--drive-a-heads <x>

Set number of heads for drive A (1=single sided, 2=double sided)

--drive-b-heads <x>

Set number of heads for drive B (1=single sided, 2=double sided)

--disk-a <file>

Set disk image for floppy drive A

--disk-b <file>

Set disk image for floppy drive B

--fastfdc <bool>

Speed up FDC emulation (can cause incompatibilities)

--protect-floppy <x>

Write protect floppy image contents (on/off/auto). With "auto" option write protection is according to the disk image file attributes

Hard drive options

-d, --harddrive <dir>

GEMDOS HD emulation. Emulate hard disk partition(s) with <dir> contents. If directory contains only single letter (C-Z) subdirectories, each of these subdirectories will be treated as a separate partition, otherwise the given directory itself will be assigned to drive "C:". In the multiple partition case, the letters used as the subdirectory names will determine to which drives/partitions they’re assigned. If <dir> is an empty string, then harddrive's emulation is disabled

--protect-hd <x>

Write protect hard drive <dir> contents (on/off/auto). With "auto" option the protection can be controlled by setting individual files attributes as it disables the file attribute modifications for the GEMDOS HD emulation

--gemdos-case <x>

Specify whether new dir/filenames are forced to be in upper or lower case with GEMDOS HD emulation. Off/upper/lower, off by default

--gemdos-time <x>

Specify what file modification timestamps should be used, emulation internal (atari) ones, or ones from the machine (host) on which the machine is running. While Atari emulation and host clocks are in sync at Hatari startup, they will diverge while emulation is running, especially if you use fast forward. Default is "atari". If you modify files accessed by the Atari side, directly from the host side while Hatari is already running, you may want to use "host" option

--gemdos-conv <bool>

Whether GEMDOS file names with 8-bit (non-ASCII) characters are converted between Atari and host character sets. On Linux, host file name character set is assumed to be UTF-8. This option is disabled by default, in case you have transferred files from Atari machine without proper file name conversion (e.g. by zipping them on Atari and unzipping on PC)

--gemdos-drive <drive>

Assign (separately specified) GEMDOS HD to given drive letter (C-Z) instead of default C:, or use "skip" to specify that Hatari should add GEMDOS HD after IDE and ACSI drives (assumes Hatari and native HD driver parse same number of partitions from the partition tables in HD images)

--acsi <id>=<file>

Emulate an ACSI hard drive with given bus ID (0-7) using image <file>. If just a filename is given, it is assigned to bus ID 0

--scsi <id>=<file>

Emulate a SCSI hard drive with given bus ID (0-7) using image <file>. If just a filename is given, it is assigned to bus ID 0

--scsi-ver <id>=<version>

Emulate specified SCSI version (1-2) for given BUS ID (0-7). If just a version is given, it is applied to BUS ID 0

--ide-master <file>

Emulate an IDE 0 (master) hard drive with an image <file>

--ide-slave <file>

Emulate an IDE 1 (slave) hard drive with an image <file>

--ide-swap <id>=<x>

Set byte-swap option <x> (off/on/auto) for given IDE <id> (0/1). If just option is given, it is applied to IDE 0

Memory options

--memstate <file>

Load memory snap-shot <file>

-s, --memsize <x>

Set amount of emulated RAM, x = 1 to 14 MiB, or 0 for 512 KiB. Other values are considered as a size in KiB. While Hatari allows 14 MiB for all machine types, on real HW, ST/STE can have up to 4 MiB, MegaSTE/TT up to 10MiB, and Falcon up to 14 MiB RAM.

--ttram <x>

Set amount of emulated TT RAM (for Falcon and TT mode), x = 0 to 1024 MiB (in 4MiB steps)

ROM options

-t, --tos <imagefile>

Specify TOS ROM image to use

--patch-tos <bool>

Use this option to enable/disable TOS ROM patching. Experts only! Leave this enabled unless you know what you are doing!

--cartridge <imagefile>

Use ROM cartridge image <file> (only works if GEMDOS HD emulation and extended VDI resolution are disabled)

CPU/FPU/bus options

--cpulevel <x>

Specify CPU (680x0) to use (use x >= 1 with EmuTOS or TOS >= 2.06 only!)

--cpuclock <x>

Set the CPU clock (8, 16 or 32 MHz)

--compatible <bool>

Use a more compatible 68000 CPU mode with better prefetch accuracy and cycle counting

--cpu-exact <bool>

Use cycle exact CPU emulation

--data-cache <bool>

Emulate >=030 CPU data cache

--addr24 <bool>

Use 24-bit instead of 32-bit addressing mode (24-bit is enabled by default)

--fpu <x>

FPU type (x=none/68881/68882/internal)

--fpu-softfloat <bool>

Use full software FPU emulation (Softfloat library)

--mmu <bool>

Use MMU emulation

Misc system options

--machine <x>

Select machine type (x = st, megast, ste, megaste, tt or falcon)

--blitter <bool>

Enable blitter emulation (ST only)

--rtc-year <x>

With the default value 0, RTC date and time are taken from the host. If application does not handle current dates, this can be used to change RTC year to a more compatible one. See also "--gemdos-time" option.

--dsp <x>

Falcon DSP emulation (x = none, dummy or emu, Falcon only)

--timer-d <bool>

Patch redundantly high Timer-D frequency set by TOS. This can increase Hatari speed significantly (especially for ST/e emulation) as the original Timer-D frequency causes large amount of extra interrupts to emulate.

--fast-boot <bool>

Patch TOS and initialize the so-called "memvalid" system variables to by-pass the memory test of TOS, so that the system boots faster.

Sound options

--mic <bool>

Enable/disable (Falcon only) microphone

--sound <x>

Sound frequency: 6000-50066. "off" disables the sound and speeds up the emulation. To prevent extra sound artifacts, the frequency should be selected so that it either matches evenly with the STE/TT/Falcon sound DMA (6258, 12517, 250033, 50066 Hz) or your sound card frequencies (11025, 22050, 44100 or 6000...48000 Hz). Check what your sound card supports.

--sound-buffer-size <x>

SDL’s sound buffer size: 10-100, or 0 to use default buffer size. By default Hatari uses an SDL buffer size of 1024 samples, which gives approximately 20-30 ms of sound depending on the chosen sound frequency. Under some OS or with not fully supported sound card, this default setting can cause a bigger delay at lower frequency (nearly 0.5 sec). In that case, you can use this option to force the size of the sound buffer to a fixed number of milliseconds of sound (using 20 is often a good choice if you have such problems). Most users will not need this option.

--sound-sync <bool>

The emulation rate is nudged by +100 or 0 or -100 micro-seconds on occasion. This prevents the sound buffer from overflowing (long latency and lost samples) or underflowing (short latency and repeated samples). The emulation rate smoothly deviates by a maximum of 0.58% until synchronized, while the emulator continuously generates every sound sample and the crystal controlled sound system consumes every sample.
(on|off, off=default)

--ym-mixing <x>

Select a method for mixing the three YM2149 voice volumes together. "model" uses a mathematical model of the YM voices, "table" uses a lookup table of audio output voltage values measured on STF and "linear" just averages the 3 YM voices.

Debug options

-W, --wincon

Open console window (Windows only)

-D, --debug

Toggle whether CPU exceptions invoke the debugger

--debug-except <flags>

Specify which exceptions invoke debugger, see "--debug-except help" for available (comma separated) exception flags.

--bios-intercept <bool>

Enable/disable XBios command parsing. Allows Atari programs to use all Hatari functionality and change Hatari state through Hatari specific XBios(255) calls. XBios(20) printscreen calls produce also Hatari screenshots. XBios(11) Dbmsg call can be used to invoke the debugger.

--conout <device>

Enable console (xconout vector functions) output redirection for given <device> to host terminal. Device 2 is for the (CON:) VT52 console, which vector function catches also EmuTOS panic messages and MiNT console output, not just normal BIOS console output.

--disasm <x>

Set disassembly options. 'uae' and 'ext' select the disassembly engine to use, bitmask sets disassembly output options and 'help' lists them.

--natfeats <bool>

Enable/disable (basic) Native Features support. E.g. EmuTOS uses it for debug output.

--trace <flags>

Activate debug traces, see "--trace help" for available tracing flags

--trace-file <file>

Save trace output to <file> (default=stderr)

--msg-repeat

Toggle whether successive repeats of identical log or trace messages will be suppressed, so that only their count is shown (default=suppress). Disassembly, register and (multi-line) AES traces bypass this feature

--parse <file>

Parse/execute debugger commands from <file>

--saveconfig

Save Hatari configuration and exit. Hatari UI needs Hatari configuration file to start, this can be used to create it automatically.

--control-socket <path>

Hatari connects to given local socket file and reads commands from it. Use when the control process life-time is longer than Hatari's, or control process needs response from Hatari

--cmd-fifo <path>

Hatari creates the indicated FIFO file and reads commands from it. Commands can be echoed to FIFO file, and are same as with the control socket. Hatari outputs help for unrecognized commands and subcommands

--log-file <file>

Save log output to <file> (default=stderr)

--log-level <x>

Log output level (x=debug/todo/info/warn/error/fatal)

--alert-level <x>

Show dialog for log messages above given level

--run-vbls <x>

Exit after X VBLs. Used with --benchmark mode

--benchmark

Start in benchmark mode (use with --run-vbls). Same as --fast-forward mode, except it cannot be disabled at run-time, and FPS will be printed on emulation exit (and pausing) regardless of log level. Allows better measuring of emulation speed, in frames per second. Unless you're specifically measuring emulator audio and screen processing speed, disable them (--sound off/--disable-video on) to have as little OS overhead as possible

Type hatari --help to list all the command line options supported by a given version of Hatari.

See Hatari Debugger Manual for more information on debugging and profiling Atari programs.

Using the emulated system

Once you have started Hatari successfully, you can use the emulator as an almost complete Atari ST computer system.

The GUI

Press F12 to enter the GUI. Navigate it with the mouse. The GUI is rather self explanatory.

The Main Menu

Hatari's GUI - the main menu

You can reach the other setup dialogs from the main menu by clicking on the appropriate buttons.

You can load the current settings from a configuration file by clicking on the Load config button, and save the current settings to a configuration file by clicking on the Save config button.

Click OK to go back and continue the emulation. All changed options will be applied.

NOTE:Any changes to machine ROM or HW configuration mean that emulation is reset to apply the change. Don't do that unless you're OK to lose your current emulation state.

Select the "Reset machine" option if you want the emulated machine to perform a cold reset. This is equal to switching the power off and on again on a real Atari machine.

Click Quit to terminate Hatari and return to the host OS.

Click Cancel to abandon any changes that you have made.

(There are also keyboard shortcuts to do several of these actions.)

The File Selector Dialog

Hatari's GUI - the fileselector

The file selector dialog appears whenever you are prompted to choose a file or folder.

To enter a folder or choose a file, simply click on the entry in the main box of the dialog. To navigate in the file list, you can use the scrollbar on the right with mouse, use mousewheel, keyboard up + down arrow (with Alt), or page up + down keys.

You can use the three buttons in the upper right corner for additional folder navigation. Click the .. button to go up one level in the directory tree. The CWD button takes you to the current working directory (i.e. the folder that was current when Hatari has been started). Click the ~ button to return to your home directory. The / button can be clicked to go to the root directory of the file system.

When you tick the "Show hidden files" setting, Hatari will also show files that start with a dot in the file selection dialog.

The System Dialog

Hatari's GUI - the system dialog

The system dialog can be used to define the basic hardware attributes of the machine that should be emulated.

The machine type option is used to select the type of Atari computer to be emulated:

  • The ST was the very first 16/32-bit computer from Atari. Most older games and demos require an ST. TOS 1.00, 1.02, 1.04 or 2.06 is required for running in ST mode.
  • The Mega-ST was an slightly improved version which also provided the so-called blitter chip (for accelerating certain graphic operations) and a Real Time Clock (RTC) chip. A note for experts: Hatari emulates the Mega-ST with the so-called IMP chipset, while the normal ST is emulated with the Ricoh chipset. This gives a slightly different behavior between the two machine types with regards to bus errors.
  • The STE was introduced some years later and had some more advanced hardware features, like the blitter chip, hardware scrolling and DMA sample sound (but no Real Time Clock). There are not that many demos or games that really require an STE but since most normal ST games/demos also work with an STE, it is normally safe to always work in STE mode. Also make sure to use TOS version 1.06, 1.62 or 2.0x with this machine type.
  • The Mega-STE was an improved version of the STE, with 16 MHz (instead of 8 MHz) CPU frequency, a built-in Real Time Clock chip, and a VME bus.
  • The TT was the advanced workstation from Atari. It featured a 32MHz 68030 CPU, a very high monochrome resolution, and a lot of interfaces. However, compatibility was not as high as with the other machines. For TT emulation you need TOS 3.0x.
  • The Falcon was Atari's last computer, using a 16 MHz 68030 CPU and some more interesting new hardware feature, like the Videl graphic chip and DSP 56001 for audio processing. You need TOS 4.0x for running in Falcon mode.

Note: Falcon and especially TT emulation are still considered as experimental and incomplete, so quite a bunch of programs do not work very well yet. Also a lot of old games and demos do not work with these machine types anymore since the hardware is quite a bit different. There were only very few programs that were made for the TT exclusively, while there were some interesting games and demos specially made for the Falcon.

The video timings ("wakestate") settings influence the internal timings of the ST video chip emulation. You normally do not have to change these unless you know what you are doing, only some very few demos require a special setting here.

For Falcon mode, you can choose whether you want to disable DSP emulation, fake it or enable full emulation. Most Falcon programs only play sound or work correctly when you enable the DSP emulation, but it needs a lot of host CPU power (more than 2 GHz) for full emulation. So if you have a slow host CPU, you can try if your Falcon program also runs with DSP disabled or in the "dummy" fake mode. Note that you cannot change this option while the DSP based program already runs.

The check boxes in the "CPU and system parameters" section can be used to fine-tune the machine and CPU types.

The Blitter option can be set to enable Blitter emulation in plain ST mode. The Blitter is a custom chip that accelerates some graphical operations. Note that in Mega-ST, STE and Falcon mode, the Blitter is always enabled (since these machines have always been sold with a Blitter chip). The TT was always shipped without the Blitter chip.

The "Patch Timer-D" option changes the Timer-D initialization from TOS. TOS uses the MFP timer D as a baudrate generator for RS232. However, the TOS default value slows down the emulation. The patch gives you a better performance. It is normally safe to enable the patch, but if you encounter a program that does not work, you can try to disable the patch to see if it works better.

With the "Boot faster" option, Hatari patches the TOS ROM and some system variables, to speed up the boot process of the emulated system, e.g. by simulating a warm reset. This is a convenient option, but some very few old programs rely on an unmodified boot process, so in rare cases this option has to be switched off to get those programs running.

NOTE: The emulated Atari system is very very sensitive to all of these options so Hatari will automatically reset the emulation when changes to (most of) these settings are applied. Rest of the settings apply to next boot.

Most settings are also selected automatically when one uses the --machine command line option.

The CPU Dialog

Hatari's GUI - the CPU dialog
CPU type

The CPU type option can be used to select the level of the central processing unit. If you are not sure what to use, simply select 68000 for ST and STE machines and 68030 for TT and Falcon emulation, since these were the original configurations used in the Atari computers. In case you want to vary the CPU type, you should be aware of some constraints:

  • Atari ST and STE have only been shipped with a 68000 CPU, so for best compatibility with old programs, you should choose this CPU type.
  • If you are going to use TOS 1.0x, you also have to select the 68000 CPU, since these TOS versions are not aware of the higher CPU levels yet. If you want to use a higher CPU level with the ST or STE machine type, you have to use TOS 2.0x (or EmuTOS) instead.
  • Atari TT and Falcon computers were using the 68030 CPU, so you should select the 68030 CPU type for these machines.
  • TOS 3.0x and 4.0x also only work with a CPU >= 68020.
  • 68010 and 68040, 68060 have never been used in official Atari computers, so don't use these CPU types without a good reason.
  • The 68060 option is currently considered as experimental, so do not use this option unless you know what you are doing.
CPU options

The CPU clock option can be used to select the frequency that is used to clock the CPU. 8 MHz is the standard for ST and STE and the most compatible frequency for old software. The CPU in the TT was clocked with 32 MHz. The original Mega STE and Falcon could be run in both, 8 MHz and 16 MHz with the possibility to toggle between the two frequencies during runtime via software. Hatari supports this feature, too, so it is possible to switch between 8 and 16 MHz from the emulated side, e.g. with certain accessories or CPX. However, if you select 32 MHz or a non-standard CPU in the Hatari GUI, this feature is disabled, since Hatari assumes that you rather want to emulate a system with an accelerator CPU board (which don't implement this the software frequency switching).

The "Prefetch mode" option is used to enable the emulation of 68k address errors and the so-called CPU prefetch buffer. This is needed for best compatibility, but it slows down emulation a little bit so you can disable it if you don't need it and if you have a slow host system.

The "Cycle exact", "Data cache", "MMU emulation" and "24-bit addressing" options are considered as experimental and should only be changed if you know what you are doing.

FPU type

The FPU settings can be used to select the type of floating point unit of >= 68020 CPUs. (68882) FPU is enabled by default only for TT machines, as they were only ones that originally shipped with one.

When emulating the Motorola FPU, PC host FPU can be used to do the math. This is the default and fastest method, but its results have some slight differences compared to a real Motorala FPU.

For higher precision, you can select the "Accurate FPU emulation" mode: this will emulate all the FPU operations using only the CPU and with the same rules as the Motorala's FPU. This emulation is bit-exact (except for the last few bits in trig/log functions), and a little slower than using host FPU.

The Floppy Disks Dialog

Hatari's GUI - the floppy disks dialog

This dialog can be used to choose which floppy disks should be emulated in the disk drives. You can use most standard Atari ST disk image files. You may select and browse also zipped disk images. See "Floppy disk images" section for details.

Each drive can be enabled or disabled (as if it was not connected or turned off). You can also choose to emulate a single sided drive instead of a double sided one (some games or demos will have a different behaviour in single sided mode).

Click on the button Browse next to the A: and B: option to go to the fileselector to choose a disk image for the corresponding drive.

Click on Eject to eject a disk image from the emulated drive. The emulated ST will act as if had no floppy disk in its drive.

You can specify a default directory where Hatari will start to browse the filesystem.

Check the "Auto insert B" option if you want Hatari to be smart and insert the second disk of a two disk game automatically. Some games then use the second drive automatically. In the case that a game is not able to find the disk in the second drive, you have to insert the second disk in drive A: manually when prompted.
NOTE: This option only works properly if the file name of the first disks ends with an 'a' before the extension and the second disk name ends with a 'b'.

Select if you want to use fast FDC (Floppy Disk Controller) emulation. "Fast floppy access" option will speed up disk accesses, but this can cause incompatibilities with programs that expect correct delays (some games/demos don't expect data to be read too fast from the disk). For example, when using STX images, most protections will fail if fast floppy access is enabled.

If you want, you can set Hatari to write-protect your disks. Atari ST viruses can spread on disk images, so that can be a good idea. However, note that some programs won't work correctly (or at all) with write protected disks, and things like saving highscores in games will fail.

Hatari's GUI - the new floppy dialog

If you need to create a new blank disk image, click on Create blank image. Parameters for the new image can be set in the following dialog. HD and ED disk sector counts are for larger, non-Atari disk sizes, they can be useful with programs that don't work from hard drive, or with with GEMDOS HD emulation. Click on Create to save the new image or on Back to return to the disk dialog.

After clicking Create, a fileselector appears. You can browse the filesystem now. Select the target directory, click beside "File:" and type in a name for the new disk image. The name should terminate with .st or .msa.

Hatari can currently create plain .ST and .MSA disk images exclusively. hmsa command line utility can be used to convert disk images between .ST and .MSA formats.

The Hard Disks Dialog

Hatari's GUI - the hard disks dialog

This dialog can be used to change the hard disk settings.

Here you can select a hard disk image file for ACSI, SCSI or IDE hard drive emulation, or you can select a host directory to be emulated as the Atari hard drive. Use the arrow buttons to select the ID of the drive that you want to set, then click on Browse to choose a file which should be used for providing the contents of the hard disk, or click on Eject to disable the current ID.

IDE controllers are using a 16-bit interface, so depending on where the contents of a real hard disk have initially been created (on an Atari machine or on a PC), and depending on where the contents have been read out from the disk, 16-bit values in the image might be byte-swapped. Hatari can either try to detect this situation automatically (when "Auto" is selected), or you can tell Hatari whether it should always byte-swap the disk image contents or not.

GEMDOS HD emulation can be used to provide a folder on your host computer file system as hard drive(s) to the emulated Atari. Select Browse to choose a folder, or Eject to disable the drive(s) again.
The "Atari <-> host 8-bit filename conversion" setting can be used to tell Hatari whether it should try to convert the character set of the file names on the host to the Atari character set, since modern operating systems use different character sets than Atari TOS. File name conversion option is best-effort conversion between the host OS and Atari character set for the non-ASCII file names exposed by the GEMDOS HD emulation.
GEMDOS HD emulation can override partition(s) from HD images. With "Add GEMDOS HD after ACSI/SCSI/IDE partitions" option Hatari tries to assign it to a drive after the partitions on HD images, instead of C: (whether that works correctly depends on whether your emulated Atari hard disk interprets the HD images partition tables similarly to Hatari, and whether it starts assigning them from C: onwards). As a last resort, you can use "--gemdos-drive" command line option to explicitly specify which drive should be used for GEMDOS HD.
Finally, you can also choose whether you want to provide the files on GEMDOS HD only as write-protected to the Atari environment or not, or whether you want Hatari to select this status automatically depending on the file attributes of the file in the host file system.

Check "Boot from HD" to set given hard disk image / directory as TOS boot device (if ACSI or IDE is enabled, it is C:, otherwise it is the first specified GEMDOS HD drive). With command line options, the value of this setting depends on whether you specify floppy image or harddisk later on the command line (later one takes precedence).

Note that you need TOS version >= 2.05 to boot from IDE hard drive. ACSI hard drive emulation does not work with TOS 4.0x in Falcon mode. For SCSI emulation, you either need to run with TT or Falcon emulation. Please also refer to the Hard disk support section for more details about hard disk emulation.

The Memory Dialog

Hatari's GUI - the memory dialog

You can select the amount of RAM for the emulated machine here. While Hatari allows 14 MiB for all machine types, on real HW, ST/STE can have up to 4 MiB, MegaSTE/TT up to 10MiB, and Falcon up to 14 MiB RAM. Of values below 4 MiB, only ones that were valid on a real unmodified STFM are available

TT RAM size allows to emulate up to 1024 MiB of 32-bit RAM. This is only useful in Falcon and TT mode, and require to disable "24 bit addressing" mode in the CPU options (Hatari needs to patch Falcon TOS v4 for this)

Here you will find the options to save memory snapshots as well.

Click on Save to save a memory snapshot to file. You can select a new filename here.

Click on Restore to restore a memory snapshot from a file. Use the fileselector to select the snapshot to be restored.

NOTE: Memory snapshots are not interchangeable between different versions of Hatari. e.g. if you compile a newer Hatari, you cannot load your old memory snapshots back.

The ROM Dialog

Hatari's GUI - the ROM dialog

Here you can select the TOS image to use. Click on Browse to select it via the fileselector. You can also select an optional cartridge image to use. Click on Browse to select one via the fileselector. Click on Eject to disconnect the custom cartridge image.

Depending on the machine type that you want to emulate, you can either use EmuTOS (see emutos.txt for more info), or a TOS version that supports the machine type. For ST mode, use TOS 1.00, 1.02, 1.04 or 2.06. For STE mode, use TOS 1.06, 1.62, 2.05 or 2.06. If you want to use the TT mode, you must specify a TOS 3.05 or 3.06 image here. And in Falcon mode, you have to use either TOS 4.00, 4.02, 4.04 or 4.92. However, you should always use TOS 4.04 for Falcon mode, it is the most common one. Also note that TOS 4.92 cannot be booted from a boot disk (like it is done on a real Falcon), you have to specify it directly in the TOS ROM setup dialog here.

Keep in mind that any custom cartridge image will not work together with GEMDOS HD emulation or the VDI extended resolution emulation since some additional driver code will be used in the cartridge memory space for these emulations.

The Joystick Dialog

Hatari's GUI - the joystick dialog

In this dialog, you can configure the emulated joysticks. With the upper two arrows, you can choose the joystick which you want to configure.

Joystick 1 is the normal ST joystick port and 99.9% of all ST games use this port. Joystick 0 emulates a joystick plugged into the ST mouse port and is often used in games for two players.

With STE joypad A and B, you can enable the emulation of Jaguar joypads which are plugged in the enhanced joystick ports of the Atari STE. Only very few STE games support these joypads, so you often won't need this (but some Falcon games require them).

Finally, Hatari also emulates joysticks which were plugged on the parallel port with a special adapter on a real ST. These were used in some few multi-player games like "Gauntlet 2".

For each ST joystick, choose whether you want to disable it, use the keyboard for emulation or use a real PC joystick.

For keyboard emulation, you can select the keys by pressing the Define keys button. You will be prompted to press the keys for up, down, left, right and fire.

If you want to use a real PC joystick for the emulation, you should connect it to your PC before you start Hatari. Then you can choose the joystick with the two lower arrows.

Check the "Enable autofire" option if you are too lazy to pound on the fire button in shoot'em-up games. However, this option only works with certain games. In some other games, it gets worse if you enable this option.

"Button 2" options select whether (real joystick) fire button 2 emulates joystick up or a SPACE key press. Unless game explicitly queries other joysticks, latter will work only for joystick 1 (queried also by TOS). If SPACE key gets stuck down (can happen if querying given joystick stops while button 2 is kept down), visiting Joystick setup dialog will fix that.

See Emulated joystick section for details.

The Atari Monitor Dialog

Hatari's GUI - the Atari monitor dialog

Here you control the video output of the emulated Atari.

You can select which sort of monitor to use. This option depends on the machine type which you have selected in the "System options" dialog. In ST and STE mode, you can choose between monochrome mode (select "Mono") and color mode (select one of the other monitor types). When you select "TV" and use zoomed low resolution or switch to ST medium, you get TV-like screen with half the intensity on every other line. VGA is not a valid setting in ST and STE mode, so if you select VGA for an ST/STE, Hatari will emulate an RGB monitor instead. Switching between mono and a color monitor acts like a monitor switch on a real ST - so beware, this will reboot your emulated system!

In TT mode, you can only choose between TT-high resolution ("Mono") and normal modes (select one of the other monitor types). Finally the Falcon mode supports all four types of monitors. Note that most Falcon demos/games require a RGB or TV mode, and do not work with VGA, although there are also few VGA-only games and demos.

"Show ST/STE borders" toggles the displaying of the borders around the ST / STE. Some demos and games use the screen borders for displaying additional graphics. As enabling this option increases CPU computing time, don't enable it if you have a very slow computer. Borders are shown also in Falcon emulation, but Videl emulation doesn't yet support palette effects. This option doesn't affect TT screen mode or extended VDI resolutions.

Extended VDI resolutions will emulate a sort of extended graphics card in the emulated machine, which gives you larger (2-16 color) resolutions for GEM. Select a resolution and color depth. Check to activate. This mode isn't affect by the other video options mentioned above. Uncheck to get back to a normal ST behaviour.

Note that there are several gotchas with extended VDI resolutions:

  • Only GEM conformant applications work with them, 99% of all games and demos don't.
  • GEM programs accessing screen directly (like NVDI) may crash with large enough screen sizes.
  • Memory reserved for (larger) extended resolutions breaks TOS v3 memory detection, so you need to interrupt boot up memory detection.
  • TOS v4 isn't compatible with them. In Falcon emulation you need to use EmuTOS with extended resolutions.

Because TT and Falcon support natively larger resolutions, VDI mode is most useful with ST / STE emulation.

The Hatari Screen Dialog

Hatari's GUI - the Hatari screen dialog

Here you control how the video output of the emulated Atari appears on your screen.

Check "Fullscreen" to run Hatari in fullscreen. By default Hatari runs in windowed mode.

The "Frame Skip" option can be used to speed up the emulator if it is running too slow on your system. Disable frame-skip if you have a fast computer. When selecting 1, 2 or 4, drawing of corresponding number of frames will be skipped after each frame actually shown by Hatari. Select "Auto" to let the emulator to decide whether, and how many frames will be skipped.
Note: The frameskip option also affects the frame rate of the screen animation recording!

Indicators that you can have on the Hatari window:

  • "Statusbar" at the bottom of the screen. The statusbar shows the floppy drive LEDs, the current frameskip value, the machine type including TOS version and memory size, and whether recording is currently active.
  • "Drive led" is a colored rectangle shown on top of the Hatari window contents. It will show any disk (floppy or hard drive) activity.
  • "None" turns both of above options off.

"Keep desktop resolution" option will use your desktop resolution for fullscreen to avoid issues related to resolution switching, especially on LCD monitors (they're slow). If this isn't enabled, values from the "Max zoomed win" option are used in selecting a suitable resolution.

"Max zoomed win" option controls up to which size Hatari tries to scale the Atari resolutions and how much of the borders (enabled in Atari Monitor dialog) will be shown, within Hatari SDL framebuffer. Note that there are several limitations in this and the "Keep desktop resolution" option, partly because Hatari has different implementations for different video modes:

  • VDI resolutions (selectable in Atari Monitor dialog) aren't scaled.
  • ST and STE video emulation supports only doubling of the ST-low resolution.
  • Hatari doesn't support downscaling. If the original Atari resolution is larger than the specified size (e.g. TT-high), the Hatari screen size will also be larger than requested. Hatari Falcon/TT window size will be limited to the Desktop size though.
  • TT and Falcon resolutions support only integer scaling ratios. If the scaling ratio cannot match the requested size exactly, Hatari will use a ratio that will produce smaller size closest to the requested one.

You should set these values to a size that suits best your monitor resolution (or video recording). It is intended to help in getting Hatari to best use your monitor space on a windowed mode and in fullscreen avoiding "fuzzy" scaling done either by SDL, or by your LCD monitor.

Command line "--zoom" option can be used to tell SDL how much it should scale the resulting Hatari window framebuffer, when presenting it on screen. Non-integer scaling values will cause "fuzzy" output.

Click the Screenshot button to create a screenshot in the selected format. PNG and BMP formats will match the visual output of Hatari. NEO and XIMG formats will produce Atari ST native images, but will not include borders, or raster palette effects. NEO is limited to the 3 standard ST resolutions.

Click the Directory: button to change default screenshot saving directory.

Click the Record AVI button to record an AVI format video of Hatari screen (and audio) output.

Selecting "Crop statusbar" option will leave statusbar out from the screenshots and recorded videos.

"SDL2" options impact following:

  • GPU scaling: Hatari window scaling is HW accelerated
  • Resizable: User can resize Hatari window
  • Vsync: Hatari window updates are limited to host monitor refresh rate. This avoids tearing, but will impact emulation speed, if host monitor refresh rate does not match Atari monitor refresh (VBL) rate!

The Keyboard Dialog

Hatari's GUI - the keyboard dialog

Here you can select the keyboard mapping to use. Two different mappings called "Symbolic" and "Scancode" are predefined.

"Symbolic" tries to map the symbolic values of your PC keys to the ST keys. It should be working pretty good on all systems as long as your keyboard layout looks close to the standard English keyboard layout. However, you might experience some problems with special keys like brackets etc.

"Scancode" uses the scancode values of your PC keys for keyboard mapping. If it works on your system, this often gives better results than the symbolic mapping. Note that you also need a TOS version with the right language (e.g. use a French TOS if you are using a French keyboard).

You can also additionally load a custom keyboard mapping file here if you wish. Click Browse to select a mapping file. Please have a look at the supplied example mapfile (keymap-sample.txt) to see how to create your own keyboard mapping. If you want to disable the mapping file again, press the Clear button.

The "Shortcuts" section can be used to configure the keyboard shortcuts that can be activated while the emulation is running. Hatari supports two sets of keyboard shortcuts: The first type is activated by pressing a modifier key (AltGr / right Alt by default, Cmd key on macOS) together with the key, and the second type is directly activated by pressing a single key, without additional modifier key. Use the arrow buttons to select the shortcut that you want to change, then press one of the Define buttons to change the key for the shortcut. You'll be prompted to press the key that should be used. If you reconsider and don't want to change the key, you can press the left mouse button instead. By pressing the right mouse button during the prompt, you can also erase the current shortcut setting.

The last setting in this dialog can be used to disable the key repetition in fast forward mode. When the emulator runs in fast forward mode, and you want to type text, it can be annoying that the emulated system detects multiple key events due to the key repetition of the emulated system. So this can be avoided by enabling this option.

The Sound Dialog

Hatari's GUI - the sound dialog

Here you can control the sound subsystem.

Check "Enabled" if you want emulated sound at all. Emulation is faster if sound emulation is turned off.

If you experiment latency issues with your OS audio's output, you can check the "Synchronize" option to adjust Hatari's video emulation to match your OS audio.

Nine frequencies from low to high quality are available. Experiment a little bit to find out which fits best for your setup. For most modern computers, 44100 Hz or 48000 Hz should be fine. For older or slower host systems, you should use a lower frequency. 12517, 250033 and 50066 Hz are frequencies supported by the STE/TT/Falcon sound DMA.

YM voices volume mixing "ST table" method uses a lookup table of audio output voltage values measured on STF, "Math model" uses a complex model to mix the 3 YM voices and "Linear" just averages the 3 YM voices. Use "ST table" or "Math model" for accurate sound's emulation.

You can select to record a piece of sound here. Use the Browse button to choose a file. The file name extension that you use (.WAV or .YM) determines in which format the sound is recorded in. The Record sound button is a toggle so you will need to return to the GUI to switch sound recording off again (or to use the keyboard shortcut for that).

The Devices Dialog

Hatari's GUI - the device dialog

Check the first checkbox to enable printer support. See the Emulated printer section for details.

As Hatari currently only supports printing to file, click on Browse to select the file to print to. You can enter a new filename as well.

Check the second checkbox to enable RS232 support. The RS232 device is configured according to the settings of the emulated MFP RS232 of the Atari ST/STE/TT. This means Hatari will automatically use baudrate and handshaking as configured for the emulated machine.

Click on Browse to select suitable device files for serial input and output. On Linux a good choice is /dev/ttyS0 or /dev/ttyS1.

Check the third checkbox to enable MIDI support.

MIDI support

Hatari MIDI support can be provided either with a PortMidi library (required on Mac / Windows), or using "raw" MIDI device files. Latter supports also MIDI networking in games, and can be used for debug output, while PortMidi handles only MIDI events, but makes it easier to connect Hatari to host MIDI programs (software synthetizers etc).

"raw" MIDI device file selection

Click on Browse to select a suitable MIDI device files for MIDI input and output.

midi-linux.txt file explains how to select the correct MIDI device file, how to set up software sound synthesizing on Linux (using ALSA and virtual MIDI devices) if your sound card/driver doesn't support MIDI, and how to set up MIDI networking between multiple Hatari instances, even over internet.

Hatari's GUI - PortMidi MIDI device selection
PortMidi MIDI device selection

Click on arrow buttons to select a suitable MIDI devices for MIDI input and output.

PortMidi lists only MIDI devices that were active when Hatari was started. To see ones added after that, Hatari needs to be restarted.

PS. For MIDI devices which names are not constant, but include e.g. instance identifier at end, Hatari supports matching device by first part of the name when there's no full match. To use this, remove the device identifier part from the end of the device name in saved Hatari configuration file. Note that using GUI Devices dialog afterwards, will override prefix in config with a full name of a device (that matched the prefix at that point).

Keyboard shortcuts for the SDL GUI

There are multiple ways to interact with the SDL GUI.

TAB and cursor keys change the focus between UI elements. Home key moves focus to the first dialog item, End key to the last one. Initially focus is on the default UI element, but focus changes are remembered between dialog invocations.

Enter and Space invoke the focused item, ESC key invokes the dialog cancel option (if there is one).

UI element which name has an underlined character can be invoked directly by pressing Alt + key with that character. Alt + arrow keys will act on dialog arrow buttons.

Main keyboard interactions:

  • Options GUI main view: Enter accepts configuration, ESC cancels it.
  • Options GUI dialogs: Enter (or End + Enter if focus was moved) returns back to main view.
  • Fileselector: Page up and down keys move the file list by one page, mouse wheel and Alt + cursor keys scroll it by one item. Enter on the focused file name selects it. Enter on the OK button accepts the selected file. ESC cancels the dialog/selection.
  • Alert dialogs: Enter accepts and ESC cancels the dialog.

Keyboard shortcuts during emulation

While the emulator is running, you can activate or toggle various features via Hatari keyboard shortcuts. Most of them require the AltGr (right Alt) modifier key. On Mac macOS, the Cmd key ⌘ is used instead. Below are listed the default shortcut key bindings:

Shortcut Purpose
AltGr+a record animation
AltGr+g grab a screenshot
AltGr+i boss key: leave full screen mode, pause Hatari and iconify its window
AltGr+m (un-)lock the mouse into the window
AltGr+r (warm) reset the ST
AltGr+c cold reset the ST (same as the original power switch)
AltGr+d open dialog to select/change disk A
AltGr+s enable/disable sound
AltGr+q quit the emulator
AltGr+x toggle normal speed/fast forward
AltGr+y enable/disable sound recording
AltGr+k save memory snapshot
AltGr+l load memory snapshot
AltGr+j toggle joystick emulation via cursor keys on/off between ports 0 and 1
AltGr+F1 switch joystick type on joy port 0
AltGr+F2 switch joystick type on joy port 1
AltGr+F3 switch joystick type for joypad A
AltGr+F4 switch joystick type for joypad B
AltGr+b toggle borders on/off
AltGr+f or F11 toggle between fullscreen and windowed mode
AltGr+o or F12 activate the options GUI
Pause pause emulation
AltGr+Pause invoke the internal Hatari debugger

You can change the key bindings from the Hatari configuration file. See keymap-sample.txt file for instructions.

Emulated Atari ST keyboard

All other keys on the keyboard act as the normal Atari ST keys so pressing SPACE on your PC will result in an emulated press of the SPACE key on the ST. The following keys have special meanings:

Key Meaning
Alt will act as the ST's ALTERNATE key
left CTRL will act as the ST's CONTROL key
Print Screen will emulate the ST's HELP key
Scroll Lock will emulate the ST's UNDO key
Page Up will emulate the ST's ( key in the keypad
Page Down will emulate the ST's ) in the keypad
Num Lock Toggle cursor emulation mode in the keypad (see below)

You can use the Num Lock key to toggle between normal Atari ST keypad mode and a special cursor key mode. The cursor key mode is designed for Atari programs and games that expect the original Atari cursor key layout (like the game "Dungeon Master"), i.e. where the "Insert" and "Clr-Home" keys are right next to the cursor keys. The 7 on the keypad is then mapped to the "Insert" Atari key, and the 9 key on the keypad is mapped to the "Clr-Home" Atari key. The keypad numbers 8, 4, 5 and 6 are mapped to the ↑, ←, ↓ and → cursor keys on the Atari side respectively.

If joystick emulation via keyboard is enabled, by default cursor keys are used for the directions and right CTRL key as the fire button. Otherwise they act as corresponding keys of the emulated Atari ST.

NOTE: Problems with simultaneous keypresses most likely are not an issue in Hatari, but with the keyboard itself. Many modern keyboards report/support only three simultaneous key presses (or even just two depending on which keys are in question). Expensive gaming keyboards support more.

Emulated mouse

For obvious reasons your PC mouse will act as the emulated Atari ST mouse. In fullscreen mode it will act as expected, directly controlling the ST mouse pointer.

However it is a little bit different in windowed mode as mouse cursor positions between host and emulated Atari can get out of sync. This can be worked around by constraining the mouse to the Hatari window. Pressing the AltGr+m hotkey combination or starting Hatari with the --grab command line option grabs the mouse i.e. locks its movements to the Hatari window. Press the shortcut key (again) to go back to normal mouse behaviour which allows you to move mouse outside the Hatari window while Hatari is up and running. Note: pausing the emulation will also (temporarily) release the mouse grab.

Middle button click emulates double click, which is very useful in Fast Forward mode (where normal double clicking is nearly impossible).

Mouse scrollwheel will act as cursor up and down keys.

Emulated joystick

The Atari ST joysticks are emulated of course allowing you to play your favourite games with Hatari.

The default mode is to use a connected PC joystick. You can use any joystick that is supported by your kernel / SDL library. If your joystick works with other applications, it will likely work with Hatari as well. Make sure it is calibrated and then off you go. Move the stick to point into the desired direction. Please note that Hatari will not detect analogue movement as the Atari ST only had digital joysticks.

First firebutton will act as the normal firebutton on the Atari ST. Second firebutton will emulate either joystick up (jump), or a keypress of the SPACE key on the ST. Many ST games utilize the SPACE bar for secondary game functions (for example Xenon), and jump functionality can be useful for platformers. Third firebutton has always autofire enabled, regardless of the (first firebutton) autofire option.

If you do not have a PC joystick or joypad, then you do not need to desperate. You can emulate one of the two Atari ST joysticks via the cursor keys. Just activate it in the GUI. Then the cursor keys will act as the joystick directions, the right CTRL key will act as the firebutton. You can still use the cursor keys as the ST's cursorkeys in this mode as long as you press SHIFT along with the cursorkeys. You can also configure these keys from the joystick options.

Emulated video

Hatari emulates all screen modes of the original machine.

ST/STE shifter overscan effects are emulated, but due to the fact that these effects are achieved by using quirks and glitches in the original chips to do things beyond their specification, emulation is a bit tricky for these effects. As a result, some demos using these techniques might not be displayed correctly in Hatari, known ones are listed in the compatibility.html file.

Beside that you can setup extended VDI mode. It works well only with GEM-compliant applications, but allows using larger screen resolutions than the standard ST ones. VDI mode is not accelerated, so better optimized VDI implementation (e.g. NVDI) is recommended, especially for larger screen sizes.

Make sure to disable extended VDI mode for (non-GEM) games as 99% of all ST games will not be able to make use of higher resolutions.

Emulated printer

Due to the fact that printer handling is different on Atari and current machines, emulation of the printer is achieved by writing all printer output to a file.

The file will contain a sequence of data, the same that would appear on the data pins of the Atari ST printer port. That would include control characters and commands for graphic printing. Clicking "Print desktop" on the GEM desktop would result in a messy data dump in the printer output.

Printer emulation works best for plain text files or programs that do not format the output for a specific printer. The file contents can be used with your favourite text editor for further processing and printing to a real printer.

To get real direct printing out of Hatari you may set up a suitable (e.g. PostScript) GDOS or NVDI printer driver on the emulated Atari and set your printer device file as Hatari's printer output.
NOTE: If the driver doesn't match or there's some other problem, this can cause your printer to print out hundreds of pages of garbage.

Emulated RS232

Serial communications in Hatari is designed to directly use a serial port on your PC.

Communications parameters are set automatically upon the settings of the emulated machine. This means all you do is to set the communication parameters like baudrate from the emulated communications software. Hatari will do the rest and handle the serial input and output for you.

Note that the “normal” RS232 port of Hatari is the one that is connected to the MFP chip of the selected system. This port is only wired on the ST, STE and TT machines, but not on the Falcon. To use serial port emulation in Falcon mode, you have to use the “SCC channel B” emulation instead.

There're several programs that can be used with RS232 and the various ports

"CoNnect 95" is recommended as it supports all the ports of each machine (ST/STE, MegaSTE, TT and Falcon). Although this program was commercial and needed to be registered, his author released it later as freeware in 1998

The following table shows all the available ports (serial or lan) depending on the machine type, as well as the corresponding options used to setup each port

Port Name HW Physical Port Corresponding Options Notes
ST / STE / MegaST
Serial MFP RS232C DB-25 --rs232-in and --rs232-out
MegaSTE
Modem 1 MFP RS232C DB-25 --rs232-in and --rs232-out
Serial 2 SCC A RS232C DP-9P --scc-a-in and --scc-a-out Using daughter board in the VME slot
Lan SCC A RS422 MiniDIN 8p --scc-a-lan-in and --scc-a-lan-out
Modem 2 SCC B RS232C DP-9P --scc-b-in and --scc-b-out
TT
Modem 1 MFP RS232C DB-25 --rs232-in and --rs232-out
Serial 1 TT MFP RS232C DP-9P Only 3 wires, not really usable (not emulated), using daughter board in the VME slot
Serial 2 SCC A RS232C DP-9P --scc-a-in and --scc-a-out Using daughter board in the VME slot
Lan SCC A RS422 MiniDIN 8p --scc-a-lan-in and --scc-a-lan-out
Modem 2 SCC B RS232C DP-9P --scc-b-in and --scc-b-out
Falcon
Lan SCC A RS422 MiniDIN 8p --scc-a-lan-in and --scc-a-lan-out
Modem SCC B RS232C DP-9P --scc-b-in and --scc-b-out
Port Name HW Physical Port Corresponding Options Notes

Floppy disk images

Hatari does not use floppy disks directly but disk images due to differences between the floppy disk controllers of the ST and the PC. Several types of disk images are currently supported :

  • the raw "ST" type
  • the similar "DIM" type (not widely used)
  • the compressed "MSA" (Magic-Shadow-Archiver) type
  • the "STX" type that can store low level disk layout. This format is mainly used to dump original games with their protection. Those images are created on a real ST using pasti.prg
  • the "IPF", "RAW" and "CTR" types require the caps library. Similar to STX, they record disk layout, but at a much precise level by storing MFM data. Most of these dumps are made with the Kryoflux board

The raw type (file suffix should be "*.st") is simply a sector by sector image of a real floppy disk. You can easily create such an image with the dd program which should normally be pre-installed on every Unix-like system. Simply type something like dd if=/dev/fd0 of=myimage.st to create a disk image. Of course you need access to /dev/fd0, and depending on your system and the type of floppy disk you might have to use another device name here (for example I use /dev/fd0u720 for 720kB disks). However, if the disk is copy-protected or doesn't use a MSDOS compatible file system, this might fail. So be very careful if you are not sure about the disk format.

The other possibility is to image the disk on a real Atari ST. For non-protected disk, there are programs like the Magic Shadow Archiver for this task. Hatari supports this slightly compressed MSA disk images, too. Note that Hatari only supports the "old" MSA format, there are some Magic Shadow Archiver clones (like Jay-MSA) that create better compressed but Hatari-incompatible disk images. However, if you have got such a MSA disk and want to use it with Hatari, you can still run the corresponding MSA program within Hatari to extract the incompatible disk image to a normal floppy disk image.

For protected disk, the most widely used method is to run pasti.prg on a real Atari ST and get a .STX image.
For more complex protections or altered disk, one can use *.IPF or *.CTR which include tools to check MFM data and possible problems when dumping a disk.

While *.ST, *.MSA and *.STX are more or less the "standard" types of Atari disk images, you might sometimes also find STT or ADF images on the internet. These currently do not work with Hatari.

Hatari can now also utilize *.DIM images just as *.ST ones without any problems. Note that DIM images are nearly the same as the raw ST images (they only have an additional 32 bytes header), so you can easily transform the DIM images into ST images by stripping the header from the files. For example try something like: dd if=input.dim of=output.st bs=32 skip=1

If you have got a disk image that has been created with the old ST emulator PaCifiST (for DOS) or with early versions of the program Makedisk, and the disk image does not work with Hatari, then the disk probably suffers from the "PaCifiST bootsector bug" (Hatari will display a warning message then). In this case, the bootsector of the disk contains some illegal data, so that the disk even does not work on a real ST any more. However, if it is a .ST and not a .MSA disk, you can easily fix it by using a hex-editor to change the byte at offset $D (13) from 0 to 1 (don't forget to backup your disk image first, since you can also easily destroy your disk image when changing a wrong byte there). If the disk contains a bootsector program, you probably have to adjust the boot sector check sum, too (it can be found at offset $1FE + $1FF).

Hatari supports disk images that are compressed with (Pk-)ZIP (file suffix must be ".zip") or GZip (file suffix must be ".st.gz" or ".msa.gz"), so you can archive your disk images into zip archives. You can also directly run the zip archives you may download from the net as long as the archive contains a disk image in .ST or .MSA format.

Note: Hatari does not save disk images back to *.ZIP files so your highscores and savegames are lost if you load the game from such a zipped disk image.

Floppy formatting

Low level floppy formatting uses write track FDC command. Because simpler floppy image formats like ST / MSA don't have such low level information, they can't be low level formatted. For empty ST floppy images, one can use "create blank disk" in Hatari options "Floppy disks" dialog (see "The Floppy Disks Dialog" section).

Hatari supports low level track writes (and formatting) only for the STX format. Hatari implements that by doing all track writes to a separate *.wd1772 overlay file. To test it, copy some .STX file for example to "empty.stx". Format it from desktop, or use a separate formatting program like Fastcopy. This should create an additional "empty.wd1772" file.

(Note: IPF format itself is complete enough, but capslibrary doesn't yet have the required write support.)

Hard disk support

Hatari supports three ways of emulating Atari hard drives: The low-level ACSI and IDE hard drive emulation and a GEMDOS based HD emulation. In most cases the GEMDOS HD emulation is best as it allows exchanging files easily between the emulated and the host environment.

Please note that changing the HD-image or the GEMDOS HD-folder will reset the emulated Atari since it is not possible to switch the hard drive while the emulator is running.

On a 32-bit host system, the size of a hard disk image is limited to 2 GB. On 64-bit host systems, bigger images might be possible but the support for bigger images is not tested very well yet.

The maximum size of partitions inside the hard disk (images) depends on the TOS version. TOS 1.00 and 1.02 support up to 256 MiB, TOS 1.04 to 3.06 up to 512 MiB and TOS 4.0x supports up to 1 GB partitions.

NOTE: you need to be careful when mounting device files. Depending on the system setup (e.g. udev settings) partitions on memory cards etc. can be mounted automatically. When Hatari is started and uses a device file with partitions that are already mounted, data can be destroyed (when several programs independently write to the same device). Disable your desktop automount, or remember to manually unmount devices before giving them to Hatari.

GEMDOS based hard drive emulation

With GEMDOS HD emulation, you can easily "mount" a folder from the host file system to a drive of the emulated Atari.

If you provide Hatari a directory containing only single letter (C-Z) subdirectories, each of these subdirectories will be treated as a separate partition, otherwise the given directory itself will be assigned to drive "C:". In the multiple partition case, the letters used as the subdirectory names will determine to which drives/partitions they're assigned. For example following directory setup:

partitions/
  + C/
  + D/

That is given to Hatari as "hatari -d partitions", will give you GEMDOS HD emulated C: and D: drives.

GEMDOS HD emulation is an easy way to share files between the host system and the emulated Atari, but there are also several limitations:

  • Directory entries are returned in a (case-insensitively) sorted order, for consistency. E.g. moving files to a different directory and back (without changing their names) like AUTOSORT does, doesn't change that order. You need to rename the files.
  • Names which aren't valid TOS directory or file names, are converted to a valid format. If there are multiple files which converted names are identical in TOS-format, you see only one of those.
  • Host file paths need to be shorter than 256 characters currently (like Atari side TOS file paths).
  • It is not possible to use a cartridge image at the same time with GEMDOS HD emulation (Hatari has its own cartridge code that is used for GEMDOS HD emulation).
  • Anything that installs its own GEMDOS handler, like MiNT, does not work with GEMDOS HD emulation. Such things need to be run from a real hard disk image, or in case of MiNT, use OLDTOSFS for GEMDOS HD access.
  • GEMDOS HD file handles start from index 64. If programs have more files open on non-GEMDOS HD drives, i.e. their indexes increase over 64, accesses to those indexes could get routed (incorrectly) to GEMDOS HD drive instead
  • GEMDOS HD supports up to 64 file handles (TOS supports slightly more).
  • GEMDOS HD drive assignment can conflict with the ACSI and IDE hard drives. If you want to use GEMDOS HD directory and ACSI/IDE disk images together, either use the GEMDOS HD option for skipping ACSI & IDE partitions, or use a multiple partition GEMDOS HD emulation setup and select the partition subdirectory names (see above) so that they do not conflict with the drive letters for ACSI/IDE partitions. With HD Driver you have also another option, see Using HD Driver with GEMDOS HD partitions.
  • The GEMDOS HD emulation does not work (very well) with TOS 1.00 and 1.02. Use at least TOS 1.04 if you want the GEMDOS HD emulation to work properly.
  • Ddelete() directory removal works only for directories which have no files.
  • Fforce() file handle redirection works only for standard handles used with GEMDOS file functions, such as Fwrite() to standard output that is redirected to a file. File redirection is NOT supported e.g. for GEMDOS Ccon* console functions.
  • Only FsFirst() / FsNext() calls support drive volume labels, other GEMDOS calls return errors when volume label attribute is specified
  • Host and emulation have their own clocks, which will drift apart after emulation is started, especially when emulation is fast-forwarded or paused. Because of this, anything inside emulation that relies on file timestamps (e.g. build utilities) may not work as expected if you modify the files on the host after emulation is already running.

If your programs complain that they could not find/read/write files on the GEMDOS emulated drive, you can copy and use them from a floppy disk image or a real hard disk image instead.

ACSI & IDE hard drive emulation with EmuTOS

Accessing HD image files is easiest with EmuTOS. It supports both ASCI and IDE interfaces, regardless of emulated machine type, and understands DOS partition tables without additional drivers. atari-hd-image.sh script coming with Hatari can be used to create such image files and to copy initial data to them.

If you have an hard drive (image) with Atari format partition table, that should already have hard disk driver on it and work fine. Only partitioning/formatting them is the problem. Creating such images from scratch is described in following sections.

Note that while EmuTOS supports Atari format partition tables, and could access also hard disks with them, it doesn't run hard disk driver installed to the drive. For such drives, it may be better to use a suitable Atari TOS version.

ACSI hard drive emulation

To use the ACSI hard drive emulation, you need a hard disk image file with a pre-installed HD driver in it. You can try to get an image of your old ST hard disk or grab one from the internet (e.g. from the Hatari website). Please note that the size of ACSI hard drive is normally limited to 1 GB due to some addressing constraints of the ACSI bus. Bigger disks were only possible with certain host adapters – this behaviour is emulated by Hatari, too, but you need a hard disk driver that supports these extensions.

To create a new ACSI hard disk image, you can start with an empty image that you have created for example with the following command: dd if=/dev/zero of=hd.img bs=512 count=xxx (where 'xxx' is size in 512 byte blocks). Copy the complete AHDI 5.0 package to a floppy disk image, then boot Hatari with this floppy disk image and the fresh hard disk image like this: --acsi hd.img ahdi.st. Then start HDX.PRG from the floppy disk and format + partition the hard disk image with it.

Formatting and partitioning works currently only with AHDI 5, but you can install the AHDI 6 driver to the hard disk after it is formatted. Restart the emulated system, run AHDI.PRG from the floppy disk to access the hard disk image from the emulated Atari and then run HINSTALL.PRG. After installing the hard disk driver to the fresh HD image with HINSTALL.PRG, you can boot directly from the hard disk image.

HD Driver (v9) partitioning is also compatible with Hatari ACSI emulation. CBHD and ICDPro AdSCSI drivers work on images which have been partitioned elsewhere.

IDE hard drive emulation

As the IDE disk format (little endian) differs from the ACSI disk format (big endian), you need separate disk images for them. Hatari doesn't currently support partitioning IDE disks with AHDI, but you can do it with Cécile.

First create an empty image file with the size of your choice with: dd if=/dev/zero of=hd.img bs=1k count=xxx. Then get the Cécile hard disk driver from http://centek.free.fr/atari/softs/s_cecile.htm and put it on a floppy disk image (e.g. to one named "cecile.st" using: zip2st.sh cecile.zip).

Run Hatari with hatari --machine falcon --tos tos404.rom --ide-master hd.img cecile.st, switch to larger color resolution and warm up your French language skills. Then start the Cécile hard disk driver CECILE.PRG and run CC_TOOLS.APP to partition your hard disk image. Click the "Partition" button, select "Hatari IDE disk" and set suitable partition size with the arrows (below type field). Then click "Valider".

If you only want to use your HD image in Falcon mode, you can install the Cécile hard disk driver to the image from the Cécile CC_TOOLS.APP: Click the "Installer" button and save the Cécile driver to the 1st partition on "Hatari IDE disk". If you want to also use your HD image in ST/STE mode, you need to get and install either HD Driver or AHDI 6 driver on it instead (see ASCI hard drive emulation section).

Then you can boot from your hard disk image by simply specifying it with the --ide-master parameter.

Moving files to/from hard disk images

Moving files to and from Atari hard disk images can be done either through GEMDOS HD partitions (host directories mounted inside Hatari emulation) or accessing the images directly on the host (outside the emulation). Both have their own limitations.

If it is fine for the IDE/ACSI partitions to be first, you can either use ACSI/IDE partition skip option, or a multipartition GEMDOS HD setup as described in above sections.

If you want to boot from a GEMDOS HD partition i.e. such to be before hard disk image partitions, and still to be able to access all the IDE/ACSI partitions, you need to use HD Driver. Note: this is the preferred method with EmuTOS (v0.9.x), because it doesn't run/use driver installed to the IDE/ACSI image directly and has some limitations in its partition table/type support.

Using HD Driver with GEMDOS partitions

Uwe Seimet's HD Driver works fine with both the Hatari GEMDOS HD partitions and normal hard disk images.

First copy the HDDRIVER.PRG binary into your GEMDOS HD emulation directory AUTO folder. Then start the HDDRUTIL.APP configuration utility, locate HDDRIVER.PRG, open the "Devices and Partitions" dialog and select the "Preserve Existing Partitions" option. Then you can just start Hatari with your hard disk image and this GEMDOS HD directory, for example like this: "hatari --harddrive gemdos-hd/ --ide-master ide-hd.image".

If you're using the demo version of HD Driver, you can write files only to the C: partition, i.e. in above case only copy files from the hard disk image partition to the GEMDOS HD partition (with some write slowdowns included into the demo version). If you want to copy files to the hard disk image with the demo version of the HD Driver, you need to set the hard disk image as drive C:.

To accomplish this, set the GEMDOS HD partitions to be from D: forward, i.e. have a directory which contains only single letter subdirectories, starting from "D" like in "mkdir gemdos-hd; mkdir gemdos-hd/D". Then give Hatari (as the last parameter) a boot floppy image containing the demo version of HDDRIVER.PRG in its AUTO folder, like this: "hatari --ide-master ide-hd.image --harddrive gemdos-hd/ hd-driver-floppy.st". You can convert HD Driver ZIP package to floppy image with the zip2st utility.

Accessing HDD image partitions outside of Hatari

If you want to access the hard disk image partitions also outside the emulation, the disk image needs to have a DOS partition table. The atari-hd-image script included with Hatari can be used to create such an image.

Inside the Hatari emulator, EmuTOS can access partition(s) on these kind of images directly without any driver software. Of the Atari HD drivers mentioned above, Centek's Cécile and Uwe Seimet's HD Driver (demo) work fine with these partitions. E.g. AHDI and CBHD don't. Cécile works only with TT or Falcon.

To summarise; if EmuTOS is enough, use that. Otherwise, if you want to use TT or Falcon emulation, use Cécile (or full HD Driver version if you have it), otherwise use HD Driver (demo).

To access the content of the partitions on Linux host, there are two possibilities:

Using Mtools

For this you need to add an entry for the hard disk image to your ~/.mtoolsrc and specify which partition you want to access from the image. For an image created with the above mentioned script, the line in the configuration file should look something like this:

MTOOLS_NO_VFAT=1
drive c: file="/home/user/hatari/hd.img" partition=1

Note that Mtools is instructed to use FAT compatibility mode because EmuTOS cannot deal properly with VFAT file information. If you don't want this setting for all your Mtools drives, you can set it also via the environment like this ("::" refers to the drive image given with the "-i" option):

MTOOLS_NO_VFAT=1 mcopy -spmv -i hd.img files/* ::

Using a loopback device

This is recommended even by Mtools documentation, but it is less convenient as it requires root rights. First you need to "loop" mount the image:

$ su
# image="hd.img"; mountdir="hd"
# start=$(parted $image unit s print | awk '/ 1 /{print $2}' | tr -d s)
# losetup -f $image -o $((512*$start))
# loop=$(losetup -a | grep $image | cut -d: -f1)
# mkdir -p $mountdir
# mount -t msdos $loop $mountdir

This uses parted to find out the first partition offset in sectors and then tells losetup to bind the first free loop device to a corresponding offset from the hd.img image. mount is then used to mount the file system from the loop device on top of the "hd" directory.

After you have copied the relevant files to the "hd" directory, you need to unmount the file system and remove the loop device binding before using the disk image from Hatari:

# umount $mountdir
# losetup -d $loop

Performance

Hatari performance varies between Atari programs, depending on what features Hatari needs to emulate for them. Less accurate Atari emulators may be faster as emulation accuracy has a performance overhead.

The operating system and libraries below Hatari can also sometimes have a noticeable effect on performance.

Hatari can be sped up considerably by giving up some emulation or emulator accuracy. With ST/STe emulation, these options should be needed only on slow devices, typically ARM and/or mobile ones (e.g. Raspberry Pi).

Operating system components performance

Finding out whether there's a performance problem with the system components (like SDL) in your setup, requires profiling Hatari and rest of the system. How to do that is OS specific. On Linux that would involve running "perf record -a" command (as root) on the background for few minutes while Hatari is running, and then investigating the results with "perf report" command.

Some other process eating CPU cycles from Hatari one can see just with the (Linux/Unix) "top" command.

Build options impact on performance

Compiler: Unless you have disabled compiler optimizations (like GCC's -O2 or -O3 options) in the Hatari build, the extra optimization flags (like GCC's "-mtune=i686") don't seem to have very large effect on Hatari performance. Using GCC -O3 option instead of -O2 can give minor (5-10%) performance improvements for things (mainly demos) that use very heavily interrupts.

Older versions: If nothing else helps, try (building) much earlier Hatari version. More accurate emulation in newer Hatari versions means that they can be slower despite optimizations.

Run-time emulation options

Emulation options have the largest impact on Hatari performance. These options can be changed from the Hatari GUI System and CPU dialogs and the emulation needs to be rebooted for any of these changes to take an effect! They are enabled by default.

DSP (Falcon)

Emulating the Falcon DSP is performance-wise several times more demanding than emulating the m68k; DSP runs at higher frequency, executes many instructions for each m68k instruction and emulation isn't as mature and optimized. Unless some Falcon program needs DSP, none or dummy DSP emulation mode could be used. Even of the programs that do use DSP, many use it only for background music and work fine without the real DSP emulation.

CPU: data cache emulation (030/TT/Falcon)

CPU core supports >=030 data cache emulation for better m68k performance and cycle accuracy. This is very heavy. Unless program needs cycle accuracy to work correctly, you can disable it. Many Falcon demos need it, applications and games normally don't.

CPU: cycle exact

CPU core cycle supports CPU instruction cache emulation for cycle accuracy. This is heavy for >= 020 m68k CPUs. Unless program needs cycle accuracy to work correctly, you can disable it. Many Falcon demos need it, applications and games normally don't.

Timer-D

With ST/STe, the single largest factor contributing to general Hatari emulation performance is the handling of interrupts. Enabling Timer-D patching option greatly improves Hatari ST/STE emulation performance as it significantly reduces the number of interrupts generated by the emulated Atari machine. Using this has adverse effect only for very rare programs.

FDC

While accurate FDC emulation doesn't take that much CPU, it slows down floppy image accesses (and Hatari startup) a lot. Only very few demos and games require accurate FDC emulation for their copy protection, so enabling fast floppy access is fairly safe.

CPU: prefetch

After the DSP, cycle accuracy and interrupts, m68k emulation takes most time. Disabling the "Prefetch" / "Compatible" option can speed up the emulation noticeably, but it will be less accurate. This can be fine for many games and other programs, but won't work e.g. for demos using overscan or rasters. This is recommended only as a last resort.

Roughly speaking, for Falcon DSP emulation with cycle exact 030 cache emulation, one needs at least 3Ghz machine. For normal (unpatched) Timer-D frequency on some specific cases (like demos with overscan 512 color animations) one may need over 1GHz machine for ST/STE emulation, but some rare demos may require over 1GHz machine even with Timer-D patching.

NOTE: Above options may cause some programs to work incorrectly. The Hatari Software Compatibility List lists programs known to need real Falcon DSP emulation, Timer-D frequency or accurate FDC timings.

Emulator options

Emulator options don't usually have as large effect on performance as emulation options, but they don't affect the emulated programs at all, just the quality of the emulation "output". These options can also be toggled at run-time without rebooting the emulation.

Sound

Internal Hatari sound handling and the SDL_mixer sound thread libALSA sound processing can account up to 1/3 of the Hatari CPU usage in normal ST/STE emulation. Disabling sound will get rid of that. Using low sound frequency or one matching your sound card may also help. Best is if you disable also background music from the programs you run in Hatari as this can significantly reduce the number of generated interrupts.

If program supports both ST and STE, use STE. Emulating DMA sound, is more lightweight than interrupt heavy ST sound.

If problem is occasional performance related audio glitches, increasing the sound buffer size (with "--sound-buffer-size" option) may help, but it increases sound latency.

Frame skipping

Screen rendering can take noticeable amount of CPU time. The default Hatari "auto" frame skipping should be used unless there's a good reason not to. It will skip converting and showing some of the frames if there's not enough time for them.

Also, if your monitor refresh frequency is lower than the selected Hatari monitor frequency (e.g. LCD monitors usually use 60Hz whereas Atari monochrome monitor uses 71Hz), you should use frameskip of one. The reason is that if your SDL library uses VSync to synchronize the output to screen, with zero frame skip that forces the emulation to run slower than a real Atari. If SDL doesn't use VSync, Hatari does redundant work to convert frames you can't see.

Zooming

If you are not using frame skip, disabling zooming can have noticeable improvement on performance. You can do this by specifying suitably low "Max zoomed" resolution (--zoom 1 command line option sets it to 320x200 for ST-low resolution). If you still want to have a nice fullscreen mode, you should rather add the right resolution mode-lines (e.g. "320x200") to your xorg.conf file. If you still want to use zooming, disabling borders may help a bit.

Spec512 color handling

Handling Spec512 color modes which change the ST/e palette constantly takes some extra CPU. If you have problems with CPU usage in such screens and you care more e.g. from the sound quality than visuals, you can either increase the threshold or disable the Spec512 mode handling completely by zeroing the threshold for that with the --spec512 0 option.

Statusbar and drive LED

If your version of the SDL library uses VSync to synchronize the screen output, drawing of the statusbar or the drive LED may have some minor impact on performance too. Normally they shouldn't.

Measuring the performance

There are a couple of ways to monitor and measure Hatari performance.

By default Hatari has Statusbar visible and automatic frameskip enabled. When Hatari has enough time that it can sleep a little each frame, the statusbar frame skip ("FS") value keeps at zero. If Hatari is completely busy, it will increase to the maximum specified (automatic) frame skip value.

Hatari has also a facility to measure FPS i.e. Frames Per Second. Enable frame skipping with --fast-forward yes option (or use the corresponding keyboard shortcut) and set --log-level info. Then after a while, press the "Pause" key. Whenever Hatari emulation is paused, Hatari will output on console info on how many VBLs it could show per second, along with some other numbers.

It depends on what you want to measure, but usually it is best to disable sound and set high frame skip like --sound off --frameskips 60 so that the associated external overheads are minimized. E.g. video output can on some platforms do VSync and measurements would then show your monitor refresh frequency instead of the actual Hatari performance.

On Unix systems with times() function call, only the time spent by the Hatari process itself is measured. On other systems, much less accurate SDL "wall clock" timings are used. To make latter more accurate you could use also --run-vbls option to specify how many VBLs Hatari should run before it exits. In this case it is best to either have the test-case run automatically from the AUTO-folder or given as memory snapshot to Hatari with the frame skip set equal to the VBL count.

Note that these numbers can fluctuate quite a bit, especially when the SDL timings are used, so for (statistically) reliable numbers you may need to repeat the measurement several times. You should of course make also sure that the system doesn't have any other activity at the same time you are making the measurements.

Appendix

Copying

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, see:

https://www.gnu.org/licenses/

Introduction to Emulation

Emulation via software is an art and Hatari is an example of this.

Emulation is to make a computer behave like a (probably) completely different machine on the lowest possible level. This includes CPU and custom chip emulation allowing software written for the emulated machine to be run without it noticing a difference.

The key to emulation is to simply do those things with a software program, the emulator, that normally chips would perform. So you have an CPU emulator that basically consists of a large loop that does exactly what the real thing would do:

  • fetch an instruction from virtual memory
  • interpret this instruction
  • fetch operands from the emulated registers and memory
  • perform the operation like addition or changing the program counter on a jump instruction
  • writes results back into the intended registers or memory locations
  • increment of the program counter and loop

The typical von-Neumann CPU can be emulated very fast, stable and error-free using such a simple loop system.

But in most cases the CPU emulation is the simplest part. Correct emulation of the various custom chips and hardware parts of the emulated system, and their proper synchronization, is much trickier.


hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/memory-usage.txt000066400000000000000000000131731504763705000244660ustar00rootroot00000000000000 HATARI MEMORY USAGE Here are some stats on Hatari v1.2+ memory usage (on Linux) and what could be done to decrease it. First the binary size from "size ./hatari": text data bss dec 1489120 12828 18799688 20301636 I.e. the Hatari binary size is 1.4MB, it has 12KB of non-const/pre-defined arrays and 18MB of uninitialized (at build-time) fixed size arrays. The names of latter are listed below. To decrease the binary size slightly, disable DSP from src/Makefile, don't enabled tracing in config.h (latter may have trivial improvement on speed too). You may also try the gcc "-Os" or "-O3 -finline-limit=..." options, their effect depends on the architecture for which Hatari is being compiled. To see the objects in the Hatari 18MB BSS section, get the datadump script from here: http://live.gnome.org/MemoryReduction_2fTools Compile Hatari without stripping, and use: datadump.py -n -s .bss -r ./hatari As a result you see these array variables: 16777216 STRam hatari 404400 dsp_core hatari 324048 CyclePalettes hatari 262144 mem_banks hatari 262144 cpufunctbl hatari 176612 ConfigureParams hatari 131072 pInterceptWriteTable hatari 131072 pInterceptReadTable hatari 69632 InternalDTAs hatari 65536 ymout5_u16 hatari 32768 MixBuffer hatari 32768 DspOutBuffer hatari ... These empty arrays aren't an issue unless Hatari actually writes to them, but that will happen as Hatari uses them. Here are some ways to minimize dirtying of the related memory i.e. use of the arrays in Hatari: * Enabling only required amount of memory for the emulation. Hatari doesn't dirty (zero) all of STRam, just the part of the ST ram that user has configured (accessible as ST ram, 0.5-14MB) and 2MB at the top (used as IO-memory, TOS and cartridge memory). * Disabling DSP from build gets rid of dsp_core * Modifying Video_ClearOnVBL() and Video_ColorReg_WriteWord() to call Spec512 functions only when nSpec512Threshold configuration value is non-zero and run Hatari with spec512 support disabled. This gets rid of CyclePalettes dirtying * ConfigureParams size can be decreased 22*4KB by setting MAX_HARDDRIVES in configuration.h to one (or by removing memset from Configuration_SetDefault() as static variables in .bss are zeroed already by kernel, memset()ting them just makes them dirty and Configuration_SetDefault() is called only at Hatari startup). When I profiled Hatari with Valgrind (valgrind.kde.org) Massif plugin, it tells that Hatari does about 3MB of memory worth of dynamic allocations. The allocations and ways to make them smaller are: * In includes/vdi.h decrease MAX_VDI_WIDTH and MAX_VDI_HEIGHT to 640 and 400. As Hatari allocates 2*2 framebuffers (of size width*height/8) for page flipping in Screen_Init(), decreasing their size can have have a large effect. * Do not load bigfont in gui-sdl/sdlgui.c, especially if the device screen is smaller than VGA. Both fonts together take about 1/3 MB. * Change uncompressed file read to use mmap() instead, currently memory is allocated for the whole disk image before reading it. - With normal DD floppy image Hatari would use that amount which might be acceptable, but with e.g. 2MB disk needed for running Wolf3D v0.8, mmap() sounds much better - For compressed disk images memory needs to be allocated for uncompressed image data, i.e. there we cannot save memory. * Check whether the m86k instruction table could be made smaller: #include "cpu/readcpu.h" printf("%d -> %d\n", sizeof(struct instr), sizeof(struct instr) * 65536); On x86 it's slightly over 1MB. You can also Massif Hatari yourself, its allocation calltrees are very short & the allocations are easy to find in the Hatari source code. From /proc/sysvipc/shm one can see how much shared memory Hatari/libSDL has allocated and that it shares it with the X server: - >100KB in lowrez - ~200KB in lowrez with borders - ~500KB in monochrome or zoomed lowrez - >800KB in zoomed lowrez with borders I don't think these could be made smaller from the code. Besides, user can just use a the smaller Hatari screen mode in fullscreen and let the display scale it to fullscreen. According to Xrestop, Hatari doesn't keep any Pixmap resources at the X server side. Finally when looking at the Hatari process with "pmap", you can see that the libraries Hatari links don't use so much private (writable) memory, only couple of hundred KB: pmap $(pidof hatari) | grep / | grep rw To see how much from the memory pmap tells Hatari to have allocated is actually used/dirtied, take a peek at: /proc/$(pidof hatari)/smaps. Of the rest of the 40MB Hatari VMSIZE you see in "top" (about 16MB), half is unused/clean 8MB memory (allocated by kernel for the SDL sound thread stack) and half goes to shared library code (their .text sections with "r-x" rights) that Hatari links against. The libraries are most likely used also by other programs and even if they aren't, it's memory mapped read-only / read in on-demand pages & pagable back to disk so it's shouldn't be much of a problem either. Unmodified Hatari runs on (Linux) systems having about 20MB of _free_ memory (e.g. according to /proc/meminfo free+buffers+cached fields) and more RAM in total than the Hatari VMSIZE. Using low-rez without borders nor zooming, setting emulated ST memory amount to <=1MB, limiting the VDI screen size, disabling DSP and removing bigfont (discussed with Massif findings above) should enable running Hatari well on a system with only 10MB free memory, if it's otherwise fast enough. - Eero Tamminen PS. Any device fast enough to run Hatari at reasonable speed should already have enough memory for it... hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/midi-linux.txt000066400000000000000000000223221504763705000241270ustar00rootroot00000000000000 Getting (Linux) ALSA midi support and MIDI networking working with Hatari ========================================================================= If you don't have a real MIDI sequencer, you can use the MIDI synthesizer of your sound card (if available) or use a software synthetizer. For (Debian) package names and links to software referenced in this text, see end of the text. Most of the distros should have in their repositories packages at least for some of them though. Contents: - Quick start - Using a soundcard with built-in MIDI synthesis capability - Making MIDI soft-synthetizer to work with ALSA - Using FluidSynth instead of Timidity - Other software synthetizers - Making it all to work with Hatari - Forwarding MIDI over network - Linux & Atari MIDI related software - Additional documentation Quick start ----------- *If* Hatari is built with PortMidi, getting MIDI output from Hatari is simple. Install a MIDI software synthetizer and virtual MIDI keyboard (on Debian/Ubuntu): sudo apt install qsynth vkeybd Then start them: qsynth & vkeybd & (Hatari scans for the ALSA MIDI devices only at start, so the relevant MIDI output and input devices need to be there before starting Hatari.) Start Hatari, and in the "Devices" option screen, select "Enable MIDI emulation". Then for the input option, select "Virtual Keyboard", and for the "output" option, select "Synth input port". That's all! Rest of this document is about lower level MIDI (raw device) access without PortMidi, Linux MIDI usage in general, and notes of some useful Open Source MIDI software available on Linux. Using a soundcard with built-in MIDI synthesis capability --------------------------------------------------------- If your soundcard is capable of playing MIDI sound (i.e. you can play a .mid file with the "aplaymidi" command using the appropriate port), you can use this synthesis device for Hatari, too. However, you still might have to install and connect a virtual midi device, so that Hatari can access it through a /dev/snd/midiC*D* device file (see instructions below). Please note that you might also have to load instrument patches into your sound card first, for example with the program "sfxload" for AWE64 based sound cards, or with the program "sbiload" for OPL3 based sound cards. Making MIDI soft-synthetizer to work with ALSA ----------------------------------------------- Make Timidity into an ALSA output device with: timidity -Os -iA (-Os: output=alsa, -iA: interface=alsa) To make it use less CPU and be more responsive, use: timidity -Os -iA -B2,8 -EFreverb=0 -EFchorus=0 (-B2,8: set small buffers, -EF=0: disable given effect) Make vkeybd (virtual midi keyboard app) into an ALSA input device with: vkeybd Or use the newer & nicer looking "Virtual MIDI Piano Keyboard": vmpk View the resulting (software) ALSA input and output devices: aconnect -i -o Then connect the vkeybd output port to the timidity input port with: aconnect (Or use e.g. 'aconnectgui' GUI to do it.) => Virtual midi keyboard can now be used to test the sound synthesis. You can also test how well midi files are played. Check which ALSA port Timidity provides: aplaymidi -l And use that port for playing a midi file: aplaymidi -p test.mid ('pmidi' could also be used, and takes same args.) Remember that you need to re-connect the (virtual) device ports each time you restart program adding that port. Using FluidSynth instead of Timidity ------------------------------------ Instead of Timidity, you also use other soft-synthetizers, like FluidSynth: fluidsynth --audio-driver=alsa --midi-driver=alsa_seq soundfont.sf2 You could play a bit with other options to get more performance, sound volume etc: --reverb=no --chorus=no -o synth.polyphony=16 --gain=0.6 And if you don't like the FluidSynth shell, use: --no-shell --server Qsynth provides a GUI for above: qsynth Other software synthetizers --------------------------- Of the other soft-synthetizers, I like also Horgand organ emulator as it has pretty good organ sound, but it needs Jack connection kit (+ e.g. qjackctl) for sound to work properly (not have sound underruns). Making it all to work with Hatari --------------------------------- Hatari requires midi hardware devices to work, it doesn't support ALSA directly without PortMidi. To get the software synth ALSA devices to appear as HW midi devices, run following as *root*: modprobe snd-virmidi [midi_devs=] When you list your ALSA output devices with: aconnect -o You should see in addition to the soft-synth also virtual hardware devices (default device count is 4). Then connect (with 'aconnect' or one of the GUIs) the first virtual HW port to the same soft-synth port where you connected the virtual midi keyboard. Check which number was assigned by ALSA to the new virtual midi card: cat /proc/asound/cards And give to Hatari the corresponding ALSA midi device. In my case VirMidi was Card 1 and as the port used above was first one, I give Hatari the following midi device: hatari --midi-out /dev/snd/midiC1D0 If you use Fluid Synth (or its Qsynth GUI), you can connect that device to synthetizer for example with: aconnect "Virtual Raw MIDI 1-0" "FLUID Synth" (For the virtual midi keyboard, give same device with --midi-in option.) Note: In obsolete Linux distros, SDL_mixer may take exclusive access to the PCM (sound) device, but as the soft synthetizer is already connected to it, one may need to use '--sound off' option to get MIDI sound working. In current distros this should not be a problem (thanks to Pulseaudio etc). Forwarding MIDI over network ---------------------------- If you direct the MIDI data to stdout, you can use just ssh to forward the MIDI output over network: hatari --midi-in "" --midi-out /dev/stdout --log /dev/stderr |\ ssh user@remote.site "cat>/dev/snd/midiC1D0" (Note that logging is re-directed to stderr so that it doesn't mess the MIDI output to standard output and --midi-in is set empty in case you don't have MIDI input device locally.) MIDI-networking two Hatari emulators can be most easily done with socat. MIDI networking over normal TCP/IP network: @remote.site: socat -b1 PTY,rawer,link=/tmp/midi1 TCP4-LISTEN:33333 & hatari --midi-in /tmp/midi1 --midi-out /tmp/midi1 & @local.site: socat -b1 PTY,rawer,link=/tmp/midi2 TCP4:remote.site:33333 & hatari --midi-in /tmp/midi2 --midi-out /tmp/midi2 & Buffer size (-b) is set to one just in case (by default socat buffer size is 8K, but all the MIDI communication is done byte at the time). You may need to open a hole into your firewall for the given port (here 33333). Usually there's a hole for the www-traffic in firewalls, but the port for that (80) is below 1000, so if you use "www" as the port, most likely you need to run "socat" as root. To test this with a single machine, use "localhost" as the "remote.site". Local MIDI network: socat -b1 PTY,rawer,link=/tmp/midi1 PTY,rawer,link=/tmp/midi2 & hatari --midi-in /tmp/midi1 --midi-out /tmp/midi1 & hatari --midi-in /tmp/midi2 --midi-out /tmp/midi2 & If you don't have "socat" installed, 'hatari-local-midi-ring.sh' script shows how to join several (local) Hatari emulators into a MIDI ring using FIFOs. Note: (virtual) MIDI devices cannot be used for networking because those do not support passing of arbitrary data. Linux & Atari MIDI related Software ----------------------------------- In Debian, the tools mentioned above come from following packages: - alsa-utils (aconnect, aplaymidi) - alsa-tools (sbiload) - awesfx (sfxload) - pmidi - vkeybd - vmpk - aconnectgui - qsynth - fluidsynth - fluid-soundfont-* (soundfonts) - timidity - horgand - qjackctl - socat See http://packages.debian.org/ for more details on them. Below are upstream links to some of these tools. Vkeybd: http://alsa.opensrc.org/Vkeybd Virtual MIDI Piano Keyboard (vmpk): http://vmpk.sourceforge.net/ Patch (ALSA connecting) utilities: http://alsa.opensrc.org/AlsaMidiPatchbays FluidSynth: http://www.iiwu.org/fluidsynth/ Horgand: https://sourceforge.net/projects/horgand.berlios/ Soundfonts: http://alsa.opensrc.org/SoundFontHandling List of some soft-synthetizers: http://alsa.opensrc.org/SoftSynth Kaconnect: http://alsamodular.sourceforge.net/ QjackCtl: http://qjackctl.sourceforge.net/ socat: http://www.dest-unreach.org/socat/ As to Atari MIDI programs, here's an incomplete list of games supporting MIDI music: http://www.atari-forum.com/viewtopic.php?f=3&t=21473&start=25#p195632 MidiMaze supports up to 16 players over MIDI network: http://en.wikipedia.org/wiki/MIDI_Maze Additional documentation ------------------------ ALSA midi overview: http://alsa.opensrc.org/AlsaMidiOverview How to set up soundcards with hardware MIDI synthesis capability (AWE & OPL3): https://help.ubuntu.com/community/Midi/HardwareSynthesisSetup Virtual midi hardware setup: http://www.tldp.org/HOWTO/MIDI-HOWTO-10.html Timidity Howto: http://lau.linuxaudio.org/TiMidity-howto.html Midi with ALSA (old): http://www.linuxfocus.org/English/September2002/article259.shtml Midi on Linux: http://www.linuxjournal.com/article/7773 MIDI, Musical Instrument Digital Interface protocol: http://en.wikipedia.org/wiki/Midi hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/release-checklist.txt000066400000000000000000000076331504763705000254470ustar00rootroot00000000000000Things for Hatari dev team to check before new release... Contents: * When first thinking about new release * One or two weeks before release * Release candidate * Final release * After release When first thinking about new release ------------------------------------- * Get list of things we want to push in before the release - Make sure that any other larger changes are postponed after release * Ask about compatibility / bug fixes & features that people would like to see in next release * Check whether any of the (new) changes distro packages apply to Hatari sources should be in Hatari itself: - https://sources.debian.org/patches/hatari/ - https://src.fedoraproject.org/rpms/hatari/tree/main - https://packages.gentoo.org/packages/games-emulation/hatari - https://svnweb.mageia.org/packages/cauldron/hatari/current/ - https://build.opensuse.org/search?search_text=hatari One or two weeks before release ------------------------------- After changes deemed necessary have been implemented, check that everything that should work, does still work: * Ask people on hatari-devel to build latest Hatari and do some preliminary testing with their favorite use-cases * Run TOS boot tester for all supported TOS versions * Verify that: - Building different Hatari build configurations works: - all optional dependencies being present, and none - all optional features enabled, and disabled - Mac GUI - All above Hatari builds pass "make test" - Run Hatari with valgrind - Check a build with "cmake -D ENABLE_ASAN:BOOL=1". Use this if ASAN prints weird stack traces for leaks: export ASAN_OPTIONS="fast_unwind_on_malloc=0" - Check that everything built fine on Travis and Cirrus-CI: https://travis-ci.com/github/hatari/hatari https://cirrus-ci.com/github/hatari/hatari * Check that manual pages do not have errors: troff -man -w w $(git ls-files '*.1') > /dev/null * Check that all programs/demos listed in compatibility.html as fixed during early development still work, and make sure they're listed also in release-notes.txt * Check that (Python/Gtk) hatariui still works with latest changes, including starting with no existing Hatari config, saving changed config, and running under X11 & Wayland Release candidate ----------------- After found issues have been fixed, prepare for release candidate: * Check that Hatari documentation and WWW-site repository are up to date * Fix remaining x.x-dev versions in compatibility.html to new release: sed -i 's/x.x-dev/x.x/' * Validate HTML documentation with https://validator.w3.org/ * Go through commit log / ask others to make sure relevant changes are listed in release-notes.txt, and updated in todo.txt * Get latest etos1024.img version for binary packages and document that in emutos.txt * Build binary packages for OSes that aren't covered by the daily builds * Announce release candidate on mailing lists & Atari forum and ask people to test it on all platforms. Remember to tell which platforms should work, and which are still completely untested Final release ------------- If no release-critical issues were reported, do actual release: * Update release version & date in: + Sources: - src/includes/version.h - src/memorySnapShot.c (if needed) - Mac GUI plist files : src/gui-osx/Info-Hatari.plist src/gui-osx/Info-Hatari Winuae.plist src/gui-osx/en.lproj/InfoPlist.strings src/gui-osx/fr.lproj/InfoPlist.strings + Documentation: - readme.txt - doc/compatibility.html - doc/doxygen/Doxyfile - doc/debugger.html - doc/emutos.txt (based on latest EmuTOS release) - doc/manual.html - doc/release-notes.txt + Packaging: - hatari.spec * Build final binary packages * Announce new release "everywhere" * Party! After release ------------- * Switch back to devel, and increase devel version number in "src/includes/version.h" header hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/release-notes.txt000066400000000000000000003602041504763705000246220ustar00rootroot00000000000000 Hatari -------- Release Notes Following features are deprecated and will be removed in a future release: - SDL 2.x "bUseSdlRenderer" config and "GPU scaling" GUI options (after HW support for SDL2 is available widely enough) - Hatari XBios(255) API enabled with the "--bios-intercept" option (i.e. use "--natfeats" & "--cmd-fifo" options and "hconsole" instead) - The old ~/.hatari configuration file location (use ~/.config/hatari instead) - Less flexible command FIFO / remote socket API command variants: - "hatari-shortcut screenshot" (use "hatari-debug screenshot ") - "hatari-shortcut coldreset" (use "hatari-debug reset soft") - "hatari-shortcut warmreset" (use "hatari-debug reset hard") - "hatari-shortcut savemem" (use "hatari-debug statesave ") - "hatari-shortcut quit" (use "hatari-debug quit [exit value]") Version 2.6.1 (2025-08-15) -------------------------- Emulation improvements: - MegaSTE: - When using cache, only compare 24 lowest bits of address - Don't update cache after a bus/address error - Falcon: - Create a fast ram buffer (_FRB) cookie when running with TT RAM to fix a problem with floppy disk access - Video: - Add support for STE 224 bytes overscan in medium resolution - Set correct resolution in Shifter when accessing $FF8260 as word (fix regression in Hatari 2.6.0) - VME for MegaSTE / TT: - VME memory regions should return bus error when no VME board is present Emulator improvements: - Windows: - Fix: late locale init messes debugger readline history handling - GEMDOS HD: - Fix: caches were no always flushed on writing to emulated memory - Debugger: - Fix: show DEL character as non-printable in memdumps - CPU/FPU settings: - Disallow 68881/68882 FPU in 040/060 mode - Config / SDL GUI: - Default screenshot save directory override option - SDL GUI asks for file name when starting AVI recording - Fit Hatari screen dialog to smallest required size and log errors on invalid dialog sizes - CLI options: - GEMDOS HD & screenshot dir options accept only existing directories Fixed demos: - A Letter To Sommarhack by Effect (write word at $FF8260) - Partial fix for Double Rez Trouble by DHS (med res overscan lines) Fixed programs: - Spectre 3.0 (Hatari v2.6 regression, MegaSTE cache) Version 2.6.0 (2025-06-22) -------------------------- Option changes: - "--trace" option "vme" item renamed to "scu" - "--vme none/dummy" option removed as (now) redundant Emulation improvements: - 030+MMU: - Workaround to correct prefetch data after bus errors - DSP: - Fix: JMP can also form a long interrupt instruction - VME/SCU: - TT / MegaSTE SCU registers and interrupts implemented - Falcon Videl: - Refresh rate of monochrome monitor is now correctly detected with 71 Hz - MegaSTE: - Add support for CPU Freq / Cache control at $FF8E21 when running in cycle accurate mode. This gives cycle exact memory accesses when running at 16 MHz, as well as emulation of the external 16 KB cache - SCSI: - Fix REQUEST SENSE for SCSI v2 - Serial Ports: - Update values of DCD and CTS signals for the RS232 port connected to the MFP (as reported by the underlying OS) - Add support for using TCCLK as RTxCB clock for the Atari TT's SCC - Video: - Improve support for 4 pixel hardscroll by stopping shifter with $FF8260=3. Also handle $FF8261. - MFP: - Fix counter when timer is stopped/restarted while counting from 1 to 0 Emulator improvements: - SDL-GUI (and config options): - Improvements to GUI font characters - Add separate option for enabling/disabling >=030 CPU data cache - Fix: file selector exit with keyboard - Windows: - Open console window for error and help output, so that Windows users can actually see them - Keyboard / TOS localization: - Symbolic key mapping improved for different TOS language versions - Made it possible to use a mapping file with fallback to scancode mapping - New "--fast-forward-key-repeat" option for key repeat - (EmuTOS v1.4) Catalan country code support - Memory snapshots: - Fix: re/store all relevant YM2149 & MMU state variables - Joysticks: - Fix: segfault with negative joystick indexes - GEMDOS HD: - Fix: Fattrib() call on a directory - Fix: Return correct attrib for volume labels in FsFirst() - Fix: Fsnext() calls with DTAs having different attribs, when DTA is not from preceding Fsfirst() but earlier one - Fix: Handle additional '*' chars at end of file mask - Fix: Dfree() total/free on really large disk, free on Mac/BSD - SCC: - Fix: invalid channel B file close on uninit - SCSI: - Add CLI + config file option for (per ID) SCSI version - Command FIFO: - Fix crash when FIFO gets command right at Hatari start - Debugger: - Fix: DSP RESET instruction tracing - Similarly to Atari program symbols override, load TOS symbols on TOS load, if autoloading is enabled and .sym file is present - Add "find" command for searching CPU memory contents - Add "struct" command to better show contents of program's structures - Variable & debug symbol names can be given directly also to load, save, memwrite and register commands in addition to numeric values - File name completion for commands having optional file name args - Symbol name completion for "loadbin", "savebin" & "dspsymbols" commands - Align "memdump" character representation also when line is not full - Show also non-ASCII Atari chars in debugger memory content output - Add 'c' (character) argument type support for "memwrite" command - CPU disassembly shows resulting relative branch addresses - "dspreg" command shows also stack contents - When disassembling, translate logical addresses to physical ones if MMU is enabled - Tracing: - GEMDOS Dsetpath() + Frename() calls included to "os_base" traces - PC value added to more traces Build changes: - Get rid of and warn about VLAs (variable length arrays), as VisualStudio does not support them - Removed the "--disable-small-mem" configure option (the small mem mode is stable now, uses less memory and even still seems to be at least as fast as the other mode, so it is the one and only default mode now) Tools: - gst2ascii: - Fix: ignore configured types also with MINT+ELF symbols - Fix: weak ELF BSS/DATA symbols were handled as weak TEXT symbols - Profile data post-processor: - Fix: first costs assigned wrong when profile does not start on symbol - tos-tester: - Improved support for testing Hatari bool options Fixed demos: - Little -ME- Demo by Overlanders : when running in MegaSTE mode the 3D parts will switch to 16 MHz and use the MegaSTE's 16 KB external cache Fixed programs: - Voxx, both demo & free full version (DSP JMP instruction) - Ultimate Virus Killer (GEMDOS HD, "***.***" file mask) - JAM (stuttering sound with monochrome monitor in Falcon mode) - Linux, user-space program crashes (prefetch with 030 MMU) Fixed games: - Ready Steady Bang! (GEMDOS HD Fattrib()) - STDOOM (MegaSTE 16Mhz cache tag bug) Version 2.5.0 (2024-04-18) -------------------------- Removed features: - The "--bpp" command line option and related code (i.e. the rendering functions for 16 bits-per-pixel host screens) have been removed since almost all recent hardware should support 32 bpp nowadays Configuration changes: - In hatari.cfg, section [RS232], rename: - sSccBOutFileName => SccBOutFileName - bEnableSccB => EnableSccB Emulation improvements: - MFP: - When IRQ is cleared on one of the 2 ACIAs, do not clear IRQ on the MFP side if the IRQ is still set on the other ACIA - Blitter: - Ignore byte accesses to blitter registers defined as word only - TT/DMA: - Update sound FIFO on each HBL (like on STE) - CPU: - Sync CPU core with WinUAE CPU core 5.2 beta - Improved 68000 cycle accuracy (IPL, STOP, TRACE) - Fix IACK timings - Return vector=24 if a spurious interrupt happens during the IACK sequence - 68010 DIV overflow undefined flags update - NMI handling - MMU/generic CPU mode autovectored interrupt support - Fix 68060 MMU MOVEM.L (An)+, if regs contain An - 68040/060 MMU fault handler bug fix for MOVEM - Fix FSINCOS and FMOVE.L/FMOVEM.L register in disasm - Some softfloat fixes - Fix stacked PC for branch/jump instructions doing a bus error - DSP: - Fix: effective address with modifier=modulo - Add host interface initialisation - Add host received data interrupt - Improve bootstrap support - Video: - Add support for 4 pixel hardscroll on STF by stopping shifter with $FF8260=3 (new technique by Troed/Sync) - In monochrome mode correctly mask the video address to 22 or 24 bit space depending on the machine type. Prevent crash in some cases when Hatari is compiled with "small mem" option - Fix VBLANK location, should be line 308 on 50 Hz (was 307 before) - SCC 85C30 (for MegaSTE, TT and Falcon) - Major rewrite of most of the code, should support all modes used by TOS and EmuTOS or when accessing SCC's registers directly - Support all serial ports as well as LAN port for MegaSTE and TT - Joypads: - STE joypad emulation now supports analog / paddle input, too - FDC: - For IPF/CTR support, caps library has a bug that resets FDC's TR and DR on warm reset (68000's reset command). We keep/restore the value ourselves - IDE: - Fixed emulation of the HOB (High Order Byte) of the last LBA48 value - LBA28 capacity announcement is now correctly limited to 2^28-1 - Falcon: - DMA sound : fix SNDINT/SOUNDINT values (0=playing, 1=idle) and interrupts on TAI / GPIP7 for start of frame / end of frame - Preliminary support for more vertical refresh rates (50,60 or 71 Hz) based on Videl's registers $FF82C0 (VCO) and $FF82A2 (VFT) (was only 50 Hz before) Emulator improvements: - TOS: - Fix: set TOS country code from CountryCode setting, not Language one - Add "pl" (Poland) & "ro" (Romania) language options for EmuTOS - RTC: - CLI/config option to override NVRAM/RTC year, useful with applications that do not handle current dates - Joystick/Joypad support: - Fix: joystick button 2 space key emulation "autofiring" - Keyboard emulation support for all STE joypad buttons/keys - Support for re-mapping joystick buttons - GEMDOS HD: - Fix: Fread/Fwrite combination on Windows - Fix: Frename() should fail when target exists - Support up to 64 simultaneously open files (earlier limit was 32) - Similarly to TOS, allow programs to write to a file they have opened as read-only (by opening all writable files as read/write). As this could fail with real HW under MiNT/MagiC, show warning about it - Screen: - Support for screenshot using .NEO or .XIMG format - Add option "--screenshot-format " ( = png or bmp or neo or ximg) - Memory Snapshot: - Add some missing variables to the savestate - Logging/tracing: - "os_base" trace option outputs now also dir create/delete + file delete calls (in addition to earlier pexec/pterm & file create/open calls) - Separate CPU video cycles under its own "cpu_video_cycles" trace flag - Suppress repeats of identical log & trace messages by default (show only their count) and add "--msg-repeat" option to toggle that - Debug symbol handling: - Fix: invalid free on freeing loaded GNU debug symbols - Fix: Do not limit "A" type (constant) symbol values to 24-bit - Add support for reading symbols from new MINT+ELF binaries - Add support for demangled C++/a.out symbols (which can be very long and contain almost any characters) - ".sym" file beside program file acts as its symbols override - Add optional argument for "symbols " commands, to limit listed symbols to ones with the given substring - Improved support for weak symbols (important for C++ code) - Symbols with duplicate addresses are skipped on symbols loading - Disassembler: - Fix: CPU core disassembler crashes with longer symbol names - When entering debugger with 'history' enabled, disassembly address defaults to an address in history preceding the PC register value (to give more context than disassembling directly from PC) - Support for disassembly output options working also for CPU core disassembler, in addition to external disassember output - More flags to change the disassembly output for CPU core (upper/lower case, show memory content, show EA, ...) - The built-in "external" disassembler has been replaced by a disassembler provided by the Capstone library (version >= 4.0) - Debugger: - Fix: free all debugger allocations before exit - Fix: memdump command always outputs only requested number of items - Add address width (addr).[bwl] support to expression evaluation - History skips address repeats (e.g. with "stop" instruction) - Breakpoint ":quiet" option inhibits also extra output when ":file" option debugger command file is parsed - Line-A and line-F exceptions can be caught with "--debug-except" option (in addition to breakpoints) - Add "echo" command with escape handling, so that one can e.g. use "echo \ec" to clear (ANSI) terminal before breakpoint output from a debugger command file GUI improvements: - SDL GUI: - Fix: mouse not visible in floppy disk dialog when it is invoked from a keyboard shortcut in fullscreen mode - Fix: fileselector scrollbar works with mouse also in SDL2 scaled / fullscreen window - Support for joystick button mapping + space key vs. jump option - Mac GUI: - Accept all file names as Mac file selector does not show a list of the accepted file name extensions any more Tools: - gst2ascii: - Support for new MINT+ELF symbols - "-o" option is split to "-f" and "-g" options - "-s" option to skip symbols with duplicate addresses - By default filters same symbols out as Hatari debugger - This can be reverted with new '+' option variants (+l +g +s) - More concise listing of the duplicate symbols - Profile data post-processor: - Does not show (most) of symbol conflict messages any more (unless '--verbose' option is given) - When address has multiple symbols which names are thought as C/C++ symbols, prefer shortest one - Support for symbol files + Hatari profile data containing demangled C++ symbols - Support weak symbols (used for C++ template methods) - Overtly long C++ symbol names are shortened in callgraphs (unless '--full-symbols' option is given) - Symbols given for "--only", "--ignore" and "--ignore-from" are interpreted as match patterns (with '*?[]' wildcards) - Only single arrow shown between callgraph nodes when "--compact" option is used - m68k-instructions: (new) - Tool for printing m68k instruction breakpoints & opcode info Build improvements: - Fix: CPU core compile warnings (WinUAE upstream) - Fix: groff/troff warnings for manual pages - Use cmake config file provided by the SDL2 library instead of trying to detect the SDL2 library ourselves (fixes compilation on macOS) - Removed unused sources for HD6301 emulation - Hatari can now be compiled with emscripten, too Fixed demos: - Chaos A.D. : bad sound (Falcon DMA sound interrupts) - Little -ME- Demo end part by Overlanders : black screen after a while (IACK timing) - Monscape : bad sound (TT/DMA FIFO update) - Time Slice by Defence Force : monochrome demo (crash when Hatari is compiled with "small mem" option) - What If by Troed/Sync : 4 pixel hardscroll on STF by stopping shifter - Oergs, Terrorize your soul, Zero Three Zero demos work now also when MMU is enabled Fixed programs: - FlaySID (DSP host handling) - AFM sound with equalizer enabled (DSP modulo) - FreeMiNT (use SCC counter at start to detect the SCC clock freq) - Spectre 3.0 Macintosh emulator (stacked PC in case of bus error) - m68k-Linux works (again) with 040/060 emulation (MMU) - m68k-NetBSD does not crash on boot any more (MMU) Fixed games: - F29 Retaliator using CTR/IPF format (handle FDC's TR reset bug in caps library) - MIDI (MFP IRQ clear) handling: - Bad Mood - Midi Maze - Midi Maze II - Oxyd 2 - Work now also when MMU is enabled: - Gravon (demo) - Killing Impact - Moongame Version 2.4.1 (2022-08-03) -------------------------- Emulation improvements: - Sound: - Much more precise function to convert CPU clocks to YM2149 clocks in the case where YM2149 doesn't use the same source clock as the CPU (fixes e.g. some STE sound glitches in BLSPLAY.TTP) - Video: - Fix regression in TT mode: Update video counter again on each HBL (fixes e.g. Hextracker when run in TT mode) Emulator improvements: - DSP: - Avoid small extra overhead on non-Falcon machines when DSP config option is set - Misc: - Fix memory leaks on PNG save and GEMDOS HD program load - All Hatari output (outside of debugger) uses now appropriate logging and trace functions so that they can be controlled - VDI mode: - Fix: VDI width needs to be aligned to (at least) 16-pixels - Fix: a possible crash with (default) SMALL_MEM build config (when ST program sets a bad video base address) - Statusbar: - Show "Linux" when running Linux directly with --lilo - Debugger: - Fix: to duplicate symbol name removal - Fix: all IDE trace output did not go to trace file - Like with rest of debugger, profiler responses go now to stderr (not stdout) Documentation: - Document Hatari outputs and their controls in debugger manual Version 2.4.0 (2022-07-09) -------------------------- Removed features (that were marked as deprecated in earlier releases): - Support for the SDL 1.2 library (i.e. SDL 2.x should be used instead) - Support for building Hatari for Windows CE has been removed, too, since this was relying on version 1.2 of the SDL library. - The old UAE CPU core (i.e. the new WinUAE CPU core is now always used) - Python v2 support in Python scripts (Python 2 was end of life in 2020) Emulation improvements: - Internal timers: - Rewrite the internal timers used to emulate delays/events/mfp timers (gain ~10% on average, up to 60% in very specific cases) - CPU: - Improve FPU emulation (exceptions, unimplemeted instructions, fsincos) - Improve FPU emulation in softfloat mode - Improve IPL timing update in various opcodes - Faster emulation of 68000 in cycle exact mode (gain ~7%) - MFP: - Use the new internal timers for MFP timers - Improve accuracy when reading MFP's Timer Data Register - More accurate emulation by processing all internal timers first before reading/writing any MFP register (was previously delayed after processing the current CPU instruction, which was too late in some cases) - Video: - Fix position for detecting when VBLANK should be disabled in 50 Hz and 60 Hz - DSP: - Improved interrupts handling and SSI (from Previous NeXT emulator) - Sound: - Improve accuracy of YM2149's sample generation by using the main CPU clock as a reference for all timings (syncsquare effect as used in recent maxYMiser v1.53) - Use specific memory functions when doing DMA for STE and Falcon sound (don't generate bus error when pointed memory is not present) - Improve value of MFP GPIP bit 7 for DMA sound in the case of STE/TT (video mono XOR dma sound xsint) - Use 2 different signals SOUNDINT and SNDINT for Falcon (connected to MFP GPIP7 and MFP Timer A input) - FDC: - Stall the CPU during 32 cycles when DMA FIFO is filled/transferred - SCSI: - Improve MODE SENSE, add REPORT LUNS support - IKBD: - support deltax/deltay in command $A (set mouse keycode mode) and threshold x/y in command $B (set mouse threshold) Emulator improvements: - TOS support: - Support 1024k EmuTOS images also on TT & Falcon - Add country code option to select EmuTOS language, keyboard layout and screen refresh rate on Mega/ST/STe machines (ones lacking NVRAM) - MIDI support: - Fix: PortMidi rejects Hatari MIDI events - Fix: MIDI IRQ needs to be re-enabled on MIDI device change even when there's no reset - Support for matching PortMidi device name by prefix when there's no exact full match - RS232: - The RS232 receiver code has been rewritten to use polling instead of using a thread. This should avoid deadlocks on BSD/macOS systems when shutting down or reconfiguring the RS232 settings. - TT/Falcon: - Increase max TT-RAM amount to 1024 MiB - Separate config + CLI options to override NVRAM language and keyboard layout settings - NVRAM language default is taken from the LANG env variable - Falcon: - Fix: restore zoom mode correctly when loading snapshots - Correct also smaller specified memory amounts to valid ones - Microphone emulation now uses SDL2 instead of PortAudio - MegaSTE/TT: - Add VME/SCU registers "--vme " access option (Linux needs "none" option and TOS v2 / v3 need "dummy" one until Hatari implements real VME/SCU register emulation) - VDI mode: - Relax VDI mode width alignment from 128/planes to 16/planes, and height alignment from 16 to 8 pixels - SDL GUI: - Fix: ignore unrecognized events in file selector (buttons could be incorrectly shown as selected) - Fix: avoid key release event from SDL GUI closing leaking to emulation - Do VDI mode size changes in (faster) 16 pix increments, and prevent user seeing invalid VDI mode sizes - Fine-tune CPU dialog texts and keyboard shortcuts - GEMDOS HD: - Fix: clear GEMDOS Fread / Fwrite errors - Fix: Allow GEMDOS tasks to use other tasks' file handles - Command line: - Support "off" as alias for "none" when disabling DSP, FPU, Joysticks and VME, for consistency with HW on/off options - NatFeats: - Fix: freeze with NF_STDERR when string is empty or unterminated - Debugger: - Fix: profile header so that profile data post-processor knows how to parse built-in WinAUE CPU core ("uae") disassembly output, not just "ext" disassembler output - Repeat CPU & DSP "step" and "next" commands on Enter - Output PC location in CPU & DSP disassembly - More readable "variables" command output - Add "PConSymbol" variable for breaking on symbol addresses - Add "mmu" and "vme" options for "info" command - Add "scc" and "vme" (VME/SCU reg access) tracing support - Setting trace to "none" disables also xconout console output enabled by "os_base" trace option - "next subreturn" will run until current function returns, even if function calls other functions before that - Symbol loadind code is now shared between debugger "symbols" command & "gst2ascii" tool, and it can now read also long symbol names from Pure-C debug information - Auto-detection for whether section offsets should be used when loading symbol information - MIDI trace options are split to "midi" (device & MIDI events) and "midi_raw" (IRQ and register read/write access). Trace logging added also for MIDI byte writes, not just reads - Trace options are listed in alphabetical order - Show loaded symbols in CPU core disassembly - Windows: - Next/prev drive change buttons added to SDL GUI file select dialog Build/configure: - SMALL_MEM option is enabled by default Tools: - Add (SDL2 specific) "listkeys" key mapping helper back - TOS boot tester: support 1024k EmuTOS images, misc fixes Documentation: - Compatibility updates, especially for 060 programs that need FPSP060.PRG for missing instructions emulation Fixed demos: - Hackabonds Demo (wrong vblank detection) - We Were @ HD version under GEMDOS HD (file handle access) Fixed programs: - maxYMiser FM v1.53 by gwEn (support for syncsquare effect) Fixed games: - Chambers of Shaolin : DMA sound was wrong in STE mode with 1 MB RAM (mfp gpip bit 7) - Super Hang On : fix flickering raster colors (bclr #0,$fffffa0f sometimes happened at the same time timer C expired) Version 2.3.1 (2020-12-27) -------------------------- Emulation: - CPU: - Fix: unneeded extra prefetch for movem in 68020/30 cpu (regression in v2.3) - DSP: - Fix: master clock use for crossbar/DSP Handshake mode (partial regression in v1.6) - Fix: hostport PORTB interrupt handling Emulator: - ACSI/IDE/SCSI handling: - Fix: IDE crash when switching to Falcon mode at run-time (regression in v2.3) - Fix: No error dialog on IDE image mount errors - Fix: Duplicate disk image mount failure handling - TOS support: - Fix: TOS 2.07 (Sparrow TOS) support (regression in v2.3) - Hatari window handling: - Fix: Hatari window disappearing from Python UI when Hatari changes its framebuffer size (SDL2 specific) - macOS GUI: - Fix: FPU setting doesn't work when GUI runs in French Python UI: - Several fixes (see its own release-notes.txt) - Requires now Gtk v3.22 or newer Tools: - Fix: assert in hatari_profile (profile post-processor) with relative symbols Documentation: - Add PortMidi info to devices dialog section in manual - Updated compatibility documentation Fixed Falcon programs: - Music missing in H2O game, in Running game main screen, Stocasto demo and Epidemic music disk (crossbar/DSP handshake mode) - Lockup demo freezing at startup (DSP hostport PORTB interrupt handling) - Several demos (Cruor 96k, Hmmm..., Keff, Payback 2015) and games (Beats of Rage, Capy, Confusion preview 2, Men at War preview, Moonspeeder preview 2, Neurobot, Pacmania X68000, Slippery Sam and Sokoban by Fun) not starting with TOS4 or EmuTOS unless MMU was enabled (unneeded extra prefetch in movem) - Built-in Obsolescence demo, Delmpaint app and Sidetracked GFA musicdisk had other issues also with MMU under TOS4 or EmuTOS (unneeded extra prefetch in movem) Version 2.3.0 (2020-11-28) -------------------------- Emulation: - FDC / Disk : - Support for MegaSTE DIP switch register $FF9200 (set floppy drive to HD by default on MegaSTE/TT/Falcon) - support for DD/HD mode on MegaSTE at $FF860E (when FDC is set to HD mode, reading DD floppies will fail) - Support for the 'Disk Change' (DC) signal on TT machines on TT's MFP GPIP4 - Fix: IDE disks with sector size > 512 bytes - Fix: IDE controller is now always available in Falcon mode, even if no hard disks have been configured. - CPU: - Update CPU core to latest WinUAE 4.4.0 beta : full support for undocumented fields in 68000 bus/address error stack frames, as well as CCR flags for most of the 680x0 instructions (using a cpu tester program on the real hardware that checks all possible opcodes' combinations) - 68030 MMU hardware bus error support + various fixes - FPU fixes - Video: - Improved timing when setting Vsync signal (same place where video counter is reloaded) - Handle screen where vertical DE signal is completely disabled - Better start/end position for the VBlank signal at 50Hz and 60Hz - Sound: - Add a better filter for downsampling the internal 250 kHz signal, should give better results when the YM2149 outputs high frequency sounds - Fix microwire mask shifting when CPU runs > 8 MHz - MFP : - Rewrite MFP code to handle several MFP objects and add support for the TT's 2nd MFP (not all TT's MFP signals are emulated yet) - Improved Falcon's DMA sound interrupt on GPIP7 and AER - Blitter: - Large rewrite of the blitter's core, improving cycle accuracy and handling the complex cases where xcount=1 and nfsr=1. Code is slightly smaller and closer to the logic of the real hardware - Handle restart in non HOG mode when the CPU uses a RMW instruction (eg TAS) - Improve access to memory regions that would generate a bus error for the CPU - Emulate additional RTC/NVRAM registers - megaSTE should start at 8 MHz, not 16 MHz Emulator: - Miscellaneous: - Fix: freeze at Hatari exit and RS-232 device file changes, when device file(s) are FIFO(s) - Fix: when autostarting programs without pre-existing INF file, enable blitter also with EmuTOS - Use floppy track's size in bytes to detect DD/HD/ED (instead of counting sectors) - Add --lilo debug option for more convenient m68k Linux loading - Config file handling: - Support config file values with '=' in them - Skip reading global config file if HATARI_TEST environment variable is set - Memory handling: - Memory snapshot version increase due to FDC changes (i.e. old snapshots won't work with new version) - Accept 10MB as valid ST-RAM amount (max on real MegaSTE/TT machines) and correct invalid Falcon ST-RAM amounts - Fix: 24-bit address mode change while emulation is running (triggered e.g. when TT-RAM is enabled for TT) - HD images: - Fix: run-time IDE byte swap change requires IDE re-init - Fix: TOS booting from A: although SCSI drive was enabled - Support read-only HD image files, and show error dialogs for image file open/lock issues - GEMDOS HD emulation: - Support FASTLOAD program flag with GEMDOS HD - GEMDOS HD emulation cartridge assembly functionality is moved almost completely to emulator side. Fixes Atari side error handling when program file is not readable, and TOS stack overflows in some rare cases - Detect DTA re-use to reduce DTA cache usage, grow cache on demand and give warning if its entries need to be re-cycled (= cache max size is reached) - Invalid DTA in Fsnext() return -ENMFIL, like TOS does - Fix: skip non-existing host files on FSnext() instead of returning an error (latter broke directory listings) - Fix: Dsetpath/Dgetpath empty path handling - Hatari graphics support: - Low/med-rez line doubling uses less CPU and doubled lines in "TV" display mode are now drawn at half intensity, not as black (fixes TV mode being too dark) - SDL2: "--zoom" option accepts any values between 1.0 - 8.0 - SDL2: "--zoom" option is changed to always enable low resolution doubling before SDL framebuffer is scaled up (or down) by the zoom factor. This way Hatari output window is approximately same sized regardless of emulated Atari resolution, like on a real CRT monitor - SDL2: scale quality is selected automatically; nearest pixel for sharp output with integer scaling factors, and linear scaling to smooth out issues with non-integer scaling and window resizes - SDL2: Now redundant "nRenderScaleQuality" config option is removed and "Linear scaling" option in GUI is replaced with the "GPU scaling" option (=bUseSdlRenderer config option) - Added new "--screenshot-dir" option to select the folder for screenshots - Fix: [ffmpeg/video] Invalid PNG signature 0x89504E470D0A1A - Fix: garbage graphics (by clearing the whole render area) - Fix: Spec512_StoreCyclePalette when using 16 or 32 MHz - Input handling: - Support SDL "Hat" events in addition to "Axes" events (= support 80's game controllers, also in SDL GUI) - Map a turbofire button independent of the fire button if the game controller is capable - Show mouse grab toggle shortcut key in startup statusbar message - Center host mouse to Hatari window on Atari resets and resolution changes only when window is focused and mouse pointer is within it - Fix: keypad emulation with SDL2 - Fix: simulated (socket API) key input with SDL2 - Profiler: - Profiler backtrace shows now real caller addresses and their offsets from the function entry points (slows profiling of addresses with symbols) - All profiler outputs have now reasonable limits (so that they don't flood console) - Fix: exception in profile post-processor script (with symbol address aliases) - Tracing: - Fix: CPU disassembly trace output doesn't go to specified trace file - Trace flags can be added and removed instead of needing to always specify all the relevant ones, both with "--trace" command line option and debugger "trace" command: --trace os_base,aes, trace +xbios,bios, trace -bios,-aes - VDI trace shows names also for NVDI/Speedo/GDOS functions - VDI & AES trace function numbers are shown in hexadecimals with 0x prefix like rest of the OS calls - Debugger: - New "screenshot" command for saving screen dump to a PNG/BMP file - "symbols resident" option replaced with "symbols autoload" option which can be used to completely disable automatic symbol loading and unloading for programs run through the GEMDOS HD (helps debugging resident programs started from GEMDOS HD) - Improvements to UAE and external disassembler disassembly - UAE disassembler is now default instead of external one (latter doesn't decode all instructions correctly) - New "info" subcommands: "acia", "dmasnd", "ikbd", "mfp", "nvram", "rtc" and "scc" - Also UAE disassembler can now show the profile info - "info" command can show AES & VDI information on on their respective traps without VDI mode/tracing being enabled - Breakpoint ":info" option to call specified info function on tracing breakpoint hits (one can now use e.g. ":info vdi" on VdiOpcode breakpoints) - Improved symbols info output - Native features: - Support for missing NF_SHUTDOWN (reset) sub commands - Test code for rest of features (except for NF_SCSIDRV) - When Hatari resets or exits due to emulated program NF_SHUTDOWN / NF_EXIT call, output user a note about that Tools and Hatari Python/Gtk UI: - Python scripts use now "python3" because most current distros don't anymore install Python v2 by default. To use a script with v2, change "python3" in its first line to "python2" Building and unit tests: - Source repository moved from Mercurial to Git - Fix: issue with multilib capable cross-compilers - Only Caps library v5.1 is supported (support for old v4.2 is dropped) - Obsolete Mudflap option replaced with AddressSanitizer support - Add tests for GEMDOS HD, fullscreen/overscan display, blitter and Hatari command FIFO + improve CPU/MMU variant coverage in earlier tests Documentation: - Up to date documentation provided at: https://hatari.tuxfamily.org/doc/ - New m68k-linux.txt and m68k-netbsd.txt documents on how to test m68k Linux and NetBSD under Hatari - Debugging and profiling information is split from manual.html to a separate debugger.html file - Minor improvements Fixed demos: - Pacemaker STE demo (end part), when it's run from GEMDOS HD - Multi scrolls part in Closure by Sync (regression since Hatari 2.2, vsync in bottom border) - Fullscreen part in Hard As Ice STE demo by I.C.E. (screen with no vertical DE signal) - Fullast Vinner by Troed/Sync (improved vblank position, partial fix for now) - Electric Night Falcon demo by Dune (MFP DMA sound interrupt on timer A using AER) - Oompa by No Extra (after greetings part) (blitter access to bus error regions) - E.K.O System (music should now play in the racing scene) Fixed programs: - MS Write (crash), when it's run from GEMDOS HD - Akaisex program reading AKAI S1000 HD floppies converted to STX - EmuTos drawing vertical lines with blitter and leaving some trails (blitter in non HOG mode using TAS instruction to restart) - Cecile v2.22, now also works if no IDE drive has been configured - Trans D-Bug Express by PHF (lock when running in megaSTE mode at 16 MHz) Version 2.2.1 (2019-02-08) --------------------------- Emulation: - CPU: - Fix a bug/regression from Hatari 2.1.0 in the UNPK instruction Version 2.2.0 (2019-01-31) --------------------------- Emulation: - CPU: - CPU core updated to WinUAE 4.1.0 - Fix 68030 MMU PTEST/FSAVE/FRESTORE - Improve 68030 bus error handling and retrying faulty instruction - Fix: only enable cache emulation when relevant - Improve softfloat FPU emulation - FPU emulation mode (softfloat or not) can be changed on the fly - Ensure HW registers region is not cacheable for 68030 without MMU when write allocate mode is enabled - DSP: - Fix DSP ROM tables (sin, mulaw & a-law) - Some waitstate cycles were not correctly counted when accessing DSP IO regs in CE mode - Blitter: - Fix: a rare case in cycle exact mode when bus is shared between CPU and blitter : the last word of a transfer could be wrong if bus was owned by CPU just before processing this last word and a read-modify-write is made - State of current blitter operation was sometimes not correctly restored when CPU temporarily stopped the blitter and resumed it later - DSP emulation was not updated in parallel when blitter was running - Video: - Handle VBlank signal and mask the 2 last lines when bottom border is removed - Hard disks: - Experimental support for the NCR5380 SCSI chip in Falcon and TT mode - Serial ports: - Very experimental support for the SCC chip of the Mega-STE, TT and Falcon (only channel B for now, and output only, use the "--scc-b-out" command line switch) - Misc: - Correctly emulate bus error handling for STE lightpen registers - Experimental support for TOS 2.07 (the "Sparrow" TOS) Emulator: - Misc: - "patch TOS timer-D" speedup option is now disabled by default - Allow up to 512MB of TT-RAM (increased from 256 MB) - Additional ROM patches applied to TOS v4.x for 040 & 060 - Improved info and warning output for Hatari constraints and issues - Memory snapshot save/restore: - Fix: Include GEMDOS HD file handles so apps with open files work - Improve memory snapshot save/restore reliability (e.g. for pending interrupts) - Save/restore TT RAM content into memory snapshots - SDL GUI: - Show warning dialog when cartridge file is disabled due to conflict with other features - Fix halt dialog crash on double bus error before SDL init - Add FPU softfloat option - Move joystick info to second statusbar line - Few additional special keys can be remapped - Fix: broken CPU dialog with old UAE CPU core - Display 'SF' in status bar if FPU emulation is using softfloat library - Display 'CE' or 'PF' in status if using 'cycle exact' or 'prefetch' modes - It is now possible to configure more than one ACSI hard disk images - MacOS / GUI: - Minor GUI updates for v2.1 changes - Fix: "reset" and "don't reset" UI buttons inverted - Update MIDI panel for French version - Save screenshots in user defined location or ~/Desktop - Windows: - Fix: buffer overflow in relative Hatari datapath handling - Disk handling: - IDE byte-swap option added to config file & GUI - Fix: NF SCSI driver didn't invalidate cache for changed memory area - Fix: *.INF file in disk image being overridden even when neither VDI mode or autostart program wasn't specified (v2.1 regression) - Fix: GEMDOS HD Fwrite() can be done from ROM area (for ROM saving) - Media handling: - Fix: AVI recording with PNG codec could be wrongly limited to 4GB with some OSes - Screen handling: - Fix: TOS <= v2.x crashes when mouse is moved in 16x16 area at the bottom right corner in VDI mode (Hatari v1.0 regression) - Fix: "--tos-res high" setting - Debugging: - Fix: DSP/CPU profile "addresses" commands output line count when paging. Tell also when it wraps to start - Each log message has a log level prefix - (Almost) all Hatari output goes now through logging framework so that Hatari verbosity can be controlled - Output at default log level is reduced significantly - Add support for ISP & USP and 020+ special registers like VBR - Add "OsCallParam" variable which gives first (word sized) parameter for OS calls, for use with OS call "*Opcode" breakpoints - Add virtual V0..V7 registers to "register" command for calculations - "memdump" and "memwrite" commands output & input can now be also word or long sized instead of bytes - Show arguments in DSP XBios call traces - FPU and PMMU opcodes are now also disassembled in 68040/060 mode - Options: - Fix: "--memsize 0" legacy option - Disable "--timer-d" option by default. Increases CPU usage (noticeably for ST/e emulation), but some rare (badly written) programs need it for correct color raster & sample handling - The "--bios-intercept" option requires now a boolean parameter to determine whether the option should be enabled or disabled - Add "--cmd-fifo" option to control Hatari at run-time just by echoing commands to a FIFO file (created by Hatari) - Add "--tos none" option to run test programs without any TOS. Real Atari programs cannot be run with this, as it doesn't implement the necessary Atari OS calls - Halt dialog is skipped if --run-vbls is used (for automation) - Support hex values for --disasm, increase --slowdown max value to 30 Building: - Fix: Large File Support detection (64 bits off_t) - Fix: new compile warnings with GCC v8 (+ code cleanup) - Fix: building when DSP or tracing is disabled - Improve CMake library finding for recent macOS - Change Hatari compilation C standard to gnu99 - Add gitlab continuous integration Yaml file - Many -fsanitize fixes Tools: - Hatari Python UI migrated from PyGtk v2 to Gtk v3 - Add Python v3 support to rest of the scripts (required by Arch Linux & Clear Linux) - hatari-prg-args script: - Installed by default - Fix program argument setting for recent EmuTOS versions - Support argument setting also for programs that are run from disk images - zip2st: handle also directories with spaces in their names - Fixes to hatari_profiler Callgrind output symbol handling - Fix TOS bootup tester test output verification - Few additional tests for validating Hatari functionality. Almost all tests are now integrated with CTest framework. Use e.g. "ctest -j4" command to run them Documentation: - Tag commits for Hatari releases before v1 in Mercurial repo - Add notes about (new default) PortMidi support to MIDI usage - More details on GEMDOS HD and VDI mode emulation limits - Large updates to compatibility documents and to performance section in the manual - Add release checklist document Fixed demos: - B.I.G. Demo screen 2 (regression in Hatari 2.1, vblank in bottom border) Fixed falcon demos: - 4musiK by Dune & Sector One (don't cache HW register regions in write allocate mode) Fixed games: - Lethal Xcess in STE mode (stopping/resuming blitter and saving/restoring memory snapshots) - Lethal Xcess in STF mode (add jitter to MFP, temporary fix) Fixed programs: - Many TT/Falcon programs relying on 68030 MMU / bus error retrying are now working correctly (eg 'memwatch' by Uwe Seimet) Version 2.1.0 (2018-02-07) --------------------------- Emulation: - Video: - Fix correct number of displayed lines when removing bottom border - Improve bottom border removal on 60 Hz screen - More accurate position for reloading video counter on line 310/260 - Fix STE video counter regression at start of VBL (off by 4 cycles) - Fix Timer B counting when screen is in Mono mode and video resolution is set to low/medium - Fix bug preventing screen to be drawn when video address = 0x0 - Add (dumb) Videl address and vertical frequency counters emulation - Videl unused bits at $FF820E are read as zero - Sound: - New cycle exact emulation of the YM2149, all counters are incremented using a simulated freq of 250 kHz, giving a 250 kHz audio stream which is downsampled to the desired output frequency. Some undocumented cases were also measured on real STF Result should be much more accurate, clearer and emulate complex effects (phase cancelling on 2 voices for example) - Fix bad sound during YM sample playing on Falcon/TT - CPU: - Support undocumented behaviour for STOP on 68000 when new SR has bit S=0 - Fix some cases where bus/address error stack had a wrong PC - Correctly store the 32-bit address in the stack in case of a bus error - Better memory timings for RAM accesses in 68030 mode (for Falcon) - Accurate DIV overflow undocumented flags - Correct V flag for BCD instructions for 68020/30 - 68030 instr cache was not correctly disabled when writing EI=0 in CACR - Improve caches for 68020/30, add cache support when using MMU - Add 68040/60 data cache emulation, with optional MMU support - FPU: - Large improvements in FPU emulation and related instructions/exceptions - Support for undocumented 68882/68881 FPU constants - Support for softfloat FPU emulation (slower but more accurate) - Memory / MMU / MCU: - Add full support for STF/STE MMU/MCU at $FF8001 and address translation (emulate the RAS/CAS signal depending on the size of the memory banks, TOS memory routines don't need to be patched anymore) - Better results when reading "void" region between end of RAM and 4MB (return latest data seen on the bus on STF/STE) - Add support for more RAM combinations (256 KB and 2.5 MB) - Blitter: - Add cycle exact bus handling for the blitter (correct bus count for CPU and blitter, suspend/resume blitter after any memory access, run part of next CPU instruction in parallel when starting the blitter) - When writing busy bit=0, update interrupt line and don't clear hog bit - Floppy: - Emulate ripple carry adder "bug" for STF DMA address at $FF8609/0B/0D - For STX images, add support for the verify bit in Type I commands - Fix a rare case when looking for the next sector header of an STX image - IKBD: - Support for Audio Sculpture's custom IKBD program Emulator: - MIDI support for Windows and macOS (with PortMidi library) - Add support for AVI files > 4GB (up to 256 GB) - Miscellaneous: - Initial patching for ST-Book ROM - Add keyboard shortcut for toggling borders - Fix: rate-limit DSP illegal instruction warnings output - Disk handling: - Add "--gemdos-time " option so that user can specify whether emulation or host timestamps are used with GEMDOS HD files - Fix: off-by-one error in ACSI image file name handling - Memory handling: - Support memory check skipping also on TOS v3 & v4 - Allow RAM size values in KiB instead of MiB - SDL UI: - Add a 'blitter-meter' to the status bar, depending on the blitter's usage - Add refresh rate in the status bar (50, 60 or 71 Hz) - Disable alt+F4 under Windows so that it doesn't close Hatari (in case alt+F4 is also used in the emulated program) - Display handling: - Add --resizable option to control whether Hatari SDL2 window can be resized (= turn it off to prevent accidental resizes) - Support scaling AVI recording (PNG/BMP) frames - Extended VDI mode font selected based on VDI height - Fix: Handle window expose events with SDL 2.0 - Fix: don't switch bitdepth when recording AVI - Fix: X11 window embedding (to Python GUI) with SDL2 - Fix: TOS v2 & v3 Atari logo display in extended VDI resolutions - Fix: Limit Videl widths for GLES2 SDL2 backends - Virtual INF file handling: - Add "--auto" option to autostart programs also from somewhere else than C: root, and to enable required GEMDOS interception without full GEMDOS HD emulation - Add --tos-res option to specify TOS ST/STE/TT resolution for color monitors, with or without autostart. Also enables blitter for machines & TOS versions supporting it - Uses existing INF file as base, if one exists - If not, specifies open window for boot drive - Fix: Use separate INF files for TOS v1 and v2+ autostarting - Fix: Normal users cannot use tmpfile() directly under Windows - Debugger improvements: - Fix: '>>' bit-shift parsing - Fix: CPU prefetch when changing PC address - Add "CycleCounter" variable - Add "info dta [addr]" command - Add "cpu_regs" trace option to show register values after each executed instruction - Add support for reading GNU-style a.out symbol table format (which is GCC default) in addition to DRI/GST symbols - Add absolute symbols support in addition to BSS/DATA/TEXT ones - Use terminal size for paging (disassembly, memdump, symbols) commands. NOTE: remove old dDisasmLines & nMemdumpLines options from Hatari config file, or set them to -1, to enable this! - Add 'nSymbolLines' configuration option to specify how many lines 'symbols' command pages - 'symbols resident' command and 'bSymbolsResident' config option toggle whether program symbols are removed when program terminates, or only when the next program starts - 'symbols match" command and 'bMatchAllSymbols' config option toggle whether TAB completes all symbols or just symbol types relevant for given command - 'symbols addr' is split into 'symbols code' & 'symbols data' - TEXTEnd value is now TEXT+TextSize, instead of TEXT+TextSize-1 - Add "-f" option to 'cd' so that setup scripts can specify what directory is used after currently invoked script(s) have finished - Move exception mask setting from config file "Log" section to "Debugger" section where it belongs - Compilation: - Fix compilation when using ARM 64 target - Allow compilation on untested host architectures (e.g. MIPS & s390x) - Search for local headers first, then system's one (eg zip.h) - Don't redefine bswap_16/_32 macros if they already exist Other changes: - Add French keyboard mapping that works also with SDL2 - All symbols output by gst2ascii are now TEXT relative and it supports also a.out format symbol tables - Fix: Python GUI updated for Hatari v2.0 - Fix: Python GUI explicitly uses Python v2 (Arch Linux) - Fix: Python tools made compatible both to Python v2 & v3 (Arch Linux) - Fix: icon symlink generation (rpmbuild chroot) - Fix: DESTDIR support for RPM packaging Fixed Falcon games: - Ishar 1, 2 & 3, Lasers and Hommes, Moonspeeder preview 2 (Videl video counter) - Boom preview, Lasers and Hommes, Tank Blaster (68030 instr-cache) - Aazohm Krypth, Men at War, Push It, Running, Sky Fall, Sworm (autostarting) Fixed Falcon demos: - Bound 2, E.K.O system (memory access timings) - Bound 3 (Videl video counter) - Agony & Chaos A.D. don't freeze anymore (vertical frequency counter) - Mahabharata (Videl widths with SDL2) - 2x1287, A Rh positive, Are you experienced, Autowaschen Verboten, Bound, Bound 42, Codein, Cycedelic knockout, Dan's Lustiges Kinderfest, Derealization, Dream Dimension, Echos, E.X. Illusion, Geranium, Hex Pistols, Hmmm, Oergs, Polygon Discount, Terrorize your soul, Warum, Virtual City (68030 instr-cache) - Terrorize your soul (DSP warnings 100x slowdown) - Firestarter, Virtual City, Whirlpool (autostarting with packed demos) Fixed TT versions of demos: - 4getful, Beams (68030 instr-cache, undocumented FPU constants) Fixed programs for Falcon: - Hextracker (Videl video counter, when not auto-started) Fixed games: - Le Necromancien' STX image (verify bit in type I commands) Fixed ST/STE demos: - Sprite32 by Leonard (RESTART_VIDEO_COUNTER on line 310/260) - Jam-Cols by ICE (STE video counter timings broken in v2.0) Fixed ST/STE programs: - Protracker 2.1 and Neochrome Master 2.8 (shifter, bottom border at 60 Hz) - Audio Sculpture 1.3 and 1.5 beta (timer B in mono mode, bit S=0 in STOP, IKBD) - True RAM's size detection under TOS/EmuTOS (memory config at $FF8001) Version 2.0.0 (2016-11-04) --------------------------- Emulation: - Machine: - Support for MegaST & MegaSTE machines added (this also replaces the Real Time Clock / RTC option, since this chip is only available on the Mega machines) - The general purpose registers of the TT SCU are now correctly emulated (this fixes the problem with AHDI not finding any partitions during boot) - HD: - Fix: Disable GEMDOS opcodes after GEMDOS drive is disabled - Fix: GEMDOS HD can now be initialized multiple times, e.g. when running EmuTOS RAM image from a normal TOS - Fix: WORD access to IDE data register at 0xf00002 - Fix: '*.*' pattern with GEMDOS HD should match also files without extension - Video: - Full rewrite of the GLUE state machine, including support for the 4 wakeup states in STF mode, as well as more accurate video timings (hbl, timer b, top/bottom and left/right removal, mixing 50/60/71 Hz lines) - Correctly shows the last 8 pixels for STE 224 byte overscan - Fix: reading video counter $FF8205/07/09 after modifying it while display is ON - Fix: location of the video counter's restart when screen runs at 60 Hz - TT: sync ST & TT color registers immediately on write, handle palette bank setting correctly, fix duochrome colors - Falcon: handle byte access special case for ST color registers - Falcon: border color support also in HiColor - better hbl/timer b interrupts when CPU runs at 16/32 MHz - Sound: - Special cases for STE DMA sound when start address = end address - Increase STE DMA volume when compared to the YM2149 volume - Greatly improve Falcon DMA sound - CPU: - use WinUAE CPU for STF/STE mode too, not just Falcon (old CPU core is considered deprecated) - improved IACK and simultaneous interrupts - access IO regs on 2 cycle boundaries when possible - More accurate 68060 mode (instructions from previous CPU versions removed in 68060 aren't accepted/emulated anymore) - Misc bus error / IO mem register handling fixes for TT & Falcon (e.g. add TT DIP switch register handler) - Blitter: - Better bus arbitration when blitter is started - Don't cause bus error when accessing regions causing CPU bus error - Fixes to DSP addressing - Floppy: - Fix the value of the WPT bit when inserting/ejecting a write protected floppy Emulator: - Generic fixes: - Autosave to already existing file works first time after v1.4 (dialog for file overwrite got automatically canceled on exit) - SDL GUI: - MegaST/MegaSTe support - Shortcuts can be configured in the keyboard dialog - User can provide volume label when creating a floppy image - Show dialog on HD image size issues - Better SDL2 support: - SDL2 is now enabled by default - Resizable windows, including ST/STE screen scaling - "--desktop" option is used also for ST/STe instead of "--desktop-st" - Command line: - Renamed --fpu-type option to --fpu - Configuration: - Hatari defaults to ST both with oldUAE & WinUAE CPU core - Preferred private configuration directory is now ~/.config/hatari on Linux/Unix (the legacy location ~/.hatari is still used if the other one does not exist yet) - Windows config location is now: AppData\Local\Hatari - Support SDL key names in keymap files in addition to key codes - Comment characters (#, ;) can also be mapped in keymap file - Added NatFeats SCSI (NF_SCSI) driver for Linux version of Hatari - Debugger: - Fix: close debugger log file only when explicitly requested, not when continuing emulation from debugger - Fix: direct DSP register, disasm and memdumps to debugger log file, similarly to CPU - Fix: direct CPU and DSP symbol traces to trace file, not stderr - Fix: ':once' option when there's only one breakpoint (v1.9 regression) - Fix: NatFeats NF_DEBUGGER command now actually drops to debugger - 'n' (next) command run until (dbcc backwards branch) loop exits (in addition to running until subroutine and exception calls return) - Support Atari debugger XBios(11) / Dbmsg() API - 'variables' / 'v' command to list Hatari debugger's builtin symbols - Also trace exception can be caught - Removed features: - Support for rendering to 8-bit host screens - Support for (buggy) RsConf() interception with --bios-intercept Other: - Allow building without Zlib - zip2st tool can convert directories to .st image files, not just .zip files. Fixed games: - Chainz and Jewelz by Paradize (blitter, bus errors) Fixed demos: - spec512 image in the Intro of the Place To Be Again (video, restart counter) - Menu screen in the Place To Be Again (video, writing/reading video counter during active display) - A Little Bit Insane by Lazer (no DMA sound during the demo) - LoSTE and Closure by Sync (video, wakeup states and glue timings) - Death of the left border by TNT (video, stabilizer) - Gen4 Demo by Ziggy / Overlanders (video) - Suretrip 49% by Checkpoint (cpu, exceptions stacking) - Tymewarp by YM Rockerz (megaste mode, cpu control at $ff8e21) - overscan plasma in Graphics Sound 2 in Relapse by Cybernetic (blitter) - RGBeast by Aggression (blitter) - Drone by DHS, PhotoChrome Viewer by DML (video, last 8 pixels) Notes: TOS NF_SCSI driver is available from: https://www.hddriver.net/en/downloads.html Version 1.9.0 (2015-09-10): --------------------------- Emulation: - STE Joypads: - Fix: Joypad A Option button - Fix: Joypad B extended buttons - ACSI / IDE: - Fix: image file attribute check when using device files - Fix: v1.8 ACSI regression with A1=0 case - GEMDOS HD: - Fix: matching exactly 8 chars long file names containing '.' - Better mapping of host errors to GEMDOS error codes - Support for mapping file names with 8-bit characters between host and Atari encodings (umlauted chars etc) - Program header (TT-RAM) allocate flags support - CPU: - update WinUAE CPU core version from 2.3 -> 2.8.1 -> 3.0 -> 3.1 - MMU emulation fixed - instruction/data cache emulation - better 68020/30 prefetch pipeline - cycle accuracy, etc. - in 68000 mode, remove some un-allowed for CMPI, BTST and TST - check for address error when new PC is set at the end of RTE, RTS and RTR - fix "move.b an,", it's not allowed and should give illegal instruction - improve stack frame for bus error and address error - allow bus control only for 030 (i.e. prevent TOS forcing 16Mhz at boot with higher CPU levels) - Memory: - TT-RAM / 32-bit addressing support both for TT & Falcon emulation (when using EmuTOS, this requires version 0.9.4 or later) - FDC changes: - for STX disks, fix type I commands with verify bit on tracks with no sector - for ST/MSA, check read address and read track are not beyond max track - MFP: - better emulation of GPIP, AER and DDR - MIDI: - some RX/TX interrupt conditions were not correctly handled - TDRE bit is status register more accurately handled - Video: - fix value when reading video counter $FF8205/07/09 in high res - Blitter: - when transfer ends, hog bit should be cleared in control register - DSP: - better emulation of the HREQ signal Emulator: - Support for compiling with libSDL2 (experimental) - SDL2 supports other than 2x scaling factors for ST/e emulation - Misc fixes: - Fix: WinUAE CPU core (FPU) memory state restore - Fix: crash with VDI extended resolution emulation when C: isn't GEMDOS HD emulated drive - Fix: invalid defaults for real joysticks - Fix: compilation when zlib is missing - Fix: bugs from Debian bug tracker: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=716536 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=716084 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=688594 - Additional TOS run-time patching: - Support fast boot also with TOS v3 & v4 - Support 32-bit addressing also with TOS v4 - Replace TOS v4 instructions unsupported on 060 - HW specific phystop value in VDI mode - Options: - Fast boot (warm boot / memory check bypass) is disabled by default - 68060 CPU level and --ttram support with new WinUAE CPU core - New --png-level option to decrease AVI compression CPU load - Mouse warping on reset & resolution change can be controlled with new --mousewarp option - Hard drive emulation: - BUS ID for ACSI drive can be specified with --acsi - GUI and command line options to enable 8-bit file name Atari <-> host charset conversion for GEMDOS HD - GUI and command line options to specify GEMDOS HD emulation drive (default = C:), or to skip recognized drives used by partitions on ACSI & IDE images - Partition count parsed by Hatari might not match count used by Atari HD driver due to driver differences in interpreting disk MBR - Refuse to mount same HD image from multiple Hatari instances (on platforms supporting BSD flock()), to avoid corrupting them - SDL GUI: - joystick navigation in options GUI - keyboard navigation & shortcuts in options GUI - sort file selector items case-insensitively with folders first - Partial support for showing Latin1 chars from UTF-8 file names - AltGr+F<1-4> keyboard shortcuts to switch mode/type for emulated joysticks and joypads - User can reset or quit emulation from CPU halt dialog - Fix: sync joystick changes with statusbar - Fix: update default max window size for 2-line statusbar NOTE: size would need to be updated in Hatari config files too! - Fix: statusbar assert when FPU/MMU/TT-RAM are enabled in WinUAE CPU core - When cpu enters 'halt' state in case of double bus/address errors, show a dialog window to reset or call the debugger - Fix bug in the filesector dialog that could create bug in other dialogs used in Hatari - Debugger: - Fix: crash when GEMDOS tracing is used without GEMDOS HD emulation - Fix: update external disassembler's CPU mask when CPU type is changed (it wasn't restricted to opcodes valid for given CPU type) - Fix: breakpoint addition/removal within chained breakpoints - Fix: Atari program detection on Windows for "symbols" command - Profiler support for TT-RAM (uses *ton* of memory) - Profiler support for (030) data cache and new "profile caches" command to show i/d-cache hit/miss histograms - Add trace support for IDE, MIDI and keymaps. ACSI (SCSI) and IDE trace output shows primary partition tables for ACSI & IDE images (both Atari and DOS MBRs are supported) - Add "os_base" trace option to trace Fopen/Fclose/Pexec/Pterm*. That and "os_all" will now enable also xconout console redirection - Output from --conout goes now to stdout instead of stderr - Add "reset " command - Add "save" subcommand to "history" command - Add "basepage" address variable Other changes: - Add script to convert long host file names to Atari file names, use that in atari-hd-image script - Add hatari-prg-args script for providing arguments to autostarted programs - Fix Atari program detection on Windows in "gst2ascii" tool - Hatari & disk image icons in SVG & PNG formats (multiple sizes) - Remaining Hatari OSX UI changes for Hatari v1.8.0 (localization) - Add video-recording.txt doc on video recording/uploading best practices Fixed demos: - Graphix Sound 2 in Relapse by Cybernetics (blitter+cpu, bus arbitration) - RGBeast by Aggression (video, writing to video counter during active display) - My Socks Are Weapons by Legacy (video, reading video counter in high res) Fixed games: - Superior 65 - Blood Money (cpu, exception stack and bus error) (note that this version is bugged and will crash with TOS 1.04 or 1.62) - Obitus (STX version) (fdc, seek+verify on a track with no sector) - The Teller (STX/CTR version) (cpu, exception stack and address error) - War Heli (cpu, exception stack for address error and prefetch) Fixed programs: - Realtime and M by Eric Ameres (midi + mfp, toggle bit 0 of AER) - Notator (midi, more precise TDRE bit in status register) - Cubase 2 (midi, reported by user) Known regressions (see compatibility list): - Suretrip demo by Checkpoint - Golden Island game demo with WinUAE CPU core - TT-version of 4getful demo with WinUAE CPU core Version 1.8.0 (2014-07-30): --------------------------- Emulation: - ST video changes : - Handle 0 byte line by switching freq in STE mode - Use high res when ff8260 is set to 3 - Randomly return 0 or 1 when reading unused bits 3,7 and 11 of color registers (except when running from ROM) - Better detection of bottom border removal on a 60 Hz screen - support for color change during only 4 cycles - better bitmap/color alignment for STE med res overscan at 60 Hz - Set default value of nSpec512Threshold to "1" instead of "16" - Update the position of the next VBL when 50Hz and 60Hz lines are mixed - When video address is set into IO region, keep the video pointer on 24 bits - Videl changes : - correct masking of the true color palette registers - CPU changes : - Fix a case when MFP's interrupt happens during the IACK sequence for HBL/VBL - Many prefetch changes required by some game's protection - Sound changes : - Fix STE sound mixing using LMC when mixer=0 (DMA sound only) - Fix some crossbar's sound errors for Falcon - Fix Microwire's decoding of mask/data (based on official LMC1992's doc) - FDC changes : - Add configurable RPM speed for each floppy drive, the possibility to turn each drive ON/OFF and the choice between single or double sided drive - Correct timings, behaviour and status register for all commands when a drive is OFF or empty. - Rewrite the index pulse part and all the delays expressed as disk's revolution - Add support for the "Force Int on Index Pulse" command - Rewrite the DMA functions for better accuracy - Add support for IPF/CTR files by using the capsimage library - Add open source support for Pasti STX files, including random/fuzzy bits and variable length bits. Also allow to save writes to a .stx file into an additional .wd1772 file (store 'write sector' and 'write track' data) - Don't force drives A and B at $4c2, keep the values detected by TOS - Many other timing / status register improvements - GEMDOS HD emulation: - Fix: clip filenames given through GEMDOS calls to 8+3 length using first '.' - Fix: autostarting programs with GEMDOS-illegal host characters in their names - ACSI hard disk changes: - Support multiple ACSI devices - Fixed/improved READ CAPACITY and INQUIRY commands - Unsupported commands are now handled correctly - Now using fseeko() instead of fseek() for supporting large images - Other changes: - Don't enable MegaST's real time clock by default, it can cause some crashes in STF/STE mode - Improve ACIA's wait state with E clock - Add support for IKBD commands 0x11 and 0x13 (resume/pause output) and 0x17 (joystick monitoring) Emulator: - SDL GUI: - Update clock speed in the status bar when changing bus speed in Falcon mode - In the floppy dialog, use a checkbox to enable/disable drives A and B and another checkbox to choose single/double sided drive - In the screen dialog, add checkbox for --desktop-st option - Add a 2nd line in the status bar, with infos on FDC, joysticks, monitor - Fix OSX performance issue caused by statusbar and overlay LED doing their own SDL_UpdateRects() calls - New command line options: - Options --drive-a and --drive-b to enable/disable drives A and B - Options --drive-a-heads and --drive-b-heads to select single or double sided drives A and B - --slowdown option to increase (multiply) VBL wait times - New Native Features commands: - NF_EXIT exits emulator with given exit code - NF_DEBUGGER invokes Hatari debugger from native program - NF_FASTFORWARD sets fast forward on/off - Tracing improvements: - Add option for tracing NatFeats calls - Fix: Xbios(255) modified the given argument string - Allow BIOS, XBIOS, GEMDOS, VDI and AES tracing to be enabled without enabling also X/BIOS interception, GEMDOS HD emulation or extended VDI mode - Show PC value for traced Bios/XBios/GemDOS calls - Debugger: - Fix: release mouse in all cases where debugger can be invoked - Fix: DSP disassembler didn't in all cases show illegal opcodes correctly - Fix: "symbols" command crash when it was used during TOS bootup - Fix: TOS and cartridge addresses weren't (with all TOS versions) in address order which asserted in profile data post-processor - Fix: "next" command didn't work correctly in "hex" number base - Fix: depending on compiler/linker/assembler, DATA/BSS debug symbol offsets can in programs be either relative to their own, or to TEXT section start. If former fails, try latter when loading symbols. - New "ym" info subcommand to show YM register values - "quit" command by-passes exit confirmation dialog and takes an optional Hatari exit code value - removed limits on breakpoints and their conditions - when entering debugger, current program's DRI/GST format debug symbols are automatically read from it, if such are available - new --debug-except option to specify which exceptions invoke the debugger, and WinUAE CPU core support for -D toggle option - "next"/"dspnext" commands work like in other debuggers, they skip subroutine/exception calls, for all other instructions they works like "step" command (earlier they always moved to next instruction in memory) - "next" and "dspnext" commands support optional "instruction type" argument, which can be used (for example) to continue emulation until subroutine returns - "history" command takes optional parameter specifying how many instructions of history will be tracked - CPU & DSP profile commands have now subcommand for profiling loops - "CpuInstr" and "DspInstr" variables for count of instructions executed since emulation was last continued from debugger - In addition to "!" condition, also "<" and ">" conditions can store the checked value (when both sides of conditions are identical). With above change, this can be e.g. used to detect and profile what are worst frames in games and why - New "rename" command to rename files. Useful for scripted worst frame and spinloop profiling - Address space in 'dm' command can be given like in DSP disassembly, "dm x:$100", in addition to earlier "dm x $100" syntax - Fix out of bounds memory access that could result in bad disassembly output when using the external disassembler (only "dc.w" were printed) Tools: - Increased max partition size in atari-hd-image script to 512MB. - New hatari_spinloop.py script for post-processing loop profile data. It can tell how many times loops were executed, how many times they spinned at minimum and maximum, at which VBL those happened, and what was the standard deviation of that. - gst2ascii supports now both section relative and TEXT section relative DATA/BSS symbol offsets - hconsole.Main() takes now Hatari command line options as argument - AHCC/GCC/VBCC examples on using (all) Native Features APIs - Improved mingw cross-compilation support Fixed demos : - 4-pixel plasma screen by TOS Crew (video, color change during only 4 cycles) - HighResMode by Paradox (video, color changes in overscan med res at 60 Hz) - It's A Girl 2 by Paradox (video, bottom border removal on a 60 Hz screen) - Pacemaker by Paradox (microwire, YM sound was muted) - Panic by Paulo Simoes (video, was broken since Hatari 1.7) - Shforstv.exe by Paulo Simoes (video, did not work in STE mode) - Sommarhack 2011 Invitro by DHS (CPU/MFP, top border not removed) - Stax Compilation #65 (conflict with Mega ST's RTC) - The Union Demo (cpu, memory access, ikbd, protection) - The World Is My Oyster - Convention Report Part by Aura (video, res=3) - Tymewarp (cpu, bus error) - UMD 8730 by PHF (video, unused bits in STF color registers) - IKBD no jitter by Nyh (acia/video, use a VBL of 160240 cycles) Fixed games : - Batman The Movie (cpu, exception stack) - Chart Attack Compilation (cpu, prefetch) - Darkman (cpu, prefetch) - Dragon Flight (cpu, illegal instruction) - International 3D Tennis (cpu, prefetch) - Leavin' Teramis loader text (video address at $ffe100) - Lethal Xcess Beta (cpu, bus error, code running at $ff8800) - Lethal Xcess (mfp, top border not removed sometimes) - Maze (by Martin Dennett & John Parker) - Parasol Stars (cpu, prefetch) - Reeking Rubber (by Nature), Falcon game - Titan (cpu, prefetch) - To Be On Top (cpu, memory access, ikbd, protection) - Warp (ikbd, interrupt used for decoding sectors) - Xenon 2 (cpu, prefetch) Version 1.7.0 (2013-06-24): --------------------------- Emulation: - TT video emulation : - Extend the ST palette registers from 9bit to 12bit for ST modes - Duochrome mode instead of monochrome for ST High - Implementation of SampleHold in TT Hypermono video mode - Sound changes : - Better model of the YM2149 noise generation (no noticeable audible impact) - Slightly better volume table (measured by Paulo Simoes on a real ST) - CPU changes : - Added experimental MMU emulation for the 68030 mode - Handle the IACK cycles during an exception for HBL/VBL - ACIA/IKBD changes : - Full implementation of the MC6850 ACIA, giving much more robust IKBD emulation - Better timings for all IKBD commands (measured on a real STF) - Clock is handled internally in IKBD, host date/time is used only to initialize RTC at boot. I.e. pausing emulation doesn't cause time skips when unpausing - Fix slow mouse when absolute mode is used with scale values - MFP changes : - Fixed priority errors when simultaneous interrupts happened - MFP's IRQ should be delayed by 4 cycles before reaching the CPU - MFP's IRQ signal was not correctly updated in some cases - Handle the IACK cycles between CPU and MFP during an exception - FDC changes : - Better delays for all the GAPs inside a track - Emulate the disk's rotation and its angular position to get accurate delays when accessing a sector header - Internal timer for FDC was wrong and sometimes slower than expected - Correct delay for type I commands with "verify" bit=1 - GEMDOS HD emulation changes : - Support Fforce() for GEMDOS HD emulated files and close at Pterm*() all internal handles to emulated files program had left open - Fix: cut file and dir names to 8+3 chars like all TOS versions do - Fix: GEMDOS HD emulation overwrote last 28 bytes of basepage space for the started process command line (with the program header) - Fix: Fwrite() content wasn't flushed to disk on each write, so later Fopen() + Fread()s calls on such files (if they were not closed in the meanwhile) could get data that was out of date. - Fix: Dfree() return value Emulator: - Fix: problems with run-time CPU type changing - Fix: MIDI IRQs were not enabled if MIDI was enabled at run-time. MIDI toggling will now imply reset - Much improved console redirection functionality and a separate --conout option for it (--bios-intercept doesn't anymore imply console redirection) - Fixed a bug with the axes' mapping in the joystick's detection - Display handling: - Fix: max resolution limit wasn't handled properly for ST mode. - Don't change TOS-calculated font size for VDI mode. - Setup NVRAM video mode based on VDI mode, when in VDI mode. This makes VDI mode work with EmuTOS also under Falcon emulation which is useful as EmuTOS doesn't support native VIDEL expanders - Limit VDI screen memory usage to 300kB, this allows up to 2048x1200 monochrome and 1024x600/800x768 16-color VDI screens - GEMDOS HD emulation: - Fix: to DTA host filename cutting to 8+3 chars when whole filename was < 12 chars (caused EmuTOS crash) - Give warning if GEMDOS dir/file path exceeds 8+3 characters (as those won't work with real TOS) - New --gemdos-case option to specify whether new dir/filenames are forced to be created in upper (default) or lower case - Giving empty string as GEMDOS HD dir disables GEMDOS HD emulation - SDL GUI: - Fileselector has "CWD" button for changing to Hatari's work directory - Fileselector remembers previous position which is nicer with large directories - Make the drive leds blink with a brighter green when the FDC is executing a command - Profiler: - Fix: profiler assert on invalid PC register values - Fix: profiler usage in debugger files invoked by breakpoints - Fix: profiler CPU & DSP cycles information is for previous instruction - Fix: WinUAE CPU requires cycles counter usage for getting current CPU instruction cycles - Profiler provides timing information, based on used cycles - Profiler provides CPU instruction cache misses information when using cycle-exact WinUAE CPU core (which is Falcon emu default) - Profiler top instruction count/cycles/misses lists show the related instructions - For DSP, profiler adds min/max cycle difference info to disassembly - New "addresses" subcommand can be used to see addresses through which code passed during profiling (e.g. while program seems frozen, this gives much better overview of code that is being run, than cpu trace) - New "callers" subcommand lists all addresses from which loaded symbols' addresses were "called" from. This way one can e.g. find in which contexts interrupt handlers were called and it can be used to construction program execution callgraph - New "save" subcommand saves profile address and caller information to given file, with extra information needed for post-processing - New "stack" subcommand that can be used to get backtraces during profiling with symbols, for ':noinit' backtraces - Debugger: - New ':noinit' and ':quiet' breakpoint options. Quiet option removes most of breakpoint matching "noise" and ':noinit' breakpoints can be used with "profile stack" command, to prevent profile data and related callstack information from being reset on breakpoint match - "blitter" and "dsp" subcommand added to "info" command, to show blitter register values and DSP state (e.g. stack content) - "prg" subcommand added to "symbols" command, for loading debug symbols from DRI/GST symbol table in last started program - "step" and "next" commands added for single stepping CPU and DSP code -> 's' shortcut is now for "step", not "save" - "--disasm" option for selecting between UAE core and external CPU code disassembler and setting output option flags for latter - When -D option is used, also undefined/illegal DSP instructions and DSP stack under/overflows invoke debugger, not just CPU issues - Fix: when several breakpoints should have triggered on the same address, only first was handled - Fix: after breakpoint, 'c ' continues one instruction too little - Fix: tracking breakpoint values were updated only when all conditions matched, now they can be also used together with other conditions - Fix: info command crash (triggered on NetBSD) - Fix: disassember output bug on NetBSD (unsigned char < -1 test) - Fix: info osheader and basebase subcommands under MiNT - Fix: debugger history duplicates removal - Fix: expression expanding CPU "pc" for DSP shortcut commands - Fix: expression expansion messing lines in command line history - Remote API debugger commands can also use expression expansion - Both single and double quotes can be used to mark expressions - In addition to text section offset, data and bss section offsets can be given when loading symbols (useful for Devpac symbols) - Output what value was set by options if it's not otherwise shown by UI - New 'cpu' and 'dsp' options to 'history' command for tracing just one of these processors on Falcon - Function arguments are shown in traces for all non-MiNT GEMDOS calls and subset of arguments are now shown also for AES calls (values in intin array and strings in addrin array) - Add NVRAM read & write tracing - Support for the basic Native Features and --natfeats option to control it: http://wiki.aranym.org/natfeats/proposal Windows specific changes: - "-W" option added for opening a console window. Like on all other platforms, "-D" will now just toggle CPU exception handling - Fix: console stderr redirection (used invalid "wr" mode) - Fix Windows localtime() not supporting dates before 1970: - TOS not being able to access files with such dates, or further files - causing IKBD emulation crashes (wasn't problem with Cygwin, just with Mingw builds) Tool updates: - New hatari-profiler.py script for post-processing output from profiler's CPU and DSP profiling "save" commands: profile save dspprofile save - With symbol address information it can provide function level instruction, processor cycle & i-cache miss statistics - With callers information it can create function call callgraphs and create callgrind format files for Kcachegrind GUI - New gst2ascii tool to extract DRI/GST symbol table from Atari program, for use with the profile data post-processor - Added scripts for converting symbol tables in Devpac 3 listings and DSP LOD files to a format understood by Hatari debugger 'symbols' command, and to clean 'nm' output of GCC & VBCC built a.out binaries for it Other changes: - HTML documentation indices generated dynamically with JS - Debugger tests building fixed Fixed Games: Atomix (MFP, flickering bottom of the screen during samples) BBC 52 Menu (Video/CPU, top border not removed) Bolo (MFP, couldn't start a game) Captain Blood (IKBD, fixed problem when setting the clock) Fokker and Downfall (ACIA/IKBD, had regressed in Hatari v1.6.x) Fuzion CD 77/78/84 Menu (MFP, random crash with digidrums in STF mode) James Pond (Fuzion CD 25) (FDC, game stuck during the intro) Microprose Golf (FDC, crash during the intro) Spidertronic (Zuul CD 84) (crashed before 50/60 Hz screen) Super Hang On and Super Monaco GP (MFP, flickering rasters) The Final Conflict (MFP, locked during the sampled intro music) The Sentinel (IKBD, mouse much too slow) Zuul 100/101 Menu (MFP, flickering top border) Fixed demos : Anomaly Demo Main Menu by MJJ Prod (MFP, flickering top/bottom borders) Audio Artistic Demo by Big Alec (MFP, bad sample speed at start) Decade Demo - Reset (MFP, flickering bottom border) High Fidelity Dreams by Aura (MFP, flickering left rasters) ST-NICCC 2000 Demo by Oxygene (FDC, demo ran slower than expected) Fixed apps : Monst v1.x Spectrum 512 (IKBD, mouse too slow) Cubase (when MIDI was enabled after Hatari startup) Version 1.6.2 (2012-06-24): --------------------------- Emulation: - ST video changes : - Fixes a rare potential crash when running in color mode and switching to monochrome mode for more than one VBL (eg : protection code used in The European Demos and The Transbeauce II Demo) - add more timings for the 224 bytes overscan detection on STE - Correct write timing for BCHG/BCLR/BSET when removing borders - Fix to top/bottom border removal in a rare case - IKBD/ACIA changes : - Handle commands 0x12 and 0x14 sent during the IKBD reset (enable both mouse and joystick reporting at the same time) - Handle the TX IRQ in the ACIA (bits CR6+CR5=0x01) - Implemented IKBD set-clock function - Sound changes : - Improve YM2149 sound filtering to be closer to a real STF - Increase output volume for STE DMA sound (compared to the YM2149's volume) - DSP changes : - Fix to DSP stack overflow handling - Fix DSP to be reset on emulation reset - Old UAE core specific changes : - Changes in the prefetch code for some instructions - Correct PC in stack when JMP generates an illegal address exception - WinUAE core specific changes : - 68040 MMU emulation fixes - Fix GEMDOS/VDI emulation (illegal opcode) handling - Fix WinUAE core to work with ST emulation (boot TOS 1.x) - Fix FPU to be enabled when switching to TT emulation - Fixes to FPU register value conversion - Falcon changes : - Support for Falcon/Videl screen borders - Fixes to few Falcon IO registers - Changed behavior of the Microwire registers in Falcon mode - Misc changes : - IO registers can only be read in supervisor mode - Ignore FDC commands when no drive is selected - Fix TT SCSI register reads to return all bits zero instead of set - Fix for GEMDOS HD emu to direct special CON:/AUX:/PRN: device files to TOS instead of trying to handle them as normal files - printer output is single, not double buffered Emulator: - RS232 input&output and printer output can be disabled from command line by specifying an empty path - Fix loading of memory snapshot from SDL GUI not to leave host cursor enabled - Video changes: - Increase max allowed VDI resolution from 1280x960 to 1920x1200 - Revert preferred Videl resolution max zooming size setting from host desktop resolution back to 832x576 it was in Hatari v1.4, so that Falcon/TT emulation window sizes by default are closer to ST/STE ones on larger resolution monitors - Fix statusbar assert - Add --sound-sync option to keep video synchronized with the audio in case the OS audio's driver has some latency issues - WinUAE core specific changes: - Default to Falcon with WinUAE core (old CPU core still defaults to ST) - Support run-time changing of CPU level, FPU type and machine type from the GUI (also) with WinUAE CPU core - Debugging improvements: - Fix to debugger CPU cycle profiling modifying CPU state - Profiling info is shown at end of disassembly lines - Support for tracing all symbols loaded to the debugger, this can be used to get function traces for both CPU and DSP - Show args for all Bios and most XBios & Gemdos calls when tracing - "info" command can output opcode tables for BIOS & XBIOS too - More info to FDC, IKBD, Videl and Crossbar traces Other changes: - Hatari OSX UI updated for Hatari v1.6.1 changes - Hatari Python UI supports file paths with spaces in them and spaces in Hatari options given through its control socket can be quoted with '\'. - Support for alphanumeric characters in Hconsole "text" command - Fixes to Hatari UI and hconsole error handling and examples - Fixed hatari-local-midi-ring.sh & hatari-local-rs232.sh arg handling - zip2st removes intermediate directories from created floppy - Fixed hmsa to handle files with multiple "." characters in their names - TOS tester testing covers more GEMDOS functionality, works with all Hatari supported TOS versions and HW configurations and it's fully automated Fixed Games : Hammerfist (fire button), Automation 168 - Operation Clean Streets (prefetch in the CPU emulation) Impossible Mission II (some versions had the same prefetch issue in the CPU) Hades Nebula (fire button) Zombi (IKBD set-clock was missing) Fixed demos : Built-in Obsolescence (DSP stack overflow) Japtro and Rising Force by Holocaust (FDC, buggy loader) Delirious Demos IV (video, STE detection) Antiques by Dune/Sector One (224 bytes STE overscan) The Wave Of the Future by ICE (STE flickering top border) Electrocution I by Sphere on Stax Menu 66 (STE flickering bottom border) Musical Wonders 1990 by Offbeat (video, bottom border not removed) Version 1.6.1 (2012-01-13): --------------------------- This version is mainly a bugfix for 1.6.0, where monochrome mode gave a black screen and was not usable. Emulation: - Fixes to bootup issues in monochrome mode - Better left border removal timings - DSP external memory access cycles taken into account Other changes: - Fix to allow build with Xcode 3.1.3/OS X 10.5.8 PPC - Several fixes & updates needed to Hatari UI & hconsole for them to work correctly with Hatari v1.6.x - Test programs added for testing TOS booting with different HW configurations and for finding out values needed in Hatari keymaps Fixed Demos : Vodka Demo - Kill The Beast 2 (left border removal) Version 1.6.0 (2012-01-01): --------------------------- The Hatari project has been moved from hatari.berlios.de to http://hatari.tuxfamily.org/. Please update all bookmarks! Emulation: - More accurate FDC emulation (correct status bits and commands' timings, DMA transfer by blocks of 16 bytes, floppy change detection). This should fix a lot of non working games - More accurate microwire clock emulation - SCSI class 1 (ICD) command support for drives > 1 GB - Improved color conversion table so that colors are a little bit brighter - Improve shifter (add another method to do 4 pixel hardware scrolling, better emulation for 0 byte blank line) - Some fixes to the IKBD emulation - Better filters and model for sound emulation - Correct VBL timings in TT monochrome (double clicking works now) - More cycle accurate Falcon DSP <-> CPU emulation. All the demos that needed 32Mhz CPU with the old CPU core in Hatari v1.5, work now at correct 16Mhz with the WinUAE CPU core - 030 MMU emulation with the WinUAE CPU core Emulator: - Switch to ST mode when using TOS <= 1.04 - Replace "--slowfdc" with "--fastfdc" option and default to fast FDC being OFF - "--fast-boot" option to initialize "memvalid" system variables to by-pass the memory test of TOS, so that the system boots faster - "--force-max" option to force Hatari use specified maximum resolution to avoid window size changes messing up Hatari video recording - "--desktop-st" option to keep desktop resolution also for ST/STE modes (unfortunately without scaling besides the low-res doubling) - GEMDOS HD emulation: - Allow drives up to Z: (not Y:) - Unique name for each partition - Warn user when using too old TOS version - Dfree() reports host disk total and free size if they're below value understood by TOS and unlike earlier, it forwards Dfree() requests for other (IDE/ACSI image) partitions to TOS - Debugger improvements: - "history" command to list instructions executed before entering debugger - each trace output line is flushed to avoid it being buffered - Fixed behavior of the Caps Lock key Other changes: - Fixes to Hatari UI Hatari window embedding - Latest Linux sfdisk is borked so atari-hd-image script creates HD image partition table now itself (experimental) - Windows needs also HOMEDRIVE for full home path in case Hatari isn't installed on C:, bug 18297 - Minor fixes Fixed Demos : Overscan Demos and Shforstv.exe by Paulo Simoes (black line at top), ACF - Just Bugging (FDC), Delirious Demo IV (FDC, shifter), Overdrive Demos - Snirkel Screen (IKBD), Oxygene - Stniccc2000 (FDC), Cream - Madness (FDC) Fixed Games : Superior 65 - Super Monaco GP, DBug 24 - Knightmare, Pompey Pirates 27 - X-Out, Fuzion 32 - Pang, Fuzion 108 - The Simpson, Fuzion 40 - Super Grand Prix, Fuzion 46 - Warlock, Fuzion 51 - Navy Seals, Fuzion 61 - Gods, Fuzion 78 - Carmen Sandiego, Fuzion 82 - Flight Of The Intruder, Fuzion 83 - RBI Baseball 2, Fuzion 102 - Exile, PP46 - Yolanda, Medway Boys 15 - Murders In Venice, Medway Boys 83 - Yogi Bear, BBC 2 - Platoon, BBC 39 - The Deep, Superior 71 - The Running Man, Adrenaline 24 - Demon Blue, Superior 93 - Alien Storm Fixed Misc Programs : Procopy 1.50, Terminators Copy 1.68, maxYMizer (caps lock key) Version 1.5.0 (2011-07-19): --------------------------- Emulation: - Alternative CPU core based on WinUAE for more accurate future HW interaction emulation (see readme.txt on how to enable it) - Use precise clocks values (as described in Atari's official schematics) for better video/dma audio synchronisation (e.g. More Or Less Zero by DHS) - DSP: - Some DSP-timing sensitive Falcon demos that by luck happened to work with Hatari v1.4, don't work anymore in v1.5 with the default UAE CPU core. This is because while DSP cycle accuracy has been improved, the default UAE CPU core isn't fully cycle accurate. The experimental WinUAE core is needed to run them - Undocumented 2 bit shift special case for DSP SSI <-> crossbar exchanges in handshake mode with 32 Mhz clock (fixes DSP MP2 player used in many demos & programs, but that requires also using WinUAE core) - Sound improvements: - Major rewrite and accuracy improvements in STE DMA sound, including emulation of the 8 bytes FIFO, giving results nearly identical to a real STE (e.g. HexTracker by Paulo Simoes) - Improved precision in sound emulation, with nearly no rounding errors over successive VBL (correct sound latency on US TOS running at 60 Hz) - By default mix 3 YM voices using a lookup table of values measured on real STF to improve digisound (e.g. Flashback demo sound) - Remove old ST Sound's code used for tone and noise step compute (some low period values were not correctly emulated) - Video emulation on STF/STE: - On STE, correctly shift display 8 pixels to the left when using 224 bytes overscan - Add support for spec512 mode in med res (fixes 'Best Part Of The Creation' in 'Punish Your Machine', 'HighRes Mode' demo by Paradox) - Correctly shift the screen 4 pixels to the left when left border is removed in med res overscan (Hatari 1.4 handled only low res, fixes 'No Cooper' by 1984, 'Best Part Of The Creation' by Delta Force) - Precisely emulate the number of frames per sec (eg 50.053 fps in PAL instead of the usual 50 Hz) Emulator: - Atari program given as argument to Hatari will be automatically started after TOS boots. GEMDOS hard disk directory can now be give also as an argument, not just as a (-d) option - TOS4 or --machine falcon option use enables DSP emulation now (follow them with --dsp none to disable DSP emulation) - Memory state saving and restoring fixes, especially for Falcon - Crossbar state is included -> state file ABI break - AVI recording options can be set in the new [Video] config file section - AVI recording supports non integer frame rates. - Falcon/TT Videl/hostscreen improvements: - New setting/option for using Desktop resolution & scaling in fullscreen instead of changing the resolution. On by default - User's desktop size is used as max limit for Videl zooming. Requires SDL >= 1.2.10 - Videl resolution change is done immediately, not 3 VBLs late - Fix issues in switching between same sized VDI & TT resolutions - SDL GUI improvements: - DSP can be disabled from the GUI without needing to restart Hatari - Disk access LED and desktop-resolution options - AVI video length (mins:secs) is shown in titlebar during recording - Option for cropping statusbar from videos & screenshots - Fileselector scrollbar can be used with mouse - YM mixing method selection - Debugging improvements: - New disassembler with more Motorola like syntax - CPU & DSP "disasm" and "memdump" commands accept register & symbol names in addition to numeric addresses / address ranges - Option to disable Falcon mic ("--mic off" is needed for Mudflap debugging) - "--run-vbls" can be set also at run-time - "--bios-intercept" can be toggled from debugger (not just enabled) - BIOS CON: output is converted to ASCII and redirected to host console with the --bios-intercept option - Support for tracing DSP, Videl and Crossbar - Support for tracing AES calls. VDI calls can now be traced also without using an extended VDI resolution - BIOS/XBIOS/GEMDOS/VDI/AES/Line-A/Line-F opcode breakpoint support - TEXT, DATA and BSS variables for addresses of corresponding segments in currently loaded program - "aes", "vdi" and "gemdos" subcommands for "info". Without arguments they will output information about corresponding OS part state, with (a non-zero) argument, opcode/call name table is shown. "video" subcommand for showing video related information. "cookiejar" subcommand for showing cookiejar contents. - "file" subcommand to "lock" that executes debugger commands from given file when debugger is entered (or ":lock" breakpoint is hit) - ":lock" option to breakpoints that will show (without stopping the emulation) the same output as what's shown on entering the debugger - ":file" option to breakpoints that executes the commands from given file when the breakpoint is hit. This can be used to chain debugger actions - multiple breakpoints options can be specified per breakpoint - parenthesis in "evaluate" command are used to indicate memory accesses (instead of operator precedence like earlier) - DSP and CPU code profiling functionality. Provides statistics about profiled code (executed code address ranges, max and total counts and cycles), lists addresses/instructions taking most cycles and if symbols are loaded, what were the most used symbol addresses. - Profiling information is also shown in disassembly output Other changes: - hmsa tool can create empty disk images in addition to converting disks between ST & MSA formats - Minimal hatari-tos-register.sh Linux init script (example) to register Hatari as binfmt_misc handler/runner for TOS programs - hatari-console.py renamed to hconsole.py, documented and made extensible (hconsole is command line Python interface for Hatari remote API) - Support for plain Makefiles removed (except for internal tests), only CMake is used for configuring and building Hatari - CMake doesn't require anymore working C++, C-compiler is enough Version 1.4.0 (2010-06-12): --------------------------- Emulation: - IDE improvements: - Support for second drive (IDE slave) - WIN_FORMAT command (allows HD Driver to format IDE drives) - GEMDOS HDD emulation: - Minor fixes to Fseek(), Fopen(), Fdatime() (e.g. Pure debugger works) - On TOS v4 Fread() size arg is unsigned, on earlier TOS its signed (bad code can use -1L to read whole file instead getting fail on TOS4) - Prevent DTA and read/write functions accessing invalid memory areas - Programs can now change read-only files to be writable - Falcon sound emulation: - Microphone (jack) emulation in Falcon mode (requires portaudio library) - SSI direct sound entrance ("Audio Fun Machine" and winrec are working) - DMA sound recording - Crossbar handshake mode transfers - Max VDI rez increased to TT-hi size (1280x960) - 68020+FPU changed to 68EC030+FPU (no MMU 030) for Falcon and TT modes (Some Falcon programs didn't work with 020) - Video emulation on STF/STE: - correctly shift the screen 4 pixels to the left when left border is removed - add support for STE's 224 bytes overscan line without stabilizer - when reading $ff8205/07/09 on STE, take into account the value of horizontal scrolling/prefetch and linewidth - when writing to $ff8205/07/09 on STE, correctly handle the case where the write is made while display in ON - LMC1992 emulation / STE sound filtering Emulator: - Host mouse is centered to Hatari window on Falcon resolution changes (helps in synchronizing host and emulated mouse positions) - Toggling fullscreen doesn't unpause paused emulation - Falcon/TT resolution zooming is now controlled by separate options for monitor aspect ratio correction and maximum zoomed Hatari window size. This can reduce Hatari window resolution size changes significantly and makes e.g. FUN's Alive demo viewable in fullscreen mode. Limits for window size are also checked to see whether ST/STE low rez should be zoomed and how much of borders can be shown (when borders enabled) - Split the Screen dialog into two separate dialogs, one for Atari monitor emulation setup and one for Hatari window setup - GEMDOS drive emulation: - support long host directory names and much improved long filename support - convert host filename chars invalid in TOS to valid ones ('@') - use TOS filename matching instead of glob() (can match names with []) - Options for preventing floppy image (--protect-floppy) and GEMDOS emulated drive directory (--protect-hd) modifications - AVI file recording: - video can be stored as BMP or as PNG images - audio is stored as 16 bits stereo PCM - Statusbar shows CPU type & speed - Can create blank 2.88MB (ED) and 1.44MB (HD) floppy images from the GUI in addition to DD & SD images. After creating new floppy image, one can directly insert it to A: or B:. - Tracing for BIOS, XBIOS, GEMDOS and VDI traps gives in addition to the opcode, also the name of the corresponding OS function - Major debugger improvements: - TAB-completion for debugger commands, command arguments and symbol names. Requires readline library - "parse" command and --parse Hatari command line option to execute debugger commands from a file - "stateload" and "statesave" commands for memory snapshots - "trace" command for tracing what the emulated code does - "symbols" command for loading and listing CPU & DSP code & data symbols/addresses. Code symbols are shown on CPU & DSP disassembly and code & data symbols can be used in breakpoints - "evaluate" command for doing calculations. Register and symbol names in expressions are replaced by their values. '$' will be TAB-completed to last 'evaluate' command result - "cd" command to change Hatari work directory - "exec" command to execute shell commands (ENABLE_SYSTEM_DEBUG_CALL) - "info" command for showing Atari HW and OS information - "lock" command for setting what information is shown every time on entering the debugger, e.g. disassembly or memdump from given address. "regaddr" argument does that from address pointed by given register - improved register name handling + fixed DSP reg name matching - if both sides of conditional breakpoint condition are identical, replace right side with current value of given expression (e.g. if it's "d0", use current D0 value). If the comparison is for inequality ("!"), output the value & break only when the value changes from the previous value (not original like with other comparisons). Symbols support - ":trace" option to trace/output breakpoint hits without breaking - breakpoints count hits and can be optionally removed after first hit (":once" option) or triggered only on every Nth hit (":" option) - dsp/address command is a shortcut for conditional breakpoints and its argument can be an expression (see "evaluate" above) - Display DSP instructions cycle timings in disasm mode (in cycles) - Configuration options for how many lines to disasm & memdump - Fix VBLs/s counting to work also when --run-vbls isn't used Other changes: - Considerably expanded debugging and hard disk sections in manual - CMake build support, this fixes OSX building and adds support for building Hatari in different directory from the sources - Removed autotools usage/support, added CMake "configure" script - Hatari remote control programs updates (see their own release notes for details) Version 1.3.1 (2009-09-05): --------------------------- This is only a bug fix release: - GEMDOS HD emulation works together with ACSI HD image again - Fix incorrect use of DESTDIR in python-ui installation - Fix memdump/disasm in python-ui Version 1.3.0 (2009-08-16): --------------------------- Emulation: - Hugely improved DSP emulation: - Many more DSP using games/demos/apps work now - Preliminary sound support (e.g. most DSP based .MOD-playback works) - Better cycle counting / accuracy - Many speed improvements - Major rewrite of the internal work/structures of video.c : - Allow to mix 50/60 Hz lines of 508/512 cycles and to keep correct video/cpu sync (fixes TCB in SNY, DI in MindBomb, TEX in Syntax Terror). This also adds support for dynamic calculation of HBL/Timer B positions when freq/res are changed (fixes SHFORSTV by Paulo Simoes) - Improved Timer B accuracy when starting it in a rare case - Handle end of line as well as start of line for Timer B in event count mode (using MFP's AER) (fixes Seven Gates Of Jambala) - Add another 'O byte' line method (fixes No Buddies Land) - Some more color alignment with the shifter when using movem.w/movem.l (for spectrum512 like images) - Improved Blitter timings / cycles counting - GEMDOS emulation can emulate appropriately named host subdirectories as separate partitions - Bug fixes for GEMDOS HD emulation Fopen and Fseek calls Emulator: - DSP changes: - DSP state saved to memory snapshots - Threading support removed from DSP emulation (for better synchronization) - "keyDebug" configuration file setting was renamed to "keyPause" - Major debugger improvements: - Invoked with AltGR+Pause. New "keyDebugger" configuration file setting can be used to change this - Show PC/HBL/VBL/cycles when entering debugger - Support multiple number bases. By default values are expected in decimals; $-prefix is needed for hexadecimal and %-prefix for binary values. Default number base can be changed - Internal debugger can be used to debug also DSP code - Support for (PC) address breakpoints and conditional breakpoints (watchpoints), both on CPU and DSP. Watchpoints support multiple conditions, register & memory values and some internal Hatari variables like VBL, HBL, LineCycles, FrameCycles - Support for stepping CPU and DSP code - Emulated programs can now change Hatari options like --fast-forward, --trace etc. by giving a suitable Hatari command line string to XBios call 255. This is enabled when Hatari is started with the --bios-intercept enabled - Support Videl horizontal fine scrolling for 16 bpp and 32 bpp host screens - Process successive motion events before returning from event handler (to fix analog joystick jitter slowing Hatari input processing) - FPS measurement shown when emulation is paused & --run-vbls option - Mouse grab option (--grab) - Some fixes for building Hatari with MS-VC6 and for the Wii - Statusbar assert (bug #15512) fixed - Reworked the main dialog of the GUI and split the disk dialog into two separate dialogs, one for floppy setup and one for hard disk setup Utilities: - New atari-hd-image.sh script for creating HD image files - External Python GUI and CLI interfaces for Hatari in main repo Documentation: - Debugging and performance sections added to manual Version 1.2.0 (2009-01-24): --------------------------- - The Hatari project has been moved from hatari.sourceforge.net to http://hatari.berlios.de. Please update all bookmarks! - New zip2st.sh shell script for converting .ZIP files into .ST disk images - Fixed a bug that could write data to the wrong disk image (resulting in data loss) Emulation: - MIDI input supported in addition to output; --midi option is now replaced with separate --midi-in and --midi-out options - Support for STE hardware horizontal scrolling in medium res - Make the FDC Read Address command always return success, even if we don't really return the correct bytes for now (fixes a few game loaders) - Improved shadow register when writing to the YM2149 (fixes X-Out music) - Cleaner blitter code with improved timings - Emulation of interrupts jitter for HBL and VBL + improved timing accuracy - Improve color alignment with the shifter (for spectrum512 like images) - Fix to the fire button detection reported in some games - Added IDE hard disk emulation Emulator: - Pause/unpause shortcut Version 1.1.0 (2008-11-29): --------------------------- Emulation: - Falcon DSP emulation good enough to improve some few games/demos, e.g. Virtual City. (most still work better with emulation disabled, though) - New sound engine that fixes all problems with the old one - 16-bit stereo sound (instead of 8-bit mono) - Improved blitter emulation (blitter cycles emulation, blitter interrupt) - Improved STE support for some video registers (hscroll, linewidth, ...) - Improved printer emulation - Improved STE microwire emulation - Improved support for games & demos which are accessing IKBD directly (including a fake 6301 emulation for the known IKBD programs) - ACSI emulation fix to get HDDriver working - Some other minor bugfixes to ST/STe emulation (FDC, MFP, PSG, RS-232) - Improved MFP emulation - Improved 68k emulation (move.b Ax,(Ay) and extb.l) - Fixed bugs in the GEMDOS HD emulation (Pexec() etc.) Emulator: - Statusbar and overlay led features - Screenshots work also in VDI/TT/Falcon mode and are saved as PNGs - Support for automatic frameskip and pausing emulation - Support for embedding Hatari window (on X11) and control socket - Improved memory snapshot function - Improved the "trace" debug function Version 1.0.1 (2008-03-30): --------------------------- - This is just a bug-fix release, without new features. - Fixed some compile problems on non-unix-like systems (like MingW). - Fixed crashes in Spec512 emulation code ("Dan Dare 3" and little endian ARM). - Blitter source address is not incremented anymore in operation mode 0 and 15. - STE small overscan video effect is now displayed on the left side instead of the right side (fixes "Just Musix 2" menu for example). - Hatari now works on 256 color displays right again. - Fixed PSG mirror register emulation (fixes e.g. sample sound in "Ooh Crikey Wot A Scorcher" demo). Version 1.0.0 (2008-03-17): --------------------------- - The user's configuration files are now located in the directory ~/.hatari/ instead of the $HOME directory itself. - Improved VDI resolution mode (resolution can now be change in small steps). - The 'Frame Skip 8' option can now be correctly selected, too. - Fixed some bugs/problems in the GEMDOS HD emulation (with Fopen & Fcreate). - Keyboard shortcuts for saving and restoring memory snapshots. - Hatari can now be compiled with CeGCC, too. - Fixed some problems with the FPU emulation. NeoN Grafix renders now right. - Writing to floppy disk images works now also with TOS 4.0x. - A lot of source code clean-up and beautification. - Monochrome mode now runs in 71 Hz, and 60 Hz color mode now also really runs with 60 Hz refresh rate. - Fixed memory snapshot files (some important data has not been saved before). - It is now possible to automatically load/save memory snapshots at start/exit. - Fixed some bugs in the file selection dialog. - Some minor improvements in the GUI: Improved text edit fields, "Cancel" buttons can now be activated by pressing the ESC key, and Hatari asks the user before resetting and quitting the emulator. - The Hatari executable is now relocatable (so the RPM can be relocated, too). - It's now possible to enable special trace output with the "--trace" option. - The size of the borders can now be specified in the hatari.cfg file. - Fixed Spec512 screen plotting on big endian machines. - Native screen conversion functions for 32 bpp host display mode. - Reworked the command line options. - Added missing read for "clr" in 68000 CPU mode. - Cycle correct MULU/MULS/DIVU/DIVS in 68000 CPU mode. - Support for 68000 instructions pairing - Better emulation of exception stack frame (bus/address error), used in some protections. - Don't change illegal 68000 opcodes $8, $a and $c if no cartridge is inserted. - Ensure ACIA has consistent values when reset. - More precise interrupt handling, allowing to mix CPU cycles and MFP cycles with greater precision. - Various improvements in MFP emulation (stop/start timer without writing to data register, reading data register, handle pending cycles when timer "wraps" (i.e. data register reaches 0), ...). Supports programs using some very "fast" timers (Overscan Demos, ULM Demos) and requiring nearly cycle exact synchronisation with the 68000. - Mostly correct wait states when accessing sound registers (add wait state for $ff8801/ff8803 when needed). - Correct values of cycle counters read & write accesses for the most common cases used for fullscreen/hardscroll. - Correct values for Video_CalculateAddress, taking into account frequency and left/right borders' state, needed for correct synchronisation between video and cpu. - Improve top/bottom border removal, including 60 Hz bottom border, as well as "short" 50 Hz screen (171 lines) - Support for all left/right border removal, including 0 byte lines. - Support for hardscroll on STF, including the most recent ones using 4/5 lines. - Support for 4 pixels horizontal hardscroll on STF (ST Connexion in Punish Your Machine) - Small adjustments in cycle precise color handling (spec512.c) Version 0.95 (2007-05-12): -------------------------- - This release brings you basic Atari TT and Falcon emulation! Please note that both new emulation modes are still highly experimental, some few games and demos work, but most still have more or less big problems. - Basic emulation of Falcon video shifter (Videl), NVRAM and DMA sound is in place. The biggest drawback: There is no working Falcon DSP emulation yet. - Screen/Shifter emulation timings have slightly been changed. Some things now work better, some others work worse... - Some patches for compiling on RiscOS and AmigaOS have been included. - Compiling Hatari for Windows now works better. - Added Hatari icon (hatari-icon.bmp). - Fixed "movec" bug in 68020 CPU mode. - Keyboard shortcuts for loading & saving memory snapshots (AltGr+k & AltGr+l). - The built-in debugger has been slightly improved to be more user-friendly. - Added "hmsa" tool - a little program for converting .MSA files to .ST and vice versa. Version 0.90 (2006-08-22): -------------------------- - Better Spectrum 512 support (60Hz support, improved I/O memory waitstates). - STE right border opening support (used in Obsession, Pacemaker). - Blitter Smudge mode support (used in Pacemaker demo). - Wheel-mouse simulates cursor up and down. - Work-around to FDC handling, --slow-fdc option is not anymore needed. - Bugfix to MFP, sound works now in more YMRockerz releases. - Bugfix to GEMDOS path handling (Hatari SIGSEGV). - Bugfix to emulated memory initialization (4MB was cleared earlier, now exactly the amount set up for Hatari. Saves memory on embedded systems if less than 4MB is specified.) - Re-written command-line option handling. - (Again) lots of code const/static, type usage and indentation cleanup. - Preliminary support for TOS 3.0x and 030 TT software that runs in ST resolutions and doesn't need PMMU. - Native GUI for Mac OSX. - ACSI emulation fixes to get HD formatting to work with AHDI 5. HD emulation now works quite fine with AHDI 5 (but other HD drivers are currently not supported). - Joystick shortcut changed to toggle cursor emulation between ports 0 and 1. - Keys for all Hatari shortcuts can now be configured from hatari.cfg. - Added command line option for setting ST keyboard mapping. - Joystick command line option requires now parameter for a port for which the joystick cursor emu is enabled. - Fixed relative mouse event handling in zoomed low-rez. - Hatari shows now more of the bottom borden (screen size is now 384x276 instead of 384x267). - Fixed sync delay timings - sound should now be better (e.g. on Mac OS X). - Added basic support for compiling Hatari with MinGW. Version 0.80 (2005-10-12): -------------------------- - Support for STE hardware emulation: STE palette, STE shifter (horizontal fine scrolling, split screen effects), DMA sound and STE joypads. See the manual for a list of working STE applications/games/demos. - Hatari can now emulate up to 14 MiB ST RAM instead of only 4 MiB. - Support for parallel port joysticks. - Improved GEMDOS HD emulation (added Fattrib() call). - Adding and removing a GEMDOS or ACSI hard disk should now work correctly. - Re-factoring of the screen conversion functions. - Improved manual: Now with screenshots of the options dialogs. Version 0.70 (2005-06-05): -------------------------- - As always: Code cleanup and bug fixes. - No more crashes when a program tries to access illegal sector numbers. - Improved built-in ROM cartridge. - Rewrote the IO memory emulation code -> Better compatibility. - Support for TOS 1.06 and TOS 1.62 - Emulated CPU can now also be run at 16 MHz or 32 MHz. - File selection dialog scrollable with mouse wheel or cursor keys, too. - Hatari now works on 64-bit host CPUs, too. - Floppy disk images can now be set writable/write-protected in the GUI. - Hatari can now also load a global configuration file (e.g. /etc/hatari.cfg). - Configurable logging functions. Version 0.60 (2004-12-19): -------------------------- - Again some code cleanup and bug fixes. - Window/fullscreen mode is now correctly initialized from the configuration file. - Added --window command line option to force a start in window mode. - Added alert boxes to show warnings, errors and information messages. - PC mouse pointer is now better in sync with the ST mouse pointer. - It's now possible to load an alternative cartridge image file. Version 0.50 (2004-07-26): -------------------------- - A lot of internal code cleanup and bug fixes. - Added a dialog for creating new blank floppy disk images. - The source code has been optimized for better emulation speed. - Added RS232 emulation (still very experimental and not very well tested! It seems not to work reliable yet. Help for debugging is very appreciated!). - Some bugs in the 68000 emulation have been fixed. - The emulator now checks for double bus errors and stops the emulation if necessary (instead of crashing the emulator). - Timer-D is now patched correctly again. - The old font has been replaced by two new fonts so that the GUI now looks better in high resolutions. - The fonts are now linked into the executable. - Added support for DIM floppy disk images. Version 0.45 (2003-10-30): -------------------------- - This is just a minor release on the way to version 0.50. It is not very well tested, so be warned! - New build system (with a "configure" shell script). - A disk image destroying bug in the MSA compression function has been fixed. - It is now possible to redirect the printer output into a file. - Experimental MIDI output support. - Added the possibility to save memory snap shots. - Pending HBL and VBL interrupts are now emulated correctly (I hope). - Some speed improvements. - GEMDOS HD emulation now also works with EmuTOS. Version 0.40 (2003-07-11): -------------------------- - Support for ZIP and GZIP compressed disk images! - Configuration file support for loading and saving the emulator settings. - Hatari now works on machines with Sparc CPUs, too. - Fixed a problem that slowed down the emulator in monochrome mode when using TOS 2.06. - Inverted monochrome mode is now supported, too (some games like Maniac Mansion use this). - Added Mega-ST compatible real time clock (RTC) emulation. - The GEMDOS HD emulation has been improved (it now also works with lower-case file names) and many bugs have been fixed there. - Improved keyboard mapping (added mapping via PC keyboard scancode and via reloadable mapping files). - The screen rendering routines have been generalized (less differences between windowed and fullscreen mode). - Hatari can now be cross-compiled, too. You can even compile it for MiNT now. However, it does not run very well there yet. - Support for RAM TOS images. - Improved memory mapping (the different memory regions should now behave much more like on a real ST). - Improved M68k exceptions (bus errors and exception cycle timings). - Fixed some bugs in the extended VDI resolution mode (now it is working with EmuTOS, too). - Some games that poll the write-protection signal of the FDC to check for disk changes should now be working, too. Version 0.30 (2003-03-12): -------------------------- - Some parts of the code accessed the SR directly to read the IPL - however the UAE CPU core only updates the SR when doing a MakeSR() first. So this is done in the affected code parts now, too. - The IPL wasn't raised when a MFP interrupt occurred - fixed now. - Full screen resolution for ST-Low can now be selected from the screen setup dialog. - The IKBD emulation does not longer duplicate joystick fire buttons when a game tries to use both, joystick and mouse - Improved audio timer function - the code should now be a little bit faster. - Resynced Hatari's UAE CPU core with UAE 0.8.22 - this fixes some bugs in 68k instructions like ABCD and SBCD. - Added patches for TOS 2.05 so that this TOS version should now work, too. - Rewrote TOS patching routine. It is much more flexible now. - Removed 0xa0ff opcode for VDI resolutions; using GEMDOS_OPCODE now instead. - Fixed MMU RAM size configuration bug. - Rewrote some more screen conversion functions in C. - When a bus or address error occurred, the PC was often not set to the right exception handler routine. This has been fixed now. Version 0.25 (2002-12-30): -------------------------- - Patches for big endian systems (Spectrum 512 pictures are now working there). - Hatari now also compiles and runs under Mac OS X. - Blitter emulation has been added. - There is now the possibility to save YM or WAV sounds. - Big VDI resolutions (e.g. 800x600) are now supported, too. - Mouse grab shortcut (AltGr+M). - Fixed TOS v1.00 patching. Version 0.20 (2002-02-18): -------------------------- - Added graphical user interface for configuration of the emulator settings. - Real joysticks can now also be used to simulate the ST joysticks. - Yet another bugfix for BeOS (lseek again...) - Support for hard disk images. Version 0.11 (2001-10-10): -------------------------- - High level (GEMDOS) harddisk emulation. - ST-Med/ST-Low mixed mode now works. - Add --compatible CPU mode option. Version 0.10 (2001-08-16): -------------------------- - Improved CPU cycles emulation. - Added Spec512 support. - Some keyboard shortcuts. - Added the possibility to switch between fullscreen and window mode. - ST Medium resolution conversion routine. - Built-in debugger. - Added possibility to grab screenshots. - Sound support (not working very well yet). Version 0.05 (2001-06-01): -------------------------- - Joystick emulation via cursor keys. - ST-LOW resolution conversion routine is now working on big-endian machines. Version 0.04 (2001-05-27): -------------------------- - Added Stefan Berndtsson's patch for big-endian machines. Hatari now runs also with non-x86 Linux machines! Thanks Stefan! - Rewrote the ST-LOW resolution conversion routines in C ==> ST-LOW now works! - Added some of the WinSTon patches Paul Bates recently published at the WinSTon BBS (Thanks to Ladislav Adamec for the hint). - Cleaned up the source tree a little bit. Version 0.03 (2001-04-03): -------------------------- - Rewrote some more assembler functions. FDC emulation now works! - SDL Keyboard code finished and included a SDL-Key -> ST-Scancode table. - Added mouse support. Version 0.02 (2001-03-28): -------------------------- - Added very simple SDL support. - Rewrote a lot of assembler functions in C (e.g. intercept.c). - Adapted the UAE CPU. Now Hatari is able to boot a TOS 1.0x ROM, the Desktop shows up, but no mouse and keyboard interaction yet. Version 0.01 (2001-03-21): -------------------------- - Made the WinSTon source code compilable with GNU-C. - Added the UAE CPU sources. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/scsi-driver.txt000066400000000000000000000047541504763705000243130ustar00rootroot00000000000000Hatari provides a host component implementation of the SCSI Driver for Hatari (Linux only). The SCSI Driver standard is an open software interface for the Atari and was initiated by Steffen Engel. Please refer to src/nf_scsidrv.c and https://www.hddriver.net/en/scsidriver.html or https://github.com/uweseimet/atari_public for further information. The SCSI Driver for Hatari implements a SCSI Driver on top of the Linux SG 3 driver interface. With the SCSI Driver software running within Hatari can access all devices supported by the Linux SG driver. The Hatari user needs the proper Linux permissions to access these devices. Depending on the Linux distribution one may have to belong to the groups 'disk' and 'cdrom', for instance. The SCSI Driver for Hatari implements the SCSI Driver interface version 1.01. It consists of two software components: 1. Native SCSI Driver host implementation, which for Linux is provided by Hatari. It maps SCSI Driver calls to Linux SG driver calls. In order to run the SCSI Driver stub for TOS on other platforms than Linux, this is the code that has to be ported. 2. SCSI Driver stub for TOS, NF_SCSI.PRG. This component runs on TOS and uses Hatari's NatFeats interface to call the host driver. The stub is independent of the host platform Hatari is running on. NF_SCSI can be downloaded from https://www.hddriver.net/en/scsidriver.html. The NatFeats-based interface between the TOS stub and the host driver implementation consists of these calls (NatFeats sub-ids): 1. SCSI_INTERFACE_VERSION Returns the driver interface version number. (Note that this is not the same as the SCSI Driver version.) Only if the host implementation and the TOS stub interface version match, the stub can be installed. The format of the version string is MAJOR.MINOR, coded as a 16-bit value. 0x0105, for instance, means version 1.05. 2. SCSI_INTERFACE_FEATURES Returns the host's bus features, which depend on the host system, and the bus name. For Linux the bus name is "Linux Generic SCSI". 3. SCSI_INQUIRE_BUS, SCSI_OPEN, SCSI_CLOSE, SCSI_ERROR SCSI_CHECK_DEV The host implementations of the corresponding SCSI Driver calls. 4. SCSI_INOUT The host implementation of the SCSI Driver's In() and Out() calls. Except for the data flow direction flag both calls are identical, so that they can be mapped to the same interface all. All SCSI Driver calls not listed above are implemented by the TOS stub. Please refer to the source code (Hatari part and TOS stub) for details. Uwe Seimet hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/thanks.txt000066400000000000000000000060301504763705000233360ustar00rootroot00000000000000 Following people contributed ideas or helped to find bugs in Hatari etc., so we'd like to say thank you here (listed in random order - and if someone is missing here, please remind me!): - Anders Eriksson (Evil/DHS): Helped improving STE's emulation by running many tests programs and providing the source code for some non-working demos. - Douglas Little: wrote and run some test programs for 030 cycle accuracy and FPU, tested & gave feedback on Hatari CPU & DSP profiler features, debugged Hatari DSP emulation issues. - Roger Burrows: EmuTOS maintainer, reported bugs in several areas including I/O memory access & TT emulation (timer-C, video). Ran some test programs on real hardware and wrote some test programs himself. - Vincent Riviere: EmuTOS maintainer, reported bugs about GEMDOS HD, TOS ROM handling, 32-bit IDE transfers, bus error fault in 24-bit, Debian bugs on Hatari Python UI, debugger enhancement suggestions. - Jean Louis Guerin (DrCoolZic): 'Panzer' program, very useful for testing some FDC behaviours and timings on real hardware and to compare them with the emulated system. Also wrote some nice docs on WD1772 and methods commonly used for games' protections. - George Nakos : Helped to track down a bug in the GEMDOS HD emulation. - Pieter van der Meer : Traced a bug in the VIDEL emulation. - nash67: tested hundreds (!) of games from various CD compilations and reported the non working ones on atari-forum.com. Huge thanks for that tedious work, it helped tracking down some less common cases not used in demos (keyboard, joystick, FDC, tos, ...). - Jorge Cwik (Ijor): creator of Pasti STX disk image. Reverse-engineered some decapped STF chips (GLUE, MMU, Shifter) and many low level description of the STF inner work, very useful to improve emulation accuracy. Also wrote a Verilog Blitter matching the STE's real blitter, which helped fixing the complex cases of xcount=1 and nfsr=1 in Hatari's blitter. - Paulo Simoes (ljkb): made some of the first descriptions of wakestates, as well as documenting all positions where hi/lo and 50/60 switches can create specific lines' length. - Christian Zietz: recovered many original ASIC schemas for Atari's chips and helped in reverse-engineering the behaviour of those chips (MMU address decoding). Blitter test program. - Matthias Alles : He initiated the port of Hatari to MiNT and helped with a lot of technical questions about the ST. - Steve Kemp : Found some possible buffer overflows. - Alexandre Rebert : Debian bugs on Hatari command line option handling crashes. - Michael Tautschnig : Debian bug on ConfigureParams handling issue. - Markus Fröschle : blitter test results from real Falcon. - Andrea Musuruane (Hatari Fedora package maintainer) : Hatari bug reports - RATI / Overlanders : wrote an exhaustive test program to check various combinations of 4 pixel hardscroll when Shifter is stopped. This was used as a reference to compare real STF and emulation See "authors.txt" for people who've contributed also code to Hatari. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/toc.js000066400000000000000000000157631504763705000224450ustar00rootroot00000000000000/** toc.js This is a simplified version of the script "generated_toc.js" written by: Stuart Langridge, July 2007 The script is licensed under the terms of the MIT license. See the following page for details: http://www.kryogenix.org/code/browser/generated-toc/ Generate a table of contents, based on headings in the page. To place the TOC on the page, add
to the page where you want the TOC to appear. If this element is not present, the TOC will not appear. */ generated_toc = { generate: function() { // Identify our TOC element, and what it applies to generate_from = '2'; tocparent = document.getElementById('generated-toc'); if (!tocparent) { // They didn't specify a TOC element; exit return; } // set top_node to be the element in the document under which // we'll be analysing headings top_node = document.getElementsByTagName('body')[0]; // If there isn't a specified header level to generate from, work // out what the first header level inside top_node is // and make that the specified header level if (generate_from == 0) { first_header_found = generated_toc.findFirstHeader(top_node); if (!first_header_found) { // there were no headers at all inside top_node! return; } else { generate_from = first_header_found.toLowerCase().substr(1); } } // add all levels of heading we're paying attention to to the // headings_to_treat dictionary, ready to be filled in later headings_to_treat = {"h6":''}; for (var i=5; i>= parseInt(generate_from); i--) { headings_to_treat["h" + i] = ''; } // get headings. We can't say // getElementsByTagName("h1" or "h2" or "h3"), etc, so get all // elements and filter them ourselves // need to use .all here because IE doesn't support gEBTN('*') nodes = top_node.all ? top_node.all : top_node.getElementsByTagName('*'); // put all the headings we care about in headings headings = []; for (var i=0; i cur_head_lvl) { // this heading is at a lower level than the last one; // create additional nested lists to put it at the right level // get the *last* LI in the current list, and add our new UL to it var last_listitem_el = null; for (var j=0; j]+>/g, ''); }, findFirstHeader: function(node) { // a recursive function which returns the first header it finds inside // node, or null if there are no functions inside node. var nn = node.nodeName.toLowerCase(); if (nn.match(/^h[1-6]$/)) { // this node is itself a header; return our name return nn; } else { for (var i=0; i for counted ones, ? for conditional ones, * for others) - Shortcut command for telling to run until given (temporary) conditional breakpoint is hit - Running until code returns from ROM (exiting from super mode?) - Single stepping that skips Traps, Line-A, Line-F. And one that skips also BSRs and JSRs ('next' command runs until instruction of given type) - Saving full machine status (like registers) to history buffer each time debugger is entered (exception or breakpoint is hit) and viewing of that history - SP & SSP as CPU register names (active & supervisor stack) - Fill and copy memory command - Search for an address which disassembly output matches given instruction substring - Improved screen handling: - Line based screen change detection/checks: - blit only changed lines - simpler / faster (LED) overlay handling - Include some fancy zooming routines like 2xSaI or Super-Eagle - Add Hatari "fileid" to more files (to ease locating "stolen" code) and Git hook to remove trailing whitespace (sed -i 's/[\t ]*$//')? hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/doc/video-recording.txt000066400000000000000000000071121504763705000251300ustar00rootroot00000000000000How to record Atari videos with Hatari ====================================== Getting best output from Hatari ------------------------------- * Do NOT use external recorders (such as Quicktime X on OSX), as they won't get perfect framerate and sound sync like Hatari itself does * Disable (default) frame skip, either from the Hatari GUI, or with following command line option: --frameskips 0 * For STe you could set audio to 50066 Hz using ST table, either from the Hatari GUI, or with following command line options: --sound 50066 --ym-mixing table Getting better performance -------------------------- By default Hatari will: * Scale its framebuffer to (approximately) same size regardless of Atari resolution (= Atari monitor emulation) * Use its highest AVI compression method (PNG) * Use most accurate target machine emulation But all these are quite CPU intensive, and can together make recording very slow. It is better to: * Use "--max-width" and "--max-height" options to limit the (recorded) Hatari framebuffer size to (smaller) original Atari resolution, both to speed up compression and (significantly) reduce resulting video size. For example, if you don't need overscan borders, use this for ST-low resolution: --max-width 320 --max-height 200 + To have Hatari window contents still shown at a usable size on larger displays, ask Hatari framebuffer to be scaled to its window with "--zoom" option (requires Hatari to be built with SDL v2): --zoom 2 --max-width 320 --max-height 200 * Select suitable AVI compression method (see next section) * When recording Falcon programs, if program doesn't use DSP, disable it with "--dsp none" * When recording TT and Falcon programs, one could disable also CPU cache emulation with "--cpu-exact off" when recorded program works fine without cycle-exact emulation Hatari AVI compression notes ---------------------------- If Hatari is configured/built with PNG development installed headers (normal case with Linux distros and pre-built binaries), Hatari will use PNG compression to produce smaller AVI recordings. Additionally, by default Hatari will use the highest PNG compression level (same as with screenshots), but this is *really* CPU intensive. Because of the PNG compression CPU usage, you could try using uncompressed BMP format instead: --avi-vcodec bmp If that takes too much disk space, next best option is to ask Hatari to use lower compression level, e.g: --png-level 4 Valid compression levels are 0-9, with 9 being default/highest/slowest. Preparing videos for uploading ------------------------------ If the end goal is Youtube, it is recommended to run Hatari's AVI output through ffmpeg to do nearest neighbor upscale to 1080p. Then Youtube will keep the 50 FPS and you have non-fuzzy pixels in the recording. This ffmpeg line should do the trick for a 320x200 stream (5x scale): ffmpeg -i hatari.avi -vf "scale=1600:1000, \ pad=1920:1080:160:40:black" -sws_flags neighbor \ -vcodec png -acodec copy youtube1080p.mov And for a 416x276 stream (so you get the overscan area as well, 4x scale): ffmpeg -i hatari.avi -vf "crop=400:270:8:0, scale=1600:1080, \ pad=1920:1080:160:0:black" -sws_flags neighbor -vcodec png \ -acodec copy youtube1080p.mov Above adds padding to 1920*1080 size, that can be removed if you trust the re-encoder/player to scale properly (which has been known to fail). It also saves the stream as PNG so it is manageable to upload and store for future. (Upload information is based on atari-forum post by "evil": http://atari-forum.com/viewtopic.php?f=51&t=27595#p268185 ) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/gpl.txt000066400000000000000000000431001504763705000220620ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Moe Ghoul, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/hatari.spec000066400000000000000000000070601504763705000226700ustar00rootroot00000000000000# # RPM spec file for Hatari # # This file and all modifications and additions to the pristine # package are under the same license as the package itself. # Name: hatari URL: https://www.hatari-emu.org/ License: GPLv2+ Group: System/Emulators/Other Autoreqprov: on Version: 2.6.1 Release: 1 Summary: An Atari ST/STE/TT/Falcon emulator Source: %{name}-%{version}.tar.bz2 BuildRoot: %{_tmppath}/%{name}-%{version}-build Prefix: /usr BuildRequires: binutils cmake coreutils cpio cpp diffutils file filesystem BuildRequires: findutils gcc grep gzip libgcc make man patch sed util-linux BuildRequires: glibc-devel zlib-devel SDL2-devel libpng-devel readline-devel # Required by zip2st and atari-hd-image Requires: unzip Requires: mtools Requires: dosfstools %description Hatari is an emulator for the Atari ST, STE, TT and Falcon computers. The Atari ST was a 16/32 bit computer system which was first released by Atari in 1985. Using the Motorola 68000 CPU, it was a very popular computer having quite a lot of CPU power at that time. Unlike most other open source ST emulators which try to give you a good environment for running GEM applications, Hatari tries to emulate the hardware as close as possible so that it is able to run most of the old Atari games and demos. Because of this, it may be somewhat slower than less accurate emulators. %prep %autosetup -p1 %build ./configure --enable-werror --prefix=/usr make %{?_smp_mflags} %check make test %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %{_bindir}/* %{_datadir}/%{name} %{_datadir}/applications/*.desktop %{_datadir}/icons/hicolor/*/apps/%{name}.* %{_datadir}/icons/hicolor/*/mimetypes/* %{_datadir}/mime/packages/hatari.xml %{_mandir}/man1/* %doc %{_pkgdocdir} %license gpl.txt %changelog -n hatari * Fri Aug 15 2025 - Nicolas Pomarede - Hatari version 2.6.1 * Sun Jun 22 2025 - Nicolas Pomarede - Hatari version 2.6.0 * Thu Apr 18 2024 - Nicolas Pomarede - Hatari version 2.5.0 * Wed Aug 03 2022 - Nicolas Pomarede - Hatari version 2.4.1 * Sat Jul 09 2022 - Nicolas Pomarede - Hatari version 2.4.0 * Sun Dec 27 2020 - Nicolas Pomarede - Hatari version 2.3.1 * Sat Nov 28 2020 - Nicolas Pomarede - Hatari version 2.3.0 * Fri Feb 08 2019 - Nicolas Pomarede - Hatari version 2.2.1 * Thu Jan 31 2019 - Nicolas Pomarede - Hatari version 2.2.0 * Wed Feb 07 2018 - Nicolas Pomarede - Hatari version 2.1.0 * Fri Nov 04 2016 - Nicolas Pomarede - Hatari version 2.0.0 * Thu Sep 10 2015 - Nicolas Pomarede - Hatari version 1.9.0 * Wed Jul 30 2014 - Nicolas Pomarede - Hatari version 1.8.0 * Mon Jun 24 2013 - Nicolas Pomarede - Hatari version 1.7.0 * Sun Jun 24 2012 - Nicolas Pomarede - Hatari version 1.6.2 * Fri Jan 13 2012 - Nicolas Pomarede - Hatari version 1.6.1 * Sun Jan 01 2012 - Nicolas Pomarede - Hatari "Happy New Year 2012" version 1.6.0 * Tue Jul 19 2011 - Nicolas Pomarede - Hatari version 1.5.0 * Sat Jun 12 2010 - Nicolas Pomarede - Hatari version 1.4.0 * Sat Sep 05 2009 - Thomas Huth - Hatari version 1.3.1 * Sun Aug 16 2009 - Thomas Huth - Hatari version 1.3.0 * Sat Jan 24 2009 - Thomas Huth - Hatari version 1.2.0 * Sat Nov 29 2008 - Thomas Huth - Hatari version 1.1.0 * Wed Jan 02 2008 - Thomas Huth - Adapted RPM to the latest source code level (aiming at version 1.0.0) * Sun May 06 2007 - Thomas Huth - Adapted spec file to be able to build Hatari with RedHat, too * Sun Aug 27 2006 - Thomas Huth - Upgraded to version 0.90 * Tue Oct 18 2005 - Thomas Huth - initial package hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/000077500000000000000000000000001504763705000224755ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/CMakeLists.txt000066400000000000000000000031251504763705000252360ustar00rootroot00000000000000# conftypes.py is created to source directory (instead of build directory) # so that Hatari UI can be tested directly from the source directory add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/conftypes.py COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gentypes.py < ${CMAKE_CURRENT_SOURCE_DIR}/../src/configuration.c > ${CMAKE_CURRENT_SOURCE_DIR}/conftypes.py DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../src/configuration.c ${CMAKE_CURRENT_SOURCE_DIR}/gentypes.py) add_custom_target(conftypes ALL DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/conftypes.py) INSTALL(PROGRAMS hatariui DESTINATION ${BINDIR}) INSTALL(PROGRAMS hatariui.py debugui.py DESTINATION ${DATADIR}/hatariui/) # Hatari UI images go to same place as python code. # Changing that would require change also in uihelpers.py INSTALL(FILES hatari-icon.png hatari-logo.png config.py dialogs.py hatari.py uihelpers.py ${CMAKE_CURRENT_SOURCE_DIR}/conftypes.py DESTINATION ${DATADIR}/hatariui/) # Hatari UI Help menu items are searched from docdir. # Changing their target dir would require change also in uihelpers.py INSTALL(FILES README TODO release-notes.txt DESTINATION ${DOCDIR}/hatariui/) INSTALL(FILES hatariui.desktop DESTINATION share/applications) if(ENABLE_MAN_PAGES) add_custom_target(hatariui_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hatariui.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hatariui.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/hatariui.1 > ${CMAKE_CURRENT_BINARY_DIR}/hatariui.1.gz DEPENDS hatariui.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hatariui.1.gz DESTINATION ${MANDIR}) endif(ENABLE_MAN_PAGES) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/FILES000066400000000000000000000015741504763705000232710ustar00rootroot00000000000000UI code: - debugui.py - Debugger UI (works also as standalone) - dialogs.py - Different dialogs shown by Hatari UI - hatariui.py - Hatari UI main: option handling, main window etc - uihelpers.py - Misc helpers for Hatari UI Other code: - config.py - Hatari configuration INI file handling - hatari.py - Communication with Hatari and config option handling - hatariui - Wrapper script for hatariui.py with suitable default options - Makefile - Rules for installing Type information: - conftypes.py - Info about conf variable types for config.py - gentypes.py - Generator for that info Data files: - hatari-icon.png - Window icon - hatari.png - About dialog image - hatariui.desktop - Hatari UI .desktop file for application launchers Documentation: - release-notes.txt - UI & code changes - FILES - This file - README - Hatari UI intro / description - TODO - missing features etc hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/README000066400000000000000000000050271504763705000233610ustar00rootroot00000000000000Hatari UI --------- Hatari UI is an out-of-process user interface for the Hatari Atari ST/STe/TT/Falcon emulator and its built-in debugger which can (optionally) embed the Hatari emulator window. Having the UI in another process allows doing it with a higher level language while avoiding adding GUI toolkit dependencies to Hatari itself. The UI is written in Python language, using Gtk widget set. The main points of this new UI over the Hatari internal one are its configurability, more usable file selector, internationalization support and providing a (very) simple GUI for the (console based) debugger included with the Hatari emulator. Note: this is an additional UI, the built-in Hatari SDL UI isn't being replaced or going anywhere! Requirements ------------ - python3 >= 3.5 - python3-gi (Python GObject Introspection) - Gtk >= v3.22 and its gir bindings (gir1.2-gtk-3.0) Hatari UI is included with the Hatari sources: https://framagit.org/hatari/hatari/-/tree/main/python-ui Hatari UI has been tested on several Linux versions. I would assume it to work also on other unix systems such as Apple OSX. It may work with the Hatari Windows version, as long as it is built with socket support. Embedding the Hatari emulator window is currently supported only for systems using an X window system (from libSDL sources it would seem that Windows would also support window embedding, but support for that would need to be added both to Hatari and Hatari UI because SDL's own embedding disables all keyboard events in SDL program). Here are instructions on installing the dependencies for non-Linux platforms (neither tested nor supported as I don't use/have them): https://www.python.org/downloads/ https://pypi.org/project/PyGObject/ https://www.gtk.org/download/ Running ------- Being a Python program, Hatari UI doesn't need to be built. You can just run it from where you extracted it (or checked it out of Hatari git repo) by calling its wrapper script: /path/to/script/hatariui Or you can run just the debugger: /path/to/script/debugui.py But you can also install it to system along with Hatari: make install Notes ----- Hatari UI runs a Hatari version found on $PATH. If you want to use a version of Hatari that hasn't been installed, you need to modify the search path, for example like this: PATH=../build/src:.:$PATH hatariui If UI is started without the embedding option, the actions (in menus and toolbars) have also shortcuts. They cannot be used when Hatari window is embedded because then those shortcuts couldn't be used with Hatari. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/TODO000066400000000000000000000064361504763705000231760ustar00rootroot00000000000000TODO ==== Potential TODO items for Hatari UI (in addition to the ones listed in its sources). * Fix rest of Gtk v3 / PyGTK deprecation warnings by creating all Python UI menus with XML and Gtk application framework instead of using Gtk v2 Action + ActionGroup widgets, see: - https://python-gtk-3-tutorial.readthedocs.io/en/latest/application.html * Rewrite all configuration dialogs into Mac UI style single (notebook) dialog with tabs, using Gtk v3 Grid layout widget, so that only single configuration menu entry remains * Interface between UI and Hatari so that Hatari will forward error and other dialog texts to UI for showing, and waits until user has acknowledged them (two-way communication) * Support all 8 ACSI and SCSI drives * Use file selector widget instead of text widget for specifying input paths for memory snapshots and devices, so that user can't provide invalid paths (sound, snapshot and device output files can be non-existing as they're created on demand) * Separate "Atari monitor" and "Hatari window" settings dialogs (move options from "Display" and monitor setting from "Machine" dialogs to here and add VDI options to Atari Monitor dialog) * Features for Help menu: - Create empty floppy images (+ convert directories to hard disks?) - MSA<->ST converter - ZIP->ST converter * Add more Hatari debugger features to the debug UI: - DSP support - History support - Profiling support - DSP symbols loading (CPU symbols are loaded automatically from programs) - Support for stepping the emulation (both step & next commands) - multiple views to memory (refreshed whenever emulation is stopped) - supporting also register relative views (register values parsing should move to common functionality first) - breakpoint support (issue: how to stop emulation on breakpoint but still allow Hatari to process remote commands?) - trace & debug log viewers? * Translation support for the UI: - use gettext - build needs to build & install message catalogs - some way for Hatari to forward dialog ID to the remote UI with dialog string parameters (filenames etc) which then need to be localized too & shown... * Hatari UI specific configuration which stores: - list of last used configuration files which would be shown either in their own menu or dialog - list of last used memory snapshots (10?) - disk image dir (uses Hatari config value as default) - trace settings - remove dialog specific load/save stuff - screenshot name - needs support also to Hatari src/screenSnapShot.c * Supporting other, less important Hatari configuration options: - keyboard repeat, key mapping type & file, mouse warping & grabbing - HD booting, something for multiple GEMDOS partition directories - separate A/B disk paths for images that are within ZIP archives - joystick autofire toggling, defining the keys for joyemu - vdi planes and size, spec512 threshold, ST blitter - cartridge image (where? it has many limitations) - log file and levels, console output, bios intercept, run-VBLs (Many of these aren't supported by the internal Hatari UI either, or are missing corresponding command line options so they will require additional support on the Hatari control.c side too or they can be only enabled at boot, not disabled.) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/config.py000066400000000000000000000246351504763705000243260ustar00rootroot00000000000000# # Class and helper functions for handling (Hatari) INI style # configuration files: loading, saving, setting/getting variables, # mapping them to sections, listing changes # # Copyright (C) 2008-2012,2016-2020 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os # mapping from Hatari config variable name to type id (Bool, Int, String) from conftypes import conftypes # ------------------------------------------------------ # Helper functions for type safe Hatari configuration variable access. # Map booleans, integers and strings to Python types, and back to strings. def value_to_text(key, value): "value_to_text(key, value) -> text, convert Python type to string" #if key not in conftypes: # print("ERROR: key '%s' missing!" % key) assert(key in conftypes) valtype = type(value) if valtype == bool: assert(conftypes[key] == "Bool") if value: text = "TRUE" else: text = "FALSE" elif valtype in (float, int): assert(conftypes[key] in ("Float", "Int")) text = str(value) else: # keyboard key name or any string assert(conftypes[key] in ("Key", "String")) if value == None: text = "" else: text = value return text def text_to_value(text): "text_to_value(text) -> value, convert INI file values to real types" # bool? upper = text.upper() if upper == "FALSE": value = False elif upper == "TRUE": value = True else: try: # integer? value = int(text) # if not exact, maybe it's float? if text != '%d' % value: value = float(text) except ValueError: # string value = text return value # ------------------------------------------------------ # Handle INI style configuration files as used by Hatari class ConfigStore: def __init__(self, confdirs, defaults = {}, miss_is_error = True): "ConfigStore(userconfdir, fgfile[,defaults,miss_is_error])" self.defaults = defaults self.userpath = self._get_full_userpath(confdirs) # this is only about ConfigStore checks i.e. value existing in # current Hatari config, value_to_text() will still fail if # config name isn't even in conftypes (= all Hatari configs) self.miss_is_error = miss_is_error self.changed = False def _get_full_userpath(self, confdirs): "get_userpath(leafdir) -> config file default save path from HOME, CWD or their subdir" # user's hatari.cfg can be in home or current work dir, # current dir is used only if $HOME fails for path in (os.getenv("HOME"), os.getenv("HOMEPATH"), os.getcwd()): if path and os.path.exists(path) and os.path.isdir(path): for leafdir in confdirs: if leafdir: hpath = "%s%c%s" % (path, os.path.sep, leafdir) if os.path.exists(hpath) and os.path.isdir(hpath): return hpath return path return None def get_filepath(self, filename): "get_filepath(filename) -> return correct full path to config file" # user config has preference over system one sep = os.path.sep for path in (self.userpath, os.getenv("HATARI_SYSTEM_CONFDIR")): if path: confpath = "%s%c%s" % (path, sep, filename) if os.path.isfile(confpath): return confpath # writing needs path name although it's missing for reading return "%s%c%s" % (self.userpath, sep, filename) def load(self, path): "load(path): load given configuration file -> message on error, None on success" if os.path.isfile(path): sections = self._read(path) if sections: self.sections = sections else: return "loading failed" else: if not self.defaults: return "file missing" print("-> using dummy 'defaults'.") self.sections = self.defaults self.path = path self.cfgfile = os.path.basename(path) self.original = self.get_checkpoint() self.changed = False return None def is_loaded(self): "is_loaded() -> True if configuration loading succeeded" if self.sections: return True return False def get_path(self): "get_path() -> configuration file path" return self.path def _read(self, path): "_read(path) -> (all keys, section2key mappings)" print("Reading configuration file '%s'..." % path) config = open(path, "r") if not config: return ({}, {}) name = "[_orphans_]" seckeys = {} sections = {} for line in config.readlines(): line = line.strip() if not line or line[0] == '#': continue if line[0] == '[': if line in sections: print("WARNING: section '%s' twice in configuration" % line) if seckeys: sections[name] = seckeys seckeys = {} name = line continue offset = line.find('=') if offset < 0: print("WARNING: line without key=value pair:\n%s" % line) continue key = line[:offset].strip() text = line[offset+1:].strip() seckeys[key] = text_to_value(text) if seckeys: sections[name] = seckeys return sections def get_checkpoint(self): "get_checkpoint() -> checkpoint, get the state of variables at this point" checkpoint = {} for section in self.sections.keys(): checkpoint[section] = self.sections[section].copy() return checkpoint def get_checkpoint_changes(self, checkpoint): "get_checkpoint_changes() -> list of (key, value) pairs for later changes" changed = [] if not self.changed: return changed for section in self.sections.keys(): if section not in checkpoint: for key, value in self.sections[section].items(): changed.append((key, value)) continue for key, value in self.sections[section].items(): if (key not in checkpoint[section] or value != checkpoint[section][key]): text = value_to_text(key, value) changed.append(("%s.%s" % (section, key), text)) return changed def revert_to_checkpoint(self, checkpoint): "revert_to_checkpoint(checkpoint), revert to given checkpoint" self.sections = checkpoint def get(self, section, key): return self.sections[section][key] def set(self, section, key, value): "set(section,key,value), set given key to given section" if section not in self.sections: if self.miss_is_error: raise AttributeError("no section '%s'" % section) self.sections[section] = {} if key not in self.sections[section]: if self.miss_is_error: raise AttributeError("key '%s' not in section '%s'" % (key, section)) self.sections[section][key] = value self.changed = True elif self.sections[section][key] != value: self.changed = True self.sections[section][key] = value def is_changed(self): "is_changed() -> True if current configuration is changed" return self.changed def get_changes(self): "get_changes(), return (key, value) list for each changed config option" return self.get_checkpoint_changes(self.original) def write(self, fileobj): "write(fileobj), write current configuration to given file object" sections = list(self.sections.keys()) sections.sort() for name in sections: fileobj.write("%s\n" % name) keys = list(self.sections[name].keys()) keys.sort() for key in keys: value = value_to_text(key, self.sections[name][key]) fileobj.write("%s = %s\n" % (key, value)) fileobj.write("\n") def save(self): "save() -> path, if configuration changed, save it" if not self.changed: print("No configuration changes to save, skipping") return None fileobj = None if self.path: try: fileobj = open(self.path, "w") except: pass if not fileobj: print("WARNING: non-existing/writable configuration file, creating a new one...") if not os.path.exists(self.userpath): os.makedirs(self.userpath) self.path = "%s%c%s" % (self.userpath, os.path.sep, self.cfgfile) fileobj = open(self.path, "w") if not fileobj: print("ERROR: opening '%s' for saving failed" % self.path) return None self.write(fileobj) print("Saved configuration file:", self.path) self.changed = False return self.path def save_as(self, path): "save_as(path) -> path, save configuration to given file and select it" assert(path) if not os.path.exists(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) self.path = path self.changed = True return self.save() def save_tmp(self, path): "save_tmp(path) -> path, save configuration to given file without selecting it" if not os.path.exists(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) fileobj = open(path, "w") if not fileobj: print("ERROR: opening '%s' for saving failed" % path) return None self.write(fileobj) print("Saved temporary configuration file:", path) return path hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/debugui.py000077500000000000000000000476661504763705000245210ustar00rootroot00000000000000#!/usr/bin/python3 # # A Debug UI for the Hatari, part of Python Gtk Hatari UI # # Copyright (C) 2008-2025 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os import gi # use correct version of gtk gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import Gdk from config import ConfigStore from uihelpers import UInfo, create_button, create_toggle, \ create_table_dialog, table_add_entry_row, table_add_widget_row, \ FselEntry from dialogs import TodoDialog, ErrorDialog, AskDialog, KillDialog def dialog_apply_cb(widget, dialog): dialog.response(Gtk.ResponseType.APPLY) # ------------- # Table dialogs class SaveDialog: def __init__(self, parent): table, self.dialog = create_table_dialog(parent, "Save from memory", 3, 2) self.fsel = FselEntry(self.dialog, "Memory save file name:") table_add_widget_row(table, 0, 0, "File name:", self.fsel.get_container()) self.address = table_add_entry_row(table, 1, 0, "Save address:", 6) self.address.connect("activate", dialog_apply_cb, self.dialog) self.length = table_add_entry_row(table, 2, 0, "Number of bytes:", 6) self.length.connect("activate", dialog_apply_cb, self.dialog) def run(self, address): "run(address) -> (filename,address,length), all as strings" if address: self.address.set_text("%06X" % address) self.dialog.show_all() filename = length = None while 1: response = self.dialog.run() if response == Gtk.ResponseType.APPLY: filename = self.fsel.get_filename() address_txt = self.address.get_text() length_txt = self.length.get_text() if filename and address_txt and length_txt: try: address = int(address_txt, 16) except ValueError: ErrorDialog(self.dialog).run("address needs to be in hex") continue try: length = int(length_txt) except ValueError: ErrorDialog(self.dialog).run("length needs to be a number") continue if os.path.exists(filename): question = "File:\n%s\nexists, replace?" % filename if not AskDialog(self.dialog).run(question): continue break else: ErrorDialog(self.dialog).run("please fill the field(s)") else: break self.dialog.hide() return (filename, address, length) class LoadDialog: def __init__(self, parent): chooser = Gtk.FileChooserButton(title="Select memory file to load:") chooser.set_local_only(True) # Hatari cannot access URIs chooser.set_width_chars(12) table, self.dialog = create_table_dialog(parent, "Load to memory", 2, 2) self.fsel = table_add_widget_row(table, 0, 0, "File name:", chooser) self.address = table_add_entry_row(table, 1, 0, "Load address:", 6) self.address.connect("activate", dialog_apply_cb, self.dialog) def run(self, address): "run(address) -> (filename,address), all as strings" if address: self.address.set_text("%06X" % address) self.dialog.show_all() filename = None while 1: response = self.dialog.run() if response == Gtk.ResponseType.APPLY: filename = self.fsel.get_filename() address_txt = self.address.get_text() if filename and address_txt: try: address = int(address_txt, 16) except ValueError: ErrorDialog(self.dialog).run("address needs to be in hex") continue break else: ErrorDialog(self.dialog).run("please fill the field(s)") else: break self.dialog.hide() return (filename, address) class OptionsDialog: def __init__(self, parent): self.dialog = Gtk.Dialog("Debugger UI options", parent, modal=True, destroy_with_parent=True) self.dialog.add_buttons( Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE ) lines = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 50, 5) lines.set_tooltip_text("Change view mode afterwards to get new (min size) into effect") lines.set_digits(0) self.lines = lines follow = Gtk.CheckButton(label="On stop, set address to PC") follow.set_tooltip_text("Run-time option, not saved to Debugger config file") self.follow_pc = follow vbox = self.dialog.vbox vbox.add(Gtk.Label(label="Memdump/disasm lines:")) vbox.add(lines) vbox.add(follow) vbox.show_all() def run(self, lines, follow_pc): "run(lines,follow_pc) -> (lines,follow_pc)" self.follow_pc.set_active(follow_pc) self.lines.set_value(lines) self.dialog.show_all() response = self.dialog.run() if response == Gtk.ResponseType.APPLY: lines = int(self.lines.get_value()) follow_pc = self.follow_pc.get_active() self.dialog.hide() return (lines, follow_pc) # ---------------------------------------------------- # constants for the other classes class Constants: # dump modes NODUMP = 0 DISASM = 1 MEMDUMP = 2 REGISTERS = 3 # move IDs MOVE_MIN = 1 MOVE_MED = 2 MOVE_MAX = 3 # class for the memory address entry, view (label) and # the logic for memory dump modes and moving in memory class MemoryAddress: # class variables debug_output = None hatari = None def __init__(self, hatariobj): # hatari self.debug_output = hatariobj.open_debug_output() self.hatari = hatariobj # widgets self.entry, self.memory = self.create_widgets() # settings self.dumpmode = Constants.NODUMP self.follow_pc = True self.lines = 12 # addresses self.first = None self.second = None self.last = None def clear(self): if self.follow_pc: # get first address from PC when next stopped self.first = None self.second = None self.last = None def create_widgets(self): entry = Gtk.Entry(max_length=6, width_chars=7) entry.connect("activate", self._entry_cb) memory = Gtk.TextView() memory.set_monospace(True) memory.set_cursor_visible(False) memory.set_editable(False) return (entry, memory) def _entry_cb(self, widget): try: address = int(widget.get_text(), 16) except ValueError: ErrorDialog(widget.get_toplevel()).run("invalid address") return self.dump(address) def reset_entry(self): self.entry.set_text("%06X" % self.first) def get(self): return self.first def get_memory_view(self): return self.memory def get_address_entry(self): return self.entry def get_follow_pc(self): return self.follow_pc def set_follow_pc(self, follow_pc): self.follow_pc = follow_pc def get_lines(self): return self.lines def set_lines(self, lines): self.lines = lines def set_dumpmode(self, mode): if mode == self.dumpmode: return self.dumpmode = mode self.dump() def dump(self, address = None, move_idx = 0): if self.dumpmode == Constants.REGISTERS: output = self._get_registers() text = self.memory.get_buffer() text.set_text("".join(output)) return if not address: if not self.first: self._get_registers() address = self.first if not address: print("ERROR: address needed") return if self.dumpmode == Constants.MEMDUMP: output = self._get_memdump(address, move_idx) elif self.dumpmode == Constants.DISASM: output = self._get_disasm(address, move_idx) else: print("ERROR: unknown dumpmode:", self.dumpmode) return text = self.memory.get_buffer() text.set_text("".join(output)) if move_idx: self.reset_entry() def _get_registers(self): self.hatari.debug_command("r") output = self.hatari.get_lines(self.debug_output) if not self.first: # 2nd last line has first PC in 1st column, last line next PC in 2nd column offset = output[-1].find(":") self.second = int(output[-1][offset+2:], 16) offset = output[-2].find(" ") if offset < 0: print("ERROR: unable to parse register dump line:\n\t'%s'", output[-2]) return output self.first = int(output[-2][:offset], 16) self.reset_entry() return output def _get_memdump(self, address, move_idx): linewidth = 16 screenful = self.lines*linewidth # no move, left/right, up/down, page up/down (no overlap) offsets = [0, 2, linewidth, screenful] offset = offsets[abs(move_idx)] if move_idx < 0: address -= offset else: address += offset self._set_clamped(address, address+screenful) self.hatari.debug_command("m $%06x-$%06x" % (self.first, self.last)) # get & set debugger command results output = self.hatari.get_lines(self.debug_output) self.second = address + linewidth return output def _get_line_addr(self, line, offset): return int(line[offset:line.find(' ')], 16) def _get_disasm(self, address, move_idx): # TODO: uses brute force i.e. ask for more lines that user has # requested to be sure that the window is filled, assuming # 6 bytes is largest possible instruction+args size # (I don't remember anymore my m68k asm...) screenful = 6*self.lines # no move, left/right, up/down, page up/down offsets = [0, 2, 4, screenful] offset = offsets[abs(move_idx)] # force one line of overlap in page up/down if move_idx < 0: address -= offset if address < 0: address = 0 if move_idx == -Constants.MOVE_MAX and self.second: screenful = self.second - address else: if move_idx == Constants.MOVE_MED and self.second: address = self.second elif move_idx == Constants.MOVE_MAX and self.last: address = self.last else: address += offset self._set_clamped(address, address+screenful) self.hatari.debug_command("d $%06x-$%06x" % (self.first, self.last)) # get & set debugger command results output = self.hatari.get_lines(self.debug_output) # cut output to desired length and check new addresses if len(output) > self.lines: if move_idx < 0: output = output[-self.lines:] else: output = output[:self.lines] # remove symbol lines from the output for address checking addresses = [line for line in output if ' ' in line] # address offset for UAE core and external disassembler output offset = 0 if addresses[0][0] == '$': offset = 1 # need to re-get the addresses from the disassembly output self.first = self._get_line_addr(addresses[0], offset) self.second = self._get_line_addr(addresses[1], offset) self.last = self._get_line_addr(addresses[-1], offset) return output def _set_clamped(self, first, last): "set_clamped(first,last), clamp addresses to valid address range and set them" assert(first < last) if first < 0: last = last-first first = 0 if last > 0xffffff: first = 0xffffff - (last-first) last = 0xffffff self.first = first self.last = last # the Hatari debugger UI class and methods class HatariDebugUI: def __init__(self, hatariobj, do_destroy = False): self.hatari = hatariobj self.address = MemoryAddress(hatariobj) self.address.set_dumpmode(Constants.REGISTERS) # set when needed/created self.dialog_load = None self.dialog_save = None self.dialog_options = None # set when UI created self.keys = None self.stop_button = None # set on option load self.config = None self.load_options() # UI initialization/creation self.window = self.create_ui("Hatari Debug UI", do_destroy) def create_ui(self, title, do_destroy): # buttons at top hbox1 = Gtk.HBox() self.create_top_buttons(hbox1) # disasm/memory dump at the middle addr = self.address.get_memory_view() # buttons at bottom hbox2 = Gtk.HBox() self.create_bottom_buttons(hbox2) # their container vbox = Gtk.VBox() vbox.pack_start(hbox1, False, True, 0) vbox.pack_start(addr, True, True, 0) vbox.pack_start(hbox2, False, True, 0) # and the window for all of this window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) window.set_events(Gdk.EventMask.KEY_RELEASE_MASK) window.connect("key_release_event", self.key_event_cb) if do_destroy: window.connect("delete_event", self.quit) else: window.connect("delete_event", self.hide) info = UInfo() window.set_icon_from_file(info.icon) window.set_title(title) window.add(vbox) return window def create_top_buttons(self, box): self.stop_button = create_toggle("Stop", self.stop_cb) box.add(self.stop_button) monitor = create_button("Monitor...", self.monitor_cb) box.add(monitor) buttons = ( ("<<<", "Page_Up", -Constants.MOVE_MAX), ("<<", "Up", -Constants.MOVE_MED), ("<", None, -Constants.MOVE_MIN), (">", None, Constants.MOVE_MIN), (">>", "Down", Constants.MOVE_MED), (">>>", "Page_Down", Constants.MOVE_MAX) ) self.keys = {} for label, keyname, offset in buttons: button = create_button(label, self.set_address_offset, offset) if keyname: keyval = Gdk.keyval_from_name(keyname) self.keys[keyval] = offset box.add(button) # to middle of <<>> buttons address_entry = self.address.get_address_entry() box.pack_start(address_entry, False, True, 0) box.reorder_child(address_entry, 5) def create_bottom_buttons(self, box): radios = ( ("Registers", Constants.REGISTERS), ("Memdump", Constants.MEMDUMP), ("Disasm", Constants.DISASM) ) group = None for label, mode in radios: button = Gtk.RadioButton(label=label, group=group, can_focus=False) if not group: group = button button.connect("toggled", self.dumpmode_cb, mode) box.add(button) group.set_active(True) dialogs = ( ("Memload...", self.memload_cb), ("Memsave...", self.memsave_cb), ("Options...", self.options_cb) ) for label, cb in dialogs: button = create_button(label, cb) box.add(button) def stop_cb(self, widget): if widget.get_active(): self.hatari.pause() self.address.clear() self.address.dump() else: self.hatari.unpause() def dumpmode_cb(self, widget, mode): if widget.get_active(): self.address.set_dumpmode(mode) def key_event_cb(self, widget, event): if event.keyval in self.keys: self.address.dump(None, self.keys[event.keyval]) def set_address_offset(self, widget, move_idx): self.address.dump(None, move_idx) def monitor_cb(self, widget): TodoDialog(self.window).run("add register / memory address range monitor window.") def memload_cb(self, widget): if not self.dialog_load: self.dialog_load = LoadDialog(self.window) (filename, address) = self.dialog_load.run(self.address.get()) if filename and address: self.hatari.debug_command("l %s $%06x" % (filename, address)) def memsave_cb(self, widget): if not self.dialog_save: self.dialog_save = SaveDialog(self.window) (filename, address, length) = self.dialog_save.run(self.address.get()) if filename and address and length: self.hatari.debug_command("s %s $%06x $%06x" % (filename, address, length)) def options_cb(self, widget): if not self.dialog_options: self.dialog_options = OptionsDialog(self.window) old_lines = self.config.get("[Debugger]", "nDisasmLines") old_follow_pc = self.address.get_follow_pc() lines, follow_pc = self.dialog_options.run(old_lines, old_follow_pc) if lines != old_lines: self.config.set("[Debugger]", "nDisasmLines", lines) self.address.set_lines(lines) if follow_pc != old_follow_pc: self.address.set_follow_pc(follow_pc) self.address.dump() def load_options(self): # TODO: move config to MemoryAddress class? # (depends on how monitoring of addresses should work) lines = self.address.get_lines() # ConfigStore does checks and type conversions based on names # of Hatari config sections and keys, so this needs to use # same names to avoid asserts, and it can't e.g. save # follow_pc option value, which will keep as run-time one defaults = { "[Debugger]": { "nDisasmLines": lines, } } userconfdir = ".hatari" config = ConfigStore(userconfdir, defaults) configpath = config.get_filepath(".debugui.cfg") config.load(configpath) # set defaults try: self.address.set_lines(config.get("[Debugger]", "nDisasmLines")) except (KeyError, AttributeError): ErrorDialog(None).run("Debug UI configuration mismatch!\nTry again after removing: '%s'." % configpath) self.config = config def save_options(self): self.config.save() def show(self): self.stop_button.set_active(True) self.window.show_all() self.window.deiconify() def hide(self, widget, arg): self.window.hide() self.stop_button.set_active(False) self.save_options() return True def quit(self, widget, arg): KillDialog(self.window).run(self.hatari) Gtk.main_quit() def main(): import sys from hatari import Hatari hatariobj = Hatari() if len(sys.argv) > 1: if sys.argv[1] in ("-h", "--help"): print("usage: %s [hatari options]" % os.path.basename(sys.argv[0])) return args = sys.argv[1:] else: args = None hatariobj.run(args) debugui = HatariDebugUI(hatariobj, True) debugui.window.show_all() Gtk.main() debugui.save_options() if __name__ == "__main__": main() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/dialogs.py000066400000000000000000001132101504763705000244670ustar00rootroot00000000000000# # Classes for the Hatari UI dialogs # # Copyright (C) 2008-2025 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import gi # use correct version of gtk gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GdkPixbuf from uihelpers import UInfo, HatariTextInsert, create_table_dialog, \ table_add_widget_row, table_add_combo_row, create_button, \ FselEntry, FselAndEjectFactory # ----------------- # Dialog base class class HatariUIDialog: def __init__(self, parent): "Dialog(parent) -> object" self.parent = parent self.dialog = None def run(self, _dummy = None): """run() -> response. Shows dialog and returns response, subclasses overriding run() require also argument(s).""" response = self.dialog.run() self.dialog.hide() return response # --------------------------- # Note/Todo/Error/Ask dialogs class NoteDialog(HatariUIDialog): button = Gtk.ButtonsType.OK icontype = Gtk.MessageType.INFO textpattern = "\n%s" def run(self, text): "run(text), show message dialog with given text" dialog = Gtk.MessageDialog(self.parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, self.icontype, self.button, self.textpattern % text) dialog.run() dialog.destroy() class TodoDialog(NoteDialog): textpattern = "\nTODO: %s" class ErrorDialog(NoteDialog): button = Gtk.ButtonsType.CLOSE icontype = Gtk.MessageType.ERROR textpattern = "\nERROR: %s" class AskDialog(HatariUIDialog): def run(self, text): "run(text) -> bool, show question dialog and return True if user OKed it" dialog = Gtk.MessageDialog(self.parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, text) response = dialog.run() dialog.destroy() return (response == Gtk.ResponseType.YES) # --------------------------- # About dialog class AboutDialog(HatariUIDialog): def __init__(self, parent): info = UInfo() dialog = Gtk.AboutDialog() dialog.set_transient_for(parent) dialog.set_name(info.name) dialog.set_version(info.version) dialog.set_website("https://www.hatari-emu.org") dialog.set_website_label("Hatari emulator website") dialog.set_authors(["Eero Tamminen"]) dialog.set_artists(["The logo is from Hatari"]) dialog.set_logo(GdkPixbuf.Pixbuf.new_from_file(info.logo)) dialog.set_translator_credits("translator-credits") dialog.set_copyright(info.copyright) dialog.set_license(""" This software is licensed under GPL v2 or later. You can see the whole license at: https://spdx.org/licenses/GPL-2.0-or-later.html""") self.dialog = dialog # --------------------------- # Input dialog class InputDialog(HatariUIDialog): def __init__(self, parent): dialog = Gtk.Dialog("Key/mouse input", parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, ("Close", Gtk.ResponseType.CLOSE)) entry = Gtk.Entry() entry.connect("activate", self._entry_cb) insert = create_button("Insert", self._entry_cb) insert.set_tooltip_text("Insert given text to Hatari window") enter = create_button("Enter key", self._enter_cb) enter.set_tooltip_text("Simulate Enter key press") hbox1 = Gtk.HBox() hbox1.add(Gtk.Label(label="Text:")) hbox1.add(entry) hbox1.add(insert) hbox1.add(enter) dialog.vbox.add(hbox1) rclick = Gtk.Button("Right click") rclick.connect("pressed", self._rightpress_cb) rclick.connect("released", self._rightrelease_cb) rclick.set_tooltip_text("Simulate Atari right button press & release") dclick = create_button("Double click", self._doubleclick_cb) dclick.set_tooltip_text("Simulate Atari left button double-click") hbox2 = Gtk.HBox() hbox2.add(dclick) hbox2.add(rclick) dialog.vbox.add(hbox2) dialog.show_all() self.dialog = dialog self.entry = entry def _entry_cb(self, widget): text = self.entry.get_text() if text: HatariTextInsert(self.hatari, text) self.entry.set_text("") def _enter_cb(self, widget): self.hatari.insert_event("keypress 28") # Enter key scancode def _doubleclick_cb(self, widget): self.hatari.insert_event("doubleclick") def _rightpress_cb(self, widget): self.hatari.insert_event("rightdown") def _rightrelease_cb(self, widget): self.hatari.insert_event("rightup") def run(self, hatari): "run(hatari), do text/mouse click input" self.hatari = hatari self.dialog.run() self.dialog.hide() # --------------------------- # Quit and Save dialog class QuitSaveDialog(HatariUIDialog): def __init__(self, parent): dialog = Gtk.Dialog("Quit and Save?", parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, ("Save changes", Gtk.ResponseType.YES, "Ignore changes", Gtk.ResponseType.NO, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) dialog.vbox.add(Gtk.Label(label="You have unsaved configuration changes:")) viewport = Gtk.Viewport() viewport.add(Gtk.Label()) scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_propagate_natural_width(True) scrolledwindow.set_propagate_natural_height(True) scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwindow.add(viewport) dialog.vbox.add(scrolledwindow) dialog.show_all() self.scrolledwindow = scrolledwindow self.viewport = viewport self.dialog = dialog def run(self, config): "run(config) -> False if canceled, True otherwise or if no changes" changes = [] for key, value in config.get_changes(): changes.append("%s = %s" % (key, str(value))) if not changes: return True child = self.viewport.get_child() child.set_text(config.get_path() + ":\n" + "\n".join(changes)) width, height = child.get_size_request() if height < 320: self.scrolledwindow.set_size_request(width, height) else: self.scrolledwindow.set_size_request(-1, 320) self.viewport.show_all() response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.CANCEL: return False if response == Gtk.ResponseType.YES: config.save() return True # --------------------------- # Kill Hatari dialog class KillDialog(HatariUIDialog): def __init__(self, parent): self.dialog = Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, """\ Hatari emulator is already/still running and it needs to be terminated first. If emulated applications contain unsaved data, that will be lost. Terminate Hatari anyway?""") def run(self, hatari): "run(hatari) -> True if Hatari killed, False if left running" if not hatari.is_running(): return True # Hatari is running, OK to kill? response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.OK: hatari.kill() return True return False # --------------------------- # Reset Hatari dialog class ResetDialog(HatariUIDialog): COLD = 1 WARM = 2 def __init__(self, parent): self.dialog = Gtk.Dialog("Reset Atari?", parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, ("Cold reset", self.COLD, "Warm reset", self.WARM, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) label = Gtk.Label(label="\nRebooting will lose changes in currently\nrunning Atari programs.\n\nReset anyway?\n") self.dialog.vbox.add(label) label.show() def run(self, hatari): "run(hatari) -> True if Hatari rebooted, False if canceled" if not hatari.is_running(): return False # Hatari is running, how to reboot? response = self.dialog.run() self.dialog.hide() if response == self.COLD: hatari.trigger_shortcut("coldreset") elif response == self.WARM: hatari.trigger_shortcut("warmreset") else: return False return True # ---------------------------------- # Floppy image dialog class FloppyDialog(HatariUIDialog): def _create_dialog(self, config): table, self.dialog = create_table_dialog(self.parent, "Floppy images", 4, 2) factory = FselAndEjectFactory() row = 0 self.floppy = [] path = config.get_floppydir() for drive in ("A", "B"): label = "Disk %c image:" % drive fname = config.get_floppy(row) fsel, box = factory.get(label, path, fname, Gtk.FileChooserAction.OPEN) table_add_widget_row(table, row, 0, label, box) self.floppy.append(fsel) row += 1 protect = table_add_combo_row(table, row, 0, "Write protection:", config.get_protection_types()) protect.set_tooltip_text("Write protect floppy image contents") protect.set_active(config.get_floppy_protection()) row += 1 vbox = Gtk.VBox() ds = Gtk.CheckButton("Double sided drives") ds.set_tooltip_text("Whether drives are double or single sided. Can affect behavior of some games") ds.set_active(config.get_doublesided()) vbox.add(ds) driveb = Gtk.CheckButton("Drive B connected") driveb.set_tooltip_text("Whether drive B is connected. Can affect behavior of some demos & games") driveb.set_active(config.get_floppy_drives()[1]) vbox.add(driveb) fastfdc = Gtk.CheckButton("Fast floppy access") fastfdc.set_tooltip_text("Can cause incompatibilities with some games/demos") fastfdc.set_active(config.get_fastfdc()) vbox.add(fastfdc) table_add_widget_row(table, row, 0, None, vbox) row += 1 table.show_all() self.protect = protect self.fastfdc = fastfdc self.driveb = driveb self.ds = ds def run(self, config): "run(config), show disk image dialog" if not self.dialog: self._create_dialog(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: config.lock_updates() for drive in range(2): config.set_floppy(drive, self.floppy[drive].get_filename()) config.set_floppy_protection(self.protect.get_active()) config.set_doublesided(self.ds.get_active()) config.set_fastfdc(self.fastfdc.get_active()) drives = (config.get_floppy_drives()[0], self.driveb.get_active()) config.set_floppy_drives(drives) config.flush_updates() # ---------------------------------- # Hard disk dialog class HardDiskDialog(HatariUIDialog): def _create_dialog(self, config): table, self.dialog = create_table_dialog(self.parent, "Hard disks", 4, 2, "Set and reboot") factory = FselAndEjectFactory() row = 0 label = "ASCI HD image:" path = config.get_acsi_image() fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.OPEN) table_add_widget_row(table, row, 0, label, box, True) self.acsi = fsel row += 1 label = "IDE HD master image:" path = config.get_idemaster_image() fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.OPEN) table_add_widget_row(table, row, 0, label, box, True) self.idemaster = fsel row += 1 label = "IDE HD slave image:" path = config.get_ideslave_image() fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.OPEN) table_add_widget_row(table, row, 0, label, box, True) self.ideslave = fsel row += 1 table_add_widget_row(table, row, 0, " ", Gtk.HSeparator(), True) row += 1 label = "GEMDOS drive directory:" path = config.get_hd_dir() fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.SELECT_FOLDER) table_add_widget_row(table, row, 0, label, box, True) self.hddir = fsel row += 1 hddrive = table_add_combo_row(table, row, 0, "GEMDOS HD drive:", config.get_hd_drives()) hddrive.set_tooltip_text("Whether GEMDOS HD emulation uses fixed drive letter, or first free drive letter after ASCI & IDE drives (detection unreliable)") self.hddrive = hddrive row += 1 protect = table_add_combo_row(table, row, 0, "Write protection:", config.get_protection_types()) protect.set_tooltip_text("Whether/how to write protect (GEMDOS HD) emulation files, 'auto' means using host files' own properties") self.protect = protect row += 1 lower = table_add_combo_row(table, row, 0, "File names:", config.get_hd_cases()) lower.set_tooltip_text("What to do with names of files created by Atari programs through GEMDOS HD emulation") self.lower = lower table.show_all() def _get_config(self, config): path = config.get_acsi_image() if path: self.acsi.set_filename(path) path = config.get_idemaster_image() if path: self.idemaster.set_filename(path) path = config.get_ideslave_image() if path: self.ideslave.set_filename(path) path = config.get_hd_dir() if path: self.hddir.set_filename(path) self.hddrive.set_active(config.get_hd_drive()) self.protect.set_active(config.get_hd_protection()) self.lower.set_active(config.get_hd_case()) def _set_config(self, config): config.lock_updates() config.set_acsi_image(self.acsi.get_filename()) config.set_idemaster_image(self.idemaster.get_filename()) config.set_ideslave_image(self.ideslave.get_filename()) config.set_hd_dir(self.hddir.get_filename()) config.set_hd_drive(self.hddrive.get_active()) config.set_hd_protection(self.protect.get_active()) config.set_hd_case(self.lower.get_active()) config.flush_updates() def run(self, config): "run(config) -> bool, whether to reboot" if not self.dialog: self._create_dialog(config) self._get_config(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: self._set_config(config) return True return False # --------------------------- # Display dialog class DisplayDialog(HatariUIDialog): def _create_dialog(self, config): table, self.dialog = create_table_dialog(self.parent, "Display settings", 9, 2) row = 0 col = 0 self.skip = table_add_combo_row(table, row, col, "Frameskip:", config.get_frameskip_names()) self.skip.set_tooltip_text("Set how many frames are skipped to speed up emulation") row += 1 self.slow = table_add_combo_row(table, row, col, "Slowdown:", config.get_slowdown_names()) self.slow.set_tooltip_text("VBL wait multiplier to slow down emulation. Breaks sound and large enough slowdown causes mouse clicks not to work.") row += 1 topw, toph = config.get_desktop_size() maxw = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 320, topw, 8) maxh = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 200, toph, 8) maxw.set_tooltip_text("Preferred/maximum zoomed width") maxh.set_tooltip_text("Preferred/maximum zoomed height") maxw.set_digits(0) maxh.set_digits(0) self.maxw = table_add_widget_row(table, row, col, "Max zoom width:", maxw) row += 1 self.maxh = table_add_widget_row(table, row, col, "Max zoom height:", maxh) row += 1 vbox = Gtk.VBox() force_max = Gtk.CheckButton("Force max resolution (Falcon)") force_max.set_tooltip_text("Force maximum resolution to help recording videos of demos which do resolution changes") self.force_max = force_max vbox.add(force_max) desktop = Gtk.CheckButton("Keep desktop resolution (scales)") desktop.set_tooltip_text("Keep screen resolution in fullscreen. Avoids potential monitor res switch delay & resulting sound skips") self.desktop = desktop vbox.add(desktop) borders = Gtk.CheckButton("Atari screen borders") borders.set_tooltip_text("Show overscan borders in ST/STE low/mid-rez and in Falcon color resolutions. Visible border area is affected by max zoom size") self.borders = borders vbox.add(borders) led = Gtk.CheckButton("Show overlay led") led.set_tooltip_text("Show overlay drive led when statusbar is not visible") self.led = led vbox.add(led) statusbar = Gtk.CheckButton("Show statusbar") statusbar.set_tooltip_text("Show statusbar with TOS and machine info, disk leds etc") self.statusbar = statusbar vbox.add(statusbar) crop = Gtk.CheckButton("Remove statusbar from screen capture") crop.set_tooltip_text("Crop statusbar from screenshots and video recordings") self.crop = crop vbox.add(crop) table_add_widget_row(table, row, col, None, vbox) table.show_all() def _get_config(self, config): self.slow.set_active(0) self.skip.set_active(config.get_frameskip()) confw, confh = config.get_max_size() self.maxw.set_value(confw) self.maxh.set_value(confh) self.force_max.set_active(config.get_force_max()) self.desktop.set_active(config.get_desktop()) self.borders.set_active(config.get_borders()) self.led.set_active(config.get_led()) self.statusbar.set_active(config.get_statusbar()) self.crop.set_active(config.get_crop()) def _set_config(self, config): config.lock_updates() config.set_slowdown(self.slow.get_active()) config.set_frameskip(self.skip.get_active()) config.set_max_size(self.maxw.get_value(), self.maxh.get_value()) config.set_force_max(self.force_max.get_active()) config.set_desktop(self.desktop.get_active()) config.set_borders(self.borders.get_active()) config.set_led(self.led.get_active()) config.set_statusbar(self.statusbar.get_active()) config.set_crop(self.crop.get_active()) config.flush_updates() def run(self, config): "run(config), show display dialog" if not self.dialog: self._create_dialog(config) self._get_config(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: self._set_config(config) # ---------------------------------- # Joystick dialog class JoystickDialog(HatariUIDialog): def _create_dialog(self, config): table, self.dialog = create_table_dialog(self.parent, "Joystick settings", 9, 2) joy = 0 self.joy = [] joytypes = config.get_joystick_types() for label in config.get_joystick_names(): combo = table_add_combo_row(table, joy, 0, "%s:" % label, joytypes) combo.set_active(config.get_joystick(joy)) self.joy.append(combo) joy += 1 table.show_all() def run(self, config): "run(config), show joystick dialog" if not self.dialog: self._create_dialog(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: config.lock_updates() for joy in range(6): config.set_joystick(joy, self.joy[joy].get_active()) config.flush_updates() # --------------------------------------- # Peripherals (midi,printer,rs232) dialog class PeripheralDialog(HatariUIDialog): def _create_dialog(self, config): midi = Gtk.CheckButton("Enable MIDI") midi.set_active(config.get_midi()) printer = Gtk.CheckButton("Enable printer output") printer.set_active(config.get_printer()) rs232 = Gtk.CheckButton("Enable RS232/MFP (!Falcon)") rs232.set_active(config.get_rs232()) scca = Gtk.CheckButton("Enable RS232/SCC-A output (MegaSTE/TT/Falcon)") scca.set_active(config.get_scca()) scca_lan = Gtk.CheckButton("Enable RS232/sCC-A Lan output (MegaSTE/TT/Falcon)") scca_lan.set_active(config.get_scca_lan()) sccb = Gtk.CheckButton("Enable RS232/SCC-B output (MegaSTE/TT/Falcon)") sccb.set_active(config.get_sccb()) dialog = Gtk.Dialog("Peripherals", self.parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, (Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) dialog.vbox.add(midi) dialog.vbox.add(printer) dialog.vbox.add(rs232) dialog.vbox.add(scca) dialog.vbox.add(scca_lan) dialog.vbox.add(sccb) dialog.vbox.show_all() self.dialog = dialog self.printer = printer self.rs232 = rs232 self.scca = scca self.scca_lan = scca_lan self.sccb = sccb self.midi = midi def run(self, config): "run(config), show peripherals dialog" if not self.dialog: self._create_dialog(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: config.lock_updates() config.set_midi(self.midi.get_active()) config.set_printer(self.printer.get_active()) config.set_rs232(self.rs232.get_active()) config.set_scca(self.scca.get_active()) config.set_scca_lan(self.scca_lan.get_active()) config.set_sccb(self.sccb.get_active()) config.flush_updates() # --------------------------------------- # Path dialog class PathDialog(HatariUIDialog): def _create_dialog(self, config): paths = config.get_paths() table, self.dialog = create_table_dialog(self.parent, "File path settings", len(paths), 2) row = 0 self.paths = [] # sort by path label paths.sort(key=lambda info: info[2]) for (key, path, label) in paths: label = "%s:" % label fsel = FselEntry(self.dialog, title=label, validate=self._validate_fname, data=key) fsel.set_filename(path) self.paths.append((key, fsel)) table_add_widget_row(table, row, 0, label, fsel.get_container()) row += 1 table.show_all() def _validate_fname(self, key, fname): if key != "soundout": return True if fname.rsplit(".", 1)[-1].lower() in ("ym", "wav"): return True ErrorDialog(self.dialog).run("Sound output file name:\n\t%s\nneeds to end with '.ym' or '.wav'." % fname) return False def run(self, config): "run(config), show paths dialog" if not self.dialog: self._create_dialog(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: paths = [] for key, fsel in self.paths: paths.append((key, fsel.get_filename())) config.set_paths(paths) # --------------------------- # Sound dialog class SoundDialog(HatariUIDialog): def _create_dialog(self, config): table, self.dialog = create_table_dialog(self.parent, "Sound settings", 6, 2, "Apply") row = 0 col = 1 fullspan = True self.enabled = table_add_widget_row(table, row, col, None, Gtk.CheckButton("Sound enabled"), fullspan) row += 1 col = 0 self.hz = table_add_combo_row(table, row, col, "Sound frequency:", config.get_sound_values()) row += 1 self.ymmixer = table_add_combo_row(table, row, col, "YM mixing method:", config.get_ymmixer_types()) self.ymmixer.set_tooltip_text("Which method is used to mix YM voices") row += 1 bufsize = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 100, 10) for pos in ((0, "auto"), (10, "min"), (100, "max")): bufsize.add_mark(pos[0], Gtk.PositionType.BOTTOM, pos[1]) bufsize.set_tooltip_text("SDL sound buffer size in ms. 0 = use default value. In some situations, SDL default may cause large (~0.5s) sound delay at lower frequency. If you have this problem, try with e.g. 20 ms, otherwise keep at 0.") bufsize.set_digits(0) self.bufsize = table_add_widget_row(table, row, col, "Sound buffer size:", bufsize) row += 1 vbox = Gtk.VBox() self.sync = Gtk.CheckButton("Emulation speed synched to sound output") self.sync.set_tooltip_text("Constantly adjust emulation screen update rate to match sound output. Can help if you suffer from sound buffer under/overflow.") vbox.add(self.sync) self.mic = Gtk.CheckButton("Enable (Falcon) microphone") vbox.add(self.mic) col = 1 table_add_widget_row(table, row, col, None, vbox, fullspan) row += 1 table.show_all() def _get_config(self, config): enabled, curhz = config.get_sound() self.enabled.set_active(enabled) self.hz.set_active(curhz) self.sync.set_active(config.get_sync()) self.mic.set_active(config.get_mic()) self.ymmixer.set_active(config.get_ymmixer()) self.bufsize.set_value(config.get_bufsize()) def _set_config(self, config): config.lock_updates() enabled = self.enabled.get_active() hz = self.hz.get_active() config.set_sound(enabled, hz) config.set_mic(self.mic.get_active()) config.set_sync(self.sync.get_active()) config.set_ymmixer(self.ymmixer.get_active()) config.set_bufsize(self.bufsize.get_value()) config.flush_updates() def run(self, config): "run(config), show sound dialog" if not self.dialog: self._create_dialog(config) self._get_config(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: self._set_config(config) # --------------------------- # Trace settings dialog class TraceDialog(HatariUIDialog): # you can get this list with: # hatari --trace help 2>&1|awk '/all$/{next} /^ [^-]/ {printf(" \"%s\",\n", $1)}'|sort tracepoints = [ "acia", "aes", "bios", "blitter", "cpu_disasm", "cpu_exception", "cpu_pairing", "cpu_regs", "cpu_symbols", "cpu_video_cycles", "crossbar", "dmasound", "dsp_disasm", "dsp_disasm_mem", "dsp_disasm_reg", "dsp_host_command", "dsp_host_interface", "dsp_host_ssi", "dsp_interrupt", "dsp_state", "dsp_symbols", "fdc", "gemdos", "ide", "ikbd_acia", "ikbd_cmds", "ikbd_exec", "int", "io_read", "io_write", "keymap", "mem", "mfp_exception", "mfp_read", "mfp_start", "mfp_write", "midi", "midi_raw", "natfeats", "nvram", "os_base", "psg_read", "psg_write", "scc", "scsi_cmd", "scsidrv", "scu", "vdi", "videl", "video_addr", "video_border_h", "video_border_v", "video_color", "video_hbl", "video_res", "video_ste", "video_sync", "video_vbl", "xbios" ] def __init__(self, parent): self.savedpoints = None hbox1 = Gtk.HBox() hbox1.add(create_button("Load", self._load_traces)) hbox1.add(create_button("Clear", self._clear_traces)) hbox1.add(create_button("Save", self._save_traces)) hbox2 = Gtk.HBox() vboxes = [] for idx in (0,1,2,3): vboxes.append(Gtk.VBox()) hbox2.add(vboxes[idx]) count = 0 per_side = (len(self.tracepoints)+3)//4 self.tracewidgets = {} for trace in self.tracepoints: name = trace.replace("_", "-") widget = Gtk.CheckButton(name) self.tracewidgets[trace] = widget vboxes[count//per_side].pack_start(widget, False, True, 0) count += 1 dialog = Gtk.Dialog("Trace settings", parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, (Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)) dialog.vbox.add(hbox1) dialog.vbox.add(Gtk.Label(label="Select trace points:")) dialog.vbox.add(hbox2) dialog.vbox.show_all() self.dialog = dialog def _get_traces(self): traces = [] for trace in self.tracepoints: if self.tracewidgets[trace].get_active(): traces.append(trace) if traces: return ",".join(traces) return "none" def _set_traces(self, tracepoints): self._clear_traces() if not tracepoints: return for trace in tracepoints.split(","): if trace in self.tracewidgets: self.tracewidgets[trace].set_active(True) else: print("ERROR: unknown trace setting '%s'" % trace) def _clear_traces(self, widget = None): for trace in self.tracepoints: self.tracewidgets[trace].set_active(False) def _load_traces(self, widget): # this does not load traces, just sets them from internal variable # that run method gets from caller and sets. It is up to caller # whether the saving or loading happens actually to disk self._set_traces(self.savedpoints) def _save_traces(self, widget): self.savedpoints = self._get_traces() def run(self, hatari, savedpoints): "run(hatari,tracepoints) -> tracepoints, caller saves tracepoints" self.savedpoints = savedpoints while 1: response = self.dialog.run() if response == Gtk.ResponseType.APPLY: hatari.change_option("--trace %s" % self._get_traces()) else: self.dialog.hide() return self.savedpoints # ------------------------------------------ # Machine dialog for settings needing reboot class MachineDialog(HatariUIDialog): def _machine_cb(self, widget, types): machine = types[widget.get_active()].lower() if "mega" in machine: self.clocks.set_active(1) self.cpulevel.set_active(0) elif "st" in machine: self.clocks.set_active(0) self.cpulevel.set_active(0) elif machine == "tt": self.clocks.set_active(2) self.cpulevel.set_active(3) elif machine == "falcon": self.clocks.set_active(1) self.cpulevel.set_active(3) self.dsp.set_active(2) def _create_dialog(self, config): table, self.dialog = create_table_dialog(self.parent, "Machine configuration", 6, 2, "Set and reboot") # option for non-combos fullspan = True col = 0 row = 0 types = config.get_machine_types() self.machine = table_add_combo_row(table, row, col, "Machine:", types, self._machine_cb, types) row += 1 vbox1 = Gtk.VBox() self.blitter = Gtk.CheckButton("Blitter (ST only)") self.blitter.set_tooltip_text("Whether to emulate add-on Blitter chip for ST") self.timerd = Gtk.CheckButton("Patch Timer-D") self.timerd.set_tooltip_text("Improves ST/STE emulation performance, but some rare demos/games do not work with this") vbox1.add(self.blitter) vbox1.add(self.timerd) table_add_widget_row(table, row, col, "Misc:", vbox1, fullspan) row += 1 self.cpulevel = table_add_combo_row(table, row, col, "CPU type:", config.get_cpulevel_types()) row += 1 vbox2 = Gtk.VBox() self.compatible = Gtk.CheckButton("Prefetch emulation") self.compatible.set_tooltip_text("Needed for overscan and other timing sensitive things to work correctly. Uses more host CPU") self.cache = Gtk.CheckButton(">=030 data cache emulation") self.cache.set_tooltip_text("Data cache emulation increases emulated code performance, but uses significantly more host CPU") self.exact = Gtk.CheckButton("Cycle exact emulation") self.exact.set_tooltip_text("Cycle exactness increases emulation accuracy, but uses more host CPU") self.mmu = Gtk.CheckButton("MMU emulation") vbox2.add(self.compatible) vbox2.add(self.cache) vbox2.add(self.exact) vbox2.add(self.mmu) table_add_widget_row(table, row, col, None, vbox2, fullspan) row += 1 self.clocks = table_add_combo_row(table, row, col, "CPU clock:", config.get_cpuclock_types()) row += 1 self.fpu = table_add_combo_row(table, row, col, "FPU type:", config.get_fpu_types()) row += 1 self.softfp = Gtk.CheckButton("Accurate FPU emulation") self.softfp.set_tooltip_text("Emulate FPU in software instead of using host FPU. Uses more host CPU") table_add_widget_row(table, row, col, None, self.softfp, fullspan) row += 1 self.dsp = table_add_combo_row(table, row, col, "DSP type:", config.get_dsp_types()) self.dsp.set_tooltip_text("Disable DSP to improve Hatari performance significantly for Falcon programs that work (also) without it. Some programs using DSP unconditionally, may work with dummy mode (and just lack e.g. sound).") row += 1 self.monitors = table_add_combo_row(table, row, col, "Monitor:", config.get_monitor_types()) row += 1 self.memory = table_add_combo_row(table, row, col, "Memory:", config.get_memory_names()) row += 1 # use next table column col = 2 ttram = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 1024, 4) ttram.set_digits(0) ttram.set_tooltip_text("TT-RAM requires Falcon/TT and its use disables 24-bit addressing.") self.ttram = table_add_widget_row(table, row, col, "TT-RAM:", ttram, fullspan) row += 1 label = "TOS image:" fsel = self._fsel(label, Gtk.FileChooserAction.OPEN) self.tos = table_add_widget_row(table, row, col, label, fsel, fullspan) row += 1 table.show_all() def _fsel(self, label, action): fsel = Gtk.FileChooserButton(title=label) # Hatari cannot access URIs fsel.set_local_only(True) fsel.set_width_chars(12) fsel.set_action(action) return fsel def _get_config(self, config): self.machine.set_active(config.get_machine()) self.blitter.set_active(config.get_blitter()) self.timerd.set_active(config.get_timerd()) self.cpulevel.set_active(config.get_cpulevel()) self.compatible.set_active(config.get_compatible()) self.cache.set_active(config.get_data_cache()) self.exact.set_active(config.get_cycle_exact()) self.mmu.set_active(config.get_mmu()) self.clocks.set_active(config.get_cpuclock()) self.fpu.set_active(config.get_fpu_type()) self.softfp.set_active(config.get_fpu_soft()) self.dsp.set_active(config.get_dsp()) self.monitors.set_active(config.get_monitor()) self.memory.set_active(config.get_memory()) self.ttram.set_value(config.get_ttram()) tos = config.get_tos() if tos: self.tos.set_filename(tos) def _set_config(self, config): config.lock_updates() config.set_machine(self.machine.get_active()) config.set_blitter(self.blitter.get_active()) config.set_timerd(self.timerd.get_active()) config.set_cpulevel(self.cpulevel.get_active()) config.set_compatible(self.compatible.get_active()) config.set_data_cache(self.cache.get_active()) config.set_cycle_exact(self.exact.get_active()) config.set_mmu(self.mmu.get_active()) config.set_cpuclock(self.clocks.get_active()) config.set_fpu_type(self.fpu.get_active()) config.set_fpu_soft(self.softfp.get_active()) config.set_dsp(self.dsp.get_active()) config.set_monitor(self.monitors.get_active()) config.set_memory(self.memory.get_active()) # changes 24/32-bit addressing based on TT-RAM & machine type config.set_ttram(self.ttram.get_value(), self.machine.get_active()) config.set_tos(self.tos.get_filename()) config.flush_updates() def run(self, config): "run(config) -> bool, whether to reboot" if not self.dialog: self._create_dialog(config) self._get_config(config) response = self.dialog.run() self.dialog.hide() if response == Gtk.ResponseType.APPLY: self._set_config(config) return True return False hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/gentypes.py000077500000000000000000000027141504763705000247140ustar00rootroot00000000000000#!/usr/bin/python3 # # Utility to generate from Hatari C-code Python code for mapping # Hatari configuration variable names and types of those variables. # # Copyright (C) 2012-2025 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os, re, sys # match first two items (variable name and type) from lines like: # { "bConfirmQuit", Bool_Tag, &ConfigureParams.Log.bConfirmQuit } reg = re.compile("\"([a-zA-Z0-9_]+)\",\\s*([BFIKS][a-z]+)_Tag\\s*,") vartypes = {} for line in sys.stdin.readlines(): match = reg.search(line) if not match: continue key,value = match.groups() if key not in vartypes: vartypes[key] = value continue if vartypes[key] != value: print(f"ERROR: variable '{key}' already with type '{vartypes[key]}', not '{value}'!") sys.exit(1) print(f"# content generated by {os.path.basename(sys.argv[0])}") print("conftypes = {") for key in sorted(vartypes.keys(), key=str.casefold): print(f""" "{key}": "{vartypes[key]}",""") print("}") hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/hatari-icon.png000066400000000000000000000007711504763705000254060ustar00rootroot00000000000000PNG  IHDR Tg0PLTEg*ny=f2J[,jrǥֺƣtRNS#]bKGD pHYs]EFIDAT(]NAFq2 l5в{ǿPvT#oX Y2$:&g|'rFܽ;}{X5uKz5N` E^x >:!+2f+V2k %+iQFnyA":KC(ְ+,^rӌTCP YN*C VZoYk{|\s[M16-ғX̺n1 誐ǖix2m_XʒY;}Qh^f|7 ҘY`R\RN=Ic|JrIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/hatari-logo.png000066400000000000000000000577401504763705000254260ustar00rootroot00000000000000PNG  IHDRX%PLTE#,6,"2""52%+)""2:"9*43'DBGS\BNH\bnxbmx$B%I"R/S6B9[*#b#f#o&y6b4q5xD T A6B"$J2,an{b"b2TB`Lr\|bCBBFFVD\PB]OCB5RB4ZB<\R>DbBnE|Tb Sm]bUyaDkBbkbro}bz$RRDbbEpbRrra~r`""wm"]6N1S!bR:Z5ybd-p$v"JzVjAHJBBBBb""#(566#6t U Gk%2#5EFFT\\ZENZhzr$RDbDFTbo{zdbbă ""e~BIQb owBHT ] oyb#RTjT $,6@G T ] blyb#NDvDbcpdDgtt‚‚dDd⢄¤Ҵ;˜tRNS@ftEXtSoftwaregif2png 2.2.59 IDATx `u.rrպ[ZĶ*HϵM6BG،#! ʥ8iRTy))W&v!KwBe1#uhιwOW"0?ř{ι>ۭ6#ܘ6xMEuy=mO~~vZok6=a lF %7r{X+yj7tJ^Ft3|,/w;9FD<7Qٱ9.nܭܹv3pdwWէ}\Qdl1F? ר{ML{I\DGɑZܤٯS 2WF2_zS(g($Gyk"'Ow|I_PʤUʏ>8Pzܭ ܜὺw}8!Q*ΕЕW1t# =]t% u)ٯ&mQ|دa +_XTu7hoܫG>ۤgQ6W3e[·0jޒ :PKLѯ_ o+QGH>o\Ǣ_qPb+#.1gs48 ;w|jЧܲ v2L}c8^k.:6}Ə><37xMEMjv[UýAJ/3!(y۸"+J |kSdH#58!χg̻D !_?}:hN>Ju/!/`:ҭ?|zlٟI& p)YG3L1ìݛذavWBG>ی\bPE"g jo8!@lO(5c vܾDsDfYɶ _39v)6Sy*l6`=^31ٯ2oW_[xxmAG~Nhyg0{O0}F(o2-=JqgQ`lg}Yd^7~-xG9g,'OiZ}\[KO~2QI2_]W~d-@B0 *BA }Y|縣_ 8 xaN=^G,GcyK^ > oqR,WZy-ަ~ѯ+TZW&2աbI9} ʗ,K477Ue0x۶uHl6{Q; 96musySTsɸ.&:RHnY]rd5}XOψᕞmIED-ȷ>+|)L,rH?g$^];uX蘲QK=;XZ'}/(HZuF)] $Ofmp٨Uzl0)+?'ЭZb!` *`-~حɻ$S ?lR+vZ64a4re 2wvH$0‰dW\4MzZF1hG[ú>l<8>7Wթ`5ğ (-`(ŹHɔ>YQ>G|͔,Jl90&RƸL0F8"ew[foR+;==igcy0"/iۘ+4gʠ {utk˛"{$RƖz2"i@U녏0k`&PkofT [jN7Y"KgTqKb9C *A P ]2"XG#dyveLގKu|pQVg6kiNJoj0>8>_AssZMm*Ze7[:ZQӀAFG?/%gz-}2nkuOCͥ> b=vbbq[l0id4hnIt ՛[zєٱ' 3zEfr frڵ֚i[8g%1_Y9lpS"V( EMu[UZ`> EV&vk:==:X0>F3ü|%fbmqr&J_|pziуj##W-N3V+&{XEV`TU]@== >032{کWZ36ڪ xְ^ qA83}1_س={ fP#Ba",Rڨ(633ǸfC㳳gol7/X\+Yn#<žGGN>ׄ<;SGx/D3ݪj[#x#T4JcIQ[ ĮA؝]2|&=Cǯ]HgbUsթo~|, 7l0>thJa 4&H~{s7 {?^V >BV+l-stB*y㳺>{|%464cH1aཐ4|ΤAgSsg\w޺r[[4A.'(h4"(\2# Lf m0/Y"v 9[8 n%#W дܻY#O'z9ߗ/~S4lxGdl؂4YeᨒRF\5[?4C[%D|bϤXp[upcfffGg V>|kz:b[Vih+cJۭ[Knb:8eno3؜YT>+61a Rx-IF[Ź|·( 9ᗞ\@ghss2)@(w+tALh<ĒZq"~P4N4,`VV.N{o uΗCŒɷ\|J֪Tte j^_ܝn)4t(X~saYn8 ~EUpyC%Z%FI&kJr9!or1b.G7w 'ز pYSqvkrgiA-[`6n 338w햝oYazt}ՑdF|-C, hnʷ&9W( M}d*;7[4ih<ޭ;2o)!heey reIkQkC,*6f `4U|@%j!W٭vY82{>n83jvi) >COݺ~nts=CjA-~rژ䗿>m9;6Mpg:ʓڿɴRD*m8Z=J~éiS6]劭ոDMFb._F;,sQNQ[+0>:=wqhGƛo҆k=aw"`1%1"%[m(=^?¸[l1ubܜ fo篧Cwxҋil>E,0D#5c3e˅Z?tbB=}r#C-M ٜpBd0T= JKNᢊ.1Vu@k EDpUk}B8 9MЯN~pZqm=gr'V3"蝛Jc{f̡k;I$m *uż2r_bg,`flq&z'N\LrQ8w2=Of_z饼j{ډڀ*=zOOsZMZ7Zvގ |fj<|h\r"8BNDEYtmTϿkgw l!cv`zH#2!3pv*Qj.Sx*eqJy/ :"޾ctRh< ?>qjsl{) ;B9qYaܑ`=#e,,]045mho8=@%u7ҧ  E%d <c$N[ H/ |ysw [wwmZ/FbٮGXTɏUςCGL{ɞ5av±jdY `6(qVm5 ¯Ж=wvޓtݝ?SHlaaYiPzF(#n k˲ st;^)`3V Z ߳N]w_l7 n /yfrNij٦g]&j\ ƹ|KNdKg|($Mغ{8~tvo6'>y(8 w|r|vkPE2#Ww~rpvVͪV 9wX"YTR g%U״o{߰o{C,)iF8k58XYF"_Y_&DYG[Kg ;ɹ~e3'_.vELf bv۬nUP] \Y~ժ5xB)&借S ' ka`%~K:r'Ǘ>֙S8yĢ?8Ttc0=,;=:w'@b˖epRkB(`O!zvS +;s=su+gAb.dB ދXVרX{4AFx}Ŗ@h?b k 7@1`:LZo{cU YR1UڃgYe_]v;eW&QaH hR/VeTP @+BY+e`1멃X7ZhOZչÓXVTJVڛgU *)ݺ._rˇ>ԾiӦ}%p*.HqpOh^`+\ &8X4 VX-ӛrxӃiI5 ko hsΘ6b]n?]y릟u/ϽM^< 2 #Ƶ\Y?h~p !hx_LS]/?]Hwծ kN%yItkN)و3~jYNd]: Xcd)䠴8?/ A1m[CVaHL_<ݝJϚ֧ѠY C3J%#ȜEz[7nt|[gn+cLcVzOJh~XV.dMUg}fg~?џH ix6⏔ ;lsT*ǣ-[zpǎdwkxVUAtWa':<. DZo_`Y5{a}F@ /\J2:0 rжtuUCY類ut(-c+E]}u*g/—}0jZ!5[hfV"luLtۧMzŐY1Dbn@ڪxI/.p% ܨܬgQff= *zk(Pb44]8/u®BW04'yƨ-O' IDATj?fm\P,ܐ!SZCKrVe+W0*O|b)܋6V1AJʹ0nctw,LyHM*Ȓ*Ȝ(k$I2g+5DEETTtXp*OX3g2CPP!5ޡ.@]fVYYVʘ^dLCLęq X_e8ʢQ K$m'5  c~BX0Q1okf˺k]ssH3RxU3IJLؘ#5#si:IyXdwu ^Ya=hZa0AGsj+&ز33SzG-9SV*Ȳ'g' b lce*e3#Vx9N[ҋ!]]wpf4lS=Ͷ֑+5ۤW' heb>uN/>}:0D D#0X:ԬGҠ <5 qUXe%j$VƢL+qH%MJbOGz(kP,zp3Ҍ1SraNJd)boθΪWbق$m_k"}G}@ru1w9xXrQ~N>*ȁJ~ɓH7dCz<܀HH'ɈZr1]y.רq;wiX"^Xj%}lyQf,4P(ԤM `~Jwd2EM" #?r(1 3ruߡf3GZk +U Z&6 Ux"=ʞxz@E+E,vZb`< jYHjV_ ?ݳI>gO /W1 DzOZ2,3Uj=֌Y{ԓX^$I$wa$$ڬ<Uqb 8]\0g+"$e+.B3'$F>' gNH\r3y)ZHAAA")4FǪQヤɇy T|䑇"+]M2JQJכlVX܋! 'zf Iv0dRV "ERo^kh8+1{P'1\.L@Xu\hO) {sܩ. MݡFjcO/KB,KM*̃$:>(۩ٛ=t$/o:vl4=v+#/Jw_5e([j4]XJ,%P2;ydj$6eVLi]\cE#Q\:G*/I0I0 _Y>:F87zG_؋XRﲨ.囚J ~Lz!2W9g"zuF_YY|q =M Ѧ zSu~λ*c' #]A,(..Ҡ$`HB,,%)Y1UyK>C>A?p:cU'Zi,ʄ6IiZ _KQwe!ʪ^XUxn,zoxM+ AfrUj: "kV9%6M iKoncȢߥGM_xGog3f`n_9ڜhyFLfo-KD'e ÷\լZB**W.JS\&0[pjOERM"ԶaD<6Q)'xߡ#3X7}半X3G=ͳ#$Ѫ,Xv[XM+䲹ЮEAzz*bkg G qae6Qɕzʮ|=FS{ gf=a+`]KIY kZ9 xx bU_'@ZȣA8Go`{X\9Lz S#=ә잋*qfpե! ryiPe~'"DE(19$s3oq!!!DlBrG~|GLԡg!:YVK9D^9VQndUJë(\ccM{Ht Fe J%M/ce#rQ(wgbI=KF7/=j 5h-mdWYzzF90~KD-@e W0 bpXK&urw~ %SS^zg•,JT׳1 8&*_ @ I4'(@(KȲKדXU!YS?SEϪս!!V-Y}%KgBI?aeb4ZJY2[Y b3KwPzr2WѳS`]svb1|*WU֯_qĻÃ30נZz@pv6XʃXB!i/O{tg&,Cw9DÙFgU'mx\-WWӨ`ianx:T^̟A͡ nu>̫CJXzA}uNYTQsPpz8~*Bb9puzPs@w 0F~k;~XbM:$+$z)ZJRⰚeIX8#L#ƬĪ\_*iH|J4A"%Ě/D˄(eVbuxC#0"Mwu{KYS<|ʖVKx6l~x .:b! %RMzf,5]\4i:n.H>4} XC{Bt].+)I :U=YX#U,wG+=f̨\wU׳•J&Dk:pCјr[%8qĀ [™S_Qe˅XĒ^Pp#Uەs6bVq,Oݠ45F$X@U+)3e 50`{0E΂8u7v+^(T+6vSېzPGF8xVVVޔrFX++oV׳~Oi`2DŽM764M4۵Z!d`/./TzSzKs$^ah* ES.+0sڋxߥ~3ov&V.;ZϒLeJݥaXUί(SZ&͟X=GW9y4O _.;ɍQXo l]0̤FFH՟elHRs50⌝V2-LmvMFh4YtNׅ6 *e.Xʟ͸3T< +zV6"0ÐlԒYtfl{ZW9.K(h/EYX,pd5+_$,/bU؜Չ& qЪESIobhD$؟m+Bjx ~kזDj)s$<. Q{$TCmr,YP ӣ19V~/ch A㺬bPX׏nZFHՔ9̠N7DeRu0+J/2ܐeQ!@>x 3` 6fVĪQ7ډr7tS.41]2vr"N Q\<p༢?Jeot-X:GZ"t5>fn<+7_T֋|Si` V}c)uVe_*~]jڥv]jjKpfpӒG.KaX4+#I-R5Me6yB%h=տZblogYVӋsE~ S\b@k7)6Յ󾷋D`: Xpr j9V0Ƕ-V+.m?47w WV` -",[m_ںu<\)Y*ϐp|Ϝǫ/m߯-"nV=JBadQȚG<ո'v>WeER+^.J:戜U6f58F%6IXr2`k kk gK_`U'l8uT/!7Q7R+#Wņ5lzHEZ`RjW XD=gz:z"je"әi<{Xz '!@2_XݴP`q&i% HCg-46 "D5IFWRE _fbThPض~Ai vuR=[fR'X*9Y>uEaD.7Dzzz# (U ar KYK`r4it=VJ;ꡒryE9֢>+>Ϸi$,$r·as9V _ݖ82c opgޭi՟E?V[QzU⋀}O7UC(DwVOGD͛{"CUm w%Bwh̵t)5l "VM^s]Jߒݲ,TTeS7-5KU,=N䠮\(so鿨҉dT]JkK@8&F[7%g$|K =Qu{r,gRf!t0#LiIGF2r% nZ6+ǺPVWe6Uk6oXsa;w5?6~ ?f#|'[U[}0}sAƘȮX, M߿J/"_YvD XUXe.zdpm:Rjs}@Y?uWdۙ,OӧO2s[\S*'T䕲BH(f:8,cPQGnGGCaÚnx/dK t:gKa{KTxXԩm Jh 5њ@ [ǚ`I BExm|u\0"za[NK{ASjO" '˺VMQ֓I0B ̘RK{{ Qޭ:&Ge*LAV=чkr+j KYTIDAT-C4>m7QRJq4z9'gn>ޛmltud}g}`(µ}i"W\:WD\K;wJVd\;w>!WGlyyX='wx7J.tdL<*8e+w:>hގ-%6vgL݂@T_tEx'Np#(O O` \`4pǹ! TF MҾÀPzb@#X;v'nnƃAlͼ:;NX܄Ur?wD<6cs77x1޾J ~YuP MpMTA'&>6RQ@r $׉U,*h^[zT39Gs7%呏;f*]DzLOx*MHz\㳆:lI-a+m7h!V⪊ "r .m,/Yrykil)Q 1&+hHP5QE/Q= !<9GpIk>jk?]OnFر\׼ݟ1T>.TazgOWLOeL&*}=)T2oa-cL +ط^R?|J@ e~)uVu+k\} b7Q&f>~wKP`?LdA.\ y`W_K~#w~cVݴzj7?Հc؝y]_s>>@@%l~Ujd&ِgM cf^u,0Rj> `{;e!=`bE\ ҍ V✯gNj pۿs_=׀]G>u0:W{p7䎁'~Z(yT{6!mSIÝf&Ur5n ĿZ})G/Û2Vg睹5:zԭ;;;؅|%P7Ʊ++XqN'&pe(RKӯ=5[@V6^E,No k,-;jbDLlИ.?^a^(7{x=((ÉBQiTKڕȲ:bj*9a!,HIw:upWʬaQveH մXE c*-mO4BUq>A\Z0^.p_ӝy]u 5zw Ā3'G@U e aSewC}]um*Yu2H33mm~ ct"4mJq*ڀQDb,T`@7QdjByK2/0m3Ng/_xRB~N8g}4E+a2d[OÕp ZDRAk  `S ;p8gN|i':FFoX\}C.w ;tݻeMIQeԗi ^KT|CQL\8?VF.C1`;qPʼn̑81t\ڷ/EkRQi4&h\-;3}rBdrw| w܈w$  9Iֈ AIϏ5XNp[H*ǪcqJ<+틑epEЖs (Y/CQ?8-I zpBVr(&xx*X.7*pf:wecM!ny|]rՓ\jA~t T$pq =i!=~ZO[Z@.cgEi"Dc,0+DPM'&A.VҎ `3 ֠k,W={‹,`**#,@c#Pe_"Oh@,frGcwj]v<#r 5Lvy 1ć'>c~Ќ((Fc~,y>_\$O\@~,o4ˆ[ZR-TZRO .E8AK=*Co0מ@?<8VA⪘,5 ²,Ϗez^hTg軨mI{\/{vam{x*iAcVb,::0ߢ q4Fp%`=a E[~ʸ_Ft3-Y*#.$b$sXʏ5eQsNc~oVp6yxؕ$IXq QSW4@@D$Ư,<B kԃ3Y@i - S>(Xjի,Mqx=o"Xe?Vpj^~,4NE"2.4Ǻ]aNV1j}pR VQH#\V;Cq k|7`9q@~G2N]*RwZ}gi t~tyϦWVg:XcEz1aA避4 1NC8CW|`ZT Vb2*YV` °N kwE?Xʮ]Sݕՠ^cMIJ!c{h܏eVHkZk|EX 9FӃf&( 50hއ0_޲hcy- VF,BD.r7LJp8uS'gEμz 5N'&hakぉ؃b}%bxmrZխxL7KP; ϨKF 6)s=B=в@2%Vc~,egY|uVs7ثkWw* g0NJ vW#@ORIWMb,9$x?V \[F)7ܕ2 RS~,Uaۍ 5jԦבoXX2/UUAà~o_/&Y`}, R&@@Td .M J޸vY%,tw+4;c ;d#x]fT &qԩ̼XV`Fm#߰(0<Ŝq w.c%d)?.0@9f nf}k|E@^KEz壋: %O=0y,32(+tʢ {]S@C:iYFm_.+"q,a_cyT= 9WX79WX4ቿhQ3UXoމ`ltnLY_)z?zcTlG+ 00달DN kߺ}B@DZV)#`ۍՕccW&jG󀫣'UOU,GE}Tq.-+}|1$R7~Yˁc9- XOe< <4*Ġɤx⛌R49u|rR+^@]yYWG#YYU+߰=*sbMCӏϟ;fBښfq lx\^HjM i" k!p-+m7C `c$:;5XY ,Ȳ*oXUX!7O?[n%c+8mHT?0,Q{XOj rM~3W3Q8c duQ֨˂) kV7*Y0~V!+=>!%B\qw"pW OLpn~>?uy_(wY8sJ4ZװK-ɭ"TS{=Ue u=~a15 χп¨bTc'c=%di5{ :QwåqLEAǢ7ӟJi~:Y +ٽP`LJ6,ˬ2YVM?}bx6VT᪨a -_ w"-ďDG'8E ^DUʄ y>q>$0e`=e47 `F4 ,ZTvABz[ !kucy B; f@•PɌ Y݂_k h7uFa$y5`0O >Btu*nKA6&VA\㱼rV#V¿Rn-~X+ @K $BE$iXj7±6mPcB}wX6bb,ގPcE_ދoucyƬB̜*cW'V剸?( ؞'rZmeE le#4d,9Kv%?Vd$wZ% gE2ucy Zo4jb2j7ѹţ-}eD+xDXFT'k~D{~"^o;B`͌dHdaX FZ|B/_C*Y(8 E`oذaB[uԊWzJ Nh<@ev߫ږUt/k|BvGlp_M%܀Ʈ€5 |XɨUx[6#W;K*o.^f V X?,-gBk<[Z?bg\= 0: control = True elif line.find("--mmu") >= 0: mmu = True lines = True try: pipe.close() except IOError: pass if not lines: return "'%s' not found!" % self.hataribin if not control: return "Hatari missing required --control-socket option!" if not mmu: return "Hatari is not the expected (WinUAE CPU core) version!" return None def save_config(self): "ask Hatari to save config. Return None on success, otherwise Hatari return code" pipe = os.popen(self.hataribin + " --saveconfig") return pipe.close() def _create_server(self): if self.server: return self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) if os.path.exists(self.controlpath): os.unlink(self.controlpath) self.server.bind(self.controlpath) self.server.listen(1) def _send_message(self, msg): if self.control: self.control.send(bytes(msg, "ASCII")) return True else: print("ERROR: no Hatari (control socket)") return False def change_option(self, option): "change_option(option), changes given Hatari cli option" return self._send_message("hatari-option %s\n" % option) def set_path(self, key, path): "set_path(key, path), sets path with given key" return self._send_message("hatari-path %s %s\n" % (key, path)) def set_device(self, device, enabled): # needed because CLI options cannot disable devices, only enable "set_path(device, enabled), sets whether given device is enabled or not" if enabled: return self._send_message("hatari-enable %s\n" % device) else: return self._send_message("hatari-disable %s\n" % device) def trigger_shortcut(self, shortcut): "trigger_shortcut(shortcut), triggers given Hatari (keyboard) shortcut" return self._send_message("hatari-shortcut %s\n" % shortcut) def insert_event(self, event): "insert_event(event), synthetizes given key/mouse Atari event" return self._send_message("hatari-event %s\n" % event) def debug_command(self, cmd): "debug_command(command), runs given Hatari debugger command" return self._send_message("hatari-debug %s\n" % cmd) def pause(self): "pause(), pauses Hatari emulation" return self._send_message("hatari-stop\n") def unpause(self): "unpause(), continues Hatari emulation" return self._send_message("hatari-cont\n") def _open_output_file(self, hataricommand, option, path): if os.path.exists(path): os.unlink(path) # TODO: why fifo doesn't work properly (blocks forever on read or # reads only byte at the time and stops after first newline)? #os.mkfifo(path) #raw_input("attach strace now, then press Enter\n") # ask Hatari to open/create the requested output file... hataricommand("%s %s" % (option, path)) wait = 0.025 # ...and wait for it to appear before returning it for i in range(0, 8): time.sleep(wait) if os.path.exists(path): return open(path, "r") wait += wait return None def open_debug_output(self): "open_debug_output() -> file, opens Hatari debugger output file" return self._open_output_file(self.debug_command, "f", self.debugpath) def open_trace_output(self): "open_trace_output() -> file, opens Hatari tracing output file" return self._open_output_file(self.change_option, "--trace-file", self.tracepath) def open_log_output(self): "open_trace_output() -> file, opens Hatari debug log file" return self._open_output_file(self.change_option, "--log-file", self.logpath) def get_lines(self, fileobj): "get_lines(file) -> list of lines readable from given Hatari output file" # wait until data is available, then wait for some more # and only then the data can be read, otherwise its old print("Request&wait data from Hatari...") select.select([fileobj], [], []) time.sleep(0.1) print("...read the data lines") lines = fileobj.readlines() print("".join(lines)) return lines def enable_embed_info(self): "enable_embed_info(), request embedded Hatari window ID change information" self._send_message("hatari-embed-info\n") def get_embed_info(self): "get_embed_info() -> (width, height), get embedded Hatari window size" width, height = self.control.recv(12).split(b"x") return (int(width), int(height)) def get_control_socket(self): "get_control_socket() -> socket which can be checked for embed ID changes" return self.control def is_running(self): "is_running() -> bool, True if Hatari is running, False otherwise" if not self.pid: return False try: os.waitpid(self.pid, os.WNOHANG) except OSError as value: print("Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value)) self.pid = 0 if self.control: self.control.close() self.control = None return False return True def run(self, extra_args = None, parent_id = None): "run([embedding args][,parent window ID]), runs Hatari" # if parent_win given, embed Hatari to it pid = os.fork() if pid < 0: print("ERROR: fork()ing Hatari failed!") return if pid: # in parent self.pid = pid if self.server: print("WAIT hatari to connect to control socket...") (self.control, addr) = self.server.accept() print("connected!") else: # child runs Hatari env = os.environ if parent_id: self._set_embed_env(env, parent_id) # callers need to take care of confirming quitting args = [self.hataribin, "--confirm-quit", "off"] if self.server: args += ["--control-socket", self.controlpath] if extra_args: args += extra_args print("RUN:", args) os.execvpe(self.hataribin, args, env) def _set_embed_env(self, env, win_id): # tell SDL to use given widget's window #env["SDL_WINDOWID"] = str(win_id) # above is broken: when SDL uses a window it hasn't created itself, # it for some reason doesn't listen to any events delivered to that # window nor implements XEMBED protocol to get them in a way most # friendly to embedder: # https://specifications.freedesktop.org/xembed-spec/latest/ # # Instead we tell hatari to reparent itself after creating # its own window into this program widget window env["PARENT_WIN_ID"] = str(win_id) def kill(self): "kill(), kill Hatari if it's running" if self.is_running(): os.kill(self.pid, signal.SIGKILL) print("killed hatari with PID %d" % self.pid) self.pid = 0 if self.control: self.control.close() self.control = None # Mapping of requested values both to Hatari configuration # and command line options. # # By default this doesn't allow setting any other configuration # variables than the ones that were read from the configuration # file i.e. you get an exception if configuration variables # don't match to current Hatari. So before using this the current # Hatari configuration should have been saved at least once. # # Because of some inconsistencies in the values (see e.g. sound), # this cannot just do these according to some mapping table, but # it needs actual method for (each) setting. class HatariConfigMapping(ConfigStore): _paths = { "memauto": ("[Memory]", "szAutoSaveFileName", "Memory snapshot, automatic"), "memsave": ("[Memory]", "szMemoryCaptureFileName", "Memory snapshot, manual"), "midiin": ("[Midi]", "sMidiInFileName", "Midi input"), "midiout": ("[Midi]", "sMidiOutFileName", "Midi output"), "rs232in": ("[RS232]", "szInFileName", "RS232: MFP IO input"), "rs232out": ("[RS232]", "szOutFileName", "RS232: MFP IO output"), "sccain": ("[RS232]", "SccAInFileName", "RS232: SCC-A IO input"), "sccaout": ("[RS232]", "SccAOutFileName", "RS232: SCC-A IO output"), "sccalanin": ("[RS232]", "SccAInFileName", "RS232: SCC-A Lan IO input"), "sccalanout": ("[RS232]", "SccAOutFileName", "RS232: SCC-A Lan IO output"), "sccbin": ("[RS232]", "SccBInFileName", "RS232: SCC-B IO input"), "sccbout": ("[RS232]", "SccBOutFileName", "RS232: SCC-B IO output"), "printout": ("[Printer]", "szPrintToFileName", "Printer output"), "soundout": ("[Sound]", "szYMCaptureFileName", "Sound output") } # enable Hatari v2.5+ options has_opts_2_6 = True has_opts_2_5 = True "access methods to Hatari configuration file variables and command line options" def __init__(self, hatari): confdirs = [".config/hatari", ".hatari"] ConfigStore.__init__(self, confdirs) conffilename = "hatari.cfg" confpath = self.get_filepath(conffilename) print("confpath: %s" % confpath) error = self.load(confpath) if error: print("WARNING: %s %s!" % (confpath, error)) else: print("loaded config: %s" % confpath) self._hatari = hatari self._lock_updates = False self._desktop_w = 0 self._desktop_h = 0 self._options = [] def init_compat(self): "do config mapping initializations needing config loading to have succeeded" # initialize has_opts_ attribs for things that may not # be anymore valid on Hatari config file and/or command line try: # added for Hatari >v2.5 self.get("[RS232]", "bCpuDataCache") return except KeyError: pass self.has_opts_2_6 = False try: # added for Hatari v2.5 self.get("[RS232]", "EnableSccA") return except KeyError: pass self.has_opts_2_5 = False # drop v2.5 keys and use v2.4 option names print("Hatari v2.5 option(s) missing, reverting to v2.4 ones") for key in ("sccain","sccaout","sccalanin","sccalanout","sccbin"): del(self._paths[key]) self._paths["sccbout"] = ("[RS232]", "sSccBOutFileName", "RS232: SCC-B IO output") def validate(self): "exception is thrown if the loaded configuration isn't compatible" for method in dir(self): if '_' not in method: continue # check class getters starts = method[:method.find("_")] if starts != "get": continue # but ignore getters for other things than config ends = method[method.rfind("_")+1:] if ends in ("types", "names", "values", "changes", "checkpoint", "filepath"): continue if ends in ("floppy", "joystick"): # use port '0' for checks getattr(self, method)(0) else: getattr(self, method)() def _change_option(self, option, filename = ""): "handle option changing, and handle filenames appropriately" if filename is None: option = "%s none" % option elif filename: if os.path.isfile(filename): option = "%s %s" % (option, _path_quote(filename)) else: print("WARN: skipping '%s' option with non-existing filename '%s'" % (option, filename)) return if self._lock_updates: self._options.append(option) else: self._hatari.change_option(option) def lock_updates(self): "lock_updates(), collect Hatari configuration changes to trigger only single reboot" self._lock_updates = True def flush_updates(self): "flush_updates(), apply collected Hatari configuration changes" self._lock_updates = False if not self._options: return self._hatari.change_option(" ".join(self._options)) self._options = [] # ------------ paths --------------- def get_paths(self): paths = [] for key, item in list(self._paths.items()): paths.append((key, self.get(item[0], item[1]), item[2])) return paths def set_paths(self, paths): for key, path in paths: self.set(self._paths[key][0], self._paths[key][1], path) self._hatari.set_path(key, path) # ------------ midi --------------- def get_midi(self): return self.get("[Midi]", "bEnableMidi") def set_midi(self, value): self.set("[Midi]", "bEnableMidi", value) self._hatari.set_device("midi", value) # ------------ printer --------------- def get_printer(self): return self.get("[Printer]", "bEnablePrinting") def set_printer(self, value): self.set("[Printer]", "bEnablePrinting", value) self._hatari.set_device("printer", value) # ------------ RS232 --------------- def get_rs232(self): return self.get("[RS232]", "bEnableRS232") def set_rs232(self, value): self.set("[RS232]", "bEnableRS232", value) self._hatari.set_device("rs232", value) def get_scca(self): if not self.has_opts_2_5: return False return self.get("[RS232]", "EnableSccA") def set_scca(self, value): if not self.has_opts_2_5: return self.set("[RS232]", "EnableSccA", value) self._hatari.set_device("scca", value) def get_scca_lan(self): if not self.has_opts_2_5: return False return self.get("[RS232]", "EnableSccALan") def set_scca_lan(self, value): if not self.has_opts_2_5: return self.set("[RS232]", "EnableSccALan", value) self._hatari.set_device("sccalan", value) def get_sccb(self): if not self.has_opts_2_5: return self.get("[RS232]", "bEnableSccB") return self.get("[RS232]", "EnableSccB") def set_sccb(self, value): if not self.has_opts_2_5: self.set("[RS232]", "bEnableSccB", value) else: self.set("[RS232]", "EnableSccB", value) self._hatari.set_device("sccb", value) # ------------ machine --------------- def get_machine_types(self): return ("ST", "MegaST", "STE", "MegaSTE", "TT", "Falcon") def get_machine(self): return self.get("[System]", "nModelType") def has_accurate_winsize(self): return (self.get_machine() < 4) def set_machine(self, value): self.set("[System]", "nModelType", value) self._change_option("--machine %s" % ("st", "megast", "ste", "megaste", "tt", "falcon")[value]) # ------------ CPU level --------------- def get_cpulevel_types(self): return ("68000", "68010", "68020", "68030", "68040", "68060") def get_cpulevel(self): return self.get("[System]", "nCpuLevel") def set_cpulevel(self, value): if value == 5: # 060 value = 6 self.set("[System]", "nCpuLevel", value) self._change_option("--cpulevel %d" % value) # ------------ compatible --------------- def get_compatible(self): return self.get("[System]", "bCompatibleCpu") def set_compatible(self, value): self.set("[System]", "bCompatibleCpu", value) self._change_option("--compatible %s" % value) # ------------ CPU caches --------------- def get_data_cache(self): if not self.has_opts_2_6: return True return self.get("[System]", "bCpuDataCache") def set_data_cache(self, value): if not self.has_opts_2_6: return self.set("[System]", "bCpuDataCache", value) self._change_option("--data-cache %s" % value) # ------------ CPU exact --------------- def get_cycle_exact(self): return self.get("[System]", "bCycleExactCpu") def set_cycle_exact(self, value): self.set("[System]", "bCycleExactCpu", value) self._change_option("--cpu-exact %s" % value) # ------------ MMU --------------- def get_mmu(self): return self.get("[System]", "bMMU") def set_mmu(self, value): self.set("[System]", "bMMU", value) self._change_option("--mmu %s" % value) # ------------ CPU clock --------------- def get_cpuclock_types(self): return ("8 MHz", "16 MHz", "32 MHz") def get_cpuclock(self): clocks = {8:0, 16: 1, 32:2} return clocks[self.get("[System]", "nCpuFreq")] def set_cpuclock(self, value): clocks = [8, 16, 32] if value < 0 or value > 2: print("WARNING: CPU clock idx %d, clock fixed to 8 Mhz" % value) value = 8 else: value = clocks[value] self.set("[System]", "nCpuFreq", value) self._change_option("--cpuclock %d" % value) # ------------ FPU type --------------- def get_fpu_types(self): return ("None", "68881", "68882", "Internal") def get_fpu_type(self): return self.get("[System]", "n_FPUType") def set_fpu_type(self, value): self.set("[System]", "n_FPUType", value) self._change_option("--fpu %s" % self.get_fpu_types()[value]) # ------------ SW FPU -------------- def get_fpu_soft(self): return self.get("[System]", "bSoftFloatFPU") def set_fpu_soft(self, value): self.set("[System]", "bSoftFloatFPU", value) self._change_option("--fpu-softfloat %s" % value) # ------------ ST blitter -------------- def get_blitter(self): return self.get("[System]", "bBlitter") def set_blitter(self, value): self.set("[System]", "bBlitter", value) self._change_option("--blitter %s" % value) # ------------ DSP type --------------- def get_dsp_types(self): return ("None", "Dummy", "Emulated") def get_dsp(self): return self.get("[System]", "nDSPType") def set_dsp(self, value): self.set("[System]", "nDSPType", value) self._change_option("--dsp %s" % ("none", "dummy", "emu")[value]) # ------------ Timer-D --------------- def get_timerd(self): return self.get("[System]", "bPatchTimerD") def set_timerd(self, value): self.set("[System]", "bPatchTimerD", value) self._change_option("--timer-d %s" % value) # ------------ fastforward --------------- def get_fastforward(self): return self.get("[System]", "bFastForward") def set_fastforward(self, value): self.set("[System]", "bFastForward", value) self._change_option("--fast-forward %s" % value) # ------------ sound --------------- def get_sound_values(self): # 48kHz, 44.1kHz and STE/TT/Falcon DMA 50066Hz divisible values return ("6000", "6258", "8000", "11025", "12000", "12517", "16000", "22050", "24000", "25033", "32000", "44100", "48000", "50066") def get_sound(self): enabled = self.get("[Sound]", "bEnableSound") hz = str(self.get("[Sound]", "nPlaybackFreq")) idx = self.get_sound_values().index(hz) return (enabled, idx) def set_sound(self, enabled, idx): # map get_sound_values() index to Hatari config hz = self.get_sound_values()[idx] self.set("[Sound]", "nPlaybackFreq", int(hz)) self.set("[Sound]", "bEnableSound", enabled) # and to cli option if enabled: self._change_option("--sound %s" % hz) else: self._change_option("--sound off") def get_ymmixer_types(self): return ("Linear", "ST table", "Math model") def get_ymmixer(self): # values for types are start from 1, not 0 return self.get("[Sound]", "YmVolumeMixing")-1 def set_ymmixer(self, value): ymmixer_types = ("linear", "table", "model") self.set("[Sound]", "YmVolumeMixing", value+1) self._change_option("--ym-mixing %s" % ymmixer_types[value]) def get_bufsize(self): return self.get("[Sound]", "nSdlAudioBufferSize") def set_bufsize(self, value): value = int(value) if value < 10 or value > 100: value = 0 self.set("[Sound]", "nSdlAudioBufferSize", value) self._change_option("--sound-buffer-size %d" % value) def get_sync(self): return self.get("[Sound]", "bEnableSoundSync") def set_sync(self, value): self.set("[Sound]", "bEnableSoundSync", value) self._change_option("--sound-sync %s" % value) def get_mic(self): return self.get("[Sound]", "bEnableMicrophone") def set_mic(self, value): self.set("[Sound]", "bEnableMicrophone", value) self._change_option("--mic %s" % value) # ----------- joystick -------------- def get_joystick_types(self): return ("Disabled", "Real joystick", "Keyboard") def get_joystick_names(self): return ( "ST Joystick 0", "ST Joystick 1", "STE Joypad A", "STE Joypad B", "Parport stick 1", "Parport stick 2" ) def get_joystick(self, port): # return index to get_joystick_values() array return self.get("[Joystick%d]" % port, "nJoystickMode") def set_joystick(self, port, value): # map get_sound_values() index to Hatari config self.set("[Joystick%d]" % port, "nJoystickMode", value) joytype = ("none", "real", "keys")[value] self._change_option("--joy%d %s" % (port, joytype)) # ------------ floppy handling --------------- def get_floppydir(self): return self.get("[Floppy]", "szDiskImageDirectory") def set_floppydir(self, path): return self.set("[Floppy]", "szDiskImageDirectory", path) def get_floppy(self, drive): return self.get("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive]) def set_floppy(self, drive, filename): self.set("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive], filename) self._change_option("--disk-%c" % ("a", "b")[drive], filename) def get_floppy_drives(self): return (self.get("[Floppy]", "EnableDriveA"), self.get("[Floppy]", "EnableDriveB")) def set_floppy_drives(self, drives): idx = 0 for drive in ("A", "B"): value = drives[idx] self.set("[Floppy]", "EnableDrive%c" % drive, value) self._change_option("--drive-%c %s" % (drive.lower(), value)) idx += 1 def get_fastfdc(self): return self.get("[Floppy]", "FastFloppy") def set_fastfdc(self, value): self.set("[Floppy]", "FastFloppy", value) self._change_option("--fastfdc %s" % value) def get_doublesided(self): driveA = self.get("[Floppy]", "DriveA_NumberOfHeads") driveB = self.get("[Floppy]", "DriveB_NumberOfHeads") if driveA > 1 or driveB > 1: return True return False def set_doublesided(self, value): if value: sides = 2 else: sides = 1 for drive in ("A", "B"): self.set("[Floppy]", "Drive%c_NumberOfHeads" % drive, sides) self._change_option("--drive-%c-heads %d" % (drive.lower(), sides)) # ------------- disk protection ------------- def get_protection_types(self): return ("Off", "On", "Auto") def get_floppy_protection(self): return self.get("[Floppy]", "nWriteProtection") def get_hd_protection(self): return self.get("[HardDisk]", "nWriteProtection") def set_floppy_protection(self, value): self.set("[Floppy]", "nWriteProtection", value) self._change_option("--protect-floppy %s" % self.get_protection_types()[value]) def set_hd_protection(self, value): self.set("[HardDisk]", "nWriteProtection", value) self._change_option("--protect-hd %s" % self.get_protection_types()[value]) # ------------ GEMDOS HD (dir) emulation --------------- def get_hd_cases(self): return ("No conversion", "Upper case", "Lower case") def get_hd_case(self): return self.get("[HardDisk]", "nGemdosCase") def set_hd_case(self, value): values = ("off", "upper", "lower") self.set("[HardDisk]", "nGemdosCase", value) self._change_option("--gemdos-case %s" % values[value]) def get_hd_drives(self): return ['skip ACSI/IDE'] + [("%c:" % x) for x in range(ord('C'), ord('Z')+1)] def get_hd_drive(self): return self.get("[HardDisk]", "nGemdosDrive") + 1 def set_hd_drive(self, value): value -= 1 self.set("[HardDisk]", "nGemdosDrive", value) drive = chr(ord('C') + value) if value < 0: drive = "skip" self._change_option("--gemdos-drive %s" % drive) def get_hd_dir(self): self.get("[HardDisk]", "bUseHardDiskDirectory") # for validation return self.get("[HardDisk]", "szHardDiskDirectory") def set_hd_dir(self, dirname): self.set("[HardDisk]", "szHardDiskDirectory", dirname) if dirname: if os.path.isdir(dirname): self.set("[HardDisk]", "bUseHardDiskDirectory", True) self._change_option("--harddrive %s" % _path_quote(dirname)) else: self._change_option("--harddrive none") # ------------ ACSI HD (file) --------------- def get_acsi_image(self): self.get("[ACSI]", "bUseDevice0") return self.get("[ACSI]", "sDeviceFile0") def set_acsi_image(self, filename): if filename and os.path.isfile(filename): self.set("[ACSI]", "bUseDevice0", True) self.set("[ACSI]", "sDeviceFile0", filename) self._change_option("--acsi", filename) # ------------ IDE master (file) --------------- def get_idemaster_image(self): return self.get("[IDE]", "sDeviceFile0") def set_idemaster_image(self, filename): if filename and os.path.isfile(filename): self.set("[IDE]", "bUseDevice0", True) self.set("[IDE]", "sDeviceFile0", filename) self._change_option("--ide-master", filename) # ------------ IDE slave (file) --------------- def get_ideslave_image(self): return self.get("[IDE]", "sDeviceFile1") def set_ideslave_image(self, filename): if filename and os.path.isfile(filename): self.set("[IDE]", "bUseDevice1", True) self.set("[IDE]", "sDeviceFile1", filename) self._change_option("--ide-slave", filename) # ------------ TOS ROM --------------- def get_tos(self): return self.get("[ROM]", "szTosImageFileName") def set_tos(self, filename): self.set("[ROM]", "szTosImageFileName", filename) self._change_option("--tos", filename) # ------------ memory --------------- def get_memory_names(self): # empty item in list shouldn't be shown, filter them out return ("512kB", "1MB", "2MB", "4MB", "8MB", "14MB") def get_memory(self): "return index to what get_memory_names() returns" sizemap = (0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5) memsize = self.get("[Memory]", "nMemorySize") if memsize >= 1024 and memsize <= 14*1024: memsize //= 1024 elif memsize >= 512: memsize = 0 elif memsize < 0 or memsize >= len(sizemap): memsize = 1 return sizemap[memsize] def set_memory(self, idx): # map memory item index to memory size sizemap = (0, 1, 2, 4, 8, 14) if idx >= 0 and idx < len(sizemap): memsize = sizemap[idx] else: memsize = 1 if memsize: memsize *= 1024 else: memsize = 512 self.set("[Memory]", "nMemorySize", memsize) self._change_option("--memsize %d" % memsize) def get_ttram(self): return self.get("[Memory]", "nTTRamSize") def set_ttram(self, memsize, machine): # enforce 4MB granularity used also by Hatari memsize = (int(memsize)+3) & ~3 self.set("[Memory]", "nTTRamSize", memsize) self._change_option("--ttram %d" % memsize) if memsize and machine in ("TT", "Falcon"): # TT-RAM need 32-bit addressing (i.e. disable 24-bit) self.set("[System]", "bAddressSpace24", False) self._change_option("--addr24 off") else: # switch 24-bit addressing back for compatibility self.set("[System]", "bAddressSpace24", True) self._change_option("--addr24 on") # ------------ monitor --------------- def get_monitor_types(self): return ("Mono", "RGB", "VGA", "TV") def get_monitor(self): return self.get("[Screen]", "nMonitorType") def set_monitor(self, value): self.set("[Screen]", "nMonitorType", value) self._change_option("--monitor %s" % ("mono", "rgb", "vga", "tv")[value]) # ------------ frameskip --------------- def get_frameskip_names(self): return ( "Disabled", "1 frame", "2 frames", "3 frames", "4 frames", "Automatic" ) def get_frameskip(self): fs = self.get("[Screen]", "nFrameSkips") if fs < 0 or fs > 5: return 5 return fs def set_frameskip(self, value): value = int(value) # guarantee correct type self.set("[Screen]", "nFrameSkips", value) self._change_option("--frameskips %d" % value) # ------------ VBL slowdown --------------- def get_slowdown_names(self): return ("Disabled", "2x", "3x", "4x", "5x", "6x", "8x") def set_slowdown(self, value): value = 1 + int(value) self._change_option("--slowdown %d" % value) # ------------ spec512 --------------- def get_spec512threshold(self): return self.get("[Screen]", "nSpec512Threshold") def set_spec512threshold(self, value): value = int(value) # guarantee correct type self.set("[Screen]", "nSpec512Threshold", value) self._change_option("--spec512 %d" % value) # --------- keep desktop res ----------- def get_desktop(self): return self.get("[Screen]", "bKeepResolution") def set_desktop(self, value): self.set("[Screen]", "bKeepResolution", value) self._change_option("--desktop %s" % value) # ------------ force max --------------- def get_force_max(self): return self.get("[Screen]", "bForceMax") def set_force_max(self, value): self.set("[Screen]", "bForceMax", value) self._change_option("--force-max %s" % value) # ------------ show borders --------------- def get_borders(self): return self.get("[Screen]", "bAllowOverscan") def set_borders(self, value): self.set("[Screen]", "bAllowOverscan", value) self._change_option("--borders %s" % value) # ------------ show statusbar --------------- def get_statusbar(self): return self.get("[Screen]", "bShowStatusbar") def set_statusbar(self, value): self.set("[Screen]", "bShowStatusbar", value) self._change_option("--statusbar %s" % value) # ------------ crop statusbar --------------- def get_crop(self): return self.get("[Screen]", "bCrop") def set_crop(self, value): self.set("[Screen]", "bCrop", value) self._change_option("--crop %s" % value) # ------------ show led --------------- def get_led(self): return self.get("[Screen]", "bShowDriveLed") def set_led(self, value): self.set("[Screen]", "bShowDriveLed", value) self._change_option("--drive-led %s" % value) # ------------ monitor aspect ratio --------------- def get_aspectcorrection(self): return self.get("[Screen]", "bAspectCorrect") def set_aspectcorrection(self, value): self.set("[Screen]", "bAspectCorrect", value) self._change_option("--aspect %s" % value) # ------------ max window size --------------- def set_desktop_size(self, w, h): self._desktop_w = w self._desktop_h = h def get_desktop_size(self): return (self._desktop_w, self._desktop_h) def get_max_size(self): w = self.get("[Screen]", "nMaxWidth") h = self.get("[Screen]", "nMaxHeight") # default to desktop size? if not (w or h): w = self._desktop_w h = self._desktop_h return (w, h) def set_max_size(self, w, h): # guarantee correct type (Gtk float -> config int) w = int(w); h = int(h) self.set("[Screen]", "nMaxWidth", w) self.set("[Screen]", "nMaxHeight", h) self._change_option("--max-width %d" % w) self._change_option("--max-height %d" % h) # TODO: remove once UI doesn't need this anymore def set_zoom(self, value): print("Just setting Zoom, configuration doesn't anymore have setting for this.") if value: zoom = 2 else: zoom = 1 self._change_option("--zoom %d" % zoom) # ------------ configured Hatari window size --------------- def get_window_size(self): if self.get("[Screen]", "bFullScreen"): print("WARNING: don't start Hatari UI with fullscreened Hatari!") # VDI resolution? if self.get("[Screen]", "bUseExtVdiResolutions"): width = self.get("[Screen]", "nVdiWidth") height = self.get("[Screen]", "nVdiHeight") return (width, height) # window sizes for other than ST & STE can differ if self.has_accurate_winsize(): videl = False else: print("WARNING: With Videl, window size is unknown -> may be inaccurate!") videl = True # mono monitor? if self.get_monitor() == 0: return (640, 400) # no, color width = 320 height = 200 # statusbar? if self.get_statusbar(): sbar = 12 height += sbar else: sbar = 0 # zoom? maxw, maxh = self.get_max_size() if 2*width <= maxw and 2*height <= maxh: width *= 2 height *= 2 zoom = 2 else: zoom = 1 # overscan borders? if self.get_borders() and not videl: # properly aligned borders on top of zooming leftx = (maxw-width)//zoom borderx = 2*(min(48,leftx//2)//16)*16 lefty = (maxh-height)//zoom bordery = min(29+47, lefty) width += zoom*borderx height += zoom*bordery return (width, height) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/hatariui000077500000000000000000000036441504763705000242400ustar00rootroot00000000000000#!/bin/sh # # Don't modify the 'path' or 'conf' variable names or initial values, # those will be replaced by Makefile when this script is installed. path=${0%/*} name=${0##*/} if [ ! -e $path/$name.py ]; then # Assume package has been relocated, try relative data directory: path=${0%/*}/../share/hatari/hatariui fi # Assume hatari system configuration file dir is relative to hatariui dir # (usually system config file isn't installed, but if defaults need to be # configured differently from Hatari source code defaults, they're better # done with system config file than patching sources). conf=${path%/*}/../etc # checked by hatari UI export HATARI_SYSTEM_CONFDIR=$conf # examples for Hatari UI options # Embedding does not work under Wayland, only under X11 # Note: requesting Gtk & SDL to use "x11" backends does not help if [ -z "$WAYLAND_DISPLAY" ] && [ -n "$DISPLAY" ]; then echo "$path/$name.py --right 'about,|,run,pause,forward,|,reset,|,quit' --embed $*" $path/$name.py --right 'about,|,run,pause,forward,|,reset,|,quit' --embed $* else # this looks nicer when Hatari window embedding cannot be used echo "$path/$name.py --bottom 'about,|,run,pause,forward,|,reset,|,quit' $*" $path/$name.py --bottom 'about,|,run,pause,forward,|,reset,|,quit' $* fi exit $? # test setup without embedding, duplicate toggles $path/$name.py --top "about,run,pause,quit" \ --panel "Testpanel,pause,>,close" \ --bottom "sound,|,forward,pause,|,Testpanel" \ $* exit $? # test setup with embedding and all available controls $path/$name.py --embed \ --top "about,|,run,pause,|,reset,debug,|,quit" \ --left "run,pause,reset,machine,about" \ --panel "Keys,F1=59,F2=60,F3=61,F4=62,F5=63,F6=64,F7=65,F8=66,F9=67,F10=68,>,Macro=Test,Undo=97,Help=98,Enter=114,>,close" \ --panel "Misc,|,forward,full,|,sound,>,shot,>,close" \ --bottom "forward,full,Misc,Keys,input,display,debug,trace" \ --right "forward,full,Misc,Keys,input,display,Help=98" \ $* exit $? hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/hatariui.1000066400000000000000000000130371504763705000243710ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "hatariui" "1" "2025-05-28" "Hatari" "Hatari UI" .SH "NAME" hatariui \- Python/Gtk UI for Hatari .SH "SYNOPSIS" .B hatariui | hatariui.py .RI [options] .RI [directory|diskimage|program] .SH "DESCRIPTION" .I hatariui is a Python/Gtk UI for Hatari which can either embed the Hatari window (on X11 systems) or run in a separate window. By default it provides normal application menus and some extra buttons for faster access to fast\-forward and other functionality, but these are fully configurable with the command line options. While it lacks support for some of the Hatari configuration options that Hatari SDL GUI has, it also supports some options that the Hatari built\-in SDL GUI doesn't. .PP Besides the UI configurability, some of the other advantages .I hatariui has over the SDL GUI included with Hatari itself, are use of a normal Gtk file selector with all of its features (directory shortcuts etc), support for UTF\-8 (in file names) and in general blending better to the user's desktop environment. .PP Additionally, Hatari can run while one uses UI configuration dialogs, and it can stop Hatari completely to better save the battery on mobile computers. For devices without a keyboard, it offers a text input dialog and one can configure buttons for often used strings (with command line options). .SH "HATARIUI / HATARIUI.PY" .I hatariui is a shell script wrapper for the hatariui.py Python script. It's used to run the Python script with suitable options for default usage, and to set up the correct installation directory for rest of the Hatari UI Python scripts and data files. .PP Options below are actually for the hatariui.py script. If you want to change options given for it, modify the .I hatariui shell script or make your own based on the installed one. .\" following command line helps in updating the options: .\" hatariui.py --help|sed -e 's/^\t\+/.TP\n.B /' -e 's/\t\+/\n/g' -e 's/-/\\-/g' >> hatariui.1 .SH "OPTIONS" .TP .B \-h, \-\-help Hatari UI command line help .TP .B \-n, \-\-nomenu Omit menubar from the window .TP .B \-e, \-\-embed Embed Hatari window (to middle of controls) .TP .B \-f, \-\-fullscreen Start in fullscreen .TP .B \-l, \-\-left Add a toolbar at left .TP .B \-r, \-\-right Add a toolbar at right .TP .B \-t, \-\-top Add a toolbar at top .TP .B \-b, \-\-bottom Add a toolbar at bottom .TP .B \-p, \-\-panel , Add a separate window with given name and controls .PP You can have only one toolbar on each side of the Hatari window. Panels are separate windows and you can have as many of them as you wish. For each of the panels, you need to add a button with the name of the panel (see "MyPanel" in the Examples section). .PP Following buttons can be added to toolbars and panels: .TP .B | Separator between action buttons .TP .B > Start next toolbar row in panel windows .TP .B about Hatari UI information .TP .B authors Hatari authors .TP .B bugs Report a bug .TP .B changes Latest Hatari changes .TP .B compatibility Hatari compatibility list .TP .B debug Activate Hatari debugger .TP .B device Midi / Printer / RS232 enabling dialog .TP .B display Display settings dialog .TP .B floppy Floppy image dialog .TP .B forward Toggle Hatari fast-forward .TP .B full Toggle whether Hatari is fullscreen .TP .B harddisk Hard disk config dialog .TP .B hatari Hatari home page .TP .B hatariui Hatari UI home page .TP .B input Text / mouse click injection dialog .TP .B joystick Joystick settings dialog .TP .B lconfig Load configuration .TP .B load Load emulation snapshot .TP .B machine Hatari machine config dialog .TP .B mails Hatari mailing lists .TP .B manual Hatari manual .TP .B path Path config dialog .TP .B pause Pause Hatari to save battery .TP .B quit Quit Hatari UI .TP .B recanim Record animation .TP .B recsound Record YM/Wav .TP .B release Hatari release notes .TP .B reset Warm or cold reset Hatari .TP .B run (Re\-)run Hatari .TP .B save Save emulation snapshot .TP .B sconfig Save configuration .TP .B shot Grab a screenshot .TP .B sound Sound settings dialog .TP .B todo Hatari TODO .TP .B trace Hatari tracing dialog .TP .B uirelease Hatari UI release notes .TP .B Button for the specified panel window .TP .B = Synthetize string or single key . NOTE: disable fast-forward before injection or key-repeat will repeat each injected key! .PP If no options are given, the UI uses basic controls. .SH "EXAMPLES" Example on how to add top, right and bottom toolbars and a separate "MyPanel" panel window: .nf hatariui.py \-\-embed \\ \-t "about,run,pause,quit" \\ \-p "MyPanel,Macro=Test,Undo=97,Help=98,>,F1=59,F2=60,>,close" \\ \-r "pause,debug,trace,machine,MyPanel" \\ \-b "sound,|,forward,|,full" .fi .PP For more examples on Hatari UI options usage, see the .I hatariui shell script. .SH "SEE ALSO" .IR hmsa (1), .IR hconsole (1) .SH "COPYRIGHT" Hatari UI is written by Eero Tamminen . .PP This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .PP This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/hatariui.desktop000066400000000000000000000004731504763705000257020ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Hatari UI Comment=Gtk UI for Hatari (Atari ST/STE/TT/Falcon emulator) Comment[fr]=Interface GTK pour Hatari (émulateur Atari ST/STE/TT/Falcon) Exec=hatariui Icon=hatari StartupNotify=true Categories=Game;Emulator; Keywords=Emulator;Atari;Atari ST;Atari Falcon; Terminal=false hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/hatariui.py000077500000000000000000000745511504763705000246740ustar00rootroot00000000000000#!/usr/bin/python3 # # A Python Gtk UI for Hatari that can embed the Hatari emulator window. # # Requires Gtk 3.x and Python GLib Introspection libraries. # # Copyright (C) 2008-2025 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os import sys import getopt import gi # use correct version of gtk gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GLib from debugui import HatariDebugUI from hatari import Hatari, HatariConfigMapping from uihelpers import UInfo, UIHelp, create_button, create_toolbutton, \ create_toggle, HatariTextInsert, get_open_filename, get_save_filename from dialogs import AboutDialog, TodoDialog, NoteDialog, ErrorDialog, \ InputDialog, KillDialog, QuitSaveDialog, ResetDialog, TraceDialog, \ FloppyDialog, HardDiskDialog, DisplayDialog, JoystickDialog, \ MachineDialog, PeripheralDialog, PathDialog, SoundDialog # helper functions to match callback args def window_hide_cb(window, arg): window.hide() return True # --------------------------------------------------------------- # Class with Hatari and configuration instances which methods are # called to change those (with additional dialogs or directly). # Owns the application window and socket widget embedding Hatari. class UICallbacks: tmpconfpath = os.path.expanduser("~/.hatari/.tmp.cfg") def __init__(self): self.info = UInfo() # Hatari and configuration self.hatari = Hatari() error = self.hatari.is_compatible() if error: ErrorDialog(None).run(error) sys.exit(1) # Compatibility to older versions is enabled before validation # only after current Hatari version config has been saved, # otherwise UI could be using old options with new version. self.config = HatariConfigMapping(self.hatari) try: self.config.validate() self.config.init_compat() except (KeyError, AttributeError): NoteDialog(None).run("Hatari configuration validation failed!\nRetrying after saving Hatari configuration.") error = self.hatari.save_config() if error: ErrorDialog(None).run("Hatari configuration saving failed (code: %d), quitting!" % error) sys.exit(error) self.config = HatariConfigMapping(self.hatari) self.config.init_compat() try: self.config.validate() except (KeyError, AttributeError): ErrorDialog(None).run("Invalid Hatari configuration, quitting!") sys.exit(1) # windows are created when needed self.mainwin = None self.hatariwin = None self.debugui = None self.panels = {} # dialogs are created when needed self.aboutdialog = None self.inputdialog = None self.tracedialog = None self.resetdialog = None self.quitdialog = None self.killdialog = None self.floppydialog = None self.harddiskdialog = None self.displaydialog = None self.joystickdialog = None self.machinedialog = None self.peripheraldialog = None self.sounddialog = None self.pathdialog = None # used by run() self.memstate = None self.floppy = None self.io_id = None # TODO: Hatari UI own configuration settings save/load self.tracepoints = None def _reset_config_dialogs(self): self.floppydialog = None self.harddiskdialog = None self.displaydialog = None self.joystickdialog = None self.machinedialog = None self.peripheraldialog = None self.sounddialog = None self.pathdialog = None # ---------- create UI ---------------- def create_ui(self, accelgroup, menu, toolbars, fullscreen, embed): "create_ui(menu, toolbars, fullscreen, embed)" # add horizontal elements hbox = Gtk.HBox() if toolbars["left"]: hbox.pack_start(toolbars["left"], False, True, 0) self._set_max_win_size() if embed: self._add_uisocket(hbox) if toolbars["right"]: hbox.pack_start(toolbars["right"], False, True, 0) # add vertical elements vbox = Gtk.VBox() if menu: vbox.pack_start(menu, False, True, 0) if toolbars["top"]: vbox.pack_start(toolbars["top"], False, True, 0) vbox.add(hbox) if toolbars["bottom"]: vbox.pack_start(toolbars["bottom"], False, True, 0) # put them to main window mainwin = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) mainwin.set_title("%s %s" % (self.info.name, self.info.version)) mainwin.set_icon_from_file(self.info.icon) if accelgroup: mainwin.add_accel_group(accelgroup) if fullscreen: mainwin.fullscreen() mainwin.add(vbox) mainwin.show_all() # for run and quit callbacks self.killdialog = KillDialog(mainwin) mainwin.connect("delete_event", self.quit) self.mainwin = mainwin def _set_max_win_size(self): # set max Hatari window size = desktop size display = Gdk.Display.get_default() if not display.get_n_monitors(): print("ERROR: no monitors supported by Gdk") sys.exit(1) monitor = display.get_monitor(0) geometry = monitor.get_geometry() scale = monitor.get_scale_factor() print("%dx%d monitor @ %.2f scale" % (geometry.width, geometry.height, scale)) self.config.set_desktop_size(scale * geometry.width, scale * geometry.height) def _add_uisocket(self, box): # add Hatari parent container to given box socket = Gtk.Socket(can_focus=True) # without this, closing Hatari would remove the socket widget socket.connect("plug-removed", lambda obj: True) socket.set_events(Gdk.EventMask.ALL_EVENTS_MASK) # set initial embedded hatari size width, height = self.config.get_window_size() socket.set_size_request(width, height) # allow Hatari window resizing box.pack_start(socket, True, True, 0) self.hatariwin = socket # ------- run callback ----------- def _socket_cb(self, fd, event): if event != GLib.IO_IN: # hatari process died, make sure Hatari instance notices self.hatari.kill() return False width, height = self.hatari.get_embed_info() print("New size = %d x %d" % (width, height)) oldwidth, oldheight = self.hatariwin.get_size_request() self.hatariwin.set_size_request(width, height) if width < oldwidth or height < oldheight: # force also mainwin smaller (it automatically grows) self.mainwin.resize(width, height) return True def run(self, widget = None): if not self.killdialog.run(self.hatari): return if self.io_id: GLib.source_remove(self.io_id) args = ["--configfile"] # whether to use Hatari config or unsaved Hatari UI config? if self.config.is_changed(): args += [self.config.save_tmp(self.tmpconfpath)] else: args += [self.config.get_path()] if self.memstate: args += self.memstate # only way to change boot order is to specify disk on command line if self.floppy: args += self.floppy if self.hatariwin: self.hatari.run(args, self.hatariwin.get_id()) # get notifications of Hatari window size changes self.hatari.enable_embed_info() socket = self.hatari.get_control_socket().fileno() events = GLib.IO_IN | GLib.IO_HUP | GLib.IO_ERR self.io_id = GLib.io_add_watch(socket, events, self._socket_cb) # all keyboard events should go to Hatari window self.hatariwin.grab_focus() else: self.hatari.run(args) def set_floppy(self, floppy): self.floppy = floppy # ------- quit callback ----------- def quit(self, widget, arg = None): # due to Gtk API, needs to return True when *not* quitting if self.config.is_changed(): if not self.quitdialog: self.quitdialog = QuitSaveDialog(self.mainwin) if not self.quitdialog.run(self.config): return True if not self.killdialog.run(self.hatari): return True if self.io_id: GLib.source_remove(self.io_id) Gtk.main_quit() if os.path.exists(self.tmpconfpath): os.unlink(self.tmpconfpath) # continue to mainwin destroy if called by delete_event return False # ------- pause callback ----------- def pause(self, widget): if widget.get_active(): self.hatari.pause() else: self.hatari.unpause() # dialogs # ------- reset callback ----------- def reset(self, widget): if not self.resetdialog: self.resetdialog = ResetDialog(self.mainwin) self.resetdialog.run(self.hatari) # ------- about callback ----------- def about(self, widget): if not self.aboutdialog: self.aboutdialog = AboutDialog(self.mainwin) self.aboutdialog.run() # ------- input callback ----------- def inputs(self, widget): if not self.inputdialog: self.inputdialog = InputDialog(self.mainwin) self.inputdialog.run(self.hatari) # ------- floppydisk callback ----------- def floppydisk(self, widget): if not self.floppydialog: self.floppydialog = FloppyDialog(self.mainwin) self.floppydialog.run(self.config) # ------- harddisk callback ----------- def harddisk(self, widget): if not self.harddiskdialog: self.harddiskdialog = HardDiskDialog(self.mainwin) self.harddiskdialog.run(self.config) # ------- display callback ----------- def display(self, widget): if not self.displaydialog: self.displaydialog = DisplayDialog(self.mainwin) self.displaydialog.run(self.config) # ------- joystick callback ----------- def joystick(self, widget): if not self.joystickdialog: self.joystickdialog = JoystickDialog(self.mainwin) self.joystickdialog.run(self.config) # ------- machine callback ----------- def machine(self, widget): if not self.machinedialog: self.machinedialog = MachineDialog(self.mainwin) if self.machinedialog.run(self.config): self.hatari.trigger_shortcut("coldreset") # ------- peripheral callback ----------- def peripheral(self, widget): if not self.peripheraldialog: self.peripheraldialog = PeripheralDialog(self.mainwin) self.peripheraldialog.run(self.config) # ------- sound callback ----------- def sound(self, widget): if not self.sounddialog: self.sounddialog = SoundDialog(self.mainwin) self.sounddialog.run(self.config) # ------- path callback ----------- def path(self, widget): if not self.pathdialog: self.pathdialog = PathDialog(self.mainwin) self.pathdialog.run(self.config) # ------- debug callback ----------- def debugger(self, widget): if not self.debugui: self.debugui = HatariDebugUI(self.hatari) self.debugui.show() # ------- trace callback ----------- def trace(self, widget): if not self.tracedialog: self.tracedialog = TraceDialog(self.mainwin) self.tracepoints = self.tracedialog.run(self.hatari, self.tracepoints) # ------ snapshot load/save callbacks --------- def load(self, widget): path = os.path.expanduser("~/.config/hatari/hatari.sav") filename = get_open_filename("Select snapshot", self.mainwin, path) if filename: self.memstate = ["--memstate", filename] self.run() return True return False def save(self, widget): self.hatari.trigger_shortcut("savemem") # ------ config load/save callbacks --------- def config_load(self, widget): path = self.config.get_path() filename = get_open_filename("Select configuration file", self.mainwin, path) if filename: self.hatari.change_option("--configfile %s" % filename) self.config.load(filename) self._reset_config_dialogs() return True return False def config_save(self, widget): path = self.config.get_path() filename = get_save_filename("Save configuration as...", self.mainwin, path) if filename: self.config.save_as(filename) return True return False # ------- fast forward callback ----------- def set_fastforward(self, widget): self.config.set_fastforward(widget.get_active()) def get_fastforward(self): return self.config.get_fastforward() # ------- fullscreen callback ----------- def set_fullscreen(self, widget): # if user can select this, Hatari isn't in fullscreen self.hatari.change_option("--fullscreen") # ------- screenshot callback ----------- def screenshot(self, widget): self.hatari.trigger_shortcut("screenshot") # ------- record callbacks ----------- def recanim(self, widget): self.hatari.trigger_shortcut("recanim") def recsound(self, widget): self.hatari.trigger_shortcut("recsound") # ------- insert key special callback ----------- def keypress(self, widget, code): self.hatari.insert_event("keypress %s" % code) def textinsert(self, widget, text): HatariTextInsert(self.hatari, text) # ------- panel callback ----------- def panel(self, action, box): title = action.get_name() if title not in self.panels: window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) window.set_transient_for(self.mainwin) window.set_icon_from_file(self.info.icon) window.set_title(title) window.add(box) window.set_type_hint(Gdk.WindowTypeHint.DIALOG) window.connect("delete_event", window_hide_cb) self.panels[title] = window else: window = self.panels[title] window.show_all() window.deiconify() # --------------------------------------------------------------- # class for creating menus, toolbars and panels # and managing actions bound to them class UIActions: def __init__(self): cb = self.callbacks = UICallbacks() self.help = UIHelp() self.actions = Gtk.ActionGroup(name="All") # name, icon ID, label, accel, tooltip, callback self.actions.add_toggle_actions(( # TODO: how to know when these are changed from inside Hatari? ("recanim", Gtk.STOCK_MEDIA_RECORD, "Record animation", "A", "Record animation", cb.recanim), ("recsound", Gtk.STOCK_MEDIA_RECORD, "Record sound", "W", "Record YM/Wav", cb.recsound), ("pause", Gtk.STOCK_MEDIA_PAUSE, "Pause", "P", "Pause Hatari to save battery", cb.pause), ("forward", Gtk.STOCK_MEDIA_FORWARD, "Forward", "F", "Whether to fast forward Hatari (needs fast machine)", cb.set_fastforward, cb.get_fastforward()) )) # name, icon ID, label, accel, tooltip, callback self.actions.add_actions(( ("load", Gtk.STOCK_OPEN, "Load snapshot...", "L", "Load emulation snapshot", cb.load), ("save", Gtk.STOCK_SAVE, "Save snapshot", "S", "Save emulation snapshot", cb.save), ("shot", Gtk.STOCK_MEDIA_RECORD, "Grab screenshot", "G", "Grab a screenshot", cb.screenshot), ("quit", Gtk.STOCK_QUIT, "Quit", "Q", "Quit Hatari UI", cb.quit), ("run", Gtk.STOCK_MEDIA_PLAY, "Run", "R", "(Re-)run Hatari", cb.run), ("full", Gtk.STOCK_FULLSCREEN, "Fullscreen", "U", "Toggle whether Hatari is fullscreen", cb.set_fullscreen), ("input", Gtk.STOCK_SPELL_CHECK, "Inputs...", "N", "Simulate text input and mouse clicks", cb.inputs), ("reset", Gtk.STOCK_REFRESH, "Reset...", "E", "Warm or cold reset Hatari", cb.reset), ("display", Gtk.STOCK_PREFERENCES, "Display...", "Y", "Display settings", cb.display), ("floppy", Gtk.STOCK_FLOPPY, "Floppies...", "D", "Floppy images", cb.floppydisk), ("harddisk", Gtk.STOCK_HARDDISK, "Hard disks...", "H", "Hard disk images and directories", cb.harddisk), ("joystick", Gtk.STOCK_CONNECT, "Joysticks...", "J", "Joystick settings", cb.joystick), ("machine", Gtk.STOCK_HARDDISK, "Machine...", "M", "Hatari st/e/tt/falcon configuration", cb.machine), ("device", Gtk.STOCK_PRINT, "Peripherals...", "V", "Toggle Midi, Printer, RS232 peripherals", cb.peripheral), ("sound", Gtk.STOCK_PROPERTIES, "Sound...", "O", "Sound settings", cb.sound), ("path", Gtk.STOCK_DIRECTORY, "Paths...", None, "Device & save file paths", cb.path), ("lconfig", Gtk.STOCK_OPEN, "Load config...", "C", "Load configuration", self.config_load), ("sconfig", Gtk.STOCK_SAVE_AS, "Save config as...", None, "Save configuration", cb.config_save), ("debug", Gtk.STOCK_FIND, "Debugger...", "B", "Activate Hatari debugger", cb.debugger), ("trace", Gtk.STOCK_EXECUTE, "Trace settings...", "T", "Hatari tracing setup", cb.trace), ("manual", None, "Hatari manual", None, None, self.help.view_hatari_manual), ("compatibility", None, "Hatari compatibility list", None, None, self.help.view_hatari_compatibility), ("release", None, "Hatari release notes", None, None, self.help.view_hatari_releasenotes), ("hatariui", None, "Hatari UI information", None, None, self.help.view_hatariui_page), ("uirelease", None, "Hatari UI release notes", None, None, self.help.view_hatariui_releasenotes), ("bugs", None, "Hatari bugs", None, None, self.help.view_hatari_bugs), ("todo", None, "Hatari TODO", None, None, self.help.view_hatari_todo), ("hatari", None, "Hatari home page", None, None, self.help.view_hatari_page), ("mails", None, "Hatari mailing lists", None, None, self.help.view_hatari_mails), ("changes", None, "Latest Hatari changes", None, None, self.help.view_hatari_repository), ("authors", None, "Hatari authors", None, None, self.help.view_hatari_authors), ("about", Gtk.STOCK_INFO, "About Hatari UI", "I", "About Hatari UI", cb.about) )) self.action_names = [x.get_name() for x in self.actions.list_actions()] # no actions set yet to panels or toolbars self.toolbars = {} self.panels = [] def config_load(self, widget): # user loads a new configuration? if self.callbacks.config_load(widget): print("TODO: reset toggle actions") # ----- toolbar / panel additions --------- def set_actions(self, action_str, place): "set_actions(actions,place) -> error string, None if all OK" actions = action_str.split(",") for action in actions: if action in self.action_names: # regular action continue if action in self.panels: # user specified panel continue if action in ("close", ">"): if place != "panel": return "'close' and '>' can be only in a panel" continue if action == "|": # divider continue if action.find("=") >= 0: # special keycode/string action continue return "unrecognized action '%s'" % action if place in ("left", "right", "top", "bottom"): self.toolbars[place] = actions return None if place == "panel": if len(actions) < 3: return "panel has too few items to be useful" return None return "unknown actions position '%s'" % place def add_panel(self, spec): "add_panel(panel_specification) -> error string, None if all is OK" offset = spec.find(",") if offset <= 0: return "invalid panel specification '%s'" % spec name, panelcontrols = spec[:offset], spec[offset+1:] error = self.set_actions(panelcontrols, "panel") if error: return error if ",>," in panelcontrols: box = Gtk.VBox() splitcontrols = panelcontrols.split(",>,") for controls in splitcontrols: box.add(self._get_container(controls.split(","))) else: box = self._get_container(panelcontrols.split(",")) self.panels.append(name) self.actions.add_actions( ((name, Gtk.STOCK_ADD, name, None, name, self.callbacks.panel),), box ) return None def list_actions(self): yield ("|", "Separator between controls") yield (">", "Start next toolbar row in panel windows") # generate the list from action information for act in self.actions.list_actions(): note = act.get_property("tooltip") if not note: note = act.get_property("label") yield(act.get_name(), note) yield ("", "Button for the specified panel window") yield ("=", "Synthetize string or single key ") # ------- panel special actions ----------- def _close_cb(self, widget): widget.get_toplevel().hide() # ------- key special action ----------- def _create_key_control(self, name, textcode): "Simulate Atari key press/release and string inserting" if not textcode: return None widget = Gtk.ToolButton(Gtk.STOCK_PASTE) widget.set_label(name) try: # part after "=" converts to an int? code = int(textcode, 0) widget.connect("clicked", self.callbacks.keypress, code) tip = "keycode: %d" % code except ValueError: # no, assume a string macro is wanted instead widget.connect("clicked", self.callbacks.textinsert, textcode) tip = "string '%s'" % textcode widget.set_tooltip_text("Insert " + tip) return widget def _get_container(self, actions, horiz = True): "return Gtk container with the specified actions or None for no actions" if not actions: return None #print("ACTIONS:", actions) if len(actions) > 1: bar = Gtk.Toolbar() if horiz: bar.set_orientation(Gtk.Orientation.HORIZONTAL) else: bar.set_orientation(Gtk.Orientation.VERTICAL) bar.set_style(Gtk.ToolbarStyle.BOTH) # disable overflow menu to get toolbar sized correctly for panels bar.set_show_arrow(False) else: bar = None for action in actions: #print(action) offset = action.find("=") if offset >= 0: # handle "=" action specification name = action[:offset] text = action[offset+1:] widget = self._create_key_control(name, text) elif action == "|": widget = Gtk.SeparatorToolItem() elif action == "close": if bar: widget = create_toolbutton(Gtk.STOCK_CLOSE, self._close_cb) else: widget = create_button("Close", self._close_cb) else: widget = self.actions.get_action(action).create_tool_item() if not widget: continue if bar: if action != "|": widget.set_expand(True) bar.insert(widget, -1) if bar: return bar return widget # ------------- handling menu ------------- def _add_submenu(self, bar, title, items): submenu = Gtk.Menu() for name in items: if name: action = self.actions.get_action(name) item = action.create_menu_item() else: item = Gtk.SeparatorMenuItem() submenu.add(item) baritem = Gtk.MenuItem(label=title) baritem.set_submenu(submenu) bar.add(baritem) def _get_menu(self): allmenus = ( ("File", ("load", "save", None, "shot", "recanim", "recsound", None, "quit")), ("Emulation", ("run", "pause", "forward", None, "full", None, "input", None, "reset")), ("Devices", ("display", "floppy", "harddisk", "joystick", "machine", "device", "sound")), ("Configuration", ("path", None, "lconfig", "sconfig")), ("Debug", ("debug", "trace")), ("Help", ("manual", "compatibility", "release", "hatariui", "uirelease", "bugs", "todo", None, "hatari", "mails", "changes", None, "authors", "about",)) ) bar = Gtk.MenuBar() for title, items in allmenus: self._add_submenu(bar, title, items) if self.panels: self._add_submenu(bar, "Panels", self.panels) return bar # ------------- run the whole UI ------------- def run(self, floppy, havemenu, fullscreen, embed): accelgroup = None # create menu? if havemenu: accelgroup = Gtk.AccelGroup() for action in self.actions.list_actions(): action.set_accel_group(accelgroup) menu = self._get_menu() else: menu = None # create toolbars toolbars = { "left":None, "right":None, "top":None, "bottom":None} for side in ("left", "right"): if side in self.toolbars: toolbars[side] = self._get_container(self.toolbars[side], False) for side in ("top", "bottom"): if side in self.toolbars: toolbars[side] = self._get_container(self.toolbars[side], True) self.callbacks.create_ui(accelgroup, menu, toolbars, fullscreen, embed) self.help.set_mainwin(self.callbacks.mainwin) self.callbacks.set_floppy(floppy) # ugly, Hatari socket window ID can be gotten only # after Socket window is realized by gtk_main() GLib.idle_add(self.callbacks.run) Gtk.main() # ------------- usage / argument handling -------------- def usage(actions, msg=None): name = os.path.basename(sys.argv[0]) uiname = "%s %s" % (UInfo.name, UInfo.version) print("\n%s" % uiname) print("=" * len(uiname)) print("\nUsage: %s [options] [directory|disk image|Atari program]" % name) print("\nOptions:") print("\t-h, --help\t\tthis help") print("\t-n, --nomenu\t\tomit menus") print("\t-e, --embed\t\tembed Hatari window in middle of controls (X11 only)") print("\t-f, --fullscreen\tstart in fullscreen") print("\t-l, --left \ttoolbar at left") print("\t-r, --right \ttoolbar at right") print("\t-t, --top \ttoolbar at top") print("\t-b, --bottom \ttoolbar at bottom") print("\t-p, --panel ,") print("\t\t\t\tseparate window with given name and controls") print("\nAvailable (panel/toolbar) controls:") for action, description in actions.list_actions(): size = len(action) if size < 8: tabs = "\t\t" elif size < 16: tabs = "\t" else: tabs = "\n\t\t\t" print("\t%s%s%s" % (action, tabs, description)) print(""" You can have as many panels as you wish. For each panel you need to add a control with the name of the panel (see "MyPanel" below). For example: \t%s --embed \\ \t--top "about,run,pause,quit" \\ \t--panel "MyPanel,Macro=Test,Undo=97,Help=98,>,F1=59,F2=60,F3=61,F4=62,>,close" \\ \t--right "MyPanel,debug,trace,machine" \\ \t--bottom "sound,|,forward,|,full,|,quit" if no options are given, the UI uses basic controls. """ % name) if msg: print("ERROR: %s\n" % msg) sys.exit(1) def main(): actions = UIActions() try: longopts = ["embed", "fullscreen", "nomenu", "help", "left=", "right=", "top=", "bottom=", "panel="] opts, floppies = getopt.getopt(sys.argv[1:], "efnhl:r:t:b:p:", longopts) del longopts except getopt.GetoptError as err: usage(actions, err) menu = True embed = False fullscreen = False error = None for opt, arg in opts: print(opt, arg) if opt in ("-e", "--embed"): # GtkSocket window embedding needs both Python UI (Gtk3) and Hatari (SDL2) to run on X server if "DISPLAY" in os.environ and "WAYLAND_DISPLAY" not in os.environ: embed = True else: print("WARNING: ignoring embed option (Hatari window embedding works only under X11)") elif opt in ("-f", "--fullscreen"): fullscreen = True elif opt in ("-n", "--nomenu"): menu = False elif opt in ("-h", "--help"): usage(actions) elif opt in ("-l", "--left"): error = actions.set_actions(arg, "left") elif opt in ("-r", "--right"): error = actions.set_actions(arg, "right") elif opt in ("-t", "--top"): error = actions.set_actions(arg, "top") elif opt in ("-b", "--bottom"): error = actions.set_actions(arg, "bottom") elif opt in ("-p", "--panel"): error = actions.add_panel(arg) else: assert False, "getopt returned unhandled option" if error: usage(actions, error) if len(floppies) > 1: usage(actions, "multiple floppy images given: %s" % str(floppies)) if floppies: if not os.path.exists(floppies[0]): usage(actions, "floppy image '%s' doesn't exist" % floppies[0]) actions.run(floppies, menu, fullscreen, embed) if __name__ == "__main__": main() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/release-notes.txt000066400000000000000000000135421504763705000260110ustar00rootroot00000000000000User visible changes in Hatari (Python Gtk) UI ---------------------------------------------- 2025-05: - Fix data cache option setting - Some code improvements 2024-04: - Add support for Hatari v2.5+ >=030 data cache option 2024-03: - Add backwards compatibility support for Hatari v2.4 options - Remove compatibility support for <2.4.0 Hatari versions 2024-01: - Add support for Hatari SCC-A options 2022-06: - Add support for setting CPU compatibility + cycle-accuracy, FPU type + accuracy, MMU and ST blitter options 2022-05: - Fix: dialog exceptions under Wayland - Fix: debug UI disassembly parsing exception due to Hatari changes - Improved layouts for Display, Machine and Sound dialogs - Add hatari-ui.html doc - Add "scc" tracing 2022-04: - Increase TT-ram to 1GB supported now by Hatari 2021-12: - Add "midi_raw" tracing 2021-01: - Fix: saving of debug UI config, sound buffer size setting and more of the PyGTK (deprecation) warnings - Update TT-RAM and trace settings for latest Hatari - Use more descriptive titles for all file selectors - Remove support for obsolete (SDL1 specific) Hatari options and Python v2 support 2020-12: - Requires now Gtk v3.22 or newer - Launcher script asks for different UI layout under Wayland (as X11 embedding doesn't work with it) - Fix: asserts on missing Hatari config file - Fix: (ignore) invalid file paths in config file - Fix: GObject deprecation warnings - Allow Hatari window to be scaled freely now that Hatari supports it, and prevent menubar from padding itself - Make config save/discard/cancel dialog cancellable and its content properly resizable 2020-11: - Python scripts are changed to use "python3" hashbang (they should still work also with "python2"), and ".desktop" files were updated. Inspired by patching done by distros - Support for Hatari configuration Float and (keyboard) Key types (fixes assert when user asks UI to to save changed settings) - Ignore X11-only --embed option under Wayland to avoid crashing 2019-02: - Documentation updates 2019-01: - UI version 1.4 (due to Gtk & Python version updates) - Support both Python v2 & v3 - Fixes to machine setup dialog - SCC-B output support 2018-10: - Ported UI code to Gtk v3 by using GObject introspection (instead of PyGtk v2 that supports only Python v2) - Support Hatari's new ACSI/IDE harddisk image configuration file settings, along with the old option names 2017-01: - Fix for systems where "python" is Python v3 2016-12 (Hatari v2.0 fixes): - Add support for new nModelType config option (with MegaST/STE options), but still support also old nMachineType option - Hatari doesn't anymore support separate RTC option, remove - Support for keeping ST resolution option is enabled based on config file content (it's supported only by Hatari SDL1 builds) - Initial WinUAE vs. OldUAE CPU core support 2015-05: - Add support for --gemdos-drive, --ttram option features and new tracepoints - Debugger window supports WinUAE CPU core - Updated UI version to 1.3 2014-06: - Add support for --sound-sync, --sound-buffer-size, --slowdown, --gemdos-case, --drive-*-heads and --drive-* option features and new tracepoints - Improved option names & descriptions - update UI version to 1.2 2012-05: - Add --desktop-st and --force-max options support (latter helps video recording of Falcon demos doing lots of resolution changes) 2012-01: - Add microphone and YM voice mixing sound options - Fix asserts and empty hatari config file caused by Hatari v1.6 config variable names changes by changing how Hatari config variable types are handled - Update UI version to v1.1 (mainly due to config change) - Support spaces in file paths/names 2011-10: - Replace --slowfdc with --fastfdc 2011-04: - Support RTC and "keep desktop resolution" options 2011-02: - Support new tracepoints (AES, DSP, Videl, Crossbar) - Disasm update for new Hatari disassembly output 2011-01: - Use new Gtk v2.12 tooltip API - Support capture cropping 2010-10: - Improvements to text & key inserting - Move hatari-console.py elsewhere 2010-05: - Manual page for Hatari UI 2010-04: - UI handles Hatari system configuration properly - New settings dialog for HD dir and image configuration - Maximum/preferred zoom support to display settings dialog - Removed --spec512 support - Option for whether debugger will change to new PC address whenever emulation is stopped again 2010-03: - With the new Hatari --saveconfig option Hatari UI can ask Hatari to save its configuration (required by the UI) before the UI itself starts, user doesn't need to do it manually anymore (if user config is missing or out of date) - Added --slowfdc support to Floppy settings dialog 2009-09: - Support for setting CPU level & clock and Falcon DSP type 2009-08: - Update to latest Hatari 1.3.0: - Debug/trace fixes (Hatari 1.3.1 includes these) 2009-07: - Add Help menu items pointing to Hatari docs & site - --timer-d support + doc updates 2009-06: - Move to BerliOS Hatari repo - Update to latest Hatari 1.2.0: - midi in/out, sound freq etc 2008-10: - Support paths & peripherals settings 2008-09: - Support for auto frameskip, statusbar and overlay led - Remove support for multiple machine setups (now that run-time Hatari config/saving loading works) 2008-07: - Support recanim/sound, config load/save and memory snapshot load/save - First properly working with menus and toolbars instead of buttons - Can adapt properly also to Hatari window getting smaller (works on desktop, maemo/Matchbox WM have still issues) - Makefile for installation 2008-06: - Fairly usable version with configurable buttons - Can adapt to Hatari window size changes 2008-05: - Loading & saving Hatari configuration and checking changes against saved configuration works 2008-04: - First version with debugger UI 2008-02: - First version that can embed Hatari window (needed quite a lot of testing to find method that works well enough) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/tests/000077500000000000000000000000001504763705000236375ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/tests/README000066400000000000000000000002051504763705000245140ustar00rootroot00000000000000 Files ----- gtk-hatari-embed-test.py -- Several tries at embedding Hatari window gtk-hello-world.py -- simplest Python Gtk program hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/tests/gtk-hatari-embed-test.py000077500000000000000000000163221504763705000303020ustar00rootroot00000000000000#!/usr/bin/python3 # # To test Git version of Hatari, use something like: # PATH=../../build/src:$PATH ./gtk-hatari-embed-test.py ... # # Tests embedding hatari with three different methods: # "hatari": ask Hatari to reparent to given window # "reparent": Find Hatari window and reparent it into pygtk widget in python # - Needs "xwininfo" and "awk" i.e. not real alternative # # Using three alternative widgets: # "drawingarea" # "eventbox" # "socket" # # Results: # "drawingarea" & "evenbox" with "hatari": # -> XCB fails unknown seq num when importing Hatari window # "drawingarea" & "evenbox" with "reparent": # -> Hatari window opens outside of test app before reparented # -> keyboard input doesn't work # "socket" with "reparent": # -> Hatari window opens outside of test app before reparented # "socket" with "hatari": # -> only method working flawlessly import os import sys import time import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GdkX11 from gi.repository import GLib def usage(error): print("\nusage: %s \n" % sys.argv[0].split(os.path.sep)[-1]) print("Opens window with given , runs Hatari and tries to embed it") print("with given \n") print(" can be ") print(" can be \n") print("ERROR: %s\n" % error) sys.exit(1) class AppUI(): hatari_wd = 640 hatari_ht = 436 # Hatari window enables statusbar by default def __init__(self, widget, method): if method in ("hatari", "reparent"): self.method = method else: usage("unknown '%s'" % method) if widget == "drawingarea": widgettype = Gtk.DrawingArea elif widget == "eventbox": widgettype = Gtk.EventBox elif widget == "socket": # XEMBED socket for Hatari/SDL widgettype = Gtk.Socket else: usage("unknown '%s'" % widget) self.window = self.create_window() self.add_hatari_parent(self.window, widgettype) self.window.show_all() # wait a while before starting Hatari to make # sure parent window has been realized GLib.timeout_add(500, self.timeout_cb) def create_window(self): window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) window.connect("destroy", self.do_quit) return window def do_quit(self, widget): if self.hatari_pid: os.kill(self.hatari_pid, 9) print("killed Hatari PID %d" % self.hatari_pid) self.hatari_pid = 0 Gtk.main_quit() def add_hatari_parent(self, parent, widgettype): # Note: CAN_FOCUS has to be set for the widget embedding Hatari # and *unset* for everything else, otherwise Hatari doesn't # receive *any* keyevents. self.hatari_pid = 0 vbox = Gtk.VBox() button = Gtk.Button(label="Test Button", can_focus=False) vbox.add(button) widget = widgettype(can_focus=True) widget.set_size_request(self.hatari_wd, self.hatari_ht) widget.set_events(Gdk.EventMask.ALL_EVENTS_MASK) self.hatariparent = widget # TODO: when running 320x200, parent could be centered to here vbox.add(widget) # test focus label = Gtk.Label(label="Test SpinButton:") vbox.add(label) # disable focus, otherwise Hatari doesn't receive keys!!! spin = Gtk.SpinButton(can_focus=False) spin.set_range(0, 10) spin.set_digits(0) spin.set_numeric(True) spin.set_increments(1, 2) vbox.add(spin) parent.add(vbox) def timeout_cb(self): self.do_hatari_method() return False # only once def do_hatari_method(self): pid = os.fork() if pid < 0: print("ERROR: fork()ing Hatari failed!") return if pid: # in parent if self.method == "reparent": hatari_win = self.find_hatari_window() if hatari_win: self.reparent_hatari_window(hatari_win) self.hatari_pid = pid else: os.kill(pid, signal.SIGKILL) print("killed process with PID %d" % pid) self.hatari_pid = 0 else: print("Waiting Hatari process to embed itself...") self.hatari_pid = pid else: # child runs Hatari args = ("hatari", "-m") os.execvpe("hatari", args, self.get_hatari_env()) def get_hatari_env(self): if self.method == "reparent": return os.environ # tell Hatari to embed itself inside given widget's window win_id = self.hatariparent.get_window().get_xid() env = os.environ env["PARENT_WIN_ID"] = str(win_id) return env def find_hatari_window(self): # find hatari window by its WM class string and reparent it # wait 1s to make sure Hatari child gets its window up cmd = """sleep 1; xwininfo -root -tree|awk '/"hatari" "hatari"/{print $1}'""" counter = 0 while counter < 8: pipe = os.popen(cmd) windows = [] for line in pipe.readlines(): windows.append(int(line, 16)) try: pipe.close() except IOError: # handle child process exiting silently pass if not windows: counter += 1 print("WARNING: no Hatari window found yet, retrying...") time.sleep(1) continue if len(windows) > 1: print("WARNING: multiple Hatari windows, picking first one...") return windows[0] print("ERROR: no windows with the 'hatari' WM class found") return None def reparent_hatari_window(self, hatari_win): print("Importing foreign (Hatari) window 0x%x" % hatari_win) display = GdkX11.X11Display.get_default() window = GdkX11.X11Window.foreign_new_for_display(display, hatari_win) if not window: print("ERROR: X window importing failed!") return False parent = self.hatariparent.get_window() if not window: print("ERROR: where hatariparent window disappeared?") return False print("Found Hatari window ID: 0x%x, reparenting..." % hatari_win) print("...to container window ID: 0x%x" % parent.get_xid()) window.reparent(parent, 0, 0) #window.reparent(self.hatariparent.get_toplevel().window, 0, 0) #window.reparent(self.hatariparent.get_root_window(), 0, 0) #window.show() #window.raise_() # If python would destroy the Gtk widget when it goes out of scope, # the foreign window widget destructor would delete Hatari window. # So, keep a reference #self.hatariwindow = window return True def run(self): self.window.show_all() Gtk.main() if len(sys.argv) != 3: usage("wrong number of arguments") app = AppUI(sys.argv[1], sys.argv[2]) app.run() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/tests/gtk-hello-world.py000077500000000000000000000006511504763705000272310ustar00rootroot00000000000000#!/usr/bin/python3 import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk class AppUI(): def __init__(self): self.window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) self.window.connect("destroy", Gtk.main_quit) label = Gtk.Label(label="Hello World!") self.window.add(label) def run(self): self.window.show_all() Gtk.main() app = AppUI() app.run() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/python-ui/uihelpers.py000066400000000000000000000350621504763705000250550ustar00rootroot00000000000000# # Misc common helper classes and functions for the Hatari UI # # Copyright (C) 2008-2025 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os import sys import gi # use correct version of gtk gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GLib # leak debugging #import gc #gc.set_debug(gc.DEBUG_UNCOLLECTABLE) # --------------------- # Hatari UI information class UInfo: "constants for the UI windows" version = "v1.4" name = "Hatari UI" copyright = "Python/Gtk UI copyright (C) 2008-2025 by Eero Tamminen" def __init__(self, path = None): "UIinfo([path]), set suitable paths for resources from CWD and path" if path: self.path = path else: # path to the directory where the called script resides self.path = os.path.dirname(sys.argv[0]) # TODO: use share/icons/hicolor/*/apps/hatari.png instead self.icon = "hatari-icon.png" self.logo = "hatari-logo.png" if not os.path.exists(self.icon): self.icon = self._get_path(self.icon) if not os.path.exists(self.logo): self.logo = self._get_path(self.logo) def _get_path(self, filename): sep = os.path.sep testpath = "%s%s%s" % (self.path, sep, filename) if os.path.exists(testpath): return testpath return "" # -------------------------------------------------------- # functions for showing HTML files class UIHelp: def __init__(self): """determine HTML viewer and where docs are""" self._view = self.get_html_viewer() self._path, self._uipath = self.get_doc_path() def get_html_viewer(self): """return name of html viewer or None""" path = self.get_binary_path("xdg-open") if path: return path path = self.get_binary_path("firefox") if path: return path return None def get_binary_path(self, name): """return true if given binary is in path""" # could also try running the binary with "--version" arg # and check the exec return value if os.sys.platform == "win32": splitter = ';' else: splitter = ':' for i in os.environ['PATH'].split(splitter): fname = os.path.join(i, name) if os.access(fname, os.X_OK) and not os.path.isdir(fname): return fname return None def get_doc_path(self): """return path or URL to Hatari docs or None""" # first try whether there are local Hatari docs in standard place # for this Hatari/UI version sep = os.sep path = self.get_binary_path("hatari") path = sep.join(path.split(sep)[:-2]) # remove "bin/hatari" docpath = path + "/share/doc/hatari/" if not os.path.exists(docpath + "manual.html"): print("WARNING: using Hatari website URLs, Hatari 'manual.html' not found in: %s" % docpath) docpath = "https://www.hatari-emu.org/doc/" uipath = path + "/share/doc/hatari/hatariui/" if not os.path.exists(uipath + "README"): print("WARNING: Using Hatari UI Git URLs, Hatari UI 'README' not found in: %s" % uipath) uipath = "https://framagit.org/hatari/hatari/-/raw/main/python-ui/" return docpath, uipath def set_mainwin(self, widget): self.mainwin = widget def view_url(self, url, name): """view given URL or file path, or error use 'name' as its name""" if self._view and "://" in url or os.path.exists(url): print("RUN: '%s' '%s'" % (self._view, url)) os.spawnlp(os.P_NOWAIT, self._view, self._view, url) return if not self._view: msg = "Cannot view %s, HTML viewer missing" % name else: msg = "Cannot view %s,\n'%s' file is missing" % (name, url) from dialogs import ErrorDialog ErrorDialog(self.mainwin).run(msg) def view_hatari_manual(self, dummy=None): self.view_url(self._path + "manual.html", "Hatari manual") def view_hatari_compatibility(self, dummy=None): self.view_url(self._path + "compatibility.html", "Hatari compatibility list") def view_hatari_releasenotes(self, dummy=None): self.view_url(self._path + "release-notes.txt", "Hatari release notes") def view_hatariui_page(self, dummy=None): self.view_url(self._path + "hatari-ui.html", "Hatari UI information") def view_hatariui_releasenotes(self, dummy=None): self.view_url(self._uipath + "release-notes.txt", "Hatari UI release notes") def view_hatari_bugs(self, dummy=None): self.view_url(self._path + "bugs.txt", "Hatari bugs") def view_hatari_todo(self, dummy=None): self.view_url(self._path + "todo.txt", "Hatari TODO items") def view_hatari_authors(self, dummy=None): self.view_url(self._path + "authors.txt", "Hatari authors") def view_hatari_mails(self, dummy=None): self.view_url("https://www.hatari-emu.org/contact.html", "Hatari mailing lists") def view_hatari_repository(self, dummy=None): self.view_url("https://framagit.org/hatari/hatari/-/commits/main", "latest Hatari changes") def view_hatari_page(self, dummy=None): self.view_url("https://www.hatari-emu.org/", "Hatari home page") # -------------------------------------------------------- # auxiliary class+callback to be used with the PasteDialog class HatariTextInsert: def __init__(self, hatari, text): self.index = 0 self.text = text self.pressed = False self.hatari = hatari print("OUTPUT '%s'" % text) GLib.timeout_add(100, _text_insert_cb, self) # callback to insert text object to Hatari character at the time # (first key down, on next call up), at given interval def _text_insert_cb(textobj): char = textobj.text[textobj.index] if char == ' ': # white space gets stripped, use scancode instead char = "57" if textobj.pressed: textobj.pressed = False textobj.hatari.insert_event("keyup %s" % char) textobj.index += 1 if textobj.index >= len(textobj.text): del(textobj) return False else: textobj.pressed = True textobj.hatari.insert_event("keydown %s" % char) # call again return True # ---------------------------- # helper functions for buttons def create_button(label, cb, data = None): "create_button(label,cb[,data]) -> button widget" button = Gtk.Button(label) if data == None: button.connect("clicked", cb) else: button.connect("clicked", cb, data) return button def create_toolbutton(stock_id, cb, data = None): "create_toolbutton(stock_id,cb[,data]) -> toolbar button with stock icon+label" button = Gtk.ToolButton(stock_id) if data == None: button.connect("clicked", cb) else: button.connect("clicked", cb, data) return button def create_toggle(label, cb, data = None): "create_toggle(label,cb[,data]) -> toggle button widget" button = Gtk.ToggleButton(label) if data == None: button.connect("toggled", cb) else: button.connect("toggled", cb, data) return button # ----------------------------- # Table dialog helper functions # # TODO: rewrite to use Gtk.Grid instead of Gtk.Table def create_table_dialog(parent, title, rows, cols, oktext = Gtk.STOCK_APPLY): "create_table_dialog(parent,title,rows, cols, oktext) -> (table,dialog)" dialog = Gtk.Dialog(title, parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, (oktext, Gtk.ResponseType.APPLY, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) table = Gtk.Table(rows, cols) table.set_col_spacings(8) dialog.vbox.add(table) return (table, dialog) def table_add_entry_row(table, row, col, label, size = None): "table_add_entry_row(table,row,col,label[,entry size]) -> entry" # add given label to given row in given table # return entry for that line label = Gtk.Label(label=label, halign=Gtk.Align.END) table.attach(label, col, col+1, row, row+1, Gtk.AttachOptions.FILL) col += 1 if size: entry = Gtk.Entry(max_length=size, width_chars=size, halign=Gtk.Align.START) table.attach(entry, col, col+1, row, row+1) else: entry = Gtk.Entry() table.attach(entry, col, col+1, row, row+1) return entry def table_add_widget_row(table, row, col, label, widget, fullspan = False): "table_add_widget_row(table,row,col,label,widget[,fullspan]) -> widget" # add given label right aligned to given row in given table # add given widget to the right column and return it if label: if fullspan: lcol = 0 else: lcol = col label = Gtk.Label(label=label, halign=Gtk.Align.END) table.attach(label, lcol, lcol+1, row, row+1, Gtk.AttachOptions.FILL) if fullspan: table.attach(widget, 1, col+2, row, row+1) else: table.attach(widget, col+1, col+2, row, row+1) return widget def table_add_combo_row(table, row, col, label, texts, cb = None, data = None): "table_add_combo_row(table,row,col,label,texts[,cb]) -> combo" # - add given label right aligned to given row in given table # - create/add combo box with given texts to right column and return it combo = Gtk.ComboBoxText() for text in texts: combo.append_text(text) if cb: combo.connect("changed", cb, data) return table_add_widget_row(table, row, col, label, combo, True) def table_add_radio_rows(table, row, col, label, texts, cb = None): "table_add_radio_rows(table,row,col,label,texts[,cb]) -> [radios]" # - add given label right aligned to given row in given table # - create/add radio buttons with given texts to next row, set # the one given as "active" as active and set 'cb' as their # "toggled" callback handler # - return array or radiobuttons label = Gtk.Label(label=label, halign=Gtk.Align.END) table.attach(label, col, col+1, row, row+1) radios = [] radio = None box = Gtk.VBox() for text in texts: radio = Gtk.RadioButton(group=radio, label=text) if cb: radio.connect("toggled", cb, text) radios.append(radio) box.add(radio) table.attach(box, col+1, col+2, row, row+1) return radios def table_add_separator(table, row): "table_add_separator(table,row)" widget = Gtk.HSeparator() endcol = table.get_property("n-columns") # separator for whole table width table.attach(widget, 0, endcol, row, row+1, Gtk.AttachOptions.FILL) # ----------------------------- # File selection helpers def get_open_filename(title, parent, path = None): buttons = (Gtk.STOCK_OK, Gtk.ResponseType.OK, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) fsel = Gtk.FileChooserDialog(title, parent, Gtk.FileChooserAction.OPEN, buttons) fsel.set_local_only(True) if path: fsel.set_filename(path) if fsel.run() == Gtk.ResponseType.OK: filename = fsel.get_filename() else: filename = None fsel.destroy() return filename def get_save_filename(title, parent, path = None): buttons = (Gtk.STOCK_OK, Gtk.ResponseType.OK, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) fsel = Gtk.FileChooserDialog(title, parent, Gtk.FileChooserAction.SAVE, buttons) fsel.set_local_only(True) fsel.set_do_overwrite_confirmation(True) if path: fsel.set_filename(path) if not os.path.exists(path): # above set only folder, this is needed to set # the file name when the file doesn't exist fsel.set_current_name(os.path.basename(path)) if fsel.run() == Gtk.ResponseType.OK: filename = fsel.get_filename() else: filename = None fsel.destroy() return filename # File selection button with eject button class FselAndEjectFactory: def __init__(self): pass def get(self, label, path, filename, action): "returns file selection button and box having that + eject button" fsel = Gtk.FileChooserButton(title=label) # Hatari cannot access URIs fsel.set_local_only(True) fsel.set_width_chars(12) fsel.set_action(action) if filename: fsel.set_filename(filename) elif path: fsel.set_current_folder(path) eject = create_button("Eject", self._eject, fsel) box = Gtk.HBox() box.pack_start(fsel, True, True, 0) box.pack_start(eject, False, False, 0) return (fsel, box) def _eject(self, widget, fsel): fsel.unselect_all() # Gtk is braindead, there's no way to set a default filename # for file chooser button unless it already exists # - set_filename() works only for files that already exist # - set_current_name() works only for SAVE action, # but file chooser button doesn't support that # i.e. I had to do my own (less nice) container widget... class FselEntry: def __init__(self, parent, title = "Select a file", validate = None, data = None): self._title = title self._parent = parent self._validate = validate self._validate_data = data entry = Gtk.Entry() entry.set_width_chars(12) entry.set_editable(False) hbox = Gtk.HBox() hbox.add(entry) button = create_button("Select...", self._select_file_cb) hbox.pack_start(button, False, False, 0) self._entry = entry self._hbox = hbox def _select_file_cb(self, widget): fname = self._entry.get_text() while True: fname = get_save_filename(self._title, self._parent, fname) if not fname: # assume cancel return if self._validate: # filename needs validation and is valid? if not self._validate(self._validate_data, fname): continue self._entry.set_text(fname) return def set_filename(self, fname): self._entry.set_text(fname) def get_filename(self): return self._entry.get_text() def get_container(self): return self._hbox hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/readme.txt000066400000000000000000000274521504763705000225510ustar00rootroot00000000000000 Hatari Version 2.6.1, August 2025 https://www.hatari-emu.org/ Contents: --------- 1. License 2. What is Hatari? 3. Compiling and installing 3.1 Installing Hatari dependencies 3.2 Configuring and compiling 3.3 IPF support using capsimage library 3.4 Notes for Linux distribution packagers 3.4.1 Known distro problems 4. Running Hatari 4.1 Known Windows (SDL) issues 5. Hatari tools and their run-time dependencies 6. Hatari source subdirectory contents 7. Contact 1) License ---------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Soft- ware Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . Linking Hatari statically or dynamically with other modules is making a combined work based on Hatari. Thus, the terms and conditions of the GNU General Public License cover the whole combination. In addition, as a special exception, the copyright holders of Hatari give you permission to combine Hatari with free software programs or libraries that are released under the GNU LGPL and with code included in the standard release of the IPF support library (a.k.a. libcapsimage, see http://www.softpres.org/ for more information) under the Software Preservation Society Licence Agreement as it has been defined for IPF library version 5.1. Linking against modified versions of the IPF library is also allowed, as long as neither the license nor the purpose of the library (accessing .ipf or .ctr disk images) was changed. You may copy and distribute such a system following the terms of the GNU GPL for Hatari and the licenses of the other code concerned. 2) What is Hatari? ------------------ Hatari is an Atari ST/STE/TT/Falcon emulator for Linux, FreeBSD, NetBSD, macOS, Windows and other Systems which are supported by the SDL2 library. Unlike most other open source ST emulators which try to give you a good environment for running GEM applications, Hatari tries to emulate the hardware as close as possible so that it is able to run most of the old Atari games and demos. 3) Compiling and installing --------------------------- To build and use Hatari, you first need to install its dependent libraries. 3.1) Installing Hatari dependencies Required: - The SDL library v2.0.6 or newer (http://www.libsdl.org) Optional: - The zlib compression library (https://zlib.net/) - The PNG image library for PNG format screenshots and to decrease AVI video recording file sizes (http://www.libpng.org/) - The GNU Readline library for Hatari debugger command line editing - The Xlib library to support Hatari Python UI window embedding on systems with the X window system (Linux and other unixes) - The PortMidi library required for MIDI device support on macOS and Windows (http://portmedia.sourceforge.net/) - The udev library for NatFeats SCSI driver media change detection - The IPF support library (http://www.softpres.org/download) - The Capstone library (version >= 4.0) for traditional disassembly output in the debugger (https://www.capstone-engine.org/) Don't forget to also install the header files of these libraries for compiling Hatari (some Linux distributions use separate development packages for these header files)! For compiling Hatari, you need a C compiler that supports the C99 standard (preferably GNU C or Clang), and a working CMake (v3.3 or newer) installation, see http://www.cmake.org/ for details. On RedHat based Linux distributions, you get (most of) these with: sudo dnf install gcc cmake SDL2-devel zlib-devel libpng-devel \ readline-devel And on Debian/Ubuntu based ones with: sudo apt install gcc cmake libsdl2-dev zlib1g-dev libpng-dev \ libreadline-dev 3.2) Configuring and compiling CMake can generate makefiles for various flavours of "Make" (like GNU-Make) and various IDEs like Xcode on macOS. To run CMake, you have to pass the path to the sources of Hatari as parameter. For example, run the following command sequence to configure the build of Hatari in a separate build directory (assuming that the current working directory is the top of the source tree): mkdir -p build cd build cmake .. Have a look at the manual of CMake for other options. Alternatively, you can use the "cmake-gui" program to configure the sources with a graphical application or "ccmake" to configure them with ncurses UI. For your convenience we also ship an old-fashioned configure script which can be used as a wrapper for running cmake. Type "./configure --help" to see the options of this script. Once you have successfully configured the build settings, you can compile Hatari with: cmake --build . -j$(getconf _NPROCESSORS_ONLN) If all works fine, you should get the executable "hatari" in the src/ sub- directory of the build tree. You can then either run the executable from there, or install the emulator system-wide by typing: cmake --install . Note: This only works with CMake version 3.15 and later. On earlier versions, you have to use the install command of the generator program instead, e.g. "make install" if you are using the classical "make" for building Hatari. 3.2.1) Configuring and compiling with emscripten mkdir -p build/files # Copy your tos.img to build/files/ cd build emcmake cmake .. cmake --build . -j$(getconf _NPROCESSORS_ONLN) hatari The resulting hatari.html can't be used directly, you must provide it via a webserver to your browser (use Chromium, it currently works better than Firefox). A simple way is to use "python3 -m http.server" as a minimalist webserver for testing it locally. 3.3) IPF support using capsimage library Hatari can use the optional capsimage library to access IPF and CTR files. Those files are created using the Kryoflux board and allow to record MFM exact copies of original games, including the protection. Hatari supports version 5.1 of the library (previous support for older version 4.2 was removed as it is not used anymore) Refer to http://softpres.org/download and get the corresponding file from the "User Distribution" section that matches your OS. For version 5.1, you should have the following files in your include path : /usr/local/include/caps/ CapsAPI.h CapsFDC.h CapsForm.h CapsLibAll.h CapsLib.h CapsLibVersion.h ComLib.h CommonTypes.h You should also copy the libcapsimage.so* files in your library path, for example in /usr/local/lib/caps/ 3.4) Notes for Linux distribution packagers TOS tester in tests/tosboot/ directory can be used to verify that Hatari was built fine enough that it's able to boot all tested TOS versions in various different HW configurations and run some GEMDOS based tests. For EmuTOS, use the latest released 512k version to get best test coverage. If Hatari package will have two application menu entries for Hatari, one for the Python UI embedding Hatari, and another one for the plain SDL version, the latter could open also a terminal window for Hatari command line debugger and its console messages: x-terminal-emulator -T "Hatari debugger, invoke with AltGr+Pause" -e hatari tools/hatari-tos-register.sh is a minimal example of Linux init script registering Hatari as a (binfmt_misc) handler for TOS binaries. Alternatively one could add a mime type for TOS binaries with xdg-mime: http://portland.freedesktop.org/xdg-utils-1.0/xdg-mime.html But registering handlers for mime-types seems desktop specific. 4) Running Hatari ----------------- For information about how to use the running emulator, please read the file doc/manual.html. Here are just some hints for the impatient people: * Before you can run the emulator, you need a TOS ROM image. If one named as "tos.img" is neither in the data directory of the emulator (DATADIR variable in CMake configuration), or in the current directory, Hatari will ask you to select one. - Hatari binary packages ship unmodified EmuTOS ROM image with them (renamed as tos.img), but you may need an original Atari TOS ROM image for better compatibility. For more information on EmuTOS, see doc/emutos.txt. * While the emulator is running, you can open the configuration menu by pressing F12, the F11 key will toggle fullscreen/windowed mode. Pressing ALTGR-q will quit the emulator. 4.1) Known Windows (SDL) issues On Windows, Hatari console output doesn't go to console like on other platforms. This is because Windows SDL v2 library discards all that output by default. To see Hatari help/warning/trace output, and to interact with Hatari debugger, there are two options: - Run Hatari with "-W" option, or - Compile Hatari with "-mconsole" option (as last --linker flag) to build Hatari for the Windows console subsystem Because these cause separate console output window to be opened (in addition to the Hatari window), they are not enabled by default. 5) Hatari tools and their run-time dependencies ----------------------------------------------- While Hatari installs few binary tools binaries: - hmsa (converts between MSA & ST disk images) - gst2ascii (outputs a.out and DRI/GST debug symbol table contents) Most of its tools are Python and shell scripts. Their main run-time dependencies are: - python (hatariui, hconsole, hatari_profile, atari-convert-dir) - python & gtk3 (hatariui) - mkdosfs (atari-hd-image) - mtools (atari-hd-image / zip2st) - unzip (zip2st) 6) Hatari source subdirectory contents -------------------------------------- * cmake/ -- extra CMake files for configuring Hatari to build environment * doc/ -- Hatari documentation * python-ui/ -- external Python / Gtk UI for Hatari * share/ -- Hatari desktop integration; icons, mimetypes * src/ -- C-sources for Hatari emulator program - convert/ -- screen format conversion functions - cpu/ -- cycle-exact WinUAE CPU core (+FPU/MMU) - debug/ -- builtin debugger/profiler - falcon/ -- Falcon emulation specific code (Videl used also for TT) - includes/ -- common include files - gui-osx/ -- builtin MacOS GUI - gui-sdl/ -- builtin SDL GUI for Hatari - gui-win/ -- MS Windows console code + icon * tests/ -- shell/python scripts & programs for testing emulator functionality - autostart/ -- tests for TOS issues with too fast Hatari startup - blitter/ -- blitter emulation tests - buserror/ -- IO memory range bus error tests - cpu/ -- few CPU instruction emulation tests - cycles/ -- few CPU cycles emulation tests - debugger/ -- Hatari debugger functionality tests - gemdos/ -- GEMDOS HD emulation tests - mem_end/ -- emulation tests for screen at end of RAM - keymap/ -- programs showing keycodes to use in Hatari keymap files - natfeats/ -- tests + example Atari code for emulation Native Features - screen/ -- overscan emulation tests - serial/ -- serial output emulation tests - tosboot/ -- TOS bootup + basic GEMDOS operation tests - unit/ -- few unit tests for Hatari helper functions - xbios/ -- tests for BIOS intercept features * tools/ -- shell/python scripts & programs useful with Hatari - debugger/ -- debug symbol conversion scripts & profile data tools - hconsole/ -- out-of-process Hatari control / automation tool - hmsa/ -- floppy image format conversion tool - linux/ -- m68k Linux support files for running it under Hatari 7) Contact ---------- If you want to contact the authors of Hatari, please have a look at the file doc/authors.txt for the e-mail addresses or use the Hatari mailing list. Visit the website of Hatari for more details: https://www.hatari-emu.org/contact.html hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/000077500000000000000000000000001504763705000216435ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/CMakeLists.txt000066400000000000000000000022651504763705000244100ustar00rootroot00000000000000 foreach(size 32x32 48x48 64x64 128x128 256x256) install(FILES icons/hicolor/${size}/apps/hatari.png DESTINATION ${ICONDIR}/${size}/apps) install(FILES icons/hicolor/${size}/mimetypes/application-x-st-disk-image.png DESTINATION ${ICONDIR}/${size}/mimetypes) foreach(type vnd.msa vnd.fastcopy x-stx) install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink application-x-st-disk-image.png \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${ICONDIR}/${size}/mimetypes/application-${type}-disk-image.png) ") endforeach() endforeach() install(FILES icons/hicolor/scalable/apps/hatari.svg DESTINATION ${ICONDIR}/scalable/apps) install(FILES icons/hicolor/scalable/mimetypes/application-x-st-disk-image.svg DESTINATION ${ICONDIR}/scalable/mimetypes) foreach(type vnd.msa vnd.fastcopy x-stx) install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink application-x-st-disk-image.svg \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${ICONDIR}/scalable/mimetypes/application-${type}-disk-image.svg) ") endforeach() install(FILES mime/packages/hatari.xml DESTINATION share/mime/packages) install(FILES applications/hatari.desktop DESTINATION share/applications) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/applications/000077500000000000000000000000001504763705000243315ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/applications/hatari.desktop000066400000000000000000000006361504763705000272010ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Hatari Comment=Emulator for Atari ST/STE/TT/Falcon computers Comment[fr]=Emulateur pour les ordinateurs Atari ST/STE/TT/Falcon Exec=hatari %f Icon=hatari MimeType=application/x-st-disk-image;application/vnd.msa-disk-image;application/vnd.fastcopy-disk-image;application/x-stx-disk-image; Categories=Game;Emulator; Keywords=Emulator;Atari;Atari ST;Atari Falcon; Terminal=false hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/000077500000000000000000000000001504763705000227565ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/000077500000000000000000000000001504763705000244155ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/128x128/000077500000000000000000000000001504763705000253525ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/128x128/apps/000077500000000000000000000000001504763705000263155ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/128x128/apps/hatari.png000066400000000000000000000304651504763705000303030ustar00rootroot00000000000000PNG  IHDR>abKGD pHYs  tIME ;- IDATxm\Uz\۴*ۘ|qx4/.f6 %y&NUR2_vHhlf!|p ^hׅ@ueO&2mN>P; Of,)FH-LX/9B7d'c +n'`U06$6]e}v.K!h8 g(V)2R?V06&H45$-(paw³vvZ5C[P/t~kA5J&ctUǒYݘ{c/ 5J0"$%mBbyQϣlDT_7NSn/t~lcQ S:b jr ΙY>'ܫ®hP`X{Bф-ldﴢ.!I& uO#0N+`9)duhj,b`ޑE) H614 f>ֿPÐ0͔8rؗ 4e<B0b.yQb4c $'۟̅M[k9v3908R-S=xx0A-LSFtqpΜj 8:ƚybFW72gk >%1M^_ K ;7)Ш 4a:>kˁ@K_mv>%}SZ7H>@R>)5HBY@ju1W/MRՂPupwR>fiwK=}˥ ]Z7d;@JEخHz+fN5BPOIK;-W46n ?c"SՔPL6e: Y^)[:ZNe.3pQqB07B|V]ޛӄ[r\u!7y}>&InSfwȨNx) [ xgb6HYeۆd*ߏ!CO&F~ӢKU-TDޝrgiM@Lbb&`kWL kogaM@ "7dzI.\䝉@B,zPɮ,eN>7$ da]2ҷڀ-keW5I4 z Htn`[[S!0#rDlnBD$B&]Q: '2I6PG x ?cZf+ { {'Eu1䤟@ @rj^u'?}CZQO 1!ъ'Dͅ&̏%SgUo}߆ã6c`qYS%oUp)sh'*WW Ptg@,ERp?Y ؃ LAwDvZ>]au6? > 5}~"L!#)j9B]Zhp~΍+eE%b-߹Vm^&UKh.thths!uB"\{It]%x%/VJc#KUնvLx% - u!4 ŷ]t 55`G.r}P:_!s٢W7Y۳ 7I}@-K?/O{w{G uJIdPx)IXRH!S~ݰ=SV5cJԿwVv'B?]*싅8Ă+W(RNw:jt(O4c&g ,2)o ]Vsj>:{x _uv̰ լO}%eGoE)<9`Q@: ψrZmDԥ|s˹5.Ո ~[;qDT"ͅ 0,%p M``L a)[pK8kRq\-{l@K㵽W[4XC*$G0W fs0稶Q0pEC#?b_&i["ر&Ւ;w#7:'Y[VUyVVSfU[ {23YWHx`=Po7CxT= APPiF^:BrkٵF[D>ƨv'c s2^ڱnO5֣ Bi~u0oF ZO+ޙy*6Һrr\%@ud]Y"]Z4'46`<$ xM樆~wE]vUpZ,F: ~"BVaR$}{6R$ѽ'%l RQWBzo8 ]f)- jmگً-#tIT%-Y ZTP_ߔXp VP5A* 6mYœop/R0C=:WemY x&khF1 +pq-*4S2M`"w~}Q{y!Pk,JN\"5o=ʆz癕;GqlS;˩٬Z d8d)$B)SjZRE7k o%0mqn "I=r_6FR` p[\Y8r.Y]IosF_ڿdP̛h^i[ oj@ceo`Wh&FdʷSIIjp\?!ţ 5UBOc,4NÞ7FgncFtׄQUw-9?&Bqm0:`a"c"m4?[| SOgaU:&}5$ {y1ϻuA]$6@Q+CH$}{6*qwbh:$x"zr1u(8@8PD=q"/ƴ4|1~Bk N&L en0P}|I1tI v!ZMzr矵"VI9Yӝ@n|vo]rZ*9ZN߿r:Z&-]g܃by1ؿđu͟ImgT?VsXeV\45!q3M9!P"Ҽx∖2p(utD1,6 1iHS?ExDpcx7؎.qu| KwoO4AtU{K1o o͈ti$Tm(0voE=P~v(s:fFB,bq ?plWԥaoV]%!q^]V-LZ>+jd @EIojxęb0D쭃 IDH%o(yoož<fM0W$W{^PP>3lI&iM@2mt)8 ϕ aMڕ^z]]u]sĿ?_mwo%{d"eBbX &+II*t,ɬpE u _PdD^U2Ya9*W1S"6] ~YU,YsNT}R,R۞C&>̫.~ @2= b p;Q8JB ",y!*[dG k)aK-;$t1ŪER܊e'qDIuNG)>$%fe7B#u 3 oMsx4I;!jV:ǍOEЎ)?R鵳l,<"oV܅߉khwZ}Cf/҈!YIq<CJ( s NBϹ璔?p.5jS96N#\)v5sz|5Ud4)@wIVsL!= NmtXMR8OE =w~i_p(DN'5{W1Z8\"M&)s<6@&CkJ<0=ǎ1Ыb E]o,Z| Dl^3Z<$[{UU wߣ~@}D?͕ pxߥa ؚ:'Uۅ76@rj͉63xm`#M&%S>{L `V@7Od‡\DqɫM-m; cD0zHaC'>䳑J*K{DO9Aҟ8l ̫͉}pd4ϮƾuLu sHJb#MK.Юs-ff1t+zy@i>0)jH#(Wt~йY߸ W=(ay}esڸZjEPՌj}ͧ0D T.?Ω*|]&̍4tbZ.F}P%ؠ+Z]?ux,J;`~&)HJg }H5h2ъ׆-fQSza|50~0/v^LElY؇r= ȤVC]HUFKQr;32I:gH>| dl豎z={p7%fD#)V\zsi``%nyMzdžSg;TorzFٛtDK-/ԉR=)ؼ 9mW*\ضMt9$0gڲΨzϩ`:T'欘խa, ;`Ǐ %wu+wJ놳R& RK?N]% #sb;|1T7w\L &p vf۰ˈR\Y)(+/P i [r|~$""(NJMɎ2*!\LMFr^JC}VZ=kĶbh 1:Ȑ^!*:SJbNJgI.[T88s}$Q?*c:g иV$5wvlЌ9AV#L)KmDF볧`B eH$؆?-Hq&.ƽUCfE.ձI4ߐ|lҧϱ2TU uI5ڍ"`Du о3|uuQ߼F"Jˠâ*|bQwYO,]`-3`]X41l֏TCTEio17rH͔@Sɻr[+("޾L -1uj컳k4$yB oLi t5bz1l>y z_Fa9!j]rjtN#1Wjk+.Lzw:;G˩mΩ Ois^Oi9 7/b]P ZLZN_0Ba_"XC[f"$? 9fTvW.H䨙5 `/ֽe,_Z1;eIB_H{8eCHStG9V9m#(Z/v /zL49g(N?NJ8R\qbhVZXGmMN&MdM]0WM_<8б6fգ1"g̀ @lP+7jm7/54;*LCr%MxJ`ߍ\={-:4fbYC7#x%0t"崱L=$;ITGr'.:n ij.  *Q[vKY#vzu.=sɚ+'l.\$QKF]%? 峫 E";4W~ 5;h=O7I(zɡ7iq뉦4(1eƻP7"4@jG&zPטHj ɃOwX)K+6G`n\W 6+Ec7x~_[Z (llyMܐL=@dӝ#o})lO.)6gjd|? $pϞrh19ɷ-[>GR>o9pyfg$rՆ2 BьSx<=yyS)@-:7`>ds3#j@>ޯmם &pu{o,I+cA3g|zF(?A's큎7Œ|ob]7v 4<<)soGiXSjt+ix8ٗ*]T"Hw XЬ;/wX`eDzKD3I1\`e<^gTƚJkTJt  $mtw5MM*VdV-]%fЃRWb[Ddt=z9XSa]q?@@5J1oDCXKg wfXH B+3꼕XL*-i,PP*DA6++pOq4Uvx?z0nɷ)i礁sㆼ? ?lP?4%=1LB0A;GہٙmG[G=Dbڥ5^%xpz;.__ Ew1*#zV&tEW6 3SPz{6h*hGs =3QYRshsZ/k#+͛ՖE挌ǡR9~YՓ\>r_U zvHǫu;^\Ϊ_y(Ucx%P=Uôb.[:l`?\&E}eE?*X;7@TCz!q%(c}c؍ "Ғ̮O~-2H<0unlN&53: ?7- йK)^cj{h۵%Qֿ=ˍX)wR Z}TS(GuX4|!fgzH#{=dhuQEw}D3Q5V8S$ #9P̪cڕ% X/ U%Gm|U2nv$gP}3*/Ps]-Z:voWr"|8T=cY| 歚׀Gz9 3rB=4Bwz&zTZm+/ T(Ć~:DLnO |g?gIc'I#]|e`Fy{CMDۈP K&`S?b(gYӓ\^|S]J7ɕwkc5p- Cv]ޫV]S Zձ>s?:%v*=Oq؄oW9gI5"|Þa*JѮTՠZWLNpCʰ7jq}]ܵCKFkwPEX$bGQHa)-DvefB[1?sV,4ydngQmbM;W on-휽v2uD?ޤ7_ %zJM$(ٽjWYNy*B zr݌ȅJjR7G{okݷ{}~6سX?wZ0r2ΙC|Hg}mQŐCkD"IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/128x128/mimetypes/000077500000000000000000000000001504763705000273665ustar00rootroot00000000000000application-x-st-disk-image.png000066400000000000000000000414401504763705000352240ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/128x128/mimetypesPNG  IHDR>a pHYs.#.#x?vtIME " IDATxԼMf[9'[Un cP3AMK`^$[b s90AXH-AF2,d¦]739{`;voT" =~{$|Ư_g׿'__~ʯ~ukwSQLz__|՟JӋ3@ @&p~#_ D`dQ <`ncv><ȘJIۺr '|җ8XWp 8ft/0Qm׫~|ބF sdH}cO~w~7KO>X$Baum}~I}ҵ>ˋo/99bJ305x3?{?Xd&.` ªK$)NWAP XsЬ4ED;aF*dL҅`#G=N}LPUĴi`F`,zN$t2  z"D4@,B~f_7?M3j}T=htz7f*O'> ~cO'"!S Od&'}['?BHk?uWkd&gdҤFr 'S@L5r:*ȀHS5DdJ!qHDN Uq2uV"$].B̏UaƜ0de")H8!+B$i,56BMy? My2&8lD&O<{? Ft5Γ̈́Lt0ULz`#P ☟q2yMwwvY>|*S{$kS̳zR)Tt-yq\H9Q5[ÛLtXs2>hE@)m9&M.1LjFZ똓Ō,a*g0.|$ꦥ*9:a_k^$RL^Ho#'+~2SQ zTCD]UcLESEEX_$gL{ wW/;_==.(#9&55e d3y:҄fFd\%*U *2ЀьwLTf&ŝr}pD^z~[_HG?Xl f!>h AɃmd[,' `}o&>8H4k9YzcAoE;?S; +C/̡;39&gUd~bI,xG9޶j;wK_hy\ ,~("% XL:1'sT X8ym9D?H^twX3i늈pAn"UgfKq,>[$$P_DW9@yX]:9HnԗNAP^f>3-,Gzq1Aqy=tN< 4S*# )Môq N߃wtDw2{R34ԅG IػhGuΓ%.y(ʁU5n .gT 2 t,hM˪%ܬoDf@p!GACDI!|DSh̡wCT֛Db> [C(dmɩ-AZ2CغL :Ai0kn. 25YNң*^$U(C-^D3N[%'37X*)Ω06PFB$^]8y?Մ'wml*1,Kb}+29Tńd, Q#DP&Ǻ*rZq3/ݦDBؖ$cC&9 1-Phs6@5O92lK1ٲ<(-I4CĈ0L'ք6VJaNk+wS /MYGoq-7Q_)maNb c*{,·lzɼ=k Ɂ` ״!i|^df0SPYƂ^㔍 aj Dr7~QcHݨ5`6exѪқ2k\!wpE 瘨 No mc?M)9x0C%wbi,)-FUX l3XԘ-8ϓN+L8ʨ1x2ez.ª0]eyIee.BGvhk^3@6$|TeYr0ݙj A`FG.%J\# *蘬[re+Z1MJ%4س ӓUJ'hh<a~y(]tO$q:6qv'4spTl&@tۜSؤ q MzGc@3"AJ&KkRpf2BH6{WV88\xjːTrFb (- = y!xS/r9Me ׇrBjb-e8 Х>4iQ-ҘdT5B>3`>.Vܹh pEo pFQl-dƨ)*~5`NkX1$} xנA&RƜKͮ!ɪ]:PT:1Մ1_H3J*oMHON?%OKVª\͙4,#=#MxLC]O+e}BdIŸ)aQv5Z:-DEd1zY$gT`a EFha@Hҩ"JIІDT Q sb ~r:. 92Rءt4#R- k3mAEAIRjC74o&,81jgFknf7qP^&gpfs&uྜྷb<IAݫϬS&Lyآwl4k z& "c>:~4B˅JYBrT 2A @(#{2,&h"Yq/pCc'mEK^ H80)2"ȠJW˲V[Gd!HfRχcBve7+L4L $ W1z#0́ܖ形%2H1@E.۩V#lYܛNn'*$cD'>J~ ~uKQS-w7ŬHD6)dҙ"{Ďd iG i3VT&\>CwI&445/gIp5=}~ʽ,̀N.'5 Z/ڷ)恤4k!AHT\CV瀾.02|x 8T3(tk(k XSQ{:S1܀I[2Uub91OnDbdQ~l7QfƳEgoFIMˢL^>c |u /0,坋 ,kd$)gWUsrWTulwZ^Tr$vm̝$Gs:^vTlCs$D`Xd/%E2?/+4d˿PdӬqf>TO"&'Vk[".w1ԁh«'dy%3EYbϤGVBtCȕLJŐ͒,m5o#X1dM&5"q2cg "+{'QAю3Ҍq&1Ej _ѮMx&=U5& Å&B<}/* ' "ȼBY:6D˨9DS ܌@ X±nא&Zs%Y3“T9ͪ%}Yh|r/Ke굄5z1/p>ga`8sfrZM=YS8GB*s)3 $:fyK 3L%m έ@O7fL|OUxfU3=lڕ Z/}e1aP Z#4+ws*W0dxPEELIdj񐬒+yĝa!9S֍K!ZTMү.I9A XK㼼ygCS+h7tҽ5:5e?ESSY|3eY;r}_ʻh>-J], ŏ<ӫ40,F-x2}(h A\J))ܤb߱sǢL3vH+Rbdb#u8cȣUs~]~G?bɛGcZB:)rmk?V㻛mkxېt^GcѠYV<~C_-E(dmA3k4= G2ŵA#yhnR{ ^̝~Te\`ڑtdBNօ'G]^u8)_ؚ0' U˨/+')wkZOY*T 4Q$ZnCF'nH<ʇ1NJpϵX 駽DKa0W ̬땏;h,40tٓs=Sn<21_kMxOWR3kImi_OJAڽޫ]D^B躠v)C0c*9JX)gQ\rH C,uq0)Njq Boe4. 0 'ZCƸ,ǵ#cs^YQQxo[I*) [C;< YbJL[3s'k.kr)tP&ɪݯ8X:1),pI[wU /BrZJ!n! dZ~.l|M)F]8fxHwCAhov[p WO ޯqɼ{V9}%7]DDxֶLYz%zKmkɃ1,տ?W3`\+Y6I/^l]kaZI%ysT^w\TA@j#Ԇ_FY-iܬ4aOe``~*%G2}jAcT?/hזT^.US-/D~2kC?B{F~-vfݼ X Z1>}qU+Ymck5b~$]nKI>Թ0E\xq8Gj~ml~$g`hW7Z`AkT[ ୟwa*IbkZ f”}kVN߭gR力upzs¡V)\UiNwzKNYYZ$lx?h>x> S ʌ kXip #y$Yx8] "aa~q HkʒqYkӦpfNR?,L Q\΅!ltKJG*N#/!O݂:A업{\Z9.Mo9/Y=\j]==QN/gk֊׬Bd,Wo-Yʑʸ|mY;Z?z3:\ 7aJ&k"̀B[W)w{guuuKi3fZC!Lk߈ӆ&BE;~DQA;To3_4JX#VHNKEʤ3(1/'g׺ps䜜'ѳY~;{{=k]Z_bZ"XvSb$FI5_C…(&֜ !W]s KFA}?Lr_ݑdSƶI  d3bHYIMؒox< 5V%V [Qc sU|."[[g}z֤HZ@ ,5Lh :rakL艅\LwеJL[„RjTFhPR"k򰻑RE07LэKymۉ:-$`HBNa3JqfI0 &qUX[scH#Y]IvbԄFz" xAYckIL/;5L? UȭKW`hʸ"(sP=..$㠃L. Υ@:^hlhT˭س!ǙY9D Rɨ exx-$1J,"e;We]OJip9zsu.f FɨtI,v}ㅐGE ("AI)|*,5s l$ǩ*` Z[!Ɣt7@m_=! W<;)K7zqJLD: uҁhSeV<30v6Ied1˔R H2-X.xT pګݨwtʥU!p;'člД>20 +nHn 2*NM,Ha&n>3Ew!RB <͐!3NڙVx)gHNr42Y䋎9r7S\߁\yPyoԠM]J8I]a>X"G91mzN//+W`UJ.H?3j偿(3 ` G3ky:wLSyU$w}'|%mw|~痩iv4)||\膎ོ9دs❬R8X\D5:>М|oݑH-{5wʇuw]I)G#H*/( 7DxaaMoѮfܰUq.ەS"'7rdNq y?cs1(T*mEk]Nq.uM 4sumK-~m?f.c*l,n+7qul3ae¶&:;Hc @bƣ5zZ[ YEp 8/Xɵr tZǁAhJif`K7/q']D8Jߺmt?N$s4gFm1f)W`'a^On8*dæyQC"<4%R5WJ-GeK Q.B+O C=F0$ڼv'rCRb]kF8$;ŌjrAǖjTE xy*CSIlю3YQɥn¡G EBU5n 󱳕aDqsq\`վ-ƦXmBGe.M mC 9Yk"+LR)ali+Gq0z,FM-=5%r}0.ҵNXШ5!^:P]H&'Lp8Mjz> fBQca@؏?&0?NnL=5l$pGi%37ȂW5_ZaguǩɌ-dސ[`p]@@ ;JPR dT:{)h?NH݁‹b,RM- EŸEah6 vӠP[0j;4`XZ9h ~q oq%<$Nf]¶V0r;17*+WfWɬK X?=}$*&ɍCY]k.kSVU8v95F$=+ȗ?c`IɄ,y pNBO|6Xv( (iMK 9toq͠j1k 5tsd@nD W'.M6ÃdžIݏ386'Pb% B^Ⱥ*/3bm_Ỏ$Z@T8A]^pE1Ih4L]DlPL=e|`5LȒ(!U@&2u7mߑ?)'ѿ+g@/wwߍMކ@ν\<8?̙!|"ʜYe퉕qdJ n-J~ @CL!V]5EK:3O~2.85.]?&| /^Tw?{T͖dU {/z<<_2OR^svBfx7|9 HjI}^ -O&@0rb[ rBMO_5߿}1GN~n &v=S~Kt[ML T-ax9"S5z˻m0ٖek *lj$yk}n^.P\q*e`A8Y0ϜUg] Ĩ;3/?339^XΟOfjra4xl8Ww<$hRl-0P Ii7};g~~RNDtBX4lq`Sv-&V9' 0.POa(??kT~3tLL[þ۩yvHf野e6$w68T Ɖ{ҀP]]@#+9`Tg"11FM? |C7.W+WBO1/xam> 1ҡOqc%ND ڃ,5P1t-od`FF(5&M/zRb㔇~'K> t 4s`KHL+U['=.,eS %rr+ t[gJ<8aIp"OcQW?7~nc]WO⑘^$0yr nH*XJY#|:?K6L?{m?巀.gHZzZp{!%`%*\ m ( )L"D-0ZZcub==G}7YS' }s8^O0~㼦Ne(9 \I]ILi-V)<ZZT'z/w?{_<൷'w*w8F;¥Le*A L |U>EjVJ rW_/Q7߸dyۣc1bU$2L5EvD[a.l\X[ WQvIN<|O [( z;>ų8rG"%9ƘvXz"AZ0PYfHf^'.-O/0{H=_o_{bu-?ZEE8P;h[vDCVuظѦo1^ZNሄdb[SOz ؃Rx;a6C{Wɏ?N߽]u4ZpWG?M!¯6`kDCoݒԍ.Is(Ny=CI*^cv%%o+;B#UFH$r7cY$%vҀn]l qق~?#ƟiڎK%ۏ}>\p[L^CIй0S#KD%\L:5j$tgp^/&BVi+?C1گ7I?G%@~y98!d>PV|j"j R娶 0: Ww9W6.(p>vO/s2#roԏ|UqVW<"6!20$cS)HC+ 34Nzld`@+rUi`?N ")r=&D |VC2Iosjh!B4UdZ*"(,SkE=R9 LC(D-"lJHd4j$Uy4hX1֧kyNL$~WKw1RKtkdV G"b8"ydzxJzVx0JR=n5kb\Af;GŞV#ià+VHxDey"qC#(5G2%>gEq3k!{@ڒY-]TZNBسΡk ޡVQM\E s!F rg2F"5)d~rh p&~eBF VE'ItO ;Nq! 5I۴yFz.rSdDdZ %hLz{n-!5SO{ ^Ku]NMc0}ם77B+㖿Hog4B#^5#Q)5[EWB6.y EXIh5_G?Z0VwSscֺ/{\`Ȯ`#.}v,grFU o b:q203pHw7K'X=\7t5)sxuoQL$BPDw r(! I|6S <>9,&AW/8ՙaǭ_q!P-HaIDAT\{!sizʕs𾡟wFYljDTyprcks&S6q}B7. wɍѠsm~`4vֵUqF(:i]@.R5C,n(yiɔFág2@lBE-{]RWK^)Y%cZXn>B (#(!-TA P <&-*f @(J'x[ɧ/ č*-;Mm?u8$x NhYUM$&vXD${@} p#[8~MBmH_ k]cŧ& ))&&AS\~8Q C6{!%F*ɎhLHMR(Dzp} xG~X`V8Hœ;1eiHY1;$"w )L匥SCXO唛@PIM(hl]ѓ^x>)+wv_5*jyS-o{]fl¼'Wʥrʘn_:Kt""7+fσu3wƞϳݎ WEHU;E'>zo'Ex.NIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/256x256/000077500000000000000000000000001504763705000253565ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/256x256/apps/000077500000000000000000000000001504763705000263215ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/256x256/apps/hatari.png000066400000000000000000000557651504763705000303210ustar00rootroot00000000000000PNG  IHDR\rfbKGD pHYs  tIME &F IDATxmՕ zi !wà]23X3CA"ؘhŸO*1m m"aFw"Vָ!n̄q {!<*!dDwz?7*KʪHu{ * * * * * *40e"9~xoq3vO㽱kǶƶ*lytUoˣcHuF s?q exRO,">'<<-<<28Vϙq8(_nwc_ :tZEF Lw ji졏jE>7_ =p}8tx&/[0$Us$7^8B_i9B! U EY ;:`W%Ii&Zx}TIѴhv}+C 5ȷ6">_ӿIAc+2__eԎp&k'k`5Cjmxrw V(,KixYm7M 46^_bkA`;ثĮzmx2qڪdE<3fTA[}1P _v~k07{^u^ g.DP!zklY$Ts0|XZjV̂wEh nAM_K V'>/=$ih4BO=*2\YpS-\nK]t;9'G` Ϸ(4p@+ypzݵ' ieg*@?Rb ?$:'hDВDN+ʳeVA@>phi5vD:ReطxËI'M$1Zp|W(i1{~wF0NK7IHІn{ϣY>;ZZ]S> ΟeG;{6u11 KxH4Y tRmY~n+>ל/5tRzM}rpQnDWmD%fA b/qN>ߗm%:R {E wPYxsk{]w:*~DĦL^ 뫉2sJ1]pn%)>C$(7Tq-K $lW 4WPK7MvxbZ;2 m{$6-.6et_P=Ns8̇HhW&ISIe(T{?~}Pa 6T*FȰv{ߚ[닖/[Rq㽩or/킓#rɾ WXjI卑>+ 4_ ڮ K7~FM#~2\H'ћz賉T [ۜlˇ* `8^Zne$ɯ5ļg8#t&!~83tSH֔*ɋ#!H;;h6meJ9)$*X>_Tk .2ُG ADx9SλjnXG@xdV<0 [^[J[Ujbw~ ݸ`~)?sOl~Te=F(R굀mC*]6%ד|vƑ{)5{,Oې" rd ט/82Fj8' ]DrZ*^Sy=ARH].I rKv]=,?zd}d/ uğZ-<Gd!.k~F !+d$?b/VA!(v!^ 2w<'GC+vuλAskcd%ܮ\1!N'Gd@k'-z5gPp}Iop[j 0MGg W2YR%r>JZDXm6zU6"6Ցs/”!n.˹ b57/u܋'B2ֽ>ߏsekfQS1OfB,"aX7=^U@^;Of# zoNL $:qPϡU{ЄV%Vxxx(f"u}Sen4{1yb]v_!tغ|=[_Z7[Pex.فT>jw7'VUb7Qˈ.ØDNm1oeU`5x΀tQ2lU%V`8F=m<_6/qQ@ #\s<}w CmI=hvdE \ +ߴMx<E` jjOğd|_U%쾋-~u!?d6eyF䨫:l;U+ ~2@ǷgjW1Ex  O2kv]k+?~(JU?H}%yns ^=K[=~[~b~үL7Zi0~uHeU/ L!T[ M|8h?(.v~ lV3dNxmS`8N(qgD$4h [<>*w}Ta631qW.nfbIa FB'ȇKW ){+ y4zVC]W* lytU@Bag%Ӯ(|NJtx4S :tK PE&QI} j8D>O<{ӨY̶_Jg2bG VG MK[4փKuGx_`L'~Wfi4pɀߛۇ2cF>@VwV&r&W Ue}F`Ag&}UsG0>~ރ~O:&mN;(T!IU_K/!]"/Wq٠=]OD:}׋fAway`SWqd&W:iu'zfAf6;JDִ_(#TSNQ<ngxL}sd^5]qO`Ey[p_Wi֚V^H_?(ȃhWn o(v2ɾ~Q4xn]hF.P([ ΦRʠ, <6H㉉7mytE=W=dRᏻ̦ĎbT]X%F{Q4V /-fO옭NwVe٥-<1Oj߂]\lv'i⏓VI{1 zI>SB#jt݁SEsj;8Q2,0>y?d*+sLՅk|K >F0` G2hG߳dfTe焐fߖ7NZ/I/@[%\ od 2lUC0TwxH‹$NRUY,+qG2=J:L_* ޼ ~]ICض~YT{HY; | 9G_^NP=#AyZ_$#@+5%|yc531;qU_%tbXugD3=5L^9jva{;?[=_uzm5="LTzMM'TI)yDU--q R,{ҭZrEz5]pD*5qgF<7kq a\wQahz7HB~>(V/ȵǠF[l@~ 7(@+xyb(Ð"l`@ct|4.UA~˯U>̐':,ɾVOhj/Vjz-/ xK/.|$-aZ&I }dmop:7ـ/[&$"'ժMNN[ΠyV}NoXخ?O}3vL\zw[i@G&E^F@G_A]*h>UPŗL\^Qޅu*:f] R )oT>%:MjL*-=x̾ `ě qL6x^ƛR !-=sJOOaD3v|Zq l%kI&];N/3J eJj ٵIa7AٽPHʑ*0!]UbwVDl-1<v 3J@Md Wp}%6Ϋ.VL>yEWCvm<'sg94SZ@1s/Cqn0򤞬j,щuq%Zt=CȮ0?}+x!԰cxt>9-;箩xkHxVR͘/Hchw-$z3|vw~"~~IZ'C& JI=ݻ3Gp)X0c>9+59JjگQTy?rpe9'd 6B"Z& ѓ>m$lU^m]Z>$dQ)ok8TCwl tϊ-?JlG &c,;)#IpZ?L-=m~c m$}ۡf(#gwn'T =4 Nu!i4| ]u*&AϺ*Ա;RfEM3,oQ%~#F/cK=X2Mh`fuR?! sLfJ )9W]TElC}-JtTX>n[jPvL>N|1#K/5Y7 0JnM}"7} O% Ku/w{5ґ<~[L];6SvoXFʹgצk ~lV%Rr,?@׉s>dwb,=F6 !_/JC:@V֍+@ y2}]U'Ubr~q0xLH 4Q-g^5,gu89yAZQs{\FtLG@џ`㕉ahTwV' 7nu4}dg S~22j֞); &(VᜥJa.Eu 3"{JK;$Oܵi1s^7h]&z IDATϔu'9=!IA_/xNv3ʧSN%m]r,jB>&7i=SJ} CuWAd da{!ho oy]zw̭Jp  z/Z.E|R7ȡ T4q0uF)J4Cm^L6_,VFeYnBG|/?K%f/8L[7+QK0PH !~_/tvPpƫ"h➔Յɏс ʎ}VZ$U('ccF^ 5˶AS߁DwP ,~WwC0]S;sQT;g&179ܨFT2Ii@Me*Z9ˊyJHko&8􀿓-)@l3熀o(9ݑRq"~/Y{q HԴMSc.ۥɖ׃wa}-Y*⺞Y/@>n|@$TJ6jqX7? wy e3sc4%NdJ? Rj*0%_ N}V+}{e>3$@ldO9σH@-ǫFb2̻ ≿oHnK]Ohc.. HO1dCˆ걎J»ϳ8Q#L2?w!3 ]^TՅ]7vU Ry6|يlN{@u@ :9̀͡AyT͖*c^>l׫|&vg ~\yxdꛡ纨8?QmyI|_=Ƥ(^_M0}[z*j = ?DO9#0?RkUՃS"3NNOS_iI(kخJJ&,K5 ?_X ` c1hxv-@~' .Jnjý${@&Q緾]ʠi'KZVDaG&@*1jBȌ(c#]C0]GXLB;5-+o氎$ׄD>`FF$@Yk)xhZ#9`oB4msqR&9Uu80u wٺs͉Sk˓Q\?ITUM*$ ^"D$F~{- ;&&@ۄ T7DO"4O5᝶B3|28@߇ I2CͣDwp2.=ヨO>/2Xz@&0w;EBzQۣS֟@7ye߫4-VœG;O|͂߿/@-]o(oM~_r&h%e)R*Zl:P3V23F{%UϤeL͌T$Ji{7yIz>WTEI&crP%浉:S}ˤ bX3-HC >yA`H#T&J&@$ǵx%2) dW LC2Cٴ{Et-3醿j5I8AƁ:4[Q&T5Is.$:p®MhIt=~m+)" "DuEUmP ^w{" kh6<=]Y#[[e^]'HAu ǒIހ?h&X]t]qy*~[?rsh$cِv-=e~)}S ;Q'yqN cDfr-%:Cv E&EձF `q,5)`Oq[mېT)I߆ˉW[Ծc4Nʊ;'5lY D4ADK+_^qR gwM>FN!ѻ\;2H,[m3?G:]ܼ0357x]7 J 0(z|?BBGzuy QsUKBwdnN ubTm\>QTإ(1h7DSQHv?' 1ؒ^ hs(茶P <@%r RkNJ^kl FKbG ZA݄ԚIj<9(o$G67'>Iey$h~;"l}:-|xHY$~} IUr{7YT<7Ͼ'|lO໐~,՟'ikU$`|L5%~0,(nI_ 깗jLVٵO[fOH_C 0)N86qdC_:C'j˽cơ#'X{C7֞oQ p!}+V$"Wn3g l:ɘt! ZDs*/ ѡ|1U* .ûeOc4Ȅ4q},km9a,:+12;uH<^˞y#M*O,K͌q{w)-(tȹ+ Y Moh5|ko<_ \}@RH]K0.iNyӹPuIc-֚M<;0vS}6-m 7ˇ*@6ij3-g"lMb ꂫ=a?KO4%"~'G8 P}iL'X߈O\'wrsN )SΊ;{?ğ2JuPo nj` uqWآIlI[֮b ̶wvNeOʑv:o<>4ƱJh|@|Z/o%_wbOy?1zcN*u𾮭jiV%WۖCC][g]U@_? }3m-~]%2-^N v~1џ_U _|:fŠhB@Ld-3avXQ)[]Ĵ?|08ƽ(A@~T^ ofRsaI~C99lS5 `ݖuuI(Iw)j^;8:W "@Y$آo-JDm =o@ ~P-=* \Q?H^U8kT&2Ԗcj(QdIkM W2>B V(qZUzɋ֫Iq fI20܃ jUn}m|/ 0N] lڨm4 'KW S^*.[ 0 t6~KІtMԊ&K=|$I4&gd|z(_$ࣁs,T#kh/_'8`R7,DC?}͍nK%IxZS`܄0; {=={T $#s\aePꘛ焟lp| _[K}9ycĤH;-5瞎HrS_؏E' y^OX^* 73w`M6+|O/8i{Qܱ>`ΠMAgsOINYюDX{ΰvݚc6n:4+iu`:좈{d>@uAt ;=7]/єN',ry$rz.e4|/n>_5#5H@sM%!:I}d$ݤ%qP3!M5 !/!4_ cs1gp {8Day<^CCZ먳>6/nNI{9-=-=1-:)Dh3N$_r=<1K?${ƕ! ѩ@pQ!; ش NWKZt /aIƋYld-Njx2EH -F=F%W2ox6@v3I8ЫEu{C5!^r闰~D Ӡv^[q0k l3-e+2(h;V"~q}/NF߻9K{)?e(5؇"J*S~ېx$)zNT"뾲.XE׍8R9h1jĎq\ItCs`T$.\G|>SGꆬ0j3׬\r͋t@}Tqo! ܆Y=q"Xy* `!*07DX_DT a*FTX$(mS˵"3Yi8 ۬i#]TR+}e ˆ@I r:~wıCDu j-w+ E qqba^꺮Qb6Jrz]3>(q۠uaD dcrހS,7h+A lyõVR i/Ηzs'nnb9'e6'* pEݞ&cI5"Uu}tgᰕɼё|4K|SRE&\\=9[DjU&˾vv5P\Ǻ!uOa!A..E2s/c_ZI+ݏEx1=g(tK9yG0?U6`ujl:],xNzI 6chqSߏ$/"]G KQ"hڪZ{ mE j} \,]FI{{(bm}'0>_(be,m#-OFQB0.ֿW :E}N[ϣ填f{[z~Z&-- 1_ݍiԴNIf˦ \$ZY2kuIb[p5SФ"\i{]5K]I>(K",AQJP-5h.uo1]d!]S)o S7E]To>'h˫GI .2IC9MhyC=Eѷq`)4<\? )0Od^4>D7R<ПUis&gYLr [~8cWpN3ڸ=78jA^I.l½|]ɶ}ݒw=z`D;|< 'R'u9`1J)~$ybŞP7)A܎m6#FQ;[$"oj:Rм*aQo#G~Ij/?E&G>"@Ye1h~<~I},=ˢsl9Vn'ZǨ+/ek%9$s'fke2J 9-rr|'}P$"9ϯB3shh"c@TpWK\z S|0(ICCgfl3F@uC|NuJC&@ .@dj %*|v=B}|/ѹdXU|@D̞&$LnK~)>W9({k_,F6J ,XľrǍn5:8tnt] }ʠ)xӬ1_z!^Jn&㳧Y;GEf X?CGel.t>T!# yy'e0t\}o?TɶhTx&_50^V;Or6$G HuYf[cgHOgGOCoj#eu\^ߟ k;vW LtysXȷ!xZ#}W ,J JT!];2ޫS!`JC~oi$KkZ dNGM&+th#wp_?@mjT-VĔ7p=#/@bH0gE c.#G2Z"H2}kkD=e 2 qנ'u&J3~%>vcgĈ`;P4t 76a1>>xHď_J(5څwl$U ftKT)3pxQ0J M $kzdp7F,sZ.Up|hwpYɻ+1x}+ ԐdG<~3FHBtIB=" 0FNzQ8dP5?0ɸ4쮝e\i؇# ww`XًF%J$)@TdA~%_ e*\$)Tn?3?LF:KsU@tR~z3牝#N 6a,!l&JVΔ# ܂A 5 4g%GuxHu: \!N⿁#)pw44/N9/oiBp&@3A[XRh{y=qc#I $%U2IR_#]0Kq\`vp"9Fdw i-4q;[QXW% QeDIqr4 ,:9i9 /u(%0`JƗ+4 ocF GmYDY=o)}mU_ Jyx=jY9K, o} I zA;T30r~l5sYca[Q6S1#GCn~g?АI},nMG<;0UpPٌ{* `l*a,P!V;>%^ozG dQm' 9x$APE-֩D ǫ}`*>qp@}T wYKNM7OV5ǻBy: UySӯ?W~CP&iz7p+ bS=(#*.p7AQc]q[b]cO`"*>H{61n>] / p1Mp.TVt$):lmUuT{]"- F(2ƭLs19?DMr@g.~8R2ۇOU5LI11߳zj& [v i} ''$N߯޿c%,`?bgMO{~*IϥCwlٌ)Q#雾.ݿL& PD{ݐ59ptڜa.iw *?Zߗ0^}A1ܞ;Phhɳ[g6=TsvD-G#ef&}]+GiԶary$ ?2odF#gԴvڀe$6zQ DiHeZ_%NK~s|gQJpq3ڤ`UHdO]Y-A+%g$?bf[WkZ+@zU>?.$aY1aZ<̇@%扻EOow(~6M;zi:'rgɠu5A$vd:w/vP.Sٜ Ges$1mA[*_ kmZwXQHSbsYq",k n-LA^X>y"U^z/HvO֯DD`AXBYǟ'PWeh[kpCIsTb%&Uzb'irTN+ԾpCvdk Q?gJK5dy%!]8/Fu-H\+dXsxV9aOJ9nqh*gŏ?+`h(9gkfe{(_6$v݆{J`-UFICuEyPmxj *Y ! )ҍg?2&k#Vh|gIsB|97-K4&WQS^['Y~>곒oD]"Y+Z,'~6@qgdGtž̙33w&Sq$G C9"N?V>Ukmmls~wGcN$O]걞tN"#H[^LaxxmEKw\H6_({xp|Hx&픘 +j)rw(5AIzI8"!ƂR䷚KZIp!*N$g0` 9 a2+-*\=1 ҂xך$Z7z#:|Qhߍ!6xD6jt>܍UZx_EGyj7x3wKs}'`%vvSFrԖ~A1v82 :^S78r7^1@HQ,LZF&ØU$9 Ds?W.IɨH¯,g֑OP9Nn}eL ~4ѳz uk}=3*t6⫆>Ж`( $߀M۰V~pmc5 :d1l79Q|_NO^52P=f*|<н[C?ǜwB|_ˮK y>5A¦"uz~Fon@y=-8)]Kj3| 4XvM^Orhsy4Sh Ê~1y/ wjBI|L |:`8MyDn$?\W;:j 3Vu[*&ok$7`Osfx]I (.֎t3>|L6IІ4ϸ1$>ܚʹoA7}i]N<ܶCD%H "2,O{1g Kd s)ƼNuaZE;|.7^v\L\5x] pDT |DڋCͲqa fH/U@VnūYaafˁE$h&^6yr% vJH9 ..H~NB s(Eja܁kz~[$R$àqKs-=$KJ?9*c 4.I-*Sud:rD$6~ s`ili'Mzjevퟍ]J6e.Dt~5 ٤߰F+ /qF[BE~;y\,jE˴B0j.U+6xii'yA$y6gRO$ILvX>FYa0u[Q '/r${dabdq?ëI6̷n{!>t6 \gk~xU2u=#4${NVx.2uns o*9ޣQX_PATPATPA i?IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/256x256/mimetypes/000077500000000000000000000000001504763705000273725ustar00rootroot00000000000000application-x-st-disk-image.png000066400000000000000000001367701504763705000352430ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/256x256/mimetypesPNG  IHDR\rf pHYs.#.#x?vtIME 6.? IDATxɯlYv9Ѽ{3_EReJtA0@A3'&?Ba3Â'n4l "Hde&"٭9}Yd=Z\dwot'vZַ_~q_~qME;WſK ~3/o3^~?{}u8Oo p:p˷ӻw\^^v~_Ȅ߽ywfd'no?˿~odᗥm##᯿;oͿ;' K8a+AׄՆ\"ר_~'|R|:{yY&˚璮[ '(Ys^y<cc! iNڠpA02;Q"zq|#=N<~QO[x}uۺ2Ybֻ5=G׷+Nr/gO~F57Ӛp<xKDT_X^xH |Od'LZ&@Jo Me)%mXd'>)Opo%R7>V)ksRH]JOM{Bf5-_\NlH3,U zHUTx0nhm$,,9mܿGOUc}qb&Cr+vp?[{5vi[WNf»AFQDUPP޷9O y5tFY$Kk:~?7k 1>EɣJ\FIɜJ=SDI#|"yP)%/Qp)Gmʴ~o#?sr-taxܚ'SRh1ǵH)1 <Kҿsyݜ=Яs~i9c( >~цPsA،{|}f[NJ>H*/E>mȤ<۰ }7>vC7{ح9莨.q~7Vx9dQXU#kqoѡv=A1/S0lwNWot&n^n$Lٵc+#JJN"S&% xkCbYWϝoo8/YÚ0Γll'ckZ,L[9ԓh~,״-rm rߜ [Rb}1O X7J& GMqKJig`"GR[ 9Dx)0)ןO%f"}[n(IRr|T5<-6KСG$5Dª >Su^Ѷ8C̸(Q9!" \F/AHʯna7gBQ}c|S. 4 '`@TGݑǞ\'0~L_و_' /Y}Aϼ7wSX Ÿ()|Dcy^y <{?Ay~á)IPW2y%v?z[CvZM:o8Ęu"sfQQZ휑JѢ> ׻kpSgHSAhM[1i&JP"Q%kާ:֜O?|˪H)c9_}9ۼ",'Q)\&ԟle!%D(5RO3_ FrnLWt@~_e9emϗ.NlFEB<`âe7oIYp0<`DK9&z "[. '2r*\  L6#mIհ3 TtˉkofI̚5N^_Xp} k$2nZY] mpcΞn! J/H\7%Rv[{nz# SrLip)bы047 "UMD,KD,jjT_!#ܤ&L#1EN"0gGJzɊAqɚ('y#w#y%U!D_}TrH(E~wqKM5N\|( |@Dÿ%1ݻ$/߼8wJԢ3&% .%؁.C\yW2ő(4j۸>nH"Ajw ^oOpBАmA| ).JU\fNjFƥ_I2b 4(."*r\HZAS ZNHQUoXHѻR| X@kä6 Qણd['}B=#/P&(Qa3TLd޽I%ԏNrLHgd5dŜw+D dz? q] y;t-qq&}Ku;ciJh/YNBR[R&Dd39$ S#ܕ'8 iMo#ښgL1>d3߮T6᭎ku=1kIbB5HllK(+3x?D.09c u֧7HH+!*ہ¹c)QKMYND4zrjY4j2 膿{3)[\b繄ϱ*eM? "gY_pQ5m."q}3v]('ɹ߃ʼ*H)/JǢLY .YrJ?^n@N[1y|_imy~ZwmH$0˺rT|ɥ7yZj [%Gþ/ˌeyTɳ:["-Q5rQNX˵绹#To&[+]6q51m p ޭUܳh:BP,:-2ů}qhz(. ߐTRCQ@dQ[d&m-XXS?'k%yǰKVĔvĝ$JJj&*TJEc5U"ƵOAּs #fJߖ,#-IٿdIR4P7w"qY5fkcTm7a#IY xҞ9K@,Drؑ`BuPNt-khk-QC~#R7֍̶֢kDTS#VḀBVS+:t'5ӶQRy--([MNd̘wQc[Mod=< c/(K4W0'+̻^sL*V2"M7Sh쫄5Ў\r'c-C^CIʼn3KF~/Z>>G$(fUTH;CJR7/9.Qjн6RFo:"߄!B)%6o c s`uטiknN Sk}Ve*Tɫ D,j 7XwḀѰBƬJݡ$'&SJ.B)I-p˿ɏc>9>J7LBjF[5i7:J♎gW?.+'^{)q+Z|U +XfN Smh.I#IG2q4 ̒!Oi%;_1iظr#RJ^$d P?4'ʉ);*x a'nWu)Π=8|hr!i';n7vP1]) [Ki"м5jG1{fW~9Q0DIVXkmYhLi5󅯟Ϸ!럱sV. n5L\o swz]5|IX^bjXoIp-X QRP!nD#\y}CbήmH[0Xzrd؅D /YE12a9j_*Ap`U;e_->)B3%)5G xQ~2_T  a3P$}mJ:nЮ;us[Y"b*9O{DlX30Nb̌d-7VMZV5IMeRh|hl"Kpڷ|g^6Fb*=!xjT)T|nl4F94q. G38%|ZxhËF[~%|} tۍ?˟.,NJ3Yr1V"b ;h6yE>UB[5AOauC=c%7qL>RN)+kE2ll8M34`+fTx0TE@7SwZS@Ŕ5[6l J y +z2/-Ħ@?"OHthY#bOQr%/75r %Z02 9l֪7rW?JʉSZNJ=o}-`#jpH[By!%@ARhx.R6WRPOf{4qȟq a4Ҕ$WNJK[uݜGTCՊFg T60#kp8ʬ7.!}|NIܷ҇\K1rSFkhZl cLD" [w1S>%8j4>:qt AE]J{FEܜ/'b$J$ O.sY"1YzBV a` uMN"KOvFzr?pS?$R" 7y(7}|LĴJUH(%B9 "K-r߷*kO//}z%c,SM&&%K=s76&ip'nqH+>`n`IS-ZAڅ+dƊk'+H>TNMD5lNhw‘[𾐫NVDE.n)-бPic($e:f wJ {q T~cT sZ8- htF:L zG.QOu^jcj |g.K Vv.UA(X.kNmxGPRs!ĔXc8DH"r*FL%}9;K[n?.x6\S"XCY#/JhD@b 8R?ݥ1Eˇ{*P 9 EZtqFk7Ϊ6:\O 4w^-#qA&ɁNhf|})1I6%LS]F92 *kXtmmAT;m@ɡL7 \Ai:+]lvRqqѺkR55eJ)CM znjU ź}V&&B@JvGcxe/Q[C(p\fG9r7RؕNSSkpfi}LvIrٶW.m~iRBwnk{nη5Y7FW $ [}di4*a&bjM#GL W`ި0pX:HX~]'v\@&8qo@댭(tO?k~3([$EtIwzm9tμR^ Xױcqrܑmv;ac83[KRsSRnbWT(T4`e ̹t'KEgҚ5;ő|J;FǻZ@,̈'te>yh[E <έKaDEvB)}7L177/R5x7D4#-zެ瓪Y:^dv7嚆4$̍fAq޹amɼ֠ޯXupwsz>֐8[g#y$1pYxד>F;{Ѧ˄w-_ӀR߫3a'@ IDATZ55 l)h::^U?mِ2(CRrMc{8[ai)J2Mu|:)>8RיCbVpv]xXy k).B`9L2^?#kB7.Bm\)q} P[ `o%oPO6a||!(QK{ྋ1ڗ.]iIQo=PU,U,YEs9EO☊ucNei6s<9 n;4:99oC2Vp'ӕ=|^A6_ 6X~fϤk(%:^ 6o%V޻5`=O Юw6`gykFTֆJsr{,Q@whF1o蟢S* 5Zڒ9%%O׫R*ԑzpt'*]cN:aAHҪcƿVH~4 nt6^@ =ުNe´|!<ʒ_DaOQV0P"؉A4X"h5"@YD.X)1+죓(.t;sƐ)0PGhȇTjη95%UBvt}:l&Wu]9V=6JM[5X _/٢A w |(H5$9M䍷/ 48DM@-WJU#R5 )bCۼ. ?cY!Af>xٻ ƅfىx_֥XB~c ȦЕ2Cb]%#1L ;Qn%Ҙ3$J&װT2/q5jjZR&L ()ң$R"9;0Vd7qhp]l-#FS!C1F=/lS3°QG6\ aN 9B|d^wҸRb*za{)aSSh=U4HB< R{N +$p1ayCgWD%ߎr Ye׹khg i(.κLB_R;nzؒb Wj<後)ʾF%IXb!TPPBR/kI/Ԓ^;@ɫEcaws}YZ'sՊ< Q\춊K7ug=uG\JuOlA$@ PRC+)YCy#h3毤LxNi-ER/LIb-_d%GXN9z)E'aUՀezָxƑ̱h8Ԝ,Zê9MC-w/Y=FB07ޡVwt,y ;YZ]  ŷ]ʖk\/1*KU4\ 2Rn P(#R"˩DwȪhI{Vqtmc7saS7v "g <,'eLދ怑\Wb̡$*oj=!.z&މDZ:/#TF0D+U-:p9hMG^<6Gh<(^+4Q 8"E4%>ِ T;BþQwǐ>7^ͯy?\#+3c9X]Z}KK?"]rCw*CrH1۱YwQy[?q(6ݗ|@>PlA1;&]^3әz;2ߛvdC7`G8ʙ*C#P2]JLIjMN*/pmc90k6 J@vSjވM\$:RypQ|Q<ǁEV|PHm>%iUL]ƃʠ(˂|4-_ 5Oz?aDh f&5>3Gt!upKg2g e&smW儾k?K93 GN(æ{_Y(!{ihiME6~ l-K%^6: !llAnY "ׄ2UH >َ#/$9 . `UiXJp/$uĚcZ=:1%w NeS󧍊;a;'xb+c79 Xu ǎ <TqLZact0 GS\z>S$V/%w80"ZѥRʸX4.xg #Q6/ 1UҘzոhkuKè^Z\tAZߕr  0;aۈ/[xf+H5-ϯZz~gԱ TJ}8 V0Ҟ#p4@eN/"S׉ϣ?)xͫ]=d`S QOLt#FVVmQH+ʞݕ):3rF&.Kəd$ z zYRp%JUm"b+ 3 #*l~k.[~>UL\i' ;.J< 740ڥ{=1$.($"@S8i_^hwy ԚRi" !T,3 #범y7PpVɜvKqz|sm{P1j&Y7c}NMɠed](n])]TTra::QX0Ujf&"IkU~39H t @ 9WҥZzYN:taN,j9$İ+ h!;b3;Rr w_l429+išPm6JE`іX#*y+&-{RF{ɊL"^@2LwEHzOh?A)RdJ:%"S5AvђJͬK,U)("0ښn COl'ψp&5@MU)E%e'k-,;weu,7%u/u.ځ-lZ(!zt̽N@.+"J2rQh%ϊ׻63}.ueAۧV)s $Aغ9,56F鄞FOF |"8ӀA(3V5OKb$7NB1^IB&Y"wɰ(yT%\Ռ)M~n0c%E(å@N!)ޔz=~ FwniHk'/'⁩*dxG!j9>K_x3w|| v8R/[@#Hqu(L"7"c䚤ָrUύug,~=y?BDq5.?F6[:nI]CPxT,܅-BUAثa崯$x XS3лK JjP}qg3Ҝ,^u]f+pP98ܣ}:^&m z;8RFH)D%%OAS<"ZqHXڇˍЀ}U[?9עp*aՊw!irBEzs'l1l{g xMܴ{_=FӅ!inL[H,ndJh/z4 ֺ|؇/t؁k*wE8D=z@fQ G -@t6H#^I<&^x5GRFx5ed56w_/kh]D[]ۇ 0뭋j},PJb- Z*U-)a){Um4 u"qC- (ژTR.Ie?BU.B10eoAF4gYVSJBTcD!x$J]J\!SHқZ}ZϑeBؙPa\^jFfFUeVtM ɚ +,@O܍$Qe\:l/q7¡#lJ&$n~puWV%qǘx[DDRۺ5[11X]?Ҹת0tB\T[4H JU{N3V;duԹe&_V2 vPiƮmQv}eU-Q{[tH M_NlA<ʇ}hcnL OJ5 C.UhETIVhe_SLi#?jULםke5lr$TƠ.QÛE (bB8S5HH'[=WqɬOm{%GahB'LpQ(Y_Vo .3Mfna}i\,pz: JJB,ySkj'tEUmAd0h^@p GK6k A[juG<y(hE?[~$i .Y#UB咪 ;lJ!uѐi9)OFfJhSϹt=VyQN?)?2r9u~_r!,R E)Z)mNZP穎⢢ZO|$-/ӥ3+Qhm^#|V NS5%B0`:? ցN:-7nu7}LHBRq,R`];]sHo|bVDWQO VQA jrbBV𗸍k)2Ȉ%qCݥgR4OQTq!ay;iQݏ=40ji#V6mmS:׬̴S{.)N 4|)J zbw0܆F9ZOνCKz-)~2_hi=Ll9јVNI3yA4V>hw&B [KMˮ"IL<6|ay+tb,j`uTvy?CR52}+9j@b:˝&JфC{  <Yف-⺥@Z7U㒘ZoSR:ǻSVW L!WOT4EXޥ\˵B&v*/‹/ӈcPWzMOcP$Lia+kުS_XA EMIbѫͫ|6KK!#-<8X&vHs-˜6a˙Bs.˅KZ~mV(?|]\-ZX]m@G*J": 9mc:s|Zw:-0T\ʉ@,TP? 0* Zi.X{ ~'dwKk9мM:mc}kRƧԟC$yaIw%U0,d:9/!Tznqz p:=Rpq:gqҊ!r^ɒGe /R:1I˗ޑS);U^)+w҆5{(mǚT4v=Z).4cjK:@y6 ;#s UsΜXknZ`LL?VI#m&Led=riK՜ԟ6Eq.(/YUM4t$t|OHQ#p"ÅdT)'OdX0I%}[+6n(bSq&;ݾy?dm\]^ˮLxe"<IIUls[ڱ6an4A Ze!ڋmk]oh+2LuKV兵:X\19''82LDFҁS§a'U[ThZܮCWZKQV΃+dE3'-zsI=_Yo-*xlyM+D)V$ҪkL.݀9cG~`HvwNkDx8x%qT .I~A~ IDATCO ǟW-]*-zs!kY…cyT JN M!{R y%7O@aĶ' ȉYo!]A4\SHG UJ`EFlubmS\v(y)Ad#(ȭt 9FuPM8VNjQ{ ,SU sZv65MDDR@L35/pZ0)iMe64- 14(Cq3s RBډS~/$$}Ќ<^nlZd6vOcCNNɩqR‡Pаĭwv毜@P$V0udw4PAQn.[ϋ 6P#fluA9! Ce&Ymu [8DH:_:K6NisCUBf+N`) ݘz_@)]o>g8Վ͝,Veu rC?7C݄}@,u} {K,;lٶGDfdUw@H H4 9P#~&4?H!РhRwUwu{"w۶uVg7ȼq7۶k0N-Bը T(jsӵpej@eS,FCpr003pz V˂_Ϟuuy(FslZ(4`0{m(yF~<oᚫDx mN^%*7m0֌0#x8`0Gתsͦ {2^r-p%ϵlGq ܁xCY%\ˀ#aǖ3 s.*%KKTkM\bk%MAJ qsD /ن0`SrB`扽݇zeU)#JrmÌkKDVSHxtgFDlH (Fs% ,8ETue% BU^#VBu?pD c~ШX{Vkw}W۪#ĬwB7$>4<}w6χ^Chc̸uADf*uWPlBFlg>pkQ5:ό**A+ݬq @mulG{7"ݨX<.E*q]yT7ʝ2dUp8o1Tb,>g9BT¬F8$ܖY4>o"6~gL*CcsdS1\SNSo:דӵRaw X&i;(=6)c]nK{`zX׵rwbU]K;E @2.B30\ "[00Z"ֹ邱X=>XhN:j3MBs[9>pjqŵh`,+Zs; V9l-g GSŊ{NaY%^LC&4 kպdds tnك֚]KI;K e1ݤߏൗᮜ^&g :-4`R1pJ2>pb99,<)qHPx~IN[nnT*bT4B `h]wg 8Uя=OV <`d@LGskTP>A 4Xz1rm.313^7p$aX| KޤJ 2?0jwxCJ | p͘^V#qk,F3JD .rq6:4XhP3~Vg^em#v#7SbgfStR%M,L8kR4,20(&υ] @QO11fV9U;3Bܬ15yUD۔ SS*Gz=LPӆ^JLB:گR vb6WHLܚR@6y9cC+d, 'j-C4>yjۆ6n0ƒr'\p)ghc fW:6j*"bQf׈tjb㺠8 XJ9`n08千"]I'ļxoY8LT+H>&yݤL!8$jOfpTJ wu50@Bn,%޻‚Oa?Q~"nK_,ʲӊȽ^=qZNH`D ӄt/ή34[Um7\~u4\@G  g +_?LzJT݈` 2%b8"рHo3f3t_cb7•n΂WSpM^@k7&J@&@~#1S,:Fo ;IM]qk|x&~)rIzV 3c6ҋ3ag#;[3PI_.3UW>`._ ҢyG"%-g^j;#Bfg k?>~=#ܣp<)g ImW/S R%o{B653nf7!Fmk^3iC/?4Nb9{n /(37D] i2 z )Fsf@ݸ.!sμPeb-o '®tI  x)LԂ2ޢ EXn d%3 0p>Gd3b9rYW6Tڽ!&'>j~Tch^9Xh1%|4B2Kfi._Ҁ#"{EX n6,@ l)t0{c^ T˔(2~ٌ7}8mS mZElW`IKSRǥn>,׽>XbnĀ.@tT8 6)U7hXB B=hkRW_Wh0Bd^7m"YըI T@}Ei1/@^F > {Qm ҄IT|lRטϰZ-uDBt][*zRUBΛ'37~曕m sR`k>肜xHaV#!{u3RtC3!hK -ܻVf9v!Myl[Th\F]F]û^\ :AiRjvD@+j*l:jPBUvXXqIh>yFarCK7V5Lu sĂ!++SB(Ieո-{K;r 8ͭG'J׌+/QE| n/TV$v:Wڂlv6i"h'Sh/2@NvB +t}He\k]lJ6(6k:fmdrvm `śVt ٖ#0A㆘/^!hPVW٣6_*,/ Z+YY]ˬ 8&fV87LfN9on]]B%hIkWJ7ipNNӆ}h6`ͼ!:?\,tRx!w.JMZ>s%R8Eu 4Cٝ2^a#+,tuMKx AmD8SRrf0Ą/E*tw(j!ʾHx(uD>`jt9H[*N drܙo<3oBxx ٹ%ڜR^(;Û^|3@)ӌ*s \[)"I҅q 9Mx*tYO.#5T0 ]֝0vb9J&zm)ꬃ`D^|^[} 2Cdj3! (Ahl# ,"*lsP-ePK[S 1"#)@cK,V>( ICR0SWOSQI+OY{ן@ wrj^:[PPPz { KN|-_W |~]) @|Cn M.'8pW{nMlHyΒ^(h&Ԍ{9Mk3zŪl+nLu\iLT"-'Fu[{6@&˜= Zӵq }s63bl= r\|-t!<u,l3( V"%ƖJ>*6y. v'o @HZ<_ݎ ܠ0ƈo5@i;)dܖc矜j|쐬(1Y^P,#*ЀcdZj`#j['>`,B*wU CT0 qt%bZҫ sNmw{dd{A*tdr&̚3Sq9PbR4\QXk`HD\g%|[%E$ݲ_TkK#8 齵+_l1AftU@ZfUpmN˜"|rr\@8B/ ,tYnoXwpx]}Cj]O=_6ĺ~Sr]aQKqx$.g4Rɳٜ60oOzЩ~vzN=|kAU'>8*(ybR ̝'C;П?_۵3fg\ۥk]^L%6}@ťqQ7Ԟ,h,zf ZHs~7Xzt~:VO) s%2ד Ѿ7 u ̦tP< LDFVt~{?Gq;QfI8mA-[e*<8%75k-\7sw;l=8d H7rIkR{XsЌ{*ezz~"#( UJ3|EQP|obN?Fv)} )1薎mpdXX5Uk l<,hy]84D W3=6{6uDLU$4 @Zʠz&`ix^5Z->626G0#ѦSl}8.u`iuv^%7v/}Y7ui,Oຬ'b9q˻B>S(;[>TGk1hVXwl\풾 IDAT!omXI.+QΥu`$Of _{spt6tQ_1P hUٞèpWbS}`& gSƬ1VP)@oos)¥&ېm'J C 21+l7i]~pR,M)}8F3e@sM%5 5* ^yǀ%ÅNvz* (L=]XJyK +.?ڬka)(Z`0+BH VBM k1mO[k0*U}_s|(|:l XU-!zbR=CY8v[Ƨ=Y[rY& ~ּaQŖ_3ۼ?8`]8 {av˾kcx0,wJbzWU΋")vK;PO嵽E8!n w^OUNk'{tcec0_oЮT5"P(?@6ZR!L=ܕKׯo=wQ7Ճb݇ ]g"jqWew";h"냀Ec m*HmP6ថs ޲zC>fQيtv܂jfH aKB%`};s=Pa cXFw݅#]]@U7mŬ@@|&X#ʜb7HD E~gKS}vLn^fפj&0aeq- ^^y$+6Z{`EզbѢj VD <;= Gf4G0߿L/y2`8@/əAm+0fM{1jAI_;wII7о$,@yNCumՎ/❧"R1Xfa VohB=w*0SX |}Um6Cܢ(ݨkSצp5$W@Hی=À̶ {5fy\ZN9'|p9lN9] 'ɳ#˂T%uq]m݋[e0M)ye  01uk{h/Ilp#SD?>'`YN|q"R98@9~'"7LgYHUeW=n )]$=`5VI)5AFX{=6db'Qt2_3Q]eScDxzji^u`]1cm FG'4#O9/RG`2:p׻NU +'PϊEB8h+_W.l [H X{M pu0B%?5xx` ƼkߛBE X]24pCv3ٞIs`o4aß_pxEL1{ƖU#8F*,u K*p3Z Tia+H%*[lݲqHCt>,'«P@ԓJSNnzNLKE,yeJjvL۱rV!GNCK#Z?}U:C_|2%5Ϥ1؜w$x`a@\56}X5)?R8QI.e= 8L7w@ub,)l D.! ?y}uMGqMzHP МfR?'k3'~ 賔1U͐"ȉ0ױ(WҏԵR!<J^KjScpF<_Td! L%  oT ҎrkXZl n{vl>xA#o<=;e9%HahB9;Gş^q ݰj IGR©p>O]` L]gy# JP>Un(8ܙJ8}@!o-@ jvA@Z^ߝ!TQ\Qp1(=?QZp>,%W_W [5[pPo~?zmKHB%0hҬ4ӈuYC!4M֘R Sq:F k 0O_UGw:Qgq8.Ubx`.l~۬ē/J $|ΆOM9$hsˑ[je = SH@GW) N@=-Xw~?`ɲG_2UJ#DAgMiEy˲ph7Ws h(D) tM1ݠ܉4SLE93^ܑ#o EH1fXyЈfb1~*M`:W';iVN>82Fc:}vWyq]c9L+LF> _Y6uE(,  +ubsV] ;r x p@pkcڭVcG֒]G 4,rٰO*!~SЭ4E-=aj%7x8L;L>.z܈կ;5;r=Ii6?96uEB ޤkN Εx5XcQ ʖ4 *Wh@ 1U?̭Ð\ue 04iۘn1=a4"e(_ܛyD 2B&U iajx$Hqߏ0m|ta ykR1<%  /I\x,UJ紹)bYK̆ jSpZJM^.YNjIfeeG\Tlc  kC2Q~߯rN@;NaYhR@k BXs7~ܥJi|~&Y=ahI$ιPʖBFݟ<,ׄۈYcpۂ6U.ԙA9p+c)H쮽+Gz&V^KN}UficIjoeഴvR@د X'}l׭PJ$˒g!*(`)kh霜Gǯt<e,}Blt7T4)]jA^ZDh7SA!3tQ$:_eڌ4%#/soOc]5iW;:5n/܊O;6>7x^>렛coqBpq,="p!gAb,aoRǣzD^o1 8Oһv~|ė/{o~~8G Ӗb391-XW\ӂӑfT!*p:Zf'~>NN5#HUCQ$k6h[1Z߭ kK ޽&| ۡ9;{\4/kSٲkW1R{br2< >bIO >/|2vih!zЬ0nr8 m}Z Sn_](YUhʾ1Dyol>6Ǣq>*/8[ן@!4! ~7FÁf4o~[{t|s.H)5Cʍ#^/ȗRUk'r\@pW Mq3"H ,B~֗OVMN4\C\/p# }D5 WéKނ].l)%>7X?&')lNJ38 Ԥq[Pokm?z: @s0@q 63C ʘ8|آ SiB>ӕ6tc6TƌͶ. ]iUEJQ!W}yQ>|Ħ,gL9ޫjɉC(ߖF ҆jզ/fƗ@:eۧ3F(=0 1GX1mKܤcJ0߽TuEJ SL:>aB̼\v 0o!V\p[|)8f"6- s$4ìl/eUsR0MM'C7@嵃-I@k]ѢDq;џ}GmkóYPIi|odvD$Ŵ,-CHkïŴ=A;Bs>8,Fy}C+!ѣ+Q0oC\{v 8NU*Gi]k\dΥ Y5f" ً*4Ir"O(*Tj'G#j`ѝ\w$|rX8#@.RWmxUFSN\REqqA%nL_k x7Gjt%noh!~L5Ò`s7k f2s)xi,J:oMM7N8?H'Ǚ=J'}Ov-wɪv + xN8aSz؜Jh EW6`$}zޟsML:åuw΍OLRc9m= lR B?'},GkuWLۄM>uS ʙmYbeֆ|ÒB? $`T힬g> #h~KqWhRi@ 1aPhh O?9 Kl8 [ far\ΜkSf^D&6t >DbfvԕOs@F5NXR5L_.E/_SHNRHDV;5d!d9̀\ <ؚrsNh(~joҹ͋/atW>mY'5ٯO:Q:rLn>sV֬*EDȑPJi.gr ƲbԂ e\#]dV_K?G@Zg*C 2,N%`hz,Ě0,_ ȾӷܩƜ(ȭyVsRp[@᛼2(, JV9ݾ bs nٞltgM7r8/EKśg 9d]7fJuLNF*\r| Zؒrf)0Vj">\]R@BfdRz6SfW xF\.&đ卤s bqV oLVDMx{#Q9!b &iQs^ʆ`^?" *O7rLBHf 8Pblhy_W uAJ&&nM`v^Ae7fKbBd{^n Z儍$͙6Sj:S {iG-ېv_ߢj|.U</@? *"Ҧ (R-U-$Y>my?ƗvT’`CpTR- bB2ٰ\0m,hRu}.,_ܻ֓{ҹ |@~m&?N^/.Gׅ$Y_QvtAASW[j7.$K{0gDöRIU1]B6|MW'PqO%X7H!]VKƏk?_*?};䵤iz-=/ٷd[qD{pNuewi7#Ro\yߊ۽hQ@f*ƶLpeSYz /SNo08Vn˳޿z.\{h.ȵ~zZ#_O%8=F-5n$S+x">)w/p>}vOc7VQqلs), "`!S oi-zCw7RNqJI8Owge0IV:IJ>ÓU N{N| K9r.M8Rpa N}40M|]@ljif6>Ҩa*db)D1Wp_ گ[(@Gk״ײe?n݂׃fak=BpUt=/S{Ŵ U ?E)'eQůWžjI#fIj#Y tyAU_64RK+>R E(l(k1hVv r@P#kM#&ٜ{ۓU{ɪF1gL](ulZ76͕m&垵5j'^C-kOP;1^rq y_8)O]u"qAnk<ʭϱ|Х}WN}MpI#1_k(Nm\3,NPFfZ,Tc1fUjЎgn&g267pa&zzr?#HIc:_i?`&,G"̹@#K7~k~9S/ElK1e\i{h3uQBe嚄ia@QsѠ3Jz-zp\kf!$xDKOP;]H9q'syz>Wk&UD)rfDLMx7,kMB~*Fj0$L>PF5o Zϵn G7r;Q5u23vRB l* P6Tkn#aٝIckqg7ֲfWZ)kpOUWgޏ3dܩv0-:\TsW1!l*!ds_.##LVɲ5xdF2!dv] !K'lP#l>8`ȸ˜9#7j"0!CoBqK x$1`@{M ☁Hx+8U|g6&;PAǂܲEˈ^nyAvfW`~%@'*;m ca0+hV';cU\ZGC |J%>%ZHOpVx*w}=v{_׿\Z[r 2i03^L|JtHݏYfi}M@ɘ;Ae; R0ᒘHsz_3_hcpo6 \_v7|~=· /3_{~{C^㷟^ӟ?co i 3n40% Bj&a]!7_M_[K?t}:O''տ0kƼpHLsQk~|ҀFĕk1c~6bPP}(ԧB ` C\FoO_ ѿf_}M?_|t#RQHAA+16e۔ՄKc1jlq=W$XK>l?Œ x&,:lKg]ۗ]]0xFdAcT8CD1hB!!D<1B$9h81 Dɨ\qAd 2LwO]/߷ֻΏoWtWI:u]yyۧ|p~JPEd-D/4n[$rߜ~n?ǁ\+r&e/zы^WYqq܀t p =VYLFqL\g"XǂLwNEKxRgL3]Ipkm$L_MEBB W||>ZTgHƉ[@RSd0,\3}9崌] -4^Ŕ58(Q-*[B~S闱V ",'>,S, **(.@y8ѩ -;a%U@G j.[;tuqί\ KRIuƚQGY]q=YzEEBgP';) VЧFA"Uq]ɨ'zDCx[@G<]Ou Vuj%^4Avֶ^iF02fX԰Rhyks{} 9юOepCHo_Fҡ! :Y_עЫ%j^шE*i`HP&``:=%`MS“Tߒ rEOdǵ}aŚS:Jt4` OgRb.>8d~VdUL=g@%Fu(,ʓT\JK捭`":$/czraϚs ha> rq .KAJ검{{(' v >' |yN!/ Ecա0CӫZv,0\] &>euBQKn!)Zo "P3EpNQwMTB~8H.pOmҠ\0gFzeA. R{$Vٟϼ#. X?@J;= ,kw$$DpKRA d Vhi Ek5=lXf֔Q[Ӆ0qC2@!1Y9I:` 5ac!CԪMDt/YKk Bc6\4pgCږ`rqO TGh_iEc%adSR4-ߎ 7p:M;찛$%Bc!dAC] {[3w'L"( ӡןk;B(8 7,,o:Y $PS$ Z{ I kW`X,i#X$F%,ը⏖@eTQ'@(jIuQ"cj5`$Mȱs >3ЁO hkżV0P `S9쒆 }1o4.+_IL@݉7 b'up4^aUޞ&L~@J lYQa i7 Xz])ҏ .03,U7P+X`b&X۟7^؀=IE=ry )ۀˠǜ=CR5DZ, B_k jKm=@ }X3ȏ[ :;6@{=OKr>o[o<2x~hS8T`Ca"/ ##zi'[ 3>:b2(rva:;b"Mh!w)h¸/.c9(P2Io]_{VAvyǒz~*ԣ,ÕD`OeGziܗ%#cݱc]X]ۮ8g ~t=ekbn\ p]w p7F<t7St SUXar2 zE6+C4>  s$j`((Momx \Tq_*?яB=?awc|k~F[o{ a_;qjq"~{*G+SD, {R`Hٕl#A`2fCa Wx"):җ#APta+Ԙړ'"eo;^/cş O^J%_Ws`,8hO!uH1%j\[˃0>mGQQCI8P\%h,Κa o}+z?CW_zof>aǠ 5$=vPcMaUwȗ0/ ]+#j34"hx)pk-$P1Th~S0Pg `~@?gxSbKKZYaDA0^ 0 CϷ~Y@P7@ #.8& TL)NaRRm7Ɇ_`&RP)߻.KJ'׍tp:_@`=xB,B p^{- քT?s' 魯~U`Wר|3.2--6z0g'ޛuCƗة ,8o$<7 ;<-x$l XqX<w~:P`%, &g>MJc6-VG(aVRbbTAlk4&ϦLex?l8aSحTϏJn~k3qq@)\"BVh(`\۴PCl)Ɨ\Nc ,zAP<Y4 Iuf+ep"L0TQi3Nd cmML7!r H?~jC:g?A|%yσFۃxqס_!_$'g,egA_MScGctuC^Ǟ,wc` $Wⱟq,o ;0S@w,ꓟytwuP?dzߙ3{qV&{{!wÿ?kPbst=9'/tUQhJ)>NU`kSץ.dw{PEpmHP\T~ay*p)@c4S(ɭB!uyP˿sxWp/{Ӈ}{nWi`Y/}o(LȘMG7XY^y`"0vzspm}r4sk^s/GL2^z22IĪeIȅ@uP0W+ 3 FT_X|Пcgx@/0, w tms&2laJlVjj )ʞQ*P7Et9@wc;e@TR2L"˸Oo:|Y{sKf C֮XlKsp}l@<@GGsi]dkDgK0q**]rLae`0ћ,8q8# 2k2>~cDzS><(vGL'^+lZ;aPOSq ׂE/sͥ],GV q#e:0Olq$aW= ~0xֳPO߀ ^>P6p),,Qü=>We~l58S Lg%&"B XթYZo4hP zQw< r@'f,϶hv{;6aiѯ Ԇ&t ҐtZYMҋ\Ρz/D$ 7H ;#D,d~YŖoFs(>+x Ltn {^|c?ڄ=d *aQ|x{p5h^xXm ML|r:j0%F8Gw8P"y۞|zz eck|Td2@HxzEo +#xe.ޙ-JdO@+yLUTRa.E,<~KA|ooz;п6o{d̃1Ns/Ӣh/N] әwP0cr4FE\d& c?C nͧ X"HE1cғzX wE`"/;V\ހTC/G}bG))vcK'OL) }[!ϞEoD뿞'W9vƫE3kY f<\|K+.?]k~ςАV=[]=fދR|{8 çgJҗw\,Q`.˳,q8o ( `4 0 n#&q#o.oy}K#2pѱÊm0{k >|ڮBl6i'cL+{MșIow.CmgZ)Lg6fGzD.SL+ci 8~ӯ|w럹 +wl>qW)vII =|[4gӖL:]Di{SX(qJܬHdAǾ x`6k׾?կ=grwşI>iW! S#5E?cSZعx@nȟI^-^y$] .?KrB5QӖ4PRu8uS@55A#`~a~@ŕB(|2ũpn.q/EQޛ#mc S\ xh& Zʨah7=m )LAlI y+w>(g>}+|W!6v:Ek`570 a$,jPqZL/rďTJ%<Hӏqc1P^甋xSWoCaC}ch$.{ RiAJD;vEu3t}^Ь6.Y*F>}Wj*;a2)A?è??Y#ʉ+,ѤE\ VKZ"g&}@ -EۡVJ}YҺLl8:{NE!JN`p/~1ܹ JkX@vOAiROq2T==%ddqZl%Zp`>\+GUk~'xnp&Tk3hyArm1RsThqES7j: }̤ē5"/Qe #o=yL 1Ogh%F6ٖHI7a+Be%(3RQE0pl,9۲@tXY|Oeّ2fcm)?p< @O4?"/ZNkCHT6?Sz-|&»oNSp3ZD"pF(7؂듃 @cПz׿i _>|xiZK%_Aߴ P 7ׅ|{i 0E#5+N#} W ]wA+(CЍ8c\*L%0$D+Q>}lbF)l,նVe5 x"Rq/谛B?п|'C~7@]70a|3vs"<c);#A&N%[L,0s)Pٿ-zߎw@]㨙;_ET[?X|y;v}'ž<>74垱CaPu\ |St PЧ?Z~[Tu׷J]CR'xw zKA}tp7w0J{{;;=@{/? yA/9p]efxFXXq h˙ܰbU/+LhG$0w_jCx%])S (PCN>[MN&O=mӼM]{1^iQ9@.&}vi&p!* ]Pj-RHg3B&3 s ?W!O݁"zHEsӃ΍DnSX ,wļibϛq|gRN@%բ߇:"B5aQa hLJ|} 4`KI `4a%Ug/ 'Ua<:o~3;/*gI.'GJljĻ8怛~aw\V M`S)XQ&aK?\KHY`h^ZԿWq= L-@h؁@sM{o''wyXm{n{nžQT`Xp}#?Ks/uj$VEi&, rYuUC؅PsQlKc~@gv\'+@Nkx:)g)' D. p` rC dǏ>{Ѽ3g@]>=8aL˓()UT ,3eM?=)Kw%%Z>S |Y gv׾k_ 4^7ph~B dlf( Xz符iZR@+_ (Ap{sQb(=!wAURuʂM`uű.DQt^G/@<<S`'`^j؟)1N@5xkF`CuUS2V*.@Jan&0ȳht`) LJ]48UcY\7uhſu= Ɨ(KNg= t-IB}* COMTcW;T 84t{>;Vb 3|"dƌ$Jm1=@[oz9Uiؐ{KolܔX:0HtJ>07\<ܙ~rI\(ha[SO `ytg.pI#an{"N/-rBu&t>cpڙem)/=`lEx x>ɘI Z"Rdd\ʸ Y=ڄM {N6(vY,忷u\I)bR eszSw] 5sڟ*muR=W6,A#J1'H_.Y4g:fuu.oo\+K$^FT E<,K3D\^Nϔ"f/IVRʉ.S̅'&1~(7Gpm ]wy j4q T OleP3'ZCeAPnrY0 Ź:/dAXnZ01,7ӕ`ȧ sWJJlb??+cj7W32'eT _kpV.AhЧ/m8,A02ccyʁ ciOr+H')(%E |y)?;0?P3Эw&`:fþnA<tg xy7hBX?ԍ+ Dᕀ7 l 2xJlkEz\M餍TTާޟu =Wc( TNϊVYM@k, p$@@Mg-rر BZok0ܕ  +8 xS'2?+2 ,c`L!DGoBt{+7onw/:+Dohsk@A0A˿3~gvI0m.xvLE`d~b y`KQLo2ϔ"v}8Wf%s'R E[@ƺNoYVpV ێU{!@ץĄ}+ZT"Q _xKI9  { @ψ?߉.Pir MpSXLaaH*(ˈ5ǙRlݮ+ƍ%gӠJnKrڗs=Ϝ5gbH=K{LXʲ#G\2S% K2# 4036mg%]Z̗]@g04EcmG }`%唨,r30c=2PR9T0e ”e-Mn]y 7T… 2˜lin?~F_6XoGHxN y*keozSy*t:sG Z7;$[ƦM>g눙 ##Ɏ>ͪBL44m.pK MsMv&`cXۑOk( tӝHz[r_O z?șҫ:pfҋnd|?aKQ<+)`3մRP%tZ^Z:\`}+K+9@d q@j Ԋ~lk`&$f]Yr %Mgfe X%e1Y('[ s?+ՅRE\d8L2aKTur9/k ^v q]`˥"ڈc`lnf% ]xM!Pi2|~" ̗!GHu\r0 %´aFAo|v(#cy ИM1=Wc[A?ZJký?(R P 6…>eםS54I=SBVPJ)@ڂZ \pZXo8) :Pu;(j5%݄hjS,g*cIN@1{NcN[[[qqQ{ByR@AޭI4HYZ1/_z(NaTvcا3:@)cL|YVmmap? yZ%}["\qOBɖDT,Ѽ{nëp/0x|l,HfPP 6;_Wy^M7mnό>y}zbޖ{Kçzݭ߼MCwe|on]ߍN?cO/uS_tWӏ{:מqfo뗋GU΍}lzKF @#%Hq[`]?Gx K&CFvPU~j`2@'‚ {7+Ϛ wbZt:oĩNR Kp8(C s Ųz#Nan Xog Oh pmunZ@a1C;ڒ93I(D!Tvu֓Y)#ȇK&Tεh{!& r€\3蒼߄/䨛F7AeA`JS&R@%C7<[B`6x=@GeZ-ªx[GLG}H-^y_ *Ah>.}25,jo2qGwgwchhWp3 51o5+@6~MvAwD14>3(ȭHĆ"xK 'SW,K;ƅqO =P]@8Ut@WaoDJeH&Nf *K M5F/ۦndu`lFZs:wԝԷK=m)+'w1E]Z{ ̣;OD϶PeMLM AM&DSy||B@쪄Q.a/ c &!sy/I'ܚ&C3+M/o(9թ=tI:zIնsN47P ]ac瑬PzY#YGK*#zQm ՚49wà4Ķ mǯvV)@rPT9JfB6%܂o=; ֝t6_|ZIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/32x32/mimetypes/000077500000000000000000000000001504763705000272125ustar00rootroot00000000000000application-x-st-disk-image.png000066400000000000000000000032231504763705000350450ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/32x32/mimetypesPNG  IHDR szzbKGD pHYs.#.#x?vtIME 8 IDATXýM]W{Ϲ_ݼL+b 6`Dp`5R4B8ґN3@A@$H*%8ȳy/{޻h\p9}^ڊ;ym Uݕ>yB܈|ZnޚowTz݂q;Ɯyu͊R<|ѥ?('9FY k`)%L} c IxnW2]faRseY:xPM8(bLAt"M&Y-#:pCd M1a{[=tJ@-\v qj?$g3Zֲ33#.t ƒ(ʑTdGf8R8ʹx`K9.fRJ,f\ ub ȉa7>xVbntCųT!h(*hBT˄iL,_Dl SB41)e1Jޓ=EQ`b%3fFڱ5f2Rb27 \k&2`uq%BURt@6 - L;+0b,¤r6$M2F,i͢X{B2zqPiy>b0b3PzST!)P1EQ&7F*`&e%4l GZ*Tu{"26D:JEIyg֭^Ifi~ZO?wb מa4R4X}vK'nu1-l-2C ̄%tk{yW}VGՋč SOQUhW+*2z9]'Nz/"|_𐭛ZJyYכ[oA9o~QZ'Ob|xm/ @[JF\Ǯdx3{ѣ51\|?|QBvz~Skk瞣f1R ?!FQ0zO4SSۙj<;wz(f'}`n4UA)/.\/ @¼n]{߽ͧ5룰v/_F_o;($k ,(WF>rjsI=YjԈ_ 9C%ds[ (fCaXxy}$07dU_S } X|'5<C-Gt]H"B%[9е?16@s"O?Zp4:9=8y.9]șą~Om4ycsY49mF+Wb\6PQtd=Xe 'Rv3gwDu% a2(9mkB~w \J{yJ[:\RLll6ڀ*"-Z M렊T JԸ/fN:dl 7! 6$Nr/^."dGIs~\?. [ T~UZad*& ys3S`,; ڵY]s FQذ,- Sd0F§n3CsKw:kOFz6Qq!C/w߿/` V53{IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/48x48/mimetypes/000077500000000000000000000000001504763705000272305ustar00rootroot00000000000000application-x-st-disk-image.png000066400000000000000000000056301504763705000350670ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/48x48/mimetypesPNG  IHDR00WbKGD pHYs.#.#x?vtIME 5hqm %IDATh͚Meuyg$'RfpMC@DJ lq6 PƐ`e!cbC J N!1D# rDf~ު:Ew_w mGߪskő_w>rd25l m8-/λgrߺ?\â֬0*f ՌO!2t;cD:EkLh:o'e6cAsիL}?FkCCDZ?Űn{3ז`uB]C=Iu v!,dO@EERG8:tNHޗ/Dp5:ck$H"`-MXHG^T[0SC`*Xcbv-UݱC%`Snuk.oi:;EV<+-_&VXv̖h# ov$5qJu@b͂r[DUUM!E ahmTؘ]88q txڲJZ*S<0O]# p-;&=CmhLͽfdDd0@IkYX6# ,hbv@$H*ѺLiVbC.l*G8VB+Ml1D%R]#$]P`: 2fr vQZkm)@^Ń=?kЖ.iR#1،iZCFyGCIXF" ̓&^^0;h 2bSt*9r2 p`#6PVLVG,A]#bLLT˶E"ϘHh3UHa͞3!*.%Wb Աpy o@zȔRJadR_x 1f` F".xb5Җ,Fv̽>zQ$7#F9c cS(tVX[% m@箰̪)" &{&f"W6p3 SkRUUqbmDZh҉]6U] kdqdb* !yύ0`x*Zvjź 8@zLZ9fʱ%`@%-F1zD0jl:\@.4N!'GVou~TuzZ`x#ؙퟎ6K2 BklN.vVUĽj;6-ݛpЌ`Yw .W 6) ]#LQ'+ɱ֚$D2sŰvw:f*qo wS4V@⺧iWg+ͺYS;C B̓fլ('=HjIRJ2u[FW Qs8f}~oMEJ⅂4cJw^:)uo|6=WJs (V6|{5$I\D ĮSq#Ljװ&ƍf3K+Wﱭ9&%6b)HQȗ.mWOȆ~wY<&S:!sQ-:~L1ZOB~^zһPN\{᝿彧I޺zm8ʟCIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/64x64/000077500000000000000000000000001504763705000252105ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/64x64/apps/000077500000000000000000000000001504763705000261535ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/64x64/apps/hatari.png000066400000000000000000000101671504763705000301360ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME QiIDATxOTWv\0*`qW0 P#+`aeDBz1ʢ.^fc"ںB?yfB {Mlzy u8{fLuQRɓZUzUswso_J>z4j}\fad`tpdR!`g׾/y3&bܰ." Kh 1 ؿ9.zϐyn31?oc'I Ja 5(&N5ÿ o0<Dw~}~)gu:rm|Eup(j%^@)azfȍK1<5Ɲ%_sZ(P[߂>rxjj{S0i`6`}eTa/C`.@a-&Hfv>,ʤL T~[|nG7r& ?0XLs Ul%e[lH3/BH`M͂|> ڇuS2jHJl}d?KZ`t1)2PVPڟAQ~O .}ϭIImi@&jcrScx/I J?~ gcʦC 97 {2@@t4&=Txf~0g`X(Ƥms#P&^]=ĭ.7F@3"AkG'V9c!L(C)LovZ[Gs/CQ$.X3鉚k8 xG5VngxA_C.k PPKvyuwlU= 7w*@Փ#:8V7BrKϛZ8 =-f0Xp`JG2 E5"Zp]3м(ԐzP| l4tv^I o(W1 l,@"k6P A \OPO0k dA!gIXӬ]7=Erؽr|w& kp D\𚔡0^'W#ՋׅzP3I`>"鐍&/͟-MbicxV5{܅El2Z)Ҹ]B`xR%V( cqgSz"5MXo]n*r29j,&sҸWXOjv楉kH)Rj!w`=peRM+[ wZĨW"ɰWDg_;iBS%uƴڠ %c <$ /lb<Ù(p-v<П9-a wI~{x+[oIJrPqiE5`b߳{Dk^hu#VHBl$Q-hŗTR/ٔ!!Bxhyo[SpYcv ( V{-h,n C u(HˈkR*mG$4L|߾g Kze17uҙ/W\'׹ | b(-\eA03"`kT dI5_A/!λB|#4O"}wHģxޟD=|g p8$ 欑]`MgW.UMA,j<P'E&NAc]{I ܾK9Is@] + 'p{wvmK)yh%P 0!d@ő0et:|#ݬn0uA8c3bxYY| {*Ё 2ݲF\d"TemkIPsx!Z.̡L5vh&ٻ6 kk1ڔ(HSs5lf++|A_'hsI&ut tIvZw H㔮nc5CtpcAR?$[# W?[g@t ҅+um.ܢ^Z1s8ʑxQ=䠅MV^ӑ됟v'<` ^IOZd}2Ӂoy8Pk ryőT޵_m&n#m55w6 Pّ*A %( A]`DKJ +I}c4va&ojm˳ʐїO^N(A@Vf`ghwG*L)k9 8ʮK,/MrO,Rs46U. "**˻_ujiFӹ/X:iyNS3CvJkd A* ]!B&ޓY2Dv߂ŻxޤL$ ~=ܷ޺0"? X8x"C\(E_vxw}S:s"6ʸ6 $< 2ZHN8dKpMWq2?||b}Zژ B캔pQόj;xN_/5^''>"2Vx;nY7ܓ6wkc/?Bz^"w{9'!UnaŏHL2?d.))ٗ?+/)ʐ6k2t_As]dU E{MKcŢ԰CoI [Ɍy$iY%N:U?<{ `z77 7.P ХfoFXa~{|=j ceJQ!8JxGya&3@;M*㯥nPV^vk='5u8Qj"E K:0qf {Fm8e (xM%`h`A-BkH;z栽ۑ{tY h~/†tJ١5BzHo0z?w@*(X Vu9 8j+Gu>xL3  ԏ n4*mrTg4ô&V,~<} KVeDH)-v]Wӛ'҃pB)6W+rK@6ې9'ż5zS}XeiIwi}`b$P_}'2|Q[=,F<[ϵ9+V^wK/ZXV'jtHFFzdT*T3=iT5#4c(s |V$R.U B ~ug u'ok?c IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/64x64/mimetypes/000077500000000000000000000000001504763705000272245ustar00rootroot00000000000000application-x-st-disk-image.png000066400000000000000000000113161504763705000350610ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/64x64/mimetypesPNG  IHDR@@iqbKGD pHYs.#.#x?vtIME /)#[IDATx՛_eq_cOleGbdIAS ,`!E2PxBʛDc'LqP{Ǚ={UCs-Znu{>{ժꫯjW\O/@ wkwW__}޲;x,$zѿD֒u$1} 9.c5#3cY ͗!7@Nf}A*?/ػ9{=3퇾o}++y\/cCOHp%mܗ1~{7ײ!gG>XHh;gZDщan4[A0(1s2& Ѱ}eAxٯ>Ώo%$P|}WƯ]xoÏӗa$)ifb37$emz*qsRl}#B,f`#Hdi;ё&02I3,iBp cfF=d"H÷mg \pg{q )0D"Y&H0'3\M]o;rt\ w:VNHX}`)!Li4G_4=Y `#6wvNp$ LȄKv%%$1-h1[Y)UezOQ%D{p~mCtB/6t"v[$ :Иrwg |#k߸~ጝAq8nh:jBHzVC[+Q 3#1%M. IJZѷ G`)%w҉& `$DOv&.<`M/-ʂ;U,tHA\20d 0307FDGe"- %p@17cg΢,`c!( SpzxOYa8F1I0R4 8QedLB)b&ȶ!L!n |p$`Yv( }`-$vIb&T0ġ'bx M& "|:V V9#gH&y=]W<:9|;Bfts.I3齈B&IAD2icY*Gu "-' Frݓ5+K08PxN^5ݮv{Zʁ-E)7ӒHoswa5@Ǡʁ{H8^$baNZ!HdBUU)BD3 0RzdȤ1'lZt$̓fg^)+WY5@8QX(!2*4 %-kI 1F F:*MdˁaѮILJA}rm< hV!rf  ΆaJA+J1c0  Cl8qmF,Ȩ1tBJwŻ Pm1SG2J7FD5"G8Z@`w 'H)ټa rryc FkFdmhD27vbHdjsƀY 2{/(/D8"mCE@ m+$[_1,Ȋ[t]C#:&AO`Ԧ-s3Ӽ:|~/3?'laԻ fN*Ps,eŦ2*;PT.YHOnYaPN*Q vjwh!͠>7VOw[h2M,2'9UnG"d,nT5C1s64C r'{LQ3PtHcNHf`Hcg! wJ!$g\6Y_Yف7qmn#AӲ e΂PQfK֙zJa⼈9zZ^8cnU]Sgs"y"mXe10F #3[B7]4R\tɇVQCZKfLn (0TzCf J"&LQcjGEHdc#%Q7XWUd_,A13ɬE,KrQW╘_ڭ0ã6N+"(]ꍞ lU^achjb‰K<ɓ=ӊhn72 Q91ƹ֎UI'nl#fqy̝^YӦyy&;|6m:{0,jIMjWOڿ(ׯ_=.|?]*/^6sbOe]VK`%,Ng_w[;gyk߶uևٲ3֏O}+MBg729]7╓hq@nnj[epy/SYMc)vq .T=9z}(J^C(AI}3:Aq JaW/{VQςu rv{42,&Vafphi05;]YT=R%SjO\7u!wJ^bWxHbU2C rQ ;O=O<<\N޺E>(?񏟿|}81J "z_m(=,'9y{W0o}> <]/Y ֙2C&3 56/@>>y= NJNwľr%gc.%j.m~_o:.G>x Jɨ,w^\ HkWEy/ 3Ƀ4R#ev-)Ag?S_{ccOpbW ֨0xKQ$p9 ɝs9~lj׿/Ї p8]d)QdBEY )Jg z@ysϱٟ=D_LP5 wӧ]W-wN8_-6kT3i&="#]F N.![Y>ɋ|Sbjsc@8Sy$dp{O=?ywNNX|N/JN_b4f5dݓ }S?$<?-3?0?;y_ {ܿ|;Cv1Jwn48(k0i}0ܫzR r nڥ4mcglLdUO&_4'`.T A$kSV9/j"jv Yȟ9~E @5{_~OL\8 /0gY}~u%uX"fB1B@/|zį$:6st~%I̽Ko<֫ c gsJ"ŸՌ}&ȷ^U/Pc^`_j vv9l7d:ꅀ;yfdܒsA.*zMmUij3}pe!Rcd}3:fyDhΫD vV=u>(PU}zeu:DEQu\oyûy@,qįfXvu6%= :v|_8>Ts'7yQqq3ss\foaL*ԦmX۸vۃo~3<(/xۃn"5TAIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/scalable/000077500000000000000000000000001504763705000261635ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/scalable/apps/000077500000000000000000000000001504763705000271265ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/scalable/apps/hatari.svg000066400000000000000000001305601504763705000311240ustar00rootroot00000000000000 image/svg+xml hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/scalable/mimetypes/000077500000000000000000000000001504763705000301775ustar00rootroot00000000000000application-x-st-disk-image.svg000066400000000000000000000157751504763705000360640ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/icons/hicolor/scalable/mimetypes image/svg+xml hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/mime/000077500000000000000000000000001504763705000225725ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/mime/packages/000077500000000000000000000000001504763705000243505ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/share/mime/packages/hatari.xml000066400000000000000000000021471504763705000263460ustar00rootroot00000000000000 ST disk image Magic Shadow Archiver disk image FastCopy DIM disk image Pasti STX disk image hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/000077500000000000000000000000001504763705000213305ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/CMakeLists.txt000066400000000000000000000260011504763705000240670ustar00rootroot00000000000000 set(SOURCES acia.c audio.c avi_record.c bios.c blitter.c cart.c cfgopts.c clocks_timings.c configuration.c options.c change.c control.c cycInt.c cycles.c dialog.c dmaSnd.c fdc.c file.c floppy.c floppy_ipf.c floppy_stx.c gemdos.c hdc.c ide.c ikbd.c ioMem.c ioMemTabST.c ioMemTabSTE.c ioMemTabTT.c ioMemTabFalcon.c joy.c keymap.c m68000.c main.c midi.c memorySnapShot.c mfp.c nf_scsidrv.c ncr5380.c paths.c psg.c printer.c resolution.c rs232.c reset.c rtc.c scandir.c scc.c scu_vme.c stMemory.c screen.c screenConvert.c screenSnapShot.c shortcut.c sound.c spec512.c statusbar.c str.c tos.c utils.c vdi.c inffile.c video.c wavFormat.c xbios.c ymFormat.c lilo.c) if(EMSCRIPTEN) set(SOURCES ${SOURCES} emscripten.js emscripten_shell.html) endif() # Disk image code is shared with the hmsa tool, so we put it into a library: add_library(Floppy createBlankImage.c dim.c msa.c st.c zip.c utils.c) file(GLOB TOS_IMG_FILE tos.img LIST_DIRECTORIES false) # When building for macOS, define specific sources for gui and resources if(ENABLE_OSX_BUNDLE) set(GUIOSX_SOURCES gui-osx/AlertHooks.m gui-osx/PrefsController.m gui-osx/Shared.m gui-osx/CreateFloppyController.m gui-osx/SDLMain.m gui-osx/paths.m) set_source_files_properties(${GUIOSX_SOURCES} PROPERTIES LANGUAGE C) set(GUIOSX_RSRCS Hatari.icns gui-osx/stdisk.png gui-osx/en.lproj gui-osx/fr.lproj ${TOS_IMG_FILE}) set(GUIOSX_DOCS ${CMAKE_SOURCE_DIR}/doc/manual.html ${CMAKE_SOURCE_DIR}/doc/images ${CMAKE_SOURCE_DIR}/doc/compatibility.html ${CMAKE_SOURCE_DIR}/doc/toc.js ) # these are the macOS Interface Builder Files set (HATARI_XIBS en.lproj/SDLMain fr.lproj/SDLMain) elseif(APPLE) set(SOURCES ${SOURCES} gui-osx/paths.m) endif() # When building for Windows, define specific sources for gui and resources # and set the subsystem of the resulting .exe to "windows GUI" instead of "console" # Recent mingw version sets _FORTIFY_SOURCE, which requires to link with lib ssp. # We use "--as-needed" to keep compatibility with older mingw that don't require lib ssp if(WIN32) set(GUIWIN_SOURCES gui-win/opencon.c) set(GUIWIN_RES gui-win/hatari-winicon.rc) if(CMAKE_COMPILER_IS_GNUCC) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mwindows -Wl,--as-needed -lssp") endif(CMAKE_COMPILER_IS_GNUCC) endif(WIN32) include_directories(includes debug falcon ${CMAKE_BINARY_DIR} ${SDL2_INCLUDE_DIRS} cpu) if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIR}) set(SOURCES ${SOURCES} unzip.c) endif(ZLIB_FOUND) if(PNG_FOUND) include_directories(${PNG_INCLUDE_DIR}) endif(PNG_FOUND) if(PortMidi_FOUND) include_directories(${PORTMIDI_INCLUDE_DIR}) endif(PortMidi_FOUND) if(X11_FOUND) include_directories(${X11_INCLUDE_DIR}) endif(X11_FOUND) if(CapsImage_FOUND) include_directories(${CAPSIMAGE_INCLUDE_DIR}) endif(CapsImage_FOUND) if(Capstone_FOUND) include_directories(${CAPSTONE_INCLUDE_DIR}) endif(Capstone_FOUND) link_directories(${CMAKE_CURRENT_BINARY_DIR}/debug ${CMAKE_CURRENT_BINARY_DIR}/falcon ${CMAKE_CURRENT_BINARY_DIR}/gui-sdl ${CMAKE_CURRENT_BINARY_DIR}/cpu) add_subdirectory(debug) add_subdirectory(falcon) add_subdirectory(gui-sdl) add_subdirectory(cpu) # When building for macOS, add specific sources if(ENABLE_OSX_BUNDLE) add_executable(${APP_NAME} MACOSX_BUNDLE ${GUIOSX_RSRCS} ${GUIOSX_DOCS} ${SOURCES} ${GUIOSX_SOURCES}) set_target_properties(${APP_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/gui-osx/Info-Hatari.plist) set_target_properties(${APP_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") set_target_properties(${APP_NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) set(MACOSX_BUNDLE_ICON_FILE Hatari.icns) set(MACOSX_BUNDLE_GUI_IDENTIFIER org.hatari-emu.Hatari) if(CMAKE_GENERATOR MATCHES "Xcode") set_target_properties(${APP_NAME} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.hatari-emu.Hatari") set(BUNDLE_CONTENTS ${CMAKE_CURRENT_BINARY_DIR}/\${CONFIGURATION}/${APP_NAME}.app/Contents) else() set(BUNDLE_CONTENTS ${CMAKE_CURRENT_BINARY_DIR}/${APP_NAME}.app/Contents) endif() if(PNG_FOUND) set(COPY_PNG cp -R ${PNG_LIBRARY} ${BUNDLE_CONTENTS}/Frameworks/) endif() if(CapsImage_FOUND) set(COPY_CAPSIMAGE cp -R ${CAPSIMAGE_LIBRARY} ${BUNDLE_CONTENTS}/Frameworks/) endif() # Create the Hatari.icns file set(ICONSDIR ${CMAKE_SOURCE_DIR}/share/icons/hicolor/) add_custom_command(OUTPUT ${MACOSX_BUNDLE_ICON_FILE} COMMAND mkdir -p ${CMAKE_BINARY_DIR}/Hatari.iconset COMMAND sips -Z 16 ${ICONSDIR}/32x32/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_16x16.png COMMAND sips -Z 32 ${ICONSDIR}/32x32/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_16x16@2x.png COMMAND sips -Z 32 ${ICONSDIR}/32x32/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_32x32.png COMMAND sips -Z 64 ${ICONSDIR}/64x64/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_32x32@2x.png COMMAND sips -Z 128 ${ICONSDIR}/128x128/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_128x128.png COMMAND sips -Z 256 ${ICONSDIR}/256x256/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_128x128@2x.png COMMAND sips -Z 256 ${ICONSDIR}/256x256/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_256x256.png COMMAND sips -Z 512 ${ICONSDIR}/256x256/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_256x256@2x.png COMMAND sips -Z 512 ${ICONSDIR}/256x256/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_512x512.png COMMAND sips -Z 1024 ${ICONSDIR}/256x256/apps/hatari.png --out ${CMAKE_BINARY_DIR}/Hatari.iconset/icon_512x512@2x.png COMMAND iconutil -c icns -o ${MACOSX_BUNDLE_ICON_FILE} ${CMAKE_BINARY_DIR}/Hatari.iconset ) # Create Hatari.app bundle add_custom_target(osx_bundle_dirs COMMAND rm -rf ${BUNDLE_CONTENTS}/Resources COMMAND rm -rf ${BUNDLE_CONTENTS}/Frameworks COMMAND rm -rf ${BUNDLE_CONTENTS}/MacOS COMMAND mkdir -p ${BUNDLE_CONTENTS}/Resources COMMAND mkdir -p ${BUNDLE_CONTENTS}/Frameworks COMMAND mkdir -p ${BUNDLE_CONTENTS}/MacOS COMMAND cp ${MACOSX_BUNDLE_ICON_FILE} ${BUNDLE_CONTENTS}/Resources/${MACOSX_BUNDLE_ICON_FILE} # Copy Localized .nib and help to Bundle COMMAND cp -R ${CMAKE_CURRENT_SOURCE_DIR}/gui-osx/*.lproj ${BUNDLE_CONTENTS}/Resources/ COMMAND mkdir -p ${BUNDLE_CONTENTS}/Resources/HatariHelp COMMAND cp -R ${GUIOSX_DOCS} ${BUNDLE_CONTENTS}/Resources/HatariHelp/ #COMMAND mkdir -p ${BUNDLE_CONTENTS}/Resources/fr.lproj/HatariHelp #COMMAND cp -R ${GUIOSX_DOCS} ${BUNDLE_CONTENTS}/Resources/fr.lproj/HatariHelp/ # Copy Frameworks to Bundle COMMAND cp -R ${SDL2_LIBDIR} ${BUNDLE_CONTENTS}/Frameworks/ COMMAND ${COPY_PNG} COMMAND ${COPY_CAPSIMAGE} DEPENDS ${MACOSX_BUNDLE_ICON_FILE} ) add_dependencies(${APP_NAME} osx_bundle_dirs) set_source_files_properties(${GUIOSX_RSRCS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) set_source_files_properties(${GUIOSX_DOCS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/HatariHelp) #find ibtool find_program(IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin") if (${IBTOOL} STREQUAL "IBTOOL-NOTFOUND") message(SEND_ERROR "ibtool can not be found and is needed to compile the .xib files. It should have been installed with the Apple developer tools. The default system paths were searched in addition to ${OSX_DEVELOPER_ROOT}/usr/bin") endif() # Compile the .xib files using the 'ibtool' program with the destination being the app package foreach(xib ${HATARI_XIBS}) add_custom_command (TARGET ${APP_NAME} POST_BUILD COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text --compile ${BUNDLE_CONTENTS}/Resources/${xib}.nib ${CMAKE_CURRENT_SOURCE_DIR}/gui-osx/${xib}.xib COMMAND rm ${BUNDLE_CONTENTS}/Resources/${xib}.xib COMMENT "Compiling ${CMAKE_CURRENT_SOURCE_DIR}/gui-osx/${xib}.xib") endforeach() # When building for Windows, add specific sources + method to compile .rc files elseif(WIN32) # Set a default rc compiler if it was not defined yet if(NOT CMAKE_RC_COMPILER) set(CMAKE_RC_COMPILER windres) endif(NOT CMAKE_RC_COMPILER) ENABLE_LANGUAGE(RC) set(CMAKE_RC_COMPILE_OBJECT " -Ocoff -o ") set_source_files_properties(${GUIWIN_RES} PROPERTIES LANGUAGE RC) add_executable(${APP_NAME} ${GUIWIN_RES} ${SOURCES} ${GUIWIN_SOURCES}) #emscripten needs some em-specific linker flags elseif(EMSCRIPTEN) add_executable(${APP_NAME} ${SOURCES}) set_target_properties( ${APP_NAME} PROPERTIES LINK_FLAGS "-O3 \ -fno-exceptions \ -s USE_SDL=2 -s USE_ZLIB=1 -s INITIAL_MEMORY=262144000 -s ASYNCIFY -s -s ASSERTIONS=0 \ -s EXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\"]' \ -s EXPORTED_FUNCTIONS=\"['_main','_Reset_Warm','_Reset_Cold','_Statusbar_UpdateInfo','_Main_UnPauseEmulation','_Floppy_InsertDiskIntoDrive','_Floppy_SetDiskFileName','_Floppy_EjectDiskFromDrive','_Configuration_ChangeMemory','_Configuration_ChangeSystem','_Configuration_ChangeTos','_Configuration_ChangeUseHardDiskDirectories','_Configuration_ChangeFastForward','_Configuration_Apply','_IoMem_UnInit','_IoMem_Init']\" \ --preload-file ${CMAKE_BINARY_DIR}/files/@/share/hatari/ \ --pre-js ${CMAKE_SOURCE_DIR}/src/emscripten.js \ --shell-file ${CMAKE_SOURCE_DIR}/src/emscripten_shell.html" ) # Other targets, use default sources else() add_executable(${APP_NAME} ${SOURCES}) endif(ENABLE_OSX_BUNDLE) if(SDL2_OTHER_CFLAGS) target_compile_definitions(${APP_NAME} PRIVATE ${SDL2_OTHER_CFLAGS}) # message(STATUS "Additional CFLAGS of SDL: ${SDL2_OTHER_CFLAGS}") endif(SDL2_OTHER_CFLAGS) target_link_libraries(${APP_NAME} Falcon UaeCpu GuiSdl Floppy Debug ${SDL2_LIBRARIES}) if(Math_FOUND AND NOT APPLE) target_link_libraries(${APP_NAME} ${MATH_LIBRARY}) endif() if(Readline_FOUND) target_link_libraries(${APP_NAME} ${READLINE_LIBRARY}) endif(Readline_FOUND) if(ZLIB_FOUND) target_link_libraries(${APP_NAME} ${ZLIB_LIBRARY}) endif(ZLIB_FOUND) if(PNG_FOUND) target_link_libraries(${APP_NAME} ${PNG_LIBRARY}) endif(PNG_FOUND) if(X11_FOUND) target_link_libraries(${APP_NAME} ${X11_LIBRARIES}) endif(X11_FOUND) if(PortMidi_FOUND) target_link_libraries(${APP_NAME} ${PORTMIDI_LIBRARY}) endif(PortMidi_FOUND) if(CapsImage_FOUND) target_link_libraries(${APP_NAME} ${CAPSIMAGE_LIBRARY}) endif(CapsImage_FOUND) if(Udev_FOUND) target_link_libraries(${APP_NAME} ${UDEV_LIBRARY}) endif(Udev_FOUND) if(Capstone_FOUND) target_link_libraries(${APP_NAME} ${CAPSTONE_LIBRARY}) endif(Capstone_FOUND) if(APPLE) target_link_libraries(${APP_NAME} "-framework Cocoa") endif() if(WIN32) # Needed for socket() on Windows target_link_libraries(${APP_NAME} ws2_32) endif(WIN32) if(ENABLE_OSX_BUNDLE) install(TARGETS ${APP_NAME} BUNDLE DESTINATION /Applications) else() install(TARGETS ${APP_NAME} RUNTIME DESTINATION ${BINDIR}) install(FILES hatari-icon.bmp DESTINATION ${DATADIR}) if(TOS_IMG_FILE) install(FILES ${TOS_IMG_FILE} DESTINATION ${DATADIR}) endif(TOS_IMG_FILE) endif(ENABLE_OSX_BUNDLE) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/acia.c000066400000000000000000001115741504763705000224020ustar00rootroot00000000000000/* Hatari - acia.c Copyright (C) 2012 by Nicolas Pomarède This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. MC6850 ACIA emulation. */ const char ACIA_fileid[] = "Hatari acia.c"; /* 2012/09/28 [NP] Start of the full rewrite of the MC6850 ACIA emulation, using the official */ /* datasheets for maximum accuracy, as well as bit level serial transfers (start, */ /* stop and parity bits). */ /* 2012/12/21 [NP] Add accurate cycles delays when accessing an ACIA register, taking E Clock */ /* into account. */ /* 2013/04/24 [NP] Remove INTERRUPT_ACIA_MFP used to add a 4 cycle delay when IRQ is set, as this */ /* delay is now correctly handled directly in the MFP since 2013/03/01. */ /* 6850 ACIA (Asynchronous Communications Interface Adapter) References : - MC6850 datasheet by Motorola (DS9493R4, 1985) - A6850 datasheet by Altera (A-DS-A6850-01, 1996) (nearly identical component) Others references : - MAME's 6850acia.c for RTS, CTS and DCD behaviour ----------- VSS -| 1 24 |- CTS(INV) : connected to GND RX DATA : connected to MIDI/IKBD -| 2 23 |- DCD(INV) : connected to GND RX CLK : connected to 500 kHz -| 3 22 |- D0 TX CLK : connected to 500 kHz -| 4 21 |- D1 RTS(INV) : not connected -| 5 20 |- D2 TX DATA : connected to MIDI/IKBD -| 6 19 |- D3 IRQ(INV) : connected to MFP GPIP4 -| 7 18 |- D4 CS0 -| 8 17 |- D5 CS2(INV) -| 9 16 |- D6 CS1 -| 10 15 |- D7 RS -| 11 14 |- E VCC -| 12 13 |- R/W(INV) ----------- Pins : VSS RX DATA Receive Data RX CLK Receive Clock TX CLK Transmitter Clock RTS Request To Send TX DATA Transmitter Data IRQ Interrupt Request CS 0,1,2 Chip Select RS Register Select Vcc Voltage R/W Read/Write E Enable D0-D7 Data DCD Data Carrier Detect CTS Clear To Send Registers : 0xfffc00.b Keyboard ACIA Control (write) / Status(read) 0xfffc02.b Keyboard ACIA Data 0xfffc04.b MIDI ACIA Control (write) / Status(read) 0xfffc06.b MIDI ACIA Data Control Register (0xfffc00 write) : Bits 0,1 - These bits determine by which factor the transmitter and receiver clock will be divided. These bits also are joined with a master reset function. The 6850 has no separate reset line, so it must be accomplished though software. 0 0 RXCLK/TXCLK without division 0 1 RXCLK/TXCLK by 16 (MIDI) 1 0 RXCLK/TXCLK by 64 (Keyboard) 1 1 Master RESET Bits 2,3,4 - These so-called Word Select bits tell whether 7 or 8 data-bits are involved; whether 1 or 2 stop-bits are transferred; and the type of parity Bits 5,6 - These Transmitter Control bits set the RTS output pin, and allow or prevent an interrupt through the ACIA when the send register is emptied. Also, BREAK signals can be sent over the serial output by this line. A BREAK signal is nothing more than a long sequence of null bits 0 0 RTS low, transmitter IRQ disabled 0 1 RTS low, transmitter IRQ enabled 1 0 RTS high, transmitter IRQ disabled 1 1 RTS low, transmitter IRQ disabled, BREAK sent Bit 7 - The Receiver Interrupt Enable bit determines whether the receiver interrupt will be on. An interrupt can be caused by the DCD line changing from low to high, or by the receiver data buffer filling. Besides that, an interrupt can occur from an OVERRUN (a received character isn't properly read from the processor). 0 Interrupt disabled 1 Interrupt enabled Status Register (0xfffc00 read) : Bit 0 - When this bit is high, the RX data register is full. The byte must be read before a new character is received (otherwise an OVERRUN happens) Bit 1 - This bit reflects the status of the TX data buffer. An empty register set the bit. Bit 2 - A low-high change in pin DCD sets bit 2. If the receiver interrupt is allowable, the IRQ is cancelled. The bit is cleared when the status register and the receiver register are read. This also cancels the IRQ. Bit 2 register remains highis the signal on the DCD pin is still high; Bit 2 register low if DCD becomes low. Bit 3 - This line shows the status of CTS. This signal cannot be altered by a master reset, or by ACIA programming. Bit 4 - Shows 'Frame Errors'. Frame errors are when no stop-bit is recognized in receiver switching. It can be set with every new character. Bit 5 - This bit display the previously mentioned OVERRUN condition. Bit 5 is reset when the RX buffer is read. Bit 6 - This bit recognizes whether the parity of a received character is correct. The bit is set on an error. Bit 7 - This signals the state of the IRQ pins; this bit make it possible to switch several IRQ lines on one interrupt input. In cases where an interrupt is program-generated, bit 7 can tell which IC cut off the interrupt. ST ACIA : CTS,DCD and RTS are not connected The keyboard ACIA addresses are 0xfffc000 and 0xfffc02. The MIDI ACIA addresses are 0xfffc004 and 0xfffc06. Default keyboard parameters are : 8-bit word, 1 stopbit, no parity, 7812.5 baud; 500KHz/64 (keyboard clock div) Default MIDI parameters are as above but : 31250 baud; 500KHz/16 (MIDI clock div) CPU cycles in the ST : When accessing an ACIA register, an additional delay will be added to the usual number of cycles for this CPU instruction. This delay is made of 2 parts (for a 68000 at 8 MHz) : - a fixed delay of 6 cycles. - a variable delay of 0 to 8 cycles to synchronise with the E Clock. Examples for some common instructions measured on a real 520 STF (with a0=$fffffc00 and 'n' the delay for E Clock) : move.b (a0),d2 : 14 cycles = 8 + 6 + n move.w (a0),d2 : 14 cycles = 8 + 6 + n move.l (a0),d2 : 24 cycles = 12 + 6 + 6 + n movep.w (a0),d2 : 28 cycles = 16 + 6 + 6 + n movep.l (a0),d2 : 48 cycles = 24 + 6 + 6 + 6 + 6 + n (on ST, those values might be rounded to the next multiple of 4 cycles) When the ACIA's IRQ signal goes low, the resulting bit in the MFP is visible to the CPU only 4 cycles later. From the hardware point of view, the ACIA's irq signal is immediately propagated to the MFP, but the MFP will then add a 4 cycle delay before generating a 68000 interrupt. */ /*-----------------------------------------------------------------------*/ #include "main.h" #include "log.h" #include "memorySnapShot.h" #include "configuration.h" #include "acia.h" #include "m68000.h" #include "cycInt.h" #include "ioMem.h" #include "clocks_timings.h" #include "mfp.h" #include "video.h" #define ACIA_SR_BIT_RDRF 0x01 /* Receive Data Register Full */ #define ACIA_SR_BIT_TDRE 0x02 /* Transmit Data Register Empty */ #define ACIA_SR_BIT_DCD 0x04 /* Data Carrier Detect */ #define ACIA_SR_BIT_CTS 0x08 /* Clear To Send */ #define ACIA_SR_BIT_FE 0x10 /* Framing Error */ #define ACIA_SR_BIT_OVRN 0x20 /* Receiver Overrun */ #define ACIA_SR_BIT_PE 0x40 /* Parity Error */ #define ACIA_SR_BIT_IRQ 0x80 /* IRQ */ #define ACIA_CR_COUNTER_DIVIDE( CR ) ( CR & 0x03 ) /* CR1 + CR0 : 0x03 causes a master reset */ #define ACIA_CR_WORD_SELECT( CR ) ( ( CR >> 2 ) & 0x07 ) /* CR4 + CR3 + CR2 : size, parity, stop bits */ #define ACIA_CR_TRANSMITTER_CONTROL( CR ) ( ( CR >> 5 ) & 0x03 ) /* CR6 + CR5 : RTS + IRQ on send */ #define ACIA_CR_RECEIVE_INTERRUPT_ENABLE( CR ) ( ( CR >> 7 ) & 0x01 ) /* CR7 : Receive interrupt enable */ static const int ACIA_Counter_Divide[3] = { 1 , 16 , 64 }; /* Used to divide txclock/rxclock to get the correct baud rate */ /* Data size, parity and stop bits used for the transfer depending on CR_WORD_SELECT */ enum { ACIA_PARITY_NONE , ACIA_PARITY_EVEN , ACIA_PARITY_ODD }; static struct { int DataBits; /* 7 or 8 */ int Parity; /* EVEN or ODD or NONE */ int StopBits; /* 1 or 2 */ } ACIA_Serial_Params [ 8 ] = { { 7 , ACIA_PARITY_EVEN , 2 }, { 7 , ACIA_PARITY_ODD , 2 }, { 7 , ACIA_PARITY_EVEN , 1 }, { 7 , ACIA_PARITY_ODD , 1 }, { 8 , ACIA_PARITY_NONE , 2 }, { 8 , ACIA_PARITY_NONE , 1 }, { 8 , ACIA_PARITY_EVEN , 1 }, { 8 , ACIA_PARITY_ODD , 1 } }; /* Possible states when handling TX/RX interrupts */ enum { ACIA_STATE_IDLE = 0, ACIA_STATE_DATA_BIT, ACIA_STATE_PARITY_BIT, ACIA_STATE_STOP_BIT }; ACIA_STRUCT ACIA_Array[ ACIA_MAX_NB ]; ACIA_STRUCT *pACIA_IKBD; ACIA_STRUCT *pACIA_MIDI; /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static void ACIA_Init_Pointers ( ACIA_STRUCT *pAllACIA ); static void ACIA_Set_Line_IRQ_MFP ( ACIA_STRUCT *pACIA , int bit ); static uint8_t ACIA_Get_Line_IRQ_MFP ( ACIA_STRUCT *pACIA ); static uint8_t ACIA_Get_Line_CTS_Dummy ( void ); static uint8_t ACIA_Get_Line_DCD_Dummy ( void ); static void ACIA_Set_Line_RTS_Dummy ( int bit ); static void ACIA_Set_Timers_IKBD ( ACIA_STRUCT *pACIA ); static void ACIA_Start_InterruptHandler_IKBD ( ACIA_STRUCT *pACIA , int InternalCycleOffset ); static uint8_t ACIA_MasterReset ( ACIA_STRUCT *pACIA , uint8_t CR ); static void ACIA_UpdateIRQ ( ACIA_STRUCT *pACIA ); static uint8_t ACIA_Read_SR ( ACIA_STRUCT *pACIA ); static void ACIA_Write_CR ( ACIA_STRUCT *pACIA , uint8_t CR ); static uint8_t ACIA_Read_RDR ( ACIA_STRUCT *pACIA ); static void ACIA_Write_TDR ( ACIA_STRUCT *pACIA , uint8_t TDR ); static void ACIA_Prepare_TX ( ACIA_STRUCT *pACIA ); static void ACIA_Prepare_RX ( ACIA_STRUCT *pACIA ); static void ACIA_Clock_TX ( ACIA_STRUCT *pACIA ); static void ACIA_Clock_RX ( ACIA_STRUCT *pACIA ); /*-----------------------------------------------------------------------*/ /** * Init the 2 ACIAs in an Atari ST. * Both ACIAs have a 500 MHZ TX/RX clock. * This is called only once, when the emulator starts. * NOTE : when testing EmuTos on real hardware, it seems the tx/rx is working * after a cold reset (ST switched on), even if Clock_Divider was not initialized yet. * The default behaviour is not described in the ACIA's ref doc, but bits * seem to be transmitted (maybe with errors ?). So we default * to 9600 bauds to avoid a lock if a program uses tx/rx after a reset. */ void ACIA_Init ( ACIA_STRUCT *pAllACIA , uint32_t TX_Clock , uint32_t RX_Clock ) { int i; LOG_TRACE ( TRACE_ACIA, "acia init tx_clock=%d rx_clock=%d\n" , TX_Clock , RX_Clock ); for ( i=0 ; iSet_Timers = ACIA_Set_Timers_IKBD; // pACIA_MIDI->Set_Timers = ACIA_Set_Timers_MIDI; /* Not used for now */ } /*-----------------------------------------------------------------------*/ /** * There's no real hardware reset on the ACIA, but as the Reset_ST() * functions turns off all internal interrupts, we must restart the ACIA's * interrupt after a reset. */ void ACIA_Reset ( ACIA_STRUCT *pAllACIA ) { int i; LOG_TRACE ( TRACE_ACIA, "acia reset\n" ); for ( i=0 ; i 0 ) /* Divider already initialized */ pAllACIA[ i ].Set_Timers ( &(pAllACIA[ i ]) ); /* Restart the timer */ } } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void ACIA_MemorySnapShot_Capture ( bool bSave ) { MemorySnapShot_Store(&ACIA_Array, sizeof(ACIA_Array)); if ( !bSave ) /* If restoring */ ACIA_Init_Pointers ( ACIA_Array ); /* Restore pointers */ } /*-----------------------------------------------------------------------*/ /** * Set or reset the ACIA's IRQ signal. * IRQ signal is inverted (0/low sets irq, 1/high clears irq) * In the ST, the 2 ACIA's IRQ pins are connected to the same MFP input, * so they share the same IRQ bit in GPIP4. */ static void ACIA_Set_Line_IRQ_MFP ( ACIA_STRUCT *pACIA , int bit ) { LOG_TRACE ( TRACE_ACIA, "acia %s set irq line val=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , bit , nVBLs , nHBL ); pACIA->IRQ_Line = bit; if ( bit == 0 ) { /* There's a small delay on a real ST between the point in time * the irq bit is set and the MFP interrupt is triggered - for example * the "V8 music system" demo depends on this behaviour. * This 4 cycle delay is handled in mfp.c */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_LOW ); } else { MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_HIGH ); } } /*-----------------------------------------------------------------------*/ /** * Return the value of the ACIA's IRQ signal. * IRQ signal is inverted (0/low sets irq, 1/high clears irq) */ static uint8_t ACIA_Get_Line_IRQ_MFP ( ACIA_STRUCT *pACIA ) { return pACIA->IRQ_Line; } /*-----------------------------------------------------------------------*/ /** * Read the Clear To Send (CTS) pin * When CTS is high, TDRE should always be set to 0 * Note : this is not connected on an ST, so we always return 0. */ static uint8_t ACIA_Get_Line_CTS_Dummy ( void ) { uint8_t bit; bit = 0; LOG_TRACE ( TRACE_ACIA, "acia get cts=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL ); return bit; } /*-----------------------------------------------------------------------*/ /** * Read the Data Carrier Detect (DCD) pin * Note : this is not connected on an ST, so we always return 0. */ static uint8_t ACIA_Get_Line_DCD_Dummy ( void ) { uint8_t bit; bit = 0; LOG_TRACE ( TRACE_ACIA, "acia get dcd=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL ); return bit; } /*-----------------------------------------------------------------------*/ /** * Set the Request To Send (RTS) pin. * Note : this is not connected on an ST, so we ignore it. */ static void ACIA_Set_Line_RTS_Dummy ( int bit ) { LOG_TRACE ( TRACE_ACIA, "acia set rts val=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Set the required timers to handle RX / TX, depending on the CR_DIVIDE * value. * When CR is changed with a new CR_DIVIDE value, we restart the timers. */ static void ACIA_Set_Timers_IKBD ( struct ACIA *pACIA ) { ACIA_Start_InterruptHandler_IKBD ( pACIA , 0 ); } /*-----------------------------------------------------------------------*/ /** * Set a timer to handle the RX / TX bits at the expected baud rate. * NOTE : on ST, TX_Clock and RX_Clock are the same, so the timer's freq will be * TX_Clock / Divider and we only need one timer interrupt to handle both RX and TX. * This freq should be converted to CPU_CYCLE : 1 ACIA cycle = 16 CPU cycles * (with cpu running at 8 MHz) * InternalCycleOffset allows to compensate for a != 0 value in PendingInterruptCount * to keep a constant baud rate. * TODO : we use a fixed 8 MHz clock to convert cycles for our internal timers * in cycInt.c. This should be replaced some days by using MachineClocks.CPU_Freq. */ static void ACIA_Start_InterruptHandler_IKBD ( ACIA_STRUCT *pACIA , int InternalCycleOffset ) { int Cycles; // Cycles = MachineClocks.CPU_Freq / pACIA->TX_Clock; /* Convert ACIA cycles in CPU cycles */ Cycles = 8021247 / pACIA->TX_Clock; /* Convert ACIA cycles in CPU cycles, for a 8 MHz STF reference */ Cycles *= pACIA->Clock_Divider; LOG_TRACE ( TRACE_ACIA, "acia %s start timer divider=%d cpu_cycles=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->Clock_Divider , Cycles , nVBLs , nHBL ); CycInt_AddRelativeInterruptWithOffset ( Cycles, INT_CPU8_CYCLE, INTERRUPT_ACIA_IKBD , InternalCycleOffset ); } /*-----------------------------------------------------------------------*/ /** * Interrupt called each time a new bit must be sent / received with the IKBD. * This interrupt will be called at freq ( 500 MHz / ACIA_CR_COUNTER_DIVIDE ) * On ST, RX_Clock = TX_Clock = 500 MHz. * We continuously restart the interrupt, taking into account PendingCyclesOver. */ void ACIA_InterruptHandler_IKBD ( void ) { int PendingCyclesOver; /* Number of internal cycles we went over for this timer ( <= 0 ) */ /* Used to restart the next timer and keep a constant baud rate */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ LOG_TRACE ( TRACE_ACIA, "acia ikbd interrupt handler pending_cyc=%d VBL=%d HBL=%d\n" , PendingCyclesOver , nVBLs , nHBL ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); ACIA_Clock_TX ( pACIA_IKBD ); ACIA_Clock_RX ( pACIA_IKBD ); ACIA_Start_InterruptHandler_IKBD ( pACIA_IKBD , -PendingCyclesOver ); /* Compensate for a != 0 value of PendingCyclesOver */ } /*-----------------------------------------------------------------------*/ /** * Interrupt called each time a new bit must be sent / received with the MIDI. * This interrupt will be called at freq ( 500 MHz / ACIA_CR_COUNTER_DIVIDE ) * On ST, RX_Clock = TX_Clock = 500 MHz. */ void ACIA_InterruptHandler_MIDI ( void ) { ACIA_Clock_TX ( pACIA_MIDI ); ACIA_Clock_RX ( pACIA_MIDI ); } /*-----------------------------------------------------------------------*/ /** * - For each access to an ACIA register, a 6 cycles delay is added to the * normal 68000 timing for the current CPU instruction. If the instruction * accesses several registers at once, the delays are cumulated. * - An additional delay will also be added to ensure the 68000 clock and * the E clock are synchronised ; this delay can add between 0 and 8 cycles * to reach the next multiple of 10 cycles. This delay is added only once * per CPU instruction. * These delays are measured for an 8 MHz 68000 CPU. */ void ACIA_AddWaitCycles ( void ) { int cycles; /* Add a default of 6 cycles for each access */ cycles = 6; /* Wait for E clock only if this is the first ACIA access for this instruction */ /* (NOTE : in UAE, movep behaves like several bytes access with different IoAccessBaseAddress, */ /* so only the first movep's access should wait for E Clock) */ if ( ( ( IoAccessInstrCount == 0 ) && ( IoAccessBaseAddress == IoAccessCurrentAddress ) ) || ( IoAccessInstrCount == 1 ) ) /* First access of a movep */ cycles += M68000_WaitEClock (); M68000_WaitState ( cycles ); } /*-----------------------------------------------------------------------*/ /** * Return SR for the IKBD's ACIA (0xfffc00) */ void ACIA_IKBD_Read_SR ( void ) { ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ IoMem[0xfffc00] = ACIA_Read_SR ( pACIA_IKBD ); if (LOG_TRACE_LEVEL(TRACE_ACIA)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("acia %s read fffc00 sr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name , IoMem[0xfffc00], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Return RDR for the IKBD's ACIA (0xfffc02) : receive a byte from the IKBD */ void ACIA_IKBD_Read_RDR ( void ) { ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ IoMem[0xfffc02] = ACIA_Read_RDR ( pACIA_IKBD ); if (LOG_TRACE_LEVEL(TRACE_IKBD_ACIA)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("acia %s read fffc02 rdr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name , IoMem[0xfffc02], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Write to CR in the IKBD's ACIA (0xfffc00) */ void ACIA_IKBD_Write_CR ( void ) { int FrameCycles, HblCounterVideo, LineCycles; ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_IKBD_ACIA, "acia %s write fffc00 cr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name , IoMem[0xfffc00], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); ACIA_Write_CR ( pACIA_IKBD , IoMem[0xfffc00] ); } /*-----------------------------------------------------------------------*/ /** * Write to TDR in the IKBD's ACIA (0xfffc02) : send a byte to the IKBD */ void ACIA_IKBD_Write_TDR ( void ) { int FrameCycles, HblCounterVideo, LineCycles; ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_IKBD_ACIA, "acia %s write fffc02 tdr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name , IoMem[0xfffc02], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); ACIA_Write_TDR ( pACIA_IKBD , IoMem[0xfffc02] ); } /*----------------------------------------------------------------------*/ /* The part below is the real core of the 6850's emulation. */ /* */ /* This core is not correlated to any specific machine. All the specific*/ /* code between the 6850 and the rest of Hatari is called through some */ /* callback functions (see above). */ /*----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/ /** * Reset an ACIA. * There's no RESET pin on the MC6850, so the only way to reset the ACIA * is to set bit 0 an 1 to 0x03 in the CR to force a master reset. * This will clear SR (except CTS and DCD) and halt/initialize both the * receiver and transmitter. * This also returns the new state of the RTS bit, that must be updated * in ACIA_Write_CR. */ static uint8_t ACIA_MasterReset ( ACIA_STRUCT *pACIA , uint8_t CR ) { uint8_t dcd_bit; uint8_t cts_bit; uint8_t rts_bit; LOG_TRACE ( TRACE_ACIA, "acia %s master reset VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL ); dcd_bit = pACIA->Get_Line_DCD (); cts_bit = pACIA->Get_Line_CTS (); pACIA->SR = ACIA_SR_BIT_TDRE | ( dcd_bit << 2 ) | ( cts_bit << 3 ); pACIA->TX_State = ACIA_STATE_IDLE; pACIA->TSR = 0; pACIA->TX_Size = 0; pACIA->TX_SendBrk = 0; pACIA->RX_State = ACIA_STATE_IDLE; pACIA->RSR = 0; pACIA->RX_Size = 0; pACIA->RX_Overrun = 0; /* On Master Reset, IRQ line is high */ /* If it's the 1st reset, RTS should be high, else RTS depends on CR bit 5 and 6 */ pACIA->Set_Line_IRQ ( pACIA , 1 ); /* IRQ line goes high */ if ( pACIA->FirstMasterReset == 1 ) { pACIA->FirstMasterReset = 0; rts_bit = 1; /* RTS line goes high */ } else rts_bit = ( ACIA_CR_TRANSMITTER_CONTROL ( CR ) == 0x02 ) ? 1 : 0; return rts_bit; } /*-----------------------------------------------------------------------*/ /** * Check if the IRQ must be changed in SR. * When there's a change, we must change the IRQ line too. */ static void ACIA_UpdateIRQ ( ACIA_STRUCT *pACIA ) { uint8_t irq_bit_new; irq_bit_new = 0; if ( ACIA_CR_RECEIVE_INTERRUPT_ENABLE ( pACIA->CR ) /* Check for RX causes of interrupt */ && ( ( pACIA->SR & ( ACIA_SR_BIT_RDRF | ACIA_SR_BIT_DCD ) ) || ( pACIA->RX_Overrun ) ) ) irq_bit_new = ACIA_SR_BIT_IRQ; //fprintf(stderr , "acia irq %x %x %x %d\n" , pACIA->CR , pACIA->SR , pACIA->RX_Overrun , irq_bit_new); if ( pACIA->TX_EnableInt /* Check for TX causes of interrupt */ && ( pACIA->SR & ACIA_SR_BIT_TDRE ) && ( ( pACIA->SR & ACIA_SR_BIT_CTS ) == 0 ) ) irq_bit_new = ACIA_SR_BIT_IRQ; /* Update SR and IRQ line if a change happened */ if ( ( pACIA->SR & ACIA_SR_BIT_IRQ ) != irq_bit_new ) { LOG_TRACE ( TRACE_ACIA, "acia %s update irq irq_new=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , irq_bit_new?1:0 , nVBLs , nHBL ); if ( irq_bit_new ) { pACIA->SR |= ACIA_SR_BIT_IRQ; /* Set IRQ bit */ pACIA->Set_Line_IRQ ( pACIA , 0 ); /* IRQ line goes low */ } else { pACIA->SR &= ~ACIA_SR_BIT_IRQ; /* Clear IRQ bit */ pACIA->Set_Line_IRQ ( pACIA , 1 ); /* IRQ line goes high */ } } } /*-----------------------------------------------------------------------*/ /** * Read SR. * Also update CTS ; when CTS is high, TDRE should always be masked to 0. */ static uint8_t ACIA_Read_SR ( ACIA_STRUCT *pACIA ) { uint8_t SR; if ( pACIA->Get_Line_CTS() == 1 ) pACIA->SR |= ACIA_SR_BIT_CTS; else pACIA->SR &= ~ACIA_SR_BIT_CTS; SR = pACIA->SR; pACIA->SR_Read = 1; /* Used in ACIA_Read_RDR to clear Overrun and DCD IRQ */ if ( SR & ACIA_SR_BIT_CTS ) SR &= ~ACIA_SR_BIT_TDRE; /* Inhibit TDRE when CTS is set */ LOG_TRACE ( TRACE_ACIA, "acia %s read sr data=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , SR , nVBLs , nHBL ); return SR; } /*-----------------------------------------------------------------------*/ /** * Write to CR. */ static void ACIA_Write_CR ( ACIA_STRUCT *pACIA , uint8_t CR ) { int Divide; int Force_rts_bit; uint8_t rts_bit=0; LOG_TRACE ( TRACE_ACIA, "acia %s write cr data=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , CR , nVBLs , nHBL ); /* Bit 0 and 1 : Counter Divide */ Divide = ACIA_CR_COUNTER_DIVIDE ( CR ); if ( Divide == 0x03 ) { Force_rts_bit = ACIA_MasterReset ( pACIA , CR ); /* Special behaviour for RTS after a master reset */ } else { if ( ACIA_CR_COUNTER_DIVIDE ( CR ) != ACIA_CR_COUNTER_DIVIDE ( pACIA->CR ) ) { pACIA->Clock_Divider = ACIA_Counter_Divide[ Divide ]; pACIA->Set_Timers ( pACIA ); /* Set a timer at the baud rate computed from Clock_Divider */ } Force_rts_bit = -1; /* Don't force RTS bit, use bit 5/6 in CR */ } /* Bits 2, 3 and 4 : word select */ /* Don't do anything here, see ACIA_Prepare_TX and ACIA_Prepare_RX */ /* Bits 5 and 6 : transmitter control */ pACIA->TX_EnableInt = 0; pACIA->TX_SendBrk = 0; switch ( ACIA_CR_TRANSMITTER_CONTROL ( CR ) ) { case 0x00 : rts_bit = 0; break; case 0x01 : rts_bit = 0; pACIA->TX_EnableInt = 1; break; case 0x02 : rts_bit = 1; break; case 0x03 : rts_bit = 0; pACIA->TX_SendBrk = 1; /* We will send break bit until CR is changed */ break; } if ( Force_rts_bit >= 0 ) rts_bit = Force_rts_bit; /* Use the value from ACIA_MasterReset */ pACIA->Set_Line_RTS ( rts_bit ); /* Bits 7 : receive interrupt enable, see ACIA_UpdateIRQ */ pACIA->CR = CR; ACIA_UpdateIRQ ( pACIA ); } /*-----------------------------------------------------------------------*/ /** * Read RDR. This will clear RDRF and PE. * - OVRN / DCD bits are cleared if SR was read before reading RDR. * - OVRN bit is set only when reading RDR, not when the actual overrun happened * during ACIA_Clock_RX. * - IRQ bit should be updated depending on the new values of BIT_RDRF, * BIT_DCD and BIT_OVRN. */ static uint8_t ACIA_Read_RDR ( ACIA_STRUCT *pACIA ) { pACIA->SR &= ~( ACIA_SR_BIT_RDRF | ACIA_SR_BIT_PE ); /* If we read RDR after reading SR, we clear OVRN / DCD bits */ if ( pACIA->SR_Read == 1 ) { pACIA->SR_Read = 0; pACIA->SR &= ~( ACIA_SR_BIT_DCD | ACIA_SR_BIT_OVRN ); if ( pACIA->Get_Line_DCD () == 1 ) pACIA->SR |= ACIA_SR_BIT_DCD; } if ( pACIA->RX_Overrun ) { pACIA->SR |= ACIA_SR_BIT_OVRN; pACIA->RX_Overrun = 0; } ACIA_UpdateIRQ ( pACIA ); LOG_TRACE ( TRACE_ACIA, "acia %s read rdr data=0x%02x new sr=0x%02x overrun=%s VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RDR , pACIA->SR , ( pACIA->SR & ACIA_SR_BIT_OVRN ) ? "yes" : "no" , nVBLs , nHBL ); return pACIA->RDR; } /*-----------------------------------------------------------------------*/ /** * Write to TDR. * If the TX process is idle, we should not prepare a new transfer * immediately, to ensure that BIT_TDRE remains clear until the next bit * is sent (BIT_TDRE will be set again in ACIA_Clock_TX). */ static void ACIA_Write_TDR ( ACIA_STRUCT *pACIA , uint8_t TDR ) { LOG_TRACE ( TRACE_ACIA, "acia %s write tdr data=0x%02x overwrite=%s tx_state=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , TDR , ( pACIA->SR & ACIA_SR_BIT_TDRE ) ? "no" : "yes" , pACIA->TX_State , nVBLs , nHBL ); pACIA->TDR = TDR; pACIA->SR &= ~ACIA_SR_BIT_TDRE; /* TDR is not empty anymore */ ACIA_UpdateIRQ ( pACIA ); } /*-----------------------------------------------------------------------*/ /** * Prepare a new transfer. Copy TDR to TSR and initialize parity, data size * and stop bits. * Transfer will then start at the next call of ACIA_Clock_TX */ static void ACIA_Prepare_TX ( ACIA_STRUCT *pACIA ) { pACIA->TSR = pACIA->TDR; pACIA->TX_Parity = 0; pACIA->TX_Size = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].DataBits; pACIA->TX_StopBits = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].StopBits; pACIA->SR |= ACIA_SR_BIT_TDRE; /* TDR was copied to TSR. TDR is now empty */ LOG_TRACE ( TRACE_ACIA, "acia %s prepare tx tsr=0x%02x size=%d stop=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->TSR , pACIA->TX_Size , pACIA->TX_StopBits , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Prepare a new reception. Initialize parity, data size and stop bits. */ static void ACIA_Prepare_RX ( ACIA_STRUCT *pACIA ) { pACIA->RSR = 0; pACIA->RX_Parity = 0; pACIA->RX_Size = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].DataBits; pACIA->RX_StopBits = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].StopBits; LOG_TRACE ( TRACE_ACIA, "acia %s prepare rx size=%d stop=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RX_Size , pACIA->RX_StopBits , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Write a new bit on the TX line each time the TX clock expires. * This will send TDR over the serial line, using TSR, with additional * parity and start/stop bits. * We send bit 0 of TSR, then TSR is shifted to the right. */ static void ACIA_Clock_TX ( ACIA_STRUCT *pACIA ) { int StateNext; uint8_t tx_bit; LOG_TRACE ( TRACE_ACIA, "acia %s clock_tx tx_state=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->TX_State , nVBLs , nHBL ); StateNext = -1; switch ( pACIA->TX_State ) { case ACIA_STATE_IDLE : if ( pACIA->TX_SendBrk ) { pACIA->Set_Line_TX ( 0 ); /* Send 1 break bit */ break; } /* If TDR is not empty when we are in idle state, */ /* this means we have a new byte to send */ if ( ( pACIA->SR & ACIA_SR_BIT_TDRE ) == 0 ) ACIA_Prepare_TX ( pACIA ); if ( pACIA->TX_Size == 0 ) /* TSR is empty */ pACIA->Set_Line_TX ( 1 ); /* Send stop bits when idle */ else /* TSR has some new bits to transfer */ { pACIA->Set_Line_TX ( 0 ); /* Send 1 start bit */ StateNext = ACIA_STATE_DATA_BIT; } break; case ACIA_STATE_DATA_BIT : tx_bit = pACIA->TSR & 1; /* New bit to send */ pACIA->Set_Line_TX ( tx_bit ); pACIA->TX_Parity ^= tx_bit; pACIA->TSR >>= 1; pACIA->TX_Size--; if ( pACIA->TX_Size == 0 ) { if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity != ACIA_PARITY_NONE ) StateNext = ACIA_STATE_PARITY_BIT; else StateNext = ACIA_STATE_STOP_BIT; /* No parity */ } break; case ACIA_STATE_PARITY_BIT : if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity == ACIA_PARITY_EVEN ) pACIA->Set_Line_TX ( pACIA->TX_Parity ); else pACIA->Set_Line_TX ( ( ~pACIA->TX_Parity ) & 1 ); StateNext = ACIA_STATE_STOP_BIT; break; case ACIA_STATE_STOP_BIT : pACIA->Set_Line_TX ( 1 ); /* Send 1 stop bit */ pACIA->TX_StopBits--; if ( pACIA->TX_StopBits == 0 ) /* All stop bits were sent : transfer is complete */ { StateNext = ACIA_STATE_IDLE; /* Go to idle state to see if a new TDR need to be sent */ } break; } ACIA_UpdateIRQ ( pACIA ); if ( StateNext >= 0 ) pACIA->TX_State = StateNext; /* Go to a new state */ } /*-----------------------------------------------------------------------*/ /** * Handle a new bit on the RX line each time the RX clock expires. * This will fill RDR with bits received from the serial line, using RSR. * Incoming bits are stored in bit 7 of RSR, then RSR is shifted to the right. */ static void ACIA_Clock_RX ( ACIA_STRUCT *pACIA ) { int StateNext; uint8_t rx_bit; rx_bit = pACIA->Get_Line_RX(); LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx rx_state=%d bit=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RX_State , rx_bit , nVBLs , nHBL ); StateNext = -1; switch ( pACIA->RX_State ) { case ACIA_STATE_IDLE : if ( rx_bit == 0 ) /* Receive 1 start bit */ { ACIA_Prepare_RX ( pACIA ); StateNext = ACIA_STATE_DATA_BIT; } break; /* If no start bit, we stay in idle state */ case ACIA_STATE_DATA_BIT : if ( rx_bit ) pACIA->RSR |= 0x80; pACIA->RX_Parity ^= rx_bit; pACIA->RX_Size--; if ( pACIA->RX_Size > 0 ) /* All bits were not received yet */ { pACIA->RSR >>= 1; } else { // [NP] : MC6850 doc is not very clear "the overrun condition begins at the midpoint of the last bit // of the second character received [...]". Is it the last bit of the data word, or the stop bit ? // It makes more sense to check for overrun after the stop bit, when RSR should be copied to RDR, // because RDR could be read between the last data bit and the stop bit, so RX_Overrun and // ACIA_SR_BIT_OVRN would need to be cancelled. // if ( pACIA->SR & ACIA_SR_BIT_RDRF ) // { // LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx overrun rsr=0x%02x VBL=%d HBL=%d\n" , // pACIA->ACIA_Name , pACIA->RSR , nVBLs , nHBL ); // pACIA->RX_Overrun = 1; /* Bit in SR will be set when reading RDR */ // } if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity != ACIA_PARITY_NONE ) StateNext = ACIA_STATE_PARITY_BIT; else StateNext = ACIA_STATE_STOP_BIT; /* No parity */ } break; case ACIA_STATE_PARITY_BIT : if ( ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity == ACIA_PARITY_EVEN ) && ( pACIA->RX_Parity != rx_bit ) ) pACIA->SR |= ACIA_SR_BIT_PE; else if ( pACIA->RX_Parity == rx_bit ) /* Odd parity */ pACIA->SR |= ACIA_SR_BIT_PE; if ( pACIA->SR & ACIA_SR_BIT_PE ) LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx parity error VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL ); StateNext = ACIA_STATE_STOP_BIT; break; case ACIA_STATE_STOP_BIT : if ( rx_bit == 1 ) /* Wait for 1 or 2 "1" stop bits */ { pACIA->RX_StopBits--; if ( pACIA->RX_StopBits == 0 ) /* All stop bits were received : reception is complete */ { pACIA->SR &= ~ACIA_SR_BIT_FE; if ( ( pACIA->SR & ACIA_SR_BIT_RDRF ) == 0 ) { pACIA->RDR = pACIA->RSR; pACIA->SR |= ACIA_SR_BIT_RDRF; LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx received rdr=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RDR , nVBLs , nHBL ); } else { LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx overrun rsr=0x%02x unread rdr=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RSR , pACIA->RDR , nVBLs , nHBL ); pACIA->RX_Overrun = 1; /* Bit in SR will be set when reading RDR */ } StateNext = ACIA_STATE_IDLE; /* Go to idle state and wait for start bit */ } } else /* Not a valid stop bit */ { LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx framing error VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL ); /* According to the A6850 doc, RSR is copied to RDR in case of a framing error */ /* (Should be the same for the MC6850 ?) */ pACIA->SR |= ACIA_SR_BIT_FE; pACIA->RDR = pACIA->RSR; StateNext = ACIA_STATE_IDLE; /* Go to idle state and wait for start bit */ } break; } ACIA_UpdateIRQ ( pACIA ); if ( StateNext >= 0 ) pACIA->RX_State = StateNext; /* Go to a new state */ } void ACIA_Info(FILE *fp, uint32_t dummy) { fprintf(fp, "Keyboard ACIA:\n"); fprintf(fp, "- Control / status: 0x%02x\n", IoMem[0xfffc00]); fprintf(fp, "- Data: 0x%02x\n", IoMem[0xfffc02]); fprintf(fp, "MIDI ACIA:\n"); fprintf(fp, "- Control / status: 0x%02x\n", IoMem[0xfffc04]); fprintf(fp, "- Data: 0x%02x\n", IoMem[0xfffc06]); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/audio.c000066400000000000000000000206011504763705000225740ustar00rootroot00000000000000/* Hatari - audio.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This file contains the routines which pass the audio data to the SDL library. */ const char Audio_fileid[] = "Hatari audio.c"; #include #include "main.h" #include "audio.h" #include "configuration.h" #include "log.h" #include "sound.h" #include "dmaSnd.h" #include "falcon/crossbar.h" #include "video.h" int nAudioFrequency = 44100; /* Sound playback frequency */ bool bSoundWorking = false; /* Is sound OK */ static volatile bool bPlayingBuffer = false; /* Is playing buffer? */ int SoundBufferSize = 1024 / 4; /* Size of sound buffer (in samples) */ int SdlAudioBufferSize = 0; /* in ms (0 = use default) */ int pulse_swallowing_count = 0; /* Sound disciplined emulation rate controlled by */ /* window comparator and pulse swallowing counter */ /*-----------------------------------------------------------------------*/ /** * SDL audio callback function - copy emulation sound to audio system. */ static void Audio_CallBack(void *userdata, Uint8 *stream, int len) { Sint16 *pBuffer; int i, window, nSamplesPerFrame; pBuffer = (Sint16 *)stream; len = len / 4; // Use length in samples (16 bit stereo), not in bytes /* Adjust emulation rate within +/- 0.58% (10 cents) occasionally, * to synchronize sound. Note that an octave (frequency doubling) * has 12 semitones (12th root of two for a semitone), and that * one semitone has 100 cents (1200th root of two for one cent). * Ten cents are desired, thus, the 120th root of two minus one is * multiplied by 1,000,000 to convert to microseconds, and divided * by nScreenRefreshRate=60 to get a 96 microseconds swallow size. * (2^(10cents/(12semitones*100cents)) - 1) * 10^6 / nScreenRefreshRate * See: main.c - Main_WaitOnVbl() */ //fprintf ( stderr , "audio cb in len=%d gensmpl=%d idx=%d\n" , len , nGeneratedSamples , AudioMixBuffer_pos_read ); pulse_swallowing_count = 0; /* 0 = Unaltered emulation rate */ if (ConfigureParams.Sound.bEnableSoundSync) { /* Sound synchronized emulation */ nSamplesPerFrame = nAudioFrequency/nScreenRefreshRate; window = (nSamplesPerFrame > SoundBufferSize) ? nSamplesPerFrame : SoundBufferSize; /* Window Comparator for SoundBufferSize */ if (nGeneratedSamples < window + (window >> 1)) /* Increase emulation rate to maintain sound synchronization */ pulse_swallowing_count = -5793 / nScreenRefreshRate; else if (nGeneratedSamples > (window << 1) + (window >> 2)) /* Decrease emulation rate to maintain sound synchronization */ pulse_swallowing_count = 5793 / nScreenRefreshRate; /* Otherwise emulation rate is unaltered. */ } if (nGeneratedSamples >= len) { /* Enough samples available: Pass completed buffer to audio system * by write samples into sound buffer and by converting them from * 'signed' to 'unsigned' */ for (i = 0; i < len; i++) { *pBuffer++ = AudioMixBuffer[(AudioMixBuffer_pos_read + i) & AUDIOMIXBUFFER_SIZE_MASK][0]; *pBuffer++ = AudioMixBuffer[(AudioMixBuffer_pos_read + i) & AUDIOMIXBUFFER_SIZE_MASK][1]; } AudioMixBuffer_pos_read += len; nGeneratedSamples -= len; } else /* Not enough samples available: */ { for (i = 0; i < nGeneratedSamples; i++) { *pBuffer++ = AudioMixBuffer[(AudioMixBuffer_pos_read + i) & AUDIOMIXBUFFER_SIZE_MASK][0]; *pBuffer++ = AudioMixBuffer[(AudioMixBuffer_pos_read + i) & AUDIOMIXBUFFER_SIZE_MASK][1]; } /* Clear rest of the buffer to ensure we don't play random bytes instead */ /* of missing samples */ memset(pBuffer, 0, (len - nGeneratedSamples) * 4); AudioMixBuffer_pos_read += nGeneratedSamples; nGeneratedSamples = 0; } AudioMixBuffer_pos_read = AudioMixBuffer_pos_read & AUDIOMIXBUFFER_SIZE_MASK; //fprintf ( stderr , "audio cb out len=%d gensmpl=%d idx=%d\n" , len , nGeneratedSamples , AudioMixBuffer_pos_read ); } /*-----------------------------------------------------------------------*/ /** * Initialize the audio subsystem. Return true if all OK. * We use direct access to the sound buffer, set to a unsigned 8-bit mono stream. */ void Audio_Init(void) { SDL_AudioSpec desiredAudioSpec; /* We fill in the desired SDL audio options here */ /* Is enabled? */ if (!ConfigureParams.Sound.bEnableSound) { /* Stop any sound access */ Log_Printf(LOG_DEBUG, "Sound: Disabled\n"); bSoundWorking = false; return; } /* Init the SDL's audio subsystem: */ if (SDL_WasInit(SDL_INIT_AUDIO) == 0) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { Log_Printf(LOG_WARN, "Could not init audio: %s\n", SDL_GetError() ); bSoundWorking = false; return; } } /* Set up SDL audio: */ desiredAudioSpec.freq = nAudioFrequency; desiredAudioSpec.format = AUDIO_S16SYS; /* 16-Bit signed */ desiredAudioSpec.channels = 2; /* stereo */ desiredAudioSpec.callback = Audio_CallBack; desiredAudioSpec.userdata = NULL; /* In most case, setting samples to 1024 will give an equivalent */ /* sdl sound buffer of ~20-30 ms (depending on freq). */ /* But setting samples to 1024 for all the freq can cause some faulty */ /* OS sound drivers to add an important delay when playing sound at lower freq. */ /* In that case we use SdlAudioBufferSize (in ms) to compute a value */ /* of samples that matches the corresponding freq and buffer size. */ if ( SdlAudioBufferSize == 0 ) /* don't compute "samples", use default value */ desiredAudioSpec.samples = 1024; /* buffer size in samples */ else { int samples = (desiredAudioSpec.freq / 1000) * SdlAudioBufferSize; int power2 = 1; while ( power2 < samples ) /* compute the power of 2 just above samples */ power2 *= 2; desiredAudioSpec.samples = power2; /* number of samples corresponding to the requested SdlAudioBufferSize */ } if (SDL_OpenAudio(&desiredAudioSpec, NULL)) /* Open audio device */ { Log_Printf(LOG_WARN, "Can't use audio: %s\n", SDL_GetError()); bSoundWorking = false; ConfigureParams.Sound.bEnableSound = false; SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } SoundBufferSize = desiredAudioSpec.samples; if (SoundBufferSize > AUDIOMIXBUFFER_SIZE/2) { Log_Printf(LOG_WARN, "Soundbuffer size is too big (%d > %d)!\n", SoundBufferSize, AUDIOMIXBUFFER_SIZE/2); } /* All OK */ bSoundWorking = true; /* And begin */ Audio_EnableAudio(true); } /*-----------------------------------------------------------------------*/ /** * Free audio subsystem */ void Audio_UnInit(void) { if (bSoundWorking) { /* Stop */ Audio_EnableAudio(false); SDL_CloseAudio(); bSoundWorking = false; } } /*-----------------------------------------------------------------------*/ /** * Lock the audio sub system so that the callback function will not be called. */ void Audio_Lock(void) { SDL_LockAudio(); } /*-----------------------------------------------------------------------*/ /** * Unlock the audio sub system so that the callback function will be called again. */ void Audio_Unlock(void) { SDL_UnlockAudio(); } /*-----------------------------------------------------------------------*/ /** * Set audio playback frequency variable, pass as PLAYBACK_xxxx */ void Audio_SetOutputAudioFreq(int nNewFrequency) { /* Do not reset sound system if nothing has changed! */ if (nNewFrequency != nAudioFrequency) { /* Set new frequency */ nAudioFrequency = nNewFrequency; if (Config_IsMachineFalcon()) { /* Compute Ratio between host computer sound frequency and Hatari's sound frequency. */ Crossbar_Compute_Ratio(); } else if (!Config_IsMachineST()) { /* Adapt LMC filters to this new frequency */ DmaSnd_Init_Bass_and_Treble_Tables(); } /* Re-open SDL audio interface if necessary: */ if (bSoundWorking) { Audio_UnInit(); Audio_Init(); } } /* Apply YM2149 C10 low pass filter ? (except if forced to NONE) */ if ( YM2149_LPF_Filter != YM2149_LPF_FILTER_NONE ) { if ( Config_IsMachineST() && nAudioFrequency >= 40000 ) YM2149_LPF_Filter = YM2149_LPF_FILTER_LPF_STF; else YM2149_LPF_Filter = YM2149_LPF_FILTER_PWM; } } /*-----------------------------------------------------------------------*/ /** * Start/Stop sound buffer */ void Audio_EnableAudio(bool bEnable) { if (bEnable && !bPlayingBuffer) { /* Start playing */ SDL_PauseAudio(false); bPlayingBuffer = true; } else if (!bEnable && bPlayingBuffer) { /* Stop from playing */ SDL_PauseAudio(true); bPlayingBuffer = false; } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/avi_record.c000066400000000000000000001466161504763705000236270ustar00rootroot00000000000000/* Hatari - avi_record.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. AVI File recording This allows Hatari to record a video file, with both video and audio streams, at full frame rate. Video frames are saved using the current video frequency of the emulated machine (50 Hz, 60 Hz, 70 Hz, ...). Frames can be stored using different codecs. So far, supported codecs are : - BMP : uncompressed RGB images. Very fast to save, very few cpu needed but requires a lot of disk bandwidth and a lot of space. - PNG : compressed RGB images. Depending on the compression level, this can require more cpu and could slow down Hatari. As compressed images are much smaller than BMP images, this will require less space on disk and much less disk bandwidth. Compression levels 3 or 4 give good tradeoff between cpu usage and file size and should not slow down Hatari with recent computers. PNG compression will often give a x20 ratio when compared to BMP and should be used if you have a powerful enough cpu. Sound is saved as 16 bits pcm stereo, using the current Hatari sound output frequency. For best accuracy, sound frequency should be a multiple of the video frequency (to get an integer number of samples per frame) ; this means 44.1 or 48 kHz are the best choices for 50/60 Hz video. The AVI file is divided into multiple chunks. Hatari will save one video stream and one audio stream, so the overall structure of the file is the following : Previously, Hatari was limited to a standard AVI file < 4GB (using 32 bit offsets). Since 08/2017, we support the Open DML AVI file format extension, which allows AVI files of any size (using 64 bits offsets). In that case, the AVI file is divided in several RIFF chunks. The 1st chunk is a standard RIFF AVI chunk, the next ones are RIFF AVIX extension chunks. Each RIFF chunk will contain 1 movi chunk and each movi chunk will contain 2 indexes (video and audio). All the movi's indexes are then indexed in a super index stored in the main RIFF AVI file header. RIFF AVI LIST hdrl avih LIST strl strh (vids) strf indx LIST strl strh (auds) strf indx LIST odml dmlh LIST INFO LIST movi 00db 01wb ... ix00 ix01 RIFF AVIX LIST movi 00db 01wb ... ix00 ix01 ... */ const char AVIRecord_fileid[] = "Hatari avi_record.c"; #include #include #include #include /* For off_t */ #include "main.h" #include "version.h" #include "audio.h" #include "configuration.h" #include "clocks_timings.h" #include "file.h" #include "log.h" #include "screen.h" #include "screenSnapShot.h" #include "sound.h" #include "statusbar.h" #include "video.h" #include "avi_record.h" /* after above that brings in config.h */ #if HAVE_LIBPNG #include #endif #include "pixel_convert.h" /* inline functions */ typedef struct { uint8_t ChunkName[4]; /* '00db', '00dc', '01wb', 'ix00' , 'ix01' */ uint8_t ChunkSize[4]; } AVI_CHUNK; typedef struct { uint8_t identifier[4]; /* '00db', '00dc', '01wb', 'ix00' , 'ix01' */ uint8_t flags[4]; uint8_t offset[4]; uint8_t length[4]; } AVI_CHUNK_INDEX; typedef struct { uint8_t ChunkName[4]; /* 'strh' */ uint8_t ChunkSize[4]; uint8_t stream_type[4]; /* 'vids' or 'auds' */ uint8_t stream_handler[4]; uint8_t flags[4]; uint8_t priority[2]; uint8_t language[2]; uint8_t initial_frames[4]; uint8_t time_scale[4]; uint8_t data_rate[4]; uint8_t start_time[4]; uint8_t data_length[4]; uint8_t buffer_size[4]; uint8_t quality[4]; uint8_t sample_size[4]; uint8_t dest_left[2]; uint8_t dest_top[2]; uint8_t dest_right[2]; uint8_t dest_bottom[2]; } AVI_STREAM_HEADER; #define AVI_SUPER_INDEX_SIZE 256 /* Up to 256 entries in a super index */ #define AVI_INDEX_OF_INDEXES 0x00 /* Possibles values for index_type */ #define AVI_INDEX_OF_CHUNKS 0x01 typedef struct { uint8_t offset[8]; /* 64 bit offset in avi file */ uint8_t size[4]; uint8_t duration[4]; } AVI_STREAM_SUPER_INDEX_ENTRY; typedef struct { uint8_t ChunkName[4]; /* 'indx' */ uint8_t ChunkSize[4]; uint8_t longs_per_entry[2]; /* 4 */ uint8_t index_sub_type; /* 0 */ uint8_t index_type; /* must be AVI_INDEX_OF_INDEXES */ uint8_t entries_in_use[4]; uint8_t chunk_id[4]; /* '00db', '00dc', '01wb' */ uint8_t reserved[12]; AVI_STREAM_SUPER_INDEX_ENTRY index[AVI_SUPER_INDEX_SIZE]; } AVI_STREAM_SUPER_INDEX; typedef struct { uint8_t offset[4]; /* 32 bit offset in current 'movi' chunk */ uint8_t size[4]; } AVI_STREAM_INDEX_ENTRY; typedef struct { uint8_t ChunkName[4]; /* 'ix00', 'ix01' */ uint8_t ChunkSize[4]; uint8_t longs_per_entry[2]; /* 2 */ uint8_t index_sub_type; /* must be 0 */ uint8_t index_type; /* must be AVI_INDEX_OF_CHUNKS */ uint8_t entries_in_use[4]; uint8_t chunk_id[4]; /* '00db', '00dc', '01wb' */ uint8_t base_offset[8]; /* all offsets in index array are relative to this */ uint8_t reserved[4]; // AVI_STREAM_INDEX_ENTRY *index; /* array size is dynamic, don't include it here */ } AVI_STREAM_INDEX; typedef struct { uint8_t ChunkName[4]; /* 'strf' */ uint8_t ChunkSize[4]; uint8_t size[4]; uint8_t width[4]; uint8_t height[4]; uint8_t planes[2]; uint8_t bit_count[2]; uint8_t compression[4]; uint8_t size_image[4]; uint8_t xpels_meter[4]; uint8_t ypels_meter[4]; uint8_t clr_used[4]; uint8_t clr_important[4]; } AVI_STREAM_FORMAT_VIDS; typedef struct { uint8_t ChunkName[4]; /* 'LIST' */ uint8_t ChunkSize[4]; uint8_t Name[4]; /* 'strl' */ AVI_STREAM_HEADER Header; /* 'strh' */ AVI_STREAM_FORMAT_VIDS Format; /* 'strf' */ AVI_STREAM_SUPER_INDEX SuperIndex; /* 'indx' */ } AVI_STREAM_LIST_VIDS; typedef struct { uint8_t ChunkName[4]; /* 'strf' */ uint8_t ChunkSize[4]; uint8_t codec[2]; uint8_t channels[2]; uint8_t sample_rate[4]; uint8_t bit_rate[4]; uint8_t block_align[2]; uint8_t bits_per_sample[2]; uint8_t ext_size[2]; } AVI_STREAM_FORMAT_AUDS; typedef struct { uint8_t ChunkName[4]; /* 'LIST' */ uint8_t ChunkSize[4]; uint8_t Name[4]; /* 'strl' */ AVI_STREAM_HEADER Header; /* 'strh' */ AVI_STREAM_FORMAT_AUDS Format; /* 'strf' */ AVI_STREAM_SUPER_INDEX SuperIndex; /* 'indx' */ } AVI_STREAM_LIST_AUDS; typedef struct { uint8_t ChunkName[4]; /* 'avih' */ uint8_t ChunkSize[4]; uint8_t microsec_per_frame[4]; uint8_t max_bytes_per_second[4]; uint8_t padding_granularity[4]; uint8_t flags[4]; uint8_t total_frames[4]; /* total number of frames in the 1st 'movi' chunk */ uint8_t init_frame[4]; uint8_t nb_streams[4]; uint8_t buffer_size[4]; uint8_t width[4]; uint8_t height[4]; uint8_t scale[4]; uint8_t rate[4]; uint8_t start[4]; uint8_t length[4]; } AVI_STREAM_AVIH; typedef struct { uint8_t ChunkName[4]; /* 'LIST' */ uint8_t ChunkSize[4]; uint8_t Name[4]; /* 'hdrl' */ AVI_STREAM_AVIH Header; } AVI_STREAM_LIST_AVIH; typedef struct { uint8_t ChunkName[4]; /* 'dmlh' */ uint8_t ChunkSize[4]; uint8_t total_frames[4]; /* total number of frames in the whole avi file */ uint8_t reserved[244]; } AVI_STREAM_DMLH; typedef struct { uint8_t ChunkName[4]; /* 'LIST' */ uint8_t ChunkSize[4]; uint8_t Name[4]; /* 'odml' */ AVI_STREAM_DMLH Header; } AVI_STREAM_LIST_ODML; typedef struct { uint8_t ChunkName[4]; /* 'ISFT' (software used) */ uint8_t ChunkSize[4]; // uint8_t Text[2]; /* Text's size should be multiple of 2 (including '\0') */ } AVI_STREAM_INFO; typedef struct { uint8_t ChunkName[4]; /* 'LIST' */ uint8_t ChunkSize[4]; uint8_t Name[4]; /* 'INFO' */ AVI_STREAM_INFO Info; } AVI_STREAM_LIST_INFO; typedef struct { uint8_t ChunkName[4]; /* 'LIST' */ uint8_t ChunkSize[4]; uint8_t Name[4]; /* 'movi' */ } AVI_STREAM_LIST_MOVI; typedef struct { uint8_t signature[4]; /* 'RIFF' */ uint8_t filesize[4]; uint8_t type[4]; /* 'AVI ' */ } RIFF_HEADER; typedef struct { RIFF_HEADER RiffHeader; AVI_STREAM_LIST_AVIH AviHeader; AVI_STREAM_LIST_VIDS VideoStream; AVI_STREAM_LIST_AUDS AudioStream; AVI_STREAM_LIST_ODML Odml; } AVI_FILE_HEADER; #define AUDIO_STREAM_WAVE_FORMAT_PCM 0x0001 #define VIDEO_STREAM_RGB 0x00000000 /* fourcc for BMP video frames */ #define VIDEO_STREAM_PNG "MPNG" /* fourcc for PNG video frames */ #define AVIF_HASINDEX 0x00000010 /* index at the end of the file */ #define AVIF_ISINTERLEAVED 0x00000100 /* data are interleaved */ #define AVIF_TRUSTCKTYPE 0x00000800 /* trust chunk type */ #define AVI_FRAME_INDEX_ALLOC_SIZE 50000 /* How many more entries to alloc each time pAviFrameIndex is full */ /* We use 50000 (~800 KB) at a time to avoid allocating too often */ typedef struct { uint32_t VideoFrame_Pos; uint32_t VideoFrame_Length; uint32_t AudioFrame_Pos; uint32_t AudioFrame_Length; } RECORD_AVI_FRAME_INDEX; typedef struct { /* Input params to start recording */ int VideoCodec; int VideoCodecCompressionLevel; /* 0-9 for png compression */ SDL_Surface *Surface; int CropLeft; int CropRight; int CropTop; int CropBottom; int Fps; /* Fps << 24 */ int Fps_scale; /* 1 << 24 */ int AudioCodec; int AudioFreq; /* Internal data used by the avi recorder */ int Width; int Height; int BitCount; FILE *FileOut; /* file to write to */ int TotalVideoFrames; /* number of recorded video frames */ int TotalAudioFrames; /* number of recorded audio frames */ int TotalAudioSamples; /* number of recorded audio samples */ off_t RiffChunkPosStart; /* as returned by ftello() */ off_t MoviChunkPosStart; int MoviChunkCount; /* current 'movi' chunk nbr (0..n) */ off_t VideoFrames_Base_Offset; /* for video indexes */ off_t AudioFrames_Base_Offset; /* for audio indexes */ off_t SuperIndexChunk_Video_Pos; off_t SuperIndexChunk_Audio_Pos; /* Internal video/audio index, written to file at the end of each 'movi' chunk */ RECORD_AVI_FRAME_INDEX *pAviFrameIndex; /* array of max AviFrameIndex_AllocSize entries */ int AviFrameIndex_AllocSize; /* Number of elements allocated in *pAviFrameIndex */ int AviFrameIndex_Count; /* Number of elements used in *pAviFrameIndex (must be >= 8; *p = val & 0xff; } static void Avi_StoreU32 ( uint8_t *p , uint32_t val ) { *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p = val & 0xff; } static void Avi_StoreU64 ( uint8_t *p , uint64_t val ) { *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p++ = val & 0xff; val >>= 8; *p = val & 0xff; } static void Avi_Store4cc ( uint8_t *p , const char *text ) { memcpy ( p , text , 4 ); } /*-----------------------------------------------------------------------*/ /** * Check if our internal index array is full or not (to add new index frames) * If the array is full, we extend it by allocating AVI_FRAME_INDEX_ALLOC_SIZE * new entries in the current array. */ static bool Avi_FrameIndex_GrowIfNeeded ( RECORD_AVI_PARAMS *pAviParams ) { void *mem; if ( pAviParams->pAviFrameIndex == NULL ) /* Nothing allocated so far */ { mem = malloc ( sizeof ( RECORD_AVI_FRAME_INDEX ) * AVI_FRAME_INDEX_ALLOC_SIZE ); if ( mem == NULL ) return false; pAviParams->AviFrameIndex_AllocSize = AVI_FRAME_INDEX_ALLOC_SIZE; pAviParams->AviFrameIndex_Count = 0; } else if ( pAviParams->AviFrameIndex_Count == pAviParams->AviFrameIndex_AllocSize ) /* Grow an existing array */ { mem = realloc ( pAviParams->pAviFrameIndex , sizeof ( RECORD_AVI_FRAME_INDEX ) * ( pAviParams->AviFrameIndex_AllocSize + AVI_FRAME_INDEX_ALLOC_SIZE ) ); if ( mem == NULL ) return false; pAviParams->AviFrameIndex_AllocSize += AVI_FRAME_INDEX_ALLOC_SIZE; } else return true; /* Enough space for now */ pAviParams->pAviFrameIndex = mem; //fprintf ( stderr , "avi_grow2 max=%d cur=%d\n" , pAviParams->AviFrameIndex_AllocSize , pAviParams->AviFrameIndex_Count ); return true; } /*-----------------------------------------------------------------------*/ /** * Free our internal index array */ static bool Avi_FrameIndex_Free ( RECORD_AVI_PARAMS *pAviParams ) { if ( pAviParams->pAviFrameIndex != NULL ) free ( pAviParams->pAviFrameIndex ); return true; } /*-----------------------------------------------------------------------*/ /** * Store the position / length of a frame in our internal index array * If 'type' = 0, we store a video frame, else we store an audio frame * If the last video frame exceed AVI_MOVI_CHUNK_MAX_SIZE, we create a new * 'movi' chunk to handle avi files > 4GB */ static bool Avi_FrameIndex_Add ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader , int type , off_t Frame_Pos , int Frame_Length ) { //fprintf ( stderr , "avi_add type=%d pos=%ld length=%d count=%d %d %d\n" , type , Frame_Pos , Frame_Length , pAviParams->AviFrameIndex_Count , pAviParams->TotalVideoFrames , pAviParams->TotalAudioFrames ); if ( Avi_FrameIndex_GrowIfNeeded ( pAviParams ) == false ) return false; if ( type == 0 ) /* Video frame */ { if ( pAviParams->AviFrameIndex_Count == 0 ) /* The 1st frame will be the base offset for all entries in the index */ pAviParams->VideoFrames_Base_Offset = Frame_Pos; pAviParams->pAviFrameIndex[ pAviParams->AviFrameIndex_Count ].VideoFrame_Pos = (uint32_t)(Frame_Pos - pAviParams->VideoFrames_Base_Offset ); pAviParams->pAviFrameIndex[ pAviParams->AviFrameIndex_Count ].VideoFrame_Length = Frame_Length; } else /* Audio frame */ { if ( pAviParams->AviFrameIndex_Count == 0 ) /* The 1st frame will be the base offset for all entries in the index */ pAviParams->AudioFrames_Base_Offset = Frame_Pos; pAviParams->pAviFrameIndex[ pAviParams->AviFrameIndex_Count ].AudioFrame_Pos = (uint32_t)(Frame_Pos - pAviParams->AudioFrames_Base_Offset ); pAviParams->pAviFrameIndex[ pAviParams->AviFrameIndex_Count ].AudioFrame_Length = Frame_Length; } /* If positions were stored for these audio and video frames, increment index counter for next frames */ if ( pAviParams->TotalVideoFrames == pAviParams->TotalAudioFrames ) { pAviParams->AviFrameIndex_Count++; /* If we exceed the size of a 'movi' chunk with the video frame we just added to the index, */ /* we "close" it and we create a new 'movi' chunk */ if ( pAviParams->pAviFrameIndex[ pAviParams->AviFrameIndex_Count - 1 ].VideoFrame_Pos > AVI_MOVI_CHUNK_MAX_SIZE ) return Avi_CreateNewMoviChunk ( pAviParams , pAviFileHeader ); } return true; } /*-----------------------------------------------------------------------*/ /** * Write one index (video or audio) * If 'type' = 0, we write a video index 'ix00', else we write an audio index 'ix01'. * We return the value for Position, Size and Duration that must be stored in the corresponding super index entry * - for video super index, duration = entries_in_use in the video index (=AviFrameIndex_Count) * - for audio super index, duration = sum of all AudioFrame_Length */ static bool Avi_WriteMoviIndex ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader , int type , off_t *pPosition , int *pSize , int *pDuration ) { AVI_STREAM_INDEX IndexChunk; int IndexChunk_Size; AVI_STREAM_INDEX_ENTRY IndexEntry; int i; //fprintf ( stderr , "avi_write_index type=%d count=%d %d %d\n" , type , pAviParams->AviFrameIndex_Count , pAviParams->TotalVideoFrames , pAviParams->TotalAudioFrames ); memset ( &IndexChunk , 0 , sizeof ( IndexChunk ) ); *pPosition = ftello ( pAviParams->FileOut ); /* Write the 'ix0#' chunk header */ if ( type == 0 ) /* Video index */ { Avi_Store4cc ( IndexChunk.ChunkName , "ix00" ); if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) Avi_Store4cc ( IndexChunk.chunk_id , "00db" ); else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) Avi_Store4cc ( IndexChunk.chunk_id , "00dc" ); Avi_StoreU64 ( IndexChunk.base_offset , pAviParams->VideoFrames_Base_Offset ); *pDuration = pAviParams->AviFrameIndex_Count; /* For video super index, duration=entries_in_use */ } else /* Audio index */ { Avi_Store4cc ( IndexChunk.ChunkName , "ix01" ); if ( pAviParams->AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) Avi_Store4cc ( IndexChunk.chunk_id , "01wb" ); Avi_StoreU64 ( IndexChunk.base_offset , pAviParams->AudioFrames_Base_Offset ); *pDuration = 0; } IndexChunk_Size = sizeof ( AVI_STREAM_INDEX ) + sizeof ( AVI_STREAM_INDEX_ENTRY ) * pAviParams->AviFrameIndex_Count - 8; Avi_StoreU32 ( IndexChunk.ChunkSize , IndexChunk_Size ); *pSize = IndexChunk_Size+8; /* For video super index */ Avi_StoreU16 ( IndexChunk.longs_per_entry , 2 ); Avi_StoreU8 ( &(IndexChunk.index_sub_type) , 0 ); Avi_StoreU8 ( &(IndexChunk.index_type) , AVI_INDEX_OF_CHUNKS ); Avi_StoreU32 ( IndexChunk.entries_in_use , pAviParams->AviFrameIndex_Count ); /* Write the header */ if ( fwrite ( &IndexChunk , sizeof ( AVI_STREAM_INDEX ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_WriteMoviIndex" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write index header" ); return false; } /* Write the index array */ for ( i=0 ; iAviFrameIndex_Count ; i++ ) { if ( type == 0 ) { Avi_StoreU32 ( IndexEntry.offset , pAviParams->pAviFrameIndex[ i ].VideoFrame_Pos ); Avi_StoreU32 ( IndexEntry.size , pAviParams->pAviFrameIndex[ i ].VideoFrame_Length ); } else { Avi_StoreU32 ( IndexEntry.offset , pAviParams->pAviFrameIndex[ i ].AudioFrame_Pos ); Avi_StoreU32 ( IndexEntry.size , pAviParams->pAviFrameIndex[ i ].AudioFrame_Length ); *pDuration += pAviParams->pAviFrameIndex[ i ].AudioFrame_Length; /* For audio super index, duration=sum of all audio frames length */ } if ( fwrite ( &IndexEntry , sizeof ( IndexEntry ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_WriteMoviIndex" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write index entry" ); return false; } } return true; } /*-----------------------------------------------------------------------*/ /** * Write video and audio indexes at the current file position (after the * 'movi' data) and update the 2 super indexes in the avi header. */ static bool Avi_WriteMoviAllIndexes ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader ) { off_t IndexPos; int IndexSize; int IndexDuration; /* Write video index + update super index */ if ( Avi_WriteMoviIndex ( pAviParams , pAviFileHeader , 0 , &IndexPos , &IndexSize , &IndexDuration ) == false ) return false; Avi_StoreU64 ( pAviFileHeader->VideoStream.SuperIndex.index[ pAviParams->MoviChunkCount ].offset , IndexPos ); Avi_StoreU32 ( pAviFileHeader->VideoStream.SuperIndex.index[ pAviParams->MoviChunkCount ].size , IndexSize ); Avi_StoreU32 ( pAviFileHeader->VideoStream.SuperIndex.index[ pAviParams->MoviChunkCount ].duration , IndexDuration ); Avi_StoreU32 ( pAviFileHeader->VideoStream.SuperIndex.entries_in_use , pAviParams->MoviChunkCount + 1 ); /* Write audio index + update super index */ if ( Avi_WriteMoviIndex ( pAviParams , pAviFileHeader , 1 , &IndexPos , &IndexSize , &IndexDuration ) == false ) return false; Avi_StoreU64 ( pAviFileHeader->AudioStream.SuperIndex.index[ pAviParams->MoviChunkCount ].offset , IndexPos ); Avi_StoreU32 ( pAviFileHeader->AudioStream.SuperIndex.index[ pAviParams->MoviChunkCount ].size , IndexSize ); Avi_StoreU32 ( pAviFileHeader->AudioStream.SuperIndex.index[ pAviParams->MoviChunkCount ].duration , IndexDuration ); Avi_StoreU32 ( pAviFileHeader->AudioStream.SuperIndex.entries_in_use , pAviParams->MoviChunkCount + 1 ); return true; } /*-----------------------------------------------------------------------*/ /** * Complete the current 'movi' chunk (when starting a new 'movi' chunk' or * when recording is stopped) */ static bool Avi_CloseMoviChunk ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader ) { off_t Pos_End; uint8_t TempSize[4]; //fprintf ( stderr , "avi_close_movi nb=%d fr=%d\n" , pAviParams->MoviChunkCount , pAviParams->TotalVideoFrames ); /* Write the index chunks just after the 'movi' data */ if ( Avi_WriteMoviAllIndexes ( pAviParams , pAviFileHeader ) == false ) { return false; } Pos_End = ftello ( pAviParams->FileOut ); /* Update the size of the 'movi' chunk (including the indexes) */ Avi_StoreU32 ( TempSize , Pos_End - pAviParams->MoviChunkPosStart - 8 ); if ( fseeko ( pAviParams->FileOut , pAviParams->MoviChunkPosStart+4 , SEEK_SET ) != 0 ) { perror ( "Avi_CloseMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to seek to movi start" ); return false; } if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_CloseMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write movi size" ); return false; } Avi_StoreU32 ( pAviFileHeader->Odml.Header.total_frames , pAviParams->TotalVideoFrames ); /* number of video frames */ /* If an AVI file has more than 1 'movi' chunk (to support >4 GB file), then AVI header */ /* should be updated with only the information of the 1st chunk (to keep a standard non-odml AVI header) */ if ( pAviParams->MoviChunkCount == 0 ) { Avi_StoreU32 ( pAviFileHeader->RiffHeader.filesize , Pos_End - 8 ); /* 32 bits, limited to 4GB */ Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.total_frames , pAviParams->TotalVideoFrames ); /* number of video frames */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_length , pAviParams->TotalVideoFrames ); /* number of video frames */ Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_length , pAviParams->TotalAudioSamples );/* number of audio samples */ } /* For 'riff' / 'movi' chunks 2 ... n */ else { Avi_StoreU32 ( TempSize , (uint32_t)(Pos_End - pAviParams->RiffChunkPosStart - 8 ) ); if ( fseeko ( pAviParams->FileOut , pAviParams->RiffChunkPosStart+4 , SEEK_SET ) != 0 ) { perror ( "Avi_CloseMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to seek to riff start" ); return false; } if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_CloseMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write riff size" ); return false; } } if ( fseeko ( pAviParams->FileOut , 0 , SEEK_END ) != 0 ) { perror ( "Avi_CloseMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to seek to end of file" ); return false; } return true; } /*-----------------------------------------------------------------------*/ /** * Create a new 'movi' chunk. This uses the ODML extended AVIX chunk to * record several 'movi' chunk in a single avi file (allowing to handle * files > 4GB). */ static bool Avi_CreateNewMoviChunk ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader ) { RIFF_HEADER RiffHeader; AVI_STREAM_LIST_MOVI ListMovi; /* Complete current 'movi' chunk + write indexes */ if ( Avi_CloseMoviChunk ( pAviParams , pAviFileHeader ) == false ) { return false; } pAviParams->MoviChunkCount++; pAviParams->AviFrameIndex_Count = 0; //fprintf ( stderr , "avi_create_movi nb=%d fr=%d\n" , pAviParams->MoviChunkCount , pAviParams->TotalVideoFrames ); /* Write a new RIFF / AVIX header */ Avi_Store4cc ( RiffHeader.signature , "RIFF" ); Avi_StoreU32 ( RiffHeader.filesize , 0 ); /* completed when closing this chunk */ Avi_Store4cc ( RiffHeader.type , "AVIX" ); pAviParams->RiffChunkPosStart = ftello ( pAviParams->FileOut ); if ( fwrite ( &RiffHeader , sizeof ( RiffHeader ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_CreateNewMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write next riff header" ); return false; } /* Write a new 'movi' header */ Avi_Store4cc ( ListMovi.ChunkName , "LIST" ); Avi_StoreU32 ( ListMovi.ChunkSize , 0 ); /* completed when closing this chunk */ Avi_Store4cc ( ListMovi.Name , "movi" ); pAviParams->MoviChunkPosStart = ftello ( pAviParams->FileOut ); if ( fwrite ( &ListMovi , sizeof ( ListMovi ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_CreateNewMoviChunk" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write next movi header" ); return false; } return true; } static int Avi_GetBmpSize ( int Width , int Height , int BitCount ) { return ( Width * Height * BitCount / 8 ); /* bytes in one video frame */ } static bool Avi_RecordVideoStream_BMP ( RECORD_AVI_PARAMS *pAviParams ) { AVI_CHUNK Chunk; int SizeImage; uint8_t *pBitmapIn , *pBitmapOut; int y, src_y; int NeedLock; uint8_t *LineBuf = alloca(3 * pAviParams->Width); /* temp buffer to convert to 24-bit BGR format */ assert(pAviParams->Surface->format->BytesPerPixel == 4); SizeImage = Avi_GetBmpSize ( pAviParams->Width , pAviParams->Height , pAviParams->BitCount ); /* Write the video frame header */ Avi_Store4cc ( Chunk.ChunkName , "00db" ); /* stream 0, uncompressed DIB bytes */ Avi_StoreU32 ( Chunk.ChunkSize , SizeImage ); /* max size of RGB image */ if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_RecordVideoStream_BMP" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp frame header" ); return false; } /* Write the video frame data */ NeedLock = SDL_MUSTLOCK( pAviParams->Surface ); for ( y=0 ; yHeight ; y++ ) { if ( NeedLock ) SDL_LockSurface ( pAviParams->Surface ); /* Points to the top left pixel after cropping borders. For BMP * format, frame is stored from bottom to top (origin is in * bottom left corner) and bytes are in BGR order (not RGB) */ src_y = pAviParams->Surface->h - 1 - pAviParams->CropTop - pAviParams->CropBottom; src_y = src_y - (y * (src_y + 1) + pAviParams->Height/2) / pAviParams->Height; pBitmapIn = (uint8_t *)pAviParams->Surface->pixels + pAviParams->Surface->pitch * src_y + pAviParams->CropLeft * pAviParams->Surface->format->BytesPerPixel; pBitmapOut = LineBuf; PixelConvert_32to24Bits_BGR(LineBuf, (uint32_t *)pBitmapIn, pAviParams->Width, pAviParams->Surface); if ( NeedLock ) SDL_UnlockSurface ( pAviParams->Surface ); if ( (int)fwrite ( pBitmapOut , 1 , pAviParams->Width*3 , pAviParams->FileOut ) != pAviParams->Width*3 ) { perror ( "Avi_RecordVideoStream_BMP" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write bmp video frame" ); return false; } } return true; } #if HAVE_LIBPNG static bool Avi_RecordVideoStream_PNG ( RECORD_AVI_PARAMS *pAviParams ) { AVI_CHUNK Chunk; int SizeImage; off_t ChunkPos; uint8_t TempSize[4]; /* Write the video frame header */ ChunkPos = ftello ( pAviParams->FileOut ); Avi_Store4cc ( Chunk.ChunkName , "00dc" ); /* stream 0, compressed DIB bytes */ Avi_StoreU32 ( Chunk.ChunkSize , 0 ); /* size of PNG image (-> completed later) */ if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) goto png_error; /* Write the video frame data */ SizeImage = ScreenSnapShot_SavePNG_ToFile(pAviParams->Surface, pAviParams->Width, pAviParams->Height, pAviParams->FileOut, pAviParams->VideoCodecCompressionLevel , PNG_FILTER_NONE , pAviParams->CropLeft , pAviParams->CropRight , pAviParams->CropTop , pAviParams->CropBottom ); if ( SizeImage <= 0 ) goto png_error; /* Update the size of the video chunk */ Avi_StoreU32 ( TempSize , SizeImage ); if ( fseeko ( pAviParams->FileOut , ChunkPos+4 , SEEK_SET ) != 0 ) goto png_error; if ( fwrite ( TempSize , sizeof ( TempSize ) , 1 , pAviParams->FileOut ) != 1 ) goto png_error; /* Go to the end of the video frame data */ if ( fseeko ( pAviParams->FileOut , 0 , SEEK_END ) != 0 ) goto png_error; return true; png_error: perror ( "Avi_RecordVideoStream_PNG" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write png frame" ); return false; } #endif /* HAVE_LIBPNG */ bool Avi_RecordVideoStream ( void ) { off_t Pos_Start , Pos_End; Pos_Start = ftello ( AviParams.FileOut ); if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) { if ( Avi_RecordVideoStream_BMP ( &AviParams ) == false ) { return false; } } #if HAVE_LIBPNG else if ( AviParams.VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) { if ( Avi_RecordVideoStream_PNG ( &AviParams ) == false ) { return false; } } #endif else { return false; } Pos_End = ftello ( AviParams.FileOut ); AviParams.TotalVideoFrames++; /* Store index for this video frame */ Pos_Start += 8; /* skip header */ if ( Avi_FrameIndex_Add ( &AviParams , &AviFileHeader , 0 , Pos_Start , (uint32_t)( Pos_End - Pos_Start ) ) == false ) return false; if (AviParams.TotalVideoFrames % ( AviParams.Fps / AviParams.Fps_scale ) == 0) { char str[20]; int secs , hours , mins; secs = AviParams.TotalVideoFrames / ( AviParams.Fps / AviParams.Fps_scale ); hours = secs / 3600; mins = ( secs % 3600 ) / 60; secs = secs % 60; snprintf ( str , 20 , "%d:%02d:%02d" , hours , mins , secs ); Main_SetTitle(str); } return true; } static bool Avi_RecordAudioStream_PCM ( RECORD_AVI_PARAMS *pAviParams , int16_t pSamples[][2] , int SampleIndex , int SampleLength ) { AVI_CHUNK Chunk; int16_t sample[2]; int i; int idx; /* Write the audio frame header */ Avi_Store4cc ( Chunk.ChunkName , "01wb" ); /* stream 1, wave bytes */ Avi_StoreU32 ( Chunk.ChunkSize , SampleLength * 4 ); /* 16 bits, stereo -> 4 bytes */ if ( fwrite ( &Chunk , sizeof ( Chunk ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_RecordAudioStream_PCM" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame header" ); return false; } /* Write the audio frame data */ idx = SampleIndex & AUDIOMIXBUFFER_SIZE_MASK; for ( i = 0 ; i < SampleLength; i++ ) { /* Convert sample to little endian */ sample[0] = SDL_SwapLE16 ( pSamples[ idx ][0]); sample[1] = SDL_SwapLE16 ( pSamples[ idx ][1]); idx = ( idx+1 ) & AUDIOMIXBUFFER_SIZE_MASK; /* And store */ if ( fwrite ( &sample , sizeof ( sample ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "Avi_RecordAudioStream_PCM" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write pcm frame" ); return false; } } return true; } bool Avi_RecordAudioStream ( int16_t pSamples[][2] , int SampleIndex , int SampleLength ) { off_t Pos_Start , Pos_End; Pos_Start = ftello ( AviParams.FileOut ); if ( AviParams.AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) { if ( Avi_RecordAudioStream_PCM ( &AviParams , pSamples , SampleIndex , SampleLength ) == false ) { return false; } } else { return false; } Pos_End = ftello ( AviParams.FileOut ); AviParams.TotalAudioFrames++; AviParams.TotalAudioSamples += SampleLength; /* Store index for this audio frame */ Pos_Start += 8; /* skip header */ if ( Avi_FrameIndex_Add ( &AviParams , &AviFileHeader , 1 , Pos_Start , (uint32_t)( Pos_End - Pos_Start ) ) == false ) return false; return true; } static void Avi_BuildFileHeader ( RECORD_AVI_PARAMS *pAviParams , AVI_FILE_HEADER *pAviFileHeader ) { int Width , Height , BitCount , Fps , Fps_scale , SizeImage; int AudioFreq; memset ( pAviFileHeader , 0 , sizeof ( *pAviFileHeader ) ); Width = pAviParams->Width; Height =pAviParams->Height; BitCount = pAviParams->BitCount; Fps = pAviParams->Fps; Fps_scale = pAviParams->Fps_scale; AudioFreq = pAviParams->AudioFreq; SizeImage = 0; if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) SizeImage = Avi_GetBmpSize ( Width , Height , BitCount ); /* size of a BMP image */ else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) SizeImage = Avi_GetBmpSize ( Width , Height , BitCount ); /* max size of a PNG image */ /* RIFF / AVI headers */ Avi_Store4cc ( pAviFileHeader->RiffHeader.signature , "RIFF" ); Avi_StoreU32 ( pAviFileHeader->RiffHeader.filesize , 0 ); /* total file size (-> completed later) */ Avi_Store4cc ( pAviFileHeader->RiffHeader.type , "AVI " ); pAviParams->RiffChunkPosStart = 0; Avi_Store4cc ( pAviFileHeader->AviHeader.ChunkName , "LIST" ); Avi_StoreU32 ( pAviFileHeader->AviHeader.ChunkSize , sizeof ( AVI_STREAM_LIST_AVIH ) + sizeof ( AVI_STREAM_LIST_VIDS ) + sizeof ( AVI_STREAM_LIST_AUDS ) - 8 ); Avi_Store4cc ( pAviFileHeader->AviHeader.Name , "hdrl" ); Avi_Store4cc ( pAviFileHeader->AviHeader.Header.ChunkName , "avih" ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.ChunkSize , sizeof ( AVI_STREAM_AVIH ) - 8 ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.microsec_per_frame , (uint32_t)( ( 1000000 * (int64_t)Fps_scale ) / Fps ) ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.max_bytes_per_second , (uint32_t)( ( (int64_t)SizeImage * Fps ) / Fps_scale + AudioFreq * 4 ) ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.padding_granularity , 0 ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.flags , AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.total_frames , 0 ); /* number of video frames (-> completed later) */ Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.init_frame , 0 ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.nb_streams , 2 ); /* 1 video and 1 audio */ Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.buffer_size , SizeImage ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.width , Width ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.height , Height ); Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.scale , 0 ); /* reserved */ Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.rate , 0 ); /* reserved */ Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.start , 0 ); /* reserved */ Avi_StoreU32 ( pAviFileHeader->AviHeader.Header.length , 0 ); /* reserved */ /* Video Stream : strl ( strh + strf + indx ) */ Avi_Store4cc ( pAviFileHeader->VideoStream.ChunkName , "LIST" ); Avi_StoreU32 ( pAviFileHeader->VideoStream.ChunkSize , sizeof ( AVI_STREAM_LIST_VIDS ) - 8 ); Avi_Store4cc ( pAviFileHeader->VideoStream.Name , "strl" ); Avi_Store4cc ( pAviFileHeader->VideoStream.Header.ChunkName , "strh" ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 ); Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_type , "vids" ); if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_RGB ); else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) Avi_Store4cc ( pAviFileHeader->VideoStream.Header.stream_handler , VIDEO_STREAM_PNG ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.flags , 0 ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.priority , 0 ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.language , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.initial_frames , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.time_scale , Fps_scale ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_rate , Fps ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.start_time , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.data_length , 0 ); /* number of video frames (-> completed later) */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.buffer_size , SizeImage ); /* size of an uncompressed frame */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.quality , -1 ); /* use default quality */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Header.sample_size , 0 ); /* 0 for video */ Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_left , 0 ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_top , 0 ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_right , Width ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Header.dest_bottom , Height ); Avi_Store4cc ( pAviFileHeader->VideoStream.Format.ChunkName , "strf" ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 ); if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) { Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 ); /* always 1 */ Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_RGB ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 ); /* no color map */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 ); /* no color map */ } else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) { Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size , sizeof ( AVI_STREAM_FORMAT_VIDS ) - 8 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.width , Width ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.height , Height ); Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.planes , 1 ); /* always 1 */ Avi_StoreU16 ( pAviFileHeader->VideoStream.Format.bit_count , BitCount ); Avi_Store4cc ( pAviFileHeader->VideoStream.Format.compression , VIDEO_STREAM_PNG ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.size_image , SizeImage ); /* max size if uncompressed */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.xpels_meter , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.ypels_meter , 0 ); Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_used , 0 ); /* no color map */ Avi_StoreU32 ( pAviFileHeader->VideoStream.Format.clr_important , 0 ); /* no color map */ } Avi_Store4cc ( pAviFileHeader->VideoStream.SuperIndex.ChunkName , "indx" ); Avi_StoreU32 ( pAviFileHeader->VideoStream.SuperIndex.ChunkSize , sizeof ( AVI_STREAM_SUPER_INDEX ) - 8 ); Avi_StoreU16 ( pAviFileHeader->VideoStream.SuperIndex.longs_per_entry , 4 ); Avi_StoreU8 ( &(pAviFileHeader->VideoStream.SuperIndex.index_sub_type) , 0 ); Avi_StoreU8 ( &(pAviFileHeader->VideoStream.SuperIndex.index_type) , AVI_INDEX_OF_INDEXES ); Avi_StoreU32 ( pAviFileHeader->VideoStream.SuperIndex.entries_in_use , 0 ); /* number of entries (-> completed later) */ if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_BMP ) Avi_Store4cc ( pAviFileHeader->VideoStream.SuperIndex.chunk_id , "00db" ); else if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) Avi_Store4cc ( pAviFileHeader->VideoStream.SuperIndex.chunk_id , "00dc" ); /* Audio Stream : strl ( strh + strf + indx ) */ Avi_Store4cc ( pAviFileHeader->AudioStream.ChunkName , "LIST" ); Avi_StoreU32 ( pAviFileHeader->AudioStream.ChunkSize , sizeof ( AVI_STREAM_LIST_AUDS ) - 8 ); Avi_Store4cc ( pAviFileHeader->AudioStream.Name , "strl" ); Avi_Store4cc ( pAviFileHeader->AudioStream.Header.ChunkName , "strh" ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.ChunkSize , sizeof ( AVI_STREAM_HEADER ) - 8 ); Avi_Store4cc ( pAviFileHeader->AudioStream.Header.stream_type , "auds" ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.stream_handler , 0 ); /* not used (or could be 1 for pcm ?) */ Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.flags , 0 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.priority , 0 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.language , 0 ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.initial_frames , 0 ); /* should be 1 in interleaved ? */ Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.time_scale , 1 ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_rate , AudioFreq ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.start_time , 0 ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.data_length , 0 ); /* number of audio samples (-> completed later) */ Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.buffer_size , AudioFreq * 4 / 50 ); /* min VBL freq is 50 Hz */ Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.quality , -1 ); /* use default quality */ Avi_StoreU32 ( pAviFileHeader->AudioStream.Header.sample_size , 4 ); /* 2 bytes, stereo */ Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_left , 0 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_top , 0 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_right , 0 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Header.dest_bottom , 0 ); Avi_Store4cc ( pAviFileHeader->AudioStream.Format.ChunkName , "strf" ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.ChunkSize , sizeof ( AVI_STREAM_FORMAT_AUDS ) - 8 ); if ( pAviParams->AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) /* 16 bits stereo pcm */ { Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.codec , AUDIO_STREAM_WAVE_FORMAT_PCM ); /* 0x0001 */ Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.channels , 2 ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.sample_rate , AudioFreq ); Avi_StoreU32 ( pAviFileHeader->AudioStream.Format.bit_rate , AudioFreq * 2 * 2 ); /* 2 channels * 2 bytes */ Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.block_align , 4 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.bits_per_sample , 16 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.Format.ext_size , 0 ); } Avi_Store4cc ( pAviFileHeader->AudioStream.SuperIndex.ChunkName , "indx" ); Avi_StoreU32 ( pAviFileHeader->AudioStream.SuperIndex.ChunkSize , sizeof ( AVI_STREAM_SUPER_INDEX ) - 8 ); Avi_StoreU16 ( pAviFileHeader->AudioStream.SuperIndex.longs_per_entry , 4 ); Avi_StoreU8 ( &(pAviFileHeader->AudioStream.SuperIndex.index_sub_type) , 0 ); Avi_StoreU8 ( &(pAviFileHeader->AudioStream.SuperIndex.index_type) , AVI_INDEX_OF_INDEXES ); Avi_StoreU32 ( pAviFileHeader->AudioStream.SuperIndex.entries_in_use , 0 ); /* number of entries (-> completed later) */ if ( pAviParams->AudioCodec == AVI_RECORD_AUDIO_CODEC_PCM ) /* 16 bits stereo pcm */ Avi_Store4cc ( pAviFileHeader->AudioStream.SuperIndex.chunk_id , "01wb" ); /* ODML infos */ Avi_Store4cc ( pAviFileHeader->Odml.ChunkName , "LIST" ); Avi_StoreU32 ( pAviFileHeader->Odml.ChunkSize , sizeof ( AVI_STREAM_LIST_ODML ) - 8 ); Avi_Store4cc ( pAviFileHeader->Odml.Name , "odml" ); Avi_Store4cc ( pAviFileHeader->Odml.Header.ChunkName , "dmlh" ); Avi_StoreU32 ( pAviFileHeader->Odml.Header.ChunkSize , sizeof ( AVI_STREAM_DMLH ) - 8 ); Avi_StoreU32 ( pAviFileHeader->Odml.Header.total_frames , 0 ); /* number of video frames (-> completed later) */ } static bool Avi_StartRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams , char *AviFileName ) { AVI_STREAM_LIST_INFO ListInfo; char InfoString[ 100 ]; int Len , Len_rounded; AVI_STREAM_LIST_MOVI ListMovi; if ( bRecordingAvi == true ) /* already recording ? */ return false; /* Compute some video parameters */ pAviParams->Width = pAviParams->Surface->w - pAviParams->CropLeft - pAviParams->CropRight; pAviParams->Height = pAviParams->Surface->h - pAviParams->CropTop - pAviParams->CropBottom; pAviParams->BitCount = 24; #if !HAVE_LIBPNG if ( pAviParams->VideoCodec == AVI_RECORD_VIDEO_CODEC_PNG ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : Hatari was not built with libpng support" ); return false; } #endif /* Open the file */ pAviParams->FileOut = fopen ( AviFileName , "wb+" ); if ( !pAviParams->FileOut ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to open file" ); return false; } /* Alloc memory to store frames' index */ if ( Avi_FrameIndex_GrowIfNeeded ( pAviParams ) == false ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to alloc index memory" ); return false; } /* Build the AVI header */ Avi_BuildFileHeader ( pAviParams , &AviFileHeader ); /* Write the AVI header */ if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write avi header" ); return false; } /* Write the INFO header */ memset ( InfoString , 0 , sizeof ( InfoString ) ); Len = snprintf ( InfoString , sizeof ( InfoString ) , "%s - the Atari ST, STE, TT and Falcon emulator" , PROG_NAME ) + 1; Len_rounded = Len + ( Len % 2 == 0 ? 0 : 1 ); /* round Len to the next multiple of 2 */ Avi_Store4cc ( ListInfo.ChunkName , "LIST" ); Avi_StoreU32 ( ListInfo.ChunkSize , sizeof ( AVI_STREAM_LIST_INFO ) - 8 + Len_rounded ); Avi_Store4cc ( ListInfo.Name , "INFO" ); Avi_Store4cc ( ListInfo.Info.ChunkName , "ISFT" ); Avi_StoreU32 ( ListInfo.Info.ChunkSize , Len ); if ( fwrite ( &ListInfo , sizeof ( ListInfo ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" ); return false; } /* Write the info string + '\0' and write an optional extra '\0' byte to get a total multiple of 2 */ if ( fwrite ( InfoString , Len_rounded , 1 , pAviParams->FileOut ) != 1 ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write info header" ); return false; } /* Write the MOVI header */ Avi_Store4cc ( ListMovi.ChunkName , "LIST" ); Avi_StoreU32 ( ListMovi.ChunkSize , 0 ); /* completed when recording stops */ Avi_Store4cc ( ListMovi.Name , "movi" ); pAviParams->MoviChunkPosStart = ftello ( pAviParams->FileOut ); if ( fwrite ( &ListMovi , sizeof ( ListMovi ) , 1 , pAviParams->FileOut ) != 1 ) { perror ( "AviStartRecording" ); Log_AlertDlg ( LOG_ERROR, "AVI recording : failed to write movi header" ); return false; } /* We're ok to record */ Log_AlertDlg ( LOG_INFO, "AVI recording has been started in %s", AviFileName ); bRecordingAvi = true; return true; } static bool Avi_StopRecording_WithParams ( RECORD_AVI_PARAMS *pAviParams ) { if ( bRecordingAvi == false ) /* no recording ? */ return true; /* Complete the current 'movi' chunk */ if ( Avi_CloseMoviChunk ( pAviParams , &AviFileHeader ) == false ) goto stoprec_error; /* Write the updated AVI header */ if ( fseeko ( pAviParams->FileOut , 0 , SEEK_SET ) != 0 ) goto stoprec_error; if ( fwrite ( &AviFileHeader , sizeof ( AviFileHeader ) , 1 , pAviParams->FileOut ) != 1 ) goto stoprec_error; /* Close the file */ fclose ( pAviParams->FileOut ); /* Free index' memory */ Avi_FrameIndex_Free ( pAviParams ); Log_AlertDlg ( LOG_INFO, "AVI recording has been stopped"); bRecordingAvi = false; return true; stoprec_error: fclose (pAviParams->FileOut); Avi_FrameIndex_Free ( pAviParams ); perror("AviStopRecording"); Log_AlertDlg(LOG_ERROR, "AVI recording : failed to update header"); return false; } /*-----------------------------------------------------------------------*/ /** * Are we recording an AVI ? */ bool Avi_AreWeRecording ( void ) { return bRecordingAvi; } /* PNG compression level, 0-9 */ static int compression_level = 9; /** * Set recording level from given string * return true for valid, false for invalid value */ bool Avi_SetCompressionLevel(const char *str) { char *end; long level = strtol(str, &end, 10); if (*end) return false; if (level < 0 || level > 9) return false; compression_level = level; return true; } static bool Avi_StartRecording ( char *FileName , bool CropGui , uint32_t Fps , uint32_t Fps_scale , int VideoCodec ) { memset ( &AviParams , 0 , sizeof ( AviParams ) ); AviParams.VideoCodec = VideoCodec; AviParams.VideoCodecCompressionLevel = compression_level; AviParams.AudioCodec = AVI_RECORD_AUDIO_CODEC_PCM; AviParams.AudioFreq = ConfigureParams.Sound.nPlaybackFreq; AviParams.Surface = sdlscrn; /* Some video players (quicktime, ...) don't support a value of Fps_scale */ /* above 100000. So we decrease the precision from << 24 to << 16 for Fps and Fps_scale */ AviParams.Fps = Fps >> 8; /* refresh rate << 16 */ AviParams.Fps_scale = Fps_scale >> 8; /* 1 << 16 */ if ( !CropGui ) /* Keep gui's status bar */ { AviParams.CropLeft = 0; AviParams.CropRight = 0; AviParams.CropTop = 0; AviParams.CropBottom = 0; } else /* Record only the content of the Atari's screen */ { AviParams.CropLeft = 0; AviParams.CropRight = 0; AviParams.CropTop = 0; AviParams.CropBottom = Statusbar_GetHeight(); } if (Avi_StartRecording_WithParams ( &AviParams , FileName )) { Main_SetTitle("00:00"); return true; } return false; } void Avi_SetSurface(SDL_Surface *surf) { AviParams.Surface = surf; } bool Avi_StartRecording_WithConfig ( void ) { return Avi_StartRecording( ConfigureParams.Video.AviRecordFile , ConfigureParams.Screen.bCrop , ConfigureParams.Video.AviRecordFps == 0 ? ClocksTimings_GetVBLPerSec ( ConfigureParams.System.nMachineType , nScreenRefreshRate ) : ClocksTimings_GetVBLPerSec ( ConfigureParams.System.nMachineType , ConfigureParams.Video.AviRecordFps ) , 1 << CLOCKS_TIMINGS_SHIFT_VBL , ConfigureParams.Video.AviRecordVcodec ); } bool Avi_StopRecording ( void ) { if (Avi_StopRecording_WithParams ( &AviParams )) { Main_SetTitle(NULL); return true; } return false; } bool Avi_StopRecording_WithMsg(void) { if (!bRecordingAvi) return true; /* cleanly close the AVI file */ Statusbar_AddMessage("Finishing AVI file...", 100); Statusbar_Update(sdlscrn, true); return Avi_StopRecording(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/bios.c000066400000000000000000000106561504763705000224400ustar00rootroot00000000000000/* Hatari - bios.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Bios Handler (Trap #13) Intercept some Bios calls for tracing/debugging */ const char Bios__fileid[] = "Hatari bios.c"; #include "main.h" #include "configuration.h" #include "floppy.h" #include "log.h" #include "m68000.h" #include "printer.h" #include "rs232.h" #include "stMemory.h" #include "bios.h" /*-----------------------------------------------------------------------*/ /** * BIOS Read/Write disk sector * Call 4 */ static void Bios_RWabs(uint32_t Params) { #if ENABLE_TRACING uint32_t pBuffer; uint16_t RWFlag, Number, RecNo, Dev; /* Read details from stack */ RWFlag = STMemory_ReadWord(Params); pBuffer = STMemory_ReadLong(Params+SIZE_WORD); Number = STMemory_ReadWord(Params+SIZE_WORD+SIZE_LONG); RecNo = STMemory_ReadWord(Params+SIZE_WORD+SIZE_LONG+SIZE_WORD); Dev = STMemory_ReadWord(Params+SIZE_WORD+SIZE_LONG+SIZE_WORD+SIZE_WORD); LOG_TRACE(TRACE_OS_BIOS, "BIOS 0x04 Rwabs(%d,0x%x,%d,%d,%i) at PC 0x%X\n", RWFlag, pBuffer, Number, RecNo, Dev, M68000_GetPC()); #endif } /*-----------------------------------------------------------------------*/ /** * BIOS Set/query exception vectors * Call 5 */ static void Bios_Setexe(uint32_t Params) { #if ENABLE_TRACING uint16_t vec = STMemory_ReadWord(Params); uint32_t addr = STMemory_ReadLong(Params+SIZE_WORD); struct { int vec; const char *name; } *vecname, vecnames[] = { { 0x002, "BUSERROR" }, { 0x003, "ADDRESSERROR" }, { 0x004, "ILLEGALINSTRUCTION" }, { 0x021, "GEMDOS" }, { 0x022, "GEM" }, { 0x02D, "BIOS" }, { 0x02E, "XBIOS" }, { 0x100, "TIMER" }, { 0x101, "CRITICALERROR" }, { 0x102, "TERMINATE" }, { 0x000, "???" } }; for (vecname = &(vecnames[0]); vecname->vec && vec != vecname->vec; vecname++) ; LOG_TRACE(TRACE_OS_BIOS, "BIOS 0x05 Setexc(0x%hX VEC_%s, 0x%X) at PC 0x%X\n", vec, vecname->name, addr, M68000_GetPC()); #endif } /*-----------------------------------------------------------------------*/ #if ENABLE_TRACING /** * Map BIOS call opcode to BIOS function name */ static const char* Bios_Call2Name(uint16_t opcode) { /* GCC uses substrings from above trace statements * where they match, so having them again here * wastes only a pointer & simplifies things */ static const char* names[] = { "Getmpb", "Bconstat","Bconin", "Bconout", "Rwabs", "Setexc", "Tickcal","Getbpb", "Bcostat","Mediach", "Drvmap", "Kbshift" }; if (opcode < ARRAY_SIZE(names) && names[opcode]) { return names[opcode]; } return "???"; } void Bios_Info(FILE *fp, uint32_t dummy) { uint16_t opcode; for (opcode = 0; opcode <= 0xB; ) { fprintf(fp, "%02x %-9s", opcode, Bios_Call2Name(opcode)); if (++opcode % 6 == 0) { fputs("\n", fp); } } } #else /* !ENABLE_TRACING */ void Bios_Info(FILE *fp, uint32_t bShowOpcodes) { fputs("Hatari isn't configured with ENABLE_TRACING\n", fp); } #endif /* !ENABLE_TRACING */ /*-----------------------------------------------------------------------*/ /** * Check Bios call and see if we need to re-direct to our own routines. * Return true if we've handled the exception, else return false to let * TOS attempt it */ bool Bios(void) { uint32_t Params; uint16_t BiosCall; /* Get call */ Params = Regs[REG_A7]; BiosCall = STMemory_ReadWord(Params); Params += SIZE_WORD; /* Intercept? */ switch(BiosCall) { case 0x0: LOG_TRACE(TRACE_OS_BIOS, "BIOS 0x00 Getmpb(0x%X) at PC 0x%X\n", STMemory_ReadLong(Params), M68000_GetPC()); break; case 0x3: LOG_TRACE(TRACE_OS_BIOS, "BIOS 0x03 Bconout(%i, 0x%02hX) at PC 0x%X\n", STMemory_ReadWord(Params), STMemory_ReadWord(Params+SIZE_WORD), M68000_GetPC()); break; case 0x4: Bios_RWabs(Params); break; case 0x5: Bios_Setexe(Params); break; case 0x1: case 0x2: case 0x7: case 0x8: case 0x9: case 0xB: /* commands taking a single word */ LOG_TRACE(TRACE_OS_BIOS, "BIOS 0x%02hX %s(0x%hX) at PC 0x%X\n", BiosCall, Bios_Call2Name(BiosCall), STMemory_ReadWord(Params), M68000_GetPC()); break; case 0x6: case 0xA: /* commands taking no args */ LOG_TRACE(TRACE_OS_BIOS, "BIOS 0x%02hX %s() at PC 0x%X\n", BiosCall, Bios_Call2Name(BiosCall), M68000_GetPC()); break; default: Log_Printf(LOG_WARN, "Unknown BIOS call 0x%x! at PC 0x%X\n", BiosCall, M68000_GetPC()); break; } return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/blitter.c000066400000000000000000001552111504763705000231460ustar00rootroot00000000000000/* * Hatari - blitter.c * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Blitter emulation. The 'Blitter' chip is found in the Mega-ST, STE/Mega-STE * and Falcon. It provides a very fast BitBlit function in hardware. * * This file has originally been taken from STonX's blitter by Martin Griffiths, * but it has been completely modified for better maintainability and higher compatibility. * */ /* NOTES [NP] : As of August 2017, the blitter code was partly rewritten to allow cycle exact bus accesses between the blitter and the CPU, allowing to have CPU instruction running in parallel to the blitter when the CPU doesn't need to access the bus. The internal work of the blitter regarding bus accesses was deduced from studying many cases on a real STE, including several demos using the blitter with overscan, some tests programs on www.atari-forum.com (from Cyprian Konador), as well as my own tests on an STE. Depending on the CPU instruction used to start the blitter, we can see that the next instruction can be partially or totally executed during the blit, or that it will be executed only after the blit is done. To understand the reason for this parallel execution, we must know the sequence used by the blitter until it gets the bus and start transferring data. Based on several examples, possible sequence when starting the blitter seems to be : - t+0 : write to FF8A3C is complete or blitter "restarts" itself in non-hog mode - t+0 : CPU can still run during 4 cycles and access bus - t+4 : bus arbitration takes 4 cycles (no access for CPU and blitter during this time) - t+8 : blitter owns the bus and starts transferring data In case of MegaSTE, the arbitration to grant the bus to the blitter takes 8 cycles instead of 4. But when bus is granted back to the CPU, it takes 4 cycles on both STE and MegaSTE. We can see there's a 4 cycles latency between when the busy bit is set and when the blitter asks for a bus grant. It's during these 4 cycles that part of the next CPU instruction can be run, if the current instruction that started the blitter doesn't need the bus anymore. For example : - move.b d0,(a0) + nop : MOVE.B will do a write then a read to prefetch the next instruction -> NOP will run after the blit - bset #7,(a0) + nop : BSET will read first then finish with a write -> NOP will run before the blit - bset #7,(a0) + mulu dx,dy : BSET will read first then finish with a write. Then mulu will prefetch before the blit starts -> MULU will run in parallel to the blitter - bset #7,(a0) + divu dx,dy : BSET will read first then finish with a write. Then divu will run internal cycles and finish with a prefetch -> all the cycles from the DIVU will run in parallel to the blitter until we reach the DIVU's prefetch So, by interleaving CPU instructions with some blitter transfers, it is possible to run part of those instructions in parallel to the blitter, thus saving CPU cycles on some costly instructions (div,mul,...) - Number of bus shared between CPU and blitter : as described in Atari developers documentation, when HOG mode is disabled the blitter will run during 64 bus accesses (read or write), then it will give the bus to the CPU for 64 bus accesses too. But in some cases (see below), a possible bug in the blitter will make it use the bus during only 63 accesses instead of 64. - As verified on a real STE, when the blitter owns the bus in non-hog mode, it will give back the bus to the CPU exactly after the 64th (or 63th) bus access, not just after writing the result of the current word transfer. For the emulation, this means the blitter's state must be preserved to be able to resume after any bus read made by the blitter (blitter's operations can have between 0 and 2 reads and 1 write). - Blitter doing only 63 bus accesses instead of 64 in non-hog mode : my guess is that in non-hog mode the blitter will always count bus accesses as soon as busy bit is set, even when it has not started to transfer its own data. So, if the CPU does a bus access during the 4 cycles latency between t+0 and t+4 above, then this CPU bus access will be counted by the blitter as a blitter bus access, thus effectively losing one bus access and doing only 63 bus accesses after that (before granting the bus to the CPU for the next 64 bus accesses). Some examples of demos requiring cycle exact blitter mode to correctly work : - 'Relapse - Graphix Sound 2' by Cybernetics (overscan plasma using blitter) This demo uses a self-calibration routine to adapt blitter code to the MegaSTE. $e764 : move.b d5,(a4) + dbra d1,$fff2 : 4 cycles of the dbra can be executed while blitter starts From an emulation point of view, the code needed for cycle exact blitter mode requires to intercept bus accesses before and after they occur, as well as intercepting "do_cycle" before and after too. Parallel execution of the CPU is obtained by skipping as many CPU cycles as the blitter ran, or by skipping CPU cycles until the next CPU bus access (at which point the CPU would stall). When HOG mode is disabled, the CPU can also stop the blitter in case it needs more than 64 bus accesses (for example to handle some timing-sensitive interrupts). When CPU owns the bus, writing '0' to bit 7 of the control register will stop the blitter, writing '1' will resume the blitter from where it was before being stopped. Note that writing '0' will not end the current blitter transfer, reading 'busy' bit 7 will still return '1'. It's only when 'y count' reaches '0' that transfer will be complete and busy bit will be cleared. As measured on STE (as well as on Falcon), the blitter produces "strange" results when each line is only 1 word (xcount=1) and NFSR is set at the same time, depending on whether src X increment is >0 or <0. This was discussed in may 2017 in Hatari mailing list and a model still need to be found to cover all cases. UPDATE : as of september 2020, this behaviour should be correctly emulated (based on reverse engineering and Verilog implementation made by Jorge Cwik (Ijor)) NOTE for Falcon using 32 bit TT RAM : standard Falcon can only address 24 bits of memory with the CPU, the Falcon's blitter is also limited to 24 bits addresses. Some extension boards such as Afterburner or CT2 could use extra "TT RAM" which required 32 bits addresses, but as normal TOS was not designed to handle 32 bits addresses (because is was not possible hardwire-wise) we need to "simulate" 32 bits source/dest address at $FF8A24 and $FF8A32 instead of masking 24 bits (see Blitter_SourceAddr_WriteLong and Blitter_DestAddr_WriteLong) */ const char Blitter_fileid[] = "Hatari blitter.c"; #include #include #include "main.h" #include "blitter.h" #include "configuration.h" #include "dmaSnd.h" #include "ioMem.h" #include "m68000.h" #include "mfp.h" #include "memorySnapShot.h" #include "stMemory.h" #include "video.h" #include "hatari-glue.h" #include "falcon/dsp.h" /* BLiTTER registers, incs are signed, others unsigned */ #define REG_HT_RAM 0xff8a00 /* - 0xff8a1e */ #define REG_SRC_X_INC 0xff8a20 #define REG_SRC_Y_INC 0xff8a22 #define REG_SRC_ADDR 0xff8a24 #define REG_END_MASK1 0xff8a28 #define REG_END_MASK2 0xff8a2a #define REG_END_MASK3 0xff8a2c #define REG_DST_X_INC 0xff8a2e #define REG_DST_Y_INC 0xff8a30 #define REG_DST_ADDR 0xff8a32 #define REG_X_COUNT 0xff8a36 #define REG_Y_COUNT 0xff8a38 #define REG_BLIT_HOP 0xff8a3a /* halftone blit operation byte */ #define REG_BLIT_LOP 0xff8a3b /* logical blit operation byte */ #define REG_CONTROL 0xff8a3c #define REG_SKEW 0xff8a3d /* Blitter registers */ typedef struct { uint32_t src_addr; uint32_t dst_addr; uint32_t x_count; uint32_t y_count; short src_x_incr; short src_y_incr; short dst_x_incr; short dst_y_incr; uint16_t end_mask_1; uint16_t end_mask_2; uint16_t end_mask_3; uint8_t hop; uint8_t lop; uint8_t ctrl; uint8_t skew; } BLITTERREGS; /* Blitter vars */ typedef struct { uint32_t pass_cycles; uint32_t op_cycles; uint32_t total_cycles; uint32_t buffer; uint32_t x_count_reset; uint8_t hog; uint8_t smudge; uint8_t halftone_line; uint8_t fxsr; uint8_t nfsr; uint8_t skew; } BLITTERVARS; /* Blitter state */ typedef struct { uint8_t fxsr; uint8_t nfsr; uint8_t have_fxsr; uint8_t need_src; uint8_t have_src; uint8_t fetch_src; uint8_t need_dst; uint8_t have_dst; uint16_t src_word; uint16_t dst_word; uint16_t bus_word; uint16_t end_mask; uint16_t CountBusBlitter; /* To count bus accesses made by the blitter */ uint16_t CountBusCpu; /* To count bus accesses made by the CPU */ uint8_t ContinueLater; /* 0=false / 1=true */ } BLITTERSTATE; /* Blitter logical op func */ typedef uint16_t (*BLITTER_OP_FUNC)(void); static BLITTERREGS BlitterRegs; static BLITTERVARS BlitterVars; static BLITTERSTATE BlitterState; static uint16_t BlitterHalftone[16]; static BLITTER_OP_FUNC Blitter_ComputeHOP; static BLITTER_OP_FUNC Blitter_ComputeLOP; /* To handle CPU/blitter bus sharing in non-hog mode (require cycle exact mode for CPU emulation) */ #define BLITTER_PHASE_STOP 0 /* blitter is completely stopped */ #define BLITTER_PHASE_PRE_START 1 #define BLITTER_PHASE_START 2 #define BLITTER_PHASE_RUN_TRANSFER 4 /* blitter owns the bus and transfer data */ #define BLITTER_PHASE_COUNT_CPU_BUS 8 /* cpu owns the bus during 64 accesses */ #define BLITTER_PHASE_IGNORE_LAST_CPU_CYCLES 16 #define BLITTER_PHASE_PAUSE 32 /* cpu owns the bus (COUNT_CPU_BUS) and stops the blitter */ uint16_t BlitterPhase = BLITTER_PHASE_STOP; /* Internal state of the blitter */ static uint16_t Blitter_CyclesBeforeStart; /* Number of cycles after setting busy bit before calling Blitter_Start */ /* (during this time, the CPU can still run and access the bus) */ static uint8_t Blitter_HOG_CPU_FromBusAccess; /* 0 or 1 (false/true) */ static uint8_t Blitter_HOG_CPU_BlitterStartDuringBusAccess; /* 0 or 1 (false/true) */ static uint16_t Blitter_HOG_CPU_BusCountError; /* 0 or 1 (false/true) */ static uint16_t Blitter_HOG_CPU_IgnoreMaxCpuCycles; /* Max number of blitter cycles during which the CPU might run in parallel */ /* (unless the CPU is stalled earlier by a bus access) */ /* Number of bus accesses allocated to blitter and CPU in non-hog mode */ #define BLITTER_NONHOG_BUS_BLITTER 64 /* Can also be 63, see Blitter_HOG_CPU_BusCountError */ #define BLITTER_NONHOG_BUS_CPU 64 #define BLITTER_CYCLES_PER_BUS_READ 4 /* The blitter takes 4 cycles to read 1 memory word on STE */ #define BLITTER_CYCLES_PER_BUS_WRITE 4 /* The blitter takes 4 cycles to write 1 memory word on STE */ /* Return 'true' if CE mode can be enabled for blitter (ie when using 68000 CE mode) */ #define BLITTER_RUN_CE ( CpuRunCycleExact && ( currprefs.cpu_model == 68000 ) ) /* Used to compute the blitter's usage during each VBL (for statusbar) */ static int BlitterStatsRate; /*-----------------------------------------------------------------------*/ /** * Reset all blitter variables */ void Blitter_Reset ( void ) { BlitterRegs.src_addr = 0; BlitterRegs.dst_addr = 0; BlitterRegs.x_count = 0; BlitterRegs.y_count = 0; BlitterRegs.src_x_incr = 0; BlitterRegs.src_y_incr = 0; BlitterRegs.dst_x_incr = 0; BlitterRegs.dst_y_incr = 0; BlitterRegs.end_mask_1 = 0; BlitterRegs.end_mask_2 = 0; BlitterRegs.end_mask_3 = 0; BlitterRegs.hop = 0; BlitterRegs.lop = 0; BlitterRegs.ctrl = 0; BlitterVars.hog = 0; BlitterVars.smudge = 0; BlitterVars.halftone_line = 0; BlitterRegs.skew = 0; BlitterVars.fxsr = 0; BlitterVars.nfsr = 0; BlitterVars.skew = 0; BlitterState.fxsr = false; BlitterState.nfsr = false; BlitterState.have_fxsr = false; BlitterState.need_src = false; BlitterState.have_src = false; BlitterState.fetch_src = false; BlitterState.need_dst = false; BlitterState.have_dst = false; BlitterState.bus_word = 0; BlitterState.ContinueLater = 0 ; } /*-----------------------------------------------------------------------*/ /** * Compute some stats for the blitter's usage during a period (eg one VBL) * Used to determine a percent per VBL and show a led in the statusbar */ void Blitter_StatsUpdateRate ( int period_cycles ) { int percent; if ( period_cycles == 0 ) percent = 0; else percent = ceil ( 100.0 * BlitterVars.total_cycles / period_cycles ); //fprintf ( stderr , "blitter %d %%\n" , percent ); BlitterVars.total_cycles = 0; BlitterStatsRate = percent; } int Blitter_StatsGetRate ( void ) { return BlitterStatsRate; } /*-----------------------------------------------------------------------*/ /** * Count blitter cycles (this assumes blitter and CPU runs at the same freq) */ static void Blitter_AddCycles(int cycles) { int all_cycles = cycles + WaitStateCycles; BlitterVars.op_cycles += all_cycles; BlitterVars.total_cycles += all_cycles; //fprintf ( stderr , "blitter add_cyc cyc=%d total=%d cur_cyc=%lu\n" , all_cycles , BlitterVars.op_cycles , currcycle/cpucycleunit ); //fprintf ( stderr , "blitter src %x dst %x ycount %d\n" , BlitterRegs.src_addr , BlitterRegs.dst_addr , BlitterRegs.y_count ); nCyclesMainCounter += all_cycles; CyclesGlobalClockCounter += all_cycles; WaitStateCycles = 0; } static void Blitter_FlushCycles(void) { //fprintf ( stderr , "blitter flush_cyc cyc=%d pass=%d %d cur_cyc=%lu\n" , BlitterVars.op_cycles , BlitterVars.pass_cycles , nCyclesMainCounter , currcycle/cpucycleunit ); if ( BLITTER_RUN_CE ) /* In CE mode, flush cycles already counted in the current cpu instruction */ { M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle = 0; } CycInt_Process(); /* Run DSP while blitter owns the bus */ if (bDspEnabled) { DSP_Run(2 * BlitterVars.op_cycles); } BlitterVars.pass_cycles += BlitterVars.op_cycles; BlitterVars.op_cycles = 0; } /*-----------------------------------------------------------------------*/ /** * Handle bus arbitration when switching between CPU and Blitter * When a write is made to FF8A3C to start the blitter, it will take a few cycles * before doing the bus arbitration. During this time the CPU will be able to * partially execute the next instruction in parallel to the blitter * (until an access to the BUS is needed by the CPU). * * Based on several examples, possible sequence when starting the blitter seems to be : * - t+0 : write to FF8A3C * - t+0 : CPU can still run during 4 cycles and access bus * - t+4 : bus arbitration takes 4 cycles (no access for cpu and blitter during this time) * - t+8 : blitter owns the bus and starts transferring data * (in case of MegaSTE bus arbitration takes 8 cycles instead of 4) * * When blitter stops owning the bus in favor of the cpu, this seems to always take 4 cycles */ static void Blitter_BusArbitration ( int RequestBusMode ) { int cycles; if ( RequestBusMode == BUS_MODE_BLITTER ) /* Bus is requested by the blitter */ { cycles = 4; /* Default case : take 4 cycles when going from cpu to blitter */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) { cycles = 8; /* MegaSTE blitter needs 4 extra cycles when requesting the bus */ MegaSTE_Cache_Flush (); /* Flush the MegaSTE's external cache when blitter request the bus */ } // fprintf ( stderr , "blitter bus start pc %x %x cyc=%d cur_cyc=%lu\n" , M68000_GetPC() , M68000_InstrPC , cycles , currcycle/cpucycleunit ); } else /* Bus is requested by the cpu */ { cycles = 4; /* Always 4 cycles (even for MegaSTE) */ // fprintf ( stderr , "blitter bus end pc %x %x cyc=%d\n" , M68000_GetPC() , M68000_InstrPC , cycles ); } /* Add arbitration cycles and update BusMode */ Blitter_AddCycles ( cycles ); Blitter_FlushCycles(); BusMode = RequestBusMode; } /*-----------------------------------------------------------------------*/ /** * Low level memory accesses to read / write a word * For each word access we increment the blitter's bus accesses counter. */ static uint16_t Blitter_ReadWord(uint32_t addr) { uint16_t value; value = STMemory_DMA_ReadWord ( addr ); BlitterState.CountBusBlitter++; Blitter_AddCycles ( BLITTER_CYCLES_PER_BUS_READ ); Blitter_FlushCycles(); BlitterState.bus_word = value; return value; } static void Blitter_WriteWord(uint32_t addr, uint16_t value) { BlitterState.bus_word = value; STMemory_DMA_WriteWord ( addr , value ); BlitterState.CountBusBlitter++; Blitter_AddCycles ( BLITTER_CYCLES_PER_BUS_WRITE ); Blitter_FlushCycles(); } /*-----------------------------------------------------------------------*/ /** * Used to determine how long the blitter can keep the bus in non-hog mode * Return true if the blitter can continue its operations and return false * if the blitter must suspend its work and give back the bus to the CPU */ static bool Blitter_ContinueNonHog ( void ) { if ( BlitterState.CountBusBlitter < BLITTER_NONHOG_BUS_BLITTER ) return true; else return false; } /* Macro to check if blitter can continue and do a 'return' if not */ #define BLITTER_RETURN_IF_MAX_BUS_REACHED if ( !BlitterVars.hog && !Blitter_ContinueNonHog() ) return 0; /* Macro to suspend this transfer for now and keep src/dst to continue later */ #define BLITTER_CONTINUE_LATER_IF_MAX_BUS_REACHED if ( !BlitterVars.hog && !Blitter_ContinueNonHog() ) \ { \ /* fprintf ( stderr , "blitter suspended before write word have_src=%d have_dst=%d\n" , BlitterState.have_src ,BlitterState.have_dst ); */ \ BlitterState.ContinueLater = 1; return; \ } /*-----------------------------------------------------------------------*/ /** * Blitter emulation - level 1 (lower level) */ static void Blitter_SourceShift(void) { if (BlitterRegs.src_x_incr < 0) BlitterVars.buffer >>= 16; else BlitterVars.buffer <<= 16; } static void Blitter_SourceFetch( bool nfsr_on ) { uint32_t src_word; if ( !nfsr_on ) src_word = (uint32_t)Blitter_ReadWord(BlitterRegs.src_addr); else src_word = (uint32_t)BlitterState.bus_word; if (BlitterRegs.src_x_incr < 0) BlitterVars.buffer |= src_word << 16; else BlitterVars.buffer |= src_word; } static uint16_t Blitter_SourceRead(void) { return (uint16_t)(BlitterVars.buffer >> BlitterVars.skew); } static uint16_t Blitter_DestRead(void) { return BlitterState.dst_word; } static uint16_t Blitter_GetHalftoneWord(void) { if ( BlitterVars.smudge ) return BlitterHalftone[Blitter_SourceRead() & 15]; else return BlitterHalftone[BlitterVars.halftone_line]; } /* HOP */ static uint16_t Blitter_HOP_0(void) { return 0xFFFF; } static uint16_t Blitter_HOP_1(void) { return Blitter_GetHalftoneWord(); } static uint16_t Blitter_HOP_2(void) { return Blitter_SourceRead(); } static uint16_t Blitter_HOP_3(void) { return Blitter_SourceRead() & Blitter_GetHalftoneWord(); } static BLITTER_OP_FUNC Blitter_HOP_Table [4] = { Blitter_HOP_0, Blitter_HOP_1, Blitter_HOP_2, Blitter_HOP_3 }; static void Blitter_Select_HOP(void) { Blitter_ComputeHOP = Blitter_HOP_Table[BlitterRegs.hop]; } /* end HOP */ /* LOP */ static uint16_t Blitter_LOP_0(void) { return 0; } static uint16_t Blitter_LOP_1(void) { return Blitter_ComputeHOP() & Blitter_DestRead(); } static uint16_t Blitter_LOP_2(void) { return Blitter_ComputeHOP() & ~Blitter_DestRead(); } static uint16_t Blitter_LOP_3(void) { return Blitter_ComputeHOP(); } static uint16_t Blitter_LOP_4(void) { return ~Blitter_ComputeHOP() & Blitter_DestRead(); } static uint16_t Blitter_LOP_5(void) { return Blitter_DestRead(); } static uint16_t Blitter_LOP_6(void) { return Blitter_ComputeHOP() ^ Blitter_DestRead(); } static uint16_t Blitter_LOP_7(void) { return Blitter_ComputeHOP() | Blitter_DestRead(); } static uint16_t Blitter_LOP_8(void) { return ~Blitter_ComputeHOP() & ~Blitter_DestRead(); } static uint16_t Blitter_LOP_9(void) { return ~Blitter_ComputeHOP() ^ Blitter_DestRead(); } static uint16_t Blitter_LOP_A(void) { return ~Blitter_DestRead(); } static uint16_t Blitter_LOP_B(void) { return Blitter_ComputeHOP() | ~Blitter_DestRead(); } static uint16_t Blitter_LOP_C(void) { return ~Blitter_ComputeHOP(); } static uint16_t Blitter_LOP_D(void) { return ~Blitter_ComputeHOP() | Blitter_DestRead(); } static uint16_t Blitter_LOP_E(void) { return ~Blitter_ComputeHOP() | ~Blitter_DestRead(); } static uint16_t Blitter_LOP_F(void) { return 0xFFFF; } static const struct { BLITTER_OP_FUNC lop_func; uint8_t need_src; uint8_t need_dst; } Blitter_LOP_Table [16] = { { Blitter_LOP_0, false, false } , { Blitter_LOP_1, true, true }, { Blitter_LOP_2, true, true }, { Blitter_LOP_3, true, false }, { Blitter_LOP_4, true, true }, { Blitter_LOP_5, false, true }, { Blitter_LOP_6, true, true }, { Blitter_LOP_7, true, true }, { Blitter_LOP_8, true, true }, { Blitter_LOP_9, true, true }, { Blitter_LOP_A, false, true }, { Blitter_LOP_B, true, true }, { Blitter_LOP_C, true, false }, { Blitter_LOP_D, true, true }, { Blitter_LOP_E, true, true }, { Blitter_LOP_F, false, false } }; static void Blitter_Select_LOP(void) { Blitter_ComputeLOP = Blitter_LOP_Table[BlitterRegs.lop].lop_func; } /* end LOP */ static void Blitter_ProcessWord(void) { uint16_t lop; uint16_t dst_data; /* Do FXSR if needed (only if src is used) */ if ( BlitterState.fxsr && !BlitterState.have_fxsr && BlitterState.need_src ) { Blitter_SourceShift(); Blitter_SourceFetch( false ); BlitterRegs.src_addr += BlitterRegs.src_x_incr; /* always increment src_addr after doing the FXSR */ BlitterState.have_fxsr = true; BLITTER_CONTINUE_LATER_IF_MAX_BUS_REACHED } /* Read src if needed */ if ( BlitterState.need_src && !BlitterState.have_src ) { if ( !BlitterState.nfsr ) { Blitter_SourceShift(); Blitter_SourceFetch( false ); BlitterState.have_src = true; BlitterState.fetch_src = true; BLITTER_CONTINUE_LATER_IF_MAX_BUS_REACHED } } /* Read dst if needed */ if ( BlitterState.need_dst && !BlitterState.have_dst ) { BlitterState.dst_word = Blitter_ReadWord(BlitterRegs.dst_addr); BlitterState.have_dst = true; BLITTER_CONTINUE_LATER_IF_MAX_BUS_REACHED } /* Special 'weird' case for x_count=1 and NFSR=1 */ if ( ( BlitterVars.nfsr ) && ( BlitterRegs.x_count == 1 ) ) { Blitter_SourceShift(); Blitter_SourceFetch( true ); } lop = Blitter_ComputeLOP(); /* When mask is not all '1', a read-modify-write is always performed */ /* NOTE : Atari's doc wrongly states that NFSR can also do a RMW, but only mask can */ /* (cf http://www.atari-forum.com/viewtopic.php?f=16&t=38157) */ if ( BlitterState.end_mask != 0xFFFF ) dst_data = (lop & BlitterState.end_mask) | (Blitter_DestRead() & ~BlitterState.end_mask); else dst_data = lop; Blitter_WriteWord(BlitterRegs.dst_addr, dst_data); /* Special 'weird' case for x_count=1 and NFSR=1 */ if ( ( BlitterVars.nfsr ) && ( BlitterRegs.x_count == 1 ) ) { Blitter_SourceShift(); Blitter_SourceFetch( true ); } } /*-----------------------------------------------------------------------*/ /** * Blitter emulation - level 2 (higher level) * * If BlitterState.ContinueLater==1, it means we're resuming from a previous * Blitter_ProcessWord() call that did not complete because we reached * maximum number of bus accesses. In that case, we continue from the latest * state, keeping the values we already have for src_word and dst_word. */ /* * Reset internal states after fully processing 1 word or when blitter is started */ static void Blitter_FlushWordState( bool FlushFxsr ) { if ( FlushFxsr ) BlitterState.have_fxsr = false; BlitterState.have_src = false; BlitterState.fetch_src = false; BlitterState.have_dst = false; } /** * Process 1 word for the current x_count/y_count values. * Update addresses/counters/states when done * If too many bus accesses were made in non-hog mode, we return * and we resume later from the same states. */ static void Blitter_Step(void) { bool FirstWord; if ( BlitterState.ContinueLater ) BlitterState.ContinueLater = 0; /* Resuming, keep previous values of have_src/have_dst/have_fxsr/... */ /* Check if this is the first word of a line */ FirstWord = ( BlitterRegs.x_count == BlitterVars.x_count_reset ); /* Set mask for this word (order of 'if' matters) */ if ( FirstWord || ( BlitterVars.x_count_reset == 1 ) ) /* 1st word or single word line */ BlitterState.end_mask = BlitterRegs.end_mask_1; else if ( BlitterRegs.x_count == 1 ) /* last word for non-single word line */ BlitterState.end_mask = BlitterRegs.end_mask_3; else /* middle word for non-single word line */ BlitterState.end_mask = BlitterRegs.end_mask_2; /* Set internal nfsr=0 by default at the start of a new line (it will be updated if needed when xcount goes from 2 to 1) */ if ( FirstWord ) BlitterState.nfsr = 0; /* Read an extra word at the start of a line if FXSR is set */ /* This extra word will only be read if the blitter LOP/HOP needs to read src */ if ( FirstWord ) BlitterState.fxsr = BlitterVars.fxsr; /* Check if this operation requires to read src */ BlitterState.need_src = Blitter_LOP_Table[BlitterRegs.lop].need_src; /* Check if HOP uses src : bit1==1 or halftone with smudge bit */ BlitterState.need_src = BlitterState.need_src && ( ( BlitterRegs.hop & 2 ) || ( ( BlitterRegs.hop == 1 ) && BlitterVars.smudge ) ); /* Check if this operation requires to read dst (if mask != 0xFFFF, read dst will be forced to do a read-modify-write */ BlitterState.need_dst = Blitter_LOP_Table[BlitterRegs.lop].need_dst || ( BlitterState.end_mask != 0xFFFF ); /* Call main function to process the data */ /* Read src/dst/halftone (if needed) + process + write to dst */ Blitter_ProcessWord(); if ( BlitterState.ContinueLater == 1 ) /* blitter did not complete due to too many bus accesses */ return; /* stop now and resume later */ /* Write was done, update counters/addresses/states for next step */ /* Take NFSR value into account (this must be checked when x_count=2, as on real blitter) */ if ( ( BlitterRegs.x_count == 2 ) && BlitterVars.nfsr ) BlitterState.nfsr = 1; /* next source read will be ignored in Blitter_SourceRead() */ /* Update source address if a word was read from src */ if ( BlitterState.fetch_src ) { /* If this was the last read of a line or if last read will be ignored, then we go to the next source line */ if ( ( BlitterRegs.x_count == 1 ) || ( BlitterState.nfsr == 1 ) ) BlitterRegs.src_addr += BlitterRegs.src_y_incr; else BlitterRegs.src_addr += BlitterRegs.src_x_incr; } /* Update X/Y count as well as dest address */ if ( BlitterRegs.x_count == 1 ) /* end of line reached */ { BlitterState.have_fxsr = false; BlitterRegs.y_count--; BlitterRegs.x_count = BlitterVars.x_count_reset; BlitterRegs.dst_addr += BlitterRegs.dst_y_incr; if ( BlitterRegs.dst_y_incr >= 0 ) BlitterVars.halftone_line = ( BlitterVars.halftone_line+1 ) & 15; else BlitterVars.halftone_line = ( BlitterVars.halftone_line-1 ) & 15; } else /* continue on the same line */ { BlitterRegs.x_count--; BlitterRegs.dst_addr += BlitterRegs.dst_x_incr; } /* ProcessWord is complete, reset internal content of src/dst words */ Blitter_FlushWordState ( false ); } /*-----------------------------------------------------------------------*/ /** * Start/Resume the blitter * * Note that in non-hog mode, the blitter only runs for 64 bus cycles * before giving the bus back to the CPU. Due to this mode, this function must * be able to abort and resume the blitting at any time, keeping the same internal states. * - In cycle exact mode, the blitter will have 64 bus accesses and the cpu 64 bus accesses * - In non cycle exact mode, the blitter will have 64 bus accesses and the cpu * will run during 64*4 = 256 cpu cycles */ static void Blitter_Start(void) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); //fprintf ( stderr , "blitter start %d video_cyc=%d %d@%d\n" , nCyclesMainCounter , FrameCycles , LineCycles, HblCounterVideo ); //fprintf ( stderr , "blitter start addr=%x dst=%x xcount=%d ycount=%d fxsr=%d nfsr=%d skew=%d src_x_incr=%d src_y_incr=%d\n" , BlitterRegs.src_addr ,BlitterRegs.dst_addr, BlitterRegs.x_count , BlitterRegs.y_count , BlitterVars.fxsr , BlitterVars.nfsr , BlitterVars.skew , BlitterRegs.src_x_incr , BlitterRegs.src_y_incr ); /* Select HOP & LOP funcs */ Blitter_Select_HOP(); Blitter_Select_LOP(); /* Setup vars */ BlitterVars.pass_cycles = 0; BlitterVars.op_cycles = 0; BlitterState.CountBusBlitter = 0; if ( Blitter_HOG_CPU_BusCountError ) BlitterState.CountBusBlitter++; /* Bug in the blitter : count 1 CPU access as a blitter access */ /* Bus arbitration */ Blitter_BusArbitration ( BUS_MODE_BLITTER ); BlitterPhase = BLITTER_PHASE_RUN_TRANSFER; /* Busy=1, set line to high/1 and clear interrupt */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_GPU_DONE , MFP_GPIP_STATE_HIGH ); /* Now we enter the main blitting loop */ do { Blitter_Step(); } while ( BlitterRegs.y_count > 0 && ( BlitterVars.hog || Blitter_ContinueNonHog() ) ); /* Bus arbitration */ Blitter_BusArbitration ( BUS_MODE_CPU ); BlitterRegs.ctrl = (BlitterRegs.ctrl & 0xF0) | BlitterVars.halftone_line; if (BlitterRegs.y_count == 0) { /* Blit complete, clear busy and hog bits */ BlitterRegs.ctrl &= ~(0x80|0x40); /* Busy=0, set line to low/0 and request interrupt */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_GPU_DONE , MFP_GPIP_STATE_LOW ); BlitterPhase = BLITTER_PHASE_STOP; if ( BLITTER_RUN_CE ) { /* In CE mode, we check if a CPU instruction could have ran in parallel to the blitter */ BlitterPhase |= BLITTER_PHASE_IGNORE_LAST_CPU_CYCLES; Blitter_HOG_CPU_IgnoreMaxCpuCycles = BlitterVars.pass_cycles; } } else { /* Blit not complete yet in non-hog mode, give back the bus to the CPU */ BlitterPhase = BLITTER_PHASE_COUNT_CPU_BUS; if ( BLITTER_RUN_CE ) { /* Continue blitting after 64 bus accesses in 68000 CE mode + check for parallel CPU instruction */ BlitterPhase |= BLITTER_PHASE_IGNORE_LAST_CPU_CYCLES; Blitter_HOG_CPU_IgnoreMaxCpuCycles = BlitterVars.pass_cycles; BlitterState.CountBusCpu = 0; /* Reset CPU bus counter */ } else { /* In non-cycle exact 68000 mode, we run the CPU for 64*4=256 cpu cycles, */ /* which gives a good approximation */ CycInt_AddRelativeInterrupt ( BLITTER_NONHOG_BUS_CPU*4, INT_CPU_CYCLE, INTERRUPT_BLITTER ); } } } /*-----------------------------------------------------------------------*/ /** * This is called when no more CPU cycles should be ignored in case an * instruction was running in parallel to the blitter */ static void Blitter_Stop_IgnoreLastCpuCycles(void) { BlitterPhase &= ~BLITTER_PHASE_IGNORE_LAST_CPU_CYCLES; /* No more CPU in parallel, stop ignoring next CPU cycles */ /* If blitter is completely OFF now, disable the cpu specific part */ if ( BlitterPhase == BLITTER_PHASE_STOP ) M68000_SetBlitter_CE ( false ); } /*-----------------------------------------------------------------------*/ /** * Check if some word or long word registers are accessed using byte * operations at address IoAccessCurrentAddress. * The blitter doesn't allow reading/writing to word registers using bytes, * in such case we must ignore the read/write. * Return true if it's a byte access. */ static bool Blitter_CheckAccess_Byte ( void ) { if ( nIoMemAccessSize == SIZE_BYTE ) { if ( LOG_TRACE_LEVEL(TRACE_BLITTER) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("blitter byte access at address=%x ignored video_cyc=%d %d@%d pc=%x instr_cyc=%d\n" , IoAccessCurrentAddress , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } return true; } return false; } /*-----------------------------------------------------------------------*/ /** * Read blitter halftone ram. */ static void Blitter_Halftone_ReadWord(int index) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_HT_RAM + index + index, BlitterHalftone[index]); } void Blitter_Halftone00_ReadWord(void) { Blitter_Halftone_ReadWord(0); } void Blitter_Halftone01_ReadWord(void) { Blitter_Halftone_ReadWord(1); } void Blitter_Halftone02_ReadWord(void) { Blitter_Halftone_ReadWord(2); } void Blitter_Halftone03_ReadWord(void) { Blitter_Halftone_ReadWord(3); } void Blitter_Halftone04_ReadWord(void) { Blitter_Halftone_ReadWord(4); } void Blitter_Halftone05_ReadWord(void) { Blitter_Halftone_ReadWord(5); } void Blitter_Halftone06_ReadWord(void) { Blitter_Halftone_ReadWord(6); } void Blitter_Halftone07_ReadWord(void) { Blitter_Halftone_ReadWord(7); } void Blitter_Halftone08_ReadWord(void) { Blitter_Halftone_ReadWord(8); } void Blitter_Halftone09_ReadWord(void) { Blitter_Halftone_ReadWord(9); } void Blitter_Halftone10_ReadWord(void) { Blitter_Halftone_ReadWord(10); } void Blitter_Halftone11_ReadWord(void) { Blitter_Halftone_ReadWord(11); } void Blitter_Halftone12_ReadWord(void) { Blitter_Halftone_ReadWord(12); } void Blitter_Halftone13_ReadWord(void) { Blitter_Halftone_ReadWord(13); } void Blitter_Halftone14_ReadWord(void) { Blitter_Halftone_ReadWord(14); } void Blitter_Halftone15_ReadWord(void) { Blitter_Halftone_ReadWord(15); } /*-----------------------------------------------------------------------*/ /** * Read blitter source x increment (0xff8a20). */ void Blitter_SourceXInc_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_SRC_X_INC, (uint16_t)(BlitterRegs.src_x_incr)); } /*-----------------------------------------------------------------------*/ /** * Read blitter source y increment (0xff8a22). */ void Blitter_SourceYInc_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_SRC_Y_INC, (uint16_t)(BlitterRegs.src_y_incr)); } /*-----------------------------------------------------------------------*/ /** * Read blitter source address (0xff8a24). */ void Blitter_SourceAddr_ReadLong(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteLong(REG_SRC_ADDR, BlitterRegs.src_addr); } /*-----------------------------------------------------------------------*/ /** * Read blitter endmask 1. */ void Blitter_Endmask1_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_END_MASK1, BlitterRegs.end_mask_1); } /*-----------------------------------------------------------------------*/ /** * Read blitter endmask 2. */ void Blitter_Endmask2_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_END_MASK2, BlitterRegs.end_mask_2); } /*-----------------------------------------------------------------------*/ /** * Read blitter endmask 3. */ void Blitter_Endmask3_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_END_MASK3, BlitterRegs.end_mask_3); } /*-----------------------------------------------------------------------*/ /** * Read blitter destination x increment (0xff8a2E). */ void Blitter_DestXInc_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_DST_X_INC, (uint16_t)(BlitterRegs.dst_x_incr)); } /*-----------------------------------------------------------------------*/ /** * Read blitter destination y increment (0xff8a30). */ void Blitter_DestYInc_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_DST_Y_INC, (uint16_t)(BlitterRegs.dst_y_incr)); } /*-----------------------------------------------------------------------*/ /** * Read blitter destination address. */ void Blitter_DestAddr_ReadLong(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteLong(REG_DST_ADDR, BlitterRegs.dst_addr); } /*-----------------------------------------------------------------------*/ /** * Read blitter words-per-line register X count. */ void Blitter_WordsPerLine_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_X_COUNT, (uint16_t)(BlitterRegs.x_count & 0xFFFF)); } /*-----------------------------------------------------------------------*/ /** * Read blitter lines-per-bitblock register Y count. */ void Blitter_LinesPerBitblock_ReadWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ IoMem_WriteWord(REG_Y_COUNT, (uint16_t)(BlitterRegs.y_count & 0xFFFF)); } /*-----------------------------------------------------------------------*/ /** * Read blitter halftone operation register. */ void Blitter_HalftoneOp_ReadByte(void) { IoMem_WriteByte(REG_BLIT_HOP, BlitterRegs.hop); } /*-----------------------------------------------------------------------*/ /** * Read blitter logical operation register. */ void Blitter_LogOp_ReadByte(void) { IoMem_WriteByte(REG_BLIT_LOP, BlitterRegs.lop); } /*-----------------------------------------------------------------------*/ /** * Read blitter control register. */ void Blitter_Control_ReadByte(void) { /* busy, hog/blit, smudge, n/a, 4 bits for halftone line number */ IoMem_WriteByte(REG_CONTROL, BlitterRegs.ctrl); } /*-----------------------------------------------------------------------*/ /** * Read blitter skew register. */ void Blitter_Skew_ReadByte(void) { IoMem_WriteByte(REG_SKEW, BlitterRegs.skew); } /*-----------------------------------------------------------------------*/ /** * Write to blitter halftone ram. */ static void Blitter_Halftone_WriteWord(int index) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterHalftone[index] = IoMem_ReadWord(REG_HT_RAM + index + index); } void Blitter_Halftone00_WriteWord(void) { Blitter_Halftone_WriteWord(0); } void Blitter_Halftone01_WriteWord(void) { Blitter_Halftone_WriteWord(1); } void Blitter_Halftone02_WriteWord(void) { Blitter_Halftone_WriteWord(2); } void Blitter_Halftone03_WriteWord(void) { Blitter_Halftone_WriteWord(3); } void Blitter_Halftone04_WriteWord(void) { Blitter_Halftone_WriteWord(4); } void Blitter_Halftone05_WriteWord(void) { Blitter_Halftone_WriteWord(5); } void Blitter_Halftone06_WriteWord(void) { Blitter_Halftone_WriteWord(6); } void Blitter_Halftone07_WriteWord(void) { Blitter_Halftone_WriteWord(7); } void Blitter_Halftone08_WriteWord(void) { Blitter_Halftone_WriteWord(8); } void Blitter_Halftone09_WriteWord(void) { Blitter_Halftone_WriteWord(9); } void Blitter_Halftone10_WriteWord(void) { Blitter_Halftone_WriteWord(10); } void Blitter_Halftone11_WriteWord(void) { Blitter_Halftone_WriteWord(11); } void Blitter_Halftone12_WriteWord(void) { Blitter_Halftone_WriteWord(12); } void Blitter_Halftone13_WriteWord(void) { Blitter_Halftone_WriteWord(13); } void Blitter_Halftone14_WriteWord(void) { Blitter_Halftone_WriteWord(14); } void Blitter_Halftone15_WriteWord(void) { Blitter_Halftone_WriteWord(15); } /*-----------------------------------------------------------------------*/ /** * Write to blitter source x increment. */ void Blitter_SourceXInc_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.src_x_incr = (short)(IoMem_ReadWord(REG_SRC_X_INC) & 0xFFFE); } /*-----------------------------------------------------------------------*/ /** * Write to blitter source y increment. */ void Blitter_SourceYInc_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.src_y_incr = (short)(IoMem_ReadWord(REG_SRC_Y_INC) & 0xFFFE); } /*-----------------------------------------------------------------------*/ /** * Write to blitter source address register (0xff8a24). */ void Blitter_SourceAddr_WriteLong(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ if ( ConfigureParams.System.bAddressSpace24 == true ) BlitterRegs.src_addr = IoMem_ReadLong(REG_SRC_ADDR) & 0x00FFFFFE; /* Normal STF/STE */ else BlitterRegs.src_addr = IoMem_ReadLong(REG_SRC_ADDR) & 0xFFFFFFFE; /* Falcon with extra TT RAM */ } /*-----------------------------------------------------------------------*/ /** * Write to blitter endmask 1. */ void Blitter_Endmask1_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.end_mask_1 = IoMem_ReadWord(REG_END_MASK1); } /*-----------------------------------------------------------------------*/ /** * Write to blitter endmask 2. */ void Blitter_Endmask2_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.end_mask_2 = IoMem_ReadWord(REG_END_MASK2); } /*-----------------------------------------------------------------------*/ /** * Write to blitter endmask 3. */ void Blitter_Endmask3_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.end_mask_3 = IoMem_ReadWord(REG_END_MASK3); } /*-----------------------------------------------------------------------*/ /** * Write to blitter destination x increment. */ void Blitter_DestXInc_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.dst_x_incr = (short)(IoMem_ReadWord(REG_DST_X_INC) & 0xFFFE); } /*-----------------------------------------------------------------------*/ /** * Write to blitter source y increment. */ void Blitter_DestYInc_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ BlitterRegs.dst_y_incr = (short)(IoMem_ReadWord(REG_DST_Y_INC) & 0xFFFE); } /*-----------------------------------------------------------------------*/ /** * Write to blitter destination address register. */ void Blitter_DestAddr_WriteLong(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ if ( ConfigureParams.System.bAddressSpace24 == true ) BlitterRegs.dst_addr = IoMem_ReadLong(REG_DST_ADDR) & 0x00FFFFFE; /* Normal STF/STE */ else BlitterRegs.dst_addr = IoMem_ReadLong(REG_DST_ADDR) & 0xFFFFFFFE; /* Falcon with extra TT RAM */ } /*-----------------------------------------------------------------------*/ /** * Write to blitter words-per-line register X count. */ void Blitter_WordsPerLine_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ uint32_t x_count = (uint32_t)IoMem_ReadWord(REG_X_COUNT); if (x_count == 0) x_count = 65536; BlitterRegs.x_count = x_count; BlitterVars.x_count_reset = x_count; } /*-----------------------------------------------------------------------*/ /** * Write to blitter lines-per-bitblock register Y count. */ void Blitter_LinesPerBitblock_WriteWord(void) { if ( Blitter_CheckAccess_Byte() ) return; /* Ignore access */ uint32_t y_count = (uint32_t)IoMem_ReadWord(REG_Y_COUNT); if (y_count == 0) y_count = 65536; BlitterRegs.y_count = y_count; } /*-----------------------------------------------------------------------*/ /** * Write to blitter halftone operation register. */ void Blitter_HalftoneOp_WriteByte(void) { /* h/ware reg masks out the top 6 bits! */ BlitterRegs.hop = IoMem_ReadByte(REG_BLIT_HOP) & 3; } /*-----------------------------------------------------------------------*/ /** * Write to blitter logical operation register. */ void Blitter_LogOp_WriteByte(void) { /* h/ware reg masks out the top 4 bits! */ BlitterRegs.lop = IoMem_ReadByte(REG_BLIT_LOP) & 0xF; } /*-----------------------------------------------------------------------*/ /** * Write to blitter control register. */ void Blitter_Control_WriteByte(void) { /* Control register bits: * bit 7 : start/stop bit (write) - busy bit (read) * - Turn on Blitter activity and stay "1" until copy finished * bit 6 : Blit-mode bit * - 0: Blit mode, CPU and Blitter get 64 bus accesses in turns * - 1: HOG Mode, Blitter reserves and hogs the bus for as long * as the copy takes, CPU and DMA get no Bus access * bit 5 : Smudge mode * Which line of the halftone pattern to start with is read from * the first source word when the copy starts * bit 4 : not used * bits 0-3 : * The lowest 4 bits contain the halftone pattern line number */ if (LOG_TRACE_LEVEL(TRACE_BLITTER)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("blitter write ctrl=%02x ctrl_old=%02x video_cyc=%d %d@%d pc=%x instr_cyc=%d\n" , IoMem_ReadByte(REG_CONTROL) , BlitterRegs.ctrl , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } BlitterRegs.ctrl = IoMem_ReadByte(REG_CONTROL) & 0xEF; BlitterVars.hog = BlitterRegs.ctrl & 0x40; BlitterVars.smudge = BlitterRegs.ctrl & 0x20; BlitterVars.halftone_line = BlitterRegs.ctrl & 0xF; /* Remove old pending update interrupt */ CycInt_RemovePendingInterrupt(INTERRUPT_BLITTER); /* Start/Stop bit set ? */ if (BlitterRegs.ctrl & 0x80) { if (BlitterRegs.y_count == 0) { /* Blitter transfer is already complete, clear busy and hog bits */ BlitterRegs.ctrl &= ~(0x80|0x40); // TODO : check on real STE, does it clear hog bit too ? } else { /* Start blitter after a small delay */ /* In non-hog mode, the cpu can "restart" the blitter immediately (without waiting */ /* for 64 cpu bus accesses) by setting "start/stop" bit. The cpu can also "stop" the blitter (PAUSE state) */ /* and restart it here (blitter continues from where it was stopped) */ /* This means we should reset internal states only if the blitter was stopped before ; */ /* else if the blitter is restarted we must keep the value of the previous states */ if ( BLITTER_RUN_CE ) { if ( BlitterPhase == BLITTER_PHASE_STOP ) /* Only when blitter is started (not when restarted) */ { M68000_SetBlitter_CE ( true ); Blitter_FlushWordState ( true ); } /* 68000 CE : 4 cycles to complete current bus write to ctrl reg + 4 cycles before blitter request the bus */ Blitter_CyclesBeforeStart = 4 + 4; BlitterPhase = BLITTER_PHASE_PRE_START; Blitter_HOG_CPU_BusCountError = 0; } else { if ( BlitterPhase == BLITTER_PHASE_STOP ) /* Only when blitter is started (not when restarted) */ { Blitter_FlushWordState ( true ); } /* Non 68000 CE mode : start blitting after the end of current instruction */ CycInt_AddRelativeInterrupt( CurrentInstrCycles+WaitStateCycles, INT_CPU_CYCLE, INTERRUPT_BLITTER); } } } else /* Start/Stop bit clear */ { /* If the blitter was running and "start/stop" bit is forced to 0 (to stop the blitter in non-hog mode) */ /* we "pause" the blitter to temporarily stop bus sharing . This does not clear busy bit, it's only */ /* when 'y count' reaches 0 that transfer will be complete and busy bit will be cleared */ /* If blitter is already stopped, we don't do anything */ if ( BlitterPhase == BLITTER_PHASE_COUNT_CPU_BUS ) { BlitterPhase = BLITTER_PHASE_PAUSE; } } } /*-----------------------------------------------------------------------*/ /** * Write to blitter skew register. */ void Blitter_Skew_WriteByte(void) { BlitterRegs.skew = IoMem_ReadByte(REG_SKEW); BlitterVars.fxsr = (BlitterRegs.skew & 0x80)?1:0; BlitterVars.nfsr = (BlitterRegs.skew & 0x40)?1:0; BlitterVars.skew = BlitterRegs.skew & 0xF; } /*-----------------------------------------------------------------------*/ /** * Handler which continues blitting after 64 bus cycles in non-CE mode */ void Blitter_InterruptHandler(void) { CycInt_AcknowledgeInterrupt(); if (BlitterRegs.ctrl & 0x80) { Blitter_Start(); } } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of Blitter variables. */ void Blitter_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&BlitterRegs, sizeof(BlitterRegs)); MemorySnapShot_Store(&BlitterVars, sizeof(BlitterVars)); MemorySnapShot_Store(&BlitterHalftone, sizeof(BlitterHalftone)); MemorySnapShot_Store(&BlitterState, sizeof(BlitterState)); MemorySnapShot_Store(&BlitterPhase, sizeof(BlitterPhase)); MemorySnapShot_Store(&Blitter_CyclesBeforeStart, sizeof(Blitter_CyclesBeforeStart)); MemorySnapShot_Store(&Blitter_HOG_CPU_FromBusAccess, sizeof(Blitter_HOG_CPU_FromBusAccess)); MemorySnapShot_Store(&Blitter_HOG_CPU_BlitterStartDuringBusAccess, sizeof(Blitter_HOG_CPU_BlitterStartDuringBusAccess)); MemorySnapShot_Store(&Blitter_HOG_CPU_BusCountError, sizeof(Blitter_HOG_CPU_BusCountError)); MemorySnapShot_Store(&Blitter_HOG_CPU_IgnoreMaxCpuCycles, sizeof(Blitter_HOG_CPU_IgnoreMaxCpuCycles)); if ( !bSave ) { /* On restore, we set blitter specific CPU functions if needed */ if ( BlitterPhase && BLITTER_RUN_CE ) M68000_SetBlitter_CE ( true ); } } /*-----------------------------------------------------------------------*/ /** * Show Blitter register values. */ void Blitter_Info(FILE *fp, uint32_t dummy) { BLITTERREGS *regs = &BlitterRegs; fprintf(fp, "src addr (0x%x): 0x%06x\n", REG_SRC_ADDR, regs->src_addr); fprintf(fp, "dst addr (0x%x): 0x%06x\n", REG_DST_ADDR, regs->dst_addr); fprintf(fp, "x count (0x%x): %u\n", REG_X_COUNT, regs->x_count); fprintf(fp, "y count (0x%x): %u\n", REG_Y_COUNT, regs->y_count); fprintf(fp, "src X-inc (0x%x): %hd\n", REG_SRC_X_INC, regs->src_x_incr); fprintf(fp, "src Y-inc (0x%x): %hd\n", REG_SRC_Y_INC, regs->src_y_incr); fprintf(fp, "dst X-inc (0x%x): %hd\n", REG_DST_X_INC, regs->dst_x_incr); fprintf(fp, "dst Y-inc (0x%x): %hd\n", REG_DST_Y_INC, regs->dst_y_incr); fprintf(fp, "end mask1 (0x%x): 0x%04x\n", REG_END_MASK1, regs->end_mask_1); fprintf(fp, "end mask2 (0x%x): 0x%04x\n", REG_END_MASK2, regs->end_mask_2); fprintf(fp, "end mask3 (0x%x): 0x%04x\n", REG_END_MASK3, regs->end_mask_3); fprintf(fp, "HOP (0x%x): 0x%02x\n", REG_BLIT_HOP, regs->hop); fprintf(fp, "LOP (0x%x): 0x%02x\n", REG_BLIT_LOP, regs->lop); /* List control bits: busy, hog/blit, smudge, n/a, 4 bits for halftone line number ? */ fprintf(fp, "control (0x%x): 0x%02x\n", REG_CONTROL, regs->ctrl); fprintf(fp, "skew (0x%x): 0x%02x\n", REG_SKEW, regs->skew); fprintf(fp, "Note: internally changed register values aren't visible to breakpoints\nor in memdump output until emulated code reads or writes them!\n"); } /*-----------------------------------------------------------------------*/ /** * This is called from the CPU emulation before doing a memory access. */ void Blitter_HOG_CPU_mem_access_before ( int bus_count ) { //fprintf ( stderr , "cpu_bus before phase=%d bus=%d %x %d cur_cyc=%lu start_acces=%d\n" , BlitterPhase , BusMode , BlitterRegs.ctrl , BlitterState.CountBusCpu , currcycle/cpucycleunit , Blitter_HOG_CPU_BlitterStartDuringBusAccess ); Blitter_HOG_CPU_FromBusAccess = 1; /* CPU bus access in progress */ /* NOTE [NP] It seems there's a bug in the blitter when it counts his own bus accesses : */ /* if a CPU bus access happens during the "pre start" blitter phase, then the blitter will wrongly */ /* count it as a blitter bus access and will do only 63 bus accesses during copy instead of 64 in non-hog mode */ if ( BlitterPhase == BLITTER_PHASE_PRE_START ) Blitter_HOG_CPU_BusCountError = 1; /* If the bus is accessed by the CPU and we're ignoring CPU cycles that occurred in parallel */ /* during the blitter's transfer, then we disable IGNORE_LAST_CPU_CYCLES as the CPU was stalled */ /* after this point because it couldn't access the bus (which was owned by the blitter) */ else if ( BlitterPhase & BLITTER_PHASE_IGNORE_LAST_CPU_CYCLES ) { Blitter_Stop_IgnoreLastCpuCycles(); /* No more CPU in parallel, stop ignoring next CPU cycles */ } } /*-----------------------------------------------------------------------*/ /** * This is called from the CPU emulation after doing a memory access. * Here, we count the number of bus accesses made by the CPU in non-hog mode. * When the CPU reaches 64 accesses, we restart the blitter. */ void Blitter_HOG_CPU_mem_access_after ( int bus_count ) { //fprintf ( stderr , "cpu_bus after phase=%d bus=%d %x %d cur_cyc=%lu start_acces=%d\n" , BlitterPhase , BusMode , BlitterRegs.ctrl , BlitterState.CountBusCpu , currcycle/cpucycleunit,Blitter_HOG_CPU_BlitterStartDuringBusAccess ); if ( BlitterPhase & BLITTER_PHASE_COUNT_CPU_BUS ) { if ( Blitter_HOG_CPU_BlitterStartDuringBusAccess ) { Blitter_HOG_CPU_BlitterStartDuringBusAccess = 0; return; } BlitterState.CountBusCpu += bus_count; if ( BlitterState.CountBusCpu >= BLITTER_NONHOG_BUS_CPU ) { Blitter_CyclesBeforeStart = 4; BlitterPhase = BLITTER_PHASE_PRE_START; Blitter_HOG_CPU_BusCountError = 0; } } Blitter_HOG_CPU_FromBusAccess = 0; /* CPU bus access is done */ } /*-----------------------------------------------------------------------*/ /** * This is called from the CPU emulation before "do_cycles()" to check * if part of an instruction was executed simultaneously to the blitter. * If so, we don't count those CPU cycles (as they were already counted * during the blitter part) and we skip the "do_cycles()" * We skip CPU cycles until Blitter_HOG_CPU_IgnoreMaxCpuCycles=0 or until * we reach a bus access (whichever comes first) */ int Blitter_Check_Simultaneous_CPU ( void ) { // static int cpu_skip_cycles = 0; // fprintf(stderr, "blitter simult phase=%d bus=%d %x cur_cyc=%lu\n", BlitterPhase, BusMode, BlitterRegs.ctrl, currcycle/cpucycleunit); if ( BlitterPhase & BLITTER_PHASE_IGNORE_LAST_CPU_CYCLES ) { Blitter_HOG_CPU_IgnoreMaxCpuCycles -= 2; if ( Blitter_HOG_CPU_IgnoreMaxCpuCycles <= 0 ) Blitter_Stop_IgnoreLastCpuCycles(); /* No more CPU in parallel, stop ignoring next CPU cycles */ // cpu_skip_cycles += 2; // fprintf(stderr, "blitter cpu skip %d cycles, max skip %d\n", cpu_skip_cycles, Blitter_HOG_CPU_IgnoreMaxCpuCycles); return 1; /* Skip next do_cycles() */ } // cpu_skip_cycles = 0; return 0; /* Don't skip next do_cycles() */ } /*-----------------------------------------------------------------------*/ /** * This is called from the cpu emulation after "do_cycles()" to count * the number of cycles since the blitter was (re)started. * After Blitter_CyclesBeforeStart cycles, we go to the next phase BLITTER_PHASE_START * to handle bus to the blitter and to copy data. * We must ensure the cpu is not doing an atomic RMW bus access ; if so, blitter must be started * later as it can't get the bus at the moment */ void Blitter_HOG_CPU_do_cycles_after ( int cycles ) { //fprintf ( stderr , "blitter do_cyc_after phase=%d bus=%d %x cyc=%d cur_cyc=%lu\n" , BlitterPhase , BusMode , BlitterRegs.ctrl , cycles , currcycle/cpucycleunit ); if ( BlitterPhase == BLITTER_PHASE_PRE_START ) { Blitter_CyclesBeforeStart -= cycles; if ( ( Blitter_CyclesBeforeStart <= 0 ) && ( cpu_bus_rmw == false ) ) { /* This is specific to our cpu emulation, to avoid counting the current */ /* bus access (during which the blitter starts) as the first cpu bus access in non-hog mode */ if ( Blitter_HOG_CPU_FromBusAccess ) Blitter_HOG_CPU_BlitterStartDuringBusAccess = 1; else Blitter_HOG_CPU_BlitterStartDuringBusAccess = 0; /* Start the main blitter part */ BlitterPhase = BLITTER_PHASE_START; Blitter_Start(); } } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cart.c000066400000000000000000000102101504763705000224170ustar00rootroot00000000000000/* Hatari - cart.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Cartridge program To load programs into memory, through TOS, we need to intercept GEMDOS so we can relocate/execute programs via GEMDOS call $4B (Pexec). We have some 68000 assembler, located at 0xFA0000 (cartridge memory), which is used as our new GEMDOS handler. This checks if we need to intercept the call. The assembler routine can be found in 'cart_asm.s', and has been converted to a byte array and stored in 'Cart_data[]' (see cartData.c). */ const char Cart_fileid[] = "Hatari cart.c"; /* 2007/12/09 [NP] Change the function associated to opcodes $8, $a and $c only if hard drive */ /* emulation is ON. Else, these opcodes should give illegal instructions (also */ /* see cpu/newcpu.c). */ #include "main.h" #include "cart.h" #include "configuration.h" #include "file.h" #include "log.h" #include "m68000.h" #include "stMemory.h" #include "inffile.h" #include "tos.h" #include "vdi.h" #include "hatari-glue.h" #include "newcpu.h" #include "cartData.c" /* Possible cartridge file extensions to scan for */ static const char * const psCartNameExts[] = { ".img", ".rom", ".stc", NULL }; /** * Load an external cartridge image file. */ static void Cart_LoadImage(void) { uint8_t *pCartData; long nCartSize; char *pCartFileName = ConfigureParams.Rom.szCartridgeImageFileName; /* Try to load the image file: */ pCartData = File_Read(pCartFileName, &nCartSize, psCartNameExts); if (!pCartData) { Log_Printf(LOG_ERROR, "Failed to load '%s'.\n", pCartFileName); return; } if (nCartSize < 40 || (nCartSize > 0x20000 && nCartSize != 0x20004)) { Log_Printf(LOG_ERROR, "Cartridge file '%s' has illegal size.\n", pCartFileName); free(pCartData); return; } /* There are two type of cartridge images, normal 1:1 images which are * always smaller than or equal to 0x20000 bytes, and the .STC images, * which are always 0x20004 bytes (the first 4 bytes are a dummy header). * So if size is 0x20004 bytes we have to skip the first 4 bytes */ if (nCartSize == 0x20004) { memcpy(&RomMem[0xfa0000], pCartData+4, 0x20000); } else { memcpy(&RomMem[0xfa0000], pCartData, nCartSize); } free(pCartData); } /** * Check whether we want to use internal cartridge code, i.e. when user wants * extended VDI resolution, use Autostarting, or to trace GEMDOS, VDI or AES * (OS_BASE does subset of GEMDOS tracing). * But don't use it on TOS 0.00, it does not work there. */ bool Cart_UseBuiltinCartridge(void) { #define NEEDS_CART (TRACE_OS_GEMDOS | TRACE_OS_BASE | TRACE_OS_VDI | TRACE_OS_AES) return (bUseVDIRes || INF_Overriding(AUTOSTART_INTERCEPT) || ConfigureParams.HardDisk.bUseHardDiskDirectories || LOG_TRACE_LEVEL(NEEDS_CART)) && (TosVersion >= 0x100 || !bUseTos); } /** * Copy ST GEMDOS intercept program image into cartridge memory space * or load an external cartridge file. * The intercept program is part of Hatari and used as an interface to the host * file system through GemDOS. It is also needed for Line-A-Init when using * extended VDI resolutions. */ void Cart_ResetImage(void) { /* "Clear" cartridge ROM space */ memset(&RomMem[0xfa0000], 0xff, 0x20000); /* Print a warning if user tries to use an external cartridge file * together with something else requiring cartridge code: * - GEMDOS hard disk emulation * - extended VDI resolution * - GEMDOS/AES/VDI tracing */ if (strlen(ConfigureParams.Rom.szCartridgeImageFileName) > 0 && (bUseVDIRes || ConfigureParams.HardDisk.bUseHardDiskDirectories || (LogTraceFlags & (TRACE_OS_GEMDOS | TRACE_OS_BASE | TRACE_OS_VDI | TRACE_OS_AES)))) { Log_AlertDlg(LOG_ERROR, "Cartridge disabled! It can't be used with VDI mode, GEMDOS HD emulation nor their tracing."); } if (Cart_UseBuiltinCartridge()) { /* Copy built-in cartridge data into the cartridge memory of the ST */ memcpy(&RomMem[0xfa0000], Cart_data, sizeof(Cart_data)); } else if (strlen(ConfigureParams.Rom.szCartridgeImageFileName) > 0) { /* Load external image file: */ Cart_LoadImage(); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cartData.c000066400000000000000000000112471504763705000232240ustar00rootroot00000000000000/* Hatari - cartData.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. The assembled cartridge Pexec() program (source code can be found in the file cart_asm.s). NOTE: This file is included by cart.c - do not compile and link this file separately! */ const char CartData_fileid[] = "Hatari cartData.c"; /* This is the assembled code from cart_asm.s with our gemdos handler. NOTE: Remove first 0x1c (PRG_HEADER_SIZE) bytes from the assembled program file or use an assembler like TurboAss or vasm that can generate absolute binary images. For example : vasmm68k_mot -devpac -showopt -o cart_asm.bin -Fbin cart_asm.s hexdump -v -e ' 16/1 "0x%02x," "\n" ' cart_asm.bin > cart_asm.txt Then replace the data below in Cart_data[] with the content of cart_asm.txt (edit the last line to remove empty "0x,") */ const uint8_t Cart_data[] = { 0xab,0xcd,0xef,0x42,0x00,0x00,0x00,0x00,0x08,0xfa,0x00,0x58,0x00,0xfa,0x00,0x5e, 0x58,0x00,0x32,0x29,0x00,0x00,0x02,0x92,0x48,0x41,0x54,0x41,0x52,0x49,0x2e,0x54, 0x4f,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x08,0x69,0x0a,0x66,0x02, 0x4e,0x73,0x2f,0x3a,0xff,0xf0,0x4e,0x75,0x4e,0x41,0x4f,0xef,0x00,0x10,0x4a,0x80, 0x6b,0xee,0x00,0x09,0x69,0xec,0x67,0xe8,0x2f,0x00,0x2f,0x08,0x3f,0x3c,0x00,0x49, 0x4e,0x41,0x5c,0x8f,0x20,0x1f,0x4e,0x73,0xa0,0x00,0x00,0x0a,0x4e,0x75,0x48,0x7a, 0x00,0x16,0x3f,0x3c,0x00,0x09,0x4e,0x41,0x5c,0x8f,0x3f,0x3c,0x00,0x07,0x4e,0x41, 0x54,0x8f,0x42,0x67,0x4e,0x41,0x1b,0x45,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d, 0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x0d,0x0a,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x20,0x48,0x61,0x74,0x61,0x72,0x69,0x20,0x6b,0x65,0x79,0x62, 0x6f,0x61,0x72,0x64,0x20,0x73,0x68,0x6f,0x72,0x74,0x63,0x75,0x74,0x73,0x0d,0x0a, 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d, 0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d, 0x3d,0x0d,0x0a,0x0d,0x0a,0x20,0x46,0x31,0x31,0x20,0x3a,0x20,0x74,0x6f,0x67,0x67, 0x6c,0x65,0x20,0x66,0x75,0x6c,0x6c,0x73,0x63,0x72,0x65,0x65,0x6e,0x2f,0x77,0x69, 0x6e,0x64,0x6f,0x77,0x65,0x64,0x20,0x6d,0x6f,0x64,0x65,0x0d,0x0a,0x20,0x46,0x31, 0x32,0x20,0x3a,0x20,0x61,0x63,0x74,0x69,0x76,0x61,0x74,0x65,0x20,0x74,0x68,0x65, 0x20,0x73,0x65,0x74,0x75,0x70,0x20,0x47,0x55,0x49,0x20,0x6f,0x66,0x20,0x48,0x61, 0x74,0x61,0x72,0x69,0x0d,0x0a,0x0d,0x0a,0x41,0x6c,0x6c,0x20,0x6f,0x74,0x68,0x65, 0x72,0x20,0x73,0x68,0x6f,0x72,0x74,0x63,0x75,0x74,0x73,0x20,0x61,0x72,0x65,0x20, 0x61,0x63,0x74,0x69,0x76,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x0d,0x0a,0x70,0x72, 0x65,0x73,0x73,0x69,0x6e,0x67,0x20,0x41,0x6c,0x74,0x47,0x72,0x20,0x6f,0x72,0x20, 0x52,0x69,0x67,0x68,0x74,0x2d,0x41,0x6c,0x74,0x20,0x6f,0x72,0x20,0x4d,0x65,0x74, 0x61,0x20,0x6b,0x65,0x79,0x0d,0x0a,0x74,0x6f,0x67,0x65,0x74,0x68,0x65,0x72,0x20, 0x77,0x69,0x74,0x68,0x20,0x6f,0x6e,0x65,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20, 0x66,0x6f,0x6c,0x6c,0x6f,0x77,0x69,0x6e,0x67,0x20,0x6b,0x65,0x79,0x73,0x3a,0x0d, 0x0a,0x0d,0x0a,0x20,0x61,0x20,0x3a,0x20,0x52,0x65,0x63,0x6f,0x72,0x64,0x20,0x61, 0x6e,0x69,0x6d,0x61,0x74,0x69,0x6f,0x6e,0x0d,0x0a,0x20,0x67,0x20,0x3a,0x20,0x47, 0x72,0x61,0x62,0x20,0x61,0x20,0x73,0x63,0x72,0x65,0x65,0x6e,0x73,0x68,0x6f,0x74, 0x0d,0x0a,0x20,0x69,0x20,0x3a,0x20,0x4c,0x65,0x61,0x76,0x65,0x20,0x66,0x75,0x6c, 0x6c,0x20,0x73,0x63,0x72,0x65,0x65,0x6e,0x20,0x26,0x20,0x69,0x63,0x6f,0x6e,0x69, 0x66,0x79,0x20,0x77,0x69,0x6e,0x64,0x6f,0x77,0x0d,0x0a,0x20,0x6a,0x20,0x3a,0x20, 0x6a,0x6f,0x79,0x73,0x74,0x69,0x63,0x6b,0x20,0x76,0x69,0x61,0x20,0x6b,0x65,0x79, 0x20,0x6a,0x6f,0x79,0x73,0x74,0x69,0x63,0x6b,0x20,0x6f,0x6e,0x2f,0x6f,0x66,0x66, 0x0d,0x0a,0x20,0x6d,0x20,0x3a,0x20,0x6d,0x6f,0x75,0x73,0x65,0x20,0x67,0x72,0x61, 0x62,0x0d,0x0a,0x20,0x72,0x20,0x3a,0x20,0x77,0x61,0x72,0x6d,0x20,0x72,0x65,0x73, 0x65,0x74,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x53,0x54,0x0d,0x0a,0x20,0x63, 0x20,0x3a,0x20,0x63,0x6f,0x6c,0x64,0x20,0x72,0x65,0x73,0x65,0x74,0x20,0x6f,0x66, 0x20,0x74,0x68,0x65,0x20,0x53,0x54,0x0d,0x0a,0x20,0x73,0x20,0x3a,0x20,0x65,0x6e, 0x61,0x62,0x6c,0x65,0x2f,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,0x20,0x73,0x6f,0x75, 0x6e,0x64,0x0d,0x0a,0x20,0x71,0x20,0x3a,0x20,0x71,0x75,0x69,0x74,0x20,0x74,0x68, 0x65,0x20,0x65,0x6d,0x75,0x6c,0x61,0x74,0x6f,0x72,0x0d,0x0a,0x20,0x78,0x20,0x3a, 0x20,0x74,0x6f,0x67,0x67,0x6c,0x65,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x2f,0x6d, 0x61,0x78,0x20,0x73,0x70,0x65,0x65,0x64,0x0d,0x0a,0x20,0x79,0x20,0x3a,0x20,0x65, 0x6e,0x61,0x62,0x6c,0x65,0x2f,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,0x20,0x73,0x6f, 0x75,0x6e,0x64,0x20,0x72,0x65,0x63,0x6f,0x72,0x64,0x69,0x6e,0x67,0x0d,0x0a,0x00, }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cart_asm.s000066400000000000000000000064611504763705000233140ustar00rootroot00000000000000; Cartridge assembler code. ; 68000 code that is used for starting programs from the emulated GEMDOS harddisk ; and for using bigger VDI resolutions ; See cartData.c for instruction to compile this file ; Force pc relative mode opt a+ ; Hatari's "illegal" (free) opcodes: GEMDOS_OPCODE equ 8 PEXEC_OPCODE equ 9 SYSINIT_OPCODE equ 10 VDI_OPCODE equ 12 ; System variables: _longframe equ $059E org $fa0000 ; This is the cartridge header: dc.l $ABCDEF42 ; C-FLAG (magic value) dc.l $00000000 ; C-NEXT dc.l sys_init+$08000000 ; C-INIT - flag has bit 3 set = before disk boot, but after GEMDOS init dc.l infoprgstart ; C-RUN dc.w %0101100000000000 ; C-TIME dc.w %0011001000101001 ; C-DATE dc.l infoprgend-infoprgstart ; C-BSIZ, offset: $14 dc.b 'HATARI.TOS',0,0 ; C-NAME even old_gemdos: ds.l 1 ; has to match the CART_OLDGEMDOS define! vdi_opcode: dc.w VDI_OPCODE ; Address to call after Trap #2 (VDI), causes illegal instruction ; New GEMDOS vector (0x84) new_gemdos: dc.w GEMDOS_OPCODE ; Returns NEG as run old vector, ZERO to return or OVERFLOW to run pexec bvs.s pexec bne.s go_oldgemdos do_rte: rte ; Branch to old GEMDOS go_oldgemdos: move.l old_gemdos(pc),-(sp) ; Set PC to 'old_gemdos' and continue execution, WITHOUT corrupting registers! rts pexec: ; GemDOS_Pexec() pushed the parameters onto the stack already trap #1 lea 16(sp),sp tst.l d0 bmi.s do_rte dc.w PEXEC_OPCODE bvs.s go_oldgemdos beq.s do_rte ; If we end up here, we've got to clean up move.l d0,-(sp) move.l a0,-(sp) move.w #73,-(sp) ; Mfree trap #1 addq.l #6,sp move.l (sp)+,d0 rte ; This code is called during TOS' boot sequence. ; It gets a pointer to the Line-A variables and uses an illegal opcode ; to run our system initialization code in OpCode_SysInit(). sys_init: dc.w $A000 ; Line-A init (needed for VDI resolutions) dc.w SYSINIT_OPCODE ; Illegal opcode to call OpCode_SysInit() rts ; This code is run when the user starts the HATARI.PRG ; in the cartridge. It simply displays some information text. infoprgstart: pea infotext(pc) move.w #9,-(sp) trap #1 ; Cconws - display the information text addq.l #6,sp move.w #7,-(sp) trap #1 ; Crawcin - wait for a key addq.l #2,sp clr.w -(sp) trap #1 ; Pterm0 infotext: dc.b 27,'E',13,10 dc.b ' =========================',13,10 dc.b ' Hatari keyboard shortcuts',13,10 dc.b ' =========================',13,10 dc.b 13,10 dc.b ' F11 : toggle fullscreen/windowed mode',13,10 dc.b ' F12 : activate the setup GUI of Hatari',13,10 dc.b 13,10 dc.b 'All other shortcuts are activated by',13,10 dc.b 'pressing AltGr or Right-Alt or Meta key',13,10 dc.b 'together with one of the following keys:',13,10 dc.b 13,10 dc.b ' a : Record animation',13,10 dc.b ' g : Grab a screenshot',13,10 dc.b ' i : Leave full screen & iconify window',13,10 dc.b ' j : joystick via key joystick on/off',13,10 dc.b ' m : mouse grab',13,10 dc.b ' r : warm reset of the ST',13,10 dc.b ' c : cold reset of the ST',13,10 dc.b ' s : enable/disable sound',13,10 dc.b ' q : quit the emulator',13,10 dc.b ' x : toggle normal/max speed',13,10 dc.b ' y : enable/disable sound recording',13,10 dc.b 0 infoprgend: END hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cfgopts.c000066400000000000000000000272201504763705000231440ustar00rootroot00000000000000/* * Hatari - cfgopts.c * * The functions in this file are used to load and save the ASCII * configuration file. * Original information text follows: */ /*<<---------------[ cfgopts.c ]------------------------/ / / / Functional / / Description: Configuration file I/O / / / / Input : Configuration file name / / Configuration parameters in a structure / / / / Process : Interpret information by parameter and read or / / write back to the configuration file. / / / / Output : updated configuration file or updated structure. / / / / Programmer : Jeffry J. Brickley / / / /---------------------------------------------------------------------*/ /*---------------------------------------------------------------------/ / / Description: CfgOpts is based on GETOPTS by Bob Stout. It will / process a configuration file based one words and / store it in a structure pointing to physical data / area for each storage item. / i.e. ???.CFG: / Port=1 / work_space=C:\temp / menus=TRUE / user=Jeffry Brickley / will write to the following structure: / struct Config_Tag configs[] = { / {"port", Int_Tag, &port_number}, / {"work_space", String_Tag, &work_space}, / {"menus", Bool_Tag, &menu_flag}, / {"user", String_Tag, &User_name}, / {NULL, Error_Tag, NULL} / }; / Note that the structure must always be terminated by a NULL row as / was the same with GETOPTS. This however is slightly more / complicated than scanning the command line (but not by much) for / data as there can be more variety in words than letters and an / number of data items limited only by memory. / / Like the original code from which this was taken, this is released / to the Public Domain. I cannot make any guarantees other than these / work for me and I find them useful. Feel free to pass these on to / a friend, but please do not charge him.... / /---------------------------------------------------------------------*/ const char CfgOpts_fileid[] = "Hatari cfgopts.c"; #include #include #include #include "main.h" #include "cfgopts.h" #include "file.h" #include "str.h" #include "keymap.h" #include "log.h" static int parse_input_config_entry(const struct Config_Tag *ptr) { const char *next; int type = ptr->type; /* get actual config value */ next = Str_Trim(strtok(NULL, "")); if (next == NULL) { if (type == String_Tag || type == Key_Tag) next = ""; /* field with empty string */ else type = Error_Tag; } switch (type) /* check type */ { case Bool_Tag: if (!strcasecmp(next,"FALSE")) *(bool *)ptr->buf = false; else if (!strcasecmp(next,"TRUE")) *(bool *)ptr->buf = true; break; case Char_Tag: sscanf(next, "%c", (char *)ptr->buf); break; case Short_Tag: sscanf(next, "%hd", (short *)ptr->buf); break; case Int_Tag: sscanf(next, "%d", (int *)ptr->buf); break; case Long_Tag: sscanf(next, "%ld", (long *)ptr->buf); break; case Float_Tag: sscanf(next, "%g", (float *)ptr->buf); break; case Double_Tag: sscanf(next, "%lg", (double *)ptr->buf); break; case String_Tag: strcpy((char *)ptr->buf, next); break; case Key_Tag: *(int *)ptr->buf = Keymap_GetKeyFromName(next); break; case Error_Tag: default: return -1; } return 0; } /** * ---------------------------------------------------------------------/ * / reads from an input configuration (INI) file. * /--------------------------------------------------------------------- * >>------[ input_config() ]-------------[ 08-02-95 14:02PM ]------/ * / return value: * / int ; number of records read or -1 on error * / parameters: * / char *filename ; filename of INI style file * / struct Config_Tag configs[]; Configuration structure * / char *header ; INI header name (i.e. "[TEST]") * /-------------------------------------------------------------------<< */ int input_config(const char *filename, const struct Config_Tag configs[], const char *header) { const struct Config_Tag *ptr; int count = 0, lineno = 0; FILE *file; char *fptr,*tok; char line[1024]; file = fopen(filename,"r"); if (file == NULL) return -1; /* return error designation. */ if (header != NULL) { do { fptr = Str_Trim(fgets(line, sizeof(line), file)); /* get input line */ if (fptr == NULL) break; } while (strncmp(fptr, header, strlen(header))); } if ( !feof(file) ) do { fptr = Str_Trim(fgets(line, sizeof(line), file)); /* get input line */ if (fptr == NULL) continue; lineno++; if (fptr[0] == '#') continue; /* skip comments */ if (fptr[0] == '[') continue; /* skip next header */ tok = Str_Trim(strtok(fptr, "=")); /* get first token */ if (tok == NULL) continue; for (ptr = configs; ptr->buf; ++ptr) /* scan for token */ { if (!strcmp(tok, ptr->code)) /* got a match? */ { if (parse_input_config_entry(ptr) == 0) count++; else Log_Printf(LOG_WARN, "Error in Config file %s on line %d\n", filename, lineno); } } } while (fptr != NULL && fptr[0] != '['); fclose(file); return count; } /** * Write out an settings line */ static int write_token(FILE *outfile, const struct Config_Tag *ptr) { fprintf(outfile,"%s = ",ptr->code); switch (ptr->type) /* check type */ { case Bool_Tag: fprintf(outfile,"%s\n", *((bool *)(ptr->buf)) ? "TRUE" : "FALSE"); break; case Char_Tag: fprintf(outfile, "%c\n", *((char *)(ptr->buf))); break; case Short_Tag: fprintf(outfile, "%hd\n", *((short *)(ptr->buf))); break; case Int_Tag: fprintf(outfile, "%d\n", *((int *)(ptr->buf))); break; case Long_Tag: fprintf(outfile, "%ld\n", *((long *)(ptr->buf))); break; case Float_Tag: fprintf(outfile, "%g\n", *((float *)ptr->buf)); break; case Double_Tag: fprintf(outfile, "%g\n", *((double *)ptr->buf)); break; case String_Tag: fprintf(outfile, "%s\n",(char *)ptr->buf); break; case Key_Tag: fprintf(outfile, "%s\n", Keymap_GetKeyName(*(int *)ptr->buf)); break; case Error_Tag: default: Log_Printf(LOG_WARN, "Internal error in Config structure (contact developers)\n"); return -1; } return 0; } /** * Write given section header and tokens for that * Return number of written tokens */ static int write_header_tokens(FILE *fp, const struct Config_Tag *ptr, const char *header) { int count = 0; if (header != NULL) { fprintf(fp, "%s\n", header); } for (; ptr->buf; ++ptr) /* scan for token */ { if (write_token(fp, ptr) == 0) ++count; } fprintf(fp, "\n"); return count; } /** * ---------------------------------------------------------------------/ * / updates an input configuration (INI) file from a structure. * /--------------------------------------------------------------------- * >>------[ update_config() ]-------------[ 08-02-95 14:02PM ]------/ * / return value: * / int ; Number of records read & updated * / parameters: * / char *filename ; filename of INI file * / struct Config_Tag configs[]; Configuration structure * / char *header ; INI header name (i.e. "[TEST]") * /-------------------------------------------------------------------<< */ int update_config(const char *filename, const struct Config_Tag configs[], const char *header) { const struct Config_Tag *ptr; int count=0, retval; FILE *cfgfile, *tempfile; char *fptr, *tok; char line[1024]; char *psTempCfgName; cfgfile = fopen(filename, "r"); /* If the cfg file does not yet exists, we can create it directly: */ if (cfgfile == NULL) { cfgfile = fopen(filename, "w"); if (cfgfile == NULL) return -1; /* return error designation. */ count = write_header_tokens(cfgfile, configs, header); fclose(cfgfile); return count; } tempfile = File_OpenTempFile(&psTempCfgName); /* Open a temporary file for output */ if (tempfile == NULL) { perror("update_config"); fclose(cfgfile); return -1; /* return error designation. */ } if (header != NULL) { int headerlen = strlen(header); do { fptr = Str_Trim(fgets(line, sizeof(line), cfgfile)); /* get input line */ if (fptr == NULL) break; fprintf(tempfile, "%s\n", fptr); } while (strncmp(fptr, header, headerlen)); } if (feof(cfgfile)) { count += write_header_tokens(tempfile, configs, header); } else { char *savedtokenflags = NULL; /* Array to log the saved tokens */ int numtokens = 0; /* Total number of tokens to save */ /* Find total number of tokens: */ for (ptr=configs; ptr->buf; ++ptr) { numtokens += 1; } if (numtokens) { savedtokenflags = calloc(numtokens, sizeof(char)); } for(;;) { fptr = Str_Trim(fgets(line, sizeof(line), cfgfile)); /* get input line */ /* error or eof? */ if (fptr == NULL) break; if (fptr[0] == '#') { fprintf(tempfile, "%s\n", fptr); continue; /* skip comments */ } if (fptr[0] == '[') { break; } tok = Str_Trim(strtok(fptr, "=")); /* get first token */ if (tok != NULL) { int i = 0; for (ptr = configs; ptr->buf; ++ptr, i++) /* scan for token */ { if (!strcmp(tok, ptr->code)) /* got a match? */ { if (write_token(tempfile, ptr) == 0) { if (savedtokenflags) savedtokenflags[i] = true; count += 1; } } } } } /* Write remaining (new?) tokens that were not in the configuration file, yet */ if (count != numtokens && savedtokenflags != NULL) { int i; for (ptr = configs, i = 0; ptr->buf; ++ptr, i++) { if (!savedtokenflags[i]) { if (write_token(tempfile, ptr) == 0) { count += 1; Log_Printf(LOG_INFO, "Wrote new token %s -> %s \n", header, ptr->code); } } } } if (savedtokenflags) { free(savedtokenflags); savedtokenflags = NULL; } if (!feof(cfgfile) && fptr != NULL) fprintf(tempfile, "\n%s\n", line); for(;;) { fptr = Str_Trim(fgets(line, sizeof(line), cfgfile)); /* get input line */ if (fptr == NULL) break; fprintf(tempfile, "%s\n", fptr); } } /* Re-open the config file for writing: */ fclose(cfgfile); cfgfile = fopen(filename, "wb"); if (cfgfile == NULL || fseek(tempfile, 0, SEEK_SET) != 0) { retval = -1; goto cleanup; } /* Now copy the temporary file to the configuration file: */ retval = count; while(!(feof(tempfile) || ferror(cfgfile))) { size_t copycount; copycount = fread(line, sizeof(char), sizeof(line), tempfile); if (copycount == 0) break; if (fwrite(line, sizeof(char), copycount, cfgfile) != copycount) { retval = -1; break; } } cleanup: if (cfgfile) { if (ferror(cfgfile)) perror("update_config"); fclose(cfgfile); } if (tempfile) { fclose(tempfile); if (psTempCfgName) unlink(psTempCfgName); } return retval; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/change.c000066400000000000000000000441661504763705000227340ustar00rootroot00000000000000/* Hatari - change.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This code handles run-time configuration changes. We keep all our configuration details in a structure called 'ConfigureParams'. Before doing he changes, a backup copy is done of this structure. When the changes are done, these are compared to see whether emulator needs to be rebooted */ const char Change_fileid[] = "Hatari change.c"; #include #include "main.h" #include "configuration.h" #include "audio.h" #include "change.h" #include "dialog.h" #include "floppy.h" #include "fdc.h" #include "gemdos.h" #include "hdc.h" #include "ide.h" #include "ioMem.h" #include "joy.h" #include "keymap.h" #include "m68000.h" #include "midi.h" #include "ncr5380.h" #include "options.h" #include "printer.h" #include "reset.h" #include "rs232.h" #include "scc.h" #include "screen.h" #include "sound.h" #include "statusbar.h" #include "tos.h" #include "vdi.h" #include "video.h" #include "hatari-glue.h" #if ENABLE_DSP_EMU # include "falcon/dsp.h" #endif #define DEBUG 0 #if DEBUG #define Dprintf(...) printf(__VA_ARGS__) #else #define Dprintf(...) #endif /*-----------------------------------------------------------------------*/ /** * Check if user needs to be warned that changes will take place after reset. * Return true if wants to reset. */ bool Change_DoNeedReset(CNF_PARAMS *current, CNF_PARAMS *changed) { int i; /* Did we change monitor type? If so, must reset */ if (current->Screen.nMonitorType != changed->Screen.nMonitorType && (changed->System.nMachineType == MACHINE_FALCON || current->Screen.nMonitorType == MONITOR_TYPE_MONO || changed->Screen.nMonitorType == MONITOR_TYPE_MONO)) return true; /* Did change to GEM VDI display? */ if (current->Screen.bUseExtVdiResolutions != changed->Screen.bUseExtVdiResolutions) return true; /* Did change GEM resolution or color depth? */ if (changed->Screen.bUseExtVdiResolutions && (current->Screen.nVdiWidth != changed->Screen.nVdiWidth || current->Screen.nVdiHeight != changed->Screen.nVdiHeight || current->Screen.nVdiColors != changed->Screen.nVdiColors)) return true; /* Did change TOS ROM image? */ if (strcmp(changed->Rom.szTosImageFileName, current->Rom.szTosImageFileName)) return true; /* Did change ACSI hard disk image? */ for (i = 0; i < MAX_ACSI_DEVS; i++) { if (changed->Acsi[i].bUseDevice != current->Acsi[i].bUseDevice || (strcmp(changed->Acsi[i].sDeviceFile, current->Acsi[i].sDeviceFile) && changed->Acsi[i].bUseDevice)) return true; } /* Did change SCSI hard disk image? */ for (i = 0; i < MAX_SCSI_DEVS; i++) { if (changed->Scsi[i].bUseDevice != current->Scsi[i].bUseDevice || (strcmp(changed->Scsi[i].sDeviceFile, current->Scsi[i].sDeviceFile) && changed->Scsi[i].bUseDevice)) return true; } /* Did change IDE hard disk image? */ for (i = 0; i < MAX_IDE_DEVS; i++) { if (changed->Ide[i].bUseDevice != current->Ide[i].bUseDevice || changed->Ide[i].nByteSwap != current->Ide[i].nByteSwap || (strcmp(changed->Ide[i].sDeviceFile, current->Ide[i].sDeviceFile) && changed->Ide[i].bUseDevice)) return true; } /* Did change GEMDOS drive Atari/host location or enabling? */ if (changed->HardDisk.nGemdosDrive != current->HardDisk.nGemdosDrive || changed->HardDisk.bUseHardDiskDirectories != current->HardDisk.bUseHardDiskDirectories || (strcmp(changed->HardDisk.szHardDiskDirectories[0], current->HardDisk.szHardDiskDirectories[0]) && changed->HardDisk.bUseHardDiskDirectories)) return true; /* Did change machine type? */ if (changed->System.nMachineType != current->System.nMachineType) return true; /* did change ST Blitter? */ else if (current->System.nMachineType == MACHINE_ST && current->System.bBlitter != changed->System.bBlitter) return true; #if ENABLE_DSP_EMU /* enabling DSP needs reset (disabling it not) */ if (current->System.nDSPType != DSP_TYPE_EMU && changed->System.nDSPType == DSP_TYPE_EMU) return true; #endif /* did change CPU type? */ if (changed->System.nCpuLevel != current->System.nCpuLevel) return true; /* Did change CPU address mode? */ if (changed->System.bAddressSpace24 != current->System.bAddressSpace24) return true; /* Did change CPU prefetch mode? */ if (changed->System.bCompatibleCpu != current->System.bCompatibleCpu) return true; /* Did change CPU cycle exact? */ if (changed->System.bCycleExactCpu != current->System.bCycleExactCpu) return true; /* Did change CPU data cache? */ if (changed->System.bCpuDataCache != current->System.bCpuDataCache) return true; /* Did change MMU? */ if (changed->System.bMMU != current->System.bMMU) return true; /* Did change FPU? */ if (changed->System.n_FPUType != current->System.n_FPUType) return true; /* Did change size of TT-RAM? */ if (current->Memory.TTRamSize_KB != changed->Memory.TTRamSize_KB) return true; /* Did change size of memory? */ if (current->Memory.STRamSize_KB != changed->Memory.STRamSize_KB) return true; /* MIDI related IRQs start/stop needs reset */ if (current->Midi.bEnableMidi != changed->Midi.bEnableMidi) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Copy details back to configuration and perform reset. */ void Change_CopyChangedParamsToConfiguration(CNF_PARAMS *current, CNF_PARAMS *changed, bool bForceReset) { bool NeedReset; bool bReInitGemdosDrive = false; bool bReInitScsiEmu = false; bool bReInitHdcEmu = false; bool bReInitIDEEmu = false; bool bReInitIoMem = false; bool bReInitKeymap = false; bool bScreenModeChange = false; bool bReInitMidi = false; bool bReInitPrinter = false; bool bFloppyInsert[MAX_FLOPPYDRIVES]; int i; Dprintf("Changes for:\n"); /* Do we need to warn user that changes will only take effect after reset? */ if (bForceReset) NeedReset = bForceReset; else NeedReset = Change_DoNeedReset(current, changed); /* Do need to change resolution? Need if change display/overscan settings * (if switch between Colour/Mono cause reset later) or toggle statusbar */ if (!NeedReset && (changed->Screen.bAspectCorrect != current->Screen.bAspectCorrect || changed->Screen.nMaxWidth != current->Screen.nMaxWidth || changed->Screen.nMaxHeight != current->Screen.nMaxHeight || changed->Screen.bAllowOverscan != current->Screen.bAllowOverscan || changed->Screen.bShowStatusbar != current->Screen.bShowStatusbar || changed->Screen.bUseSdlRenderer != current->Screen.bUseSdlRenderer || changed->Screen.bResizable != current->Screen.bResizable || changed->Screen.bUseVsync != current->Screen.bUseVsync )) { Dprintf("- screenmode>\n"); bScreenModeChange = true; } /* Did set new printer parameters? */ if (changed->Printer.bEnablePrinting != current->Printer.bEnablePrinting || strcmp(changed->Printer.szPrintToFileName,current->Printer.szPrintToFileName)) { Dprintf("- printer>\n"); Printer_UnInit(); bReInitPrinter = true; } /* Did set new RS232 parameters? */ if (changed->RS232.bEnableRS232 != current->RS232.bEnableRS232 || strcmp(changed->RS232.szOutFileName, current->RS232.szOutFileName) || strcmp(changed->RS232.szInFileName, current->RS232.szInFileName)) { Dprintf("- RS-232>\n"); RS232_UnInit(); } /* Did set new SCC parameters? */ if (changed->RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL] != current->RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL] || strcmp(changed->RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL], current->RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL]) || strcmp(changed->RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL], current->RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL]) || changed->RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN] != current->RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN] || strcmp(changed->RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN], current->RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN]) || strcmp(changed->RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN], current->RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN]) || changed->RS232.EnableScc[CNF_SCC_CHANNELS_B] != current->RS232.EnableScc[CNF_SCC_CHANNELS_B] || strcmp(changed->RS232.SccInFileName[CNF_SCC_CHANNELS_B], current->RS232.SccInFileName[CNF_SCC_CHANNELS_B]) || strcmp(changed->RS232.SccOutFileName[CNF_SCC_CHANNELS_B], current->RS232.SccOutFileName[CNF_SCC_CHANNELS_B]) || (SCC_IsAvailable(current) && !SCC_IsAvailable(changed))) { Dprintf("- SCC>\n"); SCC_UnInit(); } /* Did stop sound? Or change playback Hz. If so, also stop sound recording */ if (!changed->Sound.bEnableSound || changed->Sound.nPlaybackFreq != current->Sound.nPlaybackFreq) { Dprintf("- sound>\n"); if (Sound_AreWeRecording()) Sound_EndRecording(); Audio_UnInit(); } /* Did change floppy (images)? */ for (i = 0; i < MAX_FLOPPYDRIVES; i++) { /* Log_Printf(LOG_DEBUG, "Old and new disk %c:\n\t%s\n\t%s", 'A'+i, current->DiskImage.szDiskFileName[i], changed->DiskImage.szDiskFileName[i]); */ if (strcmp(changed->DiskImage.szDiskFileName[i], current->DiskImage.szDiskFileName[i]) || strcmp(changed->DiskImage.szDiskZipPath[i], current->DiskImage.szDiskZipPath[i])) bFloppyInsert[i] = true; else bFloppyInsert[i] = false; } if ( changed->DiskImage.EnableDriveA != current->DiskImage.EnableDriveA ) FDC_Drive_Set_Enable ( 0 , changed->DiskImage.EnableDriveA ); if ( changed->DiskImage.EnableDriveB != current->DiskImage.EnableDriveB ) FDC_Drive_Set_Enable ( 1 , changed->DiskImage.EnableDriveB ); if ( changed->DiskImage.DriveA_NumberOfHeads != current->DiskImage.DriveA_NumberOfHeads ) FDC_Drive_Set_NumberOfHeads ( 0 , changed->DiskImage.DriveA_NumberOfHeads ); if ( changed->DiskImage.DriveB_NumberOfHeads != current->DiskImage.DriveB_NumberOfHeads ) FDC_Drive_Set_NumberOfHeads ( 1 , changed->DiskImage.DriveB_NumberOfHeads ); /* Did change GEMDOS drive Atari/host location or enabling? */ if (changed->HardDisk.nGemdosDrive != current->HardDisk.nGemdosDrive || changed->HardDisk.bUseHardDiskDirectories != current->HardDisk.bUseHardDiskDirectories || (strcmp(changed->HardDisk.szHardDiskDirectories[0], current->HardDisk.szHardDiskDirectories[0]) && changed->HardDisk.bUseHardDiskDirectories)) { Dprintf("- gemdos HD>\n"); GemDOS_UnInitDrives(); bReInitGemdosDrive = true; } /* Did change ACSI images? */ for (i = 0; i < MAX_ACSI_DEVS; i++) { if (changed->Acsi[i].bUseDevice != current->Acsi[i].bUseDevice || (strcmp(changed->Acsi[i].sDeviceFile, current->Acsi[i].sDeviceFile) && changed->Acsi[i].bUseDevice)) { Dprintf("- ACSI image %i>\n", i); bReInitHdcEmu = true; } } if (bReInitHdcEmu) HDC_UnInit(); /* Did change SCSI images? */ for (i = 0; i < MAX_SCSI_DEVS; i++) { if (changed->Scsi[i].bUseDevice != current->Scsi[i].bUseDevice || (strcmp(changed->Scsi[i].sDeviceFile, current->Scsi[i].sDeviceFile) && changed->Scsi[i].bUseDevice)) { Dprintf("- SCSI image %i>\n", i); bReInitScsiEmu = true; } } if (bReInitScsiEmu) Ncr5380_UnInit(); /* Did change IDE HD images or their settings? */ for (i = 0; i < MAX_IDE_DEVS; i++) { if (changed->Ide[i].bUseDevice != current->Ide[i].bUseDevice || changed->Ide[i].nByteSwap != current->Ide[i].nByteSwap || (strcmp(changed->Ide[i].sDeviceFile, current->Ide[i].sDeviceFile) && changed->Ide[i].bUseDevice)) { Dprintf("- IDE image %i>\n", i); bReInitIDEEmu = true; } } /* Falcon has always an IDE controller */ if (!bReInitIDEEmu && changed->System.nMachineType == MACHINE_FALCON && (current->System.nMachineType != MACHINE_FALCON || current->System.bFastBoot != changed->System.bFastBoot)) { Dprintf("- IDE subsystem>\n"); bReInitIDEEmu = true; } if (bReInitIDEEmu) Ide_UnInit(); /* Did change blitter, DSP or system type? */ if (changed->System.bBlitter != current->System.bBlitter #if ENABLE_DSP_EMU || changed->System.nDSPType != current->System.nDSPType #endif || changed->System.nMachineType != current->System.nMachineType) { Dprintf("- blitter/dsp/machine>\n"); IoMem_UnInit(current->System.nMachineType); bReInitIoMem = true; } #if ENABLE_DSP_EMU /* Disabled DSP? */ if (current->System.nDSPType == DSP_TYPE_EMU && changed->System.nDSPType != DSP_TYPE_EMU) { Dprintf("- DSP>\n"); DSP_Disable(); } #endif /* Did change MIDI settings? */ if (current->Midi.bEnableMidi != changed->Midi.bEnableMidi #ifdef HAVE_PORTMIDI || strcmp(changed->Midi.sMidiOutPortName, current->Midi.sMidiOutPortName) || strcmp(changed->Midi.sMidiInPortName, current->Midi.sMidiInPortName) #else || strcmp(changed->Midi.sMidiOutFileName, current->Midi.sMidiOutFileName) || strcmp(changed->Midi.sMidiInFileName, current->Midi.sMidiInFileName) #endif ) { Dprintf("- midi>\n"); Midi_UnInit(); bReInitMidi = true; } bReInitKeymap = strcmp(changed->Keyboard.szMappingFileName, current->Keyboard.szMappingFileName); /* Copy details to configuration, * so it can be saved out or set on reset */ if (changed != &ConfigureParams) { ConfigureParams = *changed; } /* Copy details to global, if we reset copy them all */ Configuration_Apply(NeedReset); #if ENABLE_DSP_EMU if (current->System.nDSPType != DSP_TYPE_EMU && changed->System.nDSPType == DSP_TYPE_EMU) { Dprintf("- DSP<\n"); DSP_Enable(); } #endif /* Set keyboard remap file */ if (bReInitKeymap) { Dprintf("- keymap<\n"); Keymap_LoadRemapFile(ConfigureParams.Keyboard.szMappingFileName); } /* Mount new ACSI HD images: */ if (bReInitHdcEmu) { Dprintf("- ACSI<\n"); HDC_Init(); } /* Mount new SCSI HD images: */ if (bReInitScsiEmu) { Dprintf("- SCSI<\n"); Ncr5380_Init(); } /* Mount new IDE HD images: */ if (bReInitIDEEmu && Ide_IsAvailable()) { Dprintf("- IDE<\n"); Ide_Init(); } /* Insert floppies? */ for (i = 0; i < MAX_FLOPPYDRIVES; i++) { if (bFloppyInsert[i]) { Dprintf("- floppy<\n"); Floppy_InsertDiskIntoDrive(i); } } /* Mount a new GEMDOS drive? */ if (bReInitGemdosDrive && ConfigureParams.HardDisk.bUseHardDiskDirectories) { Dprintf("- gemdos HD<\n"); GemDOS_InitDrives(); } /* Restart audio sub system if necessary: */ if (ConfigureParams.Sound.bEnableSound && !bSoundWorking) { Dprintf("- audio<\n"); Audio_Init(); } /* Re-initialize the RS232 emulation: */ if (ConfigureParams.RS232.bEnableRS232) { Dprintf("- RS-232<\n"); RS232_Init(); } /* Re-initialize the SCC emulation: */ if ( ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL] || ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN] || ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B] ) { Dprintf("- SCC<\n"); SCC_Init(); } /* Re-init IO memory map? */ if (bReInitIoMem) { Dprintf("- IO mem<\n"); IoMem_Init(); } /* Re-init Printer emulation? */ if (bReInitPrinter) { Dprintf("- printer<\n"); Printer_Init(); } /* Re-init MIDI emulation? */ if (bReInitMidi) { Dprintf("- midi<\n"); Midi_Init(); if (!NeedReset) { /* Restart MIDI IRQ stopped on Midi_UnInit() */ Midi_Reset(); } } /* Force things associated with screen change */ if (bScreenModeChange) { Dprintf("- screenmode<\n"); Screen_ModeChanged(true); } /* Do we need to perform reset? */ if (NeedReset) { Dprintf("- reset\n"); Reset_Cold(); } /* Go into/return from full screen if flagged */ if (!bInFullScreen && ConfigureParams.Screen.bFullScreen) Screen_EnterFullScreen(); else if (bInFullScreen && !ConfigureParams.Screen.bFullScreen) Screen_ReturnFromFullScreen(); /* update statusbar info (CPU, MHz, mem etc) */ Statusbar_UpdateInfo(); Dprintf("done.\n"); } /*-----------------------------------------------------------------------*/ /** * Change given Hatari options * Return false if parsing failed, true otherwise */ static bool Change_Options(int argc, const char *argv[]) { bool bOK; CNF_PARAMS current; Main_PauseEmulation(false); /* get configuration changes */ current = ConfigureParams; ConfigureParams.Screen.bFullScreen = bInFullScreen; bOK = Opt_ParseParameters(argc, argv); /* Check if reset is required and ask user if he really wants to continue */ if (bOK && Change_DoNeedReset(¤t, &ConfigureParams) && current.Log.nAlertDlgLogLevel > LOG_FATAL) { bOK = DlgAlert_Query("The emulated system must be " "reset to apply these changes. " "Apply changes now and reset " "the emulator?"); } /* Copy details to configuration */ if (bOK) { Change_CopyChangedParamsToConfiguration(¤t, &ConfigureParams, false); } else { ConfigureParams = current; } Main_UnPauseEmulation(); return bOK; } /*-----------------------------------------------------------------------*/ /** * Parse given command line and change Hatari options accordingly. * Given string must be stripped and not empty. * Return false if parsing failed or there were no args, true otherwise */ bool Change_ApplyCommandline(char *cmdline) { int i, argc, inarg; const char **argv; bool ret; /* count args */ inarg = argc = 0; for (i = 0; cmdline[i]; i++) { if (isspace((unsigned char)cmdline[i]) && cmdline[i-1] != '\\') { inarg = 0; continue; } if (!inarg) { inarg++; argc++; } } if (!argc) { return false; } /* 2 = "hatari" + NULL */ argv = malloc((argc+2) * sizeof(char*)); if (!argv) { perror("command line alloc"); return false; } /* parse them to array */ fprintf(stderr, "Command line with '%d' arguments:\n", argc); inarg = argc = 0; argv[argc++] = "hatari"; for (i = 0; cmdline[i]; i++) { if (isspace((unsigned char)cmdline[i])) { if (cmdline[i-1] != '\\') { cmdline[i] = '\0'; if (inarg) { fprintf(stderr, "- '%s'\n", argv[argc-1]); } inarg = 0; continue; } else { /* remove quote for space */ memcpy(cmdline+i-1, cmdline+i, strlen(cmdline+i)+1); i--; } } if (!inarg) { argv[argc++] = &(cmdline[i]); inarg++; } } if (inarg) { fprintf(stderr, "- '%s'\n", argv[argc-1]); } argv[argc] = NULL; /* do args */ ret = Change_Options(argc, argv); free((void *)argv); return ret; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/clocks_timings.c000066400000000000000000000526041504763705000245130ustar00rootroot00000000000000/* Hatari - clocks_timings.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Clocks Timings for the hardware components in each supported machine type, as well as functions taking into account the exact length of a VBL to precisely emulate video/audio parts (number of VBL per sec, number of audio samples per VBL, ...) The video freq is not exactly 50 or 60 Hz because the number of cpu cycles per second is not a multiple of the number of cpu cycles per VBL. This can cause synchronisation errors between audio and video effects when both components use different clocks (eg in STE where audio DMA clock is not the same as the cpu clock). To get the best results, it's recommended to set RoundVBLPerSec=false. Note that if you do so, the number of VBL won't be exactly 50 or 60 per sec but 50.05 or 60.04 ; if this does not work with your display, set RoundVBLPerSec=true to get an integer number of VBL per sec (but this should not be needed). ST : MCLK = 32 MHz SHIFTER IN = 32 MHz OUT = 16 MHz MMU IN = 16 MHz OUT = 8 MHz, 4 MHz GLUE IN = 8 MHz OUT = 2 MHz, 500 kHz BUS = 8 MHz CPU 68000 IN = 8 MHz DMA IN = 8 MHz MFP 68901 IN = 4 MHz, 2.4576 MHz (external clock) FDC WD1772 IN = 8 MHz BLITTER IN = 8 MHz YM2149 IN = 2 MHz ACIA MC6850 IN = 500 kHz IKBD HD6301 IN = 1 MHZ (local clock) STE : MCLK = 32 MHz EXT OSC = 8 MHZ OUT = 8 MHz (SCLK), 2 MHz (CLK2) GST SHIFTER IN = 32 MHz, 8 MHz (external clock SCLK) OUT = 16 MHz, 8 MHz (FCLK=SCLK) GST MCU IN = 16 MHz OUT = 8 MHz (CLK8), 4 MHz (CLK4), 500 kHz (KHZ500) BUS = 8 MHz CPU 68000 IN = 8 MHz (CLK8) DMA IN = 8 MHz (CLK8) DMA AUDIO IN = 8 MHz (SCLK) MFP 68901 IN = 4 MHz (CLK4), 2.4576 MHz (external clock) FDC WD1772 IN = 8 MHz (SCLK) BLITTER IN = 8 MHz (CLK8) YM2149 IN = 2 MHz (CLK2) ACIA MC6850 IN = 500 kHz (KHZ500) IKBD HD6301 IN = 1 MHZ (local clock) MEGA STE : MCLK = 32 MHz SCLK = 8 MHz GST SHIFTER IN = 32 MHz, 8 MHz (external clock SCLK) OUT = 16 MHz (CLK16), 8 MHz (FCLK=SCLK) GST MCU IN = 16 MHz (CLK16) OUT = 8 MHz (CLK8), 4 MHz (CLK4), 500 kHz (KHZ500) BUS = 8 MHz CPU 68000 IN = 16 MHz (CLK16) FPU 68881 IN = 16 MHz (CLK16) DMA IN = 8 MHz (CLK8) DMA AUDIO IN = 8 MHz (SCLK) MFP 68901 IN = 4 MHz (CLK4), 2.4576 MHz (external clock) FDC WD1772 IN = 8 MHz (SCLK) BLITTER IN = 8 MHz (CLK8) YM2149 IN = 2 MHz (CLK2 = SCLK / 4) ACIA MC6850 IN = 500 kHz (KHZ500) IKBD HD6301 IN = 1 MHZ (local clock) SCC Z85C30 IN = 8 MHz (CLK8) TT : MCLK = 32 MHz (CLK32) TT VIDEO IN = 32 MHz (CLK32) OUT = 16 MHz (CLK16), 4 MHz (CLK4), 2 MHz (CLK2) GST MCU IN = 16 MHz (CLK16A), 2 MHz (CLK2) OUT = 8 MHz (CLK8), 8 MHz (FCCLK), 1 MHz (CLKE), 500 kHz (CLKX5) BUS = 16 MHz CPU 68030 IN = 32 MHz (CLK32) FPU 68882 IN = 32 MHz (CLK32) DMA IN = 8 MHz (CLK8) SND SHIFTER IN = 16 MHz (CLK16F), 2 MHz (CLK2) OUT = ? MHz (FCLK) MFP 68901 IN = 4 MHz (CLK4), 2.4576 MHz (external clock) NOTE : TT has 2 MFPs 68901 FDC WD1772 IN = 8 MHz (FCCLK) BLITTER NOT AVAILABLE YM2149 IN = 2 MHz (CLK2) ACIA MC6850 IN = 500 kHz (CLKX5) IKBD HD6301 IN = 1 MHZ (local clock) SCC Z85C30 IN = 8 MHz (CLK8) FALCON : MCLK = 32 MHz (CLK32) VIDEL IN = 32 MHz (VID32MHZ), 25 MHz (25K) COMBEL IN = 32 MHz (CLK32) OUT = 8 MHz (CLK8, not used), 4 MHz (CLK4), 500 kHz (KHZ500) BUS = 16 MHz DSP 56001 IN = 32 MHz (DSP_32M) DMA IN = 32 MHz (DSP_32M) OUT = 8 MHz (CLK8), 16 MHz (FCCLK), 2 MHz (CLK2) CPU 68030 IN = 16 MHz (CPUCLKB) FPU 68882 IN = 16 MHz (CPUCLKA) CODEC IN = 25 MHz (25K) MFP 68901 IN = 4 MHz (CLK4), 2.4576 MHz (external clock) FDC AJAX IN = 16 MHz (FCCLK) BLITTER IN = 16 MHz YM3439 IN = 2 MHz (CLK2) ACIA MC6850 IN = 500 kHz (KHZ500) IKBD HD6301 IN = 1 MHZ (local clock) SCC Z85C30 IN = 8 MHz (CLK8) */ const char ClocksTimings_fileid[] = "Hatari clocks_timings.c"; #include "main.h" #include "configuration.h" #include "log.h" #include "clocks_timings.h" #include "m68000.h" /* The possible master frequencies used in the different machines */ /* depending on PAL/NTSC version. */ #define ATARI_STF_PAL_MCLK 32084988 /* CPU_Freq = 8.021247 MHz */ #define ATARI_STF_NTSC_MCLK 32042400 /* CPU_Freq = 8.010600 MHz */ #define ATARI_STF_CYCLES_PER_VBL_PAL 160256 /* 512 cycles * 313 lines */ #define ATARI_STF_CYCLES_PER_VBL_NTSC 133604 /* 508 cycles * 263 lines */ #define ATARI_STF_CYCLES_PER_VBL_HI 112224 /* 224 cycles * 501 lines */ #define ATARI_STE_PAL_MCLK 32084988 /* CPU_Freq = 8.021247 MHz */ #define ATARI_STE_NTSC_MCLK 32215905 /* CPU_Freq = 8.05397625 MHz */ #define ATARI_STE_EXT_OSC 8010613 /* OSC U303 */ #define ATARI_STE_CYCLES_PER_VBL_PAL 160256 /* 512 cycles * 313 lines */ #define ATARI_STE_CYCLES_PER_VBL_NTSC 133604 /* 508 cycles * 263 lines */ #define ATARI_STE_CYCLES_PER_VBL_HI 112224 /* 224 cycles * 501 lines */ #define ATARI_MEGA_STE_PAL_MCLK 32084988 /* CPU_Freq = 16.042494 MHz */ #define ATARI_MEGA_STE_NTSC_MCLK 32215905 /* CPU_Freq = 16.1079525 MHz */ #define ATARI_MEGA_STE_EXT_OSC 16021226 /* OSC U408 */ #define ATARI_TT_PAL_MCLK 32084988 /* CPU_Freq = 32.084988 MHz */ #define ATARI_TT_NTSC_MCLK 32215905 /* CPU_Freq = 32.215905 MHz */ #define ATARI_FALCON_PAL_MCLK 32084988 /* CPU_Freq = 16.042494 MHz */ #define ATARI_FALCON_NTSC_MCLK 32215905 /* CPU_Freq = 16.1079525 MHz */ #define ATARI_FALCON_25M_CLK 25175000 #define ATARI_MFP_XTAL 2457600 /* external clock for the MFP */ #define ATARI_IKBD_CLK 1000000 /* clock of the HD6301 ikbd cpu */ CLOCKS_STRUCT MachineClocks; bool RoundVBLPerSec = false; /* if false, don't round number of VBL to 50/60 Hz */ /* but compute the exact value based on cpu/video clocks */ /*--------------------------------------------------------------------------*/ /** * Initialize all the clocks information related to a specific machine type. * We consider the machine is running with PAL clocks. */ void ClocksTimings_InitMachine ( MACHINETYPE MachineType ) { //fprintf ( stderr , "clock init mach=%d shift=%d\n" , MachineType , nCpuFreqShift ); memset ( (void *)&MachineClocks , 0 , sizeof ( MachineClocks ) ); if (MachineType == MACHINE_ST || MachineType == MACHINE_MEGA_ST) { int CLK16, CLK8, CLK4, CLK2, CLK500; MachineClocks.MCLK_Freq = ATARI_STF_PAL_MCLK; /* 32.084988 MHz */ MachineClocks.SHIFTER_Freq = MachineClocks.MCLK_Freq; /* 32 MHz */ CLK16 = MachineClocks.SHIFTER_Freq / 2; MachineClocks.MMU_Freq = CLK16; /* 16 MHz */ CLK8 = MachineClocks.MMU_Freq / 2; CLK4 = MachineClocks.MMU_Freq / 4; MachineClocks.GLUE_Freq = CLK8; /* 8 MHz */ CLK2 = MachineClocks.GLUE_Freq / 4; CLK500 = MachineClocks.GLUE_Freq / 16; MachineClocks.BUS_Freq = CLK8; /* 8 MHz */ MachineClocks.CPU_Freq = CLK8; /* 8 MHz */ MachineClocks.DMA_Freq = CLK8; /* 8 MHz */ MachineClocks.MFP_Freq = CLK4; /* 4 MHz */ MachineClocks.MFP_Timer_Freq = ATARI_MFP_XTAL; /* 2.4576 MHz (XTAL)*/ MachineClocks.FDC_Freq = CLK8; /* 8 MHz */ MachineClocks.BLITTER_Freq = CLK8; /* 8 MHz */ MachineClocks.YM_Freq = CLK2; /* 2 MHz */; MachineClocks.ACIA_Freq = CLK500; /* 500 kHz */ MachineClocks.IKBD_Freq = ATARI_IKBD_CLK; /* 1 MHz */ } else if ( MachineType == MACHINE_STE ) { int SCLK, CLK16, CLK8, CLK4, CLK2, KHZ500; //int FCLK; /* not used (audio filters) */ MachineClocks.MCLK_Freq = ATARI_STE_PAL_MCLK; /* 32.084988 MHz */ SCLK = ATARI_STE_EXT_OSC; /* 8.010613 MHz (SCLK) */ CLK2 = SCLK / 4; MachineClocks.SHIFTER_Freq = MachineClocks.MCLK_Freq; /* 32 MHz */ CLK16 = MachineClocks.SHIFTER_Freq / 2; //FCLK = SCLK; MachineClocks.MCU_Freq = CLK16; /* 16 MHz */ CLK8 = MachineClocks.MCU_Freq / 2; CLK4 = MachineClocks.MCU_Freq / 4; KHZ500 = MachineClocks.MCU_Freq / 32; MachineClocks.BUS_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.CPU_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.DMA_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.DMA_Audio_Freq = SCLK; /* 8 MHz (SCLK) */ MachineClocks.MFP_Freq = CLK4; /* 4 MHz (CLK4) */ MachineClocks.MFP_Timer_Freq = ATARI_MFP_XTAL; /* 2.4576 MHz (XTAL)*/ MachineClocks.FDC_Freq = SCLK; /* 8 MHz (SCLK) */ MachineClocks.BLITTER_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.YM_Freq = CLK2; /* 2 MHz (CLK2) */ MachineClocks.ACIA_Freq = KHZ500; /* 500 kHz (KHZ500) */ MachineClocks.IKBD_Freq = ATARI_IKBD_CLK; /* 1 MHz */ } else if ( MachineType == MACHINE_MEGA_STE ) { int SCLK, CLK16, CLK8, CLK4, CLK2, KHZ500; //int FCLK; /* not used (audio filters) */ MachineClocks.MCLK_Freq = ATARI_MEGA_STE_PAL_MCLK; /* 32.084988 MHz */ SCLK = ATARI_MEGA_STE_EXT_OSC / 2; /* 16.021226 MHz / 2 = 8.010613 MHz */ CLK2 = SCLK / 4; MachineClocks.SHIFTER_Freq = MachineClocks.MCLK_Freq; /* 32 MHz */ CLK16 = MachineClocks.SHIFTER_Freq / 2; //FCLK = SCLK; MachineClocks.MCU_Freq = CLK16; /* 16 MHz (CLK16) */ CLK8 = MachineClocks.MCU_Freq / 2; CLK4 = MachineClocks.MCU_Freq / 4; KHZ500 = MachineClocks.MCU_Freq / 32; MachineClocks.BUS_Freq = CLK8; /* 8 MHz (CLK8) */ /* Special case : the Mega STE has an internal 16 MHz CPU clock */ /* but it can be set to 8 MHz for compatibility with STE using $FF8E21 */ MachineClocks.CPU_Freq = CLK16; /* 16 MHz (CLK16) */ MachineClocks.FPU_Freq = CLK16; /* 16 MHz (CLK16) */ MachineClocks.DMA_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.DMA_Audio_Freq = SCLK; /* 8 MHz (SCLK) */ MachineClocks.MFP_Freq = CLK4; /* 4 MHz (CLK4) */ MachineClocks.MFP_Timer_Freq = ATARI_MFP_XTAL; /* 2.4576 MHz (XTAL)*/ MachineClocks.FDC_Freq = SCLK; /* 8 MHz (SCLK) */ MachineClocks.BLITTER_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.YM_Freq = CLK2; /* 2 MHz (CLK2) */ MachineClocks.ACIA_Freq = KHZ500; /* 500 kHz (KHZ500) */ MachineClocks.IKBD_Freq = ATARI_IKBD_CLK; /* 1 MHz */ MachineClocks.SCC_Freq = CLK8; /* 8 MHz (CLK8) */ } else if ( MachineType == MACHINE_TT ) { int CLK32, CLK16, CLK8, FCCLK, CLK4, CLK2, CLKX5; MachineClocks.MCLK_Freq = ATARI_TT_PAL_MCLK; /* 32.084988 MHz */ CLK32 = MachineClocks.MCLK_Freq; MachineClocks.TTVIDEO_Freq = MachineClocks.MCLK_Freq; /* 32 MHz */ CLK16 = MachineClocks.TTVIDEO_Freq / 2; CLK4 = MachineClocks.TTVIDEO_Freq / 8; CLK2 = MachineClocks.TTVIDEO_Freq / 16; MachineClocks.MCU_Freq = CLK16; /* 16 MHz (CLK16A) */ CLK8 = MachineClocks.MCU_Freq / 2; FCCLK = MachineClocks.MCU_Freq / 2; CLKX5 = MachineClocks.MCU_Freq / 32; MachineClocks.BUS_Freq = CLK16; /* 16 MHz (CLK16) */ MachineClocks.CPU_Freq = CLK32; /* 32 MHz (CLK32) */ MachineClocks.FPU_Freq = CLK32; /* 32 MHz (CLK32) */ MachineClocks.DMA_Freq = CLK8; /* 8 MHz (CLK8) */ MachineClocks.DMA_Audio_Freq = CLK16; /* 16 MHz (CLK16) SND SHIFTER */ MachineClocks.MFP_Freq = CLK4; /* 4 MHz (CLK4) */ MachineClocks.MFP_Timer_Freq = ATARI_MFP_XTAL; /* 2.4576 MHz (XTAL)*/ MachineClocks.FDC_Freq = FCCLK; /* 8 MHz (FCCLK) */ MachineClocks.BLITTER_Freq = 0; /* No blitter in TT */ MachineClocks.YM_Freq = CLK2; /* 2 MHz (CLK2) */ MachineClocks.ACIA_Freq = CLKX5; /* 500 kHz (CLKX5) */ MachineClocks.IKBD_Freq = ATARI_IKBD_CLK; /* 1 MHz */ MachineClocks.SCC_Freq = CLK8; /* 8 MHz (CLK8) */ } else if ( MachineType == MACHINE_FALCON ) { /* TODO : need more docs for Falcon's clocks */ /* Note : Some clocks are made from 32 MHz MCLK, but others are made from */ /* 32 MHz DSP's clock */ /* CLK32 and VID32MHZ are coming from the same clock 32MHZ (32.084988 MHz for PAL) */ int CLK32, CLK25, CLK16, CLK8, FCCLK, CLK4, CLK2, KHZ500; MachineClocks.MCLK_Freq = ATARI_FALCON_PAL_MCLK; /* 32.084988 MHz */ CLK32 = MachineClocks.MCLK_Freq; CLK25 = ATARI_FALCON_25M_CLK; CLK16 = CLK32 / 2; MachineClocks.DSP_Freq = CLK32; /* 32 MHz */ CLK8 = MachineClocks.DSP_Freq / 4; CLK2 = MachineClocks.DSP_Freq / 16; FCCLK = MachineClocks.DSP_Freq / 2; MachineClocks.VIDEL_Freq = CLK32; /* 32 MHz (VID32MHZ) */ MachineClocks.COMBEL_Freq = CLK32; /* 32 MHz */ CLK4 = MachineClocks.COMBEL_Freq / 8; KHZ500 = MachineClocks.COMBEL_Freq / 64; MachineClocks.BUS_Freq = CLK16; /* 16 MHz (CPUCLK16A) */ MachineClocks.CPU_Freq = CLK16; /* 16 MHz (CPUCLK16B) */ MachineClocks.FPU_Freq = CLK16; /* 16 MHz (CLK32) */ MachineClocks.DMA_Freq = CLK16; /* 16 MHz (CLK16) ? */ MachineClocks.CODEC_Freq = CLK25; /* 25 MHz (CLK25) */ MachineClocks.MFP_Freq = CLK4; /* 4 MHz (CLK4) */ MachineClocks.MFP_Timer_Freq = ATARI_MFP_XTAL; /* 2.4576 MHz (XTAL)*/ MachineClocks.FDC_Freq = FCCLK; /* 16 MHz (FCCLK) ? */ MachineClocks.BLITTER_Freq = CLK16; /* 16 MHz */ MachineClocks.YM_Freq = CLK2; /* 2 MHz (CLK2) */ MachineClocks.ACIA_Freq = KHZ500; /* 500 kHz (KHZ500) */ MachineClocks.IKBD_Freq = ATARI_IKBD_CLK; /* 1 MHz */ MachineClocks.SCC_Freq = CLK8; /* 8 MHz (CLK8) */ } /* Update some other variables depending on the current nCpuFreqShift */ ClocksTimings_UpdateCpuFreqEmul ( MachineType , nCpuFreqShift ); } /*-----------------------------------------------------------------------------------------*/ /** * Update the number of emulated cycles per second for the current CPU settings, depending on the * base CPU freq in MachineClocks.CPU_Freq and nCpuFreqShift * * We use CPU_Freq as a base (instead of fixed values of 8, 16 or 32 MHz) to handle different CPU * clocks in case the machine is a PAL or NTSC model for example (in which cases CPU_Freq * values are slightly different) */ void ClocksTimings_UpdateCpuFreqEmul ( MACHINETYPE MachineType , int nCpuFreqShift ) { uint32_t Cpu_Freq_Emul; Cpu_Freq_Emul = MachineClocks.CPU_Freq; /* Machines where the base CPU is 8 MHz */ if (MachineType == MACHINE_ST || MachineType == MACHINE_MEGA_ST || MachineType == MACHINE_STE) { Cpu_Freq_Emul <<= nCpuFreqShift; /* 8, 16 or 32 MHz */ } /* Machines where the base CPU is 16 MHz */ else if (MachineType == MACHINE_MEGA_STE || MachineType == MACHINE_FALCON ) { if ( nCpuFreqShift == 0 ) Cpu_Freq_Emul >>= 1; /* 8 MHz */ else if ( nCpuFreqShift == 2 ) Cpu_Freq_Emul <<= 1; /* 32 MHz */ } /* Machines where the base CPU is 32 MHz */ else if (MachineType == MACHINE_TT ) { if ( nCpuFreqShift == 0 ) Cpu_Freq_Emul >>= 2; /* 8 MHz */ else if ( nCpuFreqShift == 1 ) Cpu_Freq_Emul >>= 1; /* 16 MHz */ } MachineClocks.CPU_Freq_Emul = Cpu_Freq_Emul; //fprintf ( stderr , "clock cpu freq mach=%d shift=%d base=%d -> %d\n" , MachineType , nCpuFreqShift , MachineClocks.CPU_Freq , MachineClocks.CPU_Freq_Emul ); } /*-----------------------------------------------------------------------------------------*/ /** * Return the number of cycles per VBL, depending on the video settings and the simulated cpu freq. * This value is only precisely known for STF/STE running at 50, 60 or 71 Hz. * For the other machines, we return CPU_Freq_Emul / ScreenRefreshRate */ uint32_t ClocksTimings_GetCyclesPerVBL ( MACHINETYPE MachineType , int ScreenRefreshRate ) { uint32_t CyclesPerVBL; /* STF and STE have the same numbers of cycles per VBL (numbers are for an 8 MHz CPU) */ if (MachineType == MACHINE_ST || MachineType == MACHINE_MEGA_ST || MachineType == MACHINE_STE || MachineType == MACHINE_MEGA_STE) { if ( ScreenRefreshRate == 50 ) CyclesPerVBL = ATARI_STF_CYCLES_PER_VBL_PAL; else if ( ScreenRefreshRate == 60 ) CyclesPerVBL = ATARI_STF_CYCLES_PER_VBL_NTSC; else if ( ScreenRefreshRate == 71 ) CyclesPerVBL = ATARI_STF_CYCLES_PER_VBL_HI; else CyclesPerVBL = MachineClocks.CPU_Freq / ScreenRefreshRate; /* unknown refresh rate, should not happen */ /* At this point CyclesPerVBL is the number of cycles per VBL for a 8 MHz CPU */ /* We need to apply nCpuFreqShift to get the number of cycles at the currently simulated CPU speed (8, 16 or 32 MHz) */ CyclesPerVBL <<= nCpuFreqShift; } else if (MachineType == MACHINE_FALCON) { /* Although our Videl emulation is not complete, it already supports 50, 60 or 71 Hz */ /* for ScreenRefreshRate. We use this value to get the number of cycles per VBL */ CyclesPerVBL = MachineClocks.CPU_Freq_Emul / ScreenRefreshRate; } /* For machines where cpu freq can be changed, the number of cycles per VBL is not constant */ else /* MACHINE_TT TODO : use ATARI_STF_CYCLES_PER_VBL_xxx too ?*/ CyclesPerVBL = MachineClocks.CPU_Freq_Emul / ScreenRefreshRate; //fprintf ( stderr , "clock cycles per vbl %d %d -> %d\n" , MachineType , ScreenRefreshRate , CyclesPerVBL ); return CyclesPerVBL; } /*-----------------------------------------------------------------------------------------*/ /** * Return the number of VBL per second, depending on the video settings and the simulated cpu freq. * Since the cpu freq is not an exact multiple of the number of cycles per VBL, the real * value slightly differs from the usual 50/60 Hz. * Precise (not rounded) values are needed in STE mode to synchronize cpu and dma sound (as they both use * 2 different clocks). * example for STF/STE : * PAL STF/STE video PAL : 50.053 VBL/sec * PAL STF/STE video NTSC : 60.037 VBL/sec * NTSC STF/STE video PAL : 49.986 VBL/sec * NTSC STF/STE video NTSC : 59.958 VBL/sec * * The returned number of VBL per sec is << 24 (=CLOCKS_TIMINGS_SHIFT_VBL) to simulate floating point using uint32_t. */ uint32_t ClocksTimings_GetVBLPerSec ( MACHINETYPE MachineType , int ScreenRefreshRate ) { uint32_t VBLPerSec; /* Upper 8 bits are for int part, 24 lower bits for float part */ if ( RoundVBLPerSec == true ) { VBLPerSec = ScreenRefreshRate << CLOCKS_TIMINGS_SHIFT_VBL; } else { VBLPerSec = ( (int64_t)MachineClocks.CPU_Freq_Emul << CLOCKS_TIMINGS_SHIFT_VBL ) / ClocksTimings_GetCyclesPerVBL ( MachineType , ScreenRefreshRate ); } //fprintf ( stderr , "clock vbl per sec %d %d %d -> %d\n" , MachineType , MachineClocks.CPU_Freq_Emul , ScreenRefreshRate , VBLPerSec ); return VBLPerSec; } /*-----------------------------------------------------------------------------------------*/ /** * Return the length in microsec of a VBL (opposite function of ClocksTimings_GetVBLPerSec) * We use precise values only in STF/STE mode, else we use 1000000 / ScreenRefreshRate. * example for STF/STE : * PAL STF/STE video PAL : 19979 micro sec (instead of 20000 for 50 Hz) * PAL STF/STE video NTSC : 16656 micro sec (instead of 16667 for 60 Hz) */ uint32_t ClocksTimings_GetVBLDuration_micro ( MACHINETYPE MachineType , int ScreenRefreshRate ) { uint32_t VBLDuration_micro; if ( RoundVBLPerSec == true ) { VBLDuration_micro = (uint32_t) (1000000.0 / ScreenRefreshRate + 0.5); } else { VBLDuration_micro = (uint32_t) (1000000.0 * ClocksTimings_GetCyclesPerVBL ( MachineType , ScreenRefreshRate ) / MachineClocks.CPU_Freq_Emul + 0.5); } //fprintf ( stderr , "clock vbl duration %d %d %d -> %d\n" , MachineType , MachineClocks.CPU_Freq_Emul , ScreenRefreshRate , VBLDuration_micro ); return VBLDuration_micro; } /*-----------------------------------------------------------------------------------------*/ /** * Return the number of samples needed to emulate the sound that was produced during one VBL. * This depends on the chosen audio output frequency, as well as the VBL's duration, * * We use precise values only in STF/STE mode, else we use AudioFreq/ScreenRefreshRate. * * The returned number of samples per VBL is << 28 to simulate maximum precision using * 64 bits integers (lower 28 bits are for the floating point part). * example for STF/STE with emulation's audio freq = 44100 : * PAL STF/STE video PAL : 881.07 samples per VBL (instead of 882 for 50 Hz) * 44053.56 samples for 50 VBLs (instead of 44100 for 1 sec at 50 Hz) */ int64_t ClocksTimings_GetSamplesPerVBL ( MACHINETYPE MachineType , int ScreenRefreshRate , int AudioFreq ) { int64_t SamplesPerVBL; if ( RoundVBLPerSec == true ) { SamplesPerVBL = ( ((int64_t)AudioFreq) << 28 ) / ScreenRefreshRate; } else { SamplesPerVBL = ( ((int64_t)AudioFreq * ClocksTimings_GetCyclesPerVBL ( MachineType , ScreenRefreshRate ) ) << 28 ) / MachineClocks.CPU_Freq_Emul; } //fprintf ( stderr , "clock sample per vbl %d %d %d -> %ld\n" , MachineType , MachineClocks.CPU_Freq_Emul , AudioFreq , SamplesPerVBL ); return SamplesPerVBL; } /*-----------------------------------------------------------------------------------------*/ /** * Convert a number of cycles "CyclesIn" for a clock running at "ClockFreqIn" into * an equivalent number of cycles for a clock running at "ClockFreqOut". * As clocks are rarely multiple of each other, this will give a remainder that must be kept * and used on next call. * This method uses only integer operations (integer division and modulo) and gives much more * precise results than using floating point, because there's no roundings that accumulate * after a while. */ void ClocksTimings_ConvertCycles ( uint64_t CyclesIn , uint64_t ClockFreqIn , CLOCKS_CYCLES_STRUCT *CyclesStructOut , uint64_t ClockFreqOut ) { uint64_t CyclesTotal; uint64_t CyclesOut; uint64_t CyclesOut_remainder; CyclesTotal = CyclesIn * ClockFreqOut; CyclesTotal += CyclesStructOut->Remainder; CyclesOut = CyclesTotal / ClockFreqIn; CyclesOut_remainder = CyclesTotal % ClockFreqIn; CyclesStructOut->Cycles = CyclesOut; CyclesStructOut->Remainder = CyclesOut_remainder; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/configuration.c000066400000000000000000002022611504763705000243460ustar00rootroot00000000000000/* Hatari - configuration.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Configuration File The configuration file is now stored in an ASCII format to allow the user to edit the file manually. */ const char Configuration_fileid[] = "Hatari configuration.c"; #include #include #include "main.h" #include "configuration.h" #include "cfgopts.h" #include "audio.h" #include "sound.h" #include "file.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "paths.h" #include "screen.h" #include "statusbar.h" #include "vdi.h" #include "video.h" #include "avi_record.h" #include "clocks_timings.h" #include "68kDisass.h" #include "disasm.h" #include "fdc.h" #include "dsp.h" #include "joy.h" #include "falcon/crossbar.h" #include "stMemory.h" #include "tos.h" #include "screenSnapShot.h" CNF_PARAMS ConfigureParams; /* List of configuration for the emulator */ char sConfigFileName[FILENAME_MAX]; /* Stores the name of the configuration file */ /* Used to load/save logging options */ static const struct Config_Tag configs_Log[] = { { "sLogFileName", String_Tag, ConfigureParams.Log.sLogFileName }, { "sTraceFileName", String_Tag, ConfigureParams.Log.sTraceFileName }, { "nTextLogLevel", Int_Tag, &ConfigureParams.Log.nTextLogLevel }, { "nAlertDlgLogLevel", Int_Tag, &ConfigureParams.Log.nAlertDlgLogLevel }, { "bConfirmQuit", Bool_Tag, &ConfigureParams.Log.bConfirmQuit }, { "bNatFeats", Bool_Tag, &ConfigureParams.Log.bNatFeats }, { "bConsoleWindow", Bool_Tag, &ConfigureParams.Log.bConsoleWindow }, { NULL , Error_Tag, NULL } }; /* Used to load/save debugger options */ static const struct Config_Tag configs_Debugger[] = { { "nNumberBase", Int_Tag, &ConfigureParams.Debugger.nNumberBase }, { "nSymbolLines", Int_Tag, &ConfigureParams.Debugger.nSymbolLines }, { "nMemdumpLines", Int_Tag, &ConfigureParams.Debugger.nMemdumpLines }, { "nFindLines", Int_Tag, &ConfigureParams.Debugger.nFindLines }, { "nDisasmLines", Int_Tag, &ConfigureParams.Debugger.nDisasmLines }, { "nBacktraceLines", Int_Tag, &ConfigureParams.Debugger.nBacktraceLines }, { "nExceptionDebugMask", Int_Tag, &ConfigureParams.Debugger.nExceptionDebugMask }, { "nDisasmOptions", Int_Tag, &ConfigureParams.Debugger.nDisasmOptions }, { "bDisasmUAE", Bool_Tag, &ConfigureParams.Debugger.bDisasmUAE }, { "bSymbolsAutoLoad", Bool_Tag, &ConfigureParams.Debugger.bSymbolsAutoLoad }, { "bMatchAllSymbols", Bool_Tag, &ConfigureParams.Debugger.bMatchAllSymbols }, { NULL , Error_Tag, NULL } }; /* Used to load/save screen options */ static const struct Config_Tag configs_Screen[] = { { "nMonitorType", Int_Tag, &ConfigureParams.Screen.nMonitorType }, { "nFrameSkips", Int_Tag, &ConfigureParams.Screen.nFrameSkips }, { "bFullScreen", Bool_Tag, &ConfigureParams.Screen.bFullScreen }, { "bKeepResolution", Bool_Tag, &ConfigureParams.Screen.bKeepResolution }, { "bResizable", Bool_Tag, &ConfigureParams.Screen.bResizable }, { "bAllowOverscan", Bool_Tag, &ConfigureParams.Screen.bAllowOverscan }, { "nSpec512Threshold", Int_Tag, &ConfigureParams.Screen.nSpec512Threshold }, { "bAspectCorrect", Bool_Tag, &ConfigureParams.Screen.bAspectCorrect }, { "bUseExtVdiResolutions", Bool_Tag, &ConfigureParams.Screen.bUseExtVdiResolutions }, { "nVdiWidth", Int_Tag, &ConfigureParams.Screen.nVdiWidth }, { "nVdiHeight", Int_Tag, &ConfigureParams.Screen.nVdiHeight }, { "nVdiColors", Int_Tag, &ConfigureParams.Screen.nVdiColors }, { "bMouseWarp", Bool_Tag, &ConfigureParams.Screen.bMouseWarp }, { "bShowStatusbar", Bool_Tag, &ConfigureParams.Screen.bShowStatusbar }, { "bShowDriveLed", Bool_Tag, &ConfigureParams.Screen.bShowDriveLed }, { "bCrop", Bool_Tag, &ConfigureParams.Screen.bCrop }, { "bForceMax", Bool_Tag, &ConfigureParams.Screen.bForceMax }, { "nMaxWidth", Int_Tag, &ConfigureParams.Screen.nMaxWidth }, { "nMaxHeight", Int_Tag, &ConfigureParams.Screen.nMaxHeight }, { "nZoomFactor", Float_Tag, &ConfigureParams.Screen.nZoomFactor }, { "bUseSdlRenderer", Bool_Tag, &ConfigureParams.Screen.bUseSdlRenderer }, { "ScreenShotFormat", Int_Tag, &ConfigureParams.Screen.ScreenShotFormat }, { "szScreenShotDir", String_Tag, ConfigureParams.Screen.szScreenShotDir }, { "bUseVsync", Bool_Tag, &ConfigureParams.Screen.bUseVsync }, { NULL , Error_Tag, NULL } }; /* Used to load/save joystick 0 options */ static const struct Config_Tag configs_Joystick0[] = { { "nJoystickMode", Int_Tag, &ConfigureParams.Joysticks.Joy[0].nJoystickMode }, { "bEnableAutoFire", Bool_Tag, &ConfigureParams.Joysticks.Joy[0].bEnableAutoFire }, { "bEnableJumpOnFire2", Bool_Tag, &ConfigureParams.Joysticks.Joy[0].bEnableJumpOnFire2 }, { "nJoyId", Int_Tag, &ConfigureParams.Joysticks.Joy[0].nJoyId }, { "nJoyBut1Index", Int_Tag, &ConfigureParams.Joysticks.Joy[0].nJoyButMap[0] }, { "nJoyBut2Index", Int_Tag, &ConfigureParams.Joysticks.Joy[0].nJoyButMap[1] }, { "nJoyBut3Index", Int_Tag, &ConfigureParams.Joysticks.Joy[0].nJoyButMap[2] }, { "kUp", Key_Tag, &ConfigureParams.Joysticks.Joy[0].nKeyCodeUp }, { "kDown", Key_Tag, &ConfigureParams.Joysticks.Joy[0].nKeyCodeDown }, { "kLeft", Key_Tag, &ConfigureParams.Joysticks.Joy[0].nKeyCodeLeft }, { "kRight", Key_Tag, &ConfigureParams.Joysticks.Joy[0].nKeyCodeRight }, { "kFire", Key_Tag, &ConfigureParams.Joysticks.Joy[0].nKeyCodeFire }, { NULL , Error_Tag, NULL } }; /* Used to load/save joystick 1 options */ static const struct Config_Tag configs_Joystick1[] = { { "nJoystickMode", Int_Tag, &ConfigureParams.Joysticks.Joy[1].nJoystickMode }, { "bEnableAutoFire", Bool_Tag, &ConfigureParams.Joysticks.Joy[1].bEnableAutoFire }, { "bEnableJumpOnFire2", Bool_Tag, &ConfigureParams.Joysticks.Joy[1].bEnableJumpOnFire2 }, { "nJoyId", Int_Tag, &ConfigureParams.Joysticks.Joy[1].nJoyId }, { "nJoyBut1Index", Int_Tag, &ConfigureParams.Joysticks.Joy[1].nJoyButMap[0] }, { "nJoyBut2Index", Int_Tag, &ConfigureParams.Joysticks.Joy[1].nJoyButMap[1] }, { "nJoyBut3Index", Int_Tag, &ConfigureParams.Joysticks.Joy[1].nJoyButMap[2] }, { "kUp", Key_Tag, &ConfigureParams.Joysticks.Joy[1].nKeyCodeUp }, { "kDown", Key_Tag, &ConfigureParams.Joysticks.Joy[1].nKeyCodeDown }, { "kLeft", Key_Tag, &ConfigureParams.Joysticks.Joy[1].nKeyCodeLeft }, { "kRight", Key_Tag, &ConfigureParams.Joysticks.Joy[1].nKeyCodeRight }, { "kFire", Key_Tag, &ConfigureParams.Joysticks.Joy[1].nKeyCodeFire }, { NULL , Error_Tag, NULL } }; /* Used to load/save joystick 2 options (joypad A) */ static const struct Config_Tag configs_Joystick2[] = { { "nJoystickMode", Int_Tag, &ConfigureParams.Joysticks.Joy[2].nJoystickMode }, { "bEnableAutoFire", Bool_Tag, &ConfigureParams.Joysticks.Joy[2].bEnableAutoFire }, { "bEnableJumpOnFire2", Bool_Tag, &ConfigureParams.Joysticks.Joy[2].bEnableJumpOnFire2 }, { "nJoyId", Int_Tag, &ConfigureParams.Joysticks.Joy[2].nJoyId }, { "nJoyBut1Index", Int_Tag, &ConfigureParams.Joysticks.Joy[2].nJoyButMap[0] }, { "nJoyBut2Index", Int_Tag, &ConfigureParams.Joysticks.Joy[2].nJoyButMap[1] }, { "nJoyBut3Index", Int_Tag, &ConfigureParams.Joysticks.Joy[2].nJoyButMap[2] }, { "kUp", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeUp }, { "kDown", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeDown }, { "kLeft", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeLeft }, { "kRight", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeRight }, { "kFire", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeFire }, { "kButtonB", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeB }, { "kButtonC", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeC }, { "kButtonOption", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeOption }, { "kButtonPause", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodePause }, { "kButtonStar", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeStar }, { "kButtonHash", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeHash }, { "kButton0", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[0] }, { "kButton1", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[1] }, { "kButton2", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[2] }, { "kButton3", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[3] }, { "kButton4", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[4] }, { "kButton5", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[5] }, { "kButton6", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[6] }, { "kButton7", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[7] }, { "kButton8", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[8] }, { "kButton9", Key_Tag, &ConfigureParams.Joysticks.Joy[2].nKeyCodeNum[9] }, { NULL , Error_Tag, NULL } }; /* Used to load/save joystick 3 options (joypad B) */ static const struct Config_Tag configs_Joystick3[] = { { "nJoystickMode", Int_Tag, &ConfigureParams.Joysticks.Joy[3].nJoystickMode }, { "bEnableAutoFire", Bool_Tag, &ConfigureParams.Joysticks.Joy[3].bEnableAutoFire }, { "bEnableJumpOnFire2", Bool_Tag, &ConfigureParams.Joysticks.Joy[3].bEnableJumpOnFire2 }, { "nJoyId", Int_Tag, &ConfigureParams.Joysticks.Joy[3].nJoyId }, { "nJoyBut1Index", Int_Tag, &ConfigureParams.Joysticks.Joy[3].nJoyButMap[0] }, { "nJoyBut2Index", Int_Tag, &ConfigureParams.Joysticks.Joy[3].nJoyButMap[1] }, { "nJoyBut3Index", Int_Tag, &ConfigureParams.Joysticks.Joy[3].nJoyButMap[2] }, { "kUp", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeUp }, { "kDown", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeDown }, { "kLeft", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeLeft }, { "kRight", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeRight }, { "kFire", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeFire }, { "kButtonB", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeB }, { "kButtonC", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeC }, { "kButtonOption", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeOption }, { "kButtonPause", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodePause }, { "kButtonStar", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeStar }, { "kButtonHash", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeHash }, { "kButton0", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[0] }, { "kButton1", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[1] }, { "kButton2", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[2] }, { "kButton3", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[3] }, { "kButton4", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[4] }, { "kButton5", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[5] }, { "kButton6", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[6] }, { "kButton7", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[7] }, { "kButton8", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[8] }, { "kButton9", Key_Tag, &ConfigureParams.Joysticks.Joy[3].nKeyCodeNum[9] }, { NULL , Error_Tag, NULL } }; /* Used to load/save joystick 4 options */ static const struct Config_Tag configs_Joystick4[] = { { "nJoystickMode", Int_Tag, &ConfigureParams.Joysticks.Joy[4].nJoystickMode }, { "bEnableAutoFire", Bool_Tag, &ConfigureParams.Joysticks.Joy[4].bEnableAutoFire }, { "bEnableJumpOnFire2", Bool_Tag, &ConfigureParams.Joysticks.Joy[4].bEnableJumpOnFire2 }, { "nJoyId", Int_Tag, &ConfigureParams.Joysticks.Joy[4].nJoyId }, { "nJoyBut1Index", Int_Tag, &ConfigureParams.Joysticks.Joy[4].nJoyButMap[0] }, { "nJoyBut2Index", Int_Tag, &ConfigureParams.Joysticks.Joy[4].nJoyButMap[1] }, { "nJoyBut3Index", Int_Tag, &ConfigureParams.Joysticks.Joy[4].nJoyButMap[2] }, { "kUp", Key_Tag, &ConfigureParams.Joysticks.Joy[4].nKeyCodeUp }, { "kDown", Key_Tag, &ConfigureParams.Joysticks.Joy[4].nKeyCodeDown }, { "kLeft", Key_Tag, &ConfigureParams.Joysticks.Joy[4].nKeyCodeLeft }, { "kRight", Key_Tag, &ConfigureParams.Joysticks.Joy[4].nKeyCodeRight }, { "kFire", Key_Tag, &ConfigureParams.Joysticks.Joy[4].nKeyCodeFire }, { NULL , Error_Tag, NULL } }; /* Used to load/save joystick 5 options */ static const struct Config_Tag configs_Joystick5[] = { { "nJoystickMode", Int_Tag, &ConfigureParams.Joysticks.Joy[5].nJoystickMode }, { "bEnableAutoFire", Bool_Tag, &ConfigureParams.Joysticks.Joy[5].bEnableAutoFire }, { "bEnableJumpOnFire2", Bool_Tag, &ConfigureParams.Joysticks.Joy[5].bEnableJumpOnFire2 }, { "nJoyId", Int_Tag, &ConfigureParams.Joysticks.Joy[5].nJoyId }, { "nJoyBut1Index", Int_Tag, &ConfigureParams.Joysticks.Joy[5].nJoyButMap[0] }, { "nJoyBut2Index", Int_Tag, &ConfigureParams.Joysticks.Joy[5].nJoyButMap[1] }, { "nJoyBut3Index", Int_Tag, &ConfigureParams.Joysticks.Joy[5].nJoyButMap[2] }, { "kUp", Key_Tag, &ConfigureParams.Joysticks.Joy[5].nKeyCodeUp }, { "kDown", Key_Tag, &ConfigureParams.Joysticks.Joy[5].nKeyCodeDown }, { "kLeft", Key_Tag, &ConfigureParams.Joysticks.Joy[5].nKeyCodeLeft }, { "kRight", Key_Tag, &ConfigureParams.Joysticks.Joy[5].nKeyCodeRight }, { "kFire", Key_Tag, &ConfigureParams.Joysticks.Joy[5].nKeyCodeFire }, { NULL , Error_Tag, NULL } }; /* Used to load/save keyboard options */ static const struct Config_Tag configs_Keyboard[] = { { "bFastForwardKeyRepeat", Bool_Tag, &ConfigureParams.Keyboard.bFastForwardKeyRepeat }, { "nKeymapType", Int_Tag, &ConfigureParams.Keyboard.nKeymapType }, { "nCountryCode", Int_Tag, &ConfigureParams.Keyboard.nCountryCode }, { "nKbdLayout", Int_Tag, &ConfigureParams.Keyboard.nKbdLayout }, { "nLanguage", Int_Tag, &ConfigureParams.Keyboard.nLanguage }, { "szMappingFileName", String_Tag, ConfigureParams.Keyboard.szMappingFileName }, { NULL , Error_Tag, NULL } }; static bool bDisableKeyRepeat; static const struct Config_Tag configs_keyboard_old[] = { { "bDisableKeyRepeat", Bool_Tag, &bDisableKeyRepeat }, { NULL , Error_Tag, NULL } }; /* Used to load/save shortcut key bindings with modifiers options */ static const struct Config_Tag configs_ShortCutWithMod[] = { { "kOptions", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_OPTIONS] }, { "kFullScreen", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_FULLSCREEN] }, { "kBorders", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_BORDERS] }, { "kMouseMode", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_MOUSEGRAB] }, { "kColdReset", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_COLDRESET] }, { "kWarmReset", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_WARMRESET] }, { "kScreenShot", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_SCREENSHOT] }, { "kBossKey", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_BOSSKEY] }, { "kCursorEmu", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_CURSOREMU] }, { "kFastForward",Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_FASTFORWARD] }, { "kRecAnim", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_RECANIM] }, { "kRecSound", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_RECSOUND] }, { "kSound", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_SOUND] }, { "kPause", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_PAUSE] }, { "kDebugger", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_DEBUG] }, { "kQuit", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_QUIT] }, { "kLoadMem", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_LOADMEM] }, { "kSaveMem", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_SAVEMEM] }, { "kInsertDiskA",Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_INSERTDISKA] }, { "kSwitchJoy0", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_JOY_0] }, { "kSwitchJoy1", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_JOY_1] }, { "kSwitchPadA", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_PAD_A] }, { "kSwitchPadB", Key_Tag, &ConfigureParams.Shortcut.withModifier[SHORTCUT_PAD_B] }, { NULL , Error_Tag, NULL } }; /* Used to load/save shortcut key bindings without modifiers options */ static const struct Config_Tag configs_ShortCutWithoutMod[] = { { "kOptions", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_OPTIONS] }, { "kFullScreen", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_FULLSCREEN] }, { "kBorders", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_BORDERS] }, { "kMouseMode", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_MOUSEGRAB] }, { "kColdReset", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_COLDRESET] }, { "kWarmReset", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_WARMRESET] }, { "kScreenShot", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_SCREENSHOT] }, { "kBossKey", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_BOSSKEY] }, { "kCursorEmu", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_CURSOREMU] }, { "kFastForward",Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_FASTFORWARD] }, { "kRecAnim", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_RECANIM] }, { "kRecSound", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_RECSOUND] }, { "kSound", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_SOUND] }, { "kPause", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_PAUSE] }, { "kDebugger", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_DEBUG] }, { "kQuit", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_QUIT] }, { "kLoadMem", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_LOADMEM] }, { "kSaveMem", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_SAVEMEM] }, { "kInsertDiskA",Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_INSERTDISKA] }, { "kSwitchJoy0", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_JOY_0] }, { "kSwitchJoy1", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_JOY_1] }, { "kSwitchPadA", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_PAD_A] }, { "kSwitchPadB", Key_Tag, &ConfigureParams.Shortcut.withoutModifier[SHORTCUT_PAD_B] }, { NULL , Error_Tag, NULL } }; /* Used to load/save sound options */ static const struct Config_Tag configs_Sound[] = { { "bEnableMicrophone", Bool_Tag, &ConfigureParams.Sound.bEnableMicrophone }, { "bEnableSound", Bool_Tag, &ConfigureParams.Sound.bEnableSound }, { "bEnableSoundSync", Bool_Tag, &ConfigureParams.Sound.bEnableSoundSync }, { "nPlaybackFreq", Int_Tag, &ConfigureParams.Sound.nPlaybackFreq }, { "nSdlAudioBufferSize", Int_Tag, &ConfigureParams.Sound.SdlAudioBufferSize }, { "szYMCaptureFileName", String_Tag, ConfigureParams.Sound.szYMCaptureFileName }, { "YmVolumeMixing", Int_Tag, &ConfigureParams.Sound.YmVolumeMixing }, { NULL , Error_Tag, NULL } }; /* Used to load/save memory options */ static const struct Config_Tag configs_Memory[] = { { "nMemorySize", Int_Tag, &ConfigureParams.Memory.STRamSize_KB }, { "nTTRamSize", Int_Tag, &ConfigureParams.Memory.TTRamSize_KB }, { "bAutoSave", Bool_Tag, &ConfigureParams.Memory.bAutoSave }, { "szMemoryCaptureFileName", String_Tag, ConfigureParams.Memory.szMemoryCaptureFileName }, { "szAutoSaveFileName", String_Tag, ConfigureParams.Memory.szAutoSaveFileName }, { NULL , Error_Tag, NULL } }; /* Used to load/save floppy options */ static const struct Config_Tag configs_Floppy[] = { { "bAutoInsertDiskB", Bool_Tag, &ConfigureParams.DiskImage.bAutoInsertDiskB }, { "FastFloppy", Bool_Tag, &ConfigureParams.DiskImage.FastFloppy }, { "EnableDriveA", Bool_Tag, &ConfigureParams.DiskImage.EnableDriveA }, { "DriveA_NumberOfHeads", Int_Tag, &ConfigureParams.DiskImage.DriveA_NumberOfHeads }, { "EnableDriveB", Bool_Tag, &ConfigureParams.DiskImage.EnableDriveB }, { "DriveB_NumberOfHeads", Int_Tag, &ConfigureParams.DiskImage.DriveB_NumberOfHeads }, { "nWriteProtection", Int_Tag, &ConfigureParams.DiskImage.nWriteProtection }, { "szDiskAZipPath", String_Tag, ConfigureParams.DiskImage.szDiskZipPath[0] }, { "szDiskAFileName", String_Tag, ConfigureParams.DiskImage.szDiskFileName[0] }, { "szDiskBZipPath", String_Tag, ConfigureParams.DiskImage.szDiskZipPath[1] }, { "szDiskBFileName", String_Tag, ConfigureParams.DiskImage.szDiskFileName[1] }, { "szDiskImageDirectory", String_Tag, ConfigureParams.DiskImage.szDiskImageDirectory }, { NULL , Error_Tag, NULL } }; /* Used to load/save HD options */ static const struct Config_Tag configs_HardDisk[] = { { "nGemdosDrive", Int_Tag, &ConfigureParams.HardDisk.nGemdosDrive }, { "bBootFromHardDisk", Bool_Tag, &ConfigureParams.HardDisk.bBootFromHardDisk }, { "bUseHardDiskDirectory", Bool_Tag, &ConfigureParams.HardDisk.bUseHardDiskDirectories }, { "szHardDiskDirectory", String_Tag, ConfigureParams.HardDisk.szHardDiskDirectories[DRIVE_C] }, { "nGemdosCase", Int_Tag, &ConfigureParams.HardDisk.nGemdosCase }, { "nWriteProtection", Int_Tag, &ConfigureParams.HardDisk.nWriteProtection }, { "bFilenameConversion", Bool_Tag, &ConfigureParams.HardDisk.bFilenameConversion }, { "bGemdosHostTime", Bool_Tag, &ConfigureParams.HardDisk.bGemdosHostTime }, { NULL , Error_Tag, NULL } }; static const struct Config_Tag configs_HardDisk_Old[] = { /* only used for loading */ { "bUseHardDiskImage", Bool_Tag, &ConfigureParams.Acsi[0].bUseDevice }, { "szHardDiskImage", String_Tag, ConfigureParams.Acsi[0].sDeviceFile }, { "bUseIdeMasterHardDiskImage", Bool_Tag, &ConfigureParams.Ide[0].bUseDevice }, { "szIdeMasterHardDiskImage", String_Tag, ConfigureParams.Ide[0].sDeviceFile }, { "bUseIdeSlaveHardDiskImage", Bool_Tag, &ConfigureParams.Ide[1].bUseDevice }, { "szIdeSlaveHardDiskImage", String_Tag, ConfigureParams.Ide[1].sDeviceFile }, { NULL , Error_Tag, NULL } }; /* Used to load/save ACSI options */ static const struct Config_Tag configs_Acsi[] = { { "bUseDevice0", Bool_Tag, &ConfigureParams.Acsi[0].bUseDevice }, { "sDeviceFile0", String_Tag, ConfigureParams.Acsi[0].sDeviceFile }, { "nBlockSize0", Int_Tag, &ConfigureParams.Acsi[0].nBlockSize }, { "bUseDevice1", Bool_Tag, &ConfigureParams.Acsi[1].bUseDevice }, { "sDeviceFile1", String_Tag, ConfigureParams.Acsi[1].sDeviceFile }, { "nBlockSize1", Int_Tag, &ConfigureParams.Acsi[1].nBlockSize }, { "bUseDevice2", Bool_Tag, &ConfigureParams.Acsi[2].bUseDevice }, { "sDeviceFile2", String_Tag, ConfigureParams.Acsi[2].sDeviceFile }, { "nBlockSize2", Int_Tag, &ConfigureParams.Acsi[2].nBlockSize }, { "bUseDevice3", Bool_Tag, &ConfigureParams.Acsi[3].bUseDevice }, { "sDeviceFile3", String_Tag, ConfigureParams.Acsi[3].sDeviceFile }, { "nBlockSize3", Int_Tag, &ConfigureParams.Acsi[3].nBlockSize }, { "bUseDevice4", Bool_Tag, &ConfigureParams.Acsi[4].bUseDevice }, { "sDeviceFile4", String_Tag, ConfigureParams.Acsi[4].sDeviceFile }, { "nBlockSize4", Int_Tag, &ConfigureParams.Acsi[4].nBlockSize }, { "bUseDevice5", Bool_Tag, &ConfigureParams.Acsi[5].bUseDevice }, { "sDeviceFile5", String_Tag, ConfigureParams.Acsi[5].sDeviceFile }, { "nBlockSize5", Int_Tag, &ConfigureParams.Acsi[5].nBlockSize }, { "bUseDevice6", Bool_Tag, &ConfigureParams.Acsi[6].bUseDevice }, { "sDeviceFile6", String_Tag, ConfigureParams.Acsi[6].sDeviceFile }, { "nBlockSize6", Int_Tag, &ConfigureParams.Acsi[6].nBlockSize }, { "bUseDevice7", Bool_Tag, &ConfigureParams.Acsi[7].bUseDevice }, { "sDeviceFile7", String_Tag, ConfigureParams.Acsi[7].sDeviceFile }, { "nBlockSize7", Int_Tag, &ConfigureParams.Acsi[7].nBlockSize }, { NULL , Error_Tag, NULL } }; /* Used to load/save SCSI options */ static const struct Config_Tag configs_Scsi[] = { { "bUseDevice0", Bool_Tag, &ConfigureParams.Scsi[0].bUseDevice }, { "sDeviceFile0", String_Tag, ConfigureParams.Scsi[0].sDeviceFile }, { "nBlockSize0", Int_Tag, &ConfigureParams.Scsi[0].nBlockSize }, { "nScsiVersion0", Int_Tag, &ConfigureParams.Scsi[0].nScsiVersion }, { "bUseDevice1", Bool_Tag, &ConfigureParams.Scsi[1].bUseDevice }, { "sDeviceFile1", String_Tag, ConfigureParams.Scsi[1].sDeviceFile }, { "nBlockSize1", Int_Tag, &ConfigureParams.Scsi[1].nBlockSize }, { "nScsiVersion1", Int_Tag, &ConfigureParams.Scsi[1].nScsiVersion }, { "bUseDevice2", Bool_Tag, &ConfigureParams.Scsi[2].bUseDevice }, { "sDeviceFile2", String_Tag, ConfigureParams.Scsi[2].sDeviceFile }, { "nBlockSize2", Int_Tag, &ConfigureParams.Scsi[2].nBlockSize }, { "nScsiVersion2", Int_Tag, &ConfigureParams.Scsi[2].nScsiVersion }, { "bUseDevice3", Bool_Tag, &ConfigureParams.Scsi[3].bUseDevice }, { "sDeviceFile3", String_Tag, ConfigureParams.Scsi[3].sDeviceFile }, { "nBlockSize3", Int_Tag, &ConfigureParams.Scsi[3].nBlockSize }, { "nScsiVersion3", Int_Tag, &ConfigureParams.Scsi[3].nScsiVersion }, { "bUseDevice4", Bool_Tag, &ConfigureParams.Scsi[4].bUseDevice }, { "sDeviceFile4", String_Tag, ConfigureParams.Scsi[4].sDeviceFile }, { "nBlockSize4", Int_Tag, &ConfigureParams.Scsi[4].nBlockSize }, { "nScsiVersion4", Int_Tag, &ConfigureParams.Scsi[4].nScsiVersion }, { "bUseDevice5", Bool_Tag, &ConfigureParams.Scsi[5].bUseDevice }, { "sDeviceFile5", String_Tag, ConfigureParams.Scsi[5].sDeviceFile }, { "nBlockSize5", Int_Tag, &ConfigureParams.Scsi[5].nBlockSize }, { "nScsiVersion5", Int_Tag, &ConfigureParams.Scsi[5].nScsiVersion }, { "bUseDevice6", Bool_Tag, &ConfigureParams.Scsi[6].bUseDevice }, { "sDeviceFile6", String_Tag, ConfigureParams.Scsi[6].sDeviceFile }, { "nBlockSize6", Int_Tag, &ConfigureParams.Scsi[6].nBlockSize }, { "nScsiVersion6", Int_Tag, &ConfigureParams.Scsi[6].nScsiVersion }, { "bUseDevice7", Bool_Tag, &ConfigureParams.Scsi[7].bUseDevice }, { "sDeviceFile7", String_Tag, ConfigureParams.Scsi[7].sDeviceFile }, { "nBlockSize7", Int_Tag, &ConfigureParams.Scsi[7].nBlockSize }, { "nScsiVersion7", Int_Tag, &ConfigureParams.Scsi[7].nScsiVersion }, { NULL , Error_Tag, NULL } }; /* Used to load/save IDE options */ static const struct Config_Tag configs_Ide[] = { { "bUseDevice0", Bool_Tag, &ConfigureParams.Ide[0].bUseDevice }, { "nByteSwap0", Int_Tag, &ConfigureParams.Ide[0].nByteSwap }, { "sDeviceFile0", String_Tag, ConfigureParams.Ide[0].sDeviceFile }, { "nBlockSize0", Int_Tag, &ConfigureParams.Ide[0].nBlockSize }, { "nDeviceType0", Int_Tag, &ConfigureParams.Ide[0].nDeviceType }, { "bUseDevice1", Bool_Tag, &ConfigureParams.Ide[1].bUseDevice }, { "nByteSwap1", Int_Tag, &ConfigureParams.Ide[1].nByteSwap }, { "sDeviceFile1", String_Tag, ConfigureParams.Ide[1].sDeviceFile }, { "nBlockSize1", Int_Tag, &ConfigureParams.Ide[1].nBlockSize }, { "nDeviceType1", Int_Tag, &ConfigureParams.Ide[1].nDeviceType }, { NULL , Error_Tag, NULL } }; /* Used to load/save ROM options */ static const struct Config_Tag configs_Rom[] = { { "szTosImageFileName", String_Tag, ConfigureParams.Rom.szTosImageFileName }, { "bPatchTos", Bool_Tag, &ConfigureParams.Rom.bPatchTos }, { "szCartridgeImageFileName", String_Tag, ConfigureParams.Rom.szCartridgeImageFileName }, { NULL , Error_Tag, NULL } }; /* Used to load/save LILO options, names are same as with Aranym */ static const struct Config_Tag configs_Lilo[] = { { "Args", String_Tag, ConfigureParams.Lilo.szCommandLine }, { "Kernel", String_Tag, ConfigureParams.Lilo.szKernelFileName }, { "Symbols", String_Tag, ConfigureParams.Lilo.szKernelSymbols }, { "Ramdisk", String_Tag, ConfigureParams.Lilo.szRamdiskFileName }, { "HaltOnReboot", Bool_Tag, &ConfigureParams.Lilo.bHaltOnReboot }, { "KernelToFastRam", Bool_Tag, &ConfigureParams.Lilo.bKernelToFastRam }, { "RamdiskToFastRam", Bool_Tag, &ConfigureParams.Lilo.bRamdiskToFastRam }, { NULL , Error_Tag, NULL } }; /* Used to load/save RS232 options */ static const struct Config_Tag configs_Rs232[] = { { "bEnableRS232", Bool_Tag, &ConfigureParams.RS232.bEnableRS232 }, { "szOutFileName", String_Tag, ConfigureParams.RS232.szOutFileName }, { "szInFileName", String_Tag, ConfigureParams.RS232.szInFileName }, { "EnableSccA", Bool_Tag, &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL] }, { "SccAOutFileName", String_Tag, ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL] }, { "SccAInFileName", String_Tag, ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL] }, { "EnableSccALan", Bool_Tag, &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN] }, { "SccALanOutFileName", String_Tag, ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN] }, { "SccALanInFileName", String_Tag, ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN] }, { "EnableSccB", Bool_Tag, &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B] }, { "SccBOutFileName", String_Tag, ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B] }, { "SccBInFileName", String_Tag, ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B] }, { NULL , Error_Tag, NULL } }; /* Used to load/save printer options */ static const struct Config_Tag configs_Printer[] = { { "bEnablePrinting", Bool_Tag, &ConfigureParams.Printer.bEnablePrinting }, { "szPrintToFileName", String_Tag, ConfigureParams.Printer.szPrintToFileName }, { NULL , Error_Tag, NULL } }; /* Used to load/save MIDI options */ static const struct Config_Tag configs_Midi[] = { { "bEnableMidi", Bool_Tag, &ConfigureParams.Midi.bEnableMidi }, { "sMidiInFileName", String_Tag, ConfigureParams.Midi.sMidiInFileName }, { "sMidiOutFileName", String_Tag, ConfigureParams.Midi.sMidiOutFileName }, { "sMidiInPortName", String_Tag, ConfigureParams.Midi.sMidiInPortName }, { "sMidiOutPortName", String_Tag, ConfigureParams.Midi.sMidiOutPortName }, { NULL , Error_Tag, NULL } }; /* Used to load system options from old config files */ static int nOldMachineType; static bool bOldRealTimeClock; static const struct Config_Tag configs_System_Old[] = { { "nMachineType", Int_Tag, &nOldMachineType }, { "bRealTimeClock", Bool_Tag, &bOldRealTimeClock }, { NULL , Error_Tag, NULL } }; /* Used to load/save system options */ static const struct Config_Tag configs_System[] = { { "nCpuLevel", Int_Tag, &ConfigureParams.System.nCpuLevel }, { "nCpuFreq", Int_Tag, &ConfigureParams.System.nCpuFreq }, { "bCompatibleCpu", Bool_Tag, &ConfigureParams.System.bCompatibleCpu }, { "nModelType", Int_Tag, &ConfigureParams.System.nMachineType }, { "bBlitter", Bool_Tag, &ConfigureParams.System.bBlitter }, { "nDSPType", Int_Tag, &ConfigureParams.System.nDSPType }, { "nRtcYear", Int_Tag, &ConfigureParams.System.nRtcYear }, { "bPatchTimerD", Bool_Tag, &ConfigureParams.System.bPatchTimerD }, { "bFastBoot", Bool_Tag, &ConfigureParams.System.bFastBoot }, { "bFastForward", Bool_Tag, &ConfigureParams.System.bFastForward }, { "bAddressSpace24", Bool_Tag, &ConfigureParams.System.bAddressSpace24 }, { "bCycleExactCpu", Bool_Tag, &ConfigureParams.System.bCycleExactCpu }, { "bCpuDataCache", Bool_Tag, &ConfigureParams.System.bCpuDataCache }, { "n_FPUType", Int_Tag, &ConfigureParams.System.n_FPUType }, /* JIT { "bCompatibleFPU", Bool_Tag, &ConfigureParams.System.bCompatibleFPU }, */ { "bSoftFloatFPU", Bool_Tag, &ConfigureParams.System.bSoftFloatFPU }, { "bMMU", Bool_Tag, &ConfigureParams.System.bMMU }, { "VideoTiming", Int_Tag, &ConfigureParams.System.VideoTimingMode }, { NULL , Error_Tag, NULL } }; /* Used to load/save video options */ static const struct Config_Tag configs_Video[] = { { "AviRecordVcodec", Int_Tag, &ConfigureParams.Video.AviRecordVcodec }, { "AviRecordFps", Int_Tag, &ConfigureParams.Video.AviRecordFps }, { "AviRecordFile", String_Tag, ConfigureParams.Video.AviRecordFile }, { NULL , Error_Tag, NULL } }; /*-----------------------------------------------------------------------*/ /** * Set default configuration values. */ void Configuration_SetDefault(void) { int i, maxjoy; const char *psHomeDir; const char *psWorkingDir; psHomeDir = Paths_GetHatariHome(); psWorkingDir = Paths_GetWorkingDir(); /* Clear parameters */ memset(&ConfigureParams, 0, sizeof(CNF_PARAMS)); /* Set defaults for logging and tracing */ strcpy(ConfigureParams.Log.sLogFileName, "stderr"); strcpy(ConfigureParams.Log.sTraceFileName, "stderr"); ConfigureParams.Log.nTextLogLevel = LOG_INFO; ConfigureParams.Log.nAlertDlgLogLevel = LOG_ERROR; ConfigureParams.Log.bConfirmQuit = true; ConfigureParams.Log.bNatFeats = false; ConfigureParams.Log.bConsoleWindow = false; /* Set defaults for debugger */ ConfigureParams.Debugger.nNumberBase = 10; ConfigureParams.Debugger.nSymbolLines = -1; /* <0: use terminal size */ ConfigureParams.Debugger.nMemdumpLines = -1; /* <0: use terminal size */ ConfigureParams.Debugger.nFindLines = -1; /* <0: use terminal size */ ConfigureParams.Debugger.nDisasmLines = -1; /* <0: use terminal size */ ConfigureParams.Debugger.nBacktraceLines = 0; /* <=0: show all */ ConfigureParams.Debugger.nExceptionDebugMask = DEFAULT_EXCEPTIONS; /* external one has nicer output, but isn't as complete as UAE one */ ConfigureParams.Debugger.bDisasmUAE = true; ConfigureParams.Debugger.bSymbolsAutoLoad = true; ConfigureParams.Debugger.bMatchAllSymbols = false; ConfigureParams.Debugger.nDisasmOptions = Disasm_GetOptions(); Disasm_Init(); /* Set defaults for floppy disk images */ ConfigureParams.DiskImage.bAutoInsertDiskB = true; ConfigureParams.DiskImage.FastFloppy = false; ConfigureParams.DiskImage.nWriteProtection = WRITEPROT_OFF; ConfigureParams.DiskImage.EnableDriveA = true; FDC_Drive_Set_Enable ( 0 , ConfigureParams.DiskImage.EnableDriveA ); ConfigureParams.DiskImage.DriveA_NumberOfHeads = 2; FDC_Drive_Set_NumberOfHeads ( 0 , ConfigureParams.DiskImage.DriveA_NumberOfHeads ); ConfigureParams.DiskImage.EnableDriveB = true; FDC_Drive_Set_Enable ( 1 , ConfigureParams.DiskImage.EnableDriveB ); ConfigureParams.DiskImage.DriveB_NumberOfHeads = 2; FDC_Drive_Set_NumberOfHeads ( 1 , ConfigureParams.DiskImage.DriveB_NumberOfHeads ); for (i = 0; i < MAX_FLOPPYDRIVES; i++) { ConfigureParams.DiskImage.szDiskZipPath[i][0] = '\0'; ConfigureParams.DiskImage.szDiskFileName[i][0] = '\0'; } strcpy(ConfigureParams.DiskImage.szDiskImageDirectory, psWorkingDir); File_AddSlashToEndFileName(ConfigureParams.DiskImage.szDiskImageDirectory); /* Set defaults for hard disks */ ConfigureParams.HardDisk.bBootFromHardDisk = false; ConfigureParams.HardDisk.bFilenameConversion = false; ConfigureParams.HardDisk.bGemdosHostTime = false; ConfigureParams.HardDisk.nGemdosCase = GEMDOS_NOP; ConfigureParams.HardDisk.nWriteProtection = WRITEPROT_OFF; ConfigureParams.HardDisk.nGemdosDrive = DRIVE_C; ConfigureParams.HardDisk.bUseHardDiskDirectories = false; for (i = 0; i < MAX_HARDDRIVES; i++) { strcpy(ConfigureParams.HardDisk.szHardDiskDirectories[i], psWorkingDir); File_CleanFileName(ConfigureParams.HardDisk.szHardDiskDirectories[i]); } /* ACSI */ for (i = 0; i < MAX_ACSI_DEVS; i++) { ConfigureParams.Acsi[i].bUseDevice = false; strcpy(ConfigureParams.Acsi[i].sDeviceFile, psWorkingDir); ConfigureParams.Acsi[i].nBlockSize = 512; } /* SCSI */ for (i = 0; i < MAX_SCSI_DEVS; i++) { ConfigureParams.Scsi[i].bUseDevice = false; strcpy(ConfigureParams.Scsi[i].sDeviceFile, psWorkingDir); ConfigureParams.Scsi[i].nBlockSize = 512; ConfigureParams.Scsi[i].nScsiVersion = 1; } /* IDE */ for (i = 0; i < MAX_IDE_DEVS; i++) { ConfigureParams.Ide[i].bUseDevice = false; ConfigureParams.Ide[i].nByteSwap = BYTESWAP_AUTO; strcpy(ConfigureParams.Ide[i].sDeviceFile, psWorkingDir); ConfigureParams.Ide[i].nBlockSize = 512; } /* Set defaults for Joysticks */ maxjoy = Joy_GetMaxId(); for (i = 0; i < JOYSTICK_COUNT; i++) { ConfigureParams.Joysticks.Joy[i].nJoystickMode = JOYSTICK_DISABLED; ConfigureParams.Joysticks.Joy[i].bEnableAutoFire = false; ConfigureParams.Joysticks.Joy[i].bEnableJumpOnFire2 = true; ConfigureParams.Joysticks.Joy[i].nJoyId = (i > maxjoy ? maxjoy : i); for (int j = 0; j < JOYSTICK_BUTTONS; j++) ConfigureParams.Joysticks.Joy[i].nJoyButMap[j] = j; ConfigureParams.Joysticks.Joy[i].nKeyCodeUp = SDLK_UP; ConfigureParams.Joysticks.Joy[i].nKeyCodeDown = SDLK_DOWN; ConfigureParams.Joysticks.Joy[i].nKeyCodeLeft = SDLK_LEFT; ConfigureParams.Joysticks.Joy[i].nKeyCodeRight = SDLK_RIGHT; ConfigureParams.Joysticks.Joy[i].nKeyCodeFire = SDLK_RCTRL; } for (i = 0; i <= 9; i++) ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodeNum[i] = SDLK_0 + i; ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodeB = SDLK_b; ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodeC = SDLK_c; ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodeOption = SDLK_o; ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodePause = SDLK_p; ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodeHash = SDLK_HASH; ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nKeyCodeStar = SDLK_PLUS; if (SDL_NumJoysticks() > 0) { /* ST Joystick #1 is default joystick */ ConfigureParams.Joysticks.Joy[1].nJoyId = 0; ConfigureParams.Joysticks.Joy[0].nJoyId = (maxjoy ? 1 : 0); ConfigureParams.Joysticks.Joy[1].nJoystickMode = JOYSTICK_REALSTICK; ConfigureParams.Joysticks.Joy[1].bEnableJumpOnFire2 = false; } /* Set defaults for Keyboard */ ConfigureParams.Keyboard.bFastForwardKeyRepeat = true; ConfigureParams.Keyboard.nKeymapType = KEYMAP_SYMBOLIC; ConfigureParams.Keyboard.nCountryCode = TOS_LANG_UNKNOWN; ConfigureParams.Keyboard.nKbdLayout = TOS_LANG_UNKNOWN; ConfigureParams.Keyboard.nLanguage = TOS_LANG_UNKNOWN; strcpy(ConfigureParams.Keyboard.szMappingFileName, ""); /* Set defaults for Shortcuts */ ConfigureParams.Shortcut.withoutModifier[SHORTCUT_OPTIONS] = SDLK_F12; ConfigureParams.Shortcut.withoutModifier[SHORTCUT_FULLSCREEN] = SDLK_F11; ConfigureParams.Shortcut.withoutModifier[SHORTCUT_PAUSE] = SDLK_PAUSE; ConfigureParams.Shortcut.withModifier[SHORTCUT_DEBUG] = SDLK_PAUSE; ConfigureParams.Shortcut.withModifier[SHORTCUT_OPTIONS] = SDLK_o; ConfigureParams.Shortcut.withModifier[SHORTCUT_FULLSCREEN] = SDLK_f; ConfigureParams.Shortcut.withModifier[SHORTCUT_BORDERS] = SDLK_b; ConfigureParams.Shortcut.withModifier[SHORTCUT_MOUSEGRAB] = SDLK_m; ConfigureParams.Shortcut.withModifier[SHORTCUT_COLDRESET] = SDLK_c; ConfigureParams.Shortcut.withModifier[SHORTCUT_WARMRESET] = SDLK_r; ConfigureParams.Shortcut.withModifier[SHORTCUT_SCREENSHOT] = SDLK_g; ConfigureParams.Shortcut.withModifier[SHORTCUT_BOSSKEY] = SDLK_i; ConfigureParams.Shortcut.withModifier[SHORTCUT_CURSOREMU] = SDLK_j; ConfigureParams.Shortcut.withModifier[SHORTCUT_FASTFORWARD] = SDLK_x; ConfigureParams.Shortcut.withModifier[SHORTCUT_RECANIM] = SDLK_a; ConfigureParams.Shortcut.withModifier[SHORTCUT_RECSOUND] = SDLK_y; ConfigureParams.Shortcut.withModifier[SHORTCUT_SOUND] = SDLK_s; ConfigureParams.Shortcut.withModifier[SHORTCUT_QUIT] = SDLK_q; ConfigureParams.Shortcut.withModifier[SHORTCUT_LOADMEM] = SDLK_l; ConfigureParams.Shortcut.withModifier[SHORTCUT_SAVEMEM] = SDLK_k; ConfigureParams.Shortcut.withModifier[SHORTCUT_INSERTDISKA] = SDLK_d; ConfigureParams.Shortcut.withModifier[SHORTCUT_JOY_0] = SDLK_F1; ConfigureParams.Shortcut.withModifier[SHORTCUT_JOY_1] = SDLK_F2; ConfigureParams.Shortcut.withModifier[SHORTCUT_PAD_A] = SDLK_F3; ConfigureParams.Shortcut.withModifier[SHORTCUT_PAD_B] = SDLK_F4; /* Set defaults for Memory */ ConfigureParams.Memory.STRamSize_KB = 1024; /* 1 MiB */ ConfigureParams.Memory.TTRamSize_KB = 0; /* disabled */ ConfigureParams.Memory.bAutoSave = false; File_MakePathBuf(ConfigureParams.Memory.szMemoryCaptureFileName, sizeof(ConfigureParams.Memory.szMemoryCaptureFileName), psHomeDir, "hatari", "sav"); File_MakePathBuf(ConfigureParams.Memory.szAutoSaveFileName, sizeof(ConfigureParams.Memory.szAutoSaveFileName), psHomeDir, "auto", "sav"); /* Set defaults for Printer */ ConfigureParams.Printer.bEnablePrinting = false; File_MakePathBuf(ConfigureParams.Printer.szPrintToFileName, sizeof(ConfigureParams.Printer.szPrintToFileName), psHomeDir, "hatari", "prn"); /* Set defaults for MFP RS232 (ST/MegaST/STE/MegaSTE/TT) */ ConfigureParams.RS232.bEnableRS232 = false; strcpy(ConfigureParams.RS232.szOutFileName, "/dev/modem"); strcpy(ConfigureParams.RS232.szInFileName, "/dev/modem"); /* Set defaults for SCC RS232 ( MegaSTE/TT/Falcon) */ ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL] = false; strcpy(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL], "/dev/modem"); strcpy(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL], "/dev/modem"); ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN] = false; strcpy(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN], "/dev/modem"); strcpy(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN], "/dev/modem"); ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B] = false; strcpy(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B], "/dev/modem"); strcpy(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B], "/dev/modem"); /* Set defaults for MIDI */ ConfigureParams.Midi.bEnableMidi = false; strcpy(ConfigureParams.Midi.sMidiInFileName, "/dev/snd/midiC1D0"); strcpy(ConfigureParams.Midi.sMidiOutFileName, "/dev/snd/midiC1D0"); strcpy(ConfigureParams.Midi.sMidiInPortName, "Off"); strcpy(ConfigureParams.Midi.sMidiOutPortName, "Off"); /* Set defaults for Screen */ ConfigureParams.Screen.bFullScreen = false; ConfigureParams.Screen.bKeepResolution = true; ConfigureParams.Screen.bResizable = true; ConfigureParams.Screen.nFrameSkips = AUTO_FRAMESKIP_LIMIT; ConfigureParams.Screen.bAllowOverscan = true; ConfigureParams.Screen.nSpec512Threshold = 1; ConfigureParams.Screen.bAspectCorrect = true; ConfigureParams.Screen.nMonitorType = MONITOR_TYPE_RGB; ConfigureParams.Screen.bUseExtVdiResolutions = false; ConfigureParams.Screen.nVdiWidth = 640; ConfigureParams.Screen.nVdiHeight = 480; ConfigureParams.Screen.nVdiColors = GEMCOLOR_16; ConfigureParams.Screen.bMouseWarp = true; ConfigureParams.Screen.bShowStatusbar = true; ConfigureParams.Screen.bShowDriveLed = true; ConfigureParams.Screen.bCrop = false; /* use approximately similar Hatari framebuffer/window size * on all resolutions (like real Atari monitor would do) by * allowing ST low resolution to be doubled (see screen.c) */ ConfigureParams.Screen.nMaxWidth = 2*NUM_VISIBLE_LINE_PIXELS; ConfigureParams.Screen.nMaxHeight = 2*NUM_VISIBLE_LINES+STATUSBAR_MAX_HEIGHT; ConfigureParams.Screen.bForceMax = false; ConfigureParams.Screen.DisableVideo = false; ConfigureParams.Screen.nZoomFactor = 1.0; ConfigureParams.Screen.bUseSdlRenderer = true; ConfigureParams.Screen.bUseVsync = false; #if HAVE_LIBPNG ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_PNG; #else ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_BMP; #endif ConfigureParams.Screen.szScreenShotDir[0] = '\0'; /* Set defaults for Sound */ ConfigureParams.Sound.bEnableMicrophone = true; ConfigureParams.Sound.bEnableSound = true; ConfigureParams.Sound.bEnableSoundSync = false; ConfigureParams.Sound.nPlaybackFreq = 44100; File_MakePathBuf(ConfigureParams.Sound.szYMCaptureFileName, sizeof(ConfigureParams.Sound.szYMCaptureFileName), psWorkingDir, "hatari", "wav"); ConfigureParams.Sound.SdlAudioBufferSize = 0; ConfigureParams.Sound.YmVolumeMixing = YM_TABLE_MIXING; /* Set defaults for Rom */ File_MakePathBuf(ConfigureParams.Rom.szTosImageFileName, sizeof(ConfigureParams.Rom.szTosImageFileName), Paths_GetDataDir(), "tos", "img"); ConfigureParams.Rom.bPatchTos = true; strcpy(ConfigureParams.Rom.szCartridgeImageFileName, ""); /* Set defaults for Lilo */ strcpy(ConfigureParams.Lilo.szCommandLine, "root=/dev/ram video=atafb:vga16 load_ramdisk=1"); File_MakePathBuf(ConfigureParams.Lilo.szKernelFileName, sizeof(ConfigureParams.Lilo.szKernelFileName), Paths_GetDataDir(), "vmlinuz", NULL); File_MakePathBuf(ConfigureParams.Lilo.szRamdiskFileName, sizeof(ConfigureParams.Lilo.szRamdiskFileName), Paths_GetDataDir(), "initrd", NULL); ConfigureParams.Lilo.szKernelSymbols[0] = '\0'; ConfigureParams.Lilo.bRamdiskToFastRam = true; ConfigureParams.Lilo.bKernelToFastRam = true; ConfigureParams.Lilo.bHaltOnReboot = true; /* Set defaults for System */ ConfigureParams.System.nMachineType = MACHINE_ST; ConfigureParams.System.nCpuLevel = 0; ConfigureParams.System.nCpuFreq = 8; nCpuFreqShift = 0; ConfigureParams.System.nDSPType = DSP_TYPE_NONE; ConfigureParams.System.nRtcYear = 0; ConfigureParams.System.bAddressSpace24 = true; ConfigureParams.System.n_FPUType = FPU_NONE; ConfigureParams.System.bCompatibleFPU = true; /* JIT */ ConfigureParams.System.bSoftFloatFPU = false; ConfigureParams.System.bMMU = false; ConfigureParams.System.bCpuDataCache = true; ConfigureParams.System.bCycleExactCpu = true; ConfigureParams.System.VideoTimingMode = VIDEO_TIMING_MODE_WS3; ConfigureParams.System.bCompatibleCpu = true; ConfigureParams.System.bBlitter = false; ConfigureParams.System.bPatchTimerD = false; ConfigureParams.System.bFastBoot = false; ConfigureParams.System.bFastForward = false; /* Set defaults for Video */ #if HAVE_LIBPNG ConfigureParams.Video.AviRecordVcodec = AVI_RECORD_VIDEO_CODEC_PNG; #else ConfigureParams.Video.AviRecordVcodec = AVI_RECORD_VIDEO_CODEC_BMP; #endif ConfigureParams.Video.AviRecordFps = 0; /* automatic FPS */ File_MakePathBuf(ConfigureParams.Video.AviRecordFile, sizeof(ConfigureParams.Video.AviRecordFile), psWorkingDir, "hatari", "avi"); /* Initialize the configuration file name */ if (File_MakePathBuf(sConfigFileName, sizeof(sConfigFileName), psHomeDir, "hatari", "cfg")) { strcpy(sConfigFileName, "hatari.cfg"); } } /*-----------------------------------------------------------------------*/ /** * Copy details from configuration structure into global variables for system, * clean file names, etc... Called from main.c and dialog.c files. */ void Configuration_Apply(bool bReset) { int i; int size; if (bReset) { /* Set resolution change */ bUseVDIRes = ConfigureParams.Screen.bUseExtVdiResolutions; bUseHighRes = ((!bUseVDIRes) && ConfigureParams.Screen.nMonitorType == MONITOR_TYPE_MONO) || (bUseVDIRes && ConfigureParams.Screen.nVdiColors == GEMCOLOR_2); if (bUseHighRes) { STRes = ST_HIGH_RES; } if (bUseVDIRes) { /* rest of VDI setup done in TOS init */ bVdiAesIntercept = true; } } if (ConfigureParams.Screen.nFrameSkips < AUTO_FRAMESKIP_LIMIT) { nFrameSkips = ConfigureParams.Screen.nFrameSkips; } /* Check/convert ST RAM size in KB */ size = STMemory_RAM_Validate_Size_KB ( ConfigureParams.Memory.STRamSize_KB ); if ( size < 0 ) { size = 1024; Log_Printf(LOG_WARN, "Unsupported %d KB ST-RAM amount, defaulting to %d KB\n", ConfigureParams.Memory.STRamSize_KB, size); } ConfigureParams.Memory.STRamSize_KB = size; STMemory_Init ( ConfigureParams.Memory.STRamSize_KB * 1024 ); /* Update variables depending on the new CPU Freq (to do before other ClocksTimings_xxx functions) */ Configuration_ChangeCpuFreq ( ConfigureParams.System.nCpuFreq ); /* Init clocks for this machine */ ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); /* Set video timings for this machine */ Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); /* Sound settings */ /* SDL sound buffer in ms (or 0 for using the default value from SDL) */ SdlAudioBufferSize = ConfigureParams.Sound.SdlAudioBufferSize; if (SdlAudioBufferSize < 10 && SdlAudioBufferSize != 0) SdlAudioBufferSize = 10; /* min of 10 ms */ else if (SdlAudioBufferSize > 100) SdlAudioBufferSize = 100; /* max of 100 ms */ /* Set playback frequency */ Audio_SetOutputAudioFreq(ConfigureParams.Sound.nPlaybackFreq); /* YM Mixing */ if ( ( ConfigureParams.Sound.YmVolumeMixing != YM_LINEAR_MIXING ) && ( ConfigureParams.Sound.YmVolumeMixing != YM_TABLE_MIXING ) && ( ConfigureParams.Sound.YmVolumeMixing != YM_MODEL_MIXING ) ) ConfigureParams.Sound.YmVolumeMixing = YM_TABLE_MIXING; YmVolumeMixing = ConfigureParams.Sound.YmVolumeMixing; Sound_SetYmVolumeMixing(); /* Falcon : update clocks values if sound freq changed */ if ( Config_IsMachineFalcon() ) Crossbar_Recalculate_Clocks_Cycles(); /* Check/constrain CPU settings and change corresponding * cpu_model/cpu_compatible/cpu_cycle_exact/... variables */ //fprintf (stderr,"M68000_CheckCpuSettings conf 1\n" ); M68000_CheckCpuSettings(); //fprintf (stderr,"M68000_CheckCpuSettings conf 2\n" ); /* Disable invalid joystick mappings */ for (i = 0; i < JOYSTICK_COUNT; i++) { int joyid = ConfigureParams.Joysticks.Joy[i].nJoyId; if (joyid < 0 || joyid >= JOYSTICK_COUNT) { if (ConfigureParams.Joysticks.Joy[i].nJoystickMode == JOYSTICK_REALSTICK) { Log_Printf(LOG_WARN, "Selected real Joystick %d unavailable, disabling ST joystick %d\n", joyid, i); ConfigureParams.Joysticks.Joy[i].nJoystickMode = JOYSTICK_DISABLED; } /* otherwise it may result in invalid array access */ ConfigureParams.Joysticks.Joy[i].nJoyId = 0; } } /* Clean file and directory names */ File_MakeAbsoluteName(ConfigureParams.Rom.szTosImageFileName); if (strlen(ConfigureParams.Rom.szCartridgeImageFileName) > 0) File_MakeAbsoluteName(ConfigureParams.Rom.szCartridgeImageFileName); if (strlen(ConfigureParams.Lilo.szKernelFileName) > 0) File_MakeAbsoluteName(ConfigureParams.Lilo.szKernelFileName); if (strlen(ConfigureParams.Lilo.szKernelSymbols) > 0) File_MakeAbsoluteName(ConfigureParams.Lilo.szKernelSymbols); if (strlen(ConfigureParams.Lilo.szRamdiskFileName) > 0) File_MakeAbsoluteName(ConfigureParams.Lilo.szRamdiskFileName); File_CleanFileName(ConfigureParams.HardDisk.szHardDiskDirectories[0]); File_MakeAbsoluteName(ConfigureParams.HardDisk.szHardDiskDirectories[0]); File_MakeAbsoluteName(ConfigureParams.Memory.szMemoryCaptureFileName); if (strlen(ConfigureParams.Screen.szScreenShotDir) > 0) { File_CleanFileName(ConfigureParams.Screen.szScreenShotDir); File_MakeAbsoluteName(ConfigureParams.Screen.szScreenShotDir); } File_MakeAbsoluteName(ConfigureParams.Sound.szYMCaptureFileName); if (strlen(ConfigureParams.Keyboard.szMappingFileName) > 0) File_MakeAbsoluteName(ConfigureParams.Keyboard.szMappingFileName); File_MakeAbsoluteName(ConfigureParams.Video.AviRecordFile); for (i = 0; i < MAX_ACSI_DEVS; i++) { File_MakeAbsoluteName(ConfigureParams.Acsi[i].sDeviceFile); } for (i = 0; i < MAX_SCSI_DEVS; i++) { File_MakeAbsoluteName(ConfigureParams.Scsi[i].sDeviceFile); } for (i = 0; i < MAX_IDE_DEVS; i++) { File_MakeAbsoluteName(ConfigureParams.Ide[i].sDeviceFile); } /* make path names absolute, but handle special file names */ File_MakeAbsoluteSpecialName(ConfigureParams.Log.sLogFileName); File_MakeAbsoluteSpecialName(ConfigureParams.Log.sTraceFileName); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.szInFileName); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.szOutFileName); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL]); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL]); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN]); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN]); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B]); File_MakeAbsoluteSpecialName(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B]); File_MakeAbsoluteSpecialName(ConfigureParams.Midi.sMidiInFileName); File_MakeAbsoluteSpecialName(ConfigureParams.Midi.sMidiOutFileName); File_MakeAbsoluteSpecialName(ConfigureParams.Printer.szPrintToFileName); /* Enable/disable floppy drives */ FDC_Drive_Set_Enable ( 0 , ConfigureParams.DiskImage.EnableDriveA ); FDC_Drive_Set_Enable ( 1 , ConfigureParams.DiskImage.EnableDriveB ); FDC_Drive_Set_NumberOfHeads ( 0 , ConfigureParams.DiskImage.DriveA_NumberOfHeads ); FDC_Drive_Set_NumberOfHeads ( 1 , ConfigureParams.DiskImage.DriveB_NumberOfHeads ); /* Update disassembler */ Disasm_Init(); #if ENABLE_DSP_EMU /* Enable DSP ? */ if ( ConfigureParams.System.nDSPType == DSP_TYPE_EMU ) DSP_Enable (); else DSP_Disable (); #endif } /*-----------------------------------------------------------------------*/ /** * Load a settings section from the configuration file. */ static int Configuration_LoadSection(const char *pFilename, const struct Config_Tag configs[], const char *pSection) { int ret; ret = input_config(pFilename, configs, pSection); if (ret < 0) Log_Printf(LOG_ERROR, "cannot load configuration file %s (section %s).\n", pFilename, pSection); return ret; } /*-----------------------------------------------------------------------*/ /** * Load program setting from configuration file. If psFileName is NULL, use * the configuration file given in configuration / last selected by user. */ void Configuration_Load(const char *psFileName) { if (psFileName == NULL) psFileName = sConfigFileName; if (!File_Exists(psFileName)) { Log_Printf(LOG_DEBUG, "Configuration file %s not found.\n", psFileName); return; } /* Try to load information from old config files */ nOldMachineType = -1; Configuration_LoadSection(psFileName, configs_System_Old, "[System]"); switch (nOldMachineType) { case 0: if (!bOldRealTimeClock) ConfigureParams.System.nMachineType = MACHINE_ST; else ConfigureParams.System.nMachineType = MACHINE_MEGA_ST; break; case 1: ConfigureParams.System.nMachineType = MACHINE_STE; break; case 2: ConfigureParams.System.nMachineType = MACHINE_TT; break; case 3: ConfigureParams.System.nMachineType = MACHINE_FALCON; break; } Configuration_LoadSection(psFileName, configs_HardDisk_Old, "[HardDisk]"); Configuration_LoadSection(psFileName, configs_keyboard_old, "[Keyboard]"); if (bDisableKeyRepeat) ConfigureParams.Keyboard.bFastForwardKeyRepeat = false; /* Now the regular loading of the sections: * Start with Log so that logging works as early as possible */ Configuration_LoadSection(psFileName, configs_Log, "[Log]"); Log_SetLevels(); Configuration_LoadSection(psFileName, configs_Debugger, "[Debugger]"); Configuration_LoadSection(psFileName, configs_Screen, "[Screen]"); Configuration_LoadSection(psFileName, configs_Joystick0, "[Joystick0]"); Configuration_LoadSection(psFileName, configs_Joystick1, "[Joystick1]"); Configuration_LoadSection(psFileName, configs_Joystick2, "[Joystick2]"); Configuration_LoadSection(psFileName, configs_Joystick3, "[Joystick3]"); Configuration_LoadSection(psFileName, configs_Joystick4, "[Joystick4]"); Configuration_LoadSection(psFileName, configs_Joystick5, "[Joystick5]"); Configuration_LoadSection(psFileName, configs_Keyboard, "[Keyboard]"); Configuration_LoadSection(psFileName, configs_ShortCutWithMod, "[KeyShortcutsWithMod]"); Configuration_LoadSection(psFileName, configs_ShortCutWithoutMod, "[KeyShortcutsWithoutMod]"); Configuration_LoadSection(psFileName, configs_Sound, "[Sound]"); Configuration_LoadSection(psFileName, configs_Memory, "[Memory]"); Configuration_LoadSection(psFileName, configs_Floppy, "[Floppy]"); Configuration_LoadSection(psFileName, configs_HardDisk, "[HardDisk]"); Configuration_LoadSection(psFileName, configs_Acsi, "[ACSI]"); Configuration_LoadSection(psFileName, configs_Scsi, "[SCSI]"); Configuration_LoadSection(psFileName, configs_Ide, "[IDE]"); Configuration_LoadSection(psFileName, configs_Rom, "[ROM]"); Configuration_LoadSection(psFileName, configs_Lilo, "[LILO]"); Configuration_LoadSection(psFileName, configs_Rs232, "[RS232]"); Configuration_LoadSection(psFileName, configs_Printer, "[Printer]"); Configuration_LoadSection(psFileName, configs_Midi, "[Midi]"); Configuration_LoadSection(psFileName, configs_System, "[System]"); Configuration_LoadSection(psFileName, configs_Video, "[Video]"); /* Some more legacy handling: */ if (ConfigureParams.Keyboard.nKeymapType >= KEYMAP_OLD_LOADED) { ConfigureParams.Keyboard.nKeymapType = KEYMAP_SYMBOLIC; } } /*-----------------------------------------------------------------------*/ /** * Save a settings section to configuration file */ static int Configuration_SaveSection(const char *pFilename, const struct Config_Tag configs[], const char *pSection) { int ret; ret = update_config(pFilename, configs, pSection); if (ret < 0) Log_Printf(LOG_ERROR, "cannot save configuration file %s (section %s)\n", pFilename, pSection); return ret; } /*-----------------------------------------------------------------------*/ /** * Save program setting to configuration file */ void Configuration_Save(void) { if (Configuration_SaveSection(sConfigFileName, configs_Log, "[Log]") < 0) { Log_AlertDlg(LOG_ERROR, "Error saving config file."); return; } Configuration_SaveSection(sConfigFileName, configs_Debugger, "[Debugger]"); Configuration_SaveSection(sConfigFileName, configs_Screen, "[Screen]"); Configuration_SaveSection(sConfigFileName, configs_Joystick0, "[Joystick0]"); Configuration_SaveSection(sConfigFileName, configs_Joystick1, "[Joystick1]"); Configuration_SaveSection(sConfigFileName, configs_Joystick2, "[Joystick2]"); Configuration_SaveSection(sConfigFileName, configs_Joystick3, "[Joystick3]"); Configuration_SaveSection(sConfigFileName, configs_Joystick4, "[Joystick4]"); Configuration_SaveSection(sConfigFileName, configs_Joystick5, "[Joystick5]"); Configuration_SaveSection(sConfigFileName, configs_Keyboard, "[Keyboard]"); Configuration_SaveSection(sConfigFileName, configs_ShortCutWithMod, "[KeyShortcutsWithMod]"); Configuration_SaveSection(sConfigFileName, configs_ShortCutWithoutMod, "[KeyShortcutsWithoutMod]"); Configuration_SaveSection(sConfigFileName, configs_Sound, "[Sound]"); Configuration_SaveSection(sConfigFileName, configs_Memory, "[Memory]"); Configuration_SaveSection(sConfigFileName, configs_Floppy, "[Floppy]"); Configuration_SaveSection(sConfigFileName, configs_HardDisk, "[HardDisk]"); Configuration_SaveSection(sConfigFileName, configs_Acsi, "[ACSI]"); Configuration_SaveSection(sConfigFileName, configs_Scsi, "[SCSI]"); Configuration_SaveSection(sConfigFileName, configs_Ide, "[IDE]"); Configuration_SaveSection(sConfigFileName, configs_Rom, "[ROM]"); Configuration_SaveSection(sConfigFileName, configs_Lilo, "[LILO]"); Configuration_SaveSection(sConfigFileName, configs_Rs232, "[RS232]"); Configuration_SaveSection(sConfigFileName, configs_Printer, "[Printer]"); Configuration_SaveSection(sConfigFileName, configs_Midi, "[Midi]"); Configuration_SaveSection(sConfigFileName, configs_System, "[System]"); Configuration_SaveSection(sConfigFileName, configs_Video, "[Video]"); } /*-----------------------------------------------------------------------*/ /** * Save/restore snapshot of configuration variables * ('MemorySnapShot_Store' handles type) */ void Configuration_MemorySnapShot_Capture(bool bSave) { int i; MemorySnapShot_Store(ConfigureParams.Rom.szTosImageFileName, sizeof(ConfigureParams.Rom.szTosImageFileName)); MemorySnapShot_Store(ConfigureParams.Rom.szCartridgeImageFileName, sizeof(ConfigureParams.Rom.szCartridgeImageFileName)); MemorySnapShot_Store(ConfigureParams.Lilo.szKernelFileName, sizeof(ConfigureParams.Lilo.szKernelFileName)); MemorySnapShot_Store(ConfigureParams.Lilo.szRamdiskFileName, sizeof(ConfigureParams.Lilo.szRamdiskFileName)); MemorySnapShot_Store(&ConfigureParams.Memory.STRamSize_KB, sizeof(ConfigureParams.Memory.STRamSize_KB)); MemorySnapShot_Store(&ConfigureParams.Memory.TTRamSize_KB, sizeof(ConfigureParams.Memory.TTRamSize_KB)); MemorySnapShot_Store(&ConfigureParams.DiskImage.szDiskFileName[0], sizeof(ConfigureParams.DiskImage.szDiskFileName[0])); MemorySnapShot_Store(&ConfigureParams.DiskImage.szDiskZipPath[0], sizeof(ConfigureParams.DiskImage.szDiskZipPath[0])); MemorySnapShot_Store(&ConfigureParams.DiskImage.EnableDriveA, sizeof(ConfigureParams.DiskImage.EnableDriveA)); MemorySnapShot_Store(&ConfigureParams.DiskImage.DriveA_NumberOfHeads, sizeof(ConfigureParams.DiskImage.DriveA_NumberOfHeads)); MemorySnapShot_Store(&ConfigureParams.DiskImage.szDiskFileName[1], sizeof(ConfigureParams.DiskImage.szDiskFileName[1])); MemorySnapShot_Store(&ConfigureParams.DiskImage.szDiskZipPath[1], sizeof(ConfigureParams.DiskImage.szDiskZipPath[1])); MemorySnapShot_Store(&ConfigureParams.DiskImage.EnableDriveB, sizeof(ConfigureParams.DiskImage.EnableDriveB)); MemorySnapShot_Store(&ConfigureParams.DiskImage.DriveB_NumberOfHeads, sizeof(ConfigureParams.DiskImage.DriveB_NumberOfHeads)); MemorySnapShot_Store(&ConfigureParams.HardDisk.bUseHardDiskDirectories, sizeof(ConfigureParams.HardDisk.bUseHardDiskDirectories)); MemorySnapShot_Store(ConfigureParams.HardDisk.szHardDiskDirectories[DRIVE_C], sizeof(ConfigureParams.HardDisk.szHardDiskDirectories[DRIVE_C])); for (i = 0; i < MAX_ACSI_DEVS; i++) { MemorySnapShot_Store(&ConfigureParams.Acsi[i].bUseDevice, sizeof(ConfigureParams.Acsi[i].bUseDevice)); MemorySnapShot_Store(ConfigureParams.Acsi[i].sDeviceFile, sizeof(ConfigureParams.Acsi[i].sDeviceFile)); } for (i = 0; i < MAX_SCSI_DEVS; i++) { MemorySnapShot_Store(&ConfigureParams.Scsi[i].bUseDevice, sizeof(ConfigureParams.Scsi[i].bUseDevice)); MemorySnapShot_Store(ConfigureParams.Scsi[i].sDeviceFile, sizeof(ConfigureParams.Scsi[i].sDeviceFile)); } for (i = 0; i < MAX_IDE_DEVS; i++) { MemorySnapShot_Store(&ConfigureParams.Ide[i].bUseDevice, sizeof(ConfigureParams.Ide[i].bUseDevice)); MemorySnapShot_Store(&ConfigureParams.Ide[i].nByteSwap, sizeof(ConfigureParams.Ide[i].nByteSwap)); MemorySnapShot_Store(ConfigureParams.Ide[i].sDeviceFile, sizeof(ConfigureParams.Ide[i].sDeviceFile)); } MemorySnapShot_Store(&ConfigureParams.Screen.nMonitorType, sizeof(ConfigureParams.Screen.nMonitorType)); MemorySnapShot_Store(&ConfigureParams.Screen.bUseExtVdiResolutions, sizeof(ConfigureParams.Screen.bUseExtVdiResolutions)); MemorySnapShot_Store(&ConfigureParams.Screen.nVdiWidth, sizeof(ConfigureParams.Screen.nVdiWidth)); MemorySnapShot_Store(&ConfigureParams.Screen.nVdiHeight, sizeof(ConfigureParams.Screen.nVdiHeight)); MemorySnapShot_Store(&ConfigureParams.Screen.nVdiColors, sizeof(ConfigureParams.Screen.nVdiColors)); MemorySnapShot_Store(&ConfigureParams.System.nCpuLevel, sizeof(ConfigureParams.System.nCpuLevel)); MemorySnapShot_Store(&ConfigureParams.System.nCpuFreq, sizeof(ConfigureParams.System.nCpuFreq)); MemorySnapShot_Store(&ConfigureParams.System.bCompatibleCpu, sizeof(ConfigureParams.System.bCompatibleCpu)); MemorySnapShot_Store(&ConfigureParams.System.nMachineType, sizeof(ConfigureParams.System.nMachineType)); MemorySnapShot_Store(&ConfigureParams.System.bBlitter, sizeof(ConfigureParams.System.bBlitter)); MemorySnapShot_Store(&ConfigureParams.System.nDSPType, sizeof(ConfigureParams.System.nDSPType)); MemorySnapShot_Store(&ConfigureParams.System.bPatchTimerD, sizeof(ConfigureParams.System.bPatchTimerD)); MemorySnapShot_Store(&ConfigureParams.System.bAddressSpace24, sizeof(ConfigureParams.System.bAddressSpace24)); MemorySnapShot_Store(&ConfigureParams.System.bCpuDataCache, sizeof(ConfigureParams.System.bCpuDataCache)); MemorySnapShot_Store(&ConfigureParams.System.bCycleExactCpu, sizeof(ConfigureParams.System.bCycleExactCpu)); MemorySnapShot_Store(&ConfigureParams.System.n_FPUType, sizeof(ConfigureParams.System.n_FPUType)); MemorySnapShot_Store(&ConfigureParams.System.bCompatibleFPU, sizeof(ConfigureParams.System.bCompatibleFPU)); MemorySnapShot_Store(&ConfigureParams.System.bMMU, sizeof(ConfigureParams.System.bMMU)); MemorySnapShot_Store(&MachineClocks,sizeof(MachineClocks)); MemorySnapShot_Store(&ConfigureParams.DiskImage.FastFloppy, sizeof(ConfigureParams.DiskImage.FastFloppy)); if (!bSave) Configuration_Apply(true); } /*-----------------------------------------------------------------------*/ /** * This function should be called each time the CPU freq is changed. * It will update the main configuration, as well as the corresponding * value for nCpuFreqShift * * In case the new CPU freq is different from the current CPU freq, we * also call MClocksTimings_UpdateCpuFreqEmul and 68000_ChangeCpuFreq * to update some low level hardware related values */ void Configuration_ChangeCpuFreq ( int CpuFreq_new ) { int CpuFreq_old = ConfigureParams.System.nCpuFreq; //fprintf ( stderr , "changing cpu freq %d -> %d\n" , ConfigureParams.System.nCpuFreq , CpuFreq_new ); /* In case value is not exactly 8, 16 or 32, then we change it so */ if ( CpuFreq_new < 12 ) { ConfigureParams.System.nCpuFreq = 8; nCpuFreqShift = 0; } else if ( CpuFreq_new > 26 ) { ConfigureParams.System.nCpuFreq = 32; nCpuFreqShift = 2; } else { ConfigureParams.System.nCpuFreq = 16; nCpuFreqShift = 1; } ClocksTimings_UpdateCpuFreqEmul ( ConfigureParams.System.nMachineType , nCpuFreqShift ); if ( CpuFreq_old != CpuFreq_new ) { M68000_ChangeCpuFreq(); } } #ifdef EMSCRIPTEN void Configuration_ChangeMemory ( int RamSizeKb ) { ConfigureParams.Memory.STRamSize_KB = RamSizeKb; int size = STMemory_RAM_Validate_Size_KB ( ConfigureParams.Memory.STRamSize_KB ); if ( size < 0 ) { size = 1024; Log_Printf(LOG_WARN, "Unsupported %d KB ST-RAM amount, defaulting to %d KB\n", ConfigureParams.Memory.STRamSize_KB, size); } ConfigureParams.Memory.STRamSize_KB = size; STMemory_Init ( ConfigureParams.Memory.STRamSize_KB * 1024 ); } void Configuration_ChangeTos ( const char* szTosImageFileName ) { if(strlen(szTosImageFileName)<4096){ strcpy(ConfigureParams.Rom.szTosImageFileName,szTosImageFileName); } } void Configuration_ChangeSystem ( int nMachineType ) { switch (nMachineType) { case 0: if (!bOldRealTimeClock) ConfigureParams.System.nMachineType = MACHINE_ST; else ConfigureParams.System.nMachineType = MACHINE_MEGA_ST; break; case 1: ConfigureParams.System.nMachineType = MACHINE_STE; break; case 2: ConfigureParams.System.nMachineType = MACHINE_TT; break; case 3: ConfigureParams.System.nMachineType = MACHINE_FALCON; break; } } void Configuration_ChangeUseHardDiskDirectories ( bool bUseHardDiskDirectories ) { ConfigureParams.HardDisk.bUseHardDiskDirectories=bUseHardDiskDirectories; } void Configuration_ChangeFastForward ( bool bFastForwardActive ) { ConfigureParams.System.bFastForward=bFastForwardActive; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/control.c000066400000000000000000000447701504763705000231700ustar00rootroot00000000000000/* Hatari - control.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This code processes commands from the Hatari control socket */ const char Control_fileid[] = "Hatari control.c"; #include "config.h" #if HAVE_UNIX_DOMAIN_SOCKETS # include # include /* mkfifo() */ # include # include #endif #include #if HAVE_SYS_TIME_H #include #endif #include #include #include #include "main.h" #include "change.h" #include "configuration.h" #include "control.h" #include "debugui.h" #include "file.h" #include "ikbd.h" #include "keymap.h" #include "log.h" #include "midi.h" #include "printer.h" #include "rs232.h" #include "scc.h" #include "shortcut.h" #include "str.h" #include "screen.h" typedef enum { DO_DISABLE, DO_ENABLE, DO_TOGGLE } action_t; /* Whether to send embedded window info */ static bool bSendEmbedInfo; /* Pausing triggered remotely (battery save pause) */ static bool bRemotePaused; /* two-way socket to which Hatari connects, reads control commands * from, and where the command responses (if any) are written to */ static int ControlSocket; /*-----------------------------------------------------------------------*/ /** * Send new window size to remote end if requested to do so */ static void Control_SendEmbedSize(int width, int height) { if (!(bSendEmbedInfo && ControlSocket)) return; char buffer[12]; /* 32-bits in hex (+ '\r') + '\n' + '\0' */ sprintf(buffer, "%dx%d", width, height); if (write(ControlSocket, buffer, strlen(buffer)) < 0) perror("Control_ReparentWindow write error"); } /*-----------------------------------------------------------------------*/ /** * Parse key command and synthesize key press/release * corresponding to given keycode or character. * Return false if parsing failed, true otherwise * * This can be used by external Hatari UI(s) for * string macros, or on devices which lack keyboard */ static bool Control_InsertKey(const char *event) { const char *key = NULL; bool up, down; if (strncmp(event, "keypress ", 9) == 0) { key = &event[9]; down = up = true; } else if (strncmp(event, "keydown ", 8) == 0) { key = &event[8]; down = true; up = false; } else if (strncmp(event, "keyup ", 6) == 0) { key = &event[6]; down = false; up = true; } if (!(key && key[0])) { fprintf(stderr, "ERROR: '%s' contains no key press/down/up event\n", event); return false; } if (key[1]) { char *endptr; /* multiple characters, assume it's a keycode */ int keycode = strtol(key, &endptr, 0); /* not a valid number or keycode is out of range? */ if (*endptr || keycode < 0 || keycode > 255) { fprintf(stderr, "ERROR: '%s' isn't a valid key scancode, got value %d\n", key, keycode); return false; } if (down) { IKBD_PressSTKey(keycode, true); } if (up) { IKBD_PressSTKey(keycode, false); } } else { if (!isalnum((unsigned char)key[0])) { fprintf(stderr, "ERROR: non-alphanumeric character '%c' needs to be given as keycode\n", key[0]); return false; } if (down) { Keymap_SimulateCharacter(key[0], true); } if (up) { Keymap_SimulateCharacter(key[0], false); } } #if 0 fprintf(stderr, "Simulated key %s of %d\n", (down? (up? "press":"down") :"up"), key); #endif return true; } /*-----------------------------------------------------------------------*/ /** * Parse event name and synthesize corresponding event to emulation * Return false if name parsing failed, true otherwise * * This can be used by external Hatari UI(s) on devices which input * methods differ from normal keyboard and mouse, such as high DPI * touchscreen (no right/middle button, inaccurate clicks) */ static bool Control_InsertEvent(const char *event) { if (strcmp(event, "doubleclick") == 0) { Keyboard.LButtonDblClk = 1; return true; } if (strcmp(event, "rightdown") == 0) { Keyboard.bRButtonDown |= BUTTON_MOUSE; return true; } if (strcmp(event, "rightup") == 0) { Keyboard.bRButtonDown &= ~BUTTON_MOUSE; return true; } if (Control_InsertKey(event)) { return true; } fprintf(stderr, "ERROR: unrecognized event: '%s'\n", event); fprintf(stderr, "Supported mouse button and key events are:\n" "- doubleclick\n" "- rightdown\n" "- rightup\n" "- keypress \n" "- keydown \n" "- keyup \n" " can be either a single ASCII character or an ST scancode\n" "(e.g. space has scancode of 57 and enter 28).\n" ); return false; } /*-----------------------------------------------------------------------*/ /** * Parse device name and enable/disable/toggle & init/uninit it according * to action. Return false if name parsing failed, true otherwise */ static bool Control_DeviceAction(const char *name, action_t action) { /* Note: e.g. RTC would require restarting emulation * and HD-boot setting emulation reboot. Devices * listed here work just with init/uninit. */ struct { const char *name; bool *pvalue; void(*init)(void); void(*uninit)(void); void(*reset)(void); } item[] = { { "printer", &ConfigureParams.Printer.bEnablePrinting, Printer_Init, Printer_UnInit, NULL }, { "rs232", &ConfigureParams.RS232.bEnableRS232, RS232_Init, RS232_UnInit, NULL }, { "scca", &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL], SCC_Init, SCC_UnInit, NULL }, { "sccalan", &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN], SCC_Init, SCC_UnInit, NULL }, { "sccb", &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B], SCC_Init, SCC_UnInit, NULL }, { "midi", &ConfigureParams.Midi.bEnableMidi, Midi_Init, Midi_UnInit, Midi_Reset }, { NULL, NULL, NULL, NULL } }; int i; bool value; for (i = 0; item[i].name; i++) { if (strcmp(name, item[i].name) == 0) { switch (action) { case DO_TOGGLE: value = !*(item[i].pvalue); break; case DO_ENABLE: value = true; break; case DO_DISABLE: default: value = false; break; } *(item[i].pvalue) = value; if (value) { item[i].init(); if (item[i].reset) item[i].reset(); } else { item[i].uninit(); } fprintf(stderr, "%s: %s\n", name, value?"ON":"OFF"); return true; } } fprintf(stderr, "WARNING: unknown device '%s'\n\n", name); fprintf(stderr, "Accepted devices are:\n"); for (i = 0; item[i].name; i++) { fprintf(stderr, "- %s\n", item[i].name); } return false; } /*-----------------------------------------------------------------------*/ /** * Parse path type name and set the path to given value. * Return false if name parsing failed, true otherwise */ static bool Control_SetPath(char *name) { struct { const char *name; char *path; } item[] = { { "memauto", ConfigureParams.Memory.szAutoSaveFileName }, { "memsave", ConfigureParams.Memory.szMemoryCaptureFileName }, { "midiin", ConfigureParams.Midi.sMidiInFileName }, { "midiout", ConfigureParams.Midi.sMidiOutFileName }, { "printout", ConfigureParams.Printer.szPrintToFileName }, { "soundout", ConfigureParams.Sound.szYMCaptureFileName }, { "rs232in", ConfigureParams.RS232.szInFileName }, { "rs232out", ConfigureParams.RS232.szOutFileName }, { "sccain", ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL] }, { "sccaout", ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL] }, { "sccalanin",ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN] }, { "sccalanout",ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN] }, { "sccbin", ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B] }, { "sccbout", ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B] }, { NULL, NULL } }; int i; char *arg; const char *value; /* argument? */ arg = strchr(name, ' '); if (arg) { *arg = '\0'; value = Str_Trim(arg+1); } else { value = ""; } for (i = 0; item[i].name; i++) { if (strcmp(name, item[i].name) == 0) { fprintf(stderr, "%s: %s -> %s\n", name, item[i].path, value); strncpy(item[i].path, value, FILENAME_MAX-1); return true; } } fprintf(stderr, "WARNING: unknown path type '%s'\n\n", name); fprintf(stderr, "Accepted paths types are:\n"); for (i = 0; item[i].name; i++) { fprintf(stderr, "- %s\n", item[i].name); } return false; } /*-----------------------------------------------------------------------*/ /** * Show Hatari remote usage info and return false */ static bool Control_Usage(const char *cmd) { fprintf(stderr, "ERROR: missing arg or unrecognized command: '%s'!\n", cmd); fprintf(stderr, "Supported commands are:\n" "- hatari-debug \n" "- hatari-event \n" "- hatari-option \n" "- hatari-enable/disable/toggle \n" "- hatari-path \n" "- hatari-shortcut \n" "- hatari-embed-info\n" "- hatari-stop\n" "- hatari-cont\n" "The last two can be used to stop and continue the Hatari emulation.\n" "All commands need to be separated by newlines. Spaces in command\n" "line option arguments need to be quoted with \\.\n" ); return false; } /*-----------------------------------------------------------------------*/ /** * Parse Hatari debug/event/option/toggle/path/shortcut command buffer. */ void Control_ProcessBuffer(const char *orig) { char *cmd, *cmdend, *arg, *buffer; int ok = true; /* this is called from several different places, * so take a copy of the original buffer so * that it can be sliced & diced */ buffer = strdup(orig); assert(buffer); cmd = buffer; do { /* command terminator? */ cmdend = strchr(cmd, '\n'); if (cmdend) { *cmdend = '\0'; } /* arguments? */ arg = strchr(cmd, ' '); if (arg) { *arg = '\0'; arg = Str_Trim(arg+1); } if (arg) { if (strcmp(cmd, "hatari-option") == 0) { ok = Change_ApplyCommandline(arg); } else if (strcmp(cmd, "hatari-debug") == 0) { ok = DebugUI_ParseLine(arg); } else if (strcmp(cmd, "hatari-shortcut") == 0) { ok = Shortcut_Invoke(arg); } else if (strcmp(cmd, "hatari-event") == 0) { ok = Control_InsertEvent(arg); } else if (strcmp(cmd, "hatari-path") == 0) { ok = Control_SetPath(arg); } else if (strcmp(cmd, "hatari-enable") == 0) { ok = Control_DeviceAction(arg, DO_ENABLE); } else if (strcmp(cmd, "hatari-disable") == 0) { ok = Control_DeviceAction(arg, DO_DISABLE); } else if (strcmp(cmd, "hatari-toggle") == 0) { ok = Control_DeviceAction(arg, DO_TOGGLE); } else { ok = Control_Usage(cmd); } } else { if (strcmp(cmd, "hatari-embed-info") == 0) { fprintf(stderr, "Embedded window ID change messages = ON\n"); bSendEmbedInfo = true; if (sdlscrn) { /* initial size */ Control_SendEmbedSize(sdlscrn->w, sdlscrn->h); } } else if (strcmp(cmd, "hatari-stop") == 0) { Main_PauseEmulation(true); bRemotePaused = true; } else if (strcmp(cmd, "hatari-cont") == 0) { Main_UnPauseEmulation(); bRemotePaused = false; } else { ok = Control_Usage(cmd); } } if (cmdend) { cmd = cmdend + 1; } } while (ok && cmdend && *cmd); free(buffer); } #if HAVE_UNIX_DOMAIN_SOCKETS /* one-way fifo which Hatari creates and reads commands from */ static char *FifoPath; static int ControlFifo; /* pre-declared local functions */ static int Control_GetUISocket(void); /*-----------------------------------------------------------------------*/ /** * Check ControlSocket for new commands and execute them. * Commands should be separated by newlines. * * Return true if remote pause ON (and connected), false otherwise */ bool Control_CheckUpdates(void) { /* setting all trace options, or paths takes a lot of space */ char buffer[4096]; struct timeval tv; fd_set readfds; ssize_t bytes; int status, sock; if (ControlFifo) { /* assume whole command can be read in one go */ bytes = read(ControlFifo, buffer, sizeof(buffer)-1); if (bytes < 0) { perror("command FIFO read error"); return false; } if (bytes == 0) { /* non-blocking read, nothing to read */ return false; } buffer[bytes] = '\0'; Control_ProcessBuffer(buffer); return false; } /* socket of file? */ if (ControlSocket) { sock = ControlSocket; } else { return false; } /* ready for reading? */ tv.tv_usec = tv.tv_sec = 0; do { FD_ZERO(&readfds); FD_SET(sock, &readfds); if (bRemotePaused) { /* return only when there're UI events * (redraws etc) to save battery: * https://github.com/libsdl-org/SDL-1.2/issues/222 */ int uisock = Control_GetUISocket(); if (uisock) { FD_SET(uisock, &readfds); if (uisock < sock) { uisock = sock; } } status = select(uisock+1, &readfds, NULL, NULL, NULL); } else { status = select(sock+1, &readfds, NULL, NULL, &tv); } if (status < 0) { perror("Control socket select() error"); return false; } /* nothing to process here */ if (status == 0) { return bRemotePaused; } if (!FD_ISSET(sock, &readfds)) { return bRemotePaused; } /* assume whole command can be read in one go */ bytes = read(sock, buffer, sizeof(buffer)-1); if (bytes < 0) { perror("Control socket read error"); return false; } if (bytes == 0) { /* closed */ fprintf(stderr, "ready control socket with 0 bytes available -> close socket\n"); close(ControlSocket); ControlSocket = 0; return false; } buffer[bytes] = '\0'; Control_ProcessBuffer(buffer); } while (bRemotePaused); return false; } /*-----------------------------------------------------------------------*/ /** * Close and remove FIFO file */ void Control_RemoveFifo(void) { if (ControlFifo) { close(ControlFifo); ControlFifo = 0; } if (FifoPath) { Log_Printf(LOG_DEBUG, "removing command FIFO: %s\n", FifoPath); if (remove(FifoPath) < 0) { perror("Remove FIFO failed"); } free(FifoPath); FifoPath = NULL; } } /*-----------------------------------------------------------------------*/ /** * Open given command FIFO * Return NULL for success, otherwise an error string */ const char *Control_SetFifo(const char *path) { int fifo; if (ControlSocket) { return "Can't use a FIFO at the same time with a control socket"; } Control_RemoveFifo(); Log_Printf(LOG_DEBUG, "creating command FIFO: %s\n", path); if (mkfifo(path, S_IRUSR | S_IWUSR)) { perror("FIFO creation error"); return "Can't create FIFO file"; } FifoPath = strdup(path); fifo = open(path, O_RDONLY | O_NONBLOCK); if (fifo < 0) { perror("FIFO open error"); Control_RemoveFifo(); return "opening non-blocking read-only FIFO failed"; } ControlFifo = fifo; return NULL; } /*-----------------------------------------------------------------------*/ /** * Open given control socket. * Return NULL for success, otherwise an error string */ const char *Control_SetSocket(const char *socketpath) { struct sockaddr_un address; int newsock; if (ControlFifo) { return "Can't use a FIFO at the same time with a control socket"; } newsock = socket(AF_UNIX, SOCK_STREAM, 0); if (newsock < 0) { perror("socket creation error"); return "Can't create AF_UNIX socket"; } address.sun_family = AF_UNIX; strncpy(address.sun_path, socketpath, sizeof(address.sun_path)); address.sun_path[sizeof(address.sun_path)-1] = '\0'; Log_Printf(LOG_INFO, "Connecting to control socket '%s'...\n", address.sun_path); if (connect(newsock, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("socket connect error"); close(newsock); return "connection to control socket failed"; } if (ControlSocket) { close(ControlSocket); } ControlSocket = newsock; Log_Printf(LOG_INFO, "new control socket is '%s'\n", socketpath); return NULL; } /*----------------------------------------------------------------------- * Currently works only on X11. * * SDL_syswm.h automatically includes everything else needed. */ #include /* X11 available and SDL_config.h states that SDL supports X11 */ #if HAVE_X11 && SDL_VIDEO_DRIVER_X11 #include /** * Reparent Hatari window if so requested. Needs to be done inside * Hatari because if SDL itself is requested to reparent itself, * SDL window stops accepting any input (specifically done like * this in SDL backends for some reason). * * 'noembed' argument tells whether the SDL window should be embedded * or not. * * If the window is embedded (which means that SDL WM window needs * to be hidden) when SDL is asked to fullscreen, Hatari window just * disappears when returning back from fullscreen. I.e. call this * with noembed=true _before_ fullscreening and any other time with * noembed=false after changing window size. You can do this by * giving bInFullscreen as the noembed value. */ void Control_ReparentWindow(int width, int height, bool noembed) { Display *display; Window parent_win, sdl_win; const char *parent_win_id; SDL_SysWMinfo info; Window wm_win; Window dw1, *dw2; unsigned int nwin; parent_win_id = getenv("PARENT_WIN_ID"); if (!parent_win_id) { return; } parent_win = strtol(parent_win_id, NULL, 0); if (!parent_win) { Log_Printf(LOG_WARN, "Invalid PARENT_WIN_ID value '%s'\n", parent_win_id); bSendEmbedInfo = false; return; } SDL_VERSION(&info.version); if (!SDL_GetWindowWMInfo(sdlWindow, &info)) { Log_Printf(LOG_WARN, "Failed to get SDL_GetWMInfo()\n"); bSendEmbedInfo = false; return; } display = info.info.x11.display; sdl_win = info.info.x11.window; XQueryTree(display, sdl_win, &dw1, &wm_win, &dw2, &nwin); if (noembed) { /* show WM window again */ XMapWindow(display, wm_win); } else { if (parent_win != wm_win) { /* hide WM window for Hatari */ XUnmapWindow(display, wm_win); /* reparent main Hatari window to given parent */ XReparentWindow(display, sdl_win, parent_win, 0, 0); } Log_Printf(LOG_INFO, "New %dx%d SDL window with ID: %lx\n", width, height, sdl_win); /* inform remote end of new window size if requested */ Control_SendEmbedSize(width, height); } XSync(display, false); } /** * Return the X connection socket or zero */ static int Control_GetUISocket(void) { SDL_SysWMinfo info; SDL_VERSION(&info.version); if (!SDL_GetWindowWMInfo(sdlWindow, &info)) { Log_Printf(LOG_WARN, "Failed to get SDL_GetWMInfo()\n"); return 0; } return ConnectionNumber(info.info.x11.display); } #else /* HAVE_X11 */ static int Control_GetUISocket(void) { return 0; } void Control_ReparentWindow(int width, int height, bool noembed) { /* TODO: implement the Windows part. SDL sources offer example */ Log_Printf(LOG_TODO, "Support for Hatari window reparenting not built in\n"); } #endif /* HAVE_X11 */ #endif /* HAVE_UNIX_DOMAIN_SOCKETS */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/000077500000000000000000000000001504763705000230105ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/low320x32.c000066400000000000000000000046541504763705000245500ustar00rootroot00000000000000/* Hatari - low320x32.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen Conversion, Low Res to 320x32Bit */ static void ConvertLowRes_320x32Bit(void) { Uint32 *edi, *ebp; Uint32 *esi; Uint32 eax, edx; Uint32 ebx, ecx; int y, x, update; Convert_StartFrame(); /* Start frame, track palettes */ for (y = STScreenStartHorizLine; y < STScreenEndHorizLine; y++) { eax = STScreenLineOffset[y] + STScreenLeftSkipBytes; /* Offset for this line + Amount to skip on left hand side */ edi = (Uint32 *)((Uint8 *)pSTScreen + eax); /* ST format screen 4-plane 16 colors */ ebp = (Uint32 *)((Uint8 *)pSTScreenCopy + eax); /* Previous ST format screen */ esi = (Uint32 *)pPCScreenDest; /* PC format screen */ update = AdjustLinePaletteRemap(y) & PALETTEMASK_UPDATEMASK; x = STScreenWidthBytes>>3; /* Amount to draw across in 16-pixels (8 bytes) */ do /* x-loop */ { /* Do 16 pixels at one time */ ebx = *edi; ecx = *(edi+1); if (update || ebx!=*ebp || ecx!=*(ebp+1)) /* Does differ? */ { /* copy word */ bScreenContentsChanged = true; #if SDL_BYTEORDER == SDL_BIG_ENDIAN /* Plot pixels */ LOW_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [12,13,14,15] */ PLOT_LOW_320_32BIT(12) ; LOW_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [4,5,6,7] */ PLOT_LOW_320_32BIT(4) ; LOW_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [8,9,10,11] */ PLOT_LOW_320_32BIT(8) ; LOW_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [0,1,2,3] */ PLOT_LOW_320_32BIT(0) ; #else /* Plot pixels */ LOW_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [4,5,6,7] */ PLOT_LOW_320_32BIT(4) ; LOW_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [12,13,14,15] */ PLOT_LOW_320_32BIT(12) ; LOW_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [0,1,2,3] */ PLOT_LOW_320_32BIT(0) ; LOW_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [8,9,10,11] */ PLOT_LOW_320_32BIT(8) ; #endif } esi += 16; /* Next PC pixels */ edi += 2; /* Next ST pixels */ ebp += 2; /* Next ST copy pixels */ } while (--x); /* Loop on X */ /* Offset to next line: */ pPCScreenDest = (((Uint8 *)pPCScreenDest)+PCScreenBytesPerLine); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/low320x32_spec.c000066400000000000000000000064561504763705000255640ustar00rootroot00000000000000/* Hatari - low320x32_spec.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen Conversion, Low Res Spec512 to 320x32Bit */ static void ConvertLowRes_320x32Bit_Spec(void) { Uint32 *edi; Uint32 *esi; Uint32 eax, ebx, ecx, edx; Uint32 pixelspace[5]; /* Workspace to store pixels to so can print in right order for Spec512 */ int y, x; /* on x86, unaligned access macro touches also * next byte, zero it for code checkers */ pixelspace[4] = 0; Spec512_StartFrame(); /* Start frame, track palettes */ for (y = STScreenStartHorizLine; y < STScreenEndHorizLine; y++) { Spec512_StartScanLine(); /* Build up palettes for every 4 pixels, store in 'ScanLinePalettes' */ /* Get screen addresses, 'edi'-ST screen, 'esi'-PC screen */ eax = STScreenLineOffset[y] + STScreenLeftSkipBytes; /* Offset for this line + Amount to skip on left hand side */ edi = (Uint32 *)((Uint8 *)pSTScreen + eax); /* ST format screen 4-plane 16 colors */ esi = (Uint32 *)pPCScreenDest; /* PC format screen */ x = STScreenWidthBytes >> 3; /* Amount to draw across in 16-pixels (8 bytes) */ do /* x-loop */ { ebx = *edi; /* Do 16 pixels at one time */ ecx = *(edi+1); #if SDL_BYTEORDER == SDL_BIG_ENDIAN /* Convert planes to byte indices - as works in wrong order store to workspace so can read back in order! */ LOW_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [12,13,14,15] */ pixelspace[3] = ecx; LOW_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [4,5,6,7] */ pixelspace[1] = ecx; LOW_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [8,9,10,11] */ pixelspace[2] = ecx; LOW_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [0,1,2,3] */ pixelspace[0] = ecx; #else LOW_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [4,5,6,7] */ pixelspace[1] = ecx; LOW_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [12,13,14,15] */ pixelspace[3] = ecx; LOW_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [0,1,2,3] */ pixelspace[0] = ecx; LOW_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [8,9,10,11] */ pixelspace[2] = ecx; #endif /* And plot, the Spec512 is offset by 1 pixel and works on 'chunks' of 4 pixels */ /* So, we plot 1_4_4_4_3 to give 16 pixels, changing palette between */ /* (last one is used for first of next 16-pixels) */ ecx = pixelspace[0]; PLOT_SPEC512_LEFT_LOW_320_32BIT(0); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 1); PLOT_SPEC512_MID_320_32BIT(1); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 5); PLOT_SPEC512_MID_320_32BIT(5); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 9); PLOT_SPEC512_MID_320_32BIT(9); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_FINAL_PIXELS(pixelspace); PLOT_SPEC512_END_LOW_320_32BIT(13); esi += 16; /* Next PC pixels */ edi += 2; /* Next ST pixels */ } while (--x); /* Loop on X */ Spec512_EndScanLine(); /* Offset to next line */ pPCScreenDest = (((Uint8 *)pPCScreenDest) + PCScreenBytesPerLine); } bScreenContentsChanged = true; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/low640x32.c000066400000000000000000000055321504763705000245510ustar00rootroot00000000000000/* Hatari - low640x32.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen Conversion, Low Res to 640x32Bit */ static void Line_ConvertLowRes_640x32Bit(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax) { Uint32 edx; Uint32 ebx, ecx; int x, update; x = STScreenWidthBytes>>3; /* Amount to draw across in 16-pixels (8 bytes) */ update = ScrUpdateFlag & PALETTEMASK_UPDATEMASK; do /* x-loop */ { /* Do 16 pixels at one time */ ebx = *edi; ecx = *(edi+1); if (update || ebx != *ebp || ecx != *(ebp+1)) /* Does differ? */ { /* copy word */ bScreenContentsChanged = true; #if SDL_BYTEORDER == SDL_BIG_ENDIAN /* Plot pixels in 'right-order' on big endian systems */ LOW_BUILD_PIXELS_0; /* Generate 'ecx' as pixels [12,13,14,15] */ PLOT_LOW_640_32BIT(24); LOW_BUILD_PIXELS_1; /* Generate 'ecx' as pixels [4,5,6,7] */ PLOT_LOW_640_32BIT(8); LOW_BUILD_PIXELS_2; /* Generate 'ecx' as pixels [8,9,10,11] */ PLOT_LOW_640_32BIT(16); LOW_BUILD_PIXELS_3; /* Generate 'ecx' as pixels [0,1,2,3]] */ PLOT_LOW_640_32BIT(0); #else /* Plot pixels in 'wrong-order', as ebx is 68000 endian */ LOW_BUILD_PIXELS_0; /* Generate 'ecx' as pixels [4,5,6,7] */ PLOT_LOW_640_32BIT(8); LOW_BUILD_PIXELS_1; /* Generate 'ecx' as pixels [12,13,14,15] */ PLOT_LOW_640_32BIT(24); LOW_BUILD_PIXELS_2; /* Generate 'ecx' as pixels [0,1,2,3] */ PLOT_LOW_640_32BIT(0); LOW_BUILD_PIXELS_3; /* Generate 'ecx' as pixels [8,9,10,11] */ PLOT_LOW_640_32BIT(16); #endif } esi += 32; /* Next PC pixels */ edi += 2; /* Next ST pixels */ ebp += 2; /* Next ST copy pixels */ } while (--x); /* Loop on X */ } static void ConvertLowRes_640x32Bit(void) { Uint32 *PCScreen = (Uint32 *)pPCScreenDest; Uint32 *edi, *ebp; Uint32 *esi; Uint32 eax; int y; Convert_StartFrame(); /* Start frame, track palettes */ for (y = STScreenStartHorizLine; y < STScreenEndHorizLine; y++) { /* Get screen addresses */ eax = STScreenLineOffset[y] + STScreenLeftSkipBytes; /* Offset for this line + Amount to skip on left hand side */ edi = (Uint32 *)((Uint8 *)pSTScreen + eax); /* ST format screen 4-plane 16 colors */ ebp = (Uint32 *)((Uint8 *)pSTScreenCopy + eax); /* Previous ST format screen */ esi = PCScreen; /* PC format screen */ if (AdjustLinePaletteRemap(y) & 0x00030000) /* Change palette table */ Line_ConvertMediumRes_640x32Bit(edi, ebp, esi, eax); else Line_ConvertLowRes_640x32Bit(edi, ebp, esi, eax); PCScreen = Double_ScreenLine32(PCScreen, PCScreenBytesPerLine); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/low640x32_spec.c000066400000000000000000000070321504763705000255600ustar00rootroot00000000000000/* Hatari - low640x32_spec.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen conversion, Low Res Spec512 to 640x32Bit */ static void ConvertLowRes_640x32Bit_Spec(void) { Uint32 *PCScreen = (Uint32 *)pPCScreenDest; Uint32 *edi, *ebp; Uint32 *esi; Uint32 eax; int y; Spec512_StartFrame(); /* Start frame, track palettes */ for (y = STScreenStartHorizLine; y < STScreenEndHorizLine; y++) { eax = STScreenLineOffset[y] + STScreenLeftSkipBytes; /* Offset for this line + Amount to skip on left hand side */ edi = (Uint32 *)((Uint8 *)pSTScreen + eax); /* ST format screen 4-plane 16 colors */ ebp = (Uint32 *)((Uint8 *)pSTScreenCopy + eax); /* Previous ST format screen */ esi = PCScreen; /* PC format screen */ Line_ConvertLowRes_640x32Bit_Spec(edi, ebp, esi, eax); PCScreen = Double_ScreenLine32(PCScreen, PCScreenBytesPerLine); } bScreenContentsChanged = true; } static void Line_ConvertLowRes_640x32Bit_Spec(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax) { int x; Uint32 ebx, ecx, edx; Uint32 pixelspace[5]; /* Workspace to store pixels to so can print in right order for Spec512 */ /* on x86, unaligned access macro touches also * next byte, zero it for code checkers */ pixelspace[4] = 0; Spec512_StartScanLine(); /* Build up palettes for every 4 pixels, store in 'ScanLinePalettes' */ x = STScreenWidthBytes >> 3; /* Amount to draw across in 16-pixels (8 bytes) */ do /* x-loop */ { ebx = *edi; /* Do 16 pixels at one time */ ecx = *(edi+1); #if SDL_BYTEORDER == SDL_BIG_ENDIAN /* Convert planes to byte indices - as works in wrong order store to workspace so can read back in order! */ LOW_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [12,13,14,15] */ pixelspace[3] = ecx; LOW_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [4,5,6,7] */ pixelspace[1] = ecx; LOW_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [8,9,10,11] */ pixelspace[2] = ecx; LOW_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [0,1,2,3] */ pixelspace[0] = ecx; #else LOW_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [4,5,6,7] */ pixelspace[1] = ecx; LOW_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [12,13,14,15] */ pixelspace[3] = ecx; LOW_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [0,1,2,3] */ pixelspace[0] = ecx; LOW_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [8,9,10,11] */ pixelspace[2] = ecx; #endif /* And plot, the Spec512 is offset by 1 pixel and works on 'chunks' of 4 pixels */ /* So, we plot 1_4_4_4_3 to give 16 pixels, changing palette between */ /* (last one is used for first of next 16-pixels) */ ecx = pixelspace[0]; PLOT_SPEC512_LEFT_LOW_640_32BIT(0); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 1); PLOT_SPEC512_MID_640_32BIT(2); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 5); PLOT_SPEC512_MID_640_32BIT(10); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 9); PLOT_SPEC512_MID_640_32BIT(18); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_FINAL_PIXELS(pixelspace); PLOT_SPEC512_END_LOW_640_32BIT(26); esi += 32; /* Next PC pixels */ edi += 2; /* Next ST pixels */ ebp += 2; /* Next ST copy pixels */ } while (--x); /* Loop on X */ Spec512_EndScanLine(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/macros.h000066400000000000000000000346451504763705000244610ustar00rootroot00000000000000/* Hatari - macros.h Lookup tables and macros for screen conversion routines. This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CONVERTMACROS_H #define HATARI_CONVERTMACROS_H /* Remap tables to convert from plane format to byte-per-pixel * (Upper is for 4-Planes so if shifted by 2) */ static const Uint32 Remap_2_Planes[256] = { 0x00000000, 0x01000000, 0x00010000, 0x01010000, 0x00000100, 0x01000100, 0x00010100, 0x01010100, 0x00000001, 0x01000001, 0x00010001, 0x01010001, 0x00000101, 0x01000101, 0x00010101, 0x01010101, 0x02000000, 0x03000000, 0x02010000, 0x03010000, 0x02000100, 0x03000100, 0x02010100, 0x03010100, 0x02000001, 0x03000001, 0x02010001, 0x03010001, 0x02000101, 0x03000101, 0x02010101, 0x03010101, 0x00020000, 0x01020000, 0x00030000, 0x01030000, 0x00020100, 0x01020100, 0x00030100, 0x01030100, 0x00020001, 0x01020001, 0x00030001, 0x01030001, 0x00020101, 0x01020101, 0x00030101, 0x01030101, 0x02020000, 0x03020000, 0x02030000, 0x03030000, 0x02020100, 0x03020100, 0x02030100, 0x03030100, 0x02020001, 0x03020001, 0x02030001, 0x03030001, 0x02020101, 0x03020101, 0x02030101, 0x03030101, 0x00000200, 0x01000200, 0x00010200, 0x01010200, 0x00000300, 0x01000300, 0x00010300, 0x01010300, 0x00000201, 0x01000201, 0x00010201, 0x01010201, 0x00000301, 0x01000301, 0x00010301, 0x01010301, 0x02000200, 0x03000200, 0x02010200, 0x03010200, 0x02000300, 0x03000300, 0x02010300, 0x03010300, 0x02000201, 0x03000201, 0x02010201, 0x03010201, 0x02000301, 0x03000301, 0x02010301, 0x03010301, 0x00020200, 0x01020200, 0x00030200, 0x01030200, 0x00020300, 0x01020300, 0x00030300, 0x01030300, 0x00020201, 0x01020201, 0x00030201, 0x01030201, 0x00020301, 0x01020301, 0x00030301, 0x01030301, 0x02020200, 0x03020200, 0x02030200, 0x03030200, 0x02020300, 0x03020300, 0x02030300, 0x03030300, 0x02020201, 0x03020201, 0x02030201, 0x03030201, 0x02020301, 0x03020301, 0x02030301, 0x03030301, 0x00000002, 0x01000002, 0x00010002, 0x01010002, 0x00000102, 0x01000102, 0x00010102, 0x01010102, 0x00000003, 0x01000003, 0x00010003, 0x01010003, 0x00000103, 0x01000103, 0x00010103, 0x01010103, 0x02000002, 0x03000002, 0x02010002, 0x03010002, 0x02000102, 0x03000102, 0x02010102, 0x03010102, 0x02000003, 0x03000003, 0x02010003, 0x03010003, 0x02000103, 0x03000103, 0x02010103, 0x03010103, 0x00020002, 0x01020002, 0x00030002, 0x01030002, 0x00020102, 0x01020102, 0x00030102, 0x01030102, 0x00020003, 0x01020003, 0x00030003, 0x01030003, 0x00020103, 0x01020103, 0x00030103, 0x01030103, 0x02020002, 0x03020002, 0x02030002, 0x03030002, 0x02020102, 0x03020102, 0x02030102, 0x03030102, 0x02020003, 0x03020003, 0x02030003, 0x03030003, 0x02020103, 0x03020103, 0x02030103, 0x03030103, 0x00000202, 0x01000202, 0x00010202, 0x01010202, 0x00000302, 0x01000302, 0x00010302, 0x01010302, 0x00000203, 0x01000203, 0x00010203, 0x01010203, 0x00000303, 0x01000303, 0x00010303, 0x01010303, 0x02000202, 0x03000202, 0x02010202, 0x03010202, 0x02000302, 0x03000302, 0x02010302, 0x03010302, 0x02000203, 0x03000203, 0x02010203, 0x03010203, 0x02000303, 0x03000303, 0x02010303, 0x03010303, 0x00020202, 0x01020202, 0x00030202, 0x01030202, 0x00020302, 0x01020302, 0x00030302, 0x01030302, 0x00020203, 0x01020203, 0x00030203, 0x01030203, 0x00020303, 0x01020303, 0x00030303, 0x01030303, 0x02020202, 0x03020202, 0x02030202, 0x03030202, 0x02020302, 0x03020302, 0x02030302, 0x03030302, 0x02020203, 0x03020203, 0x02030203, 0x03030203, 0x02020303, 0x03020303, 0x02030303, 0x03030303, }; static const Uint32 Remap_2_Planes_Upper[256] = { 0x00000000, 0x04000000, 0x00040000, 0x04040000, 0x00000400, 0x04000400, 0x00040400, 0x04040400, 0x00000004, 0x04000004, 0x00040004, 0x04040004, 0x00000404, 0x04000404, 0x00040404, 0x04040404, 0x08000000, 0x0C000000, 0x08040000, 0x0C040000, 0x08000400, 0x0C000400, 0x08040400, 0x0C040400, 0x08000004, 0x0C000004, 0x08040004, 0x0C040004, 0x08000404, 0x0C000404, 0x08040404, 0x0C040404, 0x00080000, 0x04080000, 0x000C0000, 0x040C0000, 0x00080400, 0x04080400, 0x000C0400, 0x040C0400, 0x00080004, 0x04080004, 0x000C0004, 0x040C0004, 0x00080404, 0x04080404, 0x000C0404, 0x040C0404, 0x08080000, 0x0C080000, 0x080C0000, 0x0C0C0000, 0x08080400, 0x0C080400, 0x080C0400, 0x0C0C0400, 0x08080004, 0x0C080004, 0x080C0004, 0x0C0C0004, 0x08080404, 0x0C080404, 0x080C0404, 0x0C0C0404, 0x00000800, 0x04000800, 0x00040800, 0x04040800, 0x00000C00, 0x04000C00, 0x00040C00, 0x04040C00, 0x00000804, 0x04000804, 0x00040804, 0x04040804, 0x00000C04, 0x04000C04, 0x00040C04, 0x04040C04, 0x08000800, 0x0C000800, 0x08040800, 0x0C040800, 0x08000C00, 0x0C000C00, 0x08040C00, 0x0C040C00, 0x08000804, 0x0C000804, 0x08040804, 0x0C040804, 0x08000C04, 0x0C000C04, 0x08040C04, 0x0C040C04, 0x00080800, 0x04080800, 0x000C0800, 0x040C0800, 0x00080C00, 0x04080C00, 0x000C0C00, 0x040C0C00, 0x00080804, 0x04080804, 0x000C0804, 0x040C0804, 0x00080C04, 0x04080C04, 0x000C0C04, 0x040C0C04, 0x08080800, 0x0C080800, 0x080C0800, 0x0C0C0800, 0x08080C00, 0x0C080C00, 0x080C0C00, 0x0C0C0C00, 0x08080804, 0x0C080804, 0x080C0804, 0x0C0C0804, 0x08080C04, 0x0C080C04, 0x080C0C04, 0x0C0C0C04, 0x00000008, 0x04000008, 0x00040008, 0x04040008, 0x00000408, 0x04000408, 0x00040408, 0x04040408, 0x0000000C, 0x0400000C, 0x0004000C, 0x0404000C, 0x0000040C, 0x0400040C, 0x0004040C, 0x0404040C, 0x08000008, 0x0C000008, 0x08040008, 0x0C040008, 0x08000408, 0x0C000408, 0x08040408, 0x0C040408, 0x0800000C, 0x0C00000C, 0x0804000C, 0x0C04000C, 0x0800040C, 0x0C00040C, 0x0804040C, 0x0C04040C, 0x00080008, 0x04080008, 0x000C0008, 0x040C0008, 0x00080408, 0x04080408, 0x000C0408, 0x040C0408, 0x0008000C, 0x0408000C, 0x000C000C, 0x040C000C, 0x0008040C, 0x0408040C, 0x000C040C, 0x040C040C, 0x08080008, 0x0C080008, 0x080C0008, 0x0C0C0008, 0x08080408, 0x0C080408, 0x080C0408, 0x0C0C0408, 0x0808000C, 0x0C08000C, 0x080C000C, 0x0C0C000C, 0x0808040C, 0x0C08040C, 0x080C040C, 0x0C0C040C, 0x00000808, 0x04000808, 0x00040808, 0x04040808, 0x00000C08, 0x04000C08, 0x00040C08, 0x04040C08, 0x0000080C, 0x0400080C, 0x0004080C, 0x0404080C, 0x00000C0C, 0x04000C0C, 0x00040C0C, 0x04040C0C, 0x08000808, 0x0C000808, 0x08040808, 0x0C040808, 0x08000C08, 0x0C000C08, 0x08040C08, 0x0C040C08, 0x0800080C, 0x0C00080C, 0x0804080C, 0x0C04080C, 0x08000C0C, 0x0C000C0C, 0x08040C0C, 0x0C040C0C, 0x00080808, 0x04080808, 0x000C0808, 0x040C0808, 0x00080C08, 0x04080C08, 0x000C0C08, 0x040C0C08, 0x0008080C, 0x0408080C, 0x000C080C, 0x040C080C, 0x00080C0C, 0x04080C0C, 0x000C0C0C, 0x040C0C0C, 0x08080808, 0x0C080808, 0x080C0808, 0x0C0C0808, 0x08080C08, 0x0C080C08, 0x080C0C08, 0x0C0C0C08, 0x0808080C, 0x0C08080C, 0x080C080C, 0x0C0C080C, 0x08080C0C, 0x0C080C0C, 0x080C0C0C, 0x0C0C0C0C, }; /*----------------------------------------------------------------------*/ /* Macros to convert from Atari's planar mode to chunky mode * (1 byte per pixel). Convert by blocks of 4 pixels. * 16 low res pixels -> 4 planes of 16 bits * 16 med res pixels -> 2 planes of 16 bits * 16 hi res pixels -> 1 plane of 16 bits */ #define LOW_BUILD_PIXELS_0 \ { \ ebx &= 0x0f0f0f0f; \ ecx &= 0x0f0f0f0f; \ eax = (ebx >> 12) | ebx; \ edx = (ecx >> 12) | ecx; \ ecx = Remap_2_Planes_Upper[edx & 0x00ff]; \ ecx += Remap_2_Planes[eax & 0x00ff]; \ } #define LOW_BUILD_PIXELS_1 \ { \ ecx = Remap_2_Planes_Upper[(edx >> 8) & 0x00ff]; \ ecx += Remap_2_Planes[(eax >> 8) & 0x00ff]; \ } #define LOW_BUILD_PIXELS_2 \ { \ ebx = (*edi & 0xf0f0f0f0) >> 4; \ ecx = (*(edi+1) & 0xf0f0f0f0) >> 4; \ eax = (ebx >> 12) | ebx; \ edx = (ecx >> 12) | ecx; \ ecx = Remap_2_Planes_Upper[edx & 0x00ff]; \ ecx += Remap_2_Planes[eax & 0x00ff]; \ } #define LOW_BUILD_PIXELS_3 \ { \ ecx = Remap_2_Planes_Upper[(edx >> 8) & 0x00ff]; \ ecx += Remap_2_Planes[(eax >> 8) & 0x00ff]; \ } #define MED_BUILD_PIXELS_0 \ { \ ebx &= 0x0f0f0f0f; \ eax = (ebx >> 12) | ebx; \ ecx = Remap_2_Planes[eax & 0x000000ff]; \ } #define MED_BUILD_PIXELS_1 \ { \ ecx = Remap_2_Planes[(eax >> 8) & 0x000000ff]; \ } #define MED_BUILD_PIXELS_2 \ { \ ebx = (*edi & 0xf0f0f0f0) >> 4; \ eax = (ebx >> 12) | ebx; \ ecx = Remap_2_Planes[eax & 0x000000ff]; \ } #define MED_BUILD_PIXELS_3 \ { \ ecx = Remap_2_Planes[(eax >> 8) & 0x000000ff]; \ } /*----------------------------------------------------------------------*/ /* Macros to plot Atari's pixels in the emulator's buffer * (the buffer can be 32, 16 or 8 bits per pixel) */ /* * 32 bit screen format */ /* Plot Low Resolution (320xH) 32-Bit pixels */ #define PLOT_LOW_320_32BIT(offset) \ { \ esi[offset+0] = (Uint32)STRGBPalette[ecx & 0x00ff]; \ esi[offset+1] = (Uint32)STRGBPalette[(ecx >> 8) & 0x00ff]; \ esi[offset+2] = (Uint32)STRGBPalette[(ecx >> 16) & 0x00ff]; \ esi[offset+3] = (Uint32)STRGBPalette[(ecx >> 24) & 0x00ff]; \ } /* Plot Low Resolution (640xH) 32-Bit pixels */ #define PLOT_LOW_640_32BIT(offset) \ { \ esi[offset+0] = esi[offset+1] = STRGBPalette[ecx & 0x000000ff]; \ esi[offset+2] = esi[offset+3] = STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+4] = esi[offset+5] = STRGBPalette[(ecx >> 16) & 0x000000ff]; \ esi[offset+6] = esi[offset+7] = STRGBPalette[(ecx >> 24) & 0x000000ff]; \ } /* Plot Medium Resolution(640xH) 32-Bit pixels */ #define PLOT_MED_640_32BIT(offset) \ { \ esi[offset+0] = STRGBPalette[ecx & 0x000000ff]; \ esi[offset+1] = STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+2] = STRGBPalette[(ecx >> 16) & 0x000000ff]; \ esi[offset+3] = STRGBPalette[(ecx >> 24) & 0x000000ff]; \ } /* Plot Spectrum512 Resolution (320xH) 32-Bit pixels */ #define PLOT_SPEC512_LEFT_LOW_320_32BIT(offset) \ { \ esi[offset] = STRGBPalette[ecx & 0x000000ff]; \ } /* Plot Spectrum512 Resolution (320xH) 32-Bit pixels */ #define PLOT_SPEC512_MID_320_32BIT PLOT_LOW_320_32BIT /* Plot Spectrum512 Resolution(320xH) 32-Bit pixels */ #define PLOT_SPEC512_END_LOW_320_32BIT(offset) \ { \ esi[offset+0] = STRGBPalette[ecx & 0x000000ff]; \ esi[offset+1] = STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+2] = STRGBPalette[(ecx >> 16) & 0x000000ff]; \ } /* Plot Spectrum512 Resolution (640xH) 32-Bit pixels */ #define PLOT_SPEC512_LEFT_LOW_640_32BIT(offset) \ { \ esi[offset] = esi[offset+1] = STRGBPalette[ecx & 0x000000ff]; \ } /* Plot Spectrum512 Resolution (640xH) 32-Bit pixels */ #define PLOT_SPEC512_MID_640_32BIT PLOT_LOW_640_32BIT /* Plot Spectrum512 Resolution (640xH) 32-Bit pixels */ #define PLOT_SPEC512_END_LOW_640_32BIT(offset) \ { \ esi[offset+0] = esi[offset+1] = STRGBPalette[ecx & 0x000000ff]; \ esi[offset+2] = esi[offset+3] = STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+4] = esi[offset+5] = STRGBPalette[(ecx >> 16) & 0x000000ff]; \ } /* Plot Spectrum512 Medium Resolution (640xH) 32-Bit pixels */ #define PLOT_SPEC512_LEFT_MED_640_32BIT PLOT_SPEC512_LEFT_LOW_320_32BIT #define PLOT_SPEC512_MID_MED_640_32BIT PLOT_SPEC512_MID_320_32BIT #define PLOT_SPEC512_END_MED_640_32BIT PLOT_SPEC512_END_LOW_320_32BIT /* * 16 bit screen format */ /* Plot Low Resolution (320xH) 16-Bit pixels */ #define PLOT_LOW_320_16BIT(offset) \ { \ esi[offset] = (Uint16)STRGBPalette[ecx & 0x00ff]; \ esi[offset+1] = (Uint16)STRGBPalette[(ecx >> 8) & 0x00ff]; \ esi[offset+2] = (Uint16)STRGBPalette[(ecx >> 16) & 0x00ff]; \ esi[offset+3] = (Uint16)STRGBPalette[(ecx >> 24) & 0x00ff]; \ } /* Plot Low Resolution (640xH) 16-Bit pixels */ #define PLOT_LOW_640_16BIT(offset) \ { \ esi[offset] = STRGBPalette[ecx & 0x000000ff]; \ esi[offset+1] = STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+2] = STRGBPalette[(ecx >> 16) & 0x000000ff]; \ esi[offset+3] = STRGBPalette[(ecx >> 24) & 0x000000ff]; \ } /* Plot Medium Resolution(640xH) 16-Bit pixels */ #define PLOT_MED_640_16BIT(offset) \ { \ esi[offset] = (Uint16)STRGBPalette[ecx & 0x000000ff]; \ esi[offset+1] = (Uint16)STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+2] = (Uint16)STRGBPalette[(ecx >> 16) & 0x000000ff]; \ esi[offset+3] = (Uint16)STRGBPalette[(ecx >> 24) & 0x000000ff]; \ } /* Plot Spectrum512 Resolution(320xH) 16-Bit pixels */ #define PLOT_SPEC512_LEFT_LOW_320_16BIT(offset) \ { \ esi[offset] = (Uint16)STRGBPalette[ecx & 0x000000ff]; \ } /* Plot Spectrum512 Resolution(320xH) 16-Bit pixels */ #define PLOT_SPEC512_MID_320_16BIT PLOT_LOW_640_16BIT /* Plot Spectrum512 Resolution(320xH) 16-Bit pixels */ #define PLOT_SPEC512_END_LOW_320_16BIT(offset) \ { \ esi[offset] = (Uint16)STRGBPalette[ecx & 0x000000ff]; \ esi[offset+1] = (Uint16)STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+2] = (Uint16)STRGBPalette[(ecx >> 16) & 0x000000ff]; \ } /* Plot Spectrum512 Resolution (640xH) 16-Bit pixels */ #define PLOT_SPEC512_LEFT_LOW_640_16BIT(offset) \ { \ esi[offset] = STRGBPalette[ecx & 0x000000ff]; \ } /* Plot Spectrum512 Resolution (640xH) 16-Bit pixels */ #define PLOT_SPEC512_MID_640_16BIT PLOT_LOW_640_16BIT /* Plot Spectrum512 Resolution (640xH) 16-Bit pixels */ #define PLOT_SPEC512_END_LOW_640_16BIT(offset) \ { \ esi[offset] = STRGBPalette[ecx & 0x000000ff]; \ esi[offset+1] = STRGBPalette[(ecx >> 8) & 0x000000ff]; \ esi[offset+2] = STRGBPalette[(ecx >> 16) & 0x000000ff]; \ } /* Plot Spectrum512 Medium Resolution (640xH) 16-Bit pixels */ #define PLOT_SPEC512_LEFT_MED_640_16BIT PLOT_SPEC512_LEFT_LOW_320_16BIT #define PLOT_SPEC512_MID_MED_640_16BIT PLOT_SPEC512_MID_320_16BIT #define PLOT_SPEC512_END_MED_640_16BIT PLOT_SPEC512_END_LOW_320_16BIT /* Get Spec512 pixels which are offset by 1 pixel */ #if defined(__i386__) // Unaligned direct access is only supported on i86 platforms /* on AMD XP, first one is 1/3 faster than aligned access, and * final pixels access ~15% faster than aligned operation below */ # define GET_SPEC512_OFFSET_PIXELS(pixels, x) \ (*(Uint32 *)(((Uint8 *)pixels) + x)) # define GET_SPEC512_OFFSET_FINAL_PIXELS(pixels) \ (*(Uint32 *)(((Uint8 *)pixels) + 13)) #else # define GET_SPEC512_OFFSET_PIXELS(pixels, x) \ (((*(Uint32 *)(((Uint8 *)pixels) + x-1)) >> 8) \ | ((*(Uint32 *)(((Uint8 *)pixels) + x+3)) << 24)) # define GET_SPEC512_OFFSET_FINAL_PIXELS(pixels) \ ((*(Uint32 *)(((Uint8 *)pixels) + 12)) >> 8) #endif /* __i386__ */ #endif /* HATARI_CONVERTMACROS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/med640x32.c000066400000000000000000000054371504763705000245210ustar00rootroot00000000000000/* Hatari - med640x32.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen Conversion, Medium Res to 640x32Bit */ static void ConvertMediumRes_640x32Bit(void) { Uint32 *PCScreen = (Uint32 *)pPCScreenDest; Uint32 *edi, *ebp; Uint32 *esi; Uint32 eax; int y; Convert_StartFrame(); /* Start frame, track palettes */ for (y = STScreenStartHorizLine; y < STScreenEndHorizLine; y++) { eax = STScreenLineOffset[y] + STScreenLeftSkipBytes; /* Offset for this line + Amount to skip on left hand side */ edi = (Uint32 *)((Uint8 *)pSTScreen + eax); /* ST format screen 4-plane 16 colors */ ebp = (Uint32 *)((Uint8 *)pSTScreenCopy + eax); /* Previous ST format screen */ esi = PCScreen; /* PC format screen */ if (AdjustLinePaletteRemap(y) & 0x00030000) /* Change palette table */ Line_ConvertMediumRes_640x32Bit(edi, ebp, esi, eax); else Line_ConvertLowRes_640x32Bit(edi, ebp, esi, eax); PCScreen = Double_ScreenLine32(PCScreen, PCScreenBytesPerLine); } } static void Line_ConvertMediumRes_640x32Bit(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax) { Uint32 ebx, ecx; int x, update; x = STScreenWidthBytes >> 2; /* Amount to draw across in 16-pixels (4 bytes) */ update = ScrUpdateFlag & PALETTEMASK_UPDATEMASK; do /* x-loop */ { /* Do 16 pixels at one time */ ebx = *edi; if (update || ebx != *ebp) /* Does differ? */ { /* copy word */ bScreenContentsChanged = true; #if SDL_BYTEORDER == SDL_BIG_ENDIAN /* Plot in 'right-order' on big endian systems */ MED_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [12,13,14,15] */ PLOT_MED_640_32BIT(12) ; MED_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [4,5,6,7] */ PLOT_MED_640_32BIT(4) ; MED_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [8,9,10,11] */ PLOT_MED_640_32BIT(8) ; MED_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [0,1,2,3] */ PLOT_MED_640_32BIT(0) ; #else /* Plot in 'wrong-order', as ebx is 68000 endian */ MED_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [4,5,6,7] */ PLOT_MED_640_32BIT(4) ; MED_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [12,13,14,15] */ PLOT_MED_640_32BIT(12) ; MED_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [0,1,2,3] */ PLOT_MED_640_32BIT(0) ; MED_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [8,9,10,11] */ PLOT_MED_640_32BIT(8) ; #endif } esi += 16; /* Next PC pixels */ edi += 1; /* Next ST pixels */ ebp += 1; /* Next ST copy pixels */ } while (--x); /* Loop on X */ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/med640x32_spec.c000066400000000000000000000076411504763705000255320ustar00rootroot00000000000000/* Hatari - med640x32_spec.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen Conversion, Medium Res Spec512 to 640x32Bit */ static void ConvertMediumRes_640x32Bit_Spec(void) { Uint32 *PCScreen = (Uint32 *)pPCScreenDest; Uint32 *edi, *ebp; Uint32 *esi; Uint32 eax; int y; Spec512_StartFrame(); /* Start frame, track palettes */ for (y = STScreenStartHorizLine; y < STScreenEndHorizLine; y++) { eax = STScreenLineOffset[y] + STScreenLeftSkipBytes; /* Offset for this line + Amount to skip on left hand side */ edi = (Uint32 *)((Uint8 *)pSTScreen + eax); /* ST format screen 4-plane 16 colors */ ebp = (Uint32 *)((Uint8 *)pSTScreenCopy + eax); /* Previous ST format screen */ esi = PCScreen; /* PC format screen */ if (HBLPaletteMasks[y] & 0x00030000) /* Test resolution */ Line_ConvertMediumRes_640x32Bit_Spec(edi, ebp, esi, eax); /* med res line */ else Line_ConvertLowRes_640x32Bit_Spec(edi, ebp, esi, eax); /* low res line (double on X) */ PCScreen = Double_ScreenLine32(PCScreen, PCScreenBytesPerLine); } bScreenContentsChanged = true; } static void Line_ConvertMediumRes_640x32Bit_Spec(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax) { int x; Uint32 ebx, ecx; Uint32 pixelspace[5]; /* Workspace to store pixels to so can print in right order for Spec512 */ /* on x86, unaligned access macro touches also * next byte, zero it for code checkers */ pixelspace[4] = 0; Spec512_StartScanLine(); /* Build up palettes for every 4 pixels, store in 'ScanLinePalettes' */ x = STScreenWidthBytes >> 2; /* Amount to draw across in 16-pixels (4 bytes) */ do /* x-loop */ { /* Do 16 pixels at one time */ ebx = *edi; #if SDL_BYTEORDER == SDL_BIG_ENDIAN /* Plot in 'right-order' on big endian systems */ MED_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [12,13,14,15] */ pixelspace[3] = ecx; MED_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [4,5,6,7] */ pixelspace[1] = ecx; MED_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [8,9,10,11] */ pixelspace[2] = ecx; MED_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [0,1,2,3] */ pixelspace[0] = ecx; #else /* Plot in 'wrong-order', as ebx is 68000 endian */ MED_BUILD_PIXELS_0 ; /* Generate 'ecx' as pixels [4,5,6,7] */ pixelspace[1] = ecx; MED_BUILD_PIXELS_1 ; /* Generate 'ecx' as pixels [12,13,14,15] */ pixelspace[3] = ecx; MED_BUILD_PIXELS_2 ; /* Generate 'ecx' as pixels [0,1,2,3] */ pixelspace[0] = ecx; MED_BUILD_PIXELS_3 ; /* Generate 'ecx' as pixels [8,9,10,11] */ pixelspace[2] = ecx; #endif /* And plot, the Spec512 is offset by 1 pixel and works on 'chunks' of 4 pixels */ /* So, we plot 1_4_4_4_3 to give 16 pixels, changing palette between */ /* (last one is used for first of next 16-pixels) */ /* NOTE : In med res, we display 16 pixels in 8 cycles, so palette should be */ /* updated every 8 pixels, not every 4 pixels (as in low res) */ ecx = pixelspace[0]; PLOT_SPEC512_LEFT_MED_640_32BIT(0); // Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 1); PLOT_SPEC512_MID_MED_640_32BIT(1); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 5); PLOT_SPEC512_MID_MED_640_32BIT(5); // Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_PIXELS(pixelspace, 9); PLOT_SPEC512_MID_MED_640_32BIT(9); Spec512_UpdatePaletteSpan(); ecx = GET_SPEC512_OFFSET_FINAL_PIXELS(pixelspace); PLOT_SPEC512_END_MED_640_32BIT(13); esi += 16; /* Next PC pixels */ edi += 1; /* Next ST pixels */ ebp += 1; /* Next ST copy pixels */ } while (--x); /* Loop on X */ Spec512_EndScanLine(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/convert/routines.h000066400000000000000000000016171504763705000250360ustar00rootroot00000000000000/* Hatari - routines.h Definitions for the screen conversion routines This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CONVERTROUTINES_H #define HATARI_CONVERTROUTINES_H static void ConvertLowRes_320x32Bit(void); static void ConvertLowRes_640x32Bit(void); static void ConvertLowRes_320x32Bit_Spec(void); static void Line_ConvertLowRes_640x32Bit_Spec(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax); static void ConvertLowRes_640x32Bit_Spec(void); static void Line_ConvertMediumRes_640x32Bit(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax); static void ConvertMediumRes_640x32Bit(void); static void Line_ConvertMediumRes_640x32Bit_Spec(Uint32 *edi, Uint32 *ebp, Uint32 *esi, Uint32 eax); static void ConvertMediumRes_640x32Bit_Spec(void); #endif /* HATARI_CONVERTROUTINES_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/000077500000000000000000000000001504763705000221175ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/CMakeLists.txt000066400000000000000000000102001504763705000246500ustar00rootroot00000000000000 include_directories(. ../.. ../includes ${SDL2_INCLUDE_DIRS} softfloat ${CMAKE_CURRENT_BINARY_DIR}) # The sources generated by gencpu: set(CPUEMU_SRCS cpustbl.c cpuemu_0.c cpuemu_11.c cpuemu_13.c cpuemu_20.c cpuemu_21.c cpuemu_22.c cpuemu_23.c cpuemu_24.c cpuemu_31.c cpuemu_32.c cpuemu_33.c cpuemu_34.c cpuemu_35.c cpuemu_40.c cpuemu_50.c) # Sources that are synchronized with WinUAE: set(WINUAE_SRCS cpudefs.c cpummu.c cpummu030.c debug.c disasm.c newcpu_common.c newcpu.c readcpu.c writelog.c fpp.c fpp_native.c fpp_softfloat.c softfloat/softfloat.c softfloat/softfloat_decimal.c softfloat/softfloat_fpsp.c machdep/m68k.c) # Unfortunately we've got to specify the rules for the generated files twice, # once for cross compiling (with calling the host cc directly) and once # for native compiling so that the rules also work for non-Unix environments... if(CMAKE_CROSSCOMPILING) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/build68k COMMAND cc -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/build68k.c ${CMAKE_CURRENT_SOURCE_DIR}/writelog.c -o ${CMAKE_CURRENT_BINARY_DIR}/build68k DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/build68k.c) add_custom_command(OUTPUT cpudefs.c COMMAND ./build68k < ${CMAKE_CURRENT_SOURCE_DIR}/table68k >cpudefs.c DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/table68k ${CMAKE_CURRENT_BINARY_DIR}/build68k) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gencpu COMMAND cc -I${CMAKE_CURRENT_SOURCE_DIR} cpudefs.c ${CMAKE_CURRENT_SOURCE_DIR}/gencpu.c ${CMAKE_CURRENT_SOURCE_DIR}/readcpu.c -o ${CMAKE_CURRENT_BINARY_DIR}/gencpu DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gencpu.c ${CMAKE_CURRENT_SOURCE_DIR}/readcpu.c cpudefs.c) add_custom_command(OUTPUT ${CPUEMU_SRCS} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gencpu DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gencpu) else() # Rules for normal build follow add_executable(build68k build68k.c writelog.c) add_custom_command(OUTPUT cpudefs.c COMMAND build68k < ${CMAKE_CURRENT_SOURCE_DIR}/table68k >cpudefs.c DEPENDS table68k build68k) add_executable(gencpu gencpu.c readcpu.c cpudefs.c) add_custom_command(OUTPUT ${CPUEMU_SRCS} COMMAND gencpu DEPENDS gencpu) endif(CMAKE_CROSSCOMPILING) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") # Silence some warnings for the autogenerated sources - the generated # cpuemu_xx.c contains a lot of warnings we don't really care about... set(CPUEMU_CFLAGS "-Wno-sign-compare -Wno-unused-variable") set(CPUEMU_CFLAGS "${CPUEMU_CFLAGS} -fwrapv") if (WARN_SHADOW_LOCAL_AVAILABLE) set(CPUEMU_CFLAGS "${CPUEMU_CFLAGS} -Wno-shadow=local") endif() check_c_compiler_flag(-Wunused-but-set-variable HAS_WUNUSED_BUT_SET_VAR) if (HAS_WUNUSED_BUT_SET_VAR) set (CPUEMU_CFLAGS "${CPUEMU_CFLAGS} -Wno-unused-but-set-variable") endif() set_source_files_properties(${CPUEMU_SRCS} PROPERTIES COMPILE_FLAGS ${CPUEMU_CFLAGS}) # For the other files, hide some harmless warnings, too (since we # try to keep the files in sync with WinUAE, it's hard to fix them) set(CPUMAIN_CFLAGS "-Wno-unused-variable -Wno-unused-function -Wno-unused-label") set(CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -Wno-missing-braces -Wno-sign-compare") set(CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -fwrapv") if (WARN_FALLTRHOUGH_AVAILABLE) set(CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -Wno-implicit-fallthrough") endif() if (WARN_SHADOW_LOCAL_AVAILABLE) set(CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -Wno-shadow=local") endif() if (HAS_WUNUSED_BUT_SET_VAR) set (CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -Wno-unused-but-set-variable") endif() # The remaining warnings should be hidden for release builds: if (CMAKE_BUILD_TYPE STREQUAL "Release") set(CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -Wno-bad-function-cast") if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.7) set(CPUMAIN_CFLAGS "${CPUMAIN_CFLAGS} -Wno-maybe-uninitialized") endif() endif() set_source_files_properties(${WINUAE_SRCS} PROPERTIES COMPILE_FLAGS ${CPUMAIN_CFLAGS}) endif() add_library(UaeCpu ${CPUEMU_SRCS} ${WINUAE_SRCS} custom.c events.c memory.c hatari-glue.c) target_link_libraries(UaeCpu PRIVATE ${SDL2_LIBRARIES}) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/build68k.c000066400000000000000000000173431504763705000237230ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * Read 68000 CPU specs from file "table68k" and build table68k.c * * Copyright 1995,1996 Bernd Schmidt */ #include "sysconfig.h" #include "sysdeps.h" #include #include #include #include #define TCHAR char #include "readcpu.h" static FILE *tablef; static int nextch = 0; static void getnextch(void) { do { nextch = fgetc(tablef); if (nextch == '%') { do { nextch = fgetc(tablef); } while (nextch != EOF && nextch != '\n'); } } while (nextch != EOF && isspace(nextch)); } static int nextchtohex(void) { switch (isupper (nextch) ? tolower (nextch) : nextch) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': return 10; case 'b': return 11; case 'c': return 12; case 'd': return 13; case 'e': return 14; case 'f': return 15; default: abort(); } } int main(int argc, char **argv) { int no_insns = 0; printf ("#include \"sysconfig.h\"\n"); printf ("#include \"sysdeps.h\"\n"); printf ("#include \"readcpu.h\"\n"); printf ("struct instr_def defs68k[] = {\n"); #if 0 tablef = fopen("table68k","r"); if (tablef == NULL) { fprintf(stderr, "table68k not found\n"); exit(1); } #else tablef = stdin; #endif getnextch(); while (nextch != EOF) { int cpulevel, uncpulevel, plevel, sduse; int i; char opcstr[256]; int bitpos[16]; int flagset[5], flaguse[5]; char cflow; unsigned int bitmask,bitpattern; int n_variable; int head = 0, tail = 0, clocks = 0, fetchmode = 0; n_variable = 0; bitmask = bitpattern = 0; memset (bitpos, 0, sizeof(bitpos)); for(i=0; i<16; i++) { int currbit; bitmask <<= 1; bitpattern <<= 1; switch (nextch) { case '0': currbit = bit0; bitmask |= 1; break; case '1': currbit = bit1; bitmask |= 1; bitpattern |= 1; break; case 'c': currbit = bitc; break; case 'C': currbit = bitC; break; case 'f': currbit = bitf; break; case 'i': currbit = biti; break; case 'I': currbit = bitI; break; case 'j': currbit = bitj; break; case 'J': currbit = bitJ; break; case 'k': currbit = bitk; break; case 'K': currbit = bitK; break; case 's': currbit = bits; break; case 'S': currbit = bitS; break; case 'd': currbit = bitd; break; case 'D': currbit = bitD; break; case 'r': currbit = bitr; break; case 'R': currbit = bitR; break; case 'z': currbit = bitz; break; case 'p': currbit = bitp; break; default: abort(); } if (!(bitmask & 1)) { bitpos[n_variable] = currbit; n_variable++; } if (nextch == '0' || nextch == '1') bitmask |= 1; if (nextch == '1') bitpattern |= 1; getnextch(); } while (isspace(nextch) || nextch == ':') /* Get CPU level, unimplemented level, and privilege level */ getnextch(); switch (nextch) { case '0': cpulevel = 0; break; case '1': cpulevel = 1; break; case '2': cpulevel = 2; break; case '3': cpulevel = 3; break; case '4': cpulevel = 4; break; case '5': cpulevel = 5; break; case '6': cpulevel = 6; break; case '7': cpulevel = 7; break; default: abort(); } getnextch(); switch (nextch) { case '0': uncpulevel = 0; break; case '1': uncpulevel = 1; break; case '2': uncpulevel = 2; break; case '3': uncpulevel = 3; break; case '4': uncpulevel = 4; break; case '5': uncpulevel = 5; break; case '6': uncpulevel = 6; break; case '7': uncpulevel = 7; break; default: abort(); } getnextch(); switch (nextch) { case '0': plevel = 0; break; case '1': plevel = 1; break; case '2': plevel = 2; break; case '3': plevel = 3; break; default: abort(); } getnextch(); while (isspace(nextch)) /* Get flag set information */ getnextch(); if (nextch != ':') abort(); for(i = 0; i < 5; i++) { getnextch(); switch(nextch){ case '-': flagset[i] = fa_unset; break; case '/': flagset[i] = fa_isjmp; break; case '+': flagset[i] = fa_isbranch; break; case '0': flagset[i] = fa_zero; break; case '1': flagset[i] = fa_one; break; case 'x': flagset[i] = fa_dontcare; break; case '?': flagset[i] = fa_unknown; break; default: flagset[i] = fa_set; break; } } getnextch(); while (isspace(nextch)) getnextch(); if (nextch != ':') /* Get flag used information */ abort(); for(i = 0; i < 5; i++) { getnextch(); switch(nextch){ case '-': flaguse[i] = fu_unused; break; case '/': flaguse[i] = fu_isjmp; break; case '+': flaguse[i] = fu_maybecc; break; case '?': flaguse[i] = fu_unknown; break; default: flaguse[i] = fu_used; break; } } getnextch(); while (isspace(nextch)) getnextch(); if (nextch != ':') /* Get control flow information */ abort(); cflow = 0; for (i = 0; i < 2; i++) { getnextch(); switch (nextch) { case '-': break; case 'R': cflow |= fl_return; break; case 'B': cflow |= fl_branch; break; case 'J': cflow |= fl_jump; break; case 'T': cflow |= fl_trap; break; default: abort(); } } getnextch(); while (isspace(nextch)) getnextch(); if (nextch != ':') /* Get source/dest usage information */ abort(); getnextch(); sduse = nextchtohex() << 4; getnextch(); sduse |= nextchtohex(); getnextch(); while (isspace(nextch)) getnextch(); if (nextch != ':') abort(); if (fgets(opcstr, 250, tablef) != opcstr) { abort(); } getnextch(); if (nextch == '-') { int neg; char fm[20]; getnextch(); while (isspace(nextch)) getnextch(); neg = 1; if (nextch == '-') { neg = -1; getnextch(); } for (;;) { if (nextch < '0' || nextch > '9') break; head *= 10; head += nextch - '0'; nextch = fgetc (tablef); } head *= neg; while (isspace(nextch)) getnextch(); for (;;) { if (nextch < '0' || nextch > '9') break; tail *= 10; tail += nextch - '0'; nextch = fgetc (tablef); } while (isspace(nextch)) getnextch(); for (;;) { if (nextch < '0' || nextch > '9') break; clocks *= 10; clocks += nextch - '0'; nextch = fgetc (tablef); } if (nextch == ' ') { if (fgets(fm, sizeof fm, tablef) != fm) { abort(); } if (!strnicmp(fm, "fea", 3)) fetchmode = 1; if (!strnicmp(fm, "cea", 3)) fetchmode = 2; if (!strnicmp(fm, "fiea", 4)) fetchmode = 3; if (!strnicmp(fm, "ciea", 4)) fetchmode = 4; if (!strnicmp(fm, "jea", 3)) fetchmode = 5; } getnextch(); } int j; /* Remove superfluous spaces from the string */ char *opstrp = opcstr, *osendp; char tmp[100], *p; int slen = 0; while (isspace((unsigned char)*opstrp)) opstrp++; osendp = opstrp; while (*osendp) { if (!isspace ((unsigned char)*osendp)) slen = osendp - opstrp + 1; osendp++; } opstrp[slen] = 0; if (no_insns > 0) printf(",\n"); no_insns++; strcpy (tmp, opstrp); strcat (tmp, " "); p = tmp; while (!isspace((unsigned char)*p++)); *p = 0; printf("/* %s */\n", tmp); printf("{0x%04X,%2d,{", bitpattern, n_variable); for (j = 0; j < 16; j++) { printf("%2d", bitpos[j]); if (j < 15) printf(","); } printf ("},0x%04X,%d,%d,%d,{", bitmask, cpulevel, uncpulevel, plevel); for(i = 0; i < 5; i++) { printf("{%d,%d}%s", flaguse[i], flagset[i], i == 4 ? "" : ","); } printf("},0x%02x,0x%02x,_T(\"%s\"),%2d,%2d,%2d,%2d}", cflow, sduse, opstrp, head, tail, clocks, fetchmode); } printf("};\nint n_defs68k = %d;\n", no_insns); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/compat.h000066400000000000000000000024671504763705000235640ustar00rootroot00000000000000/* Hatari - compat.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This file contains all the includes and defines specific to windows (such as TCHAR) needed by WinUAE CPU core. The aim is to have minimum changes in WinUae CPU core for next updates. */ #ifndef HATARI_COMPAT_H #define HATARI_COMPAT_H #include #include "uae/string.h" #define strnicmp strncasecmp #define console_out printf //#define console_out_f printf #define console_out_f(...) { if ( console_out_FILE ) fprintf ( console_out_FILE , __VA_ARGS__ ); else printf ( __VA_ARGS__ ); } #define gui_message console_out_f #define uae_log printf static inline void to_lower (TCHAR *s, int len) { int i; if (len < 0 ) len = uaetcslen(s); for (i = 0; i < len; i++) { s[i] = tolower(s[i]); } } static inline void to_upper (TCHAR *s, int len) { int i; if (len < 0 ) len = uaetcslen(s); for (i = 0; i < len; i++) { s[i] = toupper(s[i]); } } static inline void my_trim (TCHAR *s) { size_t len; while (_tcslen (s) > 0 && _tcscspn (s, _T("\t \r\n")) == 0) memmove (s, s + 1, (_tcslen (s + 1) + 1) * sizeof (TCHAR)); len = _tcslen (s); while (len > 0 && _tcscspn (s + len - 1, _T("\t \r\n")) == 0) s[--len] = '\0'; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/cpu_prefetch.h000066400000000000000000000233441504763705000247450ustar00rootroot00000000000000#ifndef UAE_CPU_PREFETCH_H #define UAE_CPU_PREFETCH_H #include "uae/types.h" #ifdef CPUEMU_20 extern uae_u32 get_word_020_prefetch (int); extern void continue_020_prefetch(void); STATIC_INLINE uae_u32 next_iword_020_prefetch (void) { uae_u32 r = get_word_020_prefetch (0); m68k_incpci (2); return r; } STATIC_INLINE uae_u32 next_ilong_020_prefetch (void) { uae_u32 r = next_iword_020_prefetch () << 16; r |= next_iword_020_prefetch (); return r; } STATIC_INLINE uae_u32 get_long_020_prefetch (int o) { uae_u32 r = get_word_020_prefetch (o) << 16; r |= get_word_020_prefetch (o + 2); return r; } #endif #ifdef CPUEMU_21 STATIC_INLINE void limit_cycles_ce020(int clocks) { #if 0 int cycs = clocks * cpucycleunit; int diff = regs.ce020endcycle - regs.ce020startcycle; if (diff <= cycs) return; regs.ce020startcycle = regs.ce020endcycle - cycs; #endif } STATIC_INLINE void limit_all_cycles_ce020(void) { #if 0 regs.ce020startcycle = regs.ce020endcycle; #endif } // only for CPU internal cycles STATIC_INLINE void do_cycles_ce020_internal(int clocks) { //fprintf ( stderr , "do_cycles_ce020_internal %d sp=%d" , clocks , currprefs.m68k_speed ); if (currprefs.m68k_speed < 0) { regs.ce020extracycles += clocks; return; } int cycs = clocks * cpucycleunit; //fprintf ( stderr , "do_cycles_ce020_internal %d %d" , cycs , regs.ce020memcycles ); int diff = (int)(regs.ce020endcycle - regs.ce020startcycle); if (diff > 0) { if (diff >= cycs) { regs.ce020startcycle += cycs; return; } regs.ce020startcycle = regs.ce020endcycle; cycs -= diff; } #if 0 if (regs.ce020memcycles > 0) { if (regs.ce020memcycles >= cycs) { regs.ce020memcycles -= cycs; return; } cycs = cycs - regs.ce020memcycles; } //fprintf ( stderr , " -> %d\n" , cycs ); regs.ce020memcycles = 0; #endif x_do_cycles (cycs); } STATIC_INLINE void do_cycles_020_internal(int clocks) { if (currprefs.m68k_speed < 0) return; x_do_cycles(clocks * cpucycleunit); } STATIC_INLINE void do_cycles_ce020_mem (int clocks, uae_u32 val) { x_do_cycles_post (clocks * cpucycleunit, val); } #if 0 STATIC_INLINE void do_head_cycles_ce020 (int h) { if (regs.ce020_tail) { int cycs = regs.ce020_tail_cycles - get_cycles (); if (cycs < 0) cycs = 0; cycs -= h * cpucycleunit; if (cycs) x_do_cycles (cycs < 0 ? -cycs : cycs); } else if (h > 0) { do_cycles_ce020 (h); } } #endif STATIC_INLINE uae_u32 get_long_ce020 (uaecptr addr) { return mem_access_delay_long_read_ce020 (addr); } STATIC_INLINE uae_u32 get_word_ce020 (uaecptr addr) { return mem_access_delay_word_read_ce020 (addr); } STATIC_INLINE uae_u32 get_byte_ce020 (uaecptr addr) { return mem_access_delay_byte_read_ce020 (addr); } STATIC_INLINE void put_long_ce020 (uaecptr addr, uae_u32 v) { mem_access_delay_long_write_ce020 (addr, v); } STATIC_INLINE void put_word_ce020 (uaecptr addr, uae_u32 v) { mem_access_delay_word_write_ce020 (addr, v); } STATIC_INLINE void put_byte_ce020 (uaecptr addr, uae_u32 v) { mem_access_delay_byte_write_ce020 (addr, v); } extern void continue_ce020_prefetch(void); extern uae_u32 get_word_ce020_prefetch(int); extern uae_u32 get_word_ce020_prefetch_opcode(int); STATIC_INLINE uae_u32 get_long_ce020_prefetch (int o) { uae_u32 v; uae_u16 tmp; v = get_word_ce020_prefetch (o) << 16; tmp = regs.db; v |= get_word_ce020_prefetch (o + 2); regs.db = tmp; return v; } STATIC_INLINE uae_u32 next_iword_020ce (void) { uae_u32 r = get_word_ce020_prefetch (0); m68k_incpci (2); return r; } STATIC_INLINE uae_u32 next_ilong_020ce (void) { uae_u32 r = get_long_ce020_prefetch (0); m68k_incpci (4); return r; } STATIC_INLINE void m68k_do_bsr_ce020 (uaecptr oldpc, uae_s32 offset) { m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), oldpc); m68k_incpci (offset); } STATIC_INLINE void m68k_do_rts_ce020 (void) { m68k_setpci (x_get_long (m68k_areg (regs, 7))); m68k_areg (regs, 7) += 4; } #endif #ifdef CPUEMU_22 extern void continue_030_prefetch(void); extern uae_u32 get_word_030_prefetch(int); STATIC_INLINE void put_long_030(uaecptr addr, uae_u32 v) { write_data_030_lput(addr, v); } STATIC_INLINE void put_word_030(uaecptr addr, uae_u32 v) { write_data_030_wput(addr, v); } STATIC_INLINE void put_byte_030(uaecptr addr, uae_u32 v) { write_data_030_bput(addr, v); } STATIC_INLINE uae_u32 get_long_030(uaecptr addr) { return read_data_030_lget(addr); } STATIC_INLINE uae_u32 get_word_030(uaecptr addr) { return read_data_030_wget(addr); } STATIC_INLINE uae_u32 get_byte_030(uaecptr addr) { return read_data_030_bget(addr); } STATIC_INLINE uae_u32 get_long_030_prefetch(int o) { uae_u32 v; v = get_word_030_prefetch(o) << 16; v |= get_word_030_prefetch(o + 2); return v; } STATIC_INLINE uae_u32 next_iword_030_prefetch(void) { uae_u32 r = get_word_030_prefetch(0); m68k_incpci(2); return r; } STATIC_INLINE uae_u32 next_ilong_030_prefetch(void) { uae_u32 r = get_long_030_prefetch(0); m68k_incpci(4); return r; } STATIC_INLINE void m68k_do_bsr_030(uaecptr oldpc, uae_s32 offset) { m68k_areg(regs, 7) -= 4; put_long_030(m68k_areg(regs, 7), oldpc); m68k_incpci(offset); } STATIC_INLINE void m68k_do_rts_030(void) { m68k_setpc(get_long_030(m68k_areg(regs, 7))); m68k_areg(regs, 7) += 4; } #endif #ifdef CPUEMU_23 extern void continue_ce030_prefetch(void); extern uae_u32 get_word_ce030_prefetch(int); extern uae_u32 get_word_ce030_prefetch_opcode(int); STATIC_INLINE uae_u32 get_long_ce030 (uaecptr addr) { return mem_access_delay_long_read_ce020 (addr); } STATIC_INLINE uae_u32 get_word_ce030 (uaecptr addr) { return mem_access_delay_word_read_ce020 (addr); } STATIC_INLINE uae_u32 get_byte_ce030 (uaecptr addr) { return mem_access_delay_byte_read_ce020 (addr); } STATIC_INLINE void put_long_ce030 (uaecptr addr, uae_u32 v) { mem_access_delay_long_write_ce020 (addr, v); } STATIC_INLINE void put_word_ce030 (uaecptr addr, uae_u32 v) { mem_access_delay_word_write_ce020 (addr, v); } STATIC_INLINE void put_byte_ce030 (uaecptr addr, uae_u32 v) { mem_access_delay_byte_write_ce020 (addr, v); } STATIC_INLINE void put_long_dc030 (uaecptr addr, uae_u32 v) { write_dcache030_lput(addr, v, (regs.s ? 4 : 0) | 1); } STATIC_INLINE void put_word_dc030 (uaecptr addr, uae_u32 v) { write_dcache030_wput(addr, v, (regs.s ? 4 : 0) | 1); } STATIC_INLINE void put_byte_dc030 (uaecptr addr, uae_u32 v) { write_dcache030_bput(addr, v, (regs.s ? 4 : 0) | 1); } STATIC_INLINE uae_u32 get_long_dc030 (uaecptr addr) { return read_dcache030_lget(addr, (regs.s ? 4 : 0) | 1); } STATIC_INLINE uae_u32 get_word_dc030 (uaecptr addr) { return read_dcache030_wget(addr, (regs.s ? 4 : 0) | 1); } STATIC_INLINE uae_u32 get_byte_dc030 (uaecptr addr) { return read_dcache030_bget(addr, (regs.s ? 4 : 0) | 1); } STATIC_INLINE uae_u32 get_long_ce030_prefetch (int o) { uae_u32 v; v = get_word_ce030_prefetch (o) << 16; v |= get_word_ce030_prefetch (o + 2); return v; } STATIC_INLINE uae_u32 next_iword_030ce (void) { uae_u32 r = get_word_ce030_prefetch (0); m68k_incpci (2); return r; } STATIC_INLINE uae_u32 next_ilong_030ce (void) { uae_u32 r = get_long_ce030_prefetch (0); m68k_incpci (4); return r; } STATIC_INLINE void m68k_do_bsr_ce030 (uaecptr oldpc, uae_s32 offset) { m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), oldpc); m68k_incpci (offset); } STATIC_INLINE void m68k_do_rts_ce030 (void) { m68k_setpc (x_get_long (m68k_areg (regs, 7))); m68k_areg (regs, 7) += 4; } #endif #ifdef CPUEMU_11 STATIC_INLINE uae_u32 get_word_000_prefetch(int o) { uae_u32 v = regs.irc; regs.irc = regs.read_buffer = regs.db = get_wordi (m68k_getpci () + o); return v; } STATIC_INLINE uae_u32 get_byte_000(uaecptr addr) { uae_u32 v = get_byte (addr); regs.db = (v << 8) | v; regs.read_buffer = v; return v; } STATIC_INLINE uae_u32 get_word_000(uaecptr addr) { uae_u32 v = get_word (addr); regs.db = v; regs.read_buffer = v; return v; } STATIC_INLINE void put_byte_000(uaecptr addr, uae_u32 v) { regs.db = (v << 8) | v; regs.write_buffer = v; put_byte (addr, v); } STATIC_INLINE void put_word_000(uaecptr addr, uae_u32 v) { regs.db = v; regs.write_buffer = v; put_word (addr, v); } #endif #ifdef CPUEMU_13 STATIC_INLINE void do_cycles_ce000_internal(int clocks) { if (currprefs.m68k_speed < 0) return; x_do_cycles (clocks * cpucycleunit); } STATIC_INLINE void do_cycles_ce000 (int clocks) { x_do_cycles (clocks * cpucycleunit); } uae_u32 mem_access_delay_word_read (uaecptr addr); uae_u32 mem_access_delay_wordi_read (uaecptr addr); uae_u32 mem_access_delay_byte_read (uaecptr addr); void mem_access_delay_byte_write (uaecptr addr, uae_u32 v); void mem_access_delay_word_write (uaecptr addr, uae_u32 v); STATIC_INLINE uae_u32 get_long_ce000 (uaecptr addr) { uae_u32 v = mem_access_delay_word_read (addr) << 16; v |= mem_access_delay_word_read (addr + 2); return v; } STATIC_INLINE uae_u32 get_word_ce000 (uaecptr addr) { return mem_access_delay_word_read (addr); } STATIC_INLINE uae_u32 get_wordi_ce000 (int offset) { return mem_access_delay_wordi_read (m68k_getpci () + offset); } STATIC_INLINE uae_u32 get_byte_ce000 (uaecptr addr) { return mem_access_delay_byte_read (addr); } STATIC_INLINE uae_u32 get_word_ce000_prefetch (int o) { uae_u32 v = regs.irc; regs.irc = regs.read_buffer = regs.db = x_get_iword (o); return v; } STATIC_INLINE void put_long_ce000 (uaecptr addr, uae_u32 v) { mem_access_delay_word_write (addr, v >> 16); mem_access_delay_word_write (addr + 2, v); } STATIC_INLINE void put_word_ce000 (uaecptr addr, uae_u32 v) { mem_access_delay_word_write (addr, v); } STATIC_INLINE void put_byte_ce000 (uaecptr addr, uae_u32 v) { mem_access_delay_byte_write (addr, v); } #endif STATIC_INLINE uae_u32 get_disp_ea_000 (uae_u32 base, uae_u32 dp) { int reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; return base + (uae_s8)dp + regd; } #endif /* UAE_CPU_PREFETCH_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/cpummu.c000066400000000000000000001353141504763705000236000ustar00rootroot00000000000000/* * cpummu.cpp - MMU emulation * * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by UAE MMU patch * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * ARAnyM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ARAnyM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ARAnyM; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #include "sysconfig.h" #include "sysdeps.h" #include "main.h" #include "hatari-glue.h" #include "options_cpu.h" #include "memory.h" #include "newcpu.h" #include "cpummu.h" #include "debug.h" #include "log.h" #define MMUDUMP 1 #define DBG_MMU_VERBOSE 1 #define DBG_MMU_SANITY 1 #if 0 #define write_log printf #endif #ifdef FULLMMU uae_u32 mmu_is_super; uae_u32 mmu_tagmask, mmu_pagemask, mmu_pagemaski; struct mmu_atc_line mmu_atc_array[ATC_TYPE][ATC_SLOTS][ATC_WAYS]; bool mmu_pagesize_8k; int mmu_pageshift, mmu_pageshift1m; uae_u8 mmu_cache_state; uae_u8 cache_default_ins, cache_default_data; int mmu060_state; uae_u16 mmu_opcode; bool mmu_restart; static bool locked_rmw_cycle; bool rmw_cycle; static bool ismoves; bool mmu_ttr_enabled, mmu_ttr_enabled_ins, mmu_ttr_enabled_data; int mmu_atc_ways[2]; int way_random; int mmu040_movem; uaecptr mmu040_movem_ea; uae_u32 mmu040_move16[4]; #if MMU_ICACHE struct mmu_icache mmu_icache_data[MMU_ICACHE_SZ]; #endif #if MMU_IPAGECACHE uae_u32 atc_last_ins_laddr, atc_last_ins_paddr; uae_u8 atc_last_ins_cache; #endif #if MMU_DPAGECACHE struct mmufastcache atc_data_cache_read[MMUFASTCACHE_ENTRIES]; struct mmufastcache atc_data_cache_write[MMUFASTCACHE_ENTRIES]; #endif #if CACHE_HIT_COUNT int mmu_ins_hit, mmu_ins_miss; int mmu_data_read_hit, mmu_data_read_miss; int mmu_data_write_hit, mmu_data_write_miss; #endif static void mmu_dump_ttr(const TCHAR * label, uae_u32 ttr) { DUNUSED(label); #if MMUDEBUG > 0 uae_u32 from_addr, to_addr; from_addr = ttr & MMU_TTR_LOGICAL_BASE; to_addr = (ttr & MMU_TTR_LOGICAL_MASK) << 8; console_out_f(_T("%s: [%08x] %08x - %08x enabled=%d supervisor=%d wp=%d cm=%02d\n"), label, ttr, from_addr, to_addr, ttr & MMU_TTR_BIT_ENABLED ? 1 : 0, (ttr & (MMU_TTR_BIT_SFIELD_ENABLED | MMU_TTR_BIT_SFIELD_SUPER)) >> MMU_TTR_SFIELD_SHIFT, ttr & MMU_TTR_BIT_WRITE_PROTECT ? 1 : 0, (ttr & MMU_TTR_CACHE_MASK) >> MMU_TTR_CACHE_SHIFT ); #endif } void mmu_make_transparent_region(uaecptr baseaddr, uae_u32 size, int datamode) { uae_u32 * ttr; uae_u32 * ttr0 = datamode ? ®s.dtt0 : ®s.itt0; uae_u32 * ttr1 = datamode ? ®s.dtt1 : ®s.itt1; if ((*ttr1 & MMU_TTR_BIT_ENABLED) == 0) ttr = ttr1; else if ((*ttr0 & MMU_TTR_BIT_ENABLED) == 0) ttr = ttr0; else return; *ttr = baseaddr & MMU_TTR_LOGICAL_BASE; *ttr |= ((baseaddr + size - 1) & MMU_TTR_LOGICAL_BASE) >> 8; *ttr |= MMU_TTR_BIT_ENABLED; #if MMUDEBUG > 0 write_log(_T("MMU: map transparent mapping of %08x\n"), *ttr); #endif } void mmu_tt_modified (void) { mmu_ttr_enabled_ins = ((regs.itt0 | regs.itt1) & MMU_TTR_BIT_ENABLED) != 0; mmu_ttr_enabled_data = ((regs.dtt0 | regs.dtt1) & MMU_TTR_BIT_ENABLED) != 0; mmu_ttr_enabled = mmu_ttr_enabled_ins || mmu_ttr_enabled_data; } #if MMUDUMP /* This dump output makes much more sense than old one */ #ifdef WINUAE_FOR_HATARI #define ULONG uae_u32 #endif #define LEVELA_SIZE 7 #define LEVELB_SIZE 7 #define LEVELC_SIZE (mmu_pagesize_8k ? 5 : 6) #define PAGE_SIZE_4k 12 // = 1 << 12 = 4096 #define PAGE_SIZE_8k 13 // = 1 << 13 = 8192 #define LEVELA_VAL(x) ((((uae_u32)(x)) >> (32 - (LEVELA_SIZE ))) & ((1 << LEVELA_SIZE) - 1)) #define LEVELB_VAL(x) ((((uae_u32)(x)) >> (32 - (LEVELA_SIZE + LEVELB_SIZE ))) & ((1 << LEVELB_SIZE) - 1)) #define LEVELC_VAL(x) ((((uae_u32)(x)) >> (32 - (LEVELA_SIZE + LEVELB_SIZE + LEVELC_SIZE))) & ((1 << LEVELC_SIZE) - 1)) #define LEVELA(root, x) (get_long(root + LEVELA_VAL(x) * 4)) #define LEVELB(a, x) (get_long((((uae_u32)a) & ~((1 << (LEVELB_SIZE + 2)) - 1)) + LEVELB_VAL(x) * 4)) #define LEVELC(b, x) (get_long((((uae_u32)b) & ~((1 << (LEVELC_SIZE + 2)) - 1)) + LEVELC_VAL(x) * 4)) #define ISINVALID(x) ((((ULONG)x) & 3) == 0) static uae_u32 getdesc(uae_u32 root, uae_u32 addr) { ULONG desc; desc = LEVELA(root, addr); if (ISINVALID(desc)) return desc; desc = LEVELB(desc, addr); if (ISINVALID(desc)) return desc; desc = LEVELC(desc, addr); return desc; } static void mmu_dump_table(const char * label, uaecptr root_ptr) { ULONG i; ULONG startaddr; ULONG odesc; ULONG totalpages; ULONG page_size = mmu_pagesize_8k ? PAGE_SIZE_8k : PAGE_SIZE_4k; ULONG pagemask = (1 << page_size) - 1; ULONG descmask = pagemask & ~(0x08 | 0x10); // mask out unused and M bits root_ptr &= 0xfffffe00; console_out_f(_T("MMU dump start. Root = %08x. Page = %d\n"), root_ptr, 1 << page_size); totalpages = 1 << (32 - page_size); startaddr = 0; odesc = getdesc(root_ptr, startaddr); for (i = 0; i <= totalpages; i++) { ULONG addr = i << page_size; ULONG desc = 0; if (i < totalpages) desc = getdesc(root_ptr, addr); if ((desc & descmask) != (odesc & descmask) || i == totalpages) { uae_u8 cm, sp; cm = (odesc >> 5) & 3; sp = (odesc >> 7) & 1; console_out_f(_T("%08x - %08x: %08x WP=%d S=%d CM=%d V=%d (%08x)\n"), startaddr, addr - 1, odesc & ~((1 << page_size) - 1), (odesc & 4) ? 1 : 0, sp, cm, (odesc & 3) > 0, odesc); startaddr = addr; odesc = desc; } } console_out_f(_T("MMU dump end\n")); } #else /* {{{ mmu_dump_table */ static void mmu_dump_table(const char * label, uaecptr root_ptr) { DUNUSED(label); const int ROOT_TABLE_SIZE = 128, PTR_TABLE_SIZE = 128, PAGE_TABLE_SIZE = 64, ROOT_INDEX_SHIFT = 25, PTR_INDEX_SHIFT = 18; // const int PAGE_INDEX_SHIFT = 12; int root_idx, ptr_idx, page_idx; uae_u32 root_des, ptr_des, page_des; uaecptr ptr_des_addr, page_addr, root_log, ptr_log, page_log; console_out_f(_T("%s: root=%x\n"), label, root_ptr); for (root_idx = 0; root_idx < ROOT_TABLE_SIZE; root_idx++) { root_des = phys_get_long(root_ptr + root_idx); if ((root_des & 2) == 0) continue; /* invalid */ console_out_f(_T("ROOT: %03d U=%d W=%d UDT=%02d\n"), root_idx, root_des & 8 ? 1 : 0, root_des & 4 ? 1 : 0, root_des & 3 ); root_log = root_idx << ROOT_INDEX_SHIFT; ptr_des_addr = root_des & MMU_ROOT_PTR_ADDR_MASK; for (ptr_idx = 0; ptr_idx < PTR_TABLE_SIZE; ptr_idx++) { struct { uaecptr log, phys; int start_idx, n_pages; /* number of pages covered by this entry */ uae_u32 match; } page_info[PAGE_TABLE_SIZE]; int n_pages_used; ptr_des = phys_get_long(ptr_des_addr + ptr_idx); ptr_log = root_log | (ptr_idx << PTR_INDEX_SHIFT); if ((ptr_des & 2) == 0) continue; /* invalid */ page_addr = ptr_des & (mmu_pagesize_8k ? MMU_PTR_PAGE_ADDR_MASK_8 : MMU_PTR_PAGE_ADDR_MASK_4); n_pages_used = -1; for (page_idx = 0; page_idx < PAGE_TABLE_SIZE; page_idx++) { page_des = phys_get_long(page_addr + page_idx); page_log = ptr_log | (page_idx << 2); // ??? PAGE_INDEX_SHIFT switch (page_des & 3) { case 0: /* invalid */ continue; case 1: case 3: /* resident */ case 2: /* indirect */ if (n_pages_used == -1 || page_info[n_pages_used].match != page_des) { /* use the next entry */ n_pages_used++; page_info[n_pages_used].match = page_des; page_info[n_pages_used].n_pages = 1; page_info[n_pages_used].start_idx = page_idx; page_info[n_pages_used].log = page_log; } else { page_info[n_pages_used].n_pages++; } break; } } if (n_pages_used == -1) continue; console_out_f(_T(" PTR: %03d U=%d W=%d UDT=%02d\n"), ptr_idx, ptr_des & 8 ? 1 : 0, ptr_des & 4 ? 1 : 0, ptr_des & 3 ); for (page_idx = 0; page_idx <= n_pages_used; page_idx++) { page_des = page_info[page_idx].match; if ((page_des & MMU_PDT_MASK) == 2) { console_out_f(_T(" PAGE: %03d-%03d log=%08x INDIRECT --> addr=%08x\n"), page_info[page_idx].start_idx, page_info[page_idx].start_idx + page_info[page_idx].n_pages - 1, page_info[page_idx].log, page_des & MMU_PAGE_INDIRECT_MASK ); } else { console_out_f(_T(" PAGE: %03d-%03d log=%08x addr=%08x UR=%02d G=%d U1/0=%d S=%d CM=%d M=%d U=%d W=%d\n"), page_info[page_idx].start_idx, page_info[page_idx].start_idx + page_info[page_idx].n_pages - 1, page_info[page_idx].log, page_des & (mmu_pagesize_8k ? MMU_PAGE_ADDR_MASK_8 : MMU_PAGE_ADDR_MASK_4), (page_des & (mmu_pagesize_8k ? MMU_PAGE_UR_MASK_8 : MMU_PAGE_UR_MASK_4)) >> MMU_PAGE_UR_SHIFT, page_des & MMU_DES_GLOBAL ? 1 : 0, (page_des & MMU_TTR_UX_MASK) >> MMU_TTR_UX_SHIFT, page_des & MMU_DES_SUPER ? 1 : 0, (page_des & MMU_TTR_CACHE_MASK) >> MMU_TTR_CACHE_SHIFT, page_des & MMU_DES_MODIFIED ? 1 : 0, page_des & MMU_DES_USED ? 1 : 0, page_des & MMU_DES_WP ? 1 : 0 ); } } } } } /* }}} */ #endif /* {{{ mmu_dump_atc */ static void mmu_dump_atc(void) { } /* }}} */ /* {{{ mmu_dump_tables */ void mmu_dump_tables(void) { console_out_f(_T("URP: %08x SRP: %08x MMUSR: %x TC: %x\n"), regs.urp, regs.srp, regs.mmusr, regs.tcr); mmu_dump_ttr(_T("DTT0"), regs.dtt0); mmu_dump_ttr(_T("DTT1"), regs.dtt1); mmu_dump_ttr(_T("ITT0"), regs.itt0); mmu_dump_ttr(_T("ITT1"), regs.itt1); mmu_dump_atc(); #if MMUDUMP mmu_dump_table("SRP", regs.srp); if (regs.urp != regs.srp) mmu_dump_table("URP", regs.urp); #endif } /* }}} */ static void flush_shortcut_cache(uaecptr addr, bool super) { #if MMU_IPAGECACHE atc_last_ins_laddr = mmu_pagemask; #endif #if MMU_DPAGECACHE if (addr == 0xffffffff) { memset(&atc_data_cache_read, 0xff, sizeof atc_data_cache_read); memset(&atc_data_cache_write, 0xff, sizeof atc_data_cache_write); } else { for (int i = 0; i < MMUFASTCACHE_ENTRIES; i++) { uae_u32 idx = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); if (atc_data_cache_read[i].log == idx) atc_data_cache_read[i].log = 0xffffffff; if (atc_data_cache_write[i].log == idx) atc_data_cache_write[i].log = 0xffffffff; } } #endif } static ALWAYS_INLINE int mmu_get_fc(bool super, bool data) { return (super ? 4 : 0) | (data ? 1 : 2); } void mmu_hardware_bus_error(uaecptr addr, uae_u32 v, bool read, bool ins, int size) { uae_u32 fc; if (ismoves) { fc = read ? regs.sfc : regs.dfc; } else { fc = (regs.s ? 4 : 0) | (ins ? 2 : 1); } mmu_bus_error(addr, v, fc, !read, size, 0, true); } bool mmu_is_super_access(bool read) { if (!ismoves) { return regs.s; } else { uae_u32 fc = read ? regs.sfc : regs.dfc; return (fc & 4) != 0; } } void mmu_bus_error(uaecptr addr, uae_u32 val, int fc, bool write, int size,uae_u32 status060, bool nonmmu) { if (currprefs.mmu_model == 68040) { uae_u16 ssw = 0; if (ismoves) { // MOVES special behavior int old_fc = fc = write ? regs.dfc : regs.sfc; if ((fc & 3) == 0 || (fc & 3) == 3) { ssw |= MMU_SSW_TT1; } else if (fc & 2) { fc = (fc & 4) | 1; } #if MMUDEBUGMISC > 0 write_log (_T("040 MMU MOVES fc=%d -> %d\n"), old_fc, fc); #endif } ssw |= fc & MMU_SSW_TM; /* TM = FC */ switch (size) { case sz_byte: ssw |= MMU_SSW_SIZE_B; break; case sz_word: ssw |= MMU_SSW_SIZE_W; break; case sz_long: ssw |= MMU_SSW_SIZE_L; break; } regs.wb3_status = write ? 0x80 | (ssw & 0x7f) : 0; regs.wb3_data = val; regs.wb2_status = 0; if (!write) ssw |= MMU_SSW_RW; if (size == 16) { // MOVE16 ssw |= MMU_SSW_SIZE_CL; ssw |= MMU_SSW_TT0; regs.mmu_effective_addr &= ~15; if (write) { // clear normal writeback if MOVE16 write regs.wb3_status &= ~0x80; // wb2 = cacheline size writeback regs.wb2_status = 0x80 | MMU_SSW_SIZE_CL | (ssw & 0x1f); regs.wb2_address = regs.mmu_effective_addr; write_log (_T("040 MMU MOVE16 WRITE FAULT!\n")); } } if (mmu040_movem) { ssw |= MMU_SSW_CM; regs.mmu_effective_addr = mmu040_movem_ea; mmu040_movem = 0; #if MMUDEBUGMISC > 0 write_log (_T("040 MMU_SSW_CM EA=%08X\n"), mmu040_movem_ea); #endif } if (locked_rmw_cycle) { ssw |= MMU_SSW_LK; ssw &= ~MMU_SSW_RW; #if MMUDEBUGMISC > 0 write_log (_T("040 MMU_SSW_LK!\n")); #endif } if (!nonmmu) ssw |= MMU_SSW_ATC; regs.mmu_ssw = ssw; #if MMUDEBUG > 0 write_log(_T("BF: fc=%d w=%d logical=%08x ssw=%04x PC=%08x INS=%04X\n"), fc, write, addr, ssw, m68k_getpc(), mmu_opcode); #endif } else { uae_u32 fslw = 0; fslw |= write ? MMU_FSLW_W : MMU_FSLW_R; fslw |= fc << 16; /* MMU_FSLW_TM */ switch (size) { case sz_byte: fslw |= MMU_FSLW_SIZE_B; break; case sz_word: fslw |= MMU_FSLW_SIZE_W; break; case sz_long: fslw |= MMU_FSLW_SIZE_L; break; case 16: // MOVE16 addr &= ~15; fslw |= MMU_FSLW_SIZE_D; fslw |= MMU_FSLW_TT_16; break; } if ((fc & 3) == 2) { // instruction faults always point to opcode address #if MMUDEBUGMISC > 0 write_log(_T("INS FAULT %08x %08x %d\n"), addr, regs.instruction_pc, mmu060_state); #endif addr = regs.instruction_pc; if (mmu060_state == 0) { fslw |= MMU_FSLW_IO; // opword fetch } else { fslw |= MMU_FSLW_IO | MMU_FSLW_MA; // extension word } } if (rmw_cycle) { fslw |= MMU_FSLW_W | MMU_FSLW_R; } if (locked_rmw_cycle) { fslw |= MMU_FSLW_LK; write_log (_T("060 MMU_FSLW_LK!\n")); } fslw |= status060; regs.mmu_fslw = fslw; #if MMUDEBUG > 0 write_log(_T("BF: fc=%d w=%d s=%d log=%08x ssw=%08x rmw=%d PC=%08x INS=%04X\n"), fc, write, 1 << size, addr, fslw, rmw_cycle, m68k_getpc(), mmu_opcode); #endif } ismoves = false; rmw_cycle = false; locked_rmw_cycle = false; regs.mmu_fault_addr = addr; #if 0 activate_debugger (); #endif cache_default_data &= ~CACHE_DISABLE_ALLOCATE; THROW(2); } /* * mmu access is a 4 step process: * if mmu is not enabled just read physical * check transparent region, if transparent, read physical * check ATC (address translation cache), read immediately if HIT * read from mmu with the long path (and allocate ATC entry if needed) */ /* check if an address matches a ttr */ static int mmu_do_match_ttr(uae_u32 ttr, uaecptr addr, bool super) { if (ttr & MMU_TTR_BIT_ENABLED) { /* TTR enabled */ uae_u8 msb, mask; msb = ((addr ^ ttr) & MMU_TTR_LOGICAL_BASE) >> 24; mask = (ttr & MMU_TTR_LOGICAL_MASK) >> 16; if (!(msb & ~mask)) { if ((ttr & MMU_TTR_BIT_SFIELD_ENABLED) == 0) { if (((ttr & MMU_TTR_BIT_SFIELD_SUPER) == 0) != (super == 0)) { return TTR_NO_MATCH; } } if (ttr & MMU_TTR_CACHE_DISABLE) { mmu_cache_state = CACHE_DISABLE_MMU; } else { mmu_cache_state = CACHE_ENABLE_ALL; if (ttr & MMU_TTR_CACHE_MODE) { mmu_cache_state |= CACHE_ENABLE_COPYBACK; } } return (ttr & MMU_TTR_BIT_WRITE_PROTECT) ? TTR_NO_WRITE : TTR_OK_MATCH; } } return TTR_NO_MATCH; } int mmu_match_ttr_ins(uaecptr addr, bool super) { int res; if (!mmu_ttr_enabled_ins) return TTR_NO_MATCH; res = mmu_do_match_ttr(regs.itt0, addr, super); if (res == TTR_NO_MATCH) res = mmu_do_match_ttr(regs.itt1, addr, super); return res; } int mmu_match_ttr(uaecptr addr, bool super, bool data) { int res; if (!mmu_ttr_enabled) return TTR_NO_MATCH; if (data) { res = mmu_do_match_ttr(regs.dtt0, addr, super); if (res == TTR_NO_MATCH) res = mmu_do_match_ttr(regs.dtt1, addr, super); } else { res = mmu_do_match_ttr(regs.itt0, addr, super); if (res == TTR_NO_MATCH) res = mmu_do_match_ttr(regs.itt1, addr, super); } return res; } void mmu_bus_error_ttr_write_fault(uaecptr addr, bool super, bool data, uae_u32 val, int size) { uae_u32 status = 0; if (currprefs.mmu_model == 68060) { status |= MMU_FSLW_TTR; } mmu_bus_error(addr, val, mmu_get_fc (super, data), true, size, status, false); } int mmu_match_ttr_write(uaecptr addr, bool super, bool data, uae_u32 val, int size) { int res = TTR_NO_MATCH; if (mmu_ttr_enabled) { res = mmu_match_ttr(addr, super, data); } if (res == TTR_NO_WRITE || (res == TTR_NO_MATCH && !regs.mmu_enabled && (regs.tcr & MMU_TCR_DWO))) mmu_bus_error_ttr_write_fault(addr, super, data, val, size); return res; } int mmu_match_ttr_maybe_write(uaecptr addr, bool super, bool data, int size, bool write) { int res = TTR_NO_MATCH; if (mmu_ttr_enabled) { res = mmu_match_ttr(addr, super, data); } if (write && ((res == TTR_NO_WRITE) || (res == TTR_NO_MATCH && !regs.mmu_enabled && (regs.tcr & MMU_TCR_DWO)))) mmu_bus_error_ttr_write_fault(addr, super, data, 0, size); return res; } // Descriptor read accesses can use data cache but never allocate new cache lines. static uae_u32 desc_get_long(uaecptr addr) { mmu_cache_state = ce_cachable[addr >>16] | CACHE_DISABLE_ALLOCATE; return x_phys_get_long(addr); } // Write accesses probably are always pushed to memomory static void desc_put_long(uaecptr addr, uae_u32 v) { mmu_cache_state = CACHE_DISABLE_MMU; x_phys_put_long(addr, v); } /* * Lookup the address by walking the page table and updating * the page descriptors accordingly. Returns the found descriptor * or produces a bus error. */ static uae_u32 mmu_fill_atc(uaecptr addr, bool super, uae_u32 tag, bool write, struct mmu_atc_line *l, uae_u32 *status060) { uae_u32 desc, desc_addr, wp; uae_u32 status = 0; int i; int old_s; // Use supervisor mode to access descriptors (really is fc = 7) old_s = regs.s; regs.s = 1; wp = 0; desc = super ? regs.srp : regs.urp; /* fetch root table descriptor */ i = (addr >> 23) & 0x1fc; desc_addr = (desc & MMU_ROOT_PTR_ADDR_MASK) | i; SAVE_EXCEPTION; TRY(prb) { desc = desc_get_long(desc_addr); if ((desc & 2) == 0) { #if MMUDEBUG > 1 write_log(_T("MMU: invalid root descriptor %s for %x desc at %x desc=%x\n"), super ? _T("srp"):_T("urp"), addr, desc_addr, desc); #endif *status060 |= MMU_FSLW_PTA; goto fail; } wp |= desc; if ((desc & MMU_DES_USED) == 0) { desc_put_long(desc_addr, desc | MMU_DES_USED); } /* fetch pointer table descriptor */ i = (addr >> 16) & 0x1fc; desc_addr = (desc & MMU_ROOT_PTR_ADDR_MASK) | i; desc = desc_get_long(desc_addr); if ((desc & 2) == 0) { #if MMUDEBUG > 1 write_log(_T("MMU: invalid ptr descriptor %s for %x desc at %x desc=%x\n"), super ? _T("srp"):_T("urp"), addr, desc_addr, desc); #endif *status060 |= MMU_FSLW_PTB; goto fail; } wp |= desc; if ((desc & MMU_DES_USED) == 0) desc_put_long(desc_addr, desc | MMU_DES_USED); /* fetch page table descriptor */ if (mmu_pagesize_8k) { i = (addr >> 11) & 0x7c; desc_addr = (desc & MMU_PTR_PAGE_ADDR_MASK_8) + i; } else { i = (addr >> 10) & 0xfc; desc_addr = (desc & MMU_PTR_PAGE_ADDR_MASK_4) + i; } desc = desc_get_long(desc_addr); if ((desc & 3) == 2) { /* indirect */ desc_addr = desc & MMU_PAGE_INDIRECT_MASK; desc = desc_get_long(desc_addr); } if ((desc & 1) == 1) { wp |= desc; if (write) { if ((wp & MMU_DES_WP) || ((desc & MMU_DES_SUPER) && !super)) { if ((desc & MMU_DES_USED) == 0) { desc |= MMU_DES_USED; desc_put_long(desc_addr, desc); } } else if ((desc & (MMU_DES_USED|MMU_DES_MODIFIED)) != (MMU_DES_USED|MMU_DES_MODIFIED)) { desc |= MMU_DES_USED|MMU_DES_MODIFIED; desc_put_long(desc_addr, desc); } } else { if ((desc & MMU_DES_USED) == 0) { desc |= MMU_DES_USED; desc_put_long(desc_addr, desc); } } desc |= wp & MMU_DES_WP; } else { if ((desc & 3) == 2) { *status060 |= MMU_FSLW_IL; #if MMUDEBUG > 1 write_log(_T("MMU: double indirect descriptor log=%0x desc=%08x @%08x\n"), addr, desc, desc_addr); #endif } else { *status060 |= MMU_FSLW_PF; #if MMUDEBUG > 2 write_log(_T("MMU: invalid page descriptor log=%0x desc=%08x @%08x\n"), addr, desc, desc_addr); #endif } fail: desc = 0; } /* this will cause a bus error exception */ if (!super && (desc & MMU_DES_SUPER)) { *status060 |= MMU_FSLW_SP; } else if (write && (desc & MMU_DES_WP)) { *status060 |= MMU_FSLW_WP; } // 68040 always creates ATC entry. 68060 only if valid descriptor was found. if (currprefs.mmu_model == 68040 || (desc & MMU_MMUSR_R)) { /* Create new ATC entry and return status */ l->status = desc & (MMU_MMUSR_G|MMU_MMUSR_Ux|MMU_MMUSR_S|MMU_MMUSR_CM|MMU_MMUSR_M|MMU_MMUSR_W|MMU_MMUSR_R); l->phys = desc & mmu_pagemaski; l->valid = 1; l->tag = tag; status = l->phys | l->status; } RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; /* bus error during table search */ if (currprefs.mmu_model == 68040) { l->status = 0; l->phys = 0; l->valid = 1; l->tag = tag; } status = MMU_MMUSR_B; *status060 |= MMU_FSLW_LK | MMU_FSLW_TWE; #if MMUDEBUG > 0 write_log(_T("MMU: bus error during table search.\n")); #endif } ENDTRY; // Restore original supervisor state regs.s = old_s; #if MMUDEBUG > 2 write_log(_T("translate: %x,%u,%u -> %x\n"), addr, super, write, desc); #endif flush_shortcut_cache(addr, super); return status; } static void mmu_add_cache(uaecptr addr, uaecptr phys, bool super, bool data, bool write) { if (!data) { #if MMU_IPAGECACHE uae_u32 laddr = (addr & mmu_pagemaski) | (super ? 1 : 0); atc_last_ins_laddr = laddr; atc_last_ins_paddr = phys; atc_last_ins_cache = mmu_cache_state; #else ; #endif #if MMU_DPAGECACHE } else { uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (write) { if (idx2 < MMUFASTCACHE_ENTRIES - 1) { atc_data_cache_write[idx2].log = idx1; atc_data_cache_write[idx2].phys = phys; atc_data_cache_write[idx2].cache_state = mmu_cache_state; } } else { if (idx2 < MMUFASTCACHE_ENTRIES - 1) { atc_data_cache_read[idx2].log = idx1; atc_data_cache_read[idx2].phys = phys; atc_data_cache_read[idx2].cache_state = mmu_cache_state; } } #endif } } uaecptr mmu_translate(uaecptr addr, uae_u32 val, bool super, bool data, bool write, int size) { int way, i, index, way_invalid; struct mmu_atc_line *l; uae_u32 status060 = 0; uae_u32 tag = ((super ? 0x80000000 : 0x00000000) | (addr >> 1)) & mmu_tagmask; if (mmu_pagesize_8k) index=(addr & 0x0001E000)>>13; else index=(addr & 0x0000F000)>>12; way_invalid = ATC_WAYS; way_random++; way = mmu_atc_ways[data]; for (i = 0; i < ATC_WAYS; i++) { // if we have this l = &mmu_atc_array[data][index][way]; if (l->valid) { if (tag == l->tag) { atc_retry: // check if we need to cause a page fault if (((l->status&(MMU_MMUSR_W|MMU_MMUSR_S|MMU_MMUSR_R))!=MMU_MMUSR_R)) { if (((l->status&MMU_MMUSR_W) && write) || ((l->status&MMU_MMUSR_S) && !super) || !(l->status&MMU_MMUSR_R)) { if ((l->status&MMU_MMUSR_S) && !super) status060 |= MMU_FSLW_SP; if ((l->status&MMU_MMUSR_W) && write) status060 |= MMU_FSLW_WP; mmu_bus_error(addr, val, mmu_get_fc(super, data), write, size, status060, false); return 0; // never reach, bus error longjumps out of the function } } // if first write to this page initiate table search to set M bit (but modify this slot) if (!(l->status&MMU_MMUSR_M) && write) { way_invalid = way; break; } // save way for next access (likely in same page) mmu_atc_ways[data] = way; if (l->status & MMU_MMUSR_CM_DISABLE) { mmu_cache_state = CACHE_DISABLE_MMU; } else { mmu_cache_state = CACHE_ENABLE_ALL; if (l->status & MMU_MMUSR_CM_MODE) { mmu_cache_state |= CACHE_ENABLE_COPYBACK; } } mmu_add_cache(addr, l->phys, super, data, write); // return translated addr return l->phys | (addr & mmu_pagemask); } else { way_invalid = way; } } way++; way %= ATC_WAYS; } // no entry found, we need to create a new one, first find an atc line to replace way_random %= ATC_WAYS; way = (way_invalid < ATC_WAYS) ? way_invalid : way_random; // then initiate table search and create a new entry l = &mmu_atc_array[data][index][way]; mmu_fill_atc(addr, super, tag, write, l, &status060); if (status060 && currprefs.mmu_model == 68060) { mmu_bus_error(addr, val, mmu_get_fc(super, data), write, size, status060, false); } // and retry the ATC search way_random++; goto atc_retry; } static void misalignednotfirst(uaecptr addr) { #if MMUDEBUGMISC > 0 write_log (_T("misalignednotfirst %08x -> %08x %08X\n"), regs.mmu_fault_addr, addr, regs.instruction_pc); #endif regs.mmu_fault_addr = addr; regs.mmu_fslw |= MMU_FSLW_MA; regs.mmu_ssw |= MMU_SSW_MA; } static void misalignednotfirstcheck(uaecptr addr) { if (regs.mmu_fault_addr == addr) return; misalignednotfirst (addr); } uae_u16 REGPARAM2 mmu_get_word_unaligned(uaecptr addr, bool data) { uae_u16 res; res = (uae_u16)mmu_get_byte(addr, data, sz_word) << 8; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_byte(addr + 1, data, sz_word); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY return res; } uae_u32 REGPARAM2 mmu_get_long_unaligned(uaecptr addr, bool data) { uae_u32 res; if (likely(!(addr & 1))) { res = (uae_u32)mmu_get_word(addr, data, sz_long) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_word(addr + 2, data, sz_long); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } else { res = (uae_u32)mmu_get_byte(addr, data, sz_long) << 8; SAVE_EXCEPTION; TRY(prb) { res = (res | mmu_get_byte(addr + 1, data, sz_long)) << 8; res = (res | mmu_get_byte(addr + 2, data, sz_long)) << 8; res |= mmu_get_byte(addr + 3, data, sz_long); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } return res; } uae_u32 REGPARAM2 mmu_get_ilong_unaligned(uaecptr addr) { uae_u32 res; res = (uae_u32)mmu_get_iword(addr, sz_long) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_iword(addr + 2, sz_long); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY return res; } static uae_u16 REGPARAM2 mmu_get_lrmw_word_unaligned(uaecptr addr) { uae_u16 res; res = (uae_u16)mmu_get_user_byte(addr, regs.s != 0, true, sz_word, true) << 8; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_user_byte(addr + 1, regs.s != 0, true, sz_word, true); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY return res; } static uae_u32 REGPARAM2 mmu_get_lrmw_long_unaligned(uaecptr addr) { uae_u32 res; if (likely(!(addr & 1))) { res = (uae_u32)mmu_get_user_word(addr, regs.s != 0, true, sz_long, true) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_user_word(addr + 2, regs.s != 0, true, sz_long, true); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } else { res = (uae_u32)mmu_get_user_byte(addr, regs.s != 0, true, sz_long, true) << 8; SAVE_EXCEPTION; TRY(prb) { res = (res | mmu_get_user_byte(addr + 1, regs.s != 0, true, sz_long, true)) << 8; res = (res | mmu_get_user_byte(addr + 2, regs.s != 0, true, sz_long, true)) << 8; res |= mmu_get_user_byte(addr + 3, regs.s != 0, true, sz_long, true); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } return res; } static void REGPARAM2 mmu_put_lrmw_long_unaligned(uaecptr addr, uae_u32 val) { SAVE_EXCEPTION; TRY(prb) { if (likely(!(addr & 1))) { mmu_put_user_word(addr, val >> 16, regs.s != 0, sz_long, true); mmu_put_user_word(addr + 2, val, regs.s != 0, sz_long, true); } else { mmu_put_user_byte(addr, val >> 24, regs.s != 0, sz_long, true); mmu_put_user_byte(addr + 1, val >> 16, regs.s != 0, sz_long, true); mmu_put_user_byte(addr + 2, val >> 8, regs.s != 0, sz_long, true); mmu_put_user_byte(addr + 3, val, regs.s != 0, sz_long, true); } RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; misalignednotfirstcheck(addr); THROW_AGAIN(prb); } ENDTRY } static void REGPARAM2 mmu_put_lrmw_word_unaligned(uaecptr addr, uae_u16 val) { SAVE_EXCEPTION; TRY(prb) { mmu_put_user_byte(addr, val >> 8, regs.s != 0, sz_word, true); mmu_put_user_byte(addr + 1, (uae_u8)val, regs.s != 0, sz_word, true); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; misalignednotfirstcheck(addr); THROW_AGAIN(prb); } ENDTRY } void REGPARAM2 mmu_put_long_unaligned(uaecptr addr, uae_u32 val, bool data) { SAVE_EXCEPTION; TRY(prb) { if (likely(!(addr & 1))) { mmu_put_word(addr, val >> 16, data, sz_long); mmu_put_word(addr + 2, val, data, sz_long); } else { mmu_put_byte(addr, val >> 24, data, sz_long); mmu_put_byte(addr + 1, val >> 16, data, sz_long); mmu_put_byte(addr + 2, val >> 8, data, sz_long); mmu_put_byte(addr + 3, val, data, sz_long); } RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; misalignednotfirstcheck(addr); THROW_AGAIN(prb); } ENDTRY } void REGPARAM2 mmu_put_word_unaligned(uaecptr addr, uae_u16 val, bool data) { SAVE_EXCEPTION; TRY(prb) { mmu_put_byte(addr, val >> 8, data, sz_word); mmu_put_byte(addr + 1, (uae_u8)val, data, sz_word); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; misalignednotfirstcheck(addr); THROW_AGAIN(prb); } ENDTRY } uae_u32 REGPARAM2 sfc_get_long(uaecptr addr) { bool super = (regs.sfc & 4) != 0; uae_u32 res; ismoves = true; if (likely(!is_unaligned_page(addr, 4))) { res = mmu_get_user_long(addr, super, false, sz_long, false); } else { if (likely(!(addr & 1))) { res = (uae_u32)mmu_get_user_word(addr, super, false, sz_long, false) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_user_word(addr + 2, super, false, sz_long, false); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } else { res = (uae_u32)mmu_get_user_byte(addr, super, false, sz_long, false) << 8; SAVE_EXCEPTION; TRY(prb) { res = (res | mmu_get_user_byte(addr + 1, super, false, sz_long, false)) << 8; res = (res | mmu_get_user_byte(addr + 2, super, false, sz_long, false)) << 8; res |= mmu_get_user_byte(addr + 3, super, false, sz_long, false); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } } ismoves = false; return res; } uae_u16 REGPARAM2 sfc_get_word(uaecptr addr) { bool super = (regs.sfc & 4) != 0; uae_u16 res; ismoves = true; if (likely(!is_unaligned_page(addr, 2))) { res = mmu_get_user_word(addr, super, false, sz_word, false); } else { res = (uae_u16)mmu_get_user_byte(addr, super, false, sz_word, false) << 8; SAVE_EXCEPTION; TRY(prb) { res |= mmu_get_user_byte(addr + 1, super, false, sz_word, false); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; misalignednotfirst(addr); THROW_AGAIN(prb); } ENDTRY } ismoves = false; return res; } uae_u8 REGPARAM2 sfc_get_byte(uaecptr addr) { bool super = (regs.sfc & 4) != 0; uae_u8 res; ismoves = true; res = mmu_get_user_byte(addr, super, false, sz_byte, false); ismoves = false; return res; } void REGPARAM2 dfc_put_long(uaecptr addr, uae_u32 val) { bool super = (regs.dfc & 4) != 0; ismoves = true; SAVE_EXCEPTION; TRY(prb) { if (likely(!is_unaligned_page(addr, 4))) { mmu_put_user_long(addr, val, super, sz_long, false); } else if (likely(!(addr & 1))) { mmu_put_user_word(addr, val >> 16, super, sz_long, false); mmu_put_user_word(addr + 2, val, super, sz_long, false); } else { mmu_put_user_byte(addr, val >> 24, super, sz_long, false); mmu_put_user_byte(addr + 1, val >> 16, super, sz_long, false); mmu_put_user_byte(addr + 2, val >> 8, super, sz_long, false); mmu_put_user_byte(addr + 3, val, super, sz_long, false); } RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; misalignednotfirstcheck(addr); THROW_AGAIN(prb); } ENDTRY ismoves = false; } void REGPARAM2 dfc_put_word(uaecptr addr, uae_u16 val) { bool super = (regs.dfc & 4) != 0; ismoves = true; SAVE_EXCEPTION; TRY(prb) { if (likely(!is_unaligned_page(addr, 2))) { mmu_put_user_word(addr, val, super, sz_word, false); } else { mmu_put_user_byte(addr, val >> 8, super, sz_word, false); mmu_put_user_byte(addr + 1, (uae_u8)val, super, sz_word, false); } RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; misalignednotfirstcheck(addr); THROW_AGAIN(prb); } ENDTRY ismoves = false; } void REGPARAM2 dfc_put_byte(uaecptr addr, uae_u8 val) { bool super = (regs.dfc & 4) != 0; ismoves = true; SAVE_EXCEPTION; TRY(prb) { mmu_put_user_byte(addr, val, super, sz_byte, false); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; THROW_AGAIN(prb); } ENDTRY ismoves = false; } void mmu_get_move16(uaecptr addr, uae_u32 *v, bool data, int size) { bool super = regs.s != 0; addr &= ~15; if ((!mmu_ttr_enabled || mmu_match_ttr(addr,super,data) == TTR_NO_MATCH) && regs.mmu_enabled) { addr = mmu_translate(addr, 0, super, data, false, size); } // MOVE16 read and cache miss: do not allocate new cache line mmu_cache_state |= CACHE_DISABLE_ALLOCATE; for (int i = 0; i < 4; i++) { v[i] = x_phys_get_long(addr + i * 4); } } void mmu_put_move16(uaecptr addr, uae_u32 *val, bool data, int size) { bool super = regs.s != 0; addr &= ~15; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,super,data,val[0],size) == TTR_NO_MATCH) && regs.mmu_enabled) { addr = mmu_translate(addr, val[0], super, data, true, size); } // MOVE16 write invalidates existing line and also does not allocate new cache lines. mmu_cache_state = CACHE_DISABLE_MMU; for (int i = 0; i < 4; i++) { x_phys_put_long(addr + i * 4, val[i]); } } void REGPARAM2 mmu_op_real(uae_u32 opcode, uae_u16 extra) { bool super = (regs.dfc & 4) != 0; DUNUSED(extra); if ((opcode & 0xFE0) == 0x0500) { // PFLUSH bool glob; int regno; //D(didflush = 0); uae_u32 addr; /* PFLUSH */ regno = opcode & 7; glob = (opcode & 8) != 0; if (opcode & 16) { #if MMUINSDEBUG > 1 write_log(_T("pflusha(%u,%u) PC=%08x\n"), glob, regs.dfc, m68k_getpc ()); #endif mmu_flush_atc_all(glob); } else { addr = m68k_areg(regs, regno); #if MMUINSDEBUG > 1 write_log(_T("pflush(%u,%u,%x) PC=%08x\n"), glob, regs.dfc, addr, m68k_getpc ()); #endif mmu_flush_atc(addr, super, glob); } flush_internals(); #ifdef USE_JIT flush_icache(0); #endif } else if ((opcode & 0x0FD8) == 0x0548) { // PTEST (68040) bool write; int regno; uae_u32 addr; uae_u32 status060 = 0; regno = opcode & 7; write = (opcode & 32) == 0; addr = m68k_areg(regs, regno); #if MMUINSDEBUG > 0 write_log(_T("PTEST%c (A%d) %08x DFC=%d\n"), write ? 'W' : 'R', regno, addr, regs.dfc); #endif mmu_flush_atc(addr, super, true); bool data = (regs.dfc & 3) != 2; int ttr_match = mmu_match_ttr(addr,super,data); if (ttr_match != TTR_NO_MATCH) { if (ttr_match == TTR_NO_WRITE && write) { regs.mmusr = MMU_MMUSR_B; } else { regs.mmusr = MMU_MMUSR_T | MMU_MMUSR_R; } } else if (!currprefs.mmu_ec) { int way; uae_u32 index; uae_u32 tag = ((super ? 0x80000000 : 0x00000000) | (addr >> 1)) & mmu_tagmask; if (mmu_pagesize_8k) index=(addr & 0x0001E000)>>13; else index=(addr & 0x0000F000)>>12; for (way = 0; way < ATC_WAYS; way++) { if (!mmu_atc_array[data][index][way].valid) break; } if (way >= ATC_WAYS) { way = way_random % ATC_WAYS; } regs.mmusr = mmu_fill_atc(addr, super, tag, write, &mmu_atc_array[data][index][way], &status060); #if MMUINSDEBUG > 0 write_log(_T("PTEST result: mmusr %08x\n"), regs.mmusr); #endif } } else if ((opcode & 0xFFB8) == 0xF588) { // PLPA (68060) int write = (opcode & 0x40) == 0; int regno = opcode & 7; uae_u32 addr = m68k_areg (regs, regno); bool data = (regs.dfc & 3) != 2; int ttr; #if MMUINSDEBUG > 0 write_log(_T("PLPA%c param: %08x\n"), write ? 'W' : 'R', addr); #endif if (write) ttr = mmu_match_ttr_write(addr, super, data, 0, 1); else ttr = mmu_match_ttr(addr,super,data); if (ttr == TTR_NO_MATCH) { if (!currprefs.mmu_ec) { m68k_areg (regs, regno) = mmu_translate(addr, 0, super, data, write, 1); } } #if MMUINSDEBUG > 0 write_log(_T("PLPA%c result: %08x\n"), write ? 'W' : 'R', m68k_areg (regs, regno)); #endif } else { op_illg (opcode); } } // fixme : global parameter? void REGPARAM2 mmu_flush_atc(uaecptr addr, bool super, bool global) { int way,type,index; uaecptr tag = ((super ? 0x80000000 : 0) | (addr >> 1)) & mmu_tagmask; if (mmu_pagesize_8k) index=(addr & 0x0001E000)>>13; else index=(addr & 0x0000F000)>>12; for (type=0;typestatus & MMU_MMUSR_G)) continue; // if we have this if (tag == l->tag && l->valid) { l->valid=false; } } } flush_shortcut_cache(addr, super); mmu_flush_cache(); } void REGPARAM2 mmu_flush_atc_all(bool global) { int way,slot,type; for (type=0;typestatus&MMU_MMUSR_G)) continue; l->valid=false; } } } flush_shortcut_cache(0xffffffff, 0); mmu_flush_cache(); } void REGPARAM2 mmu_set_funcs(void) { if (currprefs.mmu_model != 68040 && currprefs.mmu_model != 68060) return; x_phys_get_iword = phys_get_word; x_phys_get_ilong = phys_get_long; x_phys_get_byte = phys_get_byte; x_phys_get_word = phys_get_word; x_phys_get_long = phys_get_long; x_phys_put_byte = phys_put_byte; x_phys_put_word = phys_put_word; x_phys_put_long = phys_put_long; if (currprefs.cpu_memory_cycle_exact || currprefs.cpu_compatible) { x_phys_get_iword = get_word_icache040; x_phys_get_ilong = get_long_icache040; if (currprefs.cpu_data_cache) { x_phys_get_byte = get_byte_cache_040; x_phys_get_word = get_word_cache_040; x_phys_get_long = get_long_cache_040; x_phys_put_byte = put_byte_cache_040; x_phys_put_word = put_word_cache_040; x_phys_put_long = put_long_cache_040; } else if (currprefs.cpu_memory_cycle_exact) { x_phys_get_byte = mem_access_delay_byte_read_c040; x_phys_get_word = mem_access_delay_word_read_c040; x_phys_get_long = mem_access_delay_long_read_c040; x_phys_put_byte = mem_access_delay_byte_write_c040; x_phys_put_word = mem_access_delay_word_write_c040; x_phys_put_long = mem_access_delay_long_write_c040; } } } void REGPARAM2 mmu_reset(void) { mmu_flush_atc_all(true); mmu_set_funcs(); } uae_u16 REGPARAM2 mmu_set_tc(uae_u16 tc) { if (currprefs.mmu_ec) { tc &= ~(0x8000 | 0x4000); // at least 68EC040 always returns zero when TC is read. if (currprefs.cpu_model == 68040) tc = 0; } regs.mmu_enabled = (tc & 0x8000) != 0; mmu_pagesize_8k = (tc & 0x4000) != 0; mmu_tagmask = mmu_pagesize_8k ? 0xFFFF0000 : 0xFFFF8000; mmu_pagemask = mmu_pagesize_8k ? 0x00001FFF : 0x00000FFF; mmu_pagemaski = ~mmu_pagemask; regs.mmu_page_size = mmu_pagesize_8k ? 8192 : 4096; mmu_pageshift = mmu_pagesize_8k ? 13 : 12; mmu_pageshift1m = mmu_pageshift - 1; cache_default_ins = CACHE_ENABLE_ALL; cache_default_data = CACHE_ENABLE_ALL; if (currprefs.mmu_model == 68060) { int dc = (tc >> 3) & 3; cache_default_ins = 0; if (!(dc & 2)) cache_default_ins = CACHE_ENABLE_ALL; dc = (tc >> 8) & 3; cache_default_data = 0; if (!(dc & 2)) cache_default_data = (dc & 1) ? CACHE_ENABLE_COPYBACK | CACHE_ENABLE_ALL : CACHE_ENABLE_ALL; } mmu_flush_atc_all(true); write_log(_T("%d MMU: TC=%04x enabled=%d page8k=%d PC=%08x\n"), currprefs.mmu_model, tc, regs.mmu_enabled, mmu_pagesize_8k, m68k_getpc()); return tc; } void REGPARAM2 mmu_set_super(bool super) { mmu_is_super = super ? 0x80000000 : 0; } void REGPARAM2 mmu_flush_cache(void) { if (!currprefs.mmu_model) return; #if MMU_ICACHE int len = sizeof(mmu_icache_data); memset(&mmu_icache_data, 0xff, sizeof(mmu_icache_data)); #endif } void m68k_do_rte_mmu040 (uaecptr a7) { uae_u16 ssr = get_word_mmu040 (a7 + 8 + 4); if (ssr & MMU_SSW_CT) { uaecptr src_a7 = a7 + 8 - 8; uaecptr dst_a7 = a7 + 8 + 52; put_word_mmu040 (dst_a7 + 0, get_word_mmu040 (src_a7 + 0)); put_long_mmu040 (dst_a7 + 2, get_long_mmu040 (src_a7 + 2)); // skip this word put_long_mmu040 (dst_a7 + 8, get_long_mmu040 (src_a7 + 8)); } if (ssr & MMU_SSW_CM) { mmu040_movem = 1; mmu040_movem_ea = get_long_mmu040 (a7 + 8); #if MMUDEBUGMISC > 0 write_log (_T("MMU restarted MOVEM EA=%08X\n"), mmu040_movem_ea); #endif } if (mmu_restart) { set_special(SPCFLAG_MMURESTART); } } void m68k_do_rte_mmu060 (uaecptr a7) { #if 0 mmu060_state = 2; #endif set_special(SPCFLAG_MMURESTART); } void flush_mmu040 (uaecptr addr, int n) { mmu_flush_cache(); } void m68k_do_rts_mmu040 (void) { uaecptr stack = m68k_areg (regs, 7); uaecptr newpc = get_long_mmu040 (stack); m68k_areg (regs, 7) += 4; m68k_setpc (newpc); } void m68k_do_bsr_mmu040 (uaecptr oldpc, uae_s32 offset) { uaecptr newstack = m68k_areg (regs, 7) - 4; put_long_mmu040 (newstack, oldpc); m68k_areg (regs, 7) -= 4; m68k_incpci (offset); } void flush_mmu060 (uaecptr addr, int n) { mmu_flush_cache(); } void m68k_do_rts_mmu060 (void) { uaecptr stack = m68k_areg (regs, 7); uaecptr newpc = get_long_mmu060 (stack); m68k_areg (regs, 7) += 4; m68k_setpc (newpc); } void m68k_do_bsr_mmu060 (uaecptr oldpc, uae_s32 offset) { uaecptr newstack = m68k_areg (regs, 7) - 4; put_long_mmu060 (newstack, oldpc); m68k_areg (regs, 7) -= 4; m68k_incpci (offset); } void uae_mmu_put_lrmw (uaecptr addr, uae_u32 v, int size, int type) { locked_rmw_cycle = true; if (size == sz_byte) { mmu_put_user_byte(addr, v, regs.s, sz_byte, true); } else if (size == sz_word) { if (unlikely(is_unaligned_page(addr, 2))) { mmu_put_lrmw_word_unaligned(addr, v); } else { mmu_put_user_word(addr, v, regs.s != 0, sz_word, true); } } else { if (unlikely(is_unaligned_page(addr, 4))) mmu_put_lrmw_long_unaligned(addr, v); else mmu_put_user_long(addr, v, regs.s, sz_long, true); } locked_rmw_cycle = false; } uae_u32 uae_mmu_get_lrmw (uaecptr addr, int size, int type) { uae_u32 v; locked_rmw_cycle = true; if (size == sz_byte) { v = mmu_get_user_byte(addr, regs.s != 0, true, sz_byte, true); } else if (size == sz_word) { if (unlikely(is_unaligned_page(addr, 2))) { v = mmu_get_lrmw_word_unaligned(addr); } else { v = mmu_get_user_word(addr, regs.s != 0, true, sz_word, true); } } else { if (unlikely(is_unaligned_page(addr, 4))) v = mmu_get_lrmw_long_unaligned(addr); else v = mmu_get_user_long(addr, regs.s != 0, true, sz_long, true); } locked_rmw_cycle = false; return v; } uae_u32 REGPARAM2 mmu060_get_rmw_bitfield (uae_u32 src, uae_u32 bdata[2], uae_s32 offset, int width) { uae_u32 tmp1, tmp2, res, mask; offset &= 7; mask = 0xffffffffu << (32 - width); switch ((offset + width + 7) >> 3) { case 1: tmp1 = get_rmw_byte_mmu060 (src); res = tmp1 << (24 + offset); bdata[0] = tmp1 & ~(mask >> (24 + offset)); break; case 2: tmp1 = get_rmw_word_mmu060 (src); res = tmp1 << (16 + offset); bdata[0] = tmp1 & ~(mask >> (16 + offset)); break; case 3: tmp1 = get_rmw_word_mmu060 (src); tmp2 = get_rmw_byte_mmu060 (src + 2); res = tmp1 << (16 + offset); bdata[0] = tmp1 & ~(mask >> (16 + offset)); res |= tmp2 << (8 + offset); bdata[1] = tmp2 & ~(mask >> (8 + offset)); break; case 4: tmp1 = get_rmw_long_mmu060 (src); res = tmp1 << offset; bdata[0] = tmp1 & ~(mask >> offset); break; case 5: tmp1 = get_rmw_long_mmu060 (src); tmp2 = get_rmw_byte_mmu060 (src + 4); res = tmp1 << offset; bdata[0] = tmp1 & ~(mask >> offset); res |= tmp2 >> (8 - offset); bdata[1] = tmp2 & ~(mask << (8 - offset)); break; default: /* Panic? */ write_log (_T("x_get_bitfield() can't happen %d\n"), (offset + width + 7) >> 3); res = 0; break; } return res; } void REGPARAM2 mmu060_put_rmw_bitfield (uae_u32 dst, uae_u32 bdata[2], uae_u32 val, uae_s32 offset, int width) { offset = (offset & 7) + width; switch ((offset + 7) >> 3) { case 1: put_rmw_byte_mmu060 (dst, bdata[0] | (val << (8 - offset))); break; case 2: put_rmw_word_mmu060 (dst, bdata[0] | (val << (16 - offset))); break; case 3: put_rmw_word_mmu060 (dst, bdata[0] | (val >> (offset - 16))); put_rmw_byte_mmu060 (dst + 2, bdata[1] | (val << (24 - offset))); break; case 4: put_rmw_long_mmu060 (dst, bdata[0] | (val << (32 - offset))); break; case 5: put_rmw_long_mmu060 (dst, bdata[0] | (val >> (offset - 32))); put_rmw_byte_mmu060 (dst + 4, bdata[1] | (val << (40 - offset))); break; default: write_log (_T("x_put_bitfield() can't happen %d\n"), (offset + 7) >> 3); break; } } #ifndef __cplusplus jmp_buf __exbuf; int __exvalue; #define MAX_TRY_STACK 256 static int s_try_stack_size=0; static jmp_buf s_try_stack[MAX_TRY_STACK]; jmp_buf* __poptry(void) { if (s_try_stack_size>0) { s_try_stack_size--; if (s_try_stack_size == 0) return NULL; memcpy(&__exbuf,&s_try_stack[s_try_stack_size-1],sizeof(jmp_buf)); // fprintf(stderr,"pop jmpbuf=%08x\n",s_try_stack[s_try_stack_size][0]); return &s_try_stack[s_try_stack_size-1]; } else { fprintf(stderr,"try stack underflow...\n"); // return (NULL); abort(); } } void __pushtry(jmp_buf* j) { if (s_try_stack_size0); } #endif #else void mmu_op(uae_u32 opcode, uae_u16 /*extra*/) { if ((opcode & 0xFE0) == 0x0500) { /* PFLUSH instruction */ flush_internals(); } else if ((opcode & 0x0FD8) == 0x548) { /* PTEST instruction */ } else op_illg(opcode); } #endif /* vim:ts=4:sw=4: */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/cpummu.h000066400000000000000000000725011504763705000236030ustar00rootroot00000000000000/* * cpummu.h - MMU emulation * * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by UAE MMU patch * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * ARAnyM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ARAnyM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ARAnyM; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #ifndef UAE_CPUMMU_H #define UAE_CPUMMU_H #include "uae/types.h" #define MMU_ICACHE 0 #define MMU_IPAGECACHE 1 #define MMU_DPAGECACHE 1 #define CACHE_HIT_COUNT 0 #include "mmu_common.h" #ifndef FULLMMU #define FULLMMU #endif #define DUNUSED(x) #define D #ifndef bug #if DEBUG #define bug write_log #else #define bug #endif #endif static __inline void flush_internals (void) { } extern int mmu060_state; extern int mmu040_movem; extern uaecptr mmu040_movem_ea; extern uae_u32 mmu040_move16[4]; extern bool mmu_pagesize_8k; extern int mmu_pageshift, mmu_pageshift1m; extern uae_u16 mmu_opcode; extern bool mmu_restart; extern bool mmu_ttr_enabled, mmu_ttr_enabled_ins, mmu_ttr_enabled_data; extern bool rmw_cycle; extern uae_u8 mmu_cache_state; extern uae_u8 cache_default_ins, cache_default_data; extern void mmu_dump_tables(void); #define MMU_TTR_LOGICAL_BASE 0xff000000 #define MMU_TTR_LOGICAL_MASK 0x00ff0000 #define MMU_TTR_BIT_ENABLED (1 << 15) #define MMU_TTR_BIT_SFIELD_ENABLED (1 << 14) #define MMU_TTR_BIT_SFIELD_SUPER (1 << 13) #define MMU_TTR_SFIELD_SHIFT 13 #define MMU_TTR_UX_MASK ((1 << 9) | (1 << 8)) #define MMU_TTR_UX_SHIFT 8 #define MMU_TTR_CACHE_MASK ((1 << 6) | (1 << 5)) #define MMU_TTR_CACHE_SHIFT 5 #define MMU_TTR_CACHE_DISABLE (1 << 6) #define MMU_TTR_CACHE_MODE (1 << 5) #define MMU_TTR_BIT_WRITE_PROTECT (1 << 2) #define MMU_UDT_MASK 3 #define MMU_PDT_MASK 3 #define MMU_DES_WP 4 #define MMU_DES_USED 8 /* page descriptors only */ #define MMU_DES_MODIFIED 16 #define MMU_DES_SUPER (1 << 7) #define MMU_DES_GLOBAL (1 << 10) #define MMU_ROOT_PTR_ADDR_MASK 0xfffffe00 #define MMU_PTR_PAGE_ADDR_MASK_8 0xffffff80 #define MMU_PTR_PAGE_ADDR_MASK_4 0xffffff00 #define MMU_PAGE_INDIRECT_MASK 0xfffffffc #define MMU_PAGE_ADDR_MASK_8 0xffffe000 #define MMU_PAGE_ADDR_MASK_4 0xfffff000 #define MMU_PAGE_UR_MASK_8 ((1 << 12) | (1 << 11)) #define MMU_PAGE_UR_MASK_4 (1 << 11) #define MMU_PAGE_UR_SHIFT 11 #define MMU_MMUSR_ADDR_MASK 0xfffff000 #define MMU_MMUSR_B (1 << 11) #define MMU_MMUSR_G (1 << 10) #define MMU_MMUSR_U1 (1 << 9) #define MMU_MMUSR_U0 (1 << 8) #define MMU_MMUSR_Ux (MMU_MMUSR_U1 | MMU_MMUSR_U0) #define MMU_MMUSR_S (1 << 7) #define MMU_MMUSR_CM ((1 << 6) | ( 1 << 5)) #define MMU_MMUSR_CM_DISABLE (1 << 6) #define MMU_MMUSR_CM_MODE (1 << 5) #define MMU_MMUSR_M (1 << 4) #define MMU_MMUSR_W (1 << 2) #define MMU_MMUSR_T (1 << 1) #define MMU_MMUSR_R (1 << 0) // 68040 and 68060 #define MMU_TCR_E 0x8000 #define MMU_TCR_P 0x4000 // 68060 only #define MMU_TCR_NAD 0x2000 #define MMU_TCR_NAI 0x1000 #define MMU_TCR_FOTC 0x0800 #define MMU_TCR_FITC 0x0400 #define MMU_TCR_DCO1 0x0200 #define MMU_TCR_DCO0 0x0100 #define MMU_TCR_DUO1 0x0080 #define MMU_TCR_DUO0 0x0040 #define MMU_TCR_DWO 0x0020 #define MMU_TCR_DCI1 0x0010 #define MMU_TCR_DCI0 0x0008 #define MMU_TCR_DUI1 0x0004 #define MMU_TCR_DUI0 0x0002 #define TTR_I0 4 #define TTR_I1 5 #define TTR_D0 6 #define TTR_D1 7 #define TTR_NO_MATCH 0 #define TTR_NO_WRITE 1 #define TTR_OK_MATCH 2 struct mmu_atc_line { uaecptr tag; // tag is 16 or 17 bits S+logical uae_u32 valid; uae_u32 status; uaecptr phys; // phys base address }; /* * 68040 ATC is a 4 way 16 slot associative address translation cache * the 68040 has a DATA and an INSTRUCTION ATC. * an ATC lookup may result in : a hit, a miss and a modified state. * the 68060 can disable ATC allocation * we must take care of 8k and 4k page size, index position is relative to page size */ #define ATC_WAYS 4 #define ATC_SLOTS 16 #define ATC_TYPE 2 extern uae_u32 mmu_is_super; extern uae_u32 mmu_tagmask, mmu_pagemask, mmu_pagemaski; extern struct mmu_atc_line mmu_atc_array[ATC_TYPE][ATC_SLOTS][ATC_WAYS]; extern void mmu_tt_modified(void); extern int mmu_match_ttr_ins(uaecptr addr, bool super); extern int mmu_match_ttr(uaecptr addr, bool super, bool data); extern void mmu_bus_error_ttr_write_fault(uaecptr addr, bool super, bool data, uae_u32 val, int size); extern int mmu_match_ttr_write(uaecptr addr, bool super, bool data, uae_u32 val, int size); extern int mmu_match_ttr_maybe_write(uaecptr addr, bool super, bool data, int size, bool write); extern uaecptr mmu_translate(uaecptr addr, uae_u32 val, bool super, bool data, bool write, int size); extern void mmu_hardware_bus_error(uaecptr addr, uae_u32 v, bool read, bool ins, int size); extern bool mmu_is_super_access(bool read); extern uae_u32 REGPARAM3 mmu060_get_rmw_bitfield (uae_u32 src, uae_u32 bdata[2], uae_s32 offset, int width) REGPARAM; extern void REGPARAM3 mmu060_put_rmw_bitfield (uae_u32 dst, uae_u32 bdata[2], uae_u32 val, uae_s32 offset, int width) REGPARAM; extern uae_u16 REGPARAM3 mmu_get_word_unaligned(uaecptr addr, bool data) REGPARAM; extern uae_u32 REGPARAM3 mmu_get_long_unaligned(uaecptr addr, bool data) REGPARAM; extern uae_u32 REGPARAM3 mmu_get_ilong_unaligned(uaecptr addr) REGPARAM; extern void REGPARAM3 mmu_put_word_unaligned(uaecptr addr, uae_u16 val, bool data) REGPARAM; extern void REGPARAM3 mmu_put_long_unaligned(uaecptr addr, uae_u32 val, bool data) REGPARAM; extern void mmu_make_transparent_region(uaecptr baseaddr, uae_u32 size, int datamode); #define FC_DATA (regs.s ? 5 : 1) #define FC_INST (regs.s ? 6 : 2) extern void mmu_bus_error(uaecptr addr, uae_u32 val, int fc, bool write, int size, uae_u32 status, bool nonmmu); extern uae_u32 REGPARAM3 sfc_get_long(uaecptr addr) REGPARAM; extern uae_u16 REGPARAM3 sfc_get_word(uaecptr addr) REGPARAM; extern uae_u8 REGPARAM3 sfc_get_byte(uaecptr addr) REGPARAM; extern void REGPARAM3 dfc_put_long(uaecptr addr, uae_u32 val) REGPARAM; extern void REGPARAM3 dfc_put_word(uaecptr addr, uae_u16 val) REGPARAM; extern void REGPARAM3 dfc_put_byte(uaecptr addr, uae_u8 val) REGPARAM; #define sfc040_get_long sfc_get_long #define sfc040_get_word sfc_get_word #define sfc040_get_byte sfc_get_byte #define dfc040_put_long dfc_put_long #define dfc040_put_word dfc_put_word #define dfc040_put_byte dfc_put_byte #define sfc060_get_long sfc_get_long #define sfc060_get_word sfc_get_word #define sfc060_get_byte sfc_get_byte #define dfc060_put_long dfc_put_long #define dfc060_put_word dfc_put_word #define dfc060_put_byte dfc_put_byte extern void uae_mmu_put_lrmw (uaecptr addr, uae_u32 v, int size, int type); extern uae_u32 uae_mmu_get_lrmw (uaecptr addr, int size, int type); extern void REGPARAM3 mmu_flush_atc(uaecptr addr, bool super, bool global) REGPARAM; extern void REGPARAM3 mmu_flush_atc_all(bool global) REGPARAM; extern void REGPARAM3 mmu_op_real(uae_u32 opcode, uae_u16 extra) REGPARAM; extern void REGPARAM3 mmu_reset(void) REGPARAM; extern void REGPARAM3 mmu_set_funcs(void) REGPARAM; extern uae_u16 REGPARAM3 mmu_set_tc(uae_u16 tc) REGPARAM; extern void REGPARAM3 mmu_set_super(bool super) REGPARAM; extern void REGPARAM3 mmu_flush_cache(void) REGPARAM; static ALWAYS_INLINE uaecptr mmu_get_real_address(uaecptr addr, struct mmu_atc_line *cl) { return cl->phys | (addr & mmu_pagemask); } extern void mmu_get_move16(uaecptr addr, uae_u32 *v, bool data, int size); extern void mmu_put_move16(uaecptr addr, uae_u32 *val, bool data, int size); #if MMU_IPAGECACHE extern uae_u32 atc_last_ins_laddr, atc_last_ins_paddr; extern uae_u8 atc_last_ins_cache; #endif #if MMU_DPAGECACHE #define MMUFASTCACHE_ENTRIES 256 struct mmufastcache { uae_u32 log; uae_u32 phys; uae_u8 cache_state; }; extern struct mmufastcache atc_data_cache_read[MMUFASTCACHE_ENTRIES]; extern struct mmufastcache atc_data_cache_write[MMUFASTCACHE_ENTRIES]; #endif #if CACHE_HIT_COUNT extern int mmu_ins_hit, mmu_ins_miss; extern int mmu_data_read_hit, mmu_data_read_miss; extern int mmu_data_write_hit, mmu_data_write_miss; #endif static ALWAYS_INLINE uae_u32 mmu_get_ilong(uaecptr addr, int size) { mmu_cache_state = cache_default_ins; if ((!mmu_ttr_enabled_ins || mmu_match_ttr_ins(addr,regs.s!=0) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_IPAGECACHE if (((addr & mmu_pagemaski) | regs.s) == atc_last_ins_laddr) { #if CACHE_HIT_COUNT mmu_ins_hit++; #endif addr = atc_last_ins_paddr | (addr & mmu_pagemask); mmu_cache_state = atc_last_ins_cache; } else { #if CACHE_HIT_COUNT mmu_ins_miss++; #endif #endif addr = mmu_translate(addr, 0, regs.s!=0, false, false, size); #if MMU_IPAGECACHE } #endif } return x_phys_get_ilong(addr); } static ALWAYS_INLINE uae_u16 mmu_get_iword(uaecptr addr, int size) { mmu_cache_state = cache_default_ins; if ((!mmu_ttr_enabled_ins || mmu_match_ttr_ins(addr,regs.s!=0) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_IPAGECACHE if (((addr & mmu_pagemaski) | regs.s) == atc_last_ins_laddr) { #if CACHE_HIT_COUNT mmu_ins_hit++; #endif addr = atc_last_ins_paddr | (addr & mmu_pagemask); mmu_cache_state = atc_last_ins_cache; } else { #if CACHE_HIT_COUNT mmu_ins_miss++; #endif #endif addr = mmu_translate(addr, 0, regs.s!=0, false, false, size); #if MMU_IPAGECACHE } #endif } return x_phys_get_iword(addr); } static ALWAYS_INLINE uae_u32 mmu_get_long(uaecptr addr, bool data, int size) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr(addr,regs.s!=0,data) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | regs.s; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_read_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_read_miss++; #endif #endif addr = mmu_translate(addr, 0, regs.s!=0, data, false, size); #if MMU_DPAGECACHE } #endif } return x_phys_get_long(addr); } static ALWAYS_INLINE uae_u16 mmu_get_word(uaecptr addr, bool data, int size) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr(addr,regs.s!=0,data) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | regs.s; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_read_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_read_miss++; #endif #endif addr = mmu_translate(addr, 0, regs.s!=0, data, false, size); #if MMU_DPAGECACHE } #endif } return x_phys_get_word(addr); } static ALWAYS_INLINE uae_u8 mmu_get_byte(uaecptr addr, bool data, int size) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr(addr,regs.s!=0,data) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | regs.s; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_read_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_read_miss++; #endif #endif addr = mmu_translate(addr, 0, regs.s!=0, data, false, size); #if MMU_DPAGECACHE } #endif } return x_phys_get_byte(addr); } static ALWAYS_INLINE void mmu_put_long(uaecptr addr, uae_u32 val, bool data, int size) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,regs.s!=0,data,val,size) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | regs.s; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_write_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_write_miss++; #endif #endif addr = mmu_translate(addr, val, regs.s!=0, data, true, size); #if MMU_DPAGECACHE } #endif } x_phys_put_long(addr, val); } static ALWAYS_INLINE void mmu_put_word(uaecptr addr, uae_u16 val, bool data, int size) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,regs.s!=0,data,val,size) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | regs.s; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_write_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_write_miss++; #endif #endif addr = mmu_translate(addr, val, regs.s!=0, data, true, size); #if MMU_DPAGECACHE } #endif } x_phys_put_word(addr, val); } static ALWAYS_INLINE void mmu_put_byte(uaecptr addr, uae_u8 val, bool data, int size) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,regs.s!=0,data,val,size) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | regs.s; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_write_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_write_miss++; #endif #endif addr = mmu_translate(addr, val, regs.s!=0, data, true, size); #if MMU_DPAGECACHE } #endif } x_phys_put_byte(addr, val); } static ALWAYS_INLINE uae_u32 mmu_get_user_long(uaecptr addr, bool super, bool write, int size, bool ci) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_maybe_write(addr,super,true,size,write) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_read_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_read_miss++; #endif #endif addr = mmu_translate(addr, 0, super, true, write, size); #if MMU_DPAGECACHE } #endif } if (ci) mmu_cache_state = CACHE_DISABLE_MMU; return x_phys_get_long(addr); } static ALWAYS_INLINE uae_u16 mmu_get_user_word(uaecptr addr, bool super, bool write, int size, bool ci) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_maybe_write(addr,super,true,size,write) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_read_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_read_miss++; #endif #endif addr = mmu_translate(addr, 0, super, true, write, size); #if MMU_DPAGECACHE } #endif } if (ci) mmu_cache_state = CACHE_DISABLE_MMU; return x_phys_get_word(addr); } static ALWAYS_INLINE uae_u8 mmu_get_user_byte(uaecptr addr, bool super, bool write, int size, bool ci) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_maybe_write(addr,super,true,size,write) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_read_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_read_miss++; #endif #endif addr = mmu_translate(addr, 0, super, true, write, size); #if MMU_DPAGECACHE } #endif } if (ci) mmu_cache_state = CACHE_DISABLE_MMU; return x_phys_get_byte(addr); } static ALWAYS_INLINE void mmu_put_user_long(uaecptr addr, uae_u32 val, bool super, int size, bool ci) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,super,true,val,size) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_write_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_write_miss++; #endif #endif addr = mmu_translate(addr, val, super, true, true, size); #if MMU_DPAGECACHE } #endif } if (ci) mmu_cache_state = CACHE_DISABLE_MMU; x_phys_put_long(addr, val); } static ALWAYS_INLINE void mmu_put_user_word(uaecptr addr, uae_u16 val, bool super, int size, bool ci) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,super,true,val,size) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_write_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_write_miss++; #endif #endif addr = mmu_translate(addr, val, super, true, true, size); #if MMU_DPAGECACHE } #endif } if (ci) mmu_cache_state = CACHE_DISABLE_MMU; x_phys_put_word(addr, val); } static ALWAYS_INLINE void mmu_put_user_byte(uaecptr addr, uae_u8 val, bool super, int size, bool ci) { mmu_cache_state = cache_default_data; if ((!mmu_ttr_enabled || mmu_match_ttr_write(addr,super,true,val,size) == TTR_NO_MATCH) && regs.mmu_enabled) { #if MMU_DPAGECACHE uae_u32 idx1 = ((addr & mmu_pagemaski) >> mmu_pageshift1m) | (super ? 1 : 0); uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu_pagemask); mmu_cache_state = atc_data_cache_read[idx2].cache_state; #if CACHE_HIT_COUNT mmu_data_write_hit++; #endif } else { #if CACHE_HIT_COUNT mmu_data_write_miss++; #endif #endif addr = mmu_translate(addr, val, super, true, true, size); #if MMU_DPAGECACHE } #endif } if (ci) mmu_cache_state = CACHE_DISABLE_MMU; x_phys_put_byte(addr, val); } static ALWAYS_INLINE void HWput_l(uaecptr addr, uae_u32 l) { put_long (addr, l); } static ALWAYS_INLINE void HWput_w(uaecptr addr, uae_u32 w) { put_word (addr, w); } static ALWAYS_INLINE void HWput_b(uaecptr addr, uae_u32 b) { put_byte (addr, b); } static ALWAYS_INLINE uae_u32 HWget_l(uaecptr addr) { return get_long (addr); } static ALWAYS_INLINE uae_u32 HWget_w(uaecptr addr) { return get_word (addr); } static ALWAYS_INLINE uae_u32 HWget_b(uaecptr addr) { return get_byte (addr); } #if MMU_ICACHE #define MMU_ICACHE_SZ 4096 struct mmu_icache { uae_u16 data; uae_u32 addr; }; extern struct mmu_icache mmu_icache_data[MMU_ICACHE_SZ]; static ALWAYS_INLINE uae_u16 uae_mmu040_getc_iword(uaecptr addr) { int icidx = (addr & (MMU_ICACHE_SZ - 1)) | regs.s; if (addr != mmu_icache_data[icidx].addr || !(regs.cacr & 0x8000)) { mmu_icache_data[icidx].data = mmu_get_iword(addr, sz_word); mmu_icache_data[icidx].addr = addr; return mmu_icache_data[icidx].data; } else { return mmu_icache_data[icidx].data; } } #endif static ALWAYS_INLINE uae_u16 uae_mmu040_get_iword(uaecptr addr) { #if MMU_ICACHE return uae_mmu040_getc_iword(addr); #else return mmu_get_iword(addr, sz_word); #endif } static ALWAYS_INLINE uae_u32 uae_mmu040_get_ilong(uaecptr addr) { #if MMU_ICACHE uae_u32 result = uae_mmu040_getc_iword(addr); result <<= 16; result |= uae_mmu040_getc_iword(addr + 2); return result; #else if (unlikely(is_unaligned_page(addr, 4))) return mmu_get_ilong_unaligned(addr); return mmu_get_ilong(addr, sz_long); #endif } static ALWAYS_INLINE uae_u16 uae_mmu040_get_ibyte(uaecptr addr) { #if MMU_ICACHE uae_u16 result = uae_mmu040_getc_iword(addr & ~1); return (addr & 1) ? result & 0xFF : result >> 8; #else return mmu_get_byte(addr, false, sz_byte); #endif } static ALWAYS_INLINE uae_u32 uae_mmu040_get_long(uaecptr addr) { if (unlikely(is_unaligned_page(addr, 4))) return mmu_get_long_unaligned(addr, true); return mmu_get_long(addr, true, sz_long); } static ALWAYS_INLINE uae_u16 uae_mmu040_get_word(uaecptr addr) { if (unlikely(is_unaligned_page(addr, 2))) return mmu_get_word_unaligned(addr, true); return mmu_get_word(addr, true, sz_word); } static ALWAYS_INLINE uae_u8 uae_mmu040_get_byte(uaecptr addr) { return mmu_get_byte(addr, true, sz_byte); } static ALWAYS_INLINE void uae_mmu040_put_word(uaecptr addr, uae_u16 val) { if (unlikely(is_unaligned_page(addr, 2))) mmu_put_word_unaligned(addr, val, true); else mmu_put_word(addr, val, true, sz_word); } static ALWAYS_INLINE void uae_mmu040_put_byte(uaecptr addr, uae_u8 val) { mmu_put_byte(addr, val, true, sz_byte); } static ALWAYS_INLINE void uae_mmu040_put_long(uaecptr addr, uae_u32 val) { if (unlikely(is_unaligned_page(addr, 4))) mmu_put_long_unaligned(addr, val, true); else mmu_put_long(addr, val, true, sz_long); } static ALWAYS_INLINE uae_u32 uae_mmu060_get_ilong(uaecptr addr) { if (unlikely(is_unaligned_page(addr, 4))) return mmu_get_ilong_unaligned(addr); return mmu_get_ilong(addr, sz_long); } static ALWAYS_INLINE uae_u16 uae_mmu060_get_iword(uaecptr addr) { return mmu_get_iword(addr, sz_word); } static ALWAYS_INLINE uae_u16 uae_mmu060_get_ibyte(uaecptr addr) { return mmu_get_byte(addr, false, sz_byte); } static ALWAYS_INLINE uae_u32 uae_mmu060_get_long(uaecptr addr) { if (unlikely(is_unaligned_page(addr, 4))) return mmu_get_long_unaligned(addr, true); return mmu_get_long(addr, true, sz_long); } static ALWAYS_INLINE uae_u16 uae_mmu060_get_word(uaecptr addr) { if (unlikely(is_unaligned_page(addr, 2))) return mmu_get_word_unaligned(addr, true); return mmu_get_word(addr, true, sz_word); } static ALWAYS_INLINE uae_u8 uae_mmu060_get_byte(uaecptr addr) { return mmu_get_byte(addr, true, sz_byte); } static ALWAYS_INLINE void uae_mmu_get_move16(uaecptr addr, uae_u32 *val) { // move16 is always aligned mmu_get_move16(addr, val, true, 16); } static ALWAYS_INLINE void uae_mmu060_put_long(uaecptr addr, uae_u32 val) { if (unlikely(is_unaligned_page(addr, 4))) mmu_put_long_unaligned(addr, val, true); else mmu_put_long(addr, val, true, sz_long); } static ALWAYS_INLINE void uae_mmu060_put_word(uaecptr addr, uae_u16 val) { if (unlikely(is_unaligned_page(addr, 2))) mmu_put_word_unaligned(addr, val, true); else mmu_put_word(addr, val, true, sz_word); } static ALWAYS_INLINE void uae_mmu060_put_byte(uaecptr addr, uae_u8 val) { mmu_put_byte(addr, val, true, sz_byte); } static ALWAYS_INLINE void uae_mmu_put_move16(uaecptr addr, uae_u32 *val) { // move16 is always aligned mmu_put_move16(addr, val, true, 16); } // normal 040 STATIC_INLINE void put_byte_mmu040 (uaecptr addr, uae_u32 v) { uae_mmu040_put_byte (addr, v); } STATIC_INLINE void put_word_mmu040 (uaecptr addr, uae_u32 v) { uae_mmu040_put_word (addr, v); } STATIC_INLINE void put_long_mmu040 (uaecptr addr, uae_u32 v) { uae_mmu040_put_long (addr, v); } STATIC_INLINE uae_u32 get_byte_mmu040 (uaecptr addr) { return uae_mmu040_get_byte (addr); } STATIC_INLINE uae_u32 get_word_mmu040 (uaecptr addr) { return uae_mmu040_get_word (addr); } STATIC_INLINE uae_u32 get_long_mmu040 (uaecptr addr) { return uae_mmu040_get_long (addr); } // normal 060 STATIC_INLINE void put_byte_mmu060 (uaecptr addr, uae_u32 v) { uae_mmu060_put_byte (addr, v); } STATIC_INLINE void put_word_mmu060 (uaecptr addr, uae_u32 v) { uae_mmu060_put_word (addr, v); } STATIC_INLINE void put_long_mmu060 (uaecptr addr, uae_u32 v) { uae_mmu060_put_long (addr, v); } STATIC_INLINE uae_u32 get_byte_mmu060 (uaecptr addr) { return uae_mmu060_get_byte (addr); } STATIC_INLINE uae_u32 get_word_mmu060 (uaecptr addr) { return uae_mmu060_get_word (addr); } STATIC_INLINE uae_u32 get_long_mmu060 (uaecptr addr) { return uae_mmu060_get_long (addr); } STATIC_INLINE void get_move16_mmu (uaecptr addr, uae_u32 *v) { uae_mmu_get_move16 (addr, v); } STATIC_INLINE void put_move16_mmu (uaecptr addr, uae_u32 *v) { uae_mmu_put_move16 (addr, v); } // locked rmw 060 STATIC_INLINE void put_lrmw_byte_mmu060 (uaecptr addr, uae_u32 v) { uae_mmu_put_lrmw (addr, v, sz_byte, 1); } STATIC_INLINE void put_lrmw_word_mmu060 (uaecptr addr, uae_u32 v) { uae_mmu_put_lrmw (addr, v, sz_word, 1); } STATIC_INLINE void put_lrmw_long_mmu060 (uaecptr addr, uae_u32 v) { uae_mmu_put_lrmw (addr, v, sz_long, 1); } STATIC_INLINE uae_u32 get_lrmw_byte_mmu060 (uaecptr addr) { return uae_mmu_get_lrmw (addr, sz_byte, 1); } STATIC_INLINE uae_u32 get_lrmw_word_mmu060 (uaecptr addr) { return uae_mmu_get_lrmw (addr, sz_word, 1); } STATIC_INLINE uae_u32 get_lrmw_long_mmu060 (uaecptr addr) { return uae_mmu_get_lrmw (addr, sz_long, 1); } // normal rmw 060 STATIC_INLINE void put_rmw_byte_mmu060 (uaecptr addr, uae_u32 v) { rmw_cycle = true; uae_mmu060_put_byte (addr, v); rmw_cycle = false; } STATIC_INLINE void put_rmw_word_mmu060 (uaecptr addr, uae_u32 v) { rmw_cycle = true; uae_mmu060_put_word (addr, v); rmw_cycle = false; } STATIC_INLINE void put_rmw_long_mmu060 (uaecptr addr, uae_u32 v) { rmw_cycle = true; uae_mmu060_put_long (addr, v); rmw_cycle = false; } STATIC_INLINE uae_u32 get_rmw_byte_mmu060 (uaecptr addr) { rmw_cycle = true; uae_u32 v = uae_mmu060_get_byte (addr); rmw_cycle = false; return v; } STATIC_INLINE uae_u32 get_rmw_word_mmu060 (uaecptr addr) { rmw_cycle = true; uae_u32 v = uae_mmu060_get_word (addr); rmw_cycle = false; return v; } STATIC_INLINE uae_u32 get_rmw_long_mmu060 (uaecptr addr) { rmw_cycle = true; uae_u32 v = uae_mmu060_get_long (addr); rmw_cycle = false; return v; } // locked rmw 040 STATIC_INLINE void put_lrmw_byte_mmu040 (uaecptr addr, uae_u32 v) { uae_mmu_put_lrmw (addr, v, sz_byte, 0); } STATIC_INLINE void put_lrmw_word_mmu040 (uaecptr addr, uae_u32 v) { uae_mmu_put_lrmw (addr, v, sz_word, 0); } STATIC_INLINE void put_lrmw_long_mmu040 (uaecptr addr, uae_u32 v) { uae_mmu_put_lrmw (addr, v, sz_long, 0); } STATIC_INLINE uae_u32 get_lrmw_byte_mmu040 (uaecptr addr) { return uae_mmu_get_lrmw (addr, sz_byte, 0); } STATIC_INLINE uae_u32 get_lrmw_word_mmu040 (uaecptr addr) { return uae_mmu_get_lrmw (addr, sz_word, 0); } STATIC_INLINE uae_u32 get_lrmw_long_mmu040 (uaecptr addr) { return uae_mmu_get_lrmw (addr, sz_long, 0); } STATIC_INLINE uae_u32 get_ibyte_mmu040 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu040_get_iword (pc); } STATIC_INLINE uae_u32 get_iword_mmu040 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu040_get_iword (pc); } STATIC_INLINE uae_u32 get_ilong_mmu040 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu040_get_ilong (pc); } STATIC_INLINE uae_u32 next_iword_mmu040 (void) { uae_u32 pc = m68k_getpci (); m68k_incpci (2); return uae_mmu040_get_iword (pc); } STATIC_INLINE uae_u32 next_ilong_mmu040 (void) { uae_u32 pc = m68k_getpci (); m68k_incpci (4); return uae_mmu040_get_ilong (pc); } STATIC_INLINE uae_u32 get_ibyte_mmu060 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu060_get_iword (pc); } STATIC_INLINE uae_u32 get_iword_mmu060 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu060_get_iword (pc); } STATIC_INLINE uae_u32 get_ilong_mmu060 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu060_get_ilong (pc); } STATIC_INLINE uae_u32 next_iword_mmu060 (void) { uae_u32 pc = m68k_getpci (); m68k_incpci (2); return uae_mmu060_get_iword (pc); } STATIC_INLINE uae_u32 next_ilong_mmu060 (void) { uae_u32 pc = m68k_getpci (); m68k_incpci (4); return uae_mmu060_get_ilong (pc); } extern void flush_mmu040 (uaecptr, int); extern void m68k_do_rts_mmu040 (void); extern void m68k_do_rte_mmu040 (uaecptr a7); extern void m68k_do_bsr_mmu040 (uaecptr oldpc, uae_s32 offset); extern void flush_mmu060 (uaecptr, int); extern void m68k_do_rts_mmu060 (void); extern void m68k_do_rte_mmu060 (uaecptr a7); extern void m68k_do_bsr_mmu060 (uaecptr oldpc, uae_s32 offset); #endif /* UAE_CPUMMU_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/cpummu030.c000066400000000000000000003335451504763705000240310ustar00rootroot00000000000000/* Emulation of MC68030 MMU * This code has been written for Previous - a NeXT Computer emulator * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * * Written by Andreas Grabher * * Many thanks go to Thomas Huth and the Hatari community for helping * to test and debug this code! * * * Release notes: * 01-09-2012: First release * 29-09-2012: Improved function code handling * 16-11-2012: Improved exception handling * * * - Check if read-modify-write operations are correctly detected for * handling transparent access (see TT matching functions) * - If possible, test mmu030_table_search with all kinds of translations * (early termination, invalid descriptors, bus errors, indirect * descriptors, PTEST in different levels, etc). * - Check which bits of an ATC entry or the status register should be set * and which should be un-set, if an invalid translation occurs. * - Handle cache inhibit bit when accessing ATC entries */ #include "sysconfig.h" #include "sysdeps.h" #ifdef WINUAE_FOR_HATARI #include "main.h" #include "hatari-glue.h" #include "log.h" #endif #include "options_cpu.h" #include "memory.h" #include "newcpu.h" #include "debug.h" #include "cpummu030.h" #include "cputbl.h" #include "savestate.h" // Prefetch mode and prefetch bus error: always flush and refill prefetch pipeline #define MMU030_ALWAYS_FULL_PREFETCH 1 // if CPU is 68030 and faulted access' addressing mode was -(an) or (an)+ // register content is not restored when exception starts. #define MMU030_REG_FIXUP 1 #define MMU030_OP_DBG_MSG 0 #define MMU030_ATC_DBG_MSG 0 #define MMU030_REG_DBG_MSG 0 #define TT_FC_MASK 0x00000007 #define TT_FC_BASE 0x00000070 #define TT_RWM 0x00000100 #define TT_RW 0x00000200 #define TT_CI 0x00000400 #define TT_ENABLE 0x00008000 #define TT_ADDR_MASK 0x00FF0000 #define TT_ADDR_BASE 0xFF000000 static int bBusErrorReadWrite; static int atcindextable[32]; static int tt_enabled; int mmu030_idx, mmu030_idx_done; uae_u32 mm030_stageb_address; bool mmu030_retry; int mmu030_opcode; int mmu030_opcode_stageb; int mmu030_fake_prefetch; uaecptr mmu030_fake_prefetch_addr; uae_u16 mmu030_state[3]; uae_u32 mmu030_data_buffer_out; uae_u32 mmu030_disp_store[2]; uae_u32 mmu030_fmovem_store[2]; uae_u8 mmu030_cache_state; struct mmu030_access mmu030_ad[MAX_MMU030_ACCESS + 1]; bool ismoves030, islrmw030; static void mmu030_ptest_atc_search(uaecptr logical_addr, uae_u32 fc, bool write); static uae_u32 mmu030_table_search(uaecptr addr, uae_u32 fc, bool write, int level); static TT_info mmu030_decode_tt(uae_u32 TT); #if MMU_DPAGECACHE030 #define MMUFASTCACHE_ENTRIES030 256 struct mmufastcache030 { uae_u32 log; uae_u32 phys; uae_u8 cs; }; static struct mmufastcache030 atc_data_cache_read[MMUFASTCACHE_ENTRIES030]; static struct mmufastcache030 atc_data_cache_write[MMUFASTCACHE_ENTRIES030]; #endif /* for debugging messages */ static char table_letter[4] = {'A','B','C','D'}; static const uae_u32 mmu030_size[3] = { MMU030_SSW_SIZE_B, MMU030_SSW_SIZE_W, MMU030_SSW_SIZE_L }; uae_u64 srp_030, crp_030; uae_u32 tt0_030, tt1_030, tc_030; uae_u16 mmusr_030; /* ATC struct */ #define ATC030_NUM_ENTRIES 22 typedef struct { struct { uaecptr addr; bool modified; bool write_protect; uae_u8 cache_inhibit; bool bus_error; } physical; struct { uaecptr addr; uae_u32 fc; bool valid; } logical; /* history bit */ int mru; } MMU030_ATC_LINE; /* MMU struct for 68030 */ static struct { /* Translation tables */ struct { struct { uae_u32 mask; uae_u8 shift; } table[4]; struct { uae_u32 mask; uae_u32 imask; uae_u32 size; uae_u32 size3m; } page; uae_u8 init_shift; uae_u8 last_table; } translation; /* Transparent translation */ struct { TT_info tt0; TT_info tt1; } transparent; /* Address translation cache */ MMU030_ATC_LINE atc[ATC030_NUM_ENTRIES]; /* Condition */ bool enabled; uae_u16 status; #if MMU_IPAGECACHE030 uae_u8 mmu030_cache_state; #if MMU_DIRECT_ACCESS uae_u8 *mmu030_last_physical_address_real; #else uae_u32 mmu030_last_physical_address; #endif uae_u32 mmu030_last_logical_address; #endif } mmu030; /* MMU Status Register * * ---x ---x x-xx x--- * reserved (all 0) * * x--- ---- ---- ---- * bus error * * -x-- ---- ---- ---- * limit violation * * --x- ---- ---- ---- * supervisor only * * ---- x--- ---- ---- * write protected * * ---- -x-- ---- ---- * invalid * * ---- --x- ---- ---- * modified * * ---- ---- -x-- ---- * transparent access * * ---- ---- ---- -xxx * number of levels (number of tables accessed during search) * */ #define MMUSR_BUS_ERROR 0x8000 #define MMUSR_LIMIT_VIOLATION 0x4000 #define MMUSR_SUPER_VIOLATION 0x2000 #define MMUSR_WRITE_PROTECTED 0x0800 #define MMUSR_INVALID 0x0400 #define MMUSR_MODIFIED 0x0200 #define MMUSR_TRANSP_ACCESS 0x0040 #define MMUSR_NUM_LEVELS_MASK 0x0007 /* -- ATC flushing functions -- */ static void mmu030_flush_cache(uaecptr addr) { #if MMU_IPAGECACHE030 mmu030.mmu030_last_logical_address = 0xffffffff; #endif #if MMU_DPAGECACHE030 if (addr == 0xffffffff) { memset(&atc_data_cache_read, 0xff, sizeof atc_data_cache_read); memset(&atc_data_cache_write, 0xff, sizeof atc_data_cache_write); } else { uae_u32 idx = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | 7; for (int i = 0; i < MMUFASTCACHE_ENTRIES030; i++) { if ((atc_data_cache_read[i].log | 7) == idx) atc_data_cache_read[i].log = 0xffffffff; if ((atc_data_cache_write[i].log | 7) == idx) atc_data_cache_write[i].log = 0xffffffff; } } #endif } /* This function flushes ATC entries depending on their function code */ static void mmu030_flush_atc_fc(uae_u32 fc_base, uae_u32 fc_mask) { int i; for (i=0; i> 3) & 7; int rreg = opcode & 7; // Dn, An, (An)+, -(An), immediate and PC-relative not allowed if (eamode == 0 || eamode == 1 || eamode == 3 || eamode == 4 || (eamode == 7 && rreg > 1)) return true; return false; } int mmu_op30_pmove(uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int preg = (next >> 10) & 31; int rw = (next >> 9) & 1; int fd = (next >> 8) & 1; int unused = (next & 0xff); if (mmu_op30_invea(opcode)) return 1; // unused low 8 bits must be zeroed if (unused) return 1; // read and fd set? if (rw && fd) return 1; #if MMU030_OP_DBG_MSG switch (preg) { case 0x10: write_log(_T("PMOVE: %s TC %08X PC=%08x\n"), rw ? _T("read"): _T("write"), rw ? tc_030 : x_get_long(extra), m68k_getpc()); break; case 0x12: write_log(_T("PMOVE: %s SRP %08X%08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?(uae_u32)(srp_030>>32)&0xFFFFFFFF:x_get_long(extra), rw?(uae_u32)srp_030&0xFFFFFFFF:x_get_long(extra+4), m68k_getpc()); break; case 0x13: write_log(_T("PMOVE: %s CRP %08X%08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?(uae_u32)(crp_030>>32)&0xFFFFFFFF:x_get_long(extra), rw?(uae_u32)crp_030&0xFFFFFFFF:x_get_long(extra+4), m68k_getpc()); break; case 0x18: write_log(_T("PMOVE: %s MMUSR %04X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?mmusr_030:x_get_word(extra), m68k_getpc()); break; case 0x02: write_log(_T("PMOVE: %s TT0 %08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?tt0_030:x_get_long(extra), m68k_getpc()); break; case 0x03: write_log(_T("PMOVE: %s TT1 %08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?tt1_030:x_get_long(extra), m68k_getpc()); break; default: break; } if (!fd && !rw && !(preg==0x18)) { write_log(_T("PMOVE: flush ATC\n")); } #endif switch (preg) { case 0x10: // TC if (rw) x_put_long (extra, tc_030); else { tc_030 = x_get_long (extra); if (mmu030_decode_tc(tc_030, true)) return -1; } break; case 0x12: // SRP if (rw) { x_put_long (extra, srp_030 >> 32); x_put_long (extra + 4, (uae_u32)srp_030); } else { srp_030 = (uae_u64)x_get_long (extra) << 32; srp_030 |= x_get_long (extra + 4); if (mmu030_decode_rp(srp_030)) return -1; } break; case 0x13: // CRP if (rw) { x_put_long (extra, crp_030 >> 32); x_put_long (extra + 4, (uae_u32)crp_030); } else { crp_030 = (uae_u64)x_get_long (extra) << 32; crp_030 |= x_get_long (extra + 4); if (mmu030_decode_rp(crp_030)) return -1; } break; case 0x18: // MMUSR if (fd) { // FD must be always zero when MMUSR read or write return 1; } if (rw) x_put_word (extra, mmusr_030); else mmusr_030 = x_get_word (extra); break; case 0x02: // TT0 if (rw) x_put_long (extra, tt0_030); else { tt0_030 = x_get_long (extra); mmu030.transparent.tt0 = mmu030_decode_tt(tt0_030); } break; case 0x03: // TT1 if (rw) x_put_long (extra, tt1_030); else { tt1_030 = x_get_long (extra); mmu030.transparent.tt1 = mmu030_decode_tt(tt1_030); } break; default: write_log (_T("Bad PMOVE at %08x\n"),m68k_getpc()); return 1; } if (!fd && !rw && preg != 0x18) { mmu030_flush_atc_all(); } tt_enabled = (tt0_030 & TT_ENABLE) || (tt1_030 & TT_ENABLE); return 0; } bool mmu_op30_ptest (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { mmu030.status = mmusr_030 = 0; int level = (next&0x1C00)>>10; int rw = (next >> 9) & 1; int a = (next >> 8) & 1; int areg = (next&0xE0)>>5; uae_u32 fc; bool write = rw ? false : true; uae_u32 ret = 0; if (mmu_op30_invea(opcode)) return true; if (!mmu_op30_helper_get_fc(next, &fc)) return true; if (!level && a) { write_log(_T("PTEST: Bad instruction causing F-line unimplemented instruction exception!\n")); return true; } #if MMU030_OP_DBG_MSG write_log(_T("PTEST%c: addr = %08X, fc = %i, level = %i, PC=%08x, "), rw?'R':'W', extra, fc, level, m68k_getpc()); if (a) { write_log(_T("return descriptor to register A%i\n"), areg); } else { write_log(_T("do not return descriptor\n")); } #endif if (!level) { mmu030_ptest_atc_search(extra, fc, write); } else { ret = mmu030_table_search(extra, fc, write, level); if (a) { m68k_areg (regs, areg) = ret; } } mmusr_030 = mmu030.status; #if MMU030_OP_DBG_MSG write_log(_T("PTEST status: %04X, B = %i, L = %i, S = %i, W = %i, I = %i, M = %i, T = %i, N = %i\n"), mmusr_030, (mmusr_030&MMUSR_BUS_ERROR)?1:0, (mmusr_030&MMUSR_LIMIT_VIOLATION)?1:0, (mmusr_030&MMUSR_SUPER_VIOLATION)?1:0, (mmusr_030&MMUSR_WRITE_PROTECTED)?1:0, (mmusr_030&MMUSR_INVALID)?1:0, (mmusr_030&MMUSR_MODIFIED)?1:0, (mmusr_030&MMUSR_TRANSP_ACCESS)?1:0, mmusr_030&MMUSR_NUM_LEVELS_MASK); #endif return false; } static bool mmu_op30_pload (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int rw = (next >> 9) & 1; int unused = (next & (0x100 | 0x80 | 0x40 | 0x20)); uae_u32 fc; bool write = rw ? false : true; if (mmu_op30_invea(opcode)) return true; if (unused) return true; if (!mmu_op30_helper_get_fc(next, &fc)) return true; #if MMU030_OP_DBG_MSG write_log (_T("PLOAD%c: Create ATC entry for %08X, FC = %i\n"), write?'W':'R', extra, fc); #endif mmu030_flush_atc_page(extra); mmu030_table_search(extra, fc, write, 0); return false; } bool mmu_op30_pflush (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { uae_u16 mode = (next >> 8) & 31; uae_u32 fc_mask = (next & 0x00E0) >> 5; uae_u32 fc_base; uae_u32 fc_bits = next & 0x7f; #if MMU030_OP_DBG_MSG switch (mode) { case 0x1: write_log(_T("PFLUSH: Flush all entries\n")); break; case 0x4: write_log(_T("PFLUSH: Flush by function code only\n")); write_log(_T("PFLUSH: function code: base = %08X, mask = %08X\n"), fc_base, fc_mask); break; case 0x6: write_log(_T("PFLUSH: Flush by function code and effective address\n")); write_log(_T("PFLUSH: function code: base = %08X, mask = %08X\n"), fc_base, fc_mask); write_log(_T("PFLUSH: effective address = %08X\n"), extra); break; default: break; } #endif switch (mode) { case 0x00: // PLOAD W case 0x02: // PLOAD R return mmu_op30_pload(pc, opcode, next, extra); case 0x04: if (fc_bits) return true; mmu030_flush_atc_all(); break; case 0x10: if (!mmu_op30_helper_get_fc(next, &fc_base)) return true; mmu030_flush_atc_fc(fc_base, fc_mask); break; case 0x18: if (mmu_op30_invea(opcode)) return true; if (!mmu_op30_helper_get_fc(next, &fc_base)) return true; mmu030_flush_atc_page_fc(extra, fc_base, fc_mask); break; default: write_log(_T("PFLUSH %04x-%04x ERROR: bad mode! (%i)\n"), opcode, next, mode); return true; } return false; } /* Transparent Translation Registers (TT0 and TT1) * * ---- ---- ---- ---- -xxx x--- x--- x--- * reserved, must be 0 * * ---- ---- ---- ---- ---- ---- ---- -xxx * function code mask (FC bits to be ignored) * * ---- ---- ---- ---- ---- ---- -xxx ---- * function code base (FC value for transparent block) * * ---- ---- ---- ---- ---- ---x ---- ---- * 0 = r/w field used, 1 = read and write is transparently translated * * ---- ---- ---- ---- ---- --x- ---- ---- * r/w field: 0 = write ..., 1 = read access transparent * * ---- ---- ---- ---- ---- -x-- ---- ---- * cache inhibit: 0 = caching allowed, 1 = caching inhibited * * ---- ---- ---- ---- x--- ---- ---- ---- * 0 = transparent translation enabled disabled, 1 = enabled * * ---- ---- xxxx xxxx ---- ---- ---- ---- * logical address mask * * xxxx xxxx ---- ---- ---- ---- ---- ---- * logical address base * */ /* TT comparison results */ #define TT_NO_MATCH 0x1 #define TT_OK_MATCH 0x2 #define TT_NO_READ 0x4 #define TT_NO_WRITE 0x8 TT_info mmu030_decode_tt(uae_u32 TT) { TT_info ret; ret.fc_mask = ~((TT&TT_FC_MASK)|0xFFFFFFF8); ret.fc_base = (TT&TT_FC_BASE)>>4; ret.addr_base = TT & TT_ADDR_BASE; ret.addr_mask = ~(((TT&TT_ADDR_MASK)<<8)|0x00FFFFFF); #if MMU030_OP_DBG_MSG if ((TT&TT_ENABLE) && !(TT&TT_RWM)) { write_log(_T("MMU Warning: Transparent translation of read-modify-write cycle is not correctly handled!\n")); } #endif #if MMU030_REG_DBG_MSG /* enable or disable debugging messages */ write_log(_T("\n")); write_log(_T("TRANSPARENT TRANSLATION: %08X\n"), TT); write_log(_T("\n")); write_log(_T("TT: transparent translation ")); if (TT&TT_ENABLE) { write_log(_T("enabled\n")); } else { write_log(_T("disabled\n")); return ret; } write_log(_T("TT: caching %s\n"), (TT&TT_CI) ? _T("inhibited") : _T("enabled")); write_log(_T("TT: read-modify-write ")); if (TT&TT_RWM) { write_log(_T("enabled\n")); } else { write_log(_T("disabled (%s only)\n"), (TT&TT_RW) ? _T("read") : _T("write")); } write_log(_T("\n")); write_log(_T("TT: function code base: %08X\n"), ret.fc_base); write_log(_T("TT: function code mask: %08X\n"), ret.fc_mask); write_log(_T("\n")); write_log(_T("TT: address base: %08X\n"), ret.addr_base); write_log(_T("TT: address mask: %08X\n"), ret.addr_mask); write_log(_T("\n")); #endif return ret; } /* This function checks if an address matches a transparent * translation register */ /* FIXME: * If !(tt&TT_RMW) neither the read nor the write portion * of a read-modify-write cycle is transparently translated! */ static int mmu030_do_match_ttr(uae_u32 tt, TT_info comp, uaecptr addr, uae_u32 fc, bool write) { if (tt & TT_ENABLE) { /* transparent translation enabled */ /* Compare actual function code with function code base using mask */ if ((comp.fc_base&comp.fc_mask)==(fc&comp.fc_mask)) { /* Compare actual address with address base using mask */ if ((comp.addr_base&comp.addr_mask)==(addr&comp.addr_mask)) { if (tt&TT_RWM) { /* r/w field disabled */ return TT_OK_MATCH; } else { if (tt&TT_RW) { /* read access transparent */ return write ? TT_NO_WRITE : TT_OK_MATCH; } else { /* write access transparent */ return write ? TT_OK_MATCH : TT_NO_READ; /* TODO: check this! */ } } } } } return TT_NO_MATCH; } static int mmu030_do_match_lrmw_ttr(uae_u32 tt, TT_info comp, uaecptr addr, uae_u32 fc) { if ((tt & TT_ENABLE) && (tt & TT_RWM)) { /* transparent translation enabled */ /* Compare actual function code with function code base using mask */ if ((comp.fc_base&comp.fc_mask)==(fc&comp.fc_mask)) { /* Compare actual address with address base using mask */ if ((comp.addr_base&comp.addr_mask)==(addr&comp.addr_mask)) { return TT_OK_MATCH; } } } return TT_NO_MATCH; } /* This function compares the address with both transparent * translation registers and returns the result */ static int mmu030_match_ttr(uaecptr addr, uae_u32 fc, bool write) { int tt0, tt1; tt0 = mmu030_do_match_ttr(tt0_030, mmu030.transparent.tt0, addr, fc, write); if (tt0&TT_OK_MATCH) { if (tt0_030&TT_CI) mmu030_cache_state = CACHE_DISABLE_MMU; } tt1 = mmu030_do_match_ttr(tt1_030, mmu030.transparent.tt1, addr, fc, write); if (tt1&TT_OK_MATCH) { if (tt0_030&TT_CI) mmu030_cache_state = CACHE_DISABLE_MMU; } return (tt0|tt1); } static int mmu030_match_ttr_access(uaecptr addr, uae_u32 fc, bool write) { int tt0, tt1; if (!tt_enabled) return 0; tt0 = mmu030_do_match_ttr(tt0_030, mmu030.transparent.tt0, addr, fc, write); tt1 = mmu030_do_match_ttr(tt1_030, mmu030.transparent.tt1, addr, fc, write); return (tt0|tt1) & TT_OK_MATCH; } /* Locked Read-Modify-Write */ static int mmu030_match_lrmw_ttr_access(uaecptr addr, uae_u32 fc) { int tt0, tt1; if (!tt_enabled) return 0; tt0 = mmu030_do_match_lrmw_ttr(tt0_030, mmu030.transparent.tt0, addr, fc); tt1 = mmu030_do_match_lrmw_ttr(tt1_030, mmu030.transparent.tt1, addr, fc); return (tt0|tt1) & TT_OK_MATCH; } /* Translation Control Register: * * x--- ---- ---- ---- ---- ---- ---- ---- * translation: 1 = enable, 0 = disable * * ---- --x- ---- ---- ---- ---- ---- ---- * supervisor root: 1 = enable, 0 = disable * * ---- ---x ---- ---- ---- ---- ---- ---- * function code lookup: 1 = enable, 0 = disable * * ---- ---- xxxx ---- ---- ---- ---- ---- * page size: * 1000 = 256 bytes * 1001 = 512 bytes * 1010 = 1 kB * 1011 = 2 kB * 1100 = 4 kB * 1101 = 8 kB * 1110 = 16 kB * 1111 = 32 kB * * ---- ---- ---- xxxx ---- ---- ---- ---- * initial shift * * ---- ---- ---- ---- xxxx ---- ---- ---- * number of bits for table index A * * ---- ---- ---- ---- ---- xxxx ---- ---- * number of bits for table index B * * ---- ---- ---- ---- ---- ---- xxxx ---- * number of bits for table index C * * ---- ---- ---- ---- ---- ----- ---- xxxx * number of bits for table index D * */ #define TC_ENABLE_TRANSLATION 0x80000000 #define TC_ENABLE_SUPERVISOR 0x02000000 #define TC_ENABLE_FCL 0x01000000 #define TC_PS_MASK 0x00F00000 #define TC_IS_MASK 0x000F0000 #define TC_TIA_MASK 0x0000F000 #define TC_TIB_MASK 0x00000F00 #define TC_TIC_MASK 0x000000F0 #define TC_TID_MASK 0x0000000F static void mmu030_do_fake_prefetch(void) { if (currprefs.cpu_compatible) return; // fetch next opcode before MMU state switches. // There are programs that do following: // - enable MMU // - JMP (An) // "enable MMU" unmaps memory under us. TRY (prb) { uaecptr pc = m68k_getpci(); mmu030_fake_prefetch = -1; mmu030_fake_prefetch_addr = mmu030_translate(pc, regs.s != 0, false, false); mmu030_fake_prefetch = x_prefetch(0); // A26x0 ROM code switches off rom // NOP // JMP (a0) if (mmu030_fake_prefetch == 0x4e71) mmu030_fake_prefetch = x_prefetch(2); } CATCH (prb) { // didn't work, oh well.. mmu030_fake_prefetch = -1; } ENDTRY } bool mmu030_decode_tc(uae_u32 TC, bool check) { #if MMU_IPAGECACHE030 mmu030.mmu030_last_logical_address = 0xffffffff; #endif if (currprefs.mmu_ec) TC &= ~TC_ENABLE_TRANSLATION; /* Set MMU condition */ if (TC & TC_ENABLE_TRANSLATION) { if (!mmu030.enabled && check) mmu030_do_fake_prefetch(); mmu030.enabled = true; } else { if (mmu030.enabled) { mmu030_do_fake_prefetch(); write_log(_T("MMU disabled PC=%08x\n"), M68K_GETPC); } mmu030.enabled = false; return false; } /* Note: 0 = Table A, 1 = Table B, 2 = Table C, 3 = Table D */ int i, j; uae_u8 TI_bits[4] = {0,0,0,0}; /* Reset variables before extracting new values from TC */ for (i = 0; i < 4; i++) { mmu030.translation.table[i].mask = 0; mmu030.translation.table[i].shift = 0; } /* Extract initial shift and page size values from TC register */ mmu030.translation.page.size = (TC & TC_PS_MASK) >> 20; mmu030.translation.page.size3m = mmu030.translation.page.size - 3; mmu030.translation.init_shift = (TC & TC_IS_MASK) >> 16; regs.mmu_page_size = 1 << mmu030.translation.page.size; write_log(_T("68030 MMU enabled. Page size = %d PC=%08x\n"), regs.mmu_page_size, M68K_GETPC); if (mmu030.translation.page.size<8) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (bad page size: %i byte)\n"), 1<>shift */ /* Get number of bits for each table index */ for (i = 0; i < 4; i++) { j = (3-i)*4; TI_bits[i] = (TC >> j) & 0xF; } /* Calculate masks and shifts for each table */ mmu030.translation.last_table = 0; uae_u8 shift = 32 - mmu030.translation.init_shift; for (i = 0; (i < 4) && TI_bits[i]; i++) { /* Get the shift */ shift -= TI_bits[i]; mmu030.translation.table[i].shift = shift; /* Build the mask */ for (j = 0; j < TI_bits[i]; j++) { mmu030.translation.table[i].mask |= (1<<(mmu030.translation.table[i].shift + j)); } /* Update until reaching the last table */ mmu030.translation.last_table = i; } #if MMU030_REG_DBG_MSG /* At least one table has to be defined using at least * 1 bit for the index. At least 2 bits are necessary * if there is no second table. If these conditions are * not met, it will automatically lead to a sum <32 * and cause an exception (see below). */ if (!TI_bits[0]) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (no first table index defined)\n")); } else if ((TI_bits[0]<2) && !TI_bits[1]) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (no second table index defined and)\n")); write_log(_T("MMU Configuration Exception: Bad value in TC register! (only 1 bit for first table index)\n")); } #endif /* TI fields are summed up until a zero field is reached (see above * loop). The sum of all TI field values plus page size and initial * shift has to be 32: IS + PS + TIA + TIB + TIC + TID = 32 */ if ((shift-mmu030.translation.page.size)!=0) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (bad sum)\n")); Exception(56); /* MMU Configuration Exception */ return true; } #if MMU030_REG_DBG_MSG /* enable or disable debugging output */ write_log(_T("\n")); write_log(_T("TRANSLATION CONTROL: %08X\n"), TC); write_log(_T("\n")); write_log(_T("TC: translation %s\n"), (TC&TC_ENABLE_TRANSLATION ? _T("enabled") : _T("disabled"))); write_log(_T("TC: supervisor root pointer %s\n"), (TC&TC_ENABLE_SUPERVISOR ? _T("enabled") : _T("disabled"))); write_log(_T("TC: function code lookup %s\n"), (TC&TC_ENABLE_FCL ? _T("enabled") : _T("disabled"))); write_log(_T("\n")); write_log(_T("TC: Initial Shift: %i\n"), mmu030.translation.init_shift); write_log(_T("TC: Page Size: %i byte\n"), (1<> 32); if (!descriptor_type) { /* If descriptor type is invalid */ write_log(_T("MMU Configuration Exception: Root Pointer is invalid!\n")); Exception(56); /* MMU Configuration Exception */ return true; } return false; #if MMU030_REG_DBG_MSG /* enable or disable debugging output */ uae_u32 table_limit = (RP & RP_LIMIT_MASK) >> 48; uae_u32 first_addr = (RP & RP_ADDR_MASK); write_log(_T("\n")); write_log(_T("ROOT POINTER: %08X%08X\n"), (uae_u32)(RP>>32)&0xFFFFFFFF, (uae_u32)(RP&0xFFFFFFFF)); write_log(_T("\n")); write_log(_T("RP: descriptor type = %i "), descriptor_type); switch (descriptor_type) { case 0: write_log(_T("(invalid descriptor)\n")); break; case 1: write_log(_T("(early termination page descriptor)\n")); break; case 2: write_log(_T("(valid 4 byte descriptor)\n")); break; case 3: write_log(_T("(valid 8 byte descriptor)\n")); break; } write_log(_T("RP: %s limit = %i\n"), (RP&RP_LOWER_MASK) ? _T("lower") : _T("upper"), table_limit); write_log(_T("RP: first table address = %08X\n"), first_addr); write_log(_T("\n")); #endif } static void mmu030_atc_handle_history_bit(int entry_num) { int j; mmu030.atc[entry_num].mru = 1; for (j=0; j 1 write_log(_T("ATC: No more history zero-bits. Reset all.\n")); #endif } } static void desc_put_long(uaecptr addr, uae_u32 v) { x_phys_put_long(addr, v); } static uae_u32 desc_get_long(uaecptr addr) { return x_phys_get_long(addr); } static void desc_get_quad(uaecptr addr, uae_u32 *descr) { descr[0] = x_phys_get_long(addr); descr[1] = x_phys_get_long(addr + 4); } /* Descriptors */ #define DESCR_TYPE_MASK 0x00000003 #define DESCR_TYPE_INVALID 0 /* all tables */ #define DESCR_TYPE_EARLY_TERM 1 /* all but lowest level table */ #define DESCR_TYPE_PAGE 1 /* only lowest level table */ #define DESCR_TYPE_VALID4 2 /* all but lowest level table */ #define DESCR_TYPE_INDIRECT4 2 /* only lowest level table */ #define DESCR_TYPE_VALID8 3 /* all but lowest level table */ #define DESCR_TYPE_INDIRECT8 3 /* only lowest level table */ #define DESCR_TYPE_VALID_MASK 0x2 /* all but lowest level table */ #define DESCR_TYPE_INDIRECT_MASK 0x2 /* only lowest level table */ /* Short format (4 byte): * * ---- ---- ---- ---- ---- ---- ---- --xx * descriptor type: * 0 = invalid * 1 = page descriptor (early termination) * 2 = valid (4 byte) * 3 = valid (8 byte) * * * table descriptor: * ---- ---- ---- ---- ---- ---- ---- -x-- * write protect * * ---- ---- ---- ---- ---- ---- ---- x--- * update * * xxxx xxxx xxxx xxxx xxxx xxxx xxxx ---- * table address * * * (early termination) page descriptor: * ---- ---- ---- ---- ---- ---- ---- -x-- * write protect * * ---- ---- ---- ---- ---- ---- ---- x--- * update * * ---- ---- ---- ---- ---- ---- ---x ---- * modified * * ---- ---- ---- ---- ---- ---- -x-- ---- * cache inhibit * * ---- ---- ---- ---- ---- ---- x-x- ---- * reserved (must be 0) * * xxxx xxxx xxxx xxxx xxxx xxxx ---- ---- * page address * * * indirect descriptor: * xxxx xxxx xxxx xxxx xxxx xxxx xxxx xx-- * descriptor address * */ #define DESCR_WP 0x00000004 #define DESCR_U 0x00000008 #define DESCR_M 0x00000010 /* only last level table */ #define DESCR_CI 0x00000040 /* only last level table */ #define DESCR_TD_ADDR_MASK 0xFFFFFFF0 #define DESCR_PD_ADDR_MASK 0xFFFFFF00 #define DESCR_ID_ADDR_MASK 0xFFFFFFFC /* Long format (8 byte): * * ---- ---- ---- ---- ---- ---- ---- --xx | ---- ---- ---- ---- ---- ---- ---- ---- * descriptor type: * 0 = invalid * 1 = page descriptor (early termination) * 2 = valid (4 byte) * 3 = valid (8 byte) * * * table desctriptor: * ---- ---- ---- ---- ---- ---- ---- -x-- | ---- ---- ---- ---- ---- ---- ---- ---- * write protect * * ---- ---- ---- ---- ---- ---- ---- x--- | ---- ---- ---- ---- ---- ---- ---- ---- * update * * ---- ---- ---- ---- ---- ---- xxxx ---- | ---- ---- ---- ---- ---- ---- ---- ---- * reserved (must be 0) * * ---- ---- ---- ---- ---- ---x ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * supervisor * * ---- ---- ---- ---- xxxx xxx- ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * reserved (must be 1111 110) * * -xxx xxxx xxxx xxxx ---- ---- ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * limit * * x--- ---- ---- ---- ---- ---- ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * 0 = upper limit, 1 = lower limit * * ---- ---- ---- ---- ---- ---- ---- ---- | xxxx xxxx xxxx xxxx xxxx xxxx xxxx ---- * table address * * * (early termination) page descriptor: * ---- ---- ---- ---- ---- ---- ---- -x-- | ---- ---- ---- ---- ---- ---- ---- ---- * write protect * * ---- ---- ---- ---- ---- ---- ---- x--- | ---- ---- ---- ---- ---- ---- ---- ---- * update * * ---- ---- ---- ---- ---- ---- ---x ---- | ---- ---- ---- ---- ---- ---- ---- ---- * modified * * ---- ---- ---- ---- ---- ---- -x-- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * cache inhibit * * ---- ---- ---- ---- ---- ---x ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * supervisor * * ---- ---- ---- ---- ---- ---- x-x- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * reserved (must be 0) * * ---- ---- ---- ---- xxxx xxx- ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * reserved (must be 1111 110) * * -xxx xxxx xxxx xxxx ---- ---- ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * limit (only used with early termination page descriptor) * * x--- ---- ---- ---- ---- ---- ---- ---- | ---- ---- ---- ---- ---- ---- ---- ---- * 0 = upper limit, 1 = lower limit (only used with early termination page descriptor) * * ---- ---- ---- ---- ---- ---- ---- ---- | xxxx xxxx xxxx xxxx xxxx xxxx ---- ---- * page address * * * indirect descriptor: * ---- ---- ---- ---- ---- ---- ---- ---- | xxxx xxxx xxxx xxxx xxxx xxxx xxxx xx-- * descriptor address * */ /* only for long descriptors */ #define DESCR_S 0x00000100 #define DESCR_LIMIT_MASK 0x7FFF0000 #define DESCR_LOWER_MASK 0x80000000 /* This functions searches through the translation tables. It can be used * for PTEST (levels 1 to 7). Using level 0 creates an ATC entry. */ static uae_u32 mmu030_table_search(uaecptr addr, uae_u32 fc, bool write, int level) { /* During table walk up to 7 different descriptors are used: * root pointer, descriptors fetched from function code lookup table, * tables A, B, C and D and one indirect descriptor */ uae_u32 descr[2]; uae_u32 descr_type; uaecptr descr_addr[7]; uaecptr table_addr = 0; uaecptr page_addr = 0; uaecptr indirect_addr = 0; uae_u32 table_index = 0; uae_u32 limit = 0; uae_u32 unused_fields_mask = 0; bool super = (fc&4) ? true : false; bool super_violation = false; bool write_protected = false; uae_u8 cache_inhibit = CACHE_ENABLE_ALL; bool descr_modified = false; mmu030.status = 0; /* Reset status */ /* Initial values for condition variables. * Note: Root pointer is long descriptor. */ int t = 0; int addr_position = 1; int next_size = 0; int descr_size = 8; int descr_num = 0; bool early_termination = false; int old_s; int i; // Always use supervisor mode to access descriptors old_s = regs.s; regs.s = 1; TRY(prb) { /* Use super user root pointer if enabled in TC register and access is in * super user mode, else use cpu root pointer. */ if ((tc_030&TC_ENABLE_SUPERVISOR) && super) { descr[0] = (srp_030>>32)&0xFFFFFFFF; descr[1] = srp_030&0xFFFFFFFF; #if MMU030_REG_DBG_MSG > 2 write_log(_T("Supervisor Root Pointer: %08X%08X\n"),descr[0],descr[1]); #endif // MMU030_REG_DBG_MSG } else { descr[0] = (crp_030>>32)&0xFFFFFFFF; descr[1] = crp_030&0xFFFFFFFF; #if MMU030_REG_DBG_MSG > 2 write_log(_T("CPU Root Pointer: %08X%08X\n"),descr[0],descr[1]); #endif } if (descr[0]&RP_ZERO_BITS) { #if MMU030_REG_DBG_MSG write_log(_T("MMU Warning: Root pointer reserved bits are non-zero! %08X\n"), descr[0]); #endif descr[0] &= (~RP_ZERO_BITS); } /* Check descriptor type of root pointer */ descr_type = descr[0]&DESCR_TYPE_MASK; switch (descr_type) { case DESCR_TYPE_INVALID: write_log(_T("Fatal error: Root pointer is invalid descriptor!\n")); mmu030.status |= MMUSR_INVALID; goto stop_search; case DESCR_TYPE_EARLY_TERM: write_log(_T("Root pointer is early termination page descriptor.\n")); early_termination = true; goto handle_page_descriptor; case DESCR_TYPE_VALID4: next_size = 4; break; case DESCR_TYPE_VALID8: next_size = 8; break; } /* If function code lookup is enabled in TC register use function code as * index for top level table, limit check not required */ if (tc_030&TC_ENABLE_FCL) { write_log(_T("Function code lookup enabled, FC = %i\n"), fc); addr_position = (descr_size==4) ? 0 : 1; table_addr = descr[addr_position]&DESCR_TD_ADDR_MASK; table_index = fc; /* table index is function code */ write_log(_T("Table FCL at %08X: index = %i, "),table_addr,table_index); /* Fetch next descriptor */ descr_num++; descr_addr[descr_num] = table_addr+(table_index*next_size); if (next_size==4) { descr[0] = desc_get_long(descr_addr[descr_num]); #if MMU030_REG_DBG_MSG > 2 write_log(_T("Next descriptor: %08X\n"),descr[0]); #endif } else { desc_get_quad(descr_addr[descr_num], descr); #if MMU030_REG_DBG_MSG > 2 write_log(_T("Next descriptor: %08X%08X\n"),descr[0],descr[1]); #endif } descr_size = next_size; /* Check descriptor type */ descr_type = descr[0]&DESCR_TYPE_MASK; switch (descr_type) { case DESCR_TYPE_INVALID: write_log(_T("Invalid descriptor!\n")); /* stop table walk */ mmu030.status |= MMUSR_INVALID; goto stop_search; case DESCR_TYPE_EARLY_TERM: #if MMU030_REG_DBG_MSG > 2 write_log(_T("Early termination page descriptor!\n")); #endif early_termination = true; goto handle_page_descriptor; case DESCR_TYPE_VALID4: next_size = 4; break; case DESCR_TYPE_VALID8: next_size = 8; break; } } /* Upper level tables */ do { if (descr_num) { /* if not root pointer */ /* Check protection */ if ((descr_size==8) && (descr[0]&DESCR_S) && !super) { super_violation = true; } if (descr[0]&DESCR_WP) { write_protected = true; } /* Set the updated bit */ if (!level && !(descr[0]&DESCR_U) && !super_violation) { descr[0] |= DESCR_U; desc_put_long(descr_addr[descr_num], descr[0]); } /* Update status bits */ mmu030.status |= super_violation ? MMUSR_SUPER_VIOLATION : 0; mmu030.status |= write_protected ? MMUSR_WRITE_PROTECTED : 0; /* Check if ptest level is reached */ if (level && (level==descr_num)) { goto stop_search; } } addr_position = (descr_size==4) ? 0 : 1; table_addr = descr[addr_position]&DESCR_TD_ADDR_MASK; table_index = (addr&mmu030.translation.table[t].mask)>>mmu030.translation.table[t].shift; #if MMU030_REG_DBG_MSG > 2 write_log(_T("Table %c at %08X: index = %i, "),table_letter[t],table_addr,table_index); #endif // MMU030_REG_DBG_MSG t++; /* Proceed to the next table */ /* Perform limit check */ if (descr_size==8) { limit = (descr[0]&DESCR_LIMIT_MASK)>>16; if ((descr[0]&DESCR_LOWER_MASK) && (table_indexlimit)) { mmu030.status |= (MMUSR_LIMIT_VIOLATION|MMUSR_INVALID); #if MMU030_REG_DBG_MSG write_log(_T("limit violation (upper limit %i)\n"),limit); #endif goto stop_search; } } /* Fetch next descriptor */ descr_num++; descr_addr[descr_num] = table_addr+(table_index*next_size); if (next_size==4) { descr[0] = desc_get_long(descr_addr[descr_num]); #if MMU030_REG_DBG_MSG > 2 write_log(_T("Next descriptor: %08X\n"),descr[0]); #endif } else { desc_get_quad(descr_addr[descr_num], descr); #if MMU030_REG_DBG_MSG > 2 write_log(_T("Next descriptor: %08X%08X\n"),descr[0],descr[1]); #endif } descr_size = next_size; /* Check descriptor type */ descr_type = descr[0]&DESCR_TYPE_MASK; switch (descr_type) { case DESCR_TYPE_INVALID: #if MMU030_REG_DBG_MSG write_log(_T("Invalid descriptor!\n")); #endif /* stop table walk */ mmu030.status |= MMUSR_INVALID; goto stop_search; case DESCR_TYPE_EARLY_TERM: /* go to last level table handling code */ if (t<=mmu030.translation.last_table) { #if MMU030_REG_DBG_MSG > 2 write_log(_T("Early termination page descriptor!\n")); #endif early_termination = true; } goto handle_page_descriptor; case DESCR_TYPE_VALID4: next_size = 4; break; case DESCR_TYPE_VALID8: next_size = 8; break; } } while (t<=mmu030.translation.last_table); /* Handle indirect descriptor */ /* Check if ptest level is reached */ if (level && (level==descr_num)) { goto stop_search; } addr_position = (descr_size==4) ? 0 : 1; indirect_addr = descr[addr_position]&DESCR_ID_ADDR_MASK; #if MMU030_REG_DBG_MSG > 2 write_log(_T("Page indirect descriptor at %08X: "),indirect_addr); #endif /* Fetch indirect descriptor */ descr_num++; descr_addr[descr_num] = indirect_addr; if (next_size==4) { descr[0] = desc_get_long(descr_addr[descr_num]); #if MMU030_REG_DBG_MSG > 2 write_log(_T("descr = %08X\n"),descr[0]); #endif } else { desc_get_quad(descr_addr[descr_num], descr); #if MMU030_REG_DBG_MSG > 2 write_log(_T("descr = %08X%08X"),descr[0],descr[1]); #endif } descr_size = next_size; /* Check descriptor type, only page descriptor is valid */ descr_type = descr[0]&DESCR_TYPE_MASK; if (descr_type!=DESCR_TYPE_PAGE) { mmu030.status |= MMUSR_INVALID; goto stop_search; } handle_page_descriptor: if (descr_num) { /* if not root pointer */ /* check protection */ if ((descr_size==8) && (descr[0]&DESCR_S) && !super) { super_violation = true; } if (descr[0]&DESCR_WP) { write_protected = true; } if (!level && !super_violation) { /* set modified bit */ if (!(descr[0]&DESCR_M) && write && !write_protected) { descr[0] |= DESCR_M; descr_modified = true; } /* set updated bit */ if (!(descr[0]&DESCR_U)) { descr[0] |= DESCR_U; descr_modified = true; } /* write modified descriptor if necessary */ if (descr_modified) { desc_put_long(descr_addr[descr_num], descr[0]); } } /* update status bits */ mmu030.status |= super_violation ? MMUSR_SUPER_VIOLATION : 0; mmu030.status |= write_protected ? MMUSR_WRITE_PROTECTED : 0; /* check if caching is inhibited */ cache_inhibit = (descr[0]&DESCR_CI) ? CACHE_DISABLE_MMU : CACHE_ENABLE_ALL; /* check for the modified bit and set it in the status register */ mmu030.status |= (descr[0]&DESCR_M) ? MMUSR_MODIFIED : 0; } /* Check limit using next index field of logical address. * Limit is only checked on early termination. If we are * still at root pointer level, only check limit, if FCL * is disabled. */ if (early_termination) { if (descr_num || !(tc_030&TC_ENABLE_FCL)) { if (descr_size==8) { table_index = (addr&mmu030.translation.table[t].mask)>>mmu030.translation.table[t].shift; limit = (descr[0]&DESCR_LIMIT_MASK)>>16; if ((descr[0]&DESCR_LOWER_MASK) && (table_indexlimit)) { mmu030.status |= (MMUSR_LIMIT_VIOLATION|MMUSR_INVALID); #if MMU030_REG_DBG_MSG write_log(_T("Limit violation (upper limit %i)\n"),limit); #endif goto stop_search; } } } /* Get all unused bits of the logical address table index field. * they are added to the page address */ /* TODO: They should be added via "unsigned addition". How to? */ do { unused_fields_mask |= mmu030.translation.table[t].mask; t++; } while (t<=mmu030.translation.last_table); page_addr = addr&unused_fields_mask; #if MMU030_REG_DBG_MSG > 1 write_log(_T("Logical address unused bits: %08X (mask = %08X)\n"), page_addr,unused_fields_mask); #endif } /* Get page address */ addr_position = (descr_size==4) ? 0 : 1; page_addr += (descr[addr_position]&DESCR_PD_ADDR_MASK); #if MMU030_REG_DBG_MSG > 2 write_log(_T("Page at %08X\n"),page_addr); #endif // MMU030_REG_DBG_MSG stop_search: ; /* Make compiler happy */ } CATCH(prb) { /* We jump to this place, if a bus error occurred during table search. * bBusErrorReadWrite is set in m68000.c, M68000_BusError: read = 1 */ if (bBusErrorReadWrite) { descr_num--; } mmu030.status |= (MMUSR_BUS_ERROR|MMUSR_INVALID); write_log(_T("MMU: Bus error while %s descriptor!\n"), bBusErrorReadWrite?_T("reading"):_T("writing")); } ENDTRY; // Restore original supervisor state regs.s = old_s; /* check if we have to handle ptest */ if (level) { /* Note: wp, m and sv bits are undefined if the invalid bit is set */ mmu030.status = (mmu030.status&~MMUSR_NUM_LEVELS_MASK) | descr_num; /* If root pointer is page descriptor (descr_num 0), return 0 */ return descr_num ? descr_addr[descr_num] : 0; } /* Find an ATC entry to replace */ /* Search for invalid entry */ for (i=0; i 2 write_log(_T("ATC is full. Replacing entry %i\n"), i); #endif } if (i >= ATC030_NUM_ENTRIES) { i = 0; write_log (_T("ATC entry not found!!!\n")); } mmu030_atc_handle_history_bit(i); /* Create ATC entry */ mmu030.atc[i].logical.addr = addr & mmu030.translation.page.imask; /* delete page index bits */ mmu030.atc[i].logical.fc = fc; mmu030.atc[i].logical.valid = true; mmu030.atc[i].physical.addr = page_addr & mmu030.translation.page.imask; /* delete page index bits */ if ((mmu030.status&MMUSR_INVALID) || (mmu030.status&MMUSR_SUPER_VIOLATION)) { mmu030.atc[i].physical.bus_error = true; } else { mmu030.atc[i].physical.bus_error = false; } mmu030.atc[i].physical.cache_inhibit = cache_inhibit; mmu030.atc[i].physical.modified = (mmu030.status&MMUSR_MODIFIED) ? true : false; mmu030.atc[i].physical.write_protect = (mmu030.status&MMUSR_WRITE_PROTECTED) ? true : false; mmu030_flush_cache(mmu030.atc[i].logical.addr); #if MMU030_ATC_DBG_MSG > 1 write_log(_T("ATC create entry(%i): logical = %08X, physical = %08X, FC = %i\n"), i, mmu030.atc[i].logical.addr, mmu030.atc[i].physical.addr, mmu030.atc[i].logical.fc); write_log(_T("ATC create entry(%i): B = %i, CI = %i, WP = %i, M = %i\n"), i, mmu030.atc[i].physical.bus_error?1:0, mmu030.atc[i].physical.cache_inhibit?1:0, mmu030.atc[i].physical.write_protect?1:0, mmu030.atc[i].physical.modified?1:0); #endif // MMU030_ATC_DBG_MSG return 0; } /* This function is used for PTEST level 0. */ static void mmu030_ptest_atc_search(uaecptr logical_addr, uae_u32 fc, bool write) { int i; mmu030.status = 0; if (mmu030_match_ttr(logical_addr, fc, write)&TT_OK_MATCH) { mmu030.status |= MMUSR_TRANSP_ACCESS; return; } for (i = 0; i < ATC030_NUM_ENTRIES; i++) { if ((mmu030.atc[i].logical.fc == fc) && (mmu030.atc[i].logical.addr == logical_addr) && mmu030.atc[i].logical.valid) { break; } } if (i==ATC030_NUM_ENTRIES) { mmu030.status |= MMUSR_INVALID; return; } mmu030.status |= mmu030.atc[i].physical.bus_error ? (MMUSR_BUS_ERROR|MMUSR_INVALID) : 0; /* Note: write protect and modified bits are undefined if the invalid bit is set */ mmu030.status |= mmu030.atc[i].physical.write_protect ? MMUSR_WRITE_PROTECTED : 0; mmu030.status |= mmu030.atc[i].physical.modified ? MMUSR_MODIFIED : 0; } /* Address Translation Cache * * The ATC uses a pseudo-least-recently-used algorithm to keep track of * least recently used entries. They are replaced if the cache is full. * An internal history-bit (MRU-bit) is used to identify these entries. * If an entry is accessed, its history-bit is set to 1. If after that * there are no more entries with zero-bits, all other history-bits are * set to 0. When no more invalid entries are in the ATC, the first entry * with a zero-bit is replaced. * * * Logical Portion (28 bit): * oooo ---- xxxx xxxx xxxx xxxx xxxx xxxx * logical address (most significant 24 bit) * * oooo -xxx ---- ---- ---- ---- ---- ---- * function code * * oooo x--- ---- ---- ---- ---- ---- ---- * valid * * * Physical Portion (28 bit): * oooo ---- xxxx xxxx xxxx xxxx xxxx xxxx * physical address * * oooo ---x ---- ---- ---- ---- ---- ---- * modified * * oooo --x- ---- ---- ---- ---- ---- ---- * write protect * * oooo -x-- ---- ---- ---- ---- ---- ---- * cache inhibit * * oooo x--- ---- ---- ---- ---- ---- ---- * bus error * */ #define ATC030_MASK 0x0FFFFFFF #define ATC030_ADDR_MASK 0x00FFFFFF /* after masking shift 8 (<< 8) */ #define ATC030_LOG_FC 0x07000000 #define ATC030_LOG_V 0x08000000 #define ATC030_PHYS_M 0x01000000 #define ATC030_PHYS_WP 0x02000000 #define ATC030_PHYS_CI 0x04000000 #define ATC030_PHYS_BE 0x08000000 #if MMUDEBUG static void dump_opcode(uae_u16 opcode) { struct mnemolookup *lookup; struct instr *dp; char size = '_'; dp = table68k + opcode; if (dp->mnemo == i_ILLG) { dp = table68k + 0x4AFC; } for (lookup = lookuptab; lookup->mnemo != dp->mnemo; lookup++); if (!dp->unsized) { switch (dp->size) { case sz_byte: size = 'B'; break; case sz_word: size = 'W'; break; case sz_long: size = 'L'; break; } } write_log(_T(" %04x %s.%c"), opcode, lookup->name, size); } #endif static uae_u8 mmu030fixupreg(int i) { uae_u8 v = 0; #if MMU030_REG_FIXUP struct mmufixup *m = &mmufixup[i]; if (m->reg < 0) return v; if (!(m->reg & 0x300)) return v; v = m->reg & 7; v |= ((m->reg >> 10) & 3) << 3; // size if (m->reg & 0x200) // -(an)? v |= 1 << 5; v |= 1 << 6; #endif return v; } static void mmu030fixupmod(uae_u8 data, int dir, int idx) { #if MMU030_REG_FIXUP if (!data) return; int reg = data & 7; int adj = (data & (1 << 5)) ? -1 : 1; if (dir) adj = -adj; adj <<= (data >> 3) & 3; m68k_areg(regs, reg) += adj; if (idx >= 0) { struct mmufixup *m = &mmufixup[idx]; m->value += adj; } write_log("fixup %04x %d %d\n", mmu030_opcode & 0xffff, reg, adj); #endif } void mmu030_page_fault(uaecptr addr, bool read, int flags, uae_u32 fc) { if (flags < 0) { read = (regs.mmu_ssw & MMU030_SSW_RW) ? 1 : 0; fc = regs.mmu_ssw & MMU030_SSW_FC_MASK; flags = regs.mmu_ssw & ~(MMU030_SSW_FC | MMU030_SSW_RC | MMU030_SSW_FB | MMU030_SSW_RB | MMU030_SSW_RW | 7); } regs.wb3_status = 0; regs.wb2_status = 0; if (fc & 1) { regs.mmu_ssw = MMU030_SSW_DF | (MMU030_SSW_DF << 1); if (!(mmu030_state[1] & MMU030_STATEFLAG1_LASTWRITE)) { regs.wb2_status = mmu030fixupreg(0); mmu030fixupmod(regs.wb2_status, 0, 0); regs.wb3_status = mmu030fixupreg(1); mmu030fixupmod(regs.wb3_status, 0, 1); } } else { // only used by data fault but word sounds nice flags = MMU030_SSW_SIZE_W; if (currprefs.cpu_compatible) { regs.wb2_status = mmu030fixupreg(0); mmu030fixupmod(regs.wb2_status, 0, 0); regs.wb3_status = mmu030fixupreg(1); mmu030fixupmod(regs.wb3_status, 0, 1); regs.mmu_ssw = MMU030_SSW_FB | MMU030_SSW_RB; } else { regs.mmu_ssw = MMU030_SSW_FB | MMU030_SSW_RB; } } regs.mmu_ssw |= read ? MMU030_SSW_RW : 0; regs.mmu_ssw |= flags; regs.mmu_ssw |= fc; regs.mmu_ssw |= islrmw030 ? MMU030_SSW_RM : 0; regs.mmu_fault_addr = addr; // temporary store in 68040+ variables because stack frame creation may modify them. regs.wb3_data = mmu030_data_buffer_out; regs.wb2_address = mmu030_state[1]; bBusErrorReadWrite = read; mm030_stageb_address = addr; #if MMUDEBUG write_log(_T("MMU: %02x la=%08X SSW=%04x read=%d size=%d fc=%d pc=%08x ob=%08x"), (mmu030_state[1] & MMU030_STATEFLAG1_LASTWRITE) ? 0xa : 0xb, addr, regs.mmu_ssw, read, (flags & MMU030_SSW_SIZE_B) ? 1 : (flags & MMU030_SSW_SIZE_W) ? 2 : 4, fc, regs.instruction_pc, mmu030_data_buffer_out); dump_opcode(mmu030_opcode & 0xffff); if (regs.opcode != mmu030_opcode) dump_opcode(regs.opcode & 0xffff); write_log(_T("\n")); #endif ismoves030 = false; islrmw030 = false; #if 0 if (mmu030_state[1] & MMU030_STATEFLAG1_SUBACCESS0) write_log("!"); if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) write_log("!"); #endif THROW(2); } void mmu030_hardware_bus_error(uaecptr addr, uae_u32 v, bool read, bool ins, int size) { int flags = size == sz_byte ? MMU030_SSW_SIZE_B : (size == sz_word ? MMU030_SSW_SIZE_W : MMU030_SSW_SIZE_L); int fc; if (ismoves030) { fc = read ? regs.sfc : regs.dfc; } else { fc = (regs.s ? 4 : 0) | (ins ? 2 : 1); } if (!read) { mmu030_data_buffer_out = v; } else { flags |= MMU030_SSW_RW; } mmu030_page_fault(addr, read, flags, fc); } bool mmu030_is_super_access(bool read) { if (!ismoves030) { return regs.s; } else { uae_u32 fc = read ? regs.sfc : regs.dfc; return (fc & 4) != 0; } } static void mmu030_add_data_read_cache(uaecptr addr, uaecptr phys, uae_u32 fc) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (idx2 < MMUFASTCACHE_ENTRIES030 - 1) { atc_data_cache_read[idx2].log = idx1; atc_data_cache_read[idx2].phys = phys; atc_data_cache_read[idx2].cs = mmu030_cache_state; } #endif } static void mmu030_add_data_write_cache(uaecptr addr, uaecptr phys, uae_u32 fc) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (idx2 < MMUFASTCACHE_ENTRIES030 - 1) { atc_data_cache_write[idx2].log = idx1; atc_data_cache_write[idx2].phys = phys; atc_data_cache_write[idx2].cs = mmu030_cache_state; } #endif } static uaecptr mmu030_put_atc(uaecptr addr, int l, uae_u32 fc, uae_u32 size) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; #if MMU030_ATC_DBG_MSG > 1 write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error || mmu030.atc[l].physical.write_protect) { mmu030_page_fault(addr, false, size, fc); return 0; } mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; mmu030_add_data_write_cache(addr, physical_addr, fc); return physical_addr + page_index; } static uaecptr mmu030_get_atc(uaecptr addr, int l, uae_u32 fc, uae_u32 size) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; #if MMU030_ATC_DBG_MSG > 1 write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error) { mmu030_page_fault(addr, true, size, fc); return 0; } mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; mmu030_add_data_read_cache(addr, physical_addr, fc); return physical_addr + page_index; } static uaecptr mmu030_get_i_atc(uaecptr addr, int l, uae_u32 fc, uae_u32 size) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; #if MMU030_ATC_DBG_MSG > 1 write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error) { mmu030_page_fault(addr, true, size, fc); return 0; } #if MMU_IPAGECACHE030 mmu030.mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; #if MMU_DIRECT_ACCESS mmu030.mmu030_last_physical_address_real = get_real_address(physical_addr); #else mmu030.mmu030_last_physical_address = physical_addr; #endif mmu030.mmu030_last_logical_address = (addr & mmu030.translation.page.imask) | fc; #endif mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; return physical_addr + page_index; } /* Generic versions of above */ static uaecptr mmu030_put_atc_generic(uaecptr addr, int l, uae_u32 fc, int flags) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr & addr_mask; #if MMU030_ATC_DBG_MSG > 1 write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.write_protect || mmu030.atc[l].physical.bus_error) { mmu030_page_fault(addr, false, flags, fc); return 0; } mmu030_add_data_write_cache(addr, physical_addr, fc); return physical_addr + page_index; } static uae_u32 mmu030_get_atc_generic(uaecptr addr, int l, uae_u32 fc, int flags, bool checkwrite) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr & addr_mask; #if MMU030_ATC_DBG_MSG > 1 write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error || (checkwrite && mmu030.atc[l].physical.write_protect)) { mmu030_page_fault(addr, true, flags, fc); return 0; } mmu030_add_data_read_cache(addr, physical_addr, fc); return physical_addr + page_index; } /* This function checks if a certain logical address is in the ATC * by comparing the logical address and function code to the values * stored in the ATC entries. If a matching entry is found it sets * the history bit and returns the cache index of the entry. */ static int mmu030_logical_is_in_atc(uaecptr addr, uae_u32 fc, bool write) { uaecptr logical_addr = 0; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 maddr = addr & addr_mask; int offset = (maddr >> mmu030.translation.page.size) & 0x1f; int i, index; index = atcindextable[offset]; for (i=0; i= ATC030_NUM_ENTRIES) index = 0; } return -1; } /* Memory access functions: * If the address matches one of the transparent translation registers * use it directly as physical address, else check ATC for the * logical address. If the logical address is not resident in the ATC * create a new ATC entry and then look up the physical address. */ STATIC_INLINE void cacheablecheck(uaecptr addr) { if (mmu030_cache_state == CACHE_ENABLE_ALL) { // MMU didn't inhibit caches, use hardware cache state mmu030_cache_state = ce_cachable[addr >> 16]; } } void mmu030_put_long(uaecptr addr, uae_u32 val, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu030.translation.page.mask); mmu030_cache_state = atc_data_cache_write[idx2].cs; } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_L); } else { mmu030_table_search(addr,fc,true,0); addr = mmu030_put_atc(addr, mmu030_logical_is_in_atc(addr,fc,true), fc, MMU030_SSW_SIZE_L); } } } cacheablecheck(addr); x_phys_put_long(addr,val); } void mmu030_put_word(uaecptr addr, uae_u16 val, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu030.translation.page.mask); mmu030_cache_state = atc_data_cache_write[idx2].cs; } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_W); } else { mmu030_table_search(addr, fc, true, 0); addr = mmu030_put_atc(addr, mmu030_logical_is_in_atc(addr,fc,true), fc, MMU030_SSW_SIZE_W); } } } cacheablecheck(addr); x_phys_put_word(addr,val); } void mmu030_put_byte(uaecptr addr, uae_u8 val, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu030.translation.page.mask); mmu030_cache_state = atc_data_cache_write[idx2].cs; } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_B); } else { mmu030_table_search(addr, fc, true, 0); addr = mmu030_put_atc(addr, mmu030_logical_is_in_atc(addr,fc,true), fc, MMU030_SSW_SIZE_B); } } } cacheablecheck(addr); x_phys_put_byte(addr,val); } uae_u32 mmu030_get_long(uaecptr addr, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu030.translation.page.mask); mmu030_cache_state = atc_data_cache_read[idx2].cs; } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_L); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_atc(addr, mmu030_logical_is_in_atc(addr,fc,false), fc, MMU030_SSW_SIZE_L); } } } cacheablecheck(addr); uae_u32 v = x_phys_get_long(addr); return v; } uae_u16 mmu030_get_word(uaecptr addr, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu030.translation.page.mask); mmu030_cache_state = atc_data_cache_read[idx2].cs; } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_W); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_atc(addr, mmu030_logical_is_in_atc(addr,fc,false), fc, MMU030_SSW_SIZE_W); } } } cacheablecheck(addr); uae_u16 v = x_phys_get_word(addr); return v; } uae_u8 mmu030_get_byte(uaecptr addr, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu030.translation.page.mask); mmu030_cache_state = atc_data_cache_read[idx2].cs; } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_B); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_atc(addr, mmu030_logical_is_in_atc(addr,fc,false), fc, MMU030_SSW_SIZE_B); } } } cacheablecheck(addr); uae_u8 v = x_phys_get_byte(addr); return v; } uae_u32 mmu030_get_ilong(uaecptr addr, uae_u32 fc) { uae_u32 v; #if MMU_IPAGECACHE030 if (((addr & mmu030.translation.page.imask) | fc) == mmu030.mmu030_last_logical_address) { #if MMU_DIRECT_ACCESS uae_u8 *p = &mmu030.mmu030_last_physical_address_real[addr & mmu030.translation.page.mask]; return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3]); #else mmu030_cache_state = mmu030.mmu030_cache_state; v = x_phys_get_ilong(mmu030.mmu030_last_physical_address + (addr & mmu030.translation.page.mask)); return v; #endif } mmu030.mmu030_last_logical_address = 0xffffffff; #endif mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr, fc, false)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num >= 0) { addr = mmu030_get_i_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_L); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_i_atc(addr, mmu030_logical_is_in_atc(addr, fc, false), fc, MMU030_SSW_SIZE_L); } } cacheablecheck(addr); v = x_phys_get_ilong(addr); return v; } uae_u16 mmu030_get_iword(uaecptr addr, uae_u32 fc) { uae_u16 v; #if MMU_IPAGECACHE030 if (((addr & mmu030.translation.page.imask) | fc) == mmu030.mmu030_last_logical_address) { #if MMU_DIRECT_ACCESS uae_u8 *p = &mmu030.mmu030_last_physical_address_real[addr & mmu030.translation.page.mask]; return (p[0] << 8) | p[1]; #else mmu030_cache_state = mmu030.mmu030_cache_state; v = x_phys_get_iword(mmu030.mmu030_last_physical_address + (addr & mmu030.translation.page.mask)); return v; #endif } mmu030.mmu030_last_logical_address = 0xffffffff; #endif mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr, fc, false)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num >= 0) { addr = mmu030_get_i_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_W); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_i_atc(addr, mmu030_logical_is_in_atc(addr, fc, false), fc, MMU030_SSW_SIZE_W); } } cacheablecheck(addr); v = x_phys_get_iword(addr); return v; } /* Not commonly used access function */ static void mmu030_put_generic_lrmw(uaecptr addr, uae_u32 val, uae_u32 fc, int size, int flags) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_lrmw_ttr_access(addr,fc)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } else { mmu030_table_search(addr, fc, true, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } } cacheablecheck(addr); if (size == sz_byte) x_phys_put_byte(addr, val); else if (size == sz_word) x_phys_put_word(addr, val); else x_phys_put_long(addr, val); } void mmu030_put_generic(uaecptr addr, uae_u32 val, uae_u32 fc, int size, int flags) { mmu030_cache_state = CACHE_ENABLE_ALL; if (flags & MMU030_SSW_RM) { mmu030_put_generic_lrmw(addr, val, fc, size, flags); return; } if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } else { mmu030_table_search(addr, fc, true, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } } cacheablecheck(addr); if (size == sz_byte) x_phys_put_byte(addr, val); else if (size == sz_word) x_phys_put_word(addr, val); else x_phys_put_long(addr, val); } static uae_u32 mmu030_get_generic_lrmw(uaecptr addr, uae_u32 fc, int size, int flags) { uae_u32 v; mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_lrmw_ttr_access(addr,fc)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, true); } else { mmu030_table_search(addr, fc, true, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, true); } } cacheablecheck(addr); if (size == sz_byte) v = x_phys_get_byte(addr); else if (size == sz_word) v = x_phys_get_word(addr); else v = x_phys_get_long(addr); return v; } uae_u32 mmu030_get_generic(uaecptr addr, uae_u32 fc, int size, int flags) { uae_u32 v; mmu030_cache_state = CACHE_ENABLE_ALL; if (flags & MMU030_SSW_RM) { return mmu030_get_generic_lrmw(addr, fc, size, flags); } if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, false); } else { mmu030_table_search(addr, fc, false, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, false); } } cacheablecheck(addr); if (size == sz_byte) v = x_phys_get_byte(addr); else if (size == sz_word) v = x_phys_get_word(addr); else v = x_phys_get_long(addr); return v; } uae_u8 uae_mmu030_check_fc(uaecptr addr, bool write, uae_u32 size) { uae_u32 fc = regs.fc030; mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,write)) && mmu030.enabled) { uae_u32 flags = mmu030_size[size]; int atc_line_num = mmu030_logical_is_in_atc(addr, fc, write); if (atc_line_num>=0) { addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, write); } else { mmu030_table_search(addr, fc, write, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, write); addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, false); } } // MMU inhibited if (mmu030_cache_state != CACHE_ENABLE_ALL) return mmu030_cache_state; return ce_cachable[addr >> 16]; } /* Locked RMW is rarely used */ static uae_u32 uae_mmu030_get_lrmw_fcx(uaecptr addr, int size, int fc) { if (size == sz_byte) { return mmu030_get_generic(addr, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_B); } else if (size == sz_word) { if (unlikely(is_unaligned_bus(addr, 2))) return mmu030_get_word_unaligned(addr, fc, MMU030_SSW_RM); else return mmu030_get_generic(addr, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_W); } else { if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_long_unaligned(addr, fc, MMU030_SSW_RM); else return mmu030_get_generic(addr, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_L); } } uae_u32 uae_mmu030_get_lrmw(uaecptr addr, int size) { uae_u32 fc = (regs.s ? 4 : 0) | 1; islrmw030 = true; uae_u32 v = uae_mmu030_get_lrmw_fcx(addr, size, fc); islrmw030 = false; return v; } static void uae_mmu030_put_lrmw_fcx(uaecptr addr, uae_u32 val, int size, int fc) { if (size == sz_byte) { mmu030_put_generic(addr, val, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_B); } else if (size == sz_word) { if (unlikely(is_unaligned_bus(addr, 2))) mmu030_put_word_unaligned(addr, val, fc, MMU030_SSW_RM); else mmu030_put_generic(addr, val, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_W); } else { if (unlikely(is_unaligned_bus(addr, 4))) mmu030_put_long_unaligned(addr, val, fc, MMU030_SSW_RM); else mmu030_put_generic(addr, val, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_L); } } void uae_mmu030_put_lrmw(uaecptr addr, uae_u32 val, int size) { uae_u32 fc = (regs.s ? 4 : 0) | 1; islrmw030 = true; uae_mmu030_put_lrmw_fcx(addr, val, size, fc); islrmw030 = false; } uae_u32 REGPARAM2 mmu030_get_ilong_unaligned(uaecptr addr, uae_u32 fc, int flags) { uae_u32 res; res = (uae_u32)mmu030_get_iword(addr, fc) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu030_get_iword(addr + 2, fc); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; THROW_AGAIN(prb); } ENDTRY return res; } static void unalign_init(uaecptr addr, bool l, bool l2) { if (l2) mmu030_state[1] |= MMU030_STATEFLAG1_SUBACCESSX; if (l) mmu030_state[1] |= MMU030_STATEFLAG1_SUBACCESSL; mmu030_state[1] |= MMU030_STATEFLAG1_SUBACCESS0; #if MMU030_DEBUG > 1 write_log(_T("unalign_init %08x %08x %d %d\n"), addr, mmu030_state[1], l, l2); #endif } static void unalign_set(int state) { mmu030_state[1] |= (1 << state) << (MMU030_STATEFLAG1_SUBACCESS_SHIFT + 1); #if MMU030_DEBUG > 1 write_log(_T("unalign_set %d %08x\n"), state, mmu030_state[1]); #endif } static void unalign_clear(void) { #if MMU030_DEBUG > 1 write_log(_T("unalign_clear %08x %08x\n"), mmu030_state[1], mmu030_data_buffer_out); #endif mmu030_state[1] &= ~(MMU030_STATEFLAG1_SUBACCESSL | MMU030_STATEFLAG1_SUBACCESSX | MMU030_STATEFLAG1_SUBACCESS0 | MMU030_STATEFLAG1_SUBACCESS1 | MMU030_STATEFLAG1_SUBACCESS2 | MMU030_STATEFLAG1_SUBACCESS3); } uae_u16 REGPARAM2 mmu030_get_word_unaligned(uaecptr addr, uae_u32 fc, int flags) { unalign_init(addr, false, false); mmu030_data_buffer_out = mmu030_get_generic(addr, fc, sz_byte, flags | MMU030_SSW_SIZE_W) << 8; unalign_set(0); mmu030_data_buffer_out |= mmu030_get_generic(addr + 1, fc, sz_byte, flags | MMU030_SSW_SIZE_B); unalign_clear(); return mmu030_data_buffer_out; } uae_u32 REGPARAM2 mmu030_get_long_unaligned(uaecptr addr, uae_u32 fc, int flags) { if (likely(!(addr & 1))) { unalign_init(addr, true, false); mmu030_data_buffer_out = mmu030_get_generic(addr, fc, sz_word, flags | MMU030_SSW_SIZE_L) << 16; unalign_set(0); mmu030_data_buffer_out |= mmu030_get_generic(addr + 2, fc, sz_word, flags | MMU030_SSW_SIZE_W); } else { unalign_init(addr, true, true); mmu030_data_buffer_out = mmu030_get_generic(addr, fc, sz_byte, flags | MMU030_SSW_SIZE_L) << 24; unalign_set(0); mmu030_data_buffer_out |= mmu030_get_generic(addr + 1, fc, sz_word, flags | MMU030_SSW_SIZE_W) << 8; unalign_set(1); mmu030_data_buffer_out |= mmu030_get_generic(addr + 3, fc, sz_byte, flags | MMU030_SSW_SIZE_B); } unalign_clear(); return mmu030_data_buffer_out; } void REGPARAM2 mmu030_put_long_unaligned(uaecptr addr, uae_u32 val, uae_u32 fc, int flags) { if (likely(!(addr & 1))) { unalign_init(addr, true, false); mmu030_put_generic(addr, val >> 16, fc, sz_word, flags | MMU030_SSW_SIZE_L); unalign_set(0); mmu030_put_generic(addr + 2, val, fc, sz_word, flags | MMU030_SSW_SIZE_W); } else { unalign_init(addr, true, true); mmu030_put_generic(addr, val >> 24, fc, sz_byte, flags | MMU030_SSW_SIZE_L); unalign_set(0); mmu030_put_generic(addr + 1, val >> 8, fc, sz_word, flags | MMU030_SSW_SIZE_W); unalign_set(1); mmu030_put_generic(addr + 3, val, fc, sz_byte, flags | MMU030_SSW_SIZE_B); } unalign_clear(); } void REGPARAM2 mmu030_put_word_unaligned(uaecptr addr, uae_u16 val, uae_u32 fc, int flags) { unalign_init(addr, false, false); mmu030_put_generic(addr, val >> 8, fc, sz_byte, flags | MMU030_SSW_SIZE_W); unalign_set(0); mmu030_put_generic(addr + 1, val, fc, sz_byte, flags | MMU030_SSW_SIZE_B); unalign_clear(); } /* Used by debugger */ static uaecptr mmu030_get_addr_atc(uaecptr addr, int l, uae_u32 fc, bool write) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; physical_addr += page_index; if (mmu030.atc[l].physical.bus_error || (write && mmu030.atc[l].physical.write_protect)) { mmu030_page_fault(addr, write == 0, MMU030_SSW_SIZE_B, fc); return 0; } return physical_addr; } uaecptr mmu030_translate(uaecptr addr, bool super, bool data, bool write) { int fc = (super ? 4 : 0) | (data ? 1 : 2); if ((fc==7) || (mmu030_match_ttr(addr,fc,write)&TT_OK_MATCH) || (!mmu030.enabled)) { return addr; } int atc_line_num = mmu030_logical_is_in_atc(addr, fc, write); if (atc_line_num>=0) { return mmu030_get_addr_atc(addr, atc_line_num, fc, write); } else { mmu030_table_search(addr, fc, false, 0); return mmu030_get_addr_atc(addr, mmu030_logical_is_in_atc(addr,fc,write), fc, write); } } /* MMU Reset */ void mmu030_reset(int hardreset) { if (!savestate_state) { /* A CPU reset causes the E-bits of TC and TT registers to be zeroed. */ mmu030.enabled = false; #if MMU_IPAGECACHE030 mmu030.mmu030_last_logical_address = 0xffffffff; #endif regs.mmu_page_size = 0; if (hardreset >= 0) { tc_030 &= ~TC_ENABLE_TRANSLATION; tt0_030 &= ~TT_ENABLE; tt1_030 &= ~TT_ENABLE; } if (hardreset > 0) { srp_030 = crp_030 = 0; tt0_030 = tt1_030 = tc_030 = 0; mmusr_030 = 0; mmu030_flush_atc_all(); } } mmu030_set_funcs(); } void mmu030_set_funcs(void) { if (currprefs.mmu_model != 68030) return; if (currprefs.cpu_memory_cycle_exact) { x_phys_get_iword = mem_access_delay_wordi_read_ce020; x_phys_get_ilong = mem_access_delay_longi_read_ce020; x_phys_get_byte = mem_access_delay_byte_read_ce020; x_phys_get_word = mem_access_delay_word_read_ce020; x_phys_get_long = mem_access_delay_long_read_ce020; x_phys_put_byte = mem_access_delay_byte_write_ce020; x_phys_put_word = mem_access_delay_word_write_ce020; x_phys_put_long = mem_access_delay_long_write_ce020; } else { x_phys_get_iword = phys_get_word; x_phys_get_ilong = phys_get_long; x_phys_get_byte = phys_get_byte; x_phys_get_word = phys_get_word; x_phys_get_long = phys_get_long; x_phys_put_byte = phys_put_byte; x_phys_put_word = phys_put_word; x_phys_put_long = phys_put_long; } } #define unalign_done(f) \ st |= f; \ mmu030_state[1] = st; typedef uae_u32(*unaligned_read_func)(uaecptr addr, uae_u32 fc, int size, int flags); static void mmu030_unaligned_read_continue(uaecptr addr, int fc, unaligned_read_func func) { uae_u32 st = mmu030_state[1]; #if MMUDEBUG write_log(_T("unaligned_read_continue_s: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif if (st & MMU030_STATEFLAG1_SUBACCESSL) { if (st & MMU030_STATEFLAG1_SUBACCESSX) { // odd long access: byte + word + byte if (!(st & MMU030_STATEFLAG1_SUBACCESS1)) { mmu030_data_buffer_out &= 0x00ffffff; mmu030_data_buffer_out |= func(addr, fc, sz_byte, MMU030_SSW_SIZE_L) << 24; #if MMUDEBUG write_log(_T("unaligned_read_continue_0: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS1); addr++; } if (!(st & MMU030_STATEFLAG1_SUBACCESS2)) { mmu030_data_buffer_out &= 0xff0000ff; mmu030_data_buffer_out |= func(addr, fc, sz_word, MMU030_SSW_SIZE_W) << 8; #if MMUDEBUG write_log(_T("unaligned_read_continue_1: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS2); addr += 2; } if (!(st & MMU030_STATEFLAG1_SUBACCESS3)) { mmu030_data_buffer_out &= 0xffffff00; mmu030_data_buffer_out |= func(addr, fc, sz_byte, MMU030_SSW_SIZE_B) << 0; unalign_done(MMU030_STATEFLAG1_SUBACCESS3); addr++; } } else { // even but unaligned long access: word + word if (!(st & MMU030_STATEFLAG1_SUBACCESS1)) { mmu030_data_buffer_out &= 0x0000ffff; mmu030_data_buffer_out |= func(addr, fc, sz_word, MMU030_SSW_SIZE_L) << 16; #if MMUDEBUG write_log(_T("unaligned_read_continue_0: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS1); addr += 2; } if (!(st & MMU030_STATEFLAG1_SUBACCESS2)) { mmu030_data_buffer_out &= 0xffff0000; mmu030_data_buffer_out |= func(addr, fc, sz_word, MMU030_SSW_SIZE_W) << 0; unalign_done(MMU030_STATEFLAG1_SUBACCESS2); addr += 2; } } } else { // odd word access: byte + byte if (!(st & MMU030_STATEFLAG1_SUBACCESS1)) { mmu030_data_buffer_out &= 0x00ff; mmu030_data_buffer_out |= func(addr, fc, sz_byte, MMU030_SSW_SIZE_W) << 8; #if MMUDEBUG write_log(_T("unaligned_read_continue_0: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS1); addr++; } if (!(st & MMU030_STATEFLAG1_SUBACCESS2)) { mmu030_data_buffer_out &= 0xff00; mmu030_data_buffer_out |= func(addr, fc, sz_byte, MMU030_SSW_SIZE_B) << 0; unalign_done(MMU030_STATEFLAG1_SUBACCESS2); addr++; } } #if MMUDEBUG write_log(_T("unaligned_read_continue_e: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_clear(); } typedef void (*unaligned_write_func)(uaecptr addr, uae_u32 val, uae_u32 fc, int size, int flags); static void mmu030_unaligned_write_continue(uaecptr addr, int fc, unaligned_write_func func) { uae_u32 st = mmu030_state[1]; #if MMUDEBUG write_log(_T("unaligned_write_continue_s: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif if (st & MMU030_STATEFLAG1_SUBACCESSL) { // odd long access: byte + word + byte if (st & MMU030_STATEFLAG1_SUBACCESSX) { if (!(st & MMU030_STATEFLAG1_SUBACCESS1)) { func(addr, mmu030_data_buffer_out >> 24, fc, sz_byte, MMU030_SSW_SIZE_L); #if MMUDEBUG write_log(_T("unaligned_write_continue_0: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS1); addr++; } if (!(st & MMU030_STATEFLAG1_SUBACCESS2)) { func(addr, mmu030_data_buffer_out >> 8, fc, sz_word, MMU030_SSW_SIZE_W); #if MMUDEBUG write_log(_T("unaligned_write_continue_1: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS2); addr += 2; } if (!(st & MMU030_STATEFLAG1_SUBACCESS3)) { func(addr, mmu030_data_buffer_out >> 0, fc, sz_byte, MMU030_SSW_SIZE_B); #if MMUDEBUG write_log(_T("unaligned_write_continue_2: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS3); addr++; } } else { // even but unaligned long access: word + word if (!(st & MMU030_STATEFLAG1_SUBACCESS1)) { func(addr, mmu030_data_buffer_out >> 16, fc, sz_word, MMU030_SSW_SIZE_L); #if MMUDEBUG write_log(_T("unaligned_write_continue_0: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS1); addr += 2; } if (!(st & MMU030_STATEFLAG1_SUBACCESS2)) { func(addr, mmu030_data_buffer_out >> 0, fc, sz_word, MMU030_SSW_SIZE_W); unalign_done(MMU030_STATEFLAG1_SUBACCESS2); addr += 2; } } } else { // odd word access: byte + byte if (!(st & MMU030_STATEFLAG1_SUBACCESS1)) { func(addr, mmu030_data_buffer_out >> 8, fc, sz_byte, MMU030_SSW_SIZE_W); #if MMUDEBUG write_log(_T("unaligned_write_continue_0: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_done(MMU030_STATEFLAG1_SUBACCESS1); addr++; } if (!(st & MMU030_STATEFLAG1_SUBACCESS2)) { func(addr, mmu030_data_buffer_out >> 0, fc, sz_byte, MMU030_SSW_SIZE_B); unalign_done(MMU030_STATEFLAG1_SUBACCESS2); addr++; } } #if MMUDEBUG write_log(_T("unaligned_write_continue_e: %08x %d %08x %08x\n"), addr, fc, mmu030_data_buffer_out, st); #endif unalign_clear(); } void m68k_do_rte_mmu030 (uaecptr a7) { struct mmu030_access mmu030_ad_v[MAX_MMU030_ACCESS + 1]; // Restore access error exception state uae_u16 sr = get_word_mmu030(a7); uae_u32 pc = get_long_mmu030(a7 + 2); uae_u16 format = get_word_mmu030(a7 + 6); uae_u16 frame = format >> 12; uae_u16 ssw = get_word_mmu030(a7 + 10); uae_u32 fault_addr = get_long_mmu030(a7 + 16); // Data output buffer uae_u32 mmu030_data_buffer_out_v = get_long_mmu030(a7 + 0x18); // Internal register, our opcode storage area uae_u32 oc = get_long_mmu030(a7 + 0x14); int idxsize = -1, idxsize_done = -1; // Fetch last word, real CPU does it to allow OS bus handler to map // the page if frame crosses pages and following page is not resident. if (frame == 0xb) get_word_mmu030(a7 + 92 - 2); else get_word_mmu030(a7 + 32 - 2); // Rerun "mmu030_opcode" using restored state. mmu030_retry = true; if (frame == 0xa) { // this is always last write data write fault uae_u32 mmu030_state_1 = get_word_mmu030(a7 + 0x8); #if MMU030_DEBUG if (!(mmu030_state_1 & MMU030_STATEFLAG1_LASTWRITE)) { write_log(_T("68030 MMU short bus fault but no lastwrite set!?\n")); } if (ssw & (MMU030_SSW_FB | MMU030_SSW_FC | MMU030_SSW_FB | MMU030_SSW_RC | MMU030_SSW_RB)) { write_log(_T("68030 MMU short bus fault and pipeline fault?\n")); } if (ssw & MMU030_SSW_RW) { write_log(_T("68030 MMU short bus fault but read fault!?\n")); } #endif // did we have data fault but DF bit cleared? if (ssw & (MMU030_SSW_DF << 1) && !(ssw & MMU030_SSW_DF)) { // DF not set: mark access as done unalign_clear(); } mmu030_data_buffer_out = mmu030_data_buffer_out_v; mmu030_state[0] = 0; mmu030_state[1] = mmu030_state_1; mmu030_state[2] = 0; mmu030_opcode = oc; mmu030_idx = mmu030_idx_done = 0; m68k_areg(regs, 7) += 32; } else if (frame == 0xb) { // get_disp_ea_020 uae_u32 mmu030_disp_store_0 = get_long_mmu030(a7 + 0x1c); uae_u32 mmu030_disp_store_1 = get_long_mmu030(a7 + 0x1c + 4); // Internal register, misc flags uae_u32 ps = get_long_mmu030(a7 + 0x28); // Data buffer uae_u32 mmu030_data_buffer_in_v = get_long_mmu030(a7 + 0x2c); // Misc state data uae_u32 mmu030_state_0 = get_word_mmu030(a7 + 0x30); uae_u32 mmu030_state_1 = get_word_mmu030(a7 + 0x32); uae_u32 mmu030_state_2 = get_word_mmu030(a7 + 0x34); uae_u32 mmu030_opcode_v = (ps & 0x80000000) ? 0xffffffff : (oc & 0xffff); uae_u32 mmu030_fmovem_store_0 = 0; uae_u32 mmu030_fmovem_store_1 = 0; if (mmu030_state[1] & MMU030_STATEFLAG1_FMOVEM) { mmu030_fmovem_store_0 = get_long_mmu030(a7 + 0x5c - (7 + 1) * 4); mmu030_fmovem_store_1 = get_long_mmu030(a7 + 0x5c - (8 + 1) * 4); } uae_u16 v = get_word_mmu030(a7 + 0x36); idxsize = v & 0x0f; idxsize_done = (v >> 4) & 0x0f; for (int i = 0; i < idxsize_done + 1; i++) { mmu030_ad_v[i].val = get_long_mmu030(a7 + 0x5c - (i + 1) * 4); } regs.wb2_status = v >> 8; regs.wb3_status = mmu030_state_2 >> 8; mmu030fixupmod(regs.wb2_status, 1, -1); mmu030fixupmod(regs.wb3_status, 1, -1); // did we have data fault but DF bit cleared? if (ssw & (MMU030_SSW_DF << 1) && !(ssw & MMU030_SSW_DF)) { // DF not set: mark access as done mmu030_data_buffer_out_v = mmu030_data_buffer_in_v; if (ssw & MMU030_SSW_RM) { // Read-Modify-Write: whole instruction is considered done write_log(_T("Read-Modify-Write and DF bit cleared! PC=%08x\n"), regs.instruction_pc); mmu030_retry = false; } else if (mmu030_state_1 & MMU030_STATEFLAG1_MOVEM1) { // if movem, skip next move mmu030_state_1 |= MMU030_STATEFLAG1_MOVEM2; } else { if (ssw & MMU030_SSW_RW) { // Read and no DF: use value in data input buffer mmu030_ad_v[idxsize_done].val = mmu030_data_buffer_in_v; } // else: use value idxsize_done that was saved from regs.wb3_data; idxsize_done++; } unalign_clear(); } // did we have ins fault and RB bit cleared? if ((ssw & MMU030_SSW_FB) && !(ssw & MMU030_SSW_RB)) { uae_u16 stageb = get_word_mmu030(a7 + 0x0e); if (mmu030_opcode_v == 0xffffffff) { mmu030_opcode_stageb = stageb; write_log(_T("Software fixed stage B! opcode = %04x\n"), stageb); } else { mmu030_ad_v[idxsize_done].val = stageb; idxsize_done++; write_log(_T("Software fixed stage B! opcode = %04X, opword = %04x\n"), mmu030_opcode_v, stageb); } } #if MMU030_DEBUG if (mmu030_state_1 & MMU030_STATEFLAG1_LASTWRITE) { write_log(_T("68030 MMU long bus fault but lastwrite set!?\n")); } #endif // Retried data access is the only memory access that can be done after this. // restore global state variables mmu030_opcode = mmu030_opcode_v; mmu030_state[0] = mmu030_state_0; mmu030_state[1] = mmu030_state_1; mmu030_state[2] = mmu030_state_2; mmu030_disp_store[0] = mmu030_disp_store_0; mmu030_disp_store[1] = mmu030_disp_store_1; mmu030_fmovem_store[0] = mmu030_fmovem_store_0; mmu030_fmovem_store[1] = mmu030_fmovem_store_1; mmu030_data_buffer_out = mmu030_data_buffer_out_v; mmu030_idx = idxsize; mmu030_idx_done = idxsize_done; for (int i = 0; i < idxsize_done + 1; i++) { mmu030_ad[i].val = mmu030_ad_v[i].val; } m68k_areg(regs, 7) += 92; } regs.sr = sr; MakeFromSR_T0(); if (pc & 1) { exception3_read_prefetch(0x4E73, pc); return; } m68k_setpci(pc); if ((ssw & MMU030_SSW_DF) && (ssw & MMU030_SSW_RM)) { // Locked-Read-Modify-Write restarts whole instruction. idxsize_done = 0; } else if (ssw & MMU030_SSW_DF) { // retry faulted access uaecptr addr = fault_addr; bool read = (ssw & MMU030_SSW_RW) != 0; int size = (ssw & MMU030_SSW_SIZE_B) ? sz_byte : ((ssw & MMU030_SSW_SIZE_W) ? sz_word : sz_long); int fc = ssw & MMU030_SSW_FC_MASK; #if MMU030_DEBUG if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM2) { write_log(_T("68030 MMU MOVEM %04x retry but MMU030_STATEFLAG1_MOVEM2 was already set!?\n"), mmu030_opcode); } } #endif #if MMU030_DEBUG write_log(_T("%08x %08x %08x %08x %08x %d %d %d %08x %08x %04x\n"), mmu030_state[1], mmu030_state[2], mmu030_disp_store[0], mmu030_disp_store[1], addr, read, size, fc, mmu030_data_buffer_out, idxsize < 0 ? -1 : mmu030_ad[idxsize].val, ssw); #endif if (read) { if (mmu030_state[1] & MMU030_STATEFLAG1_SUBACCESS0) { mmu030_unaligned_read_continue(addr, fc, mmu030_get_generic); } else { switch (size) { case sz_byte: mmu030_data_buffer_out = uae_mmu030_get_byte_fcx(addr, fc); break; case sz_word: mmu030_data_buffer_out = uae_mmu030_get_word_fcx(addr, fc); break; case sz_long: mmu030_data_buffer_out = uae_mmu030_get_long_fcx(addr, fc); break; } } if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM2; } else if (idxsize >= 0) { mmu030_ad[mmu030_idx_done].val = mmu030_data_buffer_out; mmu030_idx_done++; } } else { if (mmu030_state[1] & MMU030_STATEFLAG1_SUBACCESS0) { mmu030_unaligned_write_continue(addr, fc, mmu030_put_generic); } else { switch (size) { case sz_byte: uae_mmu030_put_byte_fcx(addr, mmu030_data_buffer_out, fc); break; case sz_word: uae_mmu030_put_word_fcx(addr, mmu030_data_buffer_out, fc); break; case sz_long: uae_mmu030_put_long_fcx(addr, mmu030_data_buffer_out, fc); break; } } if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM2; } else if (idxsize >= 0) { mmu030_idx_done++; } } #if MMU030_DEBUG if (mmu030_idx >= MAX_MMU030_ACCESS) { write_log(_T("mmu030_idx (RTE) out of bounds! %d >= %d\n"), mmu030_idx, MAX_MMU030_ACCESS); } #endif } if (mmu030_state[1] & MMU030_STATEFLAG1_LASTWRITE) { mmu030_retry = false; } } void flush_mmu030 (uaecptr addr, int n) { } void m68k_do_rts_mmu030 (void) { m68k_setpc (get_long_mmu030_state (m68k_areg (regs, 7))); m68k_areg (regs, 7) += 4; } void m68k_do_bsr_mmu030 (uaecptr oldpc, uae_s32 offset) { put_long_mmu030_state (m68k_areg (regs, 7) - 4, oldpc); m68k_areg (regs, 7) -= 4; m68k_incpci (offset); } uae_u32 REGPARAM2 get_disp_ea_020_mmu030 (uae_u32 base, int idx) { uae_u16 dp; int reg; uae_u32 v; int oldidx; int pcadd = 0; // we need to do this hack here because in worst case we don't have enough // stack frame space to store two very large 020 addressing mode access state // + whatever the instruction itself does. if (mmu030_state[1] & (1 << idx)) { m68k_incpci (((mmu030_state[2] >> (idx * 4)) & 15) * 2); return mmu030_disp_store[idx]; } oldidx = mmu030_idx; dp = next_iword_mmu030_state (); pcadd += 1; reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) next_iword_mmu030_state (); pcadd += 1; } if ((dp & 0x30) == 0x30) { base += next_ilong_mmu030_state (); pcadd += 2; } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) next_iword_mmu030_state (); pcadd += 1; } if ((dp & 0x3) == 0x3) { outer = next_ilong_mmu030_state (); pcadd += 2; } if ((dp & 0x4) == 0) { base += regd; } if (dp & 0x3) { base = get_long_mmu030_state (base); } if (dp & 0x4) { base += regd; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } mmu030_state[1] |= 1 << idx; mmu030_state[2] |= pcadd << (idx * 4); mmu030_disp_store[idx] = v; mmu030_idx = oldidx; mmu030_idx_done = oldidx; return v; } // cache void m68k_do_rts_mmu030c (void) { m68k_setpc (get_long_mmu030c_state (m68k_areg (regs, 7))); m68k_areg (regs, 7) += 4; } void m68k_do_bsr_mmu030c (uaecptr oldpc, uae_s32 offset) { put_long_mmu030c_state (m68k_areg (regs, 7) - 4, oldpc); m68k_areg (regs, 7) -= 4; m68k_incpci (offset); } uae_u32 REGPARAM2 get_disp_ea_020_mmu030c (uae_u32 base, int idx) { uae_u16 dp; int reg; uae_u32 v; int oldidx; int pcadd = 0; // we need to do this hack here because in worst case we don't have enough // stack frame space to store two very large 020 addressing mode access state // + whatever the instruction itself does. if (mmu030_state[1] & (1 << idx)) { m68k_incpci(((mmu030_state[2] >> (idx * 4)) & 15) * 2); return mmu030_disp_store[idx]; } oldidx = mmu030_idx; dp = next_iword_mmu030c_state(); pcadd += 1; reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) next_iword_mmu030c_state(); pcadd += 1; } if ((dp & 0x30) == 0x30) { base += next_ilong_mmu030c_state(); pcadd += 2; } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) next_iword_mmu030c_state(); pcadd += 1; } if ((dp & 0x3) == 0x3) { outer = next_ilong_mmu030c_state(); pcadd += 2; } if ((dp & 0x4) == 0) { base += regd; } if (dp & 0x3) { base = get_long_mmu030c_state(base); } if (dp & 0x4) { base += regd; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } mmu030_state[1] |= 1 << idx; mmu030_state[2] |= pcadd << (idx * 4); mmu030_disp_store[idx] = v; mmu030_idx = oldidx; mmu030_idx_done = oldidx; return v; } void m68k_do_rte_mmu030c (uaecptr a7) { struct mmu030_access mmu030_ad_v[MAX_MMU030_ACCESS + 1]; // Restore access error exception state uae_u16 sr = get_word_mmu030c(a7); uae_u32 pc = get_long_mmu030c(a7 + 2); uae_u16 format = get_word_mmu030c(a7 + 6); uae_u16 frame = format >> 12; uae_u16 ssw = get_word_mmu030c(a7 + 10); uae_u32 fault_addr = get_long_mmu030c(a7 + 16); // Data output buffer uae_u32 mmu030_data_buffer_out_v = get_long_mmu030c(a7 + 0x18); // Internal register, our opcode storage area uae_u32 oc = get_long_mmu030c(a7 + 0x14); uae_u32 stagesbc = get_long_mmu030c(a7 + 12); int idxsize = -1, idxsize_done = -1; bool doprefetch = true; // Fetch last word, real CPU does it to allow OS bus handler to map // the page if frame crosses pages and following page is not resident. if (frame == 0xb) get_word_mmu030c(a7 + 92 - 2); else get_word_mmu030c(a7 + 32 - 2); // Rerun "mmu030_opcode" using restored state. mmu030_retry = true; if (frame == 0xa) { // this is always last write data write fault uae_u32 mmu030_state_1 = get_word_mmu030c(a7 + 0x8); uae_u32 ps = get_long_mmu030c(a7 + 0x1c); #if MMU030_DEBUG if (!(mmu030_state_1 & MMU030_STATEFLAG1_LASTWRITE)) { write_log(_T("68030 MMU short bus fault but no lastwrite set!?\n")); } if (ssw & (MMU030_SSW_FB | MMU030_SSW_FC | MMU030_SSW_FB | MMU030_SSW_RC | MMU030_SSW_RB)) { write_log(_T("68030 MMU short bus fault and pipeline fault?\n")); } if (ssw & MMU030_SSW_RW) { write_log(_T("68030 MMU short bus fault but read fault!?\n")); } #endif // did we have data fault but DF bit cleared? if (ssw & (MMU030_SSW_DF << 1) && !(ssw & MMU030_SSW_DF)) { // DF not set: mark access as done unalign_clear(); } regs.prefetch020_valid[0] = (ps & 1) ? 1 : 0; regs.prefetch020_valid[1] = (ps & 2) ? 1 : 0; regs.prefetch020_valid[2] = (ps & 4) ? 1 : 0; regs.pipeline_r8[0] = (ps >> 8) & 7; regs.pipeline_r8[1] = (ps >> 11) & 7; regs.pipeline_pos = (ps >> 16) & 15; regs.pipeline_stop = ((ps >> 20) & 15) == 15 ? -1 : (int)(ps >> 20) & 15; regs.prefetch020[2] = stagesbc; regs.prefetch020[1] = stagesbc >> 16; regs.prefetch020[0] = oc >> 16; mmu030_opcode_stageb = (uae_u16)oc; mmu030_data_buffer_out = mmu030_data_buffer_out_v; mmu030_state[0] = 0; mmu030_state[1] = mmu030_state_1; mmu030_state[2] = 0; mmu030_idx = mmu030_idx_done = 0; doprefetch = false; m68k_areg(regs, 7) += 32; } else if (frame == 0xb) { // get_disp_ea_020 uae_u32 mmu030_disp_store_0 = get_long_mmu030c(a7 + 0x1c); uae_u32 mmu030_disp_store_1 = get_long_mmu030c(a7 + 0x1c + 4); // Internal register, misc flags uae_u32 ps = get_long_mmu030c(a7 + 0x28); // Data buffer uae_u32 mmu030_data_buffer_in_v = get_long_mmu030c(a7 + 0x2c);; // Misc state data uae_u32 mmu030_state_0 = get_word_mmu030c(a7 + 0x30); uae_u32 mmu030_state_1 = get_word_mmu030c(a7 + 0x32); uae_u32 mmu030_state_2 = get_word_mmu030c(a7 + 0x34); uae_u32 mmu030_opcode_v = (ps & 0x80000000) ? 0xffffffff : (oc & 0xffff); uae_u32 mmu030_fmovem_store_0 = 0; uae_u32 mmu030_fmovem_store_1 = 0; if (mmu030_state[1] & MMU030_STATEFLAG1_FMOVEM) { mmu030_fmovem_store_0 = get_long_mmu030c(a7 + 0x5c - (7 + 1) * 4); mmu030_fmovem_store_1 = get_long_mmu030c(a7 + 0x5c - (8 + 1) * 4); } uae_u16 v = get_word_mmu030c(a7 + 0x36); idxsize = v & 0x0f; idxsize_done = (v >> 4) & 0x0f; for (int i = 0; i < idxsize_done + 1; i++) { mmu030_ad_v[i].val = get_long_mmu030c(a7 + 0x5c - (i + 1) * 4); } regs.wb2_status = v >> 8; regs.wb3_status = mmu030_state_2 >> 8; mmu030fixupmod(regs.wb2_status, 1, -1); mmu030fixupmod(regs.wb3_status, 1, -1); // did we have data fault but DF bit cleared? if (ssw & (MMU030_SSW_DF << 1) && !(ssw & MMU030_SSW_DF)) { // DF not set: mark access as done mmu030_data_buffer_out_v = mmu030_data_buffer_in_v; if (ssw & MMU030_SSW_RM) { // Read-Modify-Write: whole instruction is considered done write_log(_T("Read-Modify-Write and DF bit cleared! PC=%08x\n"), regs.instruction_pc); mmu030_retry = false; } else if (mmu030_state_1 & MMU030_STATEFLAG1_MOVEM1) { // if movem, skip next move mmu030_state_1 |= MMU030_STATEFLAG1_MOVEM2; } else { if (ssw & MMU030_SSW_RW) { // Read and no DF: use value in data input buffer mmu030_ad_v[idxsize_done].val = mmu030_data_buffer_in_v; } idxsize_done++; } unalign_clear(); } #if MMU030_DEBUG if (mmu030_state_1 & MMU030_STATEFLAG1_LASTWRITE) { write_log(_T("68030 MMU long bus fault but lastwrite set!?\n")); } #endif // Retried data access is the only memory access that can be done after this. regs.prefetch020_valid[0] = (ps & 1) ? 1 : 0; regs.prefetch020_valid[1] = (ps & 2) ? 1 : 0; regs.prefetch020_valid[2] = (ps & 4) ? 1 : 0; regs.pipeline_r8[0] = (ps >> 8) & 7; regs.pipeline_r8[1] = (ps >> 11) & 7; regs.pipeline_pos = (ps >> 16) & 15; regs.pipeline_stop = ((ps >> 20) & 15) == 15 ? -1 : (int)(ps >> 20) & 15; regs.prefetch020[2] = stagesbc; regs.prefetch020[1] = stagesbc >> 16; regs.prefetch020[0] = oc >> 16; if ((ssw & MMU030_SSW_FB) && !(ssw & MMU030_SSW_RB)) { regs.prefetch020_valid[2] = 1; write_log(_T("Software fixed stage B! opcode = %04x\n"), regs.prefetch020[2]); #if 0 if (!regs.prefetch020_valid[0]) { regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; regs.prefetch020_valid[0] = regs.prefetch020_valid[1]; regs.prefetch020_valid[1] = regs.prefetch020_valid[2]; regs.prefetch020_valid[2] = 0; } #endif } if ((ssw & MMU030_SSW_FC) && !(ssw & MMU030_SSW_RC)) { regs.prefetch020_valid[1] = 1; write_log(_T("Software fixed stage C! opcode = %04x\n"), regs.prefetch020[1]); } // restore global state variables mmu030_opcode = mmu030_opcode_v; mmu030_state[0] = mmu030_state_0; mmu030_state[1] = mmu030_state_1; mmu030_state[2] = mmu030_state_2; mmu030_disp_store[0] = mmu030_disp_store_0; mmu030_disp_store[1] = mmu030_disp_store_1; mmu030_fmovem_store[0] = mmu030_fmovem_store_0; mmu030_fmovem_store[1] = mmu030_fmovem_store_1; mmu030_data_buffer_out = mmu030_data_buffer_out_v; mmu030_idx = idxsize; mmu030_idx_done = idxsize_done; for (int i = 0; i < idxsize_done + 1; i++) { mmu030_ad[i].val = mmu030_ad_v[i].val; } m68k_areg(regs, 7) += 92; } regs.sr = sr; MakeFromSR_T0(); if (pc & 1) { exception3_read_prefetch(0x4E73, pc); return; } m68k_setpci (pc); if (!(ssw & (MMU030_SSW_DF << 1))) { // software fixed? if (((ssw & MMU030_SSW_FB) && !(ssw & MMU030_SSW_RB)) || ((ssw & MMU030_SSW_FC) && !(ssw & MMU030_SSW_RC))) { fill_prefetch_030_ntx_continue(); } else { // pipeline refill in progress? if (mmu030_opcode == -1) { #if MMU030_ALWAYS_FULL_PREFETCH fill_prefetch_030_ntx(); #else fill_prefetch_030_ntx_continue(); #endif } } } if ((ssw & MMU030_SSW_DF) && (ssw & MMU030_SSW_RM)) { // Locked-Read-Modify-Write restarts whole instruction. mmu030_idx_done = 0; } else if (ssw & MMU030_SSW_DF) { // retry faulted access uaecptr addr = fault_addr; bool read = (ssw & MMU030_SSW_RW) != 0; int size = (ssw & MMU030_SSW_SIZE_B) ? sz_byte : ((ssw & MMU030_SSW_SIZE_W) ? sz_word : sz_long); int fc = ssw & 7; #if MMU030_DEBUG if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM2) { write_log(_T("68030 MMU MOVEM %04x retry but MMU030_STATEFLAG1_MOVEM2 was already set!?\n"), mmu030_opcode); } } #endif if (read) { if (mmu030_state[1] & MMU030_STATEFLAG1_SUBACCESS0) { mmu030_unaligned_read_continue(addr, fc, read_dcache030_retry); } else { switch (size) { case sz_byte: mmu030_data_buffer_out = read_data_030_fc_bget(addr, fc); break; case sz_word: mmu030_data_buffer_out = read_data_030_fc_wget(addr, fc); break; case sz_long: mmu030_data_buffer_out = read_data_030_fc_lget(addr, fc); break; } } if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM2; } else if (idxsize >= 0) { mmu030_ad[mmu030_idx_done].val = mmu030_data_buffer_out; mmu030_idx_done++; } } else { if (mmu030_state[1] & MMU030_STATEFLAG1_SUBACCESS0) { mmu030_unaligned_write_continue(addr, fc, write_dcache030_retry); } else { switch (size) { case sz_byte: write_data_030_fc_bput(addr, mmu030_data_buffer_out, fc); break; case sz_word: write_data_030_fc_wput(addr, mmu030_data_buffer_out, fc); break; case sz_long: write_data_030_fc_lput(addr, mmu030_data_buffer_out, fc); break; } } if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM2; } else if (idxsize >= 0) { mmu030_idx_done++; } } } if (mmu030_state[1] & MMU030_STATEFLAG1_LASTWRITE) { mmu030_retry = false; if (doprefetch) { mmu030_opcode = -1; fill_prefetch_030_ntx(); } } #ifdef WINUAE_FOR_HATARI /* * [NP] NOTE : there's a bug when restoring cpu state after a "frame B" bus error, * the prefetch values in regs.prefetch020[] are not correct (they match PC+2 or PC+4, not PC) * As a temporary fix we force a full reload of prefetch registers for the current PC */ if (frame == 0xb) { mmu030_opcode = -1; fill_prefetch_030_ntx(); } #endif } void restore_mmu030_finish ( void ) { mmu030.transparent.tt0 = mmu030_decode_tt(tt0_030); mmu030.transparent.tt1 = mmu030_decode_tt(tt1_030); tt_enabled = (tt0_030 & TT_ENABLE) || (tt1_030 & TT_ENABLE); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/cpummu030.h000066400000000000000000000547061504763705000240350ustar00rootroot00000000000000#ifndef UAE_CPUMMU030_H #define UAE_CPUMMU030_H #define MMU030_DEBUG 0 #include "uae/types.h" #include "mmu_common.h" #define MMU_DPAGECACHE030 1 #define MMU_IPAGECACHE030 1 extern uae_u64 srp_030, crp_030; extern uae_u32 tt0_030, tt1_030, tc_030; extern uae_u16 mmusr_030; #define MAX_MMU030_ACCESS 9 extern uae_u32 mm030_stageb_address; extern int mmu030_idx, mmu030_idx_done; extern bool mmu030_retry; extern int mmu030_opcode, mmu030_opcode_stageb; extern int mmu030_fake_prefetch; extern uaecptr mmu030_fake_prefetch_addr; extern uae_u16 mmu030_state[3]; extern uae_u32 mmu030_data_buffer_out; extern uae_u32 mmu030_disp_store[2]; extern uae_u32 mmu030_fmovem_store[2]; extern uae_u8 mmu030_cache_state, mmu030_cache_state_default; extern bool ismoves030, islrmw030; #define MMU030_STATEFLAG1_FMOVEM 0x2000 #define MMU030_STATEFLAG1_MOVEM1 0x4000 #define MMU030_STATEFLAG1_MOVEM2 0x8000 #define MMU030_STATEFLAG1_DISP0 0x0001 #define MMU030_STATEFLAG1_DISP1 0x0002 #define MMU030_STATEFLAG1_LASTWRITE 0x0100 #define MMU030_STATEFLAG1_SUBACCESS0 0x0004 #define MMU030_STATEFLAG1_SUBACCESS1 0x0008 #define MMU030_STATEFLAG1_SUBACCESS2 0x0010 #define MMU030_STATEFLAG1_SUBACCESS3 0x0020 #define MMU030_STATEFLAG1_SUBACCESSX 0x0040 #define MMU030_STATEFLAG1_SUBACCESSL 0x0080 #define MMU030_STATEFLAG1_SUBACCESS_SHIFT 2 struct mmu030_access { uae_u32 val; }; extern struct mmu030_access mmu030_ad[MAX_MMU030_ACCESS + 1]; void mmu030_page_fault(uaecptr addr, bool read, int flags, uae_u32 fc); int mmu_op30_pmove(uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra); bool mmu_op30_ptest(uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra); bool mmu_op30_pflush(uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra); typedef struct { uae_u32 addr_base; uae_u32 addr_mask; uae_u32 fc_base; uae_u32 fc_mask; } TT_info; bool mmu030_decode_tc(uae_u32 TC, bool); bool mmu030_decode_rp(uae_u64 RP); void mmu030_flush_atc_all(void); void mmu030_reset(int hardreset); void mmu030_set_funcs(void); uaecptr mmu030_translate(uaecptr addr, bool super, bool data, bool write); void mmu030_hardware_bus_error(uaecptr addr, uae_u32 v, bool read, bool ins, int size); bool mmu030_is_super_access(bool read); void mmu030_put_long(uaecptr addr, uae_u32 val, uae_u32 fc); void mmu030_put_word(uaecptr addr, uae_u16 val, uae_u32 fc); void mmu030_put_byte(uaecptr addr, uae_u8 val, uae_u32 fc); uae_u32 mmu030_get_long(uaecptr addr, uae_u32 fc); uae_u16 mmu030_get_word(uaecptr addr, uae_u32 fc); uae_u8 mmu030_get_byte(uaecptr addr, uae_u32 fc); uae_u32 mmu030_get_ilong(uaecptr addr, uae_u32 fc); uae_u16 mmu030_get_iword(uaecptr addr, uae_u32 fc); uae_u32 uae_mmu030_get_lrmw(uaecptr addr, int size); void uae_mmu030_put_lrmw(uaecptr addr, uae_u32 val, int size); void mmu030_put_generic(uaecptr addr, uae_u32 val, uae_u32 fc, int size, int flags); uae_u32 mmu030_get_generic(uaecptr addr, uae_u32 fc, int size, int flags); extern uae_u16 REGPARAM3 mmu030_get_word_unaligned(uaecptr addr, uae_u32 fc, int flags) REGPARAM; extern uae_u32 REGPARAM3 mmu030_get_long_unaligned(uaecptr addr, uae_u32 fc, int flags) REGPARAM; extern uae_u32 REGPARAM3 mmu030_get_ilong_unaligned(uaecptr addr, uae_u32 fc, int flags) REGPARAM; extern uae_u16 REGPARAM3 mmu030_get_lrmw_word_unaligned(uaecptr addr, uae_u32 fc, int flags) REGPARAM; extern uae_u32 REGPARAM3 mmu030_get_lrmw_long_unaligned(uaecptr addr, uae_u32 fc, int flags) REGPARAM; extern void REGPARAM3 mmu030_put_word_unaligned(uaecptr addr, uae_u16 val, uae_u32 fc, int flags) REGPARAM; extern void REGPARAM3 mmu030_put_long_unaligned(uaecptr addr, uae_u32 val, uae_u32 fc, int flags) REGPARAM; static ALWAYS_INLINE uae_u32 uae_mmu030_get_fc_code(void) { return (regs.s ? 4 : 0) | 2; } static ALWAYS_INLINE uae_u32 uae_mmu030_get_fc_data(void) { return (regs.s ? 4 : 0) | 1; } static ALWAYS_INLINE uae_u32 uae_mmu030_get_ilong(uaecptr addr) { uae_u32 fc = uae_mmu030_get_fc_code(); if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_ilong_unaligned(addr, fc, 0); return mmu030_get_ilong(addr, fc); } static ALWAYS_INLINE uae_u16 uae_mmu030_get_iword(uaecptr addr) { uae_u32 fc = uae_mmu030_get_fc_code(); return mmu030_get_iword(addr, fc); } static ALWAYS_INLINE uae_u16 uae_mmu030_get_ibyte(uaecptr addr) { uae_u32 fc = uae_mmu030_get_fc_code(); return mmu030_get_byte(addr, fc); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_long(uaecptr addr) { uae_u32 fc = uae_mmu030_get_fc_data(); if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_long_unaligned(addr, fc, 0); return mmu030_get_long(addr, fc); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_word(uaecptr addr) { uae_u32 fc = uae_mmu030_get_fc_data(); if (unlikely(is_unaligned_bus(addr, 2))) return mmu030_get_word_unaligned(addr, fc, 0); return mmu030_get_word(addr, fc); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_byte(uaecptr addr) { uae_u32 fc = uae_mmu030_get_fc_data(); return mmu030_get_byte(addr, fc); } static ALWAYS_INLINE void uae_mmu030_put_long(uaecptr addr, uae_u32 val) { uae_u32 fc = uae_mmu030_get_fc_data(); if (unlikely(is_unaligned_bus(addr, 4))) mmu030_put_long_unaligned(addr, val, fc, 0); else mmu030_put_long(addr, val, fc); } static ALWAYS_INLINE void uae_mmu030_put_word(uaecptr addr, uae_u32 val) { uae_u32 fc = uae_mmu030_get_fc_data(); if (unlikely(is_unaligned_bus(addr, 2))) mmu030_put_word_unaligned(addr, val, fc, 0); else mmu030_put_word(addr, val, fc); } static ALWAYS_INLINE void uae_mmu030_put_byte(uaecptr addr, uae_u32 val) { uae_u32 fc = uae_mmu030_get_fc_data(); mmu030_put_byte(addr, val, fc); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_ilong_fc(uaecptr addr) { if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_ilong_unaligned(addr, regs.fc030, 0); return mmu030_get_ilong(addr, regs.fc030); } static ALWAYS_INLINE uae_u16 uae_mmu030_get_iword_fc(uaecptr addr) { return mmu030_get_iword(addr, regs.fc030); } static ALWAYS_INLINE uae_u16 uae_mmu030_get_ibyte_fc(uaecptr addr) { return mmu030_get_byte(addr, regs.fc030); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_long_fc(uaecptr addr) { if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_long_unaligned(addr, regs.fc030, 0); return mmu030_get_long(addr, regs.fc030); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_word_fc(uaecptr addr) { if (unlikely(is_unaligned_bus(addr, 2))) return mmu030_get_word_unaligned(addr, regs.fc030, 0); return mmu030_get_word(addr, regs.fc030); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_byte_fc(uaecptr addr) { return mmu030_get_byte(addr, regs.fc030); } static ALWAYS_INLINE void uae_mmu030_put_long_fc(uaecptr addr, uae_u32 val) { if (unlikely(is_unaligned_bus(addr, 4))) mmu030_put_long_unaligned(addr, val, regs.fc030, 0); else mmu030_put_long(addr, val, regs.fc030); } static ALWAYS_INLINE void uae_mmu030_put_word_fc(uaecptr addr, uae_u32 val) { if (unlikely(is_unaligned_bus(addr, 2))) mmu030_put_word_unaligned(addr, val, regs.fc030, 0); else mmu030_put_word(addr, val, regs.fc030); } static ALWAYS_INLINE void uae_mmu030_put_byte_fc(uaecptr addr, uae_u32 val) { mmu030_put_byte(addr, val, regs.fc030); } uae_u8 uae_mmu030_check_fc(uaecptr addr, bool write, uae_u32 size); static ALWAYS_INLINE uae_u32 uae_mmu030_get_long_fcx(uaecptr addr, int fc) { if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_long_unaligned(addr, fc, 0); return mmu030_get_long(addr, fc); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_word_fcx(uaecptr addr, int fc) { if (unlikely(is_unaligned_bus(addr, 2))) return mmu030_get_word_unaligned(addr, fc, 0); return mmu030_get_word(addr, fc); } static ALWAYS_INLINE uae_u32 uae_mmu030_get_byte_fcx(uaecptr addr, int fc) { return mmu030_get_byte(addr, fc); } static ALWAYS_INLINE void uae_mmu030_put_long_fcx(uaecptr addr, uae_u32 val, int fc) { if (unlikely(is_unaligned_bus(addr, 4))) mmu030_put_long_unaligned(addr, val, fc, 0); else mmu030_put_long(addr, val, fc); } static ALWAYS_INLINE void uae_mmu030_put_word_fcx(uaecptr addr, uae_u32 val, int fc) { if (unlikely(is_unaligned_bus(addr, 2))) mmu030_put_word_unaligned(addr, val, fc, 0); else mmu030_put_word(addr, val, fc); } static ALWAYS_INLINE void uae_mmu030_put_byte_fcx(uaecptr addr, uae_u32 val, int fc) { mmu030_put_byte(addr, val, fc); } #define ACCESS_CHECK_PUT \ if (mmu030_idx++ < mmu030_idx_done) { \ return; \ } else { \ mmu030_data_buffer_out = v; \ } #define ACCESS_CHECK_GET \ if (mmu030_idx++ < mmu030_idx_done) { \ v = mmu030_ad[mmu030_idx - 1].val; \ return v; \ } #define ACCESS_CHECK_GET_PC(pc) \ if (mmu030_idx++ < mmu030_idx_done) { \ v = mmu030_ad[mmu030_idx - 1].val; \ m68k_incpci(pc); \ return v; \ } #define ACCESS_EXIT_PUT \ mmu030_ad[mmu030_idx_done++].val = mmu030_data_buffer_out; #define ACCESS_EXIT_GET \ mmu030_ad[mmu030_idx_done++].val = v; STATIC_INLINE uae_u32 state_store_mmu030(uae_u32 v) { if (mmu030_idx++ < mmu030_idx_done) { v = mmu030_ad[mmu030_idx - 1].val; } else { mmu030_ad[mmu030_idx_done++].val = v; } return v; } // non-cache static ALWAYS_INLINE uae_u32 sfc030_get_long(uaecptr addr) { uae_u32 fc = regs.sfc; #if MMUDEBUG > 2 write_log(_T("sfc030_get_long: FC = %i\n"),fc); #endif if (unlikely(is_unaligned_bus(addr, 4))) return mmu030_get_long_unaligned(addr, fc, 0); return mmu030_get_long(addr, fc); } static ALWAYS_INLINE uae_u16 sfc030_get_word(uaecptr addr) { uae_u32 fc = regs.sfc; #if MMUDEBUG > 2 write_log(_T("sfc030_get_word: FC = %i\n"),fc); #endif if (unlikely(is_unaligned_bus(addr, 2))) return mmu030_get_word_unaligned(addr, fc, 0); return mmu030_get_word(addr, fc); } static ALWAYS_INLINE uae_u8 sfc030_get_byte(uaecptr addr) { uae_u32 fc = regs.sfc; #if MMUDEBUG > 2 write_log(_T("sfc030_get_byte: FC = %i\n"),fc); #endif return mmu030_get_byte(addr, fc); } static ALWAYS_INLINE void dfc030_put_long(uaecptr addr, uae_u32 val) { uae_u32 fc = regs.dfc; #if MMUDEBUG > 2 write_log(_T("dfc030_put_long: %08X = %08X FC = %i\n"), addr, val, fc); #endif if (unlikely(is_unaligned_bus(addr, 4))) mmu030_put_long_unaligned(addr, val, fc, 0); else mmu030_put_long(addr, val, fc); } static ALWAYS_INLINE void dfc030_put_word(uaecptr addr, uae_u16 val) { uae_u32 fc = regs.dfc; #if MMUDEBUG > 2 write_log(_T("dfc030_put_word: %08X = %04X FC = %i\n"), addr, val, fc); #endif if (unlikely(is_unaligned_bus(addr, 2))) mmu030_put_word_unaligned(addr, val, fc, 0); else mmu030_put_word(addr, val, fc); } static ALWAYS_INLINE void dfc030_put_byte(uaecptr addr, uae_u8 val) { uae_u32 fc = regs.dfc; #if MMUDEBUG > 2 write_log(_T("dfc030_put_byte: %08X = %02X FC = %i\n"), addr, val, fc); #endif mmu030_put_byte(addr, val, fc); } static ALWAYS_INLINE uae_u32 sfc030_get_long_state(uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET ismoves030 = true; v = sfc030_get_long(addr); ismoves030 = false; ACCESS_EXIT_GET return v; } static ALWAYS_INLINE uae_u16 sfc030_get_word_state(uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET ismoves030 = true; v = sfc030_get_word(addr); ismoves030 = false; ACCESS_EXIT_GET return v; } static ALWAYS_INLINE uae_u8 sfc030_get_byte_state(uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET ismoves030 = true; v = sfc030_get_byte(addr); ismoves030 = false; ACCESS_EXIT_GET return v; } static ALWAYS_INLINE void dfc030_put_long_state(uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT ismoves030 = true; dfc030_put_long(addr, v); ismoves030 = false; ACCESS_EXIT_PUT } static ALWAYS_INLINE void dfc030_put_word_state(uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT ismoves030 = true; dfc030_put_word(addr, v); ismoves030 = false; ACCESS_EXIT_PUT } static ALWAYS_INLINE void dfc030_put_byte_state(uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT ismoves030 = true; dfc030_put_byte(addr, v); ismoves030 = false; ACCESS_EXIT_PUT } uae_u32 REGPARAM3 get_disp_ea_020_mmu030 (uae_u32 base, int idx) REGPARAM; STATIC_INLINE void put_byte_mmu030_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT uae_mmu030_put_byte (addr, v); ACCESS_EXIT_PUT } STATIC_INLINE void put_lrmw_byte_mmu030_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT uae_mmu030_put_lrmw (addr, v, sz_byte); ACCESS_EXIT_PUT } STATIC_INLINE void put_word_mmu030_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT uae_mmu030_put_word (addr, v); ACCESS_EXIT_PUT } STATIC_INLINE void put_lrmw_word_mmu030_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT uae_mmu030_put_lrmw (addr, v, sz_word); ACCESS_EXIT_PUT } STATIC_INLINE void put_long_mmu030_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT uae_mmu030_put_long (addr, v); ACCESS_EXIT_PUT } STATIC_INLINE void put_lrmw_long_mmu030_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT uae_mmu030_put_lrmw (addr, v, sz_long); ACCESS_EXIT_PUT } STATIC_INLINE uae_u32 get_byte_mmu030_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = uae_mmu030_get_byte (addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_lrmw_byte_mmu030_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = uae_mmu030_get_lrmw (addr, sz_byte); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_word_mmu030_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = uae_mmu030_get_word (addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_lrmw_word_mmu030_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = uae_mmu030_get_lrmw (addr, sz_word); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_long_mmu030_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = uae_mmu030_get_long (addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_lrmw_long_mmu030_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = uae_mmu030_get_lrmw (addr, sz_long); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_ibyte_mmu030_state (int o) { uae_u32 v; uae_u32 addr = m68k_getpci () + o; ACCESS_CHECK_GET v = uae_mmu030_get_iword (addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_iword_mmu030_state (int o) { uae_u32 v; uae_u32 addr = m68k_getpci () + o; ACCESS_CHECK_GET v = uae_mmu030_get_iword (addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_ilong_mmu030_state (int o) { uae_u32 v; uae_u32 addr = m68k_getpci () + o; ACCESS_CHECK_GET v = uae_mmu030_get_ilong(addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 next_iword_mmu030_state (void) { uae_u32 v; uae_u32 addr = m68k_getpci (); ACCESS_CHECK_GET_PC(2); v = uae_mmu030_get_iword (addr); m68k_incpci (2); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 next_ilong_mmu030_state (void) { uae_u32 v; uae_u32 addr = m68k_getpci (); ACCESS_CHECK_GET_PC(4); v = uae_mmu030_get_ilong(addr); m68k_incpci (4); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_byte_mmu030 (uaecptr addr) { return uae_mmu030_get_byte (addr); } STATIC_INLINE uae_u32 get_word_mmu030 (uaecptr addr) { return uae_mmu030_get_word (addr); } STATIC_INLINE uae_u32 get_long_mmu030 (uaecptr addr) { return uae_mmu030_get_long (addr); } STATIC_INLINE void put_byte_mmu030 (uaecptr addr, uae_u32 v) { uae_mmu030_put_byte (addr, v); } STATIC_INLINE void put_word_mmu030 (uaecptr addr, uae_u32 v) { uae_mmu030_put_word (addr, v); } STATIC_INLINE void put_long_mmu030 (uaecptr addr, uae_u32 v) { uae_mmu030_put_long (addr, v); } STATIC_INLINE uae_u32 get_ibyte_mmu030 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu030_get_iword (pc); } STATIC_INLINE uae_u32 get_iword_mmu030 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu030_get_iword (pc); } STATIC_INLINE uae_u32 get_ilong_mmu030 (int o) { uae_u32 pc = m68k_getpci () + o; return uae_mmu030_get_ilong (pc); } STATIC_INLINE uae_u32 next_iword_mmu030 (void) { uae_u32 v; uae_u32 pc = m68k_getpci (); v = uae_mmu030_get_iword (pc); m68k_incpci (2); return v; } STATIC_INLINE uae_u32 next_ilong_mmu030 (void) { uae_u32 v; uae_u32 pc = m68k_getpci (); v = uae_mmu030_get_ilong (pc); m68k_incpci (4); return v; } extern void m68k_do_rts_mmu030 (void); extern void m68k_do_rte_mmu030 (uaecptr a7); extern void flush_mmu030 (uaecptr, int); extern void m68k_do_bsr_mmu030 (uaecptr oldpc, uae_s32 offset); // more compatible + optional cache static ALWAYS_INLINE uae_u32 mmu030_get_fc_byte(uaecptr addr, uae_u32 fc) { return mmu030_get_byte(addr, fc); } static ALWAYS_INLINE uae_u32 mmu030_get_fc_word(uaecptr addr, uae_u32 fc) { return mmu030_get_word(addr, fc); } static ALWAYS_INLINE uae_u32 mmu030_get_fc_long(uaecptr addr, uae_u32 fc) { return mmu030_get_long(addr, fc); } static ALWAYS_INLINE void mmu030_put_fc_byte(uaecptr addr, uae_u32 val, uae_u32 fc) { mmu030_put_byte(addr, val, fc); } static ALWAYS_INLINE void mmu030_put_fc_word(uaecptr addr, uae_u32 val, uae_u32 fc) { mmu030_put_word(addr, val, fc); } static ALWAYS_INLINE void mmu030_put_fc_long(uaecptr addr, uae_u32 val, uae_u32 fc) { mmu030_put_long(addr, val, fc); } static ALWAYS_INLINE uae_u32 sfc030c_get_long(uaecptr addr) { #if MMUDEBUG > 2 write_log(_T("sfc030_get_long: FC = %i\n"), regs.sfc); #endif return read_data_030_fc_lget(addr, regs.sfc); } static ALWAYS_INLINE uae_u16 sfc030c_get_word(uaecptr addr) { #if MMUDEBUG > 2 write_log(_T("sfc030_get_word: FC = %i\n"), regs.sfc); #endif return read_data_030_fc_wget(addr, regs.sfc); } static ALWAYS_INLINE uae_u8 sfc030c_get_byte(uaecptr addr) { #if MMUDEBUG > 2 write_log(_T("sfc030_get_byte: FC = %i\n"), regs.sfc); #endif return read_data_030_fc_bget(addr, regs.sfc); } static ALWAYS_INLINE void dfc030c_put_long(uaecptr addr, uae_u32 val) { #if MMUDEBUG > 2 write_log(_T("dfc030_put_long: %08X = %08X FC = %i\n"), addr, val, regs.dfc); #endif write_data_030_fc_lput(addr, val, regs.dfc); } static ALWAYS_INLINE void dfc030c_put_word(uaecptr addr, uae_u16 val) { #if MMUDEBUG > 2 write_log(_T("dfc030_put_word: %08X = %04X FC = %i\n"), addr, val, regs.dfc); #endif write_data_030_fc_wput(addr, val, regs.dfc); } static ALWAYS_INLINE void dfc030c_put_byte(uaecptr addr, uae_u8 val) { #if MMUDEBUG > 2 write_log(_T("dfc030_put_byte: %08X = %02X FC = %i\n"), addr, val, regs.dfc); #endif write_data_030_fc_bput(addr, val, regs.dfc); } static ALWAYS_INLINE uae_u32 sfc030c_get_long_state(uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = sfc030c_get_long(addr); ACCESS_EXIT_GET return v; } static ALWAYS_INLINE uae_u16 sfc030c_get_word_state(uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = sfc030c_get_word(addr); ACCESS_EXIT_GET return v; } static ALWAYS_INLINE uae_u8 sfc030c_get_byte_state(uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = sfc030c_get_byte(addr); ACCESS_EXIT_GET return v; } static ALWAYS_INLINE void dfc030c_put_long_state(uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT dfc030c_put_long(addr, v); ACCESS_EXIT_PUT } static ALWAYS_INLINE void dfc030c_put_word_state(uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT dfc030c_put_word(addr, v); ACCESS_EXIT_PUT } static ALWAYS_INLINE void dfc030c_put_byte_state(uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT dfc030c_put_byte(addr, v); ACCESS_EXIT_PUT } uae_u32 REGPARAM3 get_disp_ea_020_mmu030c (uae_u32 base, int idx) REGPARAM; STATIC_INLINE void put_byte_mmu030c_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT write_data_030_bput(addr, v); ACCESS_EXIT_PUT } STATIC_INLINE void put_lrmw_byte_mmu030c_state (uaecptr addr, uae_u32 v) { islrmw030 = true; ACCESS_CHECK_PUT write_dcache030_lrmw_mmu(addr, v, 0); ACCESS_EXIT_PUT islrmw030 = false; } STATIC_INLINE void put_word_mmu030c_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT write_data_030_wput(addr, v); ACCESS_EXIT_PUT } STATIC_INLINE void put_lrmw_word_mmu030c_state (uaecptr addr, uae_u32 v) { islrmw030 = true; ACCESS_CHECK_PUT write_dcache030_lrmw_mmu(addr, v, 1); ACCESS_EXIT_PUT islrmw030 = false; } STATIC_INLINE void put_long_mmu030c_state (uaecptr addr, uae_u32 v) { ACCESS_CHECK_PUT write_data_030_lput(addr, v); ACCESS_EXIT_PUT } STATIC_INLINE void put_lrmw_long_mmu030c_state (uaecptr addr, uae_u32 v) { islrmw030 = true; ACCESS_CHECK_PUT write_dcache030_lrmw_mmu(addr, v, 2); ACCESS_EXIT_PUT islrmw030 = false; } STATIC_INLINE uae_u32 get_byte_mmu030c_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = read_data_030_bget(addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_lrmw_byte_mmu030c_state (uaecptr addr) { uae_u32 v; islrmw030 = true; ACCESS_CHECK_GET v = read_dcache030_lrmw_mmu(addr, 0); ACCESS_EXIT_GET islrmw030 = false; return v; } STATIC_INLINE uae_u32 get_word_mmu030c_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = read_data_030_wget(addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_lrmw_word_mmu030c_state (uaecptr addr) { uae_u32 v; islrmw030 = true; ACCESS_CHECK_GET v = read_dcache030_lrmw_mmu(addr, 1); ACCESS_EXIT_GET islrmw030 = false; return v; } STATIC_INLINE uae_u32 get_long_mmu030c_state (uaecptr addr) { uae_u32 v; ACCESS_CHECK_GET v = read_data_030_lget(addr); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_lrmw_long_mmu030c_state (uaecptr addr) { uae_u32 v; islrmw030 = true; ACCESS_CHECK_GET v = read_dcache030_lrmw_mmu(addr, 2); ACCESS_EXIT_GET islrmw030 = false; return v; } STATIC_INLINE uae_u32 get_ibyte_mmu030c_state (int o) { uae_u32 v; uae_u32 addr = m68k_getpci () + o; ACCESS_CHECK_GET v = get_word_icache030(addr); ACCESS_EXIT_GET return v; } uae_u32 get_word_030_prefetch(int o); STATIC_INLINE uae_u32 get_iword_mmu030c_state (int o) { uae_u32 v; ACCESS_CHECK_GET; v = get_word_030_prefetch(o); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 get_ilong_mmu030c_state (int o) { uae_u32 v; v = get_iword_mmu030c_state(o + 0) << 16; v |= get_iword_mmu030c_state(o + 2) & 0xffff; return v; } STATIC_INLINE uae_u32 get_iword_mmu030c_opcode_state(int o) { return get_iword_mmu030c_state(o); } STATIC_INLINE uae_u32 next_iword_mmu030c_state (void) { uae_u32 v; ACCESS_CHECK_GET_PC(2); v = get_word_030_prefetch(0); m68k_incpci(2); ACCESS_EXIT_GET return v; } STATIC_INLINE uae_u32 next_ilong_mmu030c_state (void) { uae_u32 v; v = next_iword_mmu030c_state() << 16; v |= next_iword_mmu030c_state() & 0xffff; return v; } STATIC_INLINE uae_u32 get_word_mmu030c (uaecptr addr) { return read_data_030_wget(addr); } STATIC_INLINE uae_u32 get_long_mmu030c (uaecptr addr) { return read_data_030_lget(addr); } STATIC_INLINE void put_word_mmu030c (uaecptr addr, uae_u32 v) { write_data_030_wput(addr, v); } STATIC_INLINE void put_long_mmu030c (uaecptr addr, uae_u32 v) { write_data_030_lput(addr, v); } extern void m68k_do_rts_mmu030c(void); extern void m68k_do_rte_mmu030c(uaecptr a7); extern void m68k_do_bsr_mmu030c(uaecptr oldpc, uae_s32 offset); extern void restore_mmu030_finish(void); #endif /* UAE_CPUMMU030_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/custom.c000066400000000000000000000362701504763705000236050ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * Custom chip emulation * * Copyright 1995-2002 Bernd Schmidt * Copyright 1995 Alessandro Bissacco * Copyright 2000-2014 Toni Wilen */ #include "sysconfig.h" #include "sysdeps.h" #include "compat.h" #include "hatari-glue.h" #include "options_cpu.h" #include "events.h" #include "custom.h" #include "newcpu.h" #include "main.h" #include "cpummu.h" #include "blitter.h" #include "debug.h" #include "savestate.h" #define WRITE_LOG_BUF_SIZE 4096 /* TODO: move custom.c stuff declarations to custom.h? */ #ifdef WINUAE_FOR_HATARI /* declared in newcpu.c */ extern struct regstruct mmu_backup_regs; /* declared in events.h */ evt_t currcycle; /* declared in savestate.h */ int savestate_state = 0; TCHAR savestate_fname[MAX_DPATH]; /* declared in custom.h */ uae_u32 hsync_counter = 0, vsync_counter = 0; #endif uae_u16 dmacon; uae_u32 extra_cycle; #ifdef CPUEMU_13 #ifndef WINUAE_FOR_HATARI static void sync_cycles(void) { if (extra_cycle) { do_cycles(extra_cycle); extra_cycle = 0; } evt_t c = get_cycles(); int extra = c & (CYCLE_UNIT - 1); if (extra) { extra = CYCLE_UNIT - extra; do_cycles(extra); } } #endif uae_u32 wait_cpu_cycle_read (uaecptr addr, int mode) { #ifndef WINUAE_FOR_HATARI uae_u32 v = 0, vd = 0; int ipl = regs.ipl[0]; evt_t now = get_cycles(); sync_cycles(); x_do_cycles_pre(CYCLE_UNIT); dma_cycle(&mode, &ipl); #ifdef DEBUGGER if (debug_dma) { int reg = 0x1000; if (mode == -3) { reg |= 2; v = regs.chipset_latch_rw; } else if (mode < 0) { reg |= 4; } else if (mode > 0) { reg |= 2; } else { reg |= 1; } record_dma_read(reg, addr, DMARECORD_CPU, mode == -2 || mode == 2 ? 0 : 1); } peekdma_data.mask = 0; #else if (mode == -3) { v = regs.chipset_latch_rw; } #endif switch(mode) { case -1: v = vd = get_long(addr); break; case -2: v = vd = get_longi(addr); break; case 1: v = vd = get_word(addr); break; case 2: v = vd = get_wordi(addr); break; case 0: v = vd = get_word(addr & ~1); v >>= (addr & 1) ? 0 : 8; break; } #ifdef DEBUGGER if (debug_dma) { record_dma_read_value_pos(vd); } #endif x_do_cycles_post(CYCLE_UNIT, 0); regs.chipset_latch_rw = regs.chipset_latch_read = v; // if IPL fetch was pending and CPU had wait states // Use ipl_pin value from previous cycle if (now == regs.ipl_evt) { regs.ipl[0] = ipl; } #else /* WINUAE_FOR_HATARI */ uae_u32 v = 0; int ipl = regs.ipl[0]; evt_t now = get_cycles(); uint64_t cycle_slot; cycle_slot = ( CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ) & 3; // fprintf ( stderr , "mem read ce slot %lu %llu\n" , cycle_slot , CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ); // fprintf ( stderr , "mem read ce %x %d %llu %llu\n" , addr , mode ,currcycle / cpucycleunit , currcycle ); if ( cycle_slot != 0 ) { // fprintf ( stderr , "mem wait read %x %d %llu %llu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); x_do_cycles ( ( 4 - cycle_slot ) * cpucycleunit); // fprintf ( stderr , "mem wait read after %x %d %llu %llu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); } switch(mode) { case -1: v = get_long(addr); break; case -2: v = get_longi(addr); break; case 1: v = get_word(addr); break; case 2: v = get_wordi(addr); break; case 0: v = get_byte(addr); break; } x_do_cycles_post (2*CYCLE_UNIT, v); // if IPL fetch was pending and CPU had wait states // Use ipl_pin value from previous cycle if (now == regs.ipl_evt && regs.ipl_pin_change_evt > now + cpuipldelay2) { regs.ipl[0] = ipl; } #endif /* WINUAE_FOR_HATARI */ return v; } void wait_cpu_cycle_write (uaecptr addr, int mode, uae_u32 v) { #ifndef WINUAE_FOR_HATARI int ipl = regs.ipl[0]; evt_t now = get_cycles(); sync_cycles(); x_do_cycles_pre(CYCLE_UNIT); dma_cycle(&mode, &ipl); #ifdef DEBUGGER if (debug_dma) { int reg = 0x1100; if (mode == -3) { reg |= 2; } else if (mode < 0) { reg |= 4; } else if (mode > 0) { reg |= 2; } else { reg |= 1; } record_dma_write(reg, v, addr, DMARECORD_CPU, 1); } peekdma_data.mask = 0; #endif if (mode > -2) { if (mode < 0) { put_long(addr, v); } else if (mode > 0) { put_word(addr, v); } else if (mode == 0) { put_byte(addr, v); } } x_do_cycles_post(CYCLE_UNIT, v); regs.chipset_latch_rw = regs.chipset_latch_write = v; // if IPL fetch was pending and CPU had wait states: // Use ipl_pin value from previous cycle if (now == regs.ipl_evt) { regs.ipl[0] = ipl; } #else /* WINUAE_FOR_HATARI */ int ipl = regs.ipl[0]; evt_t now = get_cycles(); uint64_t cycle_slot; cycle_slot = ( CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ) & 3; // fprintf ( stderr , "mem read ce slot %lu %llu\n" , cycle_slot , CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ); // fprintf ( stderr , "mem write ce %x %d %llu %llu\n" , addr , mode ,currcycle / cpucycleunit , currcycle ); if ( cycle_slot != 0 ) { // fprintf ( stderr , "mem wait write %x %d %llu %llu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); x_do_cycles ( ( 4 - cycle_slot ) * cpucycleunit); // fprintf ( stderr , "mem wait write after %x %d %llu %llu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); } if (mode > -2) { if (mode < 0) { put_long(addr, v); } else if (mode > 0) { put_word(addr, v); } else if (mode == 0) { put_byte(addr, v); } } x_do_cycles_post (2*CYCLE_UNIT, v); // if IPL fetch was pending and CPU had wait states: // Use ipl_pin value from previous cycle if (now == regs.ipl_evt) { regs.ipl[0] = ipl; } #endif /* WINUAE_FOR_HATARI */ } uae_u32 wait_cpu_cycle_read_ce020 (uaecptr addr, int mode) { uae_u32 v = 0; #ifndef WINUAE_FOR_HATARI int ipl; sync_cycles(); x_do_cycles_pre(CYCLE_UNIT); dma_cycle(NULL, &ipl); #ifdef DEBUGGER if (debug_dma) { int reg = 0x1000; if (mode < 0) { reg |= 4; } else if (mode > 0) { reg |= 2; } else { reg |= 1; } record_dma_read(reg, addr, DMARECORD_CPU, mode == -2 || mode == 2 ? 0 : 1); } peekdma_data.mask = 0; #endif switch (mode) { case -1: v = get_long(addr); break; case -2: v = get_longi(addr); break; case 1: v = get_word(addr); break; case 2: v = get_wordi(addr); break; case 0: v = get_byte(addr); break; } #ifdef DEBUGGER if (debug_dma) { record_dma_read_value_pos(v); } #endif regs.chipset_latch_rw = regs.chipset_latch_read = v; x_do_cycles_post(CYCLE_UNIT, v); #else /* WINUAE_FOR_HATARI */ //fprintf ( stderr , "wait read ce020 glob %lu\n" , CyclesGlobalClockCounter ); //fprintf ( stderr , "wait read ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); int bus_pos = ( CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ) & 3; if ( ( bus_pos & 2 ) == 2 ) // if ( bus_pos ) { // fprintf ( stderr , "mem wait read %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); x_do_cycles ((4-bus_pos)*cpucycleunit); // fprintf ( stderr , "mem wait read after %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); } //fprintf ( stderr , "wait read2 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); switch (mode) { case -1: v = get_long(addr); break; case -2: v = get_longi(addr); break; case 1: v = get_word(addr); break; case 2: v = get_wordi(addr); break; case 0: v = get_byte(addr); break; } //fprintf ( stderr , "wait read3 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); x_do_cycles_post (3*cpucycleunit, v); //fprintf ( stderr , "wait read4 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); #endif /* WINUAE_FOR_HATARI */ return v; } void wait_cpu_cycle_write_ce020 (uaecptr addr, int mode, uae_u32 v) { #ifndef WINUAE_FOR_HATARI int ipl; sync_cycles(); x_do_cycles_pre(CYCLE_UNIT); dma_cycle(NULL, &ipl); #ifdef DEBUGGER if (debug_dma) { int reg = 0x1100; if (mode < 0) reg |= 4; else if (mode > 0) reg |= 2; else reg |= 1; record_dma_write(reg, v, addr, DMARECORD_CPU, 1); } peekdma_data.mask = 0; #endif if (mode < 0) { put_long(addr, v); } else if (mode > 0) { put_word(addr, v); } else if (mode == 0) { put_byte(addr, v); } regs.chipset_latch_rw = regs.chipset_latch_write = v; x_do_cycles_post(CYCLE_UNIT, v); #else /* WINUAE_FOR_HATARI */ //fprintf ( stderr , "wait read ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); int bus_pos = ( CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ) & 3; if ( ( bus_pos & 2 ) == 2 ) // if ( bus_pos ) { // fprintf ( stderr , "mem wait read %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); x_do_cycles ((4-bus_pos)*cpucycleunit); // fprintf ( stderr , "mem wait read after %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); } //fprintf ( stderr , "wait read2 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); if (mode < 0) put_long (addr, v); else if (mode > 0) put_word (addr, v); else if (mode == 0) put_byte (addr, v); //fprintf ( stderr , "wait read3 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); x_do_cycles_post (3*cpucycleunit, v); //fprintf ( stderr , "wait read4 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); #endif /* WINUAE_FOR_HATARI */ } #ifndef WINUAE_FOR_HATARI void do_cycles_ce (uae_u32 cycles) { cycles += extra_cycle; while (cycles >= CYCLE_UNIT) { do_cck(true); cycles -= CYCLE_UNIT; } extra_cycle = cycles; } #else /* [NP] Unlike Amiga, for Hatari in 68000 CE mode, we don't need to update other components */ /* on every sub cycle, so we can do all cycles in one single call to speed up */ /* emulation (this gains approx 7%) */ /* Also, for Amiga emulation, do_cycles will be called only on multiples of CYCLE_UNIT (=512), */ /* which is 2 CPU cycles and save the remaining part in extra_cycle. */ /* This is not required for Atari emulation which can be on odd number of cpu cycles too */ /* and we don't need to keep a remaining part in extra_cycle */ #undef HATARI_ROUND_CYCLES_TO_2 /* don't round to multiple of 2 cpu cycles */ void do_cycles_ce (int cycles) { //fprintf(stderr,"do cyc in %d %d\n" , cycles, extra_cycle); #ifdef HATARI_ROUND_CYCLES_TO_2 cycles += extra_cycle; extra_cycle = cycles & ( CYCLE_UNIT-1 ); do_cycles ( cycles - extra_cycle ); #else cycles += extra_cycle; extra_cycle = 0; do_cycles ( cycles ); #endif //fprintf(stderr,"do cyc out %d %d\n" , cycles -extra_cycle , extra_cycle); } #endif #ifdef WINUAE_FOR_HATARI /* Same as do_cycles_ce() with cycle exact blitter support */ void do_cycles_ce_hatari_blitter (int cycles) { cycles += extra_cycle; while (cycles >= CYCLE_UNIT) { if ( Blitter_Check_Simultaneous_CPU() == 0 ) do_cycles (1 * CYCLE_UNIT); Blitter_HOG_CPU_do_cycles_after ( 2 ); cycles -= CYCLE_UNIT; } extra_cycle = cycles; } #endif void do_cycles_ce020 (int cycles) { #ifndef WINUAE_FOR_HATARI evt_t cc; static int extra; cycles += extra; extra = 0; if (!cycles) { return; } cc = get_cycles(); while (cycles >= CYCLE_UNIT) { do_cck(true); cycles -= CYCLE_UNIT; } extra += cycles; #if 0 if (cycles > 0) { cc = get_cycles(); evt_t cc2 = cc + cycles; if ((cc & ~(CYCLE_UNIT - 1)) != (cc2 & ~(CYCLE_UNIT - 1))) { do_cck(); } } #endif #else /* WINUAE_FOR_HATARI */ static int extra; cycles += extra; extra = 0; if (!cycles) { return; } while (cycles >= CYCLE_UNIT) { do_cycles(1 * CYCLE_UNIT); cycles -= CYCLE_UNIT; } extra += cycles; #endif } bool is_cycle_ce(uaecptr addr) { #ifndef WINUAE_FOR_HATARI addrbank *ab = get_mem_bank_real(addr); if (!ab || (ab->flags & ABFLAG_CHIPRAM) || ab == &custom_bank) { struct rgabuf *r = read_rga_out(); if (r->alloc <= 0) { return false; } return true; } return false; #else /* WINUAE_FOR_HATARI */ return false; #endif /* WINUAE_FOR_HATARI */ } #endif void reset_frame_rate_hack (void) { #ifndef WINUAE_FOR_HATARI jitcount = 0; if (currprefs.m68k_speed >= 0) return; rpt_did_reset = 1; is_syncline = 0; vsyncmintime = read_processor_time () + vsynctimebase; write_log (_T("Resetting frame rate hack\n")); #endif /* WINUAE_FOR_HATARI */ } /* Code taken from main.cpp */ void fixup_cpu (struct uae_prefs *p) { if (p->cpu_frequency == 1000000) p->cpu_frequency = 0; #ifndef WINUAE_FOR_HATARI if (p->cpu_model >= 68030 && p->address_space_24) { error_log (_T("24-bit address space is not supported in 68030/040/060 configurations.")); p->address_space_24 = 0; } #else /* Hatari : don't force address_space_24=0 for 68030, as the Falcon has a 68030 EC with only 24 bits */ #endif if (p->cpu_model < 68020 && p->fpu_model && (p->cpu_compatible || p->cpu_cycle_exact)) { error_log (_T("FPU is not supported in 68000/010 configurations.")); p->fpu_model = 0; } switch (p->cpu_model) { case 68000: p->address_space_24 = 1; break; case 68010: p->address_space_24 = 1; break; case 68020: break; case 68030: break; case 68040: if (p->fpu_model) p->fpu_model = 68040; break; case 68060: if (p->fpu_model) p->fpu_model = 68060; break; } if (p->cpu_model < 68020 && p->cachesize) { p->cachesize = 0; error_log (_T("JIT requires 68020 or better CPU.")); } if (p->cpu_model >= 68040 && p->cachesize && p->cpu_compatible) p->cpu_compatible = false; if ((p->cpu_model < 68030 || p->cachesize) && p->mmu_model) { error_log (_T("MMU emulation requires 68030/040/060 and it is not JIT compatible.")); p->mmu_model = 0; } if (p->cachesize && p->cpu_cycle_exact) { error_log (_T("JIT and cycle-exact can't be enabled simultaneously.")); p->cachesize = 0; } if (p->cachesize && (p->fpu_no_unimplemented || p->int_no_unimplemented)) { error_log (_T("JIT is not compatible with unimplemented CPU/FPU instruction emulation.")); p->fpu_no_unimplemented = p->int_no_unimplemented = false; } #ifndef WINUAE_FOR_HATARI /* [NP] In Hatari, don't change m68k_speed in CE mode */ if (p->cpu_cycle_exact && p->m68k_speed < 0) p->m68k_speed = 0; #endif #ifndef WINUAE_FOR_HATARI if (p->immediate_blits && p->blitter_cycle_exact) { error_log (_T("Cycle-exact and immediate blitter can't be enabled simultaneously.\n")); p->immediate_blits = false; } if (p->immediate_blits && p->waiting_blits) { error_log (_T("Immediate blitter and waiting blits can't be enabled simultaneously.\n")); p->waiting_blits = 0; } #endif if (p->cpu_cycle_exact) p->cpu_compatible = true; } void custom_reset (bool hardreset, bool keyboardreset) { } // TODO NP remove ? #ifndef WINUAE_FOR_HATARI /* Code taken from main.cpp*/ void uae_reset (int hardreset) { currprefs.quitstatefile[0] = changed_prefs.quitstatefile[0] = 0; if (uae_quit_program == 0) { uae_quit_program = -UAE_RESET; if (keyboardreset) uae_quit_program = -UAE_RESET_KEYBOARD; if (hardreset) uae_quit_program = -UAE_RESET_HARD; } #endif /* Code taken from win32.cpp*/ void fpux_restore (int *v) { /*#ifndef _WIN64 if (v) _controlfp (*v, _MCW_IC | _MCW_RC | _MCW_PC); else _controlfp (fpucontrol, _MCW_IC | _MCW_RC | _MCW_PC); #endif */ } // TODO NP remove ? /* Code taken from win32.cpp*/ void sleep_millis (int ms) { /* Laurent : may be coded later (DSL-Delay ?) */ } /* Code just here to let newcpu.c link (original function is in inprec.cpp) */ int inprec_open(char *fname, int record) { return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/custom.h000066400000000000000000000114531504763705000236060ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator * * custom chip support * * (c) 1995 Bernd Schmidt */ #ifndef UAE_CUSTOM_H #define UAE_CUSTOM_H #include "uae/types.h" #include "machdep/rpt.h" /* These are the masks that are ORed together in the chipset_mask option. * If CSMASK_AGA is set, the ECS bits are guaranteed to be set as well. */ #define CSMASK_ECS_AGNUS 1 #define CSMASK_ECS_DENISE 2 #define CSMASK_AGA 4 #define CSMASK_MASK (CSMASK_ECS_AGNUS | CSMASK_ECS_DENISE | CSMASK_AGA) uae_u32 get_copper_address (int copno); extern int custom_init (void); extern void custom_reset (bool hardreset, bool keyboardreset); extern int intlev (void); extern void dumpcustom (void); extern void uae_reset (int hardreset); extern void do_copper (void); extern void notice_new_xcolors (void); extern void notice_screen_contents_lost(int monid); extern void init_row_map (void); extern void init_hz_normal (void); extern void init_custom (void); extern void set_picasso_hack_rate(int hz); /* Set to 1 to leave out the current frame in average frame time calculation. * Useful if the debugger was active. */ extern int bogusframe; extern uae_u32 hsync_counter, vsync_counter; extern uae_u16 dmacon; extern uae_u16 intena, intreq, intreqr; extern int vpos; extern int n_frames; STATIC_INLINE int dmaen(unsigned int dmamask) { return (dmamask & dmacon) && (dmacon & 0x200); } extern uae_u16 adkcon; extern unsigned int joy0dir, joy1dir; extern int joy0button, joy1button; extern void INTREQ(uae_u16); extern bool INTREQ_0(uae_u16); extern void INTREQ_f(uae_u16); extern void INTREQ_INT(int num, int delay); extern void rethink_uae_int(void); extern uae_u16 INTREQR(void); #define DMA_AUD0 0x0001 #define DMA_AUD1 0x0002 #define DMA_AUD2 0x0004 #define DMA_AUD3 0x0008 #define DMA_DISK 0x0010 #define DMA_SPRITE 0x0020 #define DMA_BLITTER 0x0040 #define DMA_COPPER 0x0080 #define DMA_BITPLANE 0x0100 #define DMA_MASTER 0x0200 #define DMA_BLITPRI 0x0400 #define CYCLE_BITPLANE 1 #define CYCLE_REFRESH 2 #define CYCLE_STROBE 3 #define CYCLE_MISC 4 #define CYCLE_SPRITE 5 #define CYCLE_COPPER 6 #define CYCLE_BLITTER 7 #define CYCLE_CPU 8 #define CYCLE_CPUNASTY 9 #define CYCLE_COPPER_SPECIAL 0x10 #define CYCLE_MASK 0x0f #ifdef AGA /* AGA mode color lookup tables */ extern unsigned int xredcolors[256], xgreencolors[256], xbluecolors[256]; #endif extern int xredcolor_s, xredcolor_b, xredcolor_m; extern int xgreencolor_s, xgreencolor_b, xgreencolor_m; extern int xbluecolor_s, xbluecolor_b, xbluecolor_m; #define RES_LORES 0 #define RES_HIRES 1 #define RES_SUPERHIRES 2 #define RES_MAX 2 #define VRES_NONDOUBLE 0 #define VRES_DOUBLE 1 #define VRES_QUAD 2 #define VRES_MAX 1 /* calculate shift depending on resolution (replaced "decided_hires ? 4 : 8") */ #define RES_SHIFT(res) ((res) == RES_LORES ? 8 : (res) == RES_HIRES ? 4 : 2) /* get resolution from bplcon0 */ #if AMIGA_ONLY STATIC_INLINE int GET_RES_DENISE (uae_u16 con0) { if (!(currprefs.chipset_mask & CSMASK_ECS_DENISE)) con0 &= ~0x40; // SUPERHIRES return ((con0) & 0x40) ? RES_SUPERHIRES : ((con0) & 0x8000) ? RES_HIRES : RES_LORES; } STATIC_INLINE int GET_RES_AGNUS (uae_u16 con0) { if (!(currprefs.chipset_mask & CSMASK_ECS_AGNUS)) con0 &= ~0x40; // SUPERHIRES return ((con0) & 0x40) ? RES_SUPERHIRES : ((con0) & 0x8000) ? RES_HIRES : RES_LORES; } #endif // AMIGA_ONLY /* get sprite width from FMODE */ #define GET_SPRITEWIDTH(FMODE) ((((FMODE) >> 2) & 3) == 3 ? 64 : (((FMODE) >> 2) & 3) == 0 ? 16 : 32) /* Compute the number of bitplanes from a value written to BPLCON0 */ STATIC_INLINE int GET_PLANES(uae_u16 bplcon0) { if ((bplcon0 & 0x0010) && (bplcon0 & 0x7000)) return 0; // >8 planes = 0 planes if (bplcon0 & 0x0010) return 8; // AGA 8-planes bit return (bplcon0 >> 12) & 7; // normal planes bits } extern void fpscounter_reset(void); extern frame_time_t idletime; extern int lightpen_x[2], lightpen_y[2]; extern int lightpen_cx[2], lightpen_cy[2], lightpen_active, lightpen_enabled, lightpen_enabled2; struct customhack { uae_u16 v; int vpos, hpos; }; void customhack_put (struct customhack *ch, uae_u16 v, int hpos); uae_u16 customhack_get (struct customhack *ch, int hpos); extern void alloc_cycle_ext (int, int); extern bool alloc_cycle_blitter(int hpos, uaecptr *ptr, int, int); extern uaecptr alloc_cycle_blitter_conflict_or(int, int, bool*); extern bool ispal (int *line); extern bool isvga(void); extern int current_maxvpos(void); extern int inprec_open(char *fname, int record); extern void sleep_millis (int ms); /* referred by prefetch.h */ extern uae_u32 wait_cpu_cycle_read (uaecptr addr, int mode); extern void wait_cpu_cycle_write (uaecptr addr, int mode, uae_u32 v); extern uae_u32 wait_cpu_cycle_read_ce020 (uaecptr addr, int mode); extern void wait_cpu_cycle_write_ce020 (uaecptr addr, int mode, uae_u32 v); #endif /* UAE_CUSTOM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/debug.c000066400000000000000000006461051504763705000233650ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * Debugger * * (c) 1995 Bernd Schmidt * (c) 2006 Toni Wilen * */ #include "sysconfig.h" #include "sysdeps.h" #include "main.h" #include "hatari-glue.h" #include #include "options_cpu.h" //#include "uae.h" #include "memory.h" #include "custom.h" #include "newcpu.h" #include "cpu_prefetch.h" #include "debug.h" #include "disasm.h" #include "debugmem.h" //#include "cia.h" //#include "xwin.h" //#include "identify.h" //#include "audio.h" //#include "sounddep/sound.h" //#include "disk.h" #include "savestate.h" //#include "autoconf.h" //#include "akiko.h" //#include "inputdevice.h" //#include "crc32.h" #include "cpummu.h" //#include "rommgr.h" //#include "inputrecord.h" //#include "calc.h" #include "cpummu.h" #include "cpummu030.h" //#include "ar.h" //#include "pci.h" //#include "ppc/ppcd.h" //#include "uae/io.h" //#include "uae/ppc.h" //#include "drawing.h" //#include "devices.h" //#include "blitter.h" //#include "ini.h" #include "readcpu.h" #include "cputbl.h" //#include "keybuf.h" static int trace_mode; static uae_u32 trace_param[3]; #ifdef WINUAE_FOR_HATARI #include "stMemory.h" int debugger_active; static int debug_mmu_mode; int debugging; int exception_debugging; void debug ( void ) { } /* Not used in Hatari for now */ #else int debugger_active; static int debug_rewind; static int memwatch_triggered; static int inside_debugger; int debugger_used; int memwatch_access_validator; int memwatch_enabled; int debugging; int exception_debugging; int no_trace_exceptions; int debug_copper = 0; int debug_dma = 0, debug_heatmap = 0; static uae_u32 debug_sprite_mask_val = 0xff; uae_u32 debug_sprite_mask = 0xffffffff; int debug_illegal = 0; uae_u64 debug_illegal_mask; static int debug_mmu_mode; static bool break_if_enforcer; static uaecptr debug_pc; static int trace_cycles; static int last_hpos1, last_hpos2; static int last_vpos1, last_vpos2; static int last_frame = -1; static evt_t last_cycles1, last_cycles2; static uaecptr processptr; static uae_char *processname; static uaecptr debug_copper_pc; extern int audio_channel_mask; extern int inputdevice_logging; static void debug_cycles(int mode) { trace_cycles = mode; last_cycles2 = get_cycles(); last_vpos2 = vpos; last_hpos2 = current_hpos(); } void deactivate_debugger (void) { inside_debugger = 0; debugger_active = 0; debugging = 0; exception_debugging = 0; processptr = 0; xfree (processname); processname = NULL; debugmem_enable(); debug_pc = 0xffffffff; keybuf_ignore_next_release(); deactivate_console(); } void activate_debugger (void) { disasm_init(); if (isfullscreen() > 0) return; debugger_load_libraries(); debugger_used = 1; inside_debugger = 1; debug_pc = 0xffffffff; trace_mode = 0; if (debugger_active) { // already in debugger but some break point triggered // during disassembly etc.. return; } debug_cycles(1); debugger_active = 1; set_special (SPCFLAG_BRK); debugging = 1; mmu_triggered = 0; debugmem_disable(); } void activate_debugger_new(void) { activate_debugger(); debug_pc = M68K_GETPC; } void activate_debugger_new_pc(uaecptr pc, int len) { activate_debugger(); trace_mode = TRACE_RANGE_PC; trace_param[0] = pc; trace_param[1] = pc + len; } static void debug_continue(void) { set_special(SPCFLAG_BRK); } bool debug_enforcer(void) { if (!break_if_enforcer) return false; activate_debugger(); return true; } int firsthist = 0; int lasthist = 0; struct cpuhistory { struct regstruct regs; int fp, vpos, hpos; }; static struct cpuhistory history[MAX_HIST]; static const TCHAR help[] = { _T(" HELP for UAE Debugger\n") _T(" -----------------------\n\n") _T(" g [
] Start execution at the current address or
.\n") _T(" c Dump state of the CIA, disk drives and custom registers.\n") _T(" r Dump state of the CPU.\n") _T(" r Modify CPU registers (Dx,Ax,USP,ISP,VBR,...).\n") _T(" rc[d] Show CPU instruction or data cache contents.\n") _T(" m
[] Memory dump starting at
.\n") _T(" a
Assembler.\n") _T(" d
[] Disassembly starting at
.\n") _T(" t [instructions] Step one or more instructions.\n") _T(" tx Break when any exception.\n") _T(" z Step through one instruction - useful for JSR, DBRA etc.\n") _T(" f Step forward until PC in RAM (\"boot block finder\").\n") _T(" f
[Nx] Add/remove breakpoint.\n") _T(" fa
[] []\n") _T(" Find effective address
.\n") _T(" fi Step forward until PC points to RTS, RTD or RTE.\n") _T(" fi [] [] Step forward until PC points to .\n") _T(" fp \"\"/ Step forward until process or is active.\n") _T(" fl List breakpoints.\n") _T(" fd Remove all breakpoints.\n") _T(" fs | Wait n scanlines/position.\n") _T(" fc Wait n color clocks.\n") _T(" fo [ ] Conditional register breakpoint [Nx] [Hx].\n") _T(" reg=Dx,Ax,PC,USP,ISP,VBR,SR. oper:!=,==,<,>,>=,<=,-,!- (-=val to val2 range).\n") _T(" f Step forward until <= PC <= .\n") _T(" e[x] Dump contents of all custom registers, ea = AGA colors.\n") _T(" i [] Dump contents of interrupt and trap vectors.\n") _T(" il [] Exception breakpoint.\n") _T(" o <0-2|addr> []View memory as Copper instructions.\n") _T(" od Enable/disable Copper vpos/hpos tracing.\n") _T(" ot Copper single step trace.\n") _T(" ob Copper breakpoint.\n") _T(" H[H] Show PC history (HH=full CPU info) instructions.\n") _T(" C Search for values like energy or lifes in games.\n") _T(" Cl List currently found trainer addresses.\n") _T(" D[idxzs <[max diff]>] Deep trainer. i=new value must be larger, d=smaller,\n") _T(" x = must be same, z = must be different, s = restart.\n") _T(" W Write into Amiga memory.\n") _T(" W 'string' Write into Amiga memory.\n") _T(" Wf , fill memory.\n") _T(" Wc , copy memory.\n") _T(" w
[V[.x]] (read/write/opcode) (freeze/mustchange/logonly/nobreak).\n") _T(" Add/remove memory watchpoints.\n") _T(" wd [<0-1>] Enable illegal access logger. 1 = enable break.\n") _T(" L [] Load a block of Amiga memory.\n") _T(" S Save a block of Amiga memory.\n") _T(" s \"\"/ [] []\n") _T(" Search for string/bytes.\n") _T(" T or Tt Show exec tasks and their PCs.\n") _T(" Td,Tl,Tr,Tp,Ts,TS,Ti,TO,TM,Tf Show devs, libs, resources, ports, semaphores,\n") _T(" residents, interrupts, doslist, memorylist, fsres.\n") _T(" b Step to previous state capture position.\n") _T(" M Enable or disable audio channels, bitplanes or sprites.\n") _T(" sp [] Dump sprite information.\n") _T(" di [] Break on disk access. R=DMA read,W=write,RW=both,P=PIO.\n") _T(" Also enables level 1 disk logging.\n") _T(" did Enable disk logging.\n") _T(" dj [] Enable joystick/mouse input debugging.\n") _T(" smc [<0-1>] Enable self-modifying code detector. 1 = enable break.\n") _T(" dm Dump current address space map.\n") _T(" v [] []\n") _T(" Show DMA data (accurate only in cycle-exact mode).\n") _T(" v [-1 to -4] = enable visual DMA debugger.\n") _T(" vh [ ] \"Heat map\"\n") _T(" I Send custom event string\n") _T(" ? Hex ($ and 0x)/Bin (%)/Dec (!) converter and calculator.\n") #ifdef _WIN32 _T(" x Close debugger.\n") _T(" xx Switch between console and GUI debugger.\n") _T(" mg
Memory dump starting at
in GUI.\n") _T(" dg
Disassembly starting at
in GUI.\n") #endif _T(" q Quit the emulator. You don't want to use this command.\n\n") }; void debug_help (void) { console_out (help); } struct mw_acc { uae_u32 mask; const TCHAR *name; }; static const struct mw_acc memwatch_access_masks[] = { { MW_MASK_ALL, _T("ALL") }, { MW_MASK_NONE, _T("NONE") }, { MW_MASK_ALL & ~(MW_MASK_CPU_I | MW_MASK_CPU_D_R | MW_MASK_CPU_D_W), _T("DMA") }, { MW_MASK_BLITTER_A | MW_MASK_BLITTER_B | MW_MASK_BLITTER_C | MW_MASK_BLITTER_D_N | MW_MASK_BLITTER_D_L | MW_MASK_BLITTER_D_F, _T("BLT") }, { MW_MASK_BLITTER_D_N | MW_MASK_BLITTER_D_L | MW_MASK_BLITTER_D_F, _T("BLTD") }, { MW_MASK_AUDIO_0 | MW_MASK_AUDIO_1 | MW_MASK_AUDIO_2 | MW_MASK_AUDIO_3, _T("AUD") }, { MW_MASK_BPL_0 | MW_MASK_BPL_1 | MW_MASK_BPL_2 | MW_MASK_BPL_3 | MW_MASK_BPL_4 | MW_MASK_BPL_5 | MW_MASK_BPL_6 | MW_MASK_BPL_7, _T("BPL") }, { MW_MASK_SPR_0 | MW_MASK_SPR_1 | MW_MASK_SPR_2 | MW_MASK_SPR_3 | MW_MASK_SPR_4 | MW_MASK_SPR_5 | MW_MASK_SPR_6 | MW_MASK_SPR_7, _T("SPR") }, { MW_MASK_CPU_I | MW_MASK_CPU_D_R | MW_MASK_CPU_D_W, _T("CPU") }, { MW_MASK_CPU_D_R | MW_MASK_CPU_D_W, _T("CPUD") }, { MW_MASK_CPU_I, _T("CPUI") }, { MW_MASK_CPU_D_R, _T("CPUDR") }, { MW_MASK_CPU_D_W, _T("CPUDW") }, { MW_MASK_COPPER, _T("COP") }, { MW_MASK_BLITTER_A, _T("BLTA") }, { MW_MASK_BLITTER_B, _T("BLTB") }, { MW_MASK_BLITTER_C, _T("BLTC") }, { MW_MASK_BLITTER_D_N, _T("BLTDN") }, { MW_MASK_BLITTER_D_L, _T("BLTDL") }, { MW_MASK_BLITTER_D_F, _T("BLTDF") }, { MW_MASK_DISK, _T("DSK") }, { MW_MASK_AUDIO_0, _T("AUD0") }, { MW_MASK_AUDIO_1, _T("AUD1") }, { MW_MASK_AUDIO_2, _T("AUD2") }, { MW_MASK_AUDIO_3, _T("AUD3") }, { MW_MASK_BPL_0, _T("BPL0") }, { MW_MASK_BPL_1, _T("BPL1") }, { MW_MASK_BPL_2, _T("BPL2") }, { MW_MASK_BPL_3, _T("BPL3") }, { MW_MASK_BPL_4, _T("BPL4") }, { MW_MASK_BPL_5, _T("BPL5") }, { MW_MASK_BPL_6, _T("BPL6") }, { MW_MASK_BPL_7, _T("BPL7") }, { MW_MASK_SPR_0, _T("SPR0") }, { MW_MASK_SPR_1, _T("SPR1") }, { MW_MASK_SPR_2, _T("SPR2") }, { MW_MASK_SPR_3, _T("SPR3") }, { MW_MASK_SPR_4, _T("SPR4") }, { MW_MASK_SPR_5, _T("SPR5") }, { MW_MASK_SPR_6, _T("SPR6") }, { MW_MASK_SPR_7, _T("SPR7") }, { 0, NULL }, }; static void mw_help(void) { for (int i = 0; memwatch_access_masks[i].mask; i++) { console_out_f(_T("%s "), memwatch_access_masks[i].name); } console_out_f(_T("\n")); } static int debug_linecounter; #define MAX_LINECOUNTER 1000 static int debug_out (const TCHAR *format, ...) { va_list parms; TCHAR buffer[4000]; va_start (parms, format); _vsntprintf (buffer, 4000 - 1, format, parms); va_end (parms); console_out (buffer); if (debug_linecounter < MAX_LINECOUNTER) debug_linecounter++; if (debug_linecounter >= MAX_LINECOUNTER) return 0; return 1; } #endif /* ! WINUAE_FOR_HATARI */ uae_u32 get_byte_debug (uaecptr addr) { uae_u32 v = 0xff; if (debug_mmu_mode) { flagtype olds = regs.s; regs.s = (debug_mmu_mode & 4) != 0; TRY(p) { if (currprefs.mmu_model == 68030) { v = mmu030_get_generic (addr, debug_mmu_mode, sz_byte, MMU030_SSW_SIZE_B); } else { if (debug_mmu_mode & 1) { bool odd = (addr & 1) != 0; addr &= ~1; v = mmu_get_iword(addr, sz_byte); if (!odd) v >>= 8; else v &= 0xff; } else { v = mmu_get_user_byte (addr, regs.s != 0, false, sz_byte, false); } } } CATCH(p) { } ENDTRY regs.s = olds; } else { #ifndef WINUAE_FOR_HATARI v = get_byte (addr); #else v = STMemory_ReadByte ( addr ); #endif } return v; } uae_u32 get_word_debug (uaecptr addr) { uae_u32 v = 0xffff; if (debug_mmu_mode) { flagtype olds = regs.s; regs.s = (debug_mmu_mode & 4) != 0; TRY(p) { if (currprefs.mmu_model == 68030) { v = mmu030_get_generic (addr, debug_mmu_mode, sz_word, MMU030_SSW_SIZE_W); } else { if (debug_mmu_mode & 1) { v = mmu_get_iword(addr, sz_word); } else { v = mmu_get_user_word (addr, regs.s != 0, false, sz_word, false); } } } CATCH(p) { } ENDTRY regs.s = olds; } else { #ifndef WINUAE_FOR_HATARI v = get_word (addr); #else v = STMemory_ReadWord ( addr ); #endif } return v; } uae_u32 get_long_debug (uaecptr addr) { uae_u32 v = 0xffffffff; if (debug_mmu_mode) { flagtype olds = regs.s; regs.s = (debug_mmu_mode & 4) != 0; TRY(p) { if (currprefs.mmu_model == 68030) { v = mmu030_get_generic (addr, debug_mmu_mode, sz_long, MMU030_SSW_SIZE_L); } else { if (debug_mmu_mode & 1) { v = mmu_get_ilong(addr, sz_long); } else { v = mmu_get_user_long (addr, regs.s != 0, false, sz_long, false); } } } CATCH(p) { } ENDTRY regs.s = olds; } else { #ifndef WINUAE_FOR_HATARI v = get_long (addr); #else v = STMemory_ReadLong ( addr ); #endif } return v; } uae_u32 get_iword_debug (uaecptr addr) { if (debug_mmu_mode) { return get_word_debug (addr); } else { #ifndef WINUAE_FOR_HATARI if (valid_address (addr, 2)) return get_word (addr); return 0xffff; #else return get_word_debug (addr); #endif } } uae_u32 get_ilong_debug (uaecptr addr) { if (debug_mmu_mode) { return get_long_debug (addr); } else { #ifndef WINUAE_FOR_HATARI if (valid_address (addr, 4)) return get_long (addr); return 0xffffffff; #else return get_long_debug (addr); #endif } } static uae_u8 *get_real_address_debug(uaecptr addr) { if (debug_mmu_mode) { flagtype olds = regs.s; TRY(p) { if (currprefs.mmu_model >= 68040) addr = mmu_translate(addr, 0, regs.s != 0, (debug_mmu_mode & 1), false, 0); else addr = mmu030_translate(addr, regs.s != 0, (debug_mmu_mode & 1), false); } CATCH(p) { } ENDTRY } return get_real_address(addr); } int debug_safe_addr (uaecptr addr, int size) { if (debug_mmu_mode) { flagtype olds = regs.s; regs.s = (debug_mmu_mode & 4) != 0; TRY(p) { if (currprefs.mmu_model >= 68040) addr = mmu_translate (addr, 0, regs.s != 0, (debug_mmu_mode & 1), false, size); else addr = mmu030_translate (addr, regs.s != 0, (debug_mmu_mode & 1), false); } CATCH(p) { STOPTRY; return 0; } ENDTRY regs.s = olds; } addrbank *ab = &get_mem_bank (addr); if (!ab) return 0; if (ab->flags & ABFLAG_SAFE) return 1; if (!ab->check (addr, size)) return 0; if (ab->flags & (ABFLAG_RAM | ABFLAG_ROM | ABFLAG_ROMIN | ABFLAG_SAFE)) return 1; return 0; } #ifndef WINUAE_FOR_HATARI static bool iscancel (int counter) { static int cnt; cnt++; if (cnt < counter) return false; cnt = 0; if (!console_isch ()) return false; console_getch (); return true; } static bool isoperator(TCHAR **cp) { TCHAR c = _totupper(**cp); TCHAR c1 = _totupper((*cp)[1]); return c == '+' || c == '-' || c == '/' || c == '*' || c == '(' || c == ')' || c == '|' || c == '&' || c == '^' || c == '=' || c == '>' || c == '<' || (c == 'R' && (c1 == 'L' || c1 == 'W' || c1 == 'B')); } static void ignore_ws (TCHAR **c) { while (**c && _istspace(**c)) (*c)++; } static TCHAR peekchar (TCHAR **c) { return **c; } static TCHAR readchar (TCHAR **c) { TCHAR cc = **c; (*c)++; return cc; } static TCHAR next_char(TCHAR **c) { ignore_ws (c); return *(*c)++; } static TCHAR next_char2(TCHAR **c) { return *(*c)++; } static TCHAR peek_next_char (TCHAR **c) { TCHAR *pc = *c; return pc[1]; } static int more_params(TCHAR **c) { ignore_ws(c); return (**c) != 0; } static int more_params2(TCHAR **c) { return (**c) != 0; } static uae_u32 readint(TCHAR **c, bool *err); static uae_u32 readbin(TCHAR **c, bool *err); static uae_u32 readhex(TCHAR **c, bool *err); static const TCHAR *debugoper[] = { _T("=="), _T("!="), _T("<="), _T(">="), _T("<"), _T(">"), _T("-"), _T("!-"), NULL }; static int getoperidx(TCHAR **c, bool *opersigned) { int i; TCHAR *p = *c; TCHAR tmp[10]; int extra = 0; i = 0; while (p[i]) { tmp[i] = _totupper(p[i]); if (i >= sizeof(tmp) / sizeof(TCHAR) - 1) break; i++; } tmp[i] = 0; if (!_tcsncmp(tmp, _T("!="), 2)) { (*c) += 2; return BREAKPOINT_CMP_NEQUAL; } else if (!_tcsncmp(tmp, _T("=="), 2)) { (*c) += 2; return BREAKPOINT_CMP_EQUAL; } else if (!_tcsncmp(tmp, _T(">="), 2)) { (*c) += 2; return BREAKPOINT_CMP_LARGER_EQUAL; } else if (!_tcsncmp(tmp, _T("<="), 2)) { (*c) += 2; return BREAKPOINT_CMP_SMALLER_EQUAL; } else if (!_tcsncmp(tmp, _T(">"), 1)) { (*c) += 1; return BREAKPOINT_CMP_LARGER; } else if (!_tcsncmp(tmp, _T("<"), 1)) { (*c) += 1; return BREAKPOINT_CMP_SMALLER; } else if (!_tcsncmp(tmp, _T("-"), 1)) { (*c) += 1; return BREAKPOINT_CMP_RANGE; } else if (!_tcsncmp(tmp, _T("!-"), 2)) { (*c) += 2; return BREAKPOINT_CMP_NRANGE; } *opersigned = false; if (**c == 's') { (*c)++; *opersigned = true; } return -1; } static const TCHAR *debugregs[] = { _T("D0"), _T("D1"), _T("D2"), _T("D3"), _T("D4"), _T("D5"), _T("D6"), _T("D7"), _T("A0"), _T("A1"), _T("A2"), _T("A3"), _T("A4"), _T("A5"), _T("A6"), _T("A7"), _T("PC"), _T("USP"), _T("MSP"), _T("ISP"), _T("VBR"), _T("SR"), _T("CCR"), _T("CACR"), _T("CAAR"), _T("SFC"), _T("DFC"), _T("TC"), _T("ITT0"), _T("ITT1"), _T("DTT0"), _T("DTT1"), _T("BUSC"), _T("PCR"), _T("FPIAR"), _T("FPCR"), _T("FPSR"), NULL }; int getregidx(TCHAR **c) { int i; TCHAR *p = *c; TCHAR tmp[10]; int extra = 0; i = 0; while (p[i]) { tmp[i] = _totupper(p[i]); if (i >= sizeof(tmp) / sizeof(TCHAR) - 1) break; i++; } tmp[i] = 0; for (int i = 0; debugregs[i]; i++) { if (!_tcsncmp(tmp, debugregs[i], _tcslen(debugregs[i]))) { (*c) += _tcslen(debugregs[i]); return i; } } return -1; } uae_u32 returnregx(int regid) { if (regid < BREAKPOINT_REG_PC) return regs.regs[regid]; switch(regid) { case BREAKPOINT_REG_PC: return M68K_GETPC; case BREAKPOINT_REG_USP: return regs.usp; case BREAKPOINT_REG_MSP: return regs.msp; case BREAKPOINT_REG_ISP: return regs.isp; case BREAKPOINT_REG_VBR: return regs.vbr; case BREAKPOINT_REG_SR: MakeSR(); return regs.sr; case BREAKPOINT_REG_CCR: MakeSR(); return regs.sr & 31; case BREAKPOINT_REG_CACR: return regs.cacr; case BREAKPOINT_REG_CAAR: return regs.caar; case BREAKPOINT_REG_SFC: return regs.sfc; case BREAKPOINT_REG_DFC: return regs.dfc; case BREAKPOINT_REG_TC: if (currprefs.cpu_model == 68030) return tc_030; return regs.tcr; case BREAKPOINT_REG_ITT0: if (currprefs.cpu_model == 68030) return tt0_030; return regs.itt0; case BREAKPOINT_REG_ITT1: if (currprefs.cpu_model == 68030) return tt1_030; return regs.itt1; case BREAKPOINT_REG_DTT0: return regs.dtt0; case BREAKPOINT_REG_DTT1: return regs.dtt1; case BREAKPOINT_REG_BUSC: return regs.buscr; case BREAKPOINT_REG_PCR: return regs.pcr; case BREAKPOINT_REG_FPIAR: return regs.fpiar; case BREAKPOINT_REG_FPCR: return regs.fpcr; case BREAKPOINT_REG_FPSR: return regs.fpsr; } return 0; } static int readregx(TCHAR **c, uae_u32 *valp) { int idx; uae_u32 addr; TCHAR *old = *c; addr = 0; if (_totupper(**c) == 'R') { (*c)++; } idx = getregidx(c); if (idx < 0) { *c = old; return 0; } addr = returnregx(idx); *valp = addr; return 1; } static bool checkisneg(TCHAR **c) { TCHAR nc = peekchar(c); if (nc == '-') { (*c)++; return true; } else if (nc == '+') { (*c)++; } return false; } static bool readbinx (TCHAR **c, uae_u32 *valp) { uae_u32 val = 0; bool first = true; bool negative = false; ignore_ws (c); negative = checkisneg(c); for (;;) { TCHAR nc = **c; if (nc != '1' && nc != '0' && nc != '`') { if (first) return false; break; } first = false; (*c)++; if (nc != '`') { val <<= 1; if (nc == '1') { val |= 1; } } } *valp = val * (negative ? -1 : 1); return true; } static bool readhexx (TCHAR **c, uae_u32 *valp) { uae_u32 val = 0; TCHAR nc; bool negative = false; ignore_ws(c); negative = checkisneg(c); if (!isxdigit(peekchar(c))) return false; while (isxdigit (nc = **c)) { (*c)++; val *= 16; nc = _totupper (nc); if (isdigit (nc)) { val += nc - '0'; } else { val += nc - 'A' + 10; } } *valp = val * (negative ? -1 : 1); return true; } static bool readintx (TCHAR **c, uae_u32 *valp) { uae_u32 val = 0; TCHAR nc; int negative = 0; ignore_ws (c); negative = checkisneg(c); if (!isdigit (peekchar (c))) return false; while (isdigit (nc = **c)) { (*c)++; val *= 10; val += nc - '0'; } *valp = val * (negative ? -1 : 1); return true; } static int checkvaltype2 (TCHAR **c, uae_u32 *val, TCHAR def) { TCHAR nc; ignore_ws (c); nc = _totupper (**c); if (nc == '!') { (*c)++; return readintx (c, val) ? 1 : 0; } if (nc == '$') { (*c)++; return readhexx (c, val) ? 1 : 0; } if (nc == '0' && _totupper ((*c)[1]) == 'X') { (*c)+= 2; return readhexx (c, val) ? 1 : 0; } if (nc == '%') { (*c)++; return readbinx (c, val) ? 1: 0; } if (nc >= 'A' && nc <= 'Z' && nc != 'A' && nc != 'D') { if (readregx (c, val)) return 1; } TCHAR name[256]; name[0] = 0; for (int i = 0; i < sizeof name / sizeof(TCHAR) - 1; i++) { nc = (*c)[i]; if (nc == 0 || nc == ' ') break; name[i] = nc; name[i + 1] = 0; } if (name[0]) { TCHAR *np = name; if (*np == '#') np++; if (debugmem_get_symbol_value(np, val)) { (*c) += _tcslen(name); return 1; } } if (def == '!') { return readintx (c, val) ? -1 : 0; } else if (def == '$') { return readhexx (c, val) ? -1 : 0; } else if (def == '%') { return readbinx (c, val) ? -1 : 0; } return 0; } static int readsize (int val, TCHAR **c) { TCHAR cc = _totupper (readchar(c)); if (cc == 'B') return 1; if (cc == 'W') return 2; if (cc == '3') return 3; if (cc == 'L') return 4; return 0; } static int checkvaltype(TCHAR **cp, uae_u32 *val, int *size, TCHAR def) { TCHAR form[256], *p; bool gotop = false; bool copyrest = false; double out; form[0] = 0; if (size) *size = 0; p = form; for (;;) { uae_u32 v; if (!checkvaltype2(cp, &v, def)) { if (isoperator(cp) || gotop || **cp == '\"' || **cp == '\'') { goto docalc; } return 0; } *val = v; // stupid but works! _stprintf(p, _T("%u"), v); p += _tcslen (p); *p = 0; if (peekchar(cp) == '.') { readchar(cp); if (size) { *size = readsize(v, cp); } } TCHAR *cpb = *cp; ignore_ws(cp); if (!isoperator(cp)) { *cp = cpb; break; } gotop = true; *p++= readchar(cp); *p = 0; } if (!gotop) { if (size && *size == 0) { uae_s32 v = (uae_s32)(*val); if (v > 65535 || v < -32767) { *size = 4; } else if (v > 255 || v < -127) { *size = 2; } else { *size = 1; } } return 1; } docalc: while (more_params2(cp)) { TCHAR c = readchar(cp); if (c == ' ') { break; } *p++ = c; } *p = 0; TCHAR tmp[MAX_DPATH]; int v = calc(form, &out, tmp, sizeof(tmp) / sizeof(TCHAR)); if (v > 0) { *val = (uae_u32)out; if (size && *size == 0) { uae_s32 v = (uae_s32)(*val); if (v > 255 || v < -127) { *size = 2; } else if (v > 65535 || v < -32767) { *size = 4; } else { *size = 1; } } return 1; } else if (v < 0) { console_out_f(_T("String returned: '%s'\n"), tmp); } return 0; } static uae_u32 readnum(TCHAR **c, int *size, TCHAR def, bool *err) { uae_u32 val; if (err) { *err = 0; } if (checkvaltype(c, &val, size, def)) { return val; } if (err) { *err = 1; } return 0; } static uae_u32 readint(TCHAR **c, bool *err) { int size; return readnum(c, &size, '!', err); } static uae_u32 readhex(TCHAR **c, bool *err) { int size; return readnum(c, &size, '$', err); } static uae_u32 readbin(TCHAR **c, bool *err) { int size; return readnum(c, &size, '%', err); } static uae_u32 readint(TCHAR **c, int *size, bool *err) { return readnum(c, size, '!', err); } static uae_u32 readhex(TCHAR **c, int *size, bool *err) { return readnum(c, size, '$', err); } static size_t next_string (TCHAR **c, TCHAR *out, int max, int forceupper) { TCHAR *p = out; int startmarker = 0; if (**c == '\"') { startmarker = 1; (*c)++; } *p = 0; while (**c != 0) { if (**c == '\"' && startmarker) break; if (**c == 32 && !startmarker) { ignore_ws (c); break; } *p = next_char2(c); if (forceupper) *p = _totupper(*p); *++p = 0; max--; if (max <= 1) break; } return _tcslen (out); } static void converter(TCHAR **c) { bool err; uae_u32 v = readint(c, &err); TCHAR s[100]; int i, j; if (err) { return; } for (i = 0, j = 0; i < 32; i++) { s[j++] = (v & (1 << (31 - i))) ? '1' : '0'; if (i < 31 && (i & 7) == 7) { s[j++] = '`'; } } s[j] = 0; console_out_f (_T("$%08X = %%%s = %u = %d\n"), v, s, v, (uae_s32)v); } static bool isrom(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (ab->flags & ABFLAG_ROM) { return true; } return false; } static uae_u32 lastaddr(uae_u32 start) { int lastbank = currprefs.address_space_24 ? 255 : 65535; addrbank *ab2 = get_mem_bank_real(start + 1); uae_u32 flags = ab2->flags & (ABFLAG_RAM | ABFLAG_ROM); if (start == 0xffffffff) { flags = ABFLAG_RAM; } for (int i = lastbank; i >= 0; i--) { addrbank *ab = get_mem_bank_real(i << 16); if (ab->baseaddr && (ab->flags & (ABFLAG_RAM | ABFLAG_ROM)) == flags) { return (i + 1) << 16; } } return 0; } static uae_u32 nextaddr_ab_flags = ABFLAG_RAM; static uae_u32 nextaddr_ab_flags_mask = ABFLAG_RAM; static void nextaddr_init(uaecptr addr) { addrbank *ab = get_mem_bank_real(addr + 1); if (addr != 0xffffffff && (ab->flags & ABFLAG_ROM)) { nextaddr_ab_flags = ABFLAG_ROM; nextaddr_ab_flags_mask = ABFLAG_ROM; } else { nextaddr_ab_flags = ABFLAG_RAM; nextaddr_ab_flags_mask = ABFLAG_RAM; } } static uaecptr nextaddr(uaecptr addr, uaecptr last, uaecptr *endp, bool verbose, bool *lfp) { addrbank *ab; int lastbank = currprefs.address_space_24 ? 255 : 65535; if (addr != 0xffffffff) { addrbank *ab2 = get_mem_bank_real(addr); addr++; ab = get_mem_bank_real(addr); if (ab->baseaddr && (ab->flags & nextaddr_ab_flags_mask) == nextaddr_ab_flags && ab == ab2) { return addr; } } else { addr = 0; } while (addr < (lastbank << 16)) { ab = get_mem_bank_real(addr); if (ab->baseaddr && ((ab->flags & nextaddr_ab_flags_mask) == nextaddr_ab_flags)) break; addr += 65536; } if (addr >= (lastbank << 16)) { if (endp) *endp = 0xffffffff; return 0xffffffff; } uaecptr start = addr; while (addr <= (lastbank << 16)) { addrbank *ab2 = get_mem_bank_real(addr); if ((last && last != 0xffffffff && addr >= last) || !ab2->baseaddr || ((ab2->flags & nextaddr_ab_flags_mask) != nextaddr_ab_flags) || ab != ab2) { if (endp) *endp = addr; break; } addr += 65536; } if (verbose) { if (lfp && *lfp) { console_out_f(_T("\n")); *lfp = false; } console_out_f(_T("Scanning.. %08x - %08x (%s)\n"), start, addr, get_mem_bank(start).name); } return start; } uaecptr dumpmem2 (uaecptr addr, TCHAR *out, int osize) { int i, cols = 8; int nonsafe = 0; if (osize <= (9 + cols * 5 + 1 + 2 * cols)) return addr; _stprintf (out, _T("%08X "), addr); for (i = 0; i < cols; i++) { uae_u8 b1, b2; b1 = b2 = 0; if (debug_safe_addr (addr, 1)) { b1 = get_byte_debug (addr + 0); b2 = get_byte_debug (addr + 1); _stprintf (out + 9 + i * 5, _T("%02X%02X "), b1, b2); out[9 + cols * 5 + 1 + i * 2 + 0] = b1 >= 32 && b1 < 127 ? b1 : '.'; out[9 + cols * 5 + 1 + i * 2 + 1] = b2 >= 32 && b2 < 127 ? b2 : '.'; } else { nonsafe++; _tcscpy (out + 9 + i * 5, _T("**** ")); out[9 + cols * 5 + 1 + i * 2 + 0] = '*'; out[9 + cols * 5 + 1 + i * 2 + 1] = '*'; } addr += 2; } out[9 + cols * 5] = ' '; out[9 + cols * 5 + 1 + 2 * cols] = 0; if (nonsafe == cols) { addrbank *ab = &get_mem_bank (addr); if (ab->name) memcpy (out + (9 + 4 + 1) * sizeof (TCHAR), ab->name, _tcslen (ab->name) * sizeof (TCHAR)); } return addr; } static TCHAR dumpmemline[MAX_LINEWIDTH + 1]; static void dumpmem (uaecptr addr, uaecptr *nxmem, int lines) { for (;lines--;) { addr = dumpmem2 (addr, dumpmemline, sizeof(dumpmemline) / sizeof(TCHAR)); debug_out (_T("%s"), dumpmemline); if (!debug_out (_T("\n"))) break; } *nxmem = addr; } static void dump_custom_regs(bool aga, bool ext) { size_t len; uae_u8 *p1, *p2, *p3, *p4; TCHAR extra1[256], extra2[256]; extra1[0] = 0; extra2[0] = 0; if (aga) { dump_aga_custom(); return; } p1 = p2 = save_custom (&len, 0, 1); p1 += 4; // skip chipset type for (int i = 0; i < 4; i++) { p4 = p1 + 0xa0 + i * 16; p3 = save_audio (i, &len, 0); p4[0] = p3[12]; p4[1] = p3[13]; p4[2] = p3[14]; p4[3] = p3[15]; p4[4] = p3[4]; p4[5] = p3[5]; p4[6] = p3[8]; p4[7] = p3[9]; p4[8] = 0; p4[9] = p3[1]; p4[10] = p3[10]; p4[11] = p3[11]; free (p3); } int total = 0; int i = 0; while (custd[i].name) { if (!(custd[i].special & CD_NONE)) total++; i++; } int cnt1 = 0; int cnt2 = 0; i = 0; while (i < total / 2 + 1) { for (;;) { cnt2++; if (!(custd[cnt2].special & CD_NONE)) break; } i++; } for (int i = 0; i < total / 2 + 1; i++) { uae_u16 v1, v2; int addr1, addr2; addr1 = custd[cnt1].adr & 0x1ff; addr2 = custd[cnt2].adr & 0x1ff; v1 = (p1[addr1 + 0] << 8) | p1[addr1 + 1]; v2 = (p1[addr2 + 0] << 8) | p1[addr2 + 1]; if (ext) { struct custom_store *cs; cs = &custom_storage[addr1 >> 1]; _stprintf(extra1, _T("\t%04X %08X %s"), cs->value, cs->pc & ~1, (cs->pc & 1) ? _T("COP") : _T("CPU")); cs = &custom_storage[addr2 >> 1]; _stprintf(extra2, _T("\t%04X %08X %s"), cs->value, cs->pc & ~1, (cs->pc & 1) ? _T("COP") : _T("CPU")); } console_out_f (_T("%03X %s\t%04X%s\t%03X %s\t%04X%s\n"), addr1, custd[cnt1].name, v1, extra1, addr2, custd[cnt2].name, v2, extra2); for (;;) { cnt1++; if (!(custd[cnt1].special & CD_NONE)) break; } for (;;) { cnt2++; if (!(custd[cnt2].special & CD_NONE)) break; } } xfree(p2); } static void dump_vectors (uaecptr addr) { int i = 0, j = 0; if (addr == 0xffffffff) addr = regs.vbr; while (int_labels[i].name || trap_labels[j].name) { if (int_labels[i].name) { console_out_f (_T("$%08X %02d: %12s $%08X "), int_labels[i].adr + addr, int_labels[i].adr / 4, int_labels[i].name, get_long_debug (int_labels[i].adr + addr)); i++; } if (trap_labels[j].name) { console_out_f (_T("$%08X %02d: %12s $%08X"), trap_labels[j].adr + addr, trap_labels[j].adr / 4, trap_labels[j].name, get_long_debug (trap_labels[j].adr + addr)); j++; } console_out (_T("\n")); } } static void disassemble_wait (FILE *file, unsigned long insn) { int vp, hp, ve, he, bfd, v_mask, h_mask; int doout = 0; vp = (insn & 0xff000000) >> 24; hp = (insn & 0x00fe0000) >> 16; ve = (insn & 0x00007f00) >> 8; he = (insn & 0x000000fe); bfd = (insn & 0x00008000) >> 15; /* bit15 can never be masked out*/ v_mask = vp & (ve | 0x80); h_mask = hp & he; if (v_mask > 0) { doout = 1; console_out (_T("vpos ")); if (ve != 0x7f) { console_out_f (_T("& 0x%02x "), ve); } console_out_f (_T(">= 0x%02x"), v_mask); } if (he > 0) { if (v_mask > 0) { console_out (_T(" and")); } console_out (_T(" hpos ")); if (he != 0xfe) { console_out_f (_T("& 0x%02x "), he); } console_out_f (_T(">= 0x%02x"), h_mask); } else { if (doout) console_out (_T(", ")); console_out (_T(", ignore horizontal")); } console_out_f (_T("\n \t; VP %02x, VE %02x; HP %02x, HE %02x; BFD %d\n"), vp, ve, hp, he, bfd); } #define NR_COPPER_RECORDS 100000 /* Record copper activity for the debugger. */ struct cop_rec { uae_u16 w1, w2; int hpos, vpos; int bhpos, bvpos; uaecptr addr, nextaddr; }; static struct cop_rec *cop_record[2]; static int nr_cop_records[2], curr_cop_set, selected_cop_set; #define NR_DMA_REC_LINES_MAX 1000 #define NR_DMA_REC_COLS_MAX 300 #define NR_DMA_REC_MAX (NR_DMA_REC_LINES_MAX * NR_DMA_REC_COLS_MAX) static struct dma_rec *dma_record_data; static int dma_record_cycle; static int dma_record_vpos_type; static struct dma_rec **dma_record_lines; struct dma_rec *last_dma_rec; struct dma_rec *record_dma_next_cycle(int hpos, int vpos, int vvpos) { if (!dma_record_data) { return NULL; } struct dma_rec *dr = &dma_record_data[dma_record_cycle]; struct dma_rec *dro = dr; dr->hpos = hpos; dr->vpos[0] = vpos; dr->vpos[1] = vvpos; dr->frame = vsync_counter; dr->tick = currcycle_cck; dma_record_cycle++; if (dma_record_cycle >= NR_DMA_REC_MAX) { dma_record_cycle = 0; } if (hpos == 0 && vvpos < NR_DMA_REC_LINES_MAX) { dma_record_lines[vvpos] = dr; } dr = &dma_record_data[dma_record_cycle]; memset(dr, 0, sizeof(struct dma_rec)); dr->reg = 0xffff; dr->cf_reg = 0xffff; dr->denise_evt[0] = DENISE_EVENT_UNKNOWN; dr->denise_evt[1] = DENISE_EVENT_UNKNOWN; dr->agnus_evt = dro->agnus_evt; dr->hpos = -1; return dro; } static void dma_record_init(void) { if (!dma_record_data) { dma_record_data = xcalloc(struct dma_rec, NR_DMA_REC_MAX + 2); dma_record_lines = xcalloc(struct dma_rec*, NR_DMA_REC_LINES_MAX); for (int i = 0;i < NR_DMA_REC_MAX; i++) { struct dma_rec *dr = &dma_record_data[i]; dr->reg = 0xffff; dr->cf_reg = 0xffff; dr->hpos = -1; } } } void record_dma_reset(int start) { if (start && !dma_record_data) { dma_record_init(); } if (!dma_record_data) { return; } if (start && !debug_dma) { debug_dma = start; } } void record_copper_reset (void) { /* Start a new set of copper records. */ curr_cop_set ^= 1; nr_cop_records[curr_cop_set] = 0; } STATIC_INLINE uae_u32 ledcolor (uae_u32 c, uae_u32 *rc, uae_u32 *gc, uae_u32 *bc, uae_u32 *a) { uae_u32 v = rc[(c >> 16) & 0xff] | gc[(c >> 8) & 0xff] | bc[(c >> 0) & 0xff]; if (a) v |= a[255 - ((c >> 24) & 0xff)]; return v; } #define lc(x) ledcolor (x, xredcolors, xgreencolors, xbluecolors, NULL) #define DMARECORD_SUBITEMS 8 struct dmadebug { uae_u32 l[DMARECORD_SUBITEMS]; uae_u8 r, g, b; bool enabled; int max; const TCHAR *name; }; static uae_u32 intlevc[] = { 0x000000, 0x444444, 0x008800, 0xffff00, 0x000088, 0x880000, 0xff0000, 0xffffff }; static struct dmadebug debug_colors[DMARECORD_MAX]; static bool debug_colors_set; static void set_dbg_color(int index, int extra, uae_u8 r, uae_u8 g, uae_u8 b, int max, const TCHAR *name) { if (extra <= 0) { debug_colors[index].r = r; debug_colors[index].g = g; debug_colors[index].b = b; debug_colors[index].enabled = true; } if (name != NULL) debug_colors[index].name = name; if (max > 0) debug_colors[index].max = max; if (extra >= 0) { debug_colors[index].l[extra] = lc((r << 16) | (g << 8) | (b << 0)); } else { for (int i = 0; i < DMARECORD_SUBITEMS; i++) { debug_colors[index].l[i] = lc((r << 16) | (g << 8) | (b << 0)); } } } static void set_debug_colors(void) { if (debug_colors_set) return; debug_colors_set = true; set_dbg_color(0, 0, 0x22, 0x22, 0x22, 1, _T("-")); set_dbg_color(DMARECORD_REFRESH, 0, 0x44, 0x44, 0x44, 4, _T("Refresh")); set_dbg_color(DMARECORD_CPU, 0, 0xa2, 0x53, 0x42, 2, _T("CPU")); // code set_dbg_color(DMARECORD_COPPER, 0, 0xee, 0xee, 0x00, 3, _T("Copper")); set_dbg_color(DMARECORD_AUDIO, 0, 0xff, 0x00, 0x00, 4, _T("Audio")); set_dbg_color(DMARECORD_BLITTER, 0, 0x00, 0x88, 0x88, 2, _T("Blitter")); // blit A set_dbg_color(DMARECORD_BITPLANE, 0, 0x00, 0x00, 0xff, 8, _T("Bitplane")); set_dbg_color(DMARECORD_SPRITE, 0, 0xff, 0x00, 0xff, 8, _T("Sprite")); set_dbg_color(DMARECORD_DISK, 0, 0xff, 0xff, 0xff, 3, _T("Disk")); set_dbg_color(DMARECORD_CONFLICT, 0, 0xff, 0xb8, 0x40, 0, _T("Conflict")); for (int i = 0; i < DMARECORD_MAX; i++) { for (int j = 1; j < DMARECORD_SUBITEMS; j++) { debug_colors[i].l[j] = debug_colors[i].l[0]; } } set_dbg_color(DMARECORD_CPU, 1, 0xad, 0x98, 0xd6, 0, NULL); // data set_dbg_color(DMARECORD_COPPER, 1, 0xaa, 0xaa, 0x22, 0, NULL); // wait set_dbg_color(DMARECORD_COPPER, 2, 0x66, 0x66, 0x44, 0, NULL); // special set_dbg_color(DMARECORD_BLITTER, 1, 0x00, 0x88, 0x88, 0, NULL); // blit B set_dbg_color(DMARECORD_BLITTER, 2, 0x00, 0x88, 0x88, 0, NULL); // blit C set_dbg_color(DMARECORD_BLITTER, 3, 0x00, 0xaa, 0x88, 0, NULL); // blit D (write) set_dbg_color(DMARECORD_BLITTER, 4, 0x00, 0x88, 0xff, 0, NULL); // fill A-D set_dbg_color(DMARECORD_BLITTER, 6, 0x00, 0xff, 0x00, 0, NULL); // line A-D } static int cycles_toggle; static void debug_draw_cycles(uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors) { int y, x, xx, dx, xplus, yplus; struct dma_rec *dr; if (debug_dma >= 4) yplus = 2; else yplus = 1; if (debug_dma >= 5) xplus = 3; else if (debug_dma >= 3) xplus = 2; else xplus = 1; y = line / yplus; if (yplus < 2) y -= 8; if (y < 0) return; if (y >= NR_DMA_REC_LINES_MAX) return; if (y >= height) return; dr = dma_record_lines[y]; if (!dr) return; dx = width - xplus * ((maxhpos + 1) & ~1) - 16; bool ended = false; uae_s8 intlev = 0; for (x = 0; x < NR_DMA_REC_COLS_MAX; x++) { uae_u32 c = debug_colors[0].l[0]; xx = x * xplus + dx; if (dr->end) { ended = true; } if (ended) { c = 0; } else { if (dr->reg != 0xffff && debug_colors[dr->type].enabled) { // General DMA slots c = debug_colors[dr->type].l[dr->extra & 7]; // Special cases if (dr->cf_reg != 0xffff && ((cycles_toggle ^ line) & 1)) { c = debug_colors[DMARECORD_CONFLICT].l[0]; } else if (dr->extra > 0xF) { // High bits of "extra" contain additional blitter state. if (dr->extra & 0x10) c = debug_colors[dr->type].l[4]; // blit fill, channels A-D else if (dr->extra & 0x20) c = debug_colors[dr->type].l[6]; // blit line, channels A-D } } } if (dr->intlev > intlev) intlev = dr->intlev; putpixel(buf, bpp, xx + 4, c); if (xplus > 1) putpixel(buf, bpp, xx + 4 + 1, c); if (xplus > 2) putpixel(buf, bpp, xx + 4 + 2, c); dr++; if (dr->hpos == 0) { break; } } putpixel (buf, bpp, dx + 0, 0); putpixel (buf, bpp, dx + 1, lc(intlevc[intlev])); putpixel (buf, bpp, dx + 2, lc(intlevc[intlev])); putpixel (buf, bpp, dx + 3, 0); } #define HEATMAP_WIDTH 256 #define HEATMAP_HEIGHT 256 #define HEATMAP_COUNT 32 #define HEATMAP_DIV 8 static const int max_heatmap = 16 * 1048576; // 16M static uae_u32 *heatmap_debug_colors; static struct memory_heatmap *heatmap; struct memory_heatmap { uae_u32 mask; uae_u32 cpucnt; uae_u16 cnt; uae_u16 type, extra; }; static void debug_draw_heatmap(uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors) { struct memory_heatmap *mht = heatmap; int dx = 16; int y = line; if (y < 0 || y >= HEATMAP_HEIGHT) return; mht += y * HEATMAP_WIDTH; for (int x = 0; x < HEATMAP_WIDTH; x++) { uae_u32 c = heatmap_debug_colors[mht->cnt * DMARECORD_MAX + mht->type]; //c = heatmap_debug_colors[(HEATMAP_COUNT - 1) * DMARECORD_MAX + DMARECORD_CPU_I]; int xx = x + dx; putpixel(buf, bpp, xx, c); if (mht->cnt > 0) mht->cnt--; mht++; } } void debug_draw(uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors) { if (!heatmap_debug_colors) { heatmap_debug_colors = xcalloc(uae_u32, DMARECORD_MAX * HEATMAP_COUNT); set_debug_colors(); for (int i = 0; i < HEATMAP_COUNT; i++) { uae_u32 *cp = heatmap_debug_colors + i * DMARECORD_MAX; for (int j = 0; j < DMARECORD_MAX; j++) { uae_u8 r = debug_colors[j].r; uae_u8 g = debug_colors[j].g; uae_u8 b = debug_colors[j].b; r = r * i / HEATMAP_COUNT; g = g * i / HEATMAP_COUNT; b = b * i / HEATMAP_COUNT; cp[j] = lc((r << 16) | (g << 8) | (b << 0)); } } } if (heatmap) { debug_draw_heatmap(buf, bpp, line, width, height, xredcolors, xgreencolors, xbluecolors); } else if (dma_record_data) { debug_draw_cycles(buf, bpp, line, width, height, xredcolors, xgreencolors, xbluecolors); } } struct heatmapstore { TCHAR *s; double v; }; static void heatmap_stats(TCHAR **c) { int range = 95; int maxlines = 30; double max; int maxcnt; uae_u32 mask = MW_MASK_CPU_I; const TCHAR *maskname = NULL; if (more_params(c)) { if (**c == 'c' && peek_next_char(c) == 0) { for (int i = 0; i < max_heatmap / HEATMAP_DIV; i++) { struct memory_heatmap *hm = &heatmap[i]; memset(hm, 0, sizeof(struct memory_heatmap)); } console_out(_T("heatmap data cleared\n")); return; } if (!isdigit(peek_next_char(c))) { TCHAR str[100]; if (next_string(c, str, sizeof str / sizeof (TCHAR), true)) { for (int j = 0; memwatch_access_masks[j].mask; j++) { if (!_tcsicmp(str, memwatch_access_masks[j].name)) { mask = memwatch_access_masks[j].mask; maskname = memwatch_access_masks[j].name; console_out_f(_T("Mask %08x Name %s\n"), mask, maskname); break; } } } if (more_params(c)) { maxlines = readint(c, NULL); } } else { range = readint(c, NULL); if (more_params(c)) { maxlines = readint(c, NULL); } } } if (maxlines <= 0) maxlines = 10000; if (mask != MW_MASK_CPU_I) { int found = -1; int firstaddress = 0; for (int lines = 0; lines < maxlines; lines++) { for (; firstaddress < max_heatmap / HEATMAP_DIV; firstaddress++) { struct memory_heatmap *hm = &heatmap[firstaddress]; if (hm->mask & mask) break; } if (firstaddress == max_heatmap / HEATMAP_DIV) return; int lastaddress; for (lastaddress = firstaddress; lastaddress < max_heatmap / HEATMAP_DIV; lastaddress++) { struct memory_heatmap *hm = &heatmap[lastaddress]; if (!(hm->mask & mask)) break; } lastaddress--; console_out_f(_T("%03d: %08x - %08x %08x (%d) %s\n"), lines, firstaddress * HEATMAP_DIV, lastaddress * HEATMAP_DIV + HEATMAP_DIV - 1, lastaddress * HEATMAP_DIV - firstaddress * HEATMAP_DIV + HEATMAP_DIV - 1, lastaddress * HEATMAP_DIV - firstaddress * HEATMAP_DIV + HEATMAP_DIV - 1, maskname); firstaddress = lastaddress + 1; } } else { #define MAX_HEATMAP_LINES 1000 struct heatmapstore linestore[MAX_HEATMAP_LINES] = { 0 }; int storecnt = 0; uae_u32 maxlimit = 0xffffffff; max = 0; maxcnt = 0; for (int i = 0; i < max_heatmap / HEATMAP_DIV; i++) { struct memory_heatmap *hm = &heatmap[i]; if (hm->cpucnt > 0) { max += hm->cpucnt; maxcnt++; } } if (!maxcnt) { console_out(_T("No CPU accesses found\n")); return; } for (int lines = 0; lines < maxlines; lines++) { int found = -1; int foundcnt = 0; for (int i = 0; i < max_heatmap / HEATMAP_DIV; i++) { struct memory_heatmap *hm = &heatmap[i]; if (hm->cpucnt > 0 && hm->cpucnt > foundcnt && hm->cpucnt < maxlimit) { foundcnt = hm->cpucnt; found = i; } } if (found < 0) break; int totalcnt = 0; int cntrange = foundcnt * range / 100; if (cntrange <= 0) cntrange = 1; int lastaddress; for (lastaddress = found; lastaddress < max_heatmap / HEATMAP_DIV; lastaddress++) { struct memory_heatmap *hm = &heatmap[lastaddress]; if (hm->cpucnt == 0 || hm->cpucnt < cntrange || hm->cpucnt >= maxlimit) break; totalcnt += hm->cpucnt; } lastaddress--; int firstaddress; for (firstaddress = found - 1; firstaddress >= 0; firstaddress--) { struct memory_heatmap *hm = &heatmap[firstaddress]; if (hm->cpucnt == 0 || hm->cpucnt < cntrange || hm->cpucnt >= maxlimit) break; totalcnt += hm->cpucnt; } firstaddress--; firstaddress *= HEATMAP_DIV; lastaddress *= HEATMAP_DIV; TCHAR tmp[100]; double pct = totalcnt / max * 100.0; _stprintf(tmp, _T("%03d: %08x - %08x %08x (%d) %.5f%%\n"), lines + 1, firstaddress, lastaddress + HEATMAP_DIV - 1, lastaddress - firstaddress + HEATMAP_DIV - 1, lastaddress - firstaddress + HEATMAP_DIV - 1, pct); linestore[storecnt].s = my_strdup(tmp); linestore[storecnt].v = pct; storecnt++; if (storecnt >= MAX_HEATMAP_LINES) break; maxlimit = foundcnt; } for (int lines1 = 0; lines1 < storecnt; lines1++) { for (int lines2 = lines1 + 1; lines2 < storecnt; lines2++) { if (linestore[lines1].v < linestore[lines2].v) { struct heatmapstore hms; memcpy(&hms, &linestore[lines1], sizeof(struct heatmapstore)); memcpy(&linestore[lines1], &linestore[lines2], sizeof(struct heatmapstore)); memcpy(&linestore[lines2], &hms, sizeof(struct heatmapstore)); } } } for (int lines1 = 0; lines1 < storecnt; lines1++) { console_out(linestore[lines1].s); xfree(linestore[lines1].s); } } } static void free_heatmap(void) { xfree(heatmap); heatmap = NULL; debug_heatmap = 0; } static void init_heatmap(void) { if (!heatmap) heatmap = xcalloc(struct memory_heatmap, max_heatmap / HEATMAP_DIV); } static void memwatch_heatmap(uaecptr addr, int rwi, int size, uae_u32 accessmask) { if (addr >= max_heatmap || !heatmap) return; struct memory_heatmap *hm = &heatmap[addr / HEATMAP_DIV]; if (accessmask & MW_MASK_CPU_I) { hm->cpucnt++; } hm->cnt = HEATMAP_COUNT - 1; int type = 0; int extra = 0; for (int i = 0; i < 32; i++) { if (accessmask & (1 << i)) { switch (1 << i) { case MW_MASK_BPL_0: case MW_MASK_BPL_1: case MW_MASK_BPL_2: case MW_MASK_BPL_3: case MW_MASK_BPL_4: case MW_MASK_BPL_5: case MW_MASK_BPL_6: case MW_MASK_BPL_7: type = DMARECORD_BITPLANE; break; case MW_MASK_AUDIO_0: case MW_MASK_AUDIO_1: case MW_MASK_AUDIO_2: case MW_MASK_AUDIO_3: type = DMARECORD_AUDIO; break; case MW_MASK_BLITTER_A: case MW_MASK_BLITTER_B: case MW_MASK_BLITTER_C: case MW_MASK_BLITTER_D_N: case MW_MASK_BLITTER_D_F: case MW_MASK_BLITTER_D_L: type = DMARECORD_BLITTER; break; case MW_MASK_COPPER: type = DMARECORD_COPPER; break; case MW_MASK_DISK: type = DMARECORD_DISK; break; case MW_MASK_CPU_I: type = DMARECORD_CPU; break; case MW_MASK_CPU_D_R: case MW_MASK_CPU_D_W: type = DMARECORD_CPU; extra = 1; break; } } } hm->type = type; hm->extra = extra; hm->mask |= accessmask; } struct refdata { evt_t c; uae_u32 cnt; }; static struct refdata refreshtable[1024]; static int refcheck_count; #define REFRESH_LINES 64 static void check_refreshed(void) { int max = ecs_agnus ? 512 : 256; int reffail = 0; evt_t c = get_cycles(); for (int i = 0; i < max; i++) { struct refdata *rd = &refreshtable[i]; if (rd->cnt < 10) { rd->cnt++; } if (rd->cnt == 10) { reffail++; rd->cnt = 0; } if (rd->c && (int)c - (int)rd->c >= CYCLE_UNIT * maxhpos * REFRESH_LINES) { reffail++; rd->c = 0; rd->cnt = 0; } if (reffail) { write_log("%03u ", i); } } if (reffail) { write_log("%d memory rows not refreshed fast enough!\n", reffail); } } void debug_mark_refreshed(uaecptr rp) { int ras; if (ecs_agnus && currprefs.chipmem.size > 0x100000) { ras = (rp >> 9) & 0x3ff; } else if (ecs_agnus) { ras = (rp >> 9) & 0x1ff; } else { ras = (rp >> 1) & 0xff; } struct refdata *rd = &refreshtable[ras]; evt_t c = get_cycles(); rd->c = c; rd->cnt = 0; } void record_dma_ipl(void) { struct dma_rec *dr; if (!dma_record_data) return; dr = &dma_record_data[dma_record_cycle]; dr->intlev = regs.intmask; dr->ipl = regs.ipl_pin; dr->evt |= DMA_EVENT_IPL; } void record_dma_ipl_sample(void) { struct dma_rec *dr; if (!dma_record_data) return; dr = &dma_record_data[dma_record_cycle]; dr->intlev = regs.intmask; dr->ipl2 = regs.ipl_pin; dr->evt |= DMA_EVENT_IPLSAMPLE; } void record_dma_event_denise(struct dma_rec *dr, int h, uae_u32 evt, bool onoff) { if (!dma_record_data) return; if (h && !(dr->denise_evt[1] & DENISE_EVENT_COPIED)) { dr->denise_evt[1] = dr->denise_evt[0] | DENISE_EVENT_COPIED; } if (onoff) { dr->denise_evt[h] |= evt; dr->denise_evt_changed[h] |= evt; } else { dr->denise_evt[h] &= ~evt; dr->denise_evt_changed[h] |= evt; } } void record_dma_event_agnus(uae_u32 evt, bool onoff) { struct dma_rec *dr; if (!dma_record_data) return; dr = &dma_record_data[dma_record_cycle]; if (onoff) { dr->agnus_evt |= evt; dr->agnus_evt_changed |= evt; } else { dr->agnus_evt &= ~evt; dr->agnus_evt_changed |= evt; } } void record_dma_event(uae_u32 evt) { struct dma_rec *dr; if (!dma_record_data) return; dr = &dma_record_data[dma_record_cycle]; dr->evt |= evt; dr->ipl = regs.ipl_pin; } void record_dma_event_data(uae_u32 evt, uae_u32 data) { struct dma_rec *dr; if (!dma_record_data) return; dr = &dma_record_data[dma_record_cycle]; dr->evt |= evt; dr->evtdata = data; dr->evtdataset = true; dr->ipl = regs.ipl_pin; } void record_dma_replace(int type, int extra) { struct dma_rec *dr; if (!dma_record_data) return; dr = &dma_record_data[dma_record_cycle]; if (dr->reg == 0xffff) { write_log(_T("DMA record replace without old data!\n")); return; } if (dr->type != type) { write_log(_T("DMA record replace type change %d -> %d!\n"), dr->type, type); return; } dr->extra = extra; } static void dma_conflict(int vpos, int hpos, struct dma_rec *dr, int reg, bool write) { write_log(_T("DMA conflict %c: v=%d h=%d OREG=%04X NREG=%04X\n"), write ? 'W' : 'R', vpos, hpos, dr->reg, reg); //activate_debugger(); } void record_dma_write(uae_u16 reg, uae_u32 dat, uae_u32 addr, int type, int extra) { struct dma_rec *dr; if (!dma_record_data) { dma_record_init(); if (!dma_record_data) return; } dr = &dma_record_data[dma_record_cycle]; if (dr->reg != 0xffff) { dr->cf_reg = reg; dr->cf_dat = dat; dr->cf_addr = addr; dma_conflict(dr->vpos[0], dr->hpos, dr, reg, false); return; } dr->reg = reg; dr->dat = dat; dr->addr = addr; dr->type = type; dr->extra = extra; dr->intlev = regs.intmask; dr->ipl = regs.ipl_pin; dr->size = 2; dr->end = false; last_dma_rec = dr; debug_mark_refreshed(dr->addr); } void record_dma_read_value_pos(uae_u32 v) { if (!dma_record_data) return; struct dma_rec *dr = &dma_record_data[dma_record_cycle]; last_dma_rec = dr; record_dma_read_value(v); } void record_dma_read_value(uae_u32 v) { if (last_dma_rec) { if (last_dma_rec->cf_reg != 0xffff) { last_dma_rec->cf_dat = v; } else { last_dma_rec->dat = v; } last_dma_rec->size = 2; } } void record_dma_read_value_wide(uae_u64 v, bool quad) { if (last_dma_rec) { if (last_dma_rec->cf_reg != 0xffff) { last_dma_rec->cf_dat = (uae_u16)v; } else { last_dma_rec->dat = v; } last_dma_rec->size = quad ? 8 : 4; } } bool record_dma_check(void) { if (!dma_record_data) return false; struct dma_rec *dr = &dma_record_data[dma_record_cycle]; return dr->reg != 0xffff; } void record_dma_clear(void) { if (!dma_record_data) return; struct dma_rec *dr = &dma_record_data[dma_record_cycle]; dr->reg = 0xffff; dr->cf_reg = 0xffff; } void record_cia_access(int r, int mask, uae_u16 value, bool rw, int phase) { dma_record_init(); if (!dma_record_data) return; struct dma_rec *dr = &dma_record_data[dma_record_cycle]; if (dr->ciaphase < 0) { return; } dr->ciamask = mask; dr->ciareg = r; dr->ciavalue = value; dr->ciarw = rw; dr->ciaphase = phase; } void record_dma_read(uae_u16 reg, uae_u32 addr, int type, int extra) { dma_record_init(); if (!dma_record_data) return; struct dma_rec *dr = &dma_record_data[dma_record_cycle]; if (dr->reg != 0xffff) { if (dr->reg != reg) { dma_conflict(dr->vpos[0], dr->hpos, dr, reg, false); dr->cf_reg = reg; dr->cf_addr = addr; } return; } dr->reg = reg; dr->dat = 0; dr->addr = addr; dr->type = type; dr->extra = extra; dr->intlev = regs.intmask; dr->ipl = regs.ipl_pin; dr->end = false; last_dma_rec = dr; debug_mark_refreshed(dr->addr); } static bool get_record_dma_info(struct dma_rec *drs, struct dma_rec *dr, TCHAR *l1, TCHAR *l1b, TCHAR *l1c, TCHAR *l2, TCHAR *l3, TCHAR *l4, TCHAR *l5, TCHAR *l6, uae_u32 *split, int *iplp) { int longsize = dr->size; bool got = false; int r = dr->reg; int regsize = 3; const TCHAR *sr; int br = dr->extra & 7; int chcnt = -1; TCHAR srtext[10]; bool extra64 = false; uae_u32 extraval; bool noval = false; if (l1) l1[0] = 0; if (l1b) l1b[0] = 0; if (l1c) l1c[0] = 0; if (l2) l2[0] = 0; if (l3) l3[0] = 0; if (l4) l4[0] = 0; if (l5) l5[0] = 0; if (l6) l6[0] = 0; int hpos = dr->hpos; int dhpos0 = dr->dhpos[0]; int dhpos1 = dr->dhpos[1]; if (hpos < 0) { struct dma_rec *dr2 = dr; int cnt = 0; while (dr2->vpos[dma_record_vpos_type] == dr->vpos[dma_record_vpos_type]) { if (dr2 == drs) { hpos = addrdiff(dr, drs); break; } if (dr2->hpos >= 0) { hpos = dr2->hpos + cnt; break; } cnt++; dr2--; } } if (hpos < 0) { hpos = 0; } if (split) { if ((dr->evt & DMA_EVENT_CPUINS) && dr->evtdataset) { *split = dr->evtdata; } } if (dr->type != 0 || dr->reg != 0xffff || dr->evt) got = true; sr = _T(" "); if (dr->type == DMARECORD_COPPER) { if (br == 3) sr = _T("COP-S"); else if (br == 2) sr = _T("COP-W"); else if (br == 1) sr = _T("COP-M"); else if (br == 4) sr = _T("COP-X"); else if (br == 5) sr = _T("COP-1"); else if (br == 6) sr = _T("COP-J"); else if (br == 7) sr = _T("COP-D"); else sr = _T("COP "); } else if (dr->type == DMARECORD_BLITTER) { if (dr->extra & 0x20) { if (br == 0) sr = _T("BLL-A"); if (br == 1) sr = _T("BLL-B"); if (br == 2) sr = _T("BLL-C"); if (br == 3) sr = _T("BLL-D"); } else if (dr->extra & 0x10) { if (br == 0) sr = _T("BLF-A"); if (br == 1) sr = _T("BLF-B"); if (br == 2) sr = _T("BLF-C"); if (br == 3) sr = _T("BLF-D"); } else { if (br == 0) sr = _T("BLT-A"); if (br == 1) sr = _T("BLT-B"); if (br == 2) sr = _T("BLT-C"); if (br == 3) sr = _T("BLT-D"); } regsize = 2; } else if (dr->type == DMARECORD_REFRESH) { sr = _T("RFS"); chcnt = br; noval = true; } else if (dr->type == DMARECORD_AUDIO) { sr = _T("AUD"); chcnt = br; } else if (dr->type == DMARECORD_DISK) { sr = _T("DSK"); chcnt = br; } else if (dr->type == DMARECORD_SPRITE) { sr = _T("SPR"); chcnt = br; } else if (dr->type == DMARECORD_BITPLANE) { sr = _T("BPL"); chcnt = br + 1; } else if (dr->type == DMARECORD_UHRESBPL) { sr = _T("UHB"); chcnt = 0; } else if (dr->type == DMARECORD_UHRESSPR) { sr = _T("UHS"); chcnt = 0; } if (dr->cf_reg != 0xffff) { _stprintf(srtext, _T("!%03x"), dr->cf_reg); chcnt = -1; regsize--; } else { _tcscpy(srtext, sr); } int ipl = 0; if (iplp) { ipl = *iplp; if (dr->ipl > 0) { ipl = dr->ipl; } else if (dr->ipl < 0) { ipl = 0; } *iplp = ipl; if (dr->ipl2 > 0) { } } if (ipl >= 0) { _stprintf(l1, _T("[%02X %03X/%03X %d]"), hpos, dhpos0, dhpos1, ipl); } else if (ipl == -2) { _stprintf(l1, _T("[%02X %03X/%03X -]"), hpos, dhpos0, dhpos1); } else { _stprintf(l1, _T("[%02X %03X/%03X ]"), hpos, dhpos0, dhpos1); } if (l1c) { TCHAR *p = l1c; uae_u32 v = dr->agnus_evt; uae_u32 c = dr->agnus_evt_changed; if (c & AGNUS_EVENT_VDIW) { *p++ = 'W'; } else if (v & AGNUS_EVENT_VDIW) { *p++ = 'w'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_BPRUN2) { *p++ = 'D'; } else if (v & AGNUS_EVENT_BPRUN2) { *p++ = 'd'; } else { if (c & AGNUS_EVENT_BPRUN) { *p++ = 'B'; } else if (v & AGNUS_EVENT_BPRUN) { *p++ = 'b'; } else { *p++ = '-'; } } if (c & AGNUS_EVENT_VE) { *p++ = 'E'; } else if (v & AGNUS_EVENT_VE) { *p++ = 'e'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_P_VE) { *p++ = 'E'; } else if (v & AGNUS_EVENT_P_VE) { *p++ = 'e'; } else { *p++ = '-'; } *p++ = ' '; if (c & AGNUS_EVENT_HW_HS) { *p++ = 'H'; } else if (v & AGNUS_EVENT_HW_HS) { *p++ = 'h'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_HW_VS) { *p++ = 'V'; } else if (v & AGNUS_EVENT_HW_VS) { *p++ = 'v'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_HW_CS) { *p++ = 'C'; } else if (v & AGNUS_EVENT_HW_CS) { *p++ = 'c'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_PRG_HS) { *p++ = 'H'; } else if (v & AGNUS_EVENT_PRG_HS) { *p++ = 'h'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_PRG_VS) { *p++ = 'V'; } else if (v & AGNUS_EVENT_PRG_VS) { *p++ = 'v'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_PRG_CS) { *p++ = 'C'; } else if (v & AGNUS_EVENT_PRG_CS) { *p++ = 'c'; } else { *p++ = '-'; } if (c & AGNUS_EVENT_HB) { *p++ = 'B'; } else if (v & AGNUS_EVENT_HB) { *p++ = 'b'; } else { *p++ = '-'; } *p = 0; } if (l1b) { TCHAR *p = l1b; for (int h = 0; h < 2; h++) { uae_u32 v = dr->denise_evt[h]; uae_u32 c = dr->denise_evt_changed[h]; if (v & DENISE_EVENT_UNKNOWN) { *p++ = '?'; *p++ = '?'; *p++ = '?'; *p++ = '?'; *p++ = '?'; *p++ = '?'; } else { if (c & DENISE_EVENT_HB) { *p++ = 'H'; } else if (v & DENISE_EVENT_HB) { *p++ = 'h'; } else { *p++ = '-'; } if (c & DENISE_EVENT_VB) { *p++ = 'V'; } else if (v & DENISE_EVENT_VB) { *p++ = 'v'; } else { *p++ = '-'; } if (c & DENISE_EVENT_BURST) { *p++ = 'U'; } else if (v & DENISE_EVENT_BURST) { *p++ = 'u'; } else { *p++ = '-'; } if (c & DENISE_EVENT_HDIW) { *p++ = 'W'; } else if (v & DENISE_EVENT_HDIW) { *p++ = 'w'; } else { *p++ = '-'; } if (c & DENISE_EVENT_BPL1DAT_HDIW) { *p++ = 'B'; } else if (v & DENISE_EVENT_BPL1DAT_HDIW) { *p++ = 'b'; } else { *p++ = '-'; } } *p++ = ' '; } *p = 0; } if (l4) { _tcscpy(l4, _T(" ")); } if (l2) { _tcscpy(l2, _T(" ")); } if (l3) { _tcscpy(l3, _T(" ")); } if (r != 0xffff) { if (r & 0x1000) { if ((r & 0x0100) == 0x0000) _tcscpy(l2, _T("CPU-R ")); else if ((r & 0x0100) == 0x0100) _tcscpy(l2, _T("CPU-W ")); if ((r & 0xff) == 4) { l2[5] = 'L'; longsize = 4; } if ((r & 0xff) == 2) { l2[5] = 'W'; } if ((r & 0xff) == 1) { l2[5] = 'B'; } if (br) { l2[6] = 'D'; } else { l2[6] = 'I'; } } else { if (chcnt >= 0) { if (regsize == 3) _stprintf(l2, _T("%3s%d %03X"), srtext, chcnt, r); else if (regsize == 2) _stprintf(l2, _T("%4s%d %02X"), srtext, chcnt, r); else _stprintf(l2, _T("%5s%d %02X"), srtext, chcnt, r); } else { if (regsize == 3) _stprintf(l2, _T("%4s %03X"), srtext, r); else if (regsize == 2) _stprintf(l2, _T("%5s %02X"), srtext, r); else _stprintf(l2, _T("%6s %02X"), srtext, r); } } if (l3 && !noval) { uae_u64 v = dr->dat; if (longsize == 4) { _stprintf(l3, _T("%08X"), (uae_u32)v); } else if (longsize == 8) { _stprintf(l3, _T("%08X"), (uae_u32)(v >> 32)); extra64 = true; extraval = (uae_u32)v; } else { _stprintf(l3, _T(" %04X"), (uae_u32)(v & 0xffff)); } } if (l4 && dr->addr != 0xffffffff) _stprintf (l4, _T("%08X"), dr->addr & 0x00ffffff); } if (l3) { int cl2 = 0; if (dr->evt & DMA_EVENT_BLITFINALD) l3[cl2++] = 'D'; if (dr->evt & DMA_EVENT_BLITSTARTFINISH) l3[cl2++] = 'B'; if (dr->evt & DMA_EVENT_CPUBLITTERSTEAL) l3[cl2++] = 's'; if (dr->evt & DMA_EVENT_CPUBLITTERSTOLEN) l3[cl2++] = 'S'; if (dr->evt & DMA_EVENT_BLITIRQ) l3[cl2++] = 'b'; if (dr->evt & DMA_EVENT_BPLFETCHUPDATE) l3[cl2++] = 'p'; if (dr->evt & (DMA_EVENT_COPPERWAKE | DMA_EVENT_COPPERSKIP)) l3[cl2++] = 'W'; if (dr->evt & DMA_EVENT_COPPERWAKE2) { l3[cl2++] = '#'; } else if (dr->evt & DMA_EVENT_COPPERWANTED) { l3[cl2++] = 'c'; } if (dr->evt & DMA_EVENT_CPUIRQ) l3[cl2++] = 'I'; if (dr->evt & DMA_EVENT_CPUSTOP) l3[cl2++] = '|'; if (dr->evt & DMA_EVENT_CPUSTOPIPL) l3[cl2++] = '+'; if (dr->evt & DMA_EVENT_INTREQ) l3[cl2++] = 'i'; if (dr->evt & DMA_EVENT_SPECIAL) l3[cl2++] = 'X'; if (dr->evt & DMA_EVENT_DDFSTRT) l3[cl2++] = '0'; if (dr->evt & DMA_EVENT_DDFSTOP) l3[cl2++] = '1'; if (dr->evt & DMA_EVENT_DDFSTOP2) l3[cl2++] = '2'; if (dr->evt & (DMA_EVENT_LOL | DMA_EVENT_LOF)) { l3[cl2++] = '*'; } if (dr->evt & DMA_EVENT_LOL) { l3[cl2++] = 'L'; } if (dr->evt & DMA_EVENT_LOF) { l3[cl2++] = 'F'; } if (dr->evt & (DMA_EVENT_LOL | DMA_EVENT_LOF)) { l3[cl2++] = 0; } if (dr->evt & (DMA_EVENT_CIAA_IRQ | DMA_EVENT_CIAB_IRQ)) { l3[cl2++] = '#'; } if (dr->evt & DMA_EVENT_CIAA_IRQ) { l3[cl2++] = 'A'; } if (dr->evt & DMA_EVENT_CIAB_IRQ) { l3[cl2++] = 'B'; } if (dr->evt & DMA_EVENT_IPLSAMPLE) { l3[cl2++] = '^'; } if (dr->evt & DMA_EVENT_COPPERUSE) { l3[cl2++] = 'C'; } if (dr->evt & DMA_EVENT_MODADD) { l3[cl2++] = 'M'; } } if (l5) { if (dr->ciaphase) { if (dr->ciamask) { _stprintf(l5, _T("%c%s%X %04X"), dr->ciarw ? 'W' : 'R', dr->ciamask == 1 ? _T("A") : (dr->ciamask == 2 ? _T("B") : _T("X")), dr->ciareg, dr->ciavalue); } else { int ph = dr->ciaphase; if (ph >= 100) { _tcscpy(l5, _T(" - ")); } else { _stprintf(l5, _T(" %u "), ph - 1); } } } } if (l6) { TCHAR sync1 = ' ', sync2 = ' '; if (dr->hs && dr->vs) { sync1 = 'X'; } else if (dr->hs) { sync1 = 'H'; } else if (dr->vs) { sync1 = 'V'; } if (dr->cs) { sync2 = 'C'; } if (dr->addr != 0xffffffff) { int ras, cas; TCHAR xtra = ' '; bool ret = get_ras_cas(dr->addr, &ras, &cas); if (ret) { xtra = '+'; } _stprintf(l6, _T("%c%c%c%03X %03X"), sync1, sync2, xtra, ras, cas); } else { _stprintf(l6, _T("%c%c "), sync1, sync2); } } if (extra64) { _tcscpy(l6, l4); _stprintf(l4, _T("%08X"), extraval); } return got; } static struct dma_rec *find_dma_record(int hpos, int vpos, int toggle) { int frame = vsync_counter - toggle; int found = -1; struct dma_rec *dr = NULL; if (!dma_record_data) { return NULL; } for (int i = 0; i < NR_DMA_REC_MAX; i++) { int idx = dma_record_cycle - i; if (idx < 0) { idx += NR_DMA_REC_MAX; } dr = &dma_record_data[idx]; if (found < 0) { if (dr->frame == frame) { if ((dr->hpos == 2 || dr->hpos == hpos && hpos >= 2) && dr->vpos[dma_record_vpos_type] == vpos) { for (;;) { dr = &dma_record_data[idx]; int tick = dr->tick; if (dr->vpos[dma_record_vpos_type] == vpos && dr->frame == frame) { if (dr->hpos == hpos) { break; } } else { idx++; break; } idx--; if (idx < 0) { idx += NR_DMA_REC_MAX; } dr = &dma_record_data[idx]; if (dr->tick != tick - 1) { idx++; break; } } found = idx; break; } } } } if (found >= 0) { return dr; } #if 0 for (int i = 0; i < NR_DMA_REC_MAX; i++) { int idx = dma_record_cycle - i; if (idx < 0) { idx += NR_DMA_REC_MAX; } dr = &dma_record[idx]; if (found < 0 && dr->hpos >= 0 && dr->vpos[dma_record_vpos_type] == vpos && dr->frame == frame) { found = idx; break; } } if (found >= 0) { int max = maxhpos; int idx = found; while (max-- > 0) { idx--; if (idx < 0) { idx += NR_DMA_REC_MAX; } dr = &dma_record[idx]; if (dr->hpos == 1 || dr->hpos <= hpos) { return dr; } } } #endif return NULL; } static void decode_dma_record(int hpos, int vpos, int count, int toggle, bool logfile) { struct dma_rec *dr, *dr_start; int h, i, maxh = 0; int zerohpos = 0; int cols = logfile ? 16 : 8; if (!dma_record_data || hpos < 0 || vpos < 0) return; if (hpos == 0) { zerohpos = 1; } dr_start = find_dma_record(hpos + zerohpos, vpos, toggle); if (!dr_start) { return; } dr = dr_start; dr_start -= zerohpos; if (logfile) write_dlog (_T("Line: %03X/%03X (%3d/%3d) HPOS %02X (%3d):\n"), dr->vpos[0], dr->vpos[1], dr->vpos[0], dr->vpos[1], hpos, hpos); else console_out_f (_T("Line: %03X/%03X (%3d/%3d) HPOS %02X (%3d): **********************************************************************************************\n"), dr->vpos[0], dr->vpos[1], dr->vpos[0], dr->vpos[1], hpos, hpos); h = 0; dr = dr_start; while (maxh < 300) { if (dr - dma_record_data == dma_record_cycle) { break; } if (dr->hpos == 1 && maxh >= 4) { maxh++; } dr++; if (dr == dma_record_data + NR_DMA_REC_MAX) { dr = dma_record_data; } maxh++; } dr = dr_start; if (!logfile && maxh - h > 48) { int maxh2 = maxh; maxh = h + 48; if (maxh > maxh2) { maxh = maxh2; } } int ipl = -2; zerohpos = 0; bool quit = false; while (h < maxh && !quit) { TCHAR l1[400]; TCHAR l1b[400]; TCHAR l1c[400]; TCHAR l2[400]; TCHAR l3[400]; TCHAR l4[400]; TCHAR l5[400]; TCHAR l6[400]; l1[0] = 0; l1b[0] = 0; l1c[0] = 0; l2[0] = 0; l3[0] = 0; l4[0] = 0; l5[0] = 0; l6[0] = 0; for (i = 0; i < cols; i++, h++, dr++) { TCHAR l1l[30], l1bl[30], l1cl[30], l2l[30], l3l[30], l4l[30], l5l[30], l6l[30]; uae_u32 split = 0xffffffff; get_record_dma_info(dr_start, dr, l1l, l1bl, l1cl, l2l, l3l, l4l, l5l, l6l, &split, &ipl); TCHAR *p = l1 + _tcslen(l1); _stprintf(p, _T("%15s "), l1l); p = l1b + _tcslen(l1b); _stprintf(p, _T("%15s "), l1bl); p = l1c + _tcslen(l1c); _stprintf(p, _T("%15s "), l1cl); p = l2 + _tcslen(l2); _stprintf(p, _T("%15s "), l2l); p = l3 + _tcslen(l3); _stprintf(p, _T("%15s "), l3l); p = l4 + _tcslen(l4); _stprintf(p, _T("%15s "), l4l); p = l5 + _tcslen(l5); _stprintf(p, _T("%15s "), l5l); p = l6 + _tcslen(l6); _stprintf(p, _T("%15s "), l6l); if (split != 0xffffffff) { if (split < 0x10000) { struct instr *dp = table68k + split; if (dp->mnemo == i_ILLG) { split = 0x4AFC; dp = table68k + split; } struct mnemolookup *lookup; for (lookup = lookuptab; lookup->mnemo != dp->mnemo; lookup++) ; const TCHAR *opcodename = lookup->friendlyname; if (!opcodename) { opcodename = lookup->name; } TCHAR *ptrs[10]; ptrs[0] = &l1[_tcslen(l1)]; ptrs[1] = &l1b[_tcslen(l1b)]; ptrs[2] = &l1c[_tcslen(l1c)]; ptrs[3] = &l2[_tcslen(l2)]; ptrs[4] = &l3[_tcslen(l3)]; ptrs[5] = &l4[_tcslen(l4)]; ptrs[6] = &l5[_tcslen(l5)]; ptrs[7] = &l6[_tcslen(l6)]; for (int i = 0; i < 8; i++) { if (!opcodename[i]) { break; } TCHAR *p = ptrs[i]; p[-1] = opcodename[i]; } } else { l1[_tcslen(l1) - 1] = '*'; } } if (dr - dma_record_data == dma_record_cycle) { quit = true; break; } if (h > 4 && dr->hpos == 1) { zerohpos = 1; } } if (logfile) { write_dlog(_T("%s\n"), l1); write_dlog(_T("%s\n"), l1b); write_dlog(_T("%s\n"), l1c); write_dlog(_T("%s\n"), l2); write_dlog(_T("%s\n"), l3); write_dlog(_T("%s\n"), l4); write_dlog(_T("%s\n"), l5); write_dlog(_T("%s\n"), l6); write_dlog(_T("\n")); } else { console_out_f(_T("%s\n"), l1); console_out_f(_T("%s\n"), l1b); console_out_f(_T("%s\n"), l1c); console_out_f(_T("%s\n"), l2); console_out_f(_T("%s\n"), l3); console_out_f(_T("%s\n"), l4); console_out_f(_T("%s\n"), l5); console_out_f(_T("%s\n"), l6); console_out_f(_T("\n")); } if (zerohpos) { break; } if (count > 0) { count--; if (!count) { break; } } } if (logfile) flush_log(); } void log_dma_record (void) { if (!input_record && !input_play) return; if (!debug_dma) return; decode_dma_record (0, 0, 0, 0, true); } static void init_record_copper(void) { if (!cop_record[0]) { cop_record[0] = xmalloc(struct cop_rec, NR_COPPER_RECORDS); cop_record[1] = xmalloc(struct cop_rec, NR_COPPER_RECORDS); } } void record_copper_blitwait (uaecptr addr, int hpos, int vpos) { int t = nr_cop_records[curr_cop_set]; init_record_copper(); cop_record[curr_cop_set][t].bhpos = hpos; cop_record[curr_cop_set][t].bvpos = vpos; } void record_copper (uaecptr addr, uaecptr nextaddr, uae_u16 word1, uae_u16 word2, int hpos, int vpos) { int t = nr_cop_records[curr_cop_set]; init_record_copper(); if (t < NR_COPPER_RECORDS) { cop_record[curr_cop_set][t].addr = addr; cop_record[curr_cop_set][t].nextaddr = nextaddr; cop_record[curr_cop_set][t].w1 = word1; cop_record[curr_cop_set][t].w2 = word2; cop_record[curr_cop_set][t].hpos = hpos; cop_record[curr_cop_set][t].vpos = vpos; cop_record[curr_cop_set][t].bvpos = -1; nr_cop_records[curr_cop_set] = t + 1; } if (debug_copper & 2) { /* trace */ debug_copper &= ~2; activate_debugger_new(); } if ((debug_copper & 4) && addr >= debug_copper_pc && addr <= debug_copper_pc + 3) { debug_copper &= ~4; activate_debugger_new(); } } static struct cop_rec *find_copper_records(uaecptr addr) { int s = selected_cop_set; int t = nr_cop_records[s]; int i; for (i = 0; i < t; i++) { if (cop_record[s][i].addr == addr) return &cop_record[s][i]; } return 0; } /* simple decode copper by Mark Cox */ static uaecptr decode_copper_insn(FILE *file, uae_u16 mword1, uae_u16 mword2, uaecptr addr) { struct cop_rec *cr = NULL; uae_u32 insn_type, insn; TCHAR here = ' '; TCHAR record[] = _T(" "); if ((cr = find_copper_records(addr))) { _stprintf(record, _T(" [%03x %03x]"), cr->vpos, cr->hpos); insn = (cr->w1 << 16) | cr->w2; } else { insn = (mword1 << 16) | mword2; } insn_type = insn & 0x00010001; if (get_copper_address(-1) >= addr && get_copper_address(-1) <= addr + 3) here = '*'; console_out_f (_T("%c%08x: %04x %04x%s\t;%c "), here, addr, insn >> 16, insn & 0xFFFF, record, insn != ((mword1 << 16) | mword2) ? '!' : ' '); switch (insn_type) { case 0x00010000: /* WAIT insn */ console_out(_T("Wait for ")); disassemble_wait(file, insn); if (insn == 0xfffffffe) console_out(_T(" \t; End of Copperlist\n")); break; case 0x00010001: /* SKIP insn */ console_out(_T("Skip if ")); disassemble_wait(file, insn); break; case 0x00000000: case 0x00000001: /* MOVE insn */ { int addr = (insn >> 16) & 0x1fe; int i = 0; while (custd[i].name) { if (custd[i].adr == addr + 0xdff000) break; i++; } if (custd[i].name) console_out_f(_T("%s := 0x%04x\n"), custd[i].name, insn & 0xffff); else console_out_f(_T("%04x := 0x%04x\n"), addr, insn & 0xffff); } break; default: abort (); } if (cr && cr->bvpos >= 0) { console_out_f(_T(" BLT [%03x %03x]\n"), cr->bvpos, cr->bhpos); } if (cr && cr->nextaddr != 0xffffffff && cr->nextaddr != addr + 4) { console_out_f(_T(" %08x: Copper jump\n"), cr->nextaddr); return cr->nextaddr; } return addr + 4; } static uaecptr decode_copperlist(FILE *file, uaecptr address, int nolines) { uaecptr next; while (nolines-- > 0) { next = decode_copper_insn(file, chipmem_wget_indirect(address), chipmem_wget_indirect(address + 2), address); address = next; } return address; /* You may wonder why I don't stop this at the end of the copperlist? * Well, often nice things are hidden at the end and it is debatable the actual * values that mean the end of the copperlist */ } static int copper_debugger (TCHAR **c) { static uaecptr nxcopper; uae_u32 maddr; int lines; if (**c == 'd') { next_char (c); if (debug_copper) debug_copper = 0; else debug_copper = 1; console_out_f (_T("Copper debugger %s.\n"), debug_copper ? _T("enabled") : _T("disabled")); } else if (**c == 't') { debug_copper = 1|2; return 1; } else if (**c == 'b') { (*c)++; debug_copper = 1|4; if (more_params (c)) { debug_copper_pc = readhex(c, NULL); console_out_f (_T("Copper breakpoint @0x%08x\n"), debug_copper_pc); } else { debug_copper &= ~4; } } else { if (more_params(c)) { maddr = readhex(c, NULL); if (maddr == 1 || maddr == 2 || maddr == 3) maddr = get_copper_address(maddr); else if (maddr == 0) maddr = get_copper_address(-1); } else { maddr = nxcopper; } selected_cop_set = curr_cop_set; if (!find_copper_records(maddr)) { selected_cop_set = curr_cop_set ^ 1; } if (more_params(c)) lines = readhex(c, NULL); else lines = 20; nxcopper = decode_copperlist (stdout, maddr, lines); } return 0; } #define MAX_CHEAT_VIEW 100 struct trainerstruct { uaecptr addr; int size; }; static struct trainerstruct *trainerdata; static int totaltrainers; static void clearcheater(void) { if (!trainerdata) trainerdata = xmalloc(struct trainerstruct, MAX_CHEAT_VIEW); memset(trainerdata, 0, sizeof (struct trainerstruct) * MAX_CHEAT_VIEW); totaltrainers = 0; } static int addcheater(uaecptr addr, int size) { if (totaltrainers >= MAX_CHEAT_VIEW) return 0; trainerdata[totaltrainers].addr = addr; trainerdata[totaltrainers].size = size; totaltrainers++; return 1; } static void listcheater(int mode, int size) { int i, skip; if (!trainerdata) return; if (mode) skip = 4; else skip = 8; for(i = 0; i < totaltrainers; i++) { struct trainerstruct *ts = &trainerdata[i]; uae_u16 b; if (size) { b = get_byte_debug(ts->addr); } else { b = get_word_debug(ts->addr); } if (mode) console_out_f(_T("%08X=%04X "), ts->addr, b); else console_out_f(_T("%08X "), ts->addr); if ((i % skip) == skip) console_out(_T("\n")); } } static void deepcheatsearch(TCHAR **c) { static int first = 1; static uae_u8 *memtmp; static int memsize, memsize2; uae_u8 *p1, *p2; uaecptr addr, end; int i, wasmodified, nonmodified; static int size; static int inconly, deconly, maxdiff; int addrcnt, cnt; TCHAR v; v = _totupper(**c); if(!memtmp || v == 'S') { maxdiff = 0x10000; inconly = 0; deconly = 0; size = 1; } if (**c) (*c)++; ignore_ws(c); if ((**c) == '1' || (**c) == '2') { size = **c - '0'; (*c)++; } if (more_params(c)) maxdiff = readint(c, NULL); if (!memtmp || v == 'S') { first = 1; xfree (memtmp); memsize = 0; addr = 0xffffffff; nextaddr_init(addr); while ((addr = nextaddr(addr, 0, &end, false, NULL)) != 0xffffffff) { memsize += end - addr; addr = end - 1; } memsize2 = (memsize + 7) / 8; memtmp = xmalloc(uae_u8, memsize + memsize2); if (!memtmp) return; memset(memtmp + memsize, 0xff, memsize2); p1 = memtmp; addr = 0xffffffff; nextaddr_init(addr); while ((addr = nextaddr(addr, 0, &end, true, NULL)) != 0xffffffff) { for (i = addr; i < end; i++) *p1++ = get_byte_debug(i); addr = end - 1; } console_out(_T("Deep trainer first pass complete.\n")); return; } inconly = deconly = 0; wasmodified = v == 'X' ? 0 : 1; nonmodified = v == 'Z' ? 1 : 0; if (v == 'I') inconly = 1; if (v == 'D') deconly = 1; p1 = memtmp; p2 = memtmp + memsize; addrcnt = 0; cnt = 0; addr = 0xffffffff; nextaddr_init(addr); while ((addr = nextaddr(addr, 0, NULL, true, NULL)) != 0xffffffff) { uae_s32 b, b2; int doremove = 0; int addroff; int addrmask ; if (size == 1) { b = (uae_s8)get_byte_debug(addr); b2 = (uae_s8)p1[addrcnt]; addroff = addrcnt >> 3; addrmask = 1 << (addrcnt & 7); } else { b = (uae_s16)get_word_debug(addr); b2 = (uae_s16)((p1[addrcnt] << 8) | p1[addrcnt + 1]); addroff = addrcnt >> 2; addrmask = 3 << (addrcnt & 3); } if (p2[addroff] & addrmask) { if (wasmodified && !nonmodified) { int diff = b - b2; if (b == b2) doremove = 1; if (abs(diff) > maxdiff) doremove = 1; if (inconly && diff < 0) doremove = 1; if (deconly && diff > 0) doremove = 1; } else if (nonmodified && b == b2) { doremove = 1; } else if (!wasmodified && b != b2) { doremove = 1; } if (doremove) p2[addroff] &= ~addrmask; else cnt++; } if (size == 1) { p1[addrcnt] = b; addrcnt++; } else { p1[addrcnt] = b >> 8; p1[addrcnt + 1] = b >> 0; addr = nextaddr(addr, 0, NULL, true, NULL); if (addr == 0xffffffff) break; addrcnt++; } if (iscancel(65536)) { console_out_f(_T("Aborted at %08X\n"), addr); break; } } console_out_f(_T("%d addresses found\n"), cnt); if (cnt <= MAX_CHEAT_VIEW) { clearcheater(); cnt = 0; addrcnt = 0; addr = 0xffffffff; while ((addr = nextaddr(addr, 0, NULL, true, NULL)) != 0xffffffff) { int addroff = addrcnt >> (size == 1 ? 3 : 2); int addrmask = (size == 1 ? 1 : 3) << (addrcnt & (size == 1 ? 7 : 3)); if (p2[addroff] & addrmask) addcheater(addr, size); if (size == 2) { addr = nextaddr(addr, 0, NULL, true, NULL); if (addr == 0xffffffff) { break; } } addrcnt++; cnt++; } if (cnt > 0) console_out(_T("\n")); listcheater(1, size); } else { console_out(_T("Now continue with 'g' and use 'D' again after you have lost another life\n")); } } /* cheat-search by Toni Wilen (originally by Holger Jakob) */ static void cheatsearch (TCHAR **c) { static uae_u8 *vlist; static int listsize; static int first = 1; static int size = 1; uae_u32 val, memcnt, prevmemcnt; int i, count, vcnt, memsize; uaecptr addr, end; bool err; memsize = 0; addr = 0xffffffff; nextaddr_init(addr); while ((addr = nextaddr(addr, 0, &end, false, NULL)) != 0xffffffff) { memsize += end - addr; addr = end - 1; } if (_totupper (**c) == 'L') { listcheater (1, size); return; } ignore_ws (c); if (!more_params (c)) { first = 1; console_out (_T("Search reset\n")); xfree (vlist); listsize = memsize; vlist = xcalloc (uae_u8, listsize >> 3); return; } if (first) val = readint(c, &size, &err); else val = readint(c, &err); if (err) { return; } if (vlist == NULL) { listsize = memsize; vlist = xcalloc (uae_u8, listsize >> 3); } count = 0; vcnt = 0; clearcheater (); addr = 0xffffffff; nextaddr_init(addr); prevmemcnt = memcnt = 0; while ((addr = nextaddr(addr, 0, &end, true, NULL)) != 0xffffffff) { if (addr + size < end) { for (i = 0; i < size; i++) { int shift = (size - i - 1) * 8; if (get_byte_debug (addr + i) != ((val >> shift) & 0xff)) break; } if (i == size) { int voffset = memcnt >> 3; int vmask = 1 << (memcnt & 7); if (!first) { while (prevmemcnt < memcnt) { vlist[prevmemcnt >> 3] &= ~(1 << (prevmemcnt & 7)); prevmemcnt++; } if (vlist[voffset] & vmask) { count++; addcheater(addr, size); } else { vlist[voffset] &= ~vmask; } prevmemcnt = memcnt + 1; } else { vlist[voffset] |= vmask; count++; } } } memcnt++; if (iscancel (65536)) { console_out_f (_T("Aborted at %08X\n"), addr); break; } } if (!first) { while (prevmemcnt < memcnt) { vlist[prevmemcnt >> 3] &= ~(1 << (prevmemcnt & 7)); prevmemcnt++; } listcheater (0, size); } console_out_f (_T("Found %d possible addresses with 0x%X (%u) (%d bytes)\n"), count, val, val, size); if (count > 0) console_out (_T("Now continue with 'g' and use 'C' with a different value\n")); first = 0; } struct breakpoint_node bpnodes[BREAKPOINT_TOTAL]; static addrbank **debug_mem_banks; static addrbank *debug_mem_area; struct memwatch_node mwnodes[MEMWATCH_TOTAL]; static int mwnodes_start, mwnodes_end; static struct memwatch_node mwhit; #define MUNGWALL_SLOTS 16 struct mungwall_data { int slots; uae_u32 start[MUNGWALL_SLOTS], end[MUNGWALL_SLOTS]; }; static struct mungwall_data **mungwall; static uae_u8 *illgdebug, *illghdebug; static int illgdebug_break; static void illg_free (void) { xfree (illgdebug); illgdebug = NULL; xfree (illghdebug); illghdebug = NULL; } static void illg_init (void) { int i; uae_u8 c = 3; uaecptr addr, end; illgdebug = xcalloc (uae_u8, 0x01000000); illghdebug = xcalloc (uae_u8, 65536); if (!illgdebug || !illghdebug) { illg_free(); return; } addr = 0xffffffff; nextaddr_init(addr); while ((addr = nextaddr(addr, 0, &end, false, NULL)) != 0xffffffff) { if (end < 0x01000000) { memset (illgdebug + addr, c, end - addr); } else { uae_u32 s = addr >> 16; uae_u32 e = end >> 16; memset (illghdebug + s, c, e - s); } addr = end - 1; } for (int i = 0; i < MAX_RTG_BOARDS; i++) { if (currprefs.rtgboards[i].rtgmem_size) memset (illghdebug + (gfxmem_banks[i]->start >> 16), 3, currprefs.rtgboards[i].rtgmem_size >> 16); } i = 0; while (custd[i].name) { int rw = (custd[i].special & CD_WO) ? 2 : 1; illgdebug[custd[i].adr] = rw; illgdebug[custd[i].adr + 1] = rw; i++; } for (i = 0; i < 16; i++) { /* CIAs */ if (i == 11) continue; illgdebug[0xbfe001 + i * 0x100] = c; illgdebug[0xbfd000 + i * 0x100] = c; } memset (illgdebug + 0xf80000, 1, 512 * 1024); /* KS ROM */ memset (illgdebug + 0xdc0000, c, 0x3f); /* clock */ #ifdef CDTV if (currprefs.cs_cdtvram) { memset (illgdebug + 0xdc8000, c, 4096); /* CDTV batt RAM */ memset (illgdebug + 0xf00000, 1, 256 * 1024); /* CDTV ext ROM */ } #endif #ifdef CD32 if (currprefs.cs_cd32cd) { memset (illgdebug + AKIKO_BASE, c, AKIKO_BASE_END - AKIKO_BASE); memset (illgdebug + 0xe00000, 1, 512 * 1024); /* CD32 ext ROM */ } #endif if (currprefs.cs_ksmirror_e0) memset (illgdebug + 0xe00000, 1, 512 * 1024); if (currprefs.cs_ksmirror_a8) memset (illgdebug + 0xa80000, 1, 2 * 512 * 1024); #ifdef FILESYS if (uae_boot_rom_type) /* filesys "rom" */ memset (illgdebug + rtarea_base, 1, 0x10000); #endif if (currprefs.cs_ide > 0) memset (illgdebug + 0xdd0000, 3, 65536); } /* add special custom register check here */ static void illg_debug_check (uaecptr addr, int rwi, int size, uae_u32 val) { return; } static void illg_debug_do (uaecptr addr, int rwi, int size, uae_u32 val) { uae_u8 mask; uae_u32 pc = m68k_getpc (); int i; for (i = size - 1; i >= 0; i--) { uae_u8 v = val >> (i * 8); uae_u32 ad = addr + i; if (ad >= 0x01000000) mask = illghdebug[ad >> 16]; else mask = illgdebug[ad]; if ((mask & 3) == 3) return; if (mask & 0x80) { illg_debug_check (ad, rwi, size, val); } else if ((mask & 3) == 0) { if (rwi & 2) console_out_f (_T("W: %08X=%02X PC=%08X\n"), ad, v, pc); else if (rwi & 1) console_out_f (_T("R: %08X PC=%08X\n"), ad, pc); if (illgdebug_break) activate_debugger_new(); } else if (!(mask & 1) && (rwi & 1)) { console_out_f (_T("RO: %08X=%02X PC=%08X\n"), ad, v, pc); if (illgdebug_break) activate_debugger_new(); } else if (!(mask & 2) && (rwi & 2)) { console_out_f (_T("WO: %08X PC=%08X\n"), ad, pc); if (illgdebug_break) activate_debugger_new(); } } } static int debug_mem_off (uaecptr *addrp) { uaecptr addr = *addrp; addrbank *ba; int offset = munge24 (addr) >> 16; if (!debug_mem_banks) return offset; ba = debug_mem_banks[offset]; if (!ba) return offset; if (ba->mask || ba->startmask) { uae_u32 start = ba->startmask ? ba->startmask : ba->start; addr -= start; if (ba->mask) { addr &= ba->mask; } addr += start; } *addrp = addr; return offset; } struct smc_item { uae_u32 addr; uae_u16 version; uae_u8 cnt; }; static uae_u32 smc_size, smc_mode; static struct smc_item *smc_table; static uae_u16 smc_version; static void smc_free (void) { if (smc_table) console_out (_T("SMCD disabled\n")); xfree(smc_table); smc_mode = 0; smc_table = NULL; } static void initialize_memwatch(int mode); static void smc_detect_init(TCHAR **c) { int v; ignore_ws(c); v = readint(c, NULL); smc_free(); smc_size = 1 << 24; if (highest_ram > smc_size) { smc_size = highest_ram; } smc_table = xcalloc(struct smc_item, smc_size + 4); if (!smc_table) { console_out_f(_T("Failed to allocated SMCD buffers, %d bytes needed\n."), sizeof(struct smc_item) * smc_size + 4); return; } smc_version = 0xffff; debug_smc_clear(-1, 0); if (!memwatch_enabled) initialize_memwatch(0); if (v) smc_mode = 1; console_out_f(_T("SMCD enabled. Break=%d. Last address=%08x\n"), smc_mode, smc_size); } void debug_smc_clear(uaecptr addr, int size) { if (!smc_table) return; smc_version++; if (smc_version == 0) { for (uae_u32 i = 0; i < smc_size; i += 65536) { addrbank *ab = &get_mem_bank(i); if (ab->flags & (ABFLAG_RAM | ABFLAG_ROM)) { for (uae_u32 j = 0; j < 65536; j++) { struct smc_item *si = &smc_table[i + j]; if (size < 0 || (si->addr >= addr && si->addr < addr + size)) { si->addr = 0xffffffff; si->cnt = 0; si->version = smc_version; } } } } } } #define SMC_MAXHITS 8 static void smc_detector(uaecptr addr, int rwi, int size, uae_u32 *valp) { int hitcnt; uaecptr hitaddr, hitpc; if (!smc_table) return; if (addr + size > smc_size) return; if (rwi == 2) { for (int i = 0; i < size; i++) { struct smc_item *si = &smc_table[addr + i]; if (si->version != smc_version) { si->version = smc_version; si->addr = 0xffffffff; si->cnt = 0; } if (si->cnt < SMC_MAXHITS) { si->addr = m68k_getpc(); } } return; } hitpc = 0xffffffff; for (int i = 0; i < size && hitpc == 0xffffffff && addr + i < smc_size; i += 2) { struct smc_item *si = &smc_table[addr + i]; if (si->version == smc_version) { hitpc = si->addr; } } if (hitpc == 0xffffffff) { return; } if ((hitpc & 0xFFF80000) == 0xF80000) { return; } hitaddr = addr; hitcnt = 0; while (addr < smc_size) { struct smc_item *si = &smc_table[addr]; if (si->addr == 0xffffffff || si->version != smc_version) { break; } si->addr = 0xffffffff; hitcnt++; addr++; } if (currprefs.cpu_model <= 68010 && currprefs.cpu_compatible) { /* ignore single-word unconditional jump instructions * (instruction prefetch from PC+2 can cause false positives) */ if (regs.irc == 0x4e75 || regs.irc == 0x4e74 || regs.irc == 0x4e73 || regs.irc == 0x4e77) return; /* RTS, RTD, RTE, RTR */ if ((regs.irc & 0xff00) == 0x6000 && (regs.irc & 0x00ff) != 0 && (regs.irc & 0x00ff) != 0xff) return; /* BRA.B */ } if (hitcnt < 100) { struct smc_item *si = &smc_table[hitaddr]; si->cnt++; console_out_f(_T("SMC at %08X - %08X (%d) from %08X\n"), hitaddr, hitaddr + hitcnt, hitcnt, hitpc); if (smc_mode) { activate_debugger_new(); } if (si->cnt >= SMC_MAXHITS) { console_out_f(_T("* hit count >= %d, future hits ignored\n"), SMC_MAXHITS); } } } uae_u8 *save_debug_memwatch (size_t *len, uae_u8 *dstptr) { uae_u8 *dstbak, *dst; int total; total = 0; for (int i = 0; i < MEMWATCH_TOTAL; i++) { if (mwnodes[i].size > 0) total++; } if (!total) return NULL; if (dstptr) dstbak = dst = dstptr; else dstbak = dst = xmalloc (uae_u8, 1000); save_u32 (1); save_u8 (total); for (int i = 0; i < MEMWATCH_TOTAL; i++) { struct memwatch_node *m = &mwnodes[i]; if (m->size <= 0) continue; save_store_pos (); save_u8 (i); save_u8 (m->modval_written); save_u8 (m->mustchange); save_u8 (m->frozen); save_u8 (m->val_enabled); save_u8 (m->rwi); save_u32 (m->addr); save_u32 (m->size); save_u32 (m->modval); save_u32 (m->val_mask); save_u32 (m->val_size); save_u32 (m->val); save_u32 (m->pc); save_u32 (m->access_mask); save_u32 (m->reg); save_u8(m->nobreak); save_u8(m->reportonly); save_store_size (); } *len = dst - dstbak; return dstbak; } uae_u8 *restore_debug_memwatch (uae_u8 *src) { if (restore_u32 () != 1) return src; int total = restore_u8 (); for (int i = 0; i < total; i++) { restore_store_pos (); int idx = restore_u8 (); struct memwatch_node *m = &mwnodes[idx]; m->modval_written = restore_u8 (); m->mustchange = restore_u8 (); m->frozen = restore_u8 (); m->val_enabled = restore_u8 (); m->rwi = restore_u8 (); m->addr = restore_u32 (); m->size = restore_u32 (); m->modval = restore_u32 (); m->val_mask = restore_u32 (); m->val_size = restore_u32 (); m->val = restore_u32 (); m->pc = restore_u32 (); m->access_mask = restore_u32(); m->reg = restore_u32(); m->nobreak = restore_u8(); m->reportonly = restore_u8(); restore_store_size (); } return src; } void restore_debug_memwatch_finish (void) { for (int i = 0; i < MEMWATCH_TOTAL; i++) { struct memwatch_node *m = &mwnodes[i]; if (m->size) { if (!memwatch_enabled) initialize_memwatch (0); return; } } } void debug_check_reg(uae_u32 addr, int write, uae_u16 v) { if (!memwatch_access_validator) return; int reg = addr & 0x1ff; const struct customData *cd = &custd[reg >> 1]; if (((addr & 0xfe00) != 0xf000 && (addr & 0xffff0000) != 0) || ((addr & 0xffff0000) != 0 && (addr & 0xffff0000) != 0x00df0000) || (addr & 0x0600)) { write_log(_T("Mirror custom register %08x (%s) %s access. PC=%08x\n"), addr, cd->name, write ? _T("write") : _T("read"), M68K_GETPC); } int spc = cd->special; if ((spc & CD_AGA) && !(currprefs.chipset_mask & CSMASK_AGA)) spc |= CD_NONE; if ((spc & CD_ECS_DENISE) && !(currprefs.chipset_mask & CSMASK_ECS_DENISE)) spc |= CD_NONE; if ((spc & CD_ECS_AGNUS) && !(currprefs.chipset_mask & CSMASK_ECS_AGNUS)) spc |= CD_NONE; if (spc & CD_NONE) { write_log(_T("Non-existing custom register %04x (%s) %s access. PC=%08x\n"), reg, cd->name, write ? _T("write") : _T("read"), M68K_GETPC); return; } if (spc & CD_COLOR) { if (currprefs.chipset_mask & CSMASK_AGA) return; } if (write & !(spc & CD_WO)) { write_log(_T("Write access to read-only custom register %04x (%s). PC=%08x\n"), reg, cd->name, M68K_GETPC); return; } else if (!write && (spc & CD_WO)) { write_log(_T("Read access from write-only custom register %04x (%s). PC=%08x\n"), reg, cd->name, M68K_GETPC); return; } if (write && cd->mask[2]) { int idx = (currprefs.chipset_mask & CSMASK_AGA) ? 2 : (currprefs.chipset_mask & CSMASK_ECS_AGNUS) ? 1 : 0; uae_u16 mask = cd->mask[idx]; if (v & ~mask) { write_log(_T("Unuset bits set %04x when writing custom register %04x (%s) PC=%08x\n"), v & ~mask, reg, cd->name, M68K_GETPC); } } if (spc & CD_DMA_PTR) { uae_u32 addr = (custom_storage[((reg & ~2) >> 1)].value << 16) | custom_storage[((reg | 2) >> 1)].value; if (currprefs.z3chipmem.size) { if (addr >= currprefs.z3chipmem.start_address && addr < currprefs.z3chipmem.start_address + currprefs.z3chipmem.size) return; } if(addr >= currprefs.chipmem.size) write_log(_T("DMA pointer %04x (%s) set to invalid value %08x %s=%08x\n"), reg, cd->name, addr, custom_storage[reg >> 1].pc & 1 ? _T("COP") : _T("PC"), custom_storage[reg >> 1].pc); } } void debug_invalid_reg(int reg, int size, uae_u16 v) { if (!memwatch_access_validator) return; reg &= 0x1ff; if (size == 1) { if (reg == 2) // DMACONR low byte return; if (reg == 6) // VPOS return; } const struct customData *cd = &custd[reg >> 1]; if (size == -2 && (reg & 1)) { write_log(_T("Unaligned word write to register %04x (%s) val %04x PC=%08x\n"), reg, cd->name, v, M68K_GETPC); } else if (size == -1) { write_log(_T("Byte write to register %04x (%s) val %02x PC=%08x\n"), reg, cd->name, v & 0xff, M68K_GETPC); } else if (size == 2 && (reg & 1)) { write_log(_T("Unaligned word read from register %04x (%s) PC=%08x\n"), reg, cd->name, M68K_GETPC); } else if (size == 1) { write_log(_T("Byte read from register %04x (%s) PC=%08x\n"), reg, cd->name, M68K_GETPC); } } static void is_valid_dma(int reg, int ptrreg, uaecptr addr) { if (!memwatch_access_validator) return; if (reg == 0x1fe) // refresh return; if (currprefs.z3chipmem.size) { if (addr >= currprefs.z3chipmem.start_address && addr < currprefs.z3chipmem.start_address + currprefs.z3chipmem.size) return; } if (!(addr & ~(currprefs.chipmem.size - 1))) return; const struct customData *cdreg = &custd[reg >> 1]; const struct customData *cdptr = &custd[ptrreg >> 1]; write_log(_T("DMA DAT %04x (%s), PT %04x (%s) accessed invalid memory %08x. Init: %08x, PC/COP=%08x\n"), reg, cdreg->name, ptrreg, cdptr->name, addr, (custom_storage[ptrreg >> 1].value << 16) | (custom_storage[(ptrreg >> 1) + 1].value), custom_storage[ptrreg >> 1].pc); } static void mungwall_memwatch(uaecptr addr, int rwi, int size, uae_u32 valp) { struct mungwall_data *mwd = mungwall[addr >> 16]; if (!mwd) return; for (int i = 0; i < mwd->slots; i++) { if (!mwd->end[i]) continue; if (addr + size > mwd->start[i] && addr < mwd->end[i]) { } } } static void memwatch_hit_msg(int mw) { console_out_f(_T("Memwatch %d: break at %08X.%c %c%c%c %08X PC=%08X "), mw, mwhit.addr, mwhit.size == 1 ? 'B' : (mwhit.size == 2 ? 'W' : 'L'), (mwhit.rwi & 1) ? 'R' : ' ', (mwhit.rwi & 2) ? 'W' : ' ', (mwhit.rwi & 4) ? 'I' : ' ', mwhit.val, mwhit.pc); for (int i = 0; memwatch_access_masks[i].mask; i++) { if (mwhit.access_mask == memwatch_access_masks[i].mask) console_out_f(_T("%s (%03x)\n"), memwatch_access_masks[i].name, mwhit.reg); } if (mwhit.access_mask & (MW_MASK_BLITTER_A | MW_MASK_BLITTER_B | MW_MASK_BLITTER_C | MW_MASK_BLITTER_D_N | MW_MASK_BLITTER_D_L | MW_MASK_BLITTER_D_F)) { blitter_debugdump(); } } static int memwatch_func (uaecptr addr, int rwi, int size, uae_u32 *valp, uae_u32 accessmask, uae_u32 reg) { uae_u32 val = *valp; if (inside_debugger) return 1; if (mungwall) mungwall_memwatch(addr, rwi, size, val); if (illgdebug) illg_debug_do (addr, rwi, size, val); if (heatmap) memwatch_heatmap (addr, rwi, size, accessmask); addr = munge24 (addr); if (smc_table && (rwi >= 2)) smc_detector (addr, rwi, size, valp); for (int i = mwnodes_start; i <= mwnodes_end; i++) { struct memwatch_node *m = &mwnodes[i]; uaecptr addr2 = m->addr; uaecptr addr3 = addr2 + m->size; int rwi2 = m->rwi; uae_u32 oldval = 0; int isoldval = 0; int brk = 0; uae_u32 newval = 0; if (m->size == 0) continue; if (!(rwi & rwi2)) continue; if (!(m->access_mask & accessmask)) continue; if (addr >= addr2 && addr < addr3) brk = 1; if (!brk && size == 2 && (addr + 1 >= addr2 && addr + 1 < addr3)) brk = 1; if (!brk && size == 4 && ((addr + 2 >= addr2 && addr + 2 < addr3) || (addr + 3 >= addr2 && addr + 3 < addr3))) brk = 1; if (!brk) continue; if (m->bus_error) { if (((m->bus_error & 1) && (rwi & 1)) || ((m->bus_error & 4) && (rwi & 4)) || ((m->bus_error & 2) && (rwi & 2))) { hardware_exception2(addr, val, (rwi & 2) != 0, (rwi & 4) != 0, size == 4 ? sz_long : (size == 2 ? sz_word : sz_byte)); } continue; } if (mem_banks[addr >> 16]->check (addr, size)) { uae_u8 *p = mem_banks[addr >> 16]->xlateaddr (addr); if (size == 1) { oldval = p[0]; newval = (*valp) & 0xff; } else if (size == 2) { oldval = (p[0] << 8) | p[1]; newval = (*valp) & 0xffff; } else { oldval = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0); newval = *valp; } isoldval = 1; } if (m->pc != 0xffffffff) { if (m->pc != regs.instruction_pc) continue; } if (!m->frozen && m->val_enabled) { int trigger = 0; uae_u32 mask = m->size == 4 ? 0xffffffff : (1 << (m->size * 8)) - 1; uae_u32 mval = m->val; int scnt = size; for (;;) { if (((mval & mask) & m->val_mask) == ((val & mask) & m->val_mask)) trigger = 1; if (mask & 0x80000000) break; if (m->size == 1) { mask <<= 8; mval <<= 8; scnt--; } else if (m->size == 2) { mask <<= 16; scnt -= 2; mval <<= 16; } else { scnt -= 4; } if (scnt <= 0) break; } if (!trigger) continue; } if (m->mustchange && rwi == 2 && isoldval) { if (oldval == newval) continue; } if (m->modval_written) { if (!rwi) { brk = 0; } else if (m->modval_written == 1) { m->modval_written = 2; m->modval = val; brk = 0; } else if (m->modval == val) { brk = 0; } } if (m->frozen) { if (m->val_enabled) { int shift = (addr + size - 1) - (m->addr + m->val_size - 1); uae_u32 sval; uae_u32 mask; if (m->val_size == 4) mask = 0xffffffff; else if (m->val_size == 2) mask = 0x0000ffff; else mask = 0x000000ff; sval = m->val; if (shift < 0) { shift = -8 * shift; sval >>= shift; mask >>= shift; } else { shift = 8 * shift; sval <<= shift; mask <<= shift; } *valp = (sval & mask) | ((*valp) & ~mask); //write_log (_T("%08x %08x %08x %08x %d\n"), addr, m->addr, *valp, mask, shift); return 1; } return 0; } mwhit.addr = addr; mwhit.rwi = rwi; mwhit.size = size; mwhit.val = 0; mwhit.access_mask = accessmask; mwhit.reg = reg; if (mwhit.rwi & 2) mwhit.val = val; mwhit.pc = M68K_GETPC; memwatch_triggered = i + 1; if (m->reportonly) { memwatch_hit_msg(memwatch_triggered - 1); } if (!m->nobreak && !m->reportonly) { debugging = 1; debug_pc = M68K_GETPC; debug_cycles(1); set_special(SPCFLAG_BRK); } return 1; } return 1; } #endif /* WINUAE_FOR_HATARI */ #ifndef WINUAE_FOR_HATARI static int mmu_hit (uaecptr addr, int size, int rwi, uae_u32 *v); static uae_u32 REGPARAM2 mmu_lget (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v = 0; if (!mmu_hit (addr, 4, 0, &v)) v = debug_mem_banks[off]->lget (addr); return v; } static uae_u32 REGPARAM2 mmu_wget (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v = 0; if (!mmu_hit (addr, 2, 0, &v)) v = debug_mem_banks[off]->wget (addr); return v; } static uae_u32 REGPARAM2 mmu_bget (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v = 0; if (!mmu_hit(addr, 1, 0, &v)) v = debug_mem_banks[off]->bget (addr); return v; } static void REGPARAM2 mmu_lput (uaecptr addr, uae_u32 v) { int off = debug_mem_off (&addr); if (!mmu_hit (addr, 4, 1, &v)) debug_mem_banks[off]->lput (addr, v); } static void REGPARAM2 mmu_wput (uaecptr addr, uae_u32 v) { int off = debug_mem_off (&addr); if (!mmu_hit (addr, 2, 1, &v)) debug_mem_banks[off]->wput (addr, v); } static void REGPARAM2 mmu_bput (uaecptr addr, uae_u32 v) { int off = debug_mem_off (&addr); if (!mmu_hit (addr, 1, 1, &v)) debug_mem_banks[off]->bput (addr, v); } static uae_u32 REGPARAM2 mmu_lgeti (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v = 0; if (!mmu_hit (addr, 4, 4, &v)) v = debug_mem_banks[off]->lgeti (addr); return v; } static uae_u32 REGPARAM2 mmu_wgeti (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v = 0; if (!mmu_hit (addr, 2, 4, &v)) v = debug_mem_banks[off]->wgeti (addr); return v; } static uae_u32 REGPARAM2 debug_lget(uaecptr addr) { uae_u32 off = debug_mem_off(&addr); uae_u32 v; v = debug_mem_banks[off]->lget(addr); memwatch_func(addr, 1, 4, &v, MW_MASK_CPU_D_R, 0); return v; } static uae_u32 REGPARAM2 debug_wget (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v; v = debug_mem_banks[off]->wget (addr); memwatch_func (addr, 1, 2, &v, MW_MASK_CPU_D_R, 0); return v; } static uae_u32 REGPARAM2 debug_bget (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v; v = debug_mem_banks[off]->bget (addr); memwatch_func (addr, 1, 1, &v, MW_MASK_CPU_D_R, 0); return v; } static uae_u32 REGPARAM2 debug_lgeti (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v; v = debug_mem_banks[off]->lgeti (addr); memwatch_func (addr, 4, 4, &v, MW_MASK_CPU_I, 0); return v; } static uae_u32 REGPARAM2 debug_wgeti (uaecptr addr) { int off = debug_mem_off (&addr); uae_u32 v; v = debug_mem_banks[off]->wgeti (addr); memwatch_func (addr, 4, 2, &v, MW_MASK_CPU_I, 0); return v; } static void REGPARAM2 debug_lput (uaecptr addr, uae_u32 v) { int off = debug_mem_off (&addr); if (memwatch_func (addr, 2, 4, &v, MW_MASK_CPU_D_W, 0)) debug_mem_banks[off]->lput (addr, v); } static void REGPARAM2 debug_wput (uaecptr addr, uae_u32 v) { int off = debug_mem_off (&addr); if (memwatch_func (addr, 2, 2, &v, MW_MASK_CPU_D_W, 0)) debug_mem_banks[off]->wput (addr, v); } static void REGPARAM2 debug_bput (uaecptr addr, uae_u32 v) { int off = debug_mem_off (&addr); if (memwatch_func (addr, 2, 1, &v, MW_MASK_CPU_D_W, 0)) debug_mem_banks[off]->bput (addr, v); } static int REGPARAM2 debug_check (uaecptr addr, uae_u32 size) { return debug_mem_banks[munge24 (addr) >> 16]->check (addr, size); } static uae_u8 *REGPARAM2 debug_xlate (uaecptr addr) { return debug_mem_banks[munge24 (addr) >> 16]->xlateaddr (addr); } struct peekdma peekdma_data; static void peekdma_save(int type, uaecptr addr, uae_u32 mask, int reg, int ptrreg) { peekdma_data.type = type; peekdma_data.addr = addr; peekdma_data.mask = mask; peekdma_data.reg = reg; peekdma_data.ptrreg = ptrreg; } void debug_getpeekdma_value(uae_u32 v) { uae_u32 vv = v; if (!memwatch_enabled) { return; } is_valid_dma(peekdma_data.reg, peekdma_data.ptrreg, peekdma_data.addr); if (debug_mem_banks[peekdma_data.addr >> 16] == NULL) { return; } if (!currprefs.z3chipmem.size) { peekdma_data.addr &= chipmem_bank.mask; } memwatch_func(peekdma_data.addr, 1, 2, &vv, peekdma_data.mask, peekdma_data.reg); } void debug_getpeekdma_value_long(uae_u32 v, int offset) { uae_u32 vv = v; uae_u32 mask = 0xffffffff; if (!memwatch_enabled) { return; } is_valid_dma(peekdma_data.reg, peekdma_data.ptrreg, peekdma_data.addr + offset); if (debug_mem_banks[(peekdma_data.addr + offset) >> 16] == NULL) { return; } if (!currprefs.z3chipmem.size) { mask = chipmem_bank.mask; } memwatch_func((peekdma_data.addr + offset) & mask, 1, 4, &vv, peekdma_data.mask, peekdma_data.reg); } uae_u32 debug_putpeekdma_chipset(uaecptr addr, uae_u32 v, uae_u32 mask, int reg) { peekdma_save(0, addr, mask, reg, 0); if (!memwatch_enabled) return v; peekdma_data.addr &= 0x1fe; peekdma_data.addr += 0xdff000; memwatch_func(peekdma_data.addr, 2, 2, &v, peekdma_data.mask, peekdma_data.reg); return v; } uae_u32 debug_putpeekdma_chipram(uaecptr addr, uae_u32 v, uae_u32 mask, int reg) { peekdma_save(1, addr, mask, reg, reg); if (!memwatch_enabled) return v; is_valid_dma(peekdma_data.reg, peekdma_data.ptrreg, peekdma_data.addr); if (debug_mem_banks[peekdma_data.addr >> 16] == NULL) return v; if (!currprefs.z3chipmem.size) peekdma_data.addr &= chipmem_bank.mask; memwatch_func(peekdma_data.addr & chipmem_bank.mask, 2, 2, &v, peekdma_data.mask, peekdma_data.reg); return v; } void debug_getpeekdma_chipram(uaecptr addr, uae_u32 mask, int reg) { peekdma_save(2, addr, mask, reg, reg); } static void debug_putlpeek (uaecptr addr, uae_u32 v) { if (!memwatch_enabled) return; memwatch_func (addr, 2, 4, &v, MW_MASK_CPU_D_W, 0); } void debug_wputpeek (uaecptr addr, uae_u32 v) { if (!memwatch_enabled) return; memwatch_func (addr, 2, 2, &v, MW_MASK_CPU_D_W, 0); } void debug_bputpeek (uaecptr addr, uae_u32 v) { if (!memwatch_enabled) return; memwatch_func (addr, 2, 1, &v, MW_MASK_CPU_D_W, 0); } void debug_bgetpeek (uaecptr addr, uae_u32 v) { uae_u32 vv = v; if (!memwatch_enabled) return; memwatch_func (addr, 1, 1, &vv, MW_MASK_CPU_D_R, 0); } void debug_wgetpeek (uaecptr addr, uae_u32 v) { uae_u32 vv = v; if (!memwatch_enabled) return; memwatch_func (addr, 1, 2, &vv, MW_MASK_CPU_D_R, 0); } void debug_lgetpeek (uaecptr addr, uae_u32 v) { uae_u32 vv = v; if (!memwatch_enabled) return; memwatch_func (addr, 1, 4, &vv, MW_MASK_CPU_D_R, 0); } struct membank_store { addrbank *addr; addrbank newbank; int banknr; }; static struct membank_store *membank_stores; static int membank_total; #define MEMWATCH_STORE_SLOTS 32 static void memwatch_reset (void) { for (int i = 0; i < membank_total; i++) { addrbank *ab = debug_mem_banks[i]; if (!ab) continue; map_banks_quick (ab, i, 1, 1); } for (int i = 0; membank_stores[i].addr; i++) { struct membank_store *ms = &membank_stores[i]; /* name was allocated in memwatch_remap */ xfree ((char*)ms->newbank.name); memset (ms, 0, sizeof (struct membank_store)); ms->addr = NULL; } memset (debug_mem_banks, 0, membank_total * sizeof (addrbank*)); } static void memwatch_remap (uaecptr addr) { int mode = 0; int i; int banknr; struct membank_store *ms; addrbank *bank; addrbank *newbank = NULL; addr &= ~65535; banknr = addr >> 16; if (debug_mem_banks[banknr]) return; bank = mem_banks[banknr]; for (i = 0 ; i < MEMWATCH_STORE_SLOTS; i++) { ms = &membank_stores[i]; if (ms->addr == NULL) break; if (ms->addr == bank) { newbank = &ms->newbank; break; } } if (i >= MEMWATCH_STORE_SLOTS) return; if (!newbank) { TCHAR tmp[200]; _stprintf (tmp, _T("%s [D]"), bank->name); ms->addr = bank; ms->banknr = banknr; newbank = &ms->newbank; memcpy (newbank, bank, sizeof(addrbank)); newbank->bget = mode ? mmu_bget : debug_bget; newbank->wget = mode ? mmu_wget : debug_wget; newbank->lget = mode ? mmu_lget : debug_lget; newbank->bput = mode ? mmu_bput : debug_bput; newbank->wput = mode ? mmu_wput : debug_wput; newbank->lput = mode ? mmu_lput : debug_lput; newbank->check = debug_check; newbank->xlateaddr = debug_xlate; newbank->wgeti = mode ? mmu_wgeti : debug_wgeti; newbank->lgeti = mode ? mmu_lgeti : debug_lgeti; /* name will be freed by memwatch_reset */ newbank->name = my_strdup (tmp); if (!newbank->mask) newbank->mask = -1; newbank->baseaddr_direct_r = 0; newbank->baseaddr_direct_w = 0; } debug_mem_banks[banknr] = bank; map_banks_quick (newbank, banknr, 1, 1); // map aliases for (i = 0; i < membank_total; i++) { uaecptr addr2 = i << 16; addrbank *ab = &get_mem_bank(addr2); if (ab != ms->addr) continue; if ((addr2 & ab->mask) == (addr & bank->mask)) { debug_mem_banks[i] = ms->addr; map_banks_quick (newbank, i, 1, 1); } } } static void memwatch_setup(void) { memwatch_reset(); mwnodes_start = MEMWATCH_TOTAL - 1; mwnodes_end = 0; for (int i = 0; i < MEMWATCH_TOTAL; i++) { struct memwatch_node *m = &mwnodes[i]; if (!m->size) continue; if (mwnodes_start > i) mwnodes_start = i; if (mwnodes_end < i) mwnodes_end = i; int addr = m->addr & ~65535; int eaddr = (m->addr + m->size + 65535) & ~65535; while (addr < eaddr) { memwatch_remap(addr); addr += 65536; } } } static int deinitialize_memwatch (void) { int oldmode; if (!memwatch_enabled && !mmu_enabled) return -1; memwatch_reset (); oldmode = mmu_enabled ? 1 : 0; xfree (debug_mem_banks); debug_mem_banks = NULL; xfree (debug_mem_area); debug_mem_area = NULL; xfree (membank_stores); membank_stores = NULL; memwatch_enabled = 0; mmu_enabled = 0; xfree (illgdebug); illgdebug = 0; return oldmode; } static void initialize_memwatch (int mode) { membank_total = currprefs.address_space_24 ? 256 : 65536; deinitialize_memwatch (); debug_mem_banks = xcalloc (addrbank*, 65536); if (!debug_mem_banks) return; if (membank_total < 65536) { for (int i = 256; i < 65536; i++) { debug_mem_banks[i] = &dummy_bank; } } debug_mem_area = xcalloc (addrbank, membank_total); membank_stores = xcalloc (struct membank_store, MEMWATCH_STORE_SLOTS); for (int i = 0; i < MEMWATCH_TOTAL; i++) { struct memwatch_node *m = &mwnodes[i]; m->pc = 0xffffffff; } #if 0 int i, j, as; addrbank *a1, *a2, *oa; oa = NULL; for (i = 0; i < as; i++) { a1 = debug_mem_banks[i] = debug_mem_area + i; a2 = mem_banks[i]; if (a2 != oa) { for (j = 0; membank_stores[j].addr; j++) { if (membank_stores[j].addr == a2) break; } if (membank_stores[j].addr == NULL) { membank_stores[j].addr = a2; memcpy (&membank_stores[j].store, a2, sizeof (addrbank)); } } memcpy (a1, a2, sizeof (addrbank)); } for (i = 0; i < as; i++) { a2 = mem_banks[i]; a2->bget = mode ? mmu_bget : debug_bget; a2->wget = mode ? mmu_wget : debug_wget; a2->lget = mode ? mmu_lget : debug_lget; a2->bput = mode ? mmu_bput : debug_bput; a2->wput = mode ? mmu_wput : debug_wput; a2->lput = mode ? mmu_lput : debug_lput; a2->check = debug_check; a2->xlateaddr = debug_xlate; a2->wgeti = mode ? mmu_wgeti : debug_wgeti; a2->lgeti = mode ? mmu_lgeti : debug_lgeti; } #endif if (mode) mmu_enabled = 1; else memwatch_enabled = 1; } int debug_bankchange (int mode) { if (mode == -1) { int v = deinitialize_memwatch (); if (v < 0) return -2; return v; } if (mode >= 0) { initialize_memwatch (mode); memwatch_setup (); } return -1; } addrbank *get_mem_bank_real(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!memwatch_enabled) return ab; addrbank *ab2 = debug_mem_banks[addr >> 16]; if (ab2) return ab2; return ab; } static const TCHAR *getsizechar (int size) { if (size == 4) return _T(".l"); if (size == 3) return _T(".3"); if (size == 2) return _T(".w"); if (size == 1) return _T(".b"); return _T(""); } void memwatch_dump2 (TCHAR *buf, int bufsize, int num) { int i; struct memwatch_node *mwn; if (buf) memset (buf, 0, bufsize * sizeof (TCHAR)); for (i = 0; i < MEMWATCH_TOTAL; i++) { if ((num >= 0 && num == i) || (num < 0)) { uae_u32 usedmask = 0; mwn = &mwnodes[i]; if (mwn->size == 0) continue; buf = buf_out (buf, &bufsize, _T("%2d: %08X - %08X (%d) %c%c%c"), i, mwn->addr, mwn->addr + (mwn->size - 1), mwn->size, (mwn->rwi & 1) ? 'R' : ' ', (mwn->rwi & 2) ? 'W' : ' ', (mwn->rwi & 4) ? 'I' : ' '); if (mwn->frozen) buf = buf_out (buf, &bufsize, _T(" F")); if (mwn->val_enabled) buf = buf_out (buf, &bufsize, _T(" =%X%s"), mwn->val, getsizechar (mwn->val_size)); if (mwn->modval_written) buf = buf_out (buf, &bufsize, _T(" =M")); if (mwn->mustchange) buf = buf_out(buf, &bufsize, _T(" C")); if (mwn->pc != 0xffffffff) buf = buf_out(buf, &bufsize, _T(" PC=%08x"), mwn->pc); if (mwn->reportonly) buf = buf_out(buf, &bufsize, _T(" L")); if (mwn->nobreak) buf = buf_out(buf, &bufsize, _T(" N")); if (mwn->bus_error) { buf = buf_out(buf, &bufsize, _T(" BER%s%s%s"), (mwn->bus_error & 1) ? _T("R") : _T(""), (mwn->bus_error & 2) ? _T("W") : _T(""), (mwn->bus_error & 4) ? _T("P") : _T("")); } for (int j = 0; memwatch_access_masks[j].mask; j++) { uae_u32 mask = memwatch_access_masks[j].mask; if ((mwn->access_mask & mask) == mask && (usedmask & mask) == 0) { buf = buf_out(buf, &bufsize, _T(" ")); buf = buf_out(buf, &bufsize, memwatch_access_masks[j].name); usedmask |= mask; } } buf = buf_out (buf, &bufsize, _T("\n")); } } } static void memwatch_dump (int num) { TCHAR *buf; int multiplier = num < 0 ? MEMWATCH_TOTAL : 1; buf = xmalloc (TCHAR, 50 * multiplier); if (!buf) return; memwatch_dump2 (buf, 50 * multiplier, num); f_out (stdout, _T("%s"), buf); xfree (buf); } static void memwatch (TCHAR **c) { int num; struct memwatch_node *mwn; TCHAR nc, *cp; bool err; if (!memwatch_enabled) { initialize_memwatch (0); console_out (_T("Memwatch breakpoints enabled\n")); memwatch_access_validator = 0; } cp = *c; ignore_ws (c); if (!more_params (c)) { memwatch_dump (-1); return; } nc = next_char (c); if (nc == '-') { deinitialize_memwatch (); console_out (_T("Memwatch breakpoints disabled\n")); return; } if (nc == 'l') { memwatch_access_validator = !memwatch_access_validator; console_out_f(_T("Memwatch DMA validator %s\n"), memwatch_access_validator ? _T("enabled") : _T("disabled")); return; } if (nc == 'd') { if (illgdebug) { ignore_ws (c); if (more_params (c)) { uae_u32 addr = readhex(c, NULL); uae_u32 len = 1; if (more_params (c)) len = readhex(c, NULL); console_out_f (_T("Cleared logging addresses %08X - %08X\n"), addr, addr + len); while (len > 0) { addr &= 0xffffff; illgdebug[addr] = 7; addr++; len--; } } else { illg_free(); console_out (_T("Illegal memory access logging disabled\n")); } } else { illg_init (); ignore_ws (c); illgdebug_break = 0; if (more_params (c)) illgdebug_break = 1; console_out_f (_T("Illegal memory access logging enabled. Break=%d\n"), illgdebug_break); } return; } *c = cp; num = readint(c, NULL); if (num < 0 || num >= MEMWATCH_TOTAL) return; mwn = &mwnodes[num]; mwn->size = 0; ignore_ws (c); if (!more_params (c)) { console_out_f (_T("Memwatch %d removed\n"), num); memwatch_setup (); return; } mwn->addr = readhex(c, NULL); mwn->size = 1; mwn->rwi = 7; mwn->val_enabled = 0; mwn->val_mask = 0xffffffff; mwn->val = 0; mwn->access_mask = 0; mwn->reg = 0xffffffff; mwn->frozen = 0; mwn->modval_written = 0; mwn->mustchange = 0; mwn->bus_error = 0; mwn->reportonly = false; mwn->nobreak = false; ignore_ws (c); if (more_params (c)) { mwn->size = readhex(c, NULL); ignore_ws (c); if (more_params (c)) { TCHAR *cs = *c; while (*cs) { for (int i = 0; memwatch_access_masks[i].mask; i++) { const TCHAR *n = memwatch_access_masks[i].name; int len = uaetcslen(n); if (!_tcsnicmp(cs, n, len)) { if (cs[len] == 0 || cs[len] == 10 || cs[len] == 13 || cs[len] == ' ') { mwn->access_mask |= memwatch_access_masks[i].mask; while (len > 0) { len--; cs[len] = ' '; } } } } cs++; } ignore_ws (c); if (more_params(c)) { for (;;) { TCHAR ncc = _totupper(peek_next_char(c)); TCHAR nc = _totupper(next_char(c)); if (mwn->rwi == 7 && (nc == 'W' || nc == 'R' || nc == 'I')) mwn->rwi = 0; if (nc == 'F') mwn->frozen = 1; if (nc == 'W') mwn->rwi |= 2; if (nc == 'I') mwn->rwi |= 4; if (nc == 'R') mwn->rwi |= 1; if (nc == 'B') { mwn->bus_error = 0; for (;;) { ncc = next_char2(c); if (ncc == ' ' || ncc == 0) break; if (ncc == 'R') { mwn->bus_error |= 1; mwn->rwi |= 1; } else if (ncc == 'W') { mwn->bus_error |= 2; mwn->rwi |= 2; } else if (ncc == 'P') { mwn->bus_error |= 4; mwn->rwi |= 4; } else { break; } } if (!mwn->rwi) mwn->rwi = 7; if (!mwn->bus_error) mwn->bus_error = 7; } if (nc == 'L') mwn->reportonly = true; if (nc == 'N') mwn->nobreak = true; if (nc == 'P' && ncc == 'C') { next_char(c); mwn->pc = readhex(c, NULL); } if (nc == 'M') { mwn->modval_written = 1; } if (nc == 'C') { mwn->mustchange = 1; } if (nc == 'V') { mwn->val = readhex(c, &mwn->val_size, &err); mwn->val_enabled = 1; } if (!more_params(c)) break; } ignore_ws (c); } } } if (!mwn->access_mask) mwn->access_mask = MW_MASK_CPU_D_R | MW_MASK_CPU_D_W | MW_MASK_CPU_I; if (mwn->frozen && mwn->rwi == 0) mwn->rwi = 3; memwatch_setup (); memwatch_dump (num); } static void copymem(TCHAR **c) { uae_u32 addr = 0, eaddr = 0, dst = 0; bool err; ignore_ws(c); if (!more_params(c)) return; addr = readhex(c, &err); if (err) { return; } ignore_ws (c); if (!more_params(c)) return; eaddr = readhex(c, &err); if (err) { return; } ignore_ws (c); if (!more_params(c)) return; dst = readhex(c, &err); if (err) { return; } if (addr >= eaddr) return; uae_u32 addrb = addr; uae_u32 dstb = dst; uae_u32 len = eaddr - addr; if (dst <= addr) { while (addr < eaddr) { put_byte(dst, get_byte(addr)); addr++; dst++; } } else { dst += eaddr - addr; while (addr < eaddr) { dst--; eaddr--; put_byte(dst, get_byte(eaddr)); } } console_out_f(_T("Copied from %08x - %08x to %08x - %08x\n"), addrb, addrb + len - 1, dstb, dstb + len - 1); } static void writeintomem (TCHAR **c) { uae_u32 addr = 0; uae_u32 eaddr = 0xffffffff; uae_u32 val = 0; TCHAR cc; int len = 1; bool fillmode = false; bool err; if (**c == 'f') { fillmode = true; (*c)++; } else if (**c == 'c') { (*c)++; copymem(c); return; } ignore_ws(c); addr = readhex(c, &err); if (err) { return; } ignore_ws (c); if (fillmode) { if (!more_params(c)) return; eaddr = readhex(c, &err); if (err) { return; } ignore_ws(c); } if (!more_params (c)) return; TCHAR *cb = *c; uae_u32 addrc = addr; bool retry = false; for(;;) { cc = peekchar(c); retry = false; uae_u32 addrb = addr; if (cc == '\'' || cc == '\"') { TCHAR quoted = cc; next_char2(c); while (more_params2(c)) { TCHAR str[2]; char *astr; cc = next_char2(c); if (quoted == cc) { ignore_ws(c); retry = true; break; } if (addr >= eaddr) { break; } str[0] = cc; str[1] = 0; astr = ua(str); put_byte(addr, astr[0]); if (!fillmode) { char c = astr[0]; if (c < 32) { c = '.'; } console_out_f(_T("Wrote '%c' (%02X, %02u) at %08X.B\n"), c, c, c, addr); } xfree(astr); addr++; } if (fillmode && peekchar(c) == 0) { *c = cb; } } else { for (;;) { bool err; ignore_ws (c); if (!more_params (c)) break; val = readhex(c, &len, &err); if (err) { goto end; } if (len == 4) { put_long(addr, val); cc = 'L'; } else if (len == 2) { put_word(addr, val); cc = 'W'; } else if (len == 1) { put_byte(addr, val); cc = 'B'; } else { cc = peekchar(c); if (cc == '\'' || cc == '\"') { retry = true; } else { next_char(c); retry = true; } break; } if (!fillmode) { console_out_f(_T("Wrote %X (%u) at %08X.%c\n"), val, val, addr, cc); } addr += len; if (addr >= eaddr) break; } if (fillmode && peekchar(c) == 0) { *c = cb; } } if (retry) { continue; } if (addr >= eaddr) { break; } if (eaddr == 0xffffffff || addr <= addrb) { break; } } end: if (eaddr != 0xffffffff) console_out_f(_T("Wrote data to %08x - %08x\n"), addrc, addr); } static uae_u8 *dump_xlate (uae_u32 addr) { if (!mem_banks[addr >> 16]->check (addr, 1)) return NULL; return mem_banks[addr >> 16]->xlateaddr (addr); } #if 0 #define UAE_MEMORY_REGIONS_MAX 64 #define UAE_MEMORY_REGION_NAME_LENGTH 64 #define UAE_MEMORY_REGION_RAM (1 << 0) #define UAE_MEMORY_REGION_ALIAS (1 << 1) #define UAE_MEMORY_REGION_MIRROR (1 << 2) typedef struct UaeMemoryRegion { uaecptr start; int size; TCHAR name[UAE_MEMORY_REGION_NAME_LENGTH]; TCHAR rom_name[UAE_MEMORY_REGION_NAME_LENGTH]; uaecptr alias; int flags; } UaeMemoryRegion; typedef struct UaeMemoryMap { UaeMemoryRegion regions[UAE_MEMORY_REGIONS_MAX]; int num_regions; } UaeMemoryMap; #endif static const TCHAR *bankmodes[] = { _T("F32"), _T("C16"), _T("C32"), _T("CIA"), _T("F16"), _T("F16X") }; static void memory_map_dump_3(UaeMemoryMap *map, int log) { bool imold; int i, j, max; addrbank *a1 = mem_banks[0]; TCHAR txt[256]; map->num_regions = 0; imold = currprefs.illegal_mem; currprefs.illegal_mem = false; max = currprefs.address_space_24 ? 256 : 65536; j = 0; for (i = 0; i < max + 1; i++) { addrbank *a2 = NULL; if (i < max) a2 = mem_banks[i]; if (a1 != a2) { int k, mirrored, mirrored2, size, size_out; TCHAR size_ext; uae_u8 *caddr; TCHAR tmp[MAX_DPATH]; const TCHAR *name = a1->name; struct addrbank_sub *sb = a1->sub_banks; int bankoffset = 0; int region_size; k = j; caddr = dump_xlate (k << 16); mirrored = caddr ? 1 : 0; k++; while (k < i && caddr) { if (dump_xlate (k << 16) == caddr) { mirrored++; } k++; } mirrored2 = mirrored; if (mirrored2 == 0) mirrored2 = 1; while (bankoffset < 65536) { int bankoffset2 = bankoffset; if (sb) { uaecptr daddr; if (!sb->bank) break; daddr = (j << 16) | bankoffset; a1 = get_sub_bank(&daddr); name = a1->name; for (;;) { bankoffset2 += MEMORY_MIN_SUBBANK; if (bankoffset2 >= 65536) break; daddr = (j << 16) | bankoffset2; addrbank *dab = get_sub_bank(&daddr); if (dab != a1) break; } sb++; size = (bankoffset2 - bankoffset) / 1024; region_size = size * 1024; } else { size = (i - j) << (16 - 10); region_size = ((i - j) << 16) / mirrored2; } if (name == NULL) name = _T(""); size_out = size; size_ext = 'K'; if (j >= 256 && (size_out / mirrored2 >= 1024) && !((size_out / mirrored2) & 1023)) { size_out /= 1024; size_ext = 'M'; } _stprintf (txt, _T("%08X %7d%c/%d = %7d%c %s%s%c %s %s"), (j << 16) | bankoffset, size_out, size_ext, mirrored, mirrored ? size_out / mirrored : size_out, size_ext, (a1->flags & ABFLAG_CACHE_ENABLE_INS) ? _T("I") : _T("-"), (a1->flags & ABFLAG_CACHE_ENABLE_DATA) ? _T("D") : _T("-"), a1->baseaddr == NULL ? ' ' : '*', bankmodes[ce_banktype[j]], name); tmp[0] = 0; if ((a1->flags & ABFLAG_ROM) && mirrored) { TCHAR *p = txt + _tcslen (txt); uae_u32 crc = 0xffffffff; if (a1->check(((j << 16) | bankoffset), (size * 1024) / mirrored)) crc = get_crc32 (a1->xlateaddr((j << 16) | bankoffset), (size * 1024) / mirrored); struct romdata *rd = getromdatabycrc (crc); _stprintf (p, _T(" (%08X)"), crc); if (rd) { tmp[0] = '='; getromname (rd, tmp + 1); _tcscat (tmp, _T("\n")); } } if (a1 != &dummy_bank) { for (int m = 0; m < mirrored2; m++) { if (map->num_regions >= UAE_MEMORY_REGIONS_MAX) break; UaeMemoryRegion *r = &map->regions[map->num_regions]; r->start = (j << 16) + bankoffset + region_size * m; r->size = region_size; r->flags = 0; r->memory = NULL; if (!(a1->flags & ABFLAG_PPCIOSPACE)) { r->memory = dump_xlate((j << 16) | bankoffset); if (r->memory) r->flags |= UAE_MEMORY_REGION_RAM; } /* just to make it easier to spot in debugger */ r->alias = 0xffffffff; if (m >= 0) { r->alias = j << 16; r->flags |= UAE_MEMORY_REGION_ALIAS | UAE_MEMORY_REGION_MIRROR; } _stprintf(r->name, _T("%s"), name); _stprintf(r->rom_name, _T("%s"), tmp); map->num_regions += 1; } } _tcscat (txt, _T("\n")); if (log > 0) write_log (_T("%s"), txt); else if (log == 0) console_out (txt); if (tmp[0]) { if (log > 0) write_log (_T("%s"), tmp); else if (log == 0) console_out (tmp); } if (!sb) break; bankoffset = bankoffset2; } j = i; a1 = a2; } } pci_dump(log); currprefs.illegal_mem = imold; } void uae_memory_map(UaeMemoryMap *map) { memory_map_dump_3(map, -1); } static void memory_map_dump_2 (int log) { UaeMemoryMap map; memory_map_dump_3(&map, log); #if 0 for (int i = 0; i < map.num_regions; i++) { TCHAR txt[256]; UaeMemoryRegion *r = &map.regions[i]; int size = r->size / 1024; TCHAR size_ext = 'K'; int mirrored = 1; int size_out = 0; _stprintf (txt, _T("%08X %7u%c/%d = %7u%c %s\n"), r->start, size, size_ext, r->flags & UAE_MEMORY_REGION_RAM, size, size_ext, r->name); if (log) write_log (_T("%s"), txt); else console_out (txt); if (r->rom_name[0]) { if (log) write_log (_T("%s"), r->rom_name); else console_out (r->rom_name); } } #endif } void memory_map_dump (void) { memory_map_dump_2(1); } STATIC_INLINE uaecptr BPTR2APTR (uaecptr addr) { return addr << 2; } static TCHAR *BSTR2CSTR (uae_u8 *bstr) { TCHAR *s; char *cstr = xmalloc (char, bstr[0] + 1); if (cstr) { memcpy (cstr, bstr + 1, bstr[0]); cstr[bstr[0]] = 0; } s = au (cstr); xfree (cstr); return s; } static void print_task_info (uaecptr node, bool nonactive) { TCHAR *s; int process = get_byte_debug (node + 8) == 13 ? 1 : 0; console_out_f (_T("%08X: "), node); s = au ((char*)get_real_address_debug(get_long_debug (node + 10))); console_out_f (process ? _T("PROCESS '%s'\n") : _T("TASK '%s'\n"), s); xfree (s); if (process) { uaecptr cli = BPTR2APTR (get_long_debug (node + 172)); int tasknum = get_long_debug (node + 140); if (cli && tasknum) { uae_u8 *command_bstr = get_real_address_debug(BPTR2APTR (get_long_debug (cli + 16))); TCHAR *command = BSTR2CSTR (command_bstr); console_out_f (_T(" [%d, '%s']\n"), tasknum, command); xfree (command); } else { console_out (_T("\n")); } } if (nonactive) { uae_u32 sigwait = get_long_debug(node + 22); if (sigwait) console_out_f(_T(" Waiting signals: %08x\n"), sigwait); int offset = kickstart_version >= 37 ? 74 : 70; uae_u32 sp = get_long_debug(node + 54) + offset; uae_u32 pc = get_long_debug(sp); console_out_f(_T(" SP: %08x PC: %08x\n"), sp, pc); } } static void show_exec_tasks (void) { uaecptr execbase = get_long_debug (4); uaecptr taskready = execbase + 406; uaecptr taskwait = execbase + 420; uaecptr node; console_out_f (_T("Execbase at 0x%08X\n"), execbase); console_out (_T("Current:\n")); node = get_long_debug (execbase + 276); print_task_info (node, false); console_out_f (_T("Ready:\n")); node = get_long_debug (taskready); while (node && get_long_debug(node)) { print_task_info (node, true); node = get_long_debug (node); } console_out (_T("Waiting:\n")); node = get_long_debug (taskwait); while (node && get_long_debug(node)) { print_task_info (node, true); node = get_long_debug (node); } } static uaecptr get_base (const uae_char *name, int offset) { uaecptr v = get_long_debug (4); addrbank *b = &get_mem_bank(v); if (!b || !b->check (v, 400) || !(b->flags & ABFLAG_RAM)) return 0; v += offset; while ((v = get_long_debug (v))) { uae_u32 v2; uae_u8 *p; b = &get_mem_bank (v); if (!b || !b->check (v, 32) || (!(b->flags & ABFLAG_RAM) && !(b->flags & ABFLAG_ROMIN))) goto fail; v2 = get_long_debug (v + 10); // name b = &get_mem_bank (v2); if (!b || !b->check (v2, 20)) goto fail; if ((b->flags & ABFLAG_ROM) || (b->flags & ABFLAG_RAM) || (b->flags & ABFLAG_ROMIN)) { p = get_real_address_debug(v2); if (!memcmp (p, name, strlen (name) + 1)) return v; } } return 0; fail: return 0xffffffff; } static TCHAR *getfrombstr(uaecptr pp) { uae_u8 len = get_byte(pp << 2); TCHAR *s = xcalloc (TCHAR, len + 1); char data[256]; for (int i = 0; i < len; i++) { data[i] = get_byte((pp << 2) + 1 + i); data[i + 1] = 0; } return au_copy (s, len + 1, data); } // read one byte from expansion autoconfig ROM static void copyromdata(uae_u8 bustype, uaecptr rom, int offset, uae_u8 *out, int size) { switch (bustype & 0xc0) { case 0x00: // nibble while (size-- > 0) { *out++ = (get_byte_debug(rom + offset * 4 + 0) & 0xf0) | ((get_byte_debug(rom + offset * 4 + 2) & 0xf0) >> 4); offset++; } break; case 0x40: // byte while (size-- > 0) { *out++ = get_byte_debug(rom + offset * 2); offset++; } break; case 0x80: // word default: while (size-- > 0) { *out++ = get_byte_debug(rom + offset); offset++; } break; } } static void show_exec_lists (TCHAR *t) { uaecptr execbase = get_long_debug (4); uaecptr list = 0, node; TCHAR c = t[0]; if (c == 'o' || c == 'O') { // doslist uaecptr dosbase = get_base ("dos.library", 378); if (dosbase) { uaecptr rootnode = get_long_debug (dosbase + 34); uaecptr dosinfo = get_long_debug (rootnode + 24) << 2; console_out_f (_T("ROOTNODE: %08x DOSINFO: %08x\n"), rootnode, dosinfo); uaecptr doslist = get_long_debug (dosinfo + 4) << 2; while (doslist) { int type = get_long_debug (doslist + 4); uaecptr msgport = get_long_debug (doslist + 8); uaecptr lock = get_long_debug(doslist + 12); TCHAR *name = getfrombstr(get_long_debug(doslist + 40)); console_out_f(_T("%08x: Type=%d Port=%08x Lock=%08x '%s'\n"), doslist, type, msgport, lock, name); if (type == 0) { uaecptr fssm = get_long_debug(doslist + 28) << 2; console_out_f (_T(" - H=%08x Stack=%5d Pri=%2d Start=%08x Seg=%08x GV=%08x\n"), get_long_debug (doslist + 16) << 2, get_long_debug (doslist + 20), get_long_debug (doslist + 24), fssm, get_long_debug (doslist + 32) << 2, get_long_debug (doslist + 36)); if (fssm >= 0x100 && (fssm & 3) == 0) { TCHAR *unitname = getfrombstr(get_long_debug(fssm + 4)); console_out_f (_T(" %s:%d %08x\n"), unitname, get_long_debug(fssm), get_long_debug(fssm + 8)); uaecptr de = get_long_debug(fssm + 8) << 2; if (de) { console_out_f (_T(" TableSize %u\n"), get_long_debug(de + 0)); console_out_f (_T(" SizeBlock %u\n"), get_long_debug(de + 4)); console_out_f (_T(" SecOrg %u\n"), get_long_debug(de + 8)); console_out_f (_T(" Surfaces %u\n"), get_long_debug(de + 12)); console_out_f (_T(" SectorPerBlock %u\n"), get_long_debug(de + 16)); console_out_f (_T(" BlocksPerTrack %u\n"), get_long_debug(de + 20)); console_out_f (_T(" Reserved %u\n"), get_long_debug(de + 24)); console_out_f (_T(" PreAlloc %u\n"), get_long_debug(de + 28)); console_out_f (_T(" Interleave %u\n"), get_long_debug(de + 32)); console_out_f (_T(" LowCyl %u\n"), get_long_debug(de + 36)); console_out_f (_T(" HighCyl %u (Total %u)\n"), get_long_debug(de + 40), get_long_debug(de + 40) - get_long_debug(de + 36) + 1); console_out_f (_T(" NumBuffers %u\n"), get_long_debug(de + 44)); console_out_f (_T(" BufMemType 0x%08x\n"), get_long_debug(de + 48)); console_out_f (_T(" MaxTransfer 0x%08x\n"), get_long_debug(de + 52)); console_out_f (_T(" Mask 0x%08x\n"), get_long_debug(de + 56)); console_out_f (_T(" BootPri %d\n"), get_long_debug(de + 60)); console_out_f (_T(" DosType 0x%08x\n"), get_long_debug(de + 64)); } xfree(unitname); } } else if (type == 2) { console_out_f(_T(" - VolumeDate=%08x %08x %08x LockList=%08x DiskType=%08x\n"), get_long_debug(doslist + 16), get_long_debug(doslist + 20), get_long_debug(doslist + 24), get_long_debug(doslist + 28), get_long_debug(doslist + 32)); } xfree (name); doslist = get_long_debug (doslist) << 2; } } else { console_out_f (_T("can't find dos.library\n")); } return; } else if (c == 'i' || c == 'I') { // interrupts static const int it[] = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 }; static const int it2[] = { 1, 1, 1, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 7 }; list = execbase + 84; for (int i = 0; i < 16; i++) { console_out_f (_T("%2d %d: %08X\n"), i + 1, it2[i], list); if (it[i]) { console_out_f (_T(" [H] %08X\n"), get_long_debug (list)); node = get_long_debug (list + 8); if (node) { uae_u8 *addr = get_real_address_debug(get_long_debug (node + 10)); TCHAR *name = addr ? au ((char*)addr) : au(""); console_out_f (_T(" %08X (C=%08X D=%08X) '%s'\n"), node, get_long_debug (list + 4), get_long_debug (list), name); xfree (name); } } else { int cnt = 0; node = get_long_debug (list); node = get_long_debug (node); while (get_long_debug (node)) { uae_u8 *addr = get_real_address_debug(get_long_debug (node + 10)); TCHAR *name = addr ? au ((char*)addr) : au(""); uae_s8 pri = get_byte_debug(node + 9); console_out_f (_T(" [S] %08x %+03d (C=%08x D=%08X) '%s'\n"), node, pri, get_long_debug (node + 18), get_long_debug (node + 14), name); if (i == 4 - 1 || i == 14 - 1) { if (!_tcsicmp (name, _T("cia-a")) || !_tcsicmp (name, _T("cia-b"))) { static const TCHAR *ciai[] = { _T("A"), _T("B"), _T("ALRM"), _T("SP"), _T("FLG") }; uaecptr cia = node + 22; for (int j = 0; j < 5; j++) { uaecptr ciap = get_long_debug (cia); console_out_f (_T(" %5s: %08x"), ciai[j], ciap); if (ciap) { uae_u8 *addr2 = get_real_address_debug(get_long_debug (ciap + 10)); TCHAR *name2 = addr ? au ((char*)addr2) : au(""); console_out_f (_T(" (C=%08x D=%08X) '%s'"), get_long_debug (ciap + 18), get_long_debug (ciap + 14), name2); xfree (name2); } console_out_f (_T("\n")); cia += 4; } } } xfree (name); node = get_long_debug (node); cnt++; } if (!cnt) console_out_f (_T(" [S] \n")); } list += 12; } return; } else if (c == 'e') { // expansion uaecptr expbase = get_base("expansion.library", 378); if (expbase) { if (t[1] == 'm') { uaecptr list = get_long_debug(expbase + 74); while (list && get_long_debug(list)) { uaecptr name = get_long(list + 10); uae_s8 pri = get_byte(list + 9); uae_u16 flags = get_word_debug(list + 14); uae_u32 dn = get_long_debug(list + 16); uae_u8 *addr = get_real_address_debug(name); TCHAR *name1 = addr ? au((char*)addr) : au(""); my_trim(name1); console_out_f(_T("%08x %04x %08x %d %s\n"), list, flags, dn, pri, name1); xfree(name1); list = get_long_debug(list); } } else { list = get_long_debug(expbase + 60); while (list && get_long_debug(list)) { uae_u32 addr = get_long_debug(list + 32); uae_u16 rom_vector = get_word_debug(list + 16 + 10); uae_u8 type = get_byte_debug(list + 16 + 0); console_out_f(_T("%02x %02x %08x %08x %04x %02x %08x %04x (%u/%u)\n"), type, get_byte_debug(list + 16 + 2), addr, get_long_debug(list + 36), get_word_debug(list + 16 + 4), get_byte_debug(list + 16 + 1), get_long_debug(list + 16 + 6), rom_vector, get_word_debug(list + 16 + 4), get_byte_debug(list + 16 + 1)); for (int i = 0; i < 16; i++) { console_out_f(_T("%02x"), get_byte_debug(list + 16 + i)); if (i < 15) console_out_f(_T(".")); } console_out_f(_T("\n")); if ((type & 0x10)) { uae_u8 diagarea[256]; uae_u16 nameoffset; uaecptr rom = addr + rom_vector; uae_u8 config = get_byte_debug(rom); copyromdata(config, rom, 0, diagarea, 16); nameoffset = (diagarea[8] << 8) | diagarea[9]; console_out_f(_T(" %02x %02x Size %04x Diag %04x Boot %04x Name %04x %04x %04x\n"), diagarea[0], diagarea[1], (diagarea[2] << 8) | diagarea[3], (diagarea[4] << 8) | diagarea[5], (diagarea[6] << 8) | diagarea[7], nameoffset, (diagarea[10] << 8) | diagarea[11], (diagarea[12] << 8) | diagarea[13]); if (nameoffset != 0 && nameoffset != 0xffff) { copyromdata(config, rom, nameoffset, diagarea, 256); diagarea[sizeof diagarea - 1] = 0; TCHAR *str = au((char*)diagarea); console_out_f(_T(" '%s'\n"), str); xfree(str); } } list = get_long_debug(list); } } } return; } else if (c == 'R') { // residents list = get_long_debug(execbase + 300); while (list) { uaecptr resident = get_long_debug (list); if (!resident) break; if (resident & 0x80000000) { console_out_f (_T("-> %08X\n"), resident & 0x7fffffff); list = resident & 0x7fffffff; continue; } uae_u8 *addr; addr = get_real_address_debug(get_long_debug (resident + 14)); TCHAR *name1 = addr ? au ((char*)addr) : au(""); my_trim (name1); addr = get_real_address_debug(get_long_debug (resident + 18)); TCHAR *name2 = addr ? au ((char*)addr) : au(""); my_trim (name2); console_out_f (_T("%08X %08X: %02X %3d %02X %+3.3d '%s' ('%s')\n"), list, resident, get_byte_debug (resident + 10), get_byte_debug (resident + 11), get_byte_debug (resident + 12), (uae_s8)get_byte_debug (resident + 13), name1, name2); xfree (name2); xfree (name1); list += 4; } return; } else if (c == 'f' || c== 'F') { // filesystem.resource uaecptr fs = get_base ("FileSystem.resource", 336); if (fs) { static const TCHAR *fsnames[] = { _T("DosType"), _T("Version"), _T("PatchFlags"), _T("Type"), _T("Task"), _T("Lock"), _T("Handler"), _T("StackSize"), _T("Priority"), _T("Startup"), _T("SegList"), _T("GlobalVec"), NULL }; uae_u8 *addr = get_real_address_debug(get_long_debug (fs + 14)); TCHAR *name = addr ? au ((char*)addr) : au (""); my_trim (name); console_out_f (_T("%08x: '%s'\n"), fs, name); xfree (name); node = get_long_debug (fs + 18); while (get_long_debug (node)) { TCHAR *name = au ((char*)get_real_address_debug(get_long_debug (node + 10))); my_trim (name); console_out_f (_T("%08x: '%s'\n"), node, name); xfree (name); for (int i = 0; fsnames[i]; i++) { uae_u32 v = get_long_debug (node + 14 + i * 4); console_out_f (_T("%16s = %08x %d\n"), fsnames[i], v, v); } console_out_f (_T("\n")); node = get_long_debug (node); } } else { console_out_f (_T("FileSystem.resource not found.\n")); } return; } else if (c == 'm' || c == 'M') { // memory list = execbase + 322; node = get_long_debug (list); while (get_long_debug (node)) { TCHAR *name = au ((char*)get_real_address_debug(get_long_debug (node + 10))); uae_u16 v = get_word_debug (node + 8); console_out_f (_T("%08x %d %d %s\n"), node, (int)((v >> 8) & 0xff), (uae_s8)(v & 0xff), name); xfree (name); console_out_f (_T("Attributes %04x First %08x Lower %08x Upper %08x Free %d\n"), get_word_debug (node + 14), get_long_debug (node + 16), get_long_debug (node + 20), get_long_debug (node + 24), get_long_debug (node + 28)); uaecptr mc = get_long_debug (node + 16); while (mc) { uae_u32 mc1 = get_long_debug (mc); uae_u32 mc2 = get_long_debug (mc + 4); console_out_f (_T(" %08x: %08x-%08x,%08x,%08x (%d)\n"), mc, mc, mc + mc2, mc1, mc2, mc2); mc = mc1; } console_out_f (_T("\n")); node = get_long_debug (node); } return; } bool full = false; switch (c) { case 'r': // resources list = execbase + 336; break; case 'd': // devices list = execbase + 350; full = true; break; case 'l': // libraries list = execbase + 378; full = true; break; case 'p': // ports list = execbase + 392; break; case 's': // semaphores list = execbase + 532; break; } if (list == 0) return; node = get_long_debug (list); while (get_long_debug (node)) { TCHAR *name = au ((char*)get_real_address_debug(get_long_debug (node + 10))); uae_u16 v = get_word_debug (node + 8); console_out_f (_T("%08x %d %d"), node, (int)((v >> 8) & 0xff), (uae_s8)(v & 0xff)); if (full) { uae_u16 ver = get_word_debug(node + 20); uae_u16 rev = get_word_debug(node + 22); uae_u32 op = get_word_debug(node + 32); console_out_f(_T(" %d.%d %d"), ver, rev, op); } console_out_f(_T(" %s"), name); xfree (name); if (full) { uaecptr idstring = get_long_debug(node + 24); if (idstring) { name = au((char*)get_real_address_debug(idstring)); console_out_f(_T(" (%s)"), name); xfree(name); } } console_out_f(_T("\n")); node = get_long_debug (node); } } static int debug_vpos = -1; static int debug_hpos = -1; static void breakfunc(uae_u32 v) { write_log(_T("Cycle breakpoint hit\n")); debugging = 1; debug_vpos = -1; debug_hpos = -1; debug_cycles(2); set_special(SPCFLAG_BRK); } void debug_hsync(void) { if (debug_vpos < 0) { return; } if (debug_vpos != vpos) { return; } if (debug_hpos <= 0) { breakfunc(0); } else { if (current_hpos() < debug_hpos) { event2_newevent_x(-1, debug_hpos - current_hpos(), 0, breakfunc); } else { breakfunc(0); } } } static int cycle_breakpoint(TCHAR **c) { TCHAR nc = (*c)[0]; next_char(c); if (more_params(c)) { int count = readint(c, NULL); if (nc == 's') { if (more_params(c)) { debug_vpos = count; debug_hpos = readint(c, NULL); if (debug_vpos == vpos && debug_hpos > current_hpos()) { debug_vpos = -1; count = debug_hpos - current_hpos(); debug_hpos = -1; } else { return 1; } } else { count *= maxhpos; } } if (count > 0) { event2_newevent_x(-1, count, 0, breakfunc); } return 1; } return 0; } #if 0 static int trace_same_insn_count; static uae_u8 trace_insn_copy[10]; static struct regstruct trace_prev_regs; #endif static uaecptr nextpc; static void check_breakpoint_extra(TCHAR **c, struct breakpoint_node *bpn) { bpn->cnt = 0; bpn->chain = -1; if (more_params(c)) { TCHAR nc = _totupper((*c)[0]); if (nc == 'N') { next_char(c); bpn->cnt = readint(c, NULL); } } if (more_params(c)) { TCHAR nc = _totupper((*c)[0]); if (nc == 'H') { next_char(c); bpn->chain = readint(c, NULL); if (bpn->chain < 0 || bpn->chain >= BREAKPOINT_TOTAL) { bpn->chain = -1; } } } } int instruction_breakpoint(TCHAR **c) { struct breakpoint_node *bpn; int i; TCHAR next = 0; bool err; if (more_params (c)) { TCHAR nc = _totupper ((*c)[0]); if (nc == 'O') { // bpnum register operation value1 [mask value2] next_char(c); if (more_params(c)) { int bpidx = readint(c, NULL); if (more_params(c) && bpidx >= 0 && bpidx < BREAKPOINT_TOTAL) { bpn = &bpnodes[bpidx]; int regid = getregidx(c); if (regid >= 0) { bpn->type = regid; bpn->mask = 0xffffffff; if (more_params(c)) { int operid = getoperidx(c, &bpn->opersigned); if (more_params(c) && operid >= 0) { bpn->oper = operid; bpn->value1 = readhex(c, NULL); bpn->enabled = 1; if (more_params(c)) { bpn->mask = readhex(c, NULL); if (more_params(c)) { bpn->value2 = readhex(c, NULL); } } check_breakpoint_extra(c, bpn); console_out(_T("Breakpoint added.\n")); } } } } } return 0; } else if (nc == 'I') { uae_u16 opcodes[32]; next_char(c); ignore_ws(c); trace_param[1] = 0x10000; trace_param[2] = 0x10000; int w = m68k_asm(*c, opcodes, 0); if (w > 0) { trace_param[0] = opcodes[0]; if (w > 1) { trace_param[1] = opcodes[1]; if (w > 2) { trace_param[2] = opcodes[2]; } } } else { if (more_params(c)) { trace_param[0] = readhex(c, NULL); if (more_params(c)) { trace_param[1] = readhex(c, NULL); } if (more_params(c)) { trace_param[2] = readhex(c, NULL); } } else { trace_param[0] = 0x10000; } } trace_mode = TRACE_MATCH_INS; return 1; } else if (nc == 'D' && (*c)[1] == 0) { for (i = 0; i < BREAKPOINT_TOTAL; i++) { bpnodes[i].enabled = 0; } console_out(_T("All breakpoints removed.\n")); return 0; } else if (nc == 'R' && (*c)[1] == 0) { if (more_params(c)) { int bpnum = readint(c, NULL); if (bpnum >= 0 && bpnum < BREAKPOINT_TOTAL) { bpnodes[bpnum].enabled = 0; console_out_f(_T("Breakpoint %d removed.\n"), bpnum); } } return 0; } else if (nc == 'L') { int got = 0; for (i = 0; i < BREAKPOINT_TOTAL; i++) { bpn = &bpnodes[i]; if (!bpn->enabled) continue; console_out_f (_T("%d: %s %s %08x [%08x %08x]"), i, debugregs[bpn->type], debugoper[bpn->oper], bpn->value1, bpn->mask, bpn->value2); if (bpn->cnt > 0) { console_out_f(_T(" N=%d"), bpn->cnt); } if (bpn->chain > 0) { console_out_f(_T(" H=%d"), bpn->chain); } console_out_f(_T("\n")); got = 1; } if (!got) console_out (_T("No breakpoints.\n")); else console_out (_T("\n")); return 0; } trace_mode = TRACE_RANGE_PC; trace_param[0] = readhex(c, &err); if (err) { trace_mode = 0; return 0; } if (more_params (c)) { trace_param[1] = readhex(c, &err); if (!err) { return 1; } } for (i = 0; i < BREAKPOINT_TOTAL; i++) { bpn = &bpnodes[i]; if (bpn->enabled && bpn->value1 == trace_param[0]) { bpn->enabled = 0; console_out (_T("Breakpoint removed.\n")); trace_mode = 0; return 0; } } for (i = 0; i < BREAKPOINT_TOTAL; i++) { bpn = &bpnodes[i]; if (bpn->enabled) continue; bpn->value1 = trace_param[0]; bpn->type = BREAKPOINT_REG_PC; bpn->oper = BREAKPOINT_CMP_EQUAL; bpn->enabled = 1; check_breakpoint_extra(c, bpn); console_out (_T("Breakpoint added.\n")); trace_mode = 0; break; } return 0; } trace_mode = TRACE_RAM_PC; return 1; } static int process_breakpoint(TCHAR **c) { bool err; processptr = 0; xfree(processname); processname = NULL; if (!more_params(c)) return 0; if (**c == '\"') { TCHAR pn[200]; next_string(c, pn, sizeof(pn) / sizeof(TCHAR), 0); processname = ua(pn); } else { processptr = readhex(c, &err); if (err) { return 0; } } trace_mode = TRACE_CHECKONLY; return 1; } static void saveloadmem (TCHAR **cc, bool save) { uae_u8 b; uae_u32 src, src2; int len, len2; TCHAR name[MAX_PATH]; FILE *fp; if (!more_params (cc)) goto S_argh; next_string(cc, name, sizeof(name) / sizeof(TCHAR), 0); if (!more_params (cc)) goto S_argh; src2 = src = readhex(cc, NULL); if (save) { if (!more_params(cc)) goto S_argh; } len2 = len = -1; if (more_params(cc)) { len2 = len = readhex(cc, NULL); } fp = uae_tfopen (name, save ? _T("wb") : _T("rb")); if (fp == NULL) { console_out_f (_T("Couldn't open file '%s'.\n"), name); return; } if (save) { while (len > 0) { b = get_byte_debug (src); src++; len--; if (fwrite (&b, 1, 1, fp) != 1) { console_out (_T("Error writing file.\n")); break; } } if (len == 0) console_out_f (_T("Wrote %08X - %08X (%d bytes) to '%s'.\n"), src2, src2 + len2, len2, name); } else { len2 = 0; while (len != 0) { if (fread(&b, 1, 1, fp) != 1) { if (len > 0) console_out (_T("Unexpected end of file.\n")); len = 0; break; } put_byte (src, b); src++; if (len > 0) len--; len2++; } if (len == 0) console_out_f (_T("Read %08X - %08X (%d bytes) to '%s'.\n"), src2, src2 + len2, len2, name); } fclose (fp); return; S_argh: console_out (_T("Command needs more arguments!\n")); } static void searchmem (TCHAR **cc) { int i, sslen, got, val, stringmode; uae_u8 ss[256]; uae_u32 addr, endaddr; TCHAR nc; got = 0; sslen = 0; stringmode = 0; ignore_ws (cc); while(more_params(cc)) { if (**cc == '"' || **cc == '\'') { TCHAR quoted = **cc; stringmode = 1; (*cc)++; while (**cc != quoted && **cc != 0) { ss[sslen++] = tolower(**cc); (*cc)++; } if (**cc != 0) { (*cc)++; } } else { for (;;) { if (**cc == 32 || **cc == 0) { break; } if (**cc == '"' || **cc == '\'') { break; } nc = _totupper(next_char(cc)); if (isspace(nc)) break; if (isdigit(nc)) val = nc - '0'; else val = nc - 'A' + 10; if (val < 0 || val > 15) return; val *= 16; if (**cc == 32 || **cc == 0) break; nc = _totupper(next_char(cc)); if (isspace(nc)) break; if (isdigit(nc)) val += nc - '0'; else val += nc - 'A' + 10; if (val < 0 || val > 255) return; ss[sslen++] = (uae_u8)val; } } if (**cc == 0 || **cc == 32) { break; } if (**cc != '"' && **cc != '\'') { (*cc)++; } } if (sslen == 0) return; ignore_ws (cc); addr = 0xffffffff; endaddr = lastaddr(addr); if (more_params (cc)) { addr = readhex(cc, NULL); addr--; endaddr = lastaddr(addr); if (more_params(cc)) { endaddr = readhex(cc, NULL); } } console_out_f (_T("Searching from %08X to %08X..\n"), addr + 1, endaddr); nextaddr_init(addr); bool out = false; while ((addr = nextaddr(addr, endaddr, NULL, true, &out)) != 0xffffffff) { if (addr == endaddr) break; for (i = 0; i < sslen; i++) { uae_u8 b = get_byte_debug (addr + i); if (stringmode) { if (tolower (b) != ss[i]) break; } else { if (b != ss[i]) break; } } if (i == sslen) { got++; console_out_f (_T(" %08X"), addr); out = true; if (got > 100) { console_out (_T("\nMore than 100 results, aborting..")); break; } } if (iscancel (65536)) { console_out_f (_T("Aborted at %08X\n"), addr); break; } } if (!got) console_out (_T("nothing found")); console_out (_T("\n")); } static int staterecorder (TCHAR **cc) { #if 0 TCHAR nc; if (!more_params (cc)) { if (savestate_dorewind (1)) { debug_rewind = 1; return 1; } return 0; } nc = next_char (cc); if (nc == 'l') { savestate_listrewind (); return 0; } #endif return 0; } static int debugtest_modes[DEBUGTEST_MAX]; static const TCHAR *debugtest_names[] = { _T("Blitter"), _T("Keyboard"), _T("Floppy") }; void debugtest (enum debugtest_item di, const TCHAR *format, ...) { va_list parms; TCHAR buffer[1000]; if (!debugtest_modes[di]) return; va_start (parms, format); _vsntprintf (buffer, 1000 - 1, format, parms); va_end (parms); write_log (_T("%s PC=%08X: %s\n"), debugtest_names[di], M68K_GETPC, buffer); if (debugtest_modes[di] == 2) activate_debugger_new(); } static void debugtest_set (TCHAR **inptr) { int i, val, val2; ignore_ws (inptr); val2 = 1; if (!more_params (inptr)) { for (i = 0; i < DEBUGTEST_MAX; i++) debugtest_modes[i] = 0; console_out (_T("All debugtests disabled\n")); return; } val = readint(inptr, NULL); if (more_params (inptr)) { val2 = readint(inptr, NULL); if (val2 > 0) val2 = 2; } if (val < 0) { for (i = 0; i < DEBUGTEST_MAX; i++) debugtest_modes[i] = val2; console_out (_T("All debugtests enabled\n")); return; } if (val >= 0 && val < DEBUGTEST_MAX) { if (debugtest_modes[val]) debugtest_modes[val] = 0; else debugtest_modes[val] = val2; console_out_f (_T("Debugtest '%s': %s. break = %s\n"), debugtest_names[val], debugtest_modes[val] ? _T("on") :_T("off"), val2 == 2 ? _T("on") : _T("off")); } } static void debug_sprite (TCHAR **inptr) { uaecptr saddr, addr, addr2; int xpos, xpos_ecs; int ypos, ypos_ecs; int ypose, ypose_ecs; int attach; uae_u64 w1, w2, ww1, ww2; int size = 1, width; int ecs, sh10; int y, i; TCHAR tmp[80]; int max = 14; addr2 = 0; ignore_ws(inptr); addr = readhex(inptr, NULL); ignore_ws(inptr); if (more_params (inptr)) size = readhex(inptr, NULL); if (size != 1 && size != 2 && size != 4) { addr2 = size; ignore_ws(inptr); if (more_params(inptr)) size = readint(inptr, NULL); if (size != 1 && size != 2 && size != 4) size = 1; } for (;;) { ecs = 0; sh10 = 0; saddr = addr; width = size * 16; w1 = get_word_debug (addr); w2 = get_word_debug (addr + size * 2); console_out_f (_T(" %06X "), addr); for (i = 0; i < size * 2; i++) console_out_f (_T("%04X "), get_word_debug (addr + i * 2)); console_out_f (_T("\n")); ypos = (int)(w1 >> 8); xpos = w1 & 255; ypose = (int)(w2 >> 8); attach = (w2 & 0x80) ? 1 : 0; if (w2 & 4) ypos |= 256; if (w2 & 2) ypose |= 256; ypos_ecs = ypos; ypose_ecs = ypose; if (w2 & 0x40) ypos_ecs |= 512; if (w2 & 0x20) ypose_ecs |= 512; xpos <<= 1; if (w2 & 0x01) xpos |= 1; xpos_ecs = xpos << 2; if (w2 & 0x10) xpos_ecs |= 2; if (w2 & 0x08) xpos_ecs |= 1; if (w2 & (0x40 | 0x20 | 0x10 | 0x08)) ecs = 1; if (w1 & 0x80) sh10 = 1; if (ypose < ypos) ypose += 256; if (ecs_agnus) { ypos = ypos_ecs; ypose = ypose_ecs; } for (y = ypos; y < ypose; y++) { int x; addr += size * 4; if (addr2) addr2 += size * 4; if (size == 1) { w1 = get_word_debug (addr); w2 = get_word_debug (addr + 2); if (addr2) { ww1 = get_word_debug (addr2); ww2 = get_word_debug (addr2 + 2); } } else if (size == 2) { w1 = get_long_debug (addr); w2 = get_long_debug (addr + 4); if (addr2) { ww1 = get_long_debug (addr2); ww2 = get_long_debug (addr2 + 4); } } else if (size == 4) { w1 = get_long_debug (addr + 0); w2 = get_long_debug (addr + 8); w1 <<= 32; w2 <<= 32; w1 |= get_long_debug (addr + 4); w2 |= get_long_debug (addr + 12); if (addr2) { ww1 = get_long_debug (addr2 + 0); ww2 = get_long_debug (addr2 + 8); ww1 <<= 32; ww2 <<= 32; ww1 |= get_long_debug (addr2 + 4); ww2 |= get_long_debug (addr2 + 12); } } width = size * 16; for (x = 0; x < width; x++) { int v1 = w1 & 1; int v2 = w2 & 1; int v = v2 * 2 + v1; w1 >>= 1; w2 >>= 1; if (addr2) { int vv1 = ww1 & 1; int vv2 = ww2 & 1; int vv = vv2 * 2 + vv1; ww1 >>= 1; ww2 >>= 1; v *= 4; v += vv; tmp[width - (x + 1)] = v >= 10 ? 'A' + v - 10 : v + '0'; } else { tmp[width - (x + 1)] = v + '0'; } } tmp[width] = 0; console_out_f (_T("%3d %06X %s\n"), y, addr, tmp); } console_out_f (_T("Sprite address %08X, width = %d\n"), saddr, size * 16); console_out_f (_T("OCS: StartX=%d StartY=%d EndY=%d\n"), xpos, ypos, ypose); console_out_f (_T("ECS: StartX=%d (%d.%d) StartY=%d EndY=%d%s\n"), xpos_ecs, xpos_ecs / 4, xpos_ecs & 3, ypos_ecs, ypose_ecs, ecs ? _T(" (*)") : _T("")); console_out_f (_T("Attach: %d. AGA SSCAN/SH10 bit: %d\n"), attach, sh10); addr += size * 4; if (get_word_debug (addr) == 0 && get_word_debug (addr + size * 4) == 0) break; max--; if (max <= 0) { console_out_f(_T("Max sprite count reached.\n")); break; } } } int debug_write_memory_16 (uaecptr addr, uae_u16 v) { addrbank *ad; ad = &get_mem_bank (addr); if (ad) { ad->wput (addr, v); return 1; } return -1; } int debug_write_memory_8 (uaecptr addr, uae_u8 v) { addrbank *ad; ad = &get_mem_bank (addr); if (ad) { ad->bput (addr, v); return 1; } return -1; } int debug_peek_memory_16 (uaecptr addr) { addrbank *ad; ad = &get_mem_bank (addr); if (ad->flags & (ABFLAG_RAM | ABFLAG_ROM | ABFLAG_ROMIN | ABFLAG_SAFE)) return ad->wget (addr); if (ad == &custom_bank) { addr &= 0x1fe; return (ar_custom[addr + 0] << 8) | ar_custom[addr + 1]; } return -1; } int debug_read_memory_16 (uaecptr addr) { addrbank *ad; ad = &get_mem_bank (addr); if (ad) return ad->wget (addr); return -1; } int debug_read_memory_8 (uaecptr addr) { addrbank *ad; ad = &get_mem_bank (addr); if (ad) return ad->bget (addr); return -1; } static void disk_debug (TCHAR **inptr) { TCHAR parm[10]; int i; if (**inptr == 'd') { (*inptr)++; ignore_ws (inptr); disk_debug_logging = readint(inptr, NULL); console_out_f (_T("Disk logging level %d\n"), disk_debug_logging); return; } disk_debug_mode = 0; disk_debug_track = -1; ignore_ws (inptr); if (!next_string (inptr, parm, sizeof (parm) / sizeof (TCHAR), 1)) goto end; for (i = 0; i < _tcslen(parm); i++) { if (parm[i] == 'R') disk_debug_mode |= DISK_DEBUG_DMA_READ; if (parm[i] == 'W') disk_debug_mode |= DISK_DEBUG_DMA_WRITE; if (parm[i] == 'P') disk_debug_mode |= DISK_DEBUG_PIO; } if (more_params(inptr)) disk_debug_track = readint(inptr, NULL); if (disk_debug_track < 0 || disk_debug_track > 2 * 83) disk_debug_track = -1; if (disk_debug_logging == 0) disk_debug_logging = 1; end: console_out_f (_T("Disk breakpoint mode %c%c%c track %d\n"), disk_debug_mode & DISK_DEBUG_DMA_READ ? 'R' : '-', disk_debug_mode & DISK_DEBUG_DMA_WRITE ? 'W' : '-', disk_debug_mode & DISK_DEBUG_PIO ? 'P' : '-', disk_debug_track); } static void find_ea (TCHAR **inptr) { uae_u32 ea, sea, dea; uaecptr addr, end, end2; int hits = 0; bool err; addr = 0xffffffff; end = lastaddr(addr); ea = readhex(inptr, &err); if (err) { return; } if (more_params(inptr)) { addr = readhex(inptr, &err); if (err) { return; } addr--; end = lastaddr(addr); if (more_params(inptr)) { end = readhex(inptr, &err); if (err) { return; } } } console_out_f (_T("Searching from %08X to %08X\n"), addr + 1, end); end2 = 0; nextaddr_init(addr); bool out = false; while((addr = nextaddr(addr, end, &end2, true, &out)) != 0xffffffff) { if ((addr & 1) == 0 && addr + 6 <= end2) { sea = 0xffffffff; dea = 0xffffffff; m68k_disasm_ea (addr, NULL, 1, &sea, &dea, 0xffffffff); if (ea == sea || ea == dea) { m68k_disasm (addr, NULL, 0xffffffff, 1); out = true; hits++; if (hits > 100) { console_out_f (_T("Too many hits. End addr = %08X\n"), addr); break; } } if (iscancel (65536)) { console_out_f (_T("Aborted at %08X\n"), addr); break; } } } } static void m68k_modify (TCHAR **inptr) { uae_u32 v; TCHAR parm[10]; TCHAR c1, c2; int i; if (!next_string (inptr, parm, sizeof (parm) / sizeof (TCHAR), 1)) return; c1 = _totupper (parm[0]); c2 = 99; if (c1 == 'A' || c1 == 'D' || c1 == 'P') { c2 = _totupper (parm[1]); if (isdigit (c2)) c2 -= '0'; else c2 = 99; } v = readhex(inptr, NULL); if (c1 == 'A' && c2 < 8) regs.regs[8 + c2] = v; else if (c1 == 'D' && c2 < 8) regs.regs[c2] = v; else if (c1 == 'P' && c2 == 0) regs.irc = v; else if (c1 == 'P' && c2 == 1) regs.ir = v; else if (!_tcscmp (parm, _T("SR"))) { regs.sr = v; MakeFromSR (); } else if (!_tcscmp (parm, _T("CCR"))) { regs.sr = (regs.sr & ~31) | (v & 31); MakeFromSR (); } else if (!_tcscmp (parm, _T("USP"))) { regs.usp = v; } else if (!_tcscmp (parm, _T("ISP"))) { regs.isp = v; } else if (!_tcscmp (parm, _T("PC"))) { m68k_setpc (v); fill_prefetch (); } else { for (i = 0; m2cregs[i].regname; i++) { if (!_tcscmp (parm, m2cregs[i].regname)) val_move2c2 (m2cregs[i].regno, v); } } } static void ppc_disasm(uaecptr addr, uaecptr *nextpc, int cnt) { PPCD_CB disa; while(cnt-- > 0) { uae_u32 instr = get_long_debug(addr); disa.pc = addr; disa.instr = instr; PPCDisasm(&disa); TCHAR *mnemo = au(disa.mnemonic); TCHAR *ops = au(disa.operands); console_out_f(_T("%08X %08X %-12s%-30s\n"), addr, instr, mnemo, ops); xfree(ops); xfree(mnemo); addr += 4; } if (nextpc) *nextpc = addr; } static void dma_disasm(int frames, int vp, int hp, int frames_end, int vp_end, int hp_end) { if (!dma_record_data || frames < 0 || vp < 0 || hp < 0) return; #if 0 for (;;) { struct dma_rec *dr = NULL; if (dma_record_frame[0] == frames) dr = &dma_record[0][vp * NR_DMA_REC_HPOS + hp]; else if (dma_record_frame[1] == frames) dr = &dma_record[1][vp * NR_DMA_REC_HPOS + hp]; if (!dr) return; TCHAR l1[16], l2[16], l3[16], l4[16]; if (get_record_dma_info(dr, hp, vp, l1, l2, l3, l4, NULL, NULL, NULL, NULL)) { TCHAR tmp[256]; _stprintf(tmp, _T(" - %02d %02X %s"), dr->ipl, hp, l2); while (_tcslen(tmp) < 18) { _tcscat(tmp, _T(" ")); } console_out_f(_T("%s %s %s\n"), tmp, l3, l4); } hp++; if (dr->end) { hp = 0; vp++; if (vp >= maxvpos + 1) { vp = 0; frames++; break; } } if ((frames == frames_end && vp == vp_end && hp == hp_end) || frames > frames_end) break; if (vp_end < 0 || hp_end < 0 || frames_end < 0) break; } #endif } static uaecptr nxdis, nxmem, asmaddr; static bool ppcmode, asmmode; static bool parsecmd(TCHAR *cmd, bool *out) { if (!_tcsicmp(cmd, _T("reset"))) { deactivate_debugger(); debug_continue(); uae_reset(0, 0); return true; } if (!_tcsicmp(cmd, _T("reseth"))) { deactivate_debugger(); debug_continue(); uae_reset(1, 0); return true; } if (!_tcsicmp(cmd, _T("resetk"))) { deactivate_debugger(); debug_continue(); uae_reset(0, 1); return true; } return false; } static bool debug_line (TCHAR *input) { TCHAR cmd, *inptr; uaecptr addr; bool err; inptr = input; if (asmmode) { if (more_params(&inptr)) { if (!_tcsicmp(inptr, _T("x"))) { asmmode = false; return false; } uae_u16 asmout[16]; int inss = m68k_asm(inptr, asmout, asmaddr); if (inss > 0) { for (int i = 0; i < inss; i++) { put_word(asmaddr + i * 2, asmout[i]); } m68k_disasm(asmaddr, &nxdis, 0xffffffff, 1); asmaddr = nxdis; } console_out_f(_T("%08X "), asmaddr); return false; } else { asmmode = false; return false; } } ignore_ws(&inptr); if (parsecmd(inptr, &err)) { return err; } cmd = next_char (&inptr); switch (cmd) { case 'I': if (more_params (&inptr)) { static int recursive; if (!recursive) { recursive++; handle_custom_event(inptr, 0); device_check_config(); recursive--; } } break; case 'c': dumpcia (); dumpdisk (_T("DEBUG")); dumpcustom (); break; case 'i': { if (*inptr == 'l') { next_char (&inptr); if (more_params (&inptr)) { debug_illegal_mask = readhex(&inptr, NULL); if (more_params(&inptr)) debug_illegal_mask |= ((uae_u64)readhex(&inptr, NULL)) << 32; } else { debug_illegal_mask = debug_illegal ? 0 : -1; debug_illegal_mask &= ~((uae_u64)255 << 24); // mask interrupts } console_out_f (_T("Exception breakpoint mask: %08X %08X\n"), (uae_u32)(debug_illegal_mask >> 32), (uae_u32)debug_illegal_mask); debug_illegal = debug_illegal_mask ? 1 : 0; } else { addr = 0xffffffff; if (more_params (&inptr)) addr = readhex (&inptr, NULL); dump_vectors (addr); } break; } case 'e': { bool aga = tolower(*inptr) == 'a'; if (aga) next_char(&inptr); bool ext = tolower(*inptr) == 'x'; dump_custom_regs(aga, ext); } break; case 'r': { if (*inptr == 'c') { next_char(&inptr); m68k_dumpcache(*inptr == 'd'); } else if (*inptr == 's') { if (*(inptr + 1) == 's') debugmem_list_stackframe(true); else debugmem_list_stackframe(false); } else if (more_params(&inptr)) { m68k_modify(&inptr); } else { custom_dumpstate(0); m68k_dumpstate(&nextpc, 0xffffffff); } } break; case 'D': deepcheatsearch (&inptr); break; case 'C': cheatsearch (&inptr); break; case 'W': writeintomem (&inptr); break; case 'w': memwatch (&inptr); break; case 'S': saveloadmem (&inptr, true); break; case 'L': saveloadmem (&inptr, false); break; case 's': if (*inptr == 'e' && *(inptr + 1) == 'g') { next_char(&inptr); next_char(&inptr); addr = 0xffffffff; if (*inptr == 's') { debugmem_list_segment(1, addr); } else { if (more_params(&inptr)) { addr = readhex(&inptr, NULL); } debugmem_list_segment(0, addr); } } else if (*inptr == 'c') { screenshot(-1, 1, 1); } else if (*inptr == 'p') { inptr++; debug_sprite (&inptr); } else if (*inptr == 'm') { if (*(inptr + 1) == 'c') { next_char (&inptr); next_char (&inptr); if (!smc_table) smc_detect_init (&inptr); else smc_free (); } } else { searchmem (&inptr); } break; case 'a': asmaddr = nxdis; if (more_params(&inptr)) { asmaddr = readhex(&inptr, &err); if (err) { break; } if (more_params(&inptr)) { uae_u16 asmout[16]; int inss = m68k_asm(inptr, asmout, asmaddr); if (inss > 0) { for (int i = 0; i < inss; i++) { put_word(asmaddr + i * 2, asmout[i]); } m68k_disasm(asmaddr, &nxdis, 1, 0xffffffff); asmaddr = nxdis; return false; } } } asmmode = true; console_out_f(_T("%08X "), asmaddr); break; case 'd': { if (*inptr == 'i') { next_char (&inptr); disk_debug (&inptr); } else if (*inptr == 'j') { inptr++; inputdevice_logging = 1 | 2; if (more_params (&inptr)) inputdevice_logging = readint(&inptr, NULL); console_out_f (_T("Input logging level %d\n"), inputdevice_logging); } else if (*inptr == 'm') { memory_map_dump_2 (0); } else if (*inptr == 't') { next_char (&inptr); debugtest_set (&inptr); #ifdef _WIN32 } else if (*inptr == 'g') { extern void update_disassembly (uae_u32); next_char (&inptr); if (more_params (&inptr)) update_disassembly(readhex (&inptr, NULL)); #endif } else { uae_u32 daddr; int count; if (*inptr == 'p' && inptr[1] == 'p' && inptr[2] == 'c') { ppcmode = true; next_char(&inptr); } else if(*inptr == 'o') { ppcmode = false; next_char(&inptr); } if (more_params (&inptr)) daddr = readhex(&inptr, NULL); else daddr = nxdis; if (more_params (&inptr)) count = readhex(&inptr, NULL); else count = 10; if (ppcmode) { ppc_disasm(daddr, &nxdis, count); } else { m68k_disasm (daddr, &nxdis, 0xffffffff, count); } } } break; case 'T': if (inptr[0] == 'L') debugger_scan_libraries(); else if (inptr[0] == 't' || inptr[0] == 0) show_exec_tasks (); else show_exec_lists (&inptr[0]); break; case 't': no_trace_exceptions = 0; debug_cycles(2); trace_param[0] = trace_param[1] = 0; if (*inptr == 't') { no_trace_exceptions = 1; inptr++; } if (*inptr == 'r') { // break when PC in debugmem if (debugmem_get_range(&trace_param[0], &trace_param[1])) { trace_mode = TRACE_RANGE_PC; return true; } } else if (*inptr == 's') { if (*(inptr + 1) == 'e') { debugmem_enable_stackframe(true); } else if (*(inptr + 1) == 'd') { debugmem_enable_stackframe(false); } else if (*(inptr + 1) == 'p') { if (debugmem_break_stack_pop()) { debugger_active = 0; return true; } } else { if (debugmem_break_stack_pop()) { debugger_active = 0; return true; } } } else if (*inptr == 'l') { // skip next source line if (debugmem_isactive()) { trace_mode = TRACE_SKIP_LINE; trace_param[0] = 1; trace_param[1] = debugmem_get_sourceline(M68K_GETPC, NULL, 0); return true; } } else if (*inptr == 'x') { trace_mode = TRACE_SKIP_INS; trace_param[0] = 0xffffffff; exception_debugging = 1; return true; } else { if (more_params(&inptr)) trace_param[0] = readint(&inptr, NULL); if (trace_param[0] <= 0 || trace_param[0] > 10000) trace_param[0] = 1; trace_mode = TRACE_SKIP_INS; exception_debugging = 1; return true; } break; case 'z': trace_mode = TRACE_MATCH_PC; trace_param[0] = nextpc; exception_debugging = 1; debug_cycles(2); return true; case 'f': if (inptr[0] == 'a') { next_char (&inptr); find_ea (&inptr); } else if (inptr[0] == 'p') { inptr++; if (process_breakpoint (&inptr)) return true; } else if (inptr[0] == 'c' || inptr[0] == 's') { if (cycle_breakpoint(&inptr)) { return true; } } else if (inptr[0] == 'e' && inptr[1] == 'n') { break_if_enforcer = break_if_enforcer ? false : true; console_out_f(_T("Break when enforcer hit: %s\n"), break_if_enforcer ? _T("enabled") : _T("disabled")); } else { if (instruction_breakpoint(&inptr)) { debug_cycles(1); return true; } } break; case 'q': uae_quit(); deactivate_debugger(); return true; case 'g': if (more_params (&inptr)) { uaecptr addr = readhex(&inptr, &err); if (err) { break; } m68k_setpc(addr); fill_prefetch(); } deactivate_debugger(); return true; case 'x': if (_totupper(inptr[0]) == 'X') { debugger_change(-1); } else { deactivate_debugger(); close_console(); return true; } break; case 'H': { int count, temp, badly, skip; uae_u32 addr = 0; uae_u32 oldpc = m68k_getpc (); int lastframes, lastvpos, lasthpos; struct regstruct save_regs = regs; badly = 0; if (inptr[0] == 'H') { badly = 1; inptr++; } if (more_params(&inptr)) count = readint(&inptr, NULL); else count = 10; if (count > 1000) { addr = count; count = MAX_HIST; } if (count < 0) break; skip = count; if (more_params (&inptr)) skip = count - readint(&inptr, NULL); temp = lasthist; while (count-- > 0 && temp != firsthist) { if (temp == 0) temp = MAX_HIST - 1; else temp--; } lastframes = lastvpos = lasthpos = -1; while (temp != lasthist) { regs = history[temp].regs; if (regs.pc == addr || addr == 0) { m68k_setpc (regs.pc); if (badly) { m68k_dumpstate(NULL, 0xffffffff); } else { if (lastvpos >= 0) { dma_disasm(lastframes, lastvpos, lasthpos, history[temp].fp, history[temp].vpos, history[temp].hpos); } lastframes = history[temp].fp; lastvpos = history[temp].vpos; lasthpos = history[temp].hpos; console_out_f(_T("%2d %03d/%03d "), regs.intmask ? regs.intmask : (regs.s ? -1 : 0), lasthpos, lastvpos); m68k_disasm (regs.pc, NULL, 0xffffffff, 1); } if (addr && regs.pc == addr) break; } if (skip-- < 0) break; if (++temp == MAX_HIST) temp = 0; } regs = save_regs; m68k_setpc (oldpc); } break; case 'M': if (more_params (&inptr)) { switch (next_char (&inptr)) { case 'a': if (more_params (&inptr)) audio_channel_mask = readhex(&inptr, NULL); console_out_f (_T("Audio mask = %02X\n"), audio_channel_mask); break; case 's': if (more_params (&inptr)) { debug_sprite_mask_val = readhex(&inptr, NULL); debug_sprite_mask = 0; for (int i = 0; i < 8; i++) { if (debug_sprite_mask_val & (1 << i)) { debug_sprite_mask |= (3 << (i * 2)) | (1 << (i + 16)); } } } console_out_f (_T("Sprite mask: %02X\n"), debug_sprite_mask_val); break; case 'b': if (more_params(&inptr)) { debug_bpl_mask = readhex(&inptr, NULL) & 0xff; if (more_params(&inptr)) debug_bpl_mask_one = readhex(&inptr, NULL) & 0xff; notice_screen_contents_lost(0); } console_out_f (_T("Bitplane mask: %02X (%02X)\n"), debug_bpl_mask, debug_bpl_mask_one); break; } } break; case 'm': { uae_u32 maddr; int lines; #ifdef _WIN32 if (*inptr == 'g') { extern void update_memdump (uae_u32); next_char (&inptr); if (more_params(&inptr)) { uaecptr addr = readhex(&inptr, &err); if (!err) { update_memdump(addr); } } break; } #endif if (*inptr == 'm' && inptr[1] == 'u') { inptr += 2; if (inptr[0] == 'd') { if (currprefs.mmu_model >= 68040) mmu_dump_tables(); } else { if (currprefs.mmu_model) { if (more_params (&inptr)) debug_mmu_mode = readint(&inptr, NULL); else debug_mmu_mode = 0; console_out_f (_T("MMU translation function code = %d\n"), debug_mmu_mode); } } break; } err = false; if (more_params (&inptr)) { maddr = readhex(&inptr, &err); } else { maddr = nxmem; } if (more_params (&inptr)) lines = readhex(&inptr, &err); else lines = 20; if (!err) { dumpmem(maddr, &nxmem, lines); } } break; case 'v': case 'V': { static int v1 = 0, v2 = 0, v3 = 0; if (*inptr == 'h') { inptr++; if (more_params(&inptr) && *inptr == '?') { mw_help(); } else if (!heatmap) { debug_heatmap = 1; init_heatmap(); if (more_params(&inptr)) { v1 = readint(&inptr, NULL); if (v1 < 0) { debug_heatmap = 2; } } TCHAR buf[200]; TCHAR *pbuf; _stprintf(buf, _T("0 dff000 200 NONE")); pbuf = buf; memwatch(&pbuf); _stprintf(buf, _T("1 0 %08x NONE"), currprefs.chipmem.size); pbuf = buf; memwatch(&pbuf); if (currprefs.bogomem.size) { _stprintf(buf, _T("2 c00000 %08x NONE"), currprefs.bogomem.size); pbuf = buf; memwatch(&pbuf); } console_out_f(_T("Heatmap enabled\n")); } else { if (*inptr == 'd') { console_out_f(_T("Heatmap disabled\n")); free_heatmap(); } else { heatmap_stats(&inptr); } } } else if (*inptr == 'o') { if (debug_dma) { console_out_f (_T("DMA debugger disabled\n"), debug_dma); record_dma_reset(0); reset_drawing(); debug_dma = 0; } } else if (*inptr == 'm') { set_debug_colors(); inptr++; if (more_params(&inptr)) { v1 = readint(&inptr, &err); if (!err && v1 >= 0 && v1 < DMARECORD_MAX) { v2 = readint(&inptr, &err); if (!err && v2 >= 0 && v2 <= DMARECORD_SUBITEMS) { if (more_params(&inptr)) { uae_u32 rgb = readhex(&inptr, NULL); if (v2 == 0) { for (int i = 0; i < DMARECORD_SUBITEMS; i++) { debug_colors[v1].l[i] = rgb; } } else { v2--; debug_colors[v1].l[v2] = rgb; } debug_colors[v1].enabled = true; } else { debug_colors[v1].enabled = !debug_colors[v1].enabled; } console_out_f(_T("%d,%d: %08x %s %s\n"), v1, v2, debug_colors[v1].l[v2], debug_colors[v1].enabled ? _T("*") : _T(" "), debug_colors[v1].name); } } } else { for (int i = 0; i < DMARECORD_MAX; i++) { for (int j = 0; j < DMARECORD_SUBITEMS; j++) { if (j < debug_colors[i].max) { console_out_f(_T("%d,%d: %08x %s %s\n"), i, j, debug_colors[i].l[j], debug_colors[i].enabled ? _T("*") : _T(" "), debug_colors[i].name); } } } } } else { if (more_params(&inptr) && *inptr == '?') { mw_help(); } else { dma_record_vpos_type = 0; free_heatmap(); int nextcmd = peekchar(&inptr); if (nextcmd == 'l') { next_char(&inptr); } else if (nextcmd == 'v') { next_char(&inptr); dma_record_vpos_type = 1; } if (more_params(&inptr)) v1 = readint(&inptr, NULL); if (more_params(&inptr)) v2 = readint(&inptr, NULL); if (more_params(&inptr)) v3 = readint(&inptr, NULL); if (debug_dma && v1 >= 0 && v2 >= 0) { decode_dma_record(v2, v1, v3, cmd == 'v', nextcmd == 'l'); } else { if (debug_dma) { record_dma_reset(0); reset_drawing(); } debug_dma = v1 < 0 ? -v1 : 1; console_out_f(_T("DMA debugger enabled, mode=%d.\n"), debug_dma); } } } } break; case 'o': { if (copper_debugger (&inptr)) { debugger_active = 0; debugging = 0; return true; } break; } case 'O': break; case 'b': if (staterecorder (&inptr)) return true; break; case 'u': { if (more_params(&inptr)) { if (*inptr == 'a') { debugmem_inhibit_break(1); console_out(_T("All break to debugger methods inhibited.\n")); } else if (*inptr == 'c') { debugmem_inhibit_break(-1); console_out(_T("All break to debugger methods allowed.\n")); } } else { if (debugmem_inhibit_break(0)) { console_out(_T("Current break to debugger method inhibited.\n")); } else { console_out(_T("Current break to debugger method allowed.\n")); } } } break; case 'U': if (currprefs.mmu_model && more_params (&inptr)) { int i; uaecptr addrl = readhex(&inptr, NULL); uaecptr addrp; console_out_f (_T("%08X translates to:\n"), addrl); for (i = 0; i < 4; i++) { bool super = (i & 2) != 0; bool data = (i & 1) != 0; console_out_f (_T("S%dD%d="), super, data); TRY(prb) { if (currprefs.mmu_model >= 68040) addrp = mmu_translate (addrl, 0, super, data, false, sz_long); else addrp = mmu030_translate (addrl, super, data, false); console_out_f (_T("%08X"), addrp); TRY(prb2) { if (currprefs.mmu_model >= 68040) addrp = mmu_translate (addrl, 0, super, data, true, sz_long); else addrp = mmu030_translate (addrl, super, data, true); console_out_f (_T(" RW")); } CATCH(prb2) { console_out_f (_T(" RO")); } ENDTRY } CATCH(prb) { console_out_f (_T("***********")); } ENDTRY console_out_f (_T(" ")); } console_out_f (_T("\n")); } break; case 'h': case '?': if (more_params (&inptr)) converter (&inptr); else debug_help (); break; } return false; } static TCHAR input[MAX_LINEWIDTH]; static void debug_1 (void) { custom_dumpstate(0); m68k_dumpstate(&nextpc, debug_pc); debug_pc = 0xffffffff; nxdis = nextpc; nxmem = 0; debugger_active = 1; for (;;) { int v; if (!debugger_active) return; update_debug_info (); console_out (_T(">")); console_flush (); debug_linecounter = 0; v = console_get (input, MAX_LINEWIDTH); if (v < 0) return; if (v == 0) continue; if (debug_line (input)) return; } } static void addhistory(void) { uae_u32 pc = currprefs.cpu_model >= 68020 && currprefs.cpu_compatible ? regs.instruction_pc : m68k_getpc(); int prevhist = lasthist == 0 ? MAX_HIST - 1 : lasthist - 1; if (history[prevhist].regs.pc == pc) { return; } history[lasthist].regs = regs; history[lasthist].regs.pc = pc; history[lasthist].vpos = vpos; history[lasthist].hpos = current_hpos(); history[lasthist].fp = timeframes; if (++lasthist == MAX_HIST) { lasthist = 0; } if (lasthist == firsthist) { if (++firsthist == MAX_HIST) { firsthist = 0; } } } void debug_exception(int nr) { if (debug_illegal) { if (nr <= 63 && (debug_illegal_mask & ((uae_u64)1 << nr))) { write_log(_T("Exception %d breakpoint\n"), nr); activate_debugger(); } } if (trace_param[0] == 0xffffffff && trace_mode == TRACE_SKIP_INS) { activate_debugger(); } } static bool check_breakpoint(struct breakpoint_node *bpn, uaecptr pc) { int bpnum = -1; if (!bpn->enabled) { return false; } if (bpn->type == BREAKPOINT_REG_PC) { if (bpn->value1 == pc) { return true; } } else if (bpn->type >= 0 && bpn->type < BREAKPOINT_REG_END) { uae_u32 value1 = bpn->value1 & bpn->mask; uae_u32 value2 = bpn->value2 & bpn->mask; uae_u32 cval = returnregx(bpn->type) & bpn->mask; int opersize = bpn->mask == 0xff ? 1 : (bpn->mask == 0xffff) ? 2 : 4; bool opersigned = bpn->opersigned; uae_s32 value1s = (uae_s32)value1; uae_s32 value2s = (uae_s32)value2; uae_s32 cvals = (uae_s32)cval; if (opersize == 2) { cvals = (uae_s32)(uae_s16)cvals; value1s = (uae_s32)(uae_s16)value1s; value2s = (uae_s32)(uae_s16)value2s; } else if (opersize == 1) { cvals = (uae_s32)(uae_s8)cvals; value1s = (uae_s32)(uae_s8)value1s; value2s = (uae_s32)(uae_s8)value2s; } switch (bpn->oper) { case BREAKPOINT_CMP_EQUAL: if (cval == value1) return true; break; case BREAKPOINT_CMP_NEQUAL: if (cval != value1) return true; break; case BREAKPOINT_CMP_SMALLER: if (opersigned) { if (cvals <= value1s) return true; } else { if (cval <= value1) return true; } break; case BREAKPOINT_CMP_LARGER: if (opersigned) { if (cvals >= value1s) return true; } else { if (cval >= value1) return true; } break; case BREAKPOINT_CMP_RANGE: if (opersigned) { if (cvals >= value1s && cvals <= value2s) return true; } else { if (cval >= value1 && cval <= value2) return true; } break; case BREAKPOINT_CMP_NRANGE: if (opersigned) { if (cvals <= value1s || cvals >= value2s) return true; } else { if (cval <= value1 || cval >= value2) return true; } break; } } return false; } static bool check_breakpoint_count(struct breakpoint_node *bpn, uaecptr pc) { if (bpn->cnt <= 0) { return true; } console_out_f(_T("Breakpoint %d hit: PC=%08x, count=%d.\n"), bpn - bpnodes, pc, bpn->cnt); bpn->cnt--; return false; } void debug (void) { int wasactive; if (savestate_state || quit_program) return; bogusframe = 1; disasm_init(); addhistory (); #if 0 if (do_skip && skipaddr_start == 0xC0DEDBAD) { if (trace_same_insn_count > 0) { if (memcmp (trace_insn_copy, regs.pc_p, 10) == 0 && memcmp (trace_prev_regs.regs, regs.regs, sizeof regs.regs) == 0) { trace_same_insn_count++; return; } } if (trace_same_insn_count > 1) fprintf (logfile, "[ repeated %d times ]\n", trace_same_insn_count); m68k_dumpstate (logfile, &nextpc); trace_same_insn_count = 1; memcpy (trace_insn_copy, regs.pc_p, 10); memcpy (&trace_prev_regs, ®s, sizeof regs); } #endif if (!memwatch_triggered) { if (trace_mode) { uae_u32 pc; uae_u16 opcode; int bpnum = -1; int bp = 0; pc = munge24 (m68k_getpc ()); opcode = currprefs.cpu_model < 68020 && (currprefs.cpu_compatible || currprefs.cpu_cycle_exact) ? regs.ir : get_word_debug (pc); for (int i = 0; i < BREAKPOINT_TOTAL; i++) { struct breakpoint_node *bpn = &bpnodes[i]; if (check_breakpoint(bpn, pc)) { int j; // if this breakpoint is chained, ignore it for (j = 0; j < BREAKPOINT_TOTAL; j++) { struct breakpoint_node *bpn2 = &bpnodes[j]; if (bpn2->enabled && bpn2->chain == i) { break; } } if (j >= BREAKPOINT_TOTAL) { if (!check_breakpoint_count(bpn, pc)) { break; } int max = BREAKPOINT_TOTAL; bpnum = i; // check breakpoint chain while (bpnum >= 0 && bpnodes[bpnum].chain >= 0 && bpnodes[bpnum].chain != bpnum && max > 0) { bpnum = bpnodes[bpnum].chain; struct breakpoint_node *bpn = &bpnodes[bpnum]; if (!check_breakpoint(bpn, pc)) { bpnum = -1; break; } if (!check_breakpoint_count(bpn, pc)) { bpnum = -1; break; } max--; } if (bpnum >= 0) { break; } } } } if (trace_mode) { if (trace_mode == TRACE_MATCH_PC && trace_param[0] == pc) bp = -1; if (trace_mode == TRACE_RAM_PC) { addrbank *ab = &get_mem_bank(pc); if (ab->flags & ABFLAG_RAM) { uae_u16 ins = get_word_debug(pc); // skip JMP xxxxxx (LVOs) if (ins != 0x4ef9) { bp = -1; } } } if ((processptr || processname) && !isrom(m68k_getpc())) { uaecptr execbase = get_long_debug (4); uaecptr activetask = get_long_debug (execbase + 276); int process = get_byte_debug (activetask + 8) == 13 ? 1 : 0; char *name = (char*)get_real_address_debug(get_long_debug (activetask + 10)); if (process) { uaecptr cli = BPTR2APTR(get_long_debug (activetask + 172)); uaecptr seglist = 0; uae_char *command = NULL; if (cli) { if (processname) command = (char*)get_real_address_debug(BPTR2APTR(get_long_debug (cli + 16))); seglist = BPTR2APTR(get_long_debug (cli + 60)); } else { seglist = BPTR2APTR(get_long_debug (activetask + 128)); seglist = BPTR2APTR(get_long_debug (seglist + 12)); } if (activetask == processptr || (processname && (!stricmp (name, processname) || (command && command[0] && !strnicmp (command + 1, processname, command[0]) && processname[command[0]] == 0)))) { while (seglist) { uae_u32 size = get_long_debug (seglist - 4) - 4; if (pc >= (seglist + 4) && pc < (seglist + size)) { bp = -1; break; } seglist = BPTR2APTR(get_long_debug (seglist)); } } } } else if (trace_mode == TRACE_MATCH_INS) { if (trace_param[0] == 0x10000) { if (opcode == 0x4e75 || opcode == 0x4e73 || opcode == 0x4e77) bp = -1; } else if (opcode == trace_param[0]) { bp = -1; for (int op = 1; op < 3; op++) { if (trace_param[op] != 0x10000) { uae_u16 w = 0xffff; debug_get_prefetch(op, &w); if (w != trace_param[op]) bp = 0; } } } } else if (trace_mode == TRACE_SKIP_INS) { if (trace_param[0] != 0 && trace_param[0] != 0xffffffff) trace_param[0]--; if (trace_param[0] == 0) { bp = -1; } #if 0 } else if (skipaddr_start == 0xffffffff && skipaddr_doskip > 0) { bp = -1; #endif } else if (trace_mode == TRACE_RANGE_PC) { if (pc >= trace_param[0] && pc < trace_param[1]) bp = -1; } else if (trace_mode == TRACE_SKIP_LINE) { if (trace_param[0] != 0) trace_param[0]--; if (trace_param[0] == 0) { int line = debugmem_get_sourceline(pc, NULL, 0); if (line > 0 && line != trace_param[1]) bp = -1; } } } if (!bp && bpnum < 0) { debug_continue(); return; } if (bpnum >= 0) { console_out_f(_T("Breakpoint %d triggered.\n"), bpnum); } debug_cycles(1); } } else { memwatch_hit_msg(memwatch_triggered - 1); memwatch_triggered = 0; } wasactive = ismouseactive (); #ifdef WITH_PPC uae_ppc_pause(1); #endif inputdevice_unacquire (); pause_sound (); setmouseactive(0, 0); activate_console (); trace_mode = 0; exception_debugging = 0; debug_rewind = 0; processptr = 0; #if 0 if (!currprefs.statecapture) { changed_prefs.statecapture = currprefs.statecapture = 1; savestate_init (); } #endif debugmem_disable(); if (trace_cycles && last_frame >= 0) { if (last_frame + 2 >= vsync_counter || trace_cycles > 1) { evt_t c = last_cycles2 - last_cycles1; uae_u32 cc; if (c >= 0x7fffffff) { cc = 0x7fffffff; } else { cc = (uae_u32)c; } console_out_f(_T("Cycles: %d Chip, %d CPU. (V=%d H=%d -> V=%d H=%d)\n"), cc / CYCLE_UNIT, cc / cpucycleunit, last_vpos1, last_hpos1, last_vpos2, last_hpos2); } } trace_cycles = 0; debug_1 (); debugmem_enable(); if (!debug_rewind && !currprefs.cachesize #ifdef FILESYS && nr_units () == 0 #endif ) { savestate_capture (1); } if (!trace_mode) { for (int i = 0; i < BREAKPOINT_TOTAL; i++) { if (bpnodes[i].enabled) trace_mode = TRACE_CHECKONLY; } } if (trace_mode) { set_special (SPCFLAG_BRK); debugging = -1; } resume_sound (); inputdevice_acquire (TRUE); #ifdef WITH_PPC uae_ppc_pause(0); #endif setmouseactive(0, wasactive ? 2 : 0); target_inputdevice_acquire(); last_cycles1 = get_cycles(); last_vpos1 = vpos; last_hpos1 = current_hpos(); last_frame = timeframes; } const TCHAR *debuginfo (int mode) { static TCHAR txt[100]; uae_u32 pc = M68K_GETPC; _stprintf (txt, _T("PC=%08X INS=%04X %04X %04X"), pc, get_word_debug (pc), get_word_debug (pc + 2), get_word_debug (pc + 4)); return txt; } void mmu_disasm (uaecptr pc, int lines) { debug_mmu_mode = regs.s ? 6 : 2; m68k_dumpstate(NULL, 0xffffffff); m68k_disasm (pc, NULL, 0xffffffff, lines); } static int mmu_logging; #define MMU_PAGE_SHIFT 16 struct mmudata { uae_u32 flags; uae_u32 addr; uae_u32 len; uae_u32 remap; uae_u32 p_addr; }; static struct mmudata *mmubanks; static uae_u32 mmu_struct, mmu_callback, mmu_regs; static uae_u32 mmu_fault_bank_addr, mmu_fault_addr; static int mmu_fault_size, mmu_fault_rw; static int mmu_slots; static struct regstruct mmur; struct mmunode { struct mmudata *mmubank; struct mmunode *next; }; static struct mmunode **mmunl; extern regstruct mmu_backup_regs; #define MMU_READ_U (1 << 0) #define MMU_WRITE_U (1 << 1) #define MMU_READ_S (1 << 2) #define MMU_WRITE_S (1 << 3) #define MMU_READI_U (1 << 4) #define MMU_READI_S (1 << 5) #define MMU_MAP_READ_U (1 << 8) #define MMU_MAP_WRITE_U (1 << 9) #define MMU_MAP_READ_S (1 << 10) #define MMU_MAP_WRITE_S (1 << 11) #define MMU_MAP_READI_U (1 << 12) #define MMU_MAP_READI_S (1 << 13) void mmu_do_hit (void) { int i; uaecptr p; uae_u32 pc; mmu_triggered = 0; pc = m68k_getpc (); p = mmu_regs + 18 * 4; put_long (p, pc); regs = mmu_backup_regs; regs.intmask = 7; regs.t0 = regs.t1 = 0; if (!regs.s) { regs.usp = m68k_areg (regs, 7); if (currprefs.cpu_model >= 68020) m68k_areg (regs, 7) = regs.m ? regs.msp : regs.isp; else m68k_areg (regs, 7) = regs.isp; regs.s = 1; } MakeSR (); m68k_setpc (mmu_callback); fill_prefetch (); if (currprefs.cpu_model > 68000) { for (i = 0 ; i < 9; i++) { m68k_areg (regs, 7) -= 4; put_long (m68k_areg (regs, 7), 0); } m68k_areg (regs, 7) -= 4; put_long (m68k_areg (regs, 7), mmu_fault_addr); m68k_areg (regs, 7) -= 2; put_word (m68k_areg (regs, 7), 0); /* WB1S */ m68k_areg (regs, 7) -= 2; put_word (m68k_areg (regs, 7), 0); /* WB2S */ m68k_areg (regs, 7) -= 2; put_word (m68k_areg (regs, 7), 0); /* WB3S */ m68k_areg (regs, 7) -= 2; put_word (m68k_areg (regs, 7), (mmu_fault_rw ? 0 : 0x100) | (mmu_fault_size << 5)); /* SSW */ m68k_areg (regs, 7) -= 4; put_long (m68k_areg (regs, 7), mmu_fault_bank_addr); m68k_areg (regs, 7) -= 2; put_word (m68k_areg (regs, 7), 0x7002); } m68k_areg (regs, 7) -= 4; put_long (m68k_areg (regs, 7), get_long_debug (p - 4)); m68k_areg (regs, 7) -= 2; put_word (m68k_areg (regs, 7), mmur.sr); #ifdef JIT if (currprefs.cachesize) { set_special(SPCFLAG_END_COMPILE); } #endif } static void mmu_do_hit_pre (struct mmudata *md, uaecptr addr, int size, int rwi, uae_u32 v) { uae_u32 p, pc; int i; mmur = regs; pc = m68k_getpc (); if (mmu_logging) console_out_f (_T("MMU: hit %08X SZ=%d RW=%d V=%08X PC=%08X\n"), addr, size, rwi, v, pc); p = mmu_regs; put_long (p, 0); p += 4; for (i = 0; i < 16; i++) { put_long (p, regs.regs[i]); p += 4; } put_long (p, pc); p += 4; put_long (p, 0); p += 4; put_long (p, regs.usp); p += 4; put_long (p, regs.isp); p += 4; put_long (p, regs.msp); p += 4; put_word (p, regs.sr); p += 2; put_word (p, (size << 1) | ((rwi & 2) ? 1 : 0)); /* size and rw */ p += 2; put_long (p, addr); /* fault address */ p += 4; put_long (p, md->p_addr); /* bank address */ p += 4; put_long (p, v); p += 4; mmu_fault_addr = addr; mmu_fault_bank_addr = md->p_addr; mmu_fault_size = size; mmu_fault_rw = rwi; mmu_triggered = 1; } static int mmu_hit (uaecptr addr, int size, int rwi, uae_u32 *v) { int s, trig; uae_u32 flags; struct mmudata *md; struct mmunode *mn; if (mmu_triggered) return 1; mn = mmunl[addr >> MMU_PAGE_SHIFT]; if (mn == NULL) return 0; s = regs.s; while (mn) { md = mn->mmubank; if (addr >= md->addr && addr < md->addr + md->len) { flags = md->flags; if (flags & (MMU_MAP_READ_U | MMU_MAP_WRITE_U | MMU_MAP_READ_S | MMU_MAP_WRITE_S | MMU_MAP_READI_U | MMU_MAP_READI_S)) { trig = 0; if (!s && (flags & MMU_MAP_READ_U) && (rwi & 1)) trig = 1; if (!s && (flags & MMU_MAP_WRITE_U) && (rwi & 2)) trig = 1; if (s && (flags & MMU_MAP_READ_S) && (rwi & 1)) trig = 1; if (s && (flags & MMU_MAP_WRITE_S) && (rwi & 2)) trig = 1; if (!s && (flags & MMU_MAP_READI_U) && (rwi & 4)) trig = 1; if (s && (flags & MMU_MAP_READI_S) && (rwi & 4)) trig = 1; if (trig) { uaecptr maddr = md->remap + (addr - md->addr); if (maddr == addr) /* infinite mmu hit loop? no thanks.. */ return 1; if (mmu_logging) console_out_f (_T("MMU: remap %08X -> %08X SZ=%d RW=%d\n"), addr, maddr, size, rwi); if ((rwi & 2)) { switch (size) { case 4: put_long (maddr, *v); break; case 2: put_word (maddr, *v); break; case 1: put_byte (maddr, *v); break; } } else { switch (size) { case 4: *v = get_long_debug (maddr); break; case 2: *v = get_word_debug (maddr); break; case 1: *v = get_byte_debug (maddr); break; } } return 1; } } if (flags & (MMU_READ_U | MMU_WRITE_U | MMU_READ_S | MMU_WRITE_S | MMU_READI_U | MMU_READI_S)) { trig = 0; if (!s && (flags & MMU_READ_U) && (rwi & 1)) trig = 1; if (!s && (flags & MMU_WRITE_U) && (rwi & 2)) trig = 1; if (s && (flags & MMU_READ_S) && (rwi & 1)) trig = 1; if (s && (flags & MMU_WRITE_S) && (rwi & 2)) trig = 1; if (!s && (flags & MMU_READI_U) && (rwi & 4)) trig = 1; if (s && (flags & MMU_READI_S) && (rwi & 4)) trig = 1; if (trig) { mmu_do_hit_pre (md, addr, size, rwi, *v); return 1; } } } mn = mn->next; } return 0; } #ifdef JIT static void mmu_free_node(struct mmunode *mn) { if (!mn) return; mmu_free_node (mn->next); xfree (mn); } static void mmu_free(void) { struct mmunode *mn; int i; for (i = 0; i < mmu_slots; i++) { mn = mmunl[i]; mmu_free_node (mn); } xfree (mmunl); mmunl = NULL; xfree (mmubanks); mmubanks = NULL; } #endif static int getmmubank(struct mmudata *snptr, uaecptr p) { snptr->flags = get_long_debug (p); if (snptr->flags == 0xffffffff) return 1; snptr->addr = get_long_debug (p + 4); snptr->len = get_long_debug (p + 8); snptr->remap = get_long_debug (p + 12); snptr->p_addr = p; return 0; } int mmu_init(int mode, uaecptr parm, uaecptr parm2) { uaecptr p, p2, banks; int size; struct mmudata *snptr; struct mmunode *mn; #ifdef JIT static int wasjit; if (currprefs.cachesize) { wasjit = currprefs.cachesize; changed_prefs.cachesize = 0; console_out (_T("MMU: JIT disabled\n")); check_prefs_changed_comp(false); } if (mode == 0) { if (mmu_enabled) { mmu_free (); deinitialize_memwatch (); console_out (_T("MMU: disabled\n")); changed_prefs.cachesize = wasjit; } mmu_logging = 0; return 1; } #endif if (mode == 1) { if (!mmu_enabled) return 0xffffffff; return mmu_struct; } p = parm; mmu_struct = p; if (get_long_debug (p) != 1) { console_out_f (_T("MMU: version mismatch %d <> %d\n"), get_long_debug (p), 1); return 0; } p += 4; mmu_logging = get_long_debug (p) & 1; p += 4; mmu_callback = get_long_debug (p); p += 4; mmu_regs = get_long_debug (p); p += 4; if (mode == 3) { int off; uaecptr addr = get_long_debug (parm2 + 4); if (!mmu_enabled) return 0; off = addr >> MMU_PAGE_SHIFT; mn = mmunl[off]; while (mn) { if (mn->mmubank->p_addr == parm2) { getmmubank(mn->mmubank, parm2); if (mmu_logging) console_out_f (_T("MMU: bank update %08X: %08X - %08X %08X\n"), mn->mmubank->flags, mn->mmubank->addr, mn->mmubank->len + mn->mmubank->addr, mn->mmubank->remap); } mn = mn->next; } return 1; } mmu_slots = 1 << ((currprefs.address_space_24 ? 24 : 32) - MMU_PAGE_SHIFT); mmunl = xcalloc (struct mmunode*, mmu_slots); size = 1; p2 = get_long_debug (p); while (get_long_debug (p2) != 0xffffffff) { p2 += 16; size++; } p = banks = get_long_debug (p); snptr = mmubanks = xmalloc (struct mmudata, size); for (;;) { int off; if (getmmubank(snptr, p)) break; p += 16; off = snptr->addr >> MMU_PAGE_SHIFT; if (mmunl[off] == NULL) { mn = mmunl[off] = xcalloc (struct mmunode, 1); } else { mn = mmunl[off]; while (mn->next) mn = mn->next; mn = mn->next = xcalloc (struct mmunode, 1); } mn->mmubank = snptr; snptr++; } initialize_memwatch (1); console_out_f (_T("MMU: enabled, %d banks, CB=%08X S=%08X BNK=%08X SF=%08X, %d*%d\n"), size - 1, mmu_callback, parm, banks, mmu_regs, mmu_slots, 1 << MMU_PAGE_SHIFT); set_special (SPCFLAG_BRK); return 1; } void debug_parser (const TCHAR *cmd, TCHAR *out, uae_u32 outsize) { TCHAR empty[2] = { 0 }; TCHAR *input = my_strdup (cmd); if (out == NULL && outsize == 0) { setconsolemode (empty, 1); } else if (out != NULL && outsize > 0) { out[0] = 0; setconsolemode (out, outsize); } debug_line (input); setconsolemode (NULL, 0); xfree (input); } /* trainer file is .ini file with following one or more [patch] sections. Each [patch] section describes single trainer option. After [patch] section must come at least one patch descriptor. [patch] name=name enable=true/false event=KEY_F1 ; patch descriptor data=200e46802d400026200cxx02 ; this is comment offset=2 access=write setvalue= type=nop/freeze/set/setonce ; patch descriptor data=11223344556677889900 offset=10 replacedata=4e71 replaceoffset=4 ; next patch section [patch] name: name of the option (appears in GUI in the future) enable: true = automatically enabled at startup. (false=manually activated using key shortcut etc.., will be implemented later) event: inputevents.def event name data: match data, when emulated CPU executes first opcode of this data and following words also match: match is detected. x = anything. offset: word offset from beginning of "data" that points to memory read/write instruction that you want to "patch". Default=0. access: read=read access, write=write access. Default: write if instruction does both memory read and write, read if read-only. setvalue: value to write if type is set or setonce. type=nop: found instruction's write does nothing. This instruction only. Other instruction(s) modifying same memory location are not skipped. type=freeze: found instruction's memory read always returns value in memory. Write does nothing. type=set: found instruction's memory read always returns "setvalue" contents. Write works normally. type=setonce: "setvalue" contents are written to memory when patch is detected. replacedata: data to be copied over data + replaceoffset. x masking is also supported. Memory is modified. replaceoffset: word offset from data. --- Internally it uses debugger memory watch points to modify/freeze memory contents. No memory or code is modified. Only type=setonce and replacedata modifies memory. When CPU emulator current to be executed instruction's matches contents of data[offset], other words of data are also checked. If all words match: instruction's effective address(es) are calculated and matching (read/write) EA is stored. Matching part of patch is now done. Reason for this complexity is to enable single patch to work even if game is relocatable or it uses different memory locations depending on hardware config. If type=nop/freeze/set: debugger memwatch point is set that handles faking of read/write access. If type=setonce: "setvalue" contents gets written to detected effective address. If replacedata is set: copy code. Detection phase may cause increased CPU load, this may get optimized more but it shouldn't be (too) noticeable in basic A500 or A1200 modes. */ #define TRAINER_NOP 0 #define TRAINER_FREEZE 1 #define TRAINER_SET 2 #define TRAINER_SETONCE 3 struct trainerpatch { TCHAR *name; uae_u16 *data; uae_u16 *maskdata; uae_u16 *replacedata; uae_u16 *replacemaskdata; uae_u16 *replacedata_original; uae_u16 first; int length; int offset; int access; int replacelength; int replaceoffset; uaecptr addr; uaecptr varaddr; int varsize; uae_u32 oldval; int patchtype; int setvalue; int *events; int eventcount; int memwatchindex; bool enabledatstart; bool enabled; }; static struct trainerpatch **tpptr; static int tpptrcnt; bool debug_opcode_watch; static int debug_trainer_get_ea(struct trainerpatch *tp, uaecptr pc, uae_u16 opcode, uaecptr *addr) { struct instr *dp = table68k + opcode; uae_u32 sea = 0, dea = 0; uaecptr spc = 0, dpc = 0; uaecptr pc2 = pc + 2; if (dp->suse) { spc = pc2; pc2 = ShowEA(NULL, pc2, opcode, dp->sreg, dp->smode, dp->size, NULL, &sea, NULL, 1); if (sea == spc) spc = 0xffffffff; } if (dp->duse) { dpc = pc2; pc2 = ShowEA(NULL, pc2, opcode, dp->dreg, dp->dmode, dp->size, NULL, &dea, NULL, 1); if (dea == dpc) dpc = 0xffffffff; } if (dea && dpc != 0xffffffff && tp->access == 1) { *addr = dea; return 1 << dp->size; } if (sea && spc != 0xffffffff && tp->access == 0) { *addr = sea; return 1 << dp->size; } if (dea && tp->access > 1) { *addr = dea; return 1 << dp->size; } if (sea && tp->access > 1) { *addr = sea; return 1 << dp->size; } return 0; } static void debug_trainer_enable(struct trainerpatch *tp, bool enable) { if (tp->enabled == enable) return; if (tp->replacedata) { if (enable) { bool first = false; if (!tp->replacedata_original) { tp->replacedata_original = xcalloc(uae_u16, tp->replacelength); first = true; } for (int j = 0; j < tp->replacelength; j++) { uae_u16 v = tp->replacedata[j]; uae_u16 m = tp->replacemaskdata[j]; uaecptr addr = (tp->addr - tp->offset * 2) + j * 2 + tp->replaceoffset * 2; if (m == 0xffff) { x_put_word(addr, v); } else { uae_u16 vo = x_get_word(addr); x_put_word(addr, (vo & ~m) | (v & m)); if (first) tp->replacedata_original[j] = vo; } } } else if (tp->replacedata_original) { for (int j = 0; j < tp->replacelength; j++) { uae_u16 m = tp->replacemaskdata[j]; uaecptr addr = (tp->addr - tp->offset * 2) + j * 2 + tp->replaceoffset * 2; if (m != 0xffff) { x_put_word(addr, tp->replacedata_original[j]); } } } } if (tp->patchtype == TRAINER_SETONCE && tp->varaddr != 0xffffffff) { uae_u32 v = enable ? tp->setvalue : tp->oldval; switch (tp->varsize) { case 1: x_put_byte(tp->varaddr, tp->setvalue); break; case 2: x_put_word(tp->varaddr, tp->setvalue); break; case 4: x_put_long(tp->varaddr, tp->setvalue); break; } } if ((tp->patchtype == TRAINER_NOP || tp->patchtype == TRAINER_FREEZE || tp->patchtype == TRAINER_SET) && tp->varaddr != 0xffffffff) { struct memwatch_node *mwn; if (!memwatch_enabled) initialize_memwatch(0); if (enable) { int i; for (i = MEMWATCH_TOTAL - 1; i >= 0; i--) { mwn = &mwnodes[i]; if (!mwn->size) break; } if (i < 0) { write_log(_T("Trainer out of free memwatchpoints ('%s' %08x\n).\n"), tp->name, tp->addr); } else { mwn->addr = tp->varaddr; mwn->size = tp->varsize; mwn->rwi = 1 | 2; mwn->access_mask = MW_MASK_CPU_D_R | MW_MASK_CPU_D_W; mwn->reg = 0xffffffff; mwn->pc = tp->patchtype == TRAINER_NOP ? tp->addr : 0xffffffff; mwn->frozen = tp->patchtype == TRAINER_FREEZE || tp->patchtype == TRAINER_NOP; mwn->modval_written = 0; mwn->val_enabled = 0; mwn->val_mask = 0xffffffff; mwn->val = 0; mwn->reportonly = false; if (tp->patchtype == TRAINER_SET) { mwn->val_enabled = 1; mwn->val = tp->setvalue; } mwn->nobreak = true; memwatch_setup(); TCHAR buf[256]; memwatch_dump2(buf, sizeof(buf) / sizeof(TCHAR), i); write_log(_T("%s"), buf); } } else { mwn = &mwnodes[tp->memwatchindex]; mwn->size = 0; memwatch_setup(); } } write_log(_T("Trainer '%s' %s (addr=%08x)\n"), tp->name, enable ? _T("enabled") : _T("disabled"), tp->addr); tp->enabled = enable; } void debug_trainer_match(void) { uaecptr pc = m68k_getpc(); uae_u16 opcode = x_get_word(pc); for (int i = 0; i < tpptrcnt; i++) { struct trainerpatch *tp = tpptr[i]; if (tp->first != opcode) continue; if (tp->addr) continue; int j; for (j = 0; j < tp->length; j++) { uae_u16 d = x_get_word(pc + (j - tp->offset) * 2); if ((d & tp->maskdata[j]) != tp->data[j]) break; } if (j < tp->length) continue; tp->first = 0xffff; tp->addr = pc; tp->varsize = -1; tp->varaddr = 0xffffffff; tp->oldval = 0xffffffff; if (tp->access >= 0) { tp->varsize = debug_trainer_get_ea(tp, pc, opcode, &tp->varaddr); switch (tp->varsize) { case 1: tp->oldval = x_get_byte(tp->varaddr); break; case 2: tp->oldval = x_get_word(tp->varaddr); break; case 4: tp->oldval = x_get_long(tp->varaddr); break; } } write_log(_T("Patch %d match at %08x. Addr %08x, size %d, val %08x\n"), i, pc, tp->varaddr, tp->varsize, tp->oldval); if (tp->enabledatstart) debug_trainer_enable(tp, true); // all detected? for (j = 0; j < tpptrcnt; j++) { struct trainerpatch *tp = tpptr[j]; if (!tp->addr) break; } if (j == tpptrcnt) debug_opcode_watch = false; } } static int parsetrainerdata(const TCHAR *data, uae_u16 *outdata, uae_u16 *outmask) { int len = uaetcslen(data); uae_u16 v = 0, vm = 0; int j = 0; for (int i = 0; i < len; ) { TCHAR c1 = _totupper(data[i + 0]); TCHAR c2 = _totupper(data[i + 1]); if (c1 > 0 && c1 <= ' ') { i++; continue; } if (i + 1 >= len) return 0; vm <<= 8; vm |= 0xff; if (c1 == 'X' || c1 == '?') vm &= 0x0f; if (c2 == 'X' || c2 == '?') vm &= 0xf0; if (c1 >= 'A') c1 -= 'A' - 10; else if (c1 >= '0') c1 -= '0'; if (c2 >= 'A') c2 -= 'A' - 10; else if (c2 >= '0') c2 -= '0'; v <<= 8; if (c1 >= 0 && c1 < 16) v |= c1 << 4; if (c2 >= 0 && c2 < 16) v |= c2; if (i & 2) { outdata[j] = v; outmask[j] = vm; j++; } i += 2; } return j; } void debug_init_trainer(const TCHAR *file) { TCHAR section[256]; int cnt = 1; struct ini_data *ini = ini_load(file, false); if (!ini) return; write_log(_T("Loaded '%s'\n"), file); _tcscpy(section, _T("patch")); for (;;) { struct ini_context ictx; ini_initcontext(ini, &ictx); for (;;) { TCHAR *name = NULL; TCHAR *data; ini_getstring_multi(ini, section, _T("name"), &name, &ictx); if (!ini_getstring_multi(ini, section, _T("data"), &data, &ictx)) break; ini_setcurrentasstart(ini, &ictx); ini_setlast(ini, section, _T("data"), &ictx); TCHAR *p = _tcschr(data, ';'); if (p) *p = 0; my_trim(data); struct trainerpatch *tp = xcalloc(struct trainerpatch, 1); int datalen = (uaetcslen(data) + 3) / 4; tp->data = xcalloc(uae_u16, datalen); tp->maskdata = xcalloc(uae_u16, datalen); tp->length = parsetrainerdata(data, tp->data, tp->maskdata); xfree(data); ini_getval_multi(ini, section, _T("offset"), &tp->offset, &ictx); if (tp->offset < 0 || tp->offset >= tp->length) tp->offset = 0; if (ini_getstring_multi(ini, section, _T("replacedata"), &data, &ictx)) { int replacedatalen = (uaetcslen(data) + 3) / 4; tp->replacedata = xcalloc(uae_u16, replacedatalen); tp->replacemaskdata = xcalloc(uae_u16, replacedatalen); tp->replacelength = parsetrainerdata(data, tp->replacedata, tp->replacemaskdata); ini_getval_multi(ini, section, _T("replaceoffset"), &tp->offset, &ictx); if (tp->replaceoffset < 0 || tp->replaceoffset >= tp->length) tp->replaceoffset = 0; tp->access = -1; xfree(data); } tp->access = 2; if (ini_getstring_multi(ini, section, _T("access"), &data, &ictx)) { if (!_tcsicmp(data, _T("read"))) tp->access = 0; else if (!_tcsicmp(data, _T("write"))) tp->access = 1; xfree(data); } if (ini_getstring_multi(ini, section, _T("type"), &data, &ictx)) { if (!_tcsicmp(data, _T("freeze"))) tp->patchtype = TRAINER_FREEZE; else if (!_tcsicmp(data, _T("nop"))) tp->patchtype = TRAINER_NOP; else if (!_tcsicmp(data, _T("set"))) tp->patchtype = TRAINER_SET; else if (!_tcsicmp(data, _T("setonce"))) tp->patchtype = TRAINER_SETONCE; xfree(data); } if (ini_getstring_multi(ini, section, _T("setvalue"), &data, &ictx)) { TCHAR *endptr; if (data[0] == '$') { tp->setvalue = _tcstol(data + 1, &endptr, 16); } else if (_tcslen(data) > 2 && data[0] == '0' && _totupper(data[1]) == 'x') { tp->setvalue = _tcstol(data + 2, &endptr, 16); } else { tp->setvalue = _tcstol(data, &endptr, 10); } xfree(data); } if (ini_getstring(ini, section, _T("enable"), &data)) { if (!_tcsicmp(data, _T("true"))) tp->enabledatstart = true; xfree(data); } if (ini_getstring(ini, section, _T("event"), &data)) { TCHAR *s = data; _tcscat(s, _T(",")); while (*s) { bool end = false; while (*s == ' ') s++; TCHAR *se = _tcschr(s, ','); if (se) { *se = 0; } else { end = true; } TCHAR *se2 = se - 1; while (se2 > s) { if (*se2 != ' ') break; *se2 = 0; se2--; } int evt = inputdevice_geteventid(s); if (evt > 0) { if (tp->events) { tp->events = xrealloc(int, tp->events, tp->eventcount + 1); } else { tp->events = xmalloc(int, 1); } tp->events[tp->eventcount++] = evt; } else { write_log(_T("Unknown event '%s'\n"), s); } if (end) break; s = se + 1; } xfree(data); } tp->first = tp->data[tp->offset]; tp->name = name; if (tpptrcnt) tpptr = xrealloc(struct trainerpatch*, tpptr, tpptrcnt + 1); else tpptr = xcalloc(struct trainerpatch*, tpptrcnt + 1); tpptr[tpptrcnt++] = tp; write_log(_T("%d: '%s' parsed and enabled\n"), cnt, tp->name ? tp->name : _T("")); cnt++; ini_setlastasstart(ini, &ictx); } if (!ini_nextsection(ini, section)) break; } if (tpptrcnt > 0) debug_opcode_watch = true; ini_free(ini); } bool debug_trainer_event(int evt, int state) { for (int i = 0; i < tpptrcnt; i++) { struct trainerpatch *tp = tpptr[i]; for (int j = 0; j < tp->eventcount; j++) { if (tp->events[j] <= 0) continue; if (tp->events[j] == evt) { if (!state) return true; write_log(_T("Trainer %d ('%s') -> %s\n"), i, tp->name, tp->enabled ? _T("off") : _T("on")); debug_trainer_enable(tp, !tp->enabled); return true; } } } return false; } #else /* !WINUAE_FOR_HATARI */ void debug_exception(int nr) { } #endif /* WINUAE_FOR_HATARI */ bool debug_get_prefetch(int idx, uae_u16 *opword) { if (currprefs.cpu_compatible) { if (currprefs.cpu_model < 68020) { if (idx == 0) { *opword = regs.ir; return true; } if (idx == 1) { *opword = regs.irc; return true; } *opword = get_word_debug(m68k_getpc() + idx * 2); return false; } else { if (regs.prefetch020_valid[idx]) { *opword = regs.prefetch020[idx]; return true; } *opword = get_word_debug(m68k_getpc() + idx * 2); return false; } } else { *opword = get_word_debug(m68k_getpc() + idx * 2); return false; } } #ifndef WINUAE_FOR_HATARI #define DEBUGSPRINTF_SIZE 32 static int debugsprintf_cnt; struct dsprintfstack { uae_u32 val; int size; }; static dsprintfstack debugsprintf_stack[DEBUGSPRINTF_SIZE]; static uae_u16 debugsprintf_latch, debugsprintf_latched; static evt_t debugsprintf_cycles, debugsprintf_cycles_set; static uaecptr debugsprintf_va; static int debugsprintf_mode; static void read_bstring(char *out, int max, uae_u32 addr) { out[0] = 0; if (!valid_address(addr, 1)) return; uae_u8 l = get_byte(addr); if (l > max) l = max; addr++; for (int i = 0; i < l && i < max; i++) { uae_u8 c = 0; if (valid_address(addr, 1)) { c = get_byte(addr); } if (c == 0) { c = '.'; } addr++; out[i] = c; out[i + 1] = 0; } } static void read_string(char *out, int max, uae_u32 addr) { out[0] = 0; for (int i = 0; i < max; i++) { uae_u8 c = 0; if (valid_address(addr, 1)) { c = get_byte(addr); } addr++; out[i] = c; out[i + 1] = 0; if (!c) break; } } static void parse_custom(char *out, int buffersize, char *format, char *p, char c) { bool gotv = false; bool gots = false; out[0] = 0; uae_u32 v = 0; char s[256]; if (!strcmp(p, "CYCLES")) { if (debugsprintf_cycles_set) { v = (uae_u32)((get_cycles() - debugsprintf_cycles) / CYCLE_UNIT); } else { v = 0xffffffff; } gotv = true; } if (gotv) { if (c == 'x' || c == 'X' || c == 'd' || c == 'i' || c == 'u' || c == 'o') { char *fs = format + strlen(format); *fs++ = c; *fs = 0; snprintf(out, buffersize, format, v); } else { strcpy(s, "****"); gots = true; } } if (gots) { char *fs = format + strlen(format); *fs++ = 's'; *fs = 0; snprintf(out, buffersize, format, s); } } static uae_u32 get_value(struct dsprintfstack **stackp, uae_u32 *sizep, uaecptr *ptrp, uae_u32 size) { if (debugsprintf_mode) { uae_u32 v; uaecptr ptr = *ptrp; if (size == sz_long) { v = get_long_debug(ptr); ptr += 4; } else if (size == sz_word) { v = get_word_debug(ptr); ptr += 2; } else { v = get_byte_debug(ptr); ptr++; } *ptrp = ptr; *sizep = size; return v; } else { struct dsprintfstack *stack = *stackp; uae_u32 v = stack->val; if (stack->size == 0) v &= 0xff; else if (stack->size == 1) v &= 0xffff; if (size == 1) v &= 0xffff; *sizep = size; stack++; *stackp = stack; return v; } } static void debug_sprintf_do(uae_u32 s) { int cnt = 0; char format[MAX_DPATH]; char out[MAX_DPATH]; read_string(format, MAX_DPATH - 1, s); char *p = format; char *d = out; bool gotm = false; bool l = false; uaecptr ptr = debugsprintf_va; struct dsprintfstack *stack = debugsprintf_stack; char fstr[100], *fstrp; int buffersize = MAX_DPATH - 1; fstrp = fstr; *d = 0; for (;;) { char c = *p++; if (c == 0) break; if (gotm) { bool got = false; buffersize = MAX_DPATH - uaestrlen(out); if (buffersize <= 1) break; if (c == '%') { *d++ = '%'; gotm = false; } else if (c == 'l') { l = true; } else if (c == 'c') { uae_u32 size; uae_u32 val = get_value(&stack, &size, &ptr, l ? sz_long : sz_word); *fstrp++ = c; *fstrp = 0; snprintf(d, buffersize, fstr, val); got = true; } else if (c == 'b') { uae_u32 size; uae_u32 val = get_value(&stack, &size, &ptr, sz_long); char tmp[MAX_DPATH]; read_bstring(tmp, MAX_DPATH - 1, val); *fstrp++ = 's'; *fstrp = 0; snprintf(d, buffersize, fstr, tmp); got = true; } else if (c == 's') { uae_u32 size; uae_u32 val = get_value(&stack, &size, &ptr, sz_long); char tmp[MAX_DPATH]; read_string(tmp, MAX_DPATH - 1, val); *fstrp++ = c; *fstrp = 0; snprintf(d, buffersize, fstr, tmp); got = true; } else if (c == 'p') { uae_u32 size; uae_u32 val = get_value(&stack, &size, &ptr, sz_long); snprintf(d, buffersize, "$%08x", val); got = true; } else if (c == 'x' || c == 'X' || c == 'd' || c == 'i' || c == 'u' || c == 'o') { uae_u32 size; uae_u32 val = get_value(&stack, &size, &ptr, l ? sz_long : sz_word); if (c == 'd' || c == 'i') { if (size == sz_word && (val & 0x8000)) { val = (uae_s32)(uae_s16)val; } } *fstrp++ = c; *fstrp = 0; snprintf(d, buffersize, fstr, val); got = true; } else if (c == '[') { char *next = strchr(p, ']'); if (next && next[1]) { char customout[MAX_DPATH]; customout[0] = 0; *next = 0; parse_custom(d, buffersize, fstr, p, next[1]); p = next + 2; got = true; } else { gotm = false; } } else { if (fstrp - fstr < sizeof(fstr) - 1) { *fstrp++ = c; *fstrp = 0; } } if (got) { d += strlen(d); gotm = false; } } else if (c == '%') { l = false; fstrp = fstr; *fstrp++ = c; *fstrp = 0; gotm = true; } else { *d++ = c; } *d = 0; } write_log("%s", out); } bool debug_sprintf(uaecptr addr, uae_u32 val, int size) { if (!currprefs.debug_mem) return false; uae_u32 v = val; if (size == sz_word && currprefs.cpu_model < 68020) { v &= 0xffff; if (!(addr & 2)) { debugsprintf_latch = v; debugsprintf_latched = 1; } else if (debugsprintf_latched) { v |= debugsprintf_latch << 16; size = sz_long; if (!(addr & 4) && debugsprintf_cnt > 0) { debugsprintf_cnt--; } } } if (size != sz_word) { debugsprintf_latched = 0; } if ((addr & (8 | 4)) == 4) { if (size != sz_long) return true; debug_sprintf_do(v); debugsprintf_cnt = 0; debugsprintf_latched = 0; debugsprintf_cycles = get_cycles(); debugsprintf_cycles_set = 1; } else if ((addr & (8 | 4)) == 8) { if (size != sz_long) return true; debugsprintf_va = val; debugsprintf_mode = 1; } else { if (debugsprintf_cnt < DEBUGSPRINTF_SIZE) { debugsprintf_stack[debugsprintf_cnt].val = v; debugsprintf_stack[debugsprintf_cnt].size = size; debugsprintf_cnt++; } debugsprintf_mode = 0; } return true; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/debug.h000066400000000000000000000252011504763705000233560ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator * * Debugger * * (c) 1995 Bernd Schmidt * */ #ifndef UAE_DEBUG_H #define UAE_DEBUG_H #include "uae/types.h" #ifndef D #define D #endif #ifndef bug #define bug write_log #endif #ifdef DEBUGGER #define MAX_HIST 500 #define MAX_LINEWIDTH 10000 extern int debugging; extern int memwatch_enabled; extern int exception_debugging; extern int debug_copper; extern int debug_dma, debug_heatmap; extern uae_u32 debug_sprite_mask; extern int debug_bpl_mask, debug_bpl_mask_one; extern int debugger_active; extern int debug_illegal; extern uae_u64 debug_illegal_mask; extern int debugger_used; extern void debug (void); extern void debugger_change (int mode); extern void activate_debugger(void); extern void activate_debugger_new(void); extern void activate_debugger_new_pc(uaecptr pc, int len); extern void deactivate_debugger (void); extern const TCHAR *debuginfo (int); extern void record_copper (uaecptr addr, uaecptr nextaddr, uae_u16 word1, uae_u16 word2, int hpos, int vpos); extern void record_copper_blitwait (uaecptr addr, int hpos, int vpos); extern void record_copper_reset (void); extern int mmu_init (int, uaecptr,uaecptr); extern void mmu_do_hit (void); extern void dump_aga_custom (void); extern void memory_map_dump (void); extern void debug_help (void); extern uaecptr dumpmem2 (uaecptr addr, TCHAR *out, int osize); extern void update_debug_info (void); extern int instruction_breakpoint (TCHAR **c); extern int debug_bankchange (int); extern void log_dma_record (void); extern void debug_parser (const TCHAR *cmd, TCHAR *out, uae_u32 outsize); extern void mmu_disasm (uaecptr pc, int lines); extern int debug_read_memory_16 (uaecptr addr); extern int debug_peek_memory_16 (uaecptr addr); extern int debug_read_memory_8 (uaecptr addr); extern int debug_write_memory_16 (uaecptr addr, uae_u16 v); extern int debug_write_memory_8 (uaecptr addr, uae_u8 v); extern bool debug_enforcer(void); extern int debug_safe_addr(uaecptr addr, int size); extern void debug_invalid_reg(int reg, int size, uae_u16 val); extern void debug_check_reg(uae_u32 addr, int write, uae_u16 v); extern int memwatch_access_validator; #define DEBUG_SPRINTF_ADDRESS 0xbfff00 extern bool debug_sprintf(uaecptr, uae_u32, int); extern bool debug_get_prefetch(int idx, uae_u16 *opword); extern void debug_hsync(void); extern void debug_exception(int); extern void debug_init_trainer(const TCHAR*); extern void debug_trainer_match(void); extern bool debug_opcode_watch; extern bool debug_trainer_event(int evt, int state); extern void debug_smc_clear(uaecptr addr, int size); #define BREAKPOINT_TOTAL 20 #define BREAKPOINT_REG_Dx 0 #define BREAKPOINT_REG_Ax 8 #define BREAKPOINT_REG_PC 16 #define BREAKPOINT_REG_USP 17 #define BREAKPOINT_REG_MSP 18 #define BREAKPOINT_REG_ISP 19 #define BREAKPOINT_REG_VBR 20 #define BREAKPOINT_REG_SR 21 #define BREAKPOINT_REG_CCR 22 #define BREAKPOINT_REG_CACR 23 #define BREAKPOINT_REG_CAAR 24 #define BREAKPOINT_REG_SFC 25 #define BREAKPOINT_REG_DFC 26 #define BREAKPOINT_REG_TC 27 #define BREAKPOINT_REG_ITT0 28 #define BREAKPOINT_REG_ITT1 29 #define BREAKPOINT_REG_DTT0 30 #define BREAKPOINT_REG_DTT1 31 #define BREAKPOINT_REG_BUSC 32 #define BREAKPOINT_REG_PCR 33 #define BREAKPOINT_REG_FPIAR 34 #define BREAKPOINT_REG_FPCR 35 #define BREAKPOINT_REG_FPSR 36 #define BREAKPOINT_REG_END 37 #define BREAKPOINT_CMP_EQUAL 0 #define BREAKPOINT_CMP_NEQUAL 1 #define BREAKPOINT_CMP_SMALLER_EQUAL 2 #define BREAKPOINT_CMP_LARGER_EQUAL 3 #define BREAKPOINT_CMP_SMALLER 2 #define BREAKPOINT_CMP_LARGER 3 #define BREAKPOINT_CMP_RANGE 4 #define BREAKPOINT_CMP_NRANGE 5 struct breakpoint_node { uae_u32 value1; uae_u32 value2; uae_u32 mask; int type; int oper; bool opersigned; int enabled; int cnt; int chain; }; extern struct breakpoint_node bpnodes[BREAKPOINT_TOTAL]; #define MW_MASK_CPU_I 0x00000001 #define MW_MASK_CPU_D_R 0x00000002 #define MW_MASK_CPU_D_W 0x00000004 #define MW_MASK_BLITTER_A 0x00000008 #define MW_MASK_BLITTER_B 0x00000010 #define MW_MASK_BLITTER_C 0x00000020 #define MW_MASK_BLITTER_D_N 0x00000040 #define MW_MASK_BLITTER_D_L 0x00000080 #define MW_MASK_BLITTER_D_F 0x00000100 #define MW_MASK_COPPER 0x00000200 #define MW_MASK_DISK 0x00000400 #define MW_MASK_AUDIO_0 0x00000800 #define MW_MASK_AUDIO_1 0x00001000 #define MW_MASK_AUDIO_2 0x00002000 #define MW_MASK_AUDIO_3 0x00004000 #define MW_MASK_BPL_0 0x00008000 #define MW_MASK_BPL_1 0x00010000 #define MW_MASK_BPL_2 0x00020000 #define MW_MASK_BPL_3 0x00040000 #define MW_MASK_BPL_4 0x00080000 #define MW_MASK_BPL_5 0x00100000 #define MW_MASK_BPL_6 0x00200000 #define MW_MASK_BPL_7 0x00400000 #define MW_MASK_SPR_0 0x00800000 #define MW_MASK_SPR_1 0x01000000 #define MW_MASK_SPR_2 0x02000000 #define MW_MASK_SPR_3 0x04000000 #define MW_MASK_SPR_4 0x08000000 #define MW_MASK_SPR_5 0x10000000 #define MW_MASK_SPR_6 0x20000000 #define MW_MASK_SPR_7 0x40000000 #define MW_MASK_NONE 0x80000000 #define MW_MASK_ALL (MW_MASK_NONE - 1) #define MEMWATCH_TOTAL 20 struct memwatch_node { uaecptr addr; int size; int rwi; uae_u32 val, val_mask, access_mask; int val_size, val_enabled; int mustchange; uae_u32 modval; int modval_written; int frozen; uae_u32 reg; uaecptr pc; bool nobreak; bool reportonly; int bus_error; }; extern struct memwatch_node mwnodes[MEMWATCH_TOTAL]; extern void memwatch_dump2 (TCHAR *buf, int bufsize, int num); void debug_getpeekdma_chipram(uaecptr addr, uae_u32 mask, int reg); void debug_getpeekdma_value(uae_u32); void debug_getpeekdma_value_long(uae_u32, int); uae_u32 debug_putpeekdma_chipram(uaecptr addr, uae_u32 v, uae_u32 mask, int reg); uae_u32 debug_putpeekdma_chipset(uaecptr addr, uae_u32 v, uae_u32 mask, int reg); void debug_lgetpeek(uaecptr addr, uae_u32 v); void debug_wgetpeek(uaecptr addr, uae_u32 v); void debug_bgetpeek(uaecptr addr, uae_u32 v); void debug_bputpeek(uaecptr addr, uae_u32 v); void debug_wputpeek(uaecptr addr, uae_u32 v); void debug_lputpeek(uaecptr addr, uae_u32 v); uae_u32 get_byte_debug (uaecptr addr); uae_u32 get_word_debug (uaecptr addr); uae_u32 get_long_debug (uaecptr addr); uae_u32 get_ilong_debug (uaecptr addr); uae_u32 get_iword_debug (uaecptr addr); uae_u32 get_byte_cache_debug(uaecptr addr, bool *cached); uae_u32 get_word_cache_debug(uaecptr addr, bool *cached); uae_u32 get_long_cache_debug(uaecptr addr, bool *cached); enum debugtest_item { DEBUGTEST_BLITTER, DEBUGTEST_KEYBOARD, DEBUGTEST_FLOPPY, DEBUGTEST_MAX }; void debugtest (enum debugtest_item, const TCHAR *, ...); struct peekdma { int type; uaecptr addr; uae_u32 v; uae_u32 mask; int reg; int ptrreg; }; extern struct peekdma peekdma_data; struct dma_rec { int hpos, vpos[2]; int frame; uae_u32 tick; int dhpos[2]; uae_u16 reg; uae_u64 dat; uae_u16 size; uae_u32 addr; uae_u32 evt; uae_u32 agnus_evt, agnus_evt_changed; uae_u32 denise_evt[2], denise_evt_changed[2]; uae_u32 evtdata; bool evtdataset; uae_s16 type; uae_u16 extra; uae_s8 intlev, ipl, ipl2; uae_u16 cf_reg, cf_dat, cf_addr; int ciareg; int ciamask; bool ciarw; int ciaphase; uae_u16 ciavalue; bool end; bool cs, hs, vs; }; extern struct dma_rec *last_dma_rec; #define DENISE_EVENT_VB 1 #define DENISE_EVENT_HB 2 #define DENISE_EVENT_BURST 4 #define DENISE_EVENT_HDIW 8 #define DENISE_EVENT_BPL1DAT_HDIW 16 #define DENISE_EVENT_COPIED 0x40000000 #define DENISE_EVENT_UNKNOWN 0x80000000 #define AGNUS_EVENT_HW_HS 1 #define AGNUS_EVENT_HW_VS 2 #define AGNUS_EVENT_HW_CS 4 #define AGNUS_EVENT_HW_VB 8 #define AGNUS_EVENT_PRG_HS 16 #define AGNUS_EVENT_PRG_VS 32 #define AGNUS_EVENT_PRG_CS 64 #define AGNUS_EVENT_PRG_VB 128 #define AGNUS_EVENT_VDIW 256 #define AGNUS_EVENT_BPRUN 512 #define AGNUS_EVENT_VE 1024 #define AGNUS_EVENT_P_VE 2048 #define AGNUS_EVENT_HB 4096 #define AGNUS_EVENT_BPRUN2 8192 #define DMA_EVENT_BLITIRQ 1 #define DMA_EVENT_BLITFINALD 2 #define DMA_EVENT_BLITSTARTFINISH 4 #define DMA_EVENT_BPLFETCHUPDATE 8 #define DMA_EVENT_COPPERWAKE 16 #define DMA_EVENT_CPUIRQ 32 #define DMA_EVENT_INTREQ 64 #define DMA_EVENT_COPPERWANTED 128 #define DMA_EVENT_COPPERWAKE2 256 #define DMA_EVENT_CPUBLITTERSTEAL 512 #define DMA_EVENT_CPUBLITTERSTOLEN 1024 #define DMA_EVENT_COPPERSKIP 2048 #define DMA_EVENT_DDFSTRT 4096 #define DMA_EVENT_DDFSTOP 8192 #define DMA_EVENT_DDFSTOP2 16384 #define DMA_EVENT_SPECIAL 32768 #define DMA_EVENT_IPL 0x00010000 #define DMA_EVENT_IPLSAMPLE 0x00020000 #define DMA_EVENT_COPPERUSE 0x00040000 #define DMA_EVENT_MODADD 0x00080000 #define DMA_EVENT_LOF 0x00100000 #define DMA_EVENT_LOL 0x00200000 #define DMA_EVENT_CIAA_IRQ 0x08000000 #define DMA_EVENT_CIAB_IRQ 0x10000000 #define DMA_EVENT_CPUSTOP 0x20000000 #define DMA_EVENT_CPUSTOPIPL 0x40000000 #define DMA_EVENT_CPUINS 0x80000000 #define DMARECORD_REFRESH 1 #define DMARECORD_CPU 2 #define DMARECORD_COPPER 3 #define DMARECORD_AUDIO 4 #define DMARECORD_BLITTER 5 #define DMARECORD_BITPLANE 6 #define DMARECORD_SPRITE 7 #define DMARECORD_DISK 8 #define DMARECORD_UHRESBPL 9 #define DMARECORD_UHRESSPR 10 #define DMARECORD_CONFLICT 11 #define DMARECORD_MAX 12 extern void record_dma_read(uae_u16 reg, uae_u32 addr, int type, int extra); extern void record_dma_write(uae_u16 reg, uae_u32 v, uae_u32 addr, int type, int extra); extern void record_dma_read_value(uae_u32 v); extern void record_dma_read_value_pos(uae_u32 v); extern void record_dma_read_value_wide(uae_u64 v, bool quad); extern void record_dma_replace( int type, int extra); extern void record_dma_reset(int); extern void record_dma_event_agnus(uae_u32 evt, bool onoff); extern void record_dma_event_denise(struct dma_rec *rd, int h, uae_u32 evt, bool onoff); extern void record_dma_event(uae_u32 evt); extern void record_dma_event_data(uae_u32 evt, uae_u32 data); extern void record_dma_clear(void); extern bool record_dma_check(void); extern void record_cia_access(int r, int mask, uae_u16 value, bool rw, int phase); extern void record_dma_ipl(void); extern void record_dma_ipl_sample(void); extern void debug_mark_refreshed(uaecptr); extern void debug_draw(uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors); extern struct dma_rec *record_dma_next_cycle(int hpos, int vpos, int vvpos); #define TRACE_SKIP_INS 1 #define TRACE_MATCH_PC 2 #define TRACE_MATCH_INS 3 #define TRACE_RANGE_PC 4 #define TRACE_SKIP_LINE 5 #define TRACE_RAM_PC 6 #define TRACE_CHECKONLY 10 #else STATIC_INLINE void activate_debugger (void) { }; #ifdef WINUAE_FOR_HATARI #define MAX_LINEWIDTH 150 uae_u32 get_byte_debug (uaecptr addr); uae_u32 get_word_debug (uaecptr addr); uae_u32 get_long_debug (uaecptr addr); uae_u32 get_ilong_debug (uaecptr addr); uae_u32 get_iword_debug (uaecptr addr); extern void mmu_do_hit (void); #endif #endif /* DEBUGGER */ #endif /* UAE_DEBUG_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/debugmem.h000066400000000000000000000052641504763705000240640ustar00rootroot00000000000000#ifdef WINUAE_FOR_HATARI /* [NP] We don't use debugmem_xxx functions with Hatari, so we just define some dummy */ /* functions to replace those Amiga specific cases */ /* We use some #define / static inline to replace with 'empty' equivalent at compile time */ /* and to avoid useless overhead at runtime */ #include "symbols.h" // use functions from here instead */ #define debugmem_trace 0 static inline int debugmem_get_segment(uaecptr addr, bool *exact, bool *ext, TCHAR *out, TCHAR *name) { return 0; } static inline int debugmem_get_symbol(uaecptr addr, TCHAR *out, int maxsize) { return 0; } static inline int debugmem_get_sourceline(uaecptr addr, TCHAR *out, int maxsize) { return -1; } static inline bool debugger_get_library_symbol(uaecptr base, uaecptr addr, TCHAR *out) { return false; } static inline void debugmem_flushcache(uaecptr addr, int size) {} static inline void branch_stack_pop_rte(uaecptr oldpc) {} static inline void branch_stack_pop_rts(uaecptr oldpc) {} static inline void branch_stack_push(uaecptr oldpc, uaecptr newpc) {} #else /* ! WINUAE_FOR_HATARI */ uaecptr debugmem_reloc(uaecptr exeaddress, uae_u32 len, uaecptr dbgaddress, uae_u32 dbglen, uaecptr task, uae_u32 *stack); void debugmem_init(bool); uaecptr debugmem_allocmem(int mode, uae_u32 size, uae_u32 flags, uae_u32 caller); uae_u32 debugmem_freemem(int mode, uaecptr addr, uae_u32 size, uae_u32 caller); void debugmem_trap(uaecptr addr); void debugmem_addsegs(TrapContext *ctx, uaecptr seg, uaecptr name, uae_u32 lock, bool residentonly); void debugmem_remsegs(uaecptr seg); uae_u32 debugmem_exit(void); bool debugmem_break(int); bool debugmem_inhibit_break(int mode); void debugmem_disable(void); void debugmem_enable(void); int debugmem_get_segment(uaecptr addr, bool *exact, bool *ext, TCHAR *out, TCHAR *name); int debugmem_get_symbol(uaecptr addr, TCHAR *out, int maxsize); bool debugmem_get_symbol_value(const TCHAR *name, uae_u32 *valp); bool debugmem_list_segment(int mode, uaecptr addr); int debugmem_get_sourceline(uaecptr addr, TCHAR *out, int maxsize); bool debugmem_get_range(uaecptr*, uaecptr*); bool debugmem_isactive(void); bool debugger_load_libraries(void); void debugger_scan_libraries(void); bool debugger_get_library_symbol(uaecptr base, uaecptr addr, TCHAR *out); bool debugmem_list_stackframe(bool super); bool debugmem_break_stack_pop(void); bool debugmem_break_stack_push(void); bool debugmem_enable_stackframe(bool enable); bool debugmem_illg(uae_u16); void debugmem_flushcache(uaecptr, int); extern uae_u32 debugmem_chiplimit; extern uae_u32 debugmem_chiphit(uaecptr addr, uae_u32 v, int size); extern bool debugmem_extinvalidmem(uaecptr addr, uae_u32 v, int size); #endif /* WINUAE_FOR_HATARI */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/disasm.c000066400000000000000000002021341504763705000235450ustar00rootroot00000000000000 #include "sysconfig.h" #include "sysdeps.h" #include "main.h" #include "options_cpu.h" #include "debug.h" #include "memory.h" #include "newcpu.h" #include "fpp.h" #include "debugmem.h" #include "disasm.h" #include "profile.h" int disasm_flags = DISASM_FLAG_LC_MNEMO | DISASM_FLAG_LC_REG | DISASM_FLAG_LC_SIZE | DISASM_FLAG_LC_HEX | DISASM_FLAG_CC | DISASM_FLAG_EA | DISASM_FLAG_VAL | DISASM_FLAG_WORDS | DISASM_FLAG_ABSSHORTLONG; int disasm_min_words = 5; int disasm_max_words = 16; TCHAR disasm_hexprefix[3] = { '$', 0 }; #define LINE_BUF_SIZE 80 static TCHAR disasm_areg, disasm_dreg, disasm_byte, disasm_word, disasm_long; static TCHAR disasm_pcreg[3], disasm_fpreg[3]; static bool absshort_long = false; void disasm_init(void) { _tcscpy(disasm_pcreg, _T("PC")); _tcscpy(disasm_fpreg, _T("FP")); if (disasm_flags & DISASM_FLAG_LC_REG) { _tcscpy(disasm_pcreg, _T("pc")); _tcscpy(disasm_fpreg, _T("fp")); } disasm_areg = (disasm_flags & DISASM_FLAG_LC_REG) ? 'a' : 'A'; disasm_dreg = (disasm_flags & DISASM_FLAG_LC_REG) ? 'd' : 'D'; disasm_byte = (disasm_flags & DISASM_FLAG_LC_REG) ? 'b' : 'B'; disasm_word = (disasm_flags & DISASM_FLAG_LC_REG) ? 'w' : 'W'; disasm_long = (disasm_flags & DISASM_FLAG_LC_REG) ? 'l' : 'L'; absshort_long = (disasm_flags & DISASM_FLAG_ABSSHORTLONG) != 0; } static void disasm_lc_mnemo(TCHAR *s) { if (!(disasm_flags & DISASM_FLAG_LC_MNEMO) && !(disasm_flags & DISASM_FLAG_LC_SIZE)) { return; } if ((disasm_flags & DISASM_FLAG_LC_MNEMO) && (disasm_flags & DISASM_FLAG_LC_SIZE)) { to_lower(s, -1); return; } TCHAR *s2 = _tcschr(s, '.'); if (s2) { if (disasm_flags & DISASM_FLAG_LC_SIZE) { to_lower(s2, -1); } if (disasm_flags & DISASM_FLAG_LC_MNEMO) { s2[0] = 0; to_lower(s, -1); s2[0] = '.'; } } else { if (disasm_flags & DISASM_FLAG_LC_MNEMO) { to_lower(s, -1); return; } } } static const TCHAR *disasm_lc_size(const TCHAR *s) { static TCHAR tmp[32]; if (disasm_flags & DISASM_FLAG_LC_SIZE) { _tcscpy(tmp, s); to_lower(tmp, -1); return tmp; } return s; } static const TCHAR *disasm_lc_reg(const TCHAR *s) { static TCHAR tmp[32]; if (disasm_flags & DISASM_FLAG_LC_REG) { _tcscpy(tmp, s); to_lower(tmp, -1); return tmp; } return s; } static const TCHAR *disasm_lc_hex2(const TCHAR *s, bool noprefix) { static TCHAR tmp[32]; bool copied = false; if (disasm_flags & DISASM_FLAG_LC_HEX) { const TCHAR *s2 = _tcschr(s, 'X'); if (s2) { _tcscpy(tmp, s); copied = true; tmp[s2 - s] = 'x'; for (;;) { s2 = _tcschr(tmp, 'X'); if (!s2) { break; } tmp[s2 - tmp] = 'x'; } } } if (!noprefix) { if (disasm_hexprefix[0] != '$' || disasm_hexprefix[1] != 0 || s[0] != '$') { if (!copied) { _tcscpy(tmp, s); copied = true; } const TCHAR *s2 = _tcschr(tmp, '%'); if (s2) { int len = uaetcslen(disasm_hexprefix); if (s2 > tmp && s2[-1] == '$') { len--; s2--; } if (len < 0) { memmove(tmp + (s2 - tmp), tmp + (s2 - tmp) - len, (uaetcslen(tmp + (s2 - tmp) - len) + 1) * sizeof(TCHAR)); } else { if (len > 0) { memmove(tmp + (s2 - tmp) + len, s2, (uaetcslen(s2) + 1) * sizeof(TCHAR)); } memcpy(tmp + (s2 - tmp), disasm_hexprefix, uaetcslen(disasm_hexprefix) * sizeof(TCHAR)); } } return tmp; } } if (copied) { return tmp; } return s; } static const TCHAR *disasm_lc_hex(const TCHAR *s) { return disasm_lc_hex2(s, false); } static const TCHAR *disasm_lc_nhex(const TCHAR *s) { return disasm_lc_hex2(s, true); } struct cpum2c m2cregs[] = { { 0, 31, _T("SFC") }, { 1, 31, _T("DFC") }, { 2, 30, _T("CACR") }, { 3, 24, _T("TC") }, { 4, 24, _T("ITT0") }, { 5, 24, _T("ITT1") }, { 6, 24, _T("DTT0") }, { 7, 24, _T("DTT1") }, { 8, 16, _T("BUSC") }, { 0x800, 31, _T("USP") }, { 0x801, 31, _T("VBR") }, { 0x802, 6, _T("CAAR") }, { 0x803, 15, _T("MSP") }, { 0x804, 31, _T("ISP") }, { 0x805, 8, _T("MMUS") }, { 0x806, 12, _T("URP") }, { 0x807, 12, _T("SRP") }, { 0x808, 16, _T("PCR") }, { -1, 0, NULL } }; const TCHAR *fpsizes[] = { _T("L"), _T("S"), _T("X"), _T("P"), _T("W"), _T("D"), _T("B"), _T("P") }; static const wordsizes fpsizeconv[] = { sz_long, sz_single, sz_extended, sz_packed, sz_word, sz_double, sz_byte, sz_packed }; static const int datasizes[] = { 1, 2, 4, 4, 8, 12, 12 }; static void showea_val(TCHAR *buffer, uae_u16 opcode, uaecptr addr, int size) { struct mnemolookup *lookup; struct instr *table = &table68k[opcode]; #ifndef WINUAE_FOR_HATARI #ifndef CPU_TESTER #if UAE if (addr >= 0xe90000 && addr < 0xf00000) goto skip; if (addr >= 0xdff000 && addr < 0xe00000) goto skip; #endif #endif #endif if (!(disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL))) { goto skip; } for (lookup = lookuptab; lookup->mnemo != table->mnemo; lookup++) ; if (!(lookup->flags & 1)) goto skip; buffer += _tcslen(buffer); if (debug_safe_addr(addr, datasizes[size])) { bool cached = false; switch (size) { case sz_byte: { uae_u8 v = get_byte_cache_debug(addr, &cached); uae_u8 v2 = v; if (cached) v2 = get_byte_debug(addr); if (v != v2) { _stprintf(buffer, _T(" [%02x:%02x]"), v, v2); } else { _stprintf(buffer, _T(" [%s%02x]"), cached ? _T("*") : _T(""), v); } } break; case sz_word: { uae_u16 v = get_word_cache_debug(addr, &cached); uae_u16 v2 = v; if (cached) v2 = get_word_debug(addr); if (v != v2) { _stprintf(buffer, _T(" [%04x:%04x]"), v, v2); } else { _stprintf(buffer, _T(" [%s%04x]"), cached ? _T("*") : _T(""), v); } } break; case sz_long: { uae_u32 v = get_long_cache_debug(addr, &cached); uae_u32 v2 = v; if (cached) v2 = get_long_debug(addr); if (v != v2) { _stprintf(buffer, _T(" [%08x:%08x]"), v, v2); } else { _stprintf(buffer, _T(" [%s%08x]"), cached ? _T("*") : _T(""), v); } } break; case sz_single: { fpdata fp; fpp_to_single(&fp, get_long_debug(addr)); _stprintf(buffer, _T("[%s]"), fpp_print(&fp, 0)); } break; case sz_double: { fpdata fp; fpp_to_double(&fp, get_long_debug(addr), get_long_debug(addr + 4)); _stprintf(buffer, _T("[%s]"), fpp_print(&fp, 0)); } break; case sz_extended: { fpdata fp; fpp_to_exten(&fp, get_long_debug(addr), get_long_debug(addr + 4), get_long_debug(addr + 8)); _stprintf(buffer, _T("[%s]"), fpp_print(&fp, 0)); break; } case sz_packed: _stprintf(buffer, _T("[%08x%08x%08x]"), get_long_debug(addr), get_long_debug(addr + 4), get_long_debug(addr + 8)); break; } } skip: for (int i = 0; i < size; i++) { #ifndef WINUAE_FOR_HATARI TCHAR name[256]; if (debugmem_get_symbol(addr + i, name, sizeof(name) / sizeof(TCHAR))) { _stprintf(buffer + _tcslen(buffer), _T(" %s"), name); #else const char *name; if ((name = Symbols_GetByCpuAddress(addr + i, SYMTYPE_TEXT))) { int len = _tcslen(buffer); snprintf(buffer + len, LINE_BUF_SIZE - len, _T(" %s"), name); #endif } } } uaecptr ShowEA_disp(uaecptr *pcp, uaecptr base, TCHAR *buffer, const TCHAR *name, bool pcrel) { uaecptr addr; uae_u16 dp; int r; uae_u32 dispreg; uaecptr pc = *pcp; TCHAR mult[20]; TCHAR *p = NULL; dp = get_iword_debug(pc); pc += 2; r = (dp & 0x7000) >> 12; // REGISTER dispreg = dp & 0x8000 ? m68k_areg(regs, r) : m68k_dreg(regs, r); if (!(dp & 0x800)) { // W/L dispreg = (uae_s32)(uae_s16)(dispreg); } int m = 1 << ((dp >> 9) & 3); mult[0] = 0; if (currprefs.cpu_model >= 68020) { dispreg <<= (dp >> 9) & 3; // SCALE } if (buffer) buffer[0] = 0; if ((dp & 0x100) && currprefs.cpu_model >= 68020) { TCHAR dr[20]; uae_s32 outer = 0, disp = 0; // Full format extension (68020+) if (m > 1 && buffer) { _stprintf(mult, _T("*%d"), m); } if (dp & 0x80) { // BS (base register suppress) base = 0; if (buffer) name = NULL; } if (buffer) _stprintf(dr, _T("%c%d.%c"), dp & 0x8000 ? disasm_areg : disasm_dreg, (int)r, dp & 0x800 ? disasm_long : disasm_word); if (dp & 0x40) { // IS (index suppress) dispreg = 0; dr[0] = 0; } if (buffer) { _tcscpy(buffer, _T("(")); p = buffer + _tcslen(buffer); if (dp & 3) { // indirect _stprintf(p, _T("[")); p += _tcslen(p); } else { // (an,dn,word/long) if (name) { _stprintf(p, _T("%s,"), name); p += _tcslen(p); } if (dr[0]) { _stprintf(p, _T("%s%s,"), dr, mult); p += _tcslen(p); } } } if ((dp & 0x30) == 0x20) { // BD SIZE = 2 (WORD) disp = (uae_s32)(uae_s16)get_iword_debug(pc); if (buffer) { _stprintf(p, disasm_lc_hex(_T("$%04X,")), (uae_s16)disp); p += _tcslen(p); } pc += 2; base += disp; } else if ((dp & 0x30) == 0x30) { // BD SIZE = 3 (LONG) disp = get_ilong_debug(pc); if (buffer) { _stprintf(p, disasm_lc_hex(_T("$%08X,")), disp); p += _tcslen(p); } pc += 4; base += disp; } if ((dp & 3) && buffer) { if (name) { _stprintf(p, _T("%s,"), name); p += _tcslen(p); } if (!(dp & 0x04)) { if (dr[0]) { _stprintf(p, _T("%s%s,"), dr, mult); p += _tcslen(p); } } if (p[-1] == ',') p--; _stprintf(p, _T("],")); p += _tcslen(p); if ((dp & 0x04)) { if (dr[0]) { _stprintf(p, _T("%s%s,"), dr, mult); p += _tcslen(p); } } } if ((dp & 0x03) == 0x02) { outer = (uae_s32)(uae_s16)get_iword_debug(pc); if (buffer) { _stprintf(p, disasm_lc_hex(_T("$%04X,")), (uae_s16)outer); p += _tcslen(p); } pc += 2; } else if ((dp & 0x03) == 0x03) { outer = get_ilong_debug(pc); if (buffer) { _stprintf(p, disasm_lc_hex(_T("$%08X,")), outer); p += _tcslen(p); } pc += 4; } if (buffer) { if (p[-1] == ',') p--; _stprintf(p, _T(")")); p += _tcslen(p); } if ((dp & 0x4) == 0) base += dispreg; if (dp & 0x3) base = get_long_debug(base); if (dp & 0x4) base += dispreg; addr = base + outer; if (buffer) { if (disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL)) { _stprintf(p, disasm_lc_hex(_T(" == $%08X")), addr); p += _tcslen(p); } } } else { // Brief format extension TCHAR regstr[20]; uae_s8 disp8 = dp & 0xFF; if (m > 1 && buffer) { if (currprefs.cpu_model < 68020) { _stprintf(mult, _T("[*%d]"), m); } else { _stprintf(mult, _T("*%d"), m); } } regstr[0] = 0; _stprintf(regstr, _T(",%c%d.%c"), dp & 0x8000 ? disasm_areg : disasm_dreg, (int)r, dp & 0x800 ? disasm_long : disasm_word); addr = base + (uae_s32)((uae_s8)disp8) + dispreg; if (buffer) { TCHAR offtxt[16]; if (disp8 < 0) _stprintf(offtxt, disasm_lc_hex(_T("-$%02X")), -disp8); else _stprintf(offtxt, disasm_lc_hex(_T("$%02X")), disp8); if (pcrel) { if (disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL)) { _stprintf(buffer, _T("(%s,%s%s%s=%08x) == $%08x"), offtxt, name, regstr, mult, (*pcp) += disp8, addr); } else { _stprintf(buffer, _T("(%s,%s%s%s=$%08x)"), offtxt, name, regstr, mult, (*pcp) += disp8); } } else { if (disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL)) { _stprintf(buffer, _T("(%s,%s%s%s) == $%08x"), offtxt, name, regstr, mult, addr); } else { _stprintf(buffer, _T("(%s,%s%s%s)"), offtxt, name, regstr, mult); } } if (((dp & 0x0100) || m != 1) && currprefs.cpu_model < 68020) { _tcscat(buffer, _T(" (68020+)")); } } } *pcp = pc; return addr; } uaecptr ShowEA(void *f, uaecptr pc, uae_u16 opcode, int reg, amodes mode, wordsizes size, TCHAR *buf, uae_u32 *eaddr, int *actualea, int safemode) { uaecptr addr = pc; uae_s16 disp16; uae_s32 offset = 0; TCHAR buffer[LINE_BUF_SIZE]; if (actualea) *actualea = 1; if ((opcode & 0xf0ff) == 0x60ff && currprefs.cpu_model < 68020) { // bcc.l is bcc.s if 68000/68010 mode = immi; } buffer[0] = 0; switch (mode){ case Dreg: _stprintf(buffer, _T("%c%d"), disasm_dreg, reg); if (actualea) *actualea = 0; break; case Areg: _stprintf(buffer, _T("%c%d"), disasm_areg, reg); if (actualea) *actualea = 0; break; case Aind: _stprintf(buffer, _T("(%c%d)"), disasm_areg, reg); addr = regs.regs[reg + 8]; if (disasm_flags & DISASM_FLAG_VAL_FORCE) { _stprintf(buffer + _tcslen(buffer), disasm_lc_hex(_T(" == $%08X")), addr); } showea_val(buffer, opcode, addr, size); break; case Aipi: _stprintf(buffer, _T("(%c%d)+"), disasm_areg, reg); addr = regs.regs[reg + 8]; if (disasm_flags & DISASM_FLAG_VAL_FORCE) { _stprintf(buffer + _tcslen(buffer), disasm_lc_hex(_T(" == $%08X")), addr); } showea_val(buffer, opcode, addr, size); break; case Apdi: _stprintf(buffer, _T("-(%c%d)"), disasm_areg, reg); addr = regs.regs[reg + 8] - datasizes[size]; if (disasm_flags & DISASM_FLAG_VAL_FORCE) { _stprintf(buffer + _tcslen(buffer), disasm_lc_hex(_T(" == $%08X")), addr); } showea_val(buffer, opcode, addr, size); break; case Ad16: { TCHAR offtxt[32]; disp16 = get_iword_debug (pc); pc += 2; if (disp16 < 0) _stprintf (offtxt, disasm_lc_hex(_T("-$%04X")), -disp16); else _stprintf (offtxt, disasm_lc_hex(_T("$%04X")), disp16); addr = m68k_areg (regs, reg) + disp16; _stprintf(buffer, _T("(%s,%c%d)"), offtxt, disasm_areg, reg); if (disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL)) { _stprintf(buffer + _tcslen(buffer), disasm_lc_hex(_T(" == $%08X")), addr); } showea_val(buffer, opcode, addr, size); } break; case Ad8r: { TCHAR name[10]; _stprintf(name, _T("%c%d"), disasm_areg, reg); addr = ShowEA_disp(&pc, m68k_areg(regs, reg), buffer, name, false); showea_val(buffer, opcode, addr, size); } break; case PC16: { TCHAR offtxt[32]; disp16 = get_iword_debug (pc); pc += 2; if (disp16 < 0) _stprintf(offtxt, disasm_lc_hex(_T("-$%04X")), -disp16); else _stprintf(offtxt, disasm_lc_hex(_T("$%04X")), disp16); addr += disp16; _stprintf(buffer, _T("(%s,%s)"), offtxt, disasm_pcreg); if (disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL)) { _stprintf(buffer + _tcslen(buffer), disasm_lc_hex(_T(" == $%08X")), addr); } showea_val(buffer, opcode, addr, size); } break; case PC8r: { addr = ShowEA_disp(&pc, addr, buffer, disasm_pcreg, true); showea_val(buffer, opcode, addr, size); } break; case absw: { addr = (uae_s32)(uae_s16)get_iword_debug (pc); uae_s16 saddr = (uae_s16)addr; if (absshort_long) { _stprintf(buffer, disasm_lc_hex(_T("$%08X.%c")), addr, disasm_word); } else if (saddr < 0) { _stprintf(buffer, disasm_lc_hex(_T("-$%04X.%c")), -saddr, disasm_word); } else { _stprintf(buffer, disasm_lc_hex(_T("$%04X.%c")), saddr, disasm_word); } pc += 2; showea_val(buffer, opcode, addr, size); } break; case absl: addr = get_ilong_debug (pc); _stprintf (buffer, disasm_lc_hex(_T("$%08X")), addr); pc += 4; showea_val(buffer, opcode, addr, size); break; case imm: if (actualea) *actualea = 0; switch (size){ case sz_byte: _stprintf (buffer, disasm_lc_hex(_T("#$%02X")), (get_iword_debug (pc) & 0xff)); pc += 2; break; case sz_word: _stprintf (buffer, disasm_lc_hex(_T("#$%04X")), (get_iword_debug (pc) & 0xffff)); pc += 2; break; case sz_long: _stprintf(buffer, disasm_lc_hex(_T("#$%08X")), (get_ilong_debug(pc))); pc += 4; break; case sz_single: { fpdata fp; fpp_to_single(&fp, get_ilong_debug(pc)); _stprintf(buffer, _T("#%s"), fpp_print(&fp, 0)); pc += 4; } break; case sz_double: { fpdata fp; fpp_to_double(&fp, get_ilong_debug(pc), get_ilong_debug(pc + 4)); _stprintf(buffer, _T("#%s"), fpp_print(&fp, 0)); pc += 8; } break; case sz_extended: { fpdata fp; fpp_to_exten(&fp, get_ilong_debug(pc), get_ilong_debug(pc + 4), get_ilong_debug(pc + 8)); _stprintf(buffer, _T("#%s"), fpp_print(&fp, 0)); pc += 12; break; } case sz_packed: _stprintf(buffer, disasm_lc_hex(_T("#$%08X%08X%08X")), get_ilong_debug(pc), get_ilong_debug(pc + 4), get_ilong_debug(pc + 8)); pc += 12; break; default: break; } break; case imm0: offset = (uae_s32)(uae_s8)get_iword_debug (pc); _stprintf (buffer, disasm_lc_hex(_T("#$%02X")), (uae_u32)(offset & 0xff)); addr = pc + 2 + offset; if ((opcode & 0xf000) == 0x6000) { showea_val(buffer, opcode, addr, 1); } pc += 2; if (actualea) *actualea = 0; break; case imm1: offset = (uae_s32)(uae_s16)get_iword_debug (pc); buffer[0] = 0; _stprintf (buffer, disasm_lc_hex(_T("#$%04X")), (uae_u32)(offset & 0xffff)); addr = pc + offset; if ((opcode & 0xf000) == 0x6000) { showea_val(buffer, opcode, addr, 2); } pc += 2; if (actualea) *actualea = 0; break; case imm2: offset = (uae_s32)get_ilong_debug (pc); _stprintf (buffer, disasm_lc_hex(_T("#$%08X")), (uae_u32)offset); addr = pc + offset; if ((opcode & 0xf000) == 0x6000) { showea_val(buffer, opcode, addr, 4); } pc += 4; if (actualea) *actualea = 0; break; case immi: offset = (uae_s32)(uae_s8)(reg & 0xff); _stprintf (buffer, disasm_lc_hex(_T("#$%02X")), (uae_u8)offset); addr = pc + offset; if (actualea) *actualea = 0; break; default: break; } if (buf == NULL) f_out (f, _T("%s"), buffer); else _tcscat (buf, buffer); if (eaddr) *eaddr = addr; return pc; } static const TCHAR *ccnames[] = { _T("T"), _T("F"), _T("HI"),_T("LS"),_T("CC"),_T("CS"),_T("NE"),_T("EQ"), _T("VC"),_T("VS"),_T("PL"),_T("MI"),_T("GE"),_T("LT"),_T("GT"),_T("LE") }; static const TCHAR *fpccnames[] = { _T("F"), _T("EQ"), _T("OGT"), _T("OGE"), _T("OLT"), _T("OLE"), _T("OGL"), _T("OR"), _T("UN"), _T("UEQ"), _T("UGT"), _T("UGE"), _T("ULT"), _T("ULE"), _T("NE"), _T("T"), _T("SF"), _T("SEQ"), _T("GT"), _T("GE"), _T("LT"), _T("LE"), _T("GL"), _T("GLE"), _T("NGLE"), _T("NGL"), _T("NLE"), _T("NLT"), _T("NGE"), _T("NGT"), _T("SNE"), _T("ST") }; const TCHAR *fpuopcodes[] = { _T("FMOVE"), _T("FINT"), _T("FSINH"), _T("FINTRZ"), _T("FSQRT"), NULL, _T("FLOGNP1"), NULL, _T("FETOXM1"), _T("FTANH"), _T("FATAN"), NULL, _T("FASIN"), _T("FATANH"), _T("FSIN"), _T("FTAN"), _T("FETOX"), // 0x10 _T("FTWOTOX"), _T("FTENTOX"), NULL, _T("FLOGN"), _T("FLOG10"), _T("FLOG2"), NULL, _T("FABS"), _T("FCOSH"), _T("FNEG"), NULL, _T("FACOS"), _T("FCOS"), _T("FGETEXP"), _T("FGETMAN"), _T("FDIV"), // 0x20 _T("FMOD"), _T("FADD"), _T("FMUL"), _T("FSGLDIV"), _T("FREM"), _T("FSCALE"), _T("FSGLMUL"), _T("FSUB"), NULL, NULL, NULL, NULL, NULL, NULL, NULL, _T("FSINCOS"), // 0x30 _T("FSINCOS"), _T("FSINCOS"), _T("FSINCOS"), _T("FSINCOS"), _T("FSINCOS"), _T("FSINCOS"), _T("FSINCOS"), _T("FCMP"), NULL, _T("FTST"), NULL, NULL, NULL, NULL, NULL, _T("FSMOVE"), // 0x40 _T("FSSQRT"), NULL, NULL, _T("FDMOVE"), _T("FDSQRT"), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 0x50 NULL, NULL, NULL, NULL, NULL, NULL, NULL, _T("FSABS"), NULL, _T("FSNEG"), NULL, _T("FDABS"), NULL, _T("FDNEG"), NULL, _T("FSDIV"), // 0x60 NULL, _T("FSADD"), _T("FSMUL"), _T("FDDIV"), NULL, _T("FDADD"), _T("FDMUL"), _T("FSSUB"), NULL, NULL, NULL, _T("FDSUB"), NULL, NULL, NULL, NULL, // 0x70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, _T("ILLG") // 0x80 }; static const TCHAR *movemregs[] = { _T("D0"), _T("D1"), _T("D2"), _T("D3"), _T("D4"), _T("D5"), _T("D6"), _T("D7"), _T("A0"), _T("A1"), _T("A2"), _T("A3"), _T("A4"), _T("A5"), _T("A6"), _T("A7"), _T("FP0"), _T("FP1"), _T("FP2"), _T("FP3"), _T("FP4"), _T("FP5"), _T("FP6"), _T("FP7"), _T("FPCR"), _T("FPSR"), _T("FPIAR") }; static void addmovemreg(TCHAR *out, int *prevreg, int *lastreg, int *first, int reg, int fpmode) { TCHAR s[10]; TCHAR *p = out + _tcslen (out); if (*prevreg < 0) { *prevreg = reg; *lastreg = reg; return; } if (reg < 0 || fpmode == 2 || (*prevreg) + 1 != reg || (reg & 8) != ((*prevreg & 8))) { _tcscpy(s, movemregs[*lastreg]); if (disasm_flags & DISASM_FLAG_LC_REG) { to_lower(s, -1); } _stprintf (p, _T("%s%s"), (*first) ? _T("") : _T("/"), s); p = p + _tcslen (p); if (*lastreg != *prevreg) { _tcscpy(s, movemregs[*prevreg]); if (disasm_flags & DISASM_FLAG_LC_REG) { to_lower(s, -1); } if ((*lastreg) + 2 == reg) { _stprintf(p, _T("/%s"), s); } else if ((*lastreg) != (*prevreg)) { _stprintf(p, _T("-%s"), s); } } *lastreg = reg; *first = 0; } *prevreg = reg; } static bool movemout(TCHAR *out, uae_u16 mask, int mode, int fpmode, bool dst) { unsigned int dmask, amask; int prevreg = -1, lastreg = -1, first = 1; if (mode == Apdi && !fpmode) { uae_u8 dmask2; uae_u8 amask2; amask2 = mask & 0xff; dmask2 = (mask >> 8) & 0xff; dmask = 0; amask = 0; for (int i = 0; i < 8; i++) { if (dmask2 & (1 << i)) dmask |= 1 << (7 - i); if (amask2 & (1 << i)) amask |= 1 << (7 - i); } } else { dmask = mask & 0xff; amask = (mask >> 8) & 0xff; if (fpmode == 1 && mode != Apdi) { uae_u8 dmask2 = dmask; dmask = 0; for (int i = 0; i < 8; i++) { if (dmask2 & (1 << i)) dmask |= 1 << (7 - i); } } } bool dataout = dmask != 0 || amask != 0; if (dst && dataout) _tcscat(out, _T(",")); if (fpmode) { while (dmask) { addmovemreg(out, &prevreg, &lastreg, &first, movem_index1[dmask] + (fpmode == 2 ? 24 : 16), fpmode); dmask = movem_next[dmask]; } } else { while (dmask) { addmovemreg (out, &prevreg, &lastreg, &first, movem_index1[dmask], fpmode); dmask = movem_next[dmask]; } while (amask) { addmovemreg (out, &prevreg, &lastreg, &first, movem_index1[amask] + 8, fpmode); amask = movem_next[amask]; } } addmovemreg(out, &prevreg, &lastreg, &first, -1, fpmode); return dataout; } static void disasm_size(TCHAR *instrname, struct instr *dp) { int size = dp->size; if (dp->unsized) { _tcscat(instrname, _T(" ")); return; } int m = dp->mnemo; if (dp->suse && dp->smode == immi && (m == i_MOVE || m == i_ADD || m == i_ADDA || m == i_SUB || m == i_SUBA)) { _tcscat(instrname, disasm_lc_size(_T("Q"))); if (m == i_MOVE) size = -1; } // EXT.B -> EXTB.L if (m == i_EXT && dp->size == sz_byte) { _tcscat(instrname, disasm_lc_size(_T("B"))); size = sz_long; } switch (size) { case sz_byte: _tcscat(instrname, disasm_lc_size(_T(".B "))); break; case sz_word: _tcscat(instrname, disasm_lc_size(_T(".W "))); break; case sz_long: _tcscat(instrname, disasm_lc_size(_T(".L "))); break; default: _tcscat(instrname, disasm_lc_size(_T(" "))); break; } } static void asm_add_extensions(uae_u16 *data, int *dcntp, int mode, uae_u32 v, int extcnt, uae_u16 *ext, uaecptr pc, int size) { int dcnt = *dcntp; if (mode < 0) return; if (mode == Ad16) { data[dcnt++] = v; } if (mode == PC16) { data[dcnt++] = v - (pc + 2); } if (mode == Ad8r || mode == PC8r) { for (int i = 0; i < extcnt; i++) { data[dcnt++] = ext[i]; } } if (mode == absw) { data[dcnt++] = (uae_u16)v; } if (mode == absl) { data[dcnt++] = (uae_u16)(v >> 16); data[dcnt++] = (uae_u16)v; } if ((mode == imm && size == 0) || mode == imm0) { data[dcnt++] = (uae_u8)v; } if ((mode == imm && size == 1) || mode == imm1) { data[dcnt++] = (uae_u16)v; } if ((mode == imm && size == 2) || mode == imm2) { data[dcnt++] = (uae_u16)(v >> 16); data[dcnt++] = (uae_u16)v; } *dcntp = dcnt; } static int asm_isdreg(const TCHAR *s) { if (s[0] == 'D' && s[1] >= '0' && s[1] <= '7') return s[1] - '0'; return -1; } static int asm_isareg(const TCHAR *s) { if (s[0] == 'A' && s[1] >= '0' && s[1] <= '7') return s[1] - '0'; if (s[0] == 'S' && s[1] == 'P') return 7; return -1; } static int asm_ispc(const TCHAR *s) { if (s[0] == 'P' && s[1] == 'C') return 1; return 0; } static uae_u32 asmgetval(const TCHAR *s) { TCHAR *endptr; if (s[0] == '$') { s++; } if (s[0] == '-') { return _tcstol(s, &endptr, 16); } return _tcstoul(s, &endptr, 16); } static int asm_parse_mode020(TCHAR *s, uae_u8 *reg, uae_u32 *v, int *extcnt, uae_u16 *ext) { return -1; } static int asm_parse_mode(TCHAR *s, uae_u8 *reg, uae_u32 *v, int *extcnt, uae_u16 *ext) { TCHAR *ss = s; *reg = -1; *v = 0; *ext = 0; *extcnt = 0; if (s[0] == 0) return -1; // Dn if (asm_isdreg(s) >= 0 && s[2] == 0) { *reg = asm_isdreg(s); return Dreg; } // An if (asm_isareg(s) >= 0 && s[2] == 0) { *reg = asm_isareg(s); return Areg; } // (An) and (An)+ if (s[0] == '(' && asm_isareg(s + 1) >= 0 && s[3] == ')') { *reg = asm_isareg(s + 1); if (s[4] == '+' && s[5] == 0) return Aipi; if (s[4] == 0) return Aind; return -1; } // -(An) if (s[0] == '-' && s[1] == '(' && asm_isareg(s + 2) >= 0 && s[4] == ')' && s[5] == 0) { *reg = asm_isareg(s + 2); return Apdi; } // Immediate if (s[0] == '#') { if (s[1] == '!') { *v = _tstol(s + 2); } else { *v = asmgetval(s + 1); } return imm; } // Value if (s[0] == '!') { *v = _tstol(s + 1); } else { *v = asmgetval(s); } int dots = 0; int fullext = 0; for (int i = 0; i < _tcslen(s); i++) { if (s[i] == ',') { dots++; } else if (s[i] == '[') { if (fullext > 0) fullext = -1; else fullext = 1; } else if (s[i] == ']') { if (fullext != 1) fullext = -1; else fullext = 2; } } if (fullext < 0 || fullext == 1) return -1; if (fullext == 2) { return asm_parse_mode020(s, reg, v, extcnt, ext); } while (*s != 0) { // d16(An) if (dots == 0 && s[0] == '(' && asm_isareg(s + 1) >= 0 && s[3] == ')' && s[4] == 0) { *reg = asm_isareg(s + 1); return Ad16; } // d16(PC) if (dots == 0 && s[0] == '(' && asm_ispc(s + 1) && s[3] == ')' && s[4] == 0) { *reg = 2; return PC16; } // (d16,An) / (d16,PC) if (dots == 1 && s[0] == '(' && !asm_ispc(s + 1) && asm_isareg(s + 1) < 0 && asm_isdreg(s + 1) < 0) { TCHAR *startptr, *endptr; if (s[1] == '!') { startptr = s + 2; *v = _tcstol(startptr, &endptr, 10); } else { if (s[1] == '$') startptr = s + 2; else startptr = s + 1; *v = _tcstol(startptr, &endptr, 16); } if (endptr == startptr || endptr[0] != ',') return -1; if (asm_ispc(endptr + 1) && endptr[3] == ')') { *reg = 2; return PC16; } if (asm_isareg(endptr + 1) >= 0 && endptr[3] == ')') { *reg = asm_isareg(endptr + 1); return Ad16; } return -1; } // Ad8r PC8r if (s[0] == '(') { TCHAR *s2 = s; if (!asm_ispc(s + 1) && asm_isareg(s + 1) < 0 && asm_isdreg(s + 1) < 0) { if (dots != 2) return -1; TCHAR *startptr, *endptr; if (s[1] == '!') { startptr = s + 2; *v = _tcstol(startptr, &endptr, 10); } else { if (s[1] == '$') startptr = s + 2; else startptr = s + 1; *v = _tcstol(startptr, &endptr, 16); } if (endptr == startptr || endptr[0] != ',') return -1; s2 = endptr + 1; } else if (((asm_isareg(s + 1) >= 0 || asm_ispc(s + 1)) && s[3] == ',') || (asm_isdreg(s + 4) >= 0 || asm_isareg(s + 4) >= 0)) { if (dots != 1) return -1; s2 = s + 1; } else { return -1; } uae_u8 reg2; bool ispc = asm_ispc(s2); if (ispc) { *reg = 3; } else { *reg = asm_isareg(s2); } *extcnt = 1; s2 += 2; if (*s2 != ',') return -1; s2++; if (asm_isdreg(s2) >= 0) { reg2 = asm_isdreg(s2); } else { reg2 = asm_isareg(s2); *ext |= 1 << 15; } s2 += 2; *ext |= reg2 << 12; *ext |= (*v) & 0xff; if (s2[0] == '.' && s2[1] == 'W') { s2 += 2; } else if (s2[0] == '.' && s2[1] == 'L') { *ext |= 1 << 11; s2 += 2; } if (s2[0] == '*') { TCHAR scale = s2[1]; if (scale == '2') *ext |= 1 << 9; else if (scale == '4') *ext |= 2 << 9; else if (scale == '8') *ext |= 3 << 9; else return -1; s2 += 2; } if (s2[0] == ')' && s2[1] == 0) { return ispc ? PC8r : Ad8r; } return -1; } s++; } // abs.w if (s - ss > 2 && s[-2] == '.' && s[-1] == 'W') { *reg = 0; return absw; } // abs.l *reg = 1; return absl; } static TCHAR *asm_parse_parm(TCHAR *parm, TCHAR *out) { TCHAR *p = parm; bool quote = false; for (;;) { if (*p == '(') { quote = true; } if (*p == ')') { if (!quote) return NULL; quote = false; } if ((*p == ',' || *p == 0) && !quote) { TCHAR c = *p; p[0] = 0; _tcscpy(out, parm); my_trim(out); if (c) p++; return p; } p++; } } static bool m68k_asm_parse_movec(TCHAR *s, TCHAR *d) { for (int i = 0; m2cregs[i].regname; i++) { if (!_tcscmp(s, m2cregs[i].regname)) { uae_u16 v = m2cregs[i].regno; if (asm_isareg(d) >= 0) v |= 0x8000 | (asm_isareg(d) << 12); else if (asm_isdreg(d) >= 0) v |= (asm_isdreg(d) << 12); else return false; _stprintf(s, _T("#%X"), v); return true; } } return false; } static bool m68k_asm_parse_movem(TCHAR *s, int dir) { TCHAR *d = s; uae_u16 regmask = 0; uae_u16 mask = dir ? 0x8000 : 0x0001; bool ret = false; while(*s) { int dreg = asm_isdreg(s); int areg = asm_isareg(s); if (dreg < 0 && areg < 0) break; int reg = dreg >= 0 ? dreg : areg + 8; regmask |= dir ? (mask >> reg) : (mask << reg); s += 2; if (*s == 0) { ret = true; break; } else if (*s == '/') { s++; continue; } else if (*s == '-') { s++; int dreg2 = asm_isdreg(s); int areg2 = asm_isareg(s); if (dreg2 < 0 && areg2 < 0) break; int reg2 = dreg2 >= 0 ? dreg2 : areg2 + 8; if (reg2 < reg) break; while (reg2 >= reg) { regmask |= dir ? (mask >> reg) : (mask << reg); reg++; } s += 2; if (*s == 0) { ret = true; break; } } else { break; } } if (ret) _stprintf(d, _T("#%X"), regmask); return ret; } int m68k_asm(TCHAR *sline, uae_u16 *out, uaecptr pc) { TCHAR *p; const TCHAR *cp1; TCHAR ins[256], parms[256]; TCHAR line[256]; TCHAR srcea[256], dstea[256]; uae_u16 data[16], sexts[8], dexts[8]; int sextcnt, dextcnt; int dcnt = 0; int cc = -1; int quick = 0; bool immrelpc = false; if (uaetcslen(sline) > 100) return -1; srcea[0] = dstea[0] = 0; parms[0] = 0; // strip all white space except first space p = line; bool firstsp = true; for (int i = 0; sline[i]; i++) { TCHAR c = sline[i]; if (c == 32 && firstsp) { firstsp = false; *p++ = 32; } if (c <= 32) continue; *p++ = c; } *p = 0; to_upper(line, uaetcslen(line)); p = line; while (*p && *p != ' ') p++; if (*p == ' ') { *p = 0; _tcscpy(parms, p + 1); my_trim(parms); } _tcscpy(ins, line); if (uaetcslen(ins) == 0) return 0; int size = 1; int inssize = -1; cp1 = _tcschr(line, '.'); if (cp1) { size = cp1[1]; if (size == 'W') size = 1; else if (size == 'L') size = 2; else if (size == 'B') size = 0; else return 0; inssize = size; line[cp1 - line] = 0; _tcscpy(ins, line); } TCHAR *parmp = parms; parmp = asm_parse_parm(parmp, srcea); if (!parmp) return 0; if (srcea[0]) { parmp = asm_parse_parm(parmp, dstea); if (!parmp) return 0; } int smode = -1; int dmode = -1; uae_u8 sreg = -1; uae_u8 dreg = -1; uae_u32 sval = 0; uae_u32 dval = 0; int ssize = -1; int dsize = -1; struct mnemolookup *lookup; dmode = asm_parse_mode(dstea, &dreg, &dval, &dextcnt, dexts); // Common alias if (!_tcscmp(ins, _T("BRA"))) { _tcscpy(ins, _T("BT")); } else if (!_tcscmp(ins, _T("BSR"))) { immrelpc = true; } else if (!_tcscmp(ins, _T("MOVEM"))) { _tcscpy(ins, _T("MVMLE")); if (!m68k_asm_parse_movem(srcea, dmode == Apdi)) { TCHAR tmp[256]; _tcscpy(ins, _T("MVMEL")); _tcscpy(tmp, srcea); _tcscpy(srcea, dstea); _tcscpy(dstea, tmp); if (!m68k_asm_parse_movem(srcea, 0)) return -1; dmode = asm_parse_mode(dstea, &dreg, &dval, &dextcnt, dexts); } } else if (!_tcscmp(ins, _T("MOVEC"))) { if (dmode == Dreg || dmode == Areg) { _tcscpy(ins, _T("MOVEC2")); if (!m68k_asm_parse_movec(srcea, dstea)) return -1; } else { TCHAR tmp[256]; _tcscpy(ins, _T("MOVE2C")); _tcscpy(tmp, srcea); _tcscpy(srcea, dstea); dstea[0] = 0; if (!m68k_asm_parse_movec(srcea, tmp)) return -1; } dmode = -1; } if (dmode == Areg) { int l = uaetcslen(ins); if (l <= 2) return -1; TCHAR last = ins[l- 1]; if (last == 'Q') { last = ins[l - 2]; if (last != 'A') { ins[l - 1] = 'A'; ins[l] = 'Q'; ins[l + 1] = 0; } } else if (last != 'A') { TCHAR insa[256]; _tcscpy(insa, ins); _tcscat(insa, _T("A")); for (lookup = lookuptab; lookup->name; lookup++) { if (!_tcscmp(insa, lookup->name)) { _tcscpy(ins, insa); break; } } } } bool fp = ins[0] == 'F'; int tsize = size; if (ins[uaetcslen(ins) - 1] == 'Q' && uaetcslen(ins) > 3 && !fp) { quick = 1; ins[uaetcslen(ins) - 1] = 0; if (inssize < 0) tsize = 2; } for (lookup = lookuptab; lookup->name; lookup++) { if (!_tcscmp(ins, lookup->name)) break; } if (!lookup->name) { // Check cc variants for (lookup = lookuptab; lookup->name; lookup++) { const TCHAR *ccp = _tcsstr(lookup->name, _T("cc")); if (ccp) { TCHAR tmp[256]; for (int i = 0; i < (fp ? 32 : 16); i++) { const TCHAR *ccname = fp ? fpccnames[i] : ccnames[i]; _tcscpy(tmp, lookup->name); _tcscpy(tmp + (ccp - lookup->name), ccname); if (tmp[_tcslen(tmp) - 1] == ' ') tmp[_tcslen(tmp) - 1] = 0; if (!_tcscmp(tmp, ins)) { _tcscpy(ins, lookup->name); cc = i; if (lookup->mnemo == i_DBcc || lookup->mnemo == i_Bcc) { // Bcc.B uses same encoding mode as MOVEQ immrelpc = true; } if (size == 0) { quick = 2; } break; } } } if (cc >= 0) break; } } if (!lookup->name) return 0; int mnemo = lookup->mnemo; int found = 0; int sizemask = 0; int unsized = 0; for (int round = 0; round < 9; round++) { if (!found && round == 8) return 0; if (round == 3) { bool isimm = srcea[0] == '#'; if (immrelpc && !isimm) { TCHAR tmp[256]; _tcscpy(tmp, srcea); srcea[0] = '#'; _tcscpy(srcea + 1, tmp); } smode = asm_parse_mode(srcea, &sreg, &sval, &sextcnt, sexts); if (immrelpc && !isimm) { sval = sval - (pc + 2); } if (quick) { smode = immi; sreg = sval & 0xff; } } if (round == 1) { if (!quick && (sizemask == 1 || sizemask == 2 || sizemask == 4)) { tsize = 0; if (sizemask == 2) tsize = 1; else if (sizemask == 4) tsize = 2; } else { continue; } } if (round == 2 && !found) { unsized = 1; } if (round == 4 && smode == imm) { smode = imm0; } else if (round == 5 && smode == imm0) { smode = imm1; } else if (round == 6 && smode == imm1) { smode = imm2; } else if (round == 7 && smode == imm2) { smode = immi; sreg = sval & 0xff; } else if (round == 4) { round += 5 - 1; } for (int opcode = 0; opcode < 65536; opcode++) { struct instr *table = &table68k[opcode]; if (table->mnemo != mnemo) continue; if (cc >= 0 && table->cc != cc) continue; #if 0 if (round == 0) { console_out_f(_T("%s OP=%04x S=%d SR=%d SM=%d SU=%d SP=%d DR=%d DM=%d DU=%d DP=%d SDU=%d\n"), lookup->name, opcode, table->size, table->sreg, table->smode, table->suse, table->spos, table->dreg, table->dmode, table->duse, table->dpos, table->sduse); } #endif if (table->duse && !(table->dmode == dmode || (dmode >= imm && dmode <= imm2 && table->dmode >= imm && table->dmode <= imm2))) continue; if (round == 0) { sizemask |= 1 << table->size; } if (unsized > 0 && !table->unsized) { continue; } found++; if (round >= 3) { if ( ((table->size == tsize || table->unsized)) && ((!table->suse && smode < 0) || (table->suse && table->smode == smode)) && ((!table->duse && dmode < 0) || (table->duse && (table->dmode == dmode || (dmode == imm && (table->dmode >= imm && table->dmode <= imm2))))) && ((table->sreg == sreg || (table->smode >= absw && table->smode != immi))) && ((table->dreg == dreg || table->dmode >= absw)) ) { if (inssize >= 0 && tsize != inssize) continue; data[dcnt++] = opcode; asm_add_extensions(data, &dcnt, smode, sval, sextcnt, sexts, pc, tsize); if (smode >= 0) asm_add_extensions(data, &dcnt, dmode, dval, dextcnt, dexts, pc, tsize); for (int i = 0; i < dcnt; i++) { out[i] = data[i]; } return dcnt; } } } } return 0; } static void resolve_if_jmp(TCHAR *s, uae_u32 addr) { uae_u16 opcode = get_word_debug(addr); if (opcode == 0x4ef9) { // JMP x.l TCHAR *p = s + _tcslen(s); uae_u32 addr2 = get_long_debug(addr + 2); if (disasm_flags & (DISASM_FLAG_VAL_FORCE | DISASM_FLAG_VAL)) { _stprintf(p, disasm_lc_hex(_T(" == $%08X ")), addr2); } showea_val(p + _tcslen(p), opcode, addr2, 4); TCHAR txt[256]; bool ext; if (debugmem_get_segment(addr2, NULL, &ext, NULL, txt)) { if (ext) { _tcscat(p, _T(" ")); _tcscat(p, txt); } } } } static bool mmu_op30_helper_get_fc(uae_u16 extra, TCHAR *out) { switch (extra & 0x0018) { case 0x0010: _stprintf(out, _T("#%d"), extra & 7); return true; case 0x0008: _stprintf(out, _T("%c%d"), disasm_dreg, extra & 7); return true; case 0x0000: if (extra & 1) { _tcscpy(out, disasm_lc_reg(_T("DFC"))); } else { _tcscpy(out, disasm_lc_reg(_T("SFC"))); } return true; default: return false; } } static bool mmu_op30_invea(uae_u32 opcode) { int eamode = (opcode >> 3) & 7; int rreg = opcode & 7; // Dn, An, (An)+, -(An), immediate and PC-relative not allowed if (eamode == 0 || eamode == 1 || eamode == 3 || eamode == 4 || (eamode == 7 && rreg > 1)) return true; return false; } static uaecptr disasm_mmu030(uaecptr pc, uae_u16 opcode, uae_u16 extra, struct instr *dp, TCHAR *instrname, uae_u32 *seaddr2, int *actualea, int safemode) { int type = extra >> 13; _tcscpy(instrname, _T("F-LINE (MMU 68030)")); pc += 2; switch (type) { case 0: case 2: case 3: { // PMOVE int preg = (extra >> 10) & 31; int rw = (extra >> 9) & 1; int fd = (extra >> 8) & 1; int unused = (extra & 0xff); const TCHAR *r = NULL; if (mmu_op30_invea(opcode)) break; if (unused) break; if (rw && fd) break; switch (preg) { case 0x10: r = _T("TC"); break; case 0x12: r = _T("SRP"); break; case 0x13: r = _T("CRP"); break; case 0x18: r = _T("MMUSR"); break; case 0x02: r = _T("TT0"); break; case 0x03: r = _T("TT1"); break; } if (!r) break; _tcscpy(instrname, _T("PMOVE")); if (fd) _tcscat(instrname, _T("FD")); _tcscat(instrname, _T(" ")); disasm_lc_mnemo(instrname); if (!rw) { pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, seaddr2, actualea, safemode); _tcscat(instrname, _T(",")); } _tcscat(instrname, disasm_lc_reg(r)); if (rw) { _tcscat(instrname, _T(",")); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, seaddr2, actualea, safemode); } break; } case 1: { // PLOAD/PFLUSH uae_u16 mode = (extra >> 8) & 31; int unused = (extra & (0x100 | 0x80 | 0x40 | 0x20)); uae_u16 fc_mask = (extra & 0x00E0) >> 5; uae_u16 fc_bits = extra & 0x7f; TCHAR fc[10]; if (unused) break; switch (mode) { case 0x00: // PLOAD W case 0x02: // PLOAD R if (mmu_op30_invea(opcode)) break; if (!mmu_op30_helper_get_fc(extra, fc)) break; _stprintf(instrname, _T("PLOAD%c"), mode == 0 ? 'W' : 'R'); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), _T(" %s,"), fc); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, seaddr2, actualea, safemode); break; case 0x04: // PFLUSHA if (fc_bits) break; _tcscpy(instrname, _T("PFLUSHA")); disasm_lc_mnemo(instrname); break; case 0x10: // FC if (!mmu_op30_helper_get_fc(extra, fc)) break; _tcscpy(instrname, _T("PFLUSH")); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), _T(" %s,%d"), fc, fc_mask); break; case 0x18: // FC + EA if (mmu_op30_invea(opcode)) break; if (!mmu_op30_helper_get_fc(extra, fc)) break; _tcscpy(instrname, _T("PFLUSH")); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), _T(" %s,%d"), fc, fc_mask); _tcscat(instrname, _T(",")); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, seaddr2, actualea, safemode); break; } break; } case 4: { // PTEST int level = (extra & 0x1C00) >> 10; int rw = (extra >> 9) & 1; int a = (extra >> 8) & 1; int areg = (extra & 0xE0) >> 5; TCHAR fc[10]; if (mmu_op30_invea(opcode)) break; if (!mmu_op30_helper_get_fc(extra, fc)) break; if (!level && a) break; _stprintf(instrname, _T("PTEST%c"), rw ? 'R' : 'W'); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), _T(" %s,"), fc); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, seaddr2, actualea, safemode); _stprintf(instrname + _tcslen(instrname), _T(",#%d"), level); if (a) _stprintf(instrname + _tcslen(instrname), _T(",%c%d"), disasm_areg, areg); break; } default: disasm_lc_mnemo(instrname); break; } return pc; } static uae_u16 get_disasm_word(uaecptr pc, uae_u16 *bufpc, int bufpcsizep, int offset) { offset /= 2; if (bufpc) { if (bufpcsizep > offset) { return bufpc[offset]; } return 0; } else { return get_word_debug(pc + offset * 2); } } static void add_disasm_word(uaecptr *pcp, uae_u16 **bufpcp, int *bufpcsizep, int add) { if (*bufpcp) { *bufpcp += add; *bufpcsizep -= add; } else { *pcp += add; } } uae_u32 m68k_disasm_2(TCHAR *buf, int bufsize, uaecptr pc, uae_u16 *bufpc, int bufpcsize, uaecptr *nextpc, int cnt, uae_u32 *seaddr, uae_u32 *deaddr, uaecptr lastpc, int safemode) { uae_u32 seaddr2; uae_u32 deaddr2; int actualea_src = 0; int actualea_dst = 0; #ifdef WINUAE_FOR_HATARI const int orig_size = bufsize; #endif if (!table68k) return 0; while (cnt-- > 0) { TCHAR instrname[256], *ccpt; TCHAR segout[256], segname[256]; int i; uae_u32 opcode; uae_u16 extra; struct mnemolookup *lookup; struct instr *dp; uaecptr oldpc; uaecptr m68kpc_illg = 0; int illegal = 0; int segid, lastsegid; TCHAR *symbolpos; bool skip = false; seaddr2 = deaddr2 = 0xffffffff; oldpc = pc; opcode = get_disasm_word(pc, bufpc, bufpcsize, 0); extra = get_disasm_word(pc, bufpc, bufpcsize, 2); if (cpufunctbl[opcode] == op_illg_1 || cpufunctbl[opcode] == op_unimpl_1) { m68kpc_illg = pc + 2; illegal = 1; } dp = table68k + opcode; if (dp->mnemo == i_ILLG) { illegal = 0; opcode = 0x4AFC; dp = table68k + opcode; } for (lookup = lookuptab;lookup->mnemo != dp->mnemo; lookup++) ; lastsegid = -1; bool exact = false; segid = 0; if (!bufpc) { if (lastpc != 0xffffffff) { lastsegid = debugmem_get_segment(lastpc, NULL, NULL, NULL, NULL); } segid = debugmem_get_segment(pc, &exact, NULL, segout, segname); if (segid && (lastsegid != -1 || exact) && (segid != lastsegid || pc == lastpc || exact)) { buf = buf_out(buf, &bufsize, _T("%s\n"), segname); } } symbolpos = buf; buf = buf_out (buf, &bufsize, disasm_lc_nhex(_T("%08X ")), pc); if (segid) { buf = buf_out(buf, &bufsize, _T("%s "), segout); } add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (lookup->friendlyname) _tcscpy(instrname, lookup->friendlyname); else _tcscpy(instrname, lookup->name); ccpt = _tcsstr(instrname, _T("cc")); if (ccpt != 0) { if ((opcode & 0xf000) == 0xf000) { if (lookup->mnemo == i_FBcc) { _tcscpy(ccpt, fpccnames[opcode & 0x1f]); } else { _tcscpy(ccpt, fpccnames[extra & 0x1f]); } } else { _tcscpy(ccpt, ccnames[dp->cc]); if (dp->mnemo == i_Bcc && dp->cc == 0) { _tcscpy(ccpt, _T("RA")); // BT -> BRA } } } disasm_lc_mnemo(instrname); disasm_size(instrname, dp); if (lookup->mnemo == i_MOVEC2 || lookup->mnemo == i_MOVE2C) { uae_u16 imm = extra; uae_u16 creg = imm & 0x0fff; uae_u16 r = imm >> 12; TCHAR regs[16]; const TCHAR *cname = _T("?"); int j; for (j = 0; m2cregs[j].regname; j++) { if (m2cregs[j].regno == creg) break; } _stprintf(regs, _T("%c%d"), r >= 8 ? disasm_areg : disasm_dreg, r >= 8 ? r - 8 : r); if (m2cregs[j].regname) cname = m2cregs[j].regname; if (lookup->mnemo == i_MOVE2C) { _tcscat(instrname, regs); _tcscat(instrname, _T(",")); _tcscat(instrname, cname); } else { _tcscat(instrname, cname); _tcscat(instrname, _T(",")); _tcscat(instrname, regs); } int lvl = (currprefs.cpu_model - 68000) / 10; if (lvl == 6) lvl = 5; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (lvl < 1 || !(m2cregs[j].flags & (1 << (lvl - 1)))) illegal = -1; } else if (lookup->mnemo == i_CHK2) { TCHAR *p; if (!(extra & 0x0800)) { instrname[1] = 'M'; instrname[2] = 'P'; disasm_lc_mnemo(instrname); } add_disasm_word(&pc, &bufpc, &bufpcsize, 2); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); p = instrname + _tcslen(instrname); _stprintf(p, _T(",%c%d"), (extra & 0x8000) ? disasm_areg : disasm_dreg, (extra >> 12) & 7); } else if (lookup->mnemo == i_CAS) { TCHAR *p = instrname + _tcslen(instrname); _stprintf(p, _T("%c%d,%c%d,"), disasm_dreg, extra & 7, disasm_dreg, (extra >> 6) & 7); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); } else if (lookup->mnemo == i_CAS2) { TCHAR *p = instrname + _tcslen(instrname); uae_u16 extra2 = get_word_debug(pc + 2); _stprintf(p, _T("%c%d:%c%d,%c%d,%c%d,(%c%d):(%c%d)"), disasm_dreg, extra & 7, disasm_dreg, extra2 & 7, disasm_dreg, (extra >> 6) & 7, disasm_dreg, (extra2 >> 6) & 7, (extra & 0x8000) ? disasm_areg : disasm_dreg, (extra >> 12) & 7, (extra2 & 0x8000) ? disasm_areg : disasm_dreg, (extra2 >> 12) & 7); add_disasm_word(&pc, &bufpc, &bufpcsize, 4); } else if (lookup->mnemo == i_ORSR || lookup->mnemo == i_ANDSR || lookup->mnemo == i_EORSR) { pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); _tcscat(instrname, dp->size == sz_byte ? disasm_lc_reg(_T(",CCR")) : disasm_lc_reg(_T(",SR"))); } else if (lookup->mnemo == i_MVR2USP) { pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); _tcscat(instrname, disasm_lc_reg(_T(",USP"))); } else if (lookup->mnemo == i_MVUSP2R) { _tcscat(instrname, disasm_lc_reg(_T("USP,"))); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); } else if (lookup->mnemo == i_MV2SR) { pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); _tcscat(instrname, dp->size == sz_byte ? disasm_lc_reg(_T(",CCR")) : disasm_lc_reg(_T(",SR"))); } else if (lookup->mnemo == i_MVSR2) { _tcscat(instrname, dp->size == sz_byte ? disasm_lc_reg(_T("CCR,")) : disasm_lc_reg(_T("SR,"))); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); } else if (lookup->mnemo == i_MVMEL) { uae_u16 mask = extra; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); pc = ShowEA (NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); movemout (instrname, mask, dp->dmode, 0, true); } else if (lookup->mnemo == i_MVMLE) { uae_u16 mask = extra; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (movemout(instrname, mask, dp->dmode, 0, false)) _tcscat(instrname, _T(",")); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); } else if (lookup->mnemo == i_MULL) { extra = get_disasm_word(pc, bufpc, bufpcsize, 0); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (extra & 0x0800) // signed/unsigned instrname[3] = 'S'; else instrname[3] = 'U'; disasm_lc_mnemo(instrname); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); TCHAR *p = instrname + _tcslen(instrname); if (extra & 0x0400) _stprintf(p, _T(",%c%d:%c%d"), disasm_dreg, extra & 7, disasm_dreg, (extra >> 12) & 7); else _stprintf(p, _T(",%c%d"), disasm_dreg, (extra >> 12) & 7); } else if (lookup->mnemo == i_DIVL) { extra = get_disasm_word(pc, bufpc, bufpcsize, 0); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (extra & 0x0800) // signed/unsigned instrname[3] = 'S'; else instrname[3] = 'U'; if (!(extra & 0x0400)) { // DIVS.L/DIVU.L->DIVSL.L/DIVUL.L instrname[8] = 0; instrname[7] = ' '; instrname[6] = instrname[5]; instrname[5] = instrname[4]; instrname[4] = 'L'; } disasm_lc_mnemo(instrname); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); TCHAR* p = instrname + _tcslen(instrname); _stprintf(p, _T(",%c%d:%c%d"), disasm_dreg, extra & 7, disasm_dreg, (extra >> 12) & 7); } else if (lookup->mnemo == i_MOVES) { TCHAR *p; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (!(extra & 0x0800)) { pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); p = instrname + _tcslen(instrname); _stprintf(p, _T(",%c%d"), (extra & 0x8000) ? disasm_areg : disasm_dreg, (extra >> 12) & 7); } else { p = instrname + _tcslen(instrname); _stprintf(p, _T("%c%d,"), (extra & 0x8000) ? disasm_areg : disasm_dreg, (extra >> 12) & 7); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); } } else if (lookup->mnemo == i_BFEXTS || lookup->mnemo == i_BFEXTU || lookup->mnemo == i_BFCHG || lookup->mnemo == i_BFCLR || lookup->mnemo == i_BFFFO || lookup->mnemo == i_BFINS || lookup->mnemo == i_BFSET || lookup->mnemo == i_BFTST) { TCHAR *p; int reg = -1; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); p = instrname + _tcslen(instrname); if (lookup->mnemo == i_BFEXTS || lookup->mnemo == i_BFEXTU || lookup->mnemo == i_BFFFO || lookup->mnemo == i_BFINS) reg = (extra >> 12) & 7; if (lookup->mnemo == i_BFINS) _stprintf(p, _T("%c%d,"), disasm_dreg, reg); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); _tcscat(instrname, _T(" {")); p = instrname + _tcslen(instrname); if (extra & 0x0800) _stprintf(p, _T("%c%d"), disasm_dreg, (extra >> 6) & 7); else _stprintf(p, _T("%d"), (extra >> 6) & 31); _tcscat(instrname, _T(":")); p = instrname + _tcslen(instrname); if (extra & 0x0020) _stprintf(p, _T("%c%d"), disasm_dreg, extra & 7); else _stprintf(p, _T("%d"), extra & 31); _tcscat(instrname, _T("}")); p = instrname + _tcslen(instrname); if (lookup->mnemo == i_BFFFO || lookup->mnemo == i_BFEXTS || lookup->mnemo == i_BFEXTU) _stprintf(p, _T(",%c%d"), disasm_dreg, reg); } else if (lookup->mnemo == i_CPUSHA || lookup->mnemo == i_CPUSHL || lookup->mnemo == i_CPUSHP || lookup->mnemo == i_CINVA || lookup->mnemo == i_CINVL || lookup->mnemo == i_CINVP) { if ((opcode & 0xc0) == 0xc0) _tcscat(instrname, disasm_lc_reg(_T("BC"))); else if (opcode & 0x80) _tcscat(instrname, disasm_lc_reg(_T("IC"))); else if (opcode & 0x40) _tcscat(instrname, disasm_lc_reg(_T("DC"))); else _tcscat(instrname, _T("?")); if (lookup->mnemo == i_CPUSHL || lookup->mnemo == i_CPUSHP || lookup->mnemo == i_CINVL || lookup->mnemo == i_CINVP) { TCHAR *p = instrname + _tcslen(instrname); _stprintf(p, _T(",(%c%d)"), disasm_areg, opcode & 7); } } else if (lookup->mnemo == i_MOVE16) { TCHAR *p = instrname + _tcslen(instrname); if (opcode & 0x20) { _stprintf(p, _T("(%c%d)+,(%c%d)+"), disasm_areg, opcode & 7, disasm_areg, (extra >> 12) & 7); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); } else { uae_u32 addr = get_long_debug(pc); int ay = opcode & 7; pc += 4; switch ((opcode >> 3) & 3) { case 0: _stprintf(p, _T("(%c%d)+,$%08x"), disasm_areg, ay, addr); break; case 1: _stprintf(p, _T("$%08x,(%c%d)+"), addr, disasm_areg, ay); break; case 2: _stprintf(p, _T("(%c%d),$%08x"), disasm_areg, ay, addr); break; case 3: _stprintf(p, _T("$%08x,(%c%d)"), addr, disasm_areg, ay); break; } } } else if (lookup->mnemo == i_PACK || lookup->mnemo == i_UNPK) { pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); _tcscat(instrname, _T(",")); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); extra = get_word_debug(pc); _stprintf(instrname + _tcslen(instrname), disasm_lc_hex(_T(",#$%04X")), extra); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); } else if (lookup->mnemo == i_LPSTOP) { if (extra == 0x01c0) { uae_u16 extra2 = get_word_debug(pc + 2); _tcscpy(instrname, _T("LPSTOP")); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), disasm_lc_hex(_T(" #$%04X")), extra2); add_disasm_word(&pc, &bufpc, &bufpcsize, 4); } else { _tcscpy(instrname, _T("ILLG")); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), disasm_lc_hex(_T(" #$%04X")), extra); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); } } else if (lookup->mnemo == i_CALLM) { TCHAR *p = instrname + _tcslen(instrname); _stprintf(p, _T("#%d,"), extra & 255); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); } else if (lookup->mnemo == i_FDBcc) { pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); _tcscat(instrname, _T(",")); pc = ShowEA(NULL, pc, opcode, 0, imm1, sz_word, instrname, &deaddr2, &actualea_dst, safemode); } else if (lookup->mnemo == i_FTRAPcc) { pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &seaddr2, &actualea_src, safemode); int mode = opcode & 7; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if (mode == 2) { pc = ShowEA(NULL, pc, opcode, 0, imm1, sz_word, instrname, NULL, NULL, safemode); } else if (mode == 3) { pc = ShowEA(NULL, pc, opcode, 0, imm2, sz_long, instrname, NULL, NULL, safemode); } } else if (lookup->mnemo == i_FPP) { TCHAR *p; int ins = extra & 0x7f; int size = (extra >> 10) & 7; add_disasm_word(&pc, &bufpc, &bufpcsize, 2); if ((extra & 0xfc00) == 0x5c00) { // FMOVECR (=i_FPP with source specifier = 7) fpdata fp; fpu_get_constant(&fp, extra & 0x7f); _tcscpy(instrname, _T("FMOVECR.X")); disasm_lc_mnemo(instrname); _stprintf(instrname + _tcslen(instrname), _T(" #0x%02x [%s],%s%d"), extra & 0x7f, fpp_print(&fp, 0), disasm_fpreg, (extra >> 7) & 7); } else if ((extra & 0x8000) == 0x8000) { // FMOVEM or FMOVE control register int dr = (extra >> 13) & 1; int mode; int dreg = (extra >> 4) & 7; int regmask, fpmode; if (extra & 0x4000) { mode = (extra >> 11) & 3; regmask = extra & 0xff; // FMOVEM FPx fpmode = 1; _tcscpy(instrname, _T("FMOVEM.X ")); disasm_lc_mnemo(instrname); } else { mode = 0; regmask = (extra >> 10) & 7; // FMOVEM or FMOVE control fpmode = 2; _tcscpy(instrname, _T("FMOVEM.L ")); if (regmask == 1 || regmask == 2 || regmask == 4) _tcscpy(instrname, _T("FMOVE.L ")); disasm_lc_mnemo(instrname); int msk = regmask & 2; if (regmask & 1) { msk |= 4; } if (regmask & 4) { msk |= 1; } regmask = msk; } p = instrname + _tcslen(instrname); if (dr) { if (mode & 1) _stprintf(p, _T("%c%d"), disasm_dreg, dreg); else movemout(p, regmask, dp->dmode, fpmode, false); _tcscat(instrname, _T(",")); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); } else { if ((opcode & 0x3f) == 0x3c && !(extra & 0x2000)) { // FMOVEM #xxx,control registers (strange one, can have up to 3 long word immediates) bool entry = false; if (!(extra & (0x400 | 0x800 | 0x1000))) extra |= 0x400; for (int i = 0; i < 3; i++) { if (extra & (0x1000 >> i)) { if (entry) _tcscat(p, _T("/")); entry = true; p = instrname + _tcslen(instrname); _stprintf(p, disasm_lc_hex(_T("#$%08X")), get_ilong_debug(pc)); add_disasm_word(&pc, &bufpc, &bufpcsize, 4); } } } else { pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); } p = instrname + _tcslen(instrname); if (mode & 1) _stprintf(p, _T(",%c%d"), disasm_dreg, dreg); else movemout(p, regmask, dp->dmode, fpmode, true); } } else { if (fpuopcodes[ins]) { _tcscpy(instrname, fpuopcodes[ins]); } else { _stprintf(instrname, _T("%s?%02X"), disasm_lc_reg(_T("F")), ins); } disasm_lc_mnemo(instrname); if ((extra & (0x8000 | 0x4000 | 0x2000)) == (0x4000 | 0x2000)) { // FMOVE to memory/data register int kfactor = extra & 0x7f; _tcscpy(instrname, _T("FMOVE.")); _tcscat(instrname, fpsizes[size]); disasm_lc_mnemo(instrname); _tcscat(instrname, _T(" ")); p = instrname + _tcslen(instrname); _stprintf(p, _T("%s%d,"), disasm_fpreg, (extra >> 7) & 7); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, fpsizeconv[size], instrname, &deaddr2, &actualea_dst, safemode); p = instrname + _tcslen(instrname); if (size == 7) { _stprintf(p, _T(" {%c%d}"), disasm_dreg, (kfactor >> 4)); } else if (kfactor) { if (kfactor & 0x40) kfactor |= ~0x3f; _stprintf(p, _T(" {%d}"), kfactor); } } else if ((extra & (0x8000 | 0x2000)) == 0) { if (extra & 0x4000) { // source is EA _tcscat(instrname, _T(".")); _tcscat(instrname, disasm_lc_size(fpsizes[size])); _tcscat(instrname, _T(" ")); pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, fpsizeconv[size], instrname, &seaddr2, &actualea_src, safemode); } else { // source is FPx p = instrname + _tcslen(instrname); _tcscat(p, disasm_lc_reg(_T(".X"))); p = instrname + _tcslen(instrname); _stprintf(p, _T(" %s%d"), disasm_fpreg, (extra >> 10) & 7); } p = instrname + _tcslen(instrname); if (ins >= 0x30 && ins < 0x38) { // FSINCOS p = instrname + _tcslen(instrname); _stprintf(p, _T(",%s%d"), disasm_fpreg, extra & 7); p = instrname + _tcslen(instrname); _stprintf(p, _T(",%s%d"), disasm_fpreg, (extra >> 7) & 7); } else { if ((extra & 0x4000) || (((extra >> 7) & 7) != ((extra >> 10) & 7))) { _stprintf(p, _T(",%s%d"), disasm_fpreg, (extra >> 7) & 7); } } } if (ins >= 0x40 && currprefs.fpu_model >= 68881 && fpuopcodes[ins]) { _tcscat(instrname, _T(" (68040+)")); } } } else if (lookup->mnemo == i_MMUOP030) { pc = disasm_mmu030(pc, opcode, extra, dp, instrname, &seaddr2, &actualea_src, safemode); } else if ((opcode & 0xf000) == 0xa000) { _tcscpy(instrname, _T("A-LINE")); disasm_lc_mnemo(instrname); } else { if (lookup->mnemo == i_FBcc && (opcode & 0x1f) == 0 && extra == 0) { _tcscpy(instrname, _T("FNOP")); disasm_lc_mnemo(instrname); add_disasm_word(&pc, &bufpc, &bufpcsize, 2); } else { if (dp->suse) { pc = ShowEA(NULL, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, &seaddr2, &actualea_src, safemode); // JSR x(a6) / JMP x(a6) if (opcode == 0x4ea8 + 6 || opcode == 0x4ee8 + 6) { TCHAR sname[256]; if (debugger_get_library_symbol(m68k_areg(regs, 6), 0xffff0000 | extra, sname)) { TCHAR *p = instrname + _tcslen(instrname); _stprintf(p, _T(" %s"), sname); resolve_if_jmp(instrname, m68k_areg(regs, 6) + (uae_s16)extra); } } // show target address if JSR x(pc) + JMP xxxx combination if (opcode == 0x4eba && seaddr2 && instrname[0]) { // JSR x(pc) resolve_if_jmp(instrname, seaddr2); } } if (dp->suse && dp->duse) _tcscat(instrname, _T(",")); if (dp->duse) { pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode); } if (lookup->mnemo == i_RTS || lookup->mnemo == i_RTD || lookup->mnemo == i_RTR || lookup->mnemo == i_RTE) { uaecptr a = regs.regs[15]; TCHAR eas[100]; eas[0] = 0; if (lookup->mnemo == i_RTE || lookup->mnemo == i_RTR) { a += 2; } if (disasm_flags & DISASM_FLAG_EA) { _stprintf(eas, disasm_lc_hex(_T(" == $%08X")), get_ilong_debug(a)); } _tcscat(instrname, eas); } } } if (disasm_flags & DISASM_FLAG_WORDS) { for (i = 0; i < (pc - oldpc) / 2 && i < disasm_max_words; i++) { buf = buf_out(buf, &bufsize, disasm_lc_nhex(_T("%04X ")), get_word_debug(oldpc + i * 2)); } while (i++ < disasm_min_words) { buf = buf_out(buf, &bufsize, _T(" ")); } } if (illegal) buf = buf_out (buf, &bufsize, _T("[ ")); buf = buf_out (buf, &bufsize, instrname); if (illegal) buf = buf_out (buf, &bufsize, _T(" ]")); if (ccpt != 0) { uaecptr addr2 = deaddr2 != 0xffffffff ? deaddr2 : seaddr2; if (deaddr) *deaddr = pc; if ((opcode & 0xf000) == 0xf000) { if (currprefs.fpu_model) { if (disasm_flags & DISASM_FLAG_EA) { buf = buf_out(buf, &bufsize, disasm_lc_hex(_T(" == $%08X")), addr2); } if (disasm_flags & DISASM_FLAG_CC) { if (fpp_cond(dp->cc)) { buf = buf_out(buf, &bufsize, _T(" (T)")); } else { buf = buf_out(buf, &bufsize, _T(" (F)")); } } } } else { if (dp->mnemo == i_Bcc || dp->mnemo == i_DBcc) { if (disasm_flags & DISASM_FLAG_EA) { buf = buf_out(buf, &bufsize, disasm_lc_hex(_T(" == $%08X")), addr2); } if (disasm_flags & DISASM_FLAG_CC) { if (cctrue(dp->cc)) { buf = buf_out(buf, &bufsize, _T(" (T)")); } else { buf = buf_out(buf, &bufsize, _T(" (F)")); } } } else { if (disasm_flags & DISASM_FLAG_CC) { if (cctrue(dp->cc)) { buf = buf_out(buf, &bufsize, _T(" (T)")); } else { buf = buf_out(buf, &bufsize, _T(" (F)")); } } } } } else if ((opcode & 0xff00) == 0x6100) { /* BSR */ if (deaddr) *deaddr = pc; if (disasm_flags & DISASM_FLAG_EA) { buf = buf_out(buf, &bufsize, disasm_lc_hex(_T(" == $%08X")), seaddr2); } } #ifdef WINUAE_FOR_HATARI /* outputting only single disassembly line, and there's profile info? */ if (Profile_CpuAddr_HasData(oldpc)) { # define PROFILE_OUTPUT_COLUMN 68 # define SPACE_FOR_NEWLINE 3 int count = bufsize - (orig_size - PROFILE_OUTPUT_COLUMN); if (count > 0) { snprintf(buf, bufsize, "%*c", count, ' '); count = _tcslen(buf); bufsize -= count; buf += count; } Profile_CpuAddr_DataStr(buf, bufsize - SPACE_FOR_NEWLINE, oldpc); count = _tcslen(buf); bufsize -= count; buf += count; } #endif buf = buf_out (buf, &bufsize, _T("\n")); for (uaecptr segpc = oldpc; segpc < pc; segpc++) { TCHAR segout[256]; if (debugmem_get_symbol(segpc, segout, sizeof(segout) / sizeof(TCHAR))) { _tcscat(segout, _T(":\n")); if (bufsize > uaetcslen(segout)) { memmove(symbolpos + uaetcslen(segout), symbolpos, (uaetcslen(symbolpos) + 1) * sizeof(TCHAR)); memcpy(symbolpos, segout, uaetcslen(segout) * sizeof(TCHAR)); bufsize -= uaetcslen(segout); buf += uaetcslen(segout); symbolpos += uaetcslen(segout); } } } int srcline = -1; for (uaecptr segpc = oldpc; segpc < pc; segpc++) { TCHAR sourceout[256]; int line = debugmem_get_sourceline(segpc, sourceout, sizeof(sourceout) / sizeof(TCHAR)); if (line < 0) break; if (srcline != line) { if (srcline < 0) buf = buf_out(buf, &bufsize, _T("\n")); buf = buf_out(buf, &bufsize, sourceout); srcline = line; } } if (srcline >= 0) { buf = buf_out(buf, &bufsize, _T("\n")); } if (illegal > 0) pc = m68kpc_illg; } if (nextpc) *nextpc = pc; if (seaddr) *seaddr = seaddr2; if (deaddr) *deaddr = deaddr2; return (actualea_src ? 1 : 0) | (actualea_dst ? 2 : 0); } /************************************************************* Disasm the m68kcode at the given address into instrname and instrcode *************************************************************/ void sm68k_disasm (TCHAR *instrname, TCHAR *instrcode, uaecptr addr, uaecptr *nextpc, uaecptr lastpc) { TCHAR *ccpt; uae_u32 opcode; struct mnemolookup *lookup; struct instr *dp; uaecptr pc, oldpc; pc = oldpc = addr; opcode = get_word_debug (pc); if (cpufunctbl[opcode] == op_illg_1) { opcode = 0x4AFC; } dp = table68k + opcode; for (lookup = lookuptab;lookup->mnemo != dp->mnemo; lookup++); pc += 2; _tcscpy (instrname, lookup->name); ccpt = _tcsstr (instrname, _T("cc")); if (ccpt != 0) { _tcsncpy (ccpt, ccnames[dp->cc], 2); } switch (dp->size){ case sz_byte: _tcscat (instrname, _T(".B ")); break; case sz_word: _tcscat (instrname, _T(".W ")); break; case sz_long: _tcscat (instrname, _T(".L ")); break; default: _tcscat (instrname, _T(" ")); break; } if (dp->suse) { pc = ShowEA (0, pc, opcode, dp->sreg, dp->smode, dp->size, instrname, NULL, NULL, 0); } if (dp->suse && dp->duse) _tcscat (instrname, _T(",")); if (dp->duse) { pc = ShowEA (0, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, NULL, NULL, 0); } if (instrcode) { int i; for (i = 0; i < (pc - oldpc) / 2; i++) { _stprintf (instrcode, _T("%04x "), get_iword_debug (oldpc + i * 2)); instrcode += _tcslen (instrcode); } } if (nextpc) *nextpc = pc; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/disasm.h000066400000000000000000000023551504763705000235550ustar00rootroot00000000000000 uaecptr ShowEA(void *f, uaecptr pc, uae_u16 opcode, int reg, amodes mode, wordsizes size, TCHAR *buf, uae_u32 *eaddr, int *actualea, int safemode); uaecptr ShowEA_disp(uaecptr *pcp, uaecptr base, TCHAR *buffer, const TCHAR *name, bool pcrel); uae_u32 m68k_disasm_2(TCHAR *buf, int bufsize, uaecptr pc, uae_u16 *bufpc, int bufpccount, uaecptr *nextpc, int cnt, uae_u32 *seaddr, uae_u32 *deaddr, uaecptr lastpc, int safemode); void sm68k_disasm(TCHAR *instrname, TCHAR *instrcode, uaecptr addr, uaecptr *nextpc, uaecptr lastpc); uae_u32 REGPARAM2 op_illg_1(uae_u32 opcode); void REGPARAM2 op_illg_1_noret(uae_u32 opcode); uae_u32 REGPARAM2 op_unimpl_1(uae_u32 opcode); void REGPARAM2 op_unimpl_1_noret(uae_u32 opcode); void disasm_init(void); extern struct cpum2c m2cregs[]; extern const TCHAR *fpuopcodes[]; extern const TCHAR *fpsizes[]; extern int disasm_flags; extern int disasm_min_words; extern int disasm_max_words; extern TCHAR disasm_hexprefix[3]; #define DISASM_FLAG_LC_MNEMO 1 #define DISASM_FLAG_LC_REG 2 #define DISASM_FLAG_LC_HEX 8 #define DISASM_FLAG_LC_SIZE 16 #define DISASM_FLAG_CC 32 #define DISASM_FLAG_EA 64 #define DISASM_FLAG_VAL 128 #define DISASM_FLAG_WORDS 256 #define DISASM_FLAG_ABSSHORTLONG 512 #define DISASM_FLAG_VAL_FORCE 1024 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/events.c000066400000000000000000000024101504763705000235640ustar00rootroot00000000000000/* * events.c * * Event stuff - currently just simplified to the bare minimum * in Hatari, but we might want to extend this one day... */ #include "main.h" #include "sysconfig.h" #include "sysdeps.h" #include "options_cpu.h" #include "events.h" #include "newcpu.h" #ifndef WINUAE_FOR_HATARI void do_cycles_normal(int cycles_to_add) { while ((nextevent - currcycle) <= cycles_to_add) { cycles_to_add -= (int)(nextevent - currcycle); currcycle = nextevent; for (int i = 0; i < ev_max; i++) { if (eventtab[i].active && eventtab[i].evtime == currcycle) { if (eventtab[i].handler == NULL) { gui_message(_T("eventtab[%d].handler is null!\n"), i); eventtab[i].active = 0; } else { (*eventtab[i].handler)(); } } } events_schedule(); } currcycle += cycles_to_add; } #else /* Simplified version for Hatari, we don't use eventtab[] */ void do_cycles_normal(int cycles_to_add) { //fprintf ( stderr , " do_cycles_normal add=%d curr=%d -> new=%d\n" , cycles_to_add , currcycle , currcycle+cycles_to_add ); currcycle += cycles_to_add; } #endif void do_cycles_slow (int cycles_to_add) { //fprintf ( stderr , " do_cycles_slow add=%d curr=%d -> new=%d\n" , cycles_to_add , currcycle , currcycle+cycles_to_add ); currcycle += cycles_to_add; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/events.h000066400000000000000000000071551504763705000236040ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator * * Events * These are best for low-frequency events. Having too many of them, * or using them for events that occur too frequently, can cause massive * slowdown. * * Copyright 1995-1998 Bernd Schmidt */ #ifndef UAE_EVENTS_H #define UAE_EVENTS_H #include "uae/types.h" #undef EVENT_DEBUG #include "machdep/rpt.h" #define EVT_MAX 0x7fffffffffffffff extern frame_time_t vsyncmintime, vsyncmintimepre; extern frame_time_t vsyncmaxtime, vsyncwaittime; extern frame_time_t vsynctimebase, syncbase; extern void reset_frame_rate_hack(void); extern evt_t vsync_cycles; extern evt_t start_cycles; extern int event2_count; extern bool event_wait; extern void compute_vsynctime(void); extern void init_eventtab(void); extern void do_cycles_ce(int cycles); #ifdef WINUAE_FOR_HATARI extern void do_cycles_ce_hatari_blitter (int cycles); #endif extern void do_cycles_ce020(int cycles); extern void events_schedule(void); extern void do_cycles_slow(int cycles_to_add); extern void do_cycles_normal(int cycles_to_add); extern void events_reset_syncline(void); extern void modify_eventcounter(int diff); extern void clear_events(void); extern bool is_cycle_ce(uaecptr); extern evt_t currcycle, nextevent; extern uae_u32 currcycle_cck; extern int is_syncline; extern evt_t is_syncline_end; typedef void (*evfunc)(void); typedef void (*evfunc2)(uae_u32); struct ev { bool active; evt_t evtime, oldcycles; evfunc handler; }; struct ev2 { bool active; evt_t evtime; uae_u32 data; evfunc2 handler; }; enum { ev_sync, ev_cia, ev_misc, ev_audio, ev_max }; enum { ev2_blitter, ev2_misc, ev2_max = 8 }; extern int do_cycles_cck(int); #define do_cycles do_cycles_slow extern struct ev eventtab[ev_max]; extern struct ev2 eventtab2[ev2_max]; extern int maxhpos; extern int custom_fastmode; STATIC_INLINE void cycles_do_special (void) { /* Currently unused in Hatari */ } STATIC_INLINE void do_extra_cycles (int cycles_to_add) { /* Currently unused in Hatari */ } STATIC_INLINE evt_t get_cycles(void) { return currcycle; } STATIC_INLINE uae_u32 get_cck_cycles(void) { return currcycle_cck; } STATIC_INLINE void set_cycles (evt_t x) { currcycle = x; #ifdef EVT_DEBUG if (currcycle & (CYCLE_UNIT - 1)) write_log (_T("%x\n"), currcycle); #endif } STATIC_INLINE uae_u8 current_hpos_safe(void) { extern uae_u8 agnus_hpos; return agnus_hpos; } STATIC_INLINE uae_u8 current_hpos(void) { uae_u8 hp = current_hpos_safe(); return hp; } extern uae_u8 current_hpos(void); STATIC_INLINE bool cycles_in_range(evt_t endcycles) { evt_t c = get_cycles(); return endcycles > c; } extern void MISC_handler(void); extern void event2_newevent_xx(int no, evt_t t, uae_u32 data, evfunc2 func); extern void event2_newevent_x_replace(evt_t t, uae_u32 data, evfunc2 func); extern void event2_newevent_x_replace_exists(evt_t t, uae_u32 data, evfunc2 func); extern void event2_newevent_x_remove(evfunc2 func); STATIC_INLINE void event2_newevent_x(int no, evt_t t, uae_u32 data, evfunc2 func) { if (t <= 0) { func(data); return; } event2_newevent_xx(no, t * CYCLE_UNIT, data, func); } STATIC_INLINE void event2_newevent(int no, evt_t t, uae_u32 data) { event2_newevent_x(no, t, data, eventtab2[no].handler); } STATIC_INLINE void event2_newevent2(evt_t t, uae_u32 data, evfunc2 func) { event2_newevent_x(-1, t, data, func); } STATIC_INLINE void event2_remevent(int no) { eventtab2[no].active = 0; } void event_audxdat_func(uae_u32); void event_setdsr(uae_u32); void event_CIA_synced_interrupt(uae_u32); void event_CIA_tod_inc_event(uae_u32); void event_DISK_handler(uae_u32 data); #endif /* UAE_EVENTS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/fpp.c000066400000000000000000003074541504763705000230650ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68881/68882/68040/68060 FPU emulation * * Copyright 1996 Herman ten Brugge * Modified 2005 Peter Keunecke * 68040+ exceptions and more by Toni Wilen */ #define __USE_ISOC9X /* We might be able to pick up a NaN */ #define FPU_TEST 0 #define FPU_LOG 0 #include #include #include #include "sysconfig.h" #include "sysdeps.h" #ifdef WINUAE_FOR_HATARI #include "main.h" #include "hatari-glue.h" #include "log.h" #endif #include "options_cpu.h" #include "memory.h" #include "uae/attributes.h" #include "uae/vm.h" #include "custom.h" #include "events.h" #include "newcpu.h" #include "fpp.h" #include "savestate.h" #include "cpu_prefetch.h" #include "cpummu.h" #include "cpummu030.h" #include "debug.h" #ifndef CPU_TESTER #define SUPPORT_MMU 1 #else #define SUPPORT_MMU 0 extern void cputester_fault(void); #endif #ifdef WITH_SOFTFLOAT #include "softfloat/softfloat.h" #endif // global variable for JIT FPU #ifdef USE_LONG_DOUBLE bool use_long_double = true; #else bool use_long_double = false; #endif static bool support_exceptions; static bool support_denormals; static uae_u32 fpcr_mask, fpsr_mask; FPP_PRINT fpp_print; FPP_IS fpp_unset_snan; FPP_IS fpp_is_init; FPP_IS fpp_is_snan; FPP_IS fpp_is_nan; FPP_IS fpp_is_infinity; FPP_IS fpp_is_zero; FPP_IS fpp_is_neg; FPP_IS fpp_is_denormal; FPP_IS fpp_is_unnormal; FPP_A fpp_fix_infinity; FPP_GET_STATUS fpp_get_status; FPP_CLEAR_STATUS fpp_clear_status; FPP_SET_MODE fpp_set_mode; FPP_SUPPORT_FLAGS fpp_get_support_flags; FPP_FROM_NATIVE fpp_from_native; FPP_TO_NATIVE fpp_to_native; FPP_TO_INT fpp_to_int; FPP_FROM_INT fpp_from_int; FPP_PACK fpp_to_pack; FPP_PACK fpp_from_pack; FPP_TO_SINGLE fpp_to_single; FPP_FROM_SINGLE fpp_from_single; FPP_TO_DOUBLE fpp_to_double; FPP_FROM_DOUBLE fpp_from_double; FPP_TO_EXTEN fpp_to_exten; FPP_FROM_EXTEN fpp_from_exten; FPP_TO_EXTEN fpp_to_exten_fmovem; FPP_FROM_EXTEN fpp_from_exten_fmovem; FPP_A fpp_normalize; FPP_DENORMALIZE fpp_denormalize; FPP_A fpp_get_internal_overflow; FPP_A fpp_get_internal_underflow; FPP_A fpp_get_internal_round_all; FPP_A fpp_get_internal_round; FPP_A fpp_get_internal_round_exten; FPP_A fpp_get_internal; FPP_GET32 fpp_get_internal_grs; FPP_A fpp_round_single; FPP_A fpp_round_double; FPP_A fpp_round32; FPP_A fpp_round64; FPP_AB fpp_int; FPP_AB fpp_sinh; FPP_AB fpp_intrz; FPP_ABP fpp_sqrt; FPP_AB fpp_lognp1; FPP_AB fpp_etoxm1; FPP_AB fpp_tanh; FPP_AB fpp_atan; FPP_AB fpp_atanh; FPP_AB fpp_sin; FPP_AB fpp_asin; FPP_AB fpp_tan; FPP_AB fpp_etox; FPP_AB fpp_twotox; FPP_AB fpp_tentox; FPP_AB fpp_logn; FPP_AB fpp_log10; FPP_AB fpp_log2; FPP_ABP fpp_abs; FPP_AB fpp_cosh; FPP_ABP fpp_neg; FPP_AB fpp_acos; FPP_AB fpp_cos; FPP_ABC fpp_sincos; FPP_AB fpp_getexp; FPP_AB fpp_getman; FPP_ABP fpp_div; FPP_ABQS fpp_mod; FPP_ABP fpp_add; FPP_ABP fpp_mul; FPP_ABQS fpp_rem; FPP_AB fpp_scale; FPP_ABP fpp_sub; FPP_AB fpp_sgldiv; FPP_AB fpp_sglmul; FPP_AB fpp_cmp; FPP_AB fpp_tst; FPP_ABP fpp_move; #define DEBUG_FPP 0 #define EXCEPTION_FPP 0 STATIC_INLINE int isinrom (void) { return (munge24 (m68k_getpc ()) & 0xFFF80000) == 0xF80000 && !currprefs.mmu_model; } static bool jit_fpu(void) { return currprefs.cachesize && currprefs.compfpu; } static int warned = 100; struct fpp_cr_entry { uae_u32 val[3]; uae_u8 inexact; uae_s8 rndoff[4]; }; static const struct fpp_cr_entry fpp_cr[22] = { { {0x40000000, 0xc90fdaa2, 0x2168c235}, 1, {0,-1,-1, 0} }, // 0 = pi { {0x3ffd0000, 0x9a209a84, 0xfbcff798}, 1, {0, 0, 0, 1} }, // 1 = log10(2) { {0x40000000, 0xadf85458, 0xa2bb4a9a}, 1, {0, 0, 0, 1} }, // 2 = e { {0x3fff0000, 0xb8aa3b29, 0x5c17f0bc}, 1, {0,-1,-1, 0} }, // 3 = log2(e) { {0x3ffd0000, 0xde5bd8a9, 0x37287195}, 0, {0, 0, 0, 0} }, // 4 = log10(e) { {0x00000000, 0x00000000, 0x00000000}, 0, {0, 0, 0, 0} }, // 5 = 0.0 { {0x3ffe0000, 0xb17217f7, 0xd1cf79ac}, 1, {0,-1,-1, 0} }, // 6 = ln(2) { {0x40000000, 0x935d8ddd, 0xaaa8ac17}, 1, {0,-1,-1, 0} }, // 7 = ln(10) { {0x3fff0000, 0x80000000, 0x00000000}, 0, {0, 0, 0, 0} }, // 8 = 1e0 { {0x40020000, 0xa0000000, 0x00000000}, 0, {0, 0, 0, 0} }, // 9 = 1e1 { {0x40050000, 0xc8000000, 0x00000000}, 0, {0, 0, 0, 0} }, // 10 = 1e2 { {0x400c0000, 0x9c400000, 0x00000000}, 0, {0, 0, 0, 0} }, // 11 = 1e4 { {0x40190000, 0xbebc2000, 0x00000000}, 0, {0, 0, 0, 0} }, // 12 = 1e8 { {0x40340000, 0x8e1bc9bf, 0x04000000}, 0, {0, 0, 0, 0} }, // 13 = 1e16 { {0x40690000, 0x9dc5ada8, 0x2b70b59e}, 1, {0,-1,-1, 0} }, // 14 = 1e32 { {0x40d30000, 0xc2781f49, 0xffcfa6d5}, 1, {0, 0, 0, 1} }, // 15 = 1e64 { {0x41a80000, 0x93ba47c9, 0x80e98ce0}, 1, {0,-1,-1, 0} }, // 16 = 1e128 { {0x43510000, 0xaa7eebfb, 0x9df9de8e}, 1, {0,-1,-1, 0} }, // 17 = 1e256 { {0x46a30000, 0xe319a0ae, 0xa60e91c7}, 1, {0,-1,-1, 0} }, // 18 = 1e512 { {0x4d480000, 0xc9767586, 0x81750c17}, 1, {0, 0, 0, 1} }, // 19 = 1e1024 { {0x5a920000, 0x9e8b3b5d, 0xc53d5de5}, 1, {0,-1,-1, 0} }, // 20 = 1e2048 { {0x75250000, 0xc4605202, 0x8a20979b}, 1, {0,-1,-1, 0} } // 21 = 1e4094 }; #define FPP_CR_PI 0 #define FPP_CR_LOG10_2 1 #define FPP_CR_E 2 #define FPP_CR_LOG2_E 3 #define FPP_CR_LOG10_E 4 #define FPP_CR_ZERO 5 #define FPP_CR_LN_2 6 #define FPP_CR_LN_10 7 #define FPP_CR_1E0 8 #define FPP_CR_1E1 9 #define FPP_CR_1E2 10 #define FPP_CR_1E4 11 #define FPP_CR_1E8 12 #define FPP_CR_1E16 13 #define FPP_CR_1E32 14 #define FPP_CR_1E64 15 #define FPP_CR_1E128 16 #define FPP_CR_1E256 17 #define FPP_CR_1E512 18 #define FPP_CR_1E1024 19 #define FPP_CR_1E2048 20 #define FPP_CR_1E4096 21 struct fpp_cr_entry_undef { uae_u32 val[3]; }; #define FPP_CR_NUM_SPECIAL_UNDEFINED 10 // 68881 and 68882 have identical undefined fields static const struct fpp_cr_entry_undef fpp_cr_undef[] = { { {0x40000000, 0x00000000, 0x00000000} }, { {0x40010000, 0xfe000682, 0x00000000} }, { {0x40010000, 0xffc00503, 0x80000000} }, { {0x20000000, 0x7fffffff, 0x00000000} }, { {0x00000000, 0xffffffff, 0xffffffff} }, { {0x3c000000, 0xffffffff, 0xfffff800} }, { {0x3f800000, 0xffffff00, 0x00000000} }, { {0x00010000, 0xf65d8d9c, 0x00000000} }, { {0x7fff0000, 0x001e0000, 0x00000000} }, { {0x43ff0000, 0x000e0000, 0x00000000} }, { {0x407f0000, 0x00060000, 0x00000000} } }; uae_u32 xhex_nan[] ={0x7fff0000, 0xffffffff, 0xffffffff}; static bool fpu_mmu_fixup; /* Floating Point Control Register (FPCR) * * Exception Enable Byte * x--- ---- ---- ---- bit 15: BSUN (branch/set on unordered) * -x-- ---- ---- ---- bit 14: SNAN (signaling not a number) * --x- ---- ---- ---- bit 13: OPERR (operand error) * ---x ---- ---- ---- bit 12: OVFL (overflow) * ---- x--- ---- ---- bit 11: UNFL (underflow) * ---- -x-- ---- ---- bit 10: DZ (divide by zero) * ---- --x- ---- ---- bit 9: INEX 2 (inexact operation) * ---- ---x ---- ---- bit 8: INEX 1 (inexact decimal input) * * Mode Control Byte * ---- ---- xx-- ---- bits 7 and 6: PREC (rounding precision) * ---- ---- --xx ---- bits 5 and 4: RND (rounding mode) * ---- ---- ---- xxxx bits 3 to 0: all 0 */ #define FPCR_PREC 0x00C0 #define FPCR_RND 0x0030 /* Floating Point Status Register (FPSR) * * Condition Code Byte * xxxx ---- ---- ---- ---- ---- ---- ---- bits 31 to 28: all 0 * ---- x--- ---- ---- ---- ---- ---- ---- bit 27: N (negative) * ---- -x-- ---- ---- ---- ---- ---- ---- bit 26: Z (zero) * ---- --x- ---- ---- ---- ---- ---- ---- bit 25: I (infinity) * ---- ---x ---- ---- ---- ---- ---- ---- bit 24: NAN (not a number or unordered) * * Quotient Byte (set and reset only by FMOD and FREM) * ---- ---- x--- ---- ---- ---- ---- ---- bit 23: sign of quotient * ---- ---- -xxx xxxx ---- ---- ---- ---- bits 22 to 16: 7 least significant bits of quotient * * Exception Status Byte * ---- ---- ---- ---- x--- ---- ---- ---- bit 15: BSUN (branch/set on unordered) * ---- ---- ---- ---- -x-- ---- ---- ---- bit 14: SNAN (signaling not a number) * ---- ---- ---- ---- --x- ---- ---- ---- bit 13: OPERR (operand error) * ---- ---- ---- ---- ---x ---- ---- ---- bit 12: OVFL (overflow) * ---- ---- ---- ---- ---- x--- ---- ---- bit 11: UNFL (underflow) * ---- ---- ---- ---- ---- -x-- ---- ---- bit 10: DZ (divide by zero) * ---- ---- ---- ---- ---- --x- ---- ---- bit 9: INEX 2 (inexact operation) * ---- ---- ---- ---- ---- ---x ---- ---- bit 8: INEX 1 (inexact decimal input) * * Accrued Exception Byte * ---- ---- ---- ---- ---- ---- x--- ---- bit 7: IOP (invalid operation) * ---- ---- ---- ---- ---- ---- -x-- ---- bit 6: OVFL (overflow) * ---- ---- ---- ---- ---- ---- --x- ---- bit 5: UNFL (underflow) * ---- ---- ---- ---- ---- ---- ---x ---- bit 4: DZ (divide by zero) * ---- ---- ---- ---- ---- ---- ---- x--- bit 3: INEX (inexact) * ---- ---- ---- ---- ---- ---- ---- -xxx bits 2 to 0: all 0 */ #define FPSR_ZEROBITS 0xF0000007 #define FPSR_CC_N 0x08000000 #define FPSR_CC_Z 0x04000000 #define FPSR_CC_I 0x02000000 #define FPSR_CC_NAN 0x01000000 #define FPSR_QUOT_SIGN 0x00800000 #define FPSR_QUOT_LSB 0x007F0000 #define FPSR_AE_IOP 0x00000080 #define FPSR_AE_OVFL 0x00000040 #define FPSR_AE_UNFL 0x00000020 #define FPSR_AE_DZ 0x00000010 #define FPSR_AE_INEX 0x00000008 static struct { // 6888x and 68060 uae_u32 ccr; uae_u32 eo[3]; // 68060 uae_u32 v; // 68040 uae_u32 fpiarcu; uae_u32 cmdreg3b; uae_u32 cmdreg1b; uae_u32 stag, dtag; uae_u32 e1, e3, t; uae_u32 fpt[3]; uae_u32 et[3]; uae_u32 wbt[3]; uae_u32 grs; uae_u32 wbte15; uae_u32 wbtm66; } fsave_data; static void reset_fsave_data(void) { int i; for (i = 0; i < 3; i++) { fsave_data.eo[i] = 0; fsave_data.fpt[i] = 0; fsave_data.et[i] = 0; fsave_data.wbt[i] = 0; } fsave_data.ccr = 0; fsave_data.v = 0; fsave_data.fpiarcu = 0; fsave_data.cmdreg1b = 0; fsave_data.cmdreg3b = 0; fsave_data.stag = 0; fsave_data.dtag = 0; fsave_data.e1 = 0; fsave_data.e3 = 0; fsave_data.t = 0; fsave_data.wbte15 = 0; fsave_data.wbtm66 = 0; fsave_data.grs = 0; } static uae_u32 get_ftag(fpdata *src, int size) { fpp_is_init(src); if (fpp_is_zero(src)) { return 1; // ZERO } else if (fpp_is_unnormal(src) || fpp_is_denormal(src)) { if (size == 1 || size == 5) return 5; // Single/double DENORMAL return 4; // Extended DENORMAL or UNNORMAL } else if (fpp_is_nan(src)) { return 3; // NAN } else if (fpp_is_infinity(src)) { return 2; // INF } return 0; // NORMAL } STATIC_INLINE bool fp_is_dyadic(uae_u16 extra) { return ((extra & 0x30) == 0x20 || (extra & 0x7f) == 0x38); } static bool fp_exception_pending(bool pre) { // first check for pending arithmetic exceptions if (support_exceptions && !jit_fpu()) { if (regs.fp_exp_pend) { if (warned > 0) { write_log(_T("FPU ARITHMETIC EXCEPTION (%d) PC=%08x\n"), regs.fp_exp_pend, regs.instruction_pc); } regs.fpu_exp_pre = pre; Exception(regs.fp_exp_pend); if (currprefs.fpu_model != 68882) regs.fp_exp_pend = 0; return true; } } // no arithmetic exceptions pending, check for unimplemented datatype if (regs.fp_unimp_pend) { if (warned > 0) { write_log(_T("FPU unimplemented datatype exception (%s) PC=%08x\n"), pre ? _T("pre") : _T("mid/post"), regs.instruction_pc); } if (currprefs.cpu_model == 68060 && fpu_mmu_fixup) { m68k_areg(regs, mmufixup[0].reg) = mmufixup[0].value; mmufixup[0].reg = -1; } regs.fpu_exp_pre = pre; Exception(55); regs.fp_unimp_pend = 0; return true; } return false; } static void fp_unimp_instruction_exception_pending(void) { if (regs.fp_unimp_ins) { if (warned > 0) { write_log(_T("FPU UNIMPLEMENTED INSTRUCTION/FPU DISABLED EXCEPTION PC=%08x\n"), M68K_GETPC); } if (currprefs.cpu_model == 68060 && fpu_mmu_fixup) { m68k_areg(regs, mmufixup[0].reg) = mmufixup[0].value; mmufixup[0].reg = -1; } regs.fpu_exp_pre = true; Exception(11); regs.fp_unimp_ins = false; regs.fp_unimp_pend = 0; } } void fpsr_set_exception(uae_u32 exception) { regs.fpsr |= exception; } static uae_u32 fpsr_get_vector(uae_u32 exception) { static const int vtable[8] = { 49, 49, 50, 51, 53, 52, 54, 48 }; int i; exception >>= 8; for (i = 7; i >= 0; i--) { if (exception & (1 << i)) { return vtable[i]; } } return 0; } static bool fpsr_check_arithmetic_exception(uae_u32 mask, fpdata *src, uae_u32 opcode, uae_u16 extra, uae_u32 ea, bool easet, uaecptr oldpc) { if (!support_exceptions || jit_fpu()) return false; uae_u32 exception; // Any exception status bit and matching exception enable bits set? exception = regs.fpsr & regs.fpcr & 0xff00; // Add 68040/68060 nonmaskable exceptions. Only if no unimplemented instruction emulation. if (currprefs.cpu_model >= 68040 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { exception |= regs.fpsr & (FPSR_OVFL | FPSR_UNFL | mask); } if (exception) { regs.fp_exp_pend = fpsr_get_vector(exception); bool nonmaskable = (regs.fp_exp_pend != fpsr_get_vector(regs.fpsr & regs.fpcr)); if (warned > 0) { write_log(_T("FPU %s arithmetic exception pending: FPSR: %08x, FPCR: %04x (vector: %d) PC=%08x!\n"), nonmaskable ? _T("nonmaskable") : _T(""), regs.fpsr, regs.fpcr, regs.fp_exp_pend, regs.instruction_pc); #if EXCEPTION_FPP == 0 warned--; #endif } if (!support_exceptions || jit_fpu()) { // log message and exit regs.fp_exp_pend = 0; return false; } regs.fp_opword = opcode; regs.fp_ea = ea; regs.fp_ea_set = easet; if (oldpc != 0xffffffff) { regs.fpiar = oldpc; } // data for FSAVE stack frame fpdata eo; uae_u32 opclass = (extra >> 13) & 7; reset_fsave_data(); if (currprefs.fpu_model == 68881 || currprefs.fpu_model == 68882) { // fsave data for 68881 and 68882 if (opclass == 3) { // 011 fsave_data.ccr = ((uae_u32)extra << 16) | extra; } else { // 000 or 010 fsave_data.ccr = ((uae_u32)(opcode | 0x0080) << 16) | extra; } if (regs.fp_exp_pend == 54 || regs.fp_exp_pend == 52 || regs.fp_exp_pend == 50) { // SNAN, OPERR, DZ fpp_from_exten_fmovem(src, &fsave_data.eo[0], &fsave_data.eo[1], &fsave_data.eo[2]); if (regs.fp_exp_pend == 52 && opclass == 3) { // OPERR from move to integer or packed fsave_data.eo[0] &= 0x4fff0000; fsave_data.eo[1] = fsave_data.eo[2] = 0; } } else if (regs.fp_exp_pend == 53) { // OVFL fpp_get_internal_overflow(&eo); fpp_from_exten_fmovem(&eo, &fsave_data.eo[0], &fsave_data.eo[1], &fsave_data.eo[2]); } else if (regs.fp_exp_pend == 51) { // UNFL fpp_get_internal_underflow(&eo); fpp_from_exten_fmovem(&eo, &fsave_data.eo[0], &fsave_data.eo[1], &fsave_data.eo[2]); } // else INEX1, INEX2: do nothing } else if (currprefs.cpu_model == 68060) { // fsave data for 68060 regs.fpu_exp_state = 2; // 68060 EXCP frame fsave_data.v = regs.fp_exp_pend & 7; fpp_from_exten_fmovem(src, &fsave_data.eo[0], &fsave_data.eo[1], &fsave_data.eo[2]); } else { // fsave data for 68040 regs.fpu_exp_state = 1; // 68040 UNIMP frame uae_u32 reg = (extra >> 7) & 7; int size = (extra >> 10) & 7; fsave_data.fpiarcu = regs.fpiar; if (regs.fp_exp_pend == 54) { // SNAN (undocumented) fsave_data.wbte15 = 1; fsave_data.grs = 7; } else { fsave_data.grs = 1; } if (opclass == 3) { // OPCLASS 011 fsave_data.cmdreg1b = extra; fsave_data.e1 = 1; fsave_data.t = 1; fsave_data.wbte15 = (regs.fp_exp_pend == 51 || regs.fp_exp_pend == 54) ? 1 : 0; // UNFL, SNAN fpp_is_init(src); if (fpp_is_snan(src)) { fpp_unset_snan(src); } fpp_from_exten_fmovem(src, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); fsave_data.stag = get_ftag(src, -1); } else { // OPCLASS 000 and 010 fsave_data.cmdreg1b = extra; fsave_data.e1 = 1; fsave_data.wbte15 = (regs.fp_exp_pend == 54) ? 1 : 0; // SNAN (undocumented) if (regs.fp_exp_pend == 51 || regs.fp_exp_pend == 53 || regs.fp_exp_pend == 49) { // UNFL, OVFL, INEX if ((extra & 0x30) == 0x20 || (extra & 0x3f) == 0x04) { // FADD, FSUB, FMUL, FDIV, FSQRT regs.fpu_exp_state = 2; // 68040 BUSY frame fsave_data.e3 = 1; fsave_data.e1 = 0; fsave_data.cmdreg3b = (extra & 0x3C3) | ((extra & 0x038)>>1) | ((extra & 0x004)<<3); if (regs.fp_exp_pend == 51) { // UNFL fpp_get_internal(&eo); } else { // OVFL, INEX fpp_get_internal_round(&eo); } fsave_data.grs = fpp_get_internal_grs(); fpp_from_exten_fmovem(&eo, &fsave_data.wbt[0], &fsave_data.wbt[1], &fsave_data.wbt[2]); fsave_data.wbte15 = (regs.fp_exp_pend == 51) ? 1 : 0; // UNFL // src and dst is stored (undocumented) fpp_from_exten_fmovem(src, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); fsave_data.stag = get_ftag(src, (opclass == 0) ? -1 : size); if (fp_is_dyadic(extra)) { fpp_from_exten_fmovem(®s.fp[reg], &fsave_data.fpt[0], &fsave_data.fpt[1], &fsave_data.fpt[2]); fsave_data.dtag = get_ftag(®s.fp[reg], -1); } } else { // FMOVE to register, FABS, FNEG fpp_get_internal_round_exten(&eo); fsave_data.grs = fpp_get_internal_grs(); fpp_from_exten_fmovem(&eo, &fsave_data.fpt[0], &fsave_data.fpt[1], &fsave_data.fpt[2]); fpp_get_internal_round_all(&eo); // weird fpp_from_exten_fmovem(&eo, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); // undocumented fsave_data.stag = get_ftag(src, (opclass == 0) ? -1 : size); } } else { // SNAN, OPERR, DZ fpp_from_exten_fmovem(src, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); fsave_data.stag = get_ftag(src, (opclass == 0) ? -1 : size); if (fp_is_dyadic(extra)) { fpp_from_exten_fmovem(®s.fp[reg], &fsave_data.fpt[0], &fsave_data.fpt[1], &fsave_data.fpt[2]); fsave_data.dtag = get_ftag(®s.fp[reg], -1); } } } } return nonmaskable; } return false; } // Flag that is always set immediately. static void fpsr_set_result_always(fpdata *result) { #ifdef JIT regs.fp_result = *result; #endif regs.fpsr &= 0x00fffff8; // clear cc fpp_is_init(result); if (fpp_is_neg(result)) { regs.fpsr |= FPSR_CC_N; } } // Flags that are set if instruction didn't generate exception. static void fpsr_set_result(fpdata *result) { // condition code byte if (fpp_is_nan(result)) { regs.fpsr |= FPSR_CC_NAN; } else if (fpp_is_zero(result)) { regs.fpsr |= FPSR_CC_Z; } else if (fpp_is_infinity(result)) { regs.fpsr |= FPSR_CC_I; } } static void fpsr_clear_status(void) { // clear exception status byte only regs.fpsr &= 0x0fff00f8; // clear external status fpp_clear_status(); } static void updateaccrued(void) { // update accrued exception byte if (regs.fpsr & (FPSR_BSUN | FPSR_SNAN | FPSR_OPERR)) regs.fpsr |= FPSR_AE_IOP; // IOP = BSUN || SNAN || OPERR if ((regs.fpsr & FPSR_UNFL) && (regs.fpsr & FPSR_INEX2)) regs.fpsr |= FPSR_AE_UNFL; // UNFL = UNFL && INEX2 if (regs.fpsr & FPSR_DZ) regs.fpsr |= FPSR_AE_DZ; // DZ = DZ } static uae_u32 fpsr_make_status(void) { uae_u32 exception; // get external status fpp_get_status(®s.fpsr); if (regs.fpsr & FPSR_OVFL) regs.fpsr |= FPSR_AE_OVFL; // OVFL = OVFL if (regs.fpsr & (FPSR_OVFL | FPSR_INEX2 | FPSR_INEX1)) regs.fpsr |= FPSR_AE_INEX; // INEX = INEX1 || INEX2 || OVFL if (!support_exceptions || jit_fpu()) { updateaccrued(); return 0; } // return exceptions that interrupt calculation exception = regs.fpsr & regs.fpcr & (FPSR_SNAN | FPSR_OPERR | FPSR_DZ); updateaccrued(); if (currprefs.cpu_model >= 68040 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { exception |= regs.fpsr & (FPSR_OVFL | FPSR_UNFL); } return exception; } static int fpsr_set_bsun(void) { regs.fpsr |= FPSR_BSUN; regs.fpsr |= FPSR_AE_IOP; if (regs.fpcr & FPSR_BSUN) { write_log(_T("FPU exception: BSUN! (FPSR: %08x, FPCR: %08x)\n"), regs.fpsr, regs.fpcr); if (support_exceptions && !jit_fpu()) { regs.fp_exp_pend = fpsr_get_vector(FPSR_BSUN); fp_exception_pending(true); return 1; } } return 0; } static void fpsr_set_quotient(uae_u64 quot, uae_u8 sign) { regs.fpsr &= 0x0f00fff8; regs.fpsr |= (quot << 16) & FPSR_QUOT_LSB; regs.fpsr |= sign ? FPSR_QUOT_SIGN : 0; } static void fpsr_get_quotient(uae_u64 *quot, uae_u8 *sign) { *quot = (regs.fpsr & FPSR_QUOT_LSB) >> 16; *sign = (regs.fpsr & FPSR_QUOT_SIGN) ? 1 : 0; } uae_u32 fpp_get_fpsr (void) { #ifdef JIT if (currprefs.cachesize && currprefs.compfpu) { regs.fpsr &= 0x00fffff8; // clear cc fpp_is_init(®s.fp_result); if (fpp_is_nan(®s.fp_result)) { regs.fpsr |= FPSR_CC_NAN; } else if (fpp_is_zero(®s.fp_result)) { regs.fpsr |= FPSR_CC_Z; } else if (fpp_is_infinity(®s.fp_result)) { regs.fpsr |= FPSR_CC_I; } if (fpp_is_neg(®s.fp_result)) regs.fpsr |= FPSR_CC_N; } #endif return regs.fpsr & fpsr_mask; } uae_u32 fpp_get_fpcr(void) { return regs.fpcr & fpcr_mask; } void fpp_set_fpcr (uae_u32 val) { fpp_set_mode(val); regs.fpcr = val & fpcr_mask; } static void fpnan (fpdata *fpd) { fpp_to_exten(fpd, xhex_nan[0], xhex_nan[1], xhex_nan[2]); } static void fpclear (fpdata *fpd) { fpp_from_int(fpd, 0); } static void fpset (fpdata *fpd, uae_s32 val) { fpp_from_int(fpd, val); } void fpp_set_fpsr (uae_u32 val) { regs.fpsr = val & fpsr_mask; #ifdef JIT // check comment in fpp_cond if (currprefs.cachesize && currprefs.compfpu) { if (val & 0x01000000) fpnan(®s.fp_result); else if (val & 0x04000000) fpset(®s.fp_result, 0); else if (val & 0x08000000) fpset(®s.fp_result, -1); else fpset(®s.fp_result, 1); } #endif } static void maybe_set_fpiar(uaecptr oldpc) { // if any exception (except BSUN) is enabled: update FPIAR // 68040 or 68060: always update FPIAR if ((regs.fpcr & 0x00007f00) || currprefs.fpu_model == 68040 || currprefs.fpu_model == 68060) { regs.fpiar = oldpc; } } void fpp_set_fpiar(uae_u32 val) { regs.fpiar = val; } uae_u32 fpp_get_fpiar(void) { return regs.fpiar; } bool fpu_get_constant(fpdata *fpd, int cr) { uae_u32 f[3] = { 0, 0, 0 }; int entry = 0; bool round = true; int mode = (regs.fpcr >> 4) & 3; int prec = (regs.fpcr >> 6) & 3; switch (cr) { case 0x00: // pi entry = FPP_CR_PI; break; case 0x0b: // log10(2) entry = FPP_CR_LOG10_2; break; case 0x0c: // e entry = FPP_CR_E; break; case 0x0d: // log2(e) entry = FPP_CR_LOG2_E; break; case 0x0e: // log10(e) entry = FPP_CR_LOG10_E; break; case 0x0f: // 0.0 entry = FPP_CR_ZERO; break; case 0x30: // ln(2) entry = FPP_CR_LN_2; break; case 0x31: // ln(10) entry = FPP_CR_LN_10; break; case 0x32: // 1e0 entry = FPP_CR_1E0; break; case 0x33: // 1e1 entry = FPP_CR_1E1; break; case 0x34: // 1e2 entry = FPP_CR_1E2; break; case 0x35: // 1e4 entry = FPP_CR_1E4; break; case 0x36: // 1e8 entry = FPP_CR_1E8; break; case 0x37: // 1e16 entry = FPP_CR_1E16; break; case 0x38: // 1e32 entry = FPP_CR_1E32; break; case 0x39: // 1e64 entry = FPP_CR_1E64; break; case 0x3a: // 1e128 entry = FPP_CR_1E128; break; case 0x3b: // 1e256 entry = FPP_CR_1E256; break; case 0x3c: // 1e512 entry = FPP_CR_1E512; break; case 0x3d: // 1e1024 entry = FPP_CR_1E1024; break; case 0x3e: // 1e2048 entry = FPP_CR_1E2048; break; case 0x3f: // 1e4096 entry = FPP_CR_1E4096; break; default: // undefined { bool check_f1_adjust = false; int f1_adjust = 0; uae_u32 sr = 0; if (cr > FPP_CR_NUM_SPECIAL_UNDEFINED) { cr = 0; // Most undefined fields contain this } f[0] = fpp_cr_undef[cr].val[0]; f[1] = fpp_cr_undef[cr].val[1]; f[2] = fpp_cr_undef[cr].val[2]; // Rounding mode and precision works very strangely here.. switch (cr) { case 1: check_f1_adjust = true; break; case 2: if (prec == 1 && mode == 3) f1_adjust = -1; break; case 3: if (prec == 1 && (mode == 0 || mode == 3)) sr |= FPSR_CC_I; else sr |= FPSR_CC_NAN; break; case 7: sr |= FPSR_CC_NAN; check_f1_adjust = true; break; } if (check_f1_adjust) { if (prec == 1) { if (mode == 0) { f1_adjust = -1; } else if (mode == 1 || mode == 2) { f1_adjust = 1; } } } fpp_to_exten_fmovem(fpd, f[0], f[1], f[2]); if (prec == 1) fpp_round32(fpd); if (prec >= 2) fpp_round64(fpd); if (f1_adjust) { fpp_from_exten_fmovem(fpd, &f[0], &f[1], &f[2]); f[1] += f1_adjust * 0x80; fpp_to_exten_fmovem(fpd, f[0], f[1], f[2]); } fpsr_set_result_always(fpd); fpsr_set_result(fpd); regs.fpsr |= sr; return false; } } f[0] = fpp_cr[entry].val[0]; f[1] = fpp_cr[entry].val[1]; f[2] = fpp_cr[entry].val[2]; // if constant is inexact, set inexact bit and round // note: with valid constants, LSB never wraps if (fpp_cr[entry].inexact) { fpsr_set_exception(FPSR_INEX2); f[2] += fpp_cr[entry].rndoff[mode]; } fpp_to_exten_fmovem(fpd, f[0], f[1], f[2]); if (prec == 1) fpp_round32(fpd); if (prec >= 2) fpp_round64(fpd); fpsr_set_result_always(fpd); fpsr_set_result(fpd); return true; } #if 0 static void fpu_format_error (void) { uaecptr newpc; regs.t0 = regs.t1 = 0; MakeSR (); if (!regs.s) { regs.usp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.isp; } regs.s = 1; m68k_areg (regs, 7) -= 2; x_cp_put_long (m68k_areg (regs, 7), 0x0000 + 14 * 4); m68k_areg (regs, 7) -= 4; x_cp_put_long (m68k_areg (regs, 7), m68k_getpc ()); m68k_areg (regs, 7) -= 2; x_cp_put_long (m68k_areg (regs, 7), regs.sr); newpc = x_cp_get_long (regs.vbr + 14 * 4); m68k_setpc (newpc); #ifdef JIT set_special (SPCFLAG_END_COMPILE); #endif regs.fp_exception = true; } #endif static void fp_unimp_instruction(uae_u16 opcode, uae_u16 extra, uae_u32 ea, bool easet, uaecptr oldpc, fpdata *src, int reg, int size) { if ((extra & 0x7f) == 4) // FSQRT 4->5 extra |= 1; // data for fsave stack frame regs.fpu_exp_state = 1; // 68060 IDLE frame, 68040 UNIMP frame regs.fpiar = oldpc; if (currprefs.cpu_model == 68060) { // fsave data for 68060 reset_fsave_data(); } else if(currprefs.cpu_model == 68040) { // fsave data for 68040 fsave_data.fpiarcu = regs.fpiar; if (regs.fp_unimp_pend == 0) { // else data has been saved by fp_unimp_datatype reset_fsave_data(); fsave_data.cmdreg3b = (extra & 0x3C3) | ((extra & 0x038) >> 1) | ((extra & 0x004) << 3); fsave_data.cmdreg1b = extra; fpp_from_exten_fmovem(src, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); fsave_data.stag = get_ftag(src, size); if (reg >= 0) { fpp_from_exten_fmovem(®s.fp[reg], &fsave_data.fpt[0], &fsave_data.fpt[1], &fsave_data.fpt[2]); fsave_data.dtag = get_ftag(®s.fp[reg], -1); } } } if (warned > 0) { write_log(_T("FPU unimplemented instruction: OP=%04X-%04X SRC=%08X-%08X-%08X EA=%08X PC=%08X\n"), opcode, extra, fsave_data.et[0],fsave_data.et[1],fsave_data.et[2], ea, oldpc); #if EXCEPTION_FPP == 0 warned--; #endif } regs.fp_ea = ea; regs.fp_ea_set = easet; regs.fp_unimp_ins = true; fp_unimp_instruction_exception_pending(); regs.fp_exception = true; } static void fp_unimp_datatype(uae_u16 opcode, uae_u16 extra, uae_u32 ea, bool easet, uaecptr oldpc, fpdata *src, uae_u32 *packed, bool predenormal) { uae_u32 reg = (extra >> 7) & 7; uae_u32 size = (extra >> 10) & 7; uae_u32 opclass = (extra >> 13) & 7; regs.fp_opword = opcode; regs.fp_ea = ea; regs.fp_ea_set = easet; regs.fp_unimp_pend = packed ? 2 : (predenormal ? 3 : 1); regs.fpiar = oldpc; if((extra & 0x7f) == 4) // FSQRT 4->5 extra |= 1; // data for fsave stack frame reset_fsave_data(); regs.fpu_exp_state = 2; // 68060 EXCP frame, 68040 BUSY frame if (currprefs.cpu_model == 68060) { // fsave data for 68060 if (packed) { regs.fpu_exp_state = 1; // 68060 IDLE frame } else { fsave_data.v = 7; // vector & 0x7 fpp_from_exten_fmovem(src, &fsave_data.eo[0], &fsave_data.eo[1], &fsave_data.eo[2]); } } else if (currprefs.cpu_model == 68040) { // fsave data for 68040 fsave_data.cmdreg1b = extra; fsave_data.fpiarcu = regs.fpiar; if (packed) { fsave_data.e1 = 1; // used to distinguish packed operands } if (opclass == 3) { // OPCLASS 011 fsave_data.t = 1; fpp_from_exten_fmovem(src, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); fsave_data.stag = get_ftag(src, -1); fpp_from_exten_fmovem(src, &fsave_data.fpt[0], &fsave_data.fpt[1], &fsave_data.fpt[2]); // undocumented fsave_data.dtag = get_ftag(src, -1); // undocumented } else { // OPCLASS 000 and 010 if (packed) { fsave_data.fpt[2] = packed[0]; // yes, this is correct. fsave_data.fpt[1] = packed[1]; // undocumented fsave_data.et[1] = packed[1]; fsave_data.et[2] = packed[2]; fsave_data.stag = 7; // undocumented } else { fpp_from_exten_fmovem(src, &fsave_data.et[0], &fsave_data.et[1], &fsave_data.et[2]); fsave_data.stag = get_ftag(src, (opclass == 0) ? 0xffffffff : size); if (fsave_data.stag == 5) { fsave_data.et[0] = (size == 1) ? 0x3f800000 : 0x3c000000; // exponent for denormalized single and double } if (fp_is_dyadic(extra)) { fpp_from_exten_fmovem(®s.fp[reg], &fsave_data.fpt[0], &fsave_data.fpt[1], &fsave_data.fpt[2]); fsave_data.dtag = get_ftag(®s.fp[reg], -1); } } } } if (warned > 0) { write_log(_T("FPU unimplemented datatype (%s): OP=%04X-%04X SRC=%08X-%08X-%08X EA=%08X PC=%08X\n"), packed ? _T("packed") : _T("denormal"), opcode, extra, packed ? fsave_data.fpt[2] : fsave_data.et[0], fsave_data.et[1], fsave_data.et[2], ea, oldpc); #if EXCEPTION_FPP == 0 warned--; #endif } regs.fp_exception = true; } static void fpu_op_illg(uae_u16 opcode, uae_u32 ea, bool easet, uaecptr oldpc) { if ((currprefs.cpu_model == 68060 && (currprefs.fpu_model == 0 || (regs.pcr & 2))) || (currprefs.cpu_model == 68040 && currprefs.fpu_model == 0)) { regs.fp_unimp_ins = true; regs.fp_ea = ea; regs.fp_ea_set = easet; regs.fpiar = oldpc; fp_unimp_instruction_exception_pending(); return; } regs.fp_exception = true; m68k_setpc(oldpc); op_illg(opcode); } static void fpu_noinst(uae_u16 opcode, uaecptr pc) { #if EXCEPTION_FPP write_log(_T("Unknown FPU instruction %04X %08X\n"), opcode, pc); #endif regs.fp_exception = true; m68k_setpc(pc); op_illg(opcode); } static bool if_no_fpu(void) { return (regs.pcr & 2) || currprefs.fpu_model <= 0; } static bool fault_if_no_fpu(uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc) { if (if_no_fpu()) { #if EXCEPTION_FPP write_log(_T("no FPU: %04X-%04X PC=%08X\n"), opcode, extra, oldpc); #endif if (fpu_mmu_fixup) { m68k_areg (regs, mmufixup[0].reg) = mmufixup[0].value; mmufixup[0].reg = -1; fpu_mmu_fixup = false; } fpu_op_illg(opcode, ea, easet, oldpc); return true; } return false; } static bool fault_if_nonexisting_opmode(uae_u16 opcode, uae_u16 extra, uaecptr oldpc) { uae_u16 v = extra & 0x7f; // if non-existing FPU instruction (opmode): exit immediately and generate normal frame 0 exception 11. if (currprefs.fpu_model == 68881 || currprefs.fpu_model == 68882) { if (currprefs.fpu_no_unimplemented) { if (v >= 0x40) { fpu_noinst(opcode, oldpc); return true; } return false; } // 6888x undocumented but existing opmodes switch (v) { case 0x05: case 0x07: case 0x0b: case 0x13: case 0x17: case 0x1b: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: case 0x39: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: return false; } } switch (v) { case 0x05: case 0x07: case 0x0b: case 0x13: case 0x17: case 0x1b: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: case 0x39: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: case 0x42: case 0x43: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x59: case 0x5b: case 0x5d: case 0x5f: case 0x61: case 0x65: case 0x69: case 0x6a: case 0x6b: case 0x6d: case 0x6e: case 0x6f: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: fpu_noinst(opcode, oldpc); return true; case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: // Unexpected, isn't it?! Exception(4); return true; break; } return false; } static bool fault_if_unimplemented_680x0 (uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc, fpdata *src, int reg) { if (fault_if_no_fpu (opcode, extra, ea, easet, oldpc)) return true; if (currprefs.cpu_model >= 68040 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { if ((extra & (0x8000 | 0x2000)) != 0) return false; if ((extra & 0xfc00) == 0x5c00) { // FMOVECR fp_unimp_instruction(opcode, extra, ea, easet, oldpc, src, reg, -1); return true; } uae_u16 v = extra & 0x7f; /* >=0x040 are 68040/68060 only variants. 6888x = F-line exception. */ switch (v) { case 0x00: /* FMOVE */ case 0x04: /* FSQRT */ case 0x18: /* FABS */ case 0x1a: /* FNEG */ case 0x20: /* FDIV */ case 0x22: /* FADD */ case 0x23: /* FMUL */ case 0x24: /* FSGLDIV */ case 0x27: /* FSGLMUL */ case 0x28: /* FSUB */ case 0x38: /* FCMP */ case 0x3a: /* FTST */ case 0x40: /* FSMOVE */ case 0x41: /* FSSQRT */ case 0x44: /* FDMOVE */ case 0x45: /* FDSQRT */ case 0x58: /* FSABS */ case 0x5c: /* FDABS */ case 0x5a: /* FSNEG */ case 0x5e: /* FDNEG */ case 0x60: /* FSDIV */ case 0x62: /* FSADD */ case 0x63: /* FSMUL */ case 0x64: /* FDDIV */ case 0x66: /* FDADD */ case 0x67: /* FDMUL */ case 0x68: /* FSSUB */ case 0x6c: /* FDSUB */ return false; case 0x01: /* FINT */ case 0x03: /* FINTRZ */ // Unimplemented only in 68040. if(currprefs.cpu_model != 68040) { return false; } default: fp_unimp_instruction(opcode, extra, ea, easet, oldpc, src, reg, -1); return true; } } return false; } static bool fault_if_unimplemented_6888x (uae_u16 opcode, uae_u16 extra, uaecptr oldpc) { if ((currprefs.fpu_model == 68881 || currprefs.fpu_model == 68882) && currprefs.fpu_no_unimplemented) { uae_u16 v = extra & 0x7f; switch(v) { case 0x00: /* FMOVE */ case 0x01: /* FINT */ case 0x02: /* FSINH */ case 0x03: /* FINTRZ */ case 0x04: /* FSQRT */ case 0x06: /* FLOGNP1 */ case 0x08: /* FETOXM1 */ case 0x09: /* FTANH */ case 0x0a: /* FATAN */ case 0x0c: /* FASIN */ case 0x0d: /* FATANH */ case 0x0e: /* FSIN */ case 0x0f: /* FTAN */ case 0x10: /* FETOX */ case 0x11: /* FTWOTOX */ case 0x12: /* FTENTOX */ case 0x14: /* FLOGN */ case 0x15: /* FLOG10 */ case 0x16: /* FLOG2 */ case 0x18: /* FABS */ case 0x19: /* FCOSH */ case 0x1a: /* FNEG */ case 0x1c: /* FACOS */ case 0x1d: /* FCOS */ case 0x1e: /* FGETEXP */ case 0x1f: /* FGETMAN */ case 0x20: /* FDIV */ case 0x21: /* FMOD */ case 0x22: /* FADD */ case 0x23: /* FMUL */ case 0x24: /* FSGLDIV */ case 0x25: /* FREM */ case 0x26: /* FSCALE */ case 0x27: /* FSGLMUL */ case 0x28: /* FSUB */ case 0x30: /* FSINCOS */ case 0x31: /* FSINCOS */ case 0x32: /* FSINCOS */ case 0x33: /* FSINCOS */ case 0x34: /* FSINCOS */ case 0x35: /* FSINCOS */ case 0x36: /* FSINCOS */ case 0x37: /* FSINCOS */ case 0x38: /* FCMP */ case 0x3a: /* FTST */ // 6888x invalid opmodes execute existing FPU instruction. // Only opmodes 0x40-0x7f generate F-line exception. case 0x05: case 0x07: case 0x0b: case 0x13: case 0x17: case 0x1b: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: case 0x39: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: return false; default: fpu_noinst (opcode, oldpc); return true; } } return false; } static bool fault_if_60 (void) { if (currprefs.cpu_model == 68060 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { Exception(60); return true; } return false; } static bool fault_if_no_fpu_u (uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc) { if (fault_if_no_fpu (opcode, extra, ea, easet, oldpc)) return true; if (currprefs.cpu_model == 68060 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { // 68060 FTRAPcc, FDBcc and FScc are not implemented. regs.fp_unimp_ins = true; regs.fp_ea = ea; regs.fp_ea_set = easet; regs.fpiar = oldpc; fp_unimp_instruction_exception_pending(); return true; } return false; } static bool fault_if_no_6888x (uae_u16 opcode, uae_u16 extra, uaecptr oldpc) { if (currprefs.cpu_model < 68040 && currprefs.fpu_model <= 0) { #if EXCEPTION_FPP write_log(_T("6888x no FPU: %04X-%04X PC=%08X\n"), opcode, extra, oldpc); #endif m68k_setpc (oldpc); regs.fp_exception = true; op_illg (opcode); return true; } return false; } static int get_fpu_version (int model) { int v = 0; switch (model) { case 68881: case 68882: v = 0x1f; break; case 68040: if (currprefs.fpu_revision == 0x40) v = 0x40; else v = 0x41; break; } return v; } static void fpu_null (void) { regs.fpu_state = 0; regs.fpu_exp_state = 0; regs.fpcr = 0; regs.fpsr = 0; regs.fpiar = 0; for (int i = 0; i < 8; i++) fpnan (®s.fp[i]); } // 68040/060 does not support denormals static bool normalize_or_fault_if_no_denormal_support(uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc, fpdata *src) { if (!support_denormals) return false; fpp_is_init(src); if (fpp_is_unnormal(src) || fpp_is_denormal(src)) { if (currprefs.cpu_model >= 68040 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { if (fpp_is_zero(src)) { fpp_normalize(src); // 68040/060 can only fix unnormal zeros } else { fp_unimp_datatype(opcode, extra, ea, easet, oldpc, src, NULL, true); return true; } } else { fpp_normalize(src); } } return false; } static bool normalize_or_fault_if_no_denormal_support_dst(uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc, fpdata *dst, fpdata *src) { if (!support_denormals) return false; fpp_is_init(dst); if (fpp_is_unnormal(dst) || fpp_is_denormal(dst)) { if (currprefs.cpu_model >= 68040 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { if (fpp_is_zero(dst)) { fpp_normalize(dst); // 68040/060 can only fix unnormal zeros } else { fp_unimp_datatype(opcode, extra, ea, easet, oldpc, src, NULL, false); return true; } } else { fpp_normalize(dst); } } return false; } // 68040/060 does not support packed decimal format static bool fault_if_no_packed_support(uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc, fpdata *src, uae_u32 *packed) { if (currprefs.cpu_model >= 68040 && currprefs.fpu_model && currprefs.fpu_no_unimplemented) { fp_unimp_datatype(opcode, extra, ea, easet, oldpc, src, packed, false); return true; } return false; } // 68040 does not support move to integer format static bool fault_if_68040_integer_nonmaskable(uae_u16 opcode, uae_u16 extra, uaecptr ea, bool easet, uaecptr oldpc, fpdata *src) { if (currprefs.cpu_model == 68040 && currprefs.fpu_model && currprefs.fpu_mode > 0) { fpsr_make_status(); if (regs.fpsr & (FPSR_SNAN | FPSR_OPERR)) { fpsr_check_arithmetic_exception(FPSR_SNAN | FPSR_OPERR, src, opcode, extra, ea, easet, oldpc); fp_exception_pending(false); // post return true; } } return false; } static int get_fp_value(uae_u32 opcode, uae_u16 extra, fpdata *src, uaecptr oldpc, uaecptr *adp, bool *adsetp) { int size, mode, reg; uae_u32 ad = 0; bool adset = false; static const int sz1[8] = { 4, 4, 12, 12, 2, 8, 1, 0 }; static const int sz2[8] = { 4, 4, 12, 12, 2, 8, 2, 0 }; #ifndef WINUAE_FOR_HATARI uae_u32 exts[3]; #else uae_u32 exts[3] = { 0 }; #endif int doext = 0; if (!(extra & 0x4000)) { // FPx to FPx if (fault_if_no_fpu (opcode, extra, 0, false, oldpc)) return -1; *src = regs.fp[(extra >> 10) & 7]; normalize_or_fault_if_no_denormal_support(opcode, extra, 0, false, oldpc, src); return 1; } mode = (opcode >> 3) & 7; reg = opcode & 7; size = (extra >> 10) & 7; switch (mode) { case 0: // Dn if ((size == 0 || size == 1 ||size == 4 || size == 6) && fault_if_no_fpu (opcode, extra, 0, false, oldpc)) return -1; switch (size) { case 6: // B fpset(src, (uae_s8) m68k_dreg (regs, reg)); break; case 4: // W fpset(src, (uae_s16) m68k_dreg (regs, reg)); break; case 0: // L fpset(src, (uae_s32) m68k_dreg (regs, reg)); break; case 1: // S fpp_to_single (src, m68k_dreg (regs, reg)); normalize_or_fault_if_no_denormal_support(opcode, extra, 0, false, oldpc, src); break; case 3: // P if (currprefs.cpu_model == 68060) { uae_u32 wrd[3]; if (fault_if_no_packed_support(opcode, extra, 0, false, oldpc, NULL, wrd)) return 1; } return 0; default: if (currprefs.cpu_model >= 68040) { if (fault_if_unimplemented_680x0(opcode, extra, ad, adset, oldpc, src, reg)) return -1; regs.fpiar = oldpc; } return 0; } return 1; case 1: // An if (currprefs.cpu_model >= 68040) { if (fault_if_unimplemented_680x0(opcode, extra, ad, adset, oldpc, src, reg)) return -1; } return 0; case 2: // (An) ad = m68k_areg (regs, reg); adset = true; break; case 3: // (An)+ // Also needed by fault_if_no_fpu mmufixup[0].reg = reg; mmufixup[0].value = m68k_areg (regs, reg); fpu_mmu_fixup = true; ad = m68k_areg (regs, reg); adset = true; m68k_areg (regs, reg) += reg == 7 ? sz2[size] : sz1[size]; break; case 4: // -(An) // Also needed by fault_if_no_fpu mmufixup[0].reg = reg; mmufixup[0].value = m68k_areg (regs, reg); fpu_mmu_fixup = true; m68k_areg (regs, reg) -= reg == 7 ? sz2[size] : sz1[size]; ad = m68k_areg (regs, reg); adset = true; // 68060 no fpu -(an): EA points to -4, not -12 if extended precision // or unsupported packed datatype. if (currprefs.cpu_model == 68060 && ((if_no_fpu() && sz1[size] == 12) || size == 3)) { ad += 8; } break; case 5: // (d16,An) ad = m68k_areg (regs, reg) + (uae_s32) (uae_s16) x_cp_next_iword (); adset = true; break; case 6: // (d8,An,Xn)+ ad = x_cp_get_disp_ea_020 (m68k_areg (regs, reg), 0); adset = true; break; case 7: switch (reg) { case 0: // (xxx).W ad = (uae_s32) (uae_s16) x_cp_next_iword (); adset = true; break; case 1: // (xxx).L ad = x_cp_next_ilong (); adset = true; break; case 2: // (d16,PC) ad = m68k_getpc (); ad += (uae_s32) (uae_s16) x_cp_next_iword (); adset = true; break; case 3: // (d8,PC,Xn)+ ad = x_cp_get_disp_ea_020 (m68k_getpc (), 0); adset = true; break; case 4: // #imm doext = 1; switch (size) { case 0: // L case 1: // S exts[0] = x_cp_next_ilong (); break; case 2: // X case 3: // P // 68060 and immediate X or P: unimplemented effective address if (fault_if_60()) return -1; exts[0] = x_cp_next_ilong (); exts[1] = x_cp_next_ilong (); exts[2] = x_cp_next_ilong (); break; case 4: // W exts[0] = x_cp_next_iword (); break; case 5: // D exts[0] = x_cp_next_ilong (); exts[1] = x_cp_next_ilong (); break; case 6: // B exts[0] = x_cp_next_iword (); break; } break; default: return 0; } } if (fault_if_no_fpu (opcode, extra, ad, adset, oldpc)) return -1; *adp = ad; *adsetp = adset; uae_u32 adold = ad; if (currprefs.fpu_model == 68060) { // Skip if 68040 because FSAVE frame can store both src and dst if (fault_if_unimplemented_680x0(opcode, extra, ad, adset, oldpc, src, -1)) { return -1; } } switch (size) { case 0: // L fpset(src, (uae_s32) (doext ? exts[0] : x_cp_get_long (ad))); break; case 1: // S fpp_to_single (src, (doext ? exts[0] : x_cp_get_long (ad))); normalize_or_fault_if_no_denormal_support(opcode, extra, adold, adset, oldpc, src); break; case 2: // X { uae_u32 wrd1, wrd2, wrd3; wrd1 = (doext ? exts[0] : x_cp_get_long (ad)); ad += 4; wrd2 = (doext ? exts[1] : x_cp_get_long (ad)); ad += 4; wrd3 = (doext ? exts[2] : x_cp_get_long (ad)); fpp_to_exten (src, wrd1, wrd2, wrd3); normalize_or_fault_if_no_denormal_support(opcode, extra, adold, adset, oldpc, src); } break; case 3: // P { uae_u32 wrd[3]; if (currprefs.cpu_model == 68060) { if (fault_if_no_packed_support (opcode, extra, adold, adset, oldpc, NULL, wrd)) return 1; } wrd[0] = (doext ? exts[0] : x_cp_get_long (ad)); ad += 4; wrd[1] = (doext ? exts[1] : x_cp_get_long (ad)); ad += 4; wrd[2] = (doext ? exts[2] : x_cp_get_long (ad)); if (fault_if_no_packed_support (opcode, extra, adold, adset, oldpc, NULL, wrd)) return 1; fpp_to_pack (src, wrd, 0); fpp_normalize(src); return 1; } break; case 4: // W fpset(src, (uae_s16) (doext ? exts[0] : x_cp_get_word (ad))); break; case 5: // D { uae_u32 wrd1, wrd2; wrd1 = (doext ? exts[0] : x_cp_get_long (ad)); ad += 4; wrd2 = (doext ? exts[1] : x_cp_get_long (ad)); fpp_to_double (src, wrd1, wrd2); normalize_or_fault_if_no_denormal_support(opcode, extra, adold, adset, oldpc, src); } break; case 6: // B fpset(src, (uae_s8) (doext ? exts[0] : x_cp_get_byte (ad))); break; default: return 0; } return 1; } static int put_fp_value2(fpdata *value, uae_u32 opcode, uae_u16 extra, uaecptr oldpc, uaecptr *adp, bool *adsetp) { int size, mode, reg; uae_u32 ad = 0; bool adset = false; static const int sz1[8] = { 4, 4, 12, 12, 2, 8, 1, 0 }; static const int sz2[8] = { 4, 4, 12, 12, 2, 8, 2, 0 }; #if DEBUG_FPP if (!isinrom ()) write_log(_T("PUTFP: %04X %04X\n"), opcode, extra); #endif #if 0 if (!(extra & 0x4000)) { if (fault_if_no_fpu (opcode, extra, 0, oldpc)) return 1; regs.fp[(extra >> 10) & 7] = *value; return 1; } #endif reg = opcode & 7; mode = (opcode >> 3) & 7; size = (extra >> 10) & 7; switch (mode) { case 0: // Dn if ((size == 0 || size == 1 ||size == 4 || size == 6) && fault_if_no_fpu (opcode, extra, 0, false, oldpc)) return -1; switch (size) { case 6: // B if (normalize_or_fault_if_no_denormal_support(opcode, extra, 0, false, oldpc, value)) return 1; m68k_dreg (regs, reg) = (uae_u32)(((fpp_to_int (value, 0) & 0xff) | (m68k_dreg (regs, reg) & ~0xff))); if (fault_if_68040_integer_nonmaskable(opcode, extra, ad, adset, oldpc, value)) return -1; break; case 4: // W if (normalize_or_fault_if_no_denormal_support(opcode, extra, 0, false, oldpc, value)) return 1; m68k_dreg (regs, reg) = (uae_u32)(((fpp_to_int (value, 1) & 0xffff) | (m68k_dreg (regs, reg) & ~0xffff))); if (fault_if_68040_integer_nonmaskable(opcode, extra, ad, adset, oldpc, value)) return -1; break; case 0: // L if (normalize_or_fault_if_no_denormal_support(opcode, extra, 0, false, oldpc, value)) return 1; m68k_dreg (regs, reg) = (uae_u32)fpp_to_int (value, 2); if (fault_if_68040_integer_nonmaskable(opcode, extra, ad, adset, oldpc, value)) return -1; break; case 1: // S if (normalize_or_fault_if_no_denormal_support(opcode, extra, 0, false, oldpc, value)) return 1; m68k_dreg (regs, reg) = fpp_from_single (value); break; case 3: // packed case 7: // packed { // K-factor size and other errors are checked even if EA is illegal if (!currprefs.fpu_no_unimplemented || currprefs.cpu_model < 68040) { uae_u32 wrd[3]; int kfactor = size == 7 ? m68k_dreg(regs, (extra >> 4) & 7) : extra; kfactor &= 127; if (kfactor & 64) kfactor |= ~63; fpp_normalize(value); fpp_from_pack(value, wrd, kfactor); fpp_get_status(®s.fpsr); } return -2; } default: return 0; } return 1; case 1: // An return 0; case 2: // (An) ad = m68k_areg (regs, reg); adset = true; break; case 3: // (An)+ // Also needed by fault_if_no_fpu mmufixup[0].reg = reg; mmufixup[0].value = m68k_areg (regs, reg); fpu_mmu_fixup = true; ad = m68k_areg (regs, reg); adset = true; m68k_areg (regs, reg) += reg == 7 ? sz2[size] : sz1[size]; break; case 4: // -(An) // Also needed by fault_if_no_fpu mmufixup[0].reg = reg; mmufixup[0].value = m68k_areg (regs, reg); fpu_mmu_fixup = true; m68k_areg (regs, reg) -= reg == 7 ? sz2[size] : sz1[size]; ad = m68k_areg (regs, reg); adset = true; // 68060 no fpu -(an): EA points to -4, not -12 if extended precision // or if packed datatype if (currprefs.cpu_model == 68060 && ((if_no_fpu() && sz1[size] == 12) || size == 3 || size == 7)) { ad += 8; } break; case 5: // (d16,An) ad = m68k_areg (regs, reg) + (uae_s32) (uae_s16) x_cp_next_iword (); adset = true; break; case 6: // (d8,An,Xn)+ ad = x_cp_get_disp_ea_020 (m68k_areg (regs, reg), 0); adset = true; break; case 7: switch (reg) { case 0: // (xxx).W ad = (uae_s32) (uae_s16) x_cp_next_iword (); adset = true; break; case 1: // (xxx).L ad = x_cp_next_ilong (); adset = true; break; // Immediate and PC-relative modes are not supported default: return 0; } } *adp = ad; *adsetp = adset; if (fault_if_no_fpu (opcode, extra, ad, adset, oldpc)) return -1; switch (size) { case 0: // L if (normalize_or_fault_if_no_denormal_support(opcode, extra, ad, adset, oldpc, value)) return 1; x_cp_put_long(ad, (uae_u32)fpp_to_int(value, 2)); if (fault_if_68040_integer_nonmaskable(opcode, extra, ad, adset, oldpc, value)) return -1; break; case 1: // S if (normalize_or_fault_if_no_denormal_support(opcode, extra, ad, adset, oldpc, value)) return 1; x_cp_put_long(ad, fpp_from_single(value)); break; case 2: // X { if (normalize_or_fault_if_no_denormal_support(opcode, extra, ad, adset, oldpc, value)) return 1; uae_u32 wrd1, wrd2, wrd3; fpp_from_exten(value, &wrd1, &wrd2, &wrd3); x_cp_put_long (ad, wrd1); ad += 4; x_cp_put_long (ad, wrd2); ad += 4; x_cp_put_long (ad, wrd3); } break; case 3: // Packed-Decimal Real with Static k-Factor case 7: // Packed-Decimal Real with Dynamic k-Factor (P{Dn}) (reg to memory only) { uae_u32 wrd[3]; int kfactor; if (fault_if_no_packed_support (opcode, extra, ad, adset, oldpc, value, wrd)) return 1; kfactor = size == 7 ? m68k_dreg (regs, (extra >> 4) & 7) : extra; kfactor &= 127; if (kfactor & 64) kfactor |= ~63; fpp_normalize(value); fpp_from_pack(value, wrd, kfactor); x_cp_put_long (ad, wrd[0]); ad += 4; x_cp_put_long (ad, wrd[1]); ad += 4; x_cp_put_long (ad, wrd[2]); } break; case 4: // W if (normalize_or_fault_if_no_denormal_support(opcode, extra, ad, adset, oldpc, value)) return 1; x_cp_put_word(ad, (uae_s16)fpp_to_int(value, 1)); if (fault_if_68040_integer_nonmaskable(opcode, extra, ad, adset, oldpc, value)) return -1; break; case 5: // D { if (normalize_or_fault_if_no_denormal_support(opcode, extra, ad, adset, oldpc, value)) return 1; uae_u32 wrd1, wrd2; fpp_from_double(value, &wrd1, &wrd2); x_cp_put_long (ad, wrd1); ad += 4; x_cp_put_long (ad, wrd2); } break; case 6: // B if (normalize_or_fault_if_no_denormal_support(opcode, extra, ad, adset, oldpc, value)) return 1; x_cp_put_byte(ad, (uae_s8)fpp_to_int(value, 0)); if (fault_if_68040_integer_nonmaskable(opcode, extra, ad, adset, oldpc, value)) return -1; break; default: return 0; } return 1; } static int get_fp_ad (uae_u32 opcode, uae_u32 *ad, bool *adset) { int mode; int reg; mode = (opcode >> 3) & 7; reg = opcode & 7; switch (mode) { case 0: // Dn case 1: // An return 0; case 2: // (An) *ad = m68k_areg (regs, reg); *adset = true; break; case 3: // (An)+ *ad = m68k_areg (regs, reg); *adset = true; break; case 4: // -(An) *ad = m68k_areg (regs, reg); *adset = true; break; case 5: // (d16,An) *ad = m68k_areg (regs, reg) + (uae_s32) (uae_s16) x_cp_next_iword (); *adset = true; break; case 6: // (d8,An,Xn)+ *ad = x_cp_get_disp_ea_020 (m68k_areg (regs, reg), 0); *adset = true; break; case 7: switch (reg) { case 0: // (xxx).W *ad = (uae_s32) (uae_s16) x_cp_next_iword (); *adset = true; break; case 1: // (xxx).L *ad = x_cp_next_ilong (); *adset = true; break; case 2: // (d16,PC) *ad = m68k_getpc (); *ad += (uae_s32) (uae_s16) x_cp_next_iword (); *adset = true; break; case 3: // (d8,PC,Xn)+ *ad = x_cp_get_disp_ea_020 (m68k_getpc (), 0); *adset = true; break; default: return 0; } } return 1; } static int put_fp_value(fpdata *value, uae_u32 opcode, uae_u16 extra, uaecptr oldpc, uaecptr *adp, bool *adsetp) { int v = put_fp_value2(value, opcode, extra, oldpc, adp, adsetp); if (v == -2) { int size = (extra >> 10) & 7; if (size == 3 || size == 7) { // 68040+ generates unimplemented effective mode exception even if destination EA is Dn or An. // Always invalid EAs (Destination PC-relative or immediate) generate expected f-line exception. uae_u32 wrd[3]; if (fault_if_no_packed_support(opcode, extra, 0, false, oldpc, value, wrd)) { if (regs.fp_unimp_pend) { fp_exception_pending(false); } return -1; } } return 0; } return v; } // 68040/68060 static const bool condition_table_040_060[] = { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1 }; // 68882 static const bool condition_table_6888x[] = { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; static const bool *condition_table = condition_table_6888x; int fpp_cond(int condition) { condition &= 0x1f; #ifdef JIT if (currprefs.cachesize && currprefs.compfpu) { // JIT reads and writes regs.fpu_result fpp_is_init(®s.fp_result); int NotANumber = fpp_is_nan(®s.fp_result); int I = fpp_is_infinity(®s.fp_result); int Z = fpp_is_zero(®s.fp_result); int N = fpp_is_neg(®s.fp_result); int control = (N << 3) | (Z << 2) | (I << 1) | (NotANumber << 0); return condition_table[control * 32 + condition]; } else #endif { if ((condition & 0x10) && (regs.fpsr & FPSR_CC_NAN)) { if (fpsr_set_bsun()) return -2; } int control = (regs.fpsr >> 24) & 15; return condition_table[control * 32 + condition]; } #if 0 switch (condition) { case 0x00: return 0; case 0x01: return Z; case 0x02: return !(NotANumber || Z || N); case 0x03: return Z || !(NotANumber || N); case 0x04: return N && !(NotANumber || Z); case 0x05: return Z || (N && !NotANumber); case 0x06: return !(NotANumber || Z); case 0x07: return !NotANumber; case 0x08: return NotANumber; case 0x09: return NotANumber || Z; case 0x0a: return NotANumber || !(N || Z); case 0x0b: return NotANumber || Z || !N; case 0x0c: return NotANumber || (N && !Z); case 0x0d: return NotANumber || Z || N; case 0x0e: return !Z; case 0x0f: return 1; case 0x10: return 0; case 0x11: return Z; case 0x12: return !(NotANumber || Z || N); case 0x13: return Z || !(NotANumber || N); case 0x14: return N && !(NotANumber || Z); case 0x15: return Z || (N && !NotANumber); case 0x16: return !(NotANumber || Z); case 0x17: return !NotANumber; case 0x18: return NotANumber; case 0x19: return NotANumber || Z; case 0x1a: return NotANumber || !(N || Z); case 0x1b: return NotANumber || Z || !N; case 0x1c: return NotANumber || (N && !Z); case 0x1d: return NotANumber || Z || N; case 0x1e: return !Z; case 0x1f: return 1; } return -1; #endif } static void maybe_idle_state (void) { // conditional floating point instruction does not change state // from null to idle on 68040/060. if (currprefs.fpu_model == 68881 || currprefs.fpu_model == 68882) regs.fpu_state = 1; } static void trace_t0_68040(void) { if (regs.t0 && currprefs.cpu_model == 68040) check_t0_trace(); } void fpuop_dbcc (uae_u32 opcode, uae_u16 extra) { uaecptr pc = m68k_getpc (); uae_s32 disp; int cc; if (fp_exception_pending(true)) return; regs.fp_exception = false; #if FPU_LOG write_log(_T("FDBcc %04x %04x %08x\n"), opcode, extra, M68K_GETPC); #endif if (fault_if_no_6888x (opcode, extra, pc - 4)) return; disp = (uae_s32) (uae_s16)x_cp_next_iword(); // 68060 unimplemented stacked EA contains original extra + disp values // not final absolute address. if (fault_if_no_fpu_u (opcode, extra, (extra << 16) | (disp & 0xffff), true, pc - 4)) return; maybe_idle_state (); cc = fpp_cond (extra); if (cc < 0) { if (cc == -2) return; // BSUN fpu_op_illg (opcode, 0, false, pc - 4); } else if (!cc) { int reg = opcode & 0x7; m68k_dreg (regs, reg) = ((m68k_dreg (regs, reg) & 0xffff0000) | (((m68k_dreg (regs, reg) & 0xffff) - 1) & 0xffff)); if ((m68k_dreg (regs, reg) & 0xffff) != 0xffff) { m68k_setpc (pc + disp); regs.fp_branch = true; } } // 68040 FDBCC: T0 always trace_t0_68040(); } void fpuop_scc (uae_u32 opcode, uae_u16 extra) { uae_u32 ad = 0; bool adset = false; int cc; uaecptr pc = m68k_getpc () - 4; int mode = (opcode >> 3) & 7; int reg = opcode & 7; if (fp_exception_pending(true)) return; regs.fp_exception = false; #if FPU_LOG write_log(_T("FScc %04x %04x %08x\n"), opcode, extra, M68K_GETPC); #endif if (fault_if_no_6888x (opcode, extra, pc)) return; // EA calculation needed? (Mode != 000) if (mode) { if (get_fp_ad (opcode, &ad, &adset) == 0) { fpu_noinst (opcode, regs.fpiar); return; } } // needs to be before 68060 check because // unimplemented stack frame stacked EA is adjusted // Register is not adjusted. if (mode == Apdi) { ad -= reg == 7 ? 2 : 1; } if (fault_if_no_fpu_u (opcode, extra, ad, adset, pc)) return; maybe_idle_state (); cc = fpp_cond (extra); if (cc < 0) { if (cc == -2) return; // BSUN fpu_op_illg (opcode, 0, false, regs.fpiar); } else if (!mode) { m68k_dreg (regs, reg) = (m68k_dreg (regs, reg) & ~0xff) | (cc ? 0xff : 0x00); } else { if (mode == Apdi) { mmufixup[0].reg = reg; mmufixup[0].value = m68k_areg(regs, reg); fpu_mmu_fixup = true; m68k_areg(regs, reg) = ad; } x_cp_put_byte (ad, cc ? 0xff : 0x00); if (mode == Aipi) { m68k_areg(regs, reg) += reg == 7 ? 2 : 1; } fpu_mmu_fixup = false; } } void fpuop_trapcc (uae_u32 opcode, uaecptr oldpc, uae_u16 extra) { int cc; if (fp_exception_pending(true)) return; regs.fp_exception = false; #if FPU_LOG write_log(_T("FTRAPcc %04x %04x %08x\n"), opcode, extra, M68K_GETPC); #endif if (fault_if_no_fpu_u (opcode, extra, 0, false, oldpc)) return; if (currprefs.fpu_model == 68060) { regs.fpiar = oldpc; } maybe_idle_state (); cc = fpp_cond (extra); if (cc < 0) { if (cc == -2) return; // BSUN fpu_op_illg (opcode, 0, false, oldpc); } else if (cc) { Exception_cpu_oldpc(7, oldpc); } } void fpuop_bcc(uae_u32 opcode, uaecptr oldpc, uae_u32 extra) { int cc; if (fp_exception_pending(true)) return; regs.fp_exception = false; #if FPU_LOG write_log(_T("FBcc %04x %04x %08x\n"), opcode, extra, M68K_GETPC); #endif if (fault_if_no_fpu(opcode, extra, 0, false, oldpc - 2)) return; if (currprefs.fpu_model == 68060) { regs.fpiar = oldpc - 2; } maybe_idle_state(); cc = fpp_cond(opcode); if (cc < 0) { if (cc == -2) return; // BSUN fpu_op_illg(opcode, 0, false, oldpc - 2); } else if (cc) { if ((opcode & 0x40) == 0) extra = (uae_s32) (uae_s16) extra; m68k_setpc(oldpc + extra); regs.fp_branch = true; } } void fpuop_save (uae_u32 opcode) { uae_u32 ad, adp; bool adset = false; int incr = (opcode & 0x38) == 0x20 ? -1 : 1; int fpu_version = get_fpu_version (currprefs.fpu_model); uaecptr pc = m68k_getpc () - 2; int i; #if FPU_LOG if (!isinrom()) write_log(_T("FSAVE %04x %08x\n"), opcode, M68K_GETPC); #endif regs.fp_exception = false; if (fault_if_no_6888x (opcode, 0, pc)) return; if (get_fp_ad (opcode, &ad, &adset) == 0) { fpu_op_illg (opcode, 0, false, pc); return; } if (fault_if_no_fpu (opcode, 0, ad, adset, pc)) return; // write_log(_T("FSAVE %08x %08x\n"), M68K_GETPC, ad); if (currprefs.fpu_model == 68060) { /* 12 byte 68060 NULL/IDLE/EXCP frame. */ int frame_size = 12; uae_u32 frame_id; if (regs.fpu_exp_state > 1) { frame_id = 0x0000e000 | fsave_data.v; #if 0 write_log(_T("68060 FSAVE EXCP %s\n"), fpp_print(&fsave_data.src)); #endif } else { frame_id = regs.fpu_state == 0 ? 0x00000000 : 0x00006000; } if (incr < 0) ad -= frame_size; adp = ad; x_cp_put_long (ad, (fsave_data.eo[0] & 0xffff0000) | frame_id); ad += 4; x_cp_put_long (ad, fsave_data.eo[1]); ad += 4; x_cp_put_long (ad, fsave_data.eo[2]); ad += 4; } else if (currprefs.fpu_model == 68040) { if (!regs.fpu_exp_state) { /* 4 byte 68040 NULL/IDLE frame. */ uae_u32 frame_id = regs.fpu_state == 0 ? 0 : fpu_version << 24; if (incr < 0) ad -= 4; adp = ad; x_cp_put_long (ad, frame_id); ad += 4; } else { /* 44 (rev $40) and 52 (rev $41) byte 68040 unimplemented instruction frame */ /* 96 byte 68040 busy frame */ int frame_size = regs.fpu_exp_state == 2 ? 0x64 : (fpu_version >= 0x41 ? 0x34 : 0x2c); uae_u32 frame_id = ((fpu_version << 8) | (frame_size - 4)) << 16; #if 0 write_log(_T("68040 FSAVE %d (%d), CMDREG=%04X"), regs.fp_exp_pend, frame_size, extra); if (regs.fp_exp_pend == FPU_EXP_UNIMP_DATATYPE_PACKED_PRE) { write_log(_T(" PACKED %08x-%08x-%08x"), fsave_data.pack[0], fsave_data.pack[1], fsave_data.pack[2]); } else if (regs.fp_exp_pend == FPU_EXP_UNIMP_DATATYPE_PACKED_POST) { write_log(_T(" SRC=%s (%08x-%08x-%08x %d)"), fpp_print(&fsave_data.src), src1[0], src1[1], src1[2], stag); write_log(_T(" DST=%s (%08x-%08x-%08x %d)"), fpp_print(&fsave_data.dst), src2[0], src2[1], src2[2], dtag); } #endif if (incr < 0) ad -= frame_size; adp = ad; x_cp_put_long (ad, frame_id); ad += 4; if (regs.fpu_exp_state == 2) { /* BUSY frame */ x_cp_put_long(ad, 0); ad += 4; x_cp_put_long(ad, 0); // CU_SAVEPC (Software shouldn't care) ad += 4; x_cp_put_long(ad, 0); ad += 4; x_cp_put_long(ad, 0); ad += 4; x_cp_put_long(ad, 0); ad += 4; x_cp_put_long(ad, fsave_data.wbt[0]); // WBTS/WBTE ad += 4; x_cp_put_long(ad, fsave_data.wbt[1]); // WBTM ad += 4; x_cp_put_long(ad, fsave_data.wbt[2]); // WBTM ad += 4; x_cp_put_long(ad, 0); ad += 4; x_cp_put_long(ad, fsave_data.fpiarcu); // FPIARCU (same as FPU PC or something else?) ad += 4; x_cp_put_long(ad, 0); ad += 4; x_cp_put_long(ad, 0); ad += 4; } if (fpu_version >= 0x41 || regs.fpu_exp_state == 2) { x_cp_put_long(ad, fsave_data.cmdreg3b << 16); // CMDREG3B ad += 4; x_cp_put_long (ad, 0); ad += 4; } x_cp_put_long (ad, (fsave_data.stag << 29) | (fsave_data.wbtm66 << 26) | (fsave_data.grs << 23)); // STAG ad += 4; x_cp_put_long (ad, fsave_data.cmdreg1b << 16); // CMDREG1B ad += 4; x_cp_put_long (ad, (fsave_data.dtag << 29) | (fsave_data.wbte15 << 20)); // DTAG ad += 4; x_cp_put_long (ad, (fsave_data.e1 << 26) | (fsave_data.e3 << 25) | (fsave_data.t << 20)); ad += 4; x_cp_put_long(ad, fsave_data.fpt[0]); // FPTS/FPTE ad += 4; x_cp_put_long(ad, fsave_data.fpt[1]); // FPTM ad += 4; x_cp_put_long(ad, fsave_data.fpt[2]); // FPTM ad += 4; x_cp_put_long(ad, fsave_data.et[0]); // ETS/ETE ad += 4; x_cp_put_long(ad, fsave_data.et[1]); // ETM ad += 4; x_cp_put_long(ad, fsave_data.et[2]); // ETM ad += 4; } // 68040 FSAVE: T0 always trace_t0_68040(); } else { /* 68881/68882 */ uae_u32 biu_flags = 0x540effff; int frame_size = currprefs.fpu_model == 68882 ? 0x3c : 0x1c; uae_u32 frame_id = regs.fpu_state == 0 ? ((frame_size - 4) << 16) : (fpu_version << 24) | ((frame_size - 4) << 16); regs.fp_exp_pend = 0; if (regs.fpu_exp_state) { biu_flags |= 0x20000000; } else { biu_flags |= 0x08000000; } if (regs.fpu_state == 0) frame_size = 4; if (currprefs.mmu_model) { if (incr < 0) ad -= frame_size; adp = ad; x_cp_put_long(ad, frame_id); // frame id ad += 4; if (regs.fpu_state != 0) { // idle frame x_cp_put_long(ad, fsave_data.ccr); // command/condition register ad += 4; if (currprefs.fpu_model == 68882) { // don't write unused fields to save MMU state space. ad += 8 * 4; } x_cp_put_long(ad, fsave_data.eo[0]); // exceptional operand lo ad += 4; x_cp_put_long(ad, fsave_data.eo[1]); // exceptional operand mid ad += 4; x_cp_put_long(ad, fsave_data.eo[2]); // exceptional operand hi ad += 4; x_cp_put_long(ad, 0x00000000); // operand register ad += 4; x_cp_put_long(ad, biu_flags); // biu flags ad += 4; } } else { if (incr < 0) ad -= frame_size; adp = ad; x_cp_put_long(ad, frame_id); // frame id ad += 4; if (regs.fpu_state != 0) { // idle frame x_cp_put_long(ad, fsave_data.ccr); // command/condition register ad += 4; if(currprefs.fpu_model == 68882) { for(i = 0; i < 32; i += 4) { x_cp_put_long(ad, 0x00000000); // internal ad += 4; } } x_cp_put_long(ad, fsave_data.eo[0]); // exceptional operand hi ad += 4; x_cp_put_long(ad, fsave_data.eo[1]); // exceptional operand mid ad += 4; x_cp_put_long(ad, fsave_data.eo[2]); // exceptional operand lo ad += 4; x_cp_put_long(ad, 0x00000000); // operand register ad += 4; x_cp_put_long(ad, biu_flags); // biu flags ad += 4; } } } if ((opcode & 0x38) == 0x20) // predecrement m68k_areg (regs, opcode & 7) = adp; regs.fpu_exp_state = 0; regs.fp_exp_pend = 0; } static bool fp_arithmetic(fpdata *src, fpdata *dst, int extra); void fpuop_restore (uae_u32 opcode) { int fpu_version; int frame_version; int fpu_model = currprefs.fpu_model; uaecptr pc = m68k_getpc () - 2; uae_u32 ad, ad_orig; bool adset = false; uae_u32 d; regs.fp_exception = false; #if FPU_LOG if (!isinrom()) write_log(_T("FRESTORE %04x %08x\n"), opcode, M68K_GETPC); #endif if (fault_if_no_6888x (opcode, 0, pc)) return; if (get_fp_ad (opcode, &ad, &adset) == 0) { fpu_op_illg (opcode, 0, false, pc); return; } if (fault_if_no_fpu (opcode, 0, ad, adset, pc)) return; ad_orig = ad; // write_log(_T("FRESTORE %08x %08x\n"), M68K_GETPC, ad); // FRESTORE does not support predecrement d = x_cp_get_long(ad); frame_version = (d >> 24) & 0xff; retry: ad = ad_orig + 4; fpu_version = get_fpu_version(fpu_model); if (fpu_model == 68060) { int ff = (d >> 8) & 0xff; uae_u32 v = d & 0x7; fsave_data.eo[0] = d & 0xffff0000; fsave_data.eo[1] = x_cp_get_long(ad); ad += 4; fsave_data.eo[2] = x_cp_get_long(ad); ad += 4; regs.fp_exp_pend = 0; if (ff == 0x60) { regs.fpu_state = 1; regs.fpu_exp_state = 0; } else if (ff == 0xe0) { regs.fpu_state = 1; regs.fpu_exp_state = 2; if (v == 7) { regs.fp_unimp_pend = 1; } else { regs.fp_exp_pend = 48 + v; } } else if (ff) { write_log(_T("FRESTORE invalid frame format %02x %08x ADDR=%08x\n"), ff, d, ad_orig); Exception(14); return; } else { fpu_null(); } } else if (fpu_model == 68040) { if (frame_version == fpu_version) { // not null frame uae_u32 frame_size = (d >> 16) & 0xff; if (frame_size == 0x60) { // busy fpdata src, dst; uae_u32 tmp, v, opclass, cmdreg1b, fpte15, et15, cusavepc; ad += 0x4; // offset to CU_SAVEPC field tmp = x_cp_get_long(ad); cusavepc = tmp >> 24; ad += 0x34; // offset to ET15 field tmp = x_cp_get_long(ad); et15 = (tmp & 0x10000000) >> 28; ad += 0x4; // offset to CMDREG1B field fsave_data.cmdreg1b = x_cp_get_long(ad); fsave_data.cmdreg1b >>= 16; cmdreg1b = fsave_data.cmdreg1b; ad += 0x4; // offset to FPTE15 field tmp = x_cp_get_long(ad); fpte15 = (tmp & 0x10000000) >> 28; ad += 0x8; // offset to FPTE field fsave_data.fpt[0] = x_cp_get_long(ad); ad += 0x4; fsave_data.fpt[1] = x_cp_get_long(ad); ad += 0x4; fsave_data.fpt[2] = x_cp_get_long(ad); ad += 0x4; // offset to ET field fsave_data.et[0] = x_cp_get_long(ad); ad += 0x4; fsave_data.et[1] = x_cp_get_long(ad); ad += 0x4; fsave_data.et[2] = x_cp_get_long(ad); ad += 0x4; opclass = (cmdreg1b >> 13) & 0x7; // just to be sure if (cusavepc == 0xFE) { if (opclass == 0 || opclass == 2) { fpp_to_exten_fmovem(&dst, fsave_data.fpt[0], fsave_data.fpt[1], fsave_data.fpt[2]); fpp_denormalize(&dst, fpte15); fpp_to_exten_fmovem(&src, fsave_data.et[0], fsave_data.et[1], fsave_data.et[2]); fpp_denormalize(&src, et15); #if EXCEPTION_FPP uae_u32 tmpsrc[3], tmpdst[3]; fpp_from_exten_fmovem(&src, &tmpsrc[0], &tmpsrc[1], &tmpsrc[2]); fpp_from_exten_fmovem(&dst, &tmpdst[0], &tmpdst[1], &tmpdst[2]); write_log(_T("FRESTORE src = %08X %08X %08X, dst = %08X %08X %08X, extra = %04X\n"), tmpsrc[0], tmpsrc[1], tmpsrc[2], tmpdst[0], tmpdst[1], tmpdst[2], cmdreg1b); #endif fpsr_clear_status(); v = fp_arithmetic(&src, &dst, cmdreg1b); if (v) regs.fp[(cmdreg1b>>7)&7] = dst; fpsr_check_arithmetic_exception(0, &src, regs.fp_opword, cmdreg1b, regs.fp_ea, regs.fp_ea_set, 0xffffffff); } else { write_log(_T("FRESTORE resume of opclass %d instruction not supported %08x\n"), opclass, ad_orig); } } } else if (frame_size == 0x30 || frame_size == 0x28) { // unimp // TODO: restore frame contents ad += frame_size; } else if (frame_size == 0x00) { // idle regs.fpu_state = 1; regs.fpu_exp_state = 0; } else { write_log(_T("FRESTORE invalid frame size %02x %08x %08x\n"), frame_size, d, ad_orig); Exception(14); return; } } else if (frame_version == 0x00) { // null frame fpu_null(); } else { if (!currprefs.fpu_no_unimplemented && frame_version == 0x1f && currprefs.fpu_model == fpu_model) { // horrible hack to support on the fly 6888x <> 68040 FPU switching fpu_model = 68881; goto retry; } write_log(_T("FRESTORE 68040 (%d) invalid frame version %02x %08x %08x\n"), fpu_model, frame_version, d, ad_orig); Exception(14); return; } } else { // 6888x if (frame_version == fpu_version) { // not null frame uae_u32 biu_flags; uae_u32 frame_size = (d >> 16) & 0xff; uae_u32 biu_offset = frame_size - 4; regs.fpu_state = 1; if (frame_size == 0x18 || frame_size == 0x38) { // idle fsave_data.ccr = x_cp_get_long(ad); ad += 4; // 68882 internal registers (32 bytes, unused) ad += frame_size - 24; fsave_data.eo[0] = x_cp_get_long(ad); ad += 4; fsave_data.eo[1] = x_cp_get_long(ad); ad += 4; fsave_data.eo[2] = x_cp_get_long(ad); ad += 4; // operand register (unused) ad += 4; biu_flags = x_cp_get_long(ad); ad += 4; if ((biu_flags & 0x08000000) == 0x00000000) { regs.fpu_exp_state = 2; regs.fp_exp_pend = fpsr_get_vector(regs.fpsr & regs.fpcr & 0xff00); } else { regs.fpu_exp_state = 0; regs.fp_exp_pend = 0; } } else if (frame_size == 0xB4 || frame_size == 0xD4) { write_log(_T("FRESTORE of busy frame not supported %08x\n"), ad_orig); ad += frame_size; } else { write_log(_T("FRESTORE invalid frame size %02x %08x %08x\n"), frame_size, d, ad_orig); Exception(14); return; } } else if (frame_version == 0x00) { // null frame fpu_null(); } else { if (!currprefs.fpu_no_unimplemented && (frame_version == 0x40 || frame_version == 0x41) && currprefs.fpu_model == fpu_model) { // horrible hack to support on the fly 6888x <> 68040 FPU switching fpu_model = 68040; goto retry; } write_log(_T("FRESTORE 6888x (%d) invalid frame version %02x %08x %08x\n"), fpu_model, frame_version, d, ad_orig); Exception(14); return; } } if ((opcode & 0x38) == 0x18) /// postincrement m68k_areg (regs, opcode & 7) = ad; } static uaecptr fmovem2mem (uaecptr ad, uae_u32 list, int incr, int regdir) { int reg; // 68030 MMU state saving is annoying! if (currprefs.mmu_model == 68030) { #if SUPPORT_MMU int idx = 0; uae_u32 wrd[3]; mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM1; for (int r = 0; r < 8; r++) { if (regdir < 0) reg = 7 - r; else reg = r; if (list & 0x80) { fpp_from_exten_fmovem(®s.fp[reg], &wrd[0], &wrd[1], &wrd[2]); if (incr < 0) ad -= 3 * 4; for (int i = 0; i < 3; i++) { if (mmu030_state[0] == idx * 3 + i) { if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM2) { mmu030_state[1] &= ~MMU030_STATEFLAG1_MOVEM2; } else { mmu030_data_buffer_out = wrd[i]; x_put_long(ad + i * 4, wrd[i]); } mmu030_state[0]++; } } if (incr > 0) ad += 3 * 4; idx++; } list <<= 1; } #endif } else { for (int r = 0; r < 8; r++) { uae_u32 wrd1, wrd2, wrd3; if (regdir < 0) reg = 7 - r; else reg = r; if (list & 0x80) { fpp_from_exten_fmovem(®s.fp[reg], &wrd1, &wrd2, &wrd3); if (incr < 0) ad -= 3 * 4; if (regdir < -1 || regdir > 1) { x_cp_put_long(ad + 0, wrd3); x_cp_put_long(ad + 4, wrd2); x_cp_put_long(ad + 8, wrd1); } else { x_cp_put_long(ad + 0, wrd1); x_cp_put_long(ad + 4, wrd2); x_cp_put_long(ad + 8, wrd3); } if (incr > 0) ad += 3 * 4; } list <<= 1; } } return ad; } static uaecptr fmovem2fpp (uaecptr ad, uae_u32 list, int incr, int regdir) { int reg; if (currprefs.mmu_model == 68030) { #if SUPPORT_MMU uae_u32 wrd[3]; int idx = 0; mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM1 | MMU030_STATEFLAG1_FMOVEM; for (int r = 0; r < 8; r++) { if (regdir < 0) reg = 7 - r; else reg = r; if (list & 0x80) { if (incr < 0) ad -= 3 * 4; for (int i = 0; i < 3; i++) { if (mmu030_state[0] == idx * 3 + i) { if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM2) { mmu030_state[1] &= ~MMU030_STATEFLAG1_MOVEM2; wrd[i] = mmu030_data_buffer_out; } else { wrd[i] = x_get_long (ad + i * 4); } // save first two entries if 2nd or 3rd get_long() faults. if (i == 0 || i == 1) mmu030_fmovem_store[i] = wrd[i]; mmu030_state[0]++; if (i == 2) fpp_to_exten (®s.fp[reg], mmu030_fmovem_store[0], mmu030_fmovem_store[1], wrd[2]); } } if (incr > 0) ad += 3 * 4; idx++; } list <<= 1; } #endif } else { for (int r = 0; r < 8; r++) { uae_u32 wrd1, wrd2, wrd3; if (regdir < 0) reg = 7 - r; else reg = r; if (list & 0x80) { if (incr < 0) ad -= 3 * 4; wrd1 = x_cp_get_long(ad + 0); wrd2 = x_cp_get_long(ad + 4); wrd3 = x_cp_get_long(ad + 8); if (incr > 0) ad += 3 * 4; fpp_to_exten(®s.fp[reg], wrd1, wrd2, wrd3); } list <<= 1; } } return ad; } static bool fp_arithmetic(fpdata *src, fpdata *dst, int extra) { uae_u64 q = 0; uae_u8 s = 0; switch (extra & 0x7f) { case 0x00: /* FMOVE */ fpp_move(dst, src, PREC_NORMAL); break; case 0x40: /* FSMOVE */ fpp_move(dst, src, PREC_FLOAT); break; case 0x44: /* FDMOVE */ fpp_move(dst, src, PREC_DOUBLE); break; case 0x01: /* FINT */ fpp_int(dst, src); break; case 0x02: /* FSINH */ fpp_sinh(dst, src); break; case 0x03: /* FINTRZ */ fpp_intrz(dst, src); break; case 0x04: /* FSQRT */ case 0x05: fpp_sqrt(dst, src, PREC_NORMAL); break; case 0x41: /* FSSQRT */ fpp_sqrt(dst, src, PREC_FLOAT); break; case 0x45: /* FDSQRT */ fpp_sqrt(dst, src, PREC_DOUBLE); break; case 0x06: /* FLOGNP1 */ case 0x07: fpp_lognp1(dst, src); break; case 0x08: /* FETOXM1 */ fpp_etoxm1(dst, src); break; case 0x09: /* FTANH */ fpp_tanh(dst, src); break; case 0x0a: /* FATAN */ case 0x0b: fpp_atan(dst, src); break; case 0x0c: /* FASIN */ fpp_asin(dst, src); break; case 0x0d: /* FATANH */ fpp_atanh(dst, src); break; case 0x0e: /* FSIN */ fpp_sin(dst, src); break; case 0x0f: /* FTAN */ fpp_tan(dst, src); break; case 0x10: /* FETOX */ fpp_etox(dst, src); break; case 0x11: /* FTWOTOX */ fpp_twotox(dst, src); break; case 0x12: /* FTENTOX */ case 0x13: fpp_tentox(dst, src); break; case 0x14: /* FLOGN */ fpp_logn(dst, src); break; case 0x15: /* FLOG10 */ fpp_log10(dst, src); break; case 0x16: /* FLOG2 */ case 0x17: fpp_log2(dst, src); break; case 0x18: /* FABS */ fpp_abs(dst, src, PREC_NORMAL); break; case 0x58: /* FSABS */ fpp_abs(dst, src, PREC_FLOAT); break; case 0x5c: /* FDABS */ fpp_abs(dst, src, PREC_DOUBLE); break; case 0x19: /* FCOSH */ fpp_cosh(dst, src); break; case 0x1a: /* FNEG */ case 0x1b: fpp_neg(dst, src, PREC_NORMAL); break; case 0x5a: /* FSNEG */ fpp_neg(dst, src, PREC_FLOAT); break; case 0x5e: /* FDNEG */ fpp_neg(dst, src, PREC_DOUBLE); break; case 0x1c: /* FACOS */ fpp_acos(dst, src); break; case 0x1d: /* FCOS */ fpp_cos(dst, src); break; case 0x1e: /* FGETEXP */ fpp_getexp(dst, src); break; case 0x1f: /* FGETMAN */ fpp_getman(dst, src); break; case 0x20: /* FDIV */ fpp_div(dst, src, PREC_NORMAL); break; case 0x60: /* FSDIV */ fpp_div(dst, src, PREC_FLOAT); break; case 0x64: /* FDDIV */ fpp_div(dst, src, PREC_DOUBLE); break; case 0x21: /* FMOD */ fpsr_get_quotient(&q, &s); fpp_mod(dst, src, &q, &s); fpsr_set_quotient(q, s); break; case 0x22: /* FADD */ fpp_add(dst, src, PREC_NORMAL); break; case 0x62: /* FSADD */ fpp_add(dst, src, PREC_FLOAT); break; case 0x66: /* FDADD */ fpp_add(dst, src, PREC_DOUBLE); break; case 0x23: /* FMUL */ fpp_mul(dst, src, PREC_NORMAL); break; case 0x63: /* FSMUL */ fpp_mul(dst, src, PREC_FLOAT); break; case 0x67: /* FDMUL */ fpp_mul(dst, src, PREC_DOUBLE); break; case 0x24: /* FSGLDIV */ fpp_sgldiv(dst, src); break; case 0x25: /* FREM */ fpsr_get_quotient(&q, &s); fpp_rem(dst, src, &q, &s); fpsr_set_quotient(q, s); break; case 0x26: /* FSCALE */ fpp_scale(dst, src); break; case 0x27: /* FSGLMUL */ fpp_sglmul(dst, src); break; case 0x28: /* FSUB */ case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: fpp_sub(dst, src, PREC_NORMAL); break; case 0x68: /* FSSUB */ fpp_sub(dst, src, PREC_FLOAT); break; case 0x6c: /* FDSUB */ fpp_sub(dst, src, PREC_DOUBLE); break; case 0x30: /* FSINCOS */ case 0x31: /* FSINCOS */ case 0x32: /* FSINCOS */ case 0x33: /* FSINCOS */ case 0x34: /* FSINCOS */ case 0x35: /* FSINCOS */ case 0x36: /* FSINCOS */ case 0x37: /* FSINCOS */ fpp_sincos(dst, src, ®s.fp[extra & 7]); break; case 0x38: /* FCMP */ case 0x39: case 0x3c: case 0x3d: { fpp_cmp(dst, src); fpsr_make_status(); fpsr_set_result_always(dst); fpsr_set_result(dst); return false; } case 0x3a: /* FTST */ case 0x3b: case 0x3e: case 0x3f: { fpp_tst(dst, src); fpsr_make_status(); fpsr_set_result_always(dst); fpsr_set_result(dst); return false; } default: write_log(_T("Unknown FPU arithmetic function (%02x)\n"), extra & 0x7f); return false; } fpsr_set_result_always(dst); fpsr_set_result(dst); if (fpsr_make_status()) { return false; } return true; } static void fpuop_arithmetic2 (uae_u32 opcode, uae_u16 extra) { int reg = -1; int v; fpdata src, dst; uaecptr pc = m68k_getpc () - 4; uaecptr ad = 0; bool adset = false; bool nonmaskable = false; #if DEBUG_FPP if (!isinrom ()) write_log(_T("FPP %04x %04x at %08x\n"), opcode & 0xffff, extra, pc); #endif if (fault_if_no_6888x(opcode, extra, pc)) return; if (fp_exception_pending(true)) return; switch ((extra >> 13) & 0x7) { case 3: // FMOVE FPP->EA fpsr_clear_status(); src = regs.fp[(extra >> 7) & 7]; v = put_fp_value(&src, opcode, extra, pc, &ad, &adset); if (v <= 0) { if (v == 0) { fpu_noinst(opcode, pc); } return; } fpsr_make_status(); maybe_set_fpiar(pc); fpsr_check_arithmetic_exception(0, &src, opcode, extra, ad, adset, pc); fp_exception_pending(false); // post/mid instruction return; case 4: case 5: // FMOVE(M) Control Register(s) <> Data or Address register if ((opcode & 0x38) == 0) { // FMOVE(M) Control Register(s) <> Dn if (fault_if_no_fpu (opcode, extra, 0, false, pc)) return; // Only single selected control register is allowed // All control register bits unset = FPIAR uae_u16 bits = extra & (0x1000 | 0x0800 | 0x0400); if (bits && bits != 0x1000 && bits != 0x0800 && bits != 0x400) { // 68060 does not generate f-line if multiple bits are set // but it also works unexpectedly, just do nothing for now. if (currprefs.fpu_model != 68060) { fpu_noinst(opcode, pc); } #ifdef CPU_TESTER if (currprefs.fpu_model == 68060) { cputester_fault(); } #endif return; } if (extra & 0x2000) { // CR -> Dn if (extra & 0x1000) m68k_dreg(regs, opcode & 7) = fpp_get_fpcr(); if (extra & 0x0800) m68k_dreg(regs, opcode & 7) = fpp_get_fpsr(); if ((extra & 0x0400) || !bits) m68k_dreg(regs, opcode & 7) = fpp_get_fpiar(); } else { // Dn -> CR if (extra & 0x1000) fpp_set_fpcr(m68k_dreg (regs, opcode & 7)); if (extra & 0x0800) fpp_set_fpsr(m68k_dreg (regs, opcode & 7)); if ((extra & 0x0400) || !bits) fpp_set_fpiar(m68k_dreg (regs, opcode & 7)); } } else if ((opcode & 0x38) == 0x08) { // FMOVE(M) Control Register(s) <> An if (fault_if_no_fpu (opcode, extra, 0, false, pc)) return; // Only FPIAR can be moved to/from address register // All bits unset = FPIAR uae_u16 bits = extra & (0x1000 | 0x0800 | 0x0400); if (bits && bits != 0x0400 && currprefs.fpu_model == 68060) { // 68060 does not generate f-line if multiple bits are set // but it also works unexpectedly, just do nothing for now. #ifdef CPU_TESTER cputester_fault(); #endif return; } // 68060, An and all bits unset: f-line if ((bits && bits != 0x0400) || (!bits && currprefs.fpu_model == 68060)) { fpu_noinst(opcode, pc); return; } if (extra & 0x2000) { // FPIAR -> An m68k_areg (regs, opcode & 7) = regs.fpiar; } else { // An -> FPIAR regs.fpiar = m68k_areg (regs, opcode & 7); } } else if ((opcode & 0x3f) == 0x3c) { if ((extra & 0x2000) == 0) { // FMOVE(M) #imm,Control Register(s) uae_u32 ext[3]; // 68060 FMOVEM.L #imm,more than 1 control register: unimplemented EA uae_u16 bits = extra & (0x1000 | 0x0800 | 0x0400); if (bits && bits != 0x1000 && bits != 0x0800 && bits != 0x400) { if (fault_if_60()) return; } // No control register bits set: FPIAR if (!bits) { extra |= 0x0400; } // fetch first, use only after all data has been fetched ext[0] = ext[1] = ext[2] = 0; if (extra & 0x1000) ext[0] = x_cp_next_ilong (); if (extra & 0x0800) ext[1] = x_cp_next_ilong (); if (extra & 0x0400) ext[2] = x_cp_next_ilong (); if (fault_if_no_fpu (opcode, extra, 0, false, pc)) return; if (extra & 0x1000) fpp_set_fpcr(ext[0]); if (extra & 0x0800) fpp_set_fpsr(ext[1]); if (extra & 0x0400) fpp_set_fpiar(ext[2]); } else { // immediate as destination fpu_noinst(opcode, pc); return; } } else if (extra & 0x2000) { /* FMOVE(M) Control Register(s),EA */ uae_u32 ad; int incr = 0; if (get_fp_ad(opcode, &ad, &adset) == 0) { fpu_noinst(opcode, pc); return; } if (fault_if_no_fpu(opcode, extra, ad, adset, pc)) return; if ((opcode & 0x3f) >= 0x3a) { // PC relative modes not supported fpu_noinst(opcode, pc); return; } // No control register bits set: FPIAR if (!(extra & (0x1000 | 0x0800 | 0x0400))) { extra |= 0x0400; } if ((opcode & 0x38) == 0x20) { if (extra & 0x1000) incr += 4; if (extra & 0x0800) incr += 4; if (extra & 0x0400) incr += 4; } ad -= incr; if (extra & 0x1000) { x_cp_put_long(ad, fpp_get_fpcr()); ad += 4; } if (extra & 0x0800) { x_cp_put_long(ad, fpp_get_fpsr()); ad += 4; } if (extra & 0x0400) { x_cp_put_long(ad, fpp_get_fpiar()); ad += 4; } ad -= incr; if ((opcode & 0x38) == 0x18) { // (An)+ m68k_areg(regs, opcode & 7) = ad; } if ((opcode & 0x38) == 0x20) { // -(An) m68k_areg(regs, opcode & 7) = ad; } trace_t0_68040(); } else { /* FMOVE(M) EA,Control Register(s) */ uae_u32 ad; int incr = 0; if (get_fp_ad (opcode, &ad, &adset) == 0) { fpu_noinst (opcode, pc); return; } if (fault_if_no_fpu (opcode, extra, ad, adset, pc)) return; // No control register bits set: FPIAR if (!(extra & (0x1000 | 0x0800 | 0x0400))) { extra |= 0x0400; } // -(An) if((opcode & 0x38) == 0x20) { if (extra & 0x1000) incr += 4; if (extra & 0x0800) incr += 4; if (extra & 0x0400) incr += 4; ad = ad - incr; } if (extra & 0x1000) { fpp_set_fpcr(x_cp_get_long (ad)); ad += 4; } if (extra & 0x0800) { fpp_set_fpsr(x_cp_get_long (ad)); ad += 4; } if (extra & 0x0400) { fpp_set_fpiar(x_cp_get_long (ad)); ad += 4; } if ((opcode & 0x38) == 0x18) { // (An)+ m68k_areg(regs, opcode & 7) = ad; } if ((opcode & 0x38) == 0x20) { // -(An) m68k_areg(regs, opcode & 7) = ad - incr; } } return; case 6: case 7: { // FMOVEM FPP<>Memory uae_u32 ad, list = 0; int incr = 1; int regdir = 1; if (get_fp_ad (opcode, &ad, &adset) == 0) { #ifdef CPU_TESTER // 68060 does weird things (no exception) if FMOVEM.X #imm to registers if ((opcode & 0x3f) == 0x3c && !(extra & 0x2000) && currprefs.fpu_model == 68060) { cputester_fault(); return; } #endif fpu_noinst(opcode, pc); return; } if (fault_if_no_fpu (opcode, extra, ad, adset, pc)) return; if ((extra & 0x2000) && ((opcode & 0x38) == 0x18 || (opcode & 0x3f) >= 0x3a)) { // FMOVEM FPP->Memory: (An)+ and PC relative modes not supported fpu_noinst(opcode, pc); return; } if (!(extra & 0x2000) && (opcode & 0x38) == 0x20) { // FMOVEM Memory->FPP: -(An) not supported fpu_noinst(opcode, pc); return; } int mode = (extra >> 11) & 3; switch (mode) { case 0: /* static pred */ case 2: /* static postinc */ list = extra & 0xff; break; case 1: /* dynamic pred */ case 3: /* dynamic postinc */ if (fault_if_60()) return; list = m68k_dreg (regs, (extra >> 4) & 7) & 0xff; break; } if (currprefs.fpu_model >= 68881) { // 6888x works mostly as documented if ((opcode & 0x38) == 0x20) { incr = -1; } switch (mode) { case 0: /* static pred */ case 1: /* dynamic pred */ regdir = -1; break; } } else if (currprefs.fpu_model == 68040) { // 68040 is weird.. if ((opcode & 0x38) == 0x20) { incr = -1; } switch (mode) { case 0: /* static pred */ if ((opcode & 0x38) == 0x20 || (extra & 0x2000)) { regdir = -1; } break; case 1: /* dynamic pred */ if ((opcode & 0x38) == 0x20 || (extra & 0x2000)) { regdir = -1; } break; } // FMOVEM x,any EA that is not -(An): reversed write order. // (low mantissa, high mantissa, exponent)! if (list && (extra & 0x2000) && regdir < 0 && incr > 0 && (opcode & 0x38) != 0x20) { regdir = -2; } // FMOVEM x,-(An) but postinc: also reversed. if (list && (extra & 0x2000) && regdir > 0 && incr < 0) { regdir = 2; } // 68040 hangs if FMOVEM control registers // has undefined bit 10 set. } else { // 68060 simply ignores MODE field completely. if ((opcode & 0x38) == 0x20) { incr = -1; regdir = -1; } } if (extra & 0x2000) { /* FMOVEM FPP->Memory */ ad = fmovem2mem(ad, list, incr, regdir); trace_t0_68040(); } else { /* FMOVEM Memory->FPP */ ad = fmovem2fpp(ad, list, incr, regdir); } if ((opcode & 0x38) == 0x18 || (opcode & 0x38) == 0x20) { // (an)+ or -(an) m68k_areg(regs, opcode & 7) = ad; } } return; case 0: case 2: /* Extremely common */ reg = (extra >> 7) & 7; if ((extra & 0xfc00) == 0x5c00) { // FMOVECR if (fault_if_unimplemented_680x0 (opcode, extra, ad, adset, pc, &src, reg)) return; if (extra & 0x40) { // 6888x and ROM constant 0x40 - 0x7f: f-line fpu_noinst (opcode, pc); return; } fpsr_clear_status(); maybe_set_fpiar(pc); fpu_get_constant(®s.fp[reg], extra & 0x7f); fpsr_make_status(); fpsr_check_arithmetic_exception(0, &src, opcode, extra, ad, adset, pc); return; } // 6888x does not have special exceptions, check immediately // 68040+ also generate immediate exception 11 if invalid opmode. if (fault_if_nonexisting_opmode(opcode, extra, pc)) return; fpsr_clear_status(); // 68040 and 68060 always set FPIAR if (currprefs.fpu_model == 68040 || currprefs.fpu_model == 68060) { regs.fpiar = pc; } v = get_fp_value(opcode, extra, &src, pc, &ad, &adset); if (v <= 0) { if (v == 0) { fpu_noinst(opcode, pc); } return; } if (fault_if_no_fpu (opcode, extra, ad, adset, pc)) return; dst = regs.fp[reg]; if (fp_is_dyadic(extra)) normalize_or_fault_if_no_denormal_support_dst(opcode, extra, ad, adset, pc, &dst, &src); // check for 680x0 unimplemented instruction if (fault_if_unimplemented_680x0 (opcode, extra, ad, adset, pc, &src, reg)) return; // unimplemented datatype was checked in get_fp_value if (regs.fp_unimp_pend) { // simplification: always mid/post-instruction exception fp_exception_pending(false); return; } maybe_set_fpiar(pc); v = fp_arithmetic(&src, &dst, extra); nonmaskable = fpsr_check_arithmetic_exception(0, &src, opcode, extra, ad, adset, pc); // 68040 does not update destination register if nonmasked exception was generated if (v && (currprefs.fpu_model != 68040 || !nonmaskable)) { regs.fp[reg] = dst; } if (nonmaskable) { fp_exception_pending(false); } return; default: break; } fpu_noinst (opcode, pc); } void fpuop_arithmetic (uae_u32 opcode, uae_u16 extra) { regs.fpu_state = 1; regs.fp_exception = false; fpu_mmu_fixup = false; #if FPU_LOG write_log(_T("FPUOP %04x %04x PC=%08x\n"), opcode, extra, M68K_GETPC); #endif fpuop_arithmetic2 (opcode, extra); if (fpu_mmu_fixup) { mmufixup[0].reg = -1; } } static void get_features(void) { support_exceptions = (fpp_get_support_flags() & FPU_FEATURE_EXCEPTIONS) != 0; support_denormals = (fpp_get_support_flags() & FPU_FEATURE_DENORMALS) != 0; if (currprefs.fpu_model == 68040 || currprefs.fpu_model == 68060) { condition_table = condition_table_040_060; } else { condition_table = condition_table_6888x; } fpsr_mask = 0x0ffffff8; if (currprefs.fpu_model == 68040) { fpcr_mask = 0xffff; } else { fpcr_mask = 0xfff0; } } void fpu_clearstatus(void) { fpp_clear_status(); } #ifndef CPU_TESTER void fpu_modechange(void) { uae_u32 temp_ext[8][3]; //fprintf ( stderr , "fpu_modechange old %d new %d\n" , currprefs.fpu_mode , changed_prefs.fpu_mode ); if (currprefs.fpu_mode == changed_prefs.fpu_mode) return; currprefs.fpu_mode = changed_prefs.fpu_mode; set_cpu_caches(true); for (int i = 0; i < 8; i++) { fpp_from_exten_fmovem(®s.fp[i], &temp_ext[i][0], &temp_ext[i][1], &temp_ext[i][2]); } if (currprefs.fpu_mode > 0) { #ifdef WITH_SOFTFLOAT fp_init_softfloat(currprefs.fpu_model); #endif #ifdef MSVC_LONG_DOUBLE use_long_double = false; } else if (currprefs.fpu_mode < 0) { use_long_double = true; fp_init_native_80(); #endif } else { #ifdef MSVC_LONG_DOUBLE use_long_double = false; #endif fp_init_native(); } get_features(); for (int i = 0; i < 8; i++) { fpp_to_exten_fmovem(®s.fp[i], temp_ext[i][0], temp_ext[i][1], temp_ext[i][2]); } } #endif #if FPU_TEST static void fpu_test(void) { fpdata testp; uae_u32 packed[3]; fpp_set_fpcr(0x30); fpp_to_exten_fmovem(&testp, 0xB4000000, 0x80000000, 0x000003fc); write_log(_T("INPUT: %s (%04x %16llx)\n"), fpp_print(&testp, -1), testp.fpx.high, testp.fpx.low); fpp_from_pack(&testp, packed, 17); fpp_to_pack(&testp, packed, 0); } #endif void fpu_reset (void) { #ifndef CPU_TESTER currprefs.fpu_mode = changed_prefs.fpu_mode; //fprintf(stderr, "fpu_reset model=%d mode=%d\n" , currprefs.fpu_model , currprefs.fpu_mode ); if (currprefs.fpu_mode > 0) { #ifdef WITH_SOFTFLOAT fp_init_softfloat(currprefs.fpu_model); #endif #ifdef MSVC_LONG_DOUBLE use_long_double = false; } else if (currprefs.fpu_mode < 0) { use_long_double = true; if (!fp_init_native_80()) { use_long_double = false; fp_init_softfloat(currprefs.fpu_model); } #endif } else { #ifdef MSVC_LONG_DOUBLE use_long_double = false; #endif fp_init_native(); } #ifndef WINUAE_FOR_HATARI #if defined(CPU_i386) || defined(CPU_x86_64) init_fpucw_x87(); #ifdef MSVC_LONG_DOUBLE init_fpucw_x87_80(); #endif #endif #endif /* ! WINUAE_FOR_HATARI */ #else fp_init_softfloat(currprefs.fpu_model); use_long_double = false; #endif regs.fpu_exp_state = 0; regs.fp_unimp_pend = 0; regs.fp_ea_set = false; get_features(); fpp_set_fpcr (0); fpp_set_fpsr (0); fpp_set_fpiar (0); fpux_restore (NULL); // reset precision fpp_set_mode(0x00000080 | 0x00000010); fpp_set_mode(0x00000000); #if FPU_TEST fpu_test(); #endif } #ifndef CPU_TESTER uae_u8 *restore_fpu (uae_u8 *src) { uae_u32 w1, w2, w3; int i; uae_u32 flags; changed_prefs.fpu_model = currprefs.fpu_model = restore_u32 (); fpu_reset(); flags = restore_u32 (); for (i = 0; i < 8; i++) { w1 = restore_u16 () << 16; w2 = restore_u32 (); w3 = restore_u32 (); fpp_to_exten_fmovem(®s.fp[i], w1, w2, w3); } regs.fpcr = restore_u32 (); regs.fpsr = restore_u32 (); regs.fpiar = restore_u32 (); regs.fp_ea_set = (flags & 0x00000001) != 0; fpsr_make_status(); if (flags & 0x80000000) { restore_u32 (); restore_u32 (); } if (flags & 0x20000000) { uae_u32 v = restore_u32(); regs.fpu_state = (v >> 0) & 15; regs.fpu_exp_state = (v >> 4) & 15; regs.fp_unimp_pend = (v >> 8) & 15; regs.fp_exp_pend = (v >> 16) & 0xff; regs.fp_opword = restore_u16(); regs.fp_ea = restore_u32(); if (currprefs.fpu_model == 68060 || currprefs.fpu_model >= 68881) { fsave_data.ccr = restore_u32(); fsave_data.eo[0] = restore_u32(); fsave_data.eo[1] = restore_u32(); fsave_data.eo[2] = restore_u32(); } if (currprefs.fpu_model == 68060) { fsave_data.v = restore_u32(); } if (currprefs.fpu_model == 68040) { fsave_data.fpiarcu = restore_u32(); fsave_data.cmdreg3b = restore_u32(); fsave_data.cmdreg1b = restore_u32(); fsave_data.stag = restore_u32(); fsave_data.dtag = restore_u32(); fsave_data.e1 = restore_u32(); fsave_data.e3 = restore_u32(); fsave_data.t = restore_u32(); fsave_data.fpt[0] = restore_u32(); fsave_data.fpt[1] = restore_u32(); fsave_data.fpt[2] = restore_u32(); fsave_data.et[0] = restore_u32(); fsave_data.et[1] = restore_u32(); fsave_data.et[2] = restore_u32(); fsave_data.wbt[0] = restore_u32(); fsave_data.wbt[1] = restore_u32(); fsave_data.wbt[2] = restore_u32(); fsave_data.grs = restore_u32(); fsave_data.wbte15 = restore_u32(); fsave_data.wbtm66 = restore_u32(); } } write_log(_T("FPU: %d\n"), currprefs.fpu_model); return src; } uae_u8 *save_fpu(size_t *len, uae_u8 *dstptr) { uae_u32 w1, w2, w3, v; uae_u8 *dstbak, *dst; int i; *len = 0; #ifndef WINUAE_FOR_HATARI /* Under Hatari, we save all FPU variables, even if fpu_model==0 */ if (currprefs.fpu_model == 0) return 0; #endif if (dstptr) dstbak = dst = dstptr; else dstbak = dst = xmalloc(uae_u8, 4 + 4 + 8 * 10 + 6 * 4 + 2 + 6 * 4 + 20 * 4); save_u32 (currprefs.fpu_model); save_u32 (0x80000000 | 0x20000000 | (regs.fp_ea_set ? 0x00000001 : 0x00000000)); for (i = 0; i < 8; i++) { fpp_from_exten_fmovem(®s.fp[i], &w1, &w2, &w3); save_u16 (w1 >> 16); save_u32 (w2); save_u32 (w3); } save_u32 (regs.fpcr); save_u32 (regs.fpsr); save_u32 (regs.fpiar); save_u32 (-1); save_u32 (1); v = regs.fpu_state; v |= regs.fpu_exp_state << 4; v |= regs.fp_unimp_pend << 8; v |= regs.fp_exp_pend << 16; save_u32(v); save_u16(regs.fp_opword); save_u32(regs.fp_ea); if (currprefs.fpu_model == 68060 || currprefs.fpu_model >= 68881) { save_u32(fsave_data.ccr); save_u32(fsave_data.eo[0]); save_u32(fsave_data.eo[1]); save_u32(fsave_data.eo[2]); } if (currprefs.fpu_model == 68060) { save_u32(fsave_data.v); } if (currprefs.fpu_model == 68040) { save_u32(fsave_data.fpiarcu); save_u32(fsave_data.cmdreg3b); save_u32(fsave_data.cmdreg1b); save_u32(fsave_data.stag); save_u32(fsave_data.dtag); save_u32(fsave_data.e1); save_u32(fsave_data.e3); save_u32(fsave_data.t); save_u32(fsave_data.fpt[0]); save_u32(fsave_data.fpt[1]); save_u32(fsave_data.fpt[2]); save_u32(fsave_data.et[0]); save_u32(fsave_data.et[1]); save_u32(fsave_data.et[2]); save_u32(fsave_data.wbt[0]); save_u32(fsave_data.wbt[1]); save_u32(fsave_data.wbt[2]); save_u32(fsave_data.grs); save_u32(fsave_data.wbte15); save_u32(fsave_data.wbtm66); } *len = dst - dstbak; return dstbak; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/fpp.h000066400000000000000000000114051504763705000230560ustar00rootroot00000000000000 /* single : S 8*E 23*F */ /* double : S 11*E 52*F */ /* extended : S 15*E 64*F */ /* E = 0 & F = 0 -> 0 */ /* E = MAX & F = 0 -> Infin */ /* E = MAX & F # 0 -> NotANumber */ /* E = biased by 127 (single) ,1023 (double) ,16383 (extended) */ #define FPSR_BSUN 0x00008000 #define FPSR_SNAN 0x00004000 #define FPSR_OPERR 0x00002000 #define FPSR_OVFL 0x00001000 #define FPSR_UNFL 0x00000800 #define FPSR_DZ 0x00000400 #define FPSR_INEX2 0x00000200 #define FPSR_INEX1 0x00000100 extern void fp_init_native(void); #ifdef MSVC_LONG_DOUBLE extern bool fp_init_native_80(void); #endif extern void fp_init_softfloat(int); extern void fpsr_set_exception(uae_u32 exception); extern void fpu_modechange(void); extern void fpu_clearstatus(void); #ifdef WINUAE_FOR_HATARI extern double softfloat_tan(double v); #endif extern void fpp_set_fpcr(uae_u32 val); extern void fpp_set_fpsr(uae_u32 val); extern void fpp_set_fpiar(uae_u32 val); extern uae_u32 fpp_get_fpsr(void); extern uae_u32 fpp_get_fpcr(void); extern uae_u32 fpp_get_fpiar(void); #if defined(CPU_i386) || defined(CPU_x86_64) extern void init_fpucw_x87(void); #ifdef MSVC_LONG_DOUBLE extern void init_fpucw_x87_80(void); #endif #endif #define PREC_NORMAL 0 #define PREC_FLOAT 1 #define PREC_DOUBLE 2 #define PREC_EXTENDED 3 #define FPU_FEATURE_EXCEPTIONS 1 #define FPU_FEATURE_DENORMALS 2 typedef void (*FPP_ABQS)(fpdata*, fpdata*, uae_u64*, uae_u8*); typedef void (*FPP_AB)(fpdata*, fpdata*); typedef void (*FPP_ABP)(fpdata*, fpdata*, int); typedef void (*FPP_A)(fpdata*); typedef void (*FPP_ABC)(fpdata*, fpdata*, fpdata*); typedef bool (*FPP_IS)(fpdata*); typedef void (*FPP_SET_MODE)(uae_u32); typedef void (*FPP_GET_STATUS)(uae_u32*); typedef void (*FPP_CLEAR_STATUS)(void); typedef uae_u32 (*FPP_SUPPORT_FLAGS)(void); typedef void (*FPP_FROM_NATIVE)(fptype, fpdata*); typedef void (*FPP_TO_NATIVE)(fptype*, fpdata*); typedef void (*FPP_FROM_INT)(fpdata*,uae_s32); typedef uae_s64 (*FPP_TO_INT)(fpdata*, int); typedef void (*FPP_TO_SINGLE)(fpdata*, uae_u32); typedef uae_u32 (*FPP_FROM_SINGLE)(fpdata*); typedef void (*FPP_TO_DOUBLE)(fpdata*, uae_u32, uae_u32); typedef void (*FPP_FROM_DOUBLE)(fpdata*, uae_u32*, uae_u32*); typedef void (*FPP_TO_EXTEN)(fpdata*, uae_u32, uae_u32, uae_u32); typedef void (*FPP_FROM_EXTEN)(fpdata*, uae_u32*, uae_u32*, uae_u32*); typedef void (*FPP_PACK)(fpdata*, uae_u32*, int); typedef const TCHAR* (*FPP_PRINT)(fpdata*,int); typedef uae_u32 (*FPP_GET32)(void); typedef void (*FPP_DENORMALIZE)(fpdata*,int); extern FPP_PRINT fpp_print; extern FPP_IS fpp_unset_snan; extern FPP_IS fpp_is_init; extern FPP_IS fpp_is_snan; extern FPP_IS fpp_is_nan; extern FPP_IS fpp_is_infinity; extern FPP_IS fpp_is_zero; extern FPP_IS fpp_is_neg; extern FPP_IS fpp_is_denormal; extern FPP_IS fpp_is_unnormal; extern FPP_A fpp_fix_infinity; extern FPP_GET_STATUS fpp_get_status; extern FPP_CLEAR_STATUS fpp_clear_status; extern FPP_SET_MODE fpp_set_mode; extern FPP_SUPPORT_FLAGS fpp_get_support_flags; extern FPP_TO_INT fpp_to_int; extern FPP_FROM_INT fpp_from_int; extern FPP_PACK fpp_to_pack; extern FPP_PACK fpp_from_pack; extern FPP_TO_SINGLE fpp_to_single; extern FPP_FROM_SINGLE fpp_from_single; extern FPP_TO_DOUBLE fpp_to_double; extern FPP_FROM_DOUBLE fpp_from_double; extern FPP_TO_EXTEN fpp_to_exten; extern FPP_FROM_EXTEN fpp_from_exten; extern FPP_TO_EXTEN fpp_to_exten_fmovem; extern FPP_FROM_EXTEN fpp_from_exten_fmovem; extern FPP_A fpp_round_single; extern FPP_A fpp_round_double; extern FPP_A fpp_round32; extern FPP_A fpp_round64; extern FPP_A fpp_normalize; extern FPP_DENORMALIZE fpp_denormalize; extern FPP_A fpp_get_internal_overflow; extern FPP_A fpp_get_internal_underflow; extern FPP_A fpp_get_internal_round_all; extern FPP_A fpp_get_internal_round; extern FPP_A fpp_get_internal_round_exten; extern FPP_A fpp_get_internal; extern FPP_GET32 fpp_get_internal_grs; extern FPP_AB fpp_int; extern FPP_AB fpp_sinh; extern FPP_AB fpp_intrz; extern FPP_ABP fpp_sqrt; extern FPP_AB fpp_lognp1; extern FPP_AB fpp_etoxm1; extern FPP_AB fpp_tanh; extern FPP_AB fpp_atan; extern FPP_AB fpp_atanh; extern FPP_AB fpp_sin; extern FPP_AB fpp_asin; extern FPP_AB fpp_tan; extern FPP_AB fpp_etox; extern FPP_AB fpp_twotox; extern FPP_AB fpp_tentox; extern FPP_AB fpp_logn; extern FPP_AB fpp_log10; extern FPP_AB fpp_log2; extern FPP_ABP fpp_abs; extern FPP_AB fpp_cosh; extern FPP_ABP fpp_neg; extern FPP_AB fpp_acos; extern FPP_AB fpp_cos; extern FPP_ABC fpp_sincos; extern FPP_AB fpp_getexp; extern FPP_AB fpp_getman; extern FPP_ABP fpp_div; extern FPP_ABQS fpp_mod; extern FPP_ABP fpp_add; extern FPP_ABP fpp_mul; extern FPP_ABQS fpp_rem; extern FPP_AB fpp_scale; extern FPP_ABP fpp_sub; extern FPP_AB fpp_sgldiv; extern FPP_AB fpp_sglmul; extern FPP_AB fpp_cmp; extern FPP_AB fpp_tst; extern FPP_ABP fpp_move; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/fpp_native.c000066400000000000000000000740021504763705000244210ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68881/68882/68040/68060 FPU emulation * * Copyright 1996 Herman ten Brugge * Modified 2005 Peter Keunecke * 68040+ exceptions and more by Toni Wilen */ #define __USE_ISOC9X /* We might be able to pick up a NaN */ #include #include #include #include "sysconfig.h" #include "sysdeps.h" #ifdef WINUAE_FOR_HATARI #include "main.h" #include "hatari-glue.h" #endif #define USE_HOST_ROUNDING 1 #define SOFTFLOAT_CONVERSIONS 1 #include "options_cpu.h" #include "memory.h" #include "newcpu.h" #include "fpp.h" #include "uae/attributes.h" #include "uae/vm.h" #ifdef JIT uae_u32 xhex_exp_1[] ={0xa2bb4a9a, 0xadf85458, 0x4000}; uae_u32 xhex_ln_10[] ={0xaaa8ac17, 0x935d8ddd, 0x4000}; uae_u32 xhex_l10_2[] ={0xfbcff798, 0x9a209a84, 0x3ffd}; uae_u32 xhex_l10_e[] ={0x37287195, 0xde5bd8a9, 0x3ffd}; uae_u32 xhex_1e16[] ={0x04000000, 0x8e1bc9bf, 0x4034}; uae_u32 xhex_1e32[] ={0x2b70b59e, 0x9dc5ada8, 0x4069}; uae_u32 xhex_1e64[] ={0xffcfa6d5, 0xc2781f49, 0x40d3}; uae_u32 xhex_1e128[] ={0x80e98ce0, 0x93ba47c9, 0x41a8}; uae_u32 xhex_1e256[] ={0x9df9de8e, 0xaa7eebfb, 0x4351}; uae_u32 xhex_1e512[] ={0xa60e91c7, 0xe319a0ae, 0x46a3}; uae_u32 xhex_1e1024[]={0x81750c17, 0xc9767586, 0x4d48}; uae_u32 xhex_1e2048[]={0xc53d5de5, 0x9e8b3b5d, 0x5a92}; uae_u32 xhex_1e4096[]={0x8a20979b, 0xc4605202, 0x7525}; double fp_1e8 = 1.0e8; float fp_1e0 = 1, fp_1e1 = 10, fp_1e2 = 100, fp_1e4 = 10000; #endif #ifdef USE_LONG_DOUBLE static uae_u32 xhex_nan[] ={0xffffffff, 0xffffffff, 0x7fff}; static long double *fp_nan = (long double *)xhex_nan; #else static uae_u32 dhex_nan[] ={0xffffffff, 0x7fffffff}; static double *fp_nan = (double *)dhex_nan; #endif static const double twoto32 = 4294967296.0; #define FPCR_ROUNDING_MODE 0x00000030 #define FPCR_ROUND_NEAR 0x00000000 #define FPCR_ROUND_ZERO 0x00000010 #define FPCR_ROUND_MINF 0x00000020 #define FPCR_ROUND_PINF 0x00000030 #define FPCR_ROUNDING_PRECISION 0x000000c0 #define FPCR_PRECISION_SINGLE 0x00000040 #define FPCR_PRECISION_DOUBLE 0x00000080 #define FPCR_PRECISION_EXTENDED 0x00000000 #ifdef SOFTFLOAT_CONVERSIONS static struct float_status fs; #endif static uae_u32 fpu_mode_control = 0; static int fpu_prec; static int temp_prec; #ifndef WINUAE_FOR_HATARI #if defined(CPU_i386) || defined(CPU_x86_64) /* The main motivation for dynamically creating an x86(-64) function in * memory is because MSVC (x64) does not allow you to use inline assembly, * and the x86-64 versions of _control87/_controlfp functions only modifies * SSE2 registers. */ static uae_u16 x87_cw = 0; static uae_u8 *x87_fldcw_code = NULL; typedef void (uae_cdecl *x87_fldcw_function)(void); void init_fpucw_x87(void) { if (x87_fldcw_code) { return; } x87_fldcw_code = (uae_u8 *) uae_vm_alloc( uae_vm_page_size(), UAE_VM_32BIT, UAE_VM_READ_WRITE_EXECUTE); uae_u8 *c = x87_fldcw_code; /* mov eax,0x0 */ *(c++) = 0xb8; *(c++) = 0x00; *(c++) = 0x00; *(c++) = 0x00; *(c++) = 0x00; #ifdef CPU_x86_64 /* Address override prefix */ *(c++) = 0x67; #endif /* fldcw WORD PTR [eax+addr] */ *(c++) = 0xd9; *(c++) = 0xa8; *(c++) = (((uintptr_t) &x87_cw) ) & 0xff; *(c++) = (((uintptr_t) &x87_cw) >> 8) & 0xff; *(c++) = (((uintptr_t) &x87_cw) >> 16) & 0xff; *(c++) = (((uintptr_t) &x87_cw) >> 24) & 0xff; /* ret */ *(c++) = 0xc3; /* Write-protect the function */ uae_vm_protect(x87_fldcw_code, uae_vm_page_size(), UAE_VM_READ_EXECUTE); } static void set_fpucw_x87(uae_u32 m68k_cw) { #ifdef _MSC_VER static int ex = 0; // RN, RZ, RM, RP static const unsigned int fp87_round[4] = { _RC_NEAR, _RC_CHOP, _RC_DOWN, _RC_UP }; // Extend X, Single S, Double D, Undefined static const unsigned int fp87_prec[4] = { _PC_53, _PC_24, _PC_53, 0 }; int round = (m68k_cw >> 4) & 3; #ifdef WIN64 // x64 only sets SSE2, must also call x87_fldcw_code() to set FPU rounding mode. _controlfp(ex | fp87_round[round], _MCW_RC); #else int prec = (m68k_cw >> 6) & 3; // x86 sets both FPU and SSE2 rounding mode, don't need x87_fldcw_code() _control87(ex | fp87_round[round] | fp87_prec[prec], _MCW_RC | _MCW_PC); return; #endif #endif static const uae_u16 x87_cw_tab[] = { #ifdef USE_LONG_DOUBLE 0x137f, 0x1f7f, 0x177f, 0x1b7f, /* Extended */ #else 0x127f, 0x1e7f, 0x167f, 0x1a7f, /* Double */ #endif 0x107f, 0x1c7f, 0x147f, 0x187f, /* Single */ 0x127f, 0x1e7f, 0x167f, 0x1a7f, /* Double */ 0x127f, 0x1e7f, 0x167f, 0x1a7f, /* undefined (Double) */ }; x87_cw = x87_cw_tab[(m68k_cw >> 4) & 0xf]; #if defined(X86_MSVC_ASSEMBLY) && 0 __asm { fldcw word ptr x87_cw } #elif defined(__GNUC__) && 0 __asm__("fldcw %0" : : "m" (*&x87_cw)); #else ((x87_fldcw_function) x87_fldcw_code)(); #endif } #endif /* defined(CPU_i386) || defined(CPU_x86_64) */ #endif /* !WINUAE_FOR_HATARI */ static void native_set_fpucw(uae_u32 m68k_cw) { #ifndef WINUAE_FOR_HATARI #if defined(CPU_i386) || defined(CPU_x86_64) set_fpucw_x87(m68k_cw); #endif #endif /* ! WINUAE_FOR_HATARI */ } /* Functions for setting host/library modes and getting status */ static void fp_set_mode(uae_u32 mode_control) { if (mode_control == fpu_mode_control && !currprefs.compfpu) return; switch(mode_control & FPCR_ROUNDING_PRECISION) { case FPCR_PRECISION_EXTENDED: // X fpu_prec = PREC_EXTENDED; break; case FPCR_PRECISION_SINGLE: // S fpu_prec = PREC_FLOAT; break; case FPCR_PRECISION_DOUBLE: // D default: // undefined fpu_prec = PREC_DOUBLE; break; } #if USE_HOST_ROUNDING if ((mode_control & FPCR_ROUNDING_MODE) != (fpu_mode_control & FPCR_ROUNDING_MODE)) { switch(mode_control & FPCR_ROUNDING_MODE) { case FPCR_ROUND_NEAR: // to neareset fesetround(FE_TONEAREST); break; case FPCR_ROUND_ZERO: // to zero fesetround(FE_TOWARDZERO); break; case FPCR_ROUND_MINF: // to minus fesetround(FE_DOWNWARD); break; case FPCR_ROUND_PINF: // to plus fesetround(FE_UPWARD); break; } } native_set_fpucw(mode_control); #endif fpu_mode_control = mode_control; } static void fp_get_status(uae_u32 *status) { // These can't be properly emulated using host FPU. #if 0 int exp_flags = fetestexcept(FE_ALL_EXCEPT); if (exp_flags) { if (exp_flags & FE_INEXACT) *status |= FPSR_INEX2; if (exp_flags & FE_DIVBYZERO) *status |= FPSR_DZ; if (exp_flags & FE_UNDERFLOW) *status |= FPSR_UNFL; if (exp_flags & FE_OVERFLOW) *status |= FPSR_OVFL; if (exp_flags & FE_INVALID) *status |= FPSR_OPERR; } /* FIXME: how to detect SNAN? */ #endif } static uae_u32 fp_get_support_flags(void) { return 0; } static void fp_clear_status(void) { #if 0 feclearexcept (FE_ALL_EXCEPT); #endif } /* Functions for detecting float type */ static bool fp_is_init(fpdata *fpd) { return false; } static bool fp_is_snan(fpdata *fpd) { return 0; /* FIXME: how to detect SNAN */ } static bool fp_unset_snan(fpdata *fpd) { /* FIXME: how to unset SNAN */ return 0; } static bool fp_is_nan(fpdata *fpd) { return isnan(fpd->fp) != 0; } static bool fp_is_infinity(fpdata *fpd) { return isinf(fpd->fp) != 0; } static bool fp_is_zero(fpdata *fpd) { return fpd->fp == 0.0; } static bool fp_is_neg(fpdata *fpd) { return signbit(fpd->fp) != 0; } static bool fp_is_denormal(fpdata *fpd) { return false; //return (isnormal(fpd->fp) == 0); /* FIXME: how to differ denormal/unnormal? */ } static bool fp_is_unnormal(fpdata *fpd) { return false; //return (isnormal(fpd->fp) == 0); /* FIXME: how to differ denormal/unnormal? */ } /* Functions for converting between float formats */ /* FIXME: how to preserve/fix denormals and unnormals? */ static void fp_to_native(fptype *fp, fpdata *fpd) { *fp = fpd->fp; } static void fp_from_native(fptype fp, fpdata *fpd) { fpd->fp = fp; } static void fp_to_single(fpdata *fpd, uae_u32 wrd1) { union { float f; uae_u32 u; } val; val.u = wrd1; fpd->fp = (fptype) val.f; } static uae_u32 fp_from_single(fpdata *fpd) { union { float f; uae_u32 u; } val; val.f = (float) fpd->fp; return val.u; } static void fp_to_double(fpdata *fpd, uae_u32 wrd1, uae_u32 wrd2) { union { double d; uae_u32 u[2]; } val; #ifdef WORDS_BIGENDIAN val.u[0] = wrd1; val.u[1] = wrd2; #else val.u[1] = wrd1; val.u[0] = wrd2; #endif fpd->fp = (fptype) val.d; } static void fp_from_double(fpdata *fpd, uae_u32 *wrd1, uae_u32 *wrd2) { union { double d; uae_u32 u[2]; } val; val.d = (double) fpd->fp; #ifdef WORDS_BIGENDIAN *wrd1 = val.u[0]; *wrd2 = val.u[1]; #else *wrd1 = val.u[1]; *wrd2 = val.u[0]; #endif } #ifdef USE_LONG_DOUBLE static void fp_to_exten(fpdata *fpd, uae_u32 wrd1, uae_u32 wrd2, uae_u32 wrd3) { union { long double ld; uae_u32 u[3]; } val; #if WORDS_BIGENDIAN val.u[0] = (wrd1 & 0xffff0000) | ((wrd2 & 0xffff0000) >> 16); val.u[1] = (wrd2 & 0x0000ffff) | ((wrd3 & 0xffff0000) >> 16); val.u[2] = (wrd3 & 0x0000ffff) << 16; #else val.u[0] = wrd3; val.u[1] = wrd2; val.u[2] = wrd1 >> 16; #endif fpd->fp = val.ld; } static void fp_from_exten(fpdata *fpd, uae_u32 *wrd1, uae_u32 *wrd2, uae_u32 *wrd3) { union { long double ld; uae_u32 u[3]; } val; val.ld = fpd->fp; #if WORDS_BIGENDIAN *wrd1 = val.u[0] & 0xffff0000; *wrd2 = ((val.u[0] & 0x0000ffff) << 16) | ((val.u[1] & 0xffff0000) >> 16); *wrd3 = ((val.u[1] & 0x0000ffff) << 16) | ((val.u[2] & 0xffff0000) >> 16); #else *wrd3 = val.u[0]; *wrd2 = val.u[1]; *wrd1 = val.u[2] << 16; #endif } #else // if !USE_LONG_DOUBLE static void fp_to_exten(fpdata *fpd, uae_u32 wrd1, uae_u32 wrd2, uae_u32 wrd3) { if (!currprefs.cachesize || !currprefs.compfpu) { floatx80 fx80; fx80.high = wrd1 >> 16; fx80.low = (((uae_u64)wrd2) << 32) | wrd3; fs.float_exception_flags = 0; float64 f = floatx80_to_float64(fx80, &fs); // overflow -> infinity if (fs.float_exception_flags & float_flag_overflow) { f = 0x7ff0000000000000 | (f & 0x8000000000000000); } fp_to_double(fpd, f >> 32, (uae_u32)f); } else { double frac; if ((wrd1 & 0x7fff0000) == 0 && wrd2 == 0 && wrd3 == 0) { fpd->fp = (wrd1 & 0x80000000) ? -0.0 : +0.0; return; } frac = ((double)wrd2 + ((double)wrd3 / twoto32)) / 2147483648.0; if (wrd1 & 0x80000000) { frac = -frac; } fpd->fp = ldexp (frac, ((wrd1 >> 16) & 0x7fff) - 16383); } } static void fp_from_exten(fpdata *fpd, uae_u32 *wrd1, uae_u32 *wrd2, uae_u32 *wrd3) { if (!currprefs.cachesize || !currprefs.compfpu) { uae_u32 w1, w2; fp_from_double(fpd, &w1, &w2); floatx80 f = float64_to_floatx80(((uae_u64)w1 << 32) | w2, &fs); *wrd1 = f.high << 16; *wrd2 = f.low >> 32; *wrd3 = (uae_u32)f.low; } else { int expon; double frac; fptype v; if (fp_is_zero(fpd)) { *wrd1 = signbit(fpd->fp) ? 0x80000000 : 0; *wrd2 = 0; *wrd3 = 0; return; } else if (fp_is_nan(fpd)) { *wrd1 = 0x7fff0000; *wrd2 = 0xffffffff; *wrd3 = 0xffffffff; return; } v = fpd->fp; if (v < 0) { *wrd1 = 0x80000000; v = -v; } else { *wrd1 = 0; } frac = frexp (v, &expon); frac += 0.5 / (twoto32 * twoto32); if (frac >= 1.0) { frac /= 2.0; expon++; } *wrd1 |= (((expon + 16383 - 1) & 0x7fff) << 16); *wrd2 = (uae_u32) (frac * twoto32); *wrd3 = (uae_u32) ((frac * twoto32 - *wrd2) * twoto32); } } #endif // !USE_LONG_DOUBLE #if USE_HOST_ROUNDING == 0 #ifdef USE_LONG_DOUBLE #define fp_round_to_minus_infinity(x) floorl(x) #define fp_round_to_plus_infinity(x) ceill(x) #define fp_round_to_zero(x) ((x) >= 0.0 ? floorl(x) : ceill(x)) #define fp_round_to_nearest(x) roundl(x) #else // if !USE_LONG_DOUBLE #define fp_round_to_minus_infinity(x) floor(x) #define fp_round_to_plus_infinity(x) ceil(x) #define fp_round_to_zero(x) ((x) >= 0.0 ? floor(x) : ceil(x)) #define fp_round_to_nearest(x) round(x) #endif // !USE_LONG_DOUBLE #endif // USE_HOST_ROUNDING static uae_s64 fp_to_int(fpdata *src, int size) { static const fptype fxsizes[6] = { -128.0, 127.0, -32768.0, 32767.0, -2147483648.0, 2147483647.0 }; fptype fp = src->fp; fp_is_init(src); if (fp_is_nan(src)) { uae_u32 w1, w2, w3; fp_from_exten(src, &w1, &w2, &w3); uae_s64 v = 0; // return mantissa switch (size) { case 0: v = w2 >> 24; break; case 1: v = w2 >> 16; break; case 2: v = w2 >> 0; break; } return v; } if (fp < fxsizes[size * 2 + 0]) { fp = fxsizes[size * 2 + 0]; } if (fp > fxsizes[size * 2 + 1]) { fp = fxsizes[size * 2 + 1]; } #if USE_HOST_ROUNDING return lrintl(fp); #else uae_s64 result = (int)fp; switch (regs.fpcr & 0x30) { case FPCR_ROUND_ZERO: result = (int)fp_round_to_zero (fp); break; case FPCR_ROUND_MINF: result = (int)fp_round_to_minus_infinity (fp); break; case FPCR_ROUND_NEAR: result = fp_round_to_nearest (fp); break; case FPCR_ROUND_PINF: result = (int)fp_round_to_plus_infinity (fp); break; } return result; #endif } static void fp_from_int(fpdata *fpd, uae_s32 src) { fpd->fp = (fptype) src; } /* Functions for rounding */ // round to float with extended precision exponent static void fp_round32(fpdata *fpd) { int expon; float mant; mant = (float)(frexpl(fpd->fp, &expon) * 2.0); fpd->fp = ldexpl((fptype)mant, expon - 1); } // round to double with extended precision exponent static void fp_round64(fpdata *fpd) { int expon; double mant; mant = (double)(frexpl(fpd->fp, &expon) * 2.0); fpd->fp = ldexpl((fptype)mant, expon - 1); } // round to float static void fp_round_single(fpdata *fpd) { fpd->fp = (float) fpd->fp; } // round to double static void fp_round_double(fpdata *fpd) { #ifdef USE_LONG_DOUBLE fpd->fp = (double) fpd->fp; #endif } static const TCHAR *fp_print(fpdata *fpd, int mode) { static TCHAR fsout[32]; bool n; if (mode < 0) { uae_u32 w1, w2, w3; fp_from_exten(fpd, &w1, &w2, &w3); _stprintf(fsout, _T("%04X-%08X-%08X"), w1 >> 16, w2, w3); return fsout; } n = signbit(fpd->fp) ? 1 : 0; if(isinf(fpd->fp)) { _stprintf(fsout, _T("%c%s"), n ? '-' : '+', _T("inf")); } else if(isnan(fpd->fp)) { _stprintf(fsout, _T("%c%s"), n ? '-' : '+', _T("nan")); } else { #ifdef USE_LONG_DOUBLE _stprintf(fsout, _T("#%Le"), fpd->fp); #else _stprintf(fsout, _T("#%e"), fpd->fp); #endif } if (mode == 0 || mode > _tcslen(fsout)) return fsout; fsout[mode] = 0; return fsout; } static void fp_round_prec(fpdata *fpd, int prec) { if (prec == PREC_DOUBLE) { fp_round_double(fpd); } else if (prec == PREC_FLOAT) { fp_round_single(fpd); } } static void fp_round(fpdata *fpd) { if (!currprefs.fpu_strict) return; fp_round_prec(fpd, fpu_prec); } static void fp_set_prec(int prec) { #if 0 temp_fpu_mode_control = fpu_mode_control; if (prec && fpu_prec > prec) { fpu_mode_control &= ~FPCR_ROUNDING_PRECISION; switch (prec) { case PREC_EXTENDED: fpu_mode_control |= FPCR_PRECISION_EXTENDED; break; case PREC_DOUBLE: default: fpu_mode_control |= FPCR_PRECISION_DOUBLE; break; case PREC_FLOAT: fpu_mode_control |= FPCR_PRECISION_SINGLE; break; } fp_set_mode(fpu_mode_control); } #endif temp_prec = prec; } static void fp_reset_prec(fpdata *fpd) { #if 0 fp_set_mode(temp_fpu_mode_control); #else int prec = temp_prec; if (temp_prec == PREC_NORMAL) prec = fpu_prec; fp_round_prec(fpd, prec); #endif } // Use default precision/rounding mode when calling C-library math functions. static void fp_normal_prec(void) { temp_prec = fpu_mode_control; #ifdef USE_LONG_DOUBLE if ((fpu_mode_control & FPCR_ROUNDING_PRECISION) != FPCR_PRECISION_EXTENDED || (fpu_mode_control & FPCR_ROUNDING_MODE) != FPCR_ROUND_NEAR) { fp_set_mode(FPCR_PRECISION_EXTENDED | FPCR_ROUND_NEAR); } #else if ((fpu_mode_control & FPCR_ROUNDING_PRECISION) == FPCR_PRECISION_SINGLE || (fpu_mode_control & FPCR_ROUNDING_MODE) != FPCR_ROUND_NEAR) { fp_set_mode(FPCR_PRECISION_DOUBLE | FPCR_ROUND_NEAR); } #endif } static void fp_reset_normal_prec(void) { fp_set_mode(temp_prec); } /* Arithmetic functions */ static void fp_move(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = b->fp; fp_reset_prec(a); } static void fp_int(fpdata *a, fpdata *b) { fptype bb = b->fp; #if USE_HOST_ROUNDING a->fp = rintl(bb); #else switch (regs.fpcr & FPCR_ROUNDING_MODE) { case FPCR_ROUND_NEAR: a->fp = fp_round_to_nearest(bb); case FPCR_ROUND_ZERO: a->fp = fp_round_to_zero(bb); case FPCR_ROUND_MINF: a->fp = fp_round_to_minus_infinity(bb); case FPCR_ROUND_PINF: a->fp = fp_round_to_plus_infinity(bb); default: /* never reached */ break; } #endif } static void fp_getexp(fpdata *a, fpdata *b) { int expon; fp_normal_prec(); frexpl(b->fp, &expon); a->fp = (fptype)expon - 1; fp_reset_normal_prec(); } static void fp_getman(fpdata *a, fpdata *b) { int expon; fp_normal_prec(); a->fp = frexpl(b->fp, &expon) * 2.0; fp_reset_normal_prec(); } static void fp_div(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = a->fp / b->fp; fp_reset_prec(a); } static void fp_mod(fpdata *a, fpdata *b, uae_u64 *q, uae_u8 *s) { fptype quot; #if USE_HOST_ROUNDING quot = truncl(a->fp / b->fp); #else quot = fp_round_to_zero(a->fp / b->fp); #endif if (quot < 0.0) { *s = 1; quot = -quot; } else { *s = 0; } *q = (uae_u64)quot; a->fp = fmodl(a->fp, b->fp); fp_round(a); } static void fp_rem(fpdata *a, fpdata *b, uae_u64 *q, uae_u8 *s) { fptype quot; #if USE_HOST_ROUNDING quot = roundl(a->fp / b->fp); #else quot = fp_round_to_nearest(a->fp / b->fp); #endif if (quot < 0.0) { *s = 1; quot = -quot; } else { *s = 0; } *q = (uae_u64)quot; a->fp = remainderl(a->fp, b->fp); fp_round(a); } static void fp_scale(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = ldexpl(a->fp, (int)b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_sinh(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = sinhl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_intrz(fpdata *a, fpdata *b) { #if USE_HOST_ROUNDING a->fp = truncl(b->fp); #else a->fp = fp_round_to_zero (b->fp); #endif fp_round(a); } static void fp_sqrt(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = sqrtl(b->fp); fp_reset_prec(a); } static void fp_lognp1(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = log1pl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_etoxm1(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = expm1l(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_tanh(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = tanhl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_atan(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = atanl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_atanh(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = atanhl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_sin(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = sinl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_asin(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = asinl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_tan(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = tanl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_etox(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = expl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_twotox(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = powl(2.0, b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_tentox(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = powl(10.0, b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_logn(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = logl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_log10(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = log10l(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_log2(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = log2l(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_abs(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = fabsl(b->fp); fp_reset_prec(a); } static void fp_cosh(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = coshl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_neg(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = -b->fp; fp_reset_prec(a); } static void fp_acos(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = acosl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_cos(fpdata *a, fpdata *b) { fp_normal_prec(); a->fp = cosl(b->fp); fp_reset_normal_prec(); fp_round(a); } static void fp_sincos(fpdata *a, fpdata *b, fpdata *c) { fp_normal_prec(); c->fp = cosl(b->fp); a->fp = sinl(b->fp); fp_reset_normal_prec(); fp_round(a); fp_round(c); } static void fp_sub(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = a->fp - b->fp; fp_reset_prec(a); } static void fp_add(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = a->fp + b->fp; fp_reset_prec(a); } static void fp_mul(fpdata *a, fpdata *b, int prec) { fp_set_prec(prec); a->fp = a->fp * b->fp; fp_reset_prec(a); } static void fp_sglmul(fpdata *a, fpdata *b) { fptype z; float mant; int expon; /* FIXME: truncate mantissa of a and b to single precision */ z = a->fp * b->fp; mant = (float)(frexpl(z, &expon) * 2.0); a->fp = ldexpl((fptype)mant, expon - 1); } static void fp_sgldiv(fpdata *a, fpdata *b) { fptype z; float mant; int expon; z = a->fp / b->fp; mant = (float)(frexpl(z, &expon) * 2.0); a->fp = ldexpl((fptype)mant, expon - 1); } static void fp_normalize(fpdata *a) { } static void fp_cmp(fpdata *a, fpdata *b) { fptype v = 1.0; if (currprefs.fpu_strict) { fp_is_init(a); bool a_neg = fp_is_neg(a); bool a_inf = fp_is_infinity(a); bool a_zero = fp_is_zero(a); bool a_nan = fp_is_nan(a); fp_is_init(b); bool b_neg = fp_is_neg(b); bool b_inf = fp_is_infinity(b); bool b_zero = fp_is_zero(b); bool b_nan = fp_is_nan(b); if (a_nan || b_nan) { // FCMP never returns N + NaN v = *fp_nan; } else if (a_zero && b_zero) { if ((a_neg && b_neg) || (a_neg && !b_neg)) v = -0.0; else v = 0.0; } else if (a_zero && b_inf) { if (!b_neg) v = -1.0; else v = 1.0; } else if (a_inf && b_zero) { if (!a_neg) v = -1.0; else v = 1.0; } else if (a_inf && b_inf) { if (a_neg == b_neg) v = 0.0; if ((a_neg && b_neg) || (a_neg && !b_neg)) v = -v; } else if (a_inf) { if (a_neg) v = -1.0; } else if (b_inf) { if (!b_neg) v = -1.0; } else { v = a->fp - b->fp; fp_clear_status(); } } else { v = a->fp - b->fp; fp_clear_status(); } a->fp = v; } static void fp_tst(fpdata *a, fpdata *b) { a->fp = b->fp; } /* Functions for returning exception state data */ static void fp_get_internal_overflow(fpdata *fpd) { fpd->fp = 0; } static void fp_get_internal_underflow(fpdata *fpd) { fpd->fp = 0; } static void fp_get_internal_round_all(fpdata *fpd) { fpd->fp = 0; } static void fp_get_internal_round(fpdata *fpd) { fpd->fp = 0; } static void fp_get_internal_round_exten(fpdata *fpd) { fpd->fp = 0; } static void fp_get_internal(fpdata *fpd) { fpd->fp = 0; } static uae_u32 fp_get_internal_grs(void) { return 0; } /* Function for denormalizing */ static void fp_denormalize(fpdata *fpd, int esign) { } static void fp_from_pack (fpdata *src, uae_u32 *wrd, int kfactor) { int i, j, t; int exp; int ndigits; char *cp, *strp; char str[100]; fptype fp; fp_is_init(src); if (fp_is_nan(src)) { // copy bit by bit, handle signaling nan fpp_from_exten(src, &wrd[0], &wrd[1], &wrd[2]); return; } if (fp_is_infinity(src)) { // extended exponent and all 0 packed fraction fpp_from_exten(src, &wrd[0], &wrd[1], &wrd[2]); wrd[1] = wrd[2] = 0; return; } wrd[0] = wrd[1] = wrd[2] = 0; fp_to_native(&fp, src); #ifdef USE_LONG_DOUBLE sprintf (str, "%#.17Le", fp); #else sprintf (str, "%#.17e", fp); #endif // get exponent cp = str; while (*cp != 'e') { if (*cp == 0) return; cp++; } cp++; if (*cp == '+') cp++; exp = atoi (cp); // remove trailing zeros cp = str; while (*cp != 'e') { cp++; } cp[0] = 0; cp--; while (cp > str && *cp == '0') { *cp = 0; cp--; } cp = str; // get sign if (*cp == '-') { cp++; wrd[0] = 0x80000000; } else if (*cp == '+') { cp++; } strp = cp; if (kfactor <= 0) { ndigits = abs (exp) + (-kfactor) + 1; } else { if (kfactor > 17) { kfactor = 17; fpsr_set_exception(FPSR_OPERR); } ndigits = kfactor; } if (ndigits < 0) ndigits = 0; if (ndigits > 16) ndigits = 16; // remove decimal point strp[1] = strp[0]; strp++; // add trailing zeros i = uaestrlen(strp); cp = strp + i; while (i < ndigits) { *cp++ = '0'; i++; } i = ndigits + 1; while (i < 17) { strp[i] = 0; i++; } *cp = 0; i = ndigits - 1; // need to round? if (i >= 0 && strp[i + 1] >= '5') { while (i >= 0) { strp[i]++; if (strp[i] <= '9') break; if (i == 0) { strp[i] = '1'; exp++; } else { strp[i] = '0'; } i--; } } strp[ndigits] = 0; // store first digit of mantissa cp = strp; wrd[0] |= *cp++ - '0'; // store rest of mantissa for (j = 1; j < 3; j++) { for (i = 0; i < 8; i++) { wrd[j] <<= 4; if (*cp >= '0' && *cp <= '9') wrd[j] |= *cp++ - '0'; } } // exponent if (exp < 0) { wrd[0] |= 0x40000000; exp = -exp; } if (exp > 9999) // ?? exp = 9999; if (exp > 999) { int d = exp / 1000; wrd[0] |= d << 12; exp -= d * 1000; fpsr_set_exception(FPSR_OPERR); } i = 100; t = 0; while (i >= 1) { int d = exp / i; t <<= 4; t |= d; exp -= d * i; i /= 10; } wrd[0] |= t << 16; } static void fp_to_pack (fpdata *fpd, uae_u32 *wrd, int dummy) { fptype d; char *cp; char str[100]; if (((wrd[0] >> 16) & 0x7fff) == 0x7fff) { // infinity has extended exponent and all 0 packed fraction // nans are copies bit by bit fpp_to_exten(fpd, wrd[0], wrd[1], wrd[2]); return; } if (!(wrd[0] & 0xf) && !wrd[1] && !wrd[2]) { // exponent is not cared about, if mantissa is zero wrd[0] &= 0x80000000; fpp_to_exten(fpd, wrd[0], wrd[1], wrd[2]); return; } cp = str; if (wrd[0] & 0x80000000) *cp++ = '-'; *cp++ = (wrd[0] & 0xf) + '0'; *cp++ = '.'; *cp++ = ((wrd[1] >> 28) & 0xf) + '0'; *cp++ = ((wrd[1] >> 24) & 0xf) + '0'; *cp++ = ((wrd[1] >> 20) & 0xf) + '0'; *cp++ = ((wrd[1] >> 16) & 0xf) + '0'; *cp++ = ((wrd[1] >> 12) & 0xf) + '0'; *cp++ = ((wrd[1] >> 8) & 0xf) + '0'; *cp++ = ((wrd[1] >> 4) & 0xf) + '0'; *cp++ = ((wrd[1] >> 0) & 0xf) + '0'; *cp++ = ((wrd[2] >> 28) & 0xf) + '0'; *cp++ = ((wrd[2] >> 24) & 0xf) + '0'; *cp++ = ((wrd[2] >> 20) & 0xf) + '0'; *cp++ = ((wrd[2] >> 16) & 0xf) + '0'; *cp++ = ((wrd[2] >> 12) & 0xf) + '0'; *cp++ = ((wrd[2] >> 8) & 0xf) + '0'; *cp++ = ((wrd[2] >> 4) & 0xf) + '0'; *cp++ = ((wrd[2] >> 0) & 0xf) + '0'; *cp++ = 'E'; if (wrd[0] & 0x40000000) *cp++ = '-'; *cp++ = ((wrd[0] >> 24) & 0xf) + '0'; *cp++ = ((wrd[0] >> 20) & 0xf) + '0'; *cp++ = ((wrd[0] >> 16) & 0xf) + '0'; *cp = 0; #ifdef USE_LONG_DOUBLE sscanf (str, "%Le", &d); #else sscanf (str, "%le", &d); #endif fp_from_native(d, fpd); } void fp_init_native(void) { #ifdef SOFTFLOAT_CONVERSIONS set_floatx80_rounding_precision(80, &fs); set_float_rounding_mode(float_round_to_zero, &fs); #endif fpp_print = fp_print; fpp_unset_snan = fp_unset_snan; fpp_is_init = fp_is_init; fpp_is_snan = fp_is_snan; fpp_is_nan = fp_is_nan; fpp_is_infinity = fp_is_infinity; fpp_is_zero = fp_is_zero; fpp_is_neg = fp_is_neg; fpp_is_denormal = fp_is_denormal; fpp_is_unnormal = fp_is_unnormal; fpp_fix_infinity = NULL; fpp_get_status = fp_get_status; fpp_clear_status = fp_clear_status; fpp_set_mode = fp_set_mode; fpp_get_support_flags = fp_get_support_flags; fpp_to_int = fp_to_int; fpp_from_int = fp_from_int; fpp_to_pack = fp_to_pack; fpp_from_pack = fp_from_pack; fpp_to_single = fp_to_single; fpp_from_single = fp_from_single; fpp_to_double = fp_to_double; fpp_from_double = fp_from_double; fpp_to_exten = fp_to_exten; fpp_from_exten = fp_from_exten; fpp_to_exten_fmovem = fp_to_exten; fpp_from_exten_fmovem = fp_from_exten; fpp_round_single = fp_round_single; fpp_round_double = fp_round_double; fpp_round32 = fp_round32; fpp_round64 = fp_round64; fpp_normalize = fp_normalize; fpp_denormalize = fp_denormalize; fpp_get_internal_overflow = fp_get_internal_overflow; fpp_get_internal_underflow = fp_get_internal_underflow; fpp_get_internal_round_all = fp_get_internal_round_all; fpp_get_internal_round = fp_get_internal_round; fpp_get_internal_round_exten = fp_get_internal_round_exten; fpp_get_internal = fp_get_internal; fpp_get_internal_grs = fp_get_internal_grs; fpp_int = fp_int; fpp_sinh = fp_sinh; fpp_intrz = fp_intrz; fpp_sqrt = fp_sqrt; fpp_lognp1 = fp_lognp1; fpp_etoxm1 = fp_etoxm1; fpp_tanh = fp_tanh; fpp_atan = fp_atan; fpp_atanh = fp_atanh; fpp_sin = fp_sin; fpp_asin = fp_asin; fpp_tan = fp_tan; fpp_etox = fp_etox; fpp_twotox = fp_twotox; fpp_tentox = fp_tentox; fpp_logn = fp_logn; fpp_log10 = fp_log10; fpp_log2 = fp_log2; fpp_abs = fp_abs; fpp_cosh = fp_cosh; fpp_neg = fp_neg; fpp_acos = fp_acos; fpp_cos = fp_cos; fpp_sincos = fp_sincos; fpp_getexp = fp_getexp; fpp_getman = fp_getman; fpp_div = fp_div; fpp_mod = fp_mod; fpp_add = fp_add; fpp_mul = fp_mul; fpp_rem = fp_rem; fpp_scale = fp_scale; fpp_sub = fp_sub; fpp_sgldiv = fp_sgldiv; fpp_sglmul = fp_sglmul; fpp_cmp = fp_cmp; fpp_tst = fp_tst; fpp_move = fp_move; } double softfloat_tan(double v) { #if SOFTFLOAT_CONVERSIONS struct float_status f = { 0 }; uae_u32 w1, w2; fpdata fpd = { 0 }; fpd.fp = v; set_floatx80_rounding_precision(80, &f); set_float_rounding_mode(float_round_to_zero, &f); fp_from_double(&fpd, &w1, &w2); floatx80 fv = float64_to_floatx80(((uae_u64)w1 << 32) | w2, &fs); fv = floatx80_tan(fv, &fs); float64 f64 = floatx80_to_float64(fv, &fs); fp_to_double(&fpd, f64 >> 32, (uae_u32)f64); return fpd.fp; #else return tanl(v); #endif } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/fpp_softfloat.c000066400000000000000000000462061504763705000251410ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68881/68882/68040/68060 FPU emulation * Softfloat version * * Andreas Grabher and Toni Wilen * */ #ifdef WIN32 /* WINUAE_FOR_HATARI */ #define __USE_MINGW_ANSI_STDIO 1 /* Hack for %lld format specifiers */ #endif #define __USE_ISOC9X /* We might be able to pick up a NaN */ #define SOFTFLOAT_FAST_INT64 #include #include #include #include "sysconfig.h" #include "sysdeps.h" #ifdef WINUAE_FOR_HATARI #include "main.h" #include "hatari-glue.h" #endif #include "options_cpu.h" #include "memory.h" #include "newcpu.h" #include "fpp.h" #include "newcpu.h" #include "softfloat/softfloat-macros.h" #include "softfloat/softfloat-specialize.h" #define FPCR_ROUNDING_MODE 0x00000030 #define FPCR_ROUND_NEAR 0x00000000 #define FPCR_ROUND_ZERO 0x00000010 #define FPCR_ROUND_MINF 0x00000020 #define FPCR_ROUND_PINF 0x00000030 #define FPCR_ROUNDING_PRECISION 0x000000c0 #define FPCR_PRECISION_SINGLE 0x00000040 #define FPCR_PRECISION_DOUBLE 0x00000080 #define FPCR_PRECISION_EXTENDED 0x00000000 static struct float_status fs; /* Functions for setting host/library modes and getting status */ static void fp_set_mode(uae_u32 mode_control) { set_float_detect_tininess(float_tininess_before_rounding, &fs); switch(mode_control & FPCR_ROUNDING_PRECISION) { case FPCR_PRECISION_SINGLE: // single set_floatx80_rounding_precision(32, &fs); break; default: // double case FPCR_PRECISION_DOUBLE: // double set_floatx80_rounding_precision(64, &fs); break; case FPCR_PRECISION_EXTENDED: // extended set_floatx80_rounding_precision(80, &fs); break; } switch(mode_control & FPCR_ROUNDING_MODE) { case FPCR_ROUND_NEAR: // to neareset set_float_rounding_mode(float_round_nearest_even, &fs); break; case FPCR_ROUND_ZERO: // to zero set_float_rounding_mode(float_round_to_zero, &fs); break; case FPCR_ROUND_MINF: // to minus set_float_rounding_mode(float_round_down, &fs); break; case FPCR_ROUND_PINF: // to plus set_float_rounding_mode(float_round_up, &fs); break; } } static void fp_get_status(uae_u32 *status) { if (fs.float_exception_flags & float_flag_signaling) *status |= FPSR_SNAN; if (fs.float_exception_flags & float_flag_invalid) *status |= FPSR_OPERR; if (fs.float_exception_flags & float_flag_divbyzero) *status |= FPSR_DZ; if (fs.float_exception_flags & float_flag_overflow) *status |= FPSR_OVFL; if (fs.float_exception_flags & float_flag_underflow) *status |= FPSR_UNFL; if (fs.float_exception_flags & float_flag_inexact) *status |= FPSR_INEX2; if (fs.float_exception_flags & float_flag_decimal) *status |= FPSR_INEX1; } STATIC_INLINE void fp_clear_status(void) { fs.float_exception_flags = 0; } static uae_u32 fp_get_support_flags(void) { return FPU_FEATURE_EXCEPTIONS | FPU_FEATURE_DENORMALS; } static const TCHAR *fp_printx80(floatx80 *fx, int mode) { static TCHAR fsout[32]; flag n, u, d; if (mode < 0) { _stprintf(fsout, _T("%04X-%08X-%08X"), fx->high, (uae_u32)(fx->low >> 32), (uae_u32)fx->low); return fsout; } n = floatx80_is_negative(*fx); u = floatx80_is_unnormal(*fx); d = floatx80_is_denormal(*fx); if (floatx80_is_infinity(*fx)) { _stprintf(fsout, _T("%c%s"), n ? '-' : '+', _T("inf")); } else if (floatx80_is_signaling_nan(*fx)) { _stprintf(fsout, _T("%c%s"), n ? '-' : '+', _T("snan")); } else if (floatx80_is_nan(*fx)) { _stprintf(fsout, _T("%c%s"), n ? '-' : '+', _T("nan")); } else { int32_t len = 17; int8_t save_exception_flags = fs.float_exception_flags; fs.float_exception_flags = 0; floatx80 x = floatx80_to_floatdecimal(*fx, &len, &fs); _stprintf(fsout, _T("%c%01lld.%016llde%c%05u%s%s"), n ? '-' : '+', x.low / LIT64(10000000000000000), x.low % LIT64(10000000000000000), (x.high & 0x4000) ? '-' : '+', x.high & 0x3FFF, d ? _T("D") : u ? _T("U") : _T(""), (fs.float_exception_flags & float_flag_inexact) ? _T("~") : _T("")); fs.float_exception_flags = save_exception_flags; } if (mode == 0 || mode > _tcslen(fsout)) return fsout; fsout[mode] = 0; return fsout; } static const TCHAR *fp_print(fpdata *fpd, int mode) { return fp_printx80(&fpd->fpx, mode); } /* Functions for detecting float type */ static bool fp_is_init(fpdata *fpd) { return false; } static bool fp_is_snan(fpdata *fpd) { return floatx80_is_signaling_nan(fpd->fpx) != 0; } static bool fp_unset_snan(fpdata *fpd) { fpd->fpx.low |= LIT64(0x4000000000000000); return false; } static bool fp_is_nan(fpdata *fpd) { return floatx80_is_any_nan(fpd->fpx) != 0; } static bool fp_is_infinity(fpdata *fpd) { return floatx80_is_infinity(fpd->fpx) != 0; } static void fp_fix_infinity(fpdata *fpd) { fpd->fpx.low = 0; } static bool fp_is_zero(fpdata *fpd) { return floatx80_is_zero(fpd->fpx) != 0; } static bool fp_is_neg(fpdata *fpd) { return floatx80_is_negative(fpd->fpx) != 0; } static bool fp_is_denormal(fpdata *fpd) { return floatx80_is_denormal(fpd->fpx) != 0; } static bool fp_is_unnormal(fpdata *fpd) { return floatx80_is_unnormal(fpd->fpx) != 0; } static void to_single(fpdata *fpd, uae_u32 wrd1) { float32 f = wrd1; fpd->fpx = float32_to_floatx80_allowunnormal(f, &fs); } static uae_u32 from_single(fpdata *fpd) { float32 f = floatx80_to_float32(fpd->fpx, &fs); return f; } static void to_double(fpdata *fpd, uae_u32 wrd1, uae_u32 wrd2) { float64 f = ((float64)wrd1 << 32) | wrd2; fpd->fpx = float64_to_floatx80_allowunnormal(f, &fs); } static void from_double(fpdata *fpd, uae_u32 *wrd1, uae_u32 *wrd2) { float64 f = floatx80_to_float64(fpd->fpx, &fs); *wrd1 = f >> 32; *wrd2 = (uae_u32)f; } static void to_exten(fpdata *fpd, uae_u32 wrd1, uae_u32 wrd2, uae_u32 wrd3) { fpd->fpx.high = (uae_u16)(wrd1 >> 16); fpd->fpx.low = ((uae_u64)wrd2 << 32) | wrd3; } static void from_exten(fpdata *fpd, uae_u32 *wrd1, uae_u32 *wrd2, uae_u32 *wrd3) { floatx80 f = floatx80_to_floatx80(fpd->fpx, &fs); *wrd1 = (uae_u32)(f.high << 16); *wrd2 = f.low >> 32; *wrd3 = (uae_u32)f.low; } static void to_exten_fmovem(fpdata *fpd, uae_u32 wrd1, uae_u32 wrd2, uae_u32 wrd3) { fpd->fpx.high = (uae_u16)(wrd1 >> 16); fpd->fpx.low = ((uae_u64)wrd2 << 32) | wrd3; } static void from_exten_fmovem(fpdata *fpd, uae_u32 *wrd1, uae_u32 *wrd2, uae_u32 *wrd3) { *wrd1 = (uae_u32)(fpd->fpx.high << 16); *wrd2 = fpd->fpx.low >> 32; *wrd3 = (uae_u32)fpd->fpx.low; } static uae_s64 to_int(fpdata *src, int size) { switch (size) { case 0: return floatx80_to_int8(src->fpx, &fs); case 1: return floatx80_to_int16(src->fpx, &fs); case 2: return floatx80_to_int32(src->fpx, &fs); default: return 0; } } static void from_int(fpdata *fpd, uae_s32 src) { fpd->fpx = int32_to_floatx80(src); } /* Functions for returning exception state data */ static void fp_get_internal_overflow(fpdata *fpd) { fpd->fpx = getFloatInternalOverflow(); } static void fp_get_internal_underflow(fpdata *fpd) { fpd->fpx = getFloatInternalUnderflow(); } static void fp_get_internal_round_all(fpdata *fpd) { fpd->fpx = getFloatInternalRoundedAll(); } static void fp_get_internal_round(fpdata *fpd) { fpd->fpx = getFloatInternalRoundedSome(); } static void fp_get_internal_round_exten(fpdata *fpd) { fpd->fpx = getFloatInternalFloatx80(); } static void fp_get_internal(fpdata *fpd) { fpd->fpx = getFloatInternalUnrounded(); } static uae_u32 fp_get_internal_grs(void) { return (uae_u32)getFloatInternalGRS(); } /* Function for denormalizing */ static void fp_denormalize(fpdata *fpd, int esign) { fpd->fpx = floatx80_denormalize(fpd->fpx, esign); } /* Functions for rounding */ // round to float with extended precision exponent static void fp_round32(fpdata *fpd) { fpd->fpx = floatx80_round32(fpd->fpx, &fs); } // round to double with extended precision exponent static void fp_round64(fpdata *fpd) { fpd->fpx = floatx80_round64(fpd->fpx, &fs); } // round to float static void fp_round_single(fpdata *fpd) { fpd->fpx = floatx80_round_to_float32(fpd->fpx, &fs); } // round to double static void fp_round_double(fpdata *fpd) { fpd->fpx = floatx80_round_to_float64(fpd->fpx, &fs); } #if 0 // round to selected precision static void fp_round(fpdata *a) { switch(fs.floatx80_rounding_precision) { case 32: a->fpx = floatx80_round_to_float32(a->fpx, &fs); break; case 64: a->fpx = floatx80_round_to_float64(a->fpx, &fs); break; default: break; } } #endif /* Arithmetic functions */ static void fp_int(fpdata *a, fpdata *b) { a->fpx = floatx80_round_to_int(b->fpx, &fs); } static void fp_intrz(fpdata *a, fpdata *b) { a->fpx = floatx80_round_to_int_toward_zero(b->fpx, &fs); } static void fp_getexp(fpdata *a, fpdata *b) { a->fpx = floatx80_getexp(b->fpx, &fs); } static void fp_getman(fpdata *a, fpdata *b) { a->fpx = floatx80_getman(b->fpx, &fs); } static void fp_mod(fpdata *a, fpdata *b, uae_u64 *q, uae_u8 *s) { #ifndef WINUAE_FOR_HATARI a->fpx = floatx80_mod(a->fpx, b->fpx, q, s, &fs); #else a->fpx = floatx80_mod(a->fpx, b->fpx, (uint64_t *)q, s, &fs); #endif } static void fp_sgldiv(fpdata *a, fpdata *b) { a->fpx = floatx80_sgldiv(a->fpx, b->fpx, &fs); } static void fp_sglmul(fpdata *a, fpdata *b) { a->fpx = floatx80_sglmul(a->fpx, b->fpx, &fs); } static void fp_rem(fpdata *a, fpdata *b, uae_u64 *q, uae_u8 *s) { #ifndef WINUAE_FOR_HATARI a->fpx = floatx80_rem(a->fpx, b->fpx, q, s, &fs); #else a->fpx = floatx80_rem(a->fpx, b->fpx, (uint64_t *)q, s, &fs); #endif } static void fp_scale(fpdata *a, fpdata *b) { a->fpx = floatx80_scale(a->fpx, b->fpx, &fs); } static void fp_cmp(fpdata *a, fpdata *b) { a->fpx = floatx80_cmp(a->fpx, b->fpx, &fs); } static void fp_tst(fpdata *a, fpdata *b) { a->fpx = floatx80_tst(b->fpx, &fs); } static const uint8_t prectable[] = { 0, 32, 64, 80 }; #define SETPREC \ uint8_t oldprec = fs.floatx80_rounding_precision; \ if (prec > PREC_NORMAL) \ set_floatx80_rounding_precision(prectable[prec], &fs); #define RESETPREC \ set_floatx80_rounding_precision(oldprec, &fs); /* Functions with fixed precision */ static void fp_move(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_move(b->fpx, &fs); RESETPREC } static void fp_abs(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_abs(b->fpx, &fs); RESETPREC } static void fp_neg(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_neg(b->fpx, &fs); RESETPREC } static void fp_add(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_add(a->fpx, b->fpx, &fs); RESETPREC } static void fp_sub(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_sub(a->fpx, b->fpx, &fs); RESETPREC } static void fp_mul(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_mul(a->fpx, b->fpx, &fs); RESETPREC } static void fp_div(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_div(a->fpx, b->fpx, &fs); RESETPREC } static void fp_sqrt(fpdata *a, fpdata *b, int prec) { SETPREC a->fpx = floatx80_sqrt(b->fpx, &fs); RESETPREC } static void fp_sinh(fpdata *a, fpdata *b) { a->fpx = floatx80_sinh(b->fpx, &fs); } static void fp_lognp1(fpdata *a, fpdata *b) { a->fpx = floatx80_lognp1(b->fpx, &fs); } static void fp_etoxm1(fpdata *a, fpdata *b) { a->fpx = floatx80_etoxm1(b->fpx, &fs); } static void fp_tanh(fpdata *a, fpdata *b) { a->fpx = floatx80_tanh(b->fpx, &fs); } static void fp_atan(fpdata *a, fpdata *b) { a->fpx = floatx80_atan(b->fpx, &fs); } static void fp_asin(fpdata *a, fpdata *b) { a->fpx = floatx80_asin(b->fpx, &fs); } static void fp_atanh(fpdata *a, fpdata *b) { a->fpx = floatx80_atanh(b->fpx, &fs); } static void fp_sin(fpdata *a, fpdata *b) { a->fpx = floatx80_sin(b->fpx, &fs); } static void fp_tan(fpdata *a, fpdata *b) { a->fpx = floatx80_tan(b->fpx, &fs); } static void fp_etox(fpdata *a, fpdata *b) { a->fpx = floatx80_etox(b->fpx, &fs); } static void fp_twotox(fpdata *a, fpdata *b) { a->fpx = floatx80_twotox(b->fpx, &fs); } static void fp_tentox(fpdata *a, fpdata *b) { a->fpx = floatx80_tentox(b->fpx, &fs); } static void fp_logn(fpdata *a, fpdata *b) { a->fpx = floatx80_logn(b->fpx, &fs); } static void fp_log10(fpdata *a, fpdata *b) { a->fpx = floatx80_log10(b->fpx, &fs); } static void fp_log2(fpdata *a, fpdata *b) { a->fpx = floatx80_log2(b->fpx, &fs); } static void fp_cosh(fpdata *a, fpdata *b) { a->fpx = floatx80_cosh(b->fpx, &fs); } static void fp_acos(fpdata *a, fpdata *b) { a->fpx = floatx80_acos(b->fpx, &fs); } static void fp_cos(fpdata *a, fpdata *b) { a->fpx = floatx80_cos(b->fpx, &fs); } static void fp_sincos(fpdata *a, fpdata *b, fpdata *c) { a->fpx = floatx80_sincos(b->fpx, &c->fpx, &fs); } /* Functions for converting between float formats */ static const fptype twoto32 = 4294967296.0; static void to_native(fptype *fp, fpdata *fpd) { int expon; fptype frac; expon = fpd->fpx.high & 0x7fff; fp_is_init(fpd); if (fp_is_zero(fpd)) { *fp = fp_is_neg(fpd) ? -0.0 : +0.0; return; } if (fp_is_nan(fpd)) { #ifdef USE_LONG_DOUBLE *fp = sqrtl(-1); #else *fp = sqrt(-1); #endif return; } if (fp_is_infinity(fpd)) { double zero = 0.0; #ifdef USE_LONG_DOUBLE *fp = fp_is_neg(fpd) ? logl(0.0) : (1.0 / zero); #else *fp = fp_is_neg(fpd) ? log(0.0) : (1.0 / zero); #endif return; } frac = (fptype)fpd->fpx.low / (fptype)(twoto32 * 2147483648.0); if (fp_is_neg(fpd)) frac = -frac; #ifdef USE_LONG_DOUBLE *fp = ldexpl (frac, expon - 16383); #else *fp = ldexp (frac, expon - 16383); #endif } static void from_native(fptype fp, fpdata *fpd) { int expon; fptype frac; if (signbit(fp)) fpd->fpx.high = 0x8000; else fpd->fpx.high = 0x0000; if (isnan(fp)) { fpd->fpx.high |= 0x7fff; fpd->fpx.low = LIT64(0xffffffffffffffff); return; } if (isinf(fp)) { fpd->fpx.high |= 0x7fff; fpd->fpx.low = LIT64(0x0000000000000000); return; } if (fp == 0.0) { fpd->fpx.low = LIT64(0x0000000000000000); return; } if (fp < 0.0) fp = -fp; #ifdef USE_LONG_DOUBLE frac = frexpl (fp, &expon); #else frac = frexp (fp, &expon); #endif frac += 0.5 / (twoto32 * twoto32); if (frac >= 1.0) { frac /= 2.0; expon++; } fpd->fpx.high |= (expon + 16383 - 1) & 0x7fff; fpd->fpx.low = (uint64_t)(frac * (fptype)(twoto32 * twoto32)); while (!(fpd->fpx.low & LIT64( 0x8000000000000000))) { if (fpd->fpx.high == 0) { break; } fpd->fpx.low <<= 1; fpd->fpx.high--; } } static void fp_normalize(fpdata *a) { a->fpx = floatx80_normalize(a->fpx); } static void fp_to_pack(fpdata *fp, uae_u32 *wrd, int dummy) { floatx80 f; int i; uae_s32 exp; uae_s64 mant; uae_u32 pack_exp, pack_int, pack_se, pack_sm; uae_u64 pack_frac; if (((wrd[0] >> 16) & 0x7fff) == 0x7fff) { // infinity has extended exponent and all 0 packed fraction // nans are copies bit by bit fpp_to_exten(fp, wrd[0], wrd[1], wrd[2]); return; } if (!(wrd[0] & 0xf) && !wrd[1] && !wrd[2]) { // exponent is not cared about, if mantissa is zero wrd[0] &= 0x80000000; fpp_to_exten(fp, wrd[0], wrd[1], wrd[2]); return; } pack_exp = (wrd[0] >> 16) & 0xFFF; // packed exponent pack_int = wrd[0] & 0xF; // packed integer part pack_frac = ((uae_u64)wrd[1] << 32) | wrd[2]; // packed fraction pack_se = (wrd[0] >> 30) & 1; // sign of packed exponent pack_sm = (wrd[0] >> 31) & 1; // sign of packed significand exp = 0; for (i = 0; i < 3; i++) { exp *= 10; exp += (pack_exp >> (8 - i * 4)) & 0xF; } if (pack_se) { exp = -exp; } exp -= 16; if (exp < 0) { exp = -exp; pack_se = 1; } mant = pack_int; for (i = 0; i < 16; i++) { mant *= 10; mant += (pack_frac >> (60 - i * 4)) & 0xF; } f.high = exp & 0x3FFF; f.high |= pack_se ? 0x4000 : 0; f.high |= pack_sm ? 0x8000 : 0; f.low = mant; fp->fpx = floatdecimal_to_floatx80(f, &fs); } static void fp_from_pack(fpdata *fp, uae_u32 *wrd, int kfactor) { floatx80 f = floatx80_to_floatdecimal(fp->fpx, &kfactor, &fs); uae_u32 pack_exp, pack_exp4, pack_int, pack_se, pack_sm; uae_u64 pack_frac; uae_u32 exponent; uae_u64 significand; uae_s32 len; uae_u64 digit; if ((f.high & 0x7FFF) == 0x7FFF) { wrd[0] = (uae_u32)(f.high << 16); wrd[1] = f.low >> 32; wrd[2] = (uae_u32)f.low; } else { exponent = f.high & 0x3FFF; significand = f.low; pack_int = 0; pack_frac = 0; len = kfactor; // SoftFloat saved len to kfactor variable while (len > 0) { len--; digit = significand % 10; significand /= 10; if (len == 0) { pack_int = (uae_u32)digit; } else { pack_frac |= digit << (64 - len * 4); } } pack_exp = 0; pack_exp4 = 0; len = 4; while (len > 0) { len--; digit = exponent % 10; exponent /= 10; if (len == 0) { pack_exp4 = (uae_u32)digit; } else { pack_exp |= digit << (12 - len * 4); } } pack_se = f.high & 0x4000; pack_sm = f.high & 0x8000; wrd[0] = pack_exp << 16; wrd[0] |= pack_exp4 << 12; wrd[0] |= pack_int; wrd[0] |= pack_se ? 0x40000000 : 0; wrd[0] |= pack_sm ? 0x80000000 : 0; wrd[1] = pack_frac >> 32; wrd[2] = pack_frac & 0xffffffff; } } void fp_init_softfloat(int fpu_model) { if (fpu_model == 68040) { set_special_flags(cmp_signed_nan, &fs); } else if (fpu_model == 68060) { set_special_flags(infinity_clear_intbit, &fs); } else { set_special_flags(addsub_swap_inf, &fs); } fpp_print = fp_print; fpp_unset_snan = fp_unset_snan; fpp_is_init = fp_is_init; fpp_is_snan = fp_is_snan; fpp_is_nan = fp_is_nan; fpp_is_infinity = fp_is_infinity; fpp_is_zero = fp_is_zero; fpp_is_neg = fp_is_neg; fpp_is_denormal = fp_is_denormal; fpp_is_unnormal = fp_is_unnormal; fpp_fix_infinity = fp_fix_infinity; fpp_get_status = fp_get_status; fpp_clear_status = fp_clear_status; fpp_set_mode = fp_set_mode; fpp_get_support_flags = fp_get_support_flags; fpp_to_int = to_int; fpp_from_int = from_int; fpp_to_pack = fp_to_pack; fpp_from_pack = fp_from_pack; fpp_to_single = to_single; fpp_from_single = from_single; fpp_to_double = to_double; fpp_from_double = from_double; fpp_to_exten = to_exten; fpp_from_exten = from_exten; fpp_to_exten_fmovem = to_exten_fmovem; fpp_from_exten_fmovem = from_exten_fmovem; fpp_round_single = fp_round_single; fpp_round_double = fp_round_double; fpp_round32 = fp_round32; fpp_round64 = fp_round64; fpp_normalize = fp_normalize; fpp_denormalize = fp_denormalize; fpp_get_internal_overflow = fp_get_internal_overflow; fpp_get_internal_underflow = fp_get_internal_underflow; fpp_get_internal_round_all = fp_get_internal_round_all; fpp_get_internal_round = fp_get_internal_round; fpp_get_internal_round_exten = fp_get_internal_round_exten; fpp_get_internal = fp_get_internal; fpp_get_internal_grs = fp_get_internal_grs; fpp_int = fp_int; fpp_sinh = fp_sinh; fpp_intrz = fp_intrz; fpp_sqrt = fp_sqrt; fpp_lognp1 = fp_lognp1; fpp_etoxm1 = fp_etoxm1; fpp_tanh = fp_tanh; fpp_atan = fp_atan; fpp_atanh = fp_atanh; fpp_sin = fp_sin; fpp_asin = fp_asin; fpp_tan = fp_tan; fpp_etox = fp_etox; fpp_twotox = fp_twotox; fpp_tentox = fp_tentox; fpp_logn = fp_logn; fpp_log10 = fp_log10; fpp_log2 = fp_log2; fpp_abs = fp_abs; fpp_cosh = fp_cosh; fpp_neg = fp_neg; fpp_acos = fp_acos; fpp_cos = fp_cos; fpp_sincos = fp_sincos; fpp_getexp = fp_getexp; fpp_getman = fp_getman; fpp_div = fp_div; fpp_mod = fp_mod; fpp_add = fp_add; fpp_mul = fp_mul; fpp_rem = fp_rem; fpp_scale = fp_scale; fpp_sub = fp_sub; fpp_sgldiv = fp_sgldiv; fpp_sglmul = fp_sglmul; fpp_cmp = fp_cmp; fpp_tst = fp_tst; fpp_move = fp_move; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/gencpu.c000066400000000000000000010773441504763705000235640ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68000 emulation generator * * This is a fairly stupid program that generates a lot of case labels that * can be #included in a switch statement. * As an alternative, it can generate functions that handle specific * MC68000 instructions, plus a prototype header file and a function pointer * array to look up the function for an opcode. * The generated code is sometimes sub-optimal, an optimizing compiler should * take care of this. * * The source for the insn timings is Markt & Technik's Amiga Magazin 8/1992. * * Copyright 1995, 1996, 1997, 1998, 1999, 2000 Bernd Schmidt */ #define CPU_TESTER 0 #include "sysconfig.h" #include "sysdeps.h" #include #include #include "readcpu.h" char *ua (const char *s) { return strdup(s); } #define BOOL_TYPE "int" /* Define the minimal 680x0 where NV flags are not affected by xBCD instructions. */ #define xBCD_KEEPS_N_FLAG 4 #define xBCD_KEEPS_V_FLAG 2 // if set: 68030 MMU and instruction's last memory access is write and it causes fault: // instruction is considered completed, generate short bus error stack frame. #define MMU68030_LAST_WRITE 1 // Not useful #define WAITSTATUS_020_EXTRA 0 static FILE *headerfile; static FILE *stblfile; // optional settings static int using_always_dynamic_cycles = 1; static int using_bus_error = 1; static int using_prefetch, using_indirect, using_mmu; static int using_prefetch_020, using_ce020; static int using_exception_3; static int using_ce; static int using_tracer; static int using_waitstates; static int using_simple_cycles; static int using_debugmem; static int using_noflags; static int using_test; static int using_nocycles; static int using_get_word_unswapped; static int using_optimized_flags; static int need_exception_oldpc; static int need_special_fixup; static int cpu_level, cpu_generic; static int count_readp; static int count_readw, count_readl; static int count_writew, count_writel; static int count_cycles, count_ncycles; static int count_cycles_ce020; static int count_read_ea, count_write_ea, count_cycles_ea; static const char *mmu_postfix, *xfc_postfix; static int memory_cycle_cnt; static int did_prefetch; static int ipl_fetched; static int pre_ipl; static int opcode_nextcopy; static int disable_noflags; static int do_always_dynamic_cycles; static int func_noret; #ifdef WINUAE_FOR_HATARI static int CurrentInstrCycles; /* Hatari only : Number of cycles for the current instruction in cpuemu_xx */ static int CurrentInstrCycles_pos; /* Hatari only : Stores where we have to patch in the current cycles value */ #endif #define GF_APDI 0x00001 #define GF_AD8R 0x00002 #define GF_PC8R 0x00004 #define GF_AA 0x00007 #define GF_NOREFILL 0x00008 #define GF_PREFETCH 0x00010 #define GF_FC 0x00020 #define GF_MOVE 0x00040 #define GF_IR2IRC 0x00080 #define GF_LRMW 0x00100 #define GF_NOFAULTPC 0x00200 #define GF_RMW 0x00400 #define GF_OPCE020 0x00800 #define GF_REVERSE 0x01000 #define GF_REVERSE2 0x02000 #define GF_SECONDWORDSETFLAGS 0x04000 #define GF_SECONDEA 0x08000 #define GF_NOFETCH 0x10000 // 68010 #define GF_CLR68010 0x20000 // 68010 #define GF_NOEXC3 0x40000 #define GF_EXC3 0x80000 // internal PC is 2 less than address being prefetched. #define GF_PCM2 0x100000 // internal PC is 2 more than address being prefetched. #define GF_PCP2 0x200000 // if set, long word fetch does it at the beginning (not second word) #define GF_NOLIPL 0x400000 // If set, IPL sample is in mid-cycle #define GF_IPLMID 0x800000 // genastore + IPL #define GF_IPL 0x1000000 typedef enum { flag_logical_noclobber, flag_logical, flag_add, flag_sub, flag_cmp, flag_addx, flag_subx, flag_z, flag_zn, flag_av, flag_sv } flagtypes; /* For the current opcode, the next lower level that will have different code. * Initialized to -1 for each opcode. If it remains unchanged, indicates we * are done with that opcode. */ static int next_cpu_level; static int *opcode_map; static int *opcode_next_clev; static int *opcode_last_postfix; static unsigned long *counts; static int generate_stbl; static int mmufixupcnt; static int mmufixupstate; static int disp020cnt; static bool candormw; static bool genastore_done; static char rmw_varname[100]; static struct instr *g_instr; static char g_srcname[100]; static int loopmode; static int loopmodeextra; static int loopmode_set; static int postfix; #define GENA_GETV_NO_FETCH 0 #define GENA_GETV_FETCH 1 #define GENA_GETV_FETCH_ALIGN 2 #define GENA_MOVEM_DO_INC 0 #define GENA_MOVEM_NO_INC 1 #define GENA_MOVEM_MOVE16 2 static const char *srcl, *dstl; static const char *srcw, *dstw; static const char *srcb, *dstb; static const char *srcblrmw, *srcwlrmw, *srcllrmw; static const char *dstblrmw, *dstwlrmw, *dstllrmw; static const char *srcbrmw, *srcwrmw, *srclrmw; static const char *dstbrmw, *dstwrmw, *dstlrmw; static const char *prefetch_long, *prefetch_word, *prefetch_opcode; static const char *srcli, *srcwi, *srcbi, *nextl, *nextw; static const char *srcld, *dstld; static const char *srcwd, *dstwd; static const char *do_cycles, *disp000, *disp020, *getpc; #define fetchmode_fea 1 #define fetchmode_cea 2 #define fetchmode_fiea 3 #define fetchmode_ciea 4 #define fetchmode_jea 5 static int brace_level; static char outbuffer[30000]; static int last_access_offset_ipl; static int last_access_offset_ipl_prev; static int ipl_fetch_cycles; static int ipl_fetch_cycles_prev; static int ipl_fetch_brace_level; static void out(const char *format, ...) { char outbuf[1000]; va_list parms; va_start(parms, format); _vsnprintf(outbuf, sizeof(outbuf) - 1, format, parms); outbuf[sizeof(outbuf) - 1] = 0; va_end(parms); char *p = outbuf; while (*p) { char v = *p; if (v == '\t') { memmove(p, p + 1, strlen(p + 1) + 1); } else { p++; } } p = outbuf; for (;;) { char *pe = p; int islf = 0; while (*pe != 0 && *pe != '\n') { pe++; } if (*pe == '\n') { islf = 1; *pe = 0; } char outbuf2[1000]; strcpy(outbuf2, p); outbuf2[pe - p] = 0; if (outbuf2[0]) { char *ps = outbuf2; while (*ps) { char v = *ps; if (v == '}') { brace_level--; } ps++; } for (int i = 0; i < brace_level; i++) { strcat(outbuffer, "\t"); } strcat(outbuffer, outbuf2); ps = outbuf2; while (*ps) { char v = *ps; if (v == '{') { brace_level++; } ps++; } } if (islf) { strcat(outbuffer, "\n"); pe++; } if (*pe == 0) break; p = pe; } } static int insertstring(const char *s, int offset) { int len = strlen(s); memmove(outbuffer + offset + len + ipl_fetch_brace_level, outbuffer + offset, strlen(outbuffer + offset) + 1); for (int i = 0; i < ipl_fetch_brace_level; i++) { outbuffer[offset + i] = '\t'; } memcpy(outbuffer + offset + ipl_fetch_brace_level, s, len); return len + ipl_fetch_brace_level; } static int get_current_cycles(void) { return (count_readw + count_writew) * 4 + (count_readl + count_writel) * 8 + count_cycles; } static void set_ipl_pre(void) { if (using_ce) { pre_ipl = 1; out("ipl_fetch_next_pre();\n"); } else if (using_prefetch) { pre_ipl = 1; //out("ipl_fetch_prefetch(%d);\n", get_current_cycles() + 2); } } static void set_ipl(void) { last_access_offset_ipl = strlen(outbuffer); ipl_fetch_cycles = get_current_cycles(); ipl_fetch_brace_level = brace_level; ipl_fetched = 2; } #if 0 static void set_ipl_now(void) { set_ipl(); ipl_fetched = 3; } #endif static void set_last_access_ipl(void) { if (ipl_fetched) return; last_access_offset_ipl = strlen(outbuffer); ipl_fetch_cycles = get_current_cycles(); ipl_fetch_brace_level = brace_level; } static void set_last_access_ipl_prev(uae_u32 flags) { if (flags & GF_IPLMID) { pre_ipl = 2; } if (ipl_fetched < 0) return; last_access_offset_ipl_prev = strlen(outbuffer); ipl_fetch_cycles_prev = get_current_cycles(); ipl_fetch_brace_level = brace_level; } NORETURN static void term (void) { out("Abort!\n"); abort(); } NORETURN static void term_err (const char *err) { out("%s\n", err); term(); } static void read_counts (void) { FILE *file; unsigned int opcode, count, total; char name[20]; int nr = 0; memset (counts, 0, 65536 * sizeof *counts); count = 0; file = fopen ("frequent.68k", "r"); if (file) { if (fscanf (file, "Total: %u\n", &total) == 0) { abort(); } while (fscanf (file, "%x: %u %s\n", &opcode, &count, name) == 3) { opcode_next_clev[nr] = 5; opcode_last_postfix[nr] = -1; opcode_map[nr++] = opcode; counts[opcode] = count; } fclose (file); } if (nr == nr_cpuop_funcs) return; for (opcode = 0; opcode < 0x10000; opcode++) { if (table68k[opcode].handler == -1 && table68k[opcode].mnemo != i_ILLG && counts[opcode] == 0) { opcode_next_clev[nr] = 5; opcode_last_postfix[nr] = -1; opcode_map[nr++] = opcode; counts[opcode] = count; } } if (nr != nr_cpuop_funcs) term(); } static int genamode_cnt, genamode8r_offset[2]; static int set_fpulimit; static int m68k_pc_offset, m68k_pc_offset_old; static int m68k_pc_total; static int exception_pc_offset, exception_pc_offset_extra_000; static int branch_inst; static int ir2irc; static int insn_n_cycles; static int tail_ce020, total_ce020, head_in_ea_ce020; static bool head_ce020_cycs_done, tail_ce020_done; static int subhead_ce020; static struct instr *curi_ce020; static bool no_prefetch_ce020; static bool got_ea_ce020; static bool needbuserror(void) { if (!using_bus_error) return false; if (using_mmu) return false; #if CPU_TESTER return true; #else // only 68000/010 need cpuemu internal bus error handling // 68020+ use CATCH/TRY method if (postfix >= 10 && postfix < 20) return true; return false; #endif } // 68010-40 needs different implementation than 68060 static bool next_level_060_to_040(void) { if (cpu_level >= 5) { if (next_cpu_level < 5) { next_cpu_level = 5 - 1; return true; } } return false; } // 68010-30 needs different implementation than 68040/060 static bool next_level_040_to_030(void) { if (cpu_level >= 4) { if (next_cpu_level < 4) { next_cpu_level = 4 - 1; return true; } } return false; } // 68000-010 needs different implementation than 68020+ static bool next_level_020_to_010(void) { if (cpu_level >= 2) { if (next_cpu_level < 2) { next_cpu_level = 2 - 1; return true; } } return false; } // 68000 <> 68010 static bool next_level_000(void) { if (next_cpu_level < 0) { next_cpu_level = 0; } return false; } static void fpulimit (void) { out("\n#ifdef FPUEMU\n"); set_fpulimit = 1; } static int s_count_readw, s_count_readl; static int s_count_writew, s_count_writel; static int s_count_readp; static int s_count_cycles, s_count_ncycles, s_insn_cycles; static void push_ins_cnt(void) { s_count_readw = count_readw; s_count_readl = count_readl; s_count_writew = count_writew; s_count_writel = count_writel; s_count_readp = count_readp; s_count_cycles = count_cycles; s_count_ncycles = count_ncycles; s_insn_cycles = insn_n_cycles; } static void pop_ins_cnt(void) { count_readw = s_count_readw; count_readl = s_count_readl; count_writew = s_count_writew; count_writel = s_count_writel; count_readp = s_count_readp; count_cycles = s_count_cycles; count_ncycles = s_count_ncycles; insn_n_cycles = s_insn_cycles; } static bool needmmufixup(void) { if (need_special_fixup) { // need to restore -(an)/(an)+ if unimplemented switch (g_instr->mnemo) { case i_MULL: case i_DIVL: case i_CAS: return true; } } if (!using_mmu) return false; if (using_mmu == 68040 && (mmufixupstate || mmufixupcnt > 0)) { return false; } if (using_mmu == 68030) { switch (g_instr->mnemo) { case i_LINK: case i_RTD: case i_RTR: case i_RTE: case i_RTS: return false; } } return true; } static void addmmufixup(const char *reg, int size, int mode) { if (!needmmufixup()) return; int flags = 0; if (cpu_level == 3 && size >= 0 && mode >= 0) { if (mode == Aipi) { flags |= 0x100; } else if (mode == Apdi) { flags |= 0x200; } if (size == sz_long) { flags |= 0x800; } else if (size == sz_word) { flags |= 0x400; } } out("mmufixup[%d].reg = %s | 0x%x;\n", mmufixupcnt, reg, flags); out("mmufixup[%d].value = m68k_areg(regs, %s);\n", mmufixupcnt, reg); mmufixupstate |= 1 << mmufixupcnt; mmufixupcnt++; } static void clearmmufixup(int cnt, int noclear) { if (mmufixupstate & (1 << cnt)) { out("mmufixup[%d].reg = -1;\n", cnt); if (!noclear) mmufixupstate &= ~(1 << cnt); } } static bool isce020(void) { if (!using_ce020) return false; if (using_ce020 >= 3) return false; return true; } static bool isprefetch020(void) { if (!using_prefetch_020) return false; if (using_prefetch_020 >= 3) return false; return true; } static void check_ipl(void) { if (ipl_fetched >= 2) { return; } // So far it seems 68000 IPL fetch happens when CPU is doing // memory cycle data part followed by prefetch cycle. It must // happen after possible bus error has been detected but before // following prefetch memory cycle. if (last_access_offset_ipl_prev < 0) { set_last_access_ipl(); } else { // if memory cycle happened previously: use it. last_access_offset_ipl = last_access_offset_ipl_prev; ipl_fetched = 1; ipl_fetch_cycles = ipl_fetch_cycles_prev; } } static void check_ipl_next(void) { if (using_ce) { out("ipl_fetch_next();\n"); } if (isce020()) { out("ipl_fetch_next();\n"); } } static void check_ipl_always(void) { if (using_ce) { out("ipl_fetch_now();\n"); } if (isce020()) { out("ipl_fetch_now();\n"); } } static void addcycles_020(int cycles) { if (using_ce020) { out("%s(%d);\n", do_cycles, cycles); } else if (using_prefetch_020) { out("count_cycles += %d;\n", cycles); } } static void addcycles_ce020_2 (int cycles, const char *s) { if (!isce020()) return; #if 0 if (cycles > 0) { if (s == NULL) out("%s(%d);\n", do_cycles, cycles); else out("%s(%d); /* %s */\n", do_cycles, cycles, s); } #endif count_cycles += cycles; count_cycles_ce020 += cycles; } static void addcycles_ce020 (int cycles) { addcycles_ce020_2 (cycles, NULL); } static void get_prefetch_020 (void) { if (!isprefetch020() || no_prefetch_ce020) return; out("regs.irc = %s(%d);\n", prefetch_opcode, m68k_pc_offset); } static void get_prefetch_020_continue(void) { if (!isprefetch020()) return; get_prefetch_020(); } static void returntail (bool iswrite) { if (!isce020()) { if (isprefetch020()) { if (!tail_ce020_done) { if (!did_prefetch) get_prefetch_020(); did_prefetch = 1; tail_ce020_done = true; } } return; } if (!tail_ce020_done) { total_ce020 -= 2; #if 0 if (iswrite) { out("/* C - %d = %d */\n", memory_cycle_cnt, total_ce020 - memory_cycle_cnt); total_ce020 -= memory_cycle_cnt; } else { out("/* C = %d */\n", total_ce020); } #endif if (0 && total_ce020 <= 0) { out("/* C was zero */\n"); total_ce020 = 1; } if (!did_prefetch) get_prefetch_020(); if (total_ce020 > 0) addcycles_ce020 (total_ce020); //out("regs.irc = %s;\n", prefetch_opcode); if (0 && total_ce020 >= 2) { out("op_cycles = get_cycles() - op_cycles;\n"); out("op_cycles /= cpucycleunit;\n"); out("if (op_cycles < %d) {\n", total_ce020); out("do_cycles_ce020((%d) - op_cycles);\n", total_ce020); out("}\n"); } #if 0 if (tail_ce020 > 0) { out("regs.ce020_tail = %d * cpucycleunit;\n", tail_ce020); out("regs.ce020_tail_cycles = get_cycles() + regs.ce020_tail;\n"); } else { out("regs.ce020_tail = 0;\n"); } #endif tail_ce020_done = true; } } static void returncycles(int cycles) { #ifdef WINUAE_FOR_HATARI CurrentInstrCycles = cycles; #endif if (using_nocycles) { if (func_noret) { out("return;\n"); } else { out("return 0;\n"); } return; } if (func_noret) { #if 0 if (tail_ce020 == 0) out("regs.ce020memcycles -= 2 * cpucycleunit; /* T=0 */ \n"); else if (tail_ce020 == 1) out("regs.ce020memcycles -= 1 * cpucycleunit; /* T=1 */ \n"); else if (tail_ce020 == 2) out("regs.ce020memcycles -= 0 * cpucycleunit; /* T=2 */\n"); #endif out("return;\n"); return; } if (do_always_dynamic_cycles) { int total = count_readl + count_readw + count_writel + count_writew - count_readp; if (!total) total++; if (using_mmu || using_prefetch_020) { out("return (%d * 4 * CYCLE_UNIT / 2 + count_cycles) * 4;\n", total); } else { out("return (%d * CYCLE_UNIT / 2 + count_cycles) | (((%d * 4 * CYCLE_UNIT / 2 + count_cycles) * 4) << 16);\n", cycles, total); } } else if (using_simple_cycles) { out("return %d * CYCLE_UNIT / 2 + count_cycles;\n", cycles); } else { out("return %d * CYCLE_UNIT / 2;\n", cycles); } } static void write_return_cycles_none(void) { if (func_noret) { out("return;\n"); } else { out("return 0;\n"); } } static void write_return_cycles2(int end, int no4) { if (end <= 0) { clearmmufixup(0, 1); clearmmufixup(1, 1); } if (using_ce || using_prefetch) { if (end < 0) { if (using_ce || func_noret) { out("return;\n"); } else { out("return 0;\n"); } } else { int cc = count_cycles; if (count_readw + count_writew + count_readl + count_writel + cc == 0 && !no4) cc = 4; returncycles((count_readw + count_writew) * 4 + (count_readl + count_writel) * 8 + cc); if (end) { out("}\n"); out("/* %d%s (%d/%d)", (count_readw + count_writew) * 4 + (count_readl + count_writel) * 8 + cc, count_ncycles ? "+" : "", count_readw + count_readl * 2, count_writew + count_writel * 2); out(" */\n"); } } } else { if (end < 0) { if (using_ce020 || func_noret) { out("return;\n"); } else { out("return 0;\n"); } } else { if (!using_prefetch && !using_prefetch_020) { returncycles((count_readw + count_writew) * 4 + (count_readl + count_writel) * 8 + insn_n_cycles); } else { returncycles((count_readw + count_writew) * 4 + (count_readl + count_writel) * 8 + count_cycles); } if (end) { out("}\n"); } } } } static void write_return_cycles(int end) { write_return_cycles2(end, 0); } static void write_return_cycles_noadd(int end) { write_return_cycles2(end, 1); } static void addcycles_ce020_4 (const char *name, int head, int tail, int cycles) { if (!isce020()) return; if (!head && !tail && !cycles) return; out("/* %s H:%d,T:%d,C:%d */\n", name, head, tail, cycles); } static void addcycles_ce020_5 (const char *name, int head, int tail, int cycles, int ophead) { if (!isce020()) return; if (!head && !tail && !cycles && !ophead) { out("/* OP zero */\n"); return; } count_cycles += cycles; if (!ophead) { addcycles_ce020_4 (name, head, tail, cycles); } else if (ophead > 0) { out("/* %s H:%d-,T:%d,C:%d */\n", name, head, tail, cycles); } else { out("/* %s H:%d+,T:%d,C:%d */\n", name, head, tail, cycles); } } static void addcycles000_nonces(const char *sc) { if (using_nocycles) { return; } if (using_simple_cycles || do_always_dynamic_cycles) { out("count_cycles += (%s) * CYCLE_UNIT / 2;\n", sc); count_ncycles++; } } static void addcycles000_nonce(int c) { if (using_nocycles) { return; } if (using_simple_cycles || do_always_dynamic_cycles) { out("count_cycles += %d * CYCLE_UNIT / 2;\n", c); count_ncycles++; } } static void addcycles000_onlyce (int cycles) { if (using_ce) { out("%s(%d);\n", do_cycles, cycles); } } static void addcycles000(int cycles) { if (using_ce) { out("%s(%d);\n", do_cycles, cycles); } count_cycles += cycles; insn_n_cycles += cycles; } #if 0 static void addcycles000_2(int cycles) { if (using_ce) { out("%s(%d);\n", do_cycles, cycles); } count_cycles += cycles; insn_n_cycles += cycles; } #endif static void addcycles000_3(bool notest) { if (using_ce) { if (notest) { out("%s(cycles);\n", do_cycles); } else { out("if (cycles > 0) %s(cycles);\n", do_cycles); } } count_ncycles++; } static int isreg (amodes mode) { if (mode == Dreg || mode == Areg) return 1; return 0; } static int bit_size (int size) { switch (size) { case sz_byte: return 8; case sz_word: return 16; case sz_long: return 32; default: term(); } return 0; } static const char *bit_mask (int size) { switch (size) { case sz_byte: return "0xff"; case sz_word: return "0xffff"; case sz_long: return "0xffffffff"; default: term(); } return 0; } static void swap_opcode(void) { if (using_get_word_unswapped) { out("\topcode = do_byteswap_16(opcode);\n"); } } static void real_opcode(int *have) { if (!*have) { if (using_get_word_unswapped) { out("\tuae_u32 real_opcode = do_byteswap_16(opcode);\n"); } else { out("\tuae_u32 real_opcode = opcode;\n"); } *have = 1; } } static int mmu040_movem; static void start_mmu040_movem(int movem) { if (abs(movem) != 3) return; out("if (mmu040_movem) {\n"); out("srca = mmu040_movem_ea;\n"); out("} else {\n"); mmu040_movem = 1; } static void end_mmu040_movem(void) { if (!mmu040_movem) return; out("}\n"); mmu040_movem = 0; } static void setpcstring(char *dst, const char *format, ...) { va_list parms; char buffer[1000]; va_start(parms, format); _vsnprintf(buffer, 1000 - 1, format, parms); va_end(parms); if (using_mmu) sprintf(dst, "m68k_setpci(%s);\n", buffer); else if (using_prefetch || using_prefetch_020 || using_test) sprintf(dst, "m68k_setpci_j(%s);\n", buffer); else sprintf(dst, "m68k_setpc_j(%s);\n", buffer); } static void setpc(const char *format, ...) { va_list parms; char buffer[1000]; va_start(parms, format); _vsnprintf(buffer, 1000 - 1, format, parms); va_end(parms); if (using_mmu) out("m68k_setpci(%s);\n", buffer); else if (using_prefetch || using_prefetch_020 || using_test) out("m68k_setpci_j(%s);\n", buffer); else out("m68k_setpc_j(%s);\n", buffer); } static void incpc(const char *format, ...) { va_list parms; char buffer[1000]; va_start(parms, format); _vsnprintf(buffer, 1000 - 1, format, parms); va_end(parms); if (using_mmu || using_prefetch || using_prefetch_020 || using_test) out("m68k_incpci(%s);\n", buffer); else out("m68k_incpc(%s);\n", buffer); } static void sync_m68k_pc(void) { m68k_pc_offset_old = m68k_pc_offset; if (m68k_pc_offset == 0) return; incpc("%d", m68k_pc_offset); m68k_pc_total += m68k_pc_offset; m68k_pc_offset = 0; } static void clear_m68k_offset(void) { m68k_pc_total += m68k_pc_offset; m68k_pc_offset = 0; } static void sync_m68k_pc_noreset(void) { sync_m68k_pc(); m68k_pc_offset = m68k_pc_offset_old; } static char bus_error_text[200]; static int bus_error_specials; static int bus_error_cycles; // 1 = first access, 2 = long second access only static char bus_error_code[1000], bus_error_code2[1000]; static void do_instruction_buserror(void) { if (!needbuserror()) return; if (bus_error_text[0]) { out("if(hardware_bus_error) {\n"); out("int pcoffset = 0;\n"); if (bus_error_code[0]) out("%s", bus_error_code); out("%s", bus_error_text); write_return_cycles(0); out("}\n"); } bus_error_code[0] = 0; bus_error_specials = 0; } static void check_bus_error_ins(int offset, int pcoffset) { const char *opcode; if (bus_error_specials) { opcode = "0"; } else { opcode = "opcode"; } if (pcoffset == -1) { sprintf(bus_error_text, "exception2_fetch(%s, %d, pcoffset);\n", opcode, offset); } else { sprintf(bus_error_text, "exception2_fetch(%s, %d, %d);\n", opcode, offset, pcoffset); } } static void check_bus_error_ins_opcode(int offset, int pcoffset) { const char *opcode; if (bus_error_specials) { opcode = "0"; } else { opcode = "opcode"; } if (pcoffset == -1) { sprintf(bus_error_text, "exception2_fetch_opcode(%s, %d, pcoffset);\n", opcode, offset); } else { sprintf(bus_error_text, "exception2_fetch_opcode(%s, %d, %d);\n", opcode, offset, pcoffset); } } static void check_prefetch_bus_error(int offset, int pcoffset, int secondprefetchmode) { if (!needbuserror()) return; if (offset < 0) { if (offset == -1) offset = 0; else offset = 2; // full prefetch: opcode field is zero if ((offset == 2 && !secondprefetchmode) || secondprefetchmode > 0) { bus_error_specials = 1; } } check_bus_error_ins_opcode(offset, pcoffset); do_instruction_buserror(); } static void check_prefetch_buserror(int offset, int pcoffset) { check_bus_error_ins(offset, pcoffset); do_instruction_buserror(); } static void gen_nextilong2(const char *type, const char *name, int flags, int movem) { int r = m68k_pc_offset; int pcoffset = ((flags & (GF_PCM2 | GF_PCP2)) == (GF_PCM2 | GF_PCP2) ? 0 : (((flags & GF_PCM2) ? -2 : (flags & GF_PCP2) ? 2 : 0))); m68k_pc_offset += 4; out("%s %s;\n", type, name); start_mmu040_movem(movem); if (using_ce020) { if (flags & GF_NOREFILL) out("%s = %s(%d);\n", name, prefetch_long, r); else out("%s = %s(%d);\n", name, prefetch_long, r); count_readl++; } else if (using_ce) { /* we must do this because execution order of (something | something2) is not defined */ if (flags & GF_NOREFILL) { if ((flags & GF_IPL) && !(flags & GF_IPLMID)) { set_ipl(); } else if (!(flags & GF_IPL)) { set_last_access_ipl(); } out("%s = %s(%d) << 16;\n", name, prefetch_word, r + 2); count_readw++; out("%s |= regs.irc;\n", name); check_bus_error_ins(r + 2, pcoffset); do_instruction_buserror(); strcpy(bus_error_code, bus_error_code2); bus_error_code2[0] = 0; bus_error_text[0] = 0; } else { out("%s = %s(%d) << 16;\n", name, prefetch_word, r + 2); count_readw++; check_bus_error_ins(r + 2, pcoffset); do_instruction_buserror(); strcpy(bus_error_code, bus_error_code2); bus_error_code2[0] = 0; if ((flags & GF_IPL)) { set_ipl(); } else if (!(flags & GF_IPL)) { set_last_access_ipl(); } out("%s |= %s(%d);\n", name, prefetch_word, r + 4); count_readw++; check_bus_error_ins(r + 4, -1); } } else if (using_prefetch) { if (flags & GF_NOREFILL) { set_last_access_ipl(); out("%s = %s(%d) << 16;\n", name, prefetch_word, r + 2); count_readw++; out("%s |= regs.irc;\n", name); check_bus_error_ins(r + 2, pcoffset); do_instruction_buserror(); strcpy(bus_error_code, bus_error_code2); bus_error_code2[0] = 0; bus_error_text[0] = 0; } else { out("%s = %s(%d) << 16;\n", name, prefetch_word, r + 2); count_readw++; check_bus_error_ins(r + 2, pcoffset); do_instruction_buserror(); strcpy(bus_error_code, bus_error_code2); bus_error_code2[0] = 0; set_last_access_ipl(); out("%s |= %s(%d);\n", name, prefetch_word, r + 4); count_readw++; check_bus_error_ins(r + 4, -1); } } else { if (flags & GF_NOREFILL) { count_readw++; count_readp++; } else { count_readl++; count_readp++; } out("%s = %s(%d);\n", name, prefetch_long, r); } } static void gen_nextilong (const char *type, const char *name, int flags) { bus_error_text[0] = 0; bus_error_specials = 0; gen_nextilong2 (type, name, flags, 0); } static const char *gen_nextiword(int flags) { static char buffer[80]; int r = m68k_pc_offset; int pcoffset = ((flags & (GF_PCM2 | GF_PCP2)) == (GF_PCM2 | GF_PCP2) ? 0 : (((flags & GF_PCM2) ? -2 : (flags & GF_PCP2) ? 2 : 0))); m68k_pc_offset += 2; bus_error_text[0] = 0; bus_error_specials = 0; if (using_ce020) { if (flags & GF_NOREFILL) sprintf(buffer, "%s(%d)", prefetch_word, r); else sprintf(buffer, "%s(%d)", prefetch_word, r); count_readw++; } else if (using_ce) { if (flags & GF_NOREFILL) { strcpy(buffer, "regs.irc"); } else { set_last_access_ipl(); sprintf(buffer, "%s(%d)", prefetch_word, r + 2); count_readw++; check_bus_error_ins(r + 2, pcoffset); } } else if (using_prefetch) { if (flags & GF_NOREFILL) { strcpy(buffer, "regs.irc"); } else { set_last_access_ipl(); sprintf(buffer, "%s(%d)", prefetch_word, r + 2); count_readw++; check_bus_error_ins(r + 2, pcoffset); } } else { sprintf(buffer, "%s(%d)", prefetch_word, r); if (!(flags & GF_NOREFILL)) { count_readw++; count_readp++; check_bus_error_ins(r, pcoffset); } } return buffer; } static const char *gen_nextibyte(int flags) { static char buffer[80]; int r = m68k_pc_offset; int pcoffset = ((flags & (GF_PCM2 | GF_PCP2)) == (GF_PCM2 | GF_PCP2) ? 0 : (((flags & GF_PCM2) ? -2 : (flags & GF_PCP2) ? 2 : 0))); m68k_pc_offset += 2; bus_error_text[0] = 0; bus_error_specials = 0; if (using_ce020 || using_prefetch_020) { if (flags & GF_NOREFILL) sprintf(buffer, "(uae_u8)%s(%d)", prefetch_word, r); else sprintf(buffer, "(uae_u8)%s(%d)", prefetch_word, r); count_readw++; } else if (using_ce) { if (flags & GF_NOREFILL) { strcpy(buffer, "(uae_u8)regs.irc"); } else { set_last_access_ipl(); sprintf(buffer, "(uae_u8)%s(%d)", prefetch_word, r + 2); count_readw++; check_bus_error_ins(r + 2, pcoffset); } } else if (using_prefetch) { if (flags & GF_NOREFILL) { strcpy(buffer, "(uae_u8)regs.irc"); } else { set_last_access_ipl(); sprintf(buffer, "(uae_u8)%s(%d)", prefetch_word, r + 2); count_readw++; check_bus_error_ins(r + 2, pcoffset); } } else { sprintf(buffer, "%s(%d)", srcbi, r); if (!(flags & GF_NOREFILL)) { count_readw++; count_readp++; check_bus_error_ins(r, pcoffset); } } return buffer; } static void makefromsr(void) { out("MakeFromSR();\n"); if (isce020()) { out("intlev_load(); \n"); } } static void makefromsr_t0(void) { if (isce020()) { out("intlev_load();\n"); out("ipl_fetch_now();\n"); } if (using_prefetch || using_ce) { out("MakeFromSR();\n"); } else { out("MakeFromSR_T0();\n"); } } static void irc2ir_2 (bool dozero) { if (!using_prefetch) return; if (ir2irc) return; ir2irc = 1; out("regs.ir = regs.irc;\n"); if (dozero) out("regs.irc = 0;\n"); check_ipl(); } static void irc2ir (void) { irc2ir_2 (false); } static void copy_opcode(void) { out("opcode = regs.ir;\n"); } static void fill_prefetch_bcc(void) { if (!using_prefetch) return; if (using_bus_error) { copy_opcode(); if (cpu_level == 0) { out("if(regs.t1) opcode |= 0x10000;\n"); } next_level_000(); } if (using_ce) { out("ipl_fetch_next();\n"); } out("%s(%d);\n", prefetch_word, m68k_pc_offset + 2); count_readw++; check_prefetch_bus_error(m68k_pc_offset + 2, -1, 0); did_prefetch = 1; ir2irc = 0; } static void fill_prefetch_1(int o) { if (using_prefetch) { set_last_access_ipl(); out("%s(%d);\n", prefetch_word, o); if (!loopmode || using_ce) { count_readw++; } else { addcycles000_nonce(4); } check_prefetch_bus_error(o, -1, 0); did_prefetch = 1; ir2irc = 0; } else { insn_n_cycles += 4; } } // complete prefetch static void fill_prefetch_1_empty(int o) { if (using_prefetch) { set_last_access_ipl(); out("%s(%d);\n", prefetch_word, o); count_readw++; check_prefetch_bus_error(o ? -2 : -1, 0, 0); did_prefetch = 1; ir2irc = 0; } else { insn_n_cycles += 4; } } #if 0 static void fill_prefetch_full_2 (void) { if (using_prefetch) { fill_prefetch_1_empty (0); irc2ir(); fill_prefetch_1_empty (2); } else if (isprefetch020()) { did_prefetch = 2; total_ce020 -= 4; returntail (false); if (cpu_level >= 3) out("fill_prefetch_030();\n"); else if (cpu_level == 2) out("fill_prefetch_020();\n"); } else { insn_n_cycles += 4; } } #endif // don't check trace bits static void fill_prefetch_full_ntx(int beopcode) { if (using_prefetch) { fill_prefetch_1_empty(0); irc2ir(); if (beopcode) { copy_opcode(); if (cpu_level == 0) { if (beopcode == 2) out("if(regs.t1) opcode |= 0x10000;\n"); if (beopcode == 3) out("if(t1) opcode |= 0x10000;\n"); } next_level_000(); set_ipl(); fill_prefetch_1(2); } else { fill_prefetch_1_empty(2); } } else if (isprefetch020()) { did_prefetch = 2; total_ce020 -= 4; returntail (false); if (cpu_level >= 3) out("fill_prefetch_030_ntx();\n"); else if (cpu_level == 2) out("fill_prefetch_020_ntx();\n"); } else { insn_n_cycles += 2 * 4; } } static void check_trace(void) { if (!using_prefetch && !using_ce && cpu_level >= 2) out("if(regs.t0) check_t0_trace();\n"); } static void trace_t0_68040_only(void) { if (cpu_level == 4) check_trace(); if (cpu_level == 5) { if (next_cpu_level < 5) next_cpu_level = 5 - 1; } else if (cpu_level == 4) { if (next_cpu_level < 4) next_cpu_level = 4 - 1; } } // check trace bits static void fill_prefetch_full(int beopcode) { if (using_prefetch) { fill_prefetch_1_empty(0); irc2ir(); if (beopcode) { copy_opcode(); if (beopcode > 1 && cpu_level == 0) { out("if(regs.t1) opcode |= 0x10000;\n"); } next_level_000(); fill_prefetch_1(2); } else { fill_prefetch_1_empty(2); } } else if (isprefetch020()) { did_prefetch = 2; total_ce020 -= 4; returntail (false); if (cpu_level >= 3) out("fill_prefetch_030();\n"); else if (cpu_level == 2) out("fill_prefetch_020();\n"); } else if (!using_prefetch_020 && cpu_level >= 2) { insn_n_cycles += 2 * 4; check_trace(); } else { insn_n_cycles += 2 * 4; } } // 0 = pc not changed // 1 = pc == oldpc + 2, both address and PC // 2 = pc not changed, stacked PC is oldpc + 2 // -1 = first fetch: pcoffset - adjustment second: pcoffset = 0 (dbcc) static void fill_prefetch_full_000_special(int pctype, const char *format, ...) { char tmp[100]; if (!using_prefetch) { if (format && cpu_level <= 1) { char outbuf[256]; va_list parms; va_start(parms, format); _vsnprintf(outbuf, sizeof(outbuf) - 1, format, parms); va_end(parms); out(outbuf); } insn_n_cycles += 2 * 4; return; } out("%s(%d);\n", prefetch_word, 0); count_readw++; if (pctype == 1) { setpcstring(tmp, "oldpc + 2"); strcpy(bus_error_code, tmp); } else if (pctype == 2) { sprintf(tmp, "pcoffset = oldpc - %s + 2;\n", getpc); strcpy(bus_error_code, tmp); } else if (pctype == -1) { strcpy(bus_error_code, "pcoffset += pcadjust;\n"); } check_prefetch_bus_error(-1, -1, -1); irc2ir(); if (using_bus_error) { copy_opcode(); if (cpu_level == 0) { if (g_instr->mnemo == i_RTE) { out("if(oldt1) opcode |= 0x10000;\n"); } else { out("if(regs.t1) opcode |= 0x10000;\n"); } } next_level_000(); } if (format) { char outbuf[256]; va_list parms; va_start(parms, format); _vsnprintf(outbuf, sizeof(outbuf) - 1, format, parms); va_end(parms); out(outbuf); } if (using_ce) { out("ipl_fetch_next();\n"); } out("%s(%d);\n", prefetch_word, 2); count_readw++; if (pctype > 0) { strcpy(bus_error_code, tmp); } check_prefetch_bus_error(-2, -1, -1); did_prefetch = 1; ir2irc = 0; } // 68000 and 68010 only static void fill_prefetch_full_000(int beopcode) { if (using_prefetch) { fill_prefetch_full(beopcode); } else { insn_n_cycles += 2 * 4; } } // 68020+ static void fill_prefetch_full_020 (void) { if (!using_prefetch_020) { if (!using_prefetch && !using_ce && cpu_level >= 2) out("if(regs.t0) check_t0_trace();\n"); return; } fill_prefetch_full(0); } static void fill_prefetch_0 (void) { if (using_prefetch) { out("%s(0);\n", prefetch_word); count_readw++; check_prefetch_bus_error(0, 0, 0); did_prefetch = 1; ir2irc = 0; } else { insn_n_cycles += 4; } } static void loopmode_start(void) { loopmode_set = 0; loopmodeextra = 0; if (loopmode) { out("int loop_mode = regs.loop_mode;\n"); } } static void loopmode_stop(void) { if (loopmode) { out("regs.loop_mode = loop_mode;\n"); } } static void loopmode_begin(void) { if (loopmode) { out("if(!loop_mode) {\n"); } } static void loopmode_access(void) { if (loopmode && loopmode_set) { loopmode_set = 0; out("if(loop_mode & 0xfffe) {\n"); if (using_ce) { out("%s(loop_mode & 0xfffe);\n", do_cycles); } else { addcycles000_nonces("loop_mode & 0xfffe"); } // CLR.x adds 2 extra cycles when loop exits if (g_instr->mnemo == i_CLR) { out("loop_mode &= 0xffff0000;\n"); out("loop_mode |= 1;\n"); } else { out("loop_mode = 1;\n"); } out("}\n"); } } static void loopmode_end(void) { if (loopmode) { int s = g_instr->size; int m = g_instr->mnemo; int cycs = 0; int ecycs = 0; if (using_prefetch) { if (m == i_CLR) { cycs += 2; ecycs += 4; } else if (m == i_MOVE) { cycs += 2; if (isreg(g_instr->smode)) { ecycs = 2; } } else if (m == i_ADDX || m == i_SUBX) { if (s == sz_long) { cycs += 2; } else { cycs += 4; } } else if (m == i_CMPM) { if (s == sz_long) { cycs += 4; } else { cycs += 2; } } else if (m == i_NBCD) { cycs += 6; } else { cycs += 4; } // destination -(an)? if ((m == i_MOVE || m == i_ABCD || m == i_SBCD) && g_instr->dmode == Apdi) { cycs += 2; } if (ecycs == 0) { ecycs = cycs; } if (m == i_TST && s == sz_long) { ecycs = 4; cycs = 6; } else if (m == i_MOVE) { if (isreg(g_instr->smode)) { ecycs += 2; } } else if (m == i_CMP && s == sz_long) { ecycs -= 2; } if (cycs > 0 || ecycs > 0) { out("} else {\n"); out("loop_mode = 0;\n"); if (cycs > 0) { out("loop_mode |= %d;\n", cycs); } if (ecycs > 0) { out("loop_mode |= %d << 16;\n", ecycs); } } out("}\n"); loopmode_set = 1; } } } static void fill_prefetch_next_noopcodecopy(const char *format, ...) { if (using_prefetch) { loopmode_begin(); irc2ir(); if (using_bus_error) { bus_error_code[0] = 0; if (format) { va_list parms; va_start(parms, format); _vsnprintf(bus_error_code, sizeof(bus_error_code) - 1, format, parms); va_end(parms); } if (cpu_level == 0) { out("opcode |= 0x20000;\n"); } next_level_000(); } fill_prefetch_1(m68k_pc_offset + 2); if (using_bus_error) { copy_opcode(); } loopmode_end(); } else { insn_n_cycles += 4; } } static void fill_prefetch_next(void) { if (using_prefetch) { loopmode_begin(); irc2ir(); if (using_bus_error) { copy_opcode(); } fill_prefetch_1(m68k_pc_offset + 2); loopmode_end(); } else { insn_n_cycles += 4; } } static void fill_prefetch_next_t(void) { if (using_prefetch) { loopmode_begin(); irc2ir(); if (using_bus_error) { copy_opcode(); if (cpu_level == 0) { strcat(bus_error_code, "if (regs.t1) opcode |= 0x10000;\n"); } next_level_000(); } fill_prefetch_1(m68k_pc_offset + 2); loopmode_end(); } else { insn_n_cycles += 4; } } static void fill_prefetch_next_extra(const char *cond, const char *format, ...) { if (using_prefetch) { loopmode_begin(); irc2ir(); if (using_bus_error) { if (cond) out("%s\n", cond); copy_opcode(); bus_error_code[0] = 0; if (format) { va_list parms; va_start(parms, format); _vsnprintf(bus_error_code, sizeof(bus_error_code) - 1, format, parms); va_end(parms); } } fill_prefetch_1(m68k_pc_offset + 2); loopmode_end(); } else { insn_n_cycles += 4; } } static void fill_prefetch_next_after(int copy, const char *format, ...) { if (using_prefetch) { loopmode_begin(); irc2ir(); if (cpu_level == 0) { out("opcode |= 0x20000;\n"); } next_level_000(); bus_error_code[0] = 0; if (format) { va_list parms; va_start(parms, format); _vsnprintf(bus_error_code, sizeof(bus_error_code) - 1, format, parms); va_end(parms); } fill_prefetch_1(m68k_pc_offset + 2); if (using_bus_error) { if (copy) { copy_opcode(); } else if (g_instr->size == sz_long) { // long write, only do opcode copy after first word write opcode_nextcopy = 1; } } loopmode_end(); } else { insn_n_cycles += 4; } } #if 0 static void fill_prefetch_next_skipopcode(void) { if (using_prefetch) { irc2ir(); if (using_bus_error) { if (cpu_level == 0) { out("opcode |= 0x20000;\n"); } } fill_prefetch_1(m68k_pc_offset + 2); } else { insn_n_cycles += 4; } } #endif static void fill_prefetch_next_empty(void) { if (using_prefetch) { irc2ir(); fill_prefetch_1_empty(m68k_pc_offset + 2); } else { insn_n_cycles += 4; } } static void fill_prefetch_finish (void) { if (did_prefetch) return; if (using_prefetch) { fill_prefetch_1 (m68k_pc_offset); } if (using_prefetch_020) { did_prefetch = 1; } } static void dummy_prefetch(const char *newpc, const char *oldpc) { if (using_prefetch && cpu_level == 1) { if (!newpc && !oldpc) { out("%s((m68k_getpci() & 1) ? -1 : 0);\n", prefetch_word); } else { if (newpc) { if (!oldpc) out("uaecptr d_oldpc = m68k_getpci();\n"); setpc("%s & ~1", newpc); } out("%s(0);\n", prefetch_word); if (oldpc) { setpc(oldpc); } else if (newpc) { setpc("d_oldpc"); } } } } static void duplicate_carry(void) { out("COPY_CARRY();\n"); } static void genflags_normal(flagtypes type, wordsizes size, const char *value, const char *src, const char *dst) { char vstr[100], sstr[100], dstr[100]; char usstr[100], udstr[100]; char unsstr[100], undstr[100]; switch (size) { case sz_byte: strcpy(vstr, "((uae_s8)("); strcpy(usstr, "((uae_u8)("); break; case sz_word: strcpy(vstr, "((uae_s16)("); strcpy(usstr, "((uae_u16)("); break; case sz_long: strcpy(vstr, "((uae_s32)("); strcpy(usstr, "((uae_u32)("); break; default: term(); } strcpy(unsstr, usstr); strcpy(sstr, vstr); strcpy(dstr, vstr); strcat(vstr, value); strcat(vstr, "))"); strcat(dstr, dst); strcat(dstr, "))"); strcat(sstr, src); strcat(sstr, "))"); strcpy(udstr, usstr); strcat(udstr, dst); strcat(udstr, "))"); strcat(usstr, src); strcat(usstr, "))"); strcpy(undstr, unsstr); strcat(unsstr, "-"); strcat(undstr, "~"); strcat(undstr, dst); strcat(undstr, "))"); strcat(unsstr, src); strcat(unsstr, "))"); switch (type) { case flag_logical_noclobber: case flag_logical: case flag_z: case flag_zn: case flag_av: case flag_sv: case flag_addx: case flag_subx: break; case flag_add: out("uae_u32 %s = %s + %s;\n", value, udstr, usstr); break; case flag_sub: case flag_cmp: out("uae_u32 %s = %s - %s;\n", value, udstr, usstr); break; } switch (type) { case flag_logical_noclobber: case flag_logical: case flag_z: case flag_zn: break; case flag_add: case flag_sub: case flag_addx: case flag_subx: case flag_cmp: case flag_av: case flag_sv: out("" BOOL_TYPE " flgs = %s < 0;\n", sstr); out("" BOOL_TYPE " flgo = %s < 0;\n", dstr); out("" BOOL_TYPE " flgn = %s < 0;\n", vstr); break; } switch (type) { case flag_logical: out("CLEAR_CZNV();\n"); out("SET_ZFLG(%s == 0);\n", vstr); out("SET_NFLG(%s < 0);\n", vstr); break; case flag_logical_noclobber: out("SET_ZFLG(%s == 0);\n", vstr); out("SET_NFLG(%s < 0);\n", vstr); break; case flag_av: out("SET_VFLG((flgs ^ flgn) & (flgo ^ flgn));\n"); break; case flag_sv: out("SET_VFLG((flgs ^ flgo) & (flgn ^ flgo));\n"); break; case flag_z: out("SET_ZFLG(GET_ZFLG() & (%s == 0));\n", vstr); break; case flag_zn: out("SET_ZFLG(GET_ZFLG() & (%s == 0));\n", vstr); out("SET_NFLG(%s < 0);\n", vstr); break; case flag_add: out("SET_ZFLG(%s == 0);\n", vstr); out("SET_VFLG((flgs ^ flgn) & (flgo ^ flgn));\n"); out("SET_CFLG(%s < %s);\n", undstr, usstr); duplicate_carry(); out("SET_NFLG(flgn != 0);\n"); break; case flag_sub: out("SET_ZFLG(%s == 0);\n", vstr); out("SET_VFLG((flgs ^ flgo) & (flgn ^ flgo));\n"); out("SET_CFLG(%s > %s);\n", usstr, udstr); duplicate_carry(); out("SET_NFLG(flgn != 0);\n"); break; case flag_addx: out("SET_VFLG((flgs ^ flgn) & (flgo ^ flgn));\n"); /* minterm SON: 0x42 */ out("SET_CFLG(flgs ^ ((flgs ^ flgo) & (flgo ^ flgn)));\n"); /* minterm SON: 0xD4 */ duplicate_carry(); break; case flag_subx: out("SET_VFLG((flgs ^ flgo) & (flgo ^ flgn));\n"); /* minterm SON: 0x24 */ out("SET_CFLG(flgs ^ ((flgs ^ flgn) & (flgo ^ flgn)));\n"); /* minterm SON: 0xB2 */ duplicate_carry(); break; case flag_cmp: out("SET_ZFLG(%s == 0);\n", vstr); out("SET_VFLG((flgs != flgo) && (flgn != flgo));\n"); out("SET_CFLG(%s > %s);\n", usstr, udstr); out("SET_NFLG(flgn != 0);\n"); break; } } static void genflags(flagtypes type, wordsizes size, const char *value, const char *src, const char *dst) { /* Temporarily deleted 68k/ARM flag optimizations. I'd prefer to have them in the appropriate m68k.h files and use just one copy of this code here. The API can be changed if necessary. */ if (using_optimized_flags) { switch (type) { case flag_add: case flag_sub: out("uae_u32 %s;\n", value); break; default: break; } /* At least some of those casts are fairly important! */ switch (type) { case flag_logical_noclobber: out("{uae_u32 oldcznv = GET_CZNV & ~(FLAGVAL_Z | FLAGVAL_N);\n"); if (strcmp(value, "0") == 0) { out("SET_CZNV(olcznv | FLAGVAL_Z);\n"); } else { switch (size) { case sz_byte: out("optflag_testb((uae_s8)(%s));\n", value); break; case sz_word: out("optflag_testw((uae_s16)(%s));\n", value); break; case sz_long: out("optflag_testl((uae_s32)(%s));\n", value); break; default: term(); } out("IOR_CZNV(oldcznv);\n"); } out("}\n"); return; case flag_logical: if (strcmp(value, "0") == 0) { out("SET_CZNV(FLAGVAL_Z);\n"); } else { switch (size) { case sz_byte: out("optflag_testb((uae_s8)(%s));\n", value); break; case sz_word: out("optflag_testw((uae_s16)(%s));\n", value); break; case sz_long: out("optflag_testl((uae_s32)(%s));\n", value); break; default: term(); } } return; case flag_add: switch (size) { case sz_byte: out("optflag_addb(%s, (uae_s8)(%s), (uae_s8)(%s));\n", value, src, dst); break; case sz_word: out("optflag_addw(%s, (uae_s16)(%s), (uae_s16)(%s));\n", value, src, dst); break; case sz_long: out("optflag_addl(%s, (uae_s32)(%s), (uae_s32)(%s));\n", value, src, dst); break; default: term(); } return; case flag_sub: switch (size) { case sz_byte: out("optflag_subb(%s, (uae_s8)(%s), (uae_s8)(%s));\n", value, src, dst); break; case sz_word: out("optflag_subw(%s, (uae_s16)(%s), (uae_s16)(%s));\n", value, src, dst); break; case sz_long: out("optflag_subl(%s, (uae_s32)(%s), (uae_s32)(%s));\n", value, src, dst); break; default: term(); } return; case flag_cmp: switch (size) { case sz_byte: out("optflag_cmpb((uae_s8)(%s), (uae_s8)(%s));\n", src, dst); break; case sz_word: out("optflag_cmpw((uae_s16)(%s), (uae_s16)(%s));\n", src, dst); break; case sz_long: out("optflag_cmpl((uae_s32)(%s), (uae_s32)(%s));\n", src, dst); break; default: term(); } return; default: break; } } genflags_normal(type, size, value, src, dst); } // Handle special MOVE.W/.L condition codes when destination write causes bus error. static void move_68000_bus_error(int offset, int size, int *setapdi, int *fcmodeflags) { int smode = g_instr->smode; int dmode = g_instr->dmode; if (size == sz_byte) { if (dmode == Apdi) { if (cpu_level == 0) { out("if (regs.t1) opcode |= 0x10000;\n"); // I/N set } } else if (dmode == Aipi) { // move.b x,(an)+: an is not increased out("m68k_areg(regs, dstreg) -= areg_byteinc[dstreg];\n"); } } else if (size == sz_word) { if (dmode == Apdi) { if (cpu_level == 0) { out("if (regs.t1) opcode |= 0x10000;\n"); // I/N set } } else if (dmode == Aipi) { // move.w x,(an)+: an is not increased out("m68k_areg(regs, dstreg) -= 2;\n"); } } else if (size == sz_long && ((offset == 0 && dmode != Apdi) || (offset == 2 && dmode == Apdi))) { // Long MOVE is more complex but not as complex as address error.. // First word. int set_ccr = 0; int set_high_word = 0; int set_low_word = 0; switch (smode) { case Dreg: case Areg: if (dmode == Ad16 || dmode == Ad8r) { set_high_word = 3; } else if (dmode == Apdi || dmode == absw || dmode == absl) { set_ccr = 1; } break; case Aind: case Aipi: case Apdi: case Ad16: case Ad8r: case PC16: case PC8r: case absl: case absw: if (dmode == Aind || dmode == Aipi || dmode == absw || dmode == absl) { set_low_word = 1; } else if (dmode == Apdi || dmode == Ad16 || dmode == Ad8r) { set_ccr = 1; } break; case imm: if (dmode == Ad16 || dmode == Ad8r) { set_high_word = 1; } else if (dmode == Apdi || dmode == absw || dmode == absl) { set_ccr = 1; } break; } if (set_low_word == 1) { // Low word: Z and N out("ccr_68000_long_move_ae_LZN(src);\n"); } else if (set_high_word == 3) { // High word: N only, clear Z if non-zero out("SET_NFLG(src < 0);\n"); out("if((src & 0xffff0000)) SET_ZFLG(0);\n"); } else if (set_high_word) { // High word: N set/reset and Z clear. out("ccr_68000_long_move_ae_HNZ(src);\n"); } else if (set_ccr) { // Set normally. out("ccr_68000_long_move_ae_normal(src);\n"); } if (dmode == Aipi) { out("m68k_areg(regs, dstreg) -= 4;\n"); } else if (dmode == Apdi) { out("m68k_areg(regs, dstreg) += 4;\n"); } } else if (size == sz_long) { // Second word (much simpler) int set_ccr = 0; int set_low_word = 0; switch (smode) { case Dreg: case Areg: if (dmode == Apdi || dmode == absw || dmode == absl) { set_ccr = 1; } else { set_low_word = 1; } break; case Aind: case Aipi: case Apdi: case Ad16: case Ad8r: case PC16: case PC8r: case absl: case absw: set_ccr = 1; break; case imm: if (dmode == Apdi || dmode == absw || dmode == absl) { set_ccr = 1; } else { set_low_word = 1; } break; } if (set_low_word == 1) { // Low word: Z and N out("ccr_68000_long_move_ae_LZN(src);\n"); } else if (set_ccr) { // Set normally. out("ccr_68000_long_move_ae_normal(src);\n"); } if (dmode == Aipi) { out("m68k_areg(regs, dstreg) -= 4;\n"); } else if (dmode == Apdi) { out("m68k_areg(regs, dstreg) += 4;\n"); } } } static char const *bus_error_reg; static int bus_error_reg_add; static int do_bus_error_fixes(const char *name, int offset, int write) { switch (bus_error_reg_add) { case 1: case -1: if (g_instr->mnemo == i_CMPM && bus_error_reg_add > 0) { // CMPM.B (an)+,(an)+: first increased normally, second not increased out("m68k_areg(regs, %s) += areg_byteinc[%s] + %d;\n", bus_error_reg, bus_error_reg, offset); } break; case 2: case -2: if (g_instr->mnemo == i_RTR) { ; } else if (g_instr->mnemo == i_RTE) { // stack is decreased first out("m68k_areg(regs, %s) += %d;\n", bus_error_reg, cpu_level == 0 ? 14 : 58); } else { out("m68k_areg(regs, %s) += 2 + %d;\n", bus_error_reg, offset); } break; case 3: case -3: if (g_instr->mnemo == i_CMPM && bus_error_reg_add > 0) { // CMPM.L (an)+,(an)+: first increased normally, second not increased out("m68k_areg(regs, %s) += 2 + %d;\n", bus_error_reg, offset); } break; case 4: case -4: if ((g_instr->mnemo == i_ADDX || g_instr->mnemo == i_SUBX) && g_instr->size == sz_long) { // ADDX.L/SUBX.L -(an),-(an) source: stack frame decreased by 2, not 4. offset += 2; } else if (g_instr->mnemo == i_RTR) { if (offset) { out("m68k_areg(regs, %s) += 4;\n", bus_error_reg); out("regs.sr &= 0xFF00; sr &= 0xFF;\n"); out("regs.sr |= sr;\n"); out("MakeFromSR();\n"); } else { out("m68k_areg(regs, %s) -= 2;\n", bus_error_reg); } } else if (g_instr->mnemo == i_RTE) { // stack is decreased first out("m68k_areg(regs, %s) += %d - 2;\n", bus_error_reg, cpu_level == 0 ? 14 : 58); if (offset) { out("regs.sr = sr;\n"); out("MakeFromSR();\n"); } } else if (cpu_level == 1) { // -(an).l where first word causes bus error: An is not modified // -(an).l where second word causes bus error: An is modified if (g_instr->size != sz_long || (g_instr->size == sz_long && offset)) { out("m68k_areg(regs, %s) = %sa;\n", bus_error_reg, name); } } else { out("m68k_areg(regs, %s) = %sa;\n", bus_error_reg, name); } break; } return offset; } static void check_bus_error(const char *name, int offset, int write, int size, const char *writevar, int fc, int pcoffset) { int mnemo = g_instr->mnemo; if (!needbuserror()) return; // basic support if ((!using_prefetch && !using_ce) || cpu_level >= 2) { fc &= 7; out("if(hardware_bus_error) {\n"); out("cpu_bus_rmw=false;\n"); // WINUAE_FOR_HATARI if (write) { out("exception2_write(opcode, %sa + %d, %d, %s, %d);\n", name, offset, size, writevar, (!write && (g_instr->smode == PC16 || g_instr->smode == PC8r)) || (write && (g_instr->dmode == PC16 || g_instr->dmode == PC8r)) ? 2 : fc); } else { out("exception2_read(opcode, %sa + %d, %d, %d);\n", name, offset, size, (!write && (g_instr->smode == PC16 || g_instr->smode == PC8r)) || (write && (g_instr->dmode == PC16 || g_instr->dmode == PC8r)) ? 2 : fc); } write_return_cycles(0); out("}\n"); return; } // 68000/68010 bus error if (cpu_level >= 2) return; if (!using_prefetch && !using_ce) return; next_level_000(); uae_u32 extra = fc & 0xffff0000; fc &= 0xffff; out("if(hardware_bus_error) {\n"); out("cpu_bus_rmw=false;\n"); // WINUAE_FOR_HATARI int setapdiback = 0; if (fc == 2) { out("exception2_fetch(opcode, %d);\n", offset); } else { if (bus_error_cycles > 0) { if (using_prefetch) { out("%s(%d);\n", do_cycles, bus_error_cycles); } else { out("count_cycles += % d * CYCLE_UNIT / 2;\n", bus_error_cycles); } bus_error_cycles = 0; } int pc_offset_extra = cpu_level == 0 ? exception_pc_offset_extra_000 : 0; if (pcoffset == -1) { incpc("%d", m68k_pc_offset + 2); } else if (exception_pc_offset + pc_offset_extra + pcoffset) { incpc("%d", exception_pc_offset + pc_offset_extra + pcoffset); } if (g_instr->mnemo == i_MOVE && write) { move_68000_bus_error(offset, g_instr->size, &setapdiback, &fc); } offset = do_bus_error_fixes(name, offset, write); if (mnemo == i_BTST && (g_instr->dmode == PC16 || g_instr->dmode == PC8r)) { // BTST special case where destination is read access fc = 2; } if (mnemo == i_MVMEL && (g_instr->dmode == PC16 || g_instr->dmode == PC8r)) { // MOVEM to registers fc = 2; } if ((mnemo == i_ADDX || mnemo == i_SUBX) && g_instr->size == sz_long && g_instr->dmode == Apdi && write && offset) { out( "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = ((uae_s16)(newv)) < 0;\n"); if (mnemo == i_ADDX) { out( "SET_VFLG((bflgs ^ bflgn) & (bflgo ^ bflgn));\n" "SET_CFLG(bflgs ^ ((bflgs ^ bflgo) & (bflgo ^ bflgn)));\n"); } else { out( "SET_VFLG((bflgs ^ bflgo) & (bflgo ^ bflgn));\n" "SET_CFLG(bflgs ^ ((bflgs ^ bflgn) & (bflgo ^ bflgn)));\n"); } out( "COPY_CARRY();\n" "SET_ZFLG(GET_ZFLG() & (((uae_s16)(newv)) == 0));\n" "SET_NFLG(((uae_s16)(newv)) < 0);\n"); } if (mnemo == i_LINK) { // a7 -> a0 copy done before A7 address error check if (write) { out("m68k_areg(regs, 7) += 4;\n"); } out("m68k_areg(regs, srcreg) = olda;\n"); } if (mnemo == i_PEA && write && offset && g_instr->smode != absw && g_instr->smode != absl) { if (cpu_level == 0) { out("if (regs.t1) opcode |= 0x10000;\n"); // I/N set } } if (cpu_level == 1) { // 68010 bus/address error HB bit if (extra) { out("opcode |= 0x%x;\n", extra); } // upper byte of SSW is zero -flag. if (g_instr->mnemo == i_MVSR2 && !write) { out("opcode |= 0x20000;\n"); } if (g_instr->mnemo == i_MOVES) { out("regs.irc = extra;\n"); if (!write) { out("regs.write_buffer = extra;\n"); } if (g_instr->size == sz_long) { if (g_instr->dmode == Apdi) { if (!write) { out("m68k_areg(regs, dstreg) = srca;\n"); } } else if (g_instr->dmode == Aipi) { out("m68k_areg(regs, dstreg) += 4;\n"); } } if (!write) { if (g_instr->dmode == Apdi || g_instr->dmode == absl) { incpc("2"); } else if (g_instr->dmode >= Ad16) { incpc("4"); } } } else if (g_instr->mnemo == i_TAS) { if (!write) { out("regs.read_buffer = regs.irc & 0xff00;\n"); out("regs.read_buffer |= 0x80;\n"); if (cpu_level == 1 && g_instr->smode >= Ad16) { incpc("2"); } } out("opcode |= 0x80000;\n"); } else if (g_instr->mnemo == i_CLR) { if (g_instr->smode < Ad16) { out("regflags.cznv = oldflags.cznv;\n"); } // (an)+ and -(an) is done later if (g_instr->smode == Aipi || g_instr->smode == Apdi) { if (g_instr->size == sz_byte) { out("m68k_areg(regs, srcreg) %c= areg_byteinc[srcreg];\n", g_instr->smode == Aipi ? '-' : '+'); } else { out("m68k_areg(regs, srcreg) %c= %d;\n", g_instr->smode == Aipi ? '-' : '+', 1 << g_instr->size); } } } else if (g_instr->mnemo == i_MOVE) { if (write) { if (g_instr->smode >= Aind && g_instr->smode < imm && g_instr->dmode == absl) { out("regs.irc = dsta >> 16;\n"); } } } else if (g_instr->mnemo == i_MVPRM) { if (write) { if (g_instr->size == sz_word) { out("uae_u16 val = src;\n"); } else { out("uae_u16 val = src >> %d;\n", offset <= 2 ? 16 : 0); } size |= 0x100; writevar = "val"; } } } // write causing bus error and trace: set I/N if (write && g_instr->size <= sz_word && cpu_level == 0 && mnemo != i_MOVE && mnemo != i_BSR && mnemo != i_LINK && mnemo != i_MVMEL && mnemo != i_MVMLE && mnemo != i_MVPRM && mnemo != i_MVPMR) { out("if (regs.t1) opcode |= 0x10000;\n"); // I/N set } if (write) { out("exception2_write(opcode, %sa + %d, 0x%x, %s, %d);\n", name, offset, size, writevar, (!write && (g_instr->smode == PC16 || g_instr->smode == PC8r)) || (write && (g_instr->dmode == PC16 || g_instr->dmode == PC8r)) ? 2 : fc); } else { out("exception2_read(opcode, %sa + %d, 0x%x, %d);\n", name, offset, size, (!write && (g_instr->smode == PC16 || g_instr->smode == PC8r)) || (write && (g_instr->dmode == PC16 || g_instr->dmode == PC8r)) ? 2 : fc); } } write_return_cycles(0); out("}\n"); } static void gen_set_fault_pc (bool multi, bool not68030) { int m68k_pc_total_old = m68k_pc_total; if (using_mmu == 68040) { sync_m68k_pc(); out("regs.instruction_pc = %s;\n", getpc); out("mmu_restart = false;\n"); m68k_pc_offset = 0; clearmmufixup(0, 0); } else if (using_mmu == 68030) { if (!MMU68030_LAST_WRITE) return; if (not68030) return; sync_m68k_pc(); out("regs.instruction_pc = %s;\n", getpc); out("mmu030_state[1] |= MMU030_STATEFLAG1_LASTWRITE;\n"); m68k_pc_offset = 0; } if (multi) m68k_pc_total = m68k_pc_total_old; } static void syncmovepc (int getv, int flags) { #if 0 if (!(flags & GF_MOVE)) return; if (getv == 1) { sync_m68k_pc(); //fill_prefetch_next(); } #endif } static void head_cycs (int h) { if (head_ce020_cycs_done) return; if (h < 0) return; //out("do_sync_tail(%d);\n", h); //out("do_head_cycles_ce020(%d);\n", h); head_ce020_cycs_done = true; tail_ce020 = -1; } static void add_head_cycs (int h) { if (!isce020()) return; head_ce020_cycs_done = false; head_cycs (h); } static void addopcycles_ce20 (int h, int t, int c, int subhead, int flags) { head_cycs (h); //c = 0; #if 0 if (tail_ce020 == 1) out("regs.ce020memcycles -= 1 * cpucycleunit; /* T=1 */ \n"); else if (tail_ce020 == 2) out("regs.ce020memcycles -= 2 * cpucycleunit; /* T=2 */\n"); #endif if (1 && !subhead && (h > 0 || t > 0 || c > 0) && got_ea_ce020 && !(flags & GF_LRMW)) { if (!did_prefetch) { get_prefetch_020(); did_prefetch = 1; } #if 0 if (1) { if (h > 0) { out("limit_cycles_ce020(%d);\n", h); } else { out("limit_all_cycles_ce020();\n"); } } #endif } #if 0 if (tail_ce020 >= 0 && h >= 0 && head_in_ea_ce020 == 0) { int largest = tail_ce020 > h ? tail_ce020: h; if (tail_ce020 != h) { //out("do_cycles_ce020(%d - %d);\n", tail_ce020 > h ? tail_ce020 : h, tail_ce020 > h ? h : tail_ce020); //out("do_cycles_ce020(%d - %d);\n", tail_ce020 > h ? tail_ce020 : h, tail_ce020 > h ? h : tail_ce020); if (h) { out("regs.ce020memcycles -= %d * cpucycleunit;\n", h); out("if (regs.ce020memcycles < 0) {\n"); //out("x_do_cycles(-regs.ce020memcycles);\n"); out("regs.ce020memcycles = 0;\n"); out("}\n"); } else { out("regs.ce020memcycles = 0;\n"); } //out("regs.ce020memcycles = 0;\n"); #if 0 if (tail_ce020) out("regs.ce020_tail = get_cycles() - regs.ce020_tail;\n"); else out("regs.ce020_tail = 0;\n"); out("if (regs.ce020_tail < %d * cpucycleunit)\n", largest); out("x_do_cycles(%d * cpucycleunit - regs.ce020_tail);\n", largest); #endif } else if (h) { out("/* ea tail == op head (%d) */\n", h); out("regs.ce020memcycles -= %d * cpucycleunit;\n", h); out("if (regs.ce020memcycles < 0) {\n"); //out("x_do_cycles(-regs.ce020memcycles);\n"); out("regs.ce020memcycles = 0;\n"); out("}\n"); } } #endif if (h < 0) h = 0; if (c < 8) // HACK c = 0; // c = internal cycles needed after head cycles and before tail cycles. Not total cycles. addcycles_ce020_5 ("op", h, t, c - h - t, -subhead); //out("regs.irc = get_word_ce020_prefetch(%d);\n", m68k_pc_offset); #if 0 if (c - h - t > 0) { out("%s(%d);\n", do_cycles, c - h - t); count_cycles_ce020 += c; count_cycles += c; } #endif //out("regs.ce020_tail = 0;\n"); total_ce020 = c; tail_ce020 = t; // if (total_ce020 >= 2) // out("int op_cycles = get_cycles();\n"); } static void addop_ce020 (struct instr *curi, int subhead, int flags) { if (isce020()) { int h = curi->head; int t = curi->tail; int c = curi->clocks; #if WAITSTATUS_020_EXTRA if ((((curi->sduse & 2) && !isreg (curi->smode)) || (((curi->sduse >> 4) & 2) && !isreg (curi->dmode))) && using_waitstates) { t += using_waitstates; c += using_waitstates; } #endif addopcycles_ce20 (h, t, c, -subhead, flags); } } static void addcycles_ea_ce020_5 (const char *ea, int h, int t, int c, int oph) { if (!isce020()) return; head_cycs (h + oph); // if (!h && !h && !c && !oph) // return; c = c - h - t; c = 0; // HACK if (!oph) { out("/* ea H:%d,T:%d,C:%d %s */\n", h, t, c, ea); } else { if (oph && t) term_err ("Both op head and tail can't be non-zero"); if (oph > 0) { out("/* ea H:%d+%d=%d,T:%d,C:%d %s */\n", h, oph, h + oph, t, c, ea); h += oph; } else { out("/* ea H:%d-%d=%d,T:%d,C:%d %s */\n", h, -oph, h + oph, t, c, ea); h += oph; } } #if 0 if (h) { out("limit_cycles_ce020(%d);\n", h); } else { out("limit_all_cycles_ce020();\n"); } #endif if (1 && c > 0) { out("%s(%d);\n", do_cycles, c); count_cycles += c; } tail_ce020 = t; head_in_ea_ce020 = oph; got_ea_ce020 = true; // if (t > 0) // out("regs.ce020_tail = get_cycles() + %d * cpucycleunit;\n", t); } static void addcycles_ea_ce020_4 (const char *ea, int h, int t, int c) { addcycles_ea_ce020_5 (ea, h, t, c, 0); } #define SETCE020(h2,t2,c2) { h = h2; t = t2; c = c2; } #define SETCE020H(h2,t2,c2) { h = h2; oph = curi ? curi->head : 0; t = t2; c = c2; } #if WAITSTATUS_020_EXTRA #define SETCE020WS(h2,t2,c2,ws2) { h = h2; t = t2; c = c2; ws = ws2; } #else #define SETCE020WS(h2,t2,c2,ws2) { h = h2; t = t2; c = c2; } #endif static int gence020cycles_fiea (struct instr *curi, wordsizes ssize, amodes dmode) { bool l = ssize == sz_long; int h = 0, t = 0, c = 0, oph = 0; switch ((int)dmode) { case Dreg: case Areg: if (!l) SETCE020H(2, 0, 2) else SETCE020H(4, 0, 4) break; case Aind: // (An) if (!l) SETCE020(1, 1, 3) else SETCE020(1, 0, 4) break; case Aipi: // (An)+ if (!l) SETCE020(2, 1, 5) else SETCE020(4, 1, 7) break; case Apdi: // -(An) if (!l) SETCE020(2, 2, 4) else SETCE020(2, 0, 4) break; case Ad8r: // (d8,An,Xn) case PC8r: // (d8,PC,Xn) if (!l) SETCE020(6, 2, 8) else SETCE020(8, 2, 10) break; case Ad16: // (d16,An) case PC16: // (d16,PC) if (!l) SETCE020(2, 0, 4) else SETCE020(4, 0, 6) break; case absw: if (!l) SETCE020(4, 2, 6) else SETCE020(6, 2, 8) break; case absl: if (!l) SETCE020(3, 0, 6) else SETCE020(5, 0, 8) break; } addcycles_ea_ce020_5 ("fiea", h, t, c, oph); return oph; } static int gence020cycles_ciea (struct instr *curi, wordsizes ssize, amodes dmode) { int h = 0, t = 0, c = 0, oph = 0; bool l = ssize == sz_long; switch ((int)dmode) { case Dreg: case Areg: if (!l) SETCE020H(2, 0, 2) else SETCE020H(4, 0, 4) break; case Aind: // (An) if (!l) SETCE020H(2, 0, 2) else SETCE020H(4, 0, 4) break; case Aipi: // (An)+ if (!l) SETCE020(2, 0, 4) else SETCE020(4, 0, 6) break; case Apdi: // -(An) if (!l) SETCE020H(2, 0, 2) else SETCE020H(4, 0, 4) break; case Ad8r: // (d8,An,Xn) case PC8r: // (d8,PC,Xn) if (!l) SETCE020H(6, 0, 6) else SETCE020H(8, 0, 8) break; case Ad16: // (d16,An) case PC16: // (d16,PC) if (!l) SETCE020H(4, 0, 4) else SETCE020H(6, 0, 6) break; case absw: if (!l) SETCE020H(4, 0, 4) else SETCE020H(6, 0, 6) break; case absl: if (!l) SETCE020H(6, 0, 6) else SETCE020H(8, 0, 8) break; } addcycles_ea_ce020_5 ("ciea", h, t, c, oph); return oph; } static int gence020cycles_fea (amodes mode) { int h = 0, t = 0, c = 0; #if WAITSTATUS_020_EXTRA int ws = 0; #endif switch ((int)mode) { case Dreg: case Areg: SETCE020WS(0, 0, 0, 0) break; case Aind: // (An) SETCE020WS(1, 1, 3, 1) break; case Aipi: // (An)+ SETCE020WS(0, 1, 3, 1) break; case Apdi: // -(An) SETCE020WS(2, 2, 4, 1) break; case Ad8r: // (d8,An,Xn) case PC8r: // (d8,PC,Xn) SETCE020WS(4, 2, 6, 1) break; case Ad16: // (d16,An) case PC16: // (d16,PC) SETCE020WS(2, 2, 4, 1) break; case absw: SETCE020WS(2, 2, 4, 1) break; case absl: SETCE020WS(1, 0, 4, 1) break; } #if WAITSTATUS_020_EXTRA if (using_waitstates) { t += ws * using_waitstates; c += ws * using_waitstates; } #endif addcycles_ea_ce020_4 ("fea", h, t, c); return 0; } static int gence020cycles_cea (struct instr *curi, amodes mode) { int h = 0, t = 0, c = 0, oph = 0; switch ((int)mode) { case Dreg: case Areg: SETCE020(0, 0, 0); break; case Aind: // (An) SETCE020H(2 + h, 0, 2); break; case Aipi: // (An)+ SETCE020(0, 0, 2); break; case Apdi: // -(An) SETCE020H(2, 0, 2) break; case Ad8r: // (d8,An,Xn) case PC8r: // (d8,PC,Xn) SETCE020H(4, 0, 4) break; case Ad16: // (d16,An) case PC16: // (d16,PC) SETCE020H(2, 0, 2) break; case absw: SETCE020H(2, 0, 2) break; case absl: SETCE020H(4, 0, 4) break; } addcycles_ea_ce020_5 ("cea", h, t, c, oph); return oph; } static int gence020cycles_jea (struct instr *curi, amodes mode) { int h = 0, t = 0, c = 0, oph = 0; switch ((int)mode) { case Aind: // (An) SETCE020H(2, 0, 2) break; case Ad16: // (d16,An) case PC16: // (d16,PC) SETCE020H(4, 0, 4) break; case absw: SETCE020H(2, 0, 2) break; case absl: SETCE020H(2, 0, 2) break; } addcycles_ea_ce020_5 ("jea", h, t, c, oph); return oph; } static void maybeaddop_ce020 (int flags) { if (flags & GF_OPCE020) addop_ce020 (curi_ce020, subhead_ce020, flags); } // Handle special MOVE.W/.L condition codes when destination write causes address error. static void move_68000_address_error(int size, int *setapdi, int *fcmodeflags) { int smode = g_instr->smode; int dmode = g_instr->dmode; if (dmode == Apdi) { addcycles000_onlyce(2); addcycles000_nonce(2); } else if (dmode == Aipi) { // move.x x,(an)+: an is not increased out("m68k_areg(regs, dstreg) -= %d;\n", 1 << size); } if (size == sz_word) { // Word MOVE is relatively simple int set_ccr = 0; switch(smode) { case Dreg: case Areg: set_ccr = 1; break; case Aind: case Aipi: case Apdi: case Ad16: case PC16: case Ad8r: case PC8r: case absw: case absl: case imm: if (dmode == Aind || dmode == Aipi || dmode == Apdi || dmode == Ad16 || dmode == Ad8r || dmode == absw || dmode == absl) set_ccr = 1; break; } if (set_ccr) { out("ccr_68000_word_move_ae_normal((uae_s16)(src));\n"); } } else { // Long MOVE is much more complex.. int set_ccr = 0; int set_high_word = 0; int set_low_word = 0; switch (smode) { case Dreg: case Areg: case imm: if (dmode == Aind || dmode == Aipi) { set_ccr = 0; } else if (dmode == Ad16 || dmode == Ad8r) { set_high_word = 1; } else if (dmode == Apdi || dmode == absw || dmode == absl) { set_ccr = 1; } break; case Ad16: case PC16: case Ad8r: case PC8r: case absw: case absl: if (dmode == Apdi || dmode == Ad16 || dmode == Ad8r || dmode == absw) { set_ccr = 1; } else if (dmode == Aind || dmode == Aipi || dmode == absl) { set_low_word = 1; } else { set_low_word = 2; } break; case Aind: case Aipi: case Apdi: if (dmode == Aind || dmode == Aipi || dmode == absl) { set_low_word = 1; } else { set_ccr = 1; } break; } if (dmode == Apdi) { *setapdi = 0; out("m68k_areg(regs, dstreg) += 4;\n"); out("regs.ir = opcode;\n"); } if (set_low_word == 1) { // Low word: Z and N out("ccr_68000_long_move_ae_LZN(src);\n"); } else if (set_low_word == 2) { // Low word: N only out("ccr_68000_long_move_ae_LN(src);\n"); } else if (set_high_word) { // High word: N and Z clear. out("ccr_68000_long_move_ae_HNZ(src);\n"); } else if (set_ccr) { // Set normally. out("ccr_68000_long_move_ae_normal(src);\n"); } } } // Handle special MOVE.W/.L condition codes when destination write causes address error. static void move_68010_address_error(int size, int *setapdi, int *fcmodeflags) { int smode = g_instr->smode; int dmode = g_instr->dmode; if (size == sz_word) { // Word MOVE is relatively simple int set_ccr = 0; int reset_ccr = 0; *setapdi = 1; switch (smode) { case Dreg: case Areg: case imm: if (dmode == Apdi || dmode == Ad16 || dmode == Ad8r || dmode == absw || dmode == absl) set_ccr = 1; else reset_ccr = 1; break; case Aind: case Aipi: case Apdi: case Ad16: case PC16: case Ad8r: case PC8r: case absw: case absl: if (dmode == Aind || dmode == Aipi || dmode == Apdi || dmode == Ad16 || dmode == Ad8r || dmode == absw || dmode == absl) set_ccr = 1; break; } if (dmode == Apdi) { dummy_prefetch(NULL, NULL); } else if (dmode == absl && smode >= Aind && smode < imm) { out("regs.irc = dsta >> 16;\n"); } if (reset_ccr) { out("regflags.cznv = oldflags.cznv;\n"); } if (set_ccr) { out("ccr_68000_word_move_ae_normal((uae_s16)(src));\n"); } } else { // Long MOVE is much more complex.. int set_ccr = 0; int set_high_word = 0; int set_low_word = 0; if (dmode == Apdi) { *setapdi = -4; } else { *setapdi = 1; } switch (smode) { case Dreg: case Areg: case imm: if (dmode == Aind || dmode == Aipi) { set_ccr = 0; } else if (dmode == Ad16 || dmode == Ad8r) { set_high_word = 1; } else if (dmode == Apdi || dmode == absw || dmode == absl) { set_ccr = 1; } break; case Ad16: case PC16: case Ad8r: case PC8r: case absw: case absl: if (dmode == Apdi || dmode == Ad16 || dmode == Ad8r || dmode == absw) { set_ccr = 1; } else if (dmode == Aind || dmode == Aipi || dmode == absl) { set_low_word = 1; } else { set_low_word = 2; } break; case Aind: case Aipi: case Apdi: if (dmode == Aind || dmode == Aipi || dmode == absl) { set_low_word = 1; } else { set_ccr = 1; } break; } if (dmode == absl && smode >= Aind && smode < imm) { out("regs.irc = dsta >> 16;\n"); } if (set_low_word == 1) { // Low word: Z and N out("ccr_68000_long_move_ae_LZN(src);\n"); } else if (set_low_word == 2) { // Low word: N only out("ccr_68000_long_move_ae_LN(src);\n"); } else if (set_high_word) { // High word: N and Z clear. out("ccr_68000_long_move_ae_HNZ(src);\n"); } else if (set_ccr) { // Set normally. out("ccr_68000_long_move_ae_normal(src);\n"); } } } static void check_address_error(const char *name, int mode, const char *reg, int size, int getv, int movem, int flags) { // check possible address error (if 68000/010 and enabled) if ((using_prefetch || using_ce) && using_exception_3 && getv != 0 && getv != 3 && size != sz_byte && !movem && !(flags & GF_NOEXC3)) { int setapdiback = 0; int fcmodeflags = 0; int exp3rw = getv == 2; int pcextra = 0; next_level_000(); out("if (%sa & 1) {\n", name); if (cpu_level == 1) { int bus_error_reg_add_old = bus_error_reg_add; if (abs(bus_error_reg_add) == 4) bus_error_reg_add = 0; // 68010 CLR : pre and post are not added yet if (g_instr->mnemo == i_CLR) { if (mode == Aipi) bus_error_reg_add = 0; if (mode == Apdi) bus_error_reg_add = 0; } if (g_instr->mnemo == i_MOVE || g_instr->mnemo == i_MOVEA) { if (getv != 2) { do_bus_error_fixes(name, 0, getv == 2); } } else { do_bus_error_fixes(name, 0, getv == 2); } if (g_instr->mnemo == i_MOVES) { // MOVES has strange behavior out("regs.irc = extra;\n"); if (!exp3rw) { out("regs.write_buffer = extra;\n"); if (mode == Ad8r || mode == PC8r) { pcextra = 4; } else { pcextra = 2; } } else { // moves.w an,-(an)/(an)+ (same registers): write buffer contains modified value. if (mode == Aipi || mode == Apdi) { out("if (dstreg + 8 == ((extra >> 12) & 15)) {\n"); out("src += %d;\n", mode == Aipi ? 2 : -2); out("}\n"); } if (mode >= Ad16) { pcextra = 2; } } if (size == sz_long) { if (mode == Aipi) { out("m68k_areg(regs, dstreg) += 4;\n"); } else if (mode == Apdi) { setapdiback = 1; } } } // x,-(an): an is modified (MOVE to CCR counts as word sized) if (mode == Apdi && g_instr->mnemo != i_CLR && size == sz_word) { out("m68k_areg(regs, %s) = %sa;\n", reg, name); } bus_error_reg_add = bus_error_reg_add_old; } if (g_instr->mnemo == i_ADDX || g_instr->mnemo == i_SUBX) { // ADDX/SUBX special case if (g_instr->size == sz_word) { out("m68k_areg(regs, %s) = %sa;\n", reg, name); } } else if (mode == Apdi && g_instr->mnemo != i_LINK) { // 68000 decrements register first, then checks for address error // 68010 does not if (cpu_level == 0) { setapdiback = 1; } } // adjust MOVE write address error stacked PC if (g_instr->mnemo == i_MOVE && getv == 2) { if (g_instr->smode >= Aind && g_instr->smode != imm && g_instr->dmode == absl) { pcextra = -2; } else if (g_instr->dmode >= Ad16) { pcextra = 0; } else { pcextra = 2; } } int pc_offset_extra = cpu_level == 0 ? exception_pc_offset_extra_000 : 0; if (exception_pc_offset + pc_offset_extra + pcextra) { incpc("%d", exception_pc_offset + pc_offset_extra + pcextra); } if (g_instr->mnemo == i_MOVE) { if (getv == 2) { if (cpu_level == 0) { move_68000_address_error(size, &setapdiback, &fcmodeflags); } else { move_68010_address_error(size, &setapdiback, &fcmodeflags); } if (mode != Apdi && mode != Aipi) { setapdiback = 0; } } } else if (g_instr->mnemo == i_MVSR2) { // If MOVE from SR generates address error exception, // Change it to read because it does dummy read first. exp3rw = 0; if (cpu_level == 1) { out("opcode |= 0x20000;\n"); // upper byte of SSW is zero -flag. } } else if (g_instr->mnemo == i_LINK) { // a7 -> a0 copy done before A7 address error check out("m68k_areg(regs, srcreg) = olda;\n"); setapdiback = 0; } // can be used for both Apdi and Aipi if (setapdiback) { if (setapdiback > 0) { out("m68k_areg(regs, %s) = %sa;\n", reg, name); } else { out("m68k_areg(regs, %s) = %sa + %d;\n", reg, name, -setapdiback); } } // MOVE.L EA,-(An) causing address error: stacked value is original An - 2, not An - 4. if ((flags & (GF_REVERSE | GF_REVERSE2)) && size == sz_long && mode == Apdi) out("%sa += %d;\n", name, flags & GF_REVERSE2 ? -2 : 2); if (exp3rw) { const char *shift = (size == sz_long && !(flags & GF_REVERSE)) ? " >> 16" : ""; out("exception3_write_access(opcode, %sa, %d, %s%s, %d);\n", name, size, g_srcname, shift, // PC-relative: FC=2 (getv == 1 && (g_instr->smode == PC16 || g_instr->smode == PC8r) ? 2 : 1) | fcmodeflags); } else { // 68010 address error: if addressing mode is (An), (An)+ or -(An) and byte or word: CPU does extra read access! if (cpu_level == 1 && (g_instr->smode == Aind || g_instr->smode == Aipi || g_instr->smode == Apdi) && g_instr->size < sz_long) { out("exception3_read_access2(opcode, %sa, %d, %d);\n", name, size, // PC-relative: FC=2 (getv == 1 && (g_instr->smode == PC16 || g_instr->smode == PC8r) ? 2 : 1) | fcmodeflags); } else { out("exception3_read_access(opcode, %sa, %d, %d);\n", name, size, // PC-relative: FC=2 (getv == 1 && (g_instr->smode == PC16 || g_instr->smode == PC8r) ? 2 : 1) | fcmodeflags); } } write_return_cycles_noadd(0); out("}\n"); } } /* getv == 1: fetch data; getv != 0: check for odd address. If movem != 0, * the calling routine handles Apdi and Aipi modes. * gb-- movem == 2 means the same thing but for a MOVE16 instruction */ /* fixup indicates if we want to fix up address registers in pre decrement * or post increment mode now (0) or later (1). A value of 2 will then be * used to do the actual fix up. This allows to do all memory readings * before any register is modified, and so to rerun operation without * side effect in case a bus fault is generated by any memory access. * XJ - 2006/11/13 */ static void genamode2x (amodes mode, const char *reg, wordsizes size, const char *name, int getv, int movem, int flags, int fetchmode) { char namea[100]; bool rmw = false; int pc_68000_offset = m68k_pc_offset; int pc_68000_offset_fetch = 0; int pc_68000_offset_store = 0; bool addr = false; // common EA prefetch bus error PC behavior int pcflag = !(flags & (GF_PCM2 | GF_PCP2)) ? GF_PCM2 : 0; sprintf(namea, "%sa", name); if ((flags & GF_RMW) && using_mmu == 68060) { strcpy (rmw_varname, name); candormw = true; rmw = true; } if (mode == Ad8r || mode == PC8r) { genamode8r_offset[genamode_cnt] = m68k_pc_total + m68k_pc_offset; genamode_cnt++; } loopmode_access(); switch (mode) { case Dreg: if (movem) term(); if (getv == 1) switch (size) { case sz_byte: #ifdef USE_DUBIOUS_BIGENDIAN_OPTIMIZATION /* This causes the target compiler to generate better code on few systems */ out("uae_s8 %s = ((uae_u8*)&m68k_dreg(regs, %s))[3];\n", name, reg); #else out("uae_s8 %s = m68k_dreg(regs, %s);\n", name, reg); #endif break; case sz_word: #ifdef USE_DUBIOUS_BIGENDIAN_OPTIMIZATION out("uae_s16 %s = ((uae_s16*)&m68k_dreg(regs, %s))[1];\n", name, reg); #else out("uae_s16 %s = m68k_dreg(regs, %s);\n", name, reg); #endif break; case sz_long: out("uae_s32 %s = m68k_dreg(regs, %s);\n", name, reg); break; default: term(); } maybeaddop_ce020 (flags); syncmovepc (getv, flags); strcpy(g_srcname, name); return; case Areg: if (movem) term(); if (getv == 1) switch (size) { case sz_word: out("uae_s16 %s = m68k_areg(regs, %s);\n", name, reg); break; case sz_long: out("uae_s32 %s = m68k_areg(regs, %s);\n", name, reg); break; default: term(); } maybeaddop_ce020 (flags); syncmovepc (getv, flags); strcpy(g_srcname, name); return; case Aind: // (An) switch (fetchmode) { case fetchmode_fea: addcycles_ce020 (1); break; case fetchmode_cea: addcycles_ce020 (2); break; case fetchmode_jea: addcycles_ce020 (2); break; } out("uaecptr %sa;\n", name); start_mmu040_movem(movem); out("%sa = m68k_areg(regs, %s);\n", name, reg); if ((flags & GF_NOFETCH) && using_prefetch) { addcycles000(2); } break; case Aipi: // (An)+ switch (fetchmode) { case fetchmode_fea: addcycles_ce020 (1); break; case fetchmode_cea: break; } out("uaecptr %sa;\n", name); start_mmu040_movem(movem); out("%sa = m68k_areg(regs, %s);\n", name, reg); if ((flags & GF_NOFETCH) && using_prefetch) { addcycles000(4); } break; case Apdi: // -(An) switch (fetchmode) { case fetchmode_fea: case fetchmode_cea: addcycles_ce020 (2); break; } out("uaecptr %sa;\n", name); start_mmu040_movem(movem); switch (size) { case sz_byte: if (movem) out("%sa = m68k_areg(regs, %s);\n", name, reg); else out("%sa = m68k_areg(regs, %s) - areg_byteinc[%s];\n", name, reg, reg); break; case sz_word: out("%sa = m68k_areg(regs, %s) - %d;\n", name, reg, movem ? 0 : 2); break; case sz_long: out("%sa = m68k_areg(regs, %s) - %d;\n", name, reg, movem ? 0 : 4); break; default: term(); } if (flags & GF_NOFETCH) { addcycles000(4); count_cycles_ea += 2; } else if (!(flags & GF_APDI)) { addcycles000(2); count_cycles_ea += 2; if (cpu_level == 1) { ; } else { pc_68000_offset_fetch += 2; if (size == sz_long) pc_68000_offset_fetch -= 2; } } break; case Ad16: // (d16,An) out("uaecptr %sa;\n", name); start_mmu040_movem(movem); out("%sa = m68k_areg(regs, %s) + (uae_s32)(uae_s16)%s;\n", name, reg, gen_nextiword(flags | pcflag)); count_read_ea++; addr = true; break; case PC16: // (d16,PC) out("uaecptr %sa;\n", name); start_mmu040_movem(movem); out("%sa = %s + %d;\n", name, getpc, m68k_pc_offset); out("%sa += (uae_s32)(uae_s16)%s;\n", name, gen_nextiword(flags | pcflag)); addr = true; break; case Ad8r: // (d8,An,Xn) switch (fetchmode) { case fetchmode_fea: addcycles_ce020 (4); break; case fetchmode_cea: case fetchmode_jea: break; } out("uaecptr %sa;\n", name); if (cpu_level > 1) { if (next_cpu_level < 1) next_cpu_level = 1; sync_m68k_pc(); start_mmu040_movem(movem); /* This would ordinarily be done in gen_nextiword, which we bypass. */ insn_n_cycles += 4; out("%sa = %s(m68k_areg(regs, %s), %d);\n", name, disp020, reg, disp020cnt++); } else { if (!(flags & GF_AD8R) && !(flags & GF_NOFETCH) && !(flags & GF_CLR68010)) { addcycles000(2); count_cycles_ea += 2; #ifdef WINUAE_FOR_HATARI /* Hatari : on 68000 ST, Ad8r causes an unaligned memory prefetch and take 2 cycles more */ /* JSR, JMP, LEA and PEA are handled separately */ /* We add 2 cycles only in 68000 prefetch mode, 68000 CE mode is handled at the memory access level */ if ( using_prefetch && !using_ce ) out("BusCyclePenalty += 2;\n"); #endif } if ((flags & GF_NOREFILL) && using_prefetch) { out("%sa = %s(m68k_areg(regs, %s), regs.irc);\n", name, disp000, reg); } else { out("%sa = %s(m68k_areg(regs, %s), %s);\n", name, disp000, reg, gen_nextiword(flags | pcflag)); } count_read_ea++; } if ((flags & GF_NOFETCH) && using_prefetch) { addcycles000(4); count_cycles_ea += 4; } if ((flags & GF_CLR68010) && using_prefetch) { addcycles000(4); count_cycles_ea += 4; } addr = true; break; case PC8r: // (d8,PC,Xn) switch (fetchmode) { case fetchmode_fea: addcycles_ce020 (4); break; case fetchmode_cea: case fetchmode_jea: break; } out("uaecptr %sa;\n", name); if (cpu_level > 1) { if (next_cpu_level < 1) next_cpu_level = 1; sync_m68k_pc(); start_mmu040_movem(movem); /* This would ordinarily be done in gen_nextiword, which we bypass. */ insn_n_cycles += 4; out("uaecptr tmppc = %s;\n", getpc); out("%sa = %s(tmppc, %d);\n", name, disp020, disp020cnt++); } else { out("uaecptr tmppc = %s + %d;\n", getpc, m68k_pc_offset); if (!(flags & GF_PC8R)) { addcycles000(2); count_cycles_ea += 2; #ifdef WINUAE_FOR_HATARI /* Hatari : on 68000 ST, Ad8r causes an unaligned memory prefetch and take 2 cycles more */ /* JSR, JMP, LEA and PEA are handled separately */ /* We add 2 cycles only in 68000 prefetch mode, 68000 CE mode is handled at the memory access level */ if ( using_prefetch && !using_ce ) out("BusCyclePenalty += 2;\n"); #endif } if ((flags & GF_NOREFILL) && using_prefetch) { out("%sa = %s(tmppc, regs.irc);\n", name, disp000); } else { out("%sa = %s(tmppc, %s);\n", name, disp000, gen_nextiword(flags | pcflag)); } } if ((flags & GF_NOFETCH) && using_prefetch) { addcycles000(4); } addr = true; break; case absw: out("uaecptr %sa;\n", name); start_mmu040_movem(movem); out("%sa = (uae_s32)(uae_s16)%s;\n", name, gen_nextiword(flags)); pc_68000_offset_fetch += 2; addr = true; break; case absl: gen_nextilong2("uaecptr", namea, flags | pcflag, movem); count_read_ea += 2; pc_68000_offset_fetch += 4; pc_68000_offset_store += 2; addr = true; break; case imm: // fetch immediate address if (getv != 1) term(); switch (size) { case sz_byte: out("uae_s8 %s = %s;\n", name, gen_nextibyte(flags)); count_read_ea++; break; case sz_word: out("uae_s16 %s = %s;\n", name, gen_nextiword(flags)); count_read_ea++; break; case sz_long: gen_nextilong("uae_s32", name, flags | pcflag); count_read_ea += 2; break; default: term(); } do_instruction_buserror(); maybeaddop_ce020 (flags); syncmovepc (getv, flags); strcpy(g_srcname, name); return; case imm0: if (getv != 1) term(); out("uae_s8 %s = %s;\n", name, gen_nextibyte(flags)); count_read_ea++; do_instruction_buserror(); maybeaddop_ce020 (flags); syncmovepc (getv, flags); strcpy(g_srcname, name); return; case imm1: if (getv != 1) term(); out("uae_s16 %s = %s;\n", name, gen_nextiword(flags)); count_read_ea++; do_instruction_buserror(); maybeaddop_ce020 (flags); syncmovepc (getv, flags); strcpy(g_srcname, name); return; case imm2: if (getv != 1) term(); gen_nextilong("uae_s32", name, flags); count_read_ea += 2; maybeaddop_ce020(flags); syncmovepc(getv, flags); strcpy(g_srcname, name); return; case immi: if (getv != 1) term(); out("uae_u32 %s = %s;\n", name, reg); maybeaddop_ce020 (flags); syncmovepc (getv, flags); strcpy(g_srcname, name); return; case am_unknown: // reg = internal variable out("uae_u32 %sa = %s;\n", name, reg); addr = true; break; default: term(); } if (getv == 2 && g_srcname[0] && g_srcname[strlen(g_srcname) - 1] == 'a' && g_instr->mnemo != i_PEA) { g_srcname[strlen(g_srcname) - 1] = 0; } if (g_srcname[0] == 0) { if (addr || g_instr->mnemo == i_PEA) strcpy(g_srcname, namea); else strcpy(g_srcname, name); } if (mode >= Ad16 && mode < am_unknown) { do_instruction_buserror(); } syncmovepc (getv, flags); maybeaddop_ce020 (flags); /* We get here for all non-reg non-immediate addressing modes to * actually fetch the value. */ bus_error_reg_add = 0; bus_error_reg = reg; if (!movem) { if (mode == Aipi) { switch (size) { case sz_byte: bus_error_reg_add = 1; break; case sz_word: bus_error_reg_add = 2; break; case sz_long: bus_error_reg_add = 3; break; default: term(); } } else if (mode == Apdi) { bus_error_reg_add = 4; } if (flags & GF_SECONDEA) { bus_error_reg_add = -bus_error_reg_add; } } exception_pc_offset = pc_68000_offset; if (getv == 2) { // store if (pc_68000_offset) { exception_pc_offset += pc_68000_offset_store + 2; } } else { // fetch exception_pc_offset += pc_68000_offset_fetch; } if (!(flags & GF_NOEXC3)) { check_address_error(name, mode, reg, size, getv, movem, flags); } if (flags & GF_PREFETCH) fill_prefetch_next(); else if (flags & GF_IR2IRC) irc2ir_2 (true); if (!movem && (mode == Aipi || mode == Apdi)) { addmmufixup(reg, size, mode); } set_last_access_ipl_prev(0); if (getv == 1) { const char *srcbx = !(flags & GF_FC) ? srcb : "sfc_nommu_get_byte"; const char *srcwx = !(flags & GF_FC) ? srcw : "sfc_nommu_get_word"; const char *srclx = !(flags & GF_FC) ? srcl : "sfc_nommu_get_long"; if (using_mmu) { if (flags & GF_FC) { switch (size) { case sz_byte: count_readw++; out("uae_s8 %s = sfc%s_get_byte%s(%sa);\n", name, mmu_postfix, xfc_postfix, name); break; case sz_word: count_readw++; out("uae_s16 %s = sfc%s_get_word%s(%sa);\n", name, mmu_postfix, xfc_postfix, name); break; case sz_long: count_readl++; out("uae_s32 %s = sfc%s_get_long%s(%sa);\n", name, mmu_postfix, xfc_postfix, name); break; default: term(); } } else { switch (size) { case sz_byte: count_readw++; out("uae_s8 %s = %s(%sa);\n", name, (flags & GF_LRMW) ? srcblrmw : (rmw ? srcbrmw : srcb), name); break; case sz_word: count_readw++; out("uae_s16 %s = %s(%sa);\n", name, (flags & GF_LRMW) ? srcwlrmw : (rmw ? srcwrmw : srcw), name); break; case sz_long: count_readl++; out("uae_s32 %s = %s(%sa);\n", name, (flags & GF_LRMW) ? srcllrmw : (rmw ? srclrmw : srcl), name); break; default: term(); } } } else if (using_ce020 || using_prefetch_020) { switch (size) { case sz_byte: out("uae_s8 %s = %s(%sa);\n", name, srcbx, name); count_readw++; check_bus_error(name, 0, 0, 0, NULL, 1, 0); break; case sz_word: out("uae_s16 %s = %s(%sa);\n", name, srcwx, name); count_readw++; check_bus_error(name, 0, 0, 1, NULL, 1, 0); break; case sz_long: out("uae_s32 %s = %s(%sa);\n", name, srclx, name); count_readl++; check_bus_error(name, 0, 0, 2, NULL, 1, 0); break; default: term(); } } else if (using_ce || using_prefetch) { switch (size) { case sz_byte: { if (flags & GF_IPL) { set_ipl(); } out("uae_s8 %s = %s(%sa);\n", name, srcbx, name); count_readw++; check_bus_error(name, 0, 0, 0, NULL, 1, 0); break; } case sz_word: { if (flags & GF_IPL) { set_ipl(); } out("uae_s16 %s = %s(%sa);\n", name, srcwx, name); count_readw++; check_bus_error(name, 0, 0, 1, NULL, 1, 0); break; } case sz_long: { if ((flags & GF_REVERSE) && mode == Apdi) { if ((flags & GF_IPL) && !(flags & GF_IPLMID)) { set_ipl(); } out("uae_s32 %s = %s(%sa + 2);\n", name, srcwx, name); count_readw++; check_bus_error(name, 0, 0, 1, NULL, 1, 0); if (!(flags & GF_NOLIPL) && !(flags & GF_IPL)) { set_last_access_ipl_prev(flags); } if ((flags & GF_IPL) && (flags & GF_IPLMID)) { set_ipl(); } out("%s |= %s(%sa) << 16;\n", name, srcwx, name); count_readw++; check_bus_error(name, -2, 0, 1, NULL, 1, 0); } else { if ((flags & GF_IPL) && !(flags & GF_IPLMID)) { set_ipl(); } out("uae_s32 %s = %s(%sa) << 16;\n", name, srcwx, name); count_readw++; check_bus_error(name, 0, 0, 1, NULL, 1, 0); if (!(flags & GF_NOLIPL) && !(flags & GF_IPL)) { set_last_access_ipl_prev(flags); } if ((flags & GF_IPL) && (flags & GF_IPLMID)) { set_ipl(); } out("%s |= %s(%sa + 2);\n", name, srcwx, name); count_readw++; check_bus_error(name, 2, 0, 1, NULL, 1, 0); } break; } default: term(); } } else { switch (size) { case sz_byte: out("uae_s8 %s = %s(%sa);\n", name, srcbx, name); count_readw++; check_bus_error(name, 0, 0, 0, NULL, 1, 0); break; case sz_word: out("uae_s16 %s = %s(%sa);\n", name, srcwx, name); count_readw++; check_bus_error(name, 0, 0, 1, NULL, 1, 0); break; case sz_long: out("uae_s32 %s = %s(%sa);\n", name, srclx, name); count_readl++; check_bus_error(name, 0, 0, 2, NULL, 1, 0); break; default: term(); } } } bus_error_reg_add = 0; /* We now might have to fix up the register for pre-dec or post-inc * addressing modes. */ if (!movem) { switch (mode) { case Aipi: switch (size) { case sz_byte: out("m68k_areg(regs, %s) += areg_byteinc[%s];\n", reg, reg); break; case sz_word: out("m68k_areg(regs, %s) += 2;\n", reg); break; case sz_long: out("m68k_areg(regs, %s) += 4;\n", reg); break; default: term(); } break; case Apdi: out("m68k_areg(regs, %s) = %sa;\n", reg, name); break; default: break; } } end_mmu040_movem(); } static void genamode2 (amodes mode, const char *reg, wordsizes size, const char *name, int getv, int movem, int flags) { genamode2x (mode, reg, size, name, getv, movem, flags, -1); } static void genamode(struct instr *curi, amodes mode, const char *reg, wordsizes size, const char *name, int getv, int movem, int flags) { int oldfixup = mmufixupstate; int subhead = 0; if (isce020() && curi) { switch (curi->fetchmode) { case fetchmode_fea: subhead = gence020cycles_fea (mode); break; case fetchmode_cea: subhead = gence020cycles_cea (curi, mode); break; case fetchmode_jea: subhead = gence020cycles_jea (curi, mode); break; } genamode2x (mode, reg, size, name, getv, movem, flags, curi->fetchmode); } else { genamode2 (mode, reg, size, name, getv, movem, flags); } if (using_mmu == 68040 && (oldfixup & 1)) { // we have fixup already active = this genamode call is destination mode and we can now clear previous source fixup. clearmmufixup(0, 0); } if (curi) addop_ce020 (curi, subhead, flags); } static void genamode3 (struct instr *curi, amodes mode, const char *reg, wordsizes size, const char *name, int getv, int movem, int flags) { int oldfixup = mmufixupstate; genamode2x (mode, reg, size, name, getv, movem, flags, curi ? curi->fetchmode : -1); if (using_mmu == 68040 && (oldfixup & 1)) { // we have fixup already active = this genamode call is destination mode and we can now clear previous source fixup. clearmmufixup(0, 0); } } static void genamodedual(struct instr *curi, amodes smode, const char *sreg, wordsizes ssize, const char *sname, int sgetv, int sflags, amodes dmode, const char *dreg, wordsizes dsize, const char *dname, int dgetv, int dflags) { int subhead = 0; bool eadmode = false; if (isce020()) { switch (curi->fetchmode) { case fetchmode_fea: if (smode >= imm || isreg (smode)) { subhead = gence020cycles_fea (dmode); eadmode = true; } else { subhead = gence020cycles_fea (smode); } break; case fetchmode_cea: subhead = gence020cycles_cea (curi, smode); break; case fetchmode_fiea: subhead = gence020cycles_fiea (curi, ssize, dmode); break; case fetchmode_ciea: subhead = gence020cycles_ciea (curi, ssize, dmode); break; case fetchmode_jea: subhead = gence020cycles_jea (curi, smode); break; default: out("/* No EA */\n"); break; } } subhead_ce020 = subhead; curi_ce020 = curi; genamode3 (curi, smode, sreg, ssize, sname, sgetv, 0, sflags); genamode3 (NULL, dmode, dreg, dsize, dname, dgetv, 0, dflags | (eadmode == true ? GF_OPCE020 : 0) | GF_SECONDEA); if (eadmode == false) maybeaddop_ce020 (GF_OPCE020); } static void genastore_2 (const char *from, amodes mode, const char *reg, wordsizes size, const char *to, int store_dir, int flags) { char tmp[100]; int pcoffset = (flags & GF_MOVE) ? 0 : 2; if (flags & GF_PCM2) { pcoffset -= 2; } else if (flags & GF_PCP2) { pcoffset += 2; } exception_pc_offset = m68k_pc_offset; if (candormw) { if (strcmp (rmw_varname, to) != 0) candormw = false; } loopmode_access(); genastore_done = true; if (!(flags & GF_LRMW)) { returntail(mode != Dreg && mode != Areg); } if ((flags & GF_EXC3) && !isreg(mode)) { check_address_error(to, mode, reg, size, 2, 0, flags); } switch (mode) { case Dreg: switch (size) { case sz_byte: out("m68k_dreg(regs, %s) = (m68k_dreg(regs, %s) & ~0xff) | ((%s) & 0xff);\n", reg, reg, from); break; case sz_word: out("m68k_dreg(regs, %s) = (m68k_dreg(regs, %s) & ~0xffff) | ((%s) & 0xffff);\n", reg, reg, from); break; case sz_long: out("m68k_dreg(regs, %s) = (%s);\n", reg, from); break; default: term(); } break; case Areg: switch (size) { case sz_word: out("m68k_areg(regs, %s) = (uae_s32)(uae_s16)(%s);\n", reg, from); break; case sz_long: out("m68k_areg(regs, %s) = (%s);\n", reg, from); break; default: term(); } break; case Aind: case Aipi: case Apdi: case Ad16: case Ad8r: case absw: case absl: case PC16: case PC8r: { const char *dstbx = !(flags & GF_FC) ? dstb : "dfc_nommu_put_byte"; const char *dstwx = !(flags & GF_FC) ? dstw : "dfc_nommu_put_word"; const char *dstlx = !(flags & GF_FC) ? dstl : "dfc_nommu_put_long"; set_last_access_ipl_prev(flags); if (!(flags & GF_NOFAULTPC)) gen_set_fault_pc (false, false); if (using_mmu) { switch (size) { case sz_byte: count_writew++; if (flags & GF_FC) out("dfc%s_put_byte%s(%sa, %s);\n", mmu_postfix, xfc_postfix, to, from); else out("%s(%sa, %s);\n", (flags & GF_LRMW) ? dstblrmw : (candormw ? dstbrmw : dstb), to, from); break; case sz_word: count_writew++; if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); if (flags & GF_FC) out("dfc%s_put_word%s(%sa, %s);\n", mmu_postfix, xfc_postfix, to, from); else out("%s(%sa, %s);\n", (flags & GF_LRMW) ? dstwlrmw : (candormw ? dstwrmw : dstw), to, from); break; case sz_long: count_writel++; if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); if (flags & GF_FC) out("dfc%s_put_long%s(%sa, %s);\n", mmu_postfix, xfc_postfix, to, from); else out("%s(%sa, %s);\n", (flags & GF_LRMW) ? dstllrmw : (candormw ? dstlrmw : dstl), to, from); break; default: term(); } } else if (using_ce020 || using_prefetch_020) { switch (size) { case sz_byte: out("%s(%sa, %s);\n", dstbx, to, from); count_writew++; check_bus_error(to, 0, 1, 0, from, 1, pcoffset); break; case sz_word: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); out("%s(%sa, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 0, 1, 1, from, 1, pcoffset); break; case sz_long: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); out("%s(%sa, %s);\n", dstlx, to, from); count_writel++; check_bus_error(to, 0, 1, 2, from, 1, pcoffset); break; default: term(); } } else if (using_ce) { switch (size) { case sz_byte: out("%s(%sa, %s);\n", dstbx, to, from); count_writew++; check_bus_error(to, 0, 1, 0, from, 1, pcoffset); break; case sz_word: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); out("%s(%sa, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 0, 1, 1, from, 1, pcoffset); break; case sz_long: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); if (store_dir) { out("%s(%sa + 2, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 2, 1, 1, from, 1, pcoffset); if (flags & GF_SECONDWORDSETFLAGS) { genflags(flag_logical, g_instr->size, "src", "", ""); } // ADDX.L/SUBX.L -(an),-(an) only if (store_dir > 1) { fill_prefetch_next_after(0, NULL); insn_n_cycles += 4; } if (flags & GF_IPL) { set_ipl(); } out("%s(%sa, %s >> 16);\n", dstwx, to, from); sprintf(tmp, "%s >> 16", from); count_writew++; check_bus_error(to, 0, 1, 1, tmp, 1, pcoffset); } else { out("%s(%sa, %s >> 16);\n", dstwx, to, from); sprintf(tmp, "%s >> 16", from); count_writew++; check_bus_error(to, 0, 1, 1, tmp, 1, pcoffset); if (flags & GF_SECONDWORDSETFLAGS) { genflags(flag_logical, g_instr->size, "src", "", ""); } if (flags & GF_IPL) { set_ipl(); } out("%s(%sa + 2, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 2, 1, 1, from, 1, pcoffset); } break; default: term(); } } else if (using_prefetch) { switch (size) { case sz_byte: out("%s(%sa, %s);\n", dstbx, to, from); count_writew++; check_bus_error(to, 0, 1, 0, from, 1, pcoffset); break; case sz_word: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); out("%s(%sa, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 0, 1, 1, from, 1, pcoffset); break; case sz_long: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); if (store_dir) { out("%s(%sa + 2, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 2, 1, 1, from, 1, pcoffset); if (flags & GF_SECONDWORDSETFLAGS) { genflags(flag_logical, g_instr->size, "src", "", ""); } // ADDX.L/SUBX.L -(an),-(an) only if (store_dir > 1) { fill_prefetch_next_after(0, NULL); } set_last_access_ipl_prev(flags); out("%s(%sa, %s >> 16); \n", dstwx, to, from); sprintf(tmp, "%s >> 16", from); count_writew++; check_bus_error(to, 0, 1, 1, tmp, 1, pcoffset); } else { out("%s(%sa, %s >> 16);\n", dstwx, to, from); sprintf(tmp, "%s >> 16", from); count_writew++; check_bus_error(to, 0, 1, 1, tmp, 1, pcoffset); if (flags & GF_SECONDWORDSETFLAGS) { genflags(flag_logical, g_instr->size, "src", "", ""); } set_last_access_ipl_prev(flags); out("%s(%sa + 2, %s); \n", dstwx, to, from); count_writew++; check_bus_error(to, 2, 1, 1, from, 1, pcoffset); } break; default: term(); } } else { switch (size) { case sz_byte: out("%s(%sa, %s);\n", dstbx, to, from); count_writew++; check_bus_error(to, 0, 1, 0, from, 1, pcoffset); break; case sz_word: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); out("%s(%sa, %s);\n", dstwx, to, from); count_writew++; check_bus_error(to, 0, 1, 1, from, 1, pcoffset); break; case sz_long: if (cpu_level < 2 && (mode == PC16 || mode == PC8r)) term(); // ADDX.L/SUBX.L -(an),-(an) only if (store_dir > 1) { insn_n_cycles += 4; } out("%s(%sa, %s);\n", dstlx, to, from); count_writel++; check_bus_error(to, 0, 1, 2, from, 1, pcoffset); break; default: term(); } } break; } case imm: case imm0: case imm1: case imm2: case immi: term(); break; default: term(); } if (flags & GF_LRMW) { returntail(mode != Dreg && mode != Areg); } } static void genastore(const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { genastore_2(from, mode, reg, size, to, 0, 0); } static void genastore_tas (const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { genastore_2(from, mode, reg, size, to, 0, GF_LRMW); } static void genastore_cas (const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { genastore_2(from, mode, reg, size, to, 0, GF_LRMW | GF_NOFAULTPC); } // write to addr + 2, write to addr + 0 static void genastore_rev(const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { genastore_2(from, mode, reg, size, to, 1, 0); } // write to addr + 2, prefetch, write to addr + 0 static void genastore_rev_prefetch(const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { genastore_2(from, mode, reg, size, to, 2, 0); } static void genastore_fc (const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { genastore_2(from, mode, reg, size, to, 0, GF_FC); } static void movem_mmu060 (const char *code, int size, bool put, bool aipi, bool apdi) { const char *index; int dphase; if (apdi) { dphase = 1; index = "movem_index2"; } else { dphase = 0; index = "movem_index1"; } if (!put) { out("uae_u32 tmp[16];\n"); out("int tmpreg[16];\n"); out("int idx = 0;\n"); for (int i = 0; i < 2; i++) { char reg; if (i == dphase) reg = 'd'; else reg = 'a'; out("while (%cmask) {\n", reg); if (apdi) out("srca -= %d;\n", size); out("tmpreg[idx] = %s[%cmask] + %d;\n", index, reg, i == dphase ? 0 : 8); out("tmp[idx++] = %s;\n", code); if (!apdi) out("srca += %d;\n", size); out("%cmask = movem_next[%cmask];\n", reg, reg); out("}\n"); } out("while (--idx >= 0) {\n"); out("regs.regs[tmpreg[idx]] = tmp[idx];\n"); out("}\n"); if (aipi || apdi) out("m68k_areg(regs, dstreg) = srca;\n"); } else { for (int i = 0; i < 2; i++) { char reg; if (i == dphase) reg = 'd'; else reg = 'a'; out("while (%cmask) {\n", reg); if (apdi) out("srca -= %d;\n", size); if (put) { if (apdi && !i) { out("int predec = movem_index2[amask] != dstreg ? 0 : %d;\n", size); out("%s, m68k_%creg(regs, %s[%cmask]) - predec);\n", code, reg, index, reg); } else { out("%s, m68k_%creg(regs, %s[%cmask]));\n", code, reg, index, reg); } } else { out("m68k_%creg(regs, %s[%cmask]) = %s;\n", reg, index, reg, code); } if (!apdi) out("srca += %d;\n", size); out("%cmask = movem_next[%cmask];\n", reg, reg); out("}\n"); } if (aipi || apdi) out("m68k_areg(regs, dstreg) = srca;\n"); } } static bool mmu040_special_movem (uae_u16 opcode) { if (using_mmu != 68040) return false; return true; // return (((((opcode >> 3) & 7) == 7) && ((opcode & 7) == 2 || (opcode & 7) == 3)) || ((opcode >> 3) & 7) == 6); } static void movem_mmu040 (const char *code, int size, bool put, bool aipi, bool apdi, uae_u16 opcode) { const char *index; int dphase; if (apdi) { dphase = 1; index = "movem_index2"; } else { dphase = 0; index = "movem_index1"; } out("mmu040_movem = 1;\n"); out("mmu040_movem_ea = srca;\n"); for (int i = 0; i < 2; i++) { char reg; if (i == dphase) reg = 'd'; else reg = 'a'; out("while (%cmask) {\n", reg); if (apdi) out("srca -= %d;\n", size); if (put) { if (apdi && !i) { out("int predec = movem_index2[amask] != dstreg ? 0 : %d;\n", size); out("%s, m68k_%creg(regs, %s[%cmask]) - predec);\n", code, reg, index, reg); } else { out("%s, m68k_%creg(regs, %s[%cmask]));\n", code, reg, index, reg); } } else { out("m68k_%creg(regs, %s[%cmask]) = %s;\n", reg, index, reg, code); } if (!apdi) out("srca += %d;\n", size); out("%cmask = movem_next[%cmask];\n", reg, reg); out("}\n"); } if (aipi || apdi) out("m68k_areg(regs, dstreg) = srca;\n"); out("mmu040_movem = 0;\n"); } /* 68030 MMU does not restore register state if it bus faults. * (also there wouldn't be enough space in stack frame to store all registers) */ static void movem_mmu030 (const char *code, int size, bool put, bool aipi, bool apdi) { const char *index; int dphase; int old_m68k_pc_offset = m68k_pc_offset; if (apdi) { dphase = 1; index = "movem_index2"; } else { dphase = 0; index = "movem_index1"; } if (put && MMU68030_LAST_WRITE) { out("int prefetch = 0;\n"); } out("mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM1;\n"); out("int movem_cnt = 0;\n"); if (!put) { out("uae_u32 val;\n"); // Store original EA because MOVEM from memory may modify same register out("srca = state_store_mmu030(srca);\n"); } for (int i = 0; i < 2; i++) { char reg; if (i == dphase) reg = 'd'; else reg = 'a'; out("while (%cmask) {\n", reg); out("uae_u16 nextmask = movem_next[%cmask];\n", reg); if (apdi) out("srca -= %d;\n", size); out("if (mmu030_state[0] == movem_cnt) {\n"); out("if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM2) {\n"); out("mmu030_state[1] &= ~MMU030_STATEFLAG1_MOVEM2;\n"); if (!put) out("val = %smmu030_data_buffer_out;\n", size == 2 ? "(uae_s32)(uae_s16)" : ""); out("} else {\n"); if (put) { if (apdi && !i) { out("int predec = movem_index2[amask] != dstreg ? 0 : %d;\n", size); } else { out("int predec = 0;\n"); } out("mmu030_data_buffer_out = m68k_%creg(regs, %s[%cmask]) - predec;\n", reg, index, reg); if (MMU68030_LAST_WRITE) { // last write? if (dphase == i) out("if(!amask && !nextmask) {\n"); else out("if(!dmask && !nextmask) {\n"); get_prefetch_020(); gen_set_fault_pc(true, false); out("mmu030_state[1] &= ~MMU030_STATEFLAG1_MOVEM1;\n"); m68k_pc_offset = old_m68k_pc_offset; out("prefetch = 1;\n"); if (aipi || apdi) out("m68k_areg(regs, dstreg) = srca;\n"); out("}\n"); } out("%s, mmu030_data_buffer_out);\n", code); } else { out("val = %s;\n", code); } out("}\n"); if (!put) { out("m68k_%creg(regs, %s[%cmask]) = val;\n", reg, index, reg); } out("mmu030_state[0]++;\n"); out("}\n"); if (!apdi) out("srca += %d;\n", size); out("movem_cnt++;\n"); out("%cmask = nextmask;\n", reg); out("}\n"); } if (aipi || apdi) out("m68k_areg(regs, dstreg) = srca;\n"); if (put) { if (MMU68030_LAST_WRITE) { // if both masks are zero out("if(prefetch == 0) {\n"); if (using_prefetch_020) { get_prefetch_020(); } sync_m68k_pc(); out("}\n"); m68k_pc_offset = 0; } else { if (using_prefetch_020) { get_prefetch_020(); } } } } static void movem_ex3(int write) { if ((using_prefetch || using_ce) && using_exception_3) { if (write) { // MOVEM write to memory won't generate address error // exception if mask is zero and EA is odd. out("if ((amask || dmask) && (srca & 1)) {\n"); // MOVE.L EA,-(An) causing address error: stacked value is original An - 2, not An - 4. if (g_instr->dmode == Apdi) { out("srca -= 2;\n"); } out("uaecptr srcav = srca;\n"); if (cpu_level == 1) { if (g_instr->dmode == Apdi) { out("if(amask) {\n"); out("srcav = m68k_areg(regs, movem_index2[amask]);\n"); out("} else if (dmask) {\n"); out("srcav = m68k_dreg(regs, movem_index2[dmask]);\n"); out("}\n"); } else { int shift = g_instr->size == sz_long ? 16 : 0; out("if(dmask) {\n"); out("srcav = m68k_dreg(regs, movem_index1[dmask]) >> %d;\n", shift); out("} else if (amask) {\n"); out("srcav = m68k_areg(regs, movem_index1[amask]) >> %d;\n", shift); out("}\n"); } if (g_instr->dmode == Aind || g_instr->dmode == Apdi || g_instr->dmode == Aipi) { out("regs.read_buffer = mask;\n"); } } } else { // MOVEM from memory will generate address error // exception if mask is zero and EA is odd. out("if (srca & 1) {\n"); if ((g_instr->dmode == PC16 || g_instr->dmode == PC8r) && cpu_level == 0) { out("opcode |= 0x00020000;\n"); } out("uaecptr srcav = srca;\n"); } if (write) { incpc("%d", m68k_pc_offset + 2); out("exception3_write_access(opcode, srca, %d, srcav, %d);\n", g_instr->size, (g_instr->dmode == PC16 || g_instr->dmode == PC8r) ? 2 : 1); } else { int pcoff = 2; if (cpu_level == 0 && (g_instr->dmode == Ad8r || g_instr->dmode == PC8r)) { pcoff = -2; } incpc("%d", m68k_pc_offset + pcoff); out("exception3_read_access(opcode, srca, %d, %d);\n", g_instr->size, (g_instr->dmode == PC16 || g_instr->dmode == PC8r) ? 2 : 1); } write_return_cycles(0); out("}\n"); } } static void genmovemel(uae_u16 opcode) { char getcode[100]; int size = table68k[opcode].size == sz_long ? 4 : 2; if (table68k[opcode].size == sz_long) { sprintf(getcode, "%s(srca)", srcld); } else { sprintf(getcode, "(uae_s32)(uae_s16)%s(srca)", srcwd); } if (!using_simple_cycles && !do_always_dynamic_cycles) { if (size == 4) { count_readl++; } else { count_readw++; } } out("uae_u16 mask = %s;\n", gen_nextiword (0)); out("uae_u32 dmask = mask & 0xff, amask = (mask >> 8) & 0xff;\n"); genamode(NULL, table68k[opcode].dmode, "dstreg", table68k[opcode].size, "src", 2, mmu040_special_movem (opcode) ? -3 : -1, GF_MOVE); movem_ex3(0); addcycles_ce020 (8 - 2); if (using_mmu == 68030) { movem_mmu030 (getcode, size, false, table68k[opcode].dmode == Aipi, false); } else if (using_mmu == 68060) { movem_mmu060 (getcode, size, false, table68k[opcode].dmode == Aipi, false); } else if (using_mmu == 68040) { movem_mmu040 (getcode, size, false, table68k[opcode].dmode == Aipi, false, opcode); } else { out("while (dmask) {\n"); out("m68k_dreg(regs, movem_index1[dmask]) = %s;\n", getcode); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } check_bus_error("src", 0, 0, table68k[opcode].size, NULL, 1, 0); out("srca += %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("while (amask) {\n"); out("m68k_areg(regs, movem_index1[amask]) = %s;\n", getcode); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } check_bus_error("src", 0, 0, table68k[opcode].size, NULL, 1, 0); out("srca += %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); if (table68k[opcode].dmode == Aipi) { out("m68k_areg(regs, dstreg) = srca;\n"); } set_last_access_ipl_prev(0); if (cpu_level <= 1) { out("%s(srca);\n", srcw); // and final extra word fetch that goes nowhere.. count_readw++; check_bus_error("src", 0, 0, 1, NULL, 1, 0); } if (!next_level_040_to_030()) next_level_020_to_010(); } count_ncycles++; fill_prefetch_next_t(); get_prefetch_020(); } static void genmovemel_ce(uae_u16 opcode) { int size = table68k[opcode].size == sz_long ? 4 : 2; int ipl = 0; amodes mode = table68k[opcode].dmode; if (mode == Aipi) { ipl = 1; set_ipl(); } out("uae_u16 mask = %s;\n", gen_nextiword(mode < Ad16 ? GF_PCM2 : 0)); do_instruction_buserror(); out("uae_u32 dmask = mask & 0xff, amask = (mask >> 8) & 0xff;\n"); if (mode == Ad8r || mode == PC8r) { addcycles000(2); } genamode(NULL, mode, "dstreg", table68k[opcode].size, "src", 2, -1, GF_AA | GF_MOVE | GF_PCM2); movem_ex3(0); if (table68k[opcode].size == sz_long) { out("while (dmask) {\n"); out("uae_u32 v = (%s(srca) << 16) | (m68k_dreg(regs, movem_index1[dmask]) & 0xffff);\n", srcw); addcycles000_nonce(4); check_bus_error("src", 0, 0, 1, NULL, 1, -1); if (cpu_level == 0) { // 68010 does not do partial updates out("m68k_dreg(regs, movem_index1[dmask]) = v;\n"); } out("v &= 0xffff0000;\n"); out("v |= %s(srca + 2); \n", srcw); addcycles000_nonce(4); check_bus_error("src", 2, 0, 1, NULL, 1, -1); out("m68k_dreg(regs, movem_index1[dmask]) = v;\n"); out("srca += %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("while (amask) {\n"); out("uae_u32 v = (%s(srca) << 16) | (m68k_areg(regs, movem_index1[amask]) & 0xffff);\n", srcw); addcycles000_nonce(4); check_bus_error("src", 0, 0, 1, NULL, 1, -1); if (cpu_level == 0) { out("m68k_areg(regs, movem_index1[amask]) = v;\n"); } out("v &= 0xffff0000;\n"); out("v |= %s(srca + 2);\n", srcw); addcycles000_nonce(4); check_bus_error("src", 2, 0, 1, NULL, 1, -1); out("m68k_areg(regs, movem_index1[amask]) = v;\n"); out("srca += %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); } else { out("while (dmask) {\n"); out("uae_u32 v = (uae_s32)(uae_s16)%s(srca);\n", srcw); addcycles000_nonce(4); check_bus_error("src", 0, 0, 1, NULL, 1, -1); out("m68k_dreg(regs, movem_index1[dmask]) = v;\n"); out("srca += %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("while (amask) {\n"); out("uae_u32 v = (uae_s32)(uae_s16)%s(srca);\n", srcw); addcycles000_nonce(4); check_bus_error("src", 0, 0, 1, NULL, 1, -1); out("m68k_areg(regs, movem_index1[amask]) = v;\n"); out("srca += %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); } out("%s(srca);\n", srcw); // and final extra word fetch that goes nowhere.. count_readw++; check_bus_error("src", 0, 0, 1, NULL, 1, -1); if (mode == Aipi) { out("m68k_areg(regs, dstreg) = srca;\n"); } count_ncycles++; if (!ipl) { set_ipl(); } fill_prefetch_next_t(); } static void genmovemle(uae_u16 opcode) { char putcode[100]; int size = table68k[opcode].size == sz_long ? 4 : 2; if (size == 4) { sprintf(putcode, "%s(srca", dstld); } else { sprintf(putcode, "%s(srca", dstwd); } if (!using_simple_cycles && !do_always_dynamic_cycles) { if (size == 4) count_writel++; else count_writew++; } out("uae_u16 mask = %s;\n", gen_nextiword (0)); genamode(NULL, table68k[opcode].dmode, "dstreg", table68k[opcode].size, "src", 2, mmu040_special_movem (opcode) ? 3 : 1, GF_MOVE | GF_APDI); addcycles_ce020 (4 - 2); if (using_mmu >= 68030) { if (table68k[opcode].dmode == Apdi) out("uae_u16 amask = mask & 0xff, dmask = (mask >> 8) & 0xff;\n"); else out("uae_u16 dmask = mask & 0xff, amask = (mask >> 8) & 0xff;\n"); if (using_mmu == 68030) { movem_mmu030(putcode, size, true, false, table68k[opcode].dmode == Apdi); count_ncycles++; return; } else if (using_mmu == 68060) { movem_mmu060(putcode, size, true, false, table68k[opcode].dmode == Apdi); } else if (using_mmu == 68040) { movem_mmu040(putcode, size, true, false, table68k[opcode].dmode == Apdi, opcode); } } else { if (table68k[opcode].dmode == Apdi) { out("uae_u16 amask = mask & 0xff, dmask = (mask >> 8) & 0xff;\n"); movem_ex3(1); out("int type = %d;\n", cpu_level > 1); out("while (amask) {\n"); out("srca -= %d;\n", size); out("if (!type || movem_index2[amask] != dstreg) {\n"); out("%s, m68k_areg(regs, movem_index2[amask]));\n", putcode); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } check_bus_error("src", 0, 1, table68k[opcode].size, "m68k_areg(regs, movem_index2[amask])", 1, 0); out("} else {\n"); out("%s, m68k_areg(regs, movem_index2[amask]) - %d);\n", putcode, size); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } if (size == 4) { check_bus_error("src", 0, 1, table68k[opcode].size, "m68k_areg(regs, movem_index2[amask]) - 4", 1, 0); } else { check_bus_error("src", 0, 1, table68k[opcode].size, "m68k_areg(regs, movem_index2[amask]) - 2", 1, 0); } out("}\n"); out("amask = movem_next[amask];\n"); out("}\n"); out("while (dmask) {\n"); out("srca -= %d;\n", size); out("%s, m68k_dreg(regs, movem_index2[dmask]));\n", putcode); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } check_bus_error("src", 0, 1, table68k[opcode].size, "m68k_dreg(regs, movem_index2[dmask])", 1, 0); out("dmask = movem_next[dmask];\n"); out("}\n"); out("m68k_areg(regs, dstreg) = srca;\n"); } else { out("uae_u16 dmask = mask & 0xff, amask = (mask >> 8) & 0xff;\n"); out("while (dmask) {\n"); out("%s, m68k_dreg(regs, movem_index1[dmask]));\n", putcode); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } check_bus_error("src", 0, 1, table68k[opcode].size, "m68k_dreg(regs, movem_index1[dmask])", 1, 0); out("srca += %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("while (amask) {\n"); out("%s, m68k_areg(regs, movem_index1[amask]));\n", putcode); if (cpu_level <= 3) { addcycles000_nonce(cpu_level <= 1 ? size * 2 : 4); } check_bus_error("src", 0, 1, table68k[opcode].size, "m68k_areg(regs, movem_index1[amask])", 1, 0); out("srca += %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); } if (!next_level_040_to_030()) next_level_020_to_010(); } count_ncycles++; set_last_access_ipl_prev(0); fill_prefetch_next_t(); get_prefetch_020(); } static void genmovemle_ce (uae_u16 opcode) { int size = table68k[opcode].size == sz_long ? 4 : 2; amodes mode = table68k[opcode].dmode; int pcoffset = 0; out("uae_u16 mask = %s;\n", gen_nextiword(mode >= Ad8r && mode != absw && mode != absl ? GF_PCM2 : ((mode == Ad16 || mode == PC16 || mode == absw || mode == absl) ? 0 : GF_PCP2))); do_instruction_buserror(); if (mode == Ad8r || mode == PC8r) { addcycles000(2); } strcpy(bus_error_code2, "pcoffset += 2;\n"); genamode(NULL, mode, "dstreg", table68k[opcode].size, "src", 2, 1, GF_AA | GF_MOVE | GF_REVERSE | GF_REVERSE2 | (mode == absl ? GF_PCM2 : GF_PCP2)); if (mode >= Ad16) { pcoffset = 2; } if (table68k[opcode].size == sz_long) { if (mode == Apdi) { out("uae_u16 amask = mask & 0xff, dmask = (mask >> 8) & 0xff;\n"); movem_ex3(1); out("while (amask) {\n"); out("%s(srca - 2, m68k_areg(regs, movem_index2[amask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", -2, 1, 1, "m68k_areg(regs, movem_index2[amask])", 1, pcoffset); out("%s(srca - 4, m68k_areg(regs, movem_index2[amask]) >> 16);\n", dstw); addcycles000_nonce(4); check_bus_error("src", -4, 1, 1, "m68k_areg(regs, movem_index2[amask]) >> 16", 1, pcoffset); out("srca -= %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); out("while (dmask) {\n"); out("%s(srca - 2, m68k_dreg(regs, movem_index2[dmask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", -2, 1, 1, "m68k_dreg(regs, movem_index2[dmask])", 1, pcoffset); out("%s(srca - 4, m68k_dreg(regs, movem_index2[dmask]) >> 16);\n", dstw); addcycles000_nonce(4); check_bus_error("src", -4, 1, 1, "m68k_dreg(regs, movem_index2[dmask]) >> 16", 1, pcoffset); out("srca -= %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("m68k_areg(regs, dstreg) = srca;\n"); } else { out("uae_u16 dmask = mask & 0xff, amask = (mask >> 8) & 0xff;\n"); movem_ex3(1); out("while (dmask) {\n"); out("%s(srca, m68k_dreg(regs, movem_index1[dmask]) >> 16);\n", dstw); addcycles000_nonce(4); check_bus_error("src", 0, 1, 1, "m68k_dreg(regs, movem_index1[dmask]) >> 16", 1, pcoffset); out("%s(srca + 2, m68k_dreg(regs, movem_index1[dmask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", 2, 1, 1, "m68k_dreg(regs, movem_index1[dmask])", 1, pcoffset); out("srca += %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("while (amask) {\n"); out("%s(srca, m68k_areg(regs, movem_index1[amask]) >> 16);\n", dstw); addcycles000_nonce(4); check_bus_error("src", 0, 1, 1, "m68k_areg(regs, movem_index1[amask]) >> 16", 1, pcoffset); out("%s(srca + 2, m68k_areg(regs, movem_index1[amask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", 2, 1, 1, "m68k_areg(regs, movem_index1[amask])", 1, pcoffset); out("srca += %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); } } else { if (mode == Apdi) { out("uae_u16 amask = mask & 0xff, dmask = (mask >> 8) & 0xff;\n"); movem_ex3(1); out("while (amask) {\n"); out("srca -= %d;\n", size); out("%s(srca, m68k_areg(regs, movem_index2[amask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", 0, 1, 1, "m68k_areg(regs, movem_index2[amask])", 1, pcoffset); out("amask = movem_next[amask];\n"); out("}\n"); out("while (dmask) {\n"); out("srca -= %d;\n", size); out("%s(srca, m68k_dreg(regs, movem_index2[dmask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", 0, 1, 1, "m68k_dreg(regs, movem_index2[dmask])", 1, pcoffset); out("dmask = movem_next[dmask];\n"); out("}\n"); out("m68k_areg(regs, dstreg) = srca;\n"); } else { out("uae_u16 dmask = mask & 0xff, amask = (mask >> 8) & 0xff;\n"); movem_ex3(1); out("while (dmask) {\n"); out("%s(srca, m68k_dreg(regs, movem_index1[dmask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", 0, 1, 1, "m68k_dreg(regs, movem_index1[dmask])", 1, pcoffset); out("srca += %d;\n", size); out("dmask = movem_next[dmask];\n"); out("}\n"); out("while (amask) {\n"); out("%s(srca, m68k_areg(regs, movem_index1[amask]));\n", dstw); addcycles000_nonce(4); check_bus_error("src", 0, 1, 1, "m68k_areg(regs, movem_index1[amask])", 1, pcoffset); out("srca += %d;\n", size); out("amask = movem_next[amask];\n"); out("}\n"); } } count_ncycles++; set_ipl(); fill_prefetch_next_t(); } static void force_range_for_rox (const char *var, wordsizes size) { /* Could do a modulo operation here... which one is faster? */ switch ((int)size) { case sz_long: out("if (%s >= 33) %s -= 33;\n", var, var); break; case sz_word: out("if (%s >= 34) %s -= 34;\n", var, var); out("if (%s >= 17) %s -= 17;\n", var, var); break; case sz_byte: out("if (%s >= 36) %s -= 36;\n", var, var); out("if (%s >= 18) %s -= 18;\n", var, var); out("if (%s >= 9) %s -= 9;\n", var, var); break; } } static const char *cmask (wordsizes size) { switch (size) { case sz_byte: return "0x80"; case sz_word: return "0x8000"; case sz_long: return "0x80000000"; default: term(); } } static int source_is_imm1_8(struct instr *i) { return i->stype == 3; } static void shift_ce(amodes dmode, int size) { if (isreg (dmode)) { int c = size == sz_long ? 4 : 2; if (using_ce) { out("{\n"); out("int cycles = %d;\n", c); out("cycles += 2 * ccnt;\n"); addcycles000_3(true); out("}\n"); } next_level_020_to_010(); if ((using_simple_cycles || do_always_dynamic_cycles) && cpu_level <= 1) { addcycles000_nonces("2 * ccnt"); } count_cycles += c; insn_n_cycles += c; count_ncycles++; } } // BCHG/BSET/BCLR Dx,Dx or #xx,Dx adds 2 cycles if bit number > 15 static void bsetcycles(struct instr *curi) { if (curi->size == sz_byte) { out("src &= 7;\n"); } else { out("src &= 31;\n"); if (isreg (curi->dmode)) { addcycles000(2); if (curi->mnemo != i_BTST) { if (using_ce) { out("if (src > 15) %s(2);\n", do_cycles); } next_level_020_to_010(); if ((using_simple_cycles || do_always_dynamic_cycles) && cpu_level <= 1 && !using_nocycles) { out("if (src > 15) {\n"); out("count_cycles += % d * CYCLE_UNIT / 2;\n", 2); out("}\n"); count_ncycles++; } } } } } static int islongimm(struct instr *curi) { return (curi->size == sz_long && (curi->smode == Dreg || curi->smode == imm || curi->smode == Areg)); } static void exception_cpu(const char *s) { if (need_exception_oldpc) { out("Exception_cpu_oldpc(%s,oldpc);\n", s); } else { out("Exception_cpu(%s);\n", s); } } static void exception_oldpc(void) { if (need_exception_oldpc) { out("uaecptr oldpc = %s;\n", getpc); } } static void resetvars (void) { insn_n_cycles = 0; genamode_cnt = 0; genamode8r_offset[0] = genamode8r_offset[1] = 0; m68k_pc_total = 0; branch_inst = 0; set_fpulimit = 0; bus_error_cycles = 0; exception_pc_offset = 0; exception_pc_offset_extra_000 = 0; did_prefetch = 0; ipl_fetched = 0; pre_ipl = 0; ir2irc = 0; mmufixupcnt = 0; mmufixupstate = 0; disp020cnt = 0; candormw = false; genastore_done = false; rmw_varname[0] = 0; tail_ce020 = 0; total_ce020 = 0; tail_ce020_done = false; head_in_ea_ce020 = 0; head_ce020_cycs_done = false; no_prefetch_ce020 = false; got_ea_ce020 = false; prefetch_long = NULL; prefetch_opcode = NULL; srcli = NULL; srcbi = NULL; disp000 = "get_disp_ea_000"; disp020 = "get_disp_ea_020"; nextw = NULL; nextl = NULL; do_cycles = "do_cycles"; srcwd = srcld = NULL; dstwd = dstld = NULL; srcblrmw = NULL; srcwlrmw = NULL; srcllrmw = NULL; dstblrmw = NULL; dstwlrmw = NULL; dstllrmw = NULL; getpc = "m68k_getpc()"; if (using_indirect > 0) { // tracer getpc = "m68k_getpci()"; if (using_mmu == 68030) { // 68030 cache MMU / CE cache MMU disp020 = "get_disp_ea_020_mmu030c"; prefetch_long = "get_ilong_mmu030c_state"; prefetch_word = "get_iword_mmu030c_state"; prefetch_opcode = "get_iword_mmu030c_opcode_state"; nextw = "next_iword_mmu030c_state"; nextl = "next_ilong_mmu030c_state"; srcli = "get_ilong_mmu030c_state"; srcwi = "get_iword_mmu030c_state"; srcbi = "get_ibyte_mmu030c_state"; srcl = "get_long_mmu030c_state"; dstl = "put_long_mmu030c_state"; srcw = "get_word_mmu030c_state"; dstw = "put_word_mmu030c_state"; srcb = "get_byte_mmu030c_state"; dstb = "put_byte_mmu030c_state"; srcblrmw = "get_lrmw_byte_mmu030c_state"; srcwlrmw = "get_lrmw_word_mmu030c_state"; srcllrmw = "get_lrmw_long_mmu030c_state"; dstblrmw = "put_lrmw_byte_mmu030c_state"; dstwlrmw = "put_lrmw_word_mmu030c_state"; dstllrmw = "put_lrmw_long_mmu030c_state"; srcld = "get_long_mmu030c"; srcwd = "get_word_mmu030c"; dstld = "put_long_mmu030c"; dstwd = "put_word_mmu030c"; getpc = "m68k_getpci()"; } else if (!using_ce020 && !using_prefetch_020 && !using_ce) { // generic + indirect disp020 = "x_get_disp_ea_020"; prefetch_long = "get_iilong_jit"; prefetch_word = "get_iiword_jit"; nextw = "next_iiword_jit"; nextl = "next_iilong_jit"; srcli = "get_iilong_jit"; srcwi = "get_iiword_jit"; srcbi = "get_iibyte_jit"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; getpc = "m68k_getpc()"; // special jit support } else if (!using_ce020 && !using_prefetch_020) { prefetch_word = "get_word_ce000_prefetch"; srcli = "x_get_ilong"; srcwi = "x_get_iword"; srcbi = "x_get_ibyte"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; do_cycles = "do_cycles_ce000_internal"; } else if (using_ce020 == 1) { /* x_ not used if it redirects to * get_word_ce020_prefetch() */ disp020 = "x_get_disp_ea_ce020"; prefetch_word = "get_word_ce020_prefetch"; prefetch_long = "get_long_ce020_prefetch"; prefetch_opcode = "get_word_ce020_prefetch_opcode"; srcli = "x_get_ilong"; srcwi = "x_get_iword"; srcbi = "x_get_ibyte"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; do_cycles = "do_cycles_020_internal"; nextw = "next_iword_020ce"; nextl = "next_ilong_020ce"; } else if (using_ce020 == 2) { // 68030 CE disp020 = "x_get_disp_ea_ce030"; prefetch_long = "get_long_ce030_prefetch"; prefetch_word = "get_word_ce030_prefetch"; prefetch_opcode = "get_word_ce030_prefetch_opcode"; srcli = "x_get_ilong"; srcwi = "x_get_iword"; srcbi = "x_get_ibyte"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; do_cycles = "do_cycles_020_internal"; nextw = "next_iword_030ce"; nextl = "next_ilong_030ce"; } else if (using_ce020 == 3) { // 68040/060 CE disp020 = "x_get_disp_ea_040"; prefetch_long = "get_ilong_cache_040"; prefetch_word = "get_iword_cache_040"; srcli = "x_get_ilong"; srcwi = "x_get_iword"; srcbi = "x_get_ibyte"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; do_cycles = "do_cycles_020_internal"; nextw = "next_iword_cache040"; nextl = "next_ilong_cache040"; } else if (using_prefetch_020 == 1) { disp020 = "x_get_disp_ea_020"; prefetch_word = "get_word_020_prefetch"; prefetch_long = "get_long_020_prefetch"; srcli = "x_get_ilong"; srcwi = "x_get_iword"; srcbi = "x_get_ibyte"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; nextw = "next_iword_020_prefetch"; nextl = "next_ilong_020_prefetch"; do_cycles = "do_cycles_020_internal"; } else if (using_prefetch_020 == 2) { disp020 = "x_get_disp_ea_020"; prefetch_word = "get_word_030_prefetch"; prefetch_long = "get_long_030_prefetch"; srcli = "x_get_ilong"; srcwi = "x_get_iword"; srcbi = "x_get_ibyte"; srcl = "x_get_long"; dstl = "x_put_long"; srcw = "x_get_word"; dstw = "x_put_word"; srcb = "x_get_byte"; dstb = "x_put_byte"; nextw = "next_iword_030_prefetch"; nextl = "next_ilong_030_prefetch"; do_cycles = "do_cycles_020_internal"; } #if 0 } else if (using_ce020) { disp020 = "x_get_disp_ea_020"; do_cycles = "do_cycles_ce020"; if (using_ce020 == 2) { // 68030/40/60 CE prefetch_long = "get_long_ce030_prefetch"; prefetch_word = "get_word_ce030_prefetch"; nextw = "next_iword_030ce"; nextl = "next_ilong_030ce"; srcli = "get_word_ce030_prefetch"; srcwi = "get_long_ce030_prefetch"; srcl = "get_long_ce030"; dstl = "put_long_ce030"; srcw = "get_word_ce030"; dstw = "put_word_ce030"; srcb = "get_byte_ce030"; dstb = "put_byte_ce030"; } else { // 68020 CE prefetch_long = "get_long_ce020_prefetch"; prefetch_word = "get_word_ce020_prefetch"; nextw = "next_iword_020ce"; nextl = "next_ilong_020ce"; srcli = "get_word_ce020_prefetch"; srcwi = "get_long_ce020_prefetch"; srcl = "get_long_ce020"; dstl = "put_long_ce020"; srcw = "get_word_ce020"; dstw = "put_word_ce020"; srcb = "get_byte_ce020"; dstb = "put_byte_ce020"; } #endif } else if (using_mmu == 68030) { // 68030 MMU disp020 = "get_disp_ea_020_mmu030"; prefetch_long = "get_ilong_mmu030_state"; prefetch_word = "get_iword_mmu030_state"; nextw = "next_iword_mmu030_state"; nextl = "next_ilong_mmu030_state"; srcli = "get_ilong_mmu030_state"; srcwi = "get_iword_mmu030_state"; srcbi = "get_ibyte_mmu030_state"; srcl = "get_long_mmu030_state"; dstl = "put_long_mmu030_state"; srcw = "get_word_mmu030_state"; dstw = "put_word_mmu030_state"; srcb = "get_byte_mmu030_state"; dstb = "put_byte_mmu030_state"; srcblrmw = "get_lrmw_byte_mmu030_state"; srcwlrmw = "get_lrmw_word_mmu030_state"; srcllrmw = "get_lrmw_long_mmu030_state"; dstblrmw = "put_lrmw_byte_mmu030_state"; dstwlrmw = "put_lrmw_word_mmu030_state"; dstllrmw = "put_lrmw_long_mmu030_state"; srcld = "get_long_mmu030"; srcwd = "get_word_mmu030"; dstld = "put_long_mmu030"; dstwd = "put_word_mmu030"; getpc = "m68k_getpci()"; } else if (using_mmu == 68040) { // 68040 MMU disp020 = "x_get_disp_ea_020"; prefetch_long = "get_ilong_mmu040"; prefetch_word = "get_iword_mmu040"; nextw = "next_iword_mmu040"; nextl = "next_ilong_mmu040"; srcli = "get_ilong_mmu040"; srcwi = "get_iword_mmu040"; srcbi = "get_ibyte_mmu040"; srcl = "get_long_mmu040"; dstl = "put_long_mmu040"; srcw = "get_word_mmu040"; dstw = "put_word_mmu040"; srcb = "get_byte_mmu040"; dstb = "put_byte_mmu040"; srcblrmw = "get_lrmw_byte_mmu040"; srcwlrmw = "get_lrmw_word_mmu040"; srcllrmw = "get_lrmw_long_mmu040"; dstblrmw = "put_lrmw_byte_mmu040"; dstwlrmw = "put_lrmw_word_mmu040"; dstllrmw = "put_lrmw_long_mmu040"; getpc = "m68k_getpci()"; } else if (using_mmu) { // 68060 MMU disp020 = "x_get_disp_ea_020"; prefetch_long = "get_ilong_mmu060"; prefetch_word = "get_iword_mmu060"; nextw = "next_iword_mmu060"; nextl = "next_ilong_mmu060"; srcli = "get_ilong_mmu060"; srcwi = "get_iword_mmu060"; srcbi = "get_ibyte_mmu060"; srcl = "get_long_mmu060"; dstl = "put_long_mmu060"; srcw = "get_word_mmu060"; dstw = "put_word_mmu060"; srcb = "get_byte_mmu060"; dstb = "put_byte_mmu060"; srcblrmw = "get_lrmw_byte_mmu060"; srcwlrmw = "get_lrmw_word_mmu060"; srcllrmw = "get_lrmw_long_mmu060"; dstblrmw = "put_lrmw_byte_mmu060"; dstwlrmw = "put_lrmw_word_mmu060"; dstllrmw = "put_lrmw_long_mmu060"; // 68060 only: also non-locked read-modify-write accesses are reported srcbrmw = "get_rmw_byte_mmu060"; srcwrmw = "get_rmw_word_mmu060"; srclrmw = "get_rmw_long_mmu060"; dstbrmw = "put_rmw_byte_mmu060"; dstwrmw = "put_rmw_word_mmu060"; dstlrmw = "put_rmw_long_mmu060"; getpc = "m68k_getpci()"; } else if (using_ce) { // 68000 ce prefetch_word = "get_word_ce000_prefetch"; srcwi = "get_wordi_ce000"; srcl = "get_long_ce000"; dstl = "put_long_ce000"; srcw = "get_word_ce000"; dstw = "put_word_ce000"; srcb = "get_byte_ce000"; dstb = "put_byte_ce000"; do_cycles = "do_cycles_ce000"; getpc = "m68k_getpci()"; } else if (using_prefetch) { // 68000 prefetch prefetch_word = "get_word_000_prefetch"; prefetch_long = "get_long_000_prefetch"; srcwi = "get_wordi_000"; srcl = "get_long_000"; dstl = "put_long_000"; srcw = "get_word_000"; dstw = "put_word_000"; srcb = "get_byte_000"; dstb = "put_byte_000"; getpc = "m68k_getpci()"; } else { // generic + direct prefetch_long = "get_dilong"; prefetch_word = "get_diword"; nextw = "next_diword"; nextl = "next_dilong"; srcli = "get_dilong"; srcwi = "get_diword"; srcbi = "get_dibyte"; if (using_indirect < 0) { srcl = "get_long_jit"; dstl = "put_long_jit"; srcw = "get_word_jit"; dstw = "put_word_jit"; srcb = "get_byte_jit"; dstb = "put_byte_jit"; } else { srcl = "get_long"; dstl = "put_long"; srcw = "get_word"; dstw = "put_word"; srcb = "get_byte"; dstb = "put_byte"; } } if (using_test) { prefetch_word = "get_word_test_prefetch"; srcwi = "get_wordi_test"; srcl = "get_long_test"; dstl = "put_long_test"; srcw = "get_word_test"; dstw = "put_word_test"; srcb = "get_byte_test"; dstb = "put_byte_test"; do_cycles = "do_cycles_test"; getpc = "m68k_getpci()"; disp000 = "get_disp_ea_test"; } if (!dstld) dstld = dstl; if (!dstwd) dstwd = dstw; if (!srcld) srcld = srcl; if (!srcwd) srcwd = srcw; if (!srcblrmw) { srcblrmw = srcb; srcwlrmw = srcw; srcllrmw = srcl; dstblrmw = dstb; dstwlrmw = dstw; dstllrmw = dstl; } if (!prefetch_opcode) prefetch_opcode = prefetch_word; } static void illg(void) { if (func_noret) { out("op_illg_noret(opcode);\n"); } else { out("op_illg(opcode);\n"); } } static void gen_opcode (unsigned int opcode) { struct instr *curi = table68k + opcode; int ipl = 0; resetvars(); #ifdef WINUAE_FOR_HATARI /* Hatari : Store the family of the instruction (used to check for pairing on ST, * for non-CPU cycles calculation and profiling) */ out("OpcodeFamily = %d;\n", curi->mnemo); /* leave some space for patching in the current cycles later */ if (!using_ce020) { out("CurrentInstrCycles = \n"); CurrentInstrCycles_pos = strlen(outbuffer) - 5; } #endif m68k_pc_offset = 2; g_instr = curi; g_srcname[0] = 0; bus_error_code[0] = 0; bus_error_code2[0] = 0; opcode_nextcopy = 0; last_access_offset_ipl = -1; last_access_offset_ipl_prev = -1; ipl_fetch_cycles = -1; ipl_fetch_cycles_prev = -1; loopmode = 0; // 68010 loop mode available if if (cpu_level == 1 && (using_ce || using_prefetch)) { loopmode = opcode_loop_mode(opcode); if (curi->mnemo == i_DBcc || loopmode) { next_level_000(); } loopmode_start(); } // do not unnecessarily create useless mmuop030 // functions when CPU is not 68030 if (curi->mnemo == i_MMUOP030 && cpu_level != 3 && !cpu_generic) { illg(); did_prefetch = -1; goto end; } switch (curi->plev) { case 0: /* not privileged */ break; case 1: /* unprivileged only on 68000 */ if (cpu_level == 0) break; if (next_cpu_level < 0) next_cpu_level = 0; /* fall through */ case 2: /* priviledged */ out( "if (!regs.s) {\n" "Exception(8);\n"); write_return_cycles(-1); out("}\n"); break; case 3: /* privileged if size == word */ if (curi->size == sz_byte) break; out( "if (!regs.s) {\n" "Exception(8);\n"); write_return_cycles(-1); out("}\n"); break; } switch (curi->mnemo) { case i_OR: case i_AND: case i_EOR: { // documentation error: and.l #imm,dn = 2 idle, not 1 idle (same as OR and EOR) int c = 0; genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", curi->size, "dst", 1, GF_RMW); out("src %c= dst;\n", curi->mnemo == i_OR ? '|' : curi->mnemo == i_AND ? '&' : '^'); genflags(flag_logical, curi->size, "src", "", ""); if (curi->size == sz_long) { if (curi->dmode == Dreg) { c += 2; if (curi->smode == imm || curi->smode == Dreg) { if (cpu_level == 0) { c += 2; } if (cpu_level == 1 && curi->smode == imm) { c += 2; } set_ipl_pre(); if (cpu_level == 1 && (curi->smode == imm || curi->smode == Dreg)) { fill_prefetch_next_after(0, "m68k_dreg(regs, dstreg) = (src);\n"); } else { fill_prefetch_next_after(1, "ccr_68000_long_move_ae_LZN(src);\ndreg_68000_long_replace_low(dstreg, src);\n"); } } else { if (cpu_level == 1) { fill_prefetch_next_after(0, "m68k_dreg(regs, dstreg) = (src);\n"); } else { fill_prefetch_next_after(1, "dreg_68000_long_replace_low(dstreg, src);\n"); } } loopmodeextra = 4; next_level_000(); } else { fill_prefetch_next_after(0, "ccr_68000_long_move_ae_LZN(src);\n"); } if (c > 0) { addcycles000(c); } genastore_rev("src", curi->dmode, "dstreg", curi->size, "dst"); } else { if (curi->dmode == Dreg) { genastore_rev("src", curi->dmode, "dstreg", curi->size, "dst"); } if ((curi->smode == imm || curi->smode == Dreg) && curi->dmode != Dreg) { fill_prefetch_next_after(1, NULL); } else { fill_prefetch_next_t(); loopmodeextra = 4; } if (c > 0) { addcycles000(c); } if (curi->dmode != Dreg) { genastore_rev("src", curi->dmode, "dstreg", curi->size, "dst"); } } break; } // all SR/CCR modifications do full prefetch case i_ORSR: case i_ANDSR: case i_EORSR: out("MakeSR();\n"); if (cpu_level == 0) { out("int t1 = regs.t1;\n"); } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, cpu_level == 1 ? GF_NOREFILL : 0); if (curi->size == sz_byte) { out("src &= 0xFF;\n"); if (curi->mnemo == i_ANDSR) out("src |= 0xff00;\n"); } else { check_trace(); } addcycles000(8); out("regs.sr %c= src;\n", curi->mnemo == i_ORSR ? '|' : curi->mnemo == i_ANDSR ? '&' : '^'); if (cpu_level < 5 && curi->size == sz_word) { makefromsr_t0(); } else { makefromsr(); } sync_m68k_pc(); if (cpu_level < 2 || curi->size == sz_word) { fill_prefetch_full_ntx(3); } else { fill_prefetch_next(); } next_cpu_level = cpu_level - 1; break; case i_SUB: { int c = 0; int earlyipl = curi->size == sz_long && curi->dmode == Dreg && (curi->smode == imm || curi->smode == immi || curi->smode == Dreg || curi->smode == Areg); genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, earlyipl ? GF_IPL : 0, curi->dmode, "dstreg", curi->size, "dst", 1, GF_RMW); genflags(flag_sub, curi->size, "newv", "src", "dst"); if (curi->size == sz_long) { if (curi->dmode == Dreg) { c += 2; if (curi->smode == imm || curi->smode == immi || curi->smode == Dreg || curi->smode == Areg) { if (cpu_level == 0) { c += 2; } if (cpu_level == 1 && curi->smode == immi) { c += 2; } if (curi->smode == immi || curi->smode == Dreg || curi->smode == Areg) { // SUBQ, SUB.L reg,reg set_ipl_pre(); } fill_prefetch_next_after(1, "uae_s16 bnewv = (uae_s16)dst - (uae_s16)src;\n" "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = bnewv < 0;\n" "ccr_68000_long_move_ae_LZN(bnewv);\n" "SET_CFLG(((uae_u16)(src)) > ((uae_u16)(dst)));\n" "SET_XFLG(GET_CFLG());\n" "SET_VFLG((bflgs ^ bflgo) & (bflgn ^ bflgo));\n" "dreg_68000_long_replace_low(dstreg, bnewv);\n"); } else { fill_prefetch_next_after(1, "dreg_68000_long_replace_low(dstreg, newv);\n"); } loopmodeextra = 4; next_level_000(); } else { fill_prefetch_next_after(0, "uae_s16 bnewv = (uae_s16)dst - (uae_s16)src;\n" "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = bnewv < 0;\n" "ccr_68000_long_move_ae_LZN(bnewv);\n" "SET_CFLG(((uae_u16)(src)) > ((uae_u16)(dst)));\n" "SET_XFLG(GET_CFLG());\n" "SET_VFLG((bflgs ^ bflgo) & (bflgn ^ bflgo));\n"); } if (c > 0) { addcycles000(c); } genastore_rev("newv", curi->dmode, "dstreg", curi->size, "dst"); } else { if (curi->dmode == Dreg) { genastore_rev("newv", curi->dmode, "dstreg", curi->size, "dst"); } if ((curi->smode >= imm || curi->smode == Dreg) && curi->dmode != Dreg) { fill_prefetch_next_after(1, NULL); } else { fill_prefetch_next_t(); loopmodeextra = 4; } if (c > 0) { addcycles000(c); } if (curi->dmode != Dreg) { genastore_rev("newv", curi->dmode, "dstreg", curi->size, "dst"); } } break; } case i_SUBA: { int c = 0; genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", sz_long, "dst", 1, GF_RMW); out("uae_u32 newv = dst - src;\n"); if (curi->smode == immi) { // SUBAQ.x is always 8 cycles c += 4; } else { c = curi->size == sz_long ? 2 : 4; if (islongimm(curi)) { c += 2; } loopmodeextra = curi->size == sz_long ? 4 : 2; } set_ipl_pre(); fill_prefetch_next_after(0, "areg_68000_long_replace_low(dstreg, newv);\n"); if (c > 0) { addcycles000(c); } genastore("newv", curi->dmode, "dstreg", sz_long, "dst"); break; } case i_SUBX: exception_pc_offset_extra_000 = 2; next_level_000(); if (!isreg(curi->smode)) { addcycles000(2); if (curi->size != sz_long) { set_ipl(); } } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_AA | GF_REVERSE); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, GF_AA | GF_REVERSE | GF_RMW | GF_SECONDEA | (curi->size == sz_long && !isreg(curi->smode) ? GF_IPL | GF_IPLMID : 0)); out("uae_u32 newv = dst - src - (GET_XFLG() ? 1 : 0);\n"); if (cpu_level >= 2) { genflags(flag_subx, curi->size, "newv", "src", "dst"); genflags(flag_zn, curi->size, "newv", "", ""); fill_prefetch_next(); genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); } else { if (curi->size != sz_long) { genflags(flag_subx, curi->size, "newv", "src", "dst"); genflags(flag_zn, curi->size, "newv", "", ""); } else { out("int oldz = GET_ZFLG();\n"); } if (isreg(curi->smode) && curi->size != sz_long) { genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); } if (curi->size == sz_long) { genflags(flag_subx, curi->size, "newv", "src", "dst"); genflags(flag_zn, curi->size, "newv", "", ""); } if (isreg(curi->smode)) { if (curi->size == sz_long) { set_ipl_pre(); // set CCR using only low word if prefetch bus error fill_prefetch_next_after(1, "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = ((uae_s16)(newv)) < 0;\n" "SET_VFLG((bflgs ^ bflgo) & (bflgo ^ bflgn));\n" "SET_CFLG(bflgs ^ ((bflgs ^ bflgn) & (bflgo ^ bflgn)));\n" "SET_XFLG(GET_CFLG());\n" "SET_ZFLG(oldz);\n" "if (newv & 0xffff) SET_ZFLG(0);\n" "SET_NFLG(newv & 0x8000); \n" "dreg_68000_long_replace_low(dstreg, newv);\n"); } else { fill_prefetch_next_t(); } } else if (curi->size == sz_long) { if (isreg(curi->smode)) { fill_prefetch_next_after(0, NULL); } } else { fill_prefetch_next_after(1, NULL); } if (curi->size == sz_long && isreg(curi->smode)) { if (cpu_level == 0) addcycles000(4); else addcycles000(2); next_level_000(); } exception_pc_offset_extra_000 = 0; if (curi->size == sz_long && !isreg(curi->dmode)) { // write addr + 2 // prefetch // write addr + 0 genastore_rev_prefetch("newv", curi->dmode, "dstreg", curi->size, "dst"); } else { genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); } } break; case i_SBCD: exception_pc_offset_extra_000 = 2; if (!isreg (curi->smode)) addcycles000(2); genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_AA); if (!isreg(curi->smode)) { set_ipl_pre(); } genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, GF_AA | GF_RMW); out("uae_u16 newv_lo = (dst & 0xF) - (src & 0xF) - (GET_XFLG() ? 1 : 0);\n"); out("uae_u16 newv_hi = (dst & 0xF0) - (src & 0xF0);\n"); out("uae_u16 newv, tmp_newv;\n"); out("int bcd = 0;\n"); out("newv = tmp_newv = newv_hi + newv_lo;\n"); out("if (newv_lo & 0xF0) { newv -= 6; bcd = 6; };\n"); out("if ((((dst & 0xFF) - (src & 0xFF) - (GET_XFLG() ? 1 : 0)) & 0x100) > 0xFF) { newv -= 0x60; }\n"); out("SET_CFLG((((dst & 0xFF) - (src & 0xFF) - bcd - (GET_XFLG() ? 1 : 0)) & 0x300) > 0xFF);\n"); duplicate_carry(); /* Manual says bits NV are undefined though a real 68030 doesn't change V and 68040/060 don't change both */ if (cpu_level >= xBCD_KEEPS_N_FLAG) { if (next_cpu_level < xBCD_KEEPS_N_FLAG) next_cpu_level = xBCD_KEEPS_N_FLAG - 1; genflags (flag_z, curi->size, "newv", "", ""); } else { genflags (flag_zn, curi->size, "newv", "", ""); } if (cpu_level >= xBCD_KEEPS_V_FLAG) { if (next_cpu_level < xBCD_KEEPS_V_FLAG) next_cpu_level = xBCD_KEEPS_V_FLAG - 1; if (cpu_level >= xBCD_KEEPS_V_FLAG && cpu_level < xBCD_KEEPS_N_FLAG) out("SET_VFLG(0);\n"); } else { out("SET_VFLG((tmp_newv & 0x80) != 0 && (newv & 0x80) == 0);\n"); } if (isreg(curi->smode)) { set_ipl_pre(); } fill_prefetch_next_after(1, NULL); if (isreg (curi->smode)) { addcycles000(2); } else { set_ipl(); } exception_pc_offset_extra_000 = 0; genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); break; case i_ADD: { int c = 0; int earlyipl = curi->size == sz_long && curi->dmode == Dreg && (curi->smode == imm || curi->smode == immi || curi->smode == Dreg || curi->smode == Areg); genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, earlyipl ? GF_IPL : 0, curi->dmode, "dstreg", curi->size, "dst", 1, GF_RMW); genflags(flag_add, curi->size, "newv", "src", "dst"); if (curi->size == sz_long) { if (curi->dmode == Dreg) { c += 2; if (curi->smode == imm || curi->smode == immi || curi->smode == Dreg || curi->smode == Areg) { if (cpu_level == 0) { c += 2; } if (cpu_level == 1 && curi->smode == immi) { // 68010 Immediate long instructions 2 cycles faster, Q variants have same speed. c += 2; } if (curi->smode == immi || curi->smode == Dreg || curi->smode == Areg) { // ADDQ, ADD.L reg,reg set_ipl_pre(); } fill_prefetch_next_after(1, "uae_s16 bnewv = (uae_s16)dst + (uae_s16)src;\n" "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = bnewv < 0;\n" "ccr_68000_long_move_ae_LZN(bnewv);\n" "SET_CFLG(((uae_u16)(~dst)) < ((uae_u16)(src)));\n" "SET_XFLG(GET_CFLG());\n" "SET_VFLG((bflgs ^ bflgn) & (bflgo ^ bflgn));\n" "dreg_68000_long_replace_low(dstreg, bnewv);\n"); } else { fill_prefetch_next_after(1, "dreg_68000_long_replace_low(dstreg, newv);\n"); } loopmodeextra = 4; next_level_000(); } else { fill_prefetch_next_after(0, "uae_s16 bnewv = (uae_s16)dst + (uae_s16)src;\n" "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = bnewv < 0;\n" "ccr_68000_long_move_ae_LZN(bnewv);\n" "SET_CFLG(((uae_u16)(~dst)) < ((uae_u16)(src)));\n" "SET_XFLG(GET_CFLG());\n" "SET_VFLG((bflgs ^ bflgn) & (bflgo ^ bflgn));\n"); } if (c > 0) { addcycles000(c); } genastore_rev("newv", curi->dmode, "dstreg", curi->size, "dst"); } else { if (curi->dmode == Dreg) { genastore_rev("newv", curi->dmode, "dstreg", curi->size, "dst"); } if ((curi->smode >= imm || curi->smode == Dreg) && curi->dmode != Dreg) { fill_prefetch_next_after(1, NULL); } else { fill_prefetch_next_t(); loopmodeextra = 4; } if (c > 0) { addcycles000(c); } if (curi->dmode != Dreg) { genastore_rev("newv", curi->dmode, "dstreg", curi->size, "dst"); } } break; } case i_ADDA: { int c = 0; genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", sz_long, "dst", 1, GF_RMW); out("uae_u32 newv = dst + src;\n"); if (curi->smode == immi) { // ADDAQ.x is always 8 cycles c += 4; } else { c = curi->size == sz_long ? 2 : 4; if (islongimm(curi)) { c += 2; } loopmodeextra = curi->size == sz_long ? 4 : 2; } set_ipl_pre(); fill_prefetch_next_after(1, "areg_68000_long_replace_low(dstreg, newv);\n"); if (c > 0) { addcycles000(c); } genastore("newv", curi->dmode, "dstreg", sz_long, "dst"); break; } case i_ADDX: exception_pc_offset_extra_000 = 2; next_level_000(); if (!isreg(curi->smode)) { addcycles000(2); if (curi->size != sz_long) { set_ipl(); } } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_AA | GF_REVERSE); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, GF_AA | GF_REVERSE | GF_RMW | GF_SECONDEA | (curi->size == sz_long && !isreg(curi->smode) ? GF_IPL | GF_IPLMID : 0)); out("uae_u32 newv = dst + src + (GET_XFLG() ? 1 : 0);\n"); if (cpu_level >= 2) { genflags(flag_addx, curi->size, "newv", "src", "dst"); genflags(flag_zn, curi->size, "newv", "", ""); fill_prefetch_next(); genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); } else { if (curi->size != sz_long) { genflags(flag_addx, curi->size, "newv", "src", "dst"); genflags(flag_zn, curi->size, "newv", "", ""); } else { out("int oldz = GET_ZFLG();\n"); } if (isreg(curi->smode) && curi->size != sz_long) { genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); } if (curi->size == sz_long) { genflags(flag_addx, curi->size, "newv", "src", "dst"); genflags(flag_zn, curi->size, "newv", "", ""); } if (isreg(curi->smode)) { if (curi->size == sz_long) { set_ipl_pre(); // set CCR using only low word if prefetch bus error fill_prefetch_next_after(1, "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(dst)) < 0;\n" "int bflgn = ((uae_s16)(newv)) < 0;\n" "SET_VFLG((bflgs ^ bflgn) & (bflgo ^ bflgn));\n" "SET_CFLG(bflgs ^ ((bflgs ^ bflgo) & (bflgo ^ bflgn)));\n" "SET_XFLG(GET_CFLG());\n" "SET_ZFLG(oldz);\n" "if (newv & 0xffff) SET_ZFLG(0);\n" "SET_NFLG(newv & 0x8000); \n" "dreg_68000_long_replace_low(dstreg, newv);\n"); } else { fill_prefetch_next_t(); } } else if (curi->size == sz_long) { if (isreg(curi->smode)) { fill_prefetch_next_after(0, NULL); } } else { fill_prefetch_next_after(1, NULL); } if (curi->size == sz_long && isreg(curi->smode)) { if (cpu_level == 0) addcycles000(4); else addcycles000(2); next_level_000(); } exception_pc_offset_extra_000 = 0; if (curi->size == sz_long && !isreg(curi->dmode)) { // write addr + 2 // prefetch // write addr + 0 genastore_rev_prefetch("newv", curi->dmode, "dstreg", curi->size, "dst"); } else { genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); } } break; case i_ABCD: exception_pc_offset_extra_000 = 2; if (!isreg (curi->smode)) addcycles000(2); genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_AA); if (!isreg(curi->smode)) { set_ipl_pre(); } genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, GF_AA | GF_RMW); out("uae_u16 newv_lo = (src & 0xF) + (dst & 0xF) + (GET_XFLG() ? 1 : 0);\n"); out("uae_u16 newv_hi = (src & 0xF0) + (dst & 0xF0);\n"); out("uae_u16 newv, tmp_newv;\n"); out("int cflg;\n"); out("newv = tmp_newv = newv_hi + newv_lo;"); out("if (newv_lo > 9) { newv += 6; }\n"); out("cflg = (newv & 0x3F0) > 0x90;\n"); out("if (cflg) newv += 0x60;\n"); out("SET_CFLG(cflg);\n"); duplicate_carry(); /* Manual says bits NV are undefined though a real 68030 clears V and 68040/060 don't change both */ if (cpu_level >= xBCD_KEEPS_N_FLAG) { if (next_cpu_level < xBCD_KEEPS_N_FLAG) next_cpu_level = xBCD_KEEPS_N_FLAG - 1; genflags (flag_z, curi->size, "newv", "", ""); } else { genflags (flag_zn, curi->size, "newv", "", ""); } if (cpu_level >= xBCD_KEEPS_V_FLAG) { if (next_cpu_level < xBCD_KEEPS_V_FLAG) next_cpu_level = xBCD_KEEPS_V_FLAG - 1; if (cpu_level >= xBCD_KEEPS_V_FLAG && cpu_level < xBCD_KEEPS_N_FLAG) out("SET_VFLG(0);\n"); } else { out("SET_VFLG((tmp_newv & 0x80) == 0 && (newv & 0x80) != 0);\n"); } if (isreg(curi->smode)) { set_ipl_pre(); } fill_prefetch_next_after(1, NULL); if (isreg (curi->smode)) { addcycles000(2); } exception_pc_offset_extra_000 = 0; genastore("newv", curi->dmode, "dstreg", curi->size, "dst"); break; case i_NEG: genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_RMW); genflags(flag_sub, curi->size, "dst", "src", "0"); if (curi->smode == Dreg) { if (curi->size == sz_long) { // prefetch bus error and long register: only low word is updated set_ipl_pre(); fill_prefetch_next_after(1, "dreg_68000_long_replace_low(srcreg, dst);\n"); genastore_rev("dst", curi->smode, "srcreg", curi->size, "src"); } else { genastore_rev("dst", curi->smode, "srcreg", curi->size, "src"); fill_prefetch_next_t(); } } else if (curi->size == sz_long) { // prefetch bus error and long memory: only low word CCR decoded fill_prefetch_next_after(0, "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(0)) < 0;\n" "int bflgn = ((uae_s16)(dst)) < 0;\n" "SET_ZFLG(((uae_s16)(dst)) == 0);\n" "SET_VFLG((bflgs ^ bflgo) & (bflgn ^ bflgo));\n" "SET_CFLG(((uae_u16)(src)) > ((uae_u16)(0)));\n" "SET_NFLG(bflgn != 0);\n" "SET_XFLG(GET_CFLG());\n"); } else { fill_prefetch_next_after(1, NULL); } if (isreg(curi->smode) && curi->size == sz_long) addcycles000(2); if (curi->smode != Dreg) { genastore_rev("dst", curi->smode, "srcreg", curi->size, "src"); } break; case i_NEGX: genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_RMW); out("uae_u32 newv = 0 - src - (GET_XFLG() ? 1 : 0);\n"); genflags(flag_subx, curi->size, "newv", "src", "0"); genflags(flag_zn, curi->size, "newv", "", ""); if (curi->smode == Dreg) { if (curi->size == sz_long) { // prefetch bus error and long register: only low word is updated set_ipl_pre(); fill_prefetch_next_after(1, "dreg_68000_long_replace_low(srcreg, newv);\n"); genastore_rev("newv", curi->smode, "srcreg", curi->size, "src"); } else { genastore_rev("newv", curi->smode, "srcreg", curi->size, "src"); fill_prefetch_next_t(); } } else if (curi->size == sz_long) { // prefetch bus error and long memory: only low word CCR decoded fill_prefetch_next_after(0, "int bflgs = ((uae_s16)(src)) < 0;\n" "int bflgo = ((uae_s16)(0)) < 0;\n" "int bflgn = ((uae_s16)(newv)) < 0;\n" "SET_VFLG((bflgs ^ bflgo) & (bflgo ^ bflgn));\n" "SET_CFLG(bflgs ^ ((bflgs ^ bflgn) & (bflgo ^ bflgn)));\n" "SET_ZFLG(GET_ZFLG() & (((uae_s16)(newv)) == 0));\n" "SET_NFLG(((uae_s16)(newv)) < 0);\n" "SET_XFLG(GET_CFLG());\n"); } else { fill_prefetch_next_after(1, NULL); } if (isreg (curi->smode) && curi->size == sz_long) addcycles000(2); if (curi->smode != Dreg) { genastore_rev("newv", curi->smode, "srcreg", curi->size, "src"); } break; case i_NBCD: genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_RMW | (isreg(curi->smode) ? 0 : GF_IPL)); out("uae_u16 newv_lo = - (src & 0xF) - (GET_XFLG() ? 1 : 0);\n"); out("uae_u16 newv_hi = - (src & 0xF0);\n"); out("uae_u16 newv;\n"); out("int cflg, tmp_newv;\n"); out("tmp_newv = newv_hi + newv_lo;\n"); out("if (newv_lo > 9) newv_lo -= 6;\n"); out("newv = newv_hi + newv_lo;\n"); out("cflg = (newv & 0x1F0) > 0x90;\n"); out("if (cflg) newv -= 0x60;\n"); out("SET_CFLG(cflg);\n"); duplicate_carry(); /* Manual says bits NV are undefined though a real 68030 doesn't change V and 68040/060 don't change both */ if (cpu_level >= xBCD_KEEPS_N_FLAG) { if (next_cpu_level < xBCD_KEEPS_N_FLAG) next_cpu_level = xBCD_KEEPS_N_FLAG - 1; genflags (flag_z, curi->size, "newv", "", ""); } else { genflags (flag_zn, curi->size, "newv", "", ""); } if (cpu_level >= xBCD_KEEPS_V_FLAG) { if (next_cpu_level < xBCD_KEEPS_V_FLAG) next_cpu_level = xBCD_KEEPS_V_FLAG - 1; if (cpu_level >= xBCD_KEEPS_V_FLAG && cpu_level < xBCD_KEEPS_N_FLAG) out("SET_VFLG(0);\n"); } else { out("SET_VFLG((tmp_newv & 0x80) != 0 && (newv & 0x80) == 0);\n"); } if (isreg(curi->smode)) { set_ipl_pre(); fill_prefetch_next_after(1, NULL); addcycles000(2); } else { fill_prefetch_next_after(1, NULL); } genastore("newv", curi->smode, "srcreg", curi->size, "src"); break; case i_CLR: if (cpu_level == 0) { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); genflags(flag_logical, curi->size, "0", "", ""); if (curi->smode == Dreg) { if (curi->size == sz_long) { // prefetch bus error and long register: only low word is updated // N flag from high word. Z both. set_ipl_pre(); fill_prefetch_next_after(1, "m68k_dreg(regs, srcreg) = (src & 0xffff0000);\n" "SET_VFLG(0);SET_ZFLG(1);SET_NFLG(0);SET_CFLG(0);\n"); genastore_rev("0", curi->smode, "srcreg", curi->size, "src"); } else { genastore_rev("0", curi->smode, "srcreg", curi->size, "src"); fill_prefetch_next_t(); } } else if (curi->size == sz_long) { // prefetch bus error and long memory: only low word CCR decoded fill_prefetch_next_after(0, "SET_VFLG(0);SET_ZFLG(1);SET_NFLG(0);SET_CFLG(0);\n"); } else { fill_prefetch_next_after(1, NULL); } if (isreg(curi->smode) && curi->size == sz_long) { addcycles000(2); } if (curi->smode != Dreg) { genastore_rev("0", curi->smode, "srcreg", curi->size, "src"); } } else if (cpu_level == 1) { out("struct flag_struct oldflags;\n"); out("oldflags.cznv = regflags.cznv;\n"); genamode(curi, curi->smode, "srcreg", curi->size, "src", 3, 0, GF_CLR68010); if (isreg(curi->smode) && curi->size == sz_long) { addcycles000(2); } if (!isreg(curi->smode) && using_exception_3 && curi->size != sz_byte && (using_prefetch || using_ce)) { out("if(srca & 1) {\n"); if (curi->size == sz_word) { if (curi->smode == Aipi) { out("m68k_areg(regs, srcreg) -= 2;\n"); } else if (curi->smode == Apdi) { out("m68k_areg(regs, srcreg) += 2;\n"); } else if (curi->smode >= Ad16) { out("%s(%d);\n", prefetch_word, m68k_pc_offset + 2); genflags(flag_logical, curi->size, "0", "", ""); } } else { if (curi->smode == Aipi) { out("m68k_areg(regs, srcreg) -= 4;\n"); } else if (curi->smode == Apdi) { out("m68k_areg(regs, srcreg) += 4;\n"); out("srca += 2;\n"); } else if (curi->smode >= Ad16) { out("srca += 2;\n"); out("%s(%d);\n", prefetch_word, m68k_pc_offset + 2); genflags(flag_logical, curi->size, "0", "", ""); } } incpc("%d", m68k_pc_offset + 2); out("exception3_write(opcode, srca, 1, 0, 1);\n"); write_return_cycles(0); out("}\n"); } fill_prefetch_next_after(0, "CLEAR_CZNV();\nSET_ZFLG(1);\n"); genflags(flag_logical, curi->size, "0", "", ""); genastore_rev("0", curi->smode, "srcreg", curi->size, "src"); } else { genamode(curi, curi->smode, "srcreg", curi->size, "src", 2, 0, 0); fill_prefetch_next(); if (isreg(curi->smode) && curi->size == sz_long) addcycles000(2); genflags(flag_logical, curi->size, "0", "", ""); genastore_rev("0", curi->smode, "srcreg", curi->size, "src"); } next_level_000(); break; case i_NOT: genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_RMW); out("uae_u32 dst = ~src;\n"); genflags(flag_logical, curi->size, "dst", "", ""); if (curi->smode == Dreg) { if (curi->size == sz_long) { // prefetch bus error and long register: only low word is updated // N flag from high word. Z both. set_ipl_pre(); fill_prefetch_next_after(1, "dreg_68000_long_replace_low(srcreg, dst);\n" "SET_VFLG(0);SET_ZFLG(!dst);\n" "SET_NFLG(dst & 0x80000000);\n" "SET_CFLG(0);\n"); genastore_rev("dst", curi->smode, "srcreg", curi->size, "src"); } else { genastore_rev("dst", curi->smode, "srcreg", curi->size, "src"); fill_prefetch_next_t(); } } else if (curi->size == sz_long) { // prefetch bus error and long memory: only low word CCR decoded fill_prefetch_next_after(0, "SET_VFLG(0);\n" "SET_ZFLG(!(dst & 0xffff));\n" "SET_NFLG(dst & 0x8000);\n" "SET_CFLG(0);\n"); } else { fill_prefetch_next_after(1, NULL); } if (isreg(curi->smode) && curi->size == sz_long) { addcycles000(2); } if (curi->smode != Dreg) { genastore_rev("dst", curi->smode, "srcreg", curi->size, "src"); } break; case i_TST: genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); genflags (flag_logical, curi->size, "src", "", ""); fill_prefetch_next_t(); break; case i_BTST: if (curi->size == sz_long) { if (curi->smode != Dreg) { set_ipl(); } else { set_ipl_pre(); } genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", curi->size, "dst", 1, 0); fill_prefetch_next_after(1, NULL); bsetcycles(curi); out("SET_ZFLG(1 ^ ((dst >> src) & 1));\n"); } else { genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", curi->size, "dst", 1, 0); bsetcycles(curi); if (curi->dmode == imm) { // btst dn,#x set_ipl_pre(); fill_prefetch_next_after(1, NULL); addcycles000(2); out("SET_ZFLG(1 ^ ((dst >> src) & 1));\n"); } else { out("SET_ZFLG(1 ^ ((dst >> src) & 1));\n"); fill_prefetch_next_t(); } } break; case i_BCHG: case i_BCLR: case i_BSET: // on 68000 these have weird side-effect, if EA points to write-only custom register //during instruction's read access CPU data lines appear as zero to outside world, // (normally previously fetched data appears in data lines if reading write-only register) // this allows stupid things like bset #2,$dff096 to work "correctly" // NOTE: above can't be right. genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", curi->size, "dst", 1, GF_RMW); if (curi->size == sz_long) { set_ipl_pre(); fill_prefetch_next_after(1, NULL); } else { if (curi->smode == Dreg || curi->smode >= imm) { fill_prefetch_next_after(1, NULL); } } bsetcycles(curi); // bclr needs 1 extra cycle if (curi->mnemo == i_BCLR && curi->dmode == Dreg) { addcycles000(2); } if (curi->mnemo == i_BCLR && curi->size == sz_byte) { if (cpu_level == 1) { // 68010 BCLR.B is 2 cycles slower addcycles000(2); } next_level_000(); } if (curi->mnemo == i_BCHG) { out("dst ^= (1 << src);\n"); out("SET_ZFLG(((uae_u32)dst & (1 << src)) >> src);\n"); } else if (curi->mnemo == i_BCLR) { out("SET_ZFLG(1 ^ ((dst >> src) & 1));\n"); out("dst &= ~(1 << src);\n"); } else if (curi->mnemo == i_BSET) { out("SET_ZFLG(1 ^ ((dst >> src) & 1));\n"); out("dst |= (1 << src);\n"); } if (curi->size != sz_long) { if (curi->smode < imm && curi->smode != Dreg) { fill_prefetch_next_t(); } } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; case i_CMPM: disable_noflags = 1; exception_pc_offset_extra_000 = 2; if (curi->size == sz_long) { genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, GF_AA | GF_IPL | GF_IPLMID, curi->dmode, "dstreg", curi->size, "dst", 1, GF_AA | GF_NOLIPL); } else { set_ipl(); genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, GF_AA, curi->dmode, "dstreg", curi->size, "dst", 1, GF_AA | GF_NOLIPL); } genflags (flag_cmp, curi->size, "newv", "src", "dst"); fill_prefetch_next_t(); break; case i_CMP: disable_noflags = 1; genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_cmp, curi->size, "newv", "src", "dst"); if (curi->dmode == Dreg && curi->size == sz_long) { set_ipl_pre(); fill_prefetch_next_after(1, NULL); addcycles000(2); } else { fill_prefetch_next_t(); } break; case i_CMPA: disable_noflags = 1; genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", sz_long, "dst", 1, 0); genflags(flag_cmp, sz_long, "newv", "src", "dst"); set_ipl_pre(); if (curi->dmode == Areg) { fill_prefetch_next_after(1, NULL); } else { fill_prefetch_next_t(); } addcycles000(2); break; /* The next two are coded a little unconventional, but they are doing * weird things... */ case i_MVPRM: // MOVEP R->M genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, cpu_level == 1 ? GF_NOFETCH : 0); out("uaecptr mempa = m68k_areg(regs, dstreg) + (uae_s32)(uae_s16)%s;\n", gen_nextiword(0)); check_prefetch_buserror(m68k_pc_offset, -2); if (curi->size == sz_word) { out("%s(mempa, src >> 8);\n", dstb); count_writew++; check_bus_error("memp", 0, 1, 0, "src >> 8", 1 | 0x10000, 2); out("%s(mempa + 2, src); \n", dstb); count_writew++; check_bus_error("memp", 2, 1, 0, "src", 1, 2); } else { out("%s(mempa, src >> 24);\n", dstb); count_writew++; check_bus_error("memp", 0, 1, 0, "src >> 24", 1 | 0x10000, 2); out("%s(mempa + 2, src >> 16);\n", dstb); count_writew++; check_bus_error("memp", 2, 1, 0, "src >> 16", 1, 2); out("%s(mempa + 4, src >> 8);\n", dstb); count_writew++; check_bus_error("memp", 4, 1, 0, "src >> 8", 1 | 0x10000, 2); out("%s(mempa + 6, src); \n", dstb); count_writew++; check_bus_error("memp", 6, 1, 0, "src", 1, 2); } fill_prefetch_next_t(); next_level_000(); break; case i_MVPMR: // MOVEP M->R out("uaecptr mempa = m68k_areg(regs, srcreg) + (uae_s32)(uae_s16)%s;\n", gen_nextiword(0)); check_prefetch_buserror(m68k_pc_offset, -2); set_ipl(); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 2, 0, cpu_level == 1 ? GF_NOFETCH : 0); if (curi->size == sz_word) { out("uae_u16 val = (%s(mempa) & 0xff) << 8;\n", srcb); count_readw++; check_bus_error("memp", 0, 0, 0, NULL, 1 | 0x10000, 2); out("val |= (%s(mempa + 2) & 0xff);\n", srcb); count_readw++; check_bus_error("memp", 2, 0, 0, NULL, 1, 2); } else { out("uae_u32 val = (%s(mempa) & 0xff) << 24;\n", srcb); count_readw++; check_bus_error("memp", 0, 0, 0, NULL, 1 | 0x10000, 2); out("val |= (%s(mempa + 2) & 0xff) << 16;\n", srcb); count_readw++; check_bus_error("memp", 2, 0, 0, NULL, 1, 2); set_ipl(); // unexpected position.. // upper word gets updated after two bytes (makes only difference if bus error is possible) if (cpu_level <= 1) { out("m68k_dreg(regs, dstreg) = (m68k_dreg(regs, dstreg) & 0x0000ffff) | val;\n"); } out("val |= (%s(mempa + 4) & 0xff) << 8;\n", srcb); count_readw++; check_bus_error("memp", 4, 0, 0, NULL, 1 | 0x10000, 2); out("val |= (%s(mempa + 6) & 0xff);\n", srcb); count_readw++; check_bus_error("memp", 6, 0, 0, NULL, 1, 2); } genastore("val", curi->dmode, "dstreg", curi->size, "dst"); fill_prefetch_next_t(); next_level_000(); break; case i_MOVE: case i_MOVEA: { /* 2 MOVE instruction variants have special prefetch sequence: * - MOVE ,-(An) = prefetch is before writes (Apdi) * - MOVE memory,(xxx).L = 2 prefetches after write * - move.x #imm = prefetch is done before write * - all others = prefetch is done after writes * * - move.x xxx,[at least 1 extension word here] = fetch 1 extension word before (xxx) * */ if (isce020()) { // MOVE is too complex to handle in table68k int h = 0, t = 0, c = 0, subhead = 0; bool fea = false; if (curi->smode == immi && isreg (curi->dmode)) { // MOVEQ h = 2; t = 0; c = 0; } else if (isreg (curi->smode) && isreg (curi->dmode)) { // MOVE Rn,Rn h = 2; t = 0; c = 2; } else if (isreg (curi->dmode)) { // MOVE EA,Rn h = 0; t = 0; c = 2; fea = true; } else if (curi->dmode == Aind) { if (isreg (curi->smode)) { // MOVE Rn,(An) h = 0; t = 1; c = 3; } else { // MOVE SOURCE,(An) h = 2; t = 0; c = 4; fea = true; } } else if (curi->dmode == Aipi) { if (isreg (curi->smode)) { // MOVE Rn,(An)+ h = 0; t = 1; c = 3; } else { // MOVE SOURCE,(An)+ h = 2; t = 0; c = 4; fea = true; } } else if (curi->dmode == Apdi) { if (isreg (curi->smode)) { // MOVE Rn,-(An) h = 0; t = 2; c = 4; } else { // MOVE SOURCE,-(An) h = 2; t = 0; c = 4; fea = true; } } else if (curi->dmode == Ad16) { // MOVE EA,(d16,An) h = 2; t = 0; c = 4; fea = true; } else if (curi->dmode == Ad8r) { h = 4; t = 0; c = 6; fea = true; } else if (curi->dmode == absw) { // MOVE EA,xxx.W h = 2; t = 0; c = 4; fea = true; } else if (curi->dmode == absl) { // MOVE EA,xxx.L h = 0; t = 0; c = 6; fea = true; } else { h = 4; t = 0; c = 6; fea = true; } if (fea) { if (curi->smode == imm) subhead = gence020cycles_fiea (curi, curi->size, Dreg); else subhead = gence020cycles_fea (curi->smode); } genamode2x (curi->smode, "srcreg", curi->size, "src", 1, 0, 0, fea ? fetchmode_fea : -1); genamode2 (curi->dmode, "dstreg", curi->size, "dst", 2, 0, GF_MOVE); addopcycles_ce20 (h, t, c, -subhead, 0); if (curi->mnemo == i_MOVEA && curi->size == sz_word) out("src = (uae_s32)(uae_s16)src;\n"); if (curi->mnemo == i_MOVE) genflags (flag_logical, curi->size, "src", "", ""); genastore("src", curi->dmode, "dstreg", curi->size, "dst"); sync_m68k_pc(); } else if (cpu_level < 2) { int prefetch_done = 0; int flags = 0; int dualprefetch = curi->dmode == absl && (curi->smode != Dreg && curi->smode != Areg && curi->smode != imm); if (curi->size == sz_long) { if (curi->smode >= Aind && curi->smode != absw) { flags |= GF_PCM2; } else { flags |= GF_PCM2 | GF_PCP2; } if (curi->dmode == Apdi && (curi->smode > Areg)) { flags |= GF_IPL | GF_IPLMID; } if (curi->dmode == Apdi && (curi->smode <= Areg)) { set_ipl_pre(); } } else { if (curi->smode >= Aind && curi->smode != absw && curi->smode != imm) { flags |= GF_PCM2; } else { flags |= GF_PCM2 | GF_PCP2; } if (curi->dmode == Aipi && curi->smode == imm) { set_ipl(); } else if (curi->dmode == Aipi && curi->smode > Areg) { flags |= GF_IPL; } if (curi->dmode == Apdi && curi->smode == imm) { set_ipl(); } else if (curi->dmode == Apdi && curi->smode > Areg) { flags |= GF_IPL; } if (curi->dmode == Apdi && curi->smode <= Areg) { set_ipl_pre(); } } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, flags); flags = 0; // unexpectedly MOVEA vs MOVE have different prefetch bus error stack frame PC field behavior. if (curi->mnemo == i_MOVE) { if ((curi->smode == imm && curi->dmode == absl) || (curi->smode >= Ad16 && curi->smode != imm && curi->smode != absw && curi->smode != absl && curi->dmode < Ad16) || (curi->smode == absl && curi->dmode < Ad16) || ((curi->smode == Areg || curi->smode == Dreg) && curi->dmode == absl)) { flags |= GF_PCM2; } else { flags |= GF_PCM2 | GF_PCP2; } } flags |= GF_MOVE | GF_APDI; flags |= dualprefetch ? GF_NOREFILL : 0; if (curi->dmode == Apdi && curi->size == sz_long) { flags |= GF_REVERSE; } // prefetch bus error support if (curi->mnemo == i_MOVE) { if (curi->size == sz_long) { if (curi->smode == imm) { if (curi->dmode == absw) { strcpy(bus_error_code, "ccr_68000_long_move_ae_LZN(src);\n"); } else if (curi->dmode == absl) { strcpy(bus_error_code2, "ccr_68000_long_move_ae_LZN(src);\n"); } } else if (curi->smode == Dreg || curi->smode == Areg) { if (curi->dmode == absl) { strcpy(bus_error_code, "// nothing;\n"); strcpy(bus_error_code2, "ccr_68000_long_move_ae_LZN(src);\n"); } else if (curi->dmode == absw) { strcpy(bus_error_code, "ccr_68000_long_move_ae_LZN(src);\n"); } } else { if (curi->dmode == absl) { strcpy(bus_error_code, "// nothing;\n"); strcpy(bus_error_code2, "ccr_68000_long_move_ae_LZN(src);\n"); } else { strcpy(bus_error_code, "ccr_68000_long_move_ae_LZN(src);\n"); } } } else { // if data prefetches needed for destination EA, CCR is set before fetch completes. if (curi->smode == Areg || curi->smode == Dreg || curi->smode == imm) { if (curi->dmode == absl) { strcpy(bus_error_code2, "ccr_68000_word_move_ae_normal((uae_s16)src);\n"); } else if (curi->dmode == absw) { strcpy(bus_error_code, "ccr_68000_word_move_ae_normal((uae_s16)src);\n"); } } else { if (curi->dmode == Ad16 || curi->dmode == PC16 || curi->dmode == Ad8r || curi->dmode == PC8r || curi->dmode == absw || curi->dmode == absl) { strcpy(bus_error_code, "ccr_68000_word_move_ae_normal((uae_s16)src);\n"); } } } } genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 2, 0, flags | GF_NOEXC3); if (curi->mnemo == i_MOVEA && curi->size == sz_word) { out("src = (uae_s32)(uae_s16)src;\n"); } if (curi->dmode == Apdi) { // -(an) decrease is not done if bus error if (curi->size == sz_long) { fill_prefetch_next_after(0, "m68k_areg(regs, dstreg) += 4;\n" "ccr_68000_long_move_ae_LZN(src);\n" ); } else { // x,-(an): flags are set before prefetch detects possible bus error if (curi->mnemo == i_MOVE) { fill_prefetch_next_after(1, "m68k_areg(regs, dstreg) += %s;\n" "ccr_68000_word_move_ae_normal((uae_s16)src);\n", curi->size == sz_byte ? "areg_byteinc[dstreg]" : "2"); } else { fill_prefetch_next_after(1, "m68k_areg(regs, dstreg) += %s;\n", curi->size == sz_byte ? "areg_byteinc[dstreg]" : "2"); } } prefetch_done = 1; // MOVE.L reg,-(an): 2 extra cycles if 68010 if (isreg(curi->smode) && curi->size == sz_long) { if (cpu_level == 1) { addcycles000(2); } next_level_000(); } } int storeflags = flags & (GF_REVERSE | GF_APDI); if (curi->mnemo == i_MOVE) { if (cpu_level == 1 && (isreg(curi->smode) || curi->smode == imm)) { out("struct flag_struct oldflags;\n"); out("oldflags.cznv = regflags.cznv;\n"); } if (curi->size == sz_long && (using_prefetch || using_ce) && curi->dmode >= Aind) { // to support bus error exception correct flags, flags needs to be set // after first word has been written. storeflags |= GF_SECONDWORDSETFLAGS; } else { genflags(flag_logical, curi->size, "src", "", ""); } } bus_error_code[0] = 0; bus_error_code2[0] = 0; storeflags &= ~(GF_PCM2 | GF_PCP2); if (curi->smode >= Aind && curi->smode < imm && curi->dmode == absl) { storeflags |= GF_PCM2; } else if (curi->dmode == Apdi) { storeflags |= GF_PCP2; } if (curi->size == sz_long) { if (curi->dmode == Aind && curi->smode == imm) { set_ipl(); } else if (curi->dmode == Aind && curi->smode > Areg && curi->smode != absl) { storeflags |= GF_IPL; } if (curi->dmode == Aipi && curi->smode > Areg) { set_ipl(); } if ((curi->dmode == Ad16 || curi->dmode == PC16) && curi->smode > Areg && curi->smode != imm) { storeflags |= GF_IPL; } if ((curi->dmode == Ad8r || curi->dmode == PC8r || curi->dmode == absl) && curi->smode > Areg && curi->smode != imm) { storeflags |= GF_IPL; } if (curi->dmode == absw) { storeflags |= GF_IPL; } } else { if (curi->dmode == Aipi && curi->smode <= Areg) { set_ipl_pre(); } } // MOVE EA,-(An) long writes are always reversed. Reads are normal. if (curi->dmode == Apdi && curi->size == sz_long) { genastore_2("src", curi->dmode, "dstreg", curi->size, "dst", 1, storeflags | GF_EXC3 | GF_MOVE); } else { genastore_2("src", curi->dmode, "dstreg", curi->size, "dst", 0, storeflags | GF_EXC3 | GF_MOVE); } if (curi->size == sz_long) { if (curi->dmode == Aind && curi->smode == absl) { set_ipl(); } if (curi->dmode == absl) { set_ipl(); } } else { if (curi->dmode == absl || curi->dmode == absw) { set_ipl(); } } sync_m68k_pc(); if (dualprefetch) { fill_prefetch_full_000(curi->mnemo == i_MOVE ? 2 : 1); prefetch_done = 1; } if (!prefetch_done) fill_prefetch_next_t(); } else { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 2, 0, 0); if (curi->mnemo == i_MOVEA) { if (curi->size == sz_word) out("src = (uae_s32)(uae_s16)src;\n"); } else { genflags(flag_logical, curi->size, "src", "", ""); } fill_prefetch_next(); genastore_2("src", curi->dmode, "dstreg", curi->size, "dst", 0, 0); } } break; case i_MVSR2: // MOVE FROM SR if (cpu_level == 0) { if ((curi->smode != Apdi && curi->smode != absw && curi->smode != absl) && curi->size == sz_word) { exception_pc_offset_extra_000 = -2; } } genamode(curi, curi->smode, "srcreg", sz_word, "src", cpu_level == 0 ? 2 : 3, 0, cpu_level == 1 ? GF_NOFETCH : 0); out("MakeSR();\n"); if (isreg (curi->smode)) { if (cpu_level == 0 && curi->size == sz_word) { set_ipl_pre(); fill_prefetch_next_after(1, "MakeSR();\n" "m68k_dreg(regs, srcreg) = (m68k_dreg(regs, srcreg) & ~0xffff) | ((regs.sr) & 0xffff);\n"); } else { fill_prefetch_next_after(1, NULL); } if (cpu_level == 0) { addcycles000(2); } } else { // 68000: read first and ignore result if (cpu_level == 0 && curi->size == sz_word) { out("%s(srca);\n", srcw); count_writew++; check_bus_error("src", 0, 0, 1, NULL, 1, 0); } fill_prefetch_next_after(1, NULL); } exception_pc_offset_extra_000 = 0; if (!isreg(curi->smode) && cpu_level == 1 && using_exception_3 && (using_prefetch || using_ce)) { out("if(srca & 1) {\n"); incpc("%d", m68k_pc_offset + 2); out("exception3_write(opcode, srca, 1, regs.sr & 0x%x, 1);\n", curi->size == sz_byte ? 0x00ff : 0xffff); write_return_cycles(0); out("}\n"); } // real write if (curi->size == sz_byte) genastore("regs.sr & 0xff", curi->smode, "srcreg", sz_word, "src"); else genastore("regs.sr", curi->smode, "srcreg", sz_word, "src"); next_level_000(); break; case i_MV2SR: // MOVE TO SR genamode(curi, curi->smode, "srcreg", sz_word, "src", 1, 0, 0); if (cpu_level == 0) { out("int t1 = regs.t1;\n"); } if (curi->size == sz_byte) { // MOVE TO CCR addcycles000(4); out("MakeSR();\nregs.sr &= 0xFF00;\nregs.sr |= src & 0xFF;\n"); makefromsr(); } else { // MOVE TO SR check_trace(); addcycles000(4); out("regs.sr = src;\n"); makefromsr_t0(); } if (cpu_level >= 2 && curi->size == sz_byte) { fill_prefetch_next(); } else { // does full prefetch because S-bit change may change memory mapping under the CPU sync_m68k_pc(); fill_prefetch_full_ntx(3); } next_level_000(); break; case i_SWAP: genamode(curi, curi->smode, "srcreg", sz_long, "src", 1, 0, 0); out("uae_u32 dst = ((src >> 16)&0xFFFF) | ((src&0xFFFF)<<16);\n"); if (cpu_level >= 2) { genflags(flag_logical, sz_long, "dst", "", ""); genastore("dst", curi->smode, "srcreg", sz_long, "src"); } else { genastore("dst", curi->smode, "srcreg", sz_long, "src"); genflags(flag_logical, sz_long, "dst", "", ""); } fill_prefetch_next_t(); break; case i_EXG: genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, curi->dmode, "dstreg", curi->size, "dst", 1, 0); genastore("dst", curi->smode, "srcreg", curi->size, "src"); genastore("src", curi->dmode, "dstreg", curi->size, "dst"); set_ipl_pre(); fill_prefetch_next_after(1, NULL); addcycles000(2); break; case i_EXT: // confirmed genamode(curi, curi->smode, "srcreg", sz_long, "src", 1, 0, 0); switch (curi->size) { case sz_byte: out("uae_u32 dst = (uae_s32)(uae_s8)src;\n"); break; case sz_word: out("uae_u16 dst = (uae_s16)(uae_s8)src;\n"); break; case sz_long: out("uae_u32 dst = (uae_s32)(uae_s16)src;\n"); break; default: term(); } if (curi->size != sz_word) { genflags(flag_logical, sz_long, "dst", "", ""); genastore("dst", curi->smode, "srcreg", sz_long, "src"); } else { genflags(flag_logical, sz_word, "dst", "", ""); genastore("dst", curi->smode, "srcreg", sz_word, "src"); } fill_prefetch_next_t(); break; case i_MVMEL: // confirmed if (using_ce || using_prefetch) genmovemel_ce(opcode); else genmovemel(opcode); tail_ce020_done = true; break; case i_MVMLE: // confirmed if (using_ce || using_prefetch) genmovemle_ce(opcode); else genmovemle(opcode); tail_ce020_done = true; break; case i_TRAP: exception_oldpc(); genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); gen_set_fault_pc (false, true); sync_m68k_pc(); exception_cpu("src + 32"); write_return_cycles_noadd(0); did_prefetch = -1; ipl_fetched = -1; clear_m68k_offset(); break; case i_MVR2USP: next_level_000(); genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); out("regs.usp = src;\n"); if (cpu_level == 1) { addcycles000(2); } fill_prefetch_next_t(); trace_t0_68040_only(); next_level_000(); break; case i_MVUSP2R: next_level_000(); genamode(curi, curi->smode, "srcreg", curi->size, "src", 2, 0, 0); genastore("regs.usp", curi->smode, "srcreg", curi->size, "src"); if (cpu_level == 1) { addcycles000(2); } fill_prefetch_next_t(); next_level_000(); break; case i_RESET: out("bool r = cpureset();\n"); addcycles000(128); out("if (r) {\n"); write_return_cycles(0); out("}\n"); fill_prefetch_next_t(); break; case i_NOP: fill_prefetch_next_t(); trace_t0_68040_only(); break; case i_STOP: { const char *reg = cpu_level <= 1 ? "irc" : "ir"; out("if (!regs.stopped) {\n"); if (using_prefetch) { out("uae_u16 src = regs.%s;\n", reg); } else { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); } out("regs.%s = src;\n", reg); out("}\n"); out("uae_u16 sr = regs.%s;\n", reg); // STOP undocumented features: // if new SR S-bit is not set: // 68000/68010: Update SR, increase PC and then cause privilege violation exception // 68000/68010: Traced STOP runs 4 cycles faster. // 68020 68030 68040: STOP works normally // 68060: Immediate privilege violation exception if (cpu_level >= 5) { out("if (!(sr & 0x2000)) {\n"); out("Exception(8);\n"); write_return_cycles(0); out("}\n"); } check_ipl_next(); if (cpu_level <= 1) { out("checkint();\n"); out("regs.sr = sr;\n"); out("MakeFromSR_STOP();\n"); } else { out("regs.sr = sr;\n"); out("checkint();\n"); out("MakeFromSR_STOP();\n"); } out("do_cycles_stop(4);\n"); out("m68k_setstopped(1);\n"); // STOP does not prefetch anything did_prefetch = -1; m68k_pc_offset = 0; next_cpu_level = cpu_level - 1; next_level_000(); break; } case i_LPSTOP: /* 68060 */ out("uae_u16 sw = %s;\n", gen_nextiword(0)); out("if (sw != 0x01c0) {\n"); out("Exception(11);\n"); write_return_cycles(0); out("}\n"); out("if (!(regs.sr & 0x2000)) {\n"); out("Exception(8);\n"); write_return_cycles(0); out("}\n"); out("uae_u16 newsr = %s;\n", gen_nextiword(0)); out("if (!(newsr & 0x2000)) {\n"); out("Exception(8);\n"); write_return_cycles(0); out("}\n"); out("regs.sr = newsr;\n"); out("checkint();\n"); out("MakeFromSR_STOP();\n"); out("m68k_setstopped(2);\n"); did_prefetch = -1; m68k_pc_offset = 0; break; case i_HALT: /* 68060 debug */ out("cpu_halt(CPU_HALT_68060_HALT);\n"); break; case i_PULSE: /* 68060 debug */ break; case i_RTE: ipl_fetched = 10; addop_ce020 (curi, 0, 0); next_level_000(); if (cpu_level <= 1 && using_exception_3) { out("if (m68k_areg(regs, 7) & 1) {\n"); out("exception3_read_access(opcode, m68k_areg(regs, 7), 1, 1);\n"); write_return_cycles_noadd(0); out("}\n"); } if (cpu_level == 0) { // 68000 // Read SR (SP+=6), Read PC high, Read PC low. out("uaecptr oldpc = %s;\n", getpc); out("uaecptr a = m68k_areg(regs, 7);\n"); out("uae_u16 sr = %s(a);\n", srcw); count_readw++; check_bus_error("", 0, 0, 1, NULL, 1, 0); out("m68k_areg(regs, 7) += 6;\n"); out("uae_u32 pc = %s(a + 2) << 16;\n", srcw); count_readw++; check_bus_error("", 2, 0, 1, NULL, 1, 0); out("pc |= %s(a + 2 + 2); \n", srcw); count_readw++; check_bus_error("", 4, 0, 1, NULL, 1, 0); out("uae_u16 oldt1 = regs.t1;\n"); out("regs.sr = sr;\n"); makefromsr(); out("if (pc & 1) {\n"); incpc("2"); out("exception3_read_access(opcode | 0x20000, pc, 1, 2);\n"); write_return_cycles(0); out("}\n"); setpc ("pc"); if (using_prefetch) { out("opcode |= 0x20000;\n"); } if (using_debugmem) { out("#ifdef DEBUGGER\n"); out("branch_stack_pop_rte(oldpc);\n"); out("#endif\n"); } } else if (cpu_level == 1 && using_prefetch) { // 68010 // Read SR, Read Format, Read PC high, Read PC low. out("uaecptr oldpc = %s;\n", getpc); out("uae_u16 newsr;\n"); out("uae_u32 newpc;\n"); out("uaecptr a = m68k_areg(regs, 7);\n"); out("uae_u16 sr = %s(a);\n", srcw); count_readw++; check_bus_error("", 0, 0, 1, NULL, 1, 0); out("uae_u16 format = %s(a + 2 + 4);\n", srcw); count_readw++; check_bus_error("", 6, 0, 1, NULL, 1, 0); out("uae_u32 pc = %s(a + 2) << 16;\n", srcw); count_readw++; check_bus_error("", 2, 0, 1, NULL, 1, 0); out("int frame = format >> 12;\n"); out("int offset = 8;\n"); out("if (frame == 0x0) {\n"); out("m68k_areg(regs, 7) += offset;\n"); out("} else if (frame == 0x8) {\n"); out("m68k_areg(regs, 7) += offset + 50;\n"); out("} else {\n"); out("SET_NFLG(((uae_s16)format) < 0); \n"); out("SET_ZFLG(format == 0);\n"); out("SET_VFLG(0);\n"); exception_cpu("14"); write_return_cycles(0); out("}\n"); out("pc |= %s(a + 2 + 2); \n", srcw); count_readw++; check_bus_error("", 4, 0, 1, NULL, 1, 0); out("regs.sr = sr;\n"); makefromsr(); out("if (pc & 1) {\n"); incpc("2"); out("exception3_read_prefetch_only(opcode, pc);\n"); write_return_cycles(0); out("}\n"); out("newsr = sr; newpc = pc;\n"); setpc ("newpc"); if (using_debugmem) { out("#ifdef DEBUGGER\n"); out("branch_stack_pop_rte(oldpc);\n"); out("#endif\n"); } } else { out("uaecptr oldpc = %s;\n", getpc); out("uae_u16 oldsr = regs.sr, newsr;\n"); out("uae_u32 newpc;\n"); out("for (;;) {\n"); out("uaecptr a = m68k_areg(regs, 7);\n"); out("uae_u16 sr = %s(a);\n", srcw); count_readw++; out("uae_u32 pc = %s(a + 2);\n", srcl); count_readl++; out("uae_u16 format = %s(a + 2 + 4);\n", srcw); count_readw++; out("int frame = format >> 12;\n"); out("int offset = 8;\n"); out("newsr = sr; newpc = pc;\n"); addcycles_ce020 (6); out("if (frame == 0x0) {\nm68k_areg(regs, 7) += offset; break; }\n"); if (cpu_level >= 2) { // 68020+ out("else if (frame == 0x1) {\nm68k_areg(regs, 7) += offset; }\n"); out("else if (frame == 0x2) {\nm68k_areg(regs, 7) += offset + 4; break; }\n"); } if (cpu_level >= 4) { // 68040+ out("else if (frame == 0x3) {\nm68k_areg(regs, 7) += offset + 4; break; }\n"); } if (using_mmu == 68060) { out("else if (frame == 0x4) {\nm68k_do_rte_mmu060 (a); m68k_areg(regs, 7) += offset + 8; break; }\n"); } else if (cpu_level >= 4) { // 68040+ out("else if (frame == 0x4) {\nm68k_areg(regs, 7) += offset + 8; break; }\n"); } if (cpu_level == 1) { // 68010 only out("else if (frame == 0x8) {\nm68k_areg(regs, 7) += offset + 50; break; }\n"); } if (using_mmu == 68040) { out("else if (frame == 0x7) {\nm68k_do_rte_mmu040 (a); m68k_areg(regs, 7) += offset + 52; break; }\n"); } else if (cpu_level == 4) { // 68040 only out("else if (frame == 0x7) {\nm68k_areg(regs, 7) += offset + 52; break; }\n"); } if (cpu_level == 2 || cpu_level == 3) { // 68020/68030 only out("else if (frame == 0x9) {\nm68k_areg(regs, 7) += offset + 12; break; }\n"); if (using_mmu == 68030) { if (using_prefetch_020) { out("else if (frame == 0xa) {\n"); out("m68k_do_rte_mmu030c(a);\n"); write_return_cycles(0); out("} else if (frame == 0xb) {\n"); out("m68k_do_rte_mmu030c(a);\n"); write_return_cycles(0); out("}\n"); } else { out("else if (frame == 0xa) {\n"); out("m68k_do_rte_mmu030(a);\n"); write_return_cycles(0); out("} else if (frame == 0xb) {\n"); out("m68k_do_rte_mmu030(a);\n"); write_return_cycles(0); out("}\n"); } } else { out("else if (frame == 0xa) {\nm68k_areg(regs, 7) += offset + 24; break; }\n"); out("else if (frame == 0xb) {\nm68k_areg(regs, 7) += offset + 84; break; }\n"); } } out("else {\n"); if (cpu_level == 1) { out("SET_NFLG(((uae_s16)format) < 0); \n"); out("SET_ZFLG(format == 0);\n"); out("SET_VFLG(0);\n"); exception_cpu("14"); write_return_cycles(0); } else if (cpu_level == 0) { exception_cpu("14"); write_return_cycles(0); } else if (cpu_level == 3) { // 68030: trace bits are cleared out("regs.t1 = regs.t0 = 0;\n"); exception_cpu("14"); write_return_cycles(0); } else { exception_cpu("14"); write_return_cycles(0); } out("}\n"); out("regs.sr = newsr;\n"); out("oldsr = newsr;\n"); makefromsr_t0(); out("}\n"); out("regs.sr = newsr;\n"); addcycles_ce020 (4); makefromsr_t0(); out("if (newpc & 1) {\n"); if (cpu_level == 5) { out("regs.sr = oldsr & 0xff00;\n"); makefromsr(); out("SET_ZFLG(newsr == 0);\n"); out("SET_NFLG(newsr & 0x8000);\n"); out("exception3_read_prefetch(opcode, newpc);\n"); } else if (cpu_level == 4) { makefromsr(); out("exception3_read_prefetch_68040bug(opcode, newpc, oldsr);\n"); } else { out("exception3_read_prefetch(opcode, newpc);\n"); } write_return_cycles(0); out("}\n"); setpc ("newpc"); if (using_debugmem) { out("#ifdef DEBUGGER\n"); out("branch_stack_pop_rte(oldpc);\n"); out("#endif\n"); } } /* PC is set and prefetch filled. */ clear_m68k_offset(); tail_ce020_done = true; if (using_ce || using_prefetch) { out("int pcadjust = oldpc - m68k_getpci() + 2;\n"); fill_prefetch_full_000_special(-1, NULL); } else { fill_prefetch_full_ntx(0); } branch_inst = m68k_pc_total; next_cpu_level = cpu_level - 1; break; case i_RTD: ipl_fetched = 10; out("uaecptr oldpc = %s;\n", getpc); addop_ce020 (curi, 0, 0); if (using_mmu) { genamode(curi, curi->smode, "srcreg", curi->size, "offs", GENA_GETV_FETCH, GENA_MOVEM_DO_INC, 0); genamode(NULL, Aipi, "7", sz_long, "pc", GENA_GETV_FETCH, GENA_MOVEM_DO_INC, 0); out("m68k_areg(regs, 7) += offs;\n"); } else { genamode(NULL, Aipi, "7", sz_long, "pc", 1, 0, 0); genamode(curi, curi->smode, "srcreg", curi->size, "offs", 1, 0, GF_NOREFILL); out("m68k_areg(regs, 7) += offs;\n"); } out("if (pc & 1) {\n"); if (cpu_level >= 4) { out("m68k_areg(regs, 7) -= 4 + offs;\n"); } else if (cpu_level == 1) { incpc("2"); } out("exception3_read_prefetch_only(opcode, pc);\n"); write_return_cycles(0); out("}\n"); setpc ("pc"); /* PC is set and prefetch filled. */ clear_m68k_offset(); tail_ce020_done = true; if (using_prefetch || using_ce) { fill_prefetch_full_000_special(1, NULL); } else { fill_prefetch_full(0); } branch_inst = m68k_pc_total; next_level_040_to_030(); break; case i_LINK: // ce confirmed // 68040 uses different order than other CPU models. if (using_mmu) { addmmufixup("srcreg", -1, -1); genamode(NULL, curi->dmode, "dstreg", curi->size, "offs", GENA_GETV_FETCH, GENA_MOVEM_DO_INC, 0); if (cpu_level == 4) { genamode(NULL, Apdi, "7", sz_long, "old", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC, 0); genamode(NULL, curi->smode, "srcreg", sz_long, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC, 0); genastore("m68k_areg(regs, 7)", curi->smode, "srcreg", sz_long, "src"); } else { genamode(NULL, curi->smode, "srcreg", sz_long, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC, 0); genamode(NULL, Apdi, "7", sz_long, "old", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC, 0); genastore("m68k_areg(regs, 7)", curi->smode, "srcreg", sz_long, "src"); } out("m68k_areg(regs, 7) += offs;\n"); genastore("src", Apdi, "7", sz_long, "old"); } else { addop_ce020(curi, 0, 0); // smode must be first in case it is A7. Except if 68040! set_ipl(); if (cpu_level == 4) { genamode(NULL, Apdi, "7", sz_long, "old", 2, 0, GF_AA | GF_NOEXC3); genamode(NULL, curi->smode, "srcreg", sz_long, "src", 1, 0, GF_AA); } else { genamode(NULL, curi->smode, "srcreg", sz_long, "src", 1, 0, GF_AA); genamode(NULL, Apdi, "7", sz_long, "old", 2, 0, GF_AA | GF_NOEXC3); } strcpy(bus_error_code, "m68k_areg(regs, 7) += 4;\n"); genamode(NULL, curi->dmode, "dstreg", curi->size, "offs", 1, 0, GF_PCP2); if (cpu_level <= 1 && using_exception_3) { out("if (olda & 1) {\n"); out("m68k_areg(regs, 7) += 4;\n"); out("m68k_areg(regs, srcreg) = olda;\n"); incpc("6"); out("exception3_write_access(opcode, olda, sz_word, src >> 16, 1);\n"); write_return_cycles(0); out("}\n"); } genastore_2("src", Apdi, "7", sz_long, "old", 0, 0); genastore("m68k_areg(regs, 7)", curi->smode, "srcreg", sz_long, "src"); out("m68k_areg(regs, 7) += offs;\n"); fill_prefetch_next_t(); if (!next_level_060_to_040()) next_level_020_to_010(); } break; case i_UNLK: // ce confirmed if (using_mmu) { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); out("uae_s32 old = %s(src);\n", srcl); out("m68k_areg(regs, 7) = src + 4;\n"); out("m68k_areg(regs, srcreg) = old;\n"); } else { m68k_pc_offset = 4; set_ipl(); genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); genamode(NULL, am_unknown, "src", sz_long, "old", 1, 0, 0); out("m68k_areg(regs, 7) = src + 4;\n"); m68k_pc_offset = 2; genastore("old", curi->smode, "srcreg", curi->size, "src"); fill_prefetch_next_t(); } break; case i_RTS: ipl_fetched = 10; addop_ce020 (curi, 0, 0); out("uaecptr oldpc = %s;\n", getpc); if (cpu_level <= 1 && using_exception_3) { out("if (m68k_areg(regs, 7) & 1) {\n"); incpc("2"); out("exception3_read_access(opcode, m68k_areg(regs, 7), 1, 1);\n"); write_return_cycles(0); out("}\n"); } if (using_indirect > 0 && !using_ce020 && !using_prefetch_020 && !using_ce && !using_test) { out("m68k_do_rtsi_jit();\n"); count_readl++; } else if (using_mmu) { out("m68k_do_rts_mmu%s();\n", mmu_postfix); count_readl++; } else if (using_ce020 == 1) { add_head_cycs (1); out("m68k_do_rts_ce020();\n"); count_readl++; } else if (using_ce020 == 2) { add_head_cycs (1); out("m68k_do_rts_ce030();\n"); count_readl++; } else if (using_ce || using_prefetch || (using_test && cpu_level <= 1)) { out("uaecptr newpc, dsta = m68k_areg(regs, 7);\n"); out("newpc = %s(dsta) << 16;\n", srcw); count_readw++; check_bus_error("dst", 0, 0, 1, NULL, 1, 0); out("newpc |= %s(dsta + 2);\n", srcw); count_readw++; check_bus_error("dst", 2, 0, 1, NULL, 1, 0); out("m68k_areg(regs, 7) += 4;\n"); setpc("newpc"); } else if (using_prefetch_020 || (using_test && cpu_level >= 2)) { out("m68k_do_rtsi();\n"); count_readl++; } else { out("m68k_do_rts();\n"); count_readl++; } if (using_debugmem) { out("#ifdef DEBUGGER\n"); out("if (debugmem_trace) {\n"); out("branch_stack_pop_rts(oldpc);\n"); out("}\n"); out("#endif\n"); } out("if (%s & 1) {\n", getpc); out("uaecptr faultpc = %s;\n", getpc); setpc("oldpc"); if (cpu_level >= 4) { out("m68k_areg(regs, 7) -= 4;\n"); } if (cpu_level <= 1) { incpc("2"); } out("exception3_read_prefetch_only(opcode, faultpc);\n"); write_return_cycles(0); out("}\n"); clear_m68k_offset(); if (using_prefetch || using_ce) { out("int pcadjust = oldpc - m68k_getpci() + 2;\n"); fill_prefetch_full_000_special(-1, NULL); } else { fill_prefetch_full(0); } branch_inst = m68k_pc_total; if (!next_level_040_to_030()) { if (!next_level_020_to_010()) next_level_000(); } break; case i_TRAPV: exception_oldpc(); sync_m68k_pc(); if (cpu_level == 0) { // 68000 TRAPV is really weird // If V is set but prefetch causes bus error: S is set. // for some reason T is also cleared! if (using_prefetch) { out("uae_u16 opcode_v = opcode;\n"); } if (using_ce) { // IPL is not fetched if instruction traps out("int ipl0 = regs.ipl[0];\n"); out("int ipl1 = regs.ipl[1];\n"); } fill_prefetch_next_after(1, "if (GET_VFLG()) {\n" "MakeSR();\n" "regs.sr |= 0x2000;\n" "regs.sr &= ~0x8000;\n" "MakeFromSR();\n" "pcoffset -= 2;\n" "} else {\n" "opcode = regs.ir | 0x20000;\n" "if(regs.t1) opcode |= 0x10000;\n" "}\n"); out("if (GET_VFLG()) {\n"); if (using_ce) { out("regs.ipl[0] = ipl0;\n"); out("regs.ipl[1] = ipl1;\n"); } if (using_prefetch) { // If exception vector is odd, // stacked opcode is TRAPV out("regs.ir = opcode_v;\n"); } exception_cpu("7"); write_return_cycles(0); out("}\n"); } else if (cpu_level == 1) { push_ins_cnt(); out("if (GET_VFLG()) {\n"); addcycles000(2); exception_cpu("7"); write_return_cycles(0); out("}\n"); pop_ins_cnt(); fill_prefetch_next(); } else { fill_prefetch_next(); out("if (GET_VFLG()) {\n"); exception_cpu("7"); write_return_cycles(0); out("}\n"); } next_level_000(); break; case i_RTR: ipl_fetched = 10; if (cpu_level <= 1 && using_exception_3) { out("if (m68k_areg(regs, 7) & 1) {\n"); incpc("2"); out("exception3_read_access(opcode, m68k_areg(regs, 7), 1, 1);\n"); write_return_cycles(0); out("}\n"); } out("uaecptr oldpc = %s;\n", getpc); out("MakeSR();\n"); genamode(NULL, Aipi, "7", sz_word, "sr", 1, 0, 0); genamode(NULL, Aipi, "7", sz_long, "pc", 1, 0, 0); if (cpu_level >= 4) { out("if (pc & 1) {\n"); out("m68k_areg(regs, 7) -= 6;\n"); if (cpu_level == 5) { out("regs.sr &= 0xFF00; sr &= 0xFF;\n"); out("regs.sr |= sr;\n"); makefromsr(); out("exception3_read_prefetch(opcode, pc);\n"); } else { // stacked SR is original SR. Real SR has CCR part zeroed. out("uae_u16 oldsr = regs.sr;\n"); out("regs.sr &= 0xFF00; sr &= 0xFF;\n"); out("regs.sr |= sr;\n"); makefromsr(); out("exception3_read_prefetch_68040bug(opcode, pc, oldsr);\n"); } write_return_cycles(0); out("}\n"); } out("regs.sr &= 0xFF00; sr &= 0xFF;\n"); out("regs.sr |= sr;\n"); makefromsr(); setpc ("pc"); if (cpu_level < 4) { out("if (%s & 1) {\n", getpc); out("uaecptr faultpc = %s;\n", getpc); setpc("oldpc + 2"); out("exception3_read_prefetch_only(opcode, faultpc);\n"); write_return_cycles(0); out("}\n"); } clear_m68k_offset(); if (using_prefetch || using_ce) { out("int pcadjust = oldpc - m68k_getpci() + 2;\n"); fill_prefetch_full_000_special(-1, NULL); } else { fill_prefetch_full(0); } branch_inst = m68k_pc_total; tail_ce020_done = true; next_cpu_level = cpu_level - 1; break; case i_JSR: { // possible idle cycle, prefetch from new address, stack high return addr, stack low, prefetch no_prefetch_ce020 = true; genamode(curi, curi->smode, "srcreg", curi->size, "src", 0, 0, GF_AA | GF_NOREFILL); out("uaecptr oldpc = %s;\n", getpc); out("uaecptr nextpc = oldpc + %d;\n", m68k_pc_offset); if (using_exception_3 && cpu_level <= 1) { push_ins_cnt(); out("if (srca & 1) {\n"); if (curi->smode == Ad16 || curi->smode == absw || curi->smode == PC16) { addcycles000_onlyce(2); addcycles000_nonce(2); } if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000_onlyce(6); addcycles000_nonce(6); } if (curi->smode == absl) { if (cpu_level == 0) { incpc("6"); } else { incpc("2"); } } else if (curi->smode == absw) { incpc("4"); } else { incpc("2"); } out("exception3_read_prefetch_only(opcode, srca);\n"); write_return_cycles_noadd(0); out("}\n"); pop_ins_cnt(); } if (using_mmu) { out("%s(m68k_areg(regs, 7) - 4, nextpc);\n", dstl); out("m68k_areg(regs, 7) -= 4;\n"); setpc("srca"); clear_m68k_offset(); } else { if (curi->smode == Ad16 || curi->smode == absw || curi->smode == PC16) addcycles000(2); if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000(6); #ifdef WINUAE_FOR_HATARI /* Hatari : JSR in Ad8r and PC8r mode takes 22 cycles, but on ST it takes 24 cycles */ /* because of an unaligned memory prefetch in this EA mode */ /* We add 2 cycles only in 68000 prefetch mode, 68000 CE mode is handled at the memory access level */ if ( using_prefetch && !using_ce ) addcycles000(2); #endif if (cpu_level <= 1 && using_prefetch) out("nextpc += 2;\n"); } setpc("srca"); clear_m68k_offset(); if (cpu_level >= 2 && cpu_level < 4) { out("m68k_areg(regs, 7) -= 4;\n"); } if (using_exception_3 && cpu_level >= 2) { out("if (%s & 1) {\n", getpc); out("exception3_read_prefetch(opcode, %s);\n", getpc); write_return_cycles(0); out("}\n"); } if (curi->smode == absl) { sprintf(bus_error_code, "pcoffset = (oldpc - srca) + 6;\n"); } else if (curi->smode == Ad8r || curi->smode == PC8r || curi->smode == Ad16 || curi->smode == PC16 || curi->smode == absw) { sprintf(bus_error_code, "pcoffset = (oldpc - srca) + 4;\n"); } else { sprintf(bus_error_code, "pcoffset = (oldpc - srca) + 2;\n"); } fill_prefetch_1(0); bus_error_code[0] = 0; if (cpu_level < 2) { out("m68k_areg(regs, 7) -= 4;\n"); } if (using_exception_3 && cpu_level <= 1) { out("if (m68k_areg(regs, 7) & 1) {\n"); setpc("oldpc"); if (curi->smode == absl) { incpc("6"); } else if (curi->smode == Ad8r || curi->smode == PC8r || curi->smode == Ad16 || curi->smode == PC16 || curi->smode == absw) { incpc("4"); } else { incpc("2"); } if (cpu_level == 1) { out("exception3_write_access(opcode, m68k_areg(regs, 7), 1, oldpc >> 16, 1);\n"); } else { out("exception3_write_access(opcode, m68k_areg(regs, 7), 1, m68k_areg(regs, 7) >> 16, 1);\n"); } write_return_cycles(0); out("}\n"); } if (using_ce || using_prefetch) { out("uaecptr dsta = m68k_areg(regs, 7);\n"); out("%s(dsta, nextpc >> 16);\n", dstw); count_writew++; check_bus_error("dst", 0, 1, 1, "nextpc >> 16", 1, 0); out("%s(dsta + 2, nextpc);\n", dstw); count_writew++; check_bus_error("dst", 2, 1, 1, "nextpc", 1, 0); } else { if (cpu_level < 4) out("%s(m68k_areg(regs, 7), nextpc);\n", dstl); else out("%s(m68k_areg(regs, 7) - 4, nextpc);\n", dstl); count_writel++; } if (cpu_level >= 4) out("m68k_areg(regs, 7) -= 4;\n"); if (using_debugmem) { out("#ifdef DEBUGGER\n"); out("if (debugmem_trace) {\n"); out("branch_stack_push(oldpc, nextpc);\n"); out("}\n"); out("#endif\n"); } } fill_prefetch_full_020(); if (using_prefetch || using_ce) { int sp = (curi->smode == Ad16 || curi->smode == absw || curi->smode == absl || curi->smode == PC16 || curi->smode == Ad8r || curi->smode == PC8r) ? -1 : 0; irc2ir(); copy_opcode(); if (sp < 0 && cpu_level == 0) out("if(regs.t1) opcode |= 0x10000;\n"); out("%s(%d);\n", prefetch_word, 2); count_readw++; check_prefetch_bus_error(2, 0, 0); did_prefetch = 1; ir2irc = 0; } else { fill_prefetch_next_empty(); } branch_inst = m68k_pc_total; next_level_040_to_030(); next_level_000(); } break; case i_JMP: no_prefetch_ce020 = true; genamode(curi, curi->smode, "srcreg", curi->size, "src", 0, 0, GF_AA|GF_NOREFILL); out("uaecptr oldpc = %s;\n", getpc); if (using_exception_3) { push_ins_cnt(); out("if (srca & 1) {\n"); if (curi->smode == Ad16 || curi->smode == absw || curi->smode == PC16) { addcycles000_onlyce(2); addcycles000_nonce(2); } if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000_onlyce(6); addcycles000_nonce(6); } incpc("2"); out("exception3_read_prefetch_only(opcode, srca);\n"); write_return_cycles_noadd(0); out("}\n"); pop_ins_cnt(); } if (curi->smode == Ad16 || curi->smode == absw || curi->smode == PC16) addcycles000(2); if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000(6); #ifdef WINUAE_FOR_HATARI /* Hatari : JMP in Ad8r and PC8r mode takes 22 cycles, but on ST it takes 24 cycles */ /* because of an unaligned memory prefetch in this EA mode */ /* We add 2 cycles only in 68000 prefetch mode, 68000 CE mode is handled at the memory access level */ if ( using_prefetch && !using_ce ) addcycles000(2); #endif } setpc ("srca"); clear_m68k_offset(); if (using_prefetch || using_ce) { out("%s(%d);\n", prefetch_word, 0); count_readw++; sprintf(bus_error_code, "pcoffset = (oldpc - srca) + 2;\n"); check_prefetch_bus_error(-1, -1, 0); bus_error_code[0] = 0; irc2ir(); set_last_access_ipl(); out("%s(%d);\n", prefetch_word, 2); int sp = (curi->smode == Ad16 || curi->smode == absw || curi->smode == absl || curi->smode == PC16 || curi->smode == Ad8r || curi->smode == PC8r) ? -1 : 0; copy_opcode(); if (sp < 0 && cpu_level == 0) out("if(regs.t1) opcode |= 0x10000;\n"); count_readw++; check_prefetch_bus_error(2, 0, 0); did_prefetch = 1; ir2irc = 0; } else { fill_prefetch_full(0); } branch_inst = m68k_pc_total; next_level_000(); break; case i_BSR: // .b/.w = idle cycle, store high, store low, 2xprefetch if (isce020()) no_prefetch_ce020 = true; out("uae_s32 s;\n"); if (curi->size == sz_long && cpu_level < 2) { out("uae_u32 src = 0xffffffff;\n"); } else { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_AA|GF_NOREFILL); } out("s = (uae_s32)src + 2;\n"); addcycles000(2); out("uaecptr oldpc = %s;\n", getpc); out("uaecptr nextpc = oldpc + %d;\n", m68k_pc_offset); if (using_exception_3) { if (cpu_level == 0) { out("if (m68k_areg(regs, 7) & 1) {\n"); out("m68k_areg(regs, 7) -= 4;\n"); incpc("2"); out("exception3_write_access(opcode, m68k_areg(regs, 7), sz_word, oldpc, 1);\n"); write_return_cycles(0); out("}\n"); } else if (cpu_level == 1) { out("if (m68k_areg(regs, 7) & 1) {\n"); incpc("2"); out("exception3_write_access(opcode, oldpc + s, sz_word, oldpc, 1);\n"); write_return_cycles(0); out("}\n"); } } if (using_exception_3 && cpu_level == 1) { // 68010 TODO: looks like prefetches are done first and stack writes last out("if (s & 1) {\n"); incpc("2"); out("exception3_read_prefetch_only(opcode, oldpc + s);\n"); write_return_cycles(0); out("}\n"); } if (using_exception_3 && cpu_level >= 2) { out("if (s & 1) {\n"); if (cpu_level < 4) out("m68k_areg(regs, 7) -= 4;\n"); out("exception3_read_prefetch(opcode, oldpc + s);\n"); write_return_cycles(0); out("}\n"); } if (using_indirect > 0 && !using_ce020 && !using_prefetch_020 && !using_ce && !using_test) { out("m68k_do_bsri_jit(nextpc, s);\n"); count_writel++; } else if (using_mmu) { out("m68k_do_bsr_mmu%s(nextpc, s);\n", mmu_postfix); count_writel++; } else if (using_ce020 == 1) { out("m68k_do_bsr_ce020(nextpc, s);\n"); count_writel++; } else if (using_ce020 == 2) { out("m68k_do_bsr_ce030(nextpc, s);\n"); count_writel++; } else if (using_ce || using_prefetch || (using_test && cpu_level <= 1)) { out("m68k_areg(regs, 7) -= 4;\n"); out("uaecptr dsta = m68k_areg(regs, 7);\n"); out("%s(dsta, nextpc >> 16);\n", dstw); check_bus_error("dst", 0, 1, 1, "nextpc >> 16", 1, 0); count_writew++; out("%s(dsta + 2, nextpc);\n", dstw); check_bus_error("dst", 2, 1, 1, "nextpc", 1, 0); count_writew++; incpc("s"); } else if (using_prefetch_020 || (using_test && cpu_level >= 2)) { out("m68k_do_bsri(nextpc, s);\n"); count_writel++; } else { out("m68k_do_bsr(nextpc, s);\n"); count_writel++; } if (using_exception_3 && cpu_level == 0) { // both stacked fields point to new PC out("if (%s & 1) {\n", getpc); out("uaecptr addr = %s;\n", getpc); incpc("-2"); out("exception3_read_prefetch(opcode, addr);\n"); write_return_cycles(0); out("}\n"); } if (using_debugmem) { out("#ifdef DEBUGGER\n"); out("if (debugmem_trace) {\n"); out("branch_stack_push(oldpc, nextpc);\n"); out("}\n"); out("#endif\n"); } clear_m68k_offset(); if (using_prefetch || using_ce) { fill_prefetch_full_000_special(0, NULL); } else { fill_prefetch_full(0); } branch_inst = m68k_pc_total; if (!next_level_040_to_030()) { if (!next_level_020_to_010()) next_level_000(); } break; case i_Bcc: out("uaecptr oldpc = %s;\n", getpc); tail_ce020_done = true; ipl_fetched = 10; if (curi->size == sz_long) { if (cpu_level < 2) { addcycles000(2); out("if (cctrue(%d)) {\n", curi->cc); out("exception3_read_prefetch(opcode, %s + 1);\n", getpc); write_return_cycles(0); out("}\n"); sync_m68k_pc(); addcycles000(2); irc2ir(); fill_prefetch_bcc(); goto bccl_not68020; } else { if (next_cpu_level < 1) next_cpu_level = 1; } } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_AA | (cpu_level < 2 ? GF_NOREFILL : 0)); addcycles000(2); if (using_exception_3 && cpu_level >= 4) { out("if (src & 1) {\n"); out("exception3_read_prefetch(opcode, %s + (uae_s32)src + 2);\n", getpc); write_return_cycles(0); out("}\n"); } out("if (cctrue(%d)) {\n", curi->cc); if (using_exception_3 && cpu_level < 4) { out("if (src & 1) {\n"); if (cpu_level == 1) { out("uaecptr oldpc = %s;\n", getpc); out("uae_u16 rb = regs.irc;\n"); incpc("((uae_s32)src + 2) & ~1"); dummy_prefetch(NULL, "oldpc"); out("uaecptr newpc = %s + (uae_s32)src + 2;\n", getpc); incpc("2"); out("regs.read_buffer = rb;\n"); out("exception3_read_prefetch(opcode, newpc);\n"); } else { // Addr = new address, PC = current PC out("uaecptr addr = %s + (uae_s32)src + 2;\n", getpc); out("exception3_read_prefetch(opcode, addr);\n"); } write_return_cycles(0); out("}\n"); } push_ins_cnt(); if (using_prefetch) { incpc("(uae_s32)src + 2"); out("int pcadjust = oldpc - m68k_getpci() + 2;\n"); fill_prefetch_full_000_special(-1, NULL); if (using_ce) out("return;\n"); else out("return 10 * CYCLE_UNIT / 2;\n"); } else { incpc ("(uae_s32)src + 2"); add_head_cycs (6); fill_prefetch_full_020(); returncycles (10); } pop_ins_cnt(); out("}\n"); sync_m68k_pc(); if (cpu_level == 1) { if (curi->size != sz_byte) addcycles000(2); } else if (cpu_level == 0) { addcycles000(2); } get_prefetch_020_continue(); if (curi->size == sz_byte) { irc2ir(); add_head_cycs (4); fill_prefetch_bcc(); } else if (curi->size == sz_word) { add_head_cycs (6); fill_prefetch_full_000_special(0, NULL); } else { add_head_cycs (6); fill_prefetch_full_000_special(0, NULL); } insn_n_cycles = curi->size == sz_byte ? 8 : 12; branch_inst = -m68k_pc_total; bccl_not68020: if (!next_level_040_to_030()) next_level_020_to_010(); break; case i_LEA: if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000(2); } if (curi->smode == absl) { if (cpu_level == 0) { strcpy(bus_error_code, "m68k_areg(regs, dstreg) = (m68k_areg(regs, dstreg) & 0x0000ffff) | (srca & 0xffff0000);\n"); strcpy(bus_error_code2, "m68k_areg(regs, dstreg) = (srca);\n"); } next_level_000(); } else if (curi->smode != Ad8r && curi->smode != PC8r) { if (cpu_level == 0) { strcpy(bus_error_code, "m68k_areg(regs, dstreg) = (srca);\n"); } } genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 0, GF_AA | ((curi->smode == absw) ? GF_PCM2 : 0), curi->dmode, "dstreg", curi->size, "dst", 2, GF_AA); if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000(2); #ifdef WINUAE_FOR_HATARI /* Hatari : LEA in Ad8r and PC8r mode takes 12 cycles, but on ST it takes 14 cycles */ /* because of an unaligned memory prefetch in this EA mode */ /* We add 2 cycles only in 68000 prefetch mode, 68000 CE mode is handled at the memory access level */ if ( using_prefetch && !using_ce ) addcycles000(2); #endif } genastore("srca", curi->dmode, "dstreg", curi->size, "dst"); set_ipl(); fill_prefetch_next_t(); break; case i_PEA: if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000(2); set_ipl(); ipl = 1; } if (cpu_level <= 1 && using_exception_3) { out("uae_u16 old_opcode = opcode;\n"); } genamode(curi, curi->smode, "srcreg", curi->size, "src", 0, 0, GF_AA); genamode(NULL, Apdi, "7", sz_long, "dst", 2, 0, GF_AA | GF_NOEXC3); if (curi->smode == Ad8r || curi->smode == PC8r) { addcycles000(2); #ifdef WINUAE_FOR_HATARI /* Hatari : PEA in Ad8r and PC8r mode takes 20 cycles, but on ST it takes 22 cycles */ /* because of an unaligned memory prefetch in this EA mode */ /* We add 2 cycles only in 68000 prefetch mode, 68000 CE mode is handled at the memory access level */ if ( using_prefetch && !using_ce ) addcycles000(2); #endif } if (!(curi->smode == absw || curi->smode == absl)) { if (!ipl) { set_ipl_pre(); ipl = 1; } fill_prefetch_next_after(0, "m68k_areg(regs, 7) += 4;\n"); } if (cpu_level <= 1 && using_exception_3) { out("if (dsta & 1) {\n"); out("regs.ir = old_opcode;\n"); if (cpu_level == 1) { incpc("2"); } else { incpc("%d", m68k_pc_offset + ((curi->smode == absw || curi->smode == absl) ? 0 : 2)); } out("exception3_write_access(old_opcode, dsta, sz_word, srca >> 16, 1);\n"); write_return_cycles(0); out("}\n"); } if ((curi->smode == absw || curi->smode == absl)) { genastore("srca", Apdi, "7", sz_long, "dst"); if (!ipl) { set_ipl(); } fill_prefetch_next_t(); } else { genastore_2("srca", Apdi, "7", sz_long, "dst", 0, 0); } break; case i_DBcc: // cc true: idle cycle, prefetch // cc false, counter expired: idle cycle, prefetch (from branch address), 2xprefetch (from next address) // cc false, counter not expired: idle cycle, prefetch tail_ce020_done = true; ipl_fetched = 10; if(cpu_level <= 1) { // this is quite annoying instruction.. out("int pcadjust = -2;\n"); } genamodedual(curi, curi->smode, "srcreg", curi->size, "src", 1, GF_AA | (cpu_level < 2 ? GF_NOREFILL : 0), curi->dmode, "dstreg", curi->size, "offs", 1, GF_AA | (cpu_level < 2 ? GF_NOREFILL : 0)); if (cpu_level == 1 && using_prefetch) { out("int was_loop_mode = regs.loop_mode;\n"); out("regs.loop_mode = 0;\n"); } out("uaecptr oldpc = %s;\n", getpc); addcycles000(2); if (using_exception_3 && cpu_level >= 4) { out("if (offs & 1) {\n"); out("exception3_read_prefetch(opcode, oldpc + (uae_s32)offs + 2);\n"); write_return_cycles(0); out("}\n"); } push_ins_cnt(); out("if (!cctrue(%d)) {\n", curi->cc); incpc ("(uae_s32)offs + 2"); if (cpu_level >= 2 && cpu_level < 4) { genastore("(src - 1)", curi->smode, "srcreg", curi->size, "src"); } if (using_exception_3 && cpu_level < 4) { out("if (offs & 1) {\n"); if (cpu_level == 1 && using_prefetch) { out("%s(%d);\n", prefetch_word, -1); } out("exception3_read_prefetch(opcode, %s);\n", getpc); write_return_cycles(0); out("}\n"); } // 68010 loop mode handling if (cpu_level == 1 && using_prefetch) { push_ins_cnt(); out("if(offs == -4 && !regs.t1 && loop_mode_table[regs.ird]) {\n"); // first loop takes as many cycles as normal DBcc branch // because it does 2xprefetch out("if(!was_loop_mode) {\n"); out("uae_u16 irc = regs.irc;\n"); // read looping instruction opcode addcycles000_nonce(4); out("%s(%d);\n", prefetch_word, 0); check_prefetch_bus_error(-1, 0, -1); // read dbcc opcode addcycles000_nonce(4); out("%s(%d);\n", prefetch_word, 2); check_prefetch_bus_error(-2, 0, -1); out("regs.irc = irc;\n"); out("} else {\n"); addcycles000(2); addcycles000_nonce(2); out("}\n"); out("regs.loop_mode = 1;\n"); out("src = m68k_dreg(regs, srcreg);\n"); genastore("(src - 1)", curi->smode, "srcreg", curi->size, "src"); out("if (src) {\n"); if (using_ce) { out("loop_mode_table[regs.ird](regs.ird);\n"); } else if (!using_nocycles) { out("count_cycles += loop_mode_table[regs.ird](regs.ird);\n"); } out(" // quick exit if condition false and count expired\n"); out("if (!cctrue(%d) && (m68k_dreg(regs, srcreg) & 0xffff) == 0) {\n", curi->cc); out("m68k_dreg(regs, srcreg) |= 0xffff;\n"); out(" // loop exit: add possible extra cycle(s)\n"); out("if(regs.loop_mode >> 16) {\n"); if (using_ce) { out("%s(regs.loop_mode >> 16);\n", do_cycles); } addcycles000_nonces("regs.loop_mode >> 16"); out("}\n"); out("regs.loop_mode = 0;\n"); setpc("oldpc + %d", m68k_pc_offset); int old_m68k_pc_offset = m68k_pc_offset; int old_m68k_pc_total = m68k_pc_total; clear_m68k_offset(); count_cycles -= 4; get_prefetch_020_continue(); fill_prefetch_full_000_special(1, NULL); count_cycles += 4; returncycles(8); m68k_pc_offset = old_m68k_pc_offset; m68k_pc_total = old_m68k_pc_total; out("}\n"); out(" // loop continue: add possible extra cycle(s)\n"); out("if(regs.loop_mode & 0xfffe) {\n"); addcycles000_nonces("regs.loop_mode & 0xfffe"); if (using_ce) { out("%s(regs.loop_mode & 0xfffe);\n", do_cycles); } out("}\n"); setpc("oldpc"); check_ipl_always(); returncycles(2); out("}\n"); out("regs.loop_mode = 0;\n"); setpc("oldpc + %d", m68k_pc_offset); fill_prefetch_full_000_special(1, NULL); returncycles(8); out("}\n"); pop_ins_cnt(); } sprintf(bus_error_code, "pcoffset = oldpc - %s + 4;\n", getpc); fill_prefetch_1(0); bus_error_code[0] = 0; if (cpu_level >= 4) { genastore("(src - 1)", curi->smode, "srcreg", curi->size, "src"); } out("if (src) {\n"); if (cpu_level < 2) { genastore("(src - 1)", curi->smode, "srcreg", curi->size, "src"); } irc2ir(); add_head_cycs (6); if (using_prefetch || using_ce) { copy_opcode(); if (cpu_level == 0) { out("if(regs.t1) opcode |= 0x10000;\n"); } if (using_ce) { out("ipl_fetch_next();\n"); } out("%s(%d);\n", prefetch_word, 2); check_prefetch_bus_error(-2, 0, -1); did_prefetch = 1; ir2irc = 0; count_readw++; } fill_prefetch_full_020(); returncycles(10); out("}\n"); if (cpu_level == 0) { addcycles000_nonce(2); } else { addcycles000_onlyce(2); addcycles000_nonce(6); } add_head_cycs (10); if (cpu_level <= 1) { out("pcadjust = 0;\n"); } if (cpu_level == 0 && using_ce) { out("} else {\n"); addcycles000_onlyce(2); } out("}\n"); pop_ins_cnt(); if (cpu_level == 0) { insn_n_cycles += 2; count_cycles += 2; } setpc ("oldpc + %d", m68k_pc_offset); clear_m68k_offset(); get_prefetch_020_continue(); fill_prefetch_full_000_special(-1, "if (!cctrue(%d)) {\nm68k_dreg(regs, srcreg) = (m68k_dreg(regs, srcreg) & ~0xffff) | (((src - 1)) & 0xffff);\n}\n", curi->cc); branch_inst = -m68k_pc_total; if (!next_level_040_to_030()) { if (!next_level_020_to_010()) next_level_000(); } break; case i_Scc: genamode(curi, curi->smode, "srcreg", curi->size, "src", cpu_level == 0 ? 1 : 2, 0, cpu_level == 1 ? GF_NOFETCH : 0); if (isreg(curi->smode)) { // If mode is Dn and condition true = 2 extra cycles needed. out("int val = cctrue(%d) ? 0xff : 0x00;\n", curi->cc); if (using_ce) out("int cycles = val ? 2 : 0;\n"); if (using_bus_error && cpu_level <= 1) { out("if (!val) {\n"); genastore("val", curi->smode, "srcreg", curi->size, "src"); out("}\n"); if (cpu_level == 0) { out("opcode |= 0x20000;\n"); } } fill_prefetch_next_extra("if (!val)", "if(!val && regs.t1) opcode |= 0x10000;\n"); genastore("val", curi->smode, "srcreg", curi->size, "src"); addcycles000_3(false); addcycles000_nonces("(val ? 2 : 0)"); } else { fill_prefetch_next_after(1, NULL); out("int val = cctrue(%d) ? 0xff : 0x00;\n", curi->cc); genastore("val", curi->smode, "srcreg", curi->size, "src"); } next_level_000(); break; case i_DIVU: exception_oldpc(); tail_ce020_done = true; genamodedual(curi, curi->smode, "srcreg", sz_word, "src", 1, 0, curi->dmode, "dstreg", sz_long, "dst", 1, 0); push_ins_cnt(); out("if (src == 0) {\n"); out("divbyzero_special(0, dst);\n"); incpc("%d", m68k_pc_offset); addcycles000(4); exception_cpu("5"); write_return_cycles(0); out("}\n"); pop_ins_cnt(); out("uae_u32 newv = (uae_u32)dst / (uae_u32)(uae_u16)src;\n"); out("uae_u32 rem = (uae_u32)dst %% (uae_u32)(uae_u16)src;\n"); if (using_ce) { out("int cycles = getDivu68kCycles((uae_u32)dst, (uae_u16)src);\n"); addcycles000_3(true); } if (cpu_level <= 1) { addcycles000_nonces("getDivu68kCycles((uae_u32)dst, (uae_u16)src)"); } out("if (newv > 0xffff) {\n"); out("setdivuflags((uae_u32)dst, (uae_u16)src);\n"); out("} else {\n"); genflags (flag_logical, sz_word, "newv", "", ""); out("newv = (newv & 0xffff) | ((uae_u32)rem << 16);\n"); genastore("newv", curi->dmode, "dstreg", sz_long, "dst"); out("}\n"); set_ipl(); fill_prefetch_next_t(); sync_m68k_pc(); count_ncycles++; if (!do_always_dynamic_cycles) { insn_n_cycles += 136 - (136 - 76) / 3; /* average */ } addcycles_020(34); tail_ce020_done = false; returntail(false); next_level_020_to_010(); break; case i_DIVS: ipl_fetched = 10; exception_oldpc(); tail_ce020_done = true; genamodedual(curi, curi->smode, "srcreg", sz_word, "src", 1, 0, curi->dmode, "dstreg", sz_long, "dst", 1, 0); push_ins_cnt(); out("if (src == 0) {\n"); out("divbyzero_special(1, dst);\n"); incpc("%d", m68k_pc_offset); addcycles000(4); exception_cpu("5"); write_return_cycles(0); out("}\n"); pop_ins_cnt(); if (using_ce || cpu_level <= 1) { out("int extra = 0;\n"); } if (using_ce) { out("int cycles = getDivs68kCycles((uae_s32)dst, (uae_s16)src, &extra);\n"); out("if (extra) {\n"); out("cycles -= 2;\n"); addcycles000_3(true); out("ipl_fetch_next();\n"); out("cycles = 2;\n"); addcycles000_3(true); out("} else {\n"); addcycles000_3(true); out("ipl_fetch_next();\n"); out("}\n"); } if (cpu_level <= 1) { addcycles000_nonces("getDivs68kCycles((uae_s32)dst, (uae_s16)src, &extra)"); } out("if (dst == 0x80000000 && src == -1) {\n"); out("setdivsflags((uae_s32)dst, (uae_s16)src);\n"); out("} else {\n"); out("uae_s32 newv = (uae_s32)dst / (uae_s32)(uae_s16)src;\n"); out("uae_u16 rem = (uae_s32)dst %% (uae_s32)(uae_s16)src;\n"); out("if ((newv & 0xffff8000) != 0 && (newv & 0xffff8000) != 0xffff8000) {\n"); out("setdivsflags((uae_s32)dst, (uae_s16)src);\n"); out("} else {\n"); out("if (((uae_s16)rem < 0) != ((uae_s32)dst < 0)) rem = -rem;\n"); genflags(flag_logical, sz_word, "newv", "", ""); out("newv = (newv & 0xffff) | ((uae_u32)rem << 16);\n"); genastore("newv", curi->dmode, "dstreg", sz_long, "dst"); out("}\n"); out("}\n"); fill_prefetch_next_t(); sync_m68k_pc(); count_ncycles++; if (!do_always_dynamic_cycles) { insn_n_cycles += 156 - (156 - 120) / 3; /* average */ } addcycles_020(48); tail_ce020_done = false; returntail(false); next_level_020_to_010(); break; case i_MULU: genamodedual(curi, curi->smode, "srcreg", sz_word, "src", 1, 0, curi->dmode, "dstreg", sz_word, "dst", 1, 0); fill_prefetch_next_after(1, "CLEAR_CZNV();\n" "SET_ZFLG(1);\n" "pcoffset -= %d;\n" "m68k_dreg(regs, dstreg) &= 0xffff0000;\n", // strange EA behavior, no other instruction work like this curi->smode == Apdi ? 0 : (curi->smode == absl || curi->smode == absw || curi->smode == imm ? 2 : m68k_pc_offset)); out("uae_u32 newv = (uae_u32)(uae_u16)dst * (uae_u32)(uae_u16)src;\n"); genflags (flag_logical, sz_long, "newv", "", ""); if (using_ce) { out("int cycles = getMulu68kCycles(src);\n"); addcycles000_3(true); } if (cpu_level <= 1) { addcycles000_nonces("getMulu68kCycles(src)"); } addcycles_020(20); genastore("newv", curi->dmode, "dstreg", sz_long, "dst"); sync_m68k_pc(); count_ncycles++; if (!do_always_dynamic_cycles) { insn_n_cycles += (70 - 38) / 3 + 38; /* average */ } next_level_020_to_010(); break; case i_MULS: genamodedual(curi, curi->smode, "srcreg", sz_word, "src", 1, 0, curi->dmode, "dstreg", sz_word, "dst", 1, 0); fill_prefetch_next_after(1, "CLEAR_CZNV();\n" "SET_ZFLG(1);\n" "pcoffset -= %d;\n" "m68k_dreg(regs, dstreg) &= 0xffff0000;\n", curi->smode == Apdi ? 0 : (curi->smode == absl || curi->smode == absw || curi->smode == imm ? 2 : m68k_pc_offset)); out("uae_u32 newv = (uae_s32)(uae_s16)dst * (uae_s32)(uae_s16)src;\n"); genflags (flag_logical, sz_long, "newv", "", ""); if (using_ce) { out("int cycles = getMuls68kCycles(src);\n"); addcycles000_3(true); } if (cpu_level <= 1) { addcycles000_nonces("getMuls68kCycles(src)"); } addcycles_020(20); genastore("newv", curi->dmode, "dstreg", sz_long, "dst"); count_ncycles++; if (!do_always_dynamic_cycles) { insn_n_cycles += (70 - 38) / 3 + 38; /* average */ } next_level_020_to_010(); break; case i_CHK: disable_noflags = 1; ipl_fetched = 10; exception_oldpc(); genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_IPL); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, 0); sync_m68k_pc(); addcycles000(4); out("if (dst > src) {\n"); out("setchkundefinedflags(src, dst, %d);\n", curi->size); exception_cpu("6"); write_return_cycles(0); out("}\n"); addcycles000(2); out("if ((uae_s32)dst < 0) {\n"); out("setchkundefinedflags(src, dst, %d);\n", curi->size); exception_cpu("6"); write_return_cycles(0); out("}\n"); out("setchkundefinedflags(src, dst, %d);\n", curi->size); set_ipl(); fill_prefetch_next_t(); break; case i_CHK2: disable_noflags = 1; exception_oldpc(); genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 2, 0, 0); fill_prefetch_0(); out("uae_s32 upper,lower,reg = regs.regs[(extra >> 12) & 15];\n"); switch (curi->size) { case sz_byte: out("lower = (uae_s32)(uae_s8)%s(dsta);\nupper = (uae_s32)(uae_s8)%s(dsta + 1);\n", srcb, srcb); out("if ((extra & 0x8000) == 0) reg = (uae_s32)(uae_s8)reg;\n"); break; case sz_word: out("lower = (uae_s32)(uae_s16)%s(dsta);\nupper = (uae_s32)(uae_s16)%s(dsta + 2);\n", srcw, srcw); out("if ((extra & 0x8000) == 0) reg = (uae_s32)(uae_s16)reg;\n"); break; case sz_long: out("lower = %s(dsta); upper = %s(dsta + 4);\n", srcl, srcl); break; default: term(); } sync_m68k_pc(); out("SET_CFLG(0);\n"); out("SET_ZFLG(0);\n"); out("setchk2undefinedflags(lower, upper, reg, (extra & 0x8000) ? 2 : %d);\n", curi->size); out("if(upper == reg || lower == reg) {\n"); out("SET_ZFLG(1);\n"); out("}else{\n"); out("if (lower <= upper && (reg < lower || reg > upper)) SET_ALWAYS_CFLG(1);\n"); out("if (lower > upper && reg > upper && reg < lower) SET_ALWAYS_CFLG(1);\n"); out("}\n"); out("if ((extra & 0x800) && GET_CFLG()) {\n"); exception_cpu("6"); write_return_cycles(0); out("}\n"); break; case i_ASR: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\n", bit_mask(curi->size), cmask(curi->size)); } out("uae_u32 sign = (%s & val) >> %d;\n", cmask (curi->size), bit_size (curi->size) - 1); out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); out("if (cnt >= %d) {\n", bit_size (curi->size)); out("val = %s & (uae_u32)(0 - sign);\n", bit_mask (curi->size)); out("SET_CFLG(sign);\n"); duplicate_carry(); if (source_is_imm1_8 (curi)) out("} else {\n"); else out("} else if (cnt > 0) {\n"); out("val >>= cnt - 1;\n"); out("SET_CFLG(val & 1);\n"); duplicate_carry(); out("val >>= 1;\n"); out("val |= (%s << (%d - cnt)) & (uae_u32)(0 - sign);\n", bit_mask (curi->size), bit_size (curi->size)); out("val &= %s;\n", bit_mask (curi->size)); out("}\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_ASL: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\n", bit_mask(curi->size), cmask(curi->size)); } set_ipl(); out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); out("if (cnt >= %d) {\n", bit_size (curi->size)); out("SET_VFLG(val != 0);\n"); out("SET_CFLG(cnt == %d ? val & 1 : 0);\n", bit_size (curi->size)); duplicate_carry(); out("val = 0;\n"); if (source_is_imm1_8 (curi)) out("} else {\n"); else out("} else if (cnt > 0) {\n"); out("uae_u32 mask = (%s << (%d - cnt)) & %s;\n", bit_mask (curi->size), bit_size (curi->size) - 1, bit_mask (curi->size)); out("SET_VFLG((val & mask) != mask && (val & mask) != 0);\n"); out("val <<= cnt - 1;\n"); out("SET_CFLG((val & %s) >> %d);\n", cmask (curi->size), bit_size (curi->size) - 1); duplicate_carry(); out("val <<= 1;\n"); out("val &= %s;\n", bit_mask (curi->size)); out("}\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_LSR: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\n", bit_mask(curi->size), cmask(curi->size)); } out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); out("if (cnt >= %d) {\n", bit_size (curi->size)); out("SET_CFLG((cnt == %d) & (val >> %d));\n", bit_size (curi->size), bit_size (curi->size) - 1); duplicate_carry(); out("val = 0;\n"); if (source_is_imm1_8 (curi)) out("} else {\n"); else out("} else if (cnt > 0) {\n"); out("val >>= cnt - 1;\n"); out("SET_CFLG(val & 1);\n"); duplicate_carry(); out("val >>= 1;\n"); out("}\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_LSL: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\n", bit_mask(curi->size), cmask(curi->size)); } out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); out("if (cnt >= %d) {\n", bit_size (curi->size)); out("SET_CFLG(cnt == %d ? val & 1 : 0);\n", bit_size(curi->size)); duplicate_carry(); out("val = 0;\n"); if (source_is_imm1_8(curi)) out("} else {\n"); else out("} else if (cnt > 0) {\n"); out("val <<= (cnt - 1);\n"); out("SET_CFLG((val & %s) >> %d);\n", cmask(curi->size), bit_size(curi->size) - 1); duplicate_carry(); out("val <<= 1;\n"); out("val &= %s;\n", bit_mask (curi->size)); out("}\n"); genflags(flag_logical_noclobber, curi->size, "val", "", ""); shift_ce(curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROL: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\n", bit_mask(curi->size), cmask(curi->size)); } out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); if (source_is_imm1_8 (curi)) out("{\n"); else out("if (cnt > 0) {\n"); out("uae_u32 loval;\n"); out("cnt &= %d;\n", bit_size (curi->size) - 1); out("loval = val >> (%d - cnt);\n", bit_size (curi->size)); out("val <<= cnt;\n"); out("val |= loval;\n"); out("val &= %s;\n", bit_mask (curi->size)); out("SET_CFLG(val & 1);\n"); out("}\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROR: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\n", bit_mask(curi->size), cmask(curi->size)); } out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); if (source_is_imm1_8 (curi)) out("{\n"); else out("if (cnt > 0) {\n"); out("uae_u32 hival;\n"); out("cnt &= %d;\n", bit_size (curi->size) - 1); out("hival = val << (%d - cnt);\n", bit_size (curi->size)); out("val >>= cnt;\n"); out("val |= hival;\n"); out("val &= %s;\n", bit_mask (curi->size)); out("SET_CFLG((val & %s) >> %d);\n", cmask (curi->size), bit_size (curi->size) - 1); out("}\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROXL: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\nSET_CFLG(GET_XFLG());\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\nSET_CFLG(GET_XFLG());\n", bit_mask(curi->size), cmask(curi->size)); } out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); if (source_is_imm1_8 (curi)) out("{\n"); else { force_range_for_rox ("cnt", curi->size); out("if (cnt > 0) {\n"); } out("cnt--;\n"); out("{\nuae_u32 carry;\n"); out("uae_u32 loval = val >> (%d - cnt);\n", bit_size (curi->size) - 1); out("carry = loval & 1;\n"); out("val = (((val << 1) | GET_XFLG()) << cnt) | (loval >> 1);\n"); out("SET_XFLG(carry);\n"); out("val &= %s;\n", bit_mask (curi->size)); out("}\n"); out("}\n"); out("SET_CFLG(GET_XFLG());\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROXR: genamodedual(curi, curi->smode, "srcreg", curi->size, "cnt", 1, 0, curi->dmode, "dstreg", curi->size, "data", 1, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } out("CLEAR_CZNV();\n"); set_ipl_pre(); if (curi->size == sz_long) { fill_prefetch_next_noopcodecopy("SET_NFLG(val & 0x8000);\nSET_ZFLG(!(val & 0xffff));\nSET_CFLG(GET_XFLG());\n"); } else { fill_prefetch_next_noopcodecopy("SET_ZFLG(!(val & %s));\nSET_NFLG(val & %s);\nSET_CFLG(GET_XFLG());\n", bit_mask(curi->size), cmask(curi->size)); } out("int ccnt = cnt & 63;\n"); out("cnt &= 63;\n"); if (source_is_imm1_8 (curi)) out("{\n"); else { force_range_for_rox ("cnt", curi->size); out("if (cnt > 0) {\n"); } out("cnt--;\n"); out("{\nuae_u32 carry;\n"); out("uae_u32 hival = (val << 1) | GET_XFLG();\n"); out("hival <<= (%d - cnt);\n", bit_size (curi->size) - 1); out("val >>= cnt;\n"); out("carry = val & 1;\n"); out("val >>= 1;\n"); out("val |= hival;\n"); out("SET_XFLG(carry);\n"); out("val &= %s;\n", bit_mask (curi->size)); out("}\n"); out("}\n"); out("SET_CFLG(GET_XFLG());\n"); genflags (flag_logical_noclobber, curi->size, "val", "", ""); shift_ce (curi->dmode, curi->size); genastore("val", curi->dmode, "dstreg", curi->size, "data"); break; case i_ASRW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();\nSET_CFLG(val & 1);\nSET_ZFLG(!(val >> 1));\nSET_NFLG(val & 0x8000);\nSET_XFLG(GET_CFLG());\n"); out("uae_u32 sign = %s & val;\n", cmask (curi->size)); out("uae_u32 cflg = val & 1;\n"); out("val = (val >> 1) | sign;\n"); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(cflg);\n"); duplicate_carry(); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_ASLW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();\nSET_CFLG(val & %s);\nSET_ZFLG(!(val & 0x7fff));\nSET_NFLG(val & 0x4000);\nSET_XFLG(GET_CFLG());\nSET_VFLG((val & 0x8000) != ((val << 1) & 0x8000));\n", cmask(curi->size)); out("uae_u32 sign = %s & val;\n", cmask (curi->size)); out("uae_u32 sign2;\n"); out("val <<= 1;\n"); genflags (flag_logical, curi->size, "val", "", ""); out("sign2 = %s & val;\n", cmask (curi->size)); out("SET_CFLG(sign != 0);\n"); duplicate_carry(); out("SET_VFLG(GET_VFLG() | (sign2 != sign));\n"); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_LSRW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u32 val = (uae_u8)data;\n"); break; case sz_word: out("uae_u32 val = (uae_u16)data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();\nSET_CFLG(val & 1);\nSET_ZFLG(!(val & 0xfffe));\nSET_NFLG(0);\nSET_XFLG(GET_CFLG());\n"); out("uae_u32 carry = val & 1;\n"); out("val >>= 1;\n"); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(carry);\n"); duplicate_carry(); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_LSLW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u8 val = data;\n"); break; case sz_word: out("uae_u16 val = data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();\nSET_CFLG(val & %s);\nSET_ZFLG(!(val & 0x7fff));\nSET_NFLG(val & 0x4000);\nSET_XFLG(GET_CFLG());\n", cmask(curi->size)); out("uae_u32 carry = val & %s;\n", cmask (curi->size)); out("val <<= 1;\n"); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(carry >> %d);\n", bit_size (curi->size) - 1); duplicate_carry(); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_ROLW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u8 val = data;\n"); break; case sz_word: out("uae_u16 val = data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();SET_CFLG(val & %s);\nSET_ZFLG(!val);\nSET_NFLG(val & 0x4000);\n", cmask(curi->size)); out("uae_u32 carry = val & %s;\n", cmask (curi->size)); out("val <<= 1;\n"); out("if (carry) val |= 1;\n"); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(carry >> %d);\n", bit_size (curi->size) - 1); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_RORW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u8 val = data;\n"); break; case sz_word: out("uae_u16 val = data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();\nSET_CFLG(val & 1);\nSET_ZFLG(!val);\nSET_NFLG(val & 0x0001);\n"); out("uae_u32 carry = val & 1;\n"); out("val >>= 1;\n"); out("if (carry) val |= %s;\n", cmask (curi->size)); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(carry);\n"); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_ROXLW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u8 val = data;\n"); break; case sz_word: out("uae_u16 val = data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();\nSET_CFLG(val & 0x8000);\nSET_ZFLG(!((val & 0x7fff) | GET_XFLG()));\nSET_NFLG(val & 0x4000);\nSET_XFLG(GET_CFLG());\n"); out("uae_u32 carry = val & %s;\n", cmask (curi->size)); out("val <<= 1;\n"); out("if (GET_XFLG()) val |= 1;\n"); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(carry >> %d);\n", bit_size (curi->size) - 1); duplicate_carry(); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_ROXRW: genamode(curi, curi->smode, "srcreg", curi->size, "data", 1, 0, GF_RMW); switch (curi->size) { case sz_byte: out("uae_u8 val = data;\n"); break; case sz_word: out("uae_u16 val = data;\n"); break; case sz_long: out("uae_u32 val = data;\n"); break; default: term(); } fill_prefetch_next_noopcodecopy("CLEAR_CZNV();SET_CFLG(val & 1);\nSET_ZFLG(!((val &0x7ffe) | GET_XFLG()))\n;SET_NFLG(GET_XFLG())\n;SET_XFLG(GET_CFLG());\n"); out("uae_u32 carry = val & 1;\n"); out("val >>= 1;\n"); out("if (GET_XFLG()) val |= %s;\n", cmask (curi->size)); genflags (flag_logical, curi->size, "val", "", ""); out("SET_CFLG(carry);\n"); duplicate_carry(); genastore("val", curi->smode, "srcreg", curi->size, "data"); loopmodeextra = 2; break; case i_MOVEC2: if (cpu_level == 1) { out("if(!regs.s) {\n"); out("Exception(8);\n"); write_return_cycles_none(); out("}\n"); } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); fill_prefetch_next(); out("int regno = (src >> 12) & 15;\n"); out("uae_u32 *regp = regs.regs + regno;\n"); out("if (!m68k_movec2(src & 0xFFF, regp)) {\n"); write_return_cycles(0); out("}\n"); addcycles000(4); break; case i_MOVE2C: if (cpu_level == 1) { out("if(!regs.s) {\n"); out("Exception(8);\n"); write_return_cycles_none(); out("}\n"); } genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, 0); fill_prefetch_next(); out("int regno = (src >> 12) & 15;\n"); out("uae_u32 *regp = regs.regs + regno;\n"); out("if (!m68k_move2c(src & 0xFFF, regp)) {\n"); write_return_cycles(0); out("}\n"); addcycles000(2); trace_t0_68040_only(); break; case i_CAS: { disable_noflags = 1; genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_LRMW); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, GF_LRMW); if (cpu_level == 5 && curi->size > 0) { out("if ((dsta & %d) && currprefs.int_no_unimplemented && get_cpu_model() == 68060) {\n", curi->size == 1 ? 1 : 3); if (mmufixupcnt) out("cpu_restore_fixup();\n"); sync_m68k_pc_noreset(); out("op_unimpl (opcode);\n"); write_return_cycles(0); out("}\n"); } fill_prefetch_0(); out("int ru = (src >> 6) & 7;\n"); out("int rc = src & 7;\n"); genflags (flag_cmp, curi->size, "newv", "m68k_dreg(regs, rc)", "dst"); gen_set_fault_pc (false, true); out("if (GET_ZFLG()) {\n"); genastore_cas ("(m68k_dreg(regs, ru))", curi->dmode, "dstreg", curi->size, "dst"); out("} else {\n"); get_prefetch_020(); if (cpu_level >= 4) { // apparently 68040/060 needs to always write at the end of RMW cycle genastore_cas ("dst", curi->dmode, "dstreg", curi->size, "dst"); } switch (curi->size) { case sz_byte: out("m68k_dreg(regs, rc) = (m68k_dreg(regs, rc) & ~0xff) | (dst & 0xff);\n"); break; case sz_word: out("m68k_dreg(regs, rc) = (m68k_dreg(regs, rc) & ~0xffff) | (dst & 0xffff);\n"); break; default: out("m68k_dreg(regs, rc) = dst;\n"); break; } out("}\n"); trace_t0_68040_only(); } break; case i_CAS2: disable_noflags = 1; genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, GF_LRMW); out("uae_u32 rn1 = regs.regs[(extra >> 28) & 15];\n"); out("uae_u32 rn2 = regs.regs[(extra >> 12) & 15];\n"); if (curi->size == sz_word) { out("uae_u16 dst1 = %s(rn1), dst2 = %s(rn2);\n", srcwlrmw, srcwlrmw); genflags(flag_cmp, curi->size, "newv", "m68k_dreg(regs, (extra >> 16) & 7)", "dst1"); out("if (GET_ZFLG()) {\n"); genflags(flag_cmp, curi->size, "newv", "m68k_dreg(regs, extra & 7)", "dst2"); out("if (GET_ZFLG()) {\n"); out("%s(rn2, m68k_dreg(regs, (extra >> 6) & 7));\n", dstwlrmw); out("%s(rn1, m68k_dreg(regs, (extra >> 22) & 7));\n", dstwlrmw); out("}\n"); out("}\n"); out("if (!GET_ZFLG()) {\n"); if (cpu_level >= 4) { // 68040: register update order swapped out("m68k_dreg(regs, (extra >> 16) & 7) = (m68k_dreg(regs, (extra >> 16) & 7) & ~0xffff) | (dst1 & 0xffff);\n"); out("m68k_dreg(regs, (extra >> 0) & 7) = (m68k_dreg(regs, (extra >> 0) & 7) & ~0xffff) | (dst2 & 0xffff);\n"); } else { out("m68k_dreg(regs, (extra >> 0) & 7) = (m68k_dreg(regs, (extra >> 0) & 7) & ~0xffff) | (dst2 & 0xffff);\n"); out("m68k_dreg(regs, (extra >> 16) & 7) = (m68k_dreg(regs, (extra >> 16) & 7) & ~0xffff) | (dst1 & 0xffff);\n"); } out("}\n"); } else { out("uae_u32 dst1 = %s(rn1), dst2 = %s(rn2);\n", srcllrmw, srcllrmw); genflags(flag_cmp, curi->size, "newv", "m68k_dreg(regs, (extra >> 16) & 7)", "dst1"); out("if (GET_ZFLG()) {\n"); genflags(flag_cmp, curi->size, "newv", "m68k_dreg(regs, extra & 7)", "dst2"); out("if (GET_ZFLG()) {\n"); out("%s(rn2, m68k_dreg(regs, (extra >> 6) & 7));\n", dstllrmw); out("%s(rn1, m68k_dreg(regs, (extra >> 22) & 7));\n", dstllrmw); out("}\n"); out("}\n"); out("if (!GET_ZFLG()) {\n"); if (cpu_level >= 4) { // 68040: register update order swapped out("m68k_dreg(regs, (extra >> 16) & 7) = dst1;\n"); out("m68k_dreg(regs, (extra >> 0) & 7) = dst2;\n"); } else { out("m68k_dreg(regs, (extra >> 0) & 7) = dst2;\n"); out("m68k_dreg(regs, (extra >> 16) & 7) = dst1;\n"); } out("}\n"); } trace_t0_68040_only(); break; case i_MOVES: /* ignore DFC and SFC when using_mmu == false */ { tail_ce020_done = true; genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); strcpy(g_srcname, "src"); addcycles000(4); out("if (extra & 0x800) {\n"); { // reg -> memory int old_m68k_pc_offset = m68k_pc_offset; int old_m68k_pc_total = m68k_pc_total; push_ins_cnt(); // 68060 stores original value, 68010 MOVES.L also stores original value. out("uae_u32 src = regs.regs[(extra >> 12) & 15];\n"); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 2, 0, cpu_level == 1 ? GF_NOFETCH : 0); tail_ce020_done = false; returntail(false); did_prefetch = 0; // Earlier models do -(an)/(an)+ EA calculation first if (!(cpu_level == 5 || (curi->dmode != Aipi && curi->dmode != Apdi)) || (cpu_level == 1 && curi->size == sz_long)) { out("src = regs.regs[(extra >> 12) & 15];\n"); } genastore_fc ("src", curi->dmode, "dstreg", curi->size, "dst"); sync_m68k_pc(); pop_ins_cnt(); m68k_pc_offset = old_m68k_pc_offset; m68k_pc_total = old_m68k_pc_total; } out("} else {\n"); { // memory -> reg genamode(curi, curi->dmode, "dstreg", curi->size, "src", 1, 0, GF_FC | (cpu_level == 1 ? GF_NOFETCH : 0)); out("if (extra & 0x8000) {\n"); switch (curi->size) { case sz_byte: out("m68k_areg(regs, (extra >> 12) & 7) = (uae_s32)(uae_s8)src;\n"); break; case sz_word: out("m68k_areg(regs, (extra >> 12) & 7) = (uae_s32)(uae_s16)src;\n"); break; case sz_long: out("m68k_areg(regs, (extra >> 12) & 7) = src;\n"); break; default: term(); } out("} else {\n"); genastore("src", Dreg, "(extra >> 12) & 7", curi->size, ""); out("}\n"); sync_m68k_pc(); tail_ce020_done = false; returntail(false); } out("}\n"); trace_t0_68040_only(); fill_prefetch_next(); next_level_060_to_040(); } break; case i_BKPT: /* only needed for hardware emulators */ addcycles000(4); illg(); clear_m68k_offset(); did_prefetch = -1; ipl_fetched = -1; break; case i_CALLM: /* not present in 68030 */ sync_m68k_pc(); illg(); did_prefetch = -1; break; case i_RTM: /* not present in 68030 */ sync_m68k_pc(); illg(); did_prefetch = -1; break; case i_TRAPcc: exception_oldpc(); if (curi->smode != am_unknown && curi->smode != am_illg) genamode(curi, curi->smode, "srcreg", curi->size, "dummy", 1, 0, 0); fill_prefetch_0(); sync_m68k_pc(); out("if (cctrue(%d)) {\n", curi->cc); exception_cpu("7"); write_return_cycles(0); out("}\n"); break; case i_DIVL: out("uaecptr oldpc = %s;\n", getpc); genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, 0); sync_m68k_pc(); out("int e = m68k_divl(opcode, dst, extra, oldpc);\n"); out("if (e <= 0) {\n"); if (mmufixupcnt) { out("if (e < 0) {\n"); out("cpu_restore_fixup();\n"); out("}\n"); } out("if (e < 0) {\n"); out("op_unimpl(opcode);\n"); out("}\n"); write_return_cycles(0); out("}\n"); break; case i_MULL: genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); genamode(curi, curi->dmode, "dstreg", curi->size, "dst", 1, 0, 0); sync_m68k_pc(); out("int e = m68k_mull(opcode, dst, extra);\n"); out("if (e <= 0) {\n"); if (mmufixupcnt) { out("if (e < 0) {\n"); out("cpu_restore_fixup();\n"); out("}\n"); } out("if (e < 0) {\n"); out("op_unimpl(opcode);\n"); out("}\n"); write_return_cycles(0); out("}\n"); break; case i_BFTST: case i_BFEXTU: case i_BFCHG: case i_BFEXTS: case i_BFCLR: case i_BFFFO: case i_BFSET: case i_BFINS: { const char *getb, *putb; if (using_mmu == 68060 && (curi->mnemo == i_BFCHG || curi->mnemo == i_BFCLR || curi->mnemo == i_BFSET || curi->mnemo == i_BFINS)) { getb = "mmu060_get_rmw_bitfield"; putb = "mmu060_put_rmw_bitfield"; } else if (using_mmu || using_ce020 || using_indirect > 0) { getb = "x_get_bitfield"; putb = "x_put_bitfield"; } else { getb = "get_bitfield"; putb = "put_bitfield"; } genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); genamode(curi, curi->dmode, "dstreg", sz_long, "dst", 2, 0, 0); out("uae_u32 bdata[2];\n"); out("uae_s32 offset = extra & 0x800 ? m68k_dreg(regs, (extra >> 6) & 7) : (extra >> 6) & 0x1f;\n"); out("int width = (((extra & 0x20 ? m68k_dreg(regs, extra & 7) : extra) - 1) & 0x1f) + 1;\n"); if (curi->mnemo == i_BFFFO) out("uae_u32 offset2 = offset;\n"); if (curi->dmode == Dreg) { out("uae_u32 tmp = m68k_dreg(regs, dstreg);\n"); out("offset &= 0x1f;\n"); out("if (offset) tmp = (tmp << offset) | (tmp >> (32 - offset));\n"); out("bdata[0] = tmp & ((1 << (32 - width)) - 1);\n"); } else { out("uae_u32 tmp;\n"); out("dsta += offset >> 3;\n"); out("tmp = %s(dsta, bdata, offset, width);\n", getb); } out("SET_ALWAYS_NFLG(((uae_s32)tmp) < 0 ? 1 : 0);\n"); if (curi->mnemo == i_BFEXTS) out("tmp = (uae_s32)tmp >> (32 - width);\n"); else out("tmp >>= (32 - width);\n"); out("SET_ZFLG(tmp == 0); SET_VFLG(0); SET_CFLG(0);\n"); switch (curi->mnemo) { case i_BFTST: break; case i_BFEXTU: case i_BFEXTS: out("m68k_dreg(regs, (extra >> 12) & 7) = tmp;\n"); break; case i_BFCHG: out("tmp = tmp ^ (0xffffffffu >> (32 - width));\n"); break; case i_BFCLR: out("tmp = 0;\n"); break; case i_BFFFO: out("{ uae_u32 mask = 1 << (width - 1);\n"); out("while (mask) { if (tmp & mask) break; mask >>= 1; offset2++; }}\n"); out("m68k_dreg(regs, (extra >> 12) & 7) = offset2;\n"); break; case i_BFSET: out("tmp = 0xffffffffu >> (32 - width);\n"); break; case i_BFINS: out("tmp = m68k_dreg(regs, (extra >> 12) & 7);\n"); out("tmp = tmp & (0xffffffffu >> (32 - width));\n"); out("SET_ALWAYS_NFLG(tmp & (1 << (width - 1)) ? 1 : 0);\n"); out("SET_ZFLG(tmp == 0);\n"); break; default: break; } if (curi->mnemo == i_BFCHG || curi->mnemo == i_BFCLR || curi->mnemo == i_BFSET || curi->mnemo == i_BFINS) { if (curi->dmode == Dreg) { out("tmp = bdata[0] | (tmp << (32 - width));\n"); out("m68k_dreg(regs, dstreg) = offset ? ((tmp >> offset) | (tmp << (32 - offset))) : tmp;\n"); } else { out("%s(dsta, bdata, tmp, offset, width);\n", putb); } } } break; case i_PACK: if (curi->smode == Dreg) { out("uae_u16 val = m68k_dreg(regs, srcreg) + %s;\n", gen_nextiword (0)); out("m68k_dreg(regs, dstreg) = (m68k_dreg(regs, dstreg) & 0xffffff00) | ((val >> 4) & 0xf0) | (val & 0xf);\n"); } else { out("uae_u16 val;\n"); addmmufixup("srcreg", curi->size, curi->smode); out("m68k_areg(regs, srcreg) -= 2;\n"); out("val = (uae_u16)(%s(m68k_areg(regs, srcreg)));\n", srcw); out("val += %s;\n", gen_nextiword(0)); addmmufixup("dstreg", curi->size, curi->dmode); out("m68k_areg(regs, dstreg) -= areg_byteinc[dstreg];\n"); gen_set_fault_pc (false, false); out("%s(m68k_areg(regs, dstreg),((val >> 4) & 0xf0) | (val & 0xf));\n", dstb); } break; case i_UNPK: if (curi->smode == Dreg) { out("uae_u16 val = m68k_dreg(regs, srcreg);\n"); out("val = ((val << 4) & 0xf00) | (val & 0xf);\n"); out("val += %s;\n", gen_nextiword(0)); out("m68k_dreg(regs, dstreg) = (m68k_dreg(regs, dstreg) & 0xffff0000) | (val & 0xffff);\n"); } else { out("uae_u16 val;\n"); addmmufixup ("srcreg", curi->size, curi->smode); out("m68k_areg(regs, srcreg) -= areg_byteinc[srcreg];\n"); out("val = (uae_u16)(%s(m68k_areg(regs, srcreg)) & 0xff);\n", srcb); out("val = (((val << 4) & 0xf00) | (val & 0xf)) + %s;\n", gen_nextiword (0)); addmmufixup ("dstreg", curi->size, curi->dmode); out("m68k_areg(regs, dstreg) -= 2;\n"); gen_set_fault_pc(false, false); out("%s(m68k_areg(regs, dstreg), val);\n", dstw); } break; case i_TAS: if (cpu_level == 0) { bus_error_cycles = 2; out("cpu_bus_rmw=true;\n"); // WINUAE_FOR_HATARI genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_LRMW); genflags(flag_logical, curi->size, "src", "", ""); if (!isreg(curi->smode)) { addcycles000(2); } out("uae_u8 old_src = src;\n"); out("src |= 0x80;\n"); if (isreg(curi->smode) || !using_ce) { genastore_tas("src", curi->smode, "srcreg", curi->size, "src"); } else { out("if (!is_cycle_ce(srca)) {\n"); genastore("src", curi->smode, "srcreg", curi->size, "src"); out("} else {\n"); out("%s(4);\n", do_cycles); addcycles000_nonce(4); out("}\n"); } out("cpu_bus_rmw=false;\n"); // WINUAE_FOR_HATARI fill_prefetch_next(); } else if (cpu_level == 1) { if (isreg(curi->smode)) { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_LRMW); } else { genamode(curi, curi->smode, "srcreg", curi->size, "src", 2, 0, GF_LRMW | GF_NOFETCH); out("uae_u8 src = %s(srca);\n", srcb); check_bus_error("src", 0, 0, 0, "src", 1, 0); } genflags(flag_logical, curi->size, "src", "", ""); if (!isreg(curi->smode)) { addcycles000(2); } out("src |= 0x80;\n"); if (isreg(curi->smode) || !using_ce) { genastore_tas("src", curi->smode, "srcreg", curi->size, "src"); } else { out("if (!is_cycle_ce(srca)) {\n"); genastore("src", curi->smode, "srcreg", curi->size, "src"); out("} else {\n"); out("%s(4);\n", do_cycles); addcycles000_nonce(4); out("}\n"); } fill_prefetch_next(); } else { genamode(curi, curi->smode, "srcreg", curi->size, "src", 1, 0, GF_LRMW); genflags(flag_logical, curi->size, "src", "", ""); out("src |= 0x80;\n"); if (next_cpu_level < 2) next_cpu_level = 2 - 1; genastore_tas("src", curi->smode, "srcreg", curi->size, "src"); fill_prefetch_next(); } next_level_000(); break; case i_FPP: fpulimit(); genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); sync_m68k_pc(); swap_opcode(); out("fpuop_arithmetic(opcode, extra);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); } break; case i_FDBcc: fpulimit(); genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); sync_m68k_pc(); swap_opcode(); out("fpuop_dbcc (opcode, extra);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); out("if (regs.fp_branch) {\n"); out("regs.fp_branch = false;\n"); out("fill_prefetch();\n"); write_return_cycles(0); out("}\n"); } else { out("if (regs.fp_branch) {\n"); out("regs.fp_branch = false;\n"); out("if(regs.t0) check_t0_trace();\n"); out("}\n"); } break; case i_FScc: fpulimit(); genamode(curi, curi->smode, "srcreg", curi->size, "extra", 1, 0, 0); sync_m68k_pc(); swap_opcode(); out("fpuop_scc (opcode, extra);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); } break; case i_FTRAPcc: fpulimit(); out("uaecptr oldpc = %s;\n", getpc); out("uae_u16 extra = %s;\n", gen_nextiword (0)); if (curi->smode != am_unknown && curi->smode != am_illg) genamode(curi, curi->smode, "srcreg", curi->size, "dummy", 1, 0, 0); sync_m68k_pc(); swap_opcode(); out("fpuop_trapcc (opcode, oldpc, extra);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); } break; case i_FBcc: fpulimit(); sync_m68k_pc(); out("uaecptr pc = %s;\n", getpc); genamode(curi, curi->dmode, "srcreg", curi->size, "extra", 1, 0, 0); sync_m68k_pc(); swap_opcode(); out("fpuop_bcc (opcode, pc,extra);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); out("if (regs.fp_branch) {\n"); out("regs.fp_branch = false;\n"); out("fill_prefetch();\n"); write_return_cycles(0); out("}\n"); } else { out("if (regs.fp_branch) {\n"); out("regs.fp_branch = false;\n"); out("if(regs.t0) check_t0_trace();\n"); out("}\n"); } break; case i_FSAVE: fpulimit(); sync_m68k_pc(); swap_opcode(); out("fpuop_save (opcode);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); } break; case i_FRESTORE: fpulimit(); sync_m68k_pc(); swap_opcode(); out("fpuop_restore (opcode);\n"); if (using_prefetch || using_prefetch_020) { out("if (regs.fp_exception) {\n"); write_return_cycles(0); out("}\n"); } break; case i_CINVL: case i_CINVP: case i_CINVA: case i_CPUSHL: case i_CPUSHP: case i_CPUSHA: out("flush_cpu_caches_040(opcode);\n"); if (using_mmu) out("flush_mmu%s(m68k_areg(regs, opcode & 3), (opcode >> 6) & 3);\n", mmu_postfix); out("if (opcode & 0x80) {\n"); out("flush_icache((opcode >> 6) & 3);\n"); out("}\n"); out("check_t0_trace();\n"); break; case i_MOVE16: { if ((opcode & 0xfff8) == 0xf620) { /* MOVE16 (Ax)+,(Ay)+ */ out("uaecptr mems = m68k_areg(regs, srcreg) & ~15, memd;\n"); out("dstreg = (%s >> 12) & 7;\n", gen_nextiword (0)); out("memd = m68k_areg(regs, dstreg) & ~15;\n"); if (using_mmu >= 68040) { out("uae_u32 v[4];\n"); out("get_move16_mmu (mems, v);\n"); out("put_move16_mmu (memd, v);\n"); } else { out("uae_u32 v[4];\n"); out("v[0] = %s(mems);\n", srcl); out("v[1] = %s(mems + 4);\n", srcl); out("v[2] = %s(mems + 8);\n", srcl); out("v[3] = %s(mems + 12);\n", srcl); out("%s(memd , v[0]);\n", dstl); out("%s(memd + 4, v[1]);\n", dstl); out("%s(memd + 8, v[2]);\n", dstl); out("%s(memd + 12, v[3]);\n", dstl); } out("if (srcreg != dstreg)\n"); out("m68k_areg(regs, srcreg) += 16;\n"); out("m68k_areg(regs, dstreg) += 16;\n"); } else { /* Other variants */ genamode(curi, curi->smode, "srcreg", curi->size, "mems", 0, 2, 0); genamode(curi, curi->dmode, "dstreg", curi->size, "memd", 0, 2, 0); if (using_mmu == 68040) { out("get_move16_mmu (memsa, mmu040_move16);\n"); out("put_move16_mmu (memda, mmu040_move16);\n"); } else if (using_mmu == 68060) { out("uae_u32 v[4];\n"); out("get_move16_mmu (memsa, v);\n"); out("put_move16_mmu (memda, v);\n"); } else { out("memsa &= ~15;\n"); out("memda &= ~15;\n"); out("uae_u32 v[4];\n"); out("v[0] = %s(memsa);\n", srcl); out("v[1] = %s(memsa + 4);\n", srcl); out("v[2] = %s(memsa + 8);\n", srcl); out("v[3] = %s(memsa + 12);\n", srcl); out("%s(memda , v[0]);\n", dstl); out("%s(memda + 4, v[1]);\n", dstl); out("%s(memda + 8, v[2]);\n", dstl); out("%s(memda + 12, v[3]);\n", dstl); } if ((opcode & 0xfff8) == 0xf600) out("m68k_areg(regs, srcreg) += 16;\n"); else if ((opcode & 0xfff8) == 0xf608) out("m68k_areg(regs, dstreg) += 16;\n"); } } break; case i_PFLUSHN: case i_PFLUSH: case i_PFLUSHAN: case i_PFLUSHA: sync_m68k_pc(); out("mmu_op(opcode, 0);\n"); trace_t0_68040_only(); break; case i_PLPAR: case i_PLPAW: sync_m68k_pc(); out("mmu_op(opcode, 0);\n"); break; case i_PTESTR: case i_PTESTW: sync_m68k_pc(); out("mmu_op(opcode, 0);\n"); trace_t0_68040_only(); break; case i_MMUOP030: out("uaecptr pc = %s;\n", getpc); out("uae_u16 extra = %s(2);\n", prefetch_word); m68k_pc_offset += 2; sync_m68k_pc(); if (curi->smode == Areg || curi->smode == Dreg) out("uae_u16 extraa = 0;\n"); else genamode(curi, curi->smode, "srcreg", curi->size, "extra", 0, 0, 0); sync_m68k_pc(); if (using_ce020 || using_prefetch_020) { out("if (mmu_op30(pc, opcode, extra, extraa)) {\n"); write_return_cycles(0); out("}\n"); } else { out("mmu_op30(pc, opcode, extra, extraa);\n"); } break; default: term(); break; } end: if (loopmode && loopmodeextra) { out("if (loop_mode) {\n"); addcycles000_onlyce(loopmodeextra); addcycles000_nonce(loopmodeextra); out("}\n"); } loopmode_stop(); if (!genastore_done) returntail (0); if (set_fpulimit) { out("\n#endif\n"); } if (did_prefetch >= 0) fill_prefetch_finish(); sync_m68k_pc(); if ((using_ce || using_prefetch) && did_prefetch >= 0) { #ifndef WINUAE_FOR_HATARI int ipladd = 0; #endif if (last_access_offset_ipl > 0) { char iplfetch[100]; int tc = get_current_cycles(); if (tc - ipl_fetch_cycles > 4 || ipl_fetched == 3) { if (pre_ipl >= 2) { strcpy(iplfetch, "ipl_fetch_now_pre();\n"); } else { strcpy(iplfetch, "ipl_fetch_now();\n"); } } else { strcpy(iplfetch, "ipl_fetch_next();\n"); } if (pre_ipl != 1) { if (using_ce) { #ifndef WINUAE_FOR_HATARI ipladd = insertstring(iplfetch, last_access_offset_ipl); #else insertstring(iplfetch, last_access_offset_ipl); #endif } } } else if (ipl_fetched < 10) { out("// MISSING\n"); } } if (cpu_level >= 2 && !using_ce && !using_ce020) { int v = curi->clocks; if (v < 4) v = 4; count_cycles = v; } } static void generate_macros(FILE *f) { fprintf(f, "#define SET_ALWAYS_CFLG(x) SET_CFLG(x)\n" "#define SET_ALWAYS_NFLG(x) SET_NFLG(x)\n"); } static void generate_includes (FILE *f, int id) { fprintf(f, "#include \"main.h\"\n"); // fprintf(f, "#include \"sysconfig.h\"\n"); fprintf(f, "#include \"sysdeps.h\"\n"); // fprintf(f, "#include \"options.h\"\n"); fprintf(f, "#include \"hatari-glue.h\"\n"); fprintf(f, "#include \"maccess.h\"\n"); fprintf(f, "#include \"memory.h\"\n"); fprintf(f, "#include \"custom.h\"\n"); // fprintf(f, "#include \"events.h\"\n"); fprintf(f, "#include \"newcpu.h\"\n"); fprintf(f, "#include \"cpu_prefetch.h\"\n"); fprintf(f, "#include \"cputbl.h\"\n"); fprintf(f, "#include \"debugmem.h\"\n"); if (id == 31 || id == 33) fprintf(f, "#include \"cpummu.h\"\n"); else if (id == 32 || id == 34 || id == 35) fprintf(f, "#include \"cpummu030.h\"\n"); generate_macros(f); } static char *decodeEA (amodes mode, wordsizes size) { static char buffer[80]; buffer[0] = 0; switch (mode){ case Dreg: strcpy (buffer,"Dn"); break; case Areg: strcpy (buffer,"An"); break; case Aind: strcpy (buffer,"(An)"); break; case Aipi: strcpy (buffer,"(An)+"); break; case Apdi: strcpy (buffer,"-(An)"); break; case Ad16: strcpy (buffer,"(d16,An)"); break; case Ad8r: strcpy (buffer,"(d8,An,Xn)"); break; case PC16: strcpy (buffer,"(d16,PC)"); break; case PC8r: strcpy (buffer,"(d8,PC,Xn)"); break; case absw: strcpy (buffer,"(xxx).W"); break; case absl: strcpy (buffer,"(xxx).L"); break; case imm: switch (size){ case sz_byte: strcpy (buffer,"#.B"); break; case sz_word: strcpy (buffer,"#.W"); break; case sz_long: strcpy (buffer,"#.L"); break; default: break; } break; case imm0: strcpy (buffer,"#.B"); break; case imm1: strcpy (buffer,"#.W"); break; case imm2: strcpy (buffer,"#.L"); break; case immi: strcpy (buffer,"#"); break; default: break; } return buffer; } static const char *m68k_cc[] = { "T", "F", "HI", "LS", "CC", "CS", "NE", "EQ", "VC", "VS", "PL", "MI", "GE", "LT", "GT", "LE" }; static char *outopcode (int opcode) { static char out[100]; struct instr *ins; int i; ins = &table68k[opcode]; for (i = 0; lookuptab[i].name[0]; i++) { if (ins->mnemo == lookuptab[i].mnemo) break; } { char *s = ua (lookuptab[i].name); strcpy (out, s); xfree (s); } if (ins->smode == immi) strcat (out, "Q"); if (ins->size == sz_byte) strcat (out,".B"); if (ins->size == sz_word) strcat (out,".W"); if (ins->size == sz_long) strcat (out,".L"); strcat (out," "); if (ins->suse) strcat (out, decodeEA (ins->smode, ins->size)); if (ins->duse) { if (ins->suse) strcat (out,","); strcat (out, decodeEA (ins->dmode, ins->size)); } if (ins->mnemo == i_DBcc || ins->mnemo == i_Scc || ins->mnemo == i_Bcc || ins->mnemo == i_TRAPcc) { strcat (out, " ("); strcat (out, m68k_cc[table68k[opcode].cc]); strcat (out, ")"); } return out; } struct cputbl_tmp { uae_s16 length; uae_s8 disp020[2]; uae_s8 branch; uae_s8 nf; }; static struct cputbl_tmp cputbltmp[65536]; static const char *remove_nf[] = { "SET_ZFLG", "SET_NFLG", "SET_VFLG", "SET_CFLG", "SET_XFLG", "COPY_CARRY", "CLEAR_CZNV", NULL }; static void remove_func(const char *fs, char *p) { for (;;) { const char *f1 = strstr(p, fs); if (!f1) break; const char *f2 = strchr(f1, ';'); if (!f2) { abort(); } while (f1 > p && (f1[-1] == '\t' || f1[-1] == ' ')) { f1--; } f2++; while (*f2 == '\n' || *f2 == '\r') { f2++; } if (f1[1] == '\n') { f1++; } memmove(p + (f1 - p), f2, strlen(f2) + 1); } } static void convert_to_noflags(char *p) { for (int i = 0; remove_nf[i]; i++) { remove_func(remove_nf[i], p); } const char *f1 = strstr(p, "_ff("); if (!f1) { abort(); } p[f1 - p + 1] = 'n'; } static void generate_one_opcode (int rp, const char *extra) { int idx; uae_u16 smsk, dmsk; unsigned int opcode = opcode_map[rp]; int i68000 = table68k[opcode].clev > 0; int have_realopcode = 0; brace_level = 0; disable_noflags = 0; if (table68k[opcode].mnemo == i_ILLG || table68k[opcode].clev > cpu_level) return; for (idx = 0; lookuptab[idx].name[0]; idx++) { if (table68k[opcode].mnemo == lookuptab[idx].mnemo) break; } if (table68k[opcode].handler != -1) return; outbuffer[0] = 0; if (opcode_next_clev[rp] != cpu_level) { char *name = ua (lookuptab[idx].name); if (generate_stbl) { #ifdef NOFLAGS_SUPPORT_GENCPU if (func_noret) { fprintf(stblfile, "{ NULL, NULL, op_%04x_%d%s_ff, op_%04x_%d%s_%s, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, opcode_last_postfix[rp], extra, opcode, opcode_last_postfix[rp], extra, cputbltmp[opcode].nf ? "nf" : "ff", opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } else { fprintf(stblfile, "{ op_%04x_%d%s_ff, op_%04x_%d%s_%s, NULL, NULL, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, opcode_last_postfix[rp], extra, opcode, opcode_last_postfix[rp], extra, cputbltmp[opcode].nf ? "nf" : "ff", opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } #else if (func_noret) { fprintf(stblfile, "{ NULL, op_%04x_%d%s_ff, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, opcode_last_postfix[rp], extra, opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } else { fprintf(stblfile, "{ op_%04x_%d%s_ff, NULL, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, opcode_last_postfix[rp], extra, opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } #endif } xfree (name); return; } fprintf(headerfile, "extern %s op_%04x_%d%s_nf;\n", func_noret ? "cpuop_func_noret" : "cpuop_func", opcode, postfix, extra); fprintf(headerfile, "extern %s op_%04x_%d%s_ff;\n", func_noret ? "cpuop_func_noret" : "cpuop_func", opcode, postfix, extra); out("/* %s */\n", outopcode (opcode)); if (i68000) out("#ifndef CPUEMU_68000_ONLY\n"); out("%s REGPARAM2 op_%04x_%d%s_ff(uae_u32 opcode)\n{\n", func_noret ? "void" : "uae_u32", opcode, postfix, extra); if ((using_simple_cycles || do_always_dynamic_cycles) && !using_nocycles) out("int count_cycles = 0;\n"); switch (table68k[opcode].stype) { case 0: smsk = 7; break; case 1: smsk = 255; break; case 2: smsk = 15; break; case 3: smsk = 7; break; case 4: smsk = 7; break; case 5: smsk = 63; break; case 7: smsk = 3; break; default: term(); } dmsk = 7; next_cpu_level = -1; if (table68k[opcode].suse && table68k[opcode].smode != imm && table68k[opcode].smode != imm0 && table68k[opcode].smode != imm1 && table68k[opcode].smode != imm2 && table68k[opcode].smode != absw && table68k[opcode].smode != absl && table68k[opcode].smode != PC8r && table68k[opcode].smode != PC16) { if (table68k[opcode].spos == -1) { if (((int) table68k[opcode].sreg) >= 128) out("uae_u32 srcreg = (uae_s32)(uae_s8)%d;\n", (int) table68k[opcode].sreg); else out("uae_u32 srcreg = %d;\n", (int) table68k[opcode].sreg); } else { char source[100]; int pos = table68k[opcode].spos; real_opcode(&have_realopcode); if (pos) sprintf(source, "((real_opcode >> %d) & %d)", pos, smsk); else sprintf(source, "(real_opcode & %d)", smsk); if (table68k[opcode].stype == 3) out("uae_u32 srcreg = imm8_table[%s];\n", source); else if (table68k[opcode].stype == 1) out("uae_u32 srcreg = (uae_s32)(uae_s8)%s;\n", source); else out("uae_u32 srcreg = %s;\n", source); } } if (table68k[opcode].duse /* Yes, the dmode can be imm, in case of LINK or DBcc */ && table68k[opcode].dmode != imm && table68k[opcode].dmode != imm0 && table68k[opcode].dmode != imm1 && table68k[opcode].dmode != imm2 && table68k[opcode].dmode != absw && table68k[opcode].dmode != absl) { if (table68k[opcode].dpos == -1) { if (((int) table68k[opcode].dreg) >= 128) out("uae_u32 dstreg = (uae_s32)(uae_s8)%d;\n", (int) table68k[opcode].dreg); else out("uae_u32 dstreg = %d;\n", (int) table68k[opcode].dreg); } else { int pos = table68k[opcode].dpos; real_opcode(&have_realopcode); if (pos) out("uae_u32 dstreg = (real_opcode >> %d) & %d;\n", pos, dmsk); else out("uae_u32 dstreg = real_opcode & %d;\n", dmsk); } } count_readw = count_readl = count_writew = count_writel = count_ncycles = count_cycles = 0; count_readp = 0; count_cycles_ce020 = 0; count_read_ea = count_write_ea = count_cycles_ea = 0; gen_opcode (opcode); clearmmufixup(0, 0); clearmmufixup(1, 0); write_return_cycles(1); if ((opcode & 0xf000) == 0xf000) m68k_pc_total = -1; cputbltmp[opcode].length = m68k_pc_total; cputbltmp[opcode].disp020[0] = 0; if (genamode8r_offset[0] > 0) cputbltmp[opcode].disp020[0] = m68k_pc_total - genamode8r_offset[0] + 2; cputbltmp[opcode].disp020[1] = 0; if (genamode8r_offset[1] > 0) cputbltmp[opcode].disp020[1] = m68k_pc_total - genamode8r_offset[1] + 2; cputbltmp[opcode].branch = branch_inst / 2; cputbltmp[opcode].nf = 0; if (m68k_pc_total > 0) out("/* %d %d,%d %c */\n", m68k_pc_total, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch ? 'B' : ' '); out("\n"); if (i68000) out("#endif\n"); opcode_next_clev[rp] = next_cpu_level; opcode_last_postfix[rp] = postfix; #ifdef WINUAE_FOR_HATARI /* Hatari only : Now patch in the instruction cycles at the beginning of the function: */ if (!using_ce020) { char buf_cyc[10]; sprintf ( buf_cyc , "%d;", CurrentInstrCycles ); memcpy ( outbuffer+CurrentInstrCycles_pos , buf_cyc , strlen(buf_cyc) ); } #endif printf("%s", outbuffer); // generate noflags variant if needed NOWARN_UNUSED(int nfgenerated = 0); if (using_noflags && table68k[opcode].flagdead != 0 && !disable_noflags) { convert_to_noflags(outbuffer); printf("%s", outbuffer); nfgenerated = 1; cputbltmp[opcode].nf = 1; } if (generate_stbl) { char *name = ua (lookuptab[idx].name); if (i68000) fprintf(stblfile, "#ifndef CPUEMU_68000_ONLY\n"); #ifdef NOFLAGS_SUPPORT_GENCPU if (func_noret) { fprintf(stblfile, "{ NULL, NULL, op_%04x_%d%s_ff, op_%04x_%d%s_%s, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, postfix, extra, opcode, postfix, extra, nfgenerated ? "nf" : "ff", opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } else { fprintf(stblfile, "{ op_%04x_%d%s_ff, op_%04x_%d%s_%s, NULL, NULL, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, postfix, extra, opcode, postfix, extra, nfgenerated ? "nf" : "ff", opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } #else if (func_noret) { fprintf(stblfile, "{ NULL, op_%04x_%d%s_ff, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, postfix, extra, opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } else { fprintf(stblfile, "{ op_%04x_%d%s_ff, NULL, 0x%04x, %d, { %d, %d }, %d }, /* %s */\n", opcode, postfix, extra, opcode, cputbltmp[opcode].length, cputbltmp[opcode].disp020[0], cputbltmp[opcode].disp020[1], cputbltmp[opcode].branch, name); } #endif if (i68000) fprintf(stblfile, "#endif\n"); xfree (name); } } static void generate_func (const char *extra) { int j, rp; /* sam: this is for people with low memory (eg. me :)) */ out("\n" "#if !defined(PART_1) && !defined(PART_2) && " "!defined(PART_3) && !defined(PART_4) && " "!defined(PART_5) && !defined(PART_6) && " "!defined(PART_7) && !defined(PART_8)" "\n" "#define PART_1 1\n" "#define PART_2 1\n" "#define PART_3 1\n" "#define PART_4 1\n" "#define PART_5 1\n" "#define PART_6 1\n" "#define PART_7 1\n" "#define PART_8 1\n" "#endif\n\n"); rp = 0; for(j = 1; j <= 8; ++j) { int k = (j * nr_cpuop_funcs) / 8; out("#ifdef PART_%d\n",j); for (; rp < k; rp++) generate_one_opcode (rp, extra); out("#endif\n\n"); } if (generate_stbl) { #ifdef NOFLAGS_SUPPORT_GENCPU fprintf(stblfile, "{ NULL, NULL, NULL, NULL, 0, 0, { 0, 0 }, 0 } };\n"); #else fprintf(stblfile, "{ NULL, NULL, 0, 0, { 0, 0 }, 0 } };\n"); #endif } } #if CPU_TESTER static void generate_cpu_test(int mode) { char fname[100]; const char *extra = "_test", *extraup; int rp; int id = 90 + mode; using_tracer = 0; extraup = ""; postfix = id; fprintf(stblfile, "#ifdef CPUEMU_%d%s\n", postfix, extraup); sprintf(fname, "cpuemu_%d%s.cpp", postfix, extra); if (freopen(fname, "wb", stdout) == NULL) { abort(); } generate_macros(stdout); using_exception_3 = 1; using_bus_error = 1; using_prefetch = 0; using_prefetch_020 = 0; using_ce = 0; using_ce020 = 0; using_mmu = 0; using_waitstates = 0; memory_cycle_cnt = 4; mmu_postfix = ""; xfc_postfix = ""; using_simple_cycles = 0; using_indirect = 1; cpu_generic = false; need_exception_oldpc = 0; cpu_level = 0; using_prefetch = 1; using_exception_3 = 1; using_simple_cycles = 1; func_noret = 1; if (mode == 0) { using_simple_cycles = 0; using_ce = 1; } else if (mode == 1) { using_simple_cycles = 0; using_ce = 1; cpu_level = 1; } else if (mode == 2) { cpu_level = 2; using_prefetch = 0; using_simple_cycles = 0; } else if (mode == 3) { cpu_level = 3; using_prefetch = 0; using_simple_cycles = 0; } else if (mode == 4) { cpu_level = 4; using_prefetch = 0; using_simple_cycles = 0; } else if (mode == 5) { cpu_level = 5; using_prefetch = 0; using_simple_cycles = 0; need_special_fixup = 1; } read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; printf("#include \"cputest.h\"\n"); if (!mode) { fprintf(stblfile, "#include \"cputest.h\"\n"); } fprintf(stblfile, "const struct cputbl CPUFUNC(op_smalltbl_%d%s)[] = {\n", postfix, extra); generate_func(extra); fprintf(stblfile, "#endif /* CPUEMU_%d%s */\n", postfix, extraup); } #endif static void generate_cpu (int id, int mode) { char fname[100]; const char *extra, *extraup; static int postfix2 = -1; int rp; using_tracer = mode; extra = ""; extraup = ""; if (using_tracer) { extra = "_t"; extraup = "_T"; } postfix = id; if (id == 0 || id == 11 || id == 13 || id == 20 || id == 21 || id == 22 || id == 23 || id == 24 || id == 31 || id == 32 || id == 33 || id == 34 || id == 35 || id == 40 || id == 50) { if (generate_stbl) fprintf(stblfile, "#ifdef CPUEMU_%d%s\n", postfix, extraup); postfix2 = postfix; sprintf(fname, "cpuemu_%d%s.c", postfix, extra); if (freopen (fname, "wb", stdout) == NULL) { abort(); } generate_includes (stdout, id); } using_exception_3 = 1; using_prefetch = 0; using_prefetch_020 = 0; using_ce = 0; using_ce020 = 0; using_mmu = 0; using_waitstates = 0; memory_cycle_cnt = 4; mmu_postfix = ""; xfc_postfix = ""; using_simple_cycles = 0; using_indirect = 0; cpu_generic = false; need_special_fixup = 0; need_exception_oldpc = 0; using_get_word_unswapped = 0; using_noflags = 0; if (id == 11 || id == 12) { // 11 = 68010 prefetch, 12 = 68000 prefetch cpu_level = id == 11 ? 1 : 0; using_prefetch = 1; using_exception_3 = 1; using_simple_cycles = 1; if (id == 11) { read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } } else if (id == 13 || id == 14) { // 13 = 68010 cycle-exact, 14 = 68000 cycle-exact cpu_level = id == 13 ? 1 : 0; using_prefetch = 1; using_exception_3 = 1; using_ce = 1; if (id == 13) { read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } } else if (id == 20) { // 68020 prefetch cpu_level = 2; using_prefetch_020 = 1; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 21) { // 68020 cycle-exact cpu_level = 2; using_ce020 = 1; using_prefetch_020 = 1; // timing tables are from 030 which has 2 // clock memory accesses, 68020 has 3 clock // memory accesses using_waitstates = 1; memory_cycle_cnt = 3; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 22) { // 68030 prefetch cpu_level = 3; using_prefetch_020 = 2; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 23) { // 68030 "cycle-exact" cpu_level = 3; using_ce020 = 2; using_prefetch_020 = 2; memory_cycle_cnt = 2; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 24 || id == 25) { // 68040/060 "cycle-exact" cpu_level = id == 24 ? 5 : 4; using_ce020 = 3; using_prefetch_020 = 3; memory_cycle_cnt = 0; if (id == 24) { read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } } else if (id == 31) { // 31 = 68040 MMU mmu_postfix = "040"; cpu_level = 4; using_mmu = 68040; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 32) { // 32 = 68030 MMU mmu_postfix = "030"; xfc_postfix = "_state"; cpu_level = 3; using_mmu = 68030; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 33) { // 33 = 68060 MMU mmu_postfix = "060"; cpu_level = 5; using_mmu = 68060; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 34) { // 34 = 68030 MMU + caches mmu_postfix = "030c"; xfc_postfix = "_state"; cpu_level = 3; using_prefetch_020 = 2; using_mmu = 68030; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id == 35) { // 35 = 68030 MMU + caches + CE mmu_postfix = "030c"; cpu_level = 3; using_ce020 = 2; using_prefetch_020 = 2; using_mmu = 68030; read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } else if (id < 6) { cpu_level = 5 - (id - 0); // "generic" cpu_generic = true; need_special_fixup = 1; } else if (id >= 40 && id < 46) { cpu_level = 5 - (id - 40); // "generic" + direct cpu_generic = true; need_exception_oldpc = 1; if (id == 40) { read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } using_indirect = -1; using_nocycles = 1; #ifdef NOFLAGS_SUPPORT_GENCPU using_noflags = 1; #endif #ifdef HAVE_GET_WORD_UNSWAPPED using_get_word_unswapped = 1; #endif } else if (id >= 50 && id < 56) { cpu_level = 5 - (id - 50); // "generic" + indirect cpu_generic = true; need_special_fixup = 1; need_exception_oldpc = 1; using_nocycles = 1; #ifdef NOFLAGS_SUPPORT_GENCPU using_noflags = 1; #endif #ifdef HAVE_GET_WORD_UNSWAPPED using_get_word_unswapped = 1; #endif if (id == 50) { read_counts(); for (rp = 0; rp < nr_cpuop_funcs; rp++) opcode_next_clev[rp] = cpu_level; } } do_always_dynamic_cycles = !using_simple_cycles && !using_prefetch && using_always_dynamic_cycles; func_noret = using_ce || using_ce020; if (!using_indirect) using_indirect = using_ce || using_ce020 || using_prefetch_020 || id >= 50; if (generate_stbl) { if ((id > 0 && id < 6) || (id >= 20 && id < 40) || (id > 40 && id < 46) || (id > 50 && id < 56)) fprintf(stblfile, "#ifndef CPUEMU_68000_ONLY\n"); fprintf(stblfile, "const struct cputbl op_smalltbl_%d%s[] = {\n", postfix, extra); } generate_func (extra); if (generate_stbl) { if ((id > 0 && id < 6) || (id >= 20 && id < 40) || (id > 40 && id < 46) || (id > 50 && id < 56)) fprintf(stblfile, "#endif /* CPUEMU_68000_ONLY */\n"); if (postfix2 >= 0) fprintf(stblfile, "#endif /* CPUEMU_%d%s */\n", postfix2, extraup); } postfix2 = -1; } int main(int argc, char *argv[]) { init_table68k(); opcode_map = xmalloc (int, nr_cpuop_funcs); opcode_last_postfix = xmalloc (int, nr_cpuop_funcs); opcode_next_clev = xmalloc (int, nr_cpuop_funcs); counts = xmalloc (unsigned long, 65536); read_counts(); /* It would be a lot nicer to put all in one file (we'd also get rid of * cputbl.h that way), but cpuopti can't cope. That could be fixed, but * I don't dare to touch the 68k version. */ #if CPU_TESTER using_test = 1; headerfile = fopen("cputbl_test.h", "wb"); stblfile = fopen("cpustbl_test.cpp", "wb"); generate_stbl = 1; generate_cpu_test(0); generate_cpu_test(1); generate_cpu_test(2); generate_cpu_test(3); generate_cpu_test(4); generate_cpu_test(5); #else using_debugmem = 1; headerfile = fopen("cputbl.h", "wb"); fprintf(headerfile, "#define HARDWARE_BUS_ERROR_EMULATION %d\n", using_bus_error); stblfile = fopen("cpustbl.c", "wb"); generate_includes(stblfile, 0); for (int i = 0; i <= 55; i++) { if ((i >= 6 && i < 11) || (i > 14 && i < 20) || (i > 25 && i < 31) || (i > 35 && i < 40)) continue; generate_stbl = 1; generate_cpu (i, 0); } #endif free (table68k); return 0; } #ifndef WINUAE_FOR_HATARI void write_log (const TCHAR *format,...) { } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/hatari-glue.c000066400000000000000000000212241504763705000244660ustar00rootroot00000000000000/* Hatari - hatari-glue.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This file contains some code to glue the UAE CPU core to the rest of the emulator and Hatari's "illegal" opcodes. */ const char HatariGlue_fileid[] = "Hatari hatari-glue.c"; #include #include "main.h" #include "configuration.h" #include "cycles.h" #include "cycInt.h" #include "tos.h" #include "gemdos.h" #include "natfeats.h" #include "cart.h" #include "vdi.h" #include "stMemory.h" #include "ikbd.h" #include "video.h" #include "psg.h" #include "mfp.h" #include "fdc.h" #include "nf_scsidrv.h" #include "memorySnapShot.h" #include "sysdeps.h" #include "options_cpu.h" #include "maccess.h" #include "memory.h" #include "m68000.h" #include "newcpu.h" #include "cpu_prefetch.h" #include "savestate.h" #include "hatari-glue.h" struct uae_prefs currprefs, changed_prefs; int pendingInterrupts = 0; /** * Reset custom chips * In case the RESET instruction is called, we must reset all the peripherals * connected to the CPU's reset pin. */ void customreset(void) { pendingInterrupts = 0; /* Reset the IKBD */ IKBD_Reset ( false ); /* Resetting the GLUE video chip should also set freq/res register to 0 */ Video_Reset_Glue (); /* Reset the YM2149 (stop any sound) */ PSG_Reset (); /* Reset the MFP */ MFP_Reset_All (); /* Reset the FDC */ FDC_Reset ( false ); #if defined(__linux__) /* Reset the native SCSI Driver */ nf_scsidrv_reset(); #endif } /** * Return highest interrupt number (1 - 7), 0 means no interrupt. * Note that the interrupt stays pending if it can't be executed yet * due to the interrupt level field in the SR. */ int intlev(void) { if ( pendingInterrupts & (1 << 6) ) /* MFP/DSP interrupt ? */ return 6; else if ( pendingInterrupts & (1 << 5) ) /* SCC interrupt ? */ return 5; else if ( pendingInterrupts & (1 << 4) ) /* VBL interrupt ? */ return 4; else if ( pendingInterrupts & (1 << 2) ) /* HBL interrupt ? */ return 2; else if ( pendingInterrupts & (1 << 1) ) /* SCU soft interrupt on MegaSTE/TT ? */ return 1; return 0; } void UAE_Set_Quit_Reset ( bool hard ) { //fprintf ( stderr , "UAE_Set_Quit_Reset %d\n" , hard ); if ( hard ) quit_program = UAE_RESET_HARD; else quit_program = UAE_RESET; } void UAE_Set_State_Save ( void ) { //fprintf ( stderr , "UAE_Set_State_Save\n" ); savestate_state = STATE_SAVE; } void UAE_Set_State_Restore ( void ) { //fprintf ( stderr , "UAE_Set_State_Restore\n" ); savestate_state = STATE_RESTORE; } /** * Replace WinUAE's save_state / restore_state functions with Hatari's specific ones */ int save_state (const TCHAR *filename, const TCHAR *description) { //fprintf ( stderr , "save_state in\n" ); MemorySnapShot_Capture_Do (); //fprintf ( stderr , "save_state out\n" ); savestate_state = 0; return 0; /* return value is not used */ } void restore_state (const TCHAR *filename) { MemorySnapShot_Restore_Do (); } void savestate_restore_final (void) { /* Not used for now in Hatari */ } bool savestate_restore_finish (void) { //fprintf ( stderr , "savestate_restore_finish in %d\n" , quit_program ); if (!isrestore ()) return false; restore_cpu_finish (); savestate_state = 0; quit_program = 0; /* at this point, quit_program was already processed, we must reset it */ //fprintf ( stderr , "savestate_restore_finish out %d\n" , quit_program ); return true; } /** * Initialize 680x0 emulation */ int Init680x0(void) { //fprintf ( stderr , "Init680x0 in\n" ); init_m68k(); //fprintf ( stderr , "Init680x0 out\n" ); return true; } /** * Deinitialize 680x0 emulation */ void Exit680x0(void) { memory_uninit(); free(table68k); table68k = NULL; } /** * Execute a 'NOP' opcode (increment PC by 2 bytes and take care * of prefetch at the CPU level depending on the current CPU mode) * This is used to return from SysInit / Natfeats interception, by ignoring * the intercepted opcode and executing a NOP instead once the work has been done. */ static void CpuDoNOP ( void ) { if ( !CpuRunFuncNoret ) (*cpufunctbl[0X4E71])(0x4E71); else (*cpufunctbl_noret[0X4E71])(0x4E71); } /** * Check whether PC is currently in ROM cartridge space - used * to test whether our "illegal" Hatari opcodes should be handled * or whether they are just "normal" illegal opcodes. */ static bool is_cart_pc(void) { uint32_t pc = M68000_GetPC(); if (ConfigureParams.System.bAddressSpace24 || (pc >> 24) == 0xff) { pc &= 0x00ffffff; /* Mask to 24-bit address */ } return pc >= 0xfa0000 && pc < 0xfc0000; } /** * This function will be called at system init by the cartridge routine * (after gemdos init, before booting floppies). * The GEMDOS vector (#$84) is setup and we also initialize the connected * drive mask and Line-A variables (for an extended VDI resolution) from here. */ uae_u32 REGPARAM3 OpCode_SysInit(uae_u32 opcode) { if (is_cart_pc()) { /* Add any drives mapped by TOS in the interim */ ConnectedDriveMask |= STMemory_ReadLong(0x4c2); /* Initialize the connected drive mask */ STMemory_WriteLong(0x4c2, ConnectedDriveMask); /* Init on boot - see cart.c */ GemDOS_Boot(); /* Update LineA for extended VDI res * D0: LineA base, A1: Font base */ VDI_LineA(regs.regs[0], regs.regs[9]); CpuDoNOP(); } else if (!bUseTos) { GemDOS_Boot(); CpuDoNOP(); } else { LOG_TRACE(TRACE_OS_GEMDOS | TRACE_OS_BASE | TRACE_OS_VDI | TRACE_OS_AES, "SYSINIT opcode invoked outside of cartridge space\n"); /* illegal instruction */ op_illg(opcode); fill_prefetch(); } return 4 * CYCLE_UNIT / 2; } void REGPARAM3 OpCode_SysInit_noret(uae_u32 opcode) { OpCode_SysInit(opcode); } /** * Handle illegal opcode #8 (GEMDOS_OPCODE). * When GEMDOS HD emulation is enabled, we use it to intercept GEMDOS * calls (see gemdos.c). */ uae_u32 REGPARAM3 OpCode_GemDos(uae_u32 opcode) { if (is_cart_pc()) { GemDOS_Trap(); CpuDoNOP(); } else { LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS opcode invoked outside of cartridge space\n"); /* illegal instruction */ op_illg(opcode); fill_prefetch(); } return 4 * CYCLE_UNIT / 2; } void REGPARAM3 OpCode_GemDos_noret(uae_u32 opcode) { OpCode_GemDos(opcode); } /** * Handle illegal opcode #9 (PEXEC_OPCODE). * When GEMDOS HD emulation is enabled, we use it to intercept the end of * the Pexec call (see gemdos.c). */ uae_u32 REGPARAM3 OpCode_Pexec(uae_u32 opcode) { if (is_cart_pc()) { GemDOS_PexecBpCreated(); CpuDoNOP(); } else { LOG_TRACE(TRACE_OS_GEMDOS, "PEXEC opcode invoked outside of cartridge space\n"); /* illegal instruction */ op_illg(opcode); fill_prefetch(); } return 4 * CYCLE_UNIT / 2; } void REGPARAM3 OpCode_Pexec_noret(uae_u32 opcode) { OpCode_Pexec(opcode); } /** * This is called after completion of each VDI call */ uae_u32 REGPARAM3 OpCode_VDI(uae_u32 opcode) { /* this is valid only after VDI trap, called from cartridge code */ if (VDI_OldPC && is_cart_pc()) { VDI_Complete(); /* Set PC back to where originated from to continue instruction decoding */ m68k_setpc(VDI_OldPC); VDI_OldPC = 0; } else { LOG_TRACE(TRACE_OS_VDI, "VDI opcode invoked outside of cartridge space\n"); /* illegal instruction */ op_illg(opcode); } fill_prefetch(); return 4 * CYCLE_UNIT / 2; } void REGPARAM3 OpCode_VDI_noret(uae_u32 opcode) { OpCode_VDI(opcode); } /** * Emulator Native Features ID opcode interception. */ uae_u32 REGPARAM3 OpCode_NatFeat_ID(uae_u32 opcode) { uint32_t stack = Regs[REG_A7] + SIZE_LONG; /* skip return address */ if (NatFeat_ID(stack, &(Regs[REG_D0]))) { CpuDoNOP (); } return 4 * CYCLE_UNIT / 2; } void REGPARAM3 OpCode_NatFeat_ID_noret(uae_u32 opcode) { OpCode_NatFeat_ID(opcode); } /** * Emulator Native Features call opcode interception. */ uae_u32 REGPARAM3 OpCode_NatFeat_Call(uae_u32 opcode) { uint32_t stack = Regs[REG_A7] + SIZE_LONG; /* skip return address */ uint16_t SR = M68000_GetSR(); bool super; super = ((SR & SR_SUPERMODE) == SR_SUPERMODE); if (NatFeat_Call(stack, super, &(Regs[REG_D0]))) { CpuDoNOP (); } return 4 * CYCLE_UNIT / 2; } void REGPARAM3 OpCode_NatFeat_Call_noret(uae_u32 opcode) { OpCode_NatFeat_Call(opcode); } TCHAR* buf_out (TCHAR *buffer, int *bufsize, const TCHAR *format, ...) { va_list parms; int count; if (buffer == NULL) { return NULL; } va_start (parms, format); vsnprintf (buffer, (*bufsize) - 1, format, parms); va_end (parms); count = _tcslen (buffer); *bufsize -= count; return buffer + count; } void error_log(const TCHAR *format, ...) { va_list parms; va_start(parms, format); vfprintf(stderr, format, parms); va_end(parms); if (format[strlen(format) - 1] != '\n') { fputc('\n', stderr); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/hatari-glue.h000066400000000000000000000025411504763705000244740ustar00rootroot00000000000000/* Hatari - hatari-glue.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_GLUE_H #define HATARI_GLUE_H #include "sysdeps.h" #include "options_cpu.h" #include "cycles.h" extern int pendingInterrupts; extern void customreset(void); extern int intlev (void); extern void UAE_Set_Quit_Reset ( bool hard ); extern void UAE_Set_State_Save ( void ); extern void UAE_Set_State_Restore ( void ); extern int Init680x0(void); extern void Exit680x0(void); extern uae_u32 extra_cycle; /* From cpu/custom.c */ extern uae_u32 REGPARAM3 OpCode_GemDos(uae_u32 opcode); extern void REGPARAM3 OpCode_GemDos_noret(uae_u32 opcode); extern uae_u32 REGPARAM3 OpCode_Pexec(uae_u32 opcode); extern void REGPARAM3 OpCode_Pexec_noret(uae_u32 opcode); extern uae_u32 REGPARAM3 OpCode_SysInit(uae_u32 opcode); extern void REGPARAM3 OpCode_SysInit_noret(uae_u32 opcode); extern uae_u32 REGPARAM3 OpCode_VDI(uae_u32 opcode); extern void REGPARAM3 OpCode_VDI_noret(uae_u32 opcode); extern uae_u32 REGPARAM3 OpCode_NatFeat_ID(uae_u32 opcode); extern void REGPARAM3 OpCode_NatFeat_ID_noret(uae_u32 opcode); extern uae_u32 REGPARAM3 OpCode_NatFeat_Call(uae_u32 opcode); extern void REGPARAM3 OpCode_NatFeat_Call_noret(uae_u32 opcode); #endif /* HATARI_GLUE_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/000077500000000000000000000000001504763705000227055ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/codegen_arm.cpp000066400000000000000000002330751504763705000256660ustar00rootroot00000000000000/* * compiler/codegen_arm.cpp - ARM code generator * * Copyright (c) 2013 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> ARM * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Current state: * - Experimental * - Still optimizable * - Not clock cycle optimized * - as a first step this compiler emulates x86 instruction to be compatible * with gencomp. Better would be a specialized version of gencomp compiling * 68k instructions to ARM compatible instructions. This is a step for the * future * */ #include "flags_arm.h" // Declare the built-in __clear_cache function. extern void __clear_cache (char*, char*); /************************************************************************* * Some basic information about the the target CPU * *************************************************************************/ #define R0_INDEX 0 #define R1_INDEX 1 #define R2_INDEX 2 #define R3_INDEX 3 #define R4_INDEX 4 #define R5_INDEX 5 #define R6_INDEX 6 #define R7_INDEX 7 #define R8_INDEX 8 #define R9_INDEX 9 #define R10_INDEX 10 #define R11_INDEX 11 #define R12_INDEX 12 #define R13_INDEX 13 #define R14_INDEX 14 #define R15_INDEX 15 #define RSP_INDEX 13 #define RLR_INDEX 14 #define RPC_INDEX 15 /* The register in which subroutines return an integer return value */ #define REG_RESULT R0_INDEX /* The registers subroutines take their first and second argument in */ #define REG_PAR1 R0_INDEX #define REG_PAR2 R1_INDEX #define REG_WORK1 R2_INDEX #define REG_WORK2 R3_INDEX //#define REG_DATAPTR R10_INDEX #define REG_PC_PRE R0_INDEX /* The register we use for preloading regs.pc_p */ #define REG_PC_TMP R1_INDEX /* Another register that is not the above */ #define SHIFTCOUNT_NREG R1_INDEX /* Register that can be used for shiftcount. -1 if any reg will do. Normally this can be set to -1 but compemu_support is tied to 1 */ #define MUL_NREG1 R0_INDEX /* %r4 will hold the low 32 bits after a 32x32 mul */ #define MUL_NREG2 R1_INDEX /* %r5 will hold the high 32 bits */ #define STACK_ALIGN 4 #define STACK_OFFSET sizeof(void *) #define STACK_SHADOW_SPACE 0 uae_s8 always_used[]={2,3,-1}; uae_s8 can_byte[]={0,1,4,5,6,7,8,9,10,11,12,-1}; uae_s8 can_word[]={0,1,4,5,6,7,8,9,10,11,12,-1}; uae_u8 call_saved[]={0,0,0,0,1,1,1,1,1,1,1,1,0,1,1,1}; /* This *should* be the same as call_saved. But: - We might not really know which registers are saved, and which aren't, so we need to preserve some, but don't want to rely on everyone else also saving those registers - Special registers (such like the stack pointer) should not be "preserved" by pushing, even though they are "saved" across function calls */ static const uae_u8 need_to_preserve[]={0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0}; static const uae_u32 PRESERVE_MASK = ((1<=-128 && x<=127); } static inline int is8bit(uae_s32 x) { return (x>=-255 && x<=255); } static inline int isword(uae_s32 x) { return (x>=-32768 && x<=32767); } #define jit_unimplemented(fmt, ...) do{ panicbug("**** Unimplemented ****"); panicbug(fmt, ## __VA_ARGS__); abort(); }while (0) #if 0 /* currently unused */ static void jit_fail(const char *msg, const char *file, int line, const char *function) { panicbug("JIT failure in function %s from file %s at line %d: %s", function, file, line, msg); abort(); } #endif LOWFUNC(NONE,WRITE,1,raw_push_l_r,(RR4 r)) { PUSH(r); } LOWFUNC(NONE,READ,1,raw_pop_l_r,(RR4 r)) { POP(r); } LOWFUNC(RMW,NONE,2,raw_adc_b,(RW1 d, RR1 s)) { MVN_ri(REG_WORK1, 0); // mvn r2,#0 LSL_rri(REG_WORK2, d, 24); // lsl r3, %[d], #24 ORR_rrrLSRi(REG_WORK2, REG_WORK2, REG_WORK1, 8); // orr r3, r3, r2, lsr #8 LSL_rri(REG_WORK1, s, 24); // lsl r2, %[s], #24 ADCS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adcs r3, r3, r2 BIC_rri(d, d, 0xFF); // bic %[d],%[d],#0xFF ORR_rrrLSRi(d, d, REG_WORK2, 24); // orr %[d],%[d], R3 LSR #24 } LOWFUNC(RMW,NONE,2,raw_adc_w,(RW2 d, RR2 s)) { MVN_ri(REG_WORK1, 0); // mvn r2,#0 LSL_rri(REG_WORK2, d, 16); // lsl r3, %[d], #16 ORR_rrrLSRi(REG_WORK2, REG_WORK2, REG_WORK1, 16); // orr r3, r3, r2, lsr #16 LSL_rri(REG_WORK1, s, 16); // lsl r2, %[s], #16 ADCS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adds r3, r3, r2 #ifdef ARMV6_ASSEMBLY PKHTB_rrrASRi(d,d,REG_WORK2,16); #else BIC_rri(d, d, 0xff); // bic %[d],%[d],#0xff BIC_rri(d, d, 0xff00); // bic %[d],%[d],#0xff00 ORR_rrrLSRi(d, d, REG_WORK2, 16); // orr %[d], %[d], r3, lsr #16 #endif } LOWFUNC(RMW,NONE,2,raw_adc_l,(RW4 d, RR4 s)) { ADCS_rrr(d, d, s); // adcs %[d],%[d],%[s] } LOWFUNC(WRITE,NONE,2,raw_add_b,(RW1 d, RR1 s)) { LSL_rri(REG_WORK1, s, 24); // lsl r2, %[s], #24 LSL_rri(REG_WORK2, d, 24); // lsl r3, %[d], #24 ADDS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adds r3, r3, r2 BIC_rri(d, d, 0xFF); // bic %[d],%[d],#0xFF ORR_rrrLSRi(d, d, REG_WORK2, 24); // orr %[d],%[d], r3 LSR #24 } LOWFUNC(WRITE,NONE,2,raw_add_w,(RW2 d, RR2 s)) { LSL_rri(REG_WORK1, s, 16); // lsl r2, %[s], #16 LSL_rri(REG_WORK2, d, 16); // lsl r3, %[d], #16 ADDS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adds r3, r3, r2 #ifdef ARMV6_ASSEMBLY PKHTB_rrrASRi(d,d,REG_WORK2,16); #else BIC_rri(d, d, 0xff); // bic %[d],%[d],#0xff BIC_rri(d, d, 0xff00); // bic %[d],%[d],#0xff00 ORR_rrrLSRi(d, d, REG_WORK2, 16); // orr r7, r7, r3, LSR #16 #endif } LOWFUNC(WRITE,NONE,2,raw_add_l,(RW4 d, RR4 s)) { ADDS_rrr(d, d, s); // adds %[d], %[d], %[s] } LOWFUNC(WRITE,NONE,2,raw_add_w_ri,(RW2 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_word_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldrh r2, [pc, #offs] #else # ifdef ARMV6_ASSEMBLY LDRH_rRI(REG_WORK1, RPC_INDEX, 24); // ldrh r2, [pc, #24] ; # else LDRH_rRI(REG_WORK1, RPC_INDEX, 16); // ldrh r2, [pc, #16] ; # endif #endif LSL_rri(REG_WORK2, d, 16); // lsl r3, %[d], #16 LSL_rri(REG_WORK1, REG_WORK1, 16); // lsl r2, r2, #16 ADDS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adds r3, r3, r2 #ifdef ARMV6_ASSEMBLY PKHTB_rrrASRi(d,d,REG_WORK2,16); #else BIC_rri(d, d, 0xff); // bic %[d],%[d],#0xff BIC_rri(d, d, 0xff00); // bic %[d],%[d],#0xff00 ORR_rrrLSRi(d, d, REG_WORK2, 16); // orr %[d],%[d], r3, LSR #16 #endif #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_word(i); skip_word(0); //: #endif } LOWFUNC(WRITE,NONE,2,raw_add_b_ri,(RW1 d, IMM i)) { LSL_rri(REG_WORK2, d, 24); // lsl r3, %[d], #24 ADDS_rri(REG_WORK2, REG_WORK2, i << 24); // adds r3, r3, #0x12000000 BIC_rri(d, d, 0xFF); // bic %[d],%[d], #0xFF ORR_rrrLSRi(d, d, REG_WORK2, 24); // orr %[d],%[d], r3, lsr #24 } LOWFUNC(WRITE,NONE,2,raw_add_l_ri,(RW4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ADDS_rrr(d, d, REG_WORK1); // adds %[d], %[d], r2 #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; ADDS_rrr(d, d, REG_WORK1); // adds %[d], %[d], r2 B_i(0); // b //: emit_long(i); //: #endif } LOWFUNC(WRITE,NONE,2,raw_and_b,(RW1 d, RR1 s)) { MVN_rrLSLi(REG_WORK1, s, 24); // mvn r2, %[s], lsl #24 MVN_rrLSRi(REG_WORK1, REG_WORK1, 24); // mvn r2, %[s], lsr #24 AND_rrr(d, d, REG_WORK1); // and %[d], %[d], r2 LSLS_rri(REG_WORK1, d, 24); // lsls r2, %[d], #24 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_and_w,(RW2 d, RR2 s)) { MVN_rrLSLi(REG_WORK1, s, 16); // mvn r2, %[s], lsl #16 MVN_rrLSRi(REG_WORK1, REG_WORK1, 16); // mvn r2, %[s], lsr #16 AND_rrr(d, d, REG_WORK1); // and %[d], %[d], r2 LSLS_rri(REG_WORK1, d, 16); // lsls r2, %[d], #16 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_and_l,(RW4 d, RR4 s)) { ANDS_rrr(d, d, s); // ands r7, r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_and_l_ri,(RW4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 16); // ldr r2, [pc, #16] ; #endif ANDS_rrr(d, d, REG_WORK1); // ands %[d], %[d], r2 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(i); //: #endif } LOWFUNC(WRITE,NONE,2,raw_bsf_l_rr,(W4 d, RR4 s)) { MOV_rr(REG_WORK1, s); // mov r2,%[s] RSB_rri(REG_WORK2, REG_WORK1, 0); // rsb r3,r2,#0 AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // and r2,r2,r3 CLZ_rr(REG_WORK2, REG_WORK1); // clz r3,r2 MOV_ri(d, 32); // mov %[d],#32 SUB_rrr(d, d, REG_WORK2); // sub %[d],%[d],r3 MRS_CPSR(REG_WORK2); // mrs r3,cpsr TEQ_ri(d, 0); // teq %[d],#0 CC_SUBS_rri(NATIVE_CC_NE, d,d,1); // sub %[d],%[d],#1 CC_BIC_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_Z_FLAG); // bic r3,r3,#0x40000000 CC_ORR_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_Z_FLAG); // orr r3,r3,#0x40000000 MSR_CPSR_r(REG_WORK2); // msr cpsr,r3 } LOWFUNC(WRITE,NONE,1,raw_bswap_16,(RW2 r)) { #if defined(ARMV6_ASSEMBLY) REVSH_rr(REG_WORK1,r); // revsh r2,%[r] UXTH_rr(REG_WORK1, REG_WORK1); // utxh r2,r2 LSR_rri(r, r, 16); ORR_rrrLSLi(r, REG_WORK1, r, 16); // orr %[r], %[r], r2 #else MOV_rr(REG_WORK1, r); // mov r2, r6 BIC_rri(REG_WORK1, REG_WORK1, 0xff0000); // bic r2, r2, #0xff0000 BIC_rri(REG_WORK1, REG_WORK1, 0xff000000); // bic r2, r2, #0xff000000 EOR_rrr(r, r, REG_WORK1); // eor r6, r6, r2 ORR_rrrLSRi(r, r, REG_WORK1, 8); // orr r6, r6, r2, lsr #8 BIC_rri(REG_WORK1, REG_WORK1, 0xff00); // bic r2, r2, #0xff00 ORR_rrrLSLi(r,r,REG_WORK1, 8); // orr r6, r6, r2, lsl #8 #endif } LOWFUNC(NONE,NONE,1,raw_bswap_32,(RW4 r)) { #if defined(ARMV6_ASSEMBLY) REV_rr(r,r); // rev %[r],%[r] #else EOR_rrrRORi(REG_WORK1, r, r, 16); // eor r2, r6, r6, ror #16 BIC_rri(REG_WORK1, REG_WORK1, 0xff0000); // bic r2, r2, #0xff0000 ROR_rri(r, r, 8); // ror r6, r6, #8 EOR_rrrLSRi(r, r, REG_WORK1, 8); // eor r6, r6, r2, lsr #8 #endif } LOWFUNC(WRITE,NONE,2,raw_bt_l_ri,(RR4 r, IMM i)) { int imm = (1 << (i & 0x1f)); MRS_CPSR(REG_WORK2); // mrs r3, CPSR TST_ri(r, imm); // tst r6, #0x1000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3, r3, #0x20000000 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3, r3, #0x20000000 MSR_CPSR_r(REG_WORK2); // msr CPSR_fc, r3 } LOWFUNC(WRITE,NONE,2,raw_bt_l_rr,(RR4 r, RR4 b)) { AND_rri(REG_WORK2, b, 0x1f); // and r3, r7, #0x1f LSR_rrr(REG_WORK1, r, REG_WORK2); // lsr r2, r6, r3 MRS_CPSR(REG_WORK2); // mrs r3, CPSR TST_ri(REG_WORK1, 1); // tst r2, #1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3, r3, #0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3, r3, #0x20000000 MSR_CPSR_r(REG_WORK2); // msr CPSR_fc, r3 } LOWFUNC(WRITE,NONE,2,raw_btc_l_rr,(RW4 r, RR4 b)) { MOV_ri(REG_WORK1, 1); // mov r2, #1 AND_rri(REG_WORK2, b, 0x1f); // and r3, r7, #0x1f LSL_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // lsl r2, r2, r3 MRS_CPSR(REG_WORK2); // mrs r3, CPSR TST_rr(r, REG_WORK1); // tst r6, r2 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3, r3, #0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3, r3, #0x20000000 EOR_rrr(r, r, REG_WORK1); // eor r6, r6, r2 MSR_CPSR_r(REG_WORK2); // msr CPSR_fc, r3 } LOWFUNC(WRITE,NONE,2,raw_btr_l_rr,(RW4 r, RR4 b)) { MOV_ri(REG_WORK1, 1); // mov r2, #1 AND_rri(REG_WORK2, b, 0x1f); // and r3, r7, #0x1f LSL_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // lsl r2, r2, r3 MRS_CPSR(REG_WORK2); // mrs r3, CPSR TST_rr(r, REG_WORK1); // tst r6, r2 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3, r3, #0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3, r3, #0x20000000 BIC_rrr(r, r, REG_WORK1); // bic r6, r6, r2 MSR_CPSR_r(REG_WORK2); // msr CPSR_fc, r3 } LOWFUNC(WRITE,NONE,2,raw_bts_l_rr,(RW4 r, RR4 b)) { MOV_ri(REG_WORK1, 1); // mov r2, #1 AND_rri(REG_WORK2, b, 0x1f); // and r3, r7, #0x1f LSL_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // lsl r2, r2, r3 MRS_CPSR(REG_WORK2); // mrs r3, CPSR TST_rr(r, REG_WORK1); // tst r6, r2 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3, r3, #0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3, r3, #0x20000000 ORR_rrr(r, r, REG_WORK1); // orr r6, r6, r2 MSR_CPSR_r(REG_WORK2); // msr CPSR_fc, r3 } LOWFUNC(READ,NONE,3,raw_cmov_l_rr,(RW4 d, RR4 s, IMM cc)) { switch (cc) { case 9: // LS BEQ_i(0); // beq Z != 0 BCC_i(0); // bcc C == 0 //: MOV_rr(d, s); // mov r7,r6 break; case 8: // HI BEQ_i(1); // beq Z != 0 BCS_i(0); // bcs C != 0 MOV_rr(d, s); // mov r7,#0 break; default: CC_MOV_rr(cc, d, s); // MOVcc R7,#1 break; } //: } LOWFUNC(WRITE,NONE,2,raw_cmp_b,(RR1 d, RR1 s)) { #if defined(ARMV6_ASSEMBLY) SXTB_rr(REG_WORK1, d); // sxtb r2,%[d] SXTB_rr(REG_WORK2, s); // sxtb r3,%[s] #else LSL_rri(REG_WORK1, d, 24); // lsl r2,r6,#24 LSL_rri(REG_WORK2, s, 24); // lsl r3,r7,#24 #endif CMP_rr(REG_WORK1, REG_WORK2); // cmp r2, r3 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_cmp_w,(RR2 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) SXTH_rr(REG_WORK1, d); // sxtb r2,%[d] SXTH_rr(REG_WORK2, s); // sxtb r3,%[s] #else LSL_rri(REG_WORK1, d, 16); // lsl r6, r1, #16 LSL_rri(REG_WORK2, s, 16); // lsl r7, r2, #16 #endif CMP_rr(REG_WORK1, REG_WORK2); // cmp r7, r6, asr #16 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_cmp_l,(RR4 d, RR4 s)) { CMP_rr(d, s); // cmp r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(NONE,NONE,2,raw_imul_32_32,(RW4 d, RR4 s)) { SMULL_rrrr(REG_WORK1, REG_WORK2, d, s); // smull r2,r3,r7,r6 MOV_rr(d, REG_WORK1); // mov r7,r2 } LOWFUNC(NONE,NONE,2,raw_imul_64_32,(RW4 d, RW4 s)) { SMULL_rrrr(REG_WORK1, REG_WORK2, d, s); // smull r2,r3,r7,r6 MOV_rr(MUL_NREG1, REG_WORK1); // mov r7,r2 MOV_rr(MUL_NREG2, REG_WORK2); } LOWFUNC(NONE,NONE,3,raw_lea_l_brr,(W4 d, RR4 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ADD_rrr(d, s, REG_WORK1); // add r7, r6, r2 #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; ADD_rrr(d, s, REG_WORK1); // add r7, r6, r2 B_i(0); // b //: emit_long(offset); //: #endif } LOWFUNC(NONE,NONE,5,raw_lea_l_brr_indexed,(W4 d, RR4 s, RR4 index, IMM factor, IMM offset)) { int shft; switch(factor) { case 1: shft=0; break; case 2: shft=1; break; case 4: shft=2; break; case 8: shft=3; break; default: abort(); } #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // LDR R2,[PC, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 8); // LDR R2,[PC, #8] #endif ADD_rrr(REG_WORK1, s, REG_WORK1); // ADD R7,R6,R2 ADD_rrrLSLi(d, REG_WORK1, index, shft); // ADD R7,R7,R5,LSL #2 #if !defined(USE_DATA_BUFFER) B_i(0); // B jp emit_long(offset); //; #endif } LOWFUNC(NONE,NONE,4,raw_lea_l_rr_indexed,(W4 d, RR4 s, RR4 index, IMM factor)) { int shft; switch(factor) { case 1: shft=0; break; case 2: shft=1; break; case 4: shft=2; break; case 8: shft=3; break; default: abort(); } ADD_rrrLSLi(d, s, index, shft); // ADD R7,R6,R5,LSL #2 } LOWFUNC(NONE,READ,3,raw_mov_b_brR,(W1 d, RR4 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 12); // ldr r2, [pc, #12] ; #endif LDRB_rRR(REG_WORK1, REG_WORK1, s); // ldrb r2, [r2, r6] BIC_rri(d, d, 0xff); // bic r7, r7, #0xff ORR_rrr(d, d, REG_WORK1); // orr r7, r7, r2 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(offset); //: #endif } LOWFUNC(NONE,WRITE,3,raw_mov_b_bRr,(RR4 d, RR1 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2,[pc, #offs] STRB_rRR(s, d, REG_WORK1); // strb r6,[r7, r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2,[pc,#4] STRB_rRR(s, d, REG_WORK1); // strb r6,[r7, r2] B_i(0); // b //: emit_long(offset); //: #endif } LOWFUNC(NONE,WRITE,2,raw_mov_b_mi,(MEMW d, IMM s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; #else LDR_rRI(REG_WORK1, RPC_INDEX, 8); // ldr r2, [pc, #8] ; #endif MOV_ri(REG_WORK2, s & 0xFF); // mov r3, #0x34 STRB_rR(REG_WORK2, REG_WORK1); // strb r3, [r2] #if !defined(USE_DATA_BUFFER) B_i(0); // b //d: emit_long(d); //: #endif } LOWFUNC(NONE,WRITE,2,raw_mov_b_mr,(IMM d, RR1 s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] STRB_rR(s, REG_WORK1); // strb r6, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STRB_rR(s, REG_WORK1); // strb r6, [r2] B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(NONE,NONE,2,raw_mov_b_ri,(W1 d, IMM s)) { BIC_rri(d, d, 0xff); // bic %[d], %[d], #0xff ORR_rri(d, d, (s & 0xff)); // orr %[d], %[d], #%[s] } LOWFUNC(NONE,READ,2,raw_mov_b_rm,(W1 d, IMM s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 12); // ldr r2, [pc, #12] ; #endif LDRB_rR(REG_WORK2, REG_WORK1); // ldrb r2, [r2] BIC_rri(d, d, 0xff); // bic r7, r7, #0xff ORR_rrr(d, REG_WORK2, d); // orr r7, r2, r7 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(s); //: #endif } LOWFUNC(NONE,NONE,2,raw_mov_b_rr,(W1 d, RR1 s)) { AND_rri(REG_WORK1, s, 0xff); // and r2,r2, #0xff BIC_rri(d, d, 0x0ff); // bic %[d], %[d], #0xff ORR_rrr(d, d, REG_WORK1); // orr %[d], %[d], r2 } LOWFUNC(NONE,READ,3,raw_mov_l_brR,(W4 d, RR4 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] LDR_rRR(d, REG_WORK1, s); // ldr r7, [r2, r6] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; LDR_rRR(d, REG_WORK1, s); // ldr r7, [r2, r6] B_i(0); // b emit_long(offset); //: //: #endif } LOWFUNC(NONE,WRITE,3,raw_mov_l_bRr,(RR4 d, RR4 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2,[pc, #offs] STR_rRR(s, d, REG_WORK1); // str R6,[R7, r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2,[pc,#4] ; STR_rRR(s, d, REG_WORK1); // str R6,[R7, r2] B_i(0); // b //: emit_long(offset); //: #endif } LOWFUNC(NONE,WRITE,2,raw_mov_l_mi,(MEMW d, IMM s)) { // TODO: optimize imm #if defined(USE_DATA_BUFFER) data_check_end(8, 12); long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d offs = data_long_offs(s); LDR_rRI(REG_WORK2, RPC_INDEX, offs); // ldr r3, [pc, #offs] ; s STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 8); // ldr r2, [pc, #8] ; LDR_rRI(REG_WORK2, RPC_INDEX, 8); // ldr r3, [pc, #8] ; STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] B_i(1); // b emit_long(d); //: emit_long(s); //: //: #endif } LOWFUNC(NONE,READ,3,raw_mov_w_brR,(W2 d, RR4 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else # ifdef ARMV6_ASSEMBLY LDR_rRI(REG_WORK1, RPC_INDEX, 8); // ldr r2, [pc, #16] ; # else LDR_rRI(REG_WORK1, RPC_INDEX, 16); // ldr r2, [pc, #16] ; # endif #endif LDRH_rRR(REG_WORK1, REG_WORK1, s); // ldrh r2, [r2, r6] #ifdef ARMV6_ASSEMBLY PKHBT_rrr(d,REG_WORK1,d); #else BIC_rri(d, d, 0xff); // bic r7, r7, #0xff BIC_rri(d, d, 0xff00); // bic r7, r7, #0xff00 ORR_rrr(d, d, REG_WORK1); // orr r7, r7, r2 #endif #if !defined(USE_DATA_BUFFER) B_i(0); // b emit_long(offset); //: //: #endif } LOWFUNC(NONE,WRITE,3,raw_mov_w_bRr,(RR4 d, RR2 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2,[pc, #offs] STRH_rRR(s, d, REG_WORK1); // strh r6,[r7, r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2,[pc,#4] STRH_rRR(s, d, REG_WORK1); // strh r6,[r7, r2] B_i(0); // b //: emit_long(offset); //: #endif } LOWFUNC(NONE,WRITE,2,raw_mov_w_mr,(IMM d, RR2 s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc,#offs] STRH_rR(s, REG_WORK1); // strh r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STRH_rR(s, REG_WORK1); // strh r3, [r2] B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(NONE,NONE,2,raw_mov_w_ri,(W2 d, IMM s)) { #if defined(USE_DATA_BUFFER) long offs = data_word_offs(s); LDR_rRI(REG_WORK2, RPC_INDEX, offs); // ldrh r3, [pc, #offs] #else # ifdef ARMV6_ASSEMBLY LDRH_rRI(REG_WORK2, RPC_INDEX, 12); // ldrh r3, [pc, #12] ; # else LDRH_rRI(REG_WORK2, RPC_INDEX, 4); // ldrh r3, [pc, #12] ; # endif #endif #ifdef ARMV6_ASSEMBLY PKHBT_rrr(d,REG_WORK2,d); #else BIC_rri(REG_WORK1, d, 0xff); // bic r2, r7, #0xff BIC_rri(REG_WORK1, REG_WORK1, 0xff00); // bic r2, r2, #0xff00 ORR_rrr(d, REG_WORK2, REG_WORK1); // orr r7, r3, r2 #endif #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_word(s); skip_word(0); //: #endif } LOWFUNC(NONE,WRITE,2,raw_mov_w_mi,(MEMW d, IMM s)) { // TODO: optimize imm #if defined(USE_DATA_BUFFER) data_check_end(8, 12); long offs = data_long_offs(d); LDR_rRI(REG_WORK2, RPC_INDEX, offs); // ldr r3, [pc, #offs] ; offs = data_word_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; STRH_rR(REG_WORK1, REG_WORK2); // strh r2, [r3] #else LDR_rRI(REG_WORK2, RPC_INDEX, 8); // ldr r3, [pc, #8] ; LDRH_rRI(REG_WORK1, RPC_INDEX, 8); // ldrh r2, [pc, #8] ; STRH_rR(REG_WORK1, REG_WORK2); // strh r2, [r3] B_i(1); // b //mem: emit_long(d); //imm: emit_word(s); skip_word(0); // Alignment //: #endif } LOWFUNC(NONE,WRITE,2,raw_mov_l_mr,(IMM d, RR4 s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] STR_rR(s, REG_WORK1); // str r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STR_rR(s, REG_WORK1); // str r3, [r2] B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(NONE,WRITE,3,raw_mov_w_Ri,(RR4 d, IMM i, IMM offset)) { Dif(!isbyte(offset)) abort(); #if defined(USE_DATA_BUFFER) long offs = data_word_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDRH_rRI(REG_WORK1, RPC_INDEX, 4); // ldrh r2, [pc, #4] ; #endif if (offset >= 0) STRH_rRI(REG_WORK1, d, offset); // strh r2, [r7, #0x54] else STRH_rRi(REG_WORK1, d, -offset);// strh r2, [r7, #-0x54] #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_word(i); skip_word(0); //: #endif } LOWFUNC(NONE,READ,2,raw_mov_w_rm,(W2 d, IMM s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 12); // ldr r2, [pc, #12] ; #endif LDRH_rR(REG_WORK1, REG_WORK1); // ldrh r2, [r2] LSR_rri(d, d, 16); // lsr r7, r7, #16 ORR_rrrLSLi(d, REG_WORK1, d, 16); // orr r7, r2, r7, lsl #16 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(s); //: #endif } LOWFUNC(NONE,NONE,2,raw_mov_w_rr,(W2 d, RR2 s)) { LSL_rri(REG_WORK1, s, 16); // lsl r2, r6, #16 ORR_rrrLSRi(d, REG_WORK1, d, 16); // orr r7, r2, r7, lsr #16 ROR_rri(d, d, 16); // ror r7, r7, #16 } LOWFUNC(NONE,READ,3,raw_mov_w_rR,(W2 d, RR4 s, IMM offset)) { Dif(!isbyte(offset)) abort(); if (offset >= 0) LDRH_rRI(REG_WORK1, s, offset); // ldrh r2, [r6, #12] else LDRH_rRi(REG_WORK1, s, -offset); // ldrh r2, [r6, #-12] #ifdef ARMV6_ASSEMBLY PKHBT_rrr(d,REG_WORK1,d); #else BIC_rri(d, d, 0xff); // bic r7, r7, #0xff BIC_rri(d, d, 0xff00); // bic r7, r7, #0xff00 ORR_rrr(d, d, REG_WORK1); // orr r7, r7, r2 #endif } LOWFUNC(NONE,WRITE,3,raw_mov_w_Rr,(RR4 d, RR2 s, IMM offset)) { Dif(!isbyte(offset)) abort(); if (offset >= 0) STRH_rRI(s, d, offset); // strh r6, [r7, #0x7f] else STRH_rRi(s, d, -offset);// strh r6, [r7, #-0x7f] } LOWFUNC(NONE,READ,2,raw_mov_l_rm,(W4 d, MEMR s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [r10, #offs] LDR_rR(d, REG_WORK1); // ldr r7, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; LDR_rR(d, REG_WORK1); // ldr r7, [r2] B_i(0); // b emit_long(s); //: //: #endif } LOWFUNC(NONE,READ,4,raw_mov_l_rm_indexed,(W4 d, MEMR base, RR4 index, IMM factor)) { int shft; switch(factor) { case 1: shft=0; break; case 2: shft=1; break; case 4: shft=2; break; case 8: shft=3; break; default: abort(); } #if defined(USE_DATA_BUFFER) long offs = data_long_offs(base); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] LDR_rRR_LSLi(d, REG_WORK1, index, shft); // ldr %[d], [r2, %[index], lsl #[shift]] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; LDR_rRR_LSLi(d, REG_WORK1, index, shft); // ldr %[d], [r2, %[index], lsl #[shift]] B_i(0); // b emit_long(base); //: //: #endif } LOWFUNC(NONE,WRITE,3,raw_mov_l_Ri,(RR4 d, IMM i, IMM offset8)) { Dif(!isbyte(offset8)) abort(); #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; #endif if (offset8 >= 0) STR_rRI(REG_WORK1, d, offset8); // str r2, [r7, #0x54] else STR_rRi(REG_WORK1, d, -offset8); // str r2, [r7, #-0x54] #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(i); //: #endif } LOWFUNC(NONE,READ,3,raw_mov_l_rR,(W4 d, RR4 s, IMM offset)) { Dif(!isbyte(offset)) abort(); if (offset >= 0) { LDR_rRI(d, s, offset); // ldr r2, [r1, #-12] } else LDR_rRi(d, s, -offset); // ldr r2, [r1, #12] } LOWFUNC(NONE,NONE,2,raw_mov_l_rr,(W4 d, RR4 s)) { MOV_rr(d, s); // mov %[d], %[s] } LOWFUNC(NONE,WRITE,3,raw_mov_l_Rr,(RR4 d, RR4 s, IMM offset)) { Dif(!isbyte(offset)) abort(); if (offset >= 0) STR_rRI(s, d, offset); // str r6, [r7, #12] else STR_rRi(s, d, -offset); // str r6, [r7, #-12] } LOWFUNC(NONE,NONE,2,raw_mul_64_32,(RW4 d, RW4 s)) { UMULL_rrrr(REG_WORK1, REG_WORK2, d, s); // umull r2,r3,r7,r6 MOV_rr(MUL_NREG1, REG_WORK1); // mov r7,r2 MOV_rr(MUL_NREG2, REG_WORK2); } LOWFUNC(WRITE,NONE,2,raw_or_b,(RW1 d, RR1 s)) { AND_rri(REG_WORK1, s, 0xFF); // and r2, %[s], 0xFF ORR_rrr(d, d, REG_WORK1); // orr %[d], %[d], r2 LSLS_rri(REG_WORK1, d, 24); // lsls r2, %[d], #24 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_or_w,(RW2 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, s); // UXTH r2, %[s] #else BIC_rri(REG_WORK1, s, 0xff000000); // bic r2, %[s], #0xff000000 BIC_rri(REG_WORK1, REG_WORK1, 0x00ff0000); // bic r2, r2, #0x00ff0000 #endif ORR_rrr(d, d, REG_WORK1); // orr %[d], %[d], r2 LSLS_rri(REG_WORK1, d, 16); // lsls r2, %[d], #16 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_or_l,(RW4 d, RR4 s)) { ORRS_rrr(d, d, s); // orrs r7, r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_or_l_ri,(RW4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // LDR r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 16); // LDR r2, [pc,#16] ; #endif ORRS_rrr(d, d, REG_WORK1); // ORRS r7,r7,r2 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 #if !defined(USE_DATA_BUFFER) B_i(0); // b // value: emit_long(i); //jp: #endif } LOWFUNC(WRITE,NONE,2,raw_rol_b_ri,(RW1 r, IMM i)) { // TODO: Check if the Bittest is necessary. compemu.c seems to do it itself, but meanwhile make sure, that carry is set correctly int imm = 32 - (i & 0x1f); MOV_rrLSLi(REG_WORK1, r, 24); // mov r2,r7,lsl #24 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 8); // orr r2,r2,r2,lsr #8 RORS_rri(REG_WORK1, REG_WORK1, imm); // rors r2,r2,#(32 - (i & 0x1f)) MRS_CPSR(REG_WORK2); // mrs r3,cpsr TST_ri(REG_WORK1, 1); // tst r2,#1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3,r3,#0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3,r3,#0x20000000 MSR_CPSR_r(REG_WORK2); AND_rri(REG_WORK1, REG_WORK1, 0xff); // and r2,r2,#0xff BIC_rri(r, r, 0xff); // bic r7,r7,#0xff ORR_rrr(r, r, REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_rol_b_rr,(RW1 d, RR1 r)) { // TODO: Check if the Bittest is necessary. compemu.c seems to do it itself, but meanwhile make sure, that carry is set correctly MOV_ri(REG_WORK2, 32); // mov r3,#32 AND_rri(REG_WORK1, r, 0x1f); // and r2,r6,#0x1f SUB_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // sub r3,r3,r2 MOV_rrLSLi(REG_WORK1, d, 24); // mov r2,r7,lsl #24 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 8); // orr r2,r2,r2,lsr #8 RORS_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // rors r2,r2,r3 MRS_CPSR(REG_WORK2); // mrs r3,cpsr TST_ri(REG_WORK1, 1); // tst r2,#1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3,r3,#0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3,r3,#0x20000000 MSR_CPSR_r(REG_WORK2); AND_rri(REG_WORK1, REG_WORK1, 0xff); // and r2,r2,#0xff BIC_rri(d, d, 0xff); // bic r7,r7,#0xff ORR_rrr(d, d, REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_rol_w_ri,(RW2 r, IMM i)) { // TODO: Check if the Bittest is necessary. compemu.c seems to do it itself, but meanwhile make sure, that carry is set correctly int imm = 32 - (i & 0x1f); MOV_rrLSLi(REG_WORK1, r, 16); // mov r2,r7,lsl #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 RORS_rri(REG_WORK1, REG_WORK1, imm); // rors r2,r2,#(32 - (i & 0x1f)) MRS_CPSR(REG_WORK2); // mrs r3,cpsr TST_ri(REG_WORK1, 1); // tst r2,#1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3,r3,#0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3,r3,#0x20000000 MSR_CPSR_r(REG_WORK2); BIC_rri(r, r, 0xff00); // bic r2,r2,#0xff00 BIC_rri(r, r, 0xff); // bic r2,r2,#0xff ORR_rrrLSRi(r, r, REG_WORK1, 16); // orr r7,r7,r2,lsr #16 } LOWFUNC(WRITE,NONE,2,raw_rol_w_rr,(RW2 d, RR1 r)) { // TODO: Check if the Bittest is necessary. compemu.c seems to do it itself, but meanwhile make sure, that carry is set correctly MOV_ri(REG_WORK2, 32); // mov r3,#32 AND_rri(REG_WORK1, r, 0x1f); // and r2,r6,#0x1f SUB_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // sub r3,r3,r2 MOV_rrLSLi(REG_WORK1, d, 16); // mov r2,r7,lsl #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 RORS_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // rors r2,r2,r3 MRS_CPSR(REG_WORK2); // mrs r3,cpsr TST_ri(REG_WORK1, 1); // tst r2,#1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3,r3,#0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3,r3,#0x20000000 MSR_CPSR_r(REG_WORK2); BIC_rri(d, d, 0xff00); // bic r2,r2,#0xff00 BIC_rri(d, d, 0xff); // bic r2,r2,#0xff ORR_rrrLSRi(d, d, REG_WORK1, 16); // orr r2,r2,r7,lsr #16 } LOWFUNC(WRITE,NONE,2,raw_rol_l_ri,(RW4 r, IMM i)) { // TODO: Check if the Bittest is necessary. compemu.c seems to do it itself, but meanwhile make sure, that carry is set correctly int imm = 32 - (i & 0x1f); RORS_rri(r, r, imm); // rors r7,r7,#(32 - (i & 0x1f)) MRS_CPSR(REG_WORK2); // mrs r3,cpsr TST_ri(r, 1); // tst r7,#1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3,r3,#0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3,r3,#0x20000000 MSR_CPSR_r(REG_WORK2); } LOWFUNC(WRITE,NONE,2,raw_ror_l_ri,(RW4 r, IMM i)) { RORS_rri(r, r, i & 0x1F); // RORS r7,r7,#12 } LOWFUNC(WRITE,NONE,2,raw_rol_l_rr,(RW4 d, RR1 r)) { // TODO: Check if the Bittest is necessary. compemu.c seems to do it itself, but meanwhile make sure, that carry is set correctly MOV_ri(REG_WORK1, 32); // mov r2,#32 AND_rri(REG_WORK2, r, 0x1f); // and r3,r6,#0x1f SUB_rrr(REG_WORK1, REG_WORK1, REG_WORK2); // sub r2,r2,r3 RORS_rrr(d, d, REG_WORK1); // rors r7,r7,r2 MRS_CPSR(REG_WORK2); // mrs r3,cpsr TST_ri(d, 1); // tst r7,#1 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3,r3,#0x20000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3,r3,#0x20000000 MSR_CPSR_r(REG_WORK2); } LOWFUNC(WRITE,NONE,2,raw_ror_l_rr,(RW4 d, RR1 r)) { RORS_rrr(d, d, r); // RORS r7,r7,r6 } LOWFUNC(WRITE,NONE,2,raw_ror_b_ri,(RW1 r, IMM i)) { MOV_rrLSLi(REG_WORK1, r, 24); // mov r2,r7,lsl #24 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 8); // orr r2,r2,r2,lsr #8 RORS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // rors r2,r2,#12 AND_rri(REG_WORK1, REG_WORK1, 0xff); // and r2,r2,#0xff BIC_rri(r, r, 0xff); // bic r7,r7,#0xff ORR_rrr(r, r, REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_ror_b_rr,(RW1 d, RR1 r)) { MOV_rrLSLi(REG_WORK1, d, 24); // mov r2,r7,lsl #24 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 8); // orr r2,r2,r2,lsr #8 RORS_rrr(REG_WORK1, REG_WORK1, r); // rors r2,r2,r6 AND_rri(REG_WORK1, REG_WORK1, 0xff); // and r2,r2,#0xff BIC_rri(d, d, 0xff); // bic r7,r7,#0xff ORR_rrr(d, d, REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_ror_w_ri,(RW2 r, IMM i)) { MOV_rrLSLi(REG_WORK1, r, 16); // mov r2,r7,lsl #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 RORS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // RORS r2,r2,#12 BIC_rri(r, r, 0xff00); // bic r7,r7,#0xff00 BIC_rri(r, r, 0xff); // bic r7,r7,#0xff ORR_rrrLSRi(r, r, REG_WORK1, 16); // orr r7,r7,r2,lsr #16 } LOWFUNC(WRITE,NONE,2,raw_ror_w_rr,(RW2 d, RR1 r)) { MOV_rrLSLi(REG_WORK1, d, 16); // mov r2,r7,lsl #16 ORR_rrrLSRi(REG_WORK1, REG_WORK1, REG_WORK1, 16); // orr r2,r2,r2,lsr #16 RORS_rrr(REG_WORK1, REG_WORK1, r); // RORS r2,r2,r6 BIC_rri(d, d, 0xff00); // bic r7,r7,#0xff00 BIC_rri(d, d, 0xff); // bic r7,r7,#0xff ORR_rrrLSRi(d, d, REG_WORK1, 16); // orr r7,r7,r2,lsr #16 } LOWFUNC(RMW,NONE,2,raw_sbb_b,(RW1 d, RR1 s)) { MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 LSL_rri(REG_WORK2, d, 24); // lsl r3, %[d], #24 LSL_rri(REG_WORK1, s, 24); // lsl r2, r6, #24 SBCS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 BIC_rri(d, d, 0xFF); ORR_rrrLSRi(d, d, REG_WORK2, 24); // orr r7, r7, r3 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(RMW,NONE,2,raw_sbb_l,(RW4 d, RR4 s)) { MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 SBCS_rrr(d, d, s); // sbcs r7, r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(RMW,NONE,2,raw_sbb_w,(RW2 d, RR2 s)) { MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 LSL_rri(REG_WORK2, d, 16); // lsl r3, %[d], #24 LSL_rri(REG_WORK1, s, 16); // lsl r2, r6, #16 SBCS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 BIC_rri(d,d, 0xff); BIC_rri(d,d, 0xff00); ORR_rrrLSRi(d, d, REG_WORK2, 16); // orr r7, r7, r3 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(READ,NONE,2,raw_setcc,(W1 d, IMM cc)) { switch (cc) { case 9: // LS BEQ_i(0); // beq BCC_i(1); // bcs MOV_ri(d, 1); // mov r7,#0 B_i(0); // b //: MOV_ri(d, 0); // mov r7,#1 break; case 8: // HI BEQ_i(2); // beq Z != 0 BCS_i(1); // bcc C = 0 //: MOV_ri(d, 1); // mov r7,#0 B_i(0); // b //: MOV_ri(d, 0); // mov r7,#1 break; default: CC_MOV_ri(cc, d, 1); // MOVcc R7,#1 CC_MOV_ri(cc^1, d, 0); // MOVcc^1 R7,#0 break; } //: } LOWFUNC(READ,WRITE,2,raw_setcc_m,(MEMW d, IMM cc)) { switch (cc) { case 9: // LS BEQ_i(0); // beq BCC_i(1); // bcs MOV_ri(REG_WORK1, 1); // mov r2,#0 B_i(0); // b //: MOV_ri(REG_WORK1, 0); // mov r2,#1 break; case 8: // HI BEQ_i(2); // beq Z != 0 BCS_i(1); // bcc C = 0 MOV_ri(REG_WORK1, 1); // mov r2,#0 B_i(0); // b //: MOV_ri(REG_WORK1, 0); // mov r2,#1 break; default: CC_MOV_ri(cc, REG_WORK1, 1); // MOVcc R2,#1 CC_MOV_ri(cc^1, REG_WORK1, 0); // MOVcc^1 R2,#0 break; } //: #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK2, RPC_INDEX, offs); // LDR R3,[PC, #offs] #else LDR_rRI(REG_WORK2, RPC_INDEX, 4); // LDR R3,[PC, #4] #endif STRB_rR(REG_WORK1, REG_WORK2); // STRB R2,[R3] #if !defined(USE_DATA_BUFFER) B_i(0); // B emit_long(d); //: #endif } LOWFUNC(WRITE,NONE,2,raw_shll_b_ri,(RW1 r, IMM i)) { LSL_rri(REG_WORK1, r, 24); // LSL r2,r7,#24 LSLS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // LSLS r2,r2,#12 BIC_rri(r, r, 0xff); // BIC r7,r7,0xff ORR_rrrLSRi(r, r, REG_WORK1, 24); // ORR r7,r7,r2,lsr #24 } LOWFUNC(WRITE,NONE,2,raw_shll_b_rr,(RW1 d, RR1 r)) { LSL_rri(REG_WORK1, d, 24); // LSL r2,r7,#24 LSLS_rrr(REG_WORK1, REG_WORK1, r); // LSLS r2,r2,r6 BIC_rri(d, d, 0xff); // BIC r7,r7,#0xff ORR_rrrLSRi(d, d, REG_WORK1, 24); // ORR r7,r7,r2,lsr #24 } LOWFUNC(WRITE,NONE,2,raw_shll_l_ri,(RW4 r, IMM i)) { LSLS_rri(r,r, i & 0x1f); // lsls r7,r7,#12 } LOWFUNC(WRITE,NONE,2,raw_shll_l_rr,(RW4 d, RR1 r)) { LSLS_rrr(d, d, r); } LOWFUNC(WRITE,NONE,2,raw_shll_w_ri,(RW2 r, IMM i)) { LSL_rri(REG_WORK1, r, 16); // LSL r2,r7,#16 LSLS_rri(REG_WORK1, REG_WORK1, i&0x1f); // LSLS r2,r2,#12 ORR_rrrLSRi(REG_WORK1, REG_WORK1, r, 16); // ORR r2,r2,r7,lsr #16 ROR_rri(r, REG_WORK1, 16); // ROR r7,r2,#16 } LOWFUNC(WRITE,NONE,2,raw_shll_w_rr,(RW2 d, RR1 r)) { LSL_rri(REG_WORK1, d, 16); // LSL r2,r7,#16 LSLS_rrr(REG_WORK1, REG_WORK1, r); // LSLS r2,r2,r6 ORR_rrrLSRi(REG_WORK1, REG_WORK1, d, 16); // ORR r2,r2,r7,lsr #16 ROR_rri(d, REG_WORK1, 16); // ROR r7,r2,#16 } LOWFUNC(WRITE,NONE,2,raw_shra_b_ri,(RW1 r, IMM i)) { LSL_rri(REG_WORK1, r, 24); // lsl r2,r7,#24 ASR_rri(REG_WORK1, REG_WORK1, 24); // asr r2,r2,#24 ASRS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // asrs r2,r2,#12 AND_rri(REG_WORK1, REG_WORK1, 0xff); // and r2,r2,#0xff BIC_rri(r,r, 0xff); // bic r7,r7,#0xff ORR_rrr(r,r,REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shra_b_rr,(RW1 d, RR1 r)) { LSL_rri(REG_WORK1, d, 24); // lsl r2,r7,#24 ASR_rri(REG_WORK1, REG_WORK1, 24); // asr r2,r2,#24 ASRS_rrr(REG_WORK1, REG_WORK1, r); // asrs r2,r2,r6 AND_rri(REG_WORK1, REG_WORK1, 0xff); // and r2,r2,#0xff BIC_rri(d,d, 0xff); // bic r7,r7,#0xff ORR_rrr(d,d,REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shra_w_ri,(RW2 r, IMM i)) { LSL_rri(REG_WORK1, r, 16); // lsl r2,r7,#16 ASR_rri(REG_WORK1, REG_WORK1, 16); // asr r2,r2,#16 ASRS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // asrs r2,r2,#12 #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, REG_WORK1); #else BIC_rri(REG_WORK1, REG_WORK1, 0xff000000); BIC_rri(REG_WORK1, REG_WORK1, 0xff0000); #endif BIC_rri(r,r,0xff00); // bic r7,r7,#0xff00 BIC_rri(r,r,0xff); // bic r7,r7,#0xff ORR_rrr(r,r,REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shra_w_rr,(RW2 d, RR1 r)) { LSL_rri(REG_WORK1, d, 16); // lsl r2,r7,#16 ASR_rri(REG_WORK1, REG_WORK1, 16); // asr r2,r2,#16 ASRS_rrr(REG_WORK1, REG_WORK1, r); // asrs r2,r2,r6 #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, REG_WORK1); #else BIC_rri(REG_WORK1, REG_WORK1, 0xff000000); // bic r2,r2,#0xff000000 BIC_rri(REG_WORK1, REG_WORK1, 0xff0000); // bic r2,r2,#0xff0000 #endif BIC_rri(d,d, 0xff00); // bic r7,r7,#0xff00 BIC_rri(d,d, 0xff); // bic r7,r7,#0xff ORR_rrr(d,d,REG_WORK1); // orr r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shra_l_ri,(RW4 r, IMM i)) { ASRS_rri(r, r, i & 0x1f); // ASRS r7,r7,#12 } LOWFUNC(WRITE,NONE,2,raw_shra_l_rr,(RW4 d, RR1 r)) { ASRS_rrr(d, d, r); // ASRS r7,r7,r6 } LOWFUNC(WRITE,NONE,2,raw_shrl_b_ri,(RW1 r, IMM i)) { AND_rri(REG_WORK1, r, 0xff); // AND r2,r7,#0xFF LSRS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // LSRS r2,r2,r6 BIC_rri(r, r, 0xFF); // BIC r7,r7,#0xff ORR_rrr(r, r, REG_WORK1); // ORR r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shrl_b_rr,(RW1 d, RR1 r)) { AND_rri(REG_WORK1, d, 0xff); // AND r2,r7,#0xFF LSRS_rrr(REG_WORK1, REG_WORK1, r); // LSRS r2,r2,r6 BIC_rri(d, d, 0xFF); // BIC r7,r7,#0xff ORR_rrr(d, d, REG_WORK1); // ORR r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shrl_l_ri,(RW4 r, IMM i)) { LSRS_rri(r, r, i & 0x1f); // LSRS r7,r7,#12 } LOWFUNC(WRITE,NONE,2,raw_shrl_w_ri,(RW2 r, IMM i)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, r); #else BIC_rri(REG_WORK1, r, 0xff0000); // BIC r2,r7,#0xff0000 BIC_rri(REG_WORK1, REG_WORK1, 0xff000000); // BIC r2,r2,#0xff000000 #endif LSRS_rri(REG_WORK1, REG_WORK1, i & 0x1f); // LSRS r2,r2,#12 BIC_rri(r, r, 0xFF); // BIC r7,r7,#0xff BIC_rri(r, r, 0xFF00); // BIC r7,r7,#0xff00 ORR_rrr(r, r, REG_WORK1); // ORR r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shrl_w_rr,(RW2 d, RR1 r)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, d); #else BIC_rri(REG_WORK1, d, 0xff0000); // BIC r2,r7,#0xff0000 BIC_rri(REG_WORK1, REG_WORK1, 0xff000000); // BIC r2,r2,#0xff000000 #endif LSRS_rrr(REG_WORK1, REG_WORK1, r); // LSRS r2,r2,r6 BIC_rri(d, d, 0xFF); // BIC r7,r7,#0xff BIC_rri(d, d, 0xFF00); // BIC r7,r7,#0xff00 ORR_rrr(d, d, REG_WORK1); // ORR r7,r7,r2 } LOWFUNC(WRITE,NONE,2,raw_shrl_l_rr,(RW4 d, RR1 r)) { LSRS_rrr(d, d, r); } LOWFUNC(WRITE,NONE,2,raw_sub_b,(RW1 d, RR1 s)) { LSL_rri(REG_WORK1, s, 24); // lsl r2, r6, #24 LSL_rri(REG_WORK2, d, 24); // lsl r3, r7, #24 SUBS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 BIC_rri(d, d, 0xFF); ORR_rrrLSRi(d, d, REG_WORK2, 24); // orr r7, r7, r3 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_sub_b_ri,(RW1 d, IMM i)) { LSL_rri(REG_WORK2, d, 24); // lsl r3, r7, #24 SUBS_rri(REG_WORK2, REG_WORK2, i << 24); // subs r3, r3, #0x12000000 BIC_rri(d, d, 0xFF); // bic r7, r7, #0xFF ORR_rrrLSRi(d, d, REG_WORK2, 24); // orr r7, r7, r3, lsr #24 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_sub_l,(RW4 d, RR4 s)) { SUBS_rrr(d, d, s); // subs r7, r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_sub_l_ri,(RW4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 16); // ldr r2, [pc, #16] ; #endif SUBS_rrr(d, d, REG_WORK1); // subs r7, r7, r2 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(i); //: #endif } LOWFUNC(WRITE,NONE,2,raw_sub_w,(RW2 d, RR2 s)) { LSL_rri(REG_WORK1, s, 16); // lsl r2, r6, #16 LSL_rri(REG_WORK2, d, 16); // lsl r3, r7, #16 SUBS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 BIC_rri(d, d, 0xff); BIC_rri(d, d, 0xff00); ORR_rrrLSRi(d, d, REG_WORK2, 16); // orr r7, r7, r3 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_sub_w_ri,(RW2 d, IMM i)) { // TODO: optimize_imm #if defined(USE_DATA_BUFFER) long offs = data_word_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; #else LDRH_rRI(REG_WORK1, RPC_INDEX, 36); // ldrh r2, [pc, #36] ; #endif LSL_rri(REG_WORK1, REG_WORK1, 16); // lsl r2, r2, #16 LSL_rri(REG_WORK2, d, 16); // lsl r3, r6, #16 SUBS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 BIC_rri(d, d, 0xff); BIC_rri(d, d, 0xff00); ORR_rrrLSRi(d, d, REG_WORK2, 16); // orr r6, r3, r6, lsr #16 MRS_CPSR(REG_WORK1); // mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); // eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 #if !defined(USE_DATA_BUFFER) B_i(0); // b emit_word(i); skip_word(0); //: //: #endif } LOWFUNC(WRITE,NONE,2,raw_test_b_rr,(RR1 d, RR1 s)) { #if defined(ARMV6_ASSEMBLY) SXTB_rr(REG_WORK1, s); SXTB_rr(REG_WORK2, d); #else LSL_rri(REG_WORK1, s, 24); // lsl r2, r6, #24 LSL_rri(REG_WORK2, d, 24); // lsl r3, r7, #24 #endif TST_rr(REG_WORK2, REG_WORK1); // tst r3, r2 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_test_l_ri,(RR4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rRI(REG_WORK1, RPC_INDEX, 16); // ldr r2, [pc, #16] ; #endif TST_rr(d, REG_WORK1); // tst r7, r2 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(i); //: #endif } LOWFUNC(WRITE,NONE,2,raw_test_l_rr,(RR4 d, RR4 s)) { TST_rr(d, s); // tst r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_test_w_rr,(RR2 d, RR2 s)) { #ifdef ARMV6_ASSEMBLY SXTH_rr(REG_WORK1, s); SXTH_rr(REG_WORK2, d); #else LSL_rri(REG_WORK1, s, 16); // lsl r2, r6, #16 LSL_rri(REG_WORK2, d, 16); // lsl r3, r7, #16 #endif TST_rr(REG_WORK2, REG_WORK1); // tst r3, r2 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_xor_b,(RW1 d, RR1 s)) { AND_rri(REG_WORK1, s, 0xFF); // and r2, %[s], 0xFF EOR_rrr(d, d, REG_WORK1); // eor %[d], %[d], r2 LSLS_rri(REG_WORK1, d, 24); // lsls r2, %[d], #24 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_xor_w,(RW2 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, s); // UXTH r2, %[s] #else BIC_rri(REG_WORK1, s, 0xff000000); // bic r2, %[s], #0xff000000 BIC_rri(REG_WORK1, REG_WORK1, 0x00ff0000); // bic r2, r2, #0x00ff0000 #endif EOR_rrr(d, d, REG_WORK1); // eor %[d], %[d], r2 LSLS_rri(REG_WORK1, d, 16); // lsls r2, %[d], #16 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(WRITE,NONE,2,raw_xor_l,(RW4 d, RR4 s)) { EORS_rrr(d, d, s); // eors r7, r7, r6 MRS_CPSR(REG_WORK1); // mrs r2, CPSR BIC_rri(REG_WORK1, REG_WORK1, ARM_CV_FLAGS); // bic r2, r2, #0x30000000 MSR_CPSR_r(REG_WORK1); // msr CPSR_fc, r2 } LOWFUNC(NONE,NONE,2,raw_sign_extend_16_rr,(W4 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) SXTH_rr(d, s); // sxth %[d],%[s] #else LSL_rri(d, s, 16); // lsl r6, r7, #16 ASR_rri(d, d, 16); // asr r6, r6, #16 #endif } LOWFUNC(NONE,NONE,2,raw_sign_extend_8_rr,(W4 d, RR1 s)) { #if defined(ARMV6_ASSEMBLY) SXTB_rr(d, s); // SXTB %[d],%[s] #else ROR_rri(d, s, 8); // ror r6, r7, #8 ASR_rri(d, d, 24); // asr r6, r6, #24 #endif } LOWFUNC(NONE,NONE,2,raw_zero_extend_8_rr,(W4 d, RR1 s)) { #if defined(ARMV6_ASSEMBLY) UXTB_rr(d, s); // UXTB %[d], %[s] #else ROR_rri(d, s, 8); // ror r2, r1, #8 LSR_rri(d, d, 24); // lsr r2, r2, #24 #endif } LOWFUNC(NONE,NONE,2,raw_zero_extend_16_rr,(W4 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(d, s); // UXTH %[d], %[s] #else BIC_rri(d, s, 0xff000000); // bic %[d], %[s], #0xff000000 BIC_rri(d, d, 0x00ff0000); // bic %[d], %[d], #0x00ff0000 #endif } static inline void raw_dec_sp(int off) { if (off) { LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; SUB_rrr(RSP_INDEX, RSP_INDEX, REG_WORK1); // sub r7, r7, r2 B_i(0); // b //: emit_long(off); } } static inline void raw_inc_sp(int off) { if (off) { LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; ADD_rrr(RSP_INDEX, RSP_INDEX, REG_WORK1); // sub r7, r7, r2 B_i(0); // b //: emit_long(off); } } static inline void raw_push_regs_to_preserve(void) { PUSH_REGS(PRESERVE_MASK); } static inline void raw_pop_preserved_regs(void) { POP_REGS(PRESERVE_MASK); } // Verify!!! /* FLAGX is byte sized, and we *do* write it at that size */ static inline void raw_load_flagx(uae_u32 t) { raw_mov_l_rm(t,(uintptr)live.state[FLAGX].mem); } static inline void raw_flags_evicted(int r) { //live.state[FLAGTMP].status=CLEAN; live.state[FLAGTMP].status=INMEM; live.state[FLAGTMP].realreg=-1; /* We just "evicted" FLAGTMP. */ if (live.nat[r].nholds!=1) { /* Huh? */ abort(); } live.nat[r].nholds=0; } static inline void raw_flags_init(void) { } static __inline__ void raw_flags_set_zero(int s, int tmp) { raw_mov_l_rr(tmp,s); MRS_CPSR(s); BIC_rri(s,s,ARM_Z_FLAG); AND_rri(tmp,tmp,ARM_Z_FLAG); EOR_rri(tmp,tmp,ARM_Z_FLAG); ORR_rrr(s,s,tmp); MSR_CPSR_r(s); } static inline void raw_flags_to_reg(int r) { MRS_CPSR(r); raw_mov_l_mr((uintptr)live.state[FLAGTMP].mem,r); raw_flags_evicted(r); } static inline void raw_reg_to_flags(int r) { MSR_CPSR_r(r); // msr CPSR_fc, %r } /* Apparently, there are enough instructions between flag store and flag reload to avoid the partial memory stall */ static inline void raw_load_flagreg(uae_u32 t) { raw_mov_l_rm(t,(uintptr)live.state[FLAGTMP].mem); } /* %eax register is clobbered if target processor doesn't support fucomi */ #define FFLAG_NREG_CLOBBER_CONDITION !have_cmov #define FFLAG_NREG R0_INDEX #define FLAG_NREG2 -1 #define FLAG_NREG1 -1 #define FLAG_NREG3 -1 static inline void raw_fflags_into_flags(int r) { jit_unimplemented("raw_fflags_into_flags %x", r); } static inline void raw_fp_init(void) { int i; for (i=0;i=1) { // emit_byte(0xde); // emit_byte(0xd9); live.tos-=2; } while (live.tos>=0) { // emit_byte(0xdd); // emit_byte(0xd8); live.tos--; } raw_fp_init(); } LOWFUNC(NONE,WRITE,2,raw_fmov_mr_drop,(MEMPTRW m, FR r)) { jit_unimplemented("raw_fmov_mr_drop %x %x", m, r); } LOWFUNC(NONE,WRITE,2,raw_fmov_mr,(MEMPTRW m, FR r)) { jit_unimplemented("raw_fmov_mr %x %x", m, r); } LOWFUNC(NONE,READ,2,raw_fmov_rm,(FW r, MEMPTRR m)) { jit_unimplemented("raw_fmov_rm %x %x", r, m); } LOWFUNC(NONE,NONE,2,raw_fmov_rr,(FW d, FR s)) { jit_unimplemented("raw_fmov_rr %x %x", d, s); } static inline void raw_emit_nop_filler(int nbytes) { nbytes >>= 2; while(nbytes--) { NOP(); } } static inline void raw_emit_nop(void) { NOP(); } #ifdef UAE static #endif void compiler_status() { jit_log("compiled code starts at %p, current at %p (size 0x%x)", compiled_code, current_compile_p, (unsigned int)(current_compile_p - compiled_code)); } // // ARM doesn't have bsf, but clz is a good alternative instruction for it // static bool target_check_bsf(void) { return false; } static void raw_init_cpu(void) { /* Have CMOV support, because ARM support conditions for all instructions */ have_cmov = true; align_loops = 0; align_jumps = 0; raw_flags_init(); } // // Arm instructions // LOWFUNC(WRITE,NONE,2,raw_ADD_l_rr,(RW4 d, RR4 s)) { ADD_rrr(d, d, s); } LOWFUNC(WRITE,NONE,2,raw_ADD_l_rri,(RW4 d, RR4 s, IMM i)) { ADD_rri(d, s, i); } LOWFUNC(WRITE,NONE,2,raw_SUB_l_rri,(RW4 d, RR4 s, IMM i)) { SUB_rri(d, s, i); } LOWFUNC(WRITE,NONE,2,raw_AND_b_rr,(RW1 d, RR1 s)) { MVN_rrLSLi(REG_WORK1, s, 24); // mvn r2, %[s], lsl #24 MVN_rrLSRi(REG_WORK1, REG_WORK1, 24); // mvn r2, %[s], lsr #24 AND_rrr(d, d, REG_WORK1); // and %[d], %[d], r2 } LOWFUNC(WRITE,NONE,2,raw_AND_l_rr,(RW4 d, RR4 s)) { AND_rrr(d, d, s); } LOWFUNC(WRITE,NONE,2,raw_AND_l_ri,(RW4 d, IMM i)) { AND_rri(d, d, i); } LOWFUNC(WRITE,NONE,2,raw_AND_w_rr,(RW2 d, RR2 s)) { MVN_rrLSLi(REG_WORK1, s, 16); // mvn r2, %[s], lsl #16 MVN_rrLSRi(REG_WORK1, REG_WORK1, 16); // mvn r2, %[s], lsr #16 AND_rrr(d, d, REG_WORK1); // and %[d], %[d], r2 } LOWFUNC(WRITE,NONE,2,raw_EOR_b_rr,(RW1 d, RR1 s)) { #if defined(ARMV6_ASSEMBLY) UXTB_rr(REG_WORK1, s); // UXTH r2, %[s] #else AND_rri(REG_WORK1, s, 0xFF); // and r2, %[s], 0xFF #endif EOR_rrr(d, d, REG_WORK1); // eor %[d], %[d], r2 } LOWFUNC(WRITE,NONE,2,raw_EOR_l_rr,(RW4 d, RR4 s)) { EOR_rrr(d, d, s); // eors r7, r7, r6 } LOWFUNC(WRITE,NONE,2,raw_EOR_w_rr,(RW2 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, s); // UXTH r2, %[s] EOR_rrr(d, d, REG_WORK1); // eor %[d], %[d], r2 #else LSL_rri(REG_WORK1, s, 16); // bic r2, %[s], #0xff000000 EOR_rrrLSRi(d, d, REG_WORK1, 16); // orr %[d], %[d], r2 #endif } LOWFUNC(WRITE,NONE,2,raw_LDR_l_ri,(RW4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(d, RPC_INDEX, offs); // ldr r2, [pc, #offs] #else LDR_rR(d, RPC_INDEX); B_i(0); emit_long(i); #endif } LOWFUNC(WRITE,NONE,2,raw_MOV_l_ri8,(RW4 d, IMM i)) { MOV_ri(d, i); } LOWFUNC(WRITE,NONE,2,raw_ORR_b_rr,(RW1 d, RR1 s)) { #if defined(ARMV6_ASSEMBLY) UXTB_rr(REG_WORK1, s); // UXTH r2, %[s] #else AND_rri(REG_WORK1, s, 0xFF); // and r2, %[s], 0xFF #endif ORR_rrr(d, d, REG_WORK1); // orr %[d], %[d], r2 } LOWFUNC(WRITE,NONE,2,raw_ORR_l_rr,(RW4 d, RR4 s)) { ORR_rrr(d, d, s); } LOWFUNC(WRITE,NONE,2,raw_ORR_w_rr,(RW2 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK1, s); // UXTH r2, %[s] ORR_rrr(d, d, REG_WORK1); // orr %[d], %[d], r2 #else LSL_rri(REG_WORK1, s, 16); // bic r2, %[s], #0xff000000 ORR_rrrLSRi(d, d, REG_WORK1, 16); // orr %[d], %[d], r2 #endif } LOWFUNC(WRITE,NONE,2,raw_ROR_l_ri,(RW4 r, IMM i)) { ROR_rri(r, r, i); } // // compuemu_support used raw calls // LOWFUNC(WRITE,RMW,2,compemu_raw_add_l_mi,(IMM d, IMM s)) { #if defined(USE_DATA_BUFFER) data_check_end(8, 24); long target = data_long(d, 24); long offs = get_data_offset(target); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d LDR_rR(REG_WORK2, REG_WORK1); // ldr r3, [r2] offs = data_long_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; s ADD_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adds r3, r3, r2 offs = get_data_offset(target); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 20); // ldr r2, [pc, #20] ; LDR_rR(REG_WORK2, REG_WORK1); // ldr r3, [r2] LDR_rRI(REG_WORK1, RPC_INDEX, 16); // ldr r2, [pc, #16] ; ADD_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // adds r3, r3, r2 LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] B_i(1); // b //: emit_long(d); //: emit_long(s); //: #endif } LOWFUNC(WRITE,NONE,2,compemu_raw_and_l_ri,(RW4 d, IMM i)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(i); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; AND_rrr(d, d, REG_WORK1); // ands %[d], %[d], r2 #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #16] ; AND_rrr(d, d, REG_WORK1); // ands %[d], %[d], r2 B_i(0); emit_long(i); #endif } LOWFUNC(NONE,NONE,1,compemu_raw_bswap_32,(RW4 r)) { #if defined(ARMV6_ASSEMBLY) REV_rr(r,r); // rev %[r],%[r] #else EOR_rrrRORi(REG_WORK1, r, r, 16); // eor r2, r6, r6, ror #16 BIC_rri(REG_WORK1, REG_WORK1, 0xff0000); // bic r2, r2, #0xff0000 ROR_rri(r, r, 8); // ror r6, r6, #8 EOR_rrrLSRi(r, r, REG_WORK1, 8); // eor r6, r6, r2, lsr #8 #endif } LOWFUNC(WRITE,NONE,2,compemu_raw_bt_l_ri,(RR4 r, IMM i)) { int imm = (1 << (i & 0x1f)); MRS_CPSR(REG_WORK2); // mrs r3, CPSR TST_ri(r, imm); // tst r6, #0x1000000 CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); // bic r3, r3, #0x20000000 CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); // orr r3, r3, #0x20000000 MSR_CPSR_r(REG_WORK2); // msr CPSR_fc, r3 } LOWFUNC(NONE,READ,5,compemu_raw_cmov_l_rm_indexed,(W4 d, IMM base, RR4 index, IMM factor, IMM cond)) { int shft; switch(factor) { case 1: shft=0; break; case 2: shft=1; break; case 4: shft=2; break; case 8: shft=3; break; default: abort(); } switch (cond) { case 9: // LS jit_unimplemented("cmov LS not implemented"); abort(); case 8: // HI jit_unimplemented("cmov HI not implemented"); abort(); default: #if defined(USE_DATA_BUFFER) long offs = data_long_offs(base); CC_LDR_rRI(cond, REG_WORK1, RPC_INDEX, offs); // ldrcc r2, [pc, #offs] ; CC_LDR_rRR_LSLi(cond, d, REG_WORK1, index, shft); // ldrcc %[d], [r2, %[index], lsl #[shift]] #else CC_LDR_rRI(cond, REG_WORK1, RPC_INDEX, 4); // ldrcc r2, [pc, #4] ; CC_LDR_rRR_LSLi(cond, d, REG_WORK1, index, shft); // ldrcc %[d], [r2, %[index], lsl #[shift]] B_i(0); // b #endif break; } #if !defined(USE_DATA_BUFFER) emit_long(base); // : //: #endif } LOWFUNC(WRITE,READ,2,compemu_raw_cmp_l_mi,(MEMR d, IMM s)) { #if defined(USE_DATA_BUFFER) data_check_end(8, 16); long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d LDR_rR(REG_WORK1, REG_WORK1); // ldr r2, [r2] offs = data_long_offs(s); LDR_rRI(REG_WORK2, RPC_INDEX, offs); // ldr r3, [pc, #offs] ; s CMP_rr(REG_WORK1, REG_WORK2); // cmp r2, r3 #else LDR_rRI(REG_WORK1, RPC_INDEX, 12); // ldr r2, [pc, #24] ; LDR_rR(REG_WORK1, REG_WORK1); // ldr r2, [r2] LDR_rRI(REG_WORK2, RPC_INDEX, 8); // ldr r3, [pc, #20] ; CMP_rr(REG_WORK1, REG_WORK2); // cmp r2, r3 B_i(1); // b //: emit_long(d); //: emit_long(s); //: #endif } LOWFUNC(WRITE,READ,2,compemu_raw_cmp_l_mi8,(MEMR d, IMM s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; #else LDR_rRI(REG_WORK1, RPC_INDEX, 8); // ldr r2, [pc, #8] ; #endif LDR_rR(REG_WORK1, REG_WORK1); // ldr r2, [r2] CMP_ri(REG_WORK1, s); // cmp r2, r3 #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(NONE,NONE,3,compemu_raw_lea_l_brr,(W4 d, RR4 s, IMM offset)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(offset); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; ADD_rrr(d, s, REG_WORK1); // add r7, r6, r2 #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; ADD_rrr(d, s, REG_WORK1); // add r7, r6, r2 B_i(0); // b //: emit_long(offset); //: #endif } LOWFUNC(NONE,NONE,4,compemu_raw_lea_l_rr_indexed,(W4 d, RR4 s, RR4 index, IMM factor)) { int shft; switch(factor) { case 1: shft=0; break; case 2: shft=1; break; case 4: shft=2; break; case 8: shft=3; break; default: abort(); } ADD_rrrLSLi(d, s, index, shft); // ADD R7,R6,R5,LSL #2 } LOWFUNC(NONE,WRITE,2,compemu_raw_mov_b_mr,(IMM d, RR1 s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; STRB_rR(s, REG_WORK1); // strb r6, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STRB_rR(s, REG_WORK1); // strb r6, [r2] B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(NONE,WRITE,2,compemu_raw_mov_l_mi,(MEMW d, IMM s)) { // TODO: optimize imm #if defined(USE_DATA_BUFFER) data_check_end(8, 12); long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d offs = data_long_offs(s); LDR_rRI(REG_WORK2, RPC_INDEX, offs); // ldr r3, [pc, #offs] ; s STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 8); // ldr r2, [pc, #8] ; LDR_rRI(REG_WORK2, RPC_INDEX, 8); // ldr r3, [pc, #8] ; STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] B_i(1); // b emit_long(d); //: emit_long(s); //: //: #endif } LOWFUNC(NONE,WRITE,2,compemu_raw_mov_l_mr,(IMM d, RR4 s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; STR_rR(s, REG_WORK1); // str r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STR_rR(s, REG_WORK1); // str r3, [r2] B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(NONE,NONE,2,compemu_raw_mov_l_ri,(W4 d, IMM s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(s); LDR_rRI(d, RPC_INDEX, offs); // ldr %[d], [pc, #offs] ; #else LDR_rR(d, RPC_INDEX); // ldr %[d], [pc] ; B_i(0); // b //: emit_long(s); //: #endif } LOWFUNC(NONE,READ,2,compemu_raw_mov_l_rm,(W4 d, MEMR s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; LDR_rR(d, REG_WORK1); // ldr r7, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; LDR_rR(d, REG_WORK1); // ldr r7, [r2] B_i(0); // b emit_long(s); //: //: #endif } LOWFUNC(NONE,NONE,2,compemu_raw_mov_l_rr,(W4 d, RR4 s)) { MOV_rr(d, s); // mov %[d], %[s] } LOWFUNC(NONE,WRITE,2,compemu_raw_mov_w_mr,(IMM d, RR2 s)) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(d); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; STRH_rR(s, REG_WORK1); // strh r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #4] ; STRH_rR(s, REG_WORK1); // strh r3, [r2] B_i(0); // b //: emit_long(d); //: #endif } LOWFUNC(WRITE,RMW,2,compemu_raw_sub_l_mi,(MEMRW d, IMM s)) { #if defined(USE_DATA_BUFFER) data_check_end(8, 24); long target = data_long(d, 24); long offs = get_data_offset(target); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d LDR_rR(REG_WORK2, REG_WORK1); // ldr r3, [r2] offs = data_long_offs(s); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; s SUBS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 offs = get_data_offset(target); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; d STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] #else LDR_rRI(REG_WORK1, RPC_INDEX, 20); // ldr r2, [pc, #32] ; LDR_rR(REG_WORK2, REG_WORK1); // ldr r3, [r2] LDR_rRI(REG_WORK1, RPC_INDEX, 16); // ldr r2, [pc, #28] ; SUBS_rrr(REG_WORK2, REG_WORK2, REG_WORK1); // subs r3, r3, r2 LDR_rRI(REG_WORK1, RPC_INDEX, 4); // ldr r2, [pc, #16] ; STR_rR(REG_WORK2, REG_WORK1); // str r3, [r2] B_i(1); // b //: emit_long(d); //: emit_long(s); //: #endif } LOWFUNC(WRITE,NONE,2,compemu_raw_test_l_rr,(RR4 d, RR4 s)) { TST_rr(d, s); // tst r7, r6 } LOWFUNC(NONE,NONE,2,compemu_raw_zero_extend_16_rr,(W4 d, RR2 s)) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(d, s); // UXTH %[d], %[s] #else BIC_rri(d, s, 0xff000000); // bic %[d], %[s], #0xff000000 BIC_rri(d, d, 0x00ff0000); // bic %[d], %[d], #0x00ff0000 #endif } static inline void compemu_raw_call(uae_u32 t) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(t); LDR_rRI(REG_WORK1, RPC_INDEX, offs); // ldr r2, [pc, #offs] ; #else LDR_rRI(REG_WORK1, RPC_INDEX, 12); // ldr r2, [pc, #12] ; #endif PUSH(RLR_INDEX); // push {lr} BLX_r(REG_WORK1); // blx r2 POP(RLR_INDEX); // pop {lr} #if !defined(USE_DATA_BUFFER) B_i(0); // b //: emit_long(t); //: #endif } #if defined(UAE) static inline void compemu_raw_call_r(RR4 r) { PUSH(RLR_INDEX); // push {lr} BLX_r(r); // blx r0 POP(RLR_INDEX); // pop {lr} } #endif static inline void compemu_raw_jcc_l_oponly(int cc) { switch (cc) { case 9: // LS BEQ_i(0); // beq BCC_i(2); // bcc //: LDR_rR(REG_WORK1, RPC_INDEX); // ldr r2, [pc] ; BX_r(REG_WORK1); // bx r2 break; case 8: // HI BEQ_i(3); // beq BCS_i(2); // bcs //: LDR_rR(REG_WORK1, RPC_INDEX); // ldr r2, [pc] ; BX_r(REG_WORK1); // bx r2 break; default: CC_LDR_rRI(cc, REG_WORK1, RPC_INDEX, 4); // ldrlt r2, [pc, #4] ; CC_BX_r(cc, REG_WORK1); // bxlt r2 B_i(0); // b break; } // emit of target will be done by caller } static inline void compemu_raw_jl(uae_u32 t) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(t); CC_LDR_rRI(NATIVE_CC_LT, RPC_INDEX, RPC_INDEX, offs); // ldrlt pc, [pc, offs] #else CC_LDR_rR(NATIVE_CC_LT, RPC_INDEX, RPC_INDEX); // ldrlt pc, [pc] B_i(0); // b //: emit_long(t); //: #endif } static inline void compemu_raw_jmp(uae_u32 t) { LDR_rR(REG_WORK1, RPC_INDEX); // ldr r2, [pc] BX_r(REG_WORK1); // bx r2 emit_long(t); } static inline void compemu_raw_jmp_m_indexed(uae_u32 base, uae_u32 r, uae_u32 m) { int shft; switch(m) { case 1: shft=0; break; case 2: shft=1; break; case 4: shft=2; break; case 8: shft=3; break; default: abort(); } LDR_rR(REG_WORK1, RPC_INDEX); // ldr r2, [pc] ; LDR_rRR_LSLi(RPC_INDEX, REG_WORK1, r, shft); // ldr pc, [r2, r6, lsl #3] emit_long(base); } static inline void compemu_raw_jmp_r(RR4 r) { BX_r(r); } static inline void compemu_raw_jnz(uae_u32 t) { #if defined(USE_DATA_BUFFER) long offs = data_long_offs(t); CC_LDR_rRI(NATIVE_CC_NE, RPC_INDEX, RPC_INDEX, offs); // ldrne pc, [pc, offs] #else CC_LDR_rR(NATIVE_CC_NE, RPC_INDEX, RPC_INDEX); // ldrne pc, [pc] B_i(0); // b emit_long(t); //: #endif } static inline void compemu_raw_jz_b_oponly(void) { BNE_i(2); // bne jp LDRSB_rRI(REG_WORK1, RPC_INDEX, 3); // ldrsb r2,[pc,#3] ADD_rrr(RPC_INDEX, RPC_INDEX, REG_WORK1); // add pc,pc,r2 skip_n_bytes(3); /* additionally 1 byte skipped by generic code */ // } static inline void compemu_raw_jnz_b_oponly(void) { BEQ_i(2); // beq jp LDRSB_rRI(REG_WORK1, RPC_INDEX, 3); // ldrsb r2,[pc,#3] ADD_rrr(RPC_INDEX, RPC_INDEX, REG_WORK1); // add pc,pc,r2 skip_n_bytes(3); /* additionally 1 byte skipped by generic code */ // } static inline void compemu_raw_branch(IMM d) { B_i((d >> 2) - 1); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/codegen_arm.h000066400000000000000000002417301504763705000253300ustar00rootroot00000000000000/* * compiler/codegen_arm.h - IA-32 and AMD64 code generator * * Copyright (c) 2013 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> ARM * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * This file is derived from CCG, copyright 1999-2003 Ian Piumarta * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef ARM_RTASM_H #define ARM_RTASM_H /* NOTES * */ /* --- Configuration ------------------------------------------------------- */ /* CPSR flags */ #define ARM_N_FLAG 0x80000000 #define ARM_Z_FLAG 0x40000000 #define ARM_C_FLAG 0x20000000 #define ARM_V_FLAG 0x10000000 #define ARM_Q_FLAG 0x08000000 #define ARM_CV_FLAGS (ARM_C_FLAG|ARM_V_FLAG) #define ARM_GE3 0x00080000 #define ARM_GE2 0x00040000 #define ARM_GE1 0x00020000 #define ARM_GE0 0x00010000 /* --- Macros -------------------------------------------------------------- */ /* ========================================================================= */ /* --- UTILITY ------------------------------------------------------------- */ /* ========================================================================= */ #define _W(c) emit_long(c) #define _LS2_ADDR(a) (((a) & 0x01f0000f) | (((a) & 0xf0) << 4)) /* ========================================================================= */ /* --- ENCODINGS ----------------------------------------------------------- */ /* ========================================================================= */ #define IMM32(c) (((c) & 0xffffff00) == 0 ? (c) : \ ((c) & 0x3fffffc0) == 0 ? (0x100 | (((c) >> 30) & 0x3) | ((((c) & 0x0000003f) << 2))) : \ ((c) & 0x0ffffff0) == 0 ? (0x200 | (((c) >> 28) & 0xf) | ((((c) & 0x0000000f) << 4))) : \ ((c) & 0x03fffffc) == 0 ? (0x300 | (((c) >> 26) & 0x3f) | ((((c) & 0x00000003) << 6)) ) : \ ((c) & 0x00ffffff) == 0 ? (0x400 | (((c) >> 24) & 0xff)) : \ ((c) & 0xc03fffff) == 0 ? (0x500 | ((c) >> 22)) : \ ((c) & 0xf00fffff) == 0 ? (0x600 | ((c) >> 20)) : \ ((c) & 0xfc03ffff) == 0 ? (0x700 | ((c) >> 18)) : \ ((c) & 0xff00ffff) == 0 ? (0x800 | ((c) >> 16)) : \ ((c) & 0xffc03fff) == 0 ? (0x900 | ((c) >> 14)) : \ ((c) & 0xfff00fff) == 0 ? (0xa00 | ((c) >> 12)) : \ ((c) & 0xfffc03ff) == 0 ? (0xb00 | ((c) >> 10)) : \ ((c) & 0xffff00ff) == 0 ? (0xc00 | ((c) >> 8)) : \ ((c) & 0xffffc03f) == 0 ? (0xd00 | ((c) >> 6)) : \ ((c) & 0xfffff00f) == 0 ? (0xe00 | ((c) >> 4)) : \ ((c) & 0xfffffc03) == 0 ? (0xf00 | ((c) >> 2)) : \ 0\ ) #define SHIFT_IMM(c) (0x02000000 | (IMM32((c)))) #define UNSHIFTED_IMM8(c) (0x02000000 | (c)) #define SHIFT_IMM8_ROR(c,r) (0x02000000 | (c) | ((r >> 1) << 8)) #define SHIFT_REG(Rm) (Rm) #define SHIFT_LSL_i(Rm,s) ((Rm) | ((s) << 7)) #define SHIFT_LSL_r(Rm,Rs) ((Rm) | ((Rs) << 8) | 0x10) #define SHIFT_LSR_i(Rm,s) ((Rm) | ((s) << 7) | 0x20) #define SHIFT_LSR_r(Rm,Rs) ((Rm) | ((Rs) << 8) | 0x30) #define SHIFT_ASR_i(Rm,s) ((Rm) | ((s) << 7) | 0x40) #define SHIFT_ASR_r(Rm,Rs) ((Rm) | ((Rs) << 8) | 0x50) #define SHIFT_ROR_i(Rm,s) ((Rm) | ((s) << 7) | 0x60) #define SHIFT_ROR_r(Rm,Rs) ((Rm) | ((Rs) << 8) | 0x70) #define SHIFT_RRX(Rm) ((Rm) | 0x60) #define SHIFT_PK(Rm,s) ((Rm) | ((s) << 7)) /* Load/Store addressings */ #define ADR_ADD(v) ((1 << 23) | (v)) #define ADR_SUB(v) (v) #define ADR_IMM(v) ((v) | (1 << 24)) #define ADR_IMMPOST(v) (v) #define ADR_REG(Rm) ((1 << 25) | (1 << 24) | (Rm)) #define ADR_REGPOST(Rm) ((1 << 25) | (Rm)) #define ADD_IMM(i) ADR_ADD(ADR_IMM(i)) #define SUB_IMM(i) ADR_SUB(ADR_IMM(i)) #define ADD_REG(Rm) ADR_ADD(ADR_REG(Rm)) #define SUB_REG(Rm) ADR_SUB(ADR_REG(Rm)) #define ADD_LSL(Rm,i) ADR_ADD(ADR_REG(Rm) | ((i) << 7)) #define SUB_LSL(Rm,i) ADR_SUB(ADR_REG(Rm) | ((i) << 7)) #define ADD_LSR(Rm,i) ADR_ADD(ADR_REG(Rm) | (((i) & 0x1f) << 7) | (1 << 5)) #define SUB_LSR(Rm,i) ADR_SUB(ADR_REG(Rm) | (((i) & 0x1f) << 7) | (1 << 5)) #define ADD_ASR(Rm,i) ADR_ADD(ADR_REG(Rm) | (((i) & 0x1f) << 7) | (2 << 5)) #define SUB_ASR(Rm,i) ADR_SUB(ADR_REG(Rm) | (((i) & 0x1f) << 7) | (2 << 5)) #define ADD_ROR(Rm,i) ADR_ADD(ADR_REG(Rm) | (((i) & 0x1f) << 7) | (3 << 5)) #define SUB_ROR(Rm,i) ADR_SUB(ADR_REG(Rm) | (((i) & 0x1f) << 7) | (3 << 5)) #define ADD_RRX(Rm) ADR_ADD(ADR_REG(Rm) | (3 << 5)) #define SUB_RRX(Rm) ADR_SUB(ADR_REG(Rm) | (3 << 5)) #define ADD2_IMM(i) ADR_ADD(i | (1 << 22)) #define SUB2_IMM(i) ADR_SUB(i | (1 << 22)) #define ADD2_REG(Rm) ADR_ADD(Rm) #define SUB2_REG(Rm) ADR_SUB(Rm) /* MOV, MVN */ #define _OP1(cc,op,s,Rd,shift) _W(((cc) << 28) | ((op) << 21) | ((s) << 20) | ((Rd) << 12) | (shift)) /* CMP, CMN, TST, TEQ */ #define _OP2(cc,op,Rn,shift) _W(((cc) << 28) | ((op) << 21) | (1 << 20) | ((Rn) << 16) | (shift)) /* ADD, SUB, RSB, ADC, SBC, RSC, AND, BIC, EOR, ORR */ #define _OP3(cc,op,s,Rd,Rn,shift) _W(((cc) << 28) | ((op) << 21) | ((s) << 20) | ((Rn) << 16) | ((Rd) << 12) | (shift)) /* LDR, STR */ #define _LS1(cc,l,b,Rd,Rn,a) _W(((cc) << 28) | (0x01 << 26) | ((l) << 20) | ((b) << 22) | ((Rn) << 16) | ((Rd) << 12) | (a)) #define _LS2(cc,p,l,s,h,Rd,Rn,a) _W(((cc) << 28) | ((p) << 24) | ((l) << 20) | ((Rn) << 16) | ((Rd) << 12) | ((s) << 6) | ((h) << 5) | 0x90 | _LS2_ADDR((a))) /* ========================================================================= */ /* --- OPCODES ------------------------------------------------------------- */ /* ========================================================================= */ /* Branch instructions */ #ifndef __ANDROID__ enum { _B, _BL, _BLX, _BX, _BXJ }; #endif /* Data processing instructions */ enum { _AND = 0, _EOR, _SUB, _RSB, _ADD, _ADC, _SBC, _RSC, _TST, _TEQ, _CMP, _CMN, _ORR, _MOV, _BIC, _MVN }; /* Single instruction Multiple Data (SIMD) instructions */ /* Multiply instructions */ /* Parallel instructions */ /* Extend instructions */ /* Miscellaneous arithmetic instrations */ /* Status register transfer instructions */ /* Load and Store instructions */ /* Coprocessor instructions */ /* Exception generation instructions */ /* ========================================================================= */ /* --- ASSEMBLER ----------------------------------------------------------- */ /* ========================================================================= */ #define NOP() _W(0xe1a00000) #define SETEND_BE() _W(0xf1010200) #define SETEND_LE() _W(0xf1010000) /* Data processing instructions */ /* Opcodes Type 1 */ /* MOVcc rd,#i */ #define CC_MOV_ri8(cc,Rd,i) _OP1(cc,_MOV,0,Rd,UNSHIFTED_IMM8(i)) /* MOVcc Rd,#i ROR #s */ #define CC_MOV_ri8RORi(cc,Rd,i,s) _OP1(cc,_MOV,0,Rd,SHIFT_IMM8_ROR(i,s)) #define CC_MOV_ri(cc,Rd,i) _OP1(cc,_MOV,0,Rd,SHIFT_IMM(i)) #define CC_MOV_rr(cc,Rd,Rm) _OP1(cc,_MOV,0,Rd,SHIFT_REG(Rm)) #define CC_MOV_rrLSLi(cc,Rd,Rm,i) _OP1(cc,_MOV,0,Rd,SHIFT_LSL_i(Rm,i)) #define CC_MOV_rrLSLr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,0,Rd,SHIFT_LSL_r(Rm,Rs)) #define CC_MOV_rrLSRi(cc,Rd,Rm,i) _OP1(cc,_MOV,0,Rd,SHIFT_LSR_i(Rm,i)) #define CC_MOV_rrLSRr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,0,Rd,SHIFT_LSR_r(Rm,Rs)) #define CC_MOV_rrASRi(cc,Rd,Rm,i) _OP1(cc,_MOV,0,Rd,SHIFT_ASR_i(Rm,i)) #define CC_MOV_rrASRr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,0,Rd,SHIFT_ASR_r(Rm,Rs)) #define CC_MOV_rrRORi(cc,Rd,Rm,i) _OP1(cc,_MOV,0,Rd,SHIFT_ROR_i(Rm,i)) #define CC_MOV_rrRORr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,0,Rd,SHIFT_ROR_r(Rm,Rs)) #define CC_MOV_rrRRX(cc,Rd,Rm) _OP1(cc,_MOV,0,Rd,SHIFT_RRX(Rm)) /* MOV rd,#i */ #define MOV_ri8(Rd,i) CC_MOV_ri8(NATIVE_CC_AL,Rd,i) /* MOV Rd,#i ROR #s */ #define MOV_ri8RORi(Rd,i,s) CC_MOV_ri8RORi(NATIVE_CC_AL,Rd,i,s) #define MOV_ri(Rd,i) CC_MOV_ri(NATIVE_CC_AL,Rd,i) #define MOV_rr(Rd,Rm) CC_MOV_rr(NATIVE_CC_AL,Rd,Rm) #define MOV_rrLSLi(Rd,Rm,i) CC_MOV_rrLSLi(NATIVE_CC_AL,Rd,Rm,i) #define MOV_rrLSLr(Rd,Rm,Rs) CC_MOV_rrLSLr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOV_rrLSRi(Rd,Rm,i) CC_MOV_rrLSRi(NATIVE_CC_AL,Rd,Rm,i) #define MOV_rrLSRr(Rd,Rm,Rs) CC_MOV_rrLSRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOV_rrASRi(Rd,Rm,i) CC_MOV_rrASRi(NATIVE_CC_AL,Rd,Rm,i) #define MOV_rrASRr(Rd,Rm,Rs) CC_MOV_rrASRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOV_rrRORi(Rd,Rm,i) CC_MOV_rrRORi(NATIVE_CC_AL,Rd,Rm,i) #define MOV_rrRORr(Rd,Rm,Rs) CC_MOV_rrRORr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOV_rrRRX(Rd,Rm) CC_MOV_rrRRX(NATIVE_CC_AL,Rd,Rm) #define CC_MOVS_ri(cc,Rd,i) _OP1(cc,_MOV,1,Rd,SHIFT_IMM(i)) #define CC_MOVS_rr(cc,Rd,Rm) _OP1(cc,_MOV,1,Rd,SHIFT_REG(Rm)) #define CC_MOVS_rrLSLi(cc,Rd,Rm,i) _OP1(cc,_MOV,1,Rd,SHIFT_LSL_i(Rm,i)) #define CC_MOVS_rrLSLr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,1,Rd,SHIFT_LSL_r(Rm,Rs)) #define CC_MOVS_rrLSRi(cc,Rd,Rm,i) _OP1(cc,_MOV,1,Rd,SHIFT_LSR_i(Rm,i)) #define CC_MOVS_rrLSRr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,1,Rd,SHIFT_LSR_r(Rm,Rs)) #define CC_MOVS_rrASRi(cc,Rd,Rm,i) _OP1(cc,_MOV,1,Rd,SHIFT_ASR_i(Rm,i)) #define CC_MOVS_rrASRr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,1,Rd,SHIFT_ASR_r(Rm,Rs)) #define CC_MOVS_rrRORi(cc,Rd,Rm,i) _OP1(cc,_MOV,1,Rd,SHIFT_ROR_i(Rm,i)) #define CC_MOVS_rrRORr(cc,Rd,Rm,Rs) _OP1(cc,_MOV,1,Rd,SHIFT_ROR_r(Rm,Rs)) #define CC_MOVS_rrRRX(cc,Rd,Rm) _OP1(cc,_MOV,1,Rd,SHIFT_RRX(Rm)) #define MOVS_ri(Rd,i) CC_MOVS_ri(NATIVE_CC_AL,Rd,i) #define MOVS_rr(Rd,Rm) CC_MOVS_rr(NATIVE_CC_AL,Rd,Rm) #define MOVS_rrLSLi(Rd,Rm,i) CC_MOVS_rrLSLi(NATIVE_CC_AL,Rd,Rm,i) #define MOVS_rrLSLr(Rd,Rm,Rs) CC_MOVS_rrLSLr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOVS_rrLSRi(Rd,Rm,i) CC_MOVS_rrLSRi(NATIVE_CC_AL,Rd,Rm,i) #define MOVS_rrLSRr(Rd,Rm,Rs) CC_MOVS_rrLSRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOVS_rrASRi(Rd,Rm,i) CC_MOVS_rrASRi(NATIVE_CC_AL,Rd,Rm,i) #define MOVS_rrASRr(Rd,Rm,Rs) CC_MOVS_rrASRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOVS_rrRORi(Rd,Rm,i) CC_MOVS_rrRORi(NATIVE_CC_AL,Rd,Rm,i) #define MOVS_rrRORr(Rd,Rm,Rs) CC_MOVS_rrRORr(NATIVE_CC_AL,Rd,Rm,Rs) #define MOVS_rrRRX(Rd,Rm) CC_MOVS_rrRRX(NATIVE_CC_AL,Rd,Rm) /* MVNcc rd,#i */ #define CC_MVN_ri8(cc,Rd,i) _OP1(cc,_MVN,0,Rd,UNSHIFTED_IMM8(i)) /* MVNcc Rd,#i ROR #s */ #define CC_MVN_ri8RORi(cc,Rd,i,s) _OP1(cc,_MVN,0,Rd,SHIFT_IMM8_ROR(i,s)) #define CC_MVN_ri(cc,Rd,i) _OP1(cc,_MVN,0,Rd,SHIFT_IMM(i)) #define CC_MVN_rr(cc,Rd,Rm) _OP1(cc,_MVN,0,Rd,SHIFT_REG(Rm)) #define CC_MVN_rrLSLi(cc,Rd,Rm,i) _OP1(cc,_MVN,0,Rd,SHIFT_LSL_i(Rm,i)) #define CC_MVN_rrLSLr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,0,Rd,SHIFT_LSL_r(Rm,Rs)) #define CC_MVN_rrLSRi(cc,Rd,Rm,i) _OP1(cc,_MVN,0,Rd,SHIFT_LSR_i(Rm,i)) #define CC_MVN_rrLSRr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,0,Rd,SHIFT_LSR_r(Rm,Rs)) #define CC_MVN_rrASRi(cc,Rd,Rm,i) _OP1(cc,_MVN,0,Rd,SHIFT_ASR_i(Rm,i)) #define CC_MVN_rrASRr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,0,Rd,SHIFT_ASR_r(Rm,Rs)) #define CC_MVN_rrRORi(cc,Rd,Rm,i) _OP1(cc,_MVN,0,Rd,SHIFT_ROR_i(Rm,i)) #define CC_MVN_rrRORr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,0,Rd,SHIFT_ROR_r(Rm,Rs)) #define CC_MVN_rrRRX(cc,Rd,Rm) _OP1(cc,_MVN,0,Rd,SHIFT_RRX(Rm)) /* MVN rd,#i */ #define MVN_ri8(Rd,i) CC_MVN_ri8(NATIVE_CC_AL,Rd,i) /* MVN Rd,#i ROR #s */ #define MVN_ri8RORi(Rd,i,s) CC_MVN_ri8RORi(NATIVE_CC_AL,Rd,i,s) #define MVN_ri(Rd,i) CC_MVN_ri(NATIVE_CC_AL,Rd,i) #define MVN_rr(Rd,Rm) CC_MVN_rr(NATIVE_CC_AL,Rd,Rm) #define MVN_rrLSLi(Rd,Rm,i) CC_MVN_rrLSLi(NATIVE_CC_AL,Rd,Rm,i) #define MVN_rrLSLr(Rd,Rm,Rs) CC_MVN_rrLSLr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVN_rrLSRi(Rd,Rm,i) CC_MVN_rrLSRi(NATIVE_CC_AL,Rd,Rm,i) #define MVN_rrLSRr(Rd,Rm,Rs) CC_MVN_rrLSRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVN_rrASRi(Rd,Rm,i) CC_MVN_rrASRi(NATIVE_CC_AL,Rd,Rm,i) #define MVN_rrASRr(Rd,Rm,Rs) CC_MVN_rrASRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVN_rrRORi(Rd,Rm,i) CC_MVN_rrRORi(NATIVE_CC_AL,Rd,Rm,i) #define MVN_rrRORr(Rd,Rm,Rs) CC_MVN_rrRORr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVN_rrRRX(Rd,Rm) CC_MVN_rrRRX(NATIVE_CC_AL,Rd,Rm) #define CC_MVNS_ri(cc,Rd,i) _OP1(cc,_MVN,1,Rd,SHIFT_IMM(i)) #define CC_MVNS_rr(cc,Rd,Rm) _OP1(cc,_MVN,1,Rd,SHIFT_REG(Rm)) #define CC_MVNS_rrLSLi(cc,Rd,Rm,i) _OP1(cc,_MVN,1,Rd,SHIFT_LSL_i(Rm,i)) #define CC_MVNS_rrLSLr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,1,Rd,SHIFT_LSL_r(Rm,Rs)) #define CC_MVNS_rrLSRi(cc,Rd,Rm,i) _OP1(cc,_MVN,1,Rd,SHIFT_LSR_i(Rm,i)) #define CC_MVNS_rrLSRr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,1,Rd,SHIFT_LSR_r(Rm,Rs)) #define CC_MVNS_rrASRi(cc,Rd,Rm,i) _OP1(cc,_MVN,1,Rd,SHIFT_ASR_i(Rm,i)) #define CC_MVNS_rrASRr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,1,Rd,SHIFT_ASR_r(Rm,Rs)) #define CC_MVNS_rrRORi(cc,Rd,Rm,i) _OP1(cc,_MVN,1,Rd,SHIFT_ROR_i(Rm,i)) #define CC_MVNS_rrRORr(cc,Rd,Rm,Rs) _OP1(cc,_MVN,1,Rd,SHIFT_ROR_r(Rm,Rs)) #define CC_MVNS_rrRRX(cc,Rd,Rm) _OP1(cc,_MVN,1,Rd,SHIFT_RRX(Rm)) #define MVNS_ri(Rd,i) CC_MVNS_ri(NATIVE_CC_AL,Rd,i) #define MVNS_rr(Rd,Rm) CC_MVNS_rr(NATIVE_CC_AL,Rd,Rm) #define MVNS_rrLSLi(Rd,Rm,i) CC_MVNS_rrLSLi(NATIVE_CC_AL,Rd,Rm,i) #define MVNS_rrLSLr(Rd,Rm,Rs) CC_MVNS_rrLSLr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVNS_rrLSRi(Rd,Rm,i) CC_MVNS_rrLSRi(NATIVE_CC_AL,Rd,Rm,i) #define MVNS_rrLSRr(Rd,Rm,Rs) CC_MVNS_rrLSRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVNS_rrASRi(Rd,Rm,i) CC_MVNS_rrASRi(NATIVE_CC_AL,Rd,Rm,i) #define MVNS_rrASRr(Rd,Rm,Rs) CC_MVNS_rrASRr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVNS_rrRORi(Rd,Rm,i) CC_MVNS_rrRORi(NATIVE_CC_AL,Rd,Rm,i) #define MVNS_rrRORr(Rd,Rm,Rs) CC_MVNS_rrRORr(NATIVE_CC_AL,Rd,Rm,Rs) #define MVNS_rrRRX(Rd,Rm) CC_MVNS_rrRRX(NATIVE_CC_AL,Rd,Rm) /* Opcodes Type 2 */ #define CC_CMP_ri(cc,Rn,i) _OP2(cc,_CMP,Rn,SHIFT_IMM(i)) #define CC_CMP_rr(cc,Rn,Rm) _OP2(cc,_CMP,Rn,SHIFT_REG(Rm)) #define CC_CMP_rrLSLi(cc,Rn,Rm,i) _OP2(cc,_CMP,Rn,SHIFT_LSL_i(Rm,i)) #define CC_CMP_rrLSLr(cc,Rn,Rm,Rs) _OP2(cc,_CMP,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_CMP_rrLSRi(cc,Rn,Rm,i) _OP2(cc,_CMP,Rn,SHIFT_LSR_i(Rm,i)) #define CC_CMP_rrLSRr(cc,Rn,Rm,Rs) _OP2(cc,_CMP,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_CMP_rrASRi(cc,Rn,Rm,i) _OP2(cc,_CMP,Rn,SHIFT_ASR_i(Rm,i)) #define CC_CMP_rrASRr(cc,Rn,Rm,Rs) _OP2(cc,_CMP,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_CMP_rrRORi(cc,Rn,Rm,i) _OP2(cc,_CMP,Rn,SHIFT_ROR_i(Rm,i)) #define CC_CMP_rrRORr(cc,Rn,Rm,Rs) _OP2(cc,_CMP,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_CMP_rrRRX(cc,Rn,Rm) _OP2(cc,_CMP,Rn,SHIFT_RRX(Rm)) #define CMP_ri(Rn,i) CC_CMP_ri(NATIVE_CC_AL,Rn,i) #define CMP_rr(Rn,Rm) CC_CMP_rr(NATIVE_CC_AL,Rn,Rm) #define CMP_rrLSLi(Rn,Rm,i) CC_CMP_rrLSLi(NATIVE_CC_AL,Rn,Rm,i) #define CMP_rrLSLr(Rn,Rm,Rs) CC_CMP_rrLSLr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMP_rrLSRi(Rn,Rm,i) CC_CMP_rrLSRi(NATIVE_CC_AL,Rn,Rm,i) #define CMP_rrLSRr(Rn,Rm,Rs) CC_CMP_rrLSRr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMP_rrASRi(Rn,Rm,i) CC_CMP_rrASRi(NATIVE_CC_AL,Rn,Rm,i) #define CMP_rrASRr(Rn,Rm,Rs) CC_CMP_rrASRr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMP_rrRORi(Rn,Rm,i) CC_CMP_rrRORi(NATIVE_CC_AL,Rn,Rm,i) #define CMP_rrRORr(Rn,Rm,Rs) CC_CMP_rrRORr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMP_rrRRX(Rn,Rm) CC_CMP_rrRRX(NATIVE_CC_AL,Rn,Rm) #define CC_CMN_ri(cc,Rn,i) _OP2(cc,_CMN,Rn,SHIFT_IMM(i)) #define CC_CMN_rr(cc,Rn,r) _OP2(cc,_CMN,Rn,SHIFT_REG(r)) #define CC_CMN_rrLSLi(cc,Rn,Rm,i) _OP2(cc,_CMN,Rn,SHIFT_LSL_i(Rm,i)) #define CC_CMN_rrLSLr(cc,Rn,Rm,Rs) _OP2(cc,_CMN,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_CMN_rrLSRi(cc,Rn,Rm,i) _OP2(cc,_CMN,Rn,SHIFT_LSR_i(Rm,i)) #define CC_CMN_rrLSRr(cc,Rn,Rm,Rs) _OP2(cc,_CMN,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_CMN_rrASRi(cc,Rn,Rm,i) _OP2(cc,_CMN,Rn,SHIFT_ASR_i(Rm,i)) #define CC_CMN_rrASRr(cc,Rn,Rm,Rs) _OP2(cc,_CMN,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_CMN_rrRORi(cc,Rn,Rm,i) _OP2(cc,_CMN,Rn,SHIFT_ROR_i(Rm,i)) #define CC_CMN_rrRORr(cc,Rn,Rm,Rs) _OP2(cc,_CMN,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_CMN_rrRRX(cc,Rn,Rm) _OP2(cc,_CMN,Rn,SHIFT_RRX(Rm)) #define CMN_ri(Rn,i) CC_CMN_ri(NATIVE_CC_AL,Rn,i) #define CMN_rr(Rn,r) CC_CMN_rr(NATIVE_CC_AL,Rn,r) #define CMN_rrLSLi(Rn,Rm,i) CC_CMN_rrLSLi(NATIVE_CC_AL,Rn,Rm,i) #define CMN_rrLSLr(Rn,Rm,Rs) CC_CMN_rrLSLr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMN_rrLSRi(Rn,Rm,i) CC_CMN_rrLSRi(NATIVE_CC_AL,Rn,Rm,i) #define CMN_rrLSRr(Rn,Rm,Rs) CC_CMN_rrLSRr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMN_rrASRi(Rn,Rm,i) CC_CMN_rrASRi(NATIVE_CC_AL,Rn,Rm,i) #define CMN_rrASRr(Rn,Rm,Rs) CC_CMN_rrASRr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMN_rrRORi(Rn,Rm,i) CC_CMN_rrRORi(NATIVE_CC_AL,Rn,Rm,i) #define CMN_rrRORr(Rn,Rm,Rs) CC_CMN_rrRORr(NATIVE_CC_AL,Rn,Rm,Rs) #define CMN_rrRRX(Rn,Rm) CC_CMN_rrRRX(NATIVE_CC_AL,Rn,Rm) #define CC_TST_ri(cc,Rn,i) _OP2(cc,_TST,Rn,SHIFT_IMM(i)) #define CC_TST_rr(cc,Rn,r) _OP2(cc,_TST,Rn,SHIFT_REG(r)) #define CC_TST_rrLSLi(cc,Rn,Rm,i) _OP2(cc,_TST,Rn,SHIFT_LSL_i(Rm,i)) #define CC_TST_rrLSLr(cc,Rn,Rm,Rs) _OP2(cc,_TST,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_TST_rrLSRi(cc,Rn,Rm,i) _OP2(cc,_TST,Rn,SHIFT_LSR_i(Rm,i)) #define CC_TST_rrLSRr(cc,Rn,Rm,Rs) _OP2(cc,_TST,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_TST_rrASRi(cc,Rn,Rm,i) _OP2(cc,_TST,Rn,SHIFT_ASR_i(Rm,i)) #define CC_TST_rrASRr(cc,Rn,Rm,Rs) _OP2(cc,_TST,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_TST_rrRORi(cc,Rn,Rm,i) _OP2(cc,_TST,Rn,SHIFT_ROR_i(Rm,i)) #define CC_TST_rrRORr(cc,Rn,Rm,Rs) _OP2(cc,_TST,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_TST_rrRRX(cc,Rn,Rm) _OP2(cc,_TST,Rn,SHIFT_RRX(Rm)) #define TST_ri(Rn,i) CC_TST_ri(NATIVE_CC_AL,Rn,i) #define TST_rr(Rn,r) CC_TST_rr(NATIVE_CC_AL,Rn,r) #define TST_rrLSLi(Rn,Rm,i) CC_TST_rrLSLi(NATIVE_CC_AL,Rn,Rm,i) #define TST_rrLSLr(Rn,Rm,Rs) CC_TST_rrLSLr(NATIVE_CC_AL,Rn,Rm,Rs) #define TST_rrLSRi(Rn,Rm,i) CC_TST_rrLSRi(NATIVE_CC_AL,Rn,Rm,i) #define TST_rrLSRr(Rn,Rm,Rs) CC_TST_rrLSRr(NATIVE_CC_AL,Rn,Rm,Rs) #define TST_rrASRi(Rn,Rm,i) CC_TST_rrASRi(NATIVE_CC_AL,Rn,Rm,i) #define TST_rrASRr(Rn,Rm,Rs) CC_TST_rrASRr(NATIVE_CC_AL,Rn,Rm,Rs) #define TST_rrRORi(Rn,Rm,i) CC_TST_rrRORi(NATIVE_CC_AL,Rn,Rm,i) #define TST_rrRORr(Rn,Rm,Rs) CC_TST_rrRORr(NATIVE_CC_AL,Rn,Rm,Rs) #define TST_rrRRX(Rn,Rm) CC_TST_rrRRX(NATIVE_CC_AL,Rn,Rm) #define CC_TEQ_ri(cc,Rn,i) _OP2(cc,_TEQ,Rn,SHIFT_IMM(i)) #define CC_TEQ_rr(cc,Rn,r) _OP2(cc,_TEQ,Rn,SHIFT_REG(r)) #define CC_TEQ_rrLSLi(cc,Rn,Rm,i) _OP2(cc,_TEQ,Rn,SHIFT_LSL_i(Rm,i)) #define CC_TEQ_rrLSLr(cc,Rn,Rm,Rs) _OP2(cc,_TEQ,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_TEQ_rrLSRi(cc,Rn,Rm,i) _OP2(cc,_TEQ,Rn,SHIFT_LSR_i(Rm,i)) #define CC_TEQ_rrLSRr(cc,Rn,Rm,Rs) _OP2(cc,_TEQ,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_TEQ_rrASRi(cc,Rn,Rm,i) _OP2(cc,_TEQ,Rn,SHIFT_ASR_i(Rm,i)) #define CC_TEQ_rrASRr(cc,Rn,Rm,Rs) _OP2(cc,_TEQ,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_TEQ_rrRORi(cc,Rn,Rm,i) _OP2(cc,_TEQ,Rn,SHIFT_ROR_i(Rm,i)) #define CC_TEQ_rrRORr(cc,Rn,Rm,Rs) _OP2(cc,_TEQ,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_TEQ_rrRRX(cc,Rn,Rm) _OP2(cc,_TEQ,Rn,SHIFT_RRX(Rm)) #define TEQ_ri(Rn,i) CC_TEQ_ri(NATIVE_CC_AL,Rn,i) #define TEQ_rr(Rn,r) CC_TEQ_rr(NATIVE_CC_AL,Rn,r) #define TEQ_rrLSLi(Rn,Rm,i) CC_TEQ_rrLSLi(NATIVE_CC_AL,Rn,Rm,i) #define TEQ_rrLSLr(Rn,Rm,Rs) CC_TEQ_rrLSLr(NATIVE_CC_AL,Rn,Rm,Rs) #define TEQ_rrLSRi(Rn,Rm,i) CC_TEQ_rrLSRi(NATIVE_CC_AL,Rn,Rm,i) #define TEQ_rrLSRr(Rn,Rm,Rs) CC_TEQ_rrLSRr(NATIVE_CC_AL,Rn,Rm,Rs) #define TEQ_rrASRi(Rn,Rm,i) CC_TEQ_rrASRi(NATIVE_CC_AL,Rn,Rm,i) #define TEQ_rrASRr(Rn,Rm,Rs) CC_TEQ_rrASRr(NATIVE_CC_AL,Rn,Rm,Rs) #define TEQ_rrRORi(Rn,Rm,i) CC_TEQ_rrRORi(NATIVE_CC_AL,Rn,Rm,i) #define TEQ_rrRORr(Rn,Rm,Rs) CC_TEQ_rrRORr(NATIVE_CC_AL,Rn,Rm,Rs) #define TEQ_rrRRX(Rn,Rm) CC_TEQ_rrRRX(NATIVE_CC_AL,Rn,Rm) /* Opcodes Type 3 */ #define CC_AND_rri(cc,Rd,Rn,i) _OP3(cc,_AND,0,Rd,Rn,SHIFT_IMM(i)) #define CC_AND_rrr(cc,Rd,Rn,Rm) _OP3(cc,_AND,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_AND_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_AND_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_AND_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_AND_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_AND_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_AND_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_AND_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_AND_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_AND_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_AND,0,Rd,Rn,SHIFT_RRX(Rm)) #define AND_rri(Rd,Rn,i) CC_AND_rri(NATIVE_CC_AL,Rd,Rn,i) #define AND_rrr(Rd,Rn,Rm) CC_AND_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define AND_rrrLSLi(Rd,Rn,Rm,i) CC_AND_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define AND_rrrLSLr(Rd,Rn,Rm,Rs) CC_AND_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define AND_rrrLSRi(Rd,Rn,Rm,i) CC_AND_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define AND_rrrLSRr(Rd,Rn,Rm,Rs) CC_AND_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define AND_rrrASRi(Rd,Rn,Rm,i) CC_AND_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define AND_rrrASRr(Rd,Rn,Rm,Rs) CC_AND_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define AND_rrrRORi(Rd,Rn,Rm,i) CC_AND_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define AND_rrrRORr(Rd,Rn,Rm,Rs) CC_AND_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define AND_rrrRRX(Rd,Rn,Rm) CC_AND_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_ANDS_rri(cc,Rd,Rn,i) _OP3(cc,_AND,1,Rd,Rn,SHIFT_IMM(i)) #define CC_ANDS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_AND,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_ANDS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ANDS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ANDS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ANDS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ANDS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ANDS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ANDS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_AND,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ANDS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_AND,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ANDS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_AND,1,Rd,Rn,SHIFT_RRX(Rm)) #define ANDS_rri(Rd,Rn,i) CC_ANDS_rri(NATIVE_CC_AL,Rd,Rn,i) #define ANDS_rrr(Rd,Rn,Rm) CC_ANDS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ANDS_rrrLSLi(Rd,Rn,Rm,i) CC_ANDS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ANDS_rrrLSLr(Rd,Rn,Rm,Rs) CC_ANDS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ANDS_rrrLSRi(Rd,Rn,Rm,i) CC_ANDS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ANDS_rrrLSRr(Rd,Rn,Rm,Rs) CC_ANDS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ANDS_rrrASRi(Rd,Rn,Rm,i) CC_ANDS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ANDS_rrrASRr(Rd,Rn,Rm,Rs) CC_ANDS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ANDS_rrrRORi(Rd,Rn,Rm,i) CC_ANDS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ANDS_rrrRORr(Rd,Rn,Rm,Rs) CC_ANDS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ANDS_rrrRRX(Rd,Rn,Rm) CC_ANDS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_EOR_rri(cc,Rd,Rn,i) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_IMM(i)) #define CC_EOR_rrr(cc,Rd,Rn,Rm) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_EOR_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_EOR_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_EOR_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_EOR_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_EOR_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_EOR_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_EOR_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_EOR_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_EOR_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_EOR,0,Rd,Rn,SHIFT_RRX(Rm)) #define EOR_rri(Rd,Rn,i) CC_EOR_rri(NATIVE_CC_AL,Rd,Rn,i) #define EOR_rrr(Rd,Rn,Rm) CC_EOR_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define EOR_rrrLSLi(Rd,Rn,Rm,i) CC_EOR_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EOR_rrrLSLr(Rd,Rn,Rm,Rs) CC_EOR_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EOR_rrrLSRi(Rd,Rn,Rm,i) CC_EOR_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EOR_rrrLSRr(Rd,Rn,Rm,Rs) CC_EOR_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EOR_rrrASRi(Rd,Rn,Rm,i) CC_EOR_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EOR_rrrASRr(Rd,Rn,Rm,Rs) CC_EOR_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EOR_rrrRORi(Rd,Rn,Rm,i) CC_EOR_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EOR_rrrRORr(Rd,Rn,Rm,Rs) CC_EOR_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EOR_rrrRRX(Rd,Rn,Rm) CC_EOR_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_EORS_rri(cc,Rd,Rn,i) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_IMM(i)) #define CC_EORS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_EORS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_EORS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_EORS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_EORS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_EORS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_EORS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_EORS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_EORS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_EORS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_EOR,1,Rd,Rn,SHIFT_RRX(Rm)) #define EORS_rri(Rd,Rn,i) CC_EORS_rri(NATIVE_CC_AL,Rd,Rn,i) #define EORS_rrr(Rd,Rn,Rm) CC_EORS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define EORS_rrrLSLi(Rd,Rn,Rm,i) CC_EORS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EORS_rrrLSLr(Rd,Rn,Rm,Rs) CC_EORS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EORS_rrrLSRi(Rd,Rn,Rm,i) CC_EORS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EORS_rrrLSRr(Rd,Rn,Rm,Rs) CC_EORS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EORS_rrrASRi(Rd,Rn,Rm,i) CC_EORS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EORS_rrrASRr(Rd,Rn,Rm,Rs) CC_EORS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EORS_rrrRORi(Rd,Rn,Rm,i) CC_EORS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define EORS_rrrRORr(Rd,Rn,Rm,Rs) CC_EORS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define EORS_rrrRRX(Rd,Rn,Rm) CC_EORS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_SUB_rri(cc,Rd,Rn,i) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_IMM(i)) #define CC_SUB_rrr(cc,Rd,Rn,Rm) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_SUB_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_SUB_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_SUB_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_SUB_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_SUB_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_SUB_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_SUB_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_SUB_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_SUB_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_SUB,0,Rd,Rn,SHIFT_RRX(Rm)) #define SUB_rri(Rd,Rn,i) CC_SUB_rri(NATIVE_CC_AL,Rd,Rn,i) #define SUB_rrr(Rd,Rn,Rm) CC_SUB_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define SUB_rrrLSLi(Rd,Rn,Rm,i) CC_SUB_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUB_rrrLSLr(Rd,Rn,Rm,Rs) CC_SUB_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUB_rrrLSRi(Rd,Rn,Rm,i) CC_SUB_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUB_rrrLSRr(Rd,Rn,Rm,Rs) CC_SUB_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUB_rrrASRi(Rd,Rn,Rm,i) CC_SUB_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUB_rrrASRr(Rd,Rn,Rm,Rs) CC_SUB_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUB_rrrRORi(Rd,Rn,Rm,i) CC_SUB_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUB_rrrRORr(Rd,Rn,Rm,Rs) CC_SUB_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUB_rrrRRX(Rd,Rn,Rm) CC_SUB_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_SUBS_rri(cc,Rd,Rn,i) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_IMM(i)) #define CC_SUBS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_SUBS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_SUBS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_SUBS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_SUBS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_SUBS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_SUBS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_SUBS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_SUBS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_SUBS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_SUB,1,Rd,Rn,SHIFT_RRX(Rm)) #define SUBS_rri(Rd,Rn,i) CC_SUBS_rri(NATIVE_CC_AL,Rd,Rn,i) #define SUBS_rrr(Rd,Rn,Rm) CC_SUBS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define SUBS_rrrLSLi(Rd,Rn,Rm,i) CC_SUBS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUBS_rrrLSLr(Rd,Rn,Rm,Rs) CC_SUBS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUBS_rrrLSRi(Rd,Rn,Rm,i) CC_SUBS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUBS_rrrLSRr(Rd,Rn,Rm,Rs) CC_SUBS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUBS_rrrASRi(Rd,Rn,Rm,i) CC_SUBS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUBS_rrrASRr(Rd,Rn,Rm,Rs) CC_SUBS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUBS_rrrRORi(Rd,Rn,Rm,i) CC_SUBS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SUBS_rrrRORr(Rd,Rn,Rm,Rs) CC_SUBS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SUBS_rrrRRX(Rd,Rn,Rm) CC_SUBS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_RSB_rri(cc,Rd,Rn,i) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_IMM(i)) #define CC_RSB_rrr(cc,Rd,Rn,Rm) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_RSB_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_RSB_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_RSB_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_RSB_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_RSB_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_RSB_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_RSB_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_RSB_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_RSB_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_RSB,0,Rd,Rn,SHIFT_RRX(Rm)) #define RSB_rri(Rd,Rn,i) CC_RSB_rri(NATIVE_CC_AL,Rd,Rn,i) #define RSB_rrr(Rd,Rn,Rm) CC_RSB_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define RSB_rrrLSLi(Rd,Rn,Rm,i) CC_RSB_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSB_rrrLSLr(Rd,Rn,Rm,Rs) CC_RSB_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSB_rrrLSRi(Rd,Rn,Rm,i) CC_RSB_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSB_rrrLSRr(Rd,Rn,Rm,Rs) CC_RSB_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSB_rrrASRi(Rd,Rn,Rm,i) CC_RSB_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSB_rrrASRr(Rd,Rn,Rm,Rs) CC_RSB_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSB_rrrRORi(Rd,Rn,Rm,i) CC_RSB_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSB_rrrRORr(Rd,Rn,Rm,Rs) CC_RSB_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSB_rrrRRX(Rd,Rn,Rm) CC_RSB_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_RSBS_rri(cc,Rd,Rn,i) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_IMM(i)) #define CC_RSBS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_RSBS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_RSBS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_RSBS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_RSBS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_RSBS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_RSBS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_RSBS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_RSBS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_RSBS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_RSB,1,Rd,Rn,SHIFT_RRX(Rm)) #define RSBS_rri(Rd,Rn,i) CC_RSBS_rri(NATIVE_CC_AL,Rd,Rn,i) #define RSBS_rrr(Rd,Rn,Rm) CC_RSBS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define RSBS_rrrLSLi(Rd,Rn,Rm,i) CC_RSBS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSBS_rrrLSLr(Rd,Rn,Rm,Rs) CC_RSBS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSBS_rrrLSRi(Rd,Rn,Rm,i) CC_RSBS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSBS_rrrLSRr(Rd,Rn,Rm,Rs) CC_RSBS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSBS_rrrASRi(Rd,Rn,Rm,i) CC_RSBS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSBS_rrrASRr(Rd,Rn,Rm,Rs) CC_RSBS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSBS_rrrRORi(Rd,Rn,Rm,i) CC_RSBS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSBS_rrrRORr(Rd,Rn,Rm,Rs) CC_RSBS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSBS_rrrRRX(Rd,Rn,Rm) CC_RSBS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_ADD_rri8(cc,Rd,Rn,i) _OP3(cc,_ADD,0,Rd,Rn,UNSHIFT_IMM8(i)) #define CC_ADD_rri8RORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_IMM8_ROR(Rm,i)) #define CC_ADD_rri(cc,Rd,Rn,i) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_IMM(i)) #define CC_ADD_rrr(cc,Rd,Rn,Rm) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_ADD_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ADD_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ADD_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ADD_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ADD_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ADD_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ADD_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ADD_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ADD_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_ADD,0,Rd,Rn,SHIFT_RRX(Rm)) #define ADD_rri8(cc,Rd,Rn,i) CC_ADD_rri8(NATIVE_CC_AL,Rd,Rn,i) #define ADD_rri8RORi(cc,Rd,Rn,Rm,i) CC_ADD_rri8RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADD_rri(Rd,Rn,i) CC_ADD_rri(NATIVE_CC_AL,Rd,Rn,i) #define ADD_rrr(Rd,Rn,Rm) CC_ADD_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ADD_rrrLSLi(Rd,Rn,Rm,i) CC_ADD_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADD_rrrLSLr(Rd,Rn,Rm,Rs) CC_ADD_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADD_rrrLSRi(Rd,Rn,Rm,i) CC_ADD_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADD_rrrLSRr(Rd,Rn,Rm,Rs) CC_ADD_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADD_rrrASRi(Rd,Rn,Rm,i) CC_ADD_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADD_rrrASRr(Rd,Rn,Rm,Rs) CC_ADD_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADD_rrrRORi(Rd,Rn,Rm,i) CC_ADD_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADD_rrrRORr(Rd,Rn,Rm,Rs) CC_ADD_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADD_rrrRRX(Rd,Rn,Rm) CC_ADD_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_ADDS_rri(cc,Rd,Rn,i) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_IMM(i)) #define CC_ADDS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_ADDS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ADDS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ADDS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ADDS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ADDS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ADDS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ADDS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ADDS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ADDS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_ADD,1,Rd,Rn,SHIFT_RRX(Rm)) #define ADDS_rri(Rd,Rn,i) CC_ADDS_rri(NATIVE_CC_AL,Rd,Rn,i) #define ADDS_rrr(Rd,Rn,Rm) CC_ADDS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ADDS_rrrLSLi(Rd,Rn,Rm,i) CC_ADDS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADDS_rrrLSLr(Rd,Rn,Rm,Rs) CC_ADDS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADDS_rrrLSRi(Rd,Rn,Rm,i) CC_ADDS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADDS_rrrLSRr(Rd,Rn,Rm,Rs) CC_ADDS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADDS_rrrASRi(Rd,Rn,Rm,i) CC_ADDS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADDS_rrrASRr(Rd,Rn,Rm,Rs) CC_ADDS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADDS_rrrRORi(Rd,Rn,Rm,i) CC_ADDS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADDS_rrrRORr(Rd,Rn,Rm,Rs) CC_ADDS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADDS_rrrRRX(Rd,Rn,Rm) CC_ADDS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_ADC_rri(cc,Rd,Rn,i) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_IMM(i)) #define CC_ADC_rrr(cc,Rd,Rn,Rm) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_ADC_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ADC_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ADC_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ADC_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ADC_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ADC_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ADC_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ADC_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ADC_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_ADC,0,Rd,Rn,SHIFT_RRX(Rm)) #define ADC_rri(Rd,Rn,i) CC_ADC_rri(NATIVE_CC_AL,Rd,Rn,i) #define ADC_rrr(Rd,Rn,Rm) CC_ADC_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ADC_rrrLSLi(Rd,Rn,Rm,i) CC_ADC_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADC_rrrLSLr(Rd,Rn,Rm,Rs) CC_ADC_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADC_rrrLSRi(Rd,Rn,Rm,i) CC_ADC_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADC_rrrLSRr(Rd,Rn,Rm,Rs) CC_ADC_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADC_rrrASRi(Rd,Rn,Rm,i) CC_ADC_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADC_rrrASRr(Rd,Rn,Rm,Rs) CC_ADC_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADC_rrrRORi(Rd,Rn,Rm,i) CC_ADC_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADC_rrrRORr(Rd,Rn,Rm,Rs) CC_ADC_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADC_rrrRRX(Rd,Rn,Rm) CC_ADC_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_ADCS_rri(cc,Rd,Rn,i) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_IMM(i)) #define CC_ADCS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_ADCS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ADCS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ADCS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ADCS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ADCS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ADCS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ADCS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ADCS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ADCS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_ADC,1,Rd,Rn,SHIFT_RRX(Rm)) #define ADCS_rri(Rd,Rn,i) CC_ADCS_rri(NATIVE_CC_AL,Rd,Rn,i) #define ADCS_rrr(Rd,Rn,Rm) CC_ADCS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ADCS_rrrLSLi(Rd,Rn,Rm,i) CC_ADCS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADCS_rrrLSLr(Rd,Rn,Rm,Rs) CC_ADCS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADCS_rrrLSRi(Rd,Rn,Rm,i) CC_ADCS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADCS_rrrLSRr(Rd,Rn,Rm,Rs) CC_ADCS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADCS_rrrASRi(Rd,Rn,Rm,i) CC_ADCS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADCS_rrrASRr(Rd,Rn,Rm,Rs) CC_ADCS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADCS_rrrRORi(Rd,Rn,Rm,i) CC_ADCS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ADCS_rrrRORr(Rd,Rn,Rm,Rs) CC_ADCS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ADCS_rrrRRX(Rd,Rn,Rm) CC_ADCS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_SBC_rri(cc,Rd,Rn,i) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_IMM(i)) #define CC_SBC_rrr(cc,Rd,Rn,Rm) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_SBC_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_SBC_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_SBC_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_SBC_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_SBC_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_SBC_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_SBC_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_SBC_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_SBC_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_SBC,0,Rd,Rn,SHIFT_RRX(Rm)) #define SBC_rri(Rd,Rn,i) CC_SBC_rri(NATIVE_CC_AL,Rd,Rn,i) #define SBC_rrr(Rd,Rn,Rm) CC_SBC_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define SBC_rrrLSLi(Rd,Rn,Rm,i) CC_SBC_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBC_rrrLSLr(Rd,Rn,Rm,Rs) CC_SBC_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBC_rrrLSRi(Rd,Rn,Rm,i) CC_SBC_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBC_rrrLSRr(Rd,Rn,Rm,Rs) CC_SBC_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBC_rrrASRi(Rd,Rn,Rm,i) CC_SBC_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBC_rrrASRr(Rd,Rn,Rm,Rs) CC_SBC_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBC_rrrRORi(Rd,Rn,Rm,i) CC_SBC_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBC_rrrRORr(Rd,Rn,Rm,Rs) CC_SBC_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBC_rrrRRX(Rd,Rn,Rm) CC_SBC_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_SBCS_rri(cc,Rd,Rn,i) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_IMM(i)) #define CC_SBCS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_SBCS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_SBCS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_SBCS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_SBCS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_SBCS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_SBCS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_SBCS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_SBCS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_SBCS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_SBC,1,Rd,Rn,SHIFT_RRX(Rm)) #define SBCS_rri(Rd,Rn,i) CC_SBCS_rri(NATIVE_CC_AL,Rd,Rn,i) #define SBCS_rrr(Rd,Rn,Rm) CC_SBCS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define SBCS_rrrLSLi(Rd,Rn,Rm,i) CC_SBCS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBCS_rrrLSLr(Rd,Rn,Rm,Rs) CC_SBCS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBCS_rrrLSRi(Rd,Rn,Rm,i) CC_SBCS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBCS_rrrLSRr(Rd,Rn,Rm,Rs) CC_SBCS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBCS_rrrASRi(Rd,Rn,Rm,i) CC_SBCS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBCS_rrrASRr(Rd,Rn,Rm,Rs) CC_SBCS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBCS_rrrRORi(Rd,Rn,Rm,i) CC_SBCS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define SBCS_rrrRORr(Rd,Rn,Rm,Rs) CC_SBCS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define SBCS_rrrRRX(Rd,Rn,Rm) CC_SBCS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_RSC_rri(cc,Rd,Rn,i) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_IMM(i)) #define CC_RSC_rrr(cc,Rd,Rn,Rm) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_RSC_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_RSC_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_RSC_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_RSC_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_RSC_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_RSC_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_RSC_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_RSC_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_RSC_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_RSC,0,Rd,Rn,SHIFT_RRX(Rm)) #define RSC_rri(Rd,Rn,i) CC_RSC_rri(NATIVE_CC_AL,Rd,Rn,i) #define RSC_rrr(Rd,Rn,Rm) CC_RSC_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define RSC_rrrLSLi(Rd,Rn,Rm,i) CC_RSC_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSC_rrrLSLr(Rd,Rn,Rm,Rs) CC_RSC_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSC_rrrLSRi(Rd,Rn,Rm,i) CC_RSC_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSC_rrrLSRr(Rd,Rn,Rm,Rs) CC_RSC_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSC_rrrASRi(Rd,Rn,Rm,i) CC_RSC_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSC_rrrASRr(Rd,Rn,Rm,Rs) CC_RSC_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSC_rrrRORi(Rd,Rn,Rm,i) CC_RSC_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSC_rrrRORr(Rd,Rn,Rm,Rs) CC_RSC_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSC_rrrRRX(Rd,Rn,Rm) CC_RSC_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_RSCS_rri(cc,Rd,Rn,i) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_IMM(i)) #define CC_RSCS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_RSCS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_RSCS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_RSCS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_RSCS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_RSCS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_RSCS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_RSCS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_RSCS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_RSCS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_RSC,1,Rd,Rn,SHIFT_RRX(Rm)) #define RSCS_rri(Rd,Rn,i) CC_RSCS_rri(NATIVE_CC_AL,Rd,Rn,i) #define RSCS_rrr(Rd,Rn,Rm) CC_RSCS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define RSCS_rrrLSLi(Rd,Rn,Rm,i) CC_RSCS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSCS_rrrLSLr(Rd,Rn,Rm,Rs) CC_RSCS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSCS_rrrLSRi(Rd,Rn,Rm,i) CC_RSCS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSCS_rrrLSRr(Rd,Rn,Rm,Rs) CC_RSCS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSCS_rrrASRi(Rd,Rn,Rm,i) CC_RSCS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSCS_rrrASRr(Rd,Rn,Rm,Rs) CC_RSCS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSCS_rrrRORi(Rd,Rn,Rm,i) CC_RSCS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define RSCS_rrrRORr(Rd,Rn,Rm,Rs) CC_RSCS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define RSCS_rrrRRX(Rd,Rn,Rm) CC_RSCS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) /* ORRcc Rd,Rn,#i */ #define CC_ORR_rri8(cc,Rd,Rn,i) _OP3(cc,_ORR,0,Rd,Rn,UNSHIFTED_IMM8(i)) /* ORRcc Rd,Rn,#i ROR #s */ #define CC_ORR_rri8RORi(cc,Rd,Rn,i,s) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_IMM8_ROR(i,s)) #define CC_ORR_rri(cc,Rd,Rn,i) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_IMM(i)) #define CC_ORR_rrr(cc,Rd,Rn,Rm) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_ORR_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ORR_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ORR_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ORR_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ORR_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ORR_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ORR_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ORR_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ORR_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_ORR,0,Rd,Rn,SHIFT_RRX(Rm)) /* ORR Rd,Rn,#i */ #define ORR_rri8(Rd,Rn,i) CC_ORR_rri8(NATIVE_CC_AL,Rd,Rn,i) /* ORR Rd,Rn,#i ROR #s */ #define ORR_rri8RORi(Rd,Rn,i,s) CC_ORR_rri8RORi(NATIVE_CC_AL,Rd,Rn,i,s) #define ORR_rri(Rd,Rn,i) CC_ORR_rri(NATIVE_CC_AL,Rd,Rn,i) #define ORR_rrr(Rd,Rn,Rm) CC_ORR_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ORR_rrrLSLi(Rd,Rn,Rm,i) CC_ORR_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORR_rrrLSLr(Rd,Rn,Rm,Rs) CC_ORR_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORR_rrrLSRi(Rd,Rn,Rm,i) CC_ORR_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORR_rrrLSRr(Rd,Rn,Rm,Rs) CC_ORR_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORR_rrrASRi(Rd,Rn,Rm,i) CC_ORR_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORR_rrrASRr(Rd,Rn,Rm,Rs) CC_ORR_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORR_rrrRORi(Rd,Rn,Rm,i) CC_ORR_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORR_rrrRORr(Rd,Rn,Rm,Rs) CC_ORR_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORR_rrrRRX(Rd,Rn,Rm) CC_ORR_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_ORRS_rri(cc,Rd,Rn,i) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_IMM(i)) #define CC_ORRS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_ORRS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_ORRS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_ORRS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_ORRS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_ORRS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_ORRS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_ORRS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_ORRS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_ORRS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_ORR,1,Rd,Rn,SHIFT_RRX(Rm)) #define ORRS_rri(Rd,Rn,i) CC_ORRS_rri(NATIVE_CC_AL,Rd,Rn,i) #define ORRS_rrr(Rd,Rn,Rm) CC_ORRS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define ORRS_rrrLSLi(Rd,Rn,Rm,i) CC_ORRS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORRS_rrrLSLr(Rd,Rn,Rm,Rs) CC_ORRS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORRS_rrrLSRi(Rd,Rn,Rm,i) CC_ORRS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORRS_rrrLSRr(Rd,Rn,Rm,Rs) CC_ORRS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORRS_rrrASRi(Rd,Rn,Rm,i) CC_ORRS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORRS_rrrASRr(Rd,Rn,Rm,Rs) CC_ORRS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORRS_rrrRORi(Rd,Rn,Rm,i) CC_ORRS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define ORRS_rrrRORr(Rd,Rn,Rm,Rs) CC_ORRS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define ORRS_rrrRRX(Rd,Rn,Rm) CC_ORRS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_BIC_rri(cc,Rd,Rn,i) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_IMM(i)) #define CC_BIC_rrr(cc,Rd,Rn,Rm) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_REG(Rm)) #define CC_BIC_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_BIC_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_BIC_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_BIC_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_BIC_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_BIC_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_BIC_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_BIC_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_BIC_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_BIC,0,Rd,Rn,SHIFT_RRX(Rm)) #define BIC_rri(Rd,Rn,i) CC_BIC_rri(NATIVE_CC_AL,Rd,Rn,i) #define BIC_rrr(Rd,Rn,Rm) CC_BIC_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define BIC_rrrLSLi(Rd,Rn,Rm,i) CC_BIC_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BIC_rrrLSLr(Rd,Rn,Rm,Rs) CC_BIC_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BIC_rrrLSRi(Rd,Rn,Rm,i) CC_BIC_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BIC_rrrLSRr(Rd,Rn,Rm,Rs) CC_BIC_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BIC_rrrASRi(Rd,Rn,Rm,i) CC_BIC_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BIC_rrrASRr(Rd,Rn,Rm,Rs) CC_BIC_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BIC_rrrRORi(Rd,Rn,Rm,i) CC_BIC_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BIC_rrrRORr(Rd,Rn,Rm,Rs) CC_BIC_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BIC_rrrRRX(Rd,Rn,Rm) CC_BIC_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_BICS_rri(cc,Rd,Rn,i) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_IMM(i)) #define CC_BICS_rrr(cc,Rd,Rn,Rm) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_REG(Rm)) #define CC_BICS_rrrLSLi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_LSL_i(Rm,i)) #define CC_BICS_rrrLSLr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_LSL_r(Rm,Rs)) #define CC_BICS_rrrLSRi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_LSR_i(Rm,i)) #define CC_BICS_rrrLSRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_LSR_r(Rm,Rs)) #define CC_BICS_rrrASRi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_ASR_i(Rm,i)) #define CC_BICS_rrrASRr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_ASR_r(Rm,Rs)) #define CC_BICS_rrrRORi(cc,Rd,Rn,Rm,i) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_ROR_i(Rm,i)) #define CC_BICS_rrrRORr(cc,Rd,Rn,Rm,Rs) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_ROR_r(Rm,Rs)) #define CC_BICS_rrrRRX(cc,Rd,Rn,Rm) _OP3(cc,_BIC,1,Rd,Rn,SHIFT_RRX(Rm)) #define BICS_rri(Rd,Rn,i) CC_BICS_rri(NATIVE_CC_AL,Rd,Rn,i) #define BICS_rrr(Rd,Rn,Rm) CC_BICS_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define BICS_rrrLSLi(Rd,Rn,Rm,i) CC_BICS_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BICS_rrrLSLr(Rd,Rn,Rm,Rs) CC_BICS_rrrLSLr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BICS_rrrLSRi(Rd,Rn,Rm,i) CC_BICS_rrrLSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BICS_rrrLSRr(Rd,Rn,Rm,Rs) CC_BICS_rrrLSRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BICS_rrrASRi(Rd,Rn,Rm,i) CC_BICS_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BICS_rrrASRr(Rd,Rn,Rm,Rs) CC_BICS_rrrASRr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BICS_rrrRORi(Rd,Rn,Rm,i) CC_BICS_rrrRORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define BICS_rrrRORr(Rd,Rn,Rm,Rs) CC_BICS_rrrRORr(NATIVE_CC_AL,Rd,Rn,Rm,Rs) #define BICS_rrrRRX(Rd,Rn,Rm) CC_BICS_rrrRRX(NATIVE_CC_AL,Rd,Rn,Rm) /* Branch instructions */ #define CC_B_i(cc,i) _W(((cc) << 28) | (10 << 24) | (i)) #define CC_BL_i(cc,i) _W(((cc) << 28) | (11 << 24) | (i)) #define CC_BLX_r(cc,r) _W(((cc) << 28) | (0x12 << 20) | (3 << 4) | (0xfff << 8) | (r)) #define CC_BX_r(cc,r) _W(((cc) << 28) | (0x12 << 20) | (1 << 4) | (0xfff << 8) | (r)) #define CC_BXJ_r(cc,r) _W(((cc) << 28) | (0x12 << 20) | (2 << 4) | (0xfff << 8) | (r)) #define BEQ_i(i) CC_B_i(NATIVE_CC_EQ,i) #define BNE_i(i) CC_B_i(NATIVE_CC_NE,i) #define BCS_i(i) CC_B_i(NATIVE_CC_CS,i) #define BCC_i(i) CC_B_i(NATIVE_CC_CC,i) #define BMI_i(i) CC_B_i(NATIVE_CC_MI,i) #define BPL_i(i) CC_B_i(NATIVE_CC_PL,i) #define BVS_i(i) CC_B_i(NATIVE_CC_VS,i) #define BVC_i(i) CC_B_i(NATIVE_CC_VC,i) #define BHI_i(i) CC_B_i(NATIVE_CC_HI,i) #define BLS_i(i) CC_B_i(NATIVE_CC_LS,i) #define BGE_i(i) CC_B_i(NATIVE_CC_GE,i) #define BLT_i(i) CC_B_i(NATIVE_CC_LT,i) #define BGT_i(i) CC_B_i(NATIVE_CC_GT,i) #define BLE_i(i) CC_B_i(NATIVE_CC_LE,i) #define B_i(i) CC_B_i(NATIVE_CC_AL,i) #define BL_i(i) CC_BL_i(NATIVE_CC_AL,i) #define BLX_i(i) _W((NATIVE_CC_AL << 28) | (10 << 24) | (i)) #define BLX_r(r) CC_BLX_r(NATIVE_CC_AL,r) #define BX_r(r) CC_BX_r(NATIVE_CC_AL,r) #define BXJ_r(r) CC_BXJ_r(NATIVE_CC_AL,r) /* Status register instructions */ #define CC_MRS_CPSR(cc,Rd) _W(((cc) << 28) | (0x10 << 20) | ((Rd) << 12) | (0xf << 16)) #define MRS_CPSR(Rd) CC_MRS_CPSR(NATIVE_CC_AL,Rd) #define CC_MRS_SPSR(cc,Rd) _W(((cc) << 28) | (0x14 << 20) | ((Rd) << 12) | (0xf << 16)) #define MRS_SPSR(Rd) CC_MRS_SPSR(NATIVE_CC_AL,Rd) #define CC_MSR_CPSR_i(cc,i) _W(((cc) << 28) | (0x32 << 20) | (0x9 << 16) | (0xf << 12) | SHIFT_IMM(i)) #define CC_MSR_CPSR_r(cc,Rm) _W(((cc) << 28) | (0x12 << 20) | (0x9 << 16) | (0xf << 12) | (Rm)) #define MSR_CPSR_i(i) CC_MSR_CPSR_i(NATIVE_CC_AL,(i)) #define MSR_CPSR_r(Rm) CC_MSR_CPSR_r(NATIVE_CC_AL,(Rm)) #define CC_MSR_CPSRf_i(cc,i) _W(((cc) << 28) | (0x32 << 20) | (0x8 << 16) | (0xf << 12) | SHIFT_IMM(i)) #define CC_MSR_CPSRf_r(cc,Rm) _W(((cc) << 28) | (0x12 << 20) | (0x8 << 16) | (0xf << 12) | (Rm)) #define MSR_CPSRf_i(i) CC_MSR_CPSRf_i(NATIVE_CC_AL,(i)) #define MSR_CPSRf_r(Rm) CC_MSR_CPSRf_r(NATIVE_CC_AL,(Rm)) #define CC_MSR_CPSRc_i(cc,i) _W(((cc) << 28) | (0x32 << 20) | (0x1 << 16) | (0xf << 12) | SHIFT_IMM(i)) #define CC_MSR_CPSRc_r(cc,Rm) _W(((cc) << 28) | (0x12 << 20) | (0x1 << 16) | (0xf << 12) | (Rm)) #define MSR_CPSRc_i(i) CC_MSR_CPSRc_i(NATIVE_CC_AL,(i)) #define MSR_CPSRc_r(Rm) CC_MSR_CPSRc_r(NATIVE_CC_AL,(Rm)) /* Load Store instructions */ #define CC_PUSH(cc,r) _W(((cc) << 28) | (0x92d << 16) | (1 << (r))) #define PUSH(r) CC_PUSH(NATIVE_CC_AL, r) #define CC_PUSH_REGS(cc,r) _W(((cc) << 28) | (0x92d << 16) | (r)) #define PUSH_REGS(r) CC_PUSH_REGS(NATIVE_CC_AL, r) #define CC_POP(cc,r) _W(((cc) << 28) | (0x8bd << 16) | (1 << (r))) #define POP(r) CC_POP(NATIVE_CC_AL, r) #define CC_POP_REGS(cc,r) _W(((cc) << 28) | (0x8bd << 16) | (r)) #define POP_REGS(r) CC_POP_REGS(NATIVE_CC_AL, r) #define CC_LDR_rR(cc,Rd,Rn) _LS1(cc,1,0,Rd,Rn,ADD_IMM(0)) #define CC_LDR_rRI(cc,Rd,Rn,i) _LS1(cc,1,0,Rd,Rn,(i) >= 0 ? ADD_IMM(i) : SUB_IMM(-(i))) #define CC_LDR_rRi(cc,Rd,Rn,i) _LS1(cc,1,0,Rd,Rn,SUB_IMM(i)) #define CC_LDR_rRR(cc,Rd,Rn,Rm) _LS1(cc,1,0,Rd,Rn,ADD_REG(Rm)) #define CC_LDR_rRr(cc,Rd,Rn,Rm) _LS1(cc,1,0,Rd,Rn,SUB_REG(Rm)) #define CC_LDR_rRR_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,ADD_LSL(Rm,i)) #define CC_LDR_rRr_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,SUB_LSL(Rm,i)) #define CC_LDR_rRR_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,ADD_LSR(Rm,i)) #define CC_LDR_rRr_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,SUB_LSR(Rm,i)) #define CC_LDR_rRR_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,ADD_ASR(Rm,i)) #define CC_LDR_rRr_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,SUB_ASR(Rm,i)) #define CC_LDR_rRR_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,ADD_ROR(Rm,i)) #define CC_LDR_rRr_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,1,0,Rd,Rn,SUB_ROR(Rm,i)) #define CC_LDR_rRR_RRX(cc,Rd,Rn,Rm) _LS1(cc,1,0,Rd,Rn,ADD_RRX(Rm)) #define CC_LDR_rRr_RRX(cc,Rd,Rn,Rm) _LS1(cc,1,0,Rd,Rn,SUB_RRX(Rm)) #define LDR_rR(Rd,Rn) CC_LDR_rR(NATIVE_CC_AL,Rd,Rn) #define LDR_rRI(Rd,Rn,i) CC_LDR_rRI(NATIVE_CC_AL,Rd,Rn,i) #define LDR_rRi(Rd,Rn,i) CC_LDR_rRi(NATIVE_CC_AL,Rd,Rn,i) #define LDR_rRR(Rd,Rn,Rm) CC_LDR_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define LDR_rRr(Rd,Rn,Rm) CC_LDR_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define LDR_rRR_LSLi(Rd,Rn,Rm,i) CC_LDR_rRR_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRr_LSLi(Rd,Rn,Rm,i) CC_LDR_rRr_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRR_LSRi(Rd,Rn,Rm,i) CC_LDR_rRR_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRr_LSRi(Rd,Rn,Rm,i) CC_LDR_rRr_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRR_ASRi(Rd,Rn,Rm,i) CC_LDR_rRR_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRr_ASRi(Rd,Rn,Rm,i) CC_LDR_rRr_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRR_RORi(Rd,Rn,Rm,i) CC_LDR_rRR_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRr_RORi(Rd,Rn,Rm,i) CC_LDR_rRr_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDR_rRR_RRX(Rd,Rn,Rm) CC_LDR_rRR_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define LDR_rRr_RRX(Rd,Rn,Rm) CC_LDR_rRr_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_STR_rR(cc,Rd,Rn) _LS1(cc,0,0,Rd,Rn,ADD_IMM(0)) #define CC_STR_rRI(cc,Rd,Rn,i) _LS1(cc,0,0,Rd,Rn,ADD_IMM(i)) #define CC_STR_rRi(cc,Rd,Rn,i) _LS1(cc,0,0,Rd,Rn,SUB_IMM(i)) #define CC_STR_rRR(cc,Rd,Rn,Rm) _LS1(cc,0,0,Rd,Rn,ADD_REG(Rm)) #define CC_STR_rRr(cc,Rd,Rn,Rm) _LS1(cc,0,0,Rd,Rn,SUB_REG(Rm)) #define CC_STR_rRR_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,ADD_LSL(Rm,i)) #define CC_STR_rRr_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,SUB_LSL(Rm,i)) #define CC_STR_rRR_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,ADD_LSR(Rm,i)) #define CC_STR_rRr_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,SUB_LSR(Rm,i)) #define CC_STR_rRR_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,ADD_ASR(Rm,i)) #define CC_STR_rRr_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,SUB_ASR(Rm,i)) #define CC_STR_rRR_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,ADD_ROR(Rm,i)) #define CC_STR_rRr_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,0,0,Rd,Rn,SUB_ROR(Rm,i)) #define CC_STR_rRR_RRX(cc,Rd,Rn,Rm) _LS1(cc,0,0,Rd,Rn,ADD_RRX(Rm)) #define CC_STR_rRr_RRX(cc,Rd,Rn,Rm) _LS1(cc,0,0,Rd,Rn,SUB_RRX(Rm)) #define STR_rR(Rd,Rn) CC_STR_rR(NATIVE_CC_AL,Rd,Rn) #define STR_rRI(Rd,Rn,i) CC_STR_rRI(NATIVE_CC_AL,Rd,Rn,i) #define STR_rRi(Rd,Rn,i) CC_STR_rRi(NATIVE_CC_AL,Rd,Rn,i) #define STR_rRR(Rd,Rn,Rm) CC_STR_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define STR_rRr(Rd,Rn,Rm) CC_STR_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define STR_rRR_LSLi(Rd,Rn,Rm,i) CC_STR_rRR_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRr_LSLi(Rd,Rn,Rm,i) CC_STR_rRr_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRR_LSRi(Rd,Rn,Rm,i) CC_STR_rRR_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRr_LSRi(Rd,Rn,Rm,i) CC_STR_rRr_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRR_ASRi(Rd,Rn,Rm,i) CC_STR_rRR_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRr_ASRi(Rd,Rn,Rm,i) CC_STR_rRr_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRR_RORi(Rd,Rn,Rm,i) CC_STR_rRR_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRr_RORi(Rd,Rn,Rm,i) CC_STR_rRr_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STR_rRR_RRX(Rd,Rn,Rm) CC_STR_rRR_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define STR_rRr_RRX(Rd,Rn,Rm) CC_STR_rRr_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_LDRB_rR(cc,Rd,Rn) _LS1(cc,1,1,Rd,Rn,ADD_IMM(0)) #define CC_LDRB_rRI(cc,Rd,Rn,i) _LS1(cc,1,1,Rd,Rn,ADD_IMM(i)) #define CC_LDRB_rRi(cc,Rd,Rn,i) _LS1(cc,1,1,Rd,Rn,SUB_IMM(i)) #define CC_LDRB_rRR(cc,Rd,Rn,Rm) _LS1(cc,1,1,Rd,Rn,ADD_REG(Rm)) #define CC_LDRB_rRr(cc,Rd,Rn,Rm) _LS1(cc,1,1,Rd,Rn,SUB_REG(Rm)) #define CC_LDRB_rRR_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,ADD_LSL(Rm,i)) #define CC_LDRB_rRr_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,SUB_LSL(Rm,i)) #define CC_LDRB_rRR_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,ADD_LSR(Rm,i)) #define CC_LDRB_rRr_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,SUB_LSR(Rm,i)) #define CC_LDRB_rRR_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,ADD_ASR(Rm,i)) #define CC_LDRB_rRr_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,SUB_ASR(Rm,i)) #define CC_LDRB_rRR_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,ADD_ROR(Rm,i)) #define CC_LDRB_rRr_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,1,1,Rd,Rn,SUB_ROR(Rm,i)) #define CC_LDRB_rRR_RRX(cc,Rd,Rn,Rm) _LS1(cc,1,1,Rd,Rn,ADD_RRX(Rm)) #define CC_LDRB_rRr_RRX(cc,Rd,Rn,Rm) _LS1(cc,1,1,Rd,Rn,SUB_RRX(Rm)) #define LDRB_rR(Rd,Rn) CC_LDRB_rR(NATIVE_CC_AL,Rd,Rn) #define LDRB_rRI(Rd,Rn,i) CC_LDRB_rRI(NATIVE_CC_AL,Rd,Rn,i) #define LDRB_rRi(Rd,Rn,i) CC_LDRB_rRi(NATIVE_CC_AL,Rd,Rn,i) #define LDRB_rRR(Rd,Rn,Rm) CC_LDRB_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRB_rRr(Rd,Rn,Rm) CC_LDRB_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRB_rRR_LSLi(Rd,Rn,Rm,i) CC_LDRB_rRR_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRr_LSLi(Rd,Rn,Rm,i) CC_LDRB_rRr_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRR_LSRi(Rd,Rn,Rm,i) CC_LDRB_rRR_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRr_LSRi(Rd,Rn,Rm,i) CC_LDRB_rRr_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRR_ASRi(Rd,Rn,Rm,i) CC_LDRB_rRR_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRr_ASRi(Rd,Rn,Rm,i) CC_LDRB_rRr_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRR_RORi(Rd,Rn,Rm,i) CC_LDRB_rRR_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRr_RORi(Rd,Rn,Rm,i) CC_LDRB_rRr_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define LDRB_rRR_RRX(Rd,Rn,Rm) CC_LDRB_rRR_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRB_rRr_RRX(Rd,Rn,Rm) CC_LDRB_rRr_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_STRB_rR(cc,Rd,Rn) _LS1(cc,0,1,Rd,Rn,ADD_IMM(0)) #define CC_STRB_rRI(cc,Rd,Rn,i) _LS1(cc,0,1,Rd,Rn,ADD_IMM(i)) #define CC_STRB_rRi(cc,Rd,Rn,i) _LS1(cc,0,1,Rd,Rn,SUB_IMM(i)) #define CC_STRB_rRR(cc,Rd,Rn,Rm) _LS1(cc,0,1,Rd,Rn,ADD_REG(Rm)) #define CC_STRB_rRr(cc,Rd,Rn,Rm) _LS1(cc,0,1,Rd,Rn,SUB_REG(Rm)) #define CC_STRB_rRR_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,ADD_LSL(Rm,i)) #define CC_STRB_rRr_LSLi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,SUB_LSL(Rm,i)) #define CC_STRB_rRR_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,ADD_LSR(Rm,i)) #define CC_STRB_rRr_LSRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,SUB_LSR(Rm,i)) #define CC_STRB_rRR_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,ADD_ASR(Rm,i)) #define CC_STRB_rRr_ASRi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,SUB_ASR(Rm,i)) #define CC_STRB_rRR_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,ADD_ROR(Rm,i)) #define CC_STRB_rRr_RORi(cc,Rd,Rn,Rm,i) _LS1(cc,0,1,Rd,Rn,SUB_ROR(Rm,i)) #define CC_STRB_rRR_RRX(cc,Rd,Rn,Rm) _LS1(cc,0,1,Rd,Rn,ADD_RRX(Rm)) #define CC_STRB_rRr_RRX(cc,Rd,Rn,Rm) _LS1(cc,0,1,Rd,Rn,SUB_RRX(Rm)) #define STRB_rR(Rd,Rn) CC_STRB_rR(NATIVE_CC_AL,Rd,Rn) #define STRB_rRI(Rd,Rn,i) CC_STRB_rRI(NATIVE_CC_AL,Rd,Rn,i) #define STRB_rRi(Rd,Rn,i) CC_STRB_rRi(NATIVE_CC_AL,Rd,Rn,i) #define STRB_rRR(Rd,Rn,Rm) CC_STRB_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define STRB_rRr(Rd,Rn,Rm) CC_STRB_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define STRB_rRR_LSLi(Rd,Rn,Rm,i) CC_STRB_rRR_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRr_LSLi(Rd,Rn,Rm,i) CC_STRB_rRr_LSLi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRR_LSRi(Rd,Rn,Rm,i) CC_STRB_rRR_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRr_LSRi(Rd,Rn,Rm,i) CC_STRB_rRr_LSRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRR_ASRi(Rd,Rn,Rm,i) CC_STRB_rRR_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRr_ASRi(Rd,Rn,Rm,i) CC_STRB_rRr_ASRi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRR_RORi(Rd,Rn,Rm,i) CC_STRB_rRR_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRr_RORi(Rd,Rn,Rm,i) CC_STRB_rRr_RORi(NATIVE_CC_AL,Rd,Rn,Rm,i) #define STRB_rRR_RRX(Rd,Rn,Rm) CC_STRB_rRR_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define STRB_rRr_RRX(Rd,Rn,Rm) CC_STRB_rRr_RRX(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_LDRSH_rR(cc,Rd,Rn) _LS2(cc,1,1,1,1,Rd,Rn,ADD2_IMM(0)) #define CC_LDRSH_rRI(cc,Rd,Rn,i) _LS2(cc,1,1,1,1,Rd,Rn,ADD2_IMM(i)) #define CC_LDRSH_rRi(cc,Rd,Rn,i) _LS2(cc,1,1,1,1,Rd,Rn,SUB2_IMM(i)) #define CC_LDRSH_rRR(cc,Rd,Rn,Rm) _LS2(cc,1,1,1,1,Rd,Rn,ADD2_REG(Rm)) #define CC_LDRSH_rRr(cc,Rd,Rn,Rm) _LS2(cc,1,1,1,1,Rd,Rn,SUB2_REG(Rm)) #define LDRSH_rR(Rd,Rn) CC_LDRSH_rR(NATIVE_CC_AL,Rd,Rn) #define LDRSH_rRI(Rd,Rn,i) CC_LDRSH_rRI(NATIVE_CC_AL,Rd,Rn,i) #define LDRSH_rRi(Rd,Rn,i) CC_LDRSH_rRi(NATIVE_CC_AL,Rd,Rn,i) #define LDRSH_rRR(Rd,Rn,Rm) CC_LDRSH_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRSH_rRr(Rd,Rn,Rm) CC_LDRSH_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_LDRH_rR(cc,Rd,Rn) _LS2(cc,1,1,0,1,Rd,Rn,ADD2_IMM(0)) #define CC_LDRH_rRI(cc,Rd,Rn,i) _LS2(cc,1,1,0,1,Rd,Rn,(i) >= 0 ? ADD2_IMM(i) : SUB2_IMM(-(i))) #define CC_LDRH_rRi(cc,Rd,Rn,i) _LS2(cc,1,1,0,1,Rd,Rn,SUB2_IMM(i)) #define CC_LDRH_rRR(cc,Rd,Rn,Rm) _LS2(cc,1,1,0,1,Rd,Rn,ADD2_REG(Rm)) #define CC_LDRH_rRr(cc,Rd,Rn,Rm) _LS2(cc,1,1,0,1,Rd,Rn,SUB2_REG(Rm)) #define LDRH_rR(Rd,Rn) CC_LDRH_rR(NATIVE_CC_AL,Rd,Rn) #define LDRH_rRI(Rd,Rn,i) CC_LDRH_rRI(NATIVE_CC_AL,Rd,Rn,i) #define LDRH_rRi(Rd,Rn,i) CC_LDRH_rRi(NATIVE_CC_AL,Rd,Rn,i) #define LDRH_rRR(Rd,Rn,Rm) CC_LDRH_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRH_rRr(Rd,Rn,Rm) CC_LDRH_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_STRD_rR(cc,Rd,Rn) _LS2(cc,1,0,1,1,Rd,Rn,ADD2_IMM(0)) #define CC_STRD_rRI(cc,Rd,Rn,i) _LS2(cc,1,0,1,1,Rd,Rn,ADD2_IMM(i)) #define CC_STRD_rRi(cc,Rd,Rn,i) _LS2(cc,1,0,1,1,Rd,Rn,SUB2_IMM(i)) #define CC_STRD_rRR(cc,Rd,Rn,Rm) _LS2(cc,1,0,1,1,Rd,Rn,ADD2_REG(Rm)) #define CC_STRD_rRr(cc,Rd,Rn,Rm) _LS2(cc,1,0,1,1,Rd,Rn,SUB2_REG(Rm)) #define STRD_rR(Rd,Rn) CC_STRD_rR(NATIVE_CC_AL,Rd,Rn) #define STRD_rRI(Rd,Rn,i) CC_STRD_rRI(NATIVE_CC_AL,Rd,Rn,i) #define STRD_rRi(Rd,Rn,i) CC_STRD_rRi(NATIVE_CC_AL,Rd,Rn,i) #define STRD_rRR(Rd,Rn,Rm) CC_STRD_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define STRD_rRr(Rd,Rn,Rm) CC_STRD_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_STRH_rR(cc,Rd,Rn) _LS2(cc,1,0,0,1,Rd,Rn,ADD2_IMM(0)) #define CC_STRH_rRI(cc,Rd,Rn,i) _LS2(cc,1,0,0,1,Rd,Rn,ADD2_IMM(i)) #define CC_STRH_rRi(cc,Rd,Rn,i) _LS2(cc,1,0,0,1,Rd,Rn,SUB2_IMM(i)) #define CC_STRH_rRR(cc,Rd,Rn,Rm) _LS2(cc,1,0,0,1,Rd,Rn,ADD2_REG(Rm)) #define CC_STRH_rRr(cc,Rd,Rn,Rm) _LS2(cc,1,0,0,1,Rd,Rn,SUB2_REG(Rm)) #define STRH_rR(Rd,Rn) CC_STRH_rR(NATIVE_CC_AL,Rd,Rn) #define STRH_rRI(Rd,Rn,i) CC_STRH_rRI(NATIVE_CC_AL,Rd,Rn,i) #define STRH_rRi(Rd,Rn,i) CC_STRH_rRi(NATIVE_CC_AL,Rd,Rn,i) #define STRH_rRR(Rd,Rn,Rm) CC_STRH_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define STRH_rRr(Rd,Rn,Rm) CC_STRH_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_LDRSB_rR(cc,Rd,Rn) _LS2(cc,1,1,1,0,Rd,Rn,ADD2_IMM(0)) #define CC_LDRSB_rRI(cc,Rd,Rn,i) _LS2(cc,1,1,1,0,Rd,Rn,ADD2_IMM(i)) #define CC_LDRSB_rRi(cc,Rd,Rn,i) _LS2(cc,1,1,1,0,Rd,Rn,SUB2_IMM(i)) #define CC_LDRSB_rRR(cc,Rd,Rn,Rm) _LS2(cc,1,1,1,0,Rd,Rn,ADD2_REG(Rm)) #define CC_LDRSB_rRr(cc,Rd,Rn,Rm) _LS2(cc,1,1,1,0,Rd,Rn,SUB2_REG(Rm)) #define LDRSB_rR(Rd,Rn) CC_LDRSB_rR(NATIVE_CC_AL,Rd,Rn) #define LDRSB_rRI(Rd,Rn,i) CC_LDRSB_rRI(NATIVE_CC_AL,Rd,Rn,i) #define LDRSB_rRi(Rd,Rn,i) CC_LDRSB_rRi(NATIVE_CC_AL,Rd,Rn,i) #define LDRSB_rRR(Rd,Rn,Rm) CC_LDRSB_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRSB_rRr(Rd,Rn,Rm) CC_LDRSB_rRr(NATIVE_CC_AL,Rd,Rn,Rm) #define CC_LDRD_rR(cc,Rd,Rn) _LS2(cc,1,0,1,0,Rd,Rn,ADD2_IMM(0)) #define CC_LDRD_rRI(cc,Rd,Rn,i) _LS2(cc,1,0,1,0,Rd,Rn,ADD2_IMM(i)) #define CC_LDRD_rRi(cc,Rd,Rn,i) _LS2(cc,1,0,1,0,Rd,Rn,SUB2_IMM(i)) #define CC_LDRD_rRR(cc,Rd,Rn,Rm) _LS2(cc,1,0,1,0,Rd,Rn,ADD2_REG(Rm)) #define CC_LDRD_rRr(cc,Rd,Rn,Rm) _LS2(cc,1,0,1,0,Rd,Rn,SUB2_REG(Rm)) #define LDRD_rR(Rd,Rn) CC_LDRD_rR(NATIVE_CC_AL,Rd,Rn) #define LDRD_rRI(Rd,Rn,i) CC_LDRD_rRI(NATIVE_CC_AL,Rd,Rn,i) #define LDRD_rRi(Rd,Rn,i) CC_LDRD_rRi(NATIVE_CC_AL,Rd,Rn,i) #define LDRD_rRR(Rd,Rn,Rm) CC_LDRD_rRR(NATIVE_CC_AL,Rd,Rn,Rm) #define LDRD_rRr(Rd,Rn,Rm) CC_LDRD_rRr(NATIVE_CC_AL,Rd,Rn,Rm) /* Multiply */ #define CC_SMULL_rrrr(cc, RdLo, RdHi, Rm, Rs) _W(((cc) << 28) | (0x0C << 20) | ((RdHi) << 16) | ((RdLo) << 12) | ((Rs) << 8) | (0x9 << 4) | (Rm)) #define SMULL_rrrr(RdLo,RdHi,Rm,Rs) CC_SMULL_rrrr(NATIVE_CC_AL,RdLo,RdHi,Rm,Rs) #define CC_SMULLS_rrrr(cc, RdLo, RdHi, Rm, Rs) _W(((cc) << 28) | (0x0D << 20) | ((RdHi) << 16) | ((RdLo) << 12) | ((Rs) << 8) | (0x9 << 4) | (Rm)) #define SMULLS_rrrr(RdLo,RdHi,Rm,Rs) CC_SMULLS_rrrr(NATIVE_CC_AL,RdLo,RdHi,Rm,Rs) #define CC_MUL_rrr(cc, Rd, Rm, Rs) _W(((cc) << 28) | (0x00 << 20) | ((Rd) << 16) | ((Rs) << 8) | (0x9 << 4) | (Rm)) #define MUL_rrr(Rd, Rm, Rs) CC_MUL_rrr(NATIVE_CC_AL, Rd, Rm, Rs) #define CC_MULS_rrr(cc, Rd, Rm, Rs) _W(((cc) << 28) | (0x01 << 20) | ((Rd) << 16) | ((Rs) << 8) | (0x9 << 4) | (Rm)) #define MULS_rrr(Rd, Rm, Rs) CC_MULS_rrr(NATIVE_CC_AL, Rd, Rm, Rs) #define CC_UMULL_rrrr(cc, RdLo, RdHi, Rm, Rs) _W(((cc) << 28) | (0x08 << 20) | ((RdHi) << 16) | ((RdLo) << 12) | ((Rs) << 8) | (0x9 << 4) | (Rm)) #define UMULL_rrrr(RdLo,RdHi,Rm,Rs) CC_UMULL_rrrr(NATIVE_CC_AL,RdLo,RdHi,Rm,Rs) #define CC_UMULLS_rrrr(cc, RdLo, RdHi, Rm, Rs) _W(((cc) << 28) | (0x09 << 20) | ((RdHi) << 16) | ((RdLo) << 12) | ((Rs) << 8) | (0x9 << 4) | (Rm)) #define UMULLS_rrrr(RdLo,RdHi,Rm,Rs) CC_UMULLS_rrrr(NATIVE_CC_AL,RdLo,RdHi,Rm,Rs) /* Others */ #define CC_CLZ_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x16 << 20) | (0xf << 16) | ((Rd) << 12) | (0xf << 8) | (0x1 << 4) | SHIFT_REG(Rm)) #define CLZ_rr(Rd,Rm) CC_CLZ_rr(NATIVE_CC_AL,Rd,Rm) /* Alias */ #define LSL_rri(Rd,Rm,i) MOV_rrLSLi(Rd,Rm,i) #define LSL_rrr(Rd,Rm,Rs) MOV_rrLSLr(Rd,Rm,Rs) #define LSR_rri(Rd,Rm,i) MOV_rrLSRi(Rd,Rm,i) #define LSR_rrr(Rd,Rm,Rs) MOV_rrLSRr(Rd,Rm,Rs) #define ASR_rri(Rd,Rm,i) MOV_rrASRi(Rd,Rm,i) #define ASR_rrr(Rd,Rm,Rs) MOV_rrASRr(Rd,Rm,Rs) #define ROR_rri(Rd,Rm,i) MOV_rrRORi(Rd,Rm,i) #define ROR_rrr(Rd,Rm,Rs) MOV_rrRORr(Rd,Rm,Rs) #define RRX_rr(Rd,Rm) MOV_rrRRX(Rd,Rm) #define LSLS_rri(Rd,Rm,i) MOVS_rrLSLi(Rd,Rm,i) #define LSLS_rrr(Rd,Rm,Rs) MOVS_rrLSLr(Rd,Rm,Rs) #define LSRS_rri(Rd,Rm,i) MOVS_rrLSRi(Rd,Rm,i) #define LSRS_rrr(Rd,Rm,Rs) MOVS_rrLSRr(Rd,Rm,Rs) #define ASRS_rri(Rd,Rm,i) MOVS_rrASRi(Rd,Rm,i) #define ASRS_rrr(Rd,Rm,Rs) MOVS_rrASRr(Rd,Rm,Rs) #define RORS_rri(Rd,Rm,i) MOVS_rrRORi(Rd,Rm,i) #define RORS_rrr(Rd,Rm,Rs) MOVS_rrRORr(Rd,Rm,Rs) #define RRXS_rr(Rd,Rm) MOVS_rrRRX(Rd,Rm) /* ARMV6 ops */ #define CC_SXTB_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6a << 20) | (0xf << 16) | ((Rd) << 12) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTB_rr(Rd,Rm) CC_SXTB_rr(NATIVE_CC_AL,Rd,Rm) #define CC_SXTB_rr_ROR8(cc,Rd,Rm) _W(((cc) << 28) | (0x6a << 20) | (0xf << 16) | ((Rd) << 12) | (1 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTB_rr_ROR8(Rd,Rm) CC_SXTB_rr_ROR8(NATIVE_CC_AL,Rd,Rm) #define CC_SXTB_rr_ROR16(cc,Rd,Rm) _W(((cc) << 28) | (0x6a << 20) | (0xf << 16) | ((Rd) << 12) | (2 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTB_rr_ROR16(Rd,Rm) CC_SXTB_rr_ROR16(NATIVE_CC_AL,Rd,Rm) #define CC_SXTB_rr_ROR24(cc,Rd,Rm) _W(((cc) << 28) | (0x6a << 20) | (0xf << 16) | ((Rd) << 12) | (3 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTB_rr_ROR24(Rd,Rm) CC_SXTB_rr_ROR24(NATIVE_CC_AL,Rd,Rm) #define CC_SXTH_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6b << 20) | (0xf << 16) | ((Rd) << 12) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTH_rr(Rd,Rm) CC_SXTH_rr(NATIVE_CC_AL,Rd,Rm) #define CC_SXTH_rr_ROR8(cc,Rd,Rm) _W(((cc) << 28) | (0x6b << 20) | (0xf << 16) | ((Rd) << 12) | (1 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTH_rr_ROR8(Rd,Rm) CC_SXTH_rr_ROR8(NATIVE_CC_AL,Rd,Rm) #define CC_SXTH_rr_ROR16(cc,Rd,Rm) _W(((cc) << 28) | (0x6b << 20) | (0xf << 16) | ((Rd) << 12) | (2 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTH_rr_ROR16(Rd,Rm) CC_SXTH_rr_ROR16(NATIVE_CC_AL,Rd,Rm) #define CC_SXTH_rr_ROR24(cc,Rd,Rm) _W(((cc) << 28) | (0x6b << 20) | (0xf << 16) | ((Rd) << 12) | (3 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define SXTH_rr_ROR24(Rd,Rm) CC_SXTH_rr_ROR24(NATIVE_CC_AL,Rd,Rm) #define CC_UXTB_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6e << 20) | (0xf << 16) | ((Rd) << 12) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTB_rr(Rd,Rm) CC_UXTB_rr(NATIVE_CC_AL,Rd,Rm) #define CC_UXTB_rr_ROR8(cc,Rd,Rm) _W(((cc) << 28) | (0x6e << 20) | (0xf << 16) | ((Rd) << 12) | (1 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTB_rr_ROR8(Rd,Rm) CC_UXTB_rr_ROR8(NATIVE_CC_AL,Rd,Rm) #define CC_UXTB_rr_ROR16(cc,Rd,Rm) _W(((cc) << 28) | (0x6e << 20) | (0xf << 16) | ((Rd) << 12) | (2 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTB_rr_ROR16(Rd,Rm) CC_UXTB_rr_ROR16(NATIVE_CC_AL,Rd,Rm) #define CC_UXTB_rr_ROR24(cc,Rd,Rm) _W(((cc) << 28) | (0x6e << 20) | (0xf << 16) | ((Rd) << 12) | (3 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTB_rr_ROR24(Rd,Rm) CC_UXTB_rr_ROR24(NATIVE_CC_AL,Rd,Rm) #define CC_UXTH_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6f << 20) | (0xf << 16) | ((Rd) << 12) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTH_rr(Rd,Rm) CC_UXTH_rr(NATIVE_CC_AL,Rd,Rm) #define CC_UXTH_rr_ROR8(cc,Rd,Rm) _W(((cc) << 28) | (0x6f << 20) | (0xf << 16) | ((Rd) << 12) | (1 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTH_rr_ROR8(Rd,Rm) CC_UXTH_rr_ROR8(NATIVE_CC_AL,Rd,Rm) #define CC_UXTH_rr_ROR16(cc,Rd,Rm) _W(((cc) << 28) | (0x6f << 20) | (0xf << 16) | ((Rd) << 12) | (2 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTH_rr_ROR16(Rd,Rm) CC_UXTH_rr_ROR16(NATIVE_CC_AL,Rd,Rm) #define CC_UXTH_rr_ROR24(cc,Rd,Rm) _W(((cc) << 28) | (0x6f << 20) | (0xf << 16) | ((Rd) << 12) | (3 << 10) | (0x7 << 4) | SHIFT_REG(Rm)) #define UXTH_rr_ROR24(Rd,Rm) CC_UXTH_rr_ROR24(NATIVE_CC_AL,Rd,Rm) #define CC_REV_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6b << 20) | (0xf << 16) | (0xf << 8) | ((Rd) << 12) | (0x3 << 4) | SHIFT_REG(Rm)) #define REV_rr(Rd,Rm) CC_REV_rr(NATIVE_CC_AL,Rd,Rm) #define CC_REV16_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6b << 20) | (0xf << 16) | (0xf << 8) | ((Rd) << 12) | (0xB << 4) | SHIFT_REG(Rm)) #define REV16_rr(Rd,Rm) CC_REV16_rr(NATIVE_CC_AL,Rd,Rm) #define CC_REVSH_rr(cc,Rd,Rm) _W(((cc) << 28) | (0x6f << 20) | (0xf << 16) | (0xf << 8) | ((Rd) << 12) | (0xB << 4) | SHIFT_REG(Rm)) #define REVSH_rr(Rd,Rm) CC_REVSH_rr(NATIVE_CC_AL,Rd,Rm) #define CC_PKHBT_rrr(cc,Rd,Rn,Rm) _W(((cc) << 28) | (0x68 << 20) | (Rn << 16) | (Rd << 12) | (0x1 << 4) | (Rm)) #define CC_PKHBT_rrrLSLi(cc,Rd,Rn,Rm,s) _W(((cc) << 28) | (0x68 << 20) | (Rn << 16) | (Rd << 12) | (0x1 << 4) | SHIFT_PK(Rm, s)) #define PKHBT_rrr(Rd,Rn,Rm) CC_PKHBT_rrr(NATIVE_CC_AL,Rd,Rn,Rm) #define PKHBT_rrrLSLi(Rd,Rn,Rm,s) CC_PKHBT_rrrLSLi(NATIVE_CC_AL,Rd,Rn,Rm,s) #define CC_PKHTB_rrrASRi(cc,Rd,Rn,Rm,s) _W(((cc) << 28) | (0x68 << 20) | (Rn << 16) | (Rd << 12) | (0x5 << 4) | SHIFT_PK(Rm, s)) #define PKHTB_rrrASRi(Rd,Rn,Rm,s) CC_PKHTB_rrrASRi(NATIVE_CC_AL,Rd,Rn,Rm,s) #endif /* ARM_RTASM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/codegen_x86.c000066400000000000000000002246031504763705000251710ustar00rootroot00000000000000/* * compiler/codegen_x86.cpp - IA-32 and AMD64 code generator * * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> IA-32 and AMD64 * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ /* This should eventually end up in machdep/, but for now, x86 is the only target, and it's easier this way... */ #include "flags_x86.h" /************************************************************************* * Some basic information about the the target CPU * *************************************************************************/ #define R1 RR1 #define R2 RR2 #define R4 RR4 #define EAX_INDEX 0 #define ECX_INDEX 1 #define EDX_INDEX 2 #define EBX_INDEX 3 #define ESP_INDEX 4 #define EBP_INDEX 5 #define ESI_INDEX 6 #define EDI_INDEX 7 #if defined(CPU_x86_64) #define R8_INDEX 8 #define R9_INDEX 9 #define R10_INDEX 10 #define R11_INDEX 11 #define R12_INDEX 12 #define R13_INDEX 13 #define R14_INDEX 14 #define R15_INDEX 15 #endif /* XXX this has to match X86_Reg8H_Base + 4 */ #define AH_INDEX (0x10+4+EAX_INDEX) #define CH_INDEX (0x10+4+ECX_INDEX) #define DH_INDEX (0x10+4+EDX_INDEX) #define BH_INDEX (0x10+4+EBX_INDEX) /* The register in which subroutines return an integer return value */ #define REG_RESULT EAX_INDEX /* The registers subroutines take their first and second argument in */ #ifdef _WIN32 /* Handle the _fastcall parameters of ECX and EDX */ #define REG_PAR1 ECX_INDEX #define REG_PAR2 EDX_INDEX #elif defined(CPU_x86_64) #define REG_PAR1 EDI_INDEX #define REG_PAR2 ESI_INDEX #else #define REG_PAR1 EAX_INDEX #define REG_PAR2 EDX_INDEX #endif #define REG_PC_PRE EAX_INDEX /* The register we use for preloading regs.pc_p */ #ifdef _WIN32 #define REG_PC_TMP ECX_INDEX #else #define REG_PC_TMP ECX_INDEX /* Another register that is not the above */ #endif #define SHIFTCOUNT_NREG ECX_INDEX /* Register that can be used for shiftcount. -1 if any reg will do */ #define MUL_NREG1 EAX_INDEX /* %eax will hold the low 32 bits after a 32x32 mul */ #define MUL_NREG2 EDX_INDEX /* %edx will hold the high 32 bits */ #define STACK_ALIGN 16 #define STACK_OFFSET sizeof(void *) #ifdef _WIN64 /* In the Microsoft x64 calling convention, it's the caller's responsibility * to allocate 32 bytes of "shadow space" on the stack right before calling * the function (regardless of the actual number of parameters used). */ #define STACK_SHADOW_SPACE 32 #else #define STACK_SHADOW_SPACE 0 #endif #if defined(CPU_x86_64) #ifdef UAE /* Register R12 (and ESP) cannot be used with simple [r/m + disp32] addressing, * since r/m bits 100 implies SIB byte. Simplest fix is to not use these * registers. Also note that these registers are listed in the freescratch * function as well. */ uae_s8 always_used[] = { ESP_INDEX, R12_INDEX, -1 }; #else uae_s8 always_used[] = { ESP_INDEX, -1 }; #endif uae_s8 can_byte[]={0,1,2,3,5,6,7,8,9,10,11,12,13,14,15,-1}; uae_s8 can_word[]={0,1,2,3,5,6,7,8,9,10,11,12,13,14,15,-1}; #else uae_s8 always_used[] = { ESP_INDEX, -1 }; uae_s8 can_byte[]={0,1,2,3,-1}; uae_s8 can_word[]={0,1,2,3,5,6,7,-1}; #endif static bool have_lahf_lm = true; // target has LAHF supported in long mode ? #if USE_OPTIMIZED_CALLS /* Make sure interpretive core does not use cpuopti */ uae_u8 call_saved[]={0,0,0,1,1,1,1,1}; #error FIXME: code not ready #else /* cpuopti mutate instruction handlers to assume registers are saved by the caller */ uae_u8 call_saved[]={0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}; #endif /* This *should* be the same as call_saved. But: - We might not really know which registers are saved, and which aren't, so we need to preserve some, but don't want to rely on everyone else also saving those registers - Special registers (such like the stack pointer) should not be "preserved" by pushing, even though they are "saved" across function calls */ #if defined(CPU_x86_64) #ifdef _WIN64 /* https://msdn.microsoft.com/en-us/library/6t169e9c.aspx: * "The registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14, and R15 are * considered nonvolatile and must be saved and restored by a function that * uses them". Also saving r11 for now (see comment below). */ static const uae_u8 need_to_preserve[]={0,0,0,1,0,1,1,1,0,0,0,1,1,1,1,1}; #else /* callee-saved registers as defined by Linux AMD64 ABI: rbx, rbp, rsp, r12 - r15 */ /* preserve r11 because it's generally used to hold pointers to functions */ /* FIXME: not really sure what the point of saving r11 is (??). If functions * cannot assume calle preserves it, it will not be used across calls anyway? */ static const uae_u8 need_to_preserve[]={0,0,0,1,0,1,0,0,0,0,0,1,1,1,1,1}; #endif #else /* callee-saved registers as defined by System V IA-32 ABI: edi, esi, ebx, ebp */ static const uae_u8 need_to_preserve[]={0,0,0,1,0,1,1,1}; #endif /* Whether classes of instructions do or don't clobber the native flags */ #define CLOBBER_MOV #define CLOBBER_LEA #define CLOBBER_CMOV #define CLOBBER_POP #define CLOBBER_PUSH #define CLOBBER_SUB clobber_flags() #define CLOBBER_SBB clobber_flags() #define CLOBBER_CMP clobber_flags() #define CLOBBER_ADD clobber_flags() #define CLOBBER_ADC clobber_flags() #define CLOBBER_AND clobber_flags() #define CLOBBER_OR clobber_flags() #define CLOBBER_XOR clobber_flags() #define CLOBBER_ROL clobber_flags() #define CLOBBER_ROR clobber_flags() #define CLOBBER_SHLL clobber_flags() #define CLOBBER_SHRL clobber_flags() #define CLOBBER_SHRA clobber_flags() #define CLOBBER_TEST clobber_flags() #define CLOBBER_CL16 #define CLOBBER_CL8 #define CLOBBER_SE32 #define CLOBBER_SE16 #define CLOBBER_SE8 #define CLOBBER_ZE32 #define CLOBBER_ZE16 #define CLOBBER_ZE8 #define CLOBBER_SW16 clobber_flags() #define CLOBBER_SW32 #define CLOBBER_SETCC #define CLOBBER_MUL clobber_flags() #define CLOBBER_BT clobber_flags() #define CLOBBER_BSF clobber_flags() #if defined(CPU_x86_64) #define X86_TARGET_64BIT 1 /* The address override prefix causes a 5 cycles penalty on Intel Core processors. Another solution would be to decompose the load in an LEA, MOV (to zero-extend), MOV (from memory): is it better? */ #define ADDR32 x86_emit_byte(0x67), #else #define ADDR32 #endif #define X86_FLAT_REGISTERS 0 #define X86_OPTIMIZE_ALU 1 #define X86_OPTIMIZE_ROTSHI 1 #include "codegen_x86.h" #define x86_emit_byte(B) emit_byte(B) #define x86_emit_word(W) emit_word(W) #define x86_emit_long(L) emit_long(L) #define x86_emit_quad(Q) emit_quad(Q) #define x86_get_target() get_target() #define x86_emit_failure(MSG) jit_fail(MSG, __FILE__, __LINE__, __FUNCTION__) static inline void x86_64_addr32(void) { #ifdef CPU_x86_64 emit_byte(0x67); #endif } static inline void x86_64_rex(bool /* w */, uae_u32 * /* r */, uae_u32 * /* x */, uae_u32 *b) { #ifdef CPU_x86_64 int rex_byte = 0x40; if (*b >= R8_INDEX) { *b -= R8_INDEX; rex_byte |= 1; } if (rex_byte != 0x40) { emit_byte(rex_byte); } #else UNUSED(b); #endif } static inline void x86_64_prefix( bool addr32, bool w, uae_u32 *r, uae_u32 *x, uae_u32 *b) { if (addr32) { x86_64_addr32(); } x86_64_rex(w, r, x, b); } // Some mappings to mark compemu_support calls as only used by compemu // These are still mainly x86 minded. Should be more CPU independent in the future #define compemu_raw_add_l_mi(a,b) raw_add_l_mi(a,b) #define compemu_raw_and_l_ri(a,b) raw_and_l_ri(a,b) #define compemu_raw_bswap_32(a) raw_bswap_32(a) #define compemu_raw_bt_l_ri(a,b) raw_bt_l_ri(a,b) #define compemu_raw_call(a) raw_call(a) #define compemu_raw_cmov_l_rm_indexed(a,b,c,d,e) raw_cmov_l_rm_indexed(a,b,c,d,e) #define compemu_raw_cmp_l_mi(a,b) raw_cmp_l_mi(a,b) #define compemu_raw_cmp_l_mi8(a,b) raw_cmp_l_mi(a,b) #define compemu_raw_jcc_b_oponly(a) raw_jcc_b_oponly(a) #define compemu_raw_jcc_l_oponly(a) raw_jcc_l_oponly(a) #define compemu_raw_jl(a) raw_jl(a) #define compemu_raw_jmp(a) raw_jmp(a) #define compemu_raw_jmp_m_indexed(a,b,c) raw_jmp_m_indexed(a,b,c) #define compemu_raw_jmp_r(a) raw_jmp_r(a) #define compemu_raw_jnz(a) raw_jnz(a) #define compemu_raw_jz_b_oponly() raw_jz_b_oponly() #define compemu_raw_jnz_b_oponly() raw_jnz_b_oponly() #define compemu_raw_lea_l_brr(a,b,c) raw_lea_l_brr(a,b,c) #define compemu_raw_lea_l_brr_indexed(a,b,c,d,e) raw_lea_l_brr_indexed(a,b,c,d,e) #define compemu_raw_mov_b_mr(a,b) raw_mov_b_mr(a,b) #define compemu_raw_mov_l_mi(a,b) raw_mov_l_mi(a,b) #define compemu_raw_mov_l_mr(a,b) raw_mov_l_mr(a,b) #define compemu_raw_mov_l_ri(a,b) raw_mov_l_ri(a,b) #define compemu_raw_mov_l_rm(a,b) raw_mov_l_rm(a,b) #define compemu_raw_mov_l_rr(a,b) raw_mov_l_rr(a,b) #define compemu_raw_mov_w_mr(a,b) raw_mov_w_mr(a,b) #define compemu_raw_sub_l_mi(a,b) raw_sub_l_mi(a,b) #define compemu_raw_test_l_rr(a,b) raw_test_l_rr(a,b) #define compemu_raw_zero_extend_16_rr(a,b) raw_zero_extend_16_rr(a,b) #define compemu_raw_lea_l_rr_indexed(a,b,c,d) raw_lea_l_rr_indexed(a,b,c,d) static void jit_fail(const char *msg, const char *file, int line, const char *function) { jit_abort("failure in function %s from file %s at line %d: %s", function, file, line, msg); } LOWFUNC(NONE,WRITE,1,raw_push_l_r,(R4 r)) { #if defined(CPU_x86_64) PUSHQr(r); #else PUSHLr(r); #endif } LOWFUNC(NONE,READ,1,raw_pop_l_r,(R4 r)) { #if defined(CPU_x86_64) POPQr(r); #else POPLr(r); #endif } LOWFUNC(NONE,READ,1,raw_pop_l_m,(MEMW d)) { #if defined(CPU_x86_64) POPQm(d, X86_NOREG, X86_NOREG, 1); #else POPLm(d, X86_NOREG, X86_NOREG, 1); #endif } LOWFUNC(WRITE,NONE,2,raw_bt_l_ri,(R4 r, IMM i)) { BTLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_bt_l_rr,(R4 r, R4 b)) { BTLrr(b, r); } LOWFUNC(WRITE,NONE,2,raw_btc_l_ri,(RW4 r, IMM i)) { BTCLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_btc_l_rr,(RW4 r, R4 b)) { BTCLrr(b, r); } LOWFUNC(WRITE,NONE,2,raw_btr_l_ri,(RW4 r, IMM i)) { BTRLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_btr_l_rr,(RW4 r, R4 b)) { BTRLrr(b, r); } LOWFUNC(WRITE,NONE,2,raw_bts_l_ri,(RW4 r, IMM i)) { BTSLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_bts_l_rr,(RW4 r, R4 b)) { BTSLrr(b, r); } LOWFUNC(WRITE,NONE,2,raw_sub_w_ri,(RW2 d, IMM i)) { SUBWir(i, d); } LOWFUNC(NONE,READ,2,raw_mov_l_rm,(W4 d, MEMR s)) { ADDR32 MOVLmr(s, X86_NOREG, X86_NOREG, 1, d); } LOWFUNC(NONE,WRITE,2,raw_mov_l_mi,(MEMW d, IMM s)) { ADDR32 MOVLim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,2,raw_mov_w_mi,(MEMW d, IMM s)) { ADDR32 MOVWim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,2,raw_mov_b_mi,(MEMW d, IMM s)) { ADDR32 MOVBim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,RMW,2,raw_rol_b_mi,(MEMRW d, IMM i)) { ADDR32 ROLBim(i, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,NONE,2,raw_rol_b_ri,(RW1 r, IMM i)) { ROLBir(i, r); } LOWFUNC(WRITE,NONE,2,raw_rol_w_ri,(RW2 r, IMM i)) { ROLWir(i, r); } LOWFUNC(WRITE,NONE,2,raw_rol_l_ri,(RW4 r, IMM i)) { ROLLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_rol_l_rr,(RW4 d, R1 r)) { ROLLrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_rol_w_rr,(RW2 d, R1 r)) { ROLWrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_rol_b_rr,(RW1 d, R1 r)) { ROLBrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shll_l_rr,(RW4 d, R1 r)) { SHLLrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shll_w_rr,(RW2 d, R1 r)) { SHLWrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shll_b_rr,(RW1 d, R1 r)) { SHLBrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_ror_b_ri,(RW1 r, IMM i)) { RORBir(i, r); } LOWFUNC(WRITE,NONE,2,raw_ror_w_ri,(RW2 r, IMM i)) { RORWir(i, r); } LOWFUNC(WRITE,READ,2,raw_or_l_rm,(RW4 d, MEMR s)) { ADDR32 ORLmr(s, X86_NOREG, X86_NOREG, 1, d); } LOWFUNC(WRITE,NONE,2,raw_ror_l_ri,(RW4 r, IMM i)) { RORLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_ror_l_rr,(RW4 d, R1 r)) { RORLrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_ror_w_rr,(RW2 d, R1 r)) { RORWrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_ror_b_rr,(RW1 d, R1 r)) { RORBrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shrl_l_rr,(RW4 d, R1 r)) { SHRLrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shrl_w_rr,(RW2 d, R1 r)) { SHRWrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shrl_b_rr,(RW1 d, R1 r)) { SHRBrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shra_l_rr,(RW4 d, R1 r)) { SARLrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shra_w_rr,(RW2 d, R1 r)) { SARWrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shra_b_rr,(RW1 d, R1 r)) { SARBrr(r, d); } LOWFUNC(WRITE,NONE,2,raw_shll_l_ri,(RW4 r, IMM i)) { SHLLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shll_w_ri,(RW2 r, IMM i)) { SHLWir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shll_b_ri,(RW1 r, IMM i)) { SHLBir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shrl_l_ri,(RW4 r, IMM i)) { SHRLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shrl_w_ri,(RW2 r, IMM i)) { SHRWir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shrl_b_ri,(RW1 r, IMM i)) { SHRBir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shra_l_ri,(RW4 r, IMM i)) { SARLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shra_w_ri,(RW2 r, IMM i)) { SARWir(i, r); } LOWFUNC(WRITE,NONE,2,raw_shra_b_ri,(RW1 r, IMM i)) { SARBir(i, r); } LOWFUNC(WRITE,NONE,1,raw_sahf,(R2)) { SAHF(); } LOWFUNC(NONE,NONE,1,raw_cpuid,(R4)) { CPUID(); } LOWFUNC(READ,NONE,1,raw_lahf,(W2)) { LAHF(); } LOWFUNC(READ,NONE,2,raw_setcc,(W1 d, IMM cc)) { SETCCir(cc, d); } LOWFUNC(READ,WRITE,2,raw_setcc_m,(MEMW d, IMM cc)) { ADDR32 SETCCim(cc, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(READ,NONE,3,raw_cmov_l_rr,(RW4 d, R4 s, IMM cc)) { if (have_cmov) CMOVLrr(cc, s, d); else { /* replacement using branch and mov */ uae_s8 *target_p = (uae_s8 *)x86_get_target() + 1; JCCSii(cc^1, 0); MOVLrr(s, d); *target_p = JITPTR x86_get_target() - (JITPTR target_p + 1); } } LOWFUNC(WRITE,NONE,2,raw_bsf_l_rr,(W4 d, R4 s)) { BSFLrr(s, d); } LOWFUNC(NONE,NONE,2,raw_sign_extend_32_rr,(W4 d, R4 s)) { MOVSLQrr(s, d); } LOWFUNC(NONE,NONE,2,raw_sign_extend_16_rr,(W4 d, R2 s)) { MOVSWLrr(s, d); } LOWFUNC(NONE,NONE,2,raw_sign_extend_8_rr,(W4 d, R1 s)) { MOVSBLrr(s, d); } LOWFUNC(NONE,NONE,2,raw_zero_extend_16_rr,(W4 d, R2 s)) { MOVZWLrr(s, d); } LOWFUNC(NONE,NONE,2,raw_zero_extend_8_rr,(W4 d, R1 s)) { MOVZBLrr(s, d); } LOWFUNC(NONE,NONE,2,raw_imul_32_32,(RW4 d, R4 s)) { IMULLrr(s, d); } LOWFUNC(NONE,NONE,2,raw_imul_64_32,(RW4 d, RW4 s)) { if (d!=MUL_NREG1 || s!=MUL_NREG2) { jit_abort("Bad register in IMUL: d=%d, s=%d",d,s); } IMULLr(s); } LOWFUNC(NONE,NONE,2,raw_mul_64_32,(RW4 d, RW4 s)) { if (d!=MUL_NREG1 || s!=MUL_NREG2) { jit_abort("Bad register in MUL: d=%d, s=%d",d,s); } MULLr(s); } LOWFUNC(NONE,NONE,2,raw_mul_32_32,(RW4, R4)) { x86_emit_failure("raw_mul_32_32"); /* %^$&%^$%#^ x86! */ } LOWFUNC(NONE,NONE,2,raw_mov_b_rr,(W1 d, R1 s)) { MOVBrr(s, d); } LOWFUNC(NONE,NONE,2,raw_mov_w_rr,(W2 d, R2 s)) { MOVWrr(s, d); } LOWFUNC(NONE,READ,4,raw_mov_l_rrm_indexed,(W4 d,R4 baser, R4 index, IMM factor)) { ADDR32 MOVLmr(0, baser, index, factor, d); } LOWFUNC(NONE,READ,4,raw_mov_w_rrm_indexed,(W2 d, R4 baser, R4 index, IMM factor)) { ADDR32 MOVWmr(0, baser, index, factor, d); } LOWFUNC(NONE,READ,4,raw_mov_b_rrm_indexed,(W1 d, R4 baser, R4 index, IMM factor)) { ADDR32 MOVBmr(0, baser, index, factor, d); } LOWFUNC(NONE,WRITE,4,raw_mov_l_mrr_indexed,(R4 baser, R4 index, IMM factor, R4 s)) { ADDR32 MOVLrm(s, 0, baser, index, factor); } LOWFUNC(NONE,WRITE,4,raw_mov_w_mrr_indexed,(R4 baser, R4 index, IMM factor, R2 s)) { ADDR32 MOVWrm(s, 0, baser, index, factor); } LOWFUNC(NONE,WRITE,4,raw_mov_b_mrr_indexed,(R4 baser, R4 index, IMM factor, R1 s)) { ADDR32 MOVBrm(s, 0, baser, index, factor); } LOWFUNC(NONE,WRITE,5,raw_mov_l_bmrr_indexed,(IMM base, R4 baser, R4 index, IMM factor, R4 s)) { ADDR32 MOVLrm(s, base, baser, index, factor); } LOWFUNC(NONE,WRITE,5,raw_mov_w_bmrr_indexed,(IMM base, R4 baser, R4 index, IMM factor, R2 s)) { ADDR32 MOVWrm(s, base, baser, index, factor); } LOWFUNC(NONE,WRITE,5,raw_mov_b_bmrr_indexed,(IMM base, R4 baser, R4 index, IMM factor, R1 s)) { ADDR32 MOVBrm(s, base, baser, index, factor); } LOWFUNC(NONE,READ,5,raw_mov_l_brrm_indexed,(W4 d, IMM base, R4 baser, R4 index, IMM factor)) { ADDR32 MOVLmr(base, baser, index, factor, d); } LOWFUNC(NONE,READ,5,raw_mov_w_brrm_indexed,(W2 d, IMM base, R4 baser, R4 index, IMM factor)) { ADDR32 MOVWmr(base, baser, index, factor, d); } LOWFUNC(NONE,READ,5,raw_mov_b_brrm_indexed,(W1 d, IMM base, R4 baser, R4 index, IMM factor)) { ADDR32 MOVBmr(base, baser, index, factor, d); } LOWFUNC(NONE,READ,4,raw_mov_l_rm_indexed,(W4 d, IMM base, R4 index, IMM factor)) { ADDR32 MOVLmr(base, X86_NOREG, index, factor, d); } LOWFUNC(NONE,READ,5,raw_cmov_l_rm_indexed,(W4 d, IMM base, R4 index, IMM factor, IMM cond)) { if (have_cmov) ADDR32 CMOVLmr(cond, base, X86_NOREG, index, factor, d); else { /* replacement using branch and mov */ uae_s8 *target_p = (uae_s8 *)x86_get_target() + 1; JCCSii(cond^1, 0); ADDR32 MOVLmr(base, X86_NOREG, index, factor, d); *target_p = JITPTR x86_get_target() - (JITPTR target_p + 1); } } LOWFUNC(NONE,READ,3,raw_cmov_l_rm,(W4 d, IMM mem, IMM cond)) { if (have_cmov) CMOVLmr(cond, mem, X86_NOREG, X86_NOREG, 1, d); else { /* replacement using branch and mov */ uae_s8 *target_p = (uae_s8 *)x86_get_target() + 1; JCCSii(cond^1, 0); ADDR32 MOVLmr(mem, X86_NOREG, X86_NOREG, 1, d); *target_p = JITPTR x86_get_target() - (JITPTR target_p + 1); } } LOWFUNC(NONE,READ,3,raw_mov_l_rR,(W4 d, R4 s, IMM offset)) { ADDR32 MOVLmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,READ,3,raw_mov_w_rR,(W2 d, R4 s, IMM offset)) { ADDR32 MOVWmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,READ,3,raw_mov_b_rR,(W1 d, R4 s, IMM offset)) { ADDR32 MOVBmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,READ,3,raw_mov_l_brR,(W4 d, R4 s, IMM offset)) { ADDR32 MOVLmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,READ,3,raw_mov_w_brR,(W2 d, R4 s, IMM offset)) { ADDR32 MOVWmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,READ,3,raw_mov_b_brR,(W1 d, R4 s, IMM offset)) { ADDR32 MOVBmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,WRITE,3,raw_mov_l_Ri,(R4 d, IMM i, IMM offset)) { ADDR32 MOVLim(i, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_w_Ri,(R4 d, IMM i, IMM offset)) { ADDR32 MOVWim(i, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_b_Ri,(R4 d, IMM i, IMM offset)) { ADDR32 MOVBim(i, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_l_Rr,(R4 d, R4 s, IMM offset)) { ADDR32 MOVLrm(s, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_w_Rr,(R4 d, R2 s, IMM offset)) { ADDR32 MOVWrm(s, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_b_Rr,(R4 d, R1 s, IMM offset)) { ADDR32 MOVBrm(s, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,NONE,3,raw_lea_l_brr,(W4 d, R4 s, IMM offset)) { ADDR32 LEALmr(offset, s, X86_NOREG, 1, d); } LOWFUNC(NONE,NONE,5,raw_lea_l_brr_indexed,(W4 d, R4 s, R4 index, IMM factor, IMM offset)) { ADDR32 LEALmr(offset, s, index, factor, d); } LOWFUNC(NONE,NONE,4,raw_lea_l_rr_indexed,(W4 d, R4 s, R4 index, IMM factor)) { ADDR32 LEALmr(0, s, index, factor, d); } LOWFUNC(NONE,NONE,4,raw_lea_l_r_scaled,(W4 d, R4 index, IMM factor)) { ADDR32 LEALmr(0, X86_NOREG, index, factor, d); } LOWFUNC(NONE,WRITE,3,raw_mov_l_bRr,(R4 d, R4 s, IMM offset)) { ADDR32 MOVLrm(s, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_w_bRr,(R4 d, R2 s, IMM offset)) { ADDR32 MOVWrm(s, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,3,raw_mov_b_bRr,(R4 d, R1 s, IMM offset)) { ADDR32 MOVBrm(s, offset, d, X86_NOREG, 1); } LOWFUNC(NONE,NONE,1,raw_bswap_32,(RW4 r)) { BSWAPLr(r); } LOWFUNC(WRITE,NONE,1,raw_bswap_16,(RW2 r)) { ROLWir(8, r); } LOWFUNC(NONE,NONE,2,raw_mov_l_rr,(W4 d, R4 s)) { MOVLrr(s, d); } LOWFUNC(NONE,WRITE,2,raw_mov_l_mr,(IMM d, R4 s)) { ADDR32 MOVLrm(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(NONE,WRITE,2,raw_mov_w_mr,(IMM d, R2 s)) { ADDR32 MOVWrm(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(NONE,READ,2,raw_mov_w_rm,(W2 d, IMM s)) { ADDR32 MOVWmr(s, X86_NOREG, X86_NOREG, 1, d); } LOWFUNC(NONE,WRITE,2,raw_mov_b_mr,(IMM d, R1 s)) { ADDR32 MOVBrm(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(NONE,READ,2,raw_mov_b_rm,(W1 d, IMM s)) { ADDR32 MOVBmr(s, X86_NOREG, X86_NOREG, 1, d); } LOWFUNC(NONE,NONE,2,raw_mov_l_ri,(W4 d, IMM s)) { MOVLir(s, d); } LOWFUNC(NONE,NONE,2,raw_mov_w_ri,(W2 d, IMM s)) { MOVWir(s, d); } LOWFUNC(NONE,NONE,2,raw_mov_b_ri,(W1 d, IMM s)) { MOVBir(s, d); } LOWFUNC(RMW,RMW,2,raw_adc_l_mi,(MEMRW d, IMM s)) { ADDR32 ADCLim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,RMW,2,raw_add_l_mi,(IMM d, IMM s)) { ADDR32 ADDLim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,RMW,2,raw_add_w_mi,(IMM d, IMM s)) { ADDR32 ADDWim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,RMW,2,raw_add_b_mi,(IMM d, IMM s)) { ADDR32 ADDBim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,NONE,2,raw_test_l_ri,(R4 d, IMM i)) { TESTLir(i, d); } LOWFUNC(WRITE,NONE,2,raw_test_l_rr,(R4 d, R4 s)) { TESTLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_test_w_rr,(R2 d, R2 s)) { TESTWrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_test_b_rr,(R1 d, R1 s)) { TESTBrr(s, d); } LOWFUNC(WRITE,READ,2,raw_test_b_mi,(IMM d, IMM s)) { ADDR32 TESTBim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,NONE,2,raw_xor_l_ri,(RW4 d, IMM i)) { XORLir(i, d); } LOWFUNC(WRITE,NONE,2,raw_and_l_ri,(RW4 d, IMM i)) { ANDLir(i, d); } LOWFUNC(WRITE,NONE,2,raw_and_w_ri,(RW2 d, IMM i)) { ANDWir(i, d); } LOWFUNC(WRITE,NONE,2,raw_and_l,(RW4 d, R4 s)) { ANDLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_and_w,(RW2 d, R2 s)) { ANDWrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_and_b,(RW1 d, R1 s)) { ANDBrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_or_l_ri,(RW4 d, IMM i)) { ORLir(i, d); } LOWFUNC(WRITE,NONE,2,raw_or_l,(RW4 d, R4 s)) { ORLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_or_w,(RW2 d, R2 s)) { ORWrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_or_b,(RW1 d, R1 s)) { ORBrr(s, d); } LOWFUNC(RMW,NONE,2,raw_adc_l,(RW4 d, R4 s)) { ADCLrr(s, d); } LOWFUNC(RMW,NONE,2,raw_adc_w,(RW2 d, R2 s)) { ADCWrr(s, d); } LOWFUNC(RMW,NONE,2,raw_adc_b,(RW1 d, R1 s)) { ADCBrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_add_l,(RW4 d, R4 s)) { ADDLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_add_w,(RW2 d, R2 s)) { ADDWrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_add_b,(RW1 d, R1 s)) { ADDBrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_sub_l_ri,(RW4 d, IMM i)) { SUBLir(i, d); } LOWFUNC(WRITE,NONE,2,raw_sub_b_ri,(RW1 d, IMM i)) { SUBBir(i, d); } LOWFUNC(WRITE,NONE,2,raw_add_l_ri,(RW4 d, IMM i)) { ADDLir(i, d); } LOWFUNC(WRITE,NONE,2,raw_add_w_ri,(RW2 d, IMM i)) { ADDWir(i, d); } LOWFUNC(WRITE,NONE,2,raw_add_b_ri,(RW1 d, IMM i)) { ADDBir(i, d); } LOWFUNC(RMW,NONE,2,raw_sbb_l,(RW4 d, R4 s)) { SBBLrr(s, d); } LOWFUNC(RMW,NONE,2,raw_sbb_w,(RW2 d, R2 s)) { SBBWrr(s, d); } LOWFUNC(RMW,NONE,2,raw_sbb_b,(RW1 d, R1 s)) { SBBBrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_sub_l,(RW4 d, R4 s)) { SUBLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_sub_w,(RW2 d, R2 s)) { SUBWrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_sub_b,(RW1 d, R1 s)) { SUBBrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_cmp_l,(R4 d, R4 s)) { CMPLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_cmp_l_ri,(R4 r, IMM i)) { CMPLir(i, r); } LOWFUNC(WRITE,NONE,2,raw_cmp_w,(R2 d, R2 s)) { CMPWrr(s, d); } LOWFUNC(WRITE,READ,2,raw_cmp_b_mi,(MEMR d, IMM s)) { ADDR32 CMPBim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,NONE,2,raw_cmp_b_ri,(R1 d, IMM i)) { CMPBir(i, d); } LOWFUNC(WRITE,NONE,2,raw_cmp_b,(R1 d, R1 s)) { CMPBrr(s, d); } LOWFUNC(WRITE,READ,4,raw_cmp_l_rm_indexed,(R4 d, IMM offset, R4 index, IMM factor)) { ADDR32 CMPLmr(offset, X86_NOREG, index, factor, d); } LOWFUNC(WRITE,NONE,2,raw_xor_l,(RW4 d, R4 s)) { XORLrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_xor_w,(RW2 d, R2 s)) { XORWrr(s, d); } LOWFUNC(WRITE,NONE,2,raw_xor_b,(RW1 d, R1 s)) { XORBrr(s, d); } LOWFUNC(WRITE,RMW,2,raw_sub_l_mi,(MEMRW d, IMM s)) { ADDR32 SUBLim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(WRITE,READ,2,raw_cmp_l_mi,(MEMR d, IMM s)) { ADDR32 CMPLim(s, d, X86_NOREG, X86_NOREG, 1); } LOWFUNC(NONE,NONE,2,raw_xchg_l_rr,(RW4 r1, RW4 r2)) { XCHGLrr(r2, r1); } LOWFUNC(NONE,NONE,2,raw_xchg_b_rr,(RW4 r1, RW4 r2)) { XCHGBrr(r2, r1); } LOWFUNC(READ,WRITE,0,raw_pushfl,(void)) { PUSHF(); } LOWFUNC(WRITE,READ,0,raw_popfl,(void)) { POPF(); } /* Generate floating-point instructions */ static inline void x86_fadd_m(MEMR s) { ADDR32 FADDLm(s,X86_NOREG,X86_NOREG,1); } /************************************************************************* * Unoptimizable stuff --- jump * *************************************************************************/ static inline void raw_call_r(R4 r) { CALLsr(r); } static inline void raw_call_m_indexed(uae_u32 base, uae_u32 r, uae_u32 m) { ADDR32 CALLsm(base, X86_NOREG, r, m); } static inline void raw_jmp_r(R4 r) { JMPsr(r); } static inline void raw_jmp_m_indexed(uae_u32 base, uae_u32 r, uae_u32 m) { ADDR32 JMPsm(base, X86_NOREG, r, m); } static inline void raw_jmp_m(uae_u32 base) { emit_byte(0xff); emit_byte(0x25); emit_long(base); } static inline void raw_call(uae_u32 t) { ADDR32 CALLm(t); } static inline void raw_jmp(uae_u32 t) { ADDR32 JMPm(t); } static inline void raw_jcc_l_oponly(int cc) { emit_byte(0x0f); emit_byte(0x80+cc); } static inline void raw_jz_l_oponly(void) { raw_jcc_l_oponly(NATIVE_CC_EQ); } static inline void raw_jnz_l_oponly(void) { raw_jcc_l_oponly(NATIVE_CC_NE); } static inline void raw_jl(uae_u32 t) { raw_jcc_l_oponly(NATIVE_CC_LT); emit_long(t-(uae_u32)JITPTR target-4); } static inline void raw_jz(uae_u32 t) { raw_jz_l_oponly(); emit_long(t-(uae_u32)JITPTR target-4); } static inline void raw_jnz(uae_u32 t) { raw_jnz_l_oponly(); emit_long(t-(uae_u32)JITPTR target-4); } static inline void raw_jcc_b_oponly(int cc) { emit_byte(0x70+cc); } static inline void raw_jnz_b_oponly(void) { raw_jcc_b_oponly(NATIVE_CC_NE); } static inline void raw_jz_b_oponly(void) { raw_jcc_b_oponly(NATIVE_CC_EQ); } static inline void raw_jmp_l_oponly(void) { emit_byte(0xe9); } static inline void raw_jmp_b_oponly(void) { emit_byte(0xeb); } static inline void raw_ret(void) { emit_byte(0xc3); } static inline void raw_emit_nop(void) { emit_byte(0x90); } static inline void raw_emit_nop_filler(int nbytes) { #if defined(CPU_x86_64) /* The recommended way to pad 64bit code is to use NOPs preceded by maximally four 0x66 prefixes. Balance the size of nops. */ static const uae_u8 prefixes[4] = { 0x66, 0x66, 0x66, 0x66 }; if (nbytes == 0) return; int i; int nnops = (nbytes + 3) / 4; int len = nbytes / nnops; int remains = nbytes - nnops * len; for (i = 0; i < remains; i++) { emit_block(prefixes, len); raw_emit_nop(); } for (; i < nnops; i++) { emit_block(prefixes, len - 1); raw_emit_nop(); } #else /* Source: GNU Binutils 2.12.90.0.15 */ /* Various efficient no-op patterns for aligning code labels. Note: Don't try to assemble the instructions in the comments. 0L and 0w are not legal. */ static const uae_u8 f32_1[] = {0x90}; /* nop */ static const uae_u8 f32_2[] = {0x89,0xf6}; /* movl %esi,%esi */ static const uae_u8 f32_3[] = {0x8d,0x76,0x00}; /* leal 0(%esi),%esi */ static const uae_u8 f32_4[] = {0x8d,0x74,0x26,0x00}; /* leal 0(%esi,1),%esi */ static const uae_u8 f32_5[] = {0x90, /* nop */ 0x8d,0x74,0x26,0x00}; /* leal 0(%esi,1),%esi */ static const uae_u8 f32_6[] = {0x8d,0xb6,0x00,0x00,0x00,0x00}; /* leal 0L(%esi),%esi */ static const uae_u8 f32_7[] = {0x8d,0xb4,0x26,0x00,0x00,0x00,0x00}; /* leal 0L(%esi,1),%esi */ static const uae_u8 f32_8[] = {0x90, /* nop */ 0x8d,0xb4,0x26,0x00,0x00,0x00,0x00}; /* leal 0L(%esi,1),%esi */ static const uae_u8 f32_9[] = {0x89,0xf6, /* movl %esi,%esi */ 0x8d,0xbc,0x27,0x00,0x00,0x00,0x00}; /* leal 0L(%edi,1),%edi */ static const uae_u8 f32_10[] = {0x8d,0x76,0x00, /* leal 0(%esi),%esi */ 0x8d,0xbc,0x27,0x00,0x00,0x00,0x00}; /* leal 0L(%edi,1),%edi */ static const uae_u8 f32_11[] = {0x8d,0x74,0x26,0x00, /* leal 0(%esi,1),%esi */ 0x8d,0xbc,0x27,0x00,0x00,0x00,0x00}; /* leal 0L(%edi,1),%edi */ static const uae_u8 f32_12[] = {0x8d,0xb6,0x00,0x00,0x00,0x00, /* leal 0L(%esi),%esi */ 0x8d,0xbf,0x00,0x00,0x00,0x00}; /* leal 0L(%edi),%edi */ static const uae_u8 f32_13[] = {0x8d,0xb6,0x00,0x00,0x00,0x00, /* leal 0L(%esi),%esi */ 0x8d,0xbc,0x27,0x00,0x00,0x00,0x00}; /* leal 0L(%edi,1),%edi */ static const uae_u8 f32_14[] = {0x8d,0xb4,0x26,0x00,0x00,0x00,0x00, /* leal 0L(%esi,1),%esi */ 0x8d,0xbc,0x27,0x00,0x00,0x00,0x00}; /* leal 0L(%edi,1),%edi */ static const uae_u8 f32_15[] = {0xeb,0x0d,0x90,0x90,0x90,0x90,0x90, /* jmp .+15; lotsa nops */ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90}; static const uae_u8 f32_16[] = {0xeb,0x0d,0x90,0x90,0x90,0x90,0x90, /* jmp .+15; lotsa nops */ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90}; static const uae_u8 *const f32_patt[] = { f32_1, f32_2, f32_3, f32_4, f32_5, f32_6, f32_7, f32_8, f32_9, f32_10, f32_11, f32_12, f32_13, f32_14, f32_15 }; int nloops = nbytes / 16; while (nloops-- > 0) emit_block(f32_16, sizeof(f32_16)); nbytes %= 16; if (nbytes) emit_block(f32_patt[nbytes - 1], nbytes); #endif } /************************************************************************* * Flag handling, to and fro UAE flag register * *************************************************************************/ static inline void raw_flags_evicted(int r) { //live.state[FLAGTMP].status=CLEAN; live.state[FLAGTMP].status=INMEM; live.state[FLAGTMP].realreg=-1; /* We just "evicted" FLAGTMP. */ if (live.nat[r].nholds!=1) { /* Huh? */ abort(); } live.nat[r].nholds=0; } #define FLAG_NREG1_FLAGREG EAX_INDEX /* Set to -1 if any register will do */ static inline void raw_flags_to_reg_FLAGREG(int r) { raw_lahf(0); /* Most flags in AH */ //raw_setcc(r,0); /* V flag in AL */ raw_setcc_m(JITPTR live.state[FLAGTMP].mem,0); #if 1 /* Let's avoid those nasty partial register stalls */ //raw_mov_b_mr(JITPTR live.state[FLAGTMP].mem,r); raw_mov_b_mr((JITPTR live.state[FLAGTMP].mem)+1,AH_INDEX); raw_flags_evicted(r); #endif } #define FLAG_NREG2_FLAGREG EAX_INDEX /* Set to -1 if any register will do */ static inline void raw_reg_to_flags_FLAGREG(int r) { raw_cmp_b_ri(r,-127); /* set V */ raw_sahf(0); } #define FLAG_NREG3_FLAGREG EAX_INDEX /* Set to -1 if any register will do */ static __inline__ void raw_flags_set_zero_FLAGREG(int s, int tmp) { raw_mov_l_rr(tmp,s); raw_lahf(s); /* flags into ah */ SETOr(X86_AL); /* V flag into al */ raw_and_l_ri(s,0xffffbfff); raw_and_l_ri(tmp,0x00004000); raw_xor_l_ri(tmp,0x00004000); raw_or_l(s,tmp); raw_cmp_b_ri(X86_AL,-127); /* set V */ raw_sahf(s); } static inline void raw_flags_init_FLAGREG(void) { } #define FLAG_NREG1_FLAGSTK -1 /* Set to -1 if any register will do */ static inline void raw_flags_to_reg_FLAGSTK(int r) { raw_pushfl(); raw_pop_l_r(r); raw_mov_l_mr(JITPTR live.state[FLAGTMP].mem,r); raw_flags_evicted(r); } #define FLAG_NREG2_FLAGSTK -1 /* Set to -1 if any register will do */ static inline void raw_reg_to_flags_FLAGSTK(int r) { raw_push_l_r(r); raw_popfl(); } #define FLAG_NREG3_FLAGSTK -1 /* Set to -1 if any register will do */ static inline void raw_flags_set_zero_FLAGSTK(int s, int tmp) { raw_mov_l_rr(tmp,s); raw_pushfl(); raw_pop_l_r(s); raw_and_l_ri(s,0xffffffbf); raw_and_l_ri(tmp,0x00000040); raw_xor_l_ri(tmp,0x00000040); raw_or_l(s,tmp); raw_push_l_r(s); raw_popfl(); } static inline void raw_flags_init_FLAGSTK(void) { } #if defined(CPU_x86_64) #ifndef SAHF_SETO_PROFITABLE #error x64 JIT requires SAHF_SETO_PROFITABLE #endif /* Try to use the LAHF/SETO method on x86_64 since it is faster. This can't be the default because some older CPUs don't support LAHF/SAHF in long mode. */ static int FLAG_NREG1_FLAGGEN = EAX_INDEX; static inline void raw_flags_to_reg_FLAGGEN(int r) { if (have_lahf_lm) { // NOTE: the interpreter uses the normal EFLAGS layout // pushf/popf CF(0) ZF( 6) SF( 7) OF(11) // sahf/lahf CF(8) ZF(14) SF(15) OF( 0) assert(r == 0); raw_setcc(r,0); /* V flag in AL */ raw_lea_l_r_scaled(0,0,8); /* move it to its EFLAGS location */ raw_mov_b_mr((JITPTR live.state[FLAGTMP].mem)+1,0); raw_lahf(0); /* most flags in AH */ raw_mov_b_mr(JITPTR live.state[FLAGTMP].mem,AH_INDEX); raw_flags_evicted(r); } else raw_flags_to_reg_FLAGSTK(r); } static int FLAG_NREG2_FLAGGEN = EAX_INDEX; static inline void raw_reg_to_flags_FLAGGEN(int r) { if (have_lahf_lm) { raw_xchg_b_rr(0,AH_INDEX); raw_cmp_b_ri(r,-120); /* set V */ raw_sahf(0); } else raw_reg_to_flags_FLAGSTK(r); } static int FLAG_NREG3_FLAGGEN = EAX_INDEX; static inline void raw_flags_set_zero_FLAGGEN(int s, int tmp) { if (have_lahf_lm) raw_flags_set_zero_FLAGREG(s, tmp); else raw_flags_set_zero_FLAGSTK(s, tmp); } static inline void raw_flags_init_FLAGGEN(void) { if (have_lahf_lm) { FLAG_NREG1_FLAGGEN = FLAG_NREG1_FLAGREG; FLAG_NREG2_FLAGGEN = FLAG_NREG2_FLAGREG; FLAG_NREG3_FLAGGEN = FLAG_NREG3_FLAGREG; } else { FLAG_NREG1_FLAGGEN = FLAG_NREG1_FLAGSTK; FLAG_NREG2_FLAGGEN = FLAG_NREG2_FLAGSTK; FLAG_NREG3_FLAGGEN = FLAG_NREG3_FLAGSTK; } } #endif #ifdef SAHF_SETO_PROFITABLE #define FLAG_SUFFIX FLAGREG #elif defined CPU_x86_64 #define FLAG_SUFFIX FLAGGEN #else #define FLAG_SUFFIX FLAGSTK #endif #define FLAG_GLUE_2(x, y) x ## _ ## y #define FLAG_GLUE_1(x, y) FLAG_GLUE_2(x, y) #define FLAG_GLUE(x) FLAG_GLUE_1(x, FLAG_SUFFIX) #define raw_flags_init FLAG_GLUE(raw_flags_init) #define FLAG_NREG1 FLAG_GLUE(FLAG_NREG1) #define raw_flags_to_reg FLAG_GLUE(raw_flags_to_reg) #define FLAG_NREG2 FLAG_GLUE(FLAG_NREG2) #define raw_reg_to_flags FLAG_GLUE(raw_reg_to_flags) #define FLAG_NREG3 FLAG_GLUE(FLAG_NREG3) #define raw_flags_set_zero FLAG_GLUE(raw_flags_set_zero) /* Apparently, there are enough instructions between flag store and flag reload to avoid the partial memory stall */ static inline void raw_load_flagreg(uae_u32 target) { /* attention: in 64bit mode, relies on LITTE_ENDIANESS of regflags.cznv */ raw_mov_l_rm(target,JITPTR live.state[FLAGTMP].mem); } static inline void raw_load_flagx(uae_u32 target) { #if FLAGBIT_X < 8 if (live.nat[target].canbyte) raw_mov_b_rm(target,JITPTR live.state[FLAGX].mem); else #endif if (live.nat[target].canword) raw_mov_w_rm(target,JITPTR live.state[FLAGX].mem); else raw_mov_l_rm(target,JITPTR live.state[FLAGX].mem); } static inline void raw_dec_sp(int off) { if (off) { #ifdef CPU_x86_64 emit_byte(0x48); /* REX prefix */ #endif raw_sub_l_ri(ESP_INDEX,off); } } static inline void raw_inc_sp(int off) { if (off) { #ifdef CPU_x86_64 emit_byte(0x48); /* REX prefix */ #endif raw_add_l_ri(ESP_INDEX,off); } } static inline void raw_push_regs_to_preserve(void) { for (int i=N_REGS;i--;) { if (need_to_preserve[i]) raw_push_l_r(i); } } static inline void raw_pop_preserved_regs(void) { for (int i=0;ix86_vendor_id; if (!strcmp(v, "GenuineIntel")) c->x86_vendor = X86_VENDOR_INTEL; else if (!strcmp(v, "AuthenticAMD")) c->x86_vendor = X86_VENDOR_AMD; else if (!strcmp(v, "CyrixInstead")) c->x86_vendor = X86_VENDOR_CYRIX; else if (!strcmp(v, "Geode by NSC")) c->x86_vendor = X86_VENDOR_NSC; else if (!strcmp(v, "UMC UMC UMC ")) c->x86_vendor = X86_VENDOR_UMC; else if (!strcmp(v, "CentaurHauls")) c->x86_vendor = X86_VENDOR_CENTAUR; else if (!strcmp(v, "NexGenDriven")) c->x86_vendor = X86_VENDOR_NEXGEN; else if (!strcmp(v, "RiseRiseRise")) c->x86_vendor = X86_VENDOR_RISE; else if (!strcmp(v, "GenuineTMx86") || !strcmp(v, "TransmetaCPU")) c->x86_vendor = X86_VENDOR_TRANSMETA; else c->x86_vendor = X86_VENDOR_UNKNOWN; } /* * Generic CPUID function * clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx * resulting in stale register contents being returned. */ /* Some CPUID calls want 'count' to be placed in ecx */ #ifdef __GNUC__ static void cpuid_count(uae_u32 op, uae_u32 count, uae_u32 *eax, uae_u32 *ebx, uae_u32 *ecx, uae_u32 *edx) { uae_u32 _eax, _ebx, _ecx, _edx; _eax = op; _ecx = count; __asm__ __volatile__( " movl %0,%%eax \n" " movl %2,%%ecx \n" " cpuid \n" " movl %%eax,%0 \n" " movl %%ebx,%1 \n" " movl %%ecx,%2 \n" " movl %%edx,%3 \n" : "+m" (_eax), "=m" (_ebx), "+m" (_ecx), "=m" (_edx) : : "eax", "ebx", "ecx", "edx"); *eax = _eax; *ebx = _ebx; *ecx = _ecx; *edx = _edx; } #endif #ifdef _MSC_VER #include static void cpuid_count(uae_u32 op, uae_u32 count, uae_u32 *eax, uae_u32 *ebx, uae_u32 *ecx, uae_u32 *edx) { int cpuinfo[4]; cpuinfo[0] = op; cpuinfo[1] = 0; cpuinfo[2] = count; cpuinfo[3] = 0; __cpuidex(cpuinfo, op, count); *eax = cpuinfo[0]; *ebx = cpuinfo[1]; *ecx = cpuinfo[2]; *edx = cpuinfo[3]; } #endif static void cpuid(uae_u32 op, uae_u32 *eax, uae_u32 *ebx, uae_u32 *ecx, uae_u32 *edx) { cpuid_count(op, 0, eax, ebx, ecx, edx); } static void raw_init_cpu(void) { struct cpuinfo_x86 *c = &cpuinfo; uae_u32 dummy; /* Defaults */ c->x86_processor = X86_PROCESSOR_max; c->x86_vendor = X86_VENDOR_UNKNOWN; c->cpuid_level = -1; /* CPUID not detected */ c->x86_model = c->x86_mask = 0; /* So far unknown... */ c->x86_vendor_id[0] = '\0'; /* Unset */ c->x86_hwcap = 0; #ifdef CPU_x86_64 c->x86_clflush_size = 64; #else c->x86_clflush_size = 32; #endif /* Get vendor name */ c->x86_vendor_id[12] = '\0'; cpuid(0x00000000, (uae_u32 *)&c->cpuid_level, (uae_u32 *)&c->x86_vendor_id[0], (uae_u32 *)&c->x86_vendor_id[8], (uae_u32 *)&c->x86_vendor_id[4]); x86_get_cpu_vendor(c); /* Intel-defined flags: level 0x00000001 */ c->x86_brand_id = 0; if ( c->cpuid_level >= 0x00000001 ) { uae_u32 tfms, brand_id; cpuid(0x00000001, &tfms, &brand_id, &dummy, &c->x86_hwcap); c->x86 = (tfms >> 8) & 15; if (c->x86 == 0xf) c->x86 += (tfms >> 20) & 0xff; /* extended family */ c->x86_model = (tfms >> 4) & 15; if (c->x86_model == 0xf) c->x86_model |= (tfms >> 12) & 0xf0; /* extended model */ c->x86_brand_id = brand_id & 0xff; c->x86_mask = tfms & 15; if (c->x86_hwcap & (1 << 19)) { c->x86_clflush_size = ((brand_id >> 8) & 0xff) * 8; } } else { /* Have CPUID level 0 only - unheard of */ c->x86 = 4; } /* AMD-defined flags: level 0x80000001 */ uae_u32 xlvl; cpuid(0x80000000, &xlvl, &dummy, &dummy, &dummy); if ( (xlvl & 0xffff0000) == 0x80000000 ) { if ( xlvl >= 0x80000001 ) { uae_u32 features, extra_features; cpuid(0x80000001, &dummy, &dummy, &extra_features, &features); if (features & (1 << 29)) { /* Assume x86-64 if long mode is supported */ c->x86_processor = X86_PROCESSOR_X86_64; } if (extra_features & (1 << 0)) have_lahf_lm = true; } } /* Canonicalize processor ID */ switch (c->x86) { case 3: c->x86_processor = X86_PROCESSOR_I386; break; case 4: c->x86_processor = X86_PROCESSOR_I486; break; case 5: if (c->x86_vendor == X86_VENDOR_AMD) c->x86_processor = X86_PROCESSOR_K6; else c->x86_processor = X86_PROCESSOR_PENTIUM; break; case 6: if (c->x86_vendor == X86_VENDOR_AMD) c->x86_processor = X86_PROCESSOR_ATHLON; else c->x86_processor = X86_PROCESSOR_PENTIUMPRO; break; case 15: if (c->x86_processor == X86_PROCESSOR_max) { switch (c->x86_vendor) { case X86_VENDOR_INTEL: c->x86_processor = X86_PROCESSOR_PENTIUM4; break; case X86_VENDOR_AMD: /* Assume a 32-bit Athlon processor if not in long mode */ c->x86_processor = X86_PROCESSOR_ATHLON; break; } } break; } if (c->x86_processor == X86_PROCESSOR_max) { c->x86_processor = X86_PROCESSOR_I386; jit_log("Error: unknown processor type"); jit_log(" Family : %d", c->x86); jit_log(" Model : %d", c->x86_model); jit_log(" Mask : %d", c->x86_mask); jit_log(" Vendor : %s [%d]", c->x86_vendor_id, c->x86_vendor); if (c->x86_brand_id) { jit_log(" BrandID : %02x", c->x86_brand_id); } } /* Have CMOV support? */ have_cmov = (c->x86_hwcap & (1 << 15)) != 0; #if defined(CPU_x86_64) if (!have_cmov) { jit_abort("x86-64 implementations are bound to have CMOV!"); } #endif c->x86_has_xmm2 = (c->x86_hwcap & (1 << 26)) != 0; /* Can the host CPU suffer from partial register stalls? */ // non-RAT_STALL mode is currently broken have_rat_stall = true; //(c->x86_vendor == X86_VENDOR_INTEL); #if 0 /* It appears that partial register writes are a bad idea even on AMD K7 cores, even though they are not supposed to have the dreaded rat stall. Why? Anyway, that's why we lie about it ;-) */ if (c->x86_processor == X86_PROCESSOR_ATHLON) have_rat_stall = true; #endif /* Alignments */ if (tune_alignment) { align_loops = x86_alignments[c->x86_processor].align_loop; align_jumps = x86_alignments[c->x86_processor].align_jump; } jit_log(" : Max CPUID level=%d Processor is %s [%s]", c->cpuid_level, c->x86_vendor_id, x86_processor_string_table[c->x86_processor]); raw_flags_init(); } #ifndef UAE static void __attribute_noinline__ prevent_redzone_use(void) {} static bool target_check_bsf(void) { bool mismatch = false; for (int g_ZF = 0; g_ZF <= 1; g_ZF++) { for (int g_CF = 0; g_CF <= 1; g_CF++) { for (int g_OF = 0; g_OF <= 1; g_OF++) { for (int g_SF = 0; g_SF <= 1; g_SF++) { for (int value = -1; value <= 1; value++) { uintptr flags = (g_SF << 7) | (g_OF << 11) | (g_ZF << 6) | g_CF; intptr tmp = value; prevent_redzone_use(); __asm__ __volatile__ ("push %0; popf; bsf %1,%1; pushf; pop %0" : "+r" (flags), "+r" (tmp) : : "cc"); int OF = (flags >> 11) & 1; int SF = (flags >> 7) & 1; int ZF = (flags >> 6) & 1; int CF = flags & 1; tmp = (value == 0); if (ZF != tmp || SF != g_SF || OF != g_OF || CF != g_CF) mismatch = true; } } } } } if (mismatch) { jit_log(" : Target CPU defines all flags on BSF instruction"); } return !mismatch; } #endif /************************************************************************* * FPU stuff * *************************************************************************/ static inline void raw_fp_init(void) { int i; for (i=0;i1) { emit_byte(0x9b); emit_byte(0xdb); emit_byte(0xe3); live.tos=-1; } #endif while (live.tos>=1) { emit_byte(0xde); emit_byte(0xd9); live.tos-=2; } while (live.tos>=0) { emit_byte(0xdd); emit_byte(0xd8); live.tos--; } raw_fp_init(); } static inline void make_tos(int r) { int p,q; if (live.spos[r]<0) { /* Register not yet on stack */ emit_byte(0xd9); emit_byte(0xe8); /* Push '1' on the stack, just to grow it */ live.tos++; live.spos[r]=live.tos; live.onstack[live.tos]=r; return; } /* Register is on stack */ if (live.tos==live.spos[r]) return; p=live.spos[r]; q=live.onstack[live.tos]; emit_byte(0xd9); emit_byte(0xc8+live.tos-live.spos[r]); /* exchange it with top of stack */ live.onstack[live.tos]=r; live.spos[r]=live.tos; live.onstack[p]=q; live.spos[q]=p; } static inline void make_tos2(int r, int r2) { int q; make_tos(r2); /* Put the reg that's supposed to end up in position2 on top */ if (live.spos[r]<0) { /* Register not yet on stack */ make_tos(r); /* This will extend the stack */ return; } /* Register is on stack */ emit_byte(0xd9); emit_byte(0xc9); /* Move r2 into position 2 */ q=live.onstack[live.tos-1]; live.onstack[live.tos]=q; live.spos[q]=live.tos; live.onstack[live.tos-1]=r2; live.spos[r2]=live.tos-1; make_tos(r); /* And r into 1 */ } static inline int stackpos(int r) { if (live.spos[r]<0) abort(); if (live.tos=0) { /* source is on top of stack, and we already have the dest */ int dd=stackpos(d); emit_byte(0xdd); emit_byte(0xd0+dd); } else { emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source on tos */ tos_make(d); /* store to destination, pop if necessary */ } } LOWFUNC(NONE,READ,2,raw_fldcw_m_indexed,(R4 index, IMM base)) { x86_64_prefix(true, false, NULL, NULL, &index); emit_byte(0xd9); emit_byte(0xa8 + index); emit_long(base); } LOWFUNC(NONE,NONE,2,raw_fsqrt_rr,(FW d, FR s)) { int ds; if (d!=s) { usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xfa); /* take square root */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xfa); /* take square root */ } } LOWFUNC(NONE,NONE,2,raw_fabs_rr,(FW d, FR s)) { int ds; if (d!=s) { usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xe1); /* take fabs */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xe1); /* take fabs */ } } LOWFUNC(NONE,NONE,2,raw_frndint_rr,(FW d, FR s)) { int ds; if (d!=s) { usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xfc); /* take frndint */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xfc); /* take frndint */ } } LOWFUNC(NONE,NONE,2,raw_fcos_rr,(FW d, FR s)) { int ds; if (d!=s) { usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xff); /* take cos */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xff); /* take cos */ } } LOWFUNC(NONE,NONE,2,raw_fsin_rr,(FW d, FR s)) { int ds; if (d!=s) { usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xfe); /* fsin sin(x) */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xfe); /* fsin y=sin(x) */ } } static const double one = 1; LOWFUNC(NONE,NONE,2,raw_ftwotox_rr,(FW d, FR s)) { int ds; usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xc0); /* duplicate top of stack. Now up to 8 high */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(x) */ emit_byte(0xd9); emit_byte(0xc9); /* swap top two elements */ emit_byte(0xd8); emit_byte(0xe1); /* fsub frac(x) = x - int(x) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); /* Add '1' without using extra stack space */ emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x) */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy & pop */ tos_make(d); /* store y=2^x */ } LOWFUNC(NONE,NONE,2,raw_fetox_rr,(FW d, FR s)) { int ds; usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xea); /* fldl2e log2(e) */ emit_byte(0xde); emit_byte(0xc9); /* fmulp --- multiply source by log2(e) */ emit_byte(0xd9); emit_byte(0xc0); /* duplicate top of stack. Now up to 8 high */ emit_byte(0xd9); emit_byte(0xfc); /* rndint */ emit_byte(0xd9); emit_byte(0xc9); /* swap top two elements */ emit_byte(0xd8); emit_byte(0xe1); /* subtract rounded from original */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 */ x86_fadd_m(JITPTR &one); /* Add '1' without using extra stack space */ emit_byte(0xd9); emit_byte(0xfd); /* and scale it */ emit_byte(0xdd); emit_byte(0xd9); /* take he rounded value off */ tos_make(d); /* store to destination */ } LOWFUNC(NONE,NONE,2,raw_flog2_rr,(FW d, FR s)) { int ds; usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xe8); /* push '1' */ emit_byte(0xd9); emit_byte(0xc9); /* swap top two */ emit_byte(0xd9); emit_byte(0xf1); /* take 1*log2(x) */ tos_make(d); /* store to destination */ } LOWFUNC(NONE,NONE,2,raw_fneg_rr,(FW d, FR s)) { int ds; if (d!=s) { usereg(s); ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* duplicate source */ emit_byte(0xd9); emit_byte(0xe0); /* take fchs */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xe0); /* take fchs */ } } LOWFUNC(NONE,NONE,2,raw_fadd_rr,(FRW d, FR s)) { int ds; usereg(s); usereg(d); if (live.spos[s]==live.tos) { /* Source is on top of stack */ ds=stackpos(d); emit_byte(0xdc); emit_byte(0xc0+ds); /* add source to dest*/ } else { make_tos(d); ds=stackpos(s); emit_byte(0xd8); emit_byte(0xc0+ds); /* add source to dest*/ } } LOWFUNC(NONE,NONE,2,raw_fsub_rr,(FRW d, FR s)) { int ds; usereg(s); usereg(d); if (live.spos[s]==live.tos) { /* Source is on top of stack */ ds=stackpos(d); emit_byte(0xdc); emit_byte(0xe8+ds); /* sub source from dest*/ } else { make_tos(d); ds=stackpos(s); emit_byte(0xd8); emit_byte(0xe0+ds); /* sub src from dest */ } } LOWFUNC(NONE,NONE,2,raw_fcmp_rr,(FR d, FR s)) { int ds; usereg(s); usereg(d); make_tos(d); ds=stackpos(s); emit_byte(0xdd); emit_byte(0xe0+ds); /* cmp dest with source*/ } LOWFUNC(NONE,NONE,2,raw_fmul_rr,(FRW d, FR s)) { int ds; usereg(s); usereg(d); if (live.spos[s]==live.tos) { /* Source is on top of stack */ ds=stackpos(d); emit_byte(0xdc); emit_byte(0xc8+ds); /* mul dest by source*/ } else { make_tos(d); ds=stackpos(s); emit_byte(0xd8); emit_byte(0xc8+ds); /* mul dest by source*/ } } LOWFUNC(NONE,NONE,2,raw_fdiv_rr,(FRW d, FR s)) { int ds; usereg(s); usereg(d); if (live.spos[s]==live.tos) { /* Source is on top of stack */ ds=stackpos(d); emit_byte(0xdc); emit_byte(0xf8+ds); /* div dest by source */ } else { make_tos(d); ds=stackpos(s); emit_byte(0xd8); emit_byte(0xf0+ds); /* div dest by source*/ } } LOWFUNC(NONE,NONE,2,raw_frem_rr,(FRW d, FR s)) { int ds; usereg(s); usereg(d); make_tos2(d,s); ds=stackpos(s); if (ds!=1) { jit_abort("Failed horribly in raw_frem_rr! ds is %d",ds); } emit_byte(0xd9); emit_byte(0xf8); /* take rem from dest by source */ } LOWFUNC(NONE,NONE,2,raw_frem1_rr,(FRW d, FR s)) { int ds; usereg(s); usereg(d); make_tos2(d,s); ds=stackpos(s); if (ds!=1) { jit_abort("Failed horribly in raw_frem1_rr! ds is %d",ds); } emit_byte(0xd9); emit_byte(0xf5); /* take rem1 from dest by source */ } LOWFUNC(NONE,NONE,1,raw_ftst_r,(FR r)) { make_tos(r); emit_byte(0xd9); /* ftst */ emit_byte(0xe4); } LOWFUNC(NONE,NONE,2,raw_fetoxM1_rr,(FW d, FR s)) { int ds; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xea); /* fldl2e log2(e) */ emit_byte(0xd8); emit_byte(0xc9); /* fmul x*log2(e) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy up */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap top two elements */ emit_byte(0xd8); emit_byte(0xe1); /* fsub x*log2(e) - int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ emit_byte(0xd9); emit_byte(0xfd); /* fscale ((2^frac(x))-1)*2^int(x*log2(e)) */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy & pop */ if (s!=d) tos_make(d); /* store y=(e^x)-1 */ } LOWFUNC(NONE,NONE,2,raw_ftentox_rr,(FW d, FR s)) { int ds; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xe9); /* fldl2t log2(10) */ emit_byte(0xd8); emit_byte(0xc9); /* fmul x*log2(10) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy up */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(x*log2(10)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap top two elements */ emit_byte(0xd8); emit_byte(0xe1); /* fsub x*log2(10) - int(x*log2(10)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(10)) */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy & pop */ if (s!=d) tos_make(d); /* store y=10^x */ } LOWFUNC(NONE,NONE,3,raw_fsincos_rr,(FW d, FW c, FR s)) { int ds; if (s==d) { //write_log (_T("FSINCOS src = dest\n")); make_tos(s); emit_byte(0xd9); emit_byte(0xfb); /* fsincos sin(x) push cos(x) */ tos_make(c); /* store cos(x) to c */ return; } ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xfb); /* fsincos sin(x) push cos(x) */ if (live.spos[c]<0) { if (live.spos[d]<0) { /* occupy both regs directly */ live.tos++; live.spos[d]=live.tos; live.onstack[live.tos]=d; /* sin(x) comes first */ live.tos++; live.spos[c]=live.tos; live.onstack[live.tos]=c; } else { emit_byte(0xd9); emit_byte(0xc9); /* fxch swap cos(x) with sin(x) */ emit_byte(0xdd); /* store sin(x) to d & pop */ emit_byte(0xd8+(live.tos+2)-live.spos[d]); live.tos++; /* occupy a reg for cos(x) here */ live.spos[c]=live.tos; live.onstack[live.tos]=c; } } else { emit_byte(0xdd); /* store cos(x) to c & pop */ emit_byte(0xd8+(live.tos+2)-live.spos[c]); tos_make(d); /* store sin(x) to destination */ } } LOWFUNC(NONE,NONE,2,raw_fscale_rr,(FRW d, FR s)) { int ds; if (live.spos[d]==live.tos && live.spos[s]==live.tos-1) { //write_log (_T("fscale found x in TOS-1 and y in TOS\n")); emit_byte(0xd9); emit_byte(0xfd); /* fscale y*(2^x) */ } else { make_tos(s); /* tos=x */ ds=stackpos(d); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld y */ emit_byte(0xd9); emit_byte(0xfd); /* fscale y*(2^x) */ tos_make(d); /* store y=y*(2^x) */ } } LOWFUNC(NONE,NONE,2,raw_ftan_rr,(FW d, FR s)) { int ds; if (d!=s) { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xf2); /* fptan tan(x)=y/1.0 */ emit_byte(0xdd); emit_byte(0xd8); /* fstp pop 1.0 */ tos_make(d); /* store to destination */ } else { make_tos(d); emit_byte(0xd9); emit_byte(0xf2); /* fptan tan(x)=y/1.0 */ emit_byte(0xdd); emit_byte(0xd8); /* fstp pop 1.0 */ } } #ifdef CPU_x86_64 #define REX64() emit_byte(0x48) #else #define REX64() #endif LOWFUNC(NONE,NONE,1,raw_fcuts_r,(FRW r)) { make_tos(r); /* TOS = r */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0xfc); /* add -4 to esp */ emit_byte(0xd9); emit_byte(0x1c); emit_byte(0x24); /* fstp store r as SINGLE to [esp] and pop */ emit_byte(0xd9); emit_byte(0x04); emit_byte(0x24); /* fld load r as SINGLE from [esp] */ emit_byte(0x9b); /* let the CPU wait on FPU exceptions */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0x04); /* add +4 to esp */ } LOWFUNC(NONE,NONE,1,raw_fcut_r,(FRW r)) { make_tos(r); /* TOS = r */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0xf8); /* add -8 to esp */ emit_byte(0xdd); emit_byte(0x1c); emit_byte(0x24); /* fstp store r as DOUBLE to [esp] and pop */ emit_byte(0xdd); emit_byte(0x04); emit_byte(0x24); /* fld load r as DOUBLE from [esp] */ emit_byte(0x9b); /* let the CPU wait on FPU exceptions */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0x08); /* add +8 to esp */ } LOWFUNC(NONE,NONE,2,raw_fgetexp_rr,(FW d, FR s)) { int ds; if (d!=s) { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xf4); /* fxtract exp push man */ emit_byte(0xdd); emit_byte(0xd8); /* fstp just pop man */ tos_make(d); /* store exp to destination */ } else { make_tos(d); /* tos=x=y */ emit_byte(0xd9); emit_byte(0xf4); /* fxtract exp push man */ emit_byte(0xdd); emit_byte(0xd8); /* fstp just pop man */ } } LOWFUNC(NONE,NONE,2,raw_fgetman_rr,(FW d, FR s)) { int ds; if (d!=s) { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xf4); /* fxtract exp push man */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy man up & pop */ tos_make(d); /* store man to destination */ } else { make_tos(d); /* tos=x=y */ emit_byte(0xd9); emit_byte(0xf4); /* fxtract exp push man */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy man up & pop */ } } LOWFUNC(NONE,NONE,2,raw_flogN_rr,(FW d, FR s)) { int ds; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xed); /* fldln2 logN(2) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap logN(2) with x */ emit_byte(0xd9); emit_byte(0xf1); /* fyl2x logN(2)*log2(x) */ if (s!=d) tos_make(d); /* store y=logN(x) */ } LOWFUNC(NONE,NONE,2,raw_flogNP1_rr,(FW d, FR s)) { int ds; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xed); /* fldln2 logN(2) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap logN(2) with x */ emit_byte(0xd9); emit_byte(0xf9); /* fyl2xp1 logN(2)*log2(x+1) */ if (s!=d) tos_make(d); /* store y=logN(x+1) */ } LOWFUNC(NONE,NONE,2,raw_flog10_rr,(FW d, FR s)) { int ds; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xec); /* fldlg2 log10(2) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap log10(2) with x */ emit_byte(0xd9); emit_byte(0xf1); /* fyl2x log10(2)*log2(x) */ if (s!=d) tos_make(d); /* store y=log10(x) */ } LOWFUNC(NONE,NONE,2,raw_fasin_rr,(FW d, FR s)) { int ds; ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd8); emit_byte(0xc8); /* fmul x*x */ emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xde); emit_byte(0xe1); /* fsubrp 1 - (x^2) */ emit_byte(0xd9); emit_byte(0xfa); /* fsqrt sqrt(1-(x^2)) */ emit_byte(0xd9); emit_byte(0xc1+ds); /* fld x again */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap x with sqrt(1-(x^2)) */ emit_byte(0xd9); emit_byte(0xf3); /* fpatan atan(x/sqrt(1-(x^2))) & pop */ tos_make(d); /* store y=asin(x) */ } static uae_u32 const pihalf[] = {0x2168c234, 0xc90fdaa2, 0x3fff}; // LSB=0 to get acos(1)=0 LOWFUNC(NONE,NONE,2,raw_facos_rr,(FW d, FR s)) { int ds; ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd8); emit_byte(0xc8); /* fmul x*x */ emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xde); emit_byte(0xe1); /* fsubrp 1 - (x^2) */ emit_byte(0xd9); emit_byte(0xfa); /* fsqrt sqrt(1-(x^2)) */ emit_byte(0xd9); emit_byte(0xc1+ds); /* fld x again */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap x with sqrt(1-(x^2)) */ emit_byte(0xd9); emit_byte(0xf3); /* fpatan atan(x/sqrt(1-(x^2))) & pop */ raw_fldt(JITPTR &pihalf); /* fld load pi/2 from pihalf */ emit_byte(0xde); emit_byte(0xe1); /* fsubrp pi/2 - asin(x) & pop */ tos_make(d); /* store y=acos(x) */ } LOWFUNC(NONE,NONE,2,raw_fatan_rr,(FW d, FR s)) { int ds; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xd9); emit_byte(0xf3); /* fpatan atan(x)/1 & pop*/ if (s!=d) tos_make(d); /* store y=atan(x) */ } LOWFUNC(NONE,NONE,2,raw_fatanh_rr,(FW d, FR s)) { int ds; ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xdc); emit_byte(0xc1); /* fadd 1 + x */ emit_byte(0xd8); emit_byte(0xe2+ds); /* fsub 1 - x */ emit_byte(0xde); emit_byte(0xf9); /* fdivp (1+x)/(1-x) */ emit_byte(0xd9); emit_byte(0xed); /* fldl2e logN(2) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap logN(2) with (1+x)/(1-x) */ emit_byte(0xd9); emit_byte(0xf1); /* fyl2x logN(2)*log2((1+x)/(1-x)) pop */ emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xd9); emit_byte(0xe0); /* fchs -1.0 */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd9); emit_byte(0xfd); /* fscale logN((1+x)/(1-x)) * 2^(-1) */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy & pop */ tos_make(d); /* store y=atanh(x) */ } LOWFUNC(NONE,NONE,2,raw_fsinh_rr,(FW d, FR s)) { int ds,tr; tr=live.onstack[live.tos+3]; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xea); /* fldl2e log2(e) */ emit_byte(0xd8); emit_byte(0xc9); /* fmul x*log2(e) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy x*log2(e) */ if (tr>=0) { emit_byte(0xd9); emit_byte(0xca); /* fxch swap with temp-reg */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0xf4); /* add -12 to esp */ emit_byte(0xdb); emit_byte(0x3c); emit_byte(0x24); /* fstp store temp-reg to [esp] & pop */ } emit_byte(0xd9); emit_byte(0xe0); /* fchs -x*log2(e) */ emit_byte(0xd9); emit_byte(0xc0); /* fld -x*log2(e) again */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(-x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd8); emit_byte(0xe1); /* fsub -x*log2(e) - int(-x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap e^-x with x*log2(e) in tr */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy x*log2(e) */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd8); emit_byte(0xe1); /* fsub x*log2(e) - int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(e)) */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy e^x & pop */ if (tr>=0) { emit_byte(0xdb); emit_byte(0x2c); emit_byte(0x24); /* fld load temp-reg from [esp] */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap temp-reg with e^-x in tr */ emit_byte(0xde); emit_byte(0xe9); /* fsubp (e^x)-(e^-x) */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0x0c); /* delayed add +12 to esp */ } else { emit_byte(0xde); emit_byte(0xe1); /* fsubrp (e^x)-(e^-x) */ } emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xd9); emit_byte(0xe0); /* fchs -1.0 */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd9); emit_byte(0xfd); /* fscale ((e^x)-(e^-x))/2 */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy & pop */ if (s!=d) tos_make(d); /* store y=sinh(x) */ } LOWFUNC(NONE,NONE,2,raw_fcosh_rr,(FW d, FR s)) { int ds,tr; tr=live.onstack[live.tos+3]; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xea); /* fldl2e log2(e) */ emit_byte(0xd8); emit_byte(0xc9); /* fmul x*log2(e) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy x*log2(e) */ if (tr>=0) { emit_byte(0xd9); emit_byte(0xca); /* fxch swap with temp-reg */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0xf4); /* add -12 to esp */ emit_byte(0xdb); emit_byte(0x3c); emit_byte(0x24); /* fstp store temp-reg to [esp] & pop */ } emit_byte(0xd9); emit_byte(0xe0); /* fchs -x*log2(e) */ emit_byte(0xd9); emit_byte(0xc0); /* fld -x*log2(e) again */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(-x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd8); emit_byte(0xe1); /* fsub -x*log2(e) - int(-x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap e^-x with x*log2(e) in tr */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy x*log2(e) */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd8); emit_byte(0xe1); /* fsub x*log2(e) - int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(e)) */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy e^x & pop */ if (tr>=0) { emit_byte(0xdb); emit_byte(0x2c); emit_byte(0x24); /* fld load temp-reg from [esp] */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap temp-reg with e^-x in tr */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0x0c); /* delayed add +12 to esp */ } emit_byte(0xde); emit_byte(0xc1); /* faddp (e^x)+(e^-x) */ emit_byte(0xd9); emit_byte(0xe8); /* fld 1.0 */ emit_byte(0xd9); emit_byte(0xe0); /* fchs -1.0 */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd9); emit_byte(0xfd); /* fscale ((e^x)+(e^-x))/2 */ emit_byte(0xdd); emit_byte(0xd9); /* fstp copy & pop */ if (s!=d) tos_make(d); /* store y=cosh(x) */ } LOWFUNC(NONE,NONE,2,raw_ftanh_rr,(FW d, FR s)) { int ds,tr; tr=live.onstack[live.tos+3]; if (s==d) make_tos(s); else { ds=stackpos(s); emit_byte(0xd9); emit_byte(0xc0+ds); /* fld x */ } emit_byte(0xd9); emit_byte(0xea); /* fldl2e log2(e) */ emit_byte(0xd8); emit_byte(0xc9); /* fmul x*log2(e) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy x*log2(e) */ if (tr>=0) { emit_byte(0xd9); emit_byte(0xca); /* fxch swap with temp-reg */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0xf4); /* add -12 to esp */ emit_byte(0xdb); emit_byte(0x3c); emit_byte(0x24); /* fstp store temp-reg to [esp] & pop */ } emit_byte(0xd9); emit_byte(0xe0); /* fchs -x*log2(e) */ emit_byte(0xd9); emit_byte(0xc0); /* fld -x*log2(e) again */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(-x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd8); emit_byte(0xe1); /* fsub -x*log2(e) - int(-x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap e^-x with x*log2(e) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy x*log2(e) */ emit_byte(0xd9); emit_byte(0xfc); /* frndint int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xc9); /* fxch swap */ emit_byte(0xd8); emit_byte(0xe1); /* fsub x*log2(e) - int(x*log2(e)) */ emit_byte(0xd9); emit_byte(0xf0); /* f2xm1 (2^frac(x))-1 */ x86_fadd_m(JITPTR &one); emit_byte(0xd9); emit_byte(0xfd); /* fscale (2^frac(x))*2^int(x*log2(e)) */ emit_byte(0xdd); emit_byte(0xd1); /* fst copy e^x */ emit_byte(0xd8); emit_byte(0xc2); /* fadd (e^x)+(e^-x) */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap with e^-x */ emit_byte(0xde); emit_byte(0xe9); /* fsubp (e^x)-(e^-x) */ if (tr>=0) { emit_byte(0xdb); emit_byte(0x2c); emit_byte(0x24); /* fld load temp-reg from [esp] */ emit_byte(0xd9); emit_byte(0xca); /* fxch swap temp-reg with e^-x in tr */ emit_byte(0xde); emit_byte(0xf9); /* fdivp ((e^x)-(e^-x))/((e^x)+(e^-x)) */ REX64(); emit_byte(0x83); emit_byte(0xc4); emit_byte(0x0c); /* delayed add +12 to esp */ } else { emit_byte(0xde); emit_byte(0xf1); /* fdivrp ((e^x)-(e^-x))/((e^x)+(e^-x)) */ } if (s!=d) tos_make(d); /* store y=tanh(x) */ } /* %eax register is clobbered if target processor doesn't support fucomi */ #define FFLAG_NREG_CLOBBER_CONDITION !have_cmov #define FFLAG_NREG EAX_INDEX static inline void raw_fflags_into_flags(int r) { int p; usereg(r); p=stackpos(r); emit_byte(0xd9); emit_byte(0xee); /* Push 0 */ emit_byte(0xd9); emit_byte(0xc9+p); /* swap top two around */ if (have_cmov) { // gb-- fucomi is for P6 cores only, not K6-2 then... emit_byte(0xdb); emit_byte(0xe9+p); /* fucomi them */ } else { emit_byte(0xdd); emit_byte(0xe1+p); /* fucom them */ emit_byte(0x9b); emit_byte(0xdf); emit_byte(0xe0); /* fstsw ax */ raw_sahf(0); /* sahf */ } emit_byte(0xdd); emit_byte(0xd9+p); /* store value back, and get rid of 0 */ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/codegen_x86.h000066400000000000000000002733621504763705000252040ustar00rootroot00000000000000/* * compiler/codegen_x86.h - IA-32 and AMD64 code generator * * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> IA-32 and AMD64 * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * This file is derived from CCG, copyright 1999-2003 Ian Piumarta * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #ifndef X86_RTASM_H #define X86_RTASM_H /* NOTES * * o Best viewed on a 1024x768 screen with fixed-6x10 font ;-) * * TODO * * o Fix FIXMEs * o i387 FPU instructions * o SSE instructions * o Optimize for cases where register numbers are not integral constants */ /* --- Configuration ------------------------------------------------------- */ /* Define to settle a "flat" register set, i.e. different regno for each size variant. */ #ifndef X86_FLAT_REGISTERS #define X86_FLAT_REGISTERS 1 #endif /* Define to generate x86-64 code. */ #ifndef X86_TARGET_64BIT #define X86_TARGET_64BIT 0 #endif /* Define to optimize ALU instructions. */ #ifndef X86_OPTIMIZE_ALU #define X86_OPTIMIZE_ALU 1 #endif /* Define to optimize rotate/shift instructions. */ #ifndef X86_OPTIMIZE_ROTSHI #define X86_OPTIMIZE_ROTSHI 1 #endif /* Define to optimize absolute addresses for RIP relative addressing. */ #ifndef X86_RIP_RELATIVE_ADDR #define X86_RIP_RELATIVE_ADDR 1 #endif /* --- Macros -------------------------------------------------------------- */ /* Functions used to emit code. * * x86_emit_byte(B) * x86_emit_word(W) * x86_emit_long(L) */ /* Get pointer to current code * * x86_get_target() */ /* Abort assembler, fatal failure. * * x86_emit_failure(MSG) */ #define x86_emit_failure0(MSG) (x86_emit_failure(MSG),0) /* --- Register set -------------------------------------------------------- */ enum { X86_RIP = -2, #if X86_FLAT_REGISTERS X86_NOREG = 0, X86_Reg8L_Base = 0x10, X86_Reg8H_Base = 0x20, X86_Reg16_Base = 0x30, X86_Reg32_Base = 0x40, X86_Reg64_Base = 0x50, X86_RegMMX_Base = 0x60, X86_RegXMM_Base = 0x70, #else X86_NOREG = -1, X86_Reg8L_Base = 0, X86_Reg8H_Base = 16, X86_Reg16_Base = 0, X86_Reg32_Base = 0, X86_Reg64_Base = 0, X86_RegMMX_Base = 0, X86_RegXMM_Base = 0, #endif }; enum { X86_AL = X86_Reg8L_Base, X86_CL, X86_DL, X86_BL, X86_SPL, X86_BPL, X86_SIL, X86_DIL, X86_R8B, X86_R9B, X86_R10B, X86_R11B, X86_R12B, X86_R13B, X86_R14B, X86_R15B, X86_AH = X86_Reg8H_Base + 4, X86_CH, X86_DH, X86_BH }; enum { X86_AX = X86_Reg16_Base, X86_CX, X86_DX, X86_BX, X86_SP, X86_BP, X86_SI, X86_DI, X86_R8W, X86_R9W, X86_R10W, X86_R11W, X86_R12W, X86_R13W, X86_R14W, X86_R15W }; enum { X86_EAX = X86_Reg32_Base, X86_ECX, X86_EDX, X86_EBX, X86_ESP, X86_EBP, X86_ESI, X86_EDI, X86_R8D, X86_R9D, X86_R10D, X86_R11D, X86_R12D, X86_R13D, X86_R14D, X86_R15D }; enum { X86_RAX = X86_Reg64_Base, X86_RCX, X86_RDX, X86_RBX, X86_RSP, X86_RBP, X86_RSI, X86_RDI, X86_R8, X86_R9, X86_R10, X86_R11, X86_R12, X86_R13, X86_R14, X86_R15 }; enum { X86_MM0 = X86_RegMMX_Base, X86_MM1, X86_MM2, X86_MM3, X86_MM4, X86_MM5, X86_MM6, X86_MM7, }; enum { X86_XMM0 = X86_RegXMM_Base, X86_XMM1, X86_XMM2, X86_XMM3, X86_XMM4, X86_XMM5, X86_XMM6, X86_XMM7, X86_XMM8, X86_XMM9, X86_XMM10, X86_XMM11, X86_XMM12, X86_XMM13, X86_XMM14, X86_XMM15 }; /* Register control and access * * _r0P(R) Null register? * _rIP(R) RIP register? * _rXP(R) Extended register? * * _rC(R) Class of register (only valid if X86_FLAT_REGISTERS) * _rR(R) Full register number * _rN(R) Short register number for encoding * * _r1(R) 8-bit register ID * _r2(R) 16-bit register ID * _r4(R) 32-bit register ID * _r8(R) 64-bit register ID * _rM(R) MMX register ID * _rX(R) XMM register ID * _rA(R) Address register ID used for EA calculation */ #define _r0P(R) ((int)(R) == (int)X86_NOREG) #define _rIP(R) ((int)(R) == (int)X86_RIP) #if X86_FLAT_REGISTERS #define _rC(R) ((R) & 0xf0) #define _rR(R) ((R) & 0x0f) #define _rN(R) ((R) & 0x07) #define _rXP(R) ((R) > 0 && _rR(R) > 7) #else #define _rN(R) ((R) & 0x07) #define _rR(R) (int(R)) #define _rXP(R) (_rR(R) > 7 && _rR(R) < 16) #endif #if !defined(_ASM_SAFETY) || ! X86_FLAT_REGISTERS #define _r1(R) _rN(R) #define _r2(R) _rN(R) #define _r4(R) _rN(R) #define _r8(R) _rN(R) #define _rA(R) _rN(R) #define _rM(R) _rN(R) #define _rX(R) _rN(R) #else #define _r1(R) ( ((_rC(R) & (X86_Reg8L_Base | X86_Reg8H_Base)) != 0) ? _rN(R) : x86_emit_failure0( "8-bit register required")) #define _r2(R) ( (_rC(R) == X86_Reg16_Base) ? _rN(R) : x86_emit_failure0("16-bit register required")) #define _r4(R) ( (_rC(R) == X86_Reg32_Base) ? _rN(R) : x86_emit_failure0("32-bit register required")) #define _r8(R) ( (_rC(R) == X86_Reg64_Base) ? _rN(R) : x86_emit_failure0("64-bit register required")) #define _rA(R) ( X86_TARGET_64BIT ? \ ( (_rC(R) == X86_Reg64_Base) ? _rN(R) : x86_emit_failure0("not a valid 64-bit base/index expression")) : \ ( (_rC(R) == X86_Reg32_Base) ? _rN(R) : x86_emit_failure0("not a valid 32-bit base/index expression")) ) #define _rM(R) ( (_rC(R) == X86_RegMMX_Base) ? _rN(R) : x86_emit_failure0("MMX register required")) #define _rX(R) ( (_rC(R) == X86_RegXMM_Base) ? _rN(R) : x86_emit_failure0("SSE register required")) #endif #define _rSP() (X86_TARGET_64BIT ? (int)X86_RSP : (int)X86_ESP) #define _r1e8lP(R) (int(R) >= X86_SPL && int(R) <= X86_DIL) #define _rbpP(R) (_rR(R) == _rR(X86_RBP)) #define _rspP(R) (_rR(R) == _rR(X86_RSP)) #define _rbp13P(R) (_rN(R) == _rN(X86_RBP)) #define _rsp12P(R) (_rN(R) == _rN(X86_RSP)) /* ========================================================================= */ /* --- UTILITY ------------------------------------------------------------- */ /* ========================================================================= */ typedef signed char _sc; typedef unsigned char _uc; typedef signed short _ss; typedef unsigned short _us; typedef signed int _sl; typedef unsigned int _ul; #define _UC(X) ((_uc )(uintptr_t)(X)) #define _US(X) ((_us )(uintptr_t)(X)) #define _SL(X) ((_sl )(uintptr_t)(X)) #define _UL(X) ((_ul )(uintptr_t)(X)) #define _PUC(X) ((_uc *)(X)) #define _PUS(X) ((_us *)(X)) #define _PSL(X) ((_sl *)(X)) #define _PUL(X) ((_ul *)(X)) #undef _B #undef _W #undef _L #undef _Q #define _B(B) x86_emit_byte((B)) #define _W(W) x86_emit_word((W)) #define _L(L) x86_emit_long((L)) #define _Q(Q) x86_emit_quad((Q)) #define _MASK(N) ((unsigned)((1<<(N)))-1) #define _siP(N,I) (!((((unsigned)(I))^(((unsigned)(I))<<1))&~_MASK(N))) #define _uiP(N,I) (!(((unsigned)(I))&~_MASK(N))) #define _suiP(N,I) (_siP(N,I) | _uiP(N,I)) #ifndef _ASM_SAFETY #define _ck_s(W,I) (_UL(I) & _MASK(W)) #define _ck_u(W,I) (_UL(I) & _MASK(W)) #define _ck_su(W,I) (_UL(I) & _MASK(W)) #define _ck_d(W,I) (_UL(I) & _MASK(W)) #else #define _ck_s(W,I) (_siP(W,I) ? (_UL(I) & _MASK(W)) : x86_emit_failure0( "signed integer `"#I"' too large for "#W"-bit field")) #define _ck_u(W,I) (_uiP(W,I) ? (_UL(I) & _MASK(W)) : x86_emit_failure0("unsigned integer `"#I"' too large for "#W"-bit field")) #define _ck_su(W,I) (_suiP(W,I) ? (_UL(I) & _MASK(W)) : x86_emit_failure0( "integer `"#I"' too large for "#W"-bit field")) #define _ck_d(W,I) (_siP(W,I) ? (_UL(I) & _MASK(W)) : x86_emit_failure0( "displacement `"#I"' too large for "#W"-bit field")) #endif #define _s0P(I) ((I)==0) #define _s8P(I) _siP(8,I) #define _s16P(I) _siP(16,I) #define _u8P(I) _uiP(8,I) #define _u16P(I) _uiP(16,I) #define _su8(I) _ck_su(8,I) #define _su16(I) _ck_su(16,I) #define _s1(I) _ck_s( 1,I) #define _s2(I) _ck_s( 2,I) #define _s3(I) _ck_s( 3,I) #define _s4(I) _ck_s( 4,I) #define _s5(I) _ck_s( 5,I) #define _s6(I) _ck_s( 6,I) #define _s7(I) _ck_s( 7,I) #define _s8(I) _ck_s( 8,I) #define _s9(I) _ck_s( 9,I) #define _s10(I) _ck_s(10,I) #define _s11(I) _ck_s(11,I) #define _s12(I) _ck_s(12,I) #define _s13(I) _ck_s(13,I) #define _s14(I) _ck_s(14,I) #define _s15(I) _ck_s(15,I) #define _s16(I) _ck_s(16,I) #define _s17(I) _ck_s(17,I) #define _s18(I) _ck_s(18,I) #define _s19(I) _ck_s(19,I) #define _s20(I) _ck_s(20,I) #define _s21(I) _ck_s(21,I) #define _s22(I) _ck_s(22,I) #define _s23(I) _ck_s(23,I) #define _s24(I) _ck_s(24,I) #define _s25(I) _ck_s(25,I) #define _s26(I) _ck_s(26,I) #define _s27(I) _ck_s(27,I) #define _s28(I) _ck_s(28,I) #define _s29(I) _ck_s(29,I) #define _s30(I) _ck_s(30,I) #define _s31(I) _ck_s(31,I) #define _u1(I) _ck_u( 1,I) #define _u2(I) _ck_u( 2,I) #define _u3(I) _ck_u( 3,I) #define _u4(I) _ck_u( 4,I) #define _u5(I) _ck_u( 5,I) #define _u6(I) _ck_u( 6,I) #define _u7(I) _ck_u( 7,I) #define _u8(I) _ck_u( 8,I) #define _u9(I) _ck_u( 9,I) #define _u10(I) _ck_u(10,I) #define _u11(I) _ck_u(11,I) #define _u12(I) _ck_u(12,I) #define _u13(I) _ck_u(13,I) #define _u14(I) _ck_u(14,I) #define _u15(I) _ck_u(15,I) #define _u16(I) _ck_u(16,I) #define _u17(I) _ck_u(17,I) #define _u18(I) _ck_u(18,I) #define _u19(I) _ck_u(19,I) #define _u20(I) _ck_u(20,I) #define _u21(I) _ck_u(21,I) #define _u22(I) _ck_u(22,I) #define _u23(I) _ck_u(23,I) #define _u24(I) _ck_u(24,I) #define _u25(I) _ck_u(25,I) #define _u26(I) _ck_u(26,I) #define _u27(I) _ck_u(27,I) #define _u28(I) _ck_u(28,I) #define _u29(I) _ck_u(29,I) #define _u30(I) _ck_u(30,I) #define _u31(I) _ck_u(31,I) /* ========================================================================= */ /* --- ASSEMBLER ----------------------------------------------------------- */ /* ========================================================================= */ #define _b00 0 #define _b01 1 #define _b10 2 #define _b11 3 #define _b000 0 #define _b001 1 #define _b010 2 #define _b011 3 #define _b100 4 #define _b101 5 #define _b110 6 #define _b111 7 #define _OFF4(D) (_UL(D) - _UL(x86_get_target())) #define _CKD8(D) _ck_d(8, ((_uc) _OFF4(D)) ) #define _D8(D) (_B(0), ((*(_PUC(x86_get_target())-1))= _CKD8(D))) #define _D32(D) (_L(0), ((*(_PUL(x86_get_target())-1))= _OFF4(D))) #ifndef _ASM_SAFETY # define _M(M) (M) # define _r(R) (R) # define _m(M) (M) # define _s(S) (S) # define _i(I) (I) # define _b(B) (B) #else # define _M(M) (((M)>3) ? x86_emit_failure0("internal error: mod = " #M) : (M)) # define _r(R) (((R)>7) ? x86_emit_failure0("internal error: reg = " #R) : (R)) # define _m(M) (((M)>7) ? x86_emit_failure0("internal error: r/m = " #M) : (M)) # define _s(S) (((S)>3) ? x86_emit_failure0("internal error: memory scale = " #S) : (S)) # define _i(I) (((I)>7) ? x86_emit_failure0("internal error: memory index = " #I) : (I)) # define _b(B) (((B)>7) ? x86_emit_failure0("internal error: memory base = " #B) : (B)) #endif #define _Mrm(Md,R,M) _B((_M(Md)<<6)|(_r(R)<<3)|_m(M)) #define _SIB(Sc,I, B) _B((_s(Sc)<<6)|(_i(I)<<3)|_b(B)) #define _SCL(S) ((((S)==1) ? _b00 : \ (((S)==2) ? _b01 : \ (((S)==4) ? _b10 : \ (((S)==8) ? _b11 : x86_emit_failure0("illegal scale: " #S)))))) /* --- Memory subformats - urgh! ------------------------------------------- */ /* _r_D() is RIP addressing mode if X86_TARGET_64BIT, use _r_DSIB() instead */ #define _r_D( R, D ) (_Mrm(_b00,_rN(R),_b101 ) ,_L((uae_u32)(D))) #define _r_DSIB(R, D ) (_Mrm(_b00,_rN(R),_b100 ),_SIB(_SCL(1),_b100 ,_b101 ),_L((uae_u32)(D))) #define _r_0B( R, B ) (_Mrm(_b00,_rN(R),_rA(B)) ) #define _r_0BIS(R, B,I,S) (_Mrm(_b00,_rN(R),_b100 ),_SIB(_SCL(S),_rA(I),_rA(B)) ) #define _r_1B( R, D,B ) (_Mrm(_b01,_rN(R),_rA(B)) ,_B((uae_u32)(D))) #define _r_1BIS(R, D,B,I,S) (_Mrm(_b01,_rN(R),_b100 ),_SIB(_SCL(S),_rA(I),_rA(B)),_B((uae_u32)(D))) #define _r_4B( R, D,B ) (_Mrm(_b10,_rN(R),_rA(B)) ,_L((uae_u32)(D))) #define _r_4IS( R, D,I,S) (_Mrm(_b00,_rN(R),_b100 ),_SIB(_SCL(S),_rA(I),_b101 ),_L((uae_u32)(D))) #define _r_4BIS(R, D,B,I,S) (_Mrm(_b10,_rN(R),_b100 ),_SIB(_SCL(S),_rA(I),_rA(B)),_L((uae_u32)(D))) #define _r_DB( R, D,B ) ((_s0P(D) && (!_rbp13P(B)) ? _r_0B (R, B ) : (_s8P(D) ? _r_1B( R,D,B ) : _r_4B( R,D,B )))) #define _r_DBIS(R, D,B,I,S) ((_s0P(D) && (!_rbp13P(B)) ? _r_0BIS(R, B,I,S) : (_s8P(D) ? _r_1BIS(R,D,B,I,S) : _r_4BIS(R,D,B,I,S)))) /* Use RIP-addressing in 64-bit mode, if possible */ #define _x86_RIP_addressing_possible(D,O) (X86_RIP_RELATIVE_ADDR && x86_RIP_addressing_possible(D, O)) static inline int x86_RIP_addressing_possible(uintptr addr, uintptr offset) { #if X86_TARGET_64BIT /* * address of the next instruction. * The opcode has already been emmitted, * so this is the size of an 32bit displacement + * the size of any immediate value that is part of the instruction (offset), */ uintptr dst = (uintptr)get_target() + 4 + offset; intptr disp = dst - addr; int ok = disp >= -0x80000000LL && disp <= 0x7fffffffLL; /* fprintf(stderr, "x86_RIP_addressing_possible: %llx - %llx %16llx = %d\n", (unsigned long long)dst, (unsigned long long)addr, (long long)disp, ok); */ return ok; #else UNUSED(addr); UNUSED(offset); return 0; #endif } static inline int x86_DISP32_addressing_possible(uintptr addr) { #if X86_TARGET_64BIT return addr <= 0xFFFFFFFFULL; #else UNUSED(addr); return 1; #endif } #define _r_X( R, D,B,I,S,O) (_r0P(I) ? (_r0P(B) ? (!X86_TARGET_64BIT ? _r_D(R,D) : \ (_x86_RIP_addressing_possible(D, O) ? \ _r_D(R, (D) - ((uintptr)x86_get_target() + 4 + (O))) : \ _r_DSIB(R,D))) : \ (_rIP(B) ? _r_D (R,D ) : \ (_rsp12P(B) ? _r_DBIS(R,D,_rSP(),_rSP(),1) : \ _r_DB (R,D, B )))) : \ (_r0P(B) ? _r_4IS (R,D, I,S) : \ (!_rspP(I) ? _r_DBIS(R,D, B, I,S) : \ x86_emit_failure("illegal index register: %esp")))) /* --- Instruction formats ------------------------------------------------- */ #define _m32only(X) (! X86_TARGET_64BIT ? X : x86_emit_failure("invalid instruction in 64-bit mode")) #define _m64only(X) ( X86_TARGET_64BIT ? X : x86_emit_failure("invalid instruction in 32-bit mode")) #define _m64(X) ( X86_TARGET_64BIT ? X : ((void)0) ) /* _format Opcd ModR/M dN(rB,rI,Sc) imm... */ #define _d16() ( _B(0x66 ) ) #define _O( OP ) ( _B( OP ) ) #define _Or( OP,R ) ( _B( (OP)|_r(R)) ) #define _OO( OP ) ( _B((OP)>>8), _B( (uae_u8)(OP) ) ) #define _OOr( OP,R ) ( _B((OP)>>8), _B( (OP)|_r(R)) ) #define _Os( OP,B ) ( _s8P(B) ? _B(((OP)|_b10)) : _B(OP) ) #define _sW( W ) ( _s8P(W) ? _B(W):_W(W) ) #define _sL( L ) ( _s8P(L) ? _B(L):_L(L) ) #define _sWO( W ) ( _s8P(W) ? 1 : 2 ) #define _sLO( L ) ( _s8P(L) ? 1 : 4 ) #define _O_B( OP ,B ) ( _O ( OP ) ,_B(B) ) #define _O_W( OP ,W ) ( _O ( OP ) ,_W(W) ) #define _O_L( OP ,L ) ( _O ( OP ) ,_L(L) ) #define _O_D8( OP ,D ) ( _O ( OP ) ,_D8(D) ) #define _O_D32( OP ,D ) ( _O ( OP ) ,_D32(D) ) #define _OO_D32( OP ,D ) ( _OO ( OP ) ,_D32(D) ) #define _Os_sW( OP ,W ) ( _Os ( OP,W) ,_sW(W) ) #define _Os_sL( OP ,L ) ( _Os ( OP,L) ,_sL(L) ) #define _O_W_B( OP ,W,B) ( _O ( OP ) ,_W(W),_B(B)) #define _Or_B( OP,R ,B ) ( _Or ( OP,R) ,_B(B) ) #define _Or_W( OP,R ,W ) ( _Or ( OP,R) ,_W(W) ) #define _Or_L( OP,R ,L ) ( _Or ( OP,R) ,_L(L) ) #define _Or_Q( OP,R ,Q ) ( _Or ( OP,R) ,_Q(Q) ) #define _O_Mrm( OP ,MO,R,M ) ( _O ( OP ),_Mrm(MO,R,M ) ) #define _OO_Mrm( OP ,MO,R,M ) ( _OO ( OP ),_Mrm(MO,R,M ) ) #define _O_Mrm_B( OP ,MO,R,M ,B ) ( _O ( OP ),_Mrm(MO,R,M ) ,_B(B) ) #define _O_Mrm_W( OP ,MO,R,M ,W ) ( _O ( OP ),_Mrm(MO,R,M ) ,_W(W) ) #define _O_Mrm_L( OP ,MO,R,M ,L ) ( _O ( OP ),_Mrm(MO,R,M ) ,_L(L) ) #define _OO_Mrm_B( OP ,MO,R,M ,B ) ( _OO ( OP ),_Mrm(MO,R,M ) ,_B(B) ) #define _Os_Mrm_sW(OP ,MO,R,M ,W ) ( _Os ( OP,W),_Mrm(MO,R,M ),_sW(W) ) #define _Os_Mrm_sL(OP ,MO,R,M ,L ) ( _Os ( OP,L),_Mrm(MO,R,M ),_sL(L) ) #define _O_r_X( OP ,R ,MD,MB,MI,MS ) ( _O ( OP ),_r_X( R ,MD,MB,MI,MS,0) ) #define _OO_r_X( OP ,R ,MD,MB,MI,MS ) ( _OO ( OP ),_r_X( R ,MD,MB,MI,MS,0) ) #define _O_r_X_B( OP ,R ,MD,MB,MI,MS,B ) ( _O ( OP ),_r_X( R ,MD,MB,MI,MS,1) ,_B(B) ) #define _O_r_X_W( OP ,R ,MD,MB,MI,MS,W ) ( _O ( OP ),_r_X( R ,MD,MB,MI,MS,2) ,_W(W) ) #define _O_r_X_L( OP ,R ,MD,MB,MI,MS,L ) ( _O ( OP ),_r_X( R ,MD,MB,MI,MS,4) ,_L(L) ) #define _OO_r_X_B( OP ,R ,MD,MB,MI,MS,B ) ( _OO ( OP ),_r_X( R ,MD,MB,MI,MS,1) ,_B(B) ) #define _Os_r_X_sW(OP ,R ,MD,MB,MI,MS,W ) ( _Os ( OP,W),_r_X( R ,MD,MB,MI,MS,_sWO(W)),_sW(W)) #define _Os_r_X_sL(OP ,R ,MD,MB,MI,MS,L ) ( _Os ( OP,L),_r_X( R ,MD,MB,MI,MS,_sLO(L)),_sL(L)) #define _O_X_B( OP ,MD,MB,MI,MS,B ) ( _O_r_X_B( OP ,0 ,MD,MB,MI,MS ,B) ) #define _O_X_W( OP ,MD,MB,MI,MS,W ) ( _O_r_X_W( OP ,0 ,MD,MB,MI,MS ,W) ) #define _O_X_L( OP ,MD,MB,MI,MS,L ) ( _O_r_X_L( OP ,0 ,MD,MB,MI,MS ,L) ) /* --- REX prefixes -------------------------------------------------------- */ #undef _VOID #define _VOID() ((void)0) #define _BIT(X) (!!(X)) #define _d64(W,R,X,B) (_B(0x40|(W)<<3|(R)<<2|(X)<<1|(B?1:0))) #define __REXwrxb(L,W,R,X,B) ((W|R|X|B) || (L) ? _d64(W,R,X,B) : _VOID()) #define __REXwrx_(L,W,R,X,MR) (__REXwrxb(L,W,R,X,_BIT(_rIP(MR)?0:_rXP(MR)))) #define __REXw_x_(L,W,R,X,MR) (__REXwrx_(L,W,_BIT(_rXP(R)),X,MR)) #define __REX_reg(RR) (__REXwrxb(0,0,0,00,_BIT(_rXP(RR)))) #define __REX_mem(MB,MI) (__REXwrxb(0,0,0,_BIT(_rXP(MI)),_BIT(_rXP(MB)))) // FIXME: can't mix new (SPL,BPL,SIL,DIL) with (AH,BH,CH,DH) #define _REXBrr(RR,MR) _m64(__REXw_x_(_r1e8lP(RR)||_r1e8lP(MR),0,RR,0,MR)) #define _REXBmr(MB,MI,RD) _m64(__REXw_x_(_r1e8lP(RD)||_r1e8lP(MB),0,RD,_BIT(_rXP(MI)),MB)) #define _REXBrm(RS,MB,MI) _REXBmr(MB,MI,RS) #define _REXBLrr(RR,MR) _m64(__REXw_x_(_r1e8lP(MR),0,RR,0,MR)) #define _REXLrr(RR,MR) _m64(__REXw_x_(0,0,RR,0,MR)) #define _REXLmr(MB,MI,RD) _m64(__REXw_x_(0,0,RD,_BIT(_rXP(MI)),MB)) #define _REXLrm(RS,MB,MI) _REXLmr(MB,MI,RS) #define _REXLr(RR) _m64(__REX_reg(RR)) #define _REXLm(MB,MI) _m64(__REX_mem(MB,MI)) #define _REXQrr(RR,MR) _m64only(__REXw_x_(0,1,RR,0,MR)) #define _REXQmr(MB,MI,RD) _m64only(__REXw_x_(0,1,RD,_BIT(_rXP(MI)),MB)) #define _REXQrm(RS,MB,MI) _REXQmr(MB,MI,RS) #define _REXQr(RR) _m64only(__REX_reg(RR)) #define _REXQm(MB,MI) _m64only(__REX_mem(MB,MI)) /* ========================================================================= */ /* --- Fully-qualified intrinsic instructions ------------------------------ */ /* ========================================================================= */ /* OPCODE + i = immediate operand * + r = register operand * + m = memory operand (disp,base,index,scale) * + sr/sm = a star preceding a register or memory * + 0 = top of stack register (for FPU instructions) * * NOTE in x86-64 mode: a memory operand with only a valid * displacement value will lead to the expect absolute mode. If * RIP addressing is necessary, X86_RIP shall be used as the base * register argument. */ /* --- ALU instructions ---------------------------------------------------- */ enum { X86_ADD = 0, X86_OR = 1, X86_ADC = 2, X86_SBB = 3, X86_AND = 4, X86_SUB = 5, X86_XOR = 6, X86_CMP = 7, }; /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define _ALUBrr(OP,RS, RD) (_REXBrr(RS, RD), _O_Mrm (((OP) << 3) ,_b11,_r1(RS),_r1(RD) )) #define _ALUBmr(OP, MD, MB, MI, MS, RD) (_REXBmr(MB, MI, RD), _O_r_X (((OP) << 3) + 2,_r1(RD) ,MD,MB,MI,MS )) #define _ALUBrm(OP, RS, MD, MB, MI, MS) (_REXBrm(RS, MB, MI), _O_r_X (((OP) << 3) , ,_r1(RS) ,MD,MB,MI,MS )) #define _ALUBir(OP, IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_AL) ? \ (_REXBrr(0, RD), _O_B (((OP) << 3) + 4 ,_su8(IM))) : \ (_REXBrr(0, RD), _O_Mrm_B (0x80 ,_b11,OP ,_r1(RD) ,_su8(IM))) ) #define _ALUBim(OP, IM, MD, MB, MI, MS) (_REXBrm(0, MB, MI), _O_r_X_B (0x80 ,OP ,MD,MB,MI,MS ,_su8(IM))) #define _ALUWrr(OP, RS, RD) (_d16(), _REXLrr(RS, RD), _O_Mrm (((OP) << 3) + 1,_b11,_r2(RS),_r2(RD) )) #define _ALUWmr(OP, MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _O_r_X (((OP) << 3) + 3 ,_r2(RD) ,MD,MB,MI,MS )) #define _ALUWrm(OP, RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _O_r_X (((OP) << 3) + 1 ,_r2(RS) ,MD,MB,MI,MS )) #define _ALUWir(OP, IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_AX) ? \ (_d16(), _REXLrr(0, RD), _O_W (((OP) << 3) + 5 ,_su16(IM))) : \ (_d16(), _REXLrr(0, RD), _Os_Mrm_sW (0x81 ,_b11,OP ,_r2(RD) ,_su16(IM))) ) #define _ALUWim(OP, IM, MD, MB, MI, MS) (_d16(), _REXLrm(0, MB, MI), _Os_r_X_sW (0x81 ,OP ,MD,MB,MI,MS ,_su16(IM))) #define _ALULrr(OP, RS, RD) (_REXLrr(RS, RD), _O_Mrm (((OP) << 3) + 1,_b11,_r4(RS),_r4(RD) )) #define _ALULmr(OP, MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _O_r_X (((OP) << 3) + 3 ,_r4(RD) ,MD,MB,MI,MS )) #define _ALULrm(OP, RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _O_r_X (((OP) << 3) + 1 ,_r4(RS) ,MD,MB,MI,MS )) #define _ALULir(OP, IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_EAX) ? \ (_REXLrr(0, RD), _O_L (((OP) << 3) + 5 ,IM )) : \ (_REXLrr(0, RD), _Os_Mrm_sL (0x81 ,_b11,OP ,_r4(RD) ,IM )) ) #define _ALULim(OP, IM, MD, MB, MI, MS) (_REXLrm(0, MB, MI), _Os_r_X_sL (0x81 ,OP ,MD,MB,MI,MS ,IM )) #define _ALUQrr(OP, RS, RD) (_REXQrr(RS, RD), _O_Mrm (((OP) << 3) + 1,_b11,_r8(RS),_r8(RD) )) #define _ALUQmr(OP, MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _O_r_X (((OP) << 3) + 3 ,_r8(RD) ,MD,MB,MI,MS )) #define _ALUQrm(OP, RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _O_r_X (((OP) << 3) + 1 ,_r8(RS) ,MD,MB,MI,MS )) #define _ALUQir(OP, IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_RAX) ? \ (_REXQrr(0, RD), _O_L (((OP) << 3) + 5 ,IM )) : \ (_REXQrr(0, RD), _Os_Mrm_sL (0x81 ,_b11,OP ,_r8(RD) ,IM )) ) #define _ALUQim(OP, IM, MD, MB, MI, MS) (_REXQrm(0, MB, MI), _Os_r_X_sL (0x81 ,OP ,MD,MB,MI,MS ,IM )) #define ADCBrr(RS, RD) _ALUBrr(X86_ADC, RS, RD) #define ADCBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_ADC, MD, MB, MI, MS, RD) #define ADCBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_ADC, RS, MD, MB, MI, MS) #define ADCBir(IM, RD) _ALUBir(X86_ADC, IM, RD) #define ADCBim(IM, MD, MB, MI, MS) _ALUBim(X86_ADC, IM, MD, MB, MI, MS) #define ADCWrr(RS, RD) _ALUWrr(X86_ADC, RS, RD) #define ADCWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_ADC, MD, MB, MI, MS, RD) #define ADCWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_ADC, RS, MD, MB, MI, MS) #define ADCWir(IM, RD) _ALUWir(X86_ADC, IM, RD) #define ADCWim(IM, MD, MB, MI, MS) _ALUWim(X86_ADC, IM, MD, MB, MI, MS) #define ADCLrr(RS, RD) _ALULrr(X86_ADC, RS, RD) #define ADCLmr(MD, MB, MI, MS, RD) _ALULmr(X86_ADC, MD, MB, MI, MS, RD) #define ADCLrm(RS, MD, MB, MI, MS) _ALULrm(X86_ADC, RS, MD, MB, MI, MS) #define ADCLir(IM, RD) _ALULir(X86_ADC, IM, RD) #define ADCLim(IM, MD, MB, MI, MS) _ALULim(X86_ADC, IM, MD, MB, MI, MS) #define ADCQrr(RS, RD) _ALUQrr(X86_ADC, RS, RD) #define ADCQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_ADC, MD, MB, MI, MS, RD) #define ADCQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_ADC, RS, MD, MB, MI, MS) #define ADCQir(IM, RD) _ALUQir(X86_ADC, IM, RD) #define ADCQim(IM, MD, MB, MI, MS) _ALUQim(X86_ADC, IM, MD, MB, MI, MS) #define ADDBrr(RS, RD) _ALUBrr(X86_ADD, RS, RD) #define ADDBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_ADD, MD, MB, MI, MS, RD) #define ADDBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_ADD, RS, MD, MB, MI, MS) #define ADDBir(IM, RD) _ALUBir(X86_ADD, IM, RD) #define ADDBim(IM, MD, MB, MI, MS) _ALUBim(X86_ADD, IM, MD, MB, MI, MS) #define ADDWrr(RS, RD) _ALUWrr(X86_ADD, RS, RD) #define ADDWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_ADD, MD, MB, MI, MS, RD) #define ADDWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_ADD, RS, MD, MB, MI, MS) #define ADDWir(IM, RD) _ALUWir(X86_ADD, IM, RD) #define ADDWim(IM, MD, MB, MI, MS) _ALUWim(X86_ADD, IM, MD, MB, MI, MS) #define ADDLrr(RS, RD) _ALULrr(X86_ADD, RS, RD) #define ADDLmr(MD, MB, MI, MS, RD) _ALULmr(X86_ADD, MD, MB, MI, MS, RD) #define ADDLrm(RS, MD, MB, MI, MS) _ALULrm(X86_ADD, RS, MD, MB, MI, MS) #define ADDLir(IM, RD) _ALULir(X86_ADD, IM, RD) #define ADDLim(IM, MD, MB, MI, MS) _ALULim(X86_ADD, IM, MD, MB, MI, MS) #define ADDQrr(RS, RD) _ALUQrr(X86_ADD, RS, RD) #define ADDQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_ADD, MD, MB, MI, MS, RD) #define ADDQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_ADD, RS, MD, MB, MI, MS) #define ADDQir(IM, RD) _ALUQir(X86_ADD, IM, RD) #define ADDQim(IM, MD, MB, MI, MS) _ALUQim(X86_ADD, IM, MD, MB, MI, MS) #define ANDBrr(RS, RD) _ALUBrr(X86_AND, RS, RD) #define ANDBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_AND, MD, MB, MI, MS, RD) #define ANDBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_AND, RS, MD, MB, MI, MS) #define ANDBir(IM, RD) _ALUBir(X86_AND, IM, RD) #define ANDBim(IM, MD, MB, MI, MS) _ALUBim(X86_AND, IM, MD, MB, MI, MS) #define ANDWrr(RS, RD) _ALUWrr(X86_AND, RS, RD) #define ANDWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_AND, MD, MB, MI, MS, RD) #define ANDWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_AND, RS, MD, MB, MI, MS) #define ANDWir(IM, RD) _ALUWir(X86_AND, IM, RD) #define ANDWim(IM, MD, MB, MI, MS) _ALUWim(X86_AND, IM, MD, MB, MI, MS) #define ANDLrr(RS, RD) _ALULrr(X86_AND, RS, RD) #define ANDLmr(MD, MB, MI, MS, RD) _ALULmr(X86_AND, MD, MB, MI, MS, RD) #define ANDLrm(RS, MD, MB, MI, MS) _ALULrm(X86_AND, RS, MD, MB, MI, MS) #define ANDLir(IM, RD) _ALULir(X86_AND, IM, RD) #define ANDLim(IM, MD, MB, MI, MS) _ALULim(X86_AND, IM, MD, MB, MI, MS) #define ANDQrr(RS, RD) _ALUQrr(X86_AND, RS, RD) #define ANDQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_AND, MD, MB, MI, MS, RD) #define ANDQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_AND, RS, MD, MB, MI, MS) #define ANDQir(IM, RD) _ALUQir(X86_AND, IM, RD) #define ANDQim(IM, MD, MB, MI, MS) _ALUQim(X86_AND, IM, MD, MB, MI, MS) #define CMPBrr(RS, RD) _ALUBrr(X86_CMP, RS, RD) #define CMPBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_CMP, MD, MB, MI, MS, RD) #define CMPBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_CMP, RS, MD, MB, MI, MS) #define CMPBir(IM, RD) _ALUBir(X86_CMP, IM, RD) #define CMPBim(IM, MD, MB, MI, MS) _ALUBim(X86_CMP, IM, MD, MB, MI, MS) #define CMPWrr(RS, RD) _ALUWrr(X86_CMP, RS, RD) #define CMPWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_CMP, MD, MB, MI, MS, RD) #define CMPWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_CMP, RS, MD, MB, MI, MS) #define CMPWir(IM, RD) _ALUWir(X86_CMP, IM, RD) #define CMPWim(IM, MD, MB, MI, MS) _ALUWim(X86_CMP, IM, MD, MB, MI, MS) #define CMPLrr(RS, RD) _ALULrr(X86_CMP, RS, RD) #define CMPLmr(MD, MB, MI, MS, RD) _ALULmr(X86_CMP, MD, MB, MI, MS, RD) #define CMPLrm(RS, MD, MB, MI, MS) _ALULrm(X86_CMP, RS, MD, MB, MI, MS) #define CMPLir(IM, RD) _ALULir(X86_CMP, IM, RD) #define CMPLim(IM, MD, MB, MI, MS) _ALULim(X86_CMP, IM, MD, MB, MI, MS) #define CMPQrr(RS, RD) _ALUQrr(X86_CMP, RS, RD) #define CMPQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_CMP, MD, MB, MI, MS, RD) #define CMPQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_CMP, RS, MD, MB, MI, MS) #define CMPQir(IM, RD) _ALUQir(X86_CMP, IM, RD) #define CMPQim(IM, MD, MB, MI, MS) _ALUQim(X86_CMP, IM, MD, MB, MI, MS) #define ORBrr(RS, RD) _ALUBrr(X86_OR, RS, RD) #define ORBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_OR, MD, MB, MI, MS, RD) #define ORBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_OR, RS, MD, MB, MI, MS) #define ORBir(IM, RD) _ALUBir(X86_OR, IM, RD) #define ORBim(IM, MD, MB, MI, MS) _ALUBim(X86_OR, IM, MD, MB, MI, MS) #define ORWrr(RS, RD) _ALUWrr(X86_OR, RS, RD) #define ORWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_OR, MD, MB, MI, MS, RD) #define ORWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_OR, RS, MD, MB, MI, MS) #define ORWir(IM, RD) _ALUWir(X86_OR, IM, RD) #define ORWim(IM, MD, MB, MI, MS) _ALUWim(X86_OR, IM, MD, MB, MI, MS) #define ORLrr(RS, RD) _ALULrr(X86_OR, RS, RD) #define ORLmr(MD, MB, MI, MS, RD) _ALULmr(X86_OR, MD, MB, MI, MS, RD) #define ORLrm(RS, MD, MB, MI, MS) _ALULrm(X86_OR, RS, MD, MB, MI, MS) #define ORLir(IM, RD) _ALULir(X86_OR, IM, RD) #define ORLim(IM, MD, MB, MI, MS) _ALULim(X86_OR, IM, MD, MB, MI, MS) #define ORQrr(RS, RD) _ALUQrr(X86_OR, RS, RD) #define ORQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_OR, MD, MB, MI, MS, RD) #define ORQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_OR, RS, MD, MB, MI, MS) #define ORQir(IM, RD) _ALUQir(X86_OR, IM, RD) #define ORQim(IM, MD, MB, MI, MS) _ALUQim(X86_OR, IM, MD, MB, MI, MS) #define SBBBrr(RS, RD) _ALUBrr(X86_SBB, RS, RD) #define SBBBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_SBB, MD, MB, MI, MS, RD) #define SBBBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_SBB, RS, MD, MB, MI, MS) #define SBBBir(IM, RD) _ALUBir(X86_SBB, IM, RD) #define SBBBim(IM, MD, MB, MI, MS) _ALUBim(X86_SBB, IM, MD, MB, MI, MS) #define SBBWrr(RS, RD) _ALUWrr(X86_SBB, RS, RD) #define SBBWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_SBB, MD, MB, MI, MS, RD) #define SBBWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_SBB, RS, MD, MB, MI, MS) #define SBBWir(IM, RD) _ALUWir(X86_SBB, IM, RD) #define SBBWim(IM, MD, MB, MI, MS) _ALUWim(X86_SBB, IM, MD, MB, MI, MS) #define SBBLrr(RS, RD) _ALULrr(X86_SBB, RS, RD) #define SBBLmr(MD, MB, MI, MS, RD) _ALULmr(X86_SBB, MD, MB, MI, MS, RD) #define SBBLrm(RS, MD, MB, MI, MS) _ALULrm(X86_SBB, RS, MD, MB, MI, MS) #define SBBLir(IM, RD) _ALULir(X86_SBB, IM, RD) #define SBBLim(IM, MD, MB, MI, MS) _ALULim(X86_SBB, IM, MD, MB, MI, MS) #define SBBQrr(RS, RD) _ALUQrr(X86_SBB, RS, RD) #define SBBQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_SBB, MD, MB, MI, MS, RD) #define SBBQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_SBB, RS, MD, MB, MI, MS) #define SBBQir(IM, RD) _ALUQir(X86_SBB, IM, RD) #define SBBQim(IM, MD, MB, MI, MS) _ALUQim(X86_SBB, IM, MD, MB, MI, MS) #define SUBBrr(RS, RD) _ALUBrr(X86_SUB, RS, RD) #define SUBBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_SUB, MD, MB, MI, MS, RD) #define SUBBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_SUB, RS, MD, MB, MI, MS) #define SUBBir(IM, RD) _ALUBir(X86_SUB, IM, RD) #define SUBBim(IM, MD, MB, MI, MS) _ALUBim(X86_SUB, IM, MD, MB, MI, MS) #define SUBWrr(RS, RD) _ALUWrr(X86_SUB, RS, RD) #define SUBWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_SUB, MD, MB, MI, MS, RD) #define SUBWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_SUB, RS, MD, MB, MI, MS) #define SUBWir(IM, RD) _ALUWir(X86_SUB, IM, RD) #define SUBWim(IM, MD, MB, MI, MS) _ALUWim(X86_SUB, IM, MD, MB, MI, MS) #define SUBLrr(RS, RD) _ALULrr(X86_SUB, RS, RD) #define SUBLmr(MD, MB, MI, MS, RD) _ALULmr(X86_SUB, MD, MB, MI, MS, RD) #define SUBLrm(RS, MD, MB, MI, MS) _ALULrm(X86_SUB, RS, MD, MB, MI, MS) #define SUBLir(IM, RD) _ALULir(X86_SUB, IM, RD) #define SUBLim(IM, MD, MB, MI, MS) _ALULim(X86_SUB, IM, MD, MB, MI, MS) #define SUBQrr(RS, RD) _ALUQrr(X86_SUB, RS, RD) #define SUBQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_SUB, MD, MB, MI, MS, RD) #define SUBQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_SUB, RS, MD, MB, MI, MS) #define SUBQir(IM, RD) _ALUQir(X86_SUB, IM, RD) #define SUBQim(IM, MD, MB, MI, MS) _ALUQim(X86_SUB, IM, MD, MB, MI, MS) #define XORBrr(RS, RD) _ALUBrr(X86_XOR, RS, RD) #define XORBmr(MD, MB, MI, MS, RD) _ALUBmr(X86_XOR, MD, MB, MI, MS, RD) #define XORBrm(RS, MD, MB, MI, MS) _ALUBrm(X86_XOR, RS, MD, MB, MI, MS) #define XORBir(IM, RD) _ALUBir(X86_XOR, IM, RD) #define XORBim(IM, MD, MB, MI, MS) _ALUBim(X86_XOR, IM, MD, MB, MI, MS) #define XORWrr(RS, RD) _ALUWrr(X86_XOR, RS, RD) #define XORWmr(MD, MB, MI, MS, RD) _ALUWmr(X86_XOR, MD, MB, MI, MS, RD) #define XORWrm(RS, MD, MB, MI, MS) _ALUWrm(X86_XOR, RS, MD, MB, MI, MS) #define XORWir(IM, RD) _ALUWir(X86_XOR, IM, RD) #define XORWim(IM, MD, MB, MI, MS) _ALUWim(X86_XOR, IM, MD, MB, MI, MS) #define XORLrr(RS, RD) _ALULrr(X86_XOR, RS, RD) #define XORLmr(MD, MB, MI, MS, RD) _ALULmr(X86_XOR, MD, MB, MI, MS, RD) #define XORLrm(RS, MD, MB, MI, MS) _ALULrm(X86_XOR, RS, MD, MB, MI, MS) #define XORLir(IM, RD) _ALULir(X86_XOR, IM, RD) #define XORLim(IM, MD, MB, MI, MS) _ALULim(X86_XOR, IM, MD, MB, MI, MS) #define XORQrr(RS, RD) _ALUQrr(X86_XOR, RS, RD) #define XORQmr(MD, MB, MI, MS, RD) _ALUQmr(X86_XOR, MD, MB, MI, MS, RD) #define XORQrm(RS, MD, MB, MI, MS) _ALUQrm(X86_XOR, RS, MD, MB, MI, MS) #define XORQir(IM, RD) _ALUQir(X86_XOR, IM, RD) #define XORQim(IM, MD, MB, MI, MS) _ALUQim(X86_XOR, IM, MD, MB, MI, MS) /* --- Shift/Rotate instructions ------------------------------------------- */ enum { X86_ROL = 0, X86_ROR = 1, X86_RCL = 2, X86_RCR = 3, X86_SHL = 4, X86_SHR = 5, X86_SAR = 7, }; /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define _ROTSHIBir(OP,IM,RD) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_REXBrr(0, RD), _O_Mrm (0xd0 ,_b11,OP,_r1(RD) )) : \ (_REXBrr(0, RD), _O_Mrm_B (0xc0 ,_b11,OP,_r1(RD) ,_u8(IM))) ) #define _ROTSHIBim(OP,IM,MD,MB,MI,MS) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_REXBrm(0, MB, MI), _O_r_X (0xd0 ,OP ,MD,MB,MI,MS )) : \ (_REXBrm(0, MB, MI), _O_r_X_B (0xc0 ,OP ,MD,MB,MI,MS ,_u8(IM))) ) #define _ROTSHIBrr(OP,RS,RD) (((RS) == X86_CL) ? \ (_REXBrr(RS, RD), _O_Mrm (0xd2 ,_b11,OP,_r1(RD) )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHIBrm(OP,RS,MD,MB,MI,MS) (((RS) == X86_CL) ? \ (_REXBrm(RS, MB, MI), _O_r_X (0xd2 ,OP ,MD,MB,MI,MS )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHIWir(OP,IM,RD) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_d16(), _REXLrr(0, RD), _O_Mrm (0xd1 ,_b11,OP,_r2(RD) )) : \ (_d16(), _REXLrr(0, RD), _O_Mrm_B (0xc1 ,_b11,OP,_r2(RD) ,_u8(IM))) ) #define _ROTSHIWim(OP,IM,MD,MB,MI,MS) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_d16(), _REXLrm(0, MB, MI), _O_r_X (0xd1 ,OP ,MD,MB,MI,MS )) : \ (_d16(), _REXLrm(0, MB, MI), _O_r_X_B (0xc1 ,OP ,MD,MB,MI,MS ,_u8(IM))) ) #define _ROTSHIWrr(OP,RS,RD) (((RS) == X86_CL) ? \ (_d16(), _REXLrr(RS, RD), _O_Mrm (0xd3 ,_b11,OP,_r2(RD) )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHIWrm(OP,RS,MD,MB,MI,MS) (((RS) == X86_CL) ? \ (_d16(), _REXLrm(RS, MB, MI), _O_r_X (0xd3 ,OP ,MD,MB,MI,MS )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHILir(OP,IM,RD) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_REXLrr(0, RD), _O_Mrm (0xd1 ,_b11,OP,_r4(RD) )) : \ (_REXLrr(0, RD), _O_Mrm_B (0xc1 ,_b11,OP,_r4(RD) ,_u8(IM))) ) #define _ROTSHILim(OP,IM,MD,MB,MI,MS) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_REXLrm(0, MB, MI), _O_r_X (0xd1 ,OP ,MD,MB,MI,MS )) : \ (_REXLrm(0, MB, MI), _O_r_X_B (0xc1 ,OP ,MD,MB,MI,MS ,_u8(IM))) ) #define _ROTSHILrr(OP,RS,RD) (((RS) == X86_CL) ? \ (_REXLrr(RS, RD), _O_Mrm (0xd3 ,_b11,OP,_r4(RD) )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHILrm(OP,RS,MD,MB,MI,MS) (((RS) == X86_CL) ? \ (_REXLrm(RS, MB, MI), _O_r_X (0xd3 ,OP ,MD,MB,MI,MS )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHIQir(OP,IM,RD) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_REXQrr(0, RD), _O_Mrm (0xd1 ,_b11,OP,_r8(RD) )) : \ (_REXQrr(0, RD), _O_Mrm_B (0xc1 ,_b11,OP,_r8(RD) ,_u8(IM))) ) #define _ROTSHIQim(OP,IM,MD,MB,MI,MS) (X86_OPTIMIZE_ROTSHI && ((IM) == 1) ? \ (_REXQrm(0, MB, MI), _O_r_X (0xd1 ,OP ,MD,MB,MI,MS )) : \ (_REXQrm(0, MB, MI), _O_r_X_B (0xc1 ,OP ,MD,MB,MI,MS ,_u8(IM))) ) #define _ROTSHIQrr(OP,RS,RD) (((RS) == X86_CL) ? \ (_REXQrr(RS, RD), _O_Mrm (0xd3 ,_b11,OP,_r8(RD) )) : \ x86_emit_failure("source register must be CL" ) ) #define _ROTSHIQrm(OP,RS,MD,MB,MI,MS) (((RS) == X86_CL) ? \ (_REXQrm(RS, MB, MI), _O_r_X (0xd3 ,OP ,MD,MB,MI,MS )) : \ x86_emit_failure("source register must be CL" ) ) #define ROLBir(IM, RD) _ROTSHIBir(X86_ROL, IM, RD) #define ROLBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_ROL, IM, MD, MB, MI, MS) #define ROLBrr(RS, RD) _ROTSHIBrr(X86_ROL, RS, RD) #define ROLBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_ROL, RS, MD, MB, MI, MS) #define ROLWir(IM, RD) _ROTSHIWir(X86_ROL, IM, RD) #define ROLWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_ROL, IM, MD, MB, MI, MS) #define ROLWrr(RS, RD) _ROTSHIWrr(X86_ROL, RS, RD) #define ROLWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_ROL, RS, MD, MB, MI, MS) #define ROLLir(IM, RD) _ROTSHILir(X86_ROL, IM, RD) #define ROLLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_ROL, IM, MD, MB, MI, MS) #define ROLLrr(RS, RD) _ROTSHILrr(X86_ROL, RS, RD) #define ROLLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_ROL, RS, MD, MB, MI, MS) #define ROLQir(IM, RD) _ROTSHIQir(X86_ROL, IM, RD) #define ROLQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_ROL, IM, MD, MB, MI, MS) #define ROLQrr(RS, RD) _ROTSHIQrr(X86_ROL, RS, RD) #define ROLQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_ROL, RS, MD, MB, MI, MS) #define RORBir(IM, RD) _ROTSHIBir(X86_ROR, IM, RD) #define RORBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_ROR, IM, MD, MB, MI, MS) #define RORBrr(RS, RD) _ROTSHIBrr(X86_ROR, RS, RD) #define RORBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_ROR, RS, MD, MB, MI, MS) #define RORWir(IM, RD) _ROTSHIWir(X86_ROR, IM, RD) #define RORWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_ROR, IM, MD, MB, MI, MS) #define RORWrr(RS, RD) _ROTSHIWrr(X86_ROR, RS, RD) #define RORWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_ROR, RS, MD, MB, MI, MS) #define RORLir(IM, RD) _ROTSHILir(X86_ROR, IM, RD) #define RORLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_ROR, IM, MD, MB, MI, MS) #define RORLrr(RS, RD) _ROTSHILrr(X86_ROR, RS, RD) #define RORLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_ROR, RS, MD, MB, MI, MS) #define RORQir(IM, RD) _ROTSHIQir(X86_ROR, IM, RD) #define RORQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_ROR, IM, MD, MB, MI, MS) #define RORQrr(RS, RD) _ROTSHIQrr(X86_ROR, RS, RD) #define RORQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_ROR, RS, MD, MB, MI, MS) #define RCLBir(IM, RD) _ROTSHIBir(X86_RCL, IM, RD) #define RCLBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_RCL, IM, MD, MB, MI, MS) #define RCLBrr(RS, RD) _ROTSHIBrr(X86_RCL, RS, RD) #define RCLBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_RCL, RS, MD, MB, MI, MS) #define RCLWir(IM, RD) _ROTSHIWir(X86_RCL, IM, RD) #define RCLWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_RCL, IM, MD, MB, MI, MS) #define RCLWrr(RS, RD) _ROTSHIWrr(X86_RCL, RS, RD) #define RCLWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_RCL, RS, MD, MB, MI, MS) #define RCLLir(IM, RD) _ROTSHILir(X86_RCL, IM, RD) #define RCLLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_RCL, IM, MD, MB, MI, MS) #define RCLLrr(RS, RD) _ROTSHILrr(X86_RCL, RS, RD) #define RCLLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_RCL, RS, MD, MB, MI, MS) #define RCLQir(IM, RD) _ROTSHIQir(X86_RCL, IM, RD) #define RCLQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_RCL, IM, MD, MB, MI, MS) #define RCLQrr(RS, RD) _ROTSHIQrr(X86_RCL, RS, RD) #define RCLQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_RCL, RS, MD, MB, MI, MS) #define RCRBir(IM, RD) _ROTSHIBir(X86_RCR, IM, RD) #define RCRBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_RCR, IM, MD, MB, MI, MS) #define RCRBrr(RS, RD) _ROTSHIBrr(X86_RCR, RS, RD) #define RCRBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_RCR, RS, MD, MB, MI, MS) #define RCRWir(IM, RD) _ROTSHIWir(X86_RCR, IM, RD) #define RCRWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_RCR, IM, MD, MB, MI, MS) #define RCRWrr(RS, RD) _ROTSHIWrr(X86_RCR, RS, RD) #define RCRWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_RCR, RS, MD, MB, MI, MS) #define RCRLir(IM, RD) _ROTSHILir(X86_RCR, IM, RD) #define RCRLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_RCR, IM, MD, MB, MI, MS) #define RCRLrr(RS, RD) _ROTSHILrr(X86_RCR, RS, RD) #define RCRLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_RCR, RS, MD, MB, MI, MS) #define RCRQir(IM, RD) _ROTSHIQir(X86_RCR, IM, RD) #define RCRQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_RCR, IM, MD, MB, MI, MS) #define RCRQrr(RS, RD) _ROTSHIQrr(X86_RCR, RS, RD) #define RCRQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_RCR, RS, MD, MB, MI, MS) #define SHLBir(IM, RD) _ROTSHIBir(X86_SHL, IM, RD) #define SHLBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_SHL, IM, MD, MB, MI, MS) #define SHLBrr(RS, RD) _ROTSHIBrr(X86_SHL, RS, RD) #define SHLBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_SHL, RS, MD, MB, MI, MS) #define SHLWir(IM, RD) _ROTSHIWir(X86_SHL, IM, RD) #define SHLWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_SHL, IM, MD, MB, MI, MS) #define SHLWrr(RS, RD) _ROTSHIWrr(X86_SHL, RS, RD) #define SHLWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_SHL, RS, MD, MB, MI, MS) #define SHLLir(IM, RD) _ROTSHILir(X86_SHL, IM, RD) #define SHLLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_SHL, IM, MD, MB, MI, MS) #define SHLLrr(RS, RD) _ROTSHILrr(X86_SHL, RS, RD) #define SHLLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_SHL, RS, MD, MB, MI, MS) #define SHLQir(IM, RD) _ROTSHIQir(X86_SHL, IM, RD) #define SHLQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_SHL, IM, MD, MB, MI, MS) #define SHLQrr(RS, RD) _ROTSHIQrr(X86_SHL, RS, RD) #define SHLQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_SHL, RS, MD, MB, MI, MS) #define SHRBir(IM, RD) _ROTSHIBir(X86_SHR, IM, RD) #define SHRBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_SHR, IM, MD, MB, MI, MS) #define SHRBrr(RS, RD) _ROTSHIBrr(X86_SHR, RS, RD) #define SHRBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_SHR, RS, MD, MB, MI, MS) #define SHRWir(IM, RD) _ROTSHIWir(X86_SHR, IM, RD) #define SHRWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_SHR, IM, MD, MB, MI, MS) #define SHRWrr(RS, RD) _ROTSHIWrr(X86_SHR, RS, RD) #define SHRWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_SHR, RS, MD, MB, MI, MS) #define SHRLir(IM, RD) _ROTSHILir(X86_SHR, IM, RD) #define SHRLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_SHR, IM, MD, MB, MI, MS) #define SHRLrr(RS, RD) _ROTSHILrr(X86_SHR, RS, RD) #define SHRLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_SHR, RS, MD, MB, MI, MS) #define SHRQir(IM, RD) _ROTSHIQir(X86_SHR, IM, RD) #define SHRQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_SHR, IM, MD, MB, MI, MS) #define SHRQrr(RS, RD) _ROTSHIQrr(X86_SHR, RS, RD) #define SHRQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_SHR, RS, MD, MB, MI, MS) #define SALBir SHLBir #define SALBim SHLBim #define SALBrr SHLBrr #define SALBrm SHLBrm #define SALWir SHLWir #define SALWim SHLWim #define SALWrr SHLWrr #define SALWrm SHLWrm #define SALLir SHLLir #define SALLim SHLLim #define SALLrr SHLLrr #define SALLrm SHLLrm #define SALQir SHLQir #define SALQim SHLQim #define SALQrr SHLQrr #define SALQrm SHLQrm #define SARBir(IM, RD) _ROTSHIBir(X86_SAR, IM, RD) #define SARBim(IM, MD, MB, MI, MS) _ROTSHIBim(X86_SAR, IM, MD, MB, MI, MS) #define SARBrr(RS, RD) _ROTSHIBrr(X86_SAR, RS, RD) #define SARBrm(RS, MD, MB, MI, MS) _ROTSHIBrm(X86_SAR, RS, MD, MB, MI, MS) #define SARWir(IM, RD) _ROTSHIWir(X86_SAR, IM, RD) #define SARWim(IM, MD, MB, MI, MS) _ROTSHIWim(X86_SAR, IM, MD, MB, MI, MS) #define SARWrr(RS, RD) _ROTSHIWrr(X86_SAR, RS, RD) #define SARWrm(RS, MD, MB, MI, MS) _ROTSHIWrm(X86_SAR, RS, MD, MB, MI, MS) #define SARLir(IM, RD) _ROTSHILir(X86_SAR, IM, RD) #define SARLim(IM, MD, MB, MI, MS) _ROTSHILim(X86_SAR, IM, MD, MB, MI, MS) #define SARLrr(RS, RD) _ROTSHILrr(X86_SAR, RS, RD) #define SARLrm(RS, MD, MB, MI, MS) _ROTSHILrm(X86_SAR, RS, MD, MB, MI, MS) #define SARQir(IM, RD) _ROTSHIQir(X86_SAR, IM, RD) #define SARQim(IM, MD, MB, MI, MS) _ROTSHIQim(X86_SAR, IM, MD, MB, MI, MS) #define SARQrr(RS, RD) _ROTSHIQrr(X86_SAR, RS, RD) #define SARQrm(RS, MD, MB, MI, MS) _ROTSHIQrm(X86_SAR, RS, MD, MB, MI, MS) /* --- Bit test instructions ----------------------------------------------- */ enum { X86_BT = 4, X86_BTS = 5, X86_BTR = 6, X86_BTC = 7, }; /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define _BTWir(OP, IM, RD) (_d16(), _REXLrr(0, RD), _OO_Mrm_B (0x0fba ,_b11,OP ,_r2(RD) ,_u8(IM))) #define _BTWim(OP, IM, MD, MB, MI, MS) (_d16(), _REXLrm(0, MB, MI), _OO_r_X_B (0x0fba ,OP ,MD,MB,MI,MS ,_u8(IM))) #define _BTWrr(OP, RS, RD) (_d16(), _REXLrr(RS, RD), _OO_Mrm (0x0f83|((OP)<<3),_b11,_r2(RS),_r2(RD) )) #define _BTWrm(OP, RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _OO_r_X (0x0f83|((OP)<<3) ,_r2(RS) ,MD,MB,MI,MS )) #define _BTLir(OP, IM, RD) (_REXLrr(0, RD), _OO_Mrm_B (0x0fba ,_b11,OP ,_r4(RD) ,_u8(IM))) #define _BTLim(OP, IM, MD, MB, MI, MS) (_REXLrm(0, MB, MI), _OO_r_X_B (0x0fba ,OP ,MD,MB,MI,MS ,_u8(IM))) #define _BTLrr(OP, RS, RD) (_REXLrr(RS, RD), _OO_Mrm (0x0f83|((OP)<<3),_b11,_r4(RS),_r4(RD) )) #define _BTLrm(OP, RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _OO_r_X (0x0f83|((OP)<<3) ,_r4(RS) ,MD,MB,MI,MS )) #define _BTQir(OP, IM, RD) (_REXQrr(0, RD), _OO_Mrm_B (0x0fba ,_b11,OP ,_r8(RD) ,_u8(IM))) #define _BTQim(OP, IM, MD, MB, MI, MS) (_REXQrm(0, MB, MI), _OO_r_X_B (0x0fba ,OP ,MD,MB,MI,MS ,_u8(IM))) #define _BTQrr(OP, RS, RD) (_REXQrr(RS, RD), _OO_Mrm (0x0f83|((OP)<<3),_b11,_r8(RS),_r8(RD) )) #define _BTQrm(OP, RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _OO_r_X (0x0f83|((OP)<<3) ,_r8(RS) ,MD,MB,MI,MS )) #define BTWir(IM, RD) _BTWir(X86_BT, IM, RD) #define BTWim(IM, MD, MB, MI, MS) _BTWim(X86_BT, IM, MD, MI, MS) #define BTWrr(RS, RD) _BTWrr(X86_BT, RS, RD) #define BTWrm(RS, MD, MB, MI, MS) _BTWrm(X86_BT, RS, MD, MB, MI, MS) #define BTLir(IM, RD) _BTLir(X86_BT, IM, RD) #define BTLim(IM, MD, MB, MI, MS) _BTLim(X86_BT, IM, MD, MB, MI, MS) #define BTLrr(RS, RD) _BTLrr(X86_BT, RS, RD) #define BTLrm(RS, MD, MB, MI, MS) _BTLrm(X86_BT, RS, MD, MB, MI, MS) #define BTQir(IM, RD) _BTQir(X86_BT, IM, RD) #define BTQim(IM, MD, MB, MI, MS) _BTQim(X86_BT, IM, MD, MB, MI, MS) #define BTQrr(RS, RD) _BTQrr(X86_BT, RS, RD) #define BTQrm(RS, MD, MB, MI, MS) _BTQrm(X86_BT, RS, MD, MB, MI, MS) #define BTCWir(IM, RD) _BTWir(X86_BTC, IM, RD) #define BTCWim(IM, MD, MB, MI, MS) _BTWim(X86_BTC, IM, MD, MI, MS) #define BTCWrr(RS, RD) _BTWrr(X86_BTC, RS, RD) #define BTCWrm(RS, MD, MB, MI, MS) _BTWrm(X86_BTC, RS, MD, MB, MI, MS) #define BTCLir(IM, RD) _BTLir(X86_BTC, IM, RD) #define BTCLim(IM, MD, MB, MI, MS) _BTLim(X86_BTC, IM, MD, MB, MI, MS) #define BTCLrr(RS, RD) _BTLrr(X86_BTC, RS, RD) #define BTCLrm(RS, MD, MB, MI, MS) _BTLrm(X86_BTC, RS, MD, MB, MI, MS) #define BTCQir(IM, RD) _BTQir(X86_BTC, IM, RD) #define BTCQim(IM, MD, MB, MI, MS) _BTQim(X86_BTC, IM, MD, MB, MI, MS) #define BTCQrr(RS, RD) _BTQrr(X86_BTC, RS, RD) #define BTCQrm(RS, MD, MB, MI, MS) _BTQrm(X86_BTC, RS, MD, MB, MI, MS) #define BTRWir(IM, RD) _BTWir(X86_BTR, IM, RD) #define BTRWim(IM, MD, MB, MI, MS) _BTWim(X86_BTR, IM, MD, MI, MS) #define BTRWrr(RS, RD) _BTWrr(X86_BTR, RS, RD) #define BTRWrm(RS, MD, MB, MI, MS) _BTWrm(X86_BTR, RS, MD, MB, MI, MS) #define BTRLir(IM, RD) _BTLir(X86_BTR, IM, RD) #define BTRLim(IM, MD, MB, MI, MS) _BTLim(X86_BTR, IM, MD, MB, MI, MS) #define BTRLrr(RS, RD) _BTLrr(X86_BTR, RS, RD) #define BTRLrm(RS, MD, MB, MI, MS) _BTLrm(X86_BTR, RS, MD, MB, MI, MS) #define BTRQir(IM, RD) _BTQir(X86_BTR, IM, RD) #define BTRQim(IM, MD, MB, MI, MS) _BTQim(X86_BTR, IM, MD, MB, MI, MS) #define BTRQrr(RS, RD) _BTQrr(X86_BTR, RS, RD) #define BTRQrm(RS, MD, MB, MI, MS) _BTQrm(X86_BTR, RS, MD, MB, MI, MS) #define BTSWir(IM, RD) _BTWir(X86_BTS, IM, RD) #define BTSWim(IM, MD, MB, MI, MS) _BTWim(X86_BTS, IM, MD, MI, MS) #define BTSWrr(RS, RD) _BTWrr(X86_BTS, RS, RD) #define BTSWrm(RS, MD, MB, MI, MS) _BTWrm(X86_BTS, RS, MD, MB, MI, MS) #define BTSLir(IM, RD) _BTLir(X86_BTS, IM, RD) #define BTSLim(IM, MD, MB, MI, MS) _BTLim(X86_BTS, IM, MD, MB, MI, MS) #define BTSLrr(RS, RD) _BTLrr(X86_BTS, RS, RD) #define BTSLrm(RS, MD, MB, MI, MS) _BTLrm(X86_BTS, RS, MD, MB, MI, MS) #define BTSQir(IM, RD) _BTQir(X86_BTS, IM, RD) #define BTSQim(IM, MD, MB, MI, MS) _BTQim(X86_BTS, IM, MD, MB, MI, MS) #define BTSQrr(RS, RD) _BTQrr(X86_BTS, RS, RD) #define BTSQrm(RS, MD, MB, MI, MS) _BTQrm(X86_BTS, RS, MD, MB, MI, MS) /* --- Move instructions --------------------------------------------------- */ /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define MOVBrr(RS, RD) (_REXBrr(RS, RD), _O_Mrm (0x88 ,_b11,_r1(RS),_r1(RD) )) #define MOVBmr(MD, MB, MI, MS, RD) (_REXBmr(MB, MI, RD), _O_r_X (0x8a ,_r1(RD) ,MD,MB,MI,MS )) #define MOVBrm(RS, MD, MB, MI, MS) (_REXBrm(RS, MB, MI), _O_r_X (0x88 ,_r1(RS) ,MD,MB,MI,MS )) #define MOVBir(IM, R) (_REXBrr(0, R), _Or_B (0xb0,_r1(R) ,_su8(IM))) #define MOVBim(IM, MD, MB, MI, MS) (_REXBrm(0, MB, MI), _O_X_B (0xc6 ,MD,MB,MI,MS ,_su8(IM))) #define MOVWrr(RS, RD) (_d16(), _REXLrr(RS, RD), _O_Mrm (0x89 ,_b11,_r2(RS),_r2(RD) )) #define MOVWmr(MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _O_r_X (0x8b ,_r2(RD) ,MD,MB,MI,MS )) #define MOVWrm(RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _O_r_X (0x89 ,_r2(RS) ,MD,MB,MI,MS )) #define MOVWir(IM, R) (_d16(), _REXLrr(0, R), _Or_W (0xb8,_r2(R) ,_su16(IM))) #define MOVWim(IM, MD, MB, MI, MS) (_d16(), _REXLrm(0, MB, MI), _O_X_W (0xc7 ,MD,MB,MI,MS ,_su16(IM))) #define MOVLrr(RS, RD) (_REXLrr(RS, RD), _O_Mrm (0x89 ,_b11,_r4(RS),_r4(RD) )) #define MOVLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _O_r_X (0x8b ,_r4(RD) ,MD,MB,MI,MS )) #define MOVLrm(RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _O_r_X (0x89 ,_r4(RS) ,MD,MB,MI,MS )) #define MOVLir(IM, R) (_REXLrr(0, R), _Or_L (0xb8,_r4(R) ,IM )) #define MOVLim(IM, MD, MB, MI, MS) (_REXLrm(0, MB, MI), _O_X_L (0xc7 ,MD,MB,MI,MS ,IM )) #define MOVQrr(RS, RD) (_REXQrr(RS, RD), _O_Mrm (0x89 ,_b11,_r8(RS),_r8(RD) )) #define MOVQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _O_r_X (0x8b ,_r8(RD) ,MD,MB,MI,MS )) #define MOVQrm(RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _O_r_X (0x89 ,_r8(RS) ,MD,MB,MI,MS )) #define MOVQir(IM, R) (_REXQrr(0, R), _Or_Q (0xb8,_r8(R) ,IM )) #define MOVQim(IM, MD, MB, MI, MS) (_REXQrm(0, MB, MI), _O_X_L (0xc7 ,MD,MB,MI,MS ,IM )) /* --- Unary and Multiply/Divide instructions ------------------------------ */ enum { X86_NOT = 2, X86_NEG = 3, X86_MUL = 4, X86_IMUL = 5, X86_DIV = 6, X86_IDIV = 7, }; /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define _UNARYBr(OP, RS) (_REXBrr(0, RS), _O_Mrm (0xf6 ,_b11,OP ,_r1(RS) )) #define _UNARYBm(OP, MD, MB, MI, MS) (_REXBrm(0, MB, MI), _O_r_X (0xf6 ,OP ,MD,MB,MI,MS )) #define _UNARYWr(OP, RS) (_d16(), _REXLrr(0, RS), _O_Mrm (0xf7 ,_b11,OP ,_r2(RS) )) #define _UNARYWm(OP, MD, MB, MI, MS) (_d16(), _REXLmr(MB, MI, 0), _O_r_X (0xf7 ,OP ,MD,MB,MI,MS )) #define _UNARYLr(OP, RS) (_REXLrr(0, RS), _O_Mrm (0xf7 ,_b11,OP ,_r4(RS) )) #define _UNARYLm(OP, MD, MB, MI, MS) (_REXLmr(MB, MI, 0), _O_r_X (0xf7 ,OP ,MD,MB,MI,MS )) #define _UNARYQr(OP, RS) (_REXQrr(0, RS), _O_Mrm (0xf7 ,_b11,OP ,_r8(RS) )) #define _UNARYQm(OP, MD, MB, MI, MS) (_REXQmr(MB, MI, 0), _O_r_X (0xf7 ,OP ,MD,MB,MI,MS )) #define NOTBr(RS) _UNARYBr(X86_NOT, RS) #define NOTBm(MD, MB, MI, MS) _UNARYBm(X86_NOT, MD, MB, MI, MS) #define NOTWr(RS) _UNARYWr(X86_NOT, RS) #define NOTWm(MD, MB, MI, MS) _UNARYWm(X86_NOT, MD, MB, MI, MS) #define NOTLr(RS) _UNARYLr(X86_NOT, RS) #define NOTLm(MD, MB, MI, MS) _UNARYLm(X86_NOT, MD, MB, MI, MS) #define NOTQr(RS) _UNARYQr(X86_NOT, RS) #define NOTQm(MD, MB, MI, MS) _UNARYQm(X86_NOT, MD, MB, MI, MS) #define NEGBr(RS) _UNARYBr(X86_NEG, RS) #define NEGBm(MD, MB, MI, MS) _UNARYBm(X86_NEG, MD, MB, MI, MS) #define NEGWr(RS) _UNARYWr(X86_NEG, RS) #define NEGWm(MD, MB, MI, MS) _UNARYWm(X86_NEG, MD, MB, MI, MS) #define NEGLr(RS) _UNARYLr(X86_NEG, RS) #define NEGLm(MD, MB, MI, MS) _UNARYLm(X86_NEG, MD, MB, MI, MS) #define NEGQr(RS) _UNARYQr(X86_NEG, RS) #define NEGQm(MD, MB, MI, MS) _UNARYQm(X86_NEG, MD, MB, MI, MS) #define MULBr(RS) _UNARYBr(X86_MUL, RS) #define MULBm(MD, MB, MI, MS) _UNARYBm(X86_MUL, MD, MB, MI, MS) #define MULWr(RS) _UNARYWr(X86_MUL, RS) #define MULWm(MD, MB, MI, MS) _UNARYWm(X86_MUL, MD, MB, MI, MS) #define MULLr(RS) _UNARYLr(X86_MUL, RS) #define MULLm(MD, MB, MI, MS) _UNARYLm(X86_MUL, MD, MB, MI, MS) #define MULQr(RS) _UNARYQr(X86_MUL, RS) #define MULQm(MD, MB, MI, MS) _UNARYQm(X86_MUL, MD, MB, MI, MS) #define IMULBr(RS) _UNARYBr(X86_IMUL, RS) #define IMULBm(MD, MB, MI, MS) _UNARYBm(X86_IMUL, MD, MB, MI, MS) #define IMULWr(RS) _UNARYWr(X86_IMUL, RS) #define IMULWm(MD, MB, MI, MS) _UNARYWm(X86_IMUL, MD, MB, MI, MS) #define IMULLr(RS) _UNARYLr(X86_IMUL, RS) #define IMULLm(MD, MB, MI, MS) _UNARYLm(X86_IMUL, MD, MB, MI, MS) #define IMULQr(RS) _UNARYQr(X86_IMUL, RS) #define IMULQm(MD, MB, MI, MS) _UNARYQm(X86_IMUL, MD, MB, MI, MS) #define DIVBr(RS) _UNARYBr(X86_DIV, RS) #define DIVBm(MD, MB, MI, MS) _UNARYBm(X86_DIV, MD, MB, MI, MS) #define DIVWr(RS) _UNARYWr(X86_DIV, RS) #define DIVWm(MD, MB, MI, MS) _UNARYWm(X86_DIV, MD, MB, MI, MS) #define DIVLr(RS) _UNARYLr(X86_DIV, RS) #define DIVLm(MD, MB, MI, MS) _UNARYLm(X86_DIV, MD, MB, MI, MS) #define DIVQr(RS) _UNARYQr(X86_DIV, RS) #define DIVQm(MD, MB, MI, MS) _UNARYQm(X86_DIV, MD, MB, MI, MS) #define IDIVBr(RS) _UNARYBr(X86_IDIV, RS) #define IDIVBm(MD, MB, MI, MS) _UNARYBm(X86_IDIV, MD, MB, MI, MS) #define IDIVWr(RS) _UNARYWr(X86_IDIV, RS) #define IDIVWm(MD, MB, MI, MS) _UNARYWm(X86_IDIV, MD, MB, MI, MS) #define IDIVLr(RS) _UNARYLr(X86_IDIV, RS) #define IDIVLm(MD, MB, MI, MS) _UNARYLm(X86_IDIV, MD, MB, MI, MS) #define IDIVQr(RS) _UNARYQr(X86_IDIV, RS) #define IDIVQm(MD, MB, MI, MS) _UNARYQm(X86_IDIV, MD, MB, MI, MS) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define IMULWrr(RS, RD) (_d16(), _REXLrr(RD, RS), _OO_Mrm (0x0faf ,_b11,_r2(RD),_r2(RS) )) #define IMULWmr(MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _OO_r_X (0x0faf ,_r2(RD) ,MD,MB,MI,MS )) #define IMULWirr(IM,RS,RD) (_d16(), _REXLrr(RS, RD), _Os_Mrm_sW (0x69 ,_b11,_r2(RS),_r2(RD) ,_su16(IM) )) #define IMULWimr(IM,MD,MB,MI,MS,RD) (_d16(), _REXLmr(MB, MI, RD), _Os_r_X_sW (0x69 ,_r2(RD) ,MD,MB,MI,MS ,_su16(IM) )) #define IMULLir(IM, RD) (_REXLrr(0, RD), _Os_Mrm_sL (0x69 ,_b11,_r4(RD),_r4(RD) ,IM )) #define IMULLrr(RS, RD) (_REXLrr(RD, RS), _OO_Mrm (0x0faf ,_b11,_r4(RD),_r4(RS) )) #define IMULLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0faf ,_r4(RD) ,MD,MB,MI,MS )) #define IMULQir(IM, RD) (_REXQrr(0, RD), _Os_Mrm_sL (0x69 ,_b11,_r8(RD),_r8(RD) ,IM )) #define IMULQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0faf ,_b11,_r8(RD),_r8(RS) )) #define IMULQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0faf ,_r8(RD) ,MD,MB,MI,MS )) #define IMULLirr(IM,RS,RD) (_REXLrr(RS, RD), _Os_Mrm_sL (0x69 ,_b11,_r4(RS),_r4(RD) ,IM )) #define IMULLimr(IM,MD,MB,MI,MS,RD) (_REXLmr(MB, MI, RD), _Os_r_X_sL (0x69 ,_r4(RD) ,MD,MB,MI,MS ,IM )) #define IMULQirr(IM,RS,RD) (_REXQrr(RS, RD), _Os_Mrm_sL (0x69 ,_b11,_r8(RS),_r8(RD) ,IM )) #define IMULQimr(IM,MD,MB,MI,MS,RD) (_REXQmr(MB, MI, RD), _Os_r_X_sL (0x69 ,_r8(RD) ,MD,MB,MI,MS ,IM )) /* --- Control Flow related instructions ----------------------------------- */ enum { X86_CC_O = 0x0, X86_CC_NO = 0x1, X86_CC_NAE = 0x2, X86_CC_B = 0x2, X86_CC_C = 0x2, X86_CC_AE = 0x3, X86_CC_NB = 0x3, X86_CC_NC = 0x3, X86_CC_E = 0x4, X86_CC_Z = 0x4, X86_CC_NE = 0x5, X86_CC_NZ = 0x5, X86_CC_BE = 0x6, X86_CC_NA = 0x6, X86_CC_A = 0x7, X86_CC_NBE = 0x7, X86_CC_S = 0x8, X86_CC_NS = 0x9, X86_CC_P = 0xa, X86_CC_PE = 0xa, X86_CC_NP = 0xb, X86_CC_PO = 0xb, X86_CC_L = 0xc, X86_CC_NGE = 0xc, X86_CC_GE = 0xd, X86_CC_NL = 0xd, X86_CC_LE = 0xe, X86_CC_NG = 0xe, X86_CC_G = 0xf, X86_CC_NLE = 0xf, }; /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ // FIXME: no prefix is available to encode a 32-bit operand size in 64-bit mode #define CALLm(M) _O_D32 (0xe8 ,(int)(M) ) #define _CALLLsr(R) (_REXLrr(0, R), _O_Mrm (0xff ,_b11,_b010,_r4(R) )) #define _CALLQsr(R) (_REXQrr(0, R), _O_Mrm (0xff ,_b11,_b010,_r8(R) )) #define CALLsr(R) ( X86_TARGET_64BIT ? _CALLQsr(R) : _CALLLsr(R)) #define CALLsm(D,B,I,S) (_REXLrm(0, B, I), _O_r_X (0xff ,_b010 ,(int)(D),B,I,S )) // FIXME: no prefix is available to encode a 32-bit operand size in 64-bit mode #define JMPSm(M) _O_D8 (0xeb ,(int)(M) ) #define JMPm(M) _O_D32 (0xe9 ,(int)(M) ) #define _JMPLsr(R) (_REXLrr(0, R), _O_Mrm (0xff ,_b11,_b100,_r4(R) )) #define _JMPQsr(R) (_REXQrr(0, R), _O_Mrm (0xff ,_b11,_b100,_r8(R) )) #define JMPsr(R) ( X86_TARGET_64BIT ? _JMPQsr(R) : _JMPLsr(R)) #define JMPsm(D,B,I,S) (_REXLrm(0, B, I), _O_r_X (0xff ,_b100 ,(int)(D),B,I,S )) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define JCCSii(CC, D) _O_B (0x70|(CC) ,(_sc)(int)(D) ) #define JCCSim(CC, D) _O_D8 (0x70|(CC) ,(int)(D) ) #define JOSm(D) JCCSim(0x0, D) #define JNOSm(D) JCCSim(0x1, D) #define JBSm(D) JCCSim(0x2, D) #define JNAESm(D) JCCSim(0x2, D) #define JNBSm(D) JCCSim(0x3, D) #define JAESm(D) JCCSim(0x3, D) #define JESm(D) JCCSim(0x4, D) #define JZSm(D) JCCSim(0x4, D) #define JNESm(D) JCCSim(0x5, D) #define JNZSm(D) JCCSim(0x5, D) #define JBESm(D) JCCSim(0x6, D) #define JNASm(D) JCCSim(0x6, D) #define JNBESm(D) JCCSim(0x7, D) #define JASm(D) JCCSim(0x7, D) #define JSSm(D) JCCSim(0x8, D) #define JNSSm(D) JCCSim(0x9, D) #define JPSm(D) JCCSim(0xa, D) #define JPESm(D) JCCSim(0xa, D) #define JNPSm(D) JCCSim(0xb, D) #define JPOSm(D) JCCSim(0xb, D) #define JLSm(D) JCCSim(0xc, D) #define JNGESm(D) JCCSim(0xc, D) #define JNLSm(D) JCCSim(0xd, D) #define JGESm(D) JCCSim(0xd, D) #define JLESm(D) JCCSim(0xe, D) #define JNGSm(D) JCCSim(0xe, D) #define JNLESm(D) JCCSim(0xf, D) #define JGSm(D) JCCSim(0xf, D) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define JCCii(CC, D) _OO_L (0x0f80|(CC) ,(int)(D) ) #define JCCim(CC, D) _OO_D32 (0x0f80|(CC) ,(int)(D) ) #define JOm(D) JCCim(0x0, D) #define JNOm(D) JCCim(0x1, D) #define JBm(D) JCCim(0x2, D) #define JNAEm(D) JCCim(0x2, D) #define JNBm(D) JCCim(0x3, D) #define JAEm(D) JCCim(0x3, D) #define JEm(D) JCCim(0x4, D) #define JZm(D) JCCim(0x4, D) #define JNEm(D) JCCim(0x5, D) #define JNZm(D) JCCim(0x5, D) #define JBEm(D) JCCim(0x6, D) #define JNAm(D) JCCim(0x6, D) #define JNBEm(D) JCCim(0x7, D) #define JAm(D) JCCim(0x7, D) #define JSm(D) JCCim(0x8, D) #define JNSm(D) JCCim(0x9, D) #define JPm(D) JCCim(0xa, D) #define JPEm(D) JCCim(0xa, D) #define JNPm(D) JCCim(0xb, D) #define JPOm(D) JCCim(0xb, D) #define JLm(D) JCCim(0xc, D) #define JNGEm(D) JCCim(0xc, D) #define JNLm(D) JCCim(0xd, D) #define JGEm(D) JCCim(0xd, D) #define JLEm(D) JCCim(0xe, D) #define JNGm(D) JCCim(0xe, D) #define JNLEm(D) JCCim(0xf, D) #define JGm(D) JCCim(0xf, D) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define SETCCir(CC, RD) (_REXBrr(0, RD), _OO_Mrm (0x0f90|(CC) ,_b11,_b000,_r1(RD) )) #define SETOr(RD) SETCCir(0x0,RD) #define SETNOr(RD) SETCCir(0x1,RD) #define SETBr(RD) SETCCir(0x2,RD) #define SETNAEr(RD) SETCCir(0x2,RD) #define SETNBr(RD) SETCCir(0x3,RD) #define SETAEr(RD) SETCCir(0x3,RD) #define SETEr(RD) SETCCir(0x4,RD) #define SETZr(RD) SETCCir(0x4,RD) #define SETNEr(RD) SETCCir(0x5,RD) #define SETNZr(RD) SETCCir(0x5,RD) #define SETBEr(RD) SETCCir(0x6,RD) #define SETNAr(RD) SETCCir(0x6,RD) #define SETNBEr(RD) SETCCir(0x7,RD) #define SETAr(RD) SETCCir(0x7,RD) #define SETSr(RD) SETCCir(0x8,RD) #define SETNSr(RD) SETCCir(0x9,RD) #define SETPr(RD) SETCCir(0xa,RD) #define SETPEr(RD) SETCCir(0xa,RD) #define SETNPr(RD) SETCCir(0xb,RD) #define SETPOr(RD) SETCCir(0xb,RD) #define SETLr(RD) SETCCir(0xc,RD) #define SETNGEr(RD) SETCCir(0xc,RD) #define SETNLr(RD) SETCCir(0xd,RD) #define SETGEr(RD) SETCCir(0xd,RD) #define SETLEr(RD) SETCCir(0xe,RD) #define SETNGr(RD) SETCCir(0xe,RD) #define SETNLEr(RD) SETCCir(0xf,RD) #define SETGr(RD) SETCCir(0xf,RD) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define SETCCim(CC,MD,MB,MI,MS) (_REXBrm(0, MB, MI), _OO_r_X (0x0f90|(CC) ,_b000 ,MD,MB,MI,MS )) #define SETOm(D, B, I, S) SETCCim(0x0, D, B, I, S) #define SETNOm(D, B, I, S) SETCCim(0x1, D, B, I, S) #define SETBm(D, B, I, S) SETCCim(0x2, D, B, I, S) #define SETNAEm(D, B, I, S) SETCCim(0x2, D, B, I, S) #define SETNBm(D, B, I, S) SETCCim(0x3, D, B, I, S) #define SETAEm(D, B, I, S) SETCCim(0x3, D, B, I, S) #define SETEm(D, B, I, S) SETCCim(0x4, D, B, I, S) #define SETZm(D, B, I, S) SETCCim(0x4, D, B, I, S) #define SETNEm(D, B, I, S) SETCCim(0x5, D, B, I, S) #define SETNZm(D, B, I, S) SETCCim(0x5, D, B, I, S) #define SETBEm(D, B, I, S) SETCCim(0x6, D, B, I, S) #define SETNAm(D, B, I, S) SETCCim(0x6, D, B, I, S) #define SETNBEm(D, B, I, S) SETCCim(0x7, D, B, I, S) #define SETAm(D, B, I, S) SETCCim(0x7, D, B, I, S) #define SETSm(D, B, I, S) SETCCim(0x8, D, B, I, S) #define SETNSm(D, B, I, S) SETCCim(0x9, D, B, I, S) #define SETPm(D, B, I, S) SETCCim(0xa, D, B, I, S) #define SETPEm(D, B, I, S) SETCCim(0xa, D, B, I, S) #define SETNPm(D, B, I, S) SETCCim(0xb, D, B, I, S) #define SETPOm(D, B, I, S) SETCCim(0xb, D, B, I, S) #define SETLm(D, B, I, S) SETCCim(0xc, D, B, I, S) #define SETNGEm(D, B, I, S) SETCCim(0xc, D, B, I, S) #define SETNLm(D, B, I, S) SETCCim(0xd, D, B, I, S) #define SETGEm(D, B, I, S) SETCCim(0xd, D, B, I, S) #define SETLEm(D, B, I, S) SETCCim(0xe, D, B, I, S) #define SETNGm(D, B, I, S) SETCCim(0xe, D, B, I, S) #define SETNLEm(D, B, I, S) SETCCim(0xf, D, B, I, S) #define SETGm(D, B, I, S) SETCCim(0xf, D, B, I, S) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define CMOVWrr(CC,RS,RD) (_d16(), _REXLrr(RD, RS), _OO_Mrm (0x0f40|(CC) ,_b11,_r2(RD),_r2(RS) )) #define CMOVWmr(CC,MD,MB,MI,MS,RD) (_d16(), _REXLmr(MB, MI, RD), _OO_r_X (0x0f40|(CC) ,_r2(RD) ,MD,MB,MI,MS )) #define CMOVLrr(CC,RS,RD) (_REXLrr(RD, RS), _OO_Mrm (0x0f40|(CC) ,_b11,_r4(RD),_r4(RS) )) #define CMOVLmr(CC,MD,MB,MI,MS,RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0f40|(CC) ,_r4(RD) ,MD,MB,MI,MS )) #define CMOVQrr(CC,RS,RD) (_REXQrr(RD, RS), _OO_Mrm (0x0f40|(CC) ,_b11,_r8(RD),_r8(RS) )) #define CMOVQmr(CC,MD,MB,MI,MS,RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0f40|(CC) ,_r8(RD) ,MD,MB,MI,MS )) /* --- Push/Pop instructions ----------------------------------------------- */ /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define POPWr(RD) _m32only((_d16(), _Or (0x58,_r2(RD) ))) #define POPWm(MD, MB, MI, MS) _m32only((_d16(), _O_r_X (0x8f ,_b000 ,MD,MB,MI,MS ))) #define POPLr(RD) _m32only( _Or (0x58,_r4(RD) )) #define POPLm(MD, MB, MI, MS) _m32only( _O_r_X (0x8f ,_b000 ,MD,MB,MI,MS )) #define POPQr(RD) _m64only((_REXQr(RD), _Or (0x58,_r8(RD) ))) #define POPQm(MD, MB, MI, MS) _m64only((_REXQm(MB, MI), _O_r_X (0x8f ,_b000 ,MD,MB,MI,MS ))) #define PUSHWr(RS) _m32only((_d16(), _Or (0x50,_r2(RS) ))) #define PUSHWm(MD, MB, MI, MS) _m32only((_d16(), _O_r_X (0xff, ,_b110 ,MD,MB,MI,MS ))) #define PUSHWi(IM) _m32only((_d16(), _Os_sW (0x68 ,IM ))) #define PUSHLr(RS) _m32only( _Or (0x50,_r4(RS) )) #define PUSHLm(MD, MB, MI, MS) _m32only( _O_r_X (0xff ,_b110 ,MD,MB,MI,MS )) #define PUSHLi(IM) _m32only( _Os_sL (0x68 ,IM )) #define PUSHQr(RS) _m64only((_REXQr(RS), _Or (0x50,_r8(RS) ))) #define PUSHQm(MD, MB, MI, MS) _m64only((_REXQm(MB, MI), _O_r_X (0xff ,_b110 ,MD,MB,MI,MS ))) #define PUSHQi(IM) _m64only( _Os_sL (0x68 ,IM )) #define POPA() (_d16(), _O (0x61 )) #define POPAD() _O (0x61 ) #define PUSHA() (_d16(), _O (0x60 )) #define PUSHAD() _O (0x60 ) #define POPF() _O (0x9d ) #define PUSHF() _O (0x9c ) /* --- Test instructions --------------------------------------------------- */ /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define TESTBrr(RS, RD) (_REXBrr(RS, RD), _O_Mrm (0x84 ,_b11,_r1(RS),_r1(RD) )) #define TESTBrm(RS, MD, MB, MI, MS) (_REXBrm(RS, MB, MI), _O_r_X (0x84 ,_r1(RS) ,MD,MB,MI,MS )) #define TESTBir(IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_AL) ? \ (_REXBrr(0, RD), _O_B (0xa8 ,_u8(IM))) : \ (_REXBrr(0, RD), _O_Mrm_B (0xf6 ,_b11,_b000 ,_r1(RD) ,_u8(IM))) ) #define TESTBim(IM, MD, MB, MI, MS) (_REXBrm(0, MB, MI), _O_r_X_B (0xf6 ,_b000 ,MD,MB,MI,MS ,_u8(IM))) #define TESTWrr(RS, RD) (_d16(), _REXLrr(RS, RD), _O_Mrm (0x85 ,_b11,_r2(RS),_r2(RD) )) #define TESTWrm(RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _O_r_X (0x85 ,_r2(RS) ,MD,MB,MI,MS )) #define TESTWir(IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_AX) ? \ (_d16(), _REXLrr(0, RD), _O_W (0xa9 ,_u16(IM))) : \ (_d16(), _REXLrr(0, RD), _O_Mrm_W (0xf7 ,_b11,_b000 ,_r2(RD) ,_u16(IM))) ) #define TESTWim(IM, MD, MB, MI, MS) (_d16(), _REXLrm(0, MB, MI), _O_r_X_W (0xf7 ,_b000 ,MD,MB,MI,MS ,_u16(IM))) #define TESTLrr(RS, RD) (_REXLrr(RS, RD), _O_Mrm (0x85 ,_b11,_r4(RS),_r4(RD) )) #define TESTLrm(RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _O_r_X (0x85 ,_r4(RS) ,MD,MB,MI,MS )) #define TESTLir(IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_EAX) ? \ (_REXLrr(0, RD), _O_L (0xa9 ,IM )) : \ (_REXLrr(0, RD), _O_Mrm_L (0xf7 ,_b11,_b000 ,_r4(RD) ,IM )) ) #define TESTLim(IM, MD, MB, MI, MS) (_REXLrm(0, MB, MI), _O_r_X_L (0xf7 ,_b000 ,MD,MB,MI,MS ,IM )) #define TESTQrr(RS, RD) (_REXQrr(RS, RD), _O_Mrm (0x85 ,_b11,_r8(RS),_r8(RD) )) #define TESTQrm(RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _O_r_X (0x85 ,_r8(RS) ,MD,MB,MI,MS )) #define TESTQir(IM, RD) (X86_OPTIMIZE_ALU && ((RD) == X86_RAX) ? \ (_REXQrr(0, RD), _O_L (0xa9 ,IM )) : \ (_REXQrr(0, RD), _O_Mrm_L (0xf7 ,_b11,_b000 ,_r8(RD) ,IM )) ) #define TESTQim(IM, MD, MB, MI, MS) (_REXQrm(0, MB, MI), _O_r_X_L (0xf7 ,_b000 ,MD,MB,MI,MS ,IM )) /* --- Exchange instructions ----------------------------------------------- */ /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define CMPXCHGBrr(RS, RD) (_REXBrr(RS, RD), _OO_Mrm (0x0fb0 ,_b11,_r1(RS),_r1(RD) )) #define CMPXCHGBrm(RS, MD, MB, MI, MS) (_REXBrm(RS, MB, MI), _OO_r_X (0x0fb0 ,_r1(RS) ,MD,MB,MI,MS )) #define CMPXCHGWrr(RS, RD) (_d16(), _REXLrr(RS, RD), _OO_Mrm (0x0fb1 ,_b11,_r2(RS),_r2(RD) )) #define CMPXCHGWrm(RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _OO_r_X (0x0fb1 ,_r2(RS) ,MD,MB,MI,MS )) #define CMPXCHGLrr(RS, RD) (_REXLrr(RS, RD), _OO_Mrm (0x0fb1 ,_b11,_r4(RS),_r4(RD) )) #define CMPXCHGLrm(RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _OO_r_X (0x0fb1 ,_r4(RS) ,MD,MB,MI,MS )) #define CMPXCHGQrr(RS, RD) (_REXQrr(RS, RD), _OO_Mrm (0x0fb1 ,_b11,_r8(RS),_r8(RD) )) #define CMPXCHGQrm(RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _OO_r_X (0x0fb1 ,_r8(RS) ,MD,MB,MI,MS )) #define XADDBrr(RS, RD) (_REXBrr(RS, RD), _OO_Mrm (0x0fc0 ,_b11,_r1(RS),_r1(RD) )) #define XADDBrm(RS, MD, MB, MI, MS) (_REXBrm(RS, MB, MI), _OO_r_X (0x0fc0 ,_r1(RS) ,MD,MB,MI,MS )) #define XADDWrr(RS, RD) (_d16(), _REXLrr(RS, RD), _OO_Mrm (0x0fc1 ,_b11,_r2(RS),_r2(RD) )) #define XADDWrm(RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _OO_r_X (0x0fc1 ,_r2(RS) ,MD,MB,MI,MS )) #define XADDLrr(RS, RD) (_REXLrr(RS, RD), _OO_Mrm (0x0fc1 ,_b11,_r4(RS),_r4(RD) )) #define XADDLrm(RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _OO_r_X (0x0fc1 ,_r4(RS) ,MD,MB,MI,MS )) #define XADDQrr(RS, RD) (_REXQrr(RS, RD), _OO_Mrm (0x0fc1 ,_b11,_r8(RS),_r8(RD) )) #define XADDQrm(RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _OO_r_X (0x0fc1 ,_r8(RS) ,MD,MB,MI,MS )) #define XCHGBrr(RS, RD) (_REXBrr(RS, RD), _O_Mrm (0x86 ,_b11,_r1(RS),_r1(RD) )) #define XCHGBrm(RS, MD, MB, MI, MS) (_REXBrm(RS, MB, MI), _O_r_X (0x86 ,_r1(RS) ,MD,MB,MI,MS )) #define XCHGWrr(RS, RD) (_d16(), _REXLrr(RS, RD), _O_Mrm (0x87 ,_b11,_r2(RS),_r2(RD) )) #define XCHGWrm(RS, MD, MB, MI, MS) (_d16(), _REXLrm(RS, MB, MI), _O_r_X (0x87 ,_r2(RS) ,MD,MB,MI,MS )) #define XCHGLrr(RS, RD) (_REXLrr(RS, RD), _O_Mrm (0x87 ,_b11,_r4(RS),_r4(RD) )) #define XCHGLrm(RS, MD, MB, MI, MS) (_REXLrm(RS, MB, MI), _O_r_X (0x87 ,_r4(RS) ,MD,MB,MI,MS )) #define XCHGQrr(RS, RD) (_REXQrr(RS, RD), _O_Mrm (0x87 ,_b11,_r8(RS),_r8(RD) )) #define XCHGQrm(RS, MD, MB, MI, MS) (_REXQrm(RS, MB, MI), _O_r_X (0x87 ,_r8(RS) ,MD,MB,MI,MS )) /* --- Increment/Decrement instructions ------------------------------------ */ /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define DECBm(MD, MB, MI, MS) (_REXBrm(0, MB, MI), _O_r_X (0xfe ,_b001 ,MD,MB,MI,MS )) #define DECBr(RD) (_REXBrr(0, RD), _O_Mrm (0xfe ,_b11,_b001 ,_r1(RD) )) #define DECWm(MD, MB, MI, MS) (_d16(), _REXLrm(0, MB, MI), _O_r_X (0xff ,_b001 ,MD,MB,MI,MS )) #define DECWr(RD) (! X86_TARGET_64BIT ? (_d16(), _Or (0x48,_r2(RD) )) : \ (_d16(), _REXLrr(0, RD), _O_Mrm (0xff ,_b11,_b001 ,_r2(RD) ))) #define DECLm(MD, MB, MI, MS) (_REXLrm(0, MB, MI), _O_r_X (0xff ,_b001 ,MD,MB,MI,MS )) #define DECLr(RD) (! X86_TARGET_64BIT ? _Or (0x48,_r4(RD) ) : \ (_REXLrr(0, RD), _O_Mrm (0xff ,_b11,_b001 ,_r4(RD) ))) #define DECQm(MD, MB, MI, MS) (_REXQrm(0, MB, MI), _O_r_X (0xff ,_b001 ,MD,MB,MI,MS )) #define DECQr(RD) (_REXQrr(0, RD), _O_Mrm (0xff ,_b11,_b001 ,_r8(RD) )) #define INCBm(MD, MB, MI, MS) (_REXBrm(0, MB, MI), _O_r_X (0xfe ,_b000 ,MD,MB,MI,MS )) #define INCBr(RD) (_REXBrr(0, RD), _O_Mrm (0xfe ,_b11,_b000 ,_r1(RD) )) #define INCWm(MD, MB, MI, MS) (_d16(), _REXLrm(0, MB, MI), _O_r_X (0xff ,_b000 ,MD,MB,MI,MS )) #define INCWr(RD) (! X86_TARGET_64BIT ? (_d16(), _Or (0x40,_r2(RD) )) : \ (_d16(), _REXLrr(0, RD), _O_Mrm (0xff ,_b11,_b000 ,_r2(RD) )) ) #define INCLm(MD, MB, MI, MS) (_REXLrm(0, MB, MI), _O_r_X (0xff ,_b000 ,MD,MB,MI,MS )) #define INCLr(RD) (! X86_TARGET_64BIT ? _Or (0x40,_r4(RD) ) : \ (_REXLrr(0, RD), _O_Mrm (0xff ,_b11,_b000 ,_r4(RD) ))) #define INCQm(MD, MB, MI, MS) (_REXQrm(0, MB, MI), _O_r_X (0xff ,_b000 ,MD,MB,MI,MS )) #define INCQr(RD) (_REXQrr(0, RD), _O_Mrm (0xff ,_b11,_b000 ,_r8(RD) )) /* --- Misc instructions --------------------------------------------------- */ /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define BSFWrr(RS, RD) (_d16(), _REXLrr(RD, RS), _OO_Mrm (0x0fbc ,_b11,_r2(RD),_r2(RS) )) #define BSFWmr(MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _OO_r_X (0x0fbc ,_r2(RD) ,MD,MB,MI,MS )) #define BSRWrr(RS, RD) (_d16(), _REXLrr(RD, RS), _OO_Mrm (0x0fbd ,_b11,_r2(RD),_r2(RS) )) #define BSRWmr(MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _OO_r_X (0x0fbd ,_r2(RD) ,MD,MB,MI,MS )) #define BSFLrr(RS, RD) (_REXLrr(RD, RS), _OO_Mrm (0x0fbc ,_b11,_r4(RD),_r4(RS) )) #define BSFLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0fbc ,_r4(RD) ,MD,MB,MI,MS )) #define BSRLrr(RS, RD) (_REXLrr(RD, RS), _OO_Mrm (0x0fbd ,_b11,_r4(RD),_r4(RS) )) #define BSRLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0fbd ,_r4(RD) ,MD,MB,MI,MS )) #define BSFQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0fbc ,_b11,_r8(RD),_r8(RS) )) #define BSFQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0fbc ,_r8(RD) ,MD,MB,MI,MS )) #define BSRQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0fbd ,_b11,_r8(RD),_r8(RS) )) #define BSRQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0fbd ,_r8(RD) ,MD,MB,MI,MS )) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define MOVSBWrr(RS, RD) (_d16(), _REXBLrr(RD, RS), _OO_Mrm (0x0fbe ,_b11,_r2(RD),_r1(RS) )) #define MOVSBWmr(MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _OO_r_X (0x0fbe ,_r2(RD) ,MD,MB,MI,MS )) #define MOVZBWrr(RS, RD) (_d16(), _REXBLrr(RD, RS), _OO_Mrm (0x0fb6 ,_b11,_r2(RD),_r1(RS) )) #define MOVZBWmr(MD, MB, MI, MS, RD) (_d16(), _REXLmr(MB, MI, RD), _OO_r_X (0x0fb6 ,_r2(RD) ,MD,MB,MI,MS )) #define MOVSBLrr(RS, RD) (_REXBLrr(RD, RS), _OO_Mrm (0x0fbe ,_b11,_r4(RD),_r1(RS) )) #define MOVSBLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0fbe ,_r4(RD) ,MD,MB,MI,MS )) #define MOVZBLrr(RS, RD) (_REXBLrr(RD, RS), _OO_Mrm (0x0fb6 ,_b11,_r4(RD),_r1(RS) )) #define MOVZBLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0fb6 ,_r4(RD) ,MD,MB,MI,MS )) #define MOVSBQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0fbe ,_b11,_r8(RD),_r1(RS) )) #define MOVSBQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0fbe ,_r8(RD) ,MD,MB,MI,MS )) #define MOVZBQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0fb6 ,_b11,_r8(RD),_r1(RS) )) #define MOVZBQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0fb6 ,_r8(RD) ,MD,MB,MI,MS )) #define MOVSWLrr(RS, RD) (_REXLrr(RD, RS), _OO_Mrm (0x0fbf ,_b11,_r4(RD),_r2(RS) )) #define MOVSWLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0fbf ,_r4(RD) ,MD,MB,MI,MS )) #define MOVZWLrr(RS, RD) (_REXLrr(RD, RS), _OO_Mrm (0x0fb7 ,_b11,_r4(RD),_r2(RS) )) #define MOVZWLmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _OO_r_X (0x0fb7 ,_r4(RD) ,MD,MB,MI,MS )) #define MOVSWQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0fbf ,_b11,_r8(RD),_r2(RS) )) #define MOVSWQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0fbf ,_r8(RD) ,MD,MB,MI,MS )) #define MOVZWQrr(RS, RD) (_REXQrr(RD, RS), _OO_Mrm (0x0fb7 ,_b11,_r8(RD),_r2(RS) )) #define MOVZWQmr(MD, MB, MI, MS, RD) (_REXQmr(MB, MI, RD), _OO_r_X (0x0fb7 ,_r8(RD) ,MD,MB,MI,MS )) #define MOVSLQrr(RS, RD) _m64only((_REXQrr(RD, RS), _O_Mrm (0x63 ,_b11,_r8(RD),_r4(RS) ))) #define MOVSLQmr(MD, MB, MI, MS, RD) _m64only((_REXQmr(MB, MI, RD), _O_r_X (0x63 ,_r8(RD) ,MD,MB,MI,MS ))) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define LEALmr(MD, MB, MI, MS, RD) (_REXLmr(MB, MI, RD), _O_r_X (0x8d ,_r4(RD) ,MD,MB,MI,MS )) #define BSWAPLr(R) (_REXLrr(0, R), _OOr (0x0fc8,_r4(R) )) #define BSWAPQr(R) (_REXQrr(0, R), _OOr (0x0fc8,_r8(R) )) #define CLC() _O (0xf8 ) #define STC() _O (0xf9 ) #define CMC() _O (0xf5 ) #define CLD() _O (0xfc ) #define STD() _O (0xfd ) #define CBTW() (_d16(), _O (0x98 )) #define CWTL() _O (0x98 ) #define CLTQ() _m64only(_REXQrr(0, 0), _O (0x98 )) #define CBW CBTW #define CWDE CWTL #define CDQE CLTQ #define CWTD() (_d16(), _O (0x99 )) #define CLTD() _O (0x99 ) #define CQTO() _m64only(_REXQrr(0, 0), _O (0x99 )) #define CWD CWTD #define CDQ CLTD #define CQO CQTO #define LAHF() _O (0x9f ) #define SAHF() _O (0x9e ) /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define CPUID() _OO (0x0fa2 ) #define RDTSC() _OO (0xff31 ) #define ENTERii(W, B) _O_W_B (0xc8 ,_su16(W),_su8(B)) #define LEAVE() _O (0xc9 ) #define RET() _O (0xc3 ) #define RETi(IM) _O_W (0xc2 ,_su16(IM)) #define NOP() _O (0x90 ) /* --- Media 128-bit instructions ------------------------------------------ */ enum { X86_SSE_CVTIS = 0x2a, X86_SSE_CVTSI = 0x2d, X86_SSE_UCOMI = 0x2e, X86_SSE_COMI = 0x2f, X86_SSE_SQRT = 0x51, X86_SSE_RSQRT = 0x52, X86_SSE_RCP = 0x53, X86_SSE_AND = 0x54, X86_SSE_ANDN = 0x55, X86_SSE_OR = 0x56, X86_SSE_XOR = 0x57, X86_SSE_ADD = 0x58, X86_SSE_MUL = 0x59, X86_SSE_CVTSD = 0x5a, X86_SSE_CVTDT = 0x5b, X86_SSE_SUB = 0x5c, X86_SSE_MIN = 0x5d, X86_SSE_DIV = 0x5e, X86_SSE_MAX = 0x5f, }; /* _format Opcd ,Mod ,r ,m ,mem=dsp+sib ,imm... */ #define __SSELrr(OP,RS,RSA,RD,RDA) (_REXLrr(RD, RS), _OO_Mrm (0x0f00|(OP) ,_b11,RDA(RD),RSA(RS) )) #define __SSELmr(OP,MD,MB,MI,MS,RD,RDA) (_REXLmr(MB, MI, RD), _OO_r_X (0x0f00|(OP) ,RDA(RD) ,MD,MB,MI,MS )) #define __SSELrm(OP,RS,RSA,MD,MB,MI,MS) (_REXLrm(RS, MB, MI), _OO_r_X (0x0f00|(OP) ,RSA(RS) ,MD,MB,MI,MS )) #define __SSEQrr(OP,RS,RSA,RD,RDA) (_REXQrr(RD, RS), _OO_Mrm (0x0f00|(OP) ,_b11,RDA(RD),RSA(RS) )) #define __SSEQmr(OP,MD,MB,MI,MS,RD,RDA) (_REXQmr(MB, MI, RD), _OO_r_X (0x0f00|(OP) ,RDA(RD) ,MD,MB,MI,MS )) #define __SSEQrm(OP,RS,RSA,MD,MB,MI,MS) (_REXQrm(RS, MB, MI), _OO_r_X (0x0f00|(OP) ,RSA(RS) ,MD,MB,MI,MS )) #define _SSELrr(PX,OP,RS,RSA,RD,RDA) (_B(PX), __SSELrr(OP, RS, RSA, RD, RDA)) #define _SSELmr(PX,OP,MD,MB,MI,MS,RD,RDA) (_B(PX), __SSELmr(OP, MD, MB, MI, MS, RD, RDA)) #define _SSELrm(PX,OP,RS,RSA,MD,MB,MI,MS) (_B(PX), __SSELrm(OP, RS, RSA, MD, MB, MI, MS)) #define _SSEQrr(PX,OP,RS,RSA,RD,RDA) (_B(PX), __SSEQrr(OP, RS, RSA, RD, RDA)) #define _SSEQmr(PX,OP,MD,MB,MI,MS,RD,RDA) (_B(PX), __SSEQmr(OP, MD, MB, MI, MS, RD, RDA)) #define _SSEQrm(PX,OP,RS,RSA,MD,MB,MI,MS) (_B(PX), __SSEQrm(OP, RS, RSA, MD, MB, MI, MS)) #define _SSEPSrr(OP,RS,RD) __SSELrr( OP, RS,_rX, RD,_rX) #define _SSEPSmr(OP,MD,MB,MI,MS,RD) __SSELmr( OP, MD, MB, MI, MS, RD,_rX) #define _SSEPSrm(OP,RS,MD,MB,MI,MS) __SSELrm( OP, RS,_rX, MD, MB, MI, MS) #define _SSEPDrr(OP,RS,RD) _SSELrr(0x66, OP, RS,_rX, RD,_rX) #define _SSEPDmr(OP,MD,MB,MI,MS,RD) _SSELmr(0x66, OP, MD, MB, MI, MS, RD,_rX) #define _SSEPDrm(OP,RS,MD,MB,MI,MS) _SSELrm(0x66, OP, RS,_rX, MD, MB, MI, MS) #define _SSESSrr(OP,RS,RD) _SSELrr(0xf3, OP, RS,_rX, RD,_rX) #define _SSESSmr(OP,MD,MB,MI,MS,RD) _SSELmr(0xf3, OP, MD, MB, MI, MS, RD,_rX) #define _SSESSrm(OP,RS,MD,MB,MI,MS) _SSELrm(0xf3, OP, RS,_rX, MD, MB, MI, MS) #define _SSESDrr(OP,RS,RD) _SSELrr(0xf2, OP, RS,_rX, RD,_rX) #define _SSESDmr(OP,MD,MB,MI,MS,RD) _SSELmr(0xf2, OP, MD, MB, MI, MS, RD,_rX) #define _SSESDrm(OP,RS,MD,MB,MI,MS) _SSELrm(0xf2, OP, RS,_rX, MD, MB, MI, MS) #define ADDPSrr(RS, RD) _SSEPSrr(X86_SSE_ADD, RS, RD) #define ADDPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_ADD, MD, MB, MI, MS, RD) #define ADDPDrr(RS, RD) _SSEPDrr(X86_SSE_ADD, RS, RD) #define ADDPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_ADD, MD, MB, MI, MS, RD) #define ADDSSrr(RS, RD) _SSESSrr(X86_SSE_ADD, RS, RD) #define ADDSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_ADD, MD, MB, MI, MS, RD) #define ADDSDrr(RS, RD) _SSESDrr(X86_SSE_ADD, RS, RD) #define ADDSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_ADD, MD, MB, MI, MS, RD) #define ANDNPSrr(RS, RD) _SSEPSrr(X86_SSE_ANDN, RS, RD) #define ANDNPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_ANDN, MD, MB, MI, MS, RD) #define ANDNPDrr(RS, RD) _SSEPDrr(X86_SSE_ANDN, RS, RD) #define ANDNPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_ANDN, MD, MB, MI, MS, RD) #define ANDPSrr(RS, RD) _SSEPSrr(X86_SSE_AND, RS, RD) #define ANDPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_AND, MD, MB, MI, MS, RD) #define ANDPDrr(RS, RD) _SSEPDrr(X86_SSE_AND, RS, RD) #define ANDPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_AND, MD, MB, MI, MS, RD) #define DIVPSrr(RS, RD) _SSEPSrr(X86_SSE_DIV, RS, RD) #define DIVPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_DIV, MD, MB, MI, MS, RD) #define DIVPDrr(RS, RD) _SSEPDrr(X86_SSE_DIV, RS, RD) #define DIVPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_DIV, MD, MB, MI, MS, RD) #define DIVSSrr(RS, RD) _SSESSrr(X86_SSE_DIV, RS, RD) #define DIVSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_DIV, MD, MB, MI, MS, RD) #define DIVSDrr(RS, RD) _SSESDrr(X86_SSE_DIV, RS, RD) #define DIVSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_DIV, MD, MB, MI, MS, RD) #define MAXPSrr(RS, RD) _SSEPSrr(X86_SSE_MAX, RS, RD) #define MAXPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_MAX, MD, MB, MI, MS, RD) #define MAXPDrr(RS, RD) _SSEPDrr(X86_SSE_MAX, RS, RD) #define MAXPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_MAX, MD, MB, MI, MS, RD) #define MAXSSrr(RS, RD) _SSESSrr(X86_SSE_MAX, RS, RD) #define MAXSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_MAX, MD, MB, MI, MS, RD) #define MAXSDrr(RS, RD) _SSESDrr(X86_SSE_MAX, RS, RD) #define MAXSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_MAX, MD, MB, MI, MS, RD) #define MINPSrr(RS, RD) _SSEPSrr(X86_SSE_MIN, RS, RD) #define MINPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_MIN, MD, MB, MI, MS, RD) #define MINPDrr(RS, RD) _SSEPDrr(X86_SSE_MIN, RS, RD) #define MINPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_MIN, MD, MB, MI, MS, RD) #define MINSSrr(RS, RD) _SSESSrr(X86_SSE_MIN, RS, RD) #define MINSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_MIN, MD, MB, MI, MS, RD) #define MINSDrr(RS, RD) _SSESDrr(X86_SSE_MIN, RS, RD) #define MINSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_MIN, MD, MB, MI, MS, RD) #define MULPSrr(RS, RD) _SSEPSrr(X86_SSE_MUL, RS, RD) #define MULPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_MUL, MD, MB, MI, MS, RD) #define MULPDrr(RS, RD) _SSEPDrr(X86_SSE_MUL, RS, RD) #define MULPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_MUL, MD, MB, MI, MS, RD) #define MULSSrr(RS, RD) _SSESSrr(X86_SSE_MUL, RS, RD) #define MULSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_MUL, MD, MB, MI, MS, RD) #define MULSDrr(RS, RD) _SSESDrr(X86_SSE_MUL, RS, RD) #define MULSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_MUL, MD, MB, MI, MS, RD) #define ORPSrr(RS, RD) _SSEPSrr(X86_SSE_OR, RS, RD) #define ORPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_OR, MD, MB, MI, MS, RD) #define ORPDrr(RS, RD) _SSEPDrr(X86_SSE_OR, RS, RD) #define ORPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_OR, MD, MB, MI, MS, RD) #define RCPPSrr(RS, RD) _SSEPSrr(X86_SSE_RCP, RS, RD) #define RCPPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_RCP, MD, MB, MI, MS, RD) #define RCPSSrr(RS, RD) _SSESSrr(X86_SSE_RCP, RS, RD) #define RCPSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_RCP, MD, MB, MI, MS, RD) #define RSQRTPSrr(RS, RD) _SSEPSrr(X86_SSE_RSQRT, RS, RD) #define RSQRTPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_RSQRT, MD, MB, MI, MS, RD) #define RSQRTSSrr(RS, RD) _SSESSrr(X86_SSE_RSQRT, RS, RD) #define RSQRTSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_RSQRT, MD, MB, MI, MS, RD) #define SQRTPSrr(RS, RD) _SSEPSrr(X86_SSE_SQRT, RS, RD) #define SQRTPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_SQRT, MD, MB, MI, MS, RD) #define SQRTPDrr(RS, RD) _SSEPDrr(X86_SSE_SQRT, RS, RD) #define SQRTPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_SQRT, MD, MB, MI, MS, RD) #define SQRTSSrr(RS, RD) _SSESSrr(X86_SSE_SQRT, RS, RD) #define SQRTSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_SQRT, MD, MB, MI, MS, RD) #define SQRTSDrr(RS, RD) _SSESDrr(X86_SSE_SQRT, RS, RD) #define SQRTSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_SQRT, MD, MB, MI, MS, RD) #define SUBPSrr(RS, RD) _SSEPSrr(X86_SSE_SUB, RS, RD) #define SUBPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_SUB, MD, MB, MI, MS, RD) #define SUBPDrr(RS, RD) _SSEPDrr(X86_SSE_SUB, RS, RD) #define SUBPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_SUB, MD, MB, MI, MS, RD) #define SUBSSrr(RS, RD) _SSESSrr(X86_SSE_SUB, RS, RD) #define SUBSSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_SUB, MD, MB, MI, MS, RD) #define SUBSDrr(RS, RD) _SSESDrr(X86_SSE_SUB, RS, RD) #define SUBSDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_SUB, MD, MB, MI, MS, RD) #define XORPSrr(RS, RD) _SSEPSrr(X86_SSE_XOR, RS, RD) #define XORPSmr(MD, MB, MI, MS, RD) _SSEPSmr(X86_SSE_XOR, MD, MB, MI, MS, RD) #define XORPDrr(RS, RD) _SSEPDrr(X86_SSE_XOR, RS, RD) #define XORPDmr(MD, MB, MI, MS, RD) _SSEPDmr(X86_SSE_XOR, MD, MB, MI, MS, RD) #define COMISSrr(RS, RD) _SSESSrr(X86_SSE_COMI, RS, RD) #define COMISSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_COMI, MD, MB, MI, MS, RD) #define COMISDrr(RS, RD) _SSESDrr(X86_SSE_COMI, RS, RD) #define COMISDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_COMI, MD, MB, MI, MS, RD) #define UCOMISSrr(RS, RD) _SSESSrr(X86_SSE_UCOMI, RS, RD) #define UCOMISSmr(MD, MB, MI, MS, RD) _SSESSmr(X86_SSE_UCOMI, MD, MB, MI, MS, RD) #define UCOMISDrr(RS, RD) _SSESDrr(X86_SSE_UCOMI, RS, RD) #define UCOMISDmr(MD, MB, MI, MS, RD) _SSESDmr(X86_SSE_UCOMI, MD, MB, MI, MS, RD) #define MOVAPSrr(RS, RD) _SSEPSrr(0x28, RS, RD) #define MOVAPSmr(MD, MB, MI, MS, RD) _SSEPSmr(0x28, MD, MB, MI, MS, RD) #define MOVAPSrm(RS, MD, MB, MI, MS) _SSEPSrm(0x29, RS, MD, MB, MI, MS) #define MOVAPDrr(RS, RD) _SSEPDrr(0x28, RS, RD) #define MOVAPDmr(MD, MB, MI, MS, RD) _SSEPDmr(0x28, MD, MB, MI, MS, RD) #define MOVAPDrm(RS, MD, MB, MI, MS) _SSEPDrm(0x29, RS, MD, MB, MI, MS) #define CVTPS2PIrr(RS, RD) __SSELrr( X86_SSE_CVTSI, RS,_rX, RD,_rM) #define CVTPS2PImr(MD, MB, MI, MS, RD) __SSELmr( X86_SSE_CVTSI, MD, MB, MI, MS, RD,_rM) #define CVTPD2PIrr(RS, RD) _SSELrr(0x66, X86_SSE_CVTSI, RS,_rX, RD,_rM) #define CVTPD2PImr(MD, MB, MI, MS, RD) _SSELmr(0x66, X86_SSE_CVTSI, MD, MB, MI, MS, RD,_rM) #define CVTPI2PSrr(RS, RD) __SSELrr( X86_SSE_CVTIS, RS,_rM, RD,_rX) #define CVTPI2PSmr(MD, MB, MI, MS, RD) __SSELmr( X86_SSE_CVTIS, MD, MB, MI, MS, RD,_rX) #define CVTPI2PDrr(RS, RD) _SSELrr(0x66, X86_SSE_CVTIS, RS,_rM, RD,_rX) #define CVTPI2PDmr(MD, MB, MI, MS, RD) _SSELmr(0x66, X86_SSE_CVTIS, MD, MB, MI, MS, RD,_rX) #define CVTPS2PDrr(RS, RD) __SSELrr( X86_SSE_CVTSD, RS,_rX, RD,_rX) #define CVTPS2PDmr(MD, MB, MI, MS, RD) __SSELmr( X86_SSE_CVTSD, MD, MB, MI, MS, RD,_rX) #define CVTPD2PSrr(RS, RD) _SSELrr(0x66, X86_SSE_CVTSD, RS,_rX, RD,_rX) #define CVTPD2PSmr(MD, MB, MI, MS, RD) _SSELmr(0x66, X86_SSE_CVTSD, MD, MB, MI, MS, RD,_rX) #define CVTSS2SDrr(RS, RD) _SSELrr(0xf3, X86_SSE_CVTSD, RS,_rX, RD,_rX) #define CVTSS2SDmr(MD, MB, MI, MS, RD) _SSELmr(0xf3, X86_SSE_CVTSD, MD, MB, MI, MS, RD,_rX) #define CVTSD2SSrr(RS, RD) _SSELrr(0xf2, X86_SSE_CVTSD, RS,_rX, RD,_rX) #define CVTSD2SSmr(MD, MB, MI, MS, RD) _SSELmr(0xf2, X86_SSE_CVTSD, MD, MB, MI, MS, RD,_rX) #define CVTSS2SILrr(RS, RD) _SSELrr(0xf3, X86_SSE_CVTSI, RS,_rX, RD,_r4) #define CVTSS2SILmr(MD, MB, MI, MS, RD) _SSELmr(0xf3, X86_SSE_CVTSI, MD, MB, MI, MS, RD,_r4) #define CVTSD2SILrr(RS, RD) _SSELrr(0xf2, X86_SSE_CVTSI, RS,_rX, RD,_r4) #define CVTSD2SILmr(MD, MB, MI, MS, RD) _SSELmr(0xf2, X86_SSE_CVTSI, MD, MB, MI, MS, RD,_r4) #define CVTSI2SSLrr(RS, RD) _SSELrr(0xf3, X86_SSE_CVTIS, RS,_r4, RD,_rX) #define CVTSI2SSLmr(MD, MB, MI, MS, RD) _SSELmr(0xf3, X86_SSE_CVTIS, MD, MB, MI, MS, RD,_rX) #define CVTSI2SDLrr(RS, RD) _SSELrr(0xf2, X86_SSE_CVTIS, RS,_r4, RD,_rX) #define CVTSI2SDLmr(MD, MB, MI, MS, RD) _SSELmr(0xf2, X86_SSE_CVTIS, MD, MB, MI, MS, RD,_rX) #define CVTSS2SIQrr(RS, RD) _SSEQrr(0xf3, X86_SSE_CVTSI, RS,_rX, RD,_r8) #define CVTSS2SIQmr(MD, MB, MI, MS, RD) _SSEQmr(0xf3, X86_SSE_CVTSI, MD, MB, MI, MS, RD,_r8) #define CVTSD2SIQrr(RS, RD) _SSEQrr(0xf2, X86_SSE_CVTSI, RS,_rX, RD,_r8) #define CVTSD2SIQmr(MD, MB, MI, MS, RD) _SSEQmr(0xf2, X86_SSE_CVTSI, MD, MB, MI, MS, RD,_r8) #define CVTSI2SSQrr(RS, RD) _SSEQrr(0xf3, X86_SSE_CVTIS, RS,_r8, RD,_rX) #define CVTSI2SSQmr(MD, MB, MI, MS, RD) _SSEQmr(0xf3, X86_SSE_CVTIS, MD, MB, MI, MS, RD,_rX) #define CVTSI2SDQrr(RS, RD) _SSEQrr(0xf2, X86_SSE_CVTIS, RS,_r8, RD,_rX) #define CVTSI2SDQmr(MD, MB, MI, MS, RD) _SSEQmr(0xf2, X86_SSE_CVTIS, MD, MB, MI, MS, RD,_rX) #define MOVDLXrr(RS, RD) _SSELrr(0x66, 0x6e, RS,_r4, RD,_rX) #define MOVDLXmr(MD, MB, MI, MS, RD) _SSELmr(0x66, 0x6e, MD, MB, MI, MS, RD,_rX) #define MOVDQXrr(RS, RD) _SSEQrr(0x66, 0x6e, RS,_r8, RD,_rX) #define MOVDQXmr(MD, MB, MI, MS, RD) _SSEQmr(0x66, 0x6e, MD, MB, MI, MS, RD,_rX) #define MOVDXLrr(RS, RD) _SSELrr(0x66, 0x7e, RS,_rX, RD,_r4) #define MOVDXLrm(RS, MD, MB, MI, MS) _SSELrm(0x66, 0x7e, RS,_rX, MD, MB, MI, MS) #define MOVDXQrr(RS, RD) _SSEQrr(0x66, 0x7e, RS,_rX, RD,_r8) #define MOVDXQrm(RS, MD, MB, MI, MS) _SSEQrm(0x66, 0x7e, RS,_rX, MD, MB, MI, MS) #define MOVDLMrr(RS, RD) __SSELrr( 0x6e, RS,_r4, RD,_rM) #define MOVDLMmr(MD, MB, MI, MS, RD) __SSELmr( 0x6e, MD, MB, MI, MS, RD,_rM) #define MOVDQMrr(RS, RD) __SSEQrr( 0x6e, RS,_r8, RD,_rM) #define MOVDQMmr(MD, MB, MI, MS, RD) __SSEQmr( 0x6e, MD, MB, MI, MS, RD,_rM) #define MOVDMLrr(RS, RD) __SSELrr( 0x7e, RS,_rM, RD,_r4) #define MOVDMLrm(RS, MD, MB, MI, MS) __SSELrm( 0x7e, RS,_rM, MD, MB, MI, MS) #define MOVDMQrr(RS, RD) __SSEQrr( 0x7e, RS,_rM, RD,_r8) #define MOVDMQrm(RS, MD, MB, MI, MS) __SSEQrm( 0x7e, RS,_rM, MD, MB, MI, MS) #define MOVDQ2Qrr(RS, RD) _SSELrr(0xf2, 0xd6, RS,_rX, RD,_rM) #define MOVHLPSrr(RS, RD) __SSELrr( 0x12, RS,_rX, RD,_rX) #define MOVLHPSrr(RS, RD) __SSELrr( 0x16, RS,_rX, RD,_rX) #define MOVDQArr(RS, RD) _SSELrr(0x66, 0x6f, RS,_rX, RD,_rX) #define MOVDQAmr(MD, MB, MI, MS, RD) _SSELmr(0x66, 0x6f, MD, MB, MI, MS, RD,_rX) #define MOVDQArm(RS, MD, MB, MI, MS) _SSELrm(0x66, 0x7f, RS,_rX, MD, MB, MI, MS) #define MOVDQUrr(RS, RD) _SSELrr(0xf3, 0x6f, RS,_rX, RD,_rX) #define MOVDQUmr(MD, MB, MI, MS, RD) _SSELmr(0xf3, 0x6f, MD, MB, MI, MS, RD,_rX) #define MOVDQUrm(RS, MD, MB, MI, MS) _SSELrm(0xf3, 0x7f, RS,_rX, MD, MB, MI, MS) #define MOVHPDmr(MD, MB, MI, MS, RD) _SSELmr(0x66, 0x16, MD, MB, MI, MS, RD,_rX) #define MOVHPDrm(RS, MD, MB, MI, MS) _SSELrm(0x66, 0x17, RS,_rX, MD, MB, MI, MS) #define MOVHPSmr(MD, MB, MI, MS, RD) __SSELmr( 0x16, MD, MB, MI, MS, RD,_rX) #define MOVHPSrm(RS, MD, MB, MI, MS) __SSELrm( 0x17, RS,_rX, MD, MB, MI, MS) #define MOVLPDmr(MD, MB, MI, MS, RD) _SSELmr(0x66, 0x12, MD, MB, MI, MS, RD,_rX) #define MOVLPDrm(RS, MD, MB, MI, MS) _SSELrm(0x66, 0x13, RS,_rX, MD, MB, MI, MS) #define MOVLPSmr(MD, MB, MI, MS, RD) __SSELmr( 0x12, MD, MB, MI, MS, RD,_rX) #define MOVLPSrm(RS, MD, MB, MI, MS) __SSELrm( 0x13, RS,_rX, MD, MB, MI, MS) /* --- FLoating-Point instructions ----------------------------------------- */ #define _ESCmi(D,B,I,S,OP) (_REXLrm(0,B,I), _O_r_X(0xd8|(OP & 7), (OP >> 3), D,B,I,S)) #define FLDr(R) _OOr(0xd9c0,_rN(R)) #define FLDLm(D,B,I,S) _ESCmi(D,B,I,S,005) #define FLDSm(D,B,I,S) _ESCmi(D,B,I,S,001) #define FLDTm(D,B,I,S) _ESCmi(D,B,I,S,053) #define FSTr(R) _OOr(0xddd0,_rN(R)) #define FSTSm(D,B,I,S) _ESCmi(D,B,I,S,021) #define FSTLm(D,B,I,S) _ESCmi(D,B,I,S,025) #define FSTPr(R) _OOr(0xddd8,_rN(R)) #define FSTPSm(D,B,I,S) _ESCmi(D,B,I,S,031) #define FSTPLm(D,B,I,S) _ESCmi(D,B,I,S,035) #define FSTPTm(D,B,I,S) _ESCmi(D,B,I,S,073) #define FADDr0(R) _OOr(0xd8c0,_rN(R)) #define FADD0r(R) _OOr(0xdcc0,_rN(R)) #define FADDP0r(R) _OOr(0xdec0,_rN(R)) #define FADDSm(D,B,I,S) _ESCmi(D,B,I,S,000) #define FADDLm(D,B,I,S) _ESCmi(D,B,I,S,004) #define FSUBSm(D,B,I,S) _ESCmi(D,B,I,S,040) #define FSUBLm(D,B,I,S) _ESCmi(D,B,I,S,044) #define FSUBr0(R) _OOr(0xd8e0,_rN(R)) #define FSUB0r(R) _OOr(0xdce8,_rN(R)) #define FSUBP0r(R) _OOr(0xdee8,_rN(R)) #define FSUBRr0(R) _OOr(0xd8e8,_rN(R)) #define FSUBR0r(R) _OOr(0xdce0,_rN(R)) #define FSUBRP0r(R) _OOr(0xdee0,_rN(R)) #define FSUBRSm(D,B,I,S) _ESCmi(D,B,I,S,050) #define FSUBRLm(D,B,I,S) _ESCmi(D,B,I,S,054) #define FMULr0(R) _OOr(0xd8c8,_rN(R)) #define FMUL0r(R) _OOr(0xdcc8,_rN(R)) #define FMULP0r(R) _OOr(0xdec8,_rN(R)) #define FMULSm(D,B,I,S) _ESCmi(D,B,I,S,010) #define FMULLm(D,B,I,S) _ESCmi(D,B,I,S,014) #define FDIVr0(R) _OOr(0xd8f0,_rN(R)) #define FDIV0r(R) _OOr(0xdcf8,_rN(R)) #define FDIVP0r(R) _OOr(0xdef8,_rN(R)) #define FDIVSm(D,B,I,S) _ESCmi(D,B,I,S,060) #define FDIVLm(D,B,I,S) _ESCmi(D,B,I,S,064) #define FDIVRr0(R) _OOr(0xd8f8,_rN(R)) #define FDIVR0r(R) _OOr(0xdcf0,_rN(R)) #define FDIVRP0r(R) _OOr(0xdef0,_rN(R)) #define FDIVRSm(D,B,I,S) _ESCmi(D,B,I,S,070) #define FDIVRLm(D,B,I,S) _ESCmi(D,B,I,S,074) #define FCMOVBr0(R) _OOr(0xdac0,_rN(R)) #define FCMOVBEr0(R) _OOr(0xdad0,_rN(R)) #define FCMOVEr0(R) _OOr(0xdac8,_rN(R)) #define FCMOVNBr0(R) _OOr(0xdbc0,_rN(R)) #define FCMOVNBEr0(R) _OOr(0xdbd0,_rN(R)) #define FCMOVNEr0(R) _OOr(0xdbc8,_rN(R)) #define FCMOVNUr0(R) _OOr(0xdbd8,_rN(R)) #define FCMOVUr0(R) _OOr(0xdad8,_rN(R)) #define FCOMIr0(R) _OOr(0xdbf0,_rN(R)) #define FCOMIPr0(R) _OOr(0xdff0,_rN(R)) #define FCOMr(R) _OOr(0xd8d0,_rN(R)) #define FCOMSm(D,B,I,S) _ESCmi(D,B,I,S,020) #define FCOMLm(D,B,I,S) _ESCmi(D,B,I,S,024) #define FCOMPr(R) _OOr(0xd8d8,_rN(R)) #define FCOMPSm(D,B,I,S) _ESCmi(D,B,I,S,030) #define FCOMPLm(D,B,I,S) _ESCmi(D,B,I,S,034) #define FUCOMIr0(R) _OOr(0xdbe8,_rN(R)) #define FUCOMIPr0(R) _OOr(0xdfe8,_rN(R)) #define FUCOMPr(R) _OOr(0xdde8,_rN(R)) #define FUCOMr(R) _OOr(0xdde0,_rN(R)) #define FIADDLm(D,B,I,S) _ESCmi(D,B,I,S,002) #define FICOMLm(D,B,I,S) _ESCmi(D,B,I,S,022) #define FICOMPLm(D,B,I,S) _ESCmi(D,B,I,S,032) #define FIDIVLm(D,B,I,S) _ESCmi(D,B,I,S,062) #define FIDIVRLm(D,B,I,S) _ESCmi(D,B,I,S,072) #define FILDLm(D,B,I,S) _ESCmi(D,B,I,S,003) #define FILDQm(D,B,I,S) _ESCmi(D,B,I,S,057) #define FIMULLm(D,B,I,S) _ESCmi(D,B,I,S,012) #define FISTLm(D,B,I,S) _ESCmi(D,B,I,S,023) #define FISTPLm(D,B,I,S) _ESCmi(D,B,I,S,033) #define FISTPQm(D,B,I,S) _ESCmi(D,B,I,S,077) #define FISUBLm(D,B,I,S) _ESCmi(D,B,I,S,042) #define FISUBRLm(D,B,I,S) _ESCmi(D,B,I,S,052) #define FREEr(R) _OOr(0xddc0,_rN(R)) #define FXCHr(R) _OOr(0xd9c8,_rN(R)) #endif /* X86_RTASM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu.h000066400000000000000000000337731504763705000245400ustar00rootroot00000000000000/* * compiler/compemu.h - Public interface and definitions * * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> IA-32 and AMD64 * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * ARAnyM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ARAnyM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ARAnyM; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #ifndef COMPEMU_H #define COMPEMU_H #include "sysconfig.h" #include "newcpu.h" #ifdef UAE #ifdef CPU_64_BIT typedef uae_u64 uintptr; typedef uae_s64 intptr; #else typedef uae_u32 uintptr; #endif /* FIXME: cpummu.cpp also checks for USE_JIT, possibly others */ #define USE_JIT #endif #define JITPTR (uae_u32)(uintptr) #ifdef USE_JIT #ifdef JIT_DEBUG /* dump some information (m68k block, x86 block addresses) about the compiler state */ extern void compiler_dumpstate(void); #endif /* Now that we do block chaining, and also have linked lists on each tag, TAGMASK can be much smaller and still do its job. Saves several megs of memory! */ #define TAGMASK 0x0000ffff #define TAGSIZE (TAGMASK+1) #define MAXRUN 1024 #define cacheline(x) (((uintptr)x)&TAGMASK) extern uae_u8* start_pc_p; extern uae_u32 start_pc; struct blockinfo_t; struct cpu_history { uae_u16* location; #ifdef UAE uae_u8 specmem; #endif }; union cacheline { cpuop_func *handler; blockinfo_t * bi; }; /* Use new spill/reload strategy when calling external functions */ #define USE_OPTIMIZED_CALLS 0 #if USE_OPTIMIZED_CALLS #error implementation in progress #endif /* (gb) When on, this option can save save up to 30% compilation time * when many lazy flushes occur (e.g. apps in MacOS 8.x). */ #define USE_SEPARATE_BIA 1 /* Use chain of checksum_info_t to compute the block checksum */ #define USE_CHECKSUM_INFO 1 /* Use code inlining, aka follow-up of constant jumps */ #define USE_INLINING 1 /* Inlining requires the chained checksuming information */ #if USE_INLINING #undef USE_CHECKSUM_INFO #define USE_CHECKSUM_INFO 1 #endif /* Does flush_icache_range() only check for blocks falling in the requested range? */ #define LAZY_FLUSH_ICACHE_RANGE 0 #define USE_F_ALIAS 1 #define USE_OFFSET 1 #define COMP_DEBUG 1 #if COMP_DEBUG #define Dif(x) if (x) #else #define Dif(x) if (0) #endif #define SCALE 2 #define BYTES_PER_INST 10240 /* paranoid ;-) */ #if defined(CPU_arm) #define LONGEST_68K_INST 256 /* The number of bytes the longest possible 68k instruction takes */ #else #define LONGEST_68K_INST 16 /* The number of bytes the longest possible 68k instruction takes */ #endif #define MAX_CHECKSUM_LEN 2048 /* The maximum size we calculate checksums for. Anything larger will be flushed unconditionally even with SOFT_FLUSH */ #define MAX_HOLD_BI 3 /* One for the current block, and up to two for jump targets */ #define INDIVIDUAL_INST 0 #define FLAG_X 0x0010 #define FLAG_N 0x0008 #define FLAG_Z 0x0004 #define FLAG_V 0x0002 #define FLAG_C 0x0001 #define FLAG_CZNV (FLAG_C | FLAG_Z | FLAG_N | FLAG_V) #define FLAG_ALL (FLAG_C | FLAG_Z | FLAG_N | FLAG_V | FLAG_X) #define FLAG_ZNV (FLAG_Z | FLAG_N | FLAG_V) #define KILLTHERAT 1 /* Set to 1 to avoid some partial_rat_stalls */ #if defined(CPU_arm) #define USE_DATA_BUFFER #define N_REGS 13 /* really 16, but 13 to 15 are SP, LR, PC */ #else #if defined(CPU_x86_64) #define N_REGS 16 /* really only 15, but they are numbered 0-3,5-15 */ #else #define N_REGS 8 /* really only 7, but they are numbered 0,1,2,3,5,6,7 */ #endif #endif #define N_FREGS 6 /* That leaves us two positions on the stack to play with */ /* Functions exposed to newcpu, or to what was moved from newcpu.c to * compemu_support.c */ extern void compiler_init(void); extern void compiler_exit(void); extern bool compiler_use_jit(void); extern void flush(int save_regs); void flush_reg(int reg); extern void set_target(uae_u8* t); extern uae_u8* get_target(void); #ifdef UAE extern void build_comp(void); #endif extern void set_cache_state(int enabled); extern int get_cache_state(void); extern uae_u32 get_jitted_size(void); #ifdef JIT extern void (*flush_icache)(int); #endif extern void alloc_cache(void); extern int check_for_cache_miss(void); /* JIT FPU compilation */ struct jit_disable_opcodes { bool fbcc; bool fdbcc; bool fscc; bool ftrapcc; bool fsave; bool frestore; bool fmove; bool fmovem; bool fmovec; /* for move control register */ bool fmovecr; /* for move from constant rom */ bool fint; bool fsinh; bool fintrz; bool fsqrt; bool flognp1; bool fetoxm1; bool ftanh; bool fatan; bool fasin; bool fatanh; bool fsin; bool ftan; bool fetox; bool ftwotox; bool ftentox; bool flogn; bool flog10; bool flog2; bool fabs; bool fcosh; bool fneg; bool facos; bool fcos; bool fgetexp; bool fgetman; bool fdiv; bool fmod; bool fadd; bool fmul; bool fsgldiv; bool frem; bool fscale; bool fsglmul; bool fsub; bool fsincos; bool fcmp; bool ftst; }; extern struct jit_disable_opcodes jit_disable; extern void comp_fpp_opp (uae_u32 opcode, uae_u16 extra); extern void comp_fbcc_opp (uae_u32 opcode); extern void comp_fscc_opp (uae_u32 opcode, uae_u16 extra); void comp_fdbcc_opp (uae_u32 opcode, uae_u16 extra); void comp_ftrapcc_opp (uae_u32 opcode, uaecptr oldpc); void comp_fsave_opp (uae_u32 opcode); void comp_frestore_opp (uae_u32 opcode); extern uae_u32 needed_flags; extern uae_u8* comp_pc_p; extern void* pushall_call_handler; #define VREGS 32 #define VFREGS 16 #define INMEM 1 #define CLEAN 2 #define DIRTY 3 #define UNDEF 4 #define ISCONST 5 typedef struct { uae_u32* mem; uae_u32 val; uae_u8 is_swapped; uae_u8 status; uae_s8 realreg; /* gb-- realreg can hold -1 */ uae_u8 realind; /* The index in the holds[] array */ uae_u8 needflush; uae_u8 validsize; uae_u8 dirtysize; uae_u8 dummy; } reg_status; typedef struct { uae_u32* mem; double val; uae_u8 status; uae_s8 realreg; /* gb-- realreg can hold -1 */ uae_u8 realind; uae_u8 needflush; } freg_status; #define SP_REG 15 #define PC_P 16 #define FLAGX 17 #define FLAGTMP 18 #define NEXT_HANDLER 19 #define S1 20 #define S2 21 #define S3 22 #define S4 23 #define S5 24 #define S6 25 #define S7 26 #define S8 27 #define S9 28 #define S10 29 #define S11 30 #define S12 31 #define FP_RESULT 8 #define FS1 9 #define FS2 10 #define FS3 11 typedef struct { uae_u32 touched; uae_s8 holds[VREGS]; uae_u8 nholds; uae_u8 canbyte; uae_u8 canword; uae_u8 locked; } n_status; typedef struct { uae_u32 touched; uae_s8 holds[VFREGS]; uae_u8 nholds; uae_u8 locked; } fn_status; /* For flag handling */ #define NADA 1 #define TRASH 2 #define VALID 3 /* needflush values */ #define NF_SCRATCH 0 #define NF_TOMEM 1 #define NF_HANDLER 2 typedef struct { /* Integer part */ reg_status state[VREGS]; n_status nat[N_REGS]; uae_u32 flags_on_stack; uae_u32 flags_in_flags; uae_u32 flags_are_important; /* FPU part */ freg_status fate[VFREGS]; fn_status fat[N_FREGS]; /* x86 FPU part */ uae_s8 spos[N_FREGS]; uae_s8 onstack[6]; uae_s8 tos; } bigstate; typedef struct { /* Integer part */ uae_s8 virt[VREGS]; uae_s8 nat[N_REGS]; } smallstate; extern int touchcnt; #define IMM uae_s32 #define RR1 uae_u32 #define RR2 uae_u32 #define RR4 uae_u32 /* R1, R2, R4 collides with ARM registers defined in ucontext #define R1 uae_u32 #define R2 uae_u32 #define R4 uae_u32 */ #define W1 uae_u32 #define W2 uae_u32 #define W4 uae_u32 #define RW1 uae_u32 #define RW2 uae_u32 #define RW4 uae_u32 #define MEMR uae_u32 #define MEMW uae_u32 #define MEMRW uae_u32 #define MEMPTR uintptr #define MEMPTRR MEMPTR #define MEMPTRW MEMPTR #define MEMPTRRW MEMPTR #define FW uae_u32 #define FR uae_u32 #define FRW uae_u32 #define MIDFUNC(nargs,func,args) void func args #define COMPCALL(func) func #define LOWFUNC(flags,mem,nargs,func,args) static inline void func args /* What we expose to the outside */ #define DECLARE_MIDFUNC(func) extern void func #if defined(CPU_arm) #include "compemu_midfunc_arm.h" #if defined(USE_JIT2) #include "compemu_midfunc_arm2.h" #endif #endif #if defined(CPU_i386) || defined(CPU_x86_64) #include "compemu_midfunc_x86.h" #endif #undef DECLARE_MIDFUNC extern int failure; #define FAIL(x) do { failure|=x; } while (0) /* Convenience functions exposed to gencomp */ extern uae_u32 m68k_pc_offset; extern void readbyte(int address, int dest, int tmp); extern void readword(int address, int dest, int tmp); extern void readlong(int address, int dest, int tmp); extern void writebyte(int address, int source, int tmp); extern void writeword(int address, int source, int tmp); extern void writelong(int address, int source, int tmp); extern void writeword_clobber(int address, int source, int tmp); extern void writelong_clobber(int address, int source, int tmp); extern void get_n_addr(int address, int dest, int tmp); extern void get_n_addr_jmp(int address, int dest, int tmp); extern void calc_disp_ea_020(int base, uae_u32 dp, int target, int tmp); /* Set native Z flag only if register is zero */ extern void set_zero(int r, int tmp); extern int kill_rodent(int r); #define SYNC_PC_OFFSET 100 extern void sync_m68k_pc(void); extern uae_u32 get_const(int r); extern int is_const(int r); extern void register_branch(uae_u32 not_taken, uae_u32 taken, uae_u8 cond); void compemu_make_sr(int sr, int tmp); void compemu_enter_super(int sr); void compemu_exc_make_frame(int format, int sr, int currpc, int nr, int tmp); void compemu_bkpt(void); extern bool disasm_this_inst; #define comp_get_ibyte(o) do_get_mem_byte((uae_u8 *)(comp_pc_p + (o) + 1)) #define comp_get_iword(o) do_get_mem_word((uae_u16 *)(comp_pc_p + (o))) #define comp_get_ilong(o) do_get_mem_long((uae_u32 *)(comp_pc_p + (o))) struct blockinfo_t; typedef struct dep_t { uae_u32* jmp_off; struct blockinfo_t* target; struct blockinfo_t* source; struct dep_t** prev_p; struct dep_t* next; } dependency; typedef struct checksum_info_t { uae_u8 *start_p; uae_u32 length; struct checksum_info_t *next; } checksum_info; typedef struct blockinfo_t { uae_s32 count; cpuop_func* direct_handler_to_use; cpuop_func* handler_to_use; /* The direct handler does not check for the correct address */ cpuop_func* handler; cpuop_func* direct_handler; cpuop_func* direct_pen; cpuop_func* direct_pcc; #ifdef UAE uae_u8* nexthandler; #endif uae_u8* pc_p; uae_u32 c1; uae_u32 c2; #if USE_CHECKSUM_INFO checksum_info *csi; #else uae_u32 len; uae_u32 min_pcp; #endif struct blockinfo_t* next_same_cl; struct blockinfo_t** prev_same_cl_p; struct blockinfo_t* next; struct blockinfo_t** prev_p; uae_u8 optlevel; uae_u8 needed_flags; uae_u8 status; uae_u8 havestate; dependency dep[2]; /* Holds things we depend on */ dependency* deplist; /* List of things that depend on this */ smallstate env; #ifdef JIT_DEBUG /* (gb) size of the compiled block (direct handler) */ uae_u32 direct_handler_size; #endif } blockinfo; #define BI_INVALID 0 #define BI_ACTIVE 1 #define BI_NEED_RECOMP 2 #define BI_NEED_CHECK 3 #define BI_CHECKING 4 #define BI_COMPILING 5 #define BI_FINALIZING 6 void execute_normal(void); void exec_nostats(void); void do_nothing(void); #else static inline void flush_icache(void) { } #endif /* !USE_JIT */ #ifdef UAE #define JIT_EXCEPTION_HANDLER // #define JIT_ALWAYS_DISTRUST /* ARAnyM uses fpu_register name, used in scratch_t */ /* FIXME: check that no ARAnyM code assumes different floating point type */ typedef fptype fpu_register; extern void compile_block(cpu_history* pc_hist, int blocklen, int totcyles); #define MAXCYCLES (1000 * CYCLE_UNIT) #define scaled_cycles(x) (currprefs.m68k_speed<0?(((x)/SCALE)?(((x)/SCALE (uintptr_t) 0xffffffff) { jit_abort("JIT: 64-bit pointer (0x%llx) at %s:%d (fatal)", (unsigned long long)address, file, line); } return (uae_u32) address; } #define uae_p32(x) (check_uae_p32((uintptr)(x), __FILE__, __LINE__)) #else #define uae_p32(x) ((uae_u32)(x)) #endif #endif /* COMPEMU_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_fpp.c000066400000000000000000001266131504763705000253740ustar00rootroot00000000000000/* * compiler/compemu_fpp.cpp - Dynamic translation of FPU instructions * * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> IA-32 and AMD64 * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * ARAnyM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ARAnyM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ARAnyM; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * UAE - The Un*x Amiga Emulator * * MC68881 emulation * * Copyright 1996 Herman ten Brugge * Adapted for JIT compilation (c) Bernd Meyer, 2000 */ #include "sysdeps.h" #ifdef JIT #include #include #include #include "memory-uae.h" #include "readcpu.h" #include "newcpu.h" #include "compemu.h" //#include "fpu/fpu.h" //#include "fpu/flags.h" //#include "fpu/exceptions.h" //#include "fpu/rounding.h" #define DEBUG 0 #include "debug.h" struct jit_disable_opcodes jit_disable; #if defined(USE_LONG_DOUBLE) || defined(USE_QUAD_DOUBLE) #define LD(x) x ## L #else #define LD(x) x #endif // gb-- WARNING: get_fpcr() and set_fpcr() support is experimental #define HANDLE_FPCR 0 // - IEEE-based fpu core must be used #if defined(FPU_IEEE) # define CAN_HANDLE_FPCR #endif // - Generic rounding mode and precision modes are supported if set together #if defined(FPU_USE_GENERIC_ROUNDING_MODE) && defined(FPU_USE_GENERIC_ROUNDING_PRECISION) # define CAN_HANDLE_FPCR #endif // - X86 rounding mode and precision modes are *not* supported but might work (?!) #if defined(FPU_USE_X86_ROUNDING_MODE) && defined(FPU_USE_X86_ROUNDING_PRECISION) # define CAN_HANDLE_FPCR #endif #if HANDLE_FPCR && !defined(CAN_HANDLE_FPCR) # warning "Can't handle FPCR, will FAIL(1) at runtime" # undef HANDLE_FPCR # define HANDLE_FPCR 0 #endif //#define STATIC_INLINE static inline #define MAKE_FPSR(r) do { fmov_rr(FP_RESULT,r); } while (0) #if 0 #define delay nop() ;nop() #define delay2 nop() ;nop() #else #define delay #define delay2 #endif #define UNKNOWN_EXTRA 0xFFFFFFFF #if 0 static void fpuop_illg(uae_u32 opcode, uae_u32 /* extra */) { /* if (extra == UNKNOWN_EXTRA) printf("FPU opcode %x, extra UNKNOWN_EXTRA\n",opcode & 0xFFFF); else printf("FPU opcode %x, extra %x\n",opcode & 0xFFFF,extra & 0xFFFF); */ op_illg(opcode); } #endif uae_s32 temp_fp[4]; /* To convert between FP/integer */ /* return register number, or -1 for failure */ STATIC_INLINE int get_fp_value(uae_u32 opcode, uae_u16 extra) { int size; int mode; int reg; uae_u32 ad = 0; static int const sz1[8] = { 4, 4, 12, 12, 2, 8, 1, 0 }; static int const sz2[8] = { 4, 4, 12, 12, 2, 8, 2, 0 }; if ((extra & 0x4000) == 0) { return ((extra >> 10) & 7); } mode = (opcode >> 3) & 7; reg = opcode & 7; size = (extra >> 10) & 7; switch (mode) { case 0: /* Dn */ switch (size) { case 6: /* byte */ sign_extend_8_rr(S1, reg); mov_l_mr(JITPTR temp_fp, S1); delay2; fmovi_rm(FS1, JITPTR temp_fp); return FS1; case 4: /* word */ sign_extend_16_rr(S1, reg); mov_l_mr(JITPTR temp_fp, S1); delay2; fmovi_rm(FS1, JITPTR temp_fp); return FS1; case 0: /* long */ mov_l_mr(JITPTR temp_fp, reg); delay2; fmovi_rm(FS1, JITPTR temp_fp); return FS1; case 1: /* single precision */ mov_l_mr(JITPTR temp_fp, reg); delay2; fmovs_rm(FS1, JITPTR temp_fp); return FS1; default: return -1; } return -1; /* Should be unreachable */ case 1: /* An */ return -1; /* Genuine invalid instruction */ default: break; } /* OK, we *will* have to load something from an address. Let's make sure we know how to handle that, or quit early --- i.e. *before* we do any postincrement/predecrement that we may regret */ switch (size) { case 0: /* long */ case 1: /* single precision */ case 2: /* extended precision */ case 4: /* word */ case 5: /* double precision */ case 6: /* byte */ break; case 3: /* packed decimal static */ default: return -1; } switch (mode) { case 2: /* (An) */ ad = S1; /* We will change it, anyway ;-) */ mov_l_rr(ad, reg + 8); break; case 3: /* (An)+ */ ad = S1; mov_l_rr(ad, reg + 8); lea_l_brr(reg + 8, reg + 8, (reg == 7 ? sz2[size] : sz1[size])); break; case 4: /* -(An) */ ad = S1; lea_l_brr(reg + 8, reg + 8, -(reg == 7 ? sz2[size] : sz1[size])); mov_l_rr(ad, reg + 8); break; case 5: /* d16(An) */ { uae_u32 off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; mov_l_rr(ad, reg + 8); lea_l_brr(ad, ad, off); } break; case 6: /* d8(An,Xn) */ { uae_u32 dp = comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; calc_disp_ea_020(reg + 8, dp, ad, S2); } break; case 7: switch (reg) { case 0: /* abs.w */ { uae_u32 off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; mov_l_ri(ad, off); } break; case 1: /* abs.l */ { uae_u32 off = comp_get_ilong((m68k_pc_offset += 4) - 4); ad = S1; mov_l_ri(ad, off); } break; case 2: /* d16(pc) */ { uae_u32 address = (uae_u32)(start_pc + ((char *) comp_pc_p - (char *) start_pc_p) + m68k_pc_offset); uae_s32 PC16off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; mov_l_ri(ad, address + PC16off); } break; case 3: /* d8(pc,Xn) */ return -1; case 4: /* #imm */ { uae_u32 address = (uae_u32)(start_pc + ((char *) comp_pc_p - (char *) start_pc_p) + m68k_pc_offset); ad = S1; // Immediate addressing mode && Operation Length == Byte -> // Use the low-order byte of the extension word. if (size == 6) address++; mov_l_ri(ad, address); m68k_pc_offset += sz2[size]; } break; default: return -1; } } switch (size) { case 0: /* long */ readlong(ad, S2, S3); mov_l_mr(JITPTR temp_fp, S2); delay2; fmovi_rm(FS1, JITPTR temp_fp); break; case 1: /* single precision */ readlong(ad, S2, S3); mov_l_mr(JITPTR temp_fp, S2); delay2; fmovs_rm(FS1, JITPTR temp_fp); break; case 2: /* extended precision */ readword(ad, S2, S3); mov_w_mr((JITPTR temp_fp) + 8, S2); add_l_ri(ad, 4); readlong(ad, S2, S3); // always set the explicit integer bit. or_l_ri(S2, 0x80000000); mov_l_mr(JITPTR (temp_fp) + 4, S2); add_l_ri(ad, 4); readlong(ad, S2, S3); mov_l_mr(JITPTR (temp_fp), S2); delay2; fmov_ext_rm(FS1, JITPTR (temp_fp)); break; case 3: /* packed decimal static */ return -1; /* Some silly "packed" stuff */ case 4: /* word */ readword(ad, S2, S3); sign_extend_16_rr(S2, S2); mov_l_mr(JITPTR temp_fp, S2); delay2; fmovi_rm(FS1, JITPTR temp_fp); break; case 5: /* double precision */ readlong(ad, S2, S3); mov_l_mr((JITPTR temp_fp) + 4, S2); add_l_ri(ad, 4); readlong(ad, S2, S3); mov_l_mr(JITPTR (temp_fp), S2); delay2; fmov_rm(FS1, JITPTR (temp_fp)); break; case 6: /* byte */ readbyte(ad, S2, S3); sign_extend_8_rr(S2, S2); mov_l_mr(JITPTR temp_fp, S2); delay2; fmovi_rm(FS1, JITPTR temp_fp); break; default: return -1; } return FS1; } static struct { fpu_register b[2]; fpu_register w[2]; fpu_register l[2]; } clamp_bounds = { { -128.0, 127.0 }, { -32768.0, 32767.0 }, { -2147483648.0, 2147483647.0 } }; /* return of -1 means failure, >=0 means OK */ STATIC_INLINE int put_fp_value(int val, uae_u32 opcode, uae_u16 extra) { int size; int mode; int reg; uae_u32 ad; static int const sz1[8] = { 4, 4, 12, 12, 2, 8, 1, 0 }; static int const sz2[8] = { 4, 4, 12, 12, 2, 8, 2, 0 }; if ((extra & 0x4000) == 0) { const int dest_reg = (extra >> 10) & 7; fmov_rr(dest_reg, val); // gb-- status register is affected MAKE_FPSR(dest_reg); return 0; } mode = (opcode >> 3) & 7; reg = opcode & 7; size = (extra >> 10) & 7; ad = (uae_u32) -1; switch (mode) { case 0: /* Dn */ switch (size) { case 6: /* byte */ fmovi_mrb(JITPTR temp_fp, val, clamp_bounds.b); delay; mov_b_rm(reg, JITPTR temp_fp); return 0; case 4: /* word */ fmovi_mrb(JITPTR temp_fp, val, clamp_bounds.w); delay; mov_w_rm(reg, JITPTR temp_fp); return 0; case 0: /* long */ fmovi_mrb(JITPTR temp_fp, val, clamp_bounds.l); fmovi_mr(JITPTR temp_fp, val); delay; mov_l_rm(reg, JITPTR temp_fp); return 0; case 1: /* single precision */ fmovs_mr(JITPTR temp_fp, val); delay; mov_l_rm(reg, JITPTR temp_fp); return 0; default: return -1; } case 1: /* An */ return -1; /* genuine invalid instruction */ default: break; } /* Let's make sure we get out *before* doing something silly if we can't handle the size */ switch (size) { case 0: /* long */ case 1: /* single precision */ case 2: /* extended precision */ case 4: /* word */ case 5: /* double precision */ case 6: /* byte */ break; case 3: /* packed decimal static */ default: return -1; } switch (mode) { case 2: /* (An) */ ad = S1; mov_l_rr(ad, reg + 8); break; case 3: /* (An)+ */ ad = S1; mov_l_rr(ad, reg + 8); lea_l_brr(reg + 8, reg + 8, (reg == 7 ? sz2[size] : sz1[size])); break; case 4: /* -(An) */ ad = S1; lea_l_brr(reg + 8, reg + 8, -(reg == 7 ? sz2[size] : sz1[size])); mov_l_rr(ad, reg + 8); break; case 5: /* d16(An) */ { uae_u32 off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; mov_l_rr(ad, reg + 8); add_l_ri(ad, off); } break; case 6: /* d8(An,Xn) */ { uae_u32 dp = comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; calc_disp_ea_020(reg + 8, dp, ad, S2); } break; case 7: switch (reg) { case 0: /* abs.w */ { uae_u32 off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; mov_l_ri(ad, off); } break; case 1: /* abs.l */ { uae_u32 off = comp_get_ilong((m68k_pc_offset += 4) - 4); ad = S1; mov_l_ri(ad, off); } break; case 2: /* d16(pc) */ { uae_u32 address = (uae_u32)(start_pc + ((char *) comp_pc_p - (char *) start_pc_p) + m68k_pc_offset); uae_s32 PC16off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); ad = S1; mov_l_ri(ad, address + PC16off); } break; case 3: /* d8(pc,Xn) */ return -1; case 4: /* #imm */ { uae_u32 address = (uae_u32)(start_pc + ((char *) comp_pc_p - (char *) start_pc_p) + m68k_pc_offset); ad = S1; mov_l_ri(ad, address); m68k_pc_offset += sz2[size]; } break; default: return -1; } } switch (size) { case 0: /* long */ fmovi_mrb(JITPTR temp_fp, val, clamp_bounds.l); delay; mov_l_rm(S2, JITPTR temp_fp); writelong_clobber(ad, S2, S3); break; case 1: /* single precision */ fmovs_mr(JITPTR temp_fp, val); delay; mov_l_rm(S2, JITPTR temp_fp); writelong_clobber(ad, S2, S3); break; case 2: /* extended precision */ fmov_ext_mr(JITPTR temp_fp, val); delay; mov_w_rm(S2, JITPTR temp_fp + 8); writeword_clobber(ad, S2, S3); add_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp + 4); writelong_clobber(ad, S2, S3); add_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp); writelong_clobber(ad, S2, S3); break; case 3: /* packed decimal static */ return -1; /* Packed */ case 4: /* word */ fmovi_mrb(JITPTR temp_fp, val, clamp_bounds.w); delay; mov_l_rm(S2, JITPTR temp_fp); writeword_clobber(ad, S2, S3); break; case 5: /* double precision */ fmov_mr(JITPTR temp_fp, val); delay; mov_l_rm(S2, JITPTR temp_fp + 4); writelong_clobber(ad, S2, S3); add_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp); writelong_clobber(ad, S2, S3); break; case 6: /* byte */ fmovi_mrb(JITPTR temp_fp, val, clamp_bounds.b); delay; mov_l_rm(S2, JITPTR temp_fp); writebyte(ad, S2, S3); break; default: return -1; } return 0; } /* return -1 for failure, or register number for success */ STATIC_INLINE int get_fp_ad(uae_u32 opcode) { int mode; int reg; uae_s32 off; mode = (opcode >> 3) & 7; reg = opcode & 7; switch (mode) { case 0: /* Dn */ case 1: /* An */ return -1; case 2: /* (An) */ case 3: /* (An)+ */ case 4: /* -(An) */ mov_l_rr(S1, 8 + reg); return S1; case 5: /* d16(An) */ off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); mov_l_rr(S1, 8 + reg); add_l_ri(S1, off); return S1; case 6: /* d8(An,Xn) */ return -1; break; case 7: switch (reg) { case 0: /* abs.w */ off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); mov_l_ri(S1, off); return S1; case 1: /* abs.l */ off = comp_get_ilong((m68k_pc_offset += 4) - 4); mov_l_ri(S1, off); return S1; case 2: /* d16(pc) */ off = (uae_s32)(start_pc + ((char *) comp_pc_p - (char *) start_pc_p) + m68k_pc_offset); off += (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); mov_l_ri(S1, off); return S1; case 3: /* d8(pc,Xn) */ return -1; default: return -1; } } abort(); } /* return -1 for failure, or register number for success */ void comp_fdbcc_opp (uae_u32 /* opcode */, uae_u16 /* extra */) { if (jit_disable.fdbcc) { FAIL(1); return; } FAIL(1); return; } void comp_fscc_opp(uae_u32 opcode, uae_u16 extra) { int reg; if (jit_disable.fscc) { FAIL(1); return; } if (extra & 0x20) { /* only cc from 00 to 1f are defined */ FAIL(1); return; } if ((opcode & 0x38) != 0) { /* We can only do to integer register */ FAIL(1); return; } fflags_into_flags(S2); reg = (opcode & 7); mov_l_ri(S1, 255); mov_l_ri(S4, 0); switch (extra & 0x0f) { /* according to fpp.c, the 0x10 bit is ignored */ case 0: break; /* set never */ case 1: mov_l_rr(S2, S4); cmov_l_rr(S4, S1, 4); cmov_l_rr(S4, S2, 10); break; case 2: cmov_l_rr(S4, S1, 7); break; case 3: cmov_l_rr(S4, S1, 3); break; case 4: mov_l_rr(S2, S4); cmov_l_rr(S4, S1, 2); cmov_l_rr(S4, S2, 10); break; case 5: mov_l_rr(S2, S4); cmov_l_rr(S4, S1, 6); cmov_l_rr(S4, S2, 10); break; case 6: cmov_l_rr(S4, S1, 5); break; case 7: cmov_l_rr(S4, S1, 11); break; case 8: cmov_l_rr(S4, S1, 10); break; case 9: cmov_l_rr(S4, S1, 4); break; case 10: cmov_l_rr(S4, S1, 10); cmov_l_rr(S4, S1, 7); break; case 11: cmov_l_rr(S4, S1, 4); cmov_l_rr(S4, S1, 3); break; case 12: cmov_l_rr(S4, S1, 2); break; case 13: cmov_l_rr(S4, S1, 6); break; case 14: cmov_l_rr(S4, S1, 5); cmov_l_rr(S4, S1, 10); break; case 15: mov_l_rr(S4, S1); break; } if ((opcode & 0x38) == 0) { mov_b_rr(reg, S4); } else { abort(); #if 0 int cc; if (get_fp_ad(opcode) < 0) { FAIL(1); } else { put_byte(ad, cc ? 0xff : 0x00); } #endif } } void comp_ftrapcc_opp (uae_u32 /* opcode */, uaecptr /* oldpc */) { FAIL(1); return; } void comp_fbcc_opp(uae_u32 opcode) { uae_u32 start_68k_offset = m68k_pc_offset; uae_u32 off; uae_u32 v1; uae_u32 v2; int cc; // comp_pc_p is expected to be bound to 32-bit addresses assert((uintptr) comp_pc_p <= 0xffffffffUL); if (jit_disable.fbcc) { FAIL(1); return; } if (opcode & 0x20) { /* only cc from 00 to 1f are defined */ FAIL(1); return; } if ((opcode & 0x40) == 0) { off = (uae_s32) (uae_s16) comp_get_iword((m68k_pc_offset += 2) - 2); } else { off = comp_get_ilong((m68k_pc_offset += 4) - 4); } mov_l_ri(S1, JITPTR (comp_pc_p + off - (m68k_pc_offset - start_68k_offset))); mov_l_ri(PC_P, JITPTR comp_pc_p); /* Now they are both constant. Might as well fold in m68k_pc_offset */ add_l_ri(S1, m68k_pc_offset); add_l_ri(PC_P, m68k_pc_offset); m68k_pc_offset = 0; /* according to fpp.c, the 0x10 bit is ignored (it handles exception handling, which we don't do, anyway ;-) */ cc = opcode & 0x0f; v1 = get_const(PC_P); v2 = get_const(S1); fflags_into_flags(S2); switch (cc) { case 0: break; /* jump never */ case 1: mov_l_rr(S2, PC_P); cmov_l_rr(PC_P, S1, 4); cmov_l_rr(PC_P, S2, 10); break; case 2: register_branch(v1, v2, 7); break; case 3: register_branch(v1, v2, 3); break; case 4: mov_l_rr(S2, PC_P); cmov_l_rr(PC_P, S1, 2); cmov_l_rr(PC_P, S2, 10); break; case 5: mov_l_rr(S2, PC_P); cmov_l_rr(PC_P, S1, 6); cmov_l_rr(PC_P, S2, 10); break; case 6: register_branch(v1, v2, 5); break; case 7: register_branch(v1, v2, 11); break; case 8: register_branch(v1, v2, 10); break; case 9: register_branch(v1, v2, 4); break; case 10: cmov_l_rr(PC_P, S1, 10); cmov_l_rr(PC_P, S1, 7); break; case 11: cmov_l_rr(PC_P, S1, 4); cmov_l_rr(PC_P, S1, 3); break; case 12: register_branch(v1, v2, 2); break; case 13: register_branch(v1, v2, 6); break; case 14: cmov_l_rr(PC_P, S1, 5); cmov_l_rr(PC_P, S1, 10); break; case 15: mov_l_rr(PC_P, S1); break; } } /* Floating point conditions The "NotANumber" part could be problematic; Howver, when NaN is encountered, the ftst instruction sets bot N and Z to 1 on the x87, so quite often things just fall into place. This is probably not accurate wrt the 68k FPU, but it is *as* accurate as this was before. However, some more thought should go into fixing this stuff up so it accurately emulates the 68k FPU. >==> 13) & 0x7) { case 1: /* illegal */ break; case 3: /* FMOVE Fpn, */ /* 2nd most common */ if (jit_disable.fmove) { FAIL(1); return; } if (put_fp_value((extra >> 7) & 7, opcode, extra) < 0) { FAIL(1); return; } return; case 6: /* FMOVEM , */ case 7: /* FMOVEM , */ if (jit_disable.fmovem) { FAIL(1); return; } { int ad; uae_u32 list = 0; int incr = 0; if (extra & 0x2000) { /* FMOVEM FPP->memory */ switch ((extra >> 11) & 3) { /* Get out early if failure */ case 0: /* static pred */ case 2: /* static postinc */ break; case 1: /* dynamic pred */ case 3: /* dynamic postinc */ default: FAIL(1); return; } if ((ad = get_fp_ad(opcode)) < 0) { FAIL(1); return; } switch ((extra >> 11) & 3) { case 0: /* static pred */ list = extra & 0xff; incr = -1; break; case 2: /* static postinc */ list = extra & 0xff; incr = 1; break; case 1: /* dynamic pred */ case 3: /* dynamic postinc */ abort(); } if (incr < 0) { /* Predecrement */ for (reg = 7; reg >= 0; reg--) { if (list & 0x80) { fmov_ext_mr(JITPTR temp_fp, reg); delay; sub_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp); writelong_clobber(ad, S2, S3); sub_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp + 4); writelong_clobber(ad, S2, S3); sub_l_ri(ad, 4); mov_w_rm(S2, JITPTR temp_fp + 8); writeword_clobber(ad, S2, S3); } list <<= 1; } } else { /* Postincrement */ for (reg = 0; reg < 8; reg++) { if (list & 0x80) { fmov_ext_mr(JITPTR temp_fp, reg); delay; mov_w_rm(S2, JITPTR temp_fp + 8); writeword_clobber(ad, S2, S3); add_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp + 4); writelong_clobber(ad, S2, S3); add_l_ri(ad, 4); mov_l_rm(S2, JITPTR temp_fp); writelong_clobber(ad, S2, S3); add_l_ri(ad, 4); } list <<= 1; } } if ((opcode & 0x38) == 0x18) mov_l_rr((opcode & 7) + 8, ad); if ((opcode & 0x38) == 0x20) mov_l_rr((opcode & 7) + 8, ad); } else { /* FMOVEM memory->FPP */ int ad; switch ((extra >> 11) & 3) { /* Get out early if failure */ case 0: /* static pred */ case 2: /* static postinc */ break; case 1: /* dynamic pred */ case 3: /* dynamic postinc */ default: FAIL(1); return; } ad = get_fp_ad(opcode); if (ad < 0) { D(bug("no ad\n")); FAIL(1); return; } switch ((extra >> 11) & 3) { case 0: /* static pred */ list = extra & 0xff; incr = -1; break; case 2: /* static postinc */ list = extra & 0xff; incr = 1; break; case 1: /* dynamic pred */ case 3: /* dynamic postinc */ abort(); } if (incr < 0) { // not reached for (reg = 7; reg >= 0; reg--) { if (list & 0x80) { sub_l_ri(ad, 4); readlong(ad, S2, S3); mov_l_mr(JITPTR (temp_fp), S2); sub_l_ri(ad, 4); readlong(ad, S2, S3); mov_l_mr(JITPTR (temp_fp) + 4, S2); sub_l_ri(ad, 4); readword(ad, S2, S3); mov_w_mr((JITPTR temp_fp) + 8, S2); delay2; fmov_ext_rm(reg, JITPTR (temp_fp)); } list <<= 1; } } else { for (reg = 0; reg < 8; reg++) { if (list & 0x80) { readword(ad, S2, S3); mov_w_mr((JITPTR temp_fp) + 8, S2); add_l_ri(ad, 4); readlong(ad, S2, S3); mov_l_mr(JITPTR (temp_fp) + 4, S2); add_l_ri(ad, 4); readlong(ad, S2, S3); mov_l_mr(JITPTR(temp_fp), S2); add_l_ri(ad, 4); delay2; fmov_ext_rm(reg, JITPTR (temp_fp)); } list <<= 1; } } if ((opcode & 0x38) == 0x18) mov_l_rr((opcode & 7) + 8, ad); if ((opcode & 0x38) == 0x20) mov_l_rr((opcode & 7) + 8, ad); } } return; case 4: /* FMOVEM , */ case 5: /* FMOVEM , */ if (jit_disable.fmovec) { FAIL(1); return; } /* rare */ if ((opcode & 0x30) == 0) { /* = Dn or An */ if (extra & 0x2000) { if (extra & 0x1000) { #if HANDLE_FPCR mov_l_rm(opcode & 15, (uintptr) & fpu.fpcr.rounding_mode); or_l_rm(opcode & 15, (uintptr) & fpu.fpcr.rounding_precision); #else FAIL(1); return; #endif } if (extra & 0x0800) { FAIL(1); return; } if (extra & 0x0400) { /* FPIAR: fixme; we cannot correctly return the address from compiled code */ #ifdef UAE mov_l_rm(opcode & 15, JITPTR ®s.fpiar); #else mov_l_rm(opcode & 15, JITPTR &fpu.instruction_address); #endif return; } } else { // gb-- moved here so that we may FAIL() without generating any code if (extra & 0x0800) { // set_fpsr(m68k_dreg (regs, opcode & 15)); FAIL(1); return; } if (extra & 0x1000) { #if HANDLE_FPCR #if defined(FPU_USE_X86_ROUNDING_MODE) && defined(FPU_USE_X86_ROUNDING_PRECISION) FAIL(1); return; #endif mov_l_rr(S1, opcode & 15); mov_l_rr(S2, opcode & 15); and_l_ri(S1, FPCR_ROUNDING_PRECISION); and_l_ri(S2, FPCR_ROUNDING_MODE); mov_l_mr((uintptr) & fpu.fpcr.rounding_precision, S1); mov_l_mr((uintptr) & fpu.fpcr.rounding_mode, S2); #else FAIL(1); return; #endif } if (extra & 0x0400) { /* FPIAR: does that make sense at all? */ #ifdef UAE mov_l_mr(JITPTR ®s.fpiar, opcode & 15); #else mov_l_mr(JITPTR &fpu.instruction_address, opcode & 15); #endif } return; } } else if ((opcode & 0x3f) == 0x3c) { /* = #imm */ if ((extra & 0x2000) == 0) { // gb-- moved here so that we may FAIL() without generating any code if (extra & 0x0800) { FAIL(1); return; } if (extra & 0x1000) { comp_get_ilong((m68k_pc_offset += 4) - 4); #if HANDLE_FPCR #if defined(FPU_USE_X86_ROUNDING_MODE) && defined(FPU_USE_X86_ROUNDING_PRECISION) FAIL(1); return; #endif // mov_l_mi((uintptr)®s.fpcr,val); mov_l_ri(S1, val); mov_l_ri(S2, val); and_l_ri(S1, FPCR_ROUNDING_PRECISION); and_l_ri(S2, FPCR_ROUNDING_MODE); mov_l_mr((uintptr) & fpu.fpcr.rounding_precision, S1); mov_l_mr((uintptr) & fpu.fpcr.rounding_mode, S2); #else FAIL(1); return; #endif } if (extra & 0x0400) { uae_u32 val = comp_get_ilong((m68k_pc_offset += 4) - 4); #ifdef UAE mov_l_mi(JITPTR ®s.fpiar, val); #else mov_l_mi(JITPTR &fpu.instruction_address, val); #endif } return; } FAIL(1); return; } else if (extra & 0x2000) { FAIL(1); return; } else { FAIL(1); return; } FAIL(1); return; case 0: case 2: /* Extremely common */ reg = (extra >> 7) & 7; if ((extra & 0xfc00) == 0x5c00) { if (jit_disable.fmovecr) { FAIL(1); return; } switch (extra & 0x7f) { case 0x00: fmov_pi(reg); break; case 0x0b: fmov_log10_2(reg); break; case 0x0c: #if defined(USE_LONG_DOUBLE) || defined(USE_QUAD_DOUBLE) fmov_ext_rm(reg, (uintptr) & const_e); #else fmov_rm(reg, (uintptr) & const_e); #endif break; case 0x0d: fmov_log2_e(reg); break; case 0x0e: #if defined(USE_LONG_DOUBLE) || defined(USE_QUAD_DOUBLE) fmov_ext_rm(reg, (uintptr) & const_log10_e); #else fmov_rm(reg, (uintptr) & const_log10_e); #endif break; case 0x0f: fmov_0(reg); break; case 0x30: fmov_loge_2(reg); break; case 0x31: #if defined(USE_LONG_DOUBLE) || defined(USE_QUAD_DOUBLE) fmov_ext_rm(reg, (uintptr) & const_loge_10); #else fmov_rm(reg, (uintptr) & const_loge_10); #endif break; case 0x32: fmov_1(reg); break; case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3a: case 0x3b: #if defined(USE_LONG_DOUBLE) || defined(USE_QUAD_DOUBLE) case 0x3c: case 0x3d: case 0x3e: case 0x3f: fmov_ext_rm(reg, (uintptr) (power10 + (extra & 0x7f) - 0x32)); #else fmov_rm(reg, (uintptr) (power10 + (extra & 0x7f) - 0x32)); #endif break; default: /* This is not valid, so we fail */ FAIL(1); return; } return; } switch (extra & 0x7f) { case 0x00: /* FMOVE */ case 0x40: /* FSMOVE: Explicit rounding. This is just a quick fix. Same * for all other cases that have three choices */ case 0x44: /* FDMOVE */ if (jit_disable.fmove) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fmov_rr(reg, src); MAKE_FPSR(src); break; case 0x01: /* FINT */ if (jit_disable.fint) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x02: /* FSINH */ if (jit_disable.fsinh) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x03: /* FINTRZ */ if (jit_disable.fintrz) { FAIL(1); return; } #ifdef USE_X86_FPUCW /* If we have control over the CW, we can do this */ dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } mov_l_ri(S1, 16); /* Switch to "round to zero" mode */ fldcw_m_indexed(S1, JITPTR x86_fpucw); frndint_rr(reg, src); /* restore control word */ mov_l_rm(S1, JITPTR ®s.fpcr); and_l_ri(S1, 0x000000f0); fldcw_m_indexed(S1, JITPTR x86_fpucw); MAKE_FPSR(reg); break; #endif FAIL(1); return; break; case 0x04: /* FSQRT */ case 0x41: /* FSSQRT */ case 0x45: /* FDSQRT */ if (jit_disable.fsqrt) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fsqrt_rr(reg, src); MAKE_FPSR(reg); break; case 0x06: /* FLOGNP1 */ if (jit_disable.flognp1) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x08: /* FETOXM1 */ if (jit_disable.fetoxm1) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x09: /* FTANH */ if (jit_disable.ftanh) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x0a: /* FATAN */ if (jit_disable.fatan) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x0c: /* FASIN */ if (jit_disable.fasin) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x0d: /* FATANH */ if (jit_disable.fatanh) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x0e: /* FSIN */ if (jit_disable.fsin) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fsin_rr(reg, src); MAKE_FPSR(reg); break; case 0x0f: /* FTAN */ if (jit_disable.ftan) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x10: /* FETOX */ if (jit_disable.fetox) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fetox_rr(reg, src); MAKE_FPSR(reg); break; case 0x11: /* FTWOTOX */ if (jit_disable.ftwotox) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } ftwotox_rr(reg, src); MAKE_FPSR(reg); break; case 0x12: /* FTENTOX */ if (jit_disable.ftentox) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x14: /* FLOGN */ if (jit_disable.flogn) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x15: /* FLOG10 */ if (jit_disable.flog10) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x16: /* FLOG2 */ if (jit_disable.flog2) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } flog2_rr(reg, src); MAKE_FPSR(reg); break; case 0x18: /* FABS */ case 0x58: /* FSABS */ case 0x5c: /* FDABS */ if (jit_disable.fabs) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fabs_rr(reg, src); MAKE_FPSR(reg); break; case 0x19: /* FCOSH */ if (jit_disable.fcosh) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x1a: /* FNEG */ case 0x5a: /* FSNEG */ case 0x5e: /* FDNEG */ if (jit_disable.fneg) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fneg_rr(reg, src); MAKE_FPSR(reg); break; case 0x1c: /* FACOS */ if (jit_disable.facos) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x1d: /* FCOS */ if (jit_disable.fcos) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fcos_rr(reg, src); MAKE_FPSR(reg); break; case 0x1e: /* FGETEXP */ if (jit_disable.fgetexp) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x1f: /* FGETMAN */ if (jit_disable.fgetman) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x20: /* FDIV */ case 0x60: /* FSDIV */ case 0x64: /* FDDIV */ if (jit_disable.fdiv) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fdiv_rr(reg, src); MAKE_FPSR(reg); break; case 0x21: /* FMOD */ if (jit_disable.fmod) { FAIL(1); return; } // FIXME: the quotient byte must be computed dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } frem_rr(reg, src); MAKE_FPSR(reg); break; case 0x22: /* FADD */ case 0x62: /* FSADD */ case 0x66: /* FDADD */ if (jit_disable.fadd) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fadd_rr(reg, src); MAKE_FPSR(reg); break; case 0x23: /* FMUL */ case 0x63: /* FSMUL */ case 0x67: /* FDMUL */ if (jit_disable.fmul) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fmul_rr(reg, src); MAKE_FPSR(reg); break; case 0x24: /* FSGLDIV */ if (jit_disable.fsgldiv) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fdiv_rr(reg, src); MAKE_FPSR(reg); break; case 0x25: /* FREM */ if (jit_disable.frem) { FAIL(1); return; } // gb-- disabled because the quotient byte must be computed // otherwise, free rotation in ClarisWorks doesn't work. FAIL(1); return; dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } frem1_rr(reg, src); MAKE_FPSR(reg); break; case 0x26: /* FSCALE */ if (jit_disable.fscale) { FAIL(1); return; } FAIL(1); return; break; case 0x27: /* FSGLMUL */ if (jit_disable.fsglmul) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fmul_rr(reg, src); MAKE_FPSR(reg); break; case 0x28: /* FSUB */ case 0x68: /* FSSUB */ case 0x6c: /* FDSUB */ if (jit_disable.fsub) { FAIL(1); return; } dont_care_fflags(); src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fsub_rr(reg, src); MAKE_FPSR(reg); break; case 0x30: /* FSINCOS */ case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: if (jit_disable.fsincos) { FAIL(1); return; } FAIL(1); return; dont_care_fflags(); break; case 0x38: /* FCMP */ if (jit_disable.fcmp) { FAIL(1); return; } src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fmov_rr(FP_RESULT, reg); fsub_rr(FP_RESULT, src); /* Right way? */ break; case 0x3a: /* FTST */ if (jit_disable.ftst) { FAIL(1); return; } src = get_fp_value(opcode, extra); if (src < 0) { FAIL(1); /* Illegal instruction */ return; } fmov_rr(FP_RESULT, src); break; default: FAIL(1); return; break; } return; } FAIL(1); } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_midfunc_arm.cpp000066400000000000000000000741021504763705000274260ustar00rootroot00000000000000/* * compiler/compemu_midfunc_arm.cpp - Native MIDFUNCS for ARM * * Copyright (c) 2014 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Note: * File is included by compemu_support.cpp * */ /******************************************************************** * CPU functions exposed to gencomp. Both CREATE and EMIT time * ********************************************************************/ /* * RULES FOR HANDLING REGISTERS: * * * In the function headers, order the parameters * - 1st registers written to * - 2nd read/modify/write registers * - 3rd registers read from * * Before calling raw_*, you must call readreg, writereg or rmw for * each register * * The order for this is * - 1st call remove_offset for all registers written to with size<4 * - 2nd call readreg for all registers read without offset * - 3rd call rmw for all rmw registers * - 4th call readreg_offset for all registers that can handle offsets * - 5th call get_offset for all the registers from the previous step * - 6th call writereg for all written-to registers * - 7th call raw_* * - 8th unlock2 all registers that were locked */ MIDFUNC(0,live_flags,(void)) { live.flags_on_stack=TRASH; live.flags_in_flags=VALID; live.flags_are_important=1; } MIDFUNC(0,dont_care_flags,(void)) { live.flags_are_important=0; } MIDFUNC(0,duplicate_carry,(void)) { evict(FLAGX); make_flags_live_internal(); COMPCALL(setcc_m)((uintptr)live.state[FLAGX].mem,NATIVE_CC_CS); log_vwrite(FLAGX); } MIDFUNC(0,restore_carry,(void)) { #if defined(USE_JIT2) RR4 r=readreg(FLAGX,4); MRS_CPSR(REG_WORK1); TEQ_ri(r,1); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_C_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(r); #else if (!have_rat_stall) { /* Not a P6 core, i.e. no partial stalls */ bt_l_ri_noclobber(FLAGX,0); } else { /* Avoid the stall the above creates. This is slow on non-P6, though. */ COMPCALL(rol_b_ri(FLAGX,8)); isclean(FLAGX); } #endif } MIDFUNC(0,start_needflags,(void)) { needflags=1; } MIDFUNC(0,end_needflags,(void)) { needflags=0; } MIDFUNC(0,make_flags_live,(void)) { make_flags_live_internal(); } MIDFUNC(2,bt_l_ri,(RR4 r, IMM i)) /* This is defined as only affecting C */ { int size=4; if (i<16) size=2; CLOBBER_BT; r=readreg(r,size); raw_bt_l_ri(r,i); unlock2(r); } MIDFUNC(2,bt_l_rr,(RR4 r, RR4 b)) /* This is defined as only affecting C */ { CLOBBER_BT; r=readreg(r,4); b=readreg(b,4); raw_bt_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,btc_l_rr,(RW4 r, RR4 b)) { CLOBBER_BT; b=readreg(b,4); r=rmw(r,4,4); raw_btc_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,btr_l_rr,(RW4 r, RR4 b)) { CLOBBER_BT; b=readreg(b,4); r=rmw(r,4,4); raw_btr_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,bts_l_rr,(RW4 r, RR4 b)) { CLOBBER_BT; b=readreg(b,4); r=rmw(r,4,4); raw_bts_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,mov_l_rm,(W4 d, IMM s)) { CLOBBER_MOV; d=writereg(d,4); raw_mov_l_rm(d,s); unlock2(d); } MIDFUNC(4,mov_l_rm_indexed,(W4 d, IMM base, RR4 index, IMM factor)) { CLOBBER_MOV; index=readreg(index,4); d=writereg(d,4); raw_mov_l_rm_indexed(d,base,index,factor); unlock2(index); unlock2(d); } MIDFUNC(2,mov_l_mi,(IMM d, IMM s)) { CLOBBER_MOV; raw_mov_l_mi(d,s); } MIDFUNC(2,mov_w_mi,(IMM d, IMM s)) { CLOBBER_MOV; raw_mov_w_mi(d,s); } MIDFUNC(2,mov_b_mi,(IMM d, IMM s)) { CLOBBER_MOV; raw_mov_b_mi(d,s); } MIDFUNC(2,rol_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROL; r=rmw(r,1,1); raw_rol_b_ri(r,i); unlock2(r); } MIDFUNC(2,rol_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROL; r=rmw(r,2,2); raw_rol_w_ri(r,i); unlock2(r); } MIDFUNC(2,rol_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROL; r=rmw(r,4,4); raw_rol_l_ri(r,i); unlock2(r); } MIDFUNC(2,rol_l_rr,(RW4 d, RR1 r)) { if (isconst(r)) { COMPCALL(rol_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROL; r=readreg(r,1); d=rmw(d,4,4); raw_rol_l_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,rol_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(rol_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROL; r=readreg(r,1); d=rmw(d,2,2); raw_rol_w_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,rol_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(rol_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROL; r=readreg(r,1); d=rmw(d,1,1); raw_rol_b_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shll_l_rr,(RW4 d, RR1 r)) { if (isconst(r)) { COMPCALL(shll_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHLL; r=readreg(r,1); d=rmw(d,4,4); raw_shll_l_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shll_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(shll_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHLL; r=readreg(r,1); d=rmw(d,2,2); raw_shll_w_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shll_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(shll_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHLL; r=readreg(r,1); d=rmw(d,1,1); raw_shll_b_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,ror_b_ri,(RR1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROR; r=rmw(r,1,1); raw_ror_b_ri(r,i); unlock2(r); } MIDFUNC(2,ror_w_ri,(RR2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROR; r=rmw(r,2,2); raw_ror_w_ri(r,i); unlock2(r); } MIDFUNC(2,ror_l_ri,(RR4 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROR; r=rmw(r,4,4); raw_ror_l_ri(r,i); unlock2(r); } MIDFUNC(2,ror_l_rr,(RR4 d, RR1 r)) { if (isconst(r)) { COMPCALL(ror_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROR; r=readreg(r,1); d=rmw(d,4,4); raw_ror_l_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,ror_w_rr,(RR2 d, RR1 r)) { if (isconst(r)) { COMPCALL(ror_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROR; r=readreg(r,1); d=rmw(d,2,2); raw_ror_w_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,ror_b_rr,(RR1 d, RR1 r)) { if (isconst(r)) { COMPCALL(ror_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROR; r=readreg(r,1); d=rmw(d,1,1); raw_ror_b_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shrl_l_rr,(RW4 d, RR1 r)) { if (isconst(r)) { COMPCALL(shrl_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRL; r=readreg(r,1); d=rmw(d,4,4); raw_shrl_l_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shrl_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(shrl_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRL; r=readreg(r,1); d=rmw(d,2,2); raw_shrl_w_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shrl_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(shrl_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRL; r=readreg(r,1); d=rmw(d,1,1); raw_shrl_b_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shll_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; if (isconst(r) && !needflags) { live.state[r].val<<=i; return; } CLOBBER_SHLL; r=rmw(r,4,4); raw_shll_l_ri(r,i); unlock2(r); } MIDFUNC(2,shll_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHLL; r=rmw(r,2,2); raw_shll_w_ri(r,i); unlock2(r); } MIDFUNC(2,shll_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHLL; r=rmw(r,1,1); raw_shll_b_ri(r,i); unlock2(r); } MIDFUNC(2,shrl_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; if (isconst(r) && !needflags) { live.state[r].val>>=i; return; } CLOBBER_SHRL; r=rmw(r,4,4); raw_shrl_l_ri(r,i); unlock2(r); } MIDFUNC(2,shrl_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRL; r=rmw(r,2,2); raw_shrl_w_ri(r,i); unlock2(r); } MIDFUNC(2,shrl_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRL; r=rmw(r,1,1); raw_shrl_b_ri(r,i); unlock2(r); } MIDFUNC(2,shra_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRA; r=rmw(r,4,4); raw_shra_l_ri(r,i); unlock2(r); } MIDFUNC(2,shra_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRA; r=rmw(r,2,2); raw_shra_w_ri(r,i); unlock2(r); } MIDFUNC(2,shra_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRA; r=rmw(r,1,1); raw_shra_b_ri(r,i); unlock2(r); } MIDFUNC(2,shra_l_rr,(RW4 d, RR1 r)) { if (isconst(r)) { COMPCALL(shra_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRA; r=readreg(r,1); d=rmw(d,4,4); raw_shra_l_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shra_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(shra_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRA; r=readreg(r,1); d=rmw(d,2,2); raw_shra_w_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,shra_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r)) { COMPCALL(shra_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRA; r=readreg(r,1); d=rmw(d,1,1); raw_shra_b_rr(d,r); unlock2(r); unlock2(d); } MIDFUNC(2,setcc,(W1 d, IMM cc)) { CLOBBER_SETCC; d=writereg(d,1); raw_setcc(d,cc); unlock2(d); } MIDFUNC(2,setcc_m,(IMM d, IMM cc)) { CLOBBER_SETCC; raw_setcc_m(d,cc); } MIDFUNC(3,cmov_l_rr,(RW4 d, RR4 s, IMM cc)) { if (d==s) return; CLOBBER_CMOV; s=readreg(s,4); d=rmw(d,4,4); raw_cmov_l_rr(d,s,cc); unlock2(s); unlock2(d); } MIDFUNC(2,bsf_l_rr,(W4 d, W4 s)) { CLOBBER_BSF; s = readreg(s, 4); d = writereg(d, 4); raw_bsf_l_rr(d, s); unlock2(s); unlock2(d); } /* Set the Z flag depending on the value in s. Note that the value has to be 0 or -1 (or, more precisely, for non-zero values, bit 14 must be set)! */ MIDFUNC(2,simulate_bsf,(W4 tmp, RW4 s)) { CLOBBER_BSF; s=rmw_specific(s,4,4,FLAG_NREG3); tmp=writereg(tmp,4); raw_flags_set_zero(s, tmp); unlock2(tmp); unlock2(s); } MIDFUNC(2,imul_32_32,(RW4 d, RR4 s)) { CLOBBER_MUL; s=readreg(s,4); d=rmw(d,4,4); raw_imul_32_32(d,s); unlock2(s); unlock2(d); } MIDFUNC(2,imul_64_32,(RW4 d, RW4 s)) { CLOBBER_MUL; s=rmw_specific(s,4,4,MUL_NREG2); d=rmw_specific(d,4,4,MUL_NREG1); raw_imul_64_32(d,s); unlock2(s); unlock2(d); } MIDFUNC(2,mul_64_32,(RW4 d, RW4 s)) { CLOBBER_MUL; s=rmw_specific(s,4,4,MUL_NREG2); d=rmw_specific(d,4,4,MUL_NREG1); raw_mul_64_32(d,s); unlock2(s); unlock2(d); } MIDFUNC(2,sign_extend_16_rr,(W4 d, RR2 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_s32)(uae_s16)live.state[s].val); return; } CLOBBER_SE16; isrmw=(s==d); if (!isrmw) { s=readreg(s,2); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,2); } raw_sign_extend_16_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,sign_extend_8_rr,(W4 d, RR1 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_s32)(uae_s8)live.state[s].val); return; } isrmw=(s==d); CLOBBER_SE8; if (!isrmw) { s=readreg(s,1); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,1); } raw_sign_extend_8_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,zero_extend_16_rr,(W4 d, RR2 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_u32)(uae_u16)live.state[s].val); return; } isrmw=(s==d); CLOBBER_ZE16; if (!isrmw) { s=readreg(s,2); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,2); } raw_zero_extend_16_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,zero_extend_8_rr,(W4 d, RR1 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_u32)(uae_u8)live.state[s].val); return; } isrmw=(s==d); CLOBBER_ZE8; if (!isrmw) { s=readreg(s,1); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,1); } raw_zero_extend_8_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,mov_b_rr,(W1 d, RR1 s)) { if (d==s) return; if (isconst(s)) { COMPCALL(mov_b_ri)(d,(uae_u8)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,1); d=writereg(d,1); raw_mov_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,mov_w_rr,(W2 d, RR2 s)) { if (d==s) return; if (isconst(s)) { COMPCALL(mov_w_ri)(d,(uae_u16)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,2); d=writereg(d,2); raw_mov_w_rr(d,s); unlock2(d); unlock2(s); } /* read the long at the address contained in s+offset and store in d */ MIDFUNC(3,mov_l_rR,(W4 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_l_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg(s,4); d=writereg(d,4); raw_mov_l_rR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_w_rR,(W2 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_w_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg(s,4); d=writereg(d,2); raw_mov_w_rR(d,s,offset); unlock2(d); unlock2(s); } /* read the long at the address contained in s+offset and store in d */ MIDFUNC(3,mov_l_brR,(W4 d, RR4 s, IMM offset)) { int sreg=s; if (isconst(s)) { COMPCALL(mov_l_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg_offset(s,4); offset+=get_offset(sreg); d=writereg(d,4); raw_mov_l_brR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_w_brR,(W2 d, RR4 s, IMM offset)) { int sreg=s; if (isconst(s)) { COMPCALL(mov_w_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; remove_offset(d,-1); s=readreg_offset(s,4); offset+=get_offset(sreg); d=writereg(d,2); raw_mov_w_brR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_b_brR,(W1 d, RR4 s, IMM offset)) { int sreg=s; if (isconst(s)) { COMPCALL(mov_b_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; remove_offset(d,-1); s=readreg_offset(s,4); offset+=get_offset(sreg); d=writereg(d,1); raw_mov_b_brR(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_l_Ri,(RR4 d, IMM i, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_l_mi)(live.state[d].val+offset,i); return; } CLOBBER_MOV; d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_l_Ri(d,i,offset); unlock2(d); } MIDFUNC(3,mov_w_Ri,(RR4 d, IMM i, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_w_mi)(live.state[d].val+offset,i); return; } CLOBBER_MOV; d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_w_Ri(d,i,offset); unlock2(d); } /* Warning! OFFSET is byte sized only! */ MIDFUNC(3,mov_l_Rr,(RR4 d, RR4 s, IMM offset)) { if (isconst(d)) { COMPCALL(mov_l_mr)(live.state[d].val+offset,s); return; } if (isconst(s)) { COMPCALL(mov_l_Ri)(d,live.state[s].val,offset); return; } CLOBBER_MOV; s=readreg(s,4); d=readreg(d,4); raw_mov_l_Rr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_w_Rr,(RR4 d, RR2 s, IMM offset)) { if (isconst(d)) { COMPCALL(mov_w_mr)(live.state[d].val+offset,s); return; } if (isconst(s)) { COMPCALL(mov_w_Ri)(d,(uae_u16)live.state[s].val,offset); return; } CLOBBER_MOV; s=readreg(s,2); d=readreg(d,4); raw_mov_w_Rr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,lea_l_brr,(W4 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_l_ri)(d,live.state[s].val+offset); return; } #if USE_OFFSET if (d==s) { add_offset(d,offset); return; } #endif CLOBBER_LEA; s=readreg(s,4); d=writereg(d,4); raw_lea_l_brr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(5,lea_l_brr_indexed,(W4 d, RR4 s, RR4 index, IMM factor, IMM offset)) { if (!offset) { COMPCALL(lea_l_rr_indexed)(d,s,index,factor); return; } CLOBBER_LEA; s=readreg(s,4); index=readreg(index,4); d=writereg(d,4); raw_lea_l_brr_indexed(d,s,index,factor,offset); unlock2(d); unlock2(index); unlock2(s); } MIDFUNC(4,lea_l_rr_indexed,(W4 d, RR4 s, RR4 index, IMM factor)) { CLOBBER_LEA; s=readreg(s,4); index=readreg(index,4); d=writereg(d,4); raw_lea_l_rr_indexed(d,s,index,factor); unlock2(d); unlock2(index); unlock2(s); } /* write d to the long at the address contained in s+offset */ MIDFUNC(3,mov_l_bRr,(RR4 d, RR4 s, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_l_mr)(live.state[d].val+offset,s); return; } CLOBBER_MOV; s=readreg(s,4); d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_l_bRr(d,s,offset); unlock2(d); unlock2(s); } /* write the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_w_bRr,(RR4 d, RR2 s, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_w_mr)(live.state[d].val+offset,s); return; } CLOBBER_MOV; s=readreg(s,2); d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_w_bRr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_b_bRr,(RR4 d, RR1 s, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_b_mr)(live.state[d].val+offset,s); return; } CLOBBER_MOV; s=readreg(s,1); d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_b_bRr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(1,mid_bswap_32,(RW4 r)) { if (isconst(r)) { uae_u32 oldv=live.state[r].val; live.state[r].val=reverse32(oldv); return; } CLOBBER_SW32; r=rmw(r,4,4); raw_bswap_32(r); unlock2(r); } MIDFUNC(1,mid_bswap_16,(RW2 r)) { if (isconst(r)) { uae_u32 oldv=live.state[r].val; live.state[r].val=((oldv>>8)&0xff) | ((oldv<<8)&0xff00) | (oldv&0xffff0000); return; } CLOBBER_SW16; r=rmw(r,2,2); raw_bswap_16(r); unlock2(r); } MIDFUNC(2,mov_l_rr,(W4 d, RR4 s)) { int olds; if (d==s) { /* How pointless! */ return; } if (isconst(s)) { COMPCALL(mov_l_ri)(d,live.state[s].val); return; } olds=s; disassociate(d); s=readreg_offset(s,4); live.state[d].realreg=s; live.state[d].realind=live.nat[s].nholds; live.state[d].val=live.state[olds].val; live.state[d].validsize=4; live.state[d].dirtysize=4; set_status(d,DIRTY); live.nat[s].holds[live.nat[s].nholds]=d; live.nat[s].nholds++; log_clobberreg(d); D2(panicbug("Added %d to nreg %d(%d), now holds %d regs", d,s,live.state[d].realind,live.nat[s].nholds)); unlock2(s); } MIDFUNC(2,mov_l_mr,(IMM d, RR4 s)) { if (isconst(s)) { COMPCALL(mov_l_mi)(d,live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,4); raw_mov_l_mr(d,s); unlock2(s); } MIDFUNC(2,mov_w_mr,(IMM d, RR2 s)) { if (isconst(s)) { COMPCALL(mov_w_mi)(d,(uae_u16)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,2); raw_mov_w_mr(d,s); unlock2(s); } MIDFUNC(2,mov_w_rm,(W2 d, IMM s)) { CLOBBER_MOV; d=writereg(d,2); raw_mov_w_rm(d,s); unlock2(d); } MIDFUNC(2,mov_b_mr,(IMM d, RR1 s)) { if (isconst(s)) { COMPCALL(mov_b_mi)(d,(uae_u8)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,1); raw_mov_b_mr(d,s); unlock2(s); } MIDFUNC(2,mov_b_rm,(W1 d, IMM s)) { CLOBBER_MOV; d=writereg(d,1); raw_mov_b_rm(d,s); unlock2(d); } MIDFUNC(2,mov_l_ri,(W4 d, IMM s)) { set_const(d,s); return; } MIDFUNC(2,mov_w_ri,(W2 d, IMM s)) { CLOBBER_MOV; d=writereg(d,2); raw_mov_w_ri(d,s); unlock2(d); } MIDFUNC(2,mov_b_ri,(W1 d, IMM s)) { CLOBBER_MOV; d=writereg(d,1); raw_mov_b_ri(d,s); unlock2(d); } MIDFUNC(2,test_l_ri,(RR4 d, IMM i)) { CLOBBER_TEST; d=readreg(d,4); raw_test_l_ri(d,i); unlock2(d); } MIDFUNC(2,test_l_rr,(RR4 d, RR4 s)) { CLOBBER_TEST; d=readreg(d,4); s=readreg(s,4); raw_test_l_rr(d,s);; unlock2(d); unlock2(s); } MIDFUNC(2,test_w_rr,(RR2 d, RR2 s)) { CLOBBER_TEST; d=readreg(d,2); s=readreg(s,2); raw_test_w_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,test_b_rr,(RR1 d, RR1 s)) { CLOBBER_TEST; d=readreg(d,1); s=readreg(s,1); raw_test_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,and_l_ri,(RW4 d, IMM i)) { if (isconst(d) && !needflags) { live.state[d].val &= i; return; } CLOBBER_AND; d=rmw(d,4,4); raw_and_l_ri(d,i); unlock2(d); } MIDFUNC(2,and_l,(RW4 d, RR4 s)) { CLOBBER_AND; s=readreg(s,4); d=rmw(d,4,4); raw_and_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,and_w,(RW2 d, RR2 s)) { CLOBBER_AND; s=readreg(s,2); d=rmw(d,2,2); raw_and_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,and_b,(RW1 d, RR1 s)) { CLOBBER_AND; s=readreg(s,1); d=rmw(d,1,1); raw_and_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,or_l_ri,(RW4 d, IMM i)) { if (isconst(d) && !needflags) { live.state[d].val|=i; return; } CLOBBER_OR; d=rmw(d,4,4); raw_or_l_ri(d,i); unlock2(d); } MIDFUNC(2,or_l,(RW4 d, RR4 s)) { if (isconst(d) && isconst(s) && !needflags) { live.state[d].val|=live.state[s].val; return; } CLOBBER_OR; s=readreg(s,4); d=rmw(d,4,4); raw_or_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,or_w,(RW2 d, RR2 s)) { CLOBBER_OR; s=readreg(s,2); d=rmw(d,2,2); raw_or_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,or_b,(RW1 d, RR1 s)) { CLOBBER_OR; s=readreg(s,1); d=rmw(d,1,1); raw_or_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,adc_l,(RW4 d, RR4 s)) { CLOBBER_ADC; s=readreg(s,4); d=rmw(d,4,4); raw_adc_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,adc_w,(RW2 d, RR2 s)) { CLOBBER_ADC; s=readreg(s,2); d=rmw(d,2,2); raw_adc_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,adc_b,(RW1 d, RR1 s)) { CLOBBER_ADC; s=readreg(s,1); d=rmw(d,1,1); raw_adc_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,add_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(add_l_ri)(d,live.state[s].val); return; } CLOBBER_ADD; s=readreg(s,4); d=rmw(d,4,4); raw_add_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,add_w,(RW2 d, RR2 s)) { if (isconst(s)) { COMPCALL(add_w_ri)(d,(uae_u16)live.state[s].val); return; } CLOBBER_ADD; s=readreg(s,2); d=rmw(d,2,2); raw_add_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,add_b,(RW1 d, RR1 s)) { if (isconst(s)) { COMPCALL(add_b_ri)(d,(uae_u8)live.state[s].val); return; } CLOBBER_ADD; s=readreg(s,1); d=rmw(d,1,1); raw_add_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_l_ri,(RW4 d, IMM i)) { if (!i && !needflags) return; if (isconst(d) && !needflags) { live.state[d].val-=i; return; } #if USE_OFFSET if (!needflags) { add_offset(d,-i); return; } #endif CLOBBER_SUB; d=rmw(d,4,4); raw_sub_l_ri(d,i); unlock2(d); } MIDFUNC(2,sub_w_ri,(RW2 d, IMM i)) { if (!i && !needflags) return; CLOBBER_SUB; d=rmw(d,2,2); raw_sub_w_ri(d,i); unlock2(d); } MIDFUNC(2,sub_b_ri,(RW1 d, IMM i)) { if (!i && !needflags) return; CLOBBER_SUB; d=rmw(d,1,1); raw_sub_b_ri(d,i); unlock2(d); } MIDFUNC(2,add_l_ri,(RW4 d, IMM i)) { if (!i && !needflags) return; if (isconst(d) && !needflags) { live.state[d].val+=i; return; } #if USE_OFFSET if (!needflags) { add_offset(d,i); return; } #endif CLOBBER_ADD; d=rmw(d,4,4); raw_add_l_ri(d,i); unlock2(d); } MIDFUNC(2,add_w_ri,(RW2 d, IMM i)) { if (!i && !needflags) return; CLOBBER_ADD; d=rmw(d,2,2); raw_add_w_ri(d,i); unlock2(d); } MIDFUNC(2,add_b_ri,(RW1 d, IMM i)) { if (!i && !needflags) return; CLOBBER_ADD; d=rmw(d,1,1); raw_add_b_ri(d,i); unlock2(d); } MIDFUNC(2,sbb_l,(RW4 d, RR4 s)) { CLOBBER_SBB; s=readreg(s,4); d=rmw(d,4,4); raw_sbb_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sbb_w,(RW2 d, RR2 s)) { CLOBBER_SBB; s=readreg(s,2); d=rmw(d,2,2); raw_sbb_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sbb_b,(RW1 d, RR1 s)) { CLOBBER_SBB; s=readreg(s,1); d=rmw(d,1,1); raw_sbb_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(sub_l_ri)(d,live.state[s].val); return; } CLOBBER_SUB; s=readreg(s,4); d=rmw(d,4,4); raw_sub_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_w,(RW2 d, RR2 s)) { if (isconst(s)) { COMPCALL(sub_w_ri)(d,(uae_u16)live.state[s].val); return; } CLOBBER_SUB; s=readreg(s,2); d=rmw(d,2,2); raw_sub_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_b,(RW1 d, RR1 s)) { if (isconst(s)) { COMPCALL(sub_b_ri)(d,(uae_u8)live.state[s].val); return; } CLOBBER_SUB; s=readreg(s,1); d=rmw(d,1,1); raw_sub_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,cmp_l,(RR4 d, RR4 s)) { CLOBBER_CMP; s=readreg(s,4); d=readreg(d,4); raw_cmp_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,cmp_w,(RR2 d, RR2 s)) { CLOBBER_CMP; s=readreg(s,2); d=readreg(d,2); raw_cmp_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,cmp_b,(RR1 d, RR1 s)) { CLOBBER_CMP; s=readreg(s,1); d=readreg(d,1); raw_cmp_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,xor_l,(RW4 d, RR4 s)) { CLOBBER_XOR; s=readreg(s,4); d=rmw(d,4,4); raw_xor_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,xor_w,(RW2 d, RR2 s)) { CLOBBER_XOR; s=readreg(s,2); d=rmw(d,2,2); raw_xor_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,xor_b,(RW1 d, RR1 s)) { CLOBBER_XOR; s=readreg(s,1); d=rmw(d,1,1); raw_xor_b(d,s); unlock2(d); unlock2(s); } #if defined(UAE) MIDFUNC(5,call_r_02,(RR4 r, RR4 in1, RR4 in2, IMM isize1, IMM isize2)) { clobber_flags(); in1=readreg_specific(in1,isize1,REG_PAR1); in2=readreg_specific(in2,isize2,REG_PAR2); r=readreg(r,4); prepare_for_call_1(); unlock2(r); unlock2(in1); unlock2(in2); prepare_for_call_2(); compemu_raw_call_r(r); } #endif #if defined(UAE) MIDFUNC(5,call_r_11,(W4 out1, RR4 r, RR4 in1, IMM osize, IMM isize)) { clobber_flags(); if (osize==4) { if (out1!=in1 && out1!=r) { COMPCALL(forget_about)(out1); } } else { tomem_c(out1); } in1=readreg_specific(in1,isize,REG_PAR1); r=readreg(r,4); prepare_for_call_1(); unlock2(in1); unlock2(r); prepare_for_call_2(); compemu_raw_call_r(r); live.nat[REG_RESULT].holds[0]=out1; live.nat[REG_RESULT].nholds=1; live.nat[REG_RESULT].touched=touchcnt++; live.state[out1].realreg=REG_RESULT; live.state[out1].realind=0; live.state[out1].val=0; live.state[out1].validsize=osize; live.state[out1].dirtysize=osize; set_status(out1,DIRTY); } #endif MIDFUNC(0,nop,(void)) { raw_emit_nop(); } /* forget_about() takes a mid-layer register */ MIDFUNC(1,forget_about,(W4 r)) { if (isinreg(r)) disassociate(r); live.state[r].val=0; set_status(r,UNDEF); } MIDFUNC(1,f_forget_about,(FW r)) { if (f_isinreg(r)) f_disassociate(r); live.fate[r].status=UNDEF; } // ARM optimized functions MIDFUNC(2,arm_ADD_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(arm_ADD_l_ri)(d,live.state[s].val); return; } s=readreg(s,4); d=rmw(d,4,4); raw_ADD_l_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_ADD_l_ri,(RW4 d, IMM i)) { if (!i) return; if (isconst(d)) { live.state[d].val+=i; return; } #if USE_OFFSET add_offset(d,i); return; #endif d=rmw(d,4,4); raw_LDR_l_ri(REG_WORK1, i); raw_ADD_l_rr(d,REG_WORK1); unlock2(d); } MIDFUNC(2,arm_ADD_l_ri8,(RW4 d, IMM i)) { if (!i) return; if (isconst(d)) { live.state[d].val+=i; return; } #if USE_OFFSET add_offset(d,i); return; #endif d=rmw(d,4,4); raw_ADD_l_rri(d,d,i); unlock2(d); } MIDFUNC(2,arm_SUB_l_ri8,(RW4 d, IMM i)) { if (!i) return; if (isconst(d)) { live.state[d].val-=i; return; } #if USE_OFFSET add_offset(d,-i); return; #endif d=rmw(d,4,4); raw_SUB_l_rri(d,d,i); unlock2(d); } MIDFUNC(2,arm_AND_l,(RW4 d, RR4 s)) { s=readreg(s,4); d=rmw(d,4,4); raw_AND_l_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_AND_w,(RW2 d, RR2 s)) { s=readreg(s,2); d=rmw(d,2,2); raw_AND_w_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_AND_b,(RW1 d, RR1 s)) { s=readreg(s,1); d=rmw(d,1,1); raw_AND_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_AND_l_ri8,(RW4 d, IMM i)) { if (isconst(d)) { live.state[d].val &= i; return; } d=rmw(d,4,4); raw_AND_l_ri(d,i); unlock2(d); } MIDFUNC(2,arm_EOR_b,(RW1 d, RR1 s)) { s=readreg(s,1); d=rmw(d,1,1); raw_EOR_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_EOR_l,(RW4 d, RR4 s)) { s=readreg(s,4); d=rmw(d,4,4); raw_EOR_l_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_EOR_w,(RW2 d, RR2 s)) { s=readreg(s,2); d=rmw(d,2,2); raw_EOR_w_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_ORR_b,(RW1 d, RR1 s)) { s=readreg(s,1); d=rmw(d,1,1); raw_ORR_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_ORR_l,(RW4 d, RR4 s)) { if (isconst(d) && isconst(s)) { live.state[d].val|=live.state[s].val; return; } s=readreg(s,4); d=rmw(d,4,4); raw_ORR_l_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_ORR_w,(RW2 d, RR2 s)) { s=readreg(s,2); d=rmw(d,2,2); raw_ORR_w_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,arm_ROR_l_ri8,(RW4 r, IMM i)) { if (!i) return; r=rmw(r,4,4); raw_ROR_l_ri(r,i); unlock2(r); } // Other static inline void flush_cpu_icache(void *start, void *stop) { register void *_beg __asm ("a1") = start; register void *_end __asm ("a2") = stop; register void *_flg __asm ("a3") = 0; #ifdef __ARM_EABI__ register unsigned long _scno __asm ("r7") = 0xf0002; __asm __volatile ("swi 0x0 @ sys_cacheflush" : "=r" (_beg) : "0" (_beg), "r" (_end), "r" (_flg), "r" (_scno)); #else __asm __volatile ("swi 0x9f0002 @ sys_cacheflush" : "=r" (_beg) : "0" (_beg), "r" (_end), "r" (_flg)); #endif } static inline void write_jmp_target(uae_u32* jmpaddr, cpuop_func* a) { *(jmpaddr) = (uae_u32) a; flush_cpu_icache((void *) jmpaddr, (void *) &jmpaddr[1]); } static inline void emit_jmp_target(uae_u32 a) { emit_long((uae_u32) a); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_midfunc_arm.h000066400000000000000000000164571504763705000271040ustar00rootroot00000000000000/* * compiler/compemu_midfunc_arm.h - Native MIDFUNCS for ARM * * Copyright (c) 2014 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Note: * File is included by compemu.h * */ // Arm optimized midfunc DECLARE_MIDFUNC(arm_ADD_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(arm_ADD_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(arm_ADD_l_ri8(RW4 d, IMM i)); DECLARE_MIDFUNC(arm_SUB_l_ri8(RW4 d, IMM i)); DECLARE_MIDFUNC(arm_AND_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(arm_AND_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(arm_AND_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(arm_AND_l_ri8(RW4 d, IMM i)); DECLARE_MIDFUNC(arm_EOR_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(arm_EOR_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(arm_EOR_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(arm_ORR_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(arm_ORR_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(arm_ORR_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(arm_ROR_l_ri8(RW4 r, IMM i)); // Emulated midfunc DECLARE_MIDFUNC(bt_l_ri(RR4 r, IMM i)); DECLARE_MIDFUNC(bt_l_rr(RR4 r, RR4 b)); DECLARE_MIDFUNC(btc_l_rr(RW4 r, RR4 b)); DECLARE_MIDFUNC(bts_l_rr(RW4 r, RR4 b)); DECLARE_MIDFUNC(btr_l_rr(RW4 r, RR4 b)); DECLARE_MIDFUNC(mov_l_rm(W4 d, IMM s)); DECLARE_MIDFUNC(mov_l_rm_indexed(W4 d, IMM base, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_l_mi(IMM d, IMM s)); DECLARE_MIDFUNC(mov_w_mi(IMM d, IMM s)); DECLARE_MIDFUNC(mov_b_mi(IMM d, IMM s)); DECLARE_MIDFUNC(rol_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(rol_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(rol_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(rol_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(rol_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(rol_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shll_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(shll_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(shll_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(ror_b_ri(RR1 r, IMM i)); DECLARE_MIDFUNC(ror_w_ri(RR2 r, IMM i)); DECLARE_MIDFUNC(ror_l_ri(RR4 r, IMM i)); DECLARE_MIDFUNC(ror_l_rr(RR4 d, RR1 r)); DECLARE_MIDFUNC(ror_w_rr(RR2 d, RR1 r)); DECLARE_MIDFUNC(ror_b_rr(RR1 d, RR1 r)); DECLARE_MIDFUNC(shrl_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(shrl_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(shrl_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(shra_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(shra_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(shra_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(shll_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shll_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(shll_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(shrl_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shrl_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(shrl_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(shra_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shra_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(shra_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(setcc(W1 d, IMM cc)); DECLARE_MIDFUNC(setcc_m(IMM d, IMM cc)); DECLARE_MIDFUNC(cmov_l_rr(RW4 d, RR4 s, IMM cc)); DECLARE_MIDFUNC(bsf_l_rr(W4 d, RR4 s)); DECLARE_MIDFUNC(pop_l(W4 d)); DECLARE_MIDFUNC(push_l(RR4 s)); DECLARE_MIDFUNC(sign_extend_16_rr(W4 d, RR2 s)); DECLARE_MIDFUNC(sign_extend_8_rr(W4 d, RR1 s)); DECLARE_MIDFUNC(zero_extend_16_rr(W4 d, RR2 s)); DECLARE_MIDFUNC(zero_extend_8_rr(W4 d, RR1 s)); DECLARE_MIDFUNC(simulate_bsf(W4 tmp, RW4 s)); DECLARE_MIDFUNC(imul_64_32(RW4 d, RW4 s)); DECLARE_MIDFUNC(mul_64_32(RW4 d, RW4 s)); DECLARE_MIDFUNC(imul_32_32(RW4 d, RR4 s)); DECLARE_MIDFUNC(mov_b_rr(W1 d, RR1 s)); DECLARE_MIDFUNC(mov_w_rr(W2 d, RR2 s)); DECLARE_MIDFUNC(mov_l_rR(W4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_rR(W2 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_l_brR(W4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_brR(W2 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_b_brR(W1 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_l_Ri(RR4 d, IMM i, IMM offset)); DECLARE_MIDFUNC(mov_w_Ri(RR4 d, IMM i, IMM offset)); DECLARE_MIDFUNC(mov_l_Rr(RR4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_Rr(RR4 d, RR2 s, IMM offset)); DECLARE_MIDFUNC(lea_l_brr(W4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(lea_l_brr_indexed(W4 d, RR4 s, RR4 index, IMM factor, IMM offset)); DECLARE_MIDFUNC(lea_l_rr_indexed(W4 d, RR4 s, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_l_bRr(RR4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_bRr(RR4 d, RR2 s, IMM offset)); DECLARE_MIDFUNC(mov_b_bRr(RR4 d, RR1 s, IMM offset)); DECLARE_MIDFUNC(mid_bswap_32(RW4 r)); DECLARE_MIDFUNC(mid_bswap_16(RW2 r)); DECLARE_MIDFUNC(mov_l_rr(W4 d, RR4 s)); DECLARE_MIDFUNC(mov_l_mr(IMM d, RR4 s)); DECLARE_MIDFUNC(mov_w_mr(IMM d, RR2 s)); DECLARE_MIDFUNC(mov_w_rm(W2 d, IMM s)); DECLARE_MIDFUNC(mov_b_mr(IMM d, RR1 s)); DECLARE_MIDFUNC(mov_b_rm(W1 d, IMM s)); DECLARE_MIDFUNC(mov_l_ri(W4 d, IMM s)); DECLARE_MIDFUNC(mov_w_ri(W2 d, IMM s)); DECLARE_MIDFUNC(mov_b_ri(W1 d, IMM s)); DECLARE_MIDFUNC(test_l_ri(RR4 d, IMM i)); DECLARE_MIDFUNC(test_l_rr(RR4 d, RR4 s)); DECLARE_MIDFUNC(test_w_rr(RR2 d, RR2 s)); DECLARE_MIDFUNC(test_b_rr(RR1 d, RR1 s)); DECLARE_MIDFUNC(and_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(and_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(and_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(and_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(or_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(or_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(or_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(or_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(adc_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(adc_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(adc_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(add_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(add_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(add_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(sub_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(sub_w_ri(RW2 d, IMM i)); DECLARE_MIDFUNC(sub_b_ri(RW1 d, IMM i)); DECLARE_MIDFUNC(add_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(add_w_ri(RW2 d, IMM i)); DECLARE_MIDFUNC(add_b_ri(RW1 d, IMM i)); DECLARE_MIDFUNC(sbb_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(sbb_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(sbb_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(sub_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(sub_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(sub_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(cmp_l(RR4 d, RR4 s)); DECLARE_MIDFUNC(cmp_w(RR2 d, RR2 s)); DECLARE_MIDFUNC(cmp_b(RR1 d, RR1 s)); DECLARE_MIDFUNC(xor_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(xor_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(xor_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(call_r_02(RR4 r, RR4 in1, RR4 in2, IMM isize1, IMM isize2)); DECLARE_MIDFUNC(call_r_11(W4 out1, RR4 r, RR4 in1, IMM osize, IMM isize)); DECLARE_MIDFUNC(live_flags(void)); DECLARE_MIDFUNC(dont_care_flags(void)); DECLARE_MIDFUNC(duplicate_carry(void)); DECLARE_MIDFUNC(restore_carry(void)); DECLARE_MIDFUNC(start_needflags(void)); DECLARE_MIDFUNC(end_needflags(void)); DECLARE_MIDFUNC(make_flags_live(void)); DECLARE_MIDFUNC(forget_about(W4 r)); DECLARE_MIDFUNC(nop(void)); DECLARE_MIDFUNC(f_forget_about(FW r)); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_midfunc_arm2.cpp000066400000000000000000002645771504763705000275310ustar00rootroot00000000000000/* * compiler/compemu_midfunc_arm.cpp - Native MIDFUNCS for ARM (JIT v2) * * Copyright (c) 2014 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Note: * File is included by compemu_support.cpp * */ const uae_u32 ARM_CCR_MAP[] = { 0, ARM_C_FLAG, // 1 C ARM_V_FLAG, // 2 V ARM_C_FLAG | ARM_V_FLAG, // 3 VC ARM_Z_FLAG, // 4 Z ARM_Z_FLAG | ARM_C_FLAG, // 5 ZC ARM_Z_FLAG | ARM_V_FLAG, // 6 ZV ARM_Z_FLAG | ARM_C_FLAG | ARM_V_FLAG, // 7 ZVC ARM_N_FLAG, // 8 N ARM_N_FLAG | ARM_C_FLAG, // 9 NC ARM_N_FLAG | ARM_V_FLAG, // 10 NV ARM_N_FLAG | ARM_C_FLAG | ARM_V_FLAG, // 11 NVC ARM_N_FLAG | ARM_Z_FLAG, // 12 NZ ARM_N_FLAG | ARM_Z_FLAG | ARM_C_FLAG, // 13 NZC ARM_N_FLAG | ARM_Z_FLAG | ARM_V_FLAG, // 14 NZV ARM_N_FLAG | ARM_Z_FLAG | ARM_C_FLAG | ARM_V_FLAG, // 15 NZVC }; // First we start with some helper functions (may be moved to codegen_arm) static inline void UNSIGNED8_IMM_2_REG(W4 r, IMM v) { MOV_ri8(r, (uint8) v); } static inline void SIGNED8_IMM_2_REG(W4 r, IMM v) { if (v & 0x80) { MVN_ri8(r, (uint8) ~v); } else { MOV_ri8(r, (uint8) v); } } static inline void UNSIGNED16_IMM_2_REG(W4 r, IMM v) { MOV_ri8(r, (uint8) v); ORR_rri8RORi(r, r, (uint8)(v >> 8), 24); } static inline void SIGNED16_IMM_2_REG(W4 r, IMM v) { #if defined(ARMV6_ASSEMBLY) MOV_ri8(r, (uint8) v); ORR_rri8RORi(r, r, (uint8)(v >> 8), 24); SXTH_rr(r, r); #else MOV_ri8(r, (uint8)(v << 16)); ORR_rri8RORi(r, r, (uint8)(v >> 8), 8); ASR_rri(r, r, 16); #endif } static inline void UNSIGNED8_REG_2_REG(W4 d, RR4 s) { #if defined(ARMV6_ASSEMBLY) UXTB_rr(d, s); #else ROR_rri(d, s, 8); LSR_rri(d, d, 24); #endif } static inline void SIGNED8_REG_2_REG(W4 d, RR4 s) { #if defined(ARMV6_ASSEMBLY) SXTB_rr(d, s); #else ROR_rri(d, s, 8); ASR_rri(d, d, 24); #endif } static inline void UNSIGNED16_REG_2_REG(W4 d, RR4 s) { #if defined(ARMV6_ASSEMBLY) UXTH_rr(d, s); #else LSL_rri(d, s, 16); LSR_rri(d, d, 16); #endif } static inline void SIGNED16_REG_2_REG(W4 d, RR4 s) { #if defined(ARMV6_ASSEMBLY) SXTH_rr(d, s); #else LSL_rri(d, s, 16); ASR_rri(d, d, 16); #endif } #define ZERO_EXTEND_8_REG_2_REG(d,s) UNSIGNED8_REG_2_REG(d,s) #define ZERO_EXTEND_16_REG_2_REG(d,s) UNSIGNED16_REG_2_REG(d,s) #define SIGN_EXTEND_8_REG_2_REG(d,s) SIGNED8_REG_2_REG(d,s) #define SIGN_EXTEND_16_REG_2_REG(d,s) SIGNED16_REG_2_REG(d,s) MIDFUNC(0,restore_inverted_carry,(void)) { RR4 r=readreg(FLAGX,4); MRS_CPSR(REG_WORK1); TEQ_ri(r,1); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_C_FLAG); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(r); } /* * ADD * Operand Syntax: , Dn * Dn, * * Operand Size: 8,16,32 * * X Set the same as the carry bit. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if an overflow is generated. Cleared otherwise. * C Set if a carry is generated. Cleared otherwise. * */ MIDFUNC(3,jnf_ADD_imm,(W4 d, RR4 s, IMM v)) { if (isconst(s)) { set_const(d,live.state[s].val+v); return; } s=readreg(s,4); d=writereg(d,4); compemu_raw_mov_l_ri(REG_WORK1, v); ADD_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ADD,(W4 d, RR4 s, RR4 v)) { if (isconst(v)) { COMPCALL(jnf_ADD_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); ADD_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_ADD_b_imm,(W4 d, RR1 s, IMM v)) { s=readreg(s,4); d=writereg(d,4); SIGNED8_IMM_2_REG(REG_WORK2, (uint8)v); SIGNED8_REG_2_REG(REG_WORK1, s); ADDS_rrr(d,REG_WORK1,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ADD_b,(W4 d, RR1 s, RR1 v)) { if (isconst(v)) { COMPCALL(jff_ADD_b_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); ADDS_rrr(d,REG_WORK1,REG_WORK2); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_ADD_w_imm,(W4 d, RR2 s, IMM v)) { s=readreg(s,4); d=writereg(d,4); SIGNED16_IMM_2_REG(REG_WORK2, (uint16)v); SIGNED16_REG_2_REG(REG_WORK1, s); ADDS_rrr(d,REG_WORK1,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ADD_w,(W4 d, RR2 s, RR2 v)) { if (isconst(v)) { COMPCALL(jff_ADD_w_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); ADDS_rrr(d,REG_WORK1,REG_WORK2); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_ADD_l_imm,(W4 d, RR4 s, IMM v)) { s=readreg(s,4); d=writereg(d,4); compemu_raw_mov_l_ri(REG_WORK2, v); ADDS_rrr(d,s,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ADD_l,(W4 d, RR4 s, RR4 v)) { if (isconst(v)) { COMPCALL(jff_ADD_l_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); ADDS_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } /* * ADDA * Operand Syntax: , An * * Operand Size: 16,32 * * Flags: Not affected. * */ MIDFUNC(2,jnf_ADDA_b,(W4 d, RR1 s)) { s=readreg(s,4); d=rmw(d,4,4); SIGNED8_REG_2_REG(REG_WORK1,s); ADD_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_ADDA_w,(W4 d, RR2 s)) { s=readreg(s,4); d=rmw(d,4,4); SIGNED16_REG_2_REG(REG_WORK1,s); ADD_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_ADDA_l,(W4 d, RR4 s)) { s=readreg(s,4); d=rmw(d,4,4); ADD_rrr(d,d,s); unlock2(d); unlock2(s); } /* * ADDX * Operand Syntax: Dy, Dx * -(Ay), -(Ax) * * Operand Size: 8,16,32 * * X Set the same as the carry bit. * N Set if the result is negative. Cleared otherwise. * Z Cleared if the result is nonzero; unchanged otherwise. * V Set if an overflow is generated. Cleared otherwise. * C Set if a carry is generated. Cleared otherwise. * * Attention: Z is cleared only if the result is nonzero. Unchanged otherwise * */ MIDFUNC(3,jnf_ADDX,(W4 d, RR4 s, RR4 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); ADC_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_ADDX_b,(W4 d, RR1 s, RR1 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); PUSH(REG_WORK2); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); ADCS_rrr(d,REG_WORK1,REG_WORK2); POP(REG_WORK2); MRS_CPSR(REG_WORK1); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_ADDX_w,(W4 d, RR2 s, RR2 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); PUSH(REG_WORK2); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); ADCS_rrr(d,REG_WORK1,REG_WORK2); POP(REG_WORK2); MRS_CPSR(REG_WORK1); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_ADDX_l,(W4 d, RR4 s, RR4 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); PUSH(REG_WORK2); ADCS_rrr(d,s,v); POP(REG_WORK2); MRS_CPSR(REG_WORK1); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); unlock2(v); } /* * ANDI * Operand Syntax: #, CCR * * Operand Size: 8 * * X Cleared if bit 4 of immediate operand is zero. Unchanged otherwise. * N Cleared if bit 3 of immediate operand is zero. Unchanged otherwise. * Z Cleared if bit 2 of immediate operand is zero. Unchanged otherwise. * V Cleared if bit 1 of immediate operand is zero. Unchanged otherwise. * C Cleared if bit 0 of immediate operand is zero. Unchanged otherwise. * */ MIDFUNC(1,jff_ANDSR,(IMM s, IMM x)) { MRS_CPSR(REG_WORK1); AND_rri(REG_WORK1, REG_WORK1, s); MSR_CPSRf_r(REG_WORK1); if (!x) { compemu_raw_mov_l_ri(REG_WORK1, (uintptr)live.state[FLAGX].mem); MOV_ri(REG_WORK2, 0); STRB_rR(REG_WORK2, REG_WORK1); } } /* * AND * Operand Syntax: , Dn * Dn, * * Operand Size: 8,16,32 * * X Not affected. * N Set if the most significant bit of the result is set. * Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(3,jnf_AND,(W4 d, RR4 s, RR4 v)) { if (isconst(s) && isconst(v)) { set_const(d, live.state[s].val&live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); AND_rrr(d, s, v); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_AND_b,(W4 d, RR1 s, RR1 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); MSR_CPSRf_i(0); ANDS_rrr(d, REG_WORK1, REG_WORK2); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_AND_w,(W4 d, RR2 s, RR2 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); MSR_CPSRf_i(0); ANDS_rrr(d, REG_WORK1, REG_WORK2); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_AND_l,(W4 d, RR4 s, RR4 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); ANDS_rrr(d, s,v); unlock2(v); unlock2(d); unlock2(s); } /* * ASL * Operand Syntax: Dx, Dy * #, Dy * * * Operand Size: 8,16,32 * * X Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if the most significant bit is changed at any time during the shift operation. Cleared otherwise. * C Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * */ MIDFUNC(3,jff_ASL_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d, s, 24); if (i) { MRS_CPSR(REG_WORK1); // store flags BIC_rri(REG_WORK1, REG_WORK1, ARM_N_FLAG|ARM_Z_FLAG|ARM_V_FLAG);// Clear everything except N & Z PUSH(REG_WORK1); // Calculate V Flag MVN_ri(REG_WORK2, 0); LSR_rri(REG_WORK2, REG_WORK2, (i+1)); MVN_rr(REG_WORK2, REG_WORK2); AND_rrr(REG_WORK1, d, REG_WORK2); TST_rr(REG_WORK1, REG_WORK1); CC_TEQ_rr(NATIVE_CC_NE, REG_WORK1, REG_WORK2); POP(REG_WORK1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1);// restore flags LSLS_rri(d,d,i); } else { MSR_CPSRf_i(0); TST_rr(d,d); } REV_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ASL_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d, s, 16); if (i) { MRS_CPSR(REG_WORK1); // store flags BIC_rri(REG_WORK1, REG_WORK1, ARM_N_FLAG|ARM_Z_FLAG|ARM_V_FLAG);// Clear everything except N & Z PUSH(REG_WORK1); // Calculate V Flag MVN_ri(REG_WORK2, 0); LSR_rri(REG_WORK2, REG_WORK2, (i+1)); MVN_rr(REG_WORK2, REG_WORK2); AND_rrr(REG_WORK1, d, REG_WORK2); TST_rr(REG_WORK1, REG_WORK1); CC_TEQ_rr(NATIVE_CC_NE, REG_WORK1, REG_WORK2); POP(REG_WORK1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1);// retore flags LSLS_rri(d,d,i); } else { MSR_CPSRf_i(0); TST_rr(d,d); } ASR_rri(d,d, 16); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ASL_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i) { MRS_CPSR(REG_WORK1); // store flags BIC_rri(REG_WORK1, REG_WORK1, ARM_N_FLAG|ARM_Z_FLAG|ARM_V_FLAG);// Clear everything except C PUSH(REG_WORK1); // Calculate V Flag MVN_ri(REG_WORK2, 0); LSR_rri(REG_WORK2, REG_WORK2, (i+1)); MVN_rr(REG_WORK2, REG_WORK2); AND_rrr(REG_WORK1, s, REG_WORK2); TST_rr(REG_WORK1, REG_WORK1); CC_TEQ_rr(NATIVE_CC_NE, REG_WORK1, REG_WORK2); POP(REG_WORK1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1);// retore flags LSLS_rri(d,s,i); } else { MSR_CPSRf_i(0); MOVS_rr(d, s); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ASL_b_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); // Calculate V Flag MRS_CPSR(REG_WORK1);// store flags BIC_rri(REG_WORK1, REG_WORK1, ARM_N_FLAG|ARM_Z_FLAG|ARM_V_FLAG);// Clear everything except C PUSH(REG_WORK1); LSL_rri(d, s, 24); // Calculate V Flag MVN_ri(REG_WORK2, 0); LSR_rrr(REG_WORK2, REG_WORK2, i); LSR_rri(REG_WORK2, REG_WORK2, 1); MVN_rr(REG_WORK2, REG_WORK2); AND_rrr(REG_WORK1, d, REG_WORK2); TST_rr(REG_WORK1, REG_WORK1); CC_TEQ_rr(NATIVE_CC_NE, REG_WORK1, REG_WORK2); POP(REG_WORK1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1);// retore flags AND_rri(REG_WORK2, i, 63); LSLS_rrr(d,d,REG_WORK2); ASR_rri(d,d, 24); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ASL_w_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); // Calculate V Flag MRS_CPSR(REG_WORK1);// store flags BIC_rri(REG_WORK1, REG_WORK1, ARM_N_FLAG|ARM_Z_FLAG|ARM_V_FLAG);// Clear everything except c PUSH(REG_WORK1); LSL_rri(d, s, 16); // Calculate V Flag MVN_ri(REG_WORK2, 0); LSR_rrr(REG_WORK2, REG_WORK2, i); LSR_rri(REG_WORK2, REG_WORK2, 1); MVN_rr(REG_WORK2, REG_WORK2); AND_rrr(REG_WORK1, d, REG_WORK2); TST_rr(REG_WORK1, REG_WORK1); CC_TEQ_rr(NATIVE_CC_NE, REG_WORK1, REG_WORK2); POP(REG_WORK1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1);// retore flags AND_rri(REG_WORK2, i, 63); LSLS_rrr(d,d,REG_WORK2); ASR_rri(d,d, 16); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ASL_l_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); // Calculate V Flag MRS_CPSR(REG_WORK1);// store flags BIC_rri(REG_WORK1, REG_WORK1, ARM_N_FLAG|ARM_Z_FLAG|ARM_V_FLAG);// Clear everything except C PUSH(REG_WORK1); // Calculate V Flag MVN_ri(REG_WORK2, 0); LSR_rrr(REG_WORK2, REG_WORK2, i); LSR_rri(REG_WORK2, REG_WORK2, 1); MVN_rr(REG_WORK2, REG_WORK2); AND_rrr(REG_WORK1, s, REG_WORK2); TST_rr(REG_WORK1, REG_WORK1); CC_TEQ_rr(NATIVE_CC_NE, REG_WORK1, REG_WORK2); POP(REG_WORK1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1);// retore flags AND_rri(REG_WORK2, i, 63); LSLS_rrr(d,s,REG_WORK2); unlock2(d); unlock2(s); unlock2(i); } /* * ASLW * Operand Syntax: * * Operand Size: 16 * * X Set according to the last bit shifted out of the operand. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if the most significant bit is changed at any time during the shift operation. Cleared otherwise. * C Set according to the last bit shifted out of the operand. * */ MIDFUNC(2,jnf_ASLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_ASLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); LSLS_rri(d,s,17); MRS_CPSR(REG_WORK1); CC_ORR_rri(NATIVE_CC_MI, REG_WORK1, REG_WORK1, ARM_V_FLAG); CC_EOR_rri(NATIVE_CC_CS, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(d); unlock2(s); } /* * ASR * Operand Syntax: Dx, Dy * #, Dy * * * Operand Size: 8,16,32 * * X Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if the most significant bit is changed at any time during the shift operation. Cleared otherwise. * C Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * */ MIDFUNC(3,jnf_ASR_b_imm,(W4 d, RR4 s, IMM i)) { if (!i) return; s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); ASR_rri(d,d,i); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ASR_w_imm,(W4 d, RR4 s, IMM i)) { if (!i) return; s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); ASR_rri(d,d,i); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ASR_l_imm,(W4 d, RR4 s, IMM i)) { if (!i) return; s=readreg(s,4); d=writereg(d,4); ASR_rri(d,s,i); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ASR_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); if (i) { MSR_CPSRf_i(0); ASRS_rri(d,d,i); } else { CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C TST_rr(d,d); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ASR_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); if (i) { MSR_CPSRf_i(0); ASRS_rri(d,d,i); } else { CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C TST_rr(d,d); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ASR_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i) { MSR_CPSRf_i(0); ASRS_rri(d,s,i); } else { CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C TST_rr(s,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ASR_b_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); AND_rri(REG_WORK1, i, 63); ASR_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ASR_w_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); AND_rri(REG_WORK1, i, 63); ASR_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ASR_l_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 63); ASR_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ASR_b_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); ASRS_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ASR_w_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); ASRS_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ASR_l_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); ASRS_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } /* * ASRW * Operand Syntax: * * Operand Size: 16 * * X Set according to the last bit shifted out of the operand. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if the most significant bit is changed at any time during the shift operation. Cleared otherwise. * C Set according to the last bit shifted out of the operand. * */ MIDFUNC(2,jnf_ASRW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); ASR_rri(d,d,1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_ASRW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); MSR_CPSRf_i(0); ASR_rri(d,d,1); unlock2(d); unlock2(s); } /* * BCHG * Operand Syntax: Dn, * #, * * Operand Size: 8,32 * * X Not affected. * N Not affected. * Z Set if the bit tested is zero. Cleared otherwise. * V Not affected. * C Not affected. * */ MIDFUNC(2,jnf_BCHG_b_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); EOR_rri(d,d,(1 << s)); unlock2(d); } MIDFUNC(2,jnf_BCHG_l_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); EOR_rri(d,d,(1 << s)); unlock2(d); } MIDFUNC(2,jnf_BCHG_b,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jnf_BCHG_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); EOR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_BCHG_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jnf_BCHG_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); EOR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BCHG_b_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); uae_u32 v = (1 << s); MRS_CPSR(REG_WORK1); TST_ri(d,v); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); EOR_rri(d,d,v); unlock2(d); } MIDFUNC(2,jff_BCHG_l_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); uae_u32 v = (1 << s); MRS_CPSR(REG_WORK1); TST_ri(d,v); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); EOR_rri(d,d,v); unlock2(d); } MIDFUNC(2,jff_BCHG_b,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BCHG_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); EOR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BCHG_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BCHG_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); EOR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } /* * BCLR * Operand Syntax: Dn, * #, * * Operand Size: 8,32 * * X Not affected. * N Not affected. * Z Set if the bit tested is zero. Cleared otherwise. * V Not affected. * C Not affected. * */ MIDFUNC(2,jnf_BCLR_b_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); BIC_rri(d,d,(1 << s)); unlock2(d); } MIDFUNC(2,jnf_BCLR_l_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); BIC_rri(d,d,(1 << s)); unlock2(d); } MIDFUNC(2,jnf_BCLR_b,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jnf_BCLR_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); BIC_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_BCLR_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jnf_BCLR_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); BIC_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BCLR_b_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); uae_u32 v = (1 << s); MRS_CPSR(REG_WORK1); TST_ri(d,v); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); BIC_rri(d,d,v); unlock2(d); } MIDFUNC(2,jff_BCLR_l_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); uae_u32 v = (1 << s); MRS_CPSR(REG_WORK1); TST_ri(d,v); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); BIC_rri(d,d,v); unlock2(d); } MIDFUNC(2,jff_BCLR_b,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BCLR_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); BIC_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BCLR_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BCLR_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); BIC_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } /* * BSET * Operand Syntax: Dn, * #, * * Operand Size: 8,32 * * X Not affected. * N Not affected. * Z Set if the bit tested is zero. Cleared otherwise. * V Not affected. * C Not affected. * */ MIDFUNC(2,jnf_BSET_b_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); ORR_rri(d,d,(1 << s)); unlock2(d); } MIDFUNC(2,jnf_BSET_l_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); ORR_rri(d,d,(1 << s)); unlock2(d); } MIDFUNC(2,jnf_BSET_b,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jnf_BSET_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); ORR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_BSET_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jnf_BSET_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); ORR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BSET_b_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); uae_u32 v = (1 << s); MRS_CPSR(REG_WORK1); TST_ri(d,v); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); ORR_rri(d,d,v); unlock2(d); } MIDFUNC(2,jff_BSET_l_imm,(RW4 d, IMM s)) { d=rmw(d,4,4); uae_u32 v = (1 << s); MRS_CPSR(REG_WORK1); TST_ri(d,v); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); ORR_rri(d,d,v); unlock2(d); } MIDFUNC(2,jff_BSET_b,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BSET_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); ORR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BSET_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BSET_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=rmw(d,4,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); ORR_rrr(d,d,REG_WORK2); unlock2(d); unlock2(s); } /* * BTST * Operand Syntax: Dn, * #, * * Operand Size: 8,32 * * X Not affected * N Not affected * Z Set if the bit tested is zero. Cleared otherwise * V Not affected * C Not affected * */ MIDFUNC(2,jff_BTST_b_imm,(RR4 d, IMM s)) { d=readreg(d,4); MRS_CPSR(REG_WORK1); TST_ri(d,(1 << s)); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); } MIDFUNC(2,jff_BTST_l_imm,(RR4 d, IMM s)) { d=readreg(d,4); MRS_CPSR(REG_WORK1); TST_ri(d,(1 << s)); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); } MIDFUNC(2,jff_BTST_b,(RR4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BTST_b_imm)(d,live.state[s].val&7); return; } s=readreg(s,4); d=readreg(d,4); AND_rri(REG_WORK1, s, 7); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_BTST_l,(RR4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_BTST_l_imm)(d,live.state[s].val&31); return; } s=readreg(s,4); d=readreg(d,4); AND_rri(REG_WORK1, s, 31); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); MRS_CPSR(REG_WORK1); TST_rr(d,REG_WORK2); CC_BIC_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_Z_FLAG); CC_ORR_rri(NATIVE_CC_EQ, REG_WORK1, REG_WORK1, ARM_Z_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } /* * CLR * Operand Syntax: * * Operand Size: 8,16,32 * * X Not affected. * N Always cleared. * Z Always set. * V Always cleared. * C Always cleared. * */ MIDFUNC(1,jnf_CLR,(W4 d)) { d=writereg(d,4); MOV_ri(d,0); unlock2(d); } MIDFUNC(1,jff_CLR,(W4 d)) { d=writereg(d,4); MOV_ri(d,0); MSR_CPSR_i(ARM_Z_FLAG); unlock2(d); } /* * CMP * Operand Syntax: , Dn * * Operand Size: 8,16,32 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if an overflow occurs. Cleared otherwise. * C Set if a borrow occurs. Cleared otherwise. * */ MIDFUNC(2,jff_CMP_b,(RR1 d, RR1 s)) { d=readreg(d,4); s=readreg(s,4); SIGNED8_REG_2_REG(REG_WORK1, d); SIGNED8_REG_2_REG(REG_WORK2, s); CMP_rr(REG_WORK1,REG_WORK2); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); // inverted_carry = true; unlock2(s); unlock2(d); } MIDFUNC(2,jff_CMP_w,(RR2 d, RR2 s)) { d=readreg(d,4); s=readreg(s,4); SIGNED16_REG_2_REG(REG_WORK1, d); SIGNED16_REG_2_REG(REG_WORK2, s); CMP_rr(REG_WORK1,REG_WORK2); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); // inverted_carry = true; unlock2(s); unlock2(d); } MIDFUNC(2,jff_CMP_l,(RR4 d, RR4 s)) { d=readreg(d,4); s=readreg(s,4); CMP_rr(d,s); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); // inverted_carry = true; unlock2(s); unlock2(d); } /* * CMPA * Operand Syntax: , An * * Operand Size: 16,32 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if an overflow occurs. Cleared otherwise. * C Set if a borrow occurs. Cleared otherwise. * */ MIDFUNC(2,jff_CMPA_b,(RR1 d, RR1 s)) { d=readreg(d,4); s=readreg(s,4); SIGNED8_REG_2_REG(REG_WORK2, s); CMP_rr(d,REG_WORK2); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); // invertedcarry = true; unlock2(s); unlock2(d); } MIDFUNC(2,jff_CMPA_w,(RR2 d, RR2 s)) { d=readreg(d,4); s=readreg(s,4); SIGNED16_REG_2_REG(REG_WORK2, s); CMP_rr(d,REG_WORK2); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); // invertedcarry = true; unlock2(s); unlock2(d); } MIDFUNC(2,jff_CMPA_l,(RR4 d, RR4 s)) { d=readreg(d,4); s=readreg(s,4); CMP_rr(d,s); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); // invertedcarry = true; unlock2(s); unlock2(d); } /* * EOR * Operand Syntax: Dn, * * Operand Size: 8,16,32 * * X Not affected. * N Set if the most significant bit of the result is set. * Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(3,jnf_EOR,(W4 d, RR4 s, RR4 v)) { if (isconst(s) && isconst(v)) { set_const(d, live.state[s].val^live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); EOR_rrr(d, s, v); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_EOR_b,(W4 d, RR1 s, RR1 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); MSR_CPSRf_i(0); EORS_rrr(d, REG_WORK1, REG_WORK2); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_EOR_w,(W4 d, RR2 s, RR2 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); MSR_CPSRf_i(0); EORS_rrr(d, REG_WORK1, REG_WORK2); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_EOR_l,(W4 d, RR4 s, RR4 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); EORS_rrr(d, s,v); unlock2(v); unlock2(d); unlock2(s); } /* * EORI * Operand Syntax: #, CCR * * Operand Size: 8 * * X — Changed if bit 4 of immediate operand is one; unchanged otherwise. * N — Changed if bit 3 of immediate operand is one; unchanged otherwise. * Z — Changed if bit 2 of immediate operand is one; unchanged otherwise. * V — Changed if bit 1 of immediate operand is one; unchanged otherwise. * C — Changed if bit 0 of immediate operand is one; unchanged otherwise. * */ MIDFUNC(1,jff_EORSR,(IMM s, IMM x)) { MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, s); MSR_CPSRf_r(REG_WORK1); if (x) { compemu_raw_mov_l_ri(REG_WORK1, (uintptr)live.state[FLAGX].mem); LDRB_rR(REG_WORK2, REG_WORK1); EOR_rri(REG_WORK2, REG_WORK2, 1); STRB_rR(REG_WORK2, REG_WORK1); } } /* * EXT * Operand Syntax: * * Operand Size: 16,32 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(2,jnf_EXT_b,(W4 d, RR4 s)) { if (isconst(s)) { set_const(d,(uae_s32)(uae_s8)live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jnf_EXT_w,(W4 d, RR4 s)) { if (isconst(s)) { set_const(d,(uae_s32)(uae_s8)live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jnf_EXT_l,(W4 d, RR4 s)) { if (isconst(s)) { set_const(d,(uae_s32)(uae_s16)live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jff_EXT_b,(W4 d, RR4 s)) { if (isconst(s)) { d=writereg(d,4); SIGNED8_IMM_2_REG(d, (uint8)live.state[s].val); } else { s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); unlock2(s); } MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } MIDFUNC(2,jff_EXT_w,(W4 d, RR4 s)) { if (isconst(s)) { d=writereg(d,4); SIGNED8_IMM_2_REG(d, (uint8)live.state[s].val); } else { s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); unlock2(s); } MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } MIDFUNC(2,jff_EXT_l,(W4 d, RR4 s)) { if (isconst(s)) { d=writereg(d,4); SIGNED16_IMM_2_REG(d, (uint16)live.state[s].val); } else { s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); unlock2(s); } MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } /* * LSL * Operand Syntax: Dx, Dy * #, Dy * * * Operand Size: 8,16,32 * * X Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit shifted out of the operand. Cleared for a shift count of zero. * */ MIDFUNC(3,jnf_LSL_imm,(W4 d, RR4 s, IMM i)) { if (!i) return; s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,i); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_LSL_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 63); LSL_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_LSL_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); UNSIGNED8_REG_2_REG(d, s); MSR_CPSRf_i(0); REV_rr(d,d); if (i) { LSLS_rri(d,d,i); } else { TST_rr(d,d); } REV_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jff_LSL_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); LSL_rri(d,s,16); if (i) { LSLS_rri(d,d,i); } else { TST_rr(d,d); } LSR_rri(d,d,16); unlock2(d); unlock2(s); } MIDFUNC(3,jff_LSL_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); if (i) { LSLS_rri(d,s,i); } else { MOV_rr(d,s); TST_rr(d,d); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_LSL_b_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); UNSIGNED8_REG_2_REG(d,s); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C REV_rr(d,d); AND_rri(REG_WORK1, i, 63); LSLS_rrr(d,d,REG_WORK1); REV_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_LSL_w_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C LSL_rri(d, s, 16); AND_rri(REG_WORK1, i, 63); LSLS_rrr(d,d,REG_WORK1); LSR_rri(d, d, 16); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_LSL_l_reg,(W4 d, RR4 s, RR4 i)) { i=readreg(i,4); s=readreg(s,4); d=writereg(d,4); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); LSLS_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } /* * LSLW * Operand Syntax: * * Operand Size: 16 * * X Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit shifted out of the operand. Cleared for a shift count of zero. * */ MIDFUNC(2,jnf_LSLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_LSLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); LSLS_rri(d,s,17); LSR_rri(d,d,16); unlock2(d); unlock2(s); } /* * LSR * Operand Syntax: Dx, Dy * #, Dy * * * Operand Size: 8,16,32 * * X Set according to the last bit shifted out of the operand. * Unaffected for a shift count of zero. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit shifted out of the operand. * Cleared for a shift count of zero. * */ MIDFUNC(3,jnf_LSR_b_imm,(W4 d, RR4 s, IMM i)) { int isrmw; if (!i) return; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED8_REG_2_REG(d, s); LSR_rri(d,d,i); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(3,jnf_LSR_w_imm,(W4 d, RR4 s, IMM i)) { int isrmw; if (!i) return; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED16_REG_2_REG(d, s); LSR_rri(d,d,i); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(3,jnf_LSR_l_imm,(W4 d, RR4 s, IMM i)) { int isrmw; if (!i) return; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } LSR_rri(d,s,i); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(3,jff_LSR_b_imm,(W4 d, RR4 s, IMM i)) { int isrmw; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED8_REG_2_REG(d, s); MSR_CPSRf_i(0); if (i) { LSRS_rri(d,d,i); } else { TST_rr(d,d); } if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(3,jff_LSR_w_imm,(W4 d, RR4 s, IMM i)) { int isrmw; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED16_REG_2_REG(d, s); MSR_CPSRf_i(0); if (i) { LSRS_rri(d,d,i); } else { TST_rr(d,d); } if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(3,jff_LSR_l_imm,(W4 d, RR4 s, IMM i)) { int isrmw; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } MSR_CPSRf_i(0); if (i) { LSRS_rri(d,s,i); } else { TST_rr(s,s); } if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(3,jnf_LSR_b_reg,(W4 d, RR4 s, RR4 i)) { int isrmw; i=readreg(i,4); isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED8_REG_2_REG(d, s); AND_rri(REG_WORK1, i, 63); LSR_rrr(d,d,REG_WORK1); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } unlock2(i); } MIDFUNC(3,jnf_LSR_w_reg,(W4 d, RR4 s, RR4 i)) { int isrmw; i=readreg(i,4); isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED16_REG_2_REG(d, s); AND_rri(REG_WORK1, i, 63); LSR_rrr(d,d,REG_WORK1); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } unlock2(i); } MIDFUNC(3,jnf_LSR_l_reg,(W4 d, RR4 s, RR4 i)) { int isrmw; i=readreg(i,4); isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } AND_rri(REG_WORK1, i, 63); LSR_rrr(d,s,REG_WORK1); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } unlock2(i); } MIDFUNC(3,jff_LSR_b_reg,(W4 d, RR4 s, RR4 i)) { int isrmw; i=readreg(i,4); isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED8_REG_2_REG(d, s); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); LSRS_rrr(d,d,REG_WORK1); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } unlock2(i); } MIDFUNC(3,jff_LSR_w_reg,(W4 d, RR4 s, RR4 i)) { int isrmw; i=readreg(i,4); isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } UNSIGNED16_REG_2_REG(d, s); CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); LSRS_rrr(d,d,REG_WORK1); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } unlock2(i); } MIDFUNC(3,jff_LSR_l_reg,(W4 d, RR4 s, RR4 i)) { int isrmw; i=readreg(i,4); isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { s=d=rmw(s,4,4); } CC_MSR_CPSRf_r(NATIVE_CC_CC, 0); // Clear everything except C CC_MSR_CPSRf_r(NATIVE_CC_CS, ARM_C_FLAG);// Clear everything except C AND_rri(REG_WORK1, i, 63); LSRS_rrr(d,s,REG_WORK1); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } unlock2(i); } /* * LSRW * Operand Syntax: * * Operand Size: 16 * * X Set according to the last bit shifted out of the operand. Unaffected for a shift count of zero. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit shifted out of the operand. Cleared for a shift count of zero. * */ MIDFUNC(2,jnf_LSRW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); UNSIGNED16_REG_2_REG(d, s); LSR_rri(d,d,1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_LSRW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); UNSIGNED16_REG_2_REG(d, s); MSR_CPSRf_i(0); LSR_rri(d,d,1); unlock2(d); unlock2(s); } /* * MOVE * Operand Syntax: , * * Operand Size: 8,16,32 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(2,jnf_MOVE,(W4 d, RR4 s)) { if (isconst(s)) { set_const(d,live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); MOV_rr(d, s); unlock2(d); unlock2(s); } MIDFUNC(2,jff_MOVE_b_imm,(W4 d, IMM s)) { d=writereg(d,4); SIGNED8_IMM_2_REG(d, (uint8)s); MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } MIDFUNC(2,jff_MOVE_w_imm,(W4 d, IMM s)) { d=writereg(d,4); SIGNED16_IMM_2_REG(d, (uint16)s); MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } MIDFUNC(2,jff_MOVE_l_imm,(W4 d, IMM s)) { d=writereg(d,4); compemu_raw_mov_l_ri(d, s); MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } MIDFUNC(2,jff_MOVE_b,(W4 d, RR1 s)) { if (isconst(s)) { COMPCALL(jff_MOVE_b_imm)(d,live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(d, s); MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(2,jff_MOVE_w,(W4 d, RR2 s)) { if (isconst(s)) { COMPCALL(jff_MOVE_w_imm)(d,live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d, s); MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(2,jff_MOVE_l,(W4 d, RR4 s)) { if (isconst(s)) { COMPCALL(jff_MOVE_l_imm)(d,live.state[s].val); return; } s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); MOVS_rr(d,s); unlock2(d); unlock2(s); } /* * MOVE16 * * Flags: Not affected. * */ MIDFUNC(2,jnf_MOVE16,(RR4 d, RR4 s)) { s=readreg(s,4); d=readreg(d,4); BIC_rri(s, s, 0x000000FF); BIC_rri(d, d, 0x000000FF); compemu_raw_mov_l_ri(REG_WORK1, (IMM)MEMBaseDiff); ADD_rrr(s, s, REG_WORK1); ADD_rrr(d, d, REG_WORK1); LDR_rRI(REG_WORK1, s, 8); LDR_rRI(REG_WORK2, s, 12); PUSH_REGS((1<, An * * Operand Size: 16,32 * * Flags: Not affected. * */ MIDFUNC(2,jnf_MOVEA_w,(W4 d, RR2 s)) { s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_MOVEA_l,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); MOV_rr(d,s); unlock2(d); unlock2(s); } /* * MULS * Operand Syntax: , Dn * * Operand Size: 16 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if overflow. Cleared otherwise. (32 Bit multiply only) * C Always cleared. * */ MIDFUNC(2,jnf_MULS,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); SIGN_EXTEND_16_REG_2_REG(d,d); SIGN_EXTEND_16_REG_2_REG(REG_WORK1,s); MUL_rrr(d, d, REG_WORK1); unlock2(s); unlock2(d); } MIDFUNC(2,jff_MULS,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); SIGN_EXTEND_16_REG_2_REG(d,d); SIGN_EXTEND_16_REG_2_REG(REG_WORK1,s); MSR_CPSRf_i(0); MULS_rrr(d, d, REG_WORK1); unlock2(s); unlock2(d); } MIDFUNC(2,jnf_MULS32,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); MUL_rrr(d, d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jff_MULS32,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); MSR_CPSRf_i(0); // L, H, SMULLS_rrrr(d, REG_WORK2, d, s); MRS_CPSR(REG_WORK1); TEQ_rrASRi(REG_WORK2,d,31); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(s); unlock2(d); } MIDFUNC(2,jnf_MULS64,(RW4 d, RW4 s)) { s = rmw(s, 4, 4); d = rmw(d, 4, 4); // L, H, SMULL_rrrr(d, s, d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jff_MULS64,(RW4 d, RW4 s)) { s = rmw(s, 4, 4); d = rmw(d, 4, 4); MSR_CPSRf_i(0); // L, H, SMULLS_rrrr(d, s, d, s); MRS_CPSR(REG_WORK1); TEQ_rrASRi(s,d,31); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(s); unlock2(d); } /* * MULU * Operand Syntax: , Dn * * Operand Size: 16 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if overflow. Cleared otherwise. (32 Bit multiply only) * C Always cleared. * */ MIDFUNC(2,jnf_MULU,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); ZERO_EXTEND_16_REG_2_REG(d,d); ZERO_EXTEND_16_REG_2_REG(REG_WORK1,s); MUL_rrr(d, d, REG_WORK1); unlock2(s); unlock2(d); } MIDFUNC(2,jff_MULU,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); ZERO_EXTEND_16_REG_2_REG(d,d); ZERO_EXTEND_16_REG_2_REG(REG_WORK1, s); MSR_CPSRf_i(0); MULS_rrr(d, d, REG_WORK1); unlock2(s); unlock2(d); } MIDFUNC(2,jnf_MULU32,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); MUL_rrr(d, d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jff_MULU32,(RW4 d, RR4 s)) { s = readreg(s, 4); d = rmw(d, 4, 4); // L, H, MSR_CPSRf_i(0); UMULLS_rrrr(d, REG_WORK2, d, s); MRS_CPSR(REG_WORK1); TST_rr(REG_WORK2,REG_WORK2); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(s); unlock2(d); } MIDFUNC(2,jnf_MULU64,(RW4 d, RW4 s)) { s = rmw(s, 4, 4); d = rmw(d, 4, 4); // L, H, UMULL_rrrr(d, s, d, s); unlock2(s); unlock2(d); } MIDFUNC(2,jff_MULU64,(RW4 d, RW4 s)) { s = rmw(s, 4, 4); d = rmw(d, 4, 4); // L, H, MSR_CPSRf_i(0); UMULLS_rrrr(d, s, d, s); MRS_CPSR(REG_WORK1); TST_rr(s,s); CC_ORR_rri(NATIVE_CC_NE, REG_WORK1, REG_WORK1, ARM_V_FLAG); MSR_CPSRf_r(REG_WORK1); unlock2(s); unlock2(d); } /* * NEG * Operand Syntax: * * Operand Size: 8,16,32 * * X Set the same as the carry bit. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if an overflow occurs. Cleared otherwise. * C Cleared if the result is zero. Set otherwise. * */ MIDFUNC(2,jnf_NEG,(W4 d, RR4 s)) { d=writereg(d,4); s=readreg(s,4); RSB_rri(d,s,0); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NEG_b,(W4 d, RR1 s)) { d=writereg(d,4); s=readreg(s,4); SIGNED8_REG_2_REG(REG_WORK1, s); RSBS_rri(d,REG_WORK1,0); // inverted_carry = true; MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NEG_w,(W4 d, RR2 s)) { d=writereg(d,4); s=readreg(s,4); SIGNED16_REG_2_REG(REG_WORK1, s); RSBS_rri(d,REG_WORK1,0); // inverted_carry = true; MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NEG_l,(W4 d, RR4 s)) { d=writereg(d,4); s=readreg(s,4); RSBS_rri(d,s,0); // inverted_carry = true; MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } /* * NEGX * Operand Syntax: * * Operand Size: 8,16,32 * * X Set the same as the carry bit. * N Set if the result is negative. Cleared otherwise. * Z Cleared if the result is nonzero; unchanged otherwise. * V Set if an overflow occurs. Cleared otherwise. * C Cleared if the result is zero. Set otherwise. * * Attention: Z is cleared only if the result is nonzero. Unchanged otherwise * */ MIDFUNC(2,jnf_NEGX,(W4 d, RR4 s)) { d=writereg(d,4); s=readreg(s,4); RSC_rri(d,s,0); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NEGX_b,(W4 d, RR1 s)) { d=writereg(d,4); s=readreg(s,4); MRS_CPSR(REG_WORK2); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); SIGNED8_REG_2_REG(REG_WORK1, s); RSCS_rri(d,REG_WORK1,0); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NEGX_w,(W4 d, RR2 s)) { d=writereg(d,4); s=readreg(s,4); MRS_CPSR(REG_WORK2); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); SIGNED16_REG_2_REG(REG_WORK1, s); RSCS_rri(d,REG_WORK1,0); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NEGX_l,(W4 d, RR4 s)) { d=writereg(d,4); s=readreg(s,4); MRS_CPSR(REG_WORK2); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); RSCS_rri(d,s,0); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); } /* * NOT * Operand Syntax: * * Operand Size: 8,16,32 * * X Not affected. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(2,jnf_NOT,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); MVN_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NOT_b,(W4 d, RR1 s)) { s=readreg(s,4); d=writereg(d,4); UNSIGNED8_REG_2_REG(d,s); MSR_CPSRf_i(0); // Clear flags MVNS_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NOT_w,(W4 d, RR2 s)) { s=readreg(s,4); d=writereg(d,4); UNSIGNED16_REG_2_REG(d,s); MSR_CPSRf_i(0); // Clear flags MVNS_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(2,jff_NOT_l,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); // Clear flags MVNS_rr(d,s); unlock2(d); unlock2(s); } /* * OR * Operand Syntax: , Dn * Dn, * * Operand Size: 8,16,32 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(3,jnf_OR,(W4 d, RR4 s, RR4 v)) { if (isconst(s) && isconst(v)) { set_const(d, live.state[s].val|live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); ORR_rrr(d, s, v); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_OR_b,(W4 d, RR1 s, RR1 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); MSR_CPSRf_i(0); ORRS_rrr(d, REG_WORK1, REG_WORK2); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_OR_w,(W4 d, RR2 s, RR2 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); MSR_CPSRf_i(0); ORRS_rrr(d, REG_WORK1, REG_WORK2); unlock2(v); unlock2(d); unlock2(s); } MIDFUNC(3,jff_OR_l,(W4 d, RR4 s, RR4 v)) { v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); ORRS_rrr(d, s,v); unlock2(v); unlock2(d); unlock2(s); } /* * ORI * Operand Syntax: #, CCR * * Operand Size: 8 * * X — Set if bit 4 of immediate operand is one; unchanged otherwise. * N — Set if bit 3 of immediate operand is one; unchanged otherwise. * Z — Set if bit 2 of immediate operand is one; unchanged otherwise. * V — Set if bit 1 of immediate operand is one; unchanged otherwise. * C — Set if bit 0 of immediate operand is one; unchanged otherwise. * */ MIDFUNC(1,jff_ORSR,(IMM s, IMM x)) { MRS_CPSR(REG_WORK1); ORR_rri(REG_WORK1, REG_WORK1, s); MSR_CPSRf_r(REG_WORK1); if (x) { compemu_raw_mov_l_ri(REG_WORK1, (uintptr)live.state[FLAGX].mem); MOV_ri(REG_WORK2, 1); STRB_rR(REG_WORK2, REG_WORK1); } } /* * ROL * Operand Syntax: Dx, Dy * #, Dy * * * Operand Size: 8,16,32 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * */ MIDFUNC(3,jnf_ROL_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); ROR_rri(d,d,(32-(i&0x1f))); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROL_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); ROR_rri(d,d,(32-(i&0x1f))); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROL_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); ROR_rri(d,s,(32-(i&0x1f))); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROL_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); if (i) { RORS_rri(d,d,(32-(i&0x1f))); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); } else { TST_rr(d,d); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROL_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); if (i) { RORS_rri(d,d,(32-(i&0x1f))); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); } else { TST_rr(d,d); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROL_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); if (i) { RORS_rri(d,s,(32-(i&0x1f))); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); } else { MOVS_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROL_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROL_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 0x1f); RSB_rri(REG_WORK1, REG_WORK1, 32); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); ROR_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROL_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROL_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 0x1f); RSB_rri(REG_WORK1, REG_WORK1, 32); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); ROR_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROL_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROL_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 0x1f); RSB_rri(REG_WORK1, REG_WORK1, 32); ROR_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROL_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROL_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 0x1f); RSB_rri(REG_WORK1, REG_WORK1, 32); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); RORS_rrr(d,d,REG_WORK1); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROL_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROL_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 0x1f); RSB_rri(REG_WORK1, REG_WORK1, 32); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); RORS_rrr(d,d,REG_WORK1); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROL_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROL_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); AND_rri(REG_WORK1, i, 0x1f); RSB_rri(REG_WORK1, REG_WORK1, 32); MSR_CPSRf_i(0); RORS_rrr(d,s,REG_WORK1); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); unlock2(d); unlock2(s); unlock2(i); } /* * ROLW * Operand Syntax: * * Operand Size: 16 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * */ MIDFUNC(2,jnf_ROLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); ROR_rri(d,d,(32-1)); unlock2(d); unlock2(s); } MIDFUNC(2,jff_ROLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); RORS_rri(d,d,(32-1)); MRS_CPSR(REG_WORK2); TST_ri(d, 1); CC_ORR_rri(NATIVE_CC_NE, REG_WORK2, REG_WORK2, ARM_C_FLAG); CC_BIC_rri(NATIVE_CC_EQ, REG_WORK2, REG_WORK2, ARM_C_FLAG); MSR_CPSR_r(REG_WORK2); unlock2(d); unlock2(s); } /* * RORW * Operand Syntax: * * Operand Size: 16 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. * */ MIDFUNC(2,jnf_RORW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); ROR_rri(d,d,1); unlock2(d); unlock2(s); } MIDFUNC(2,jff_RORW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); RORS_rri(d,d,1); unlock2(d); unlock2(s); } /* * ROXL * Operand Syntax: Dx, Dy * #, Dy * * Operand Size: 8,16,32 * * X Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * */ MIDFUNC(3,jnf_ROXL_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { UNSIGNED8_REG_2_REG(d,s); LSL_rri(d,d,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (1 << (i - 1))); if (i > 1) ORR_rrrLSRi(d,d,d,9); } else { MOV_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROXL_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { UNSIGNED16_REG_2_REG(d,s); LSL_rri(d,d,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (1 << (i - 1))); if (i > 1) ORR_rrrLSRi(d,d,d,17); } else { MOV_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROXL_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { LSL_rri(d,s,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (1 << (i - 1))); if (i > 1) ORR_rrrLSRi(d,d,s,(32-i)); } else { MOV_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROXL_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { UNSIGNED8_REG_2_REG(d,s); LSL_rri(d,d,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (1 << (i - 1))); if (i > 1) ORR_rrrLSRi(d,d,d,9); TST_ri(s, (1<<(8-i))); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); } else { MOV_rr(d,s); MSR_CPSRf_i(0); } SIGNED8_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROXL_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { UNSIGNED16_REG_2_REG(d,s); LSL_rri(d,d,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (1 << (i - 1))); if (i > 1) ORR_rrrLSRi(d,d,d,17); TST_ri(s, (1<<(16-i))); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); } else { MOV_rr(d,s); MSR_CPSRf_i(0); } SIGNED16_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROXL_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { LSL_rri(d,s,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (1 << (i - 1))); if (i > 1) ORR_rrrLSRi(d,d,s,(32-i)); TST_ri(s, (1<<(32-i))); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); } else { MOV_rr(d,s); MSR_CPSRf_i(0); } TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROXL_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROXL_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 36); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 36); CMP_ri(REG_WORK1, 18); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 18); CMP_ri(REG_WORK1, 9); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 9); CMP_ri(REG_WORK1, 0); #if defined(ARMV6_ASSEMBLY) BLE_i(8-1); #else BLE_i(9-1); #endif SUB_rri(REG_WORK1, REG_WORK1, 1); LSL_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,1); LSL_rrr(d, d, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 8); #if defined(ARMV6_ASSEMBLY) UXTB_rr(REG_WORK2, s); #else ROR_rri(REG_WORK2, s, 8); LSR_rri(REG_WORK2, REG_WORK2, 24); #endif ORR_rrrLSRr(d,d,REG_WORK2,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROXL_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROXL_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); UNSIGNED16_REG_2_REG(d,s); MRS_CPSR(REG_WORK2); CMP_ri(REG_WORK1, 34); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 34); CMP_ri(REG_WORK1, 17); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 17); CMP_ri(REG_WORK1, 0); #if defined(ARMV6_ASSEMBLY) BLE_i(8-1); #else BLE_i(9-1); #endif SUB_rri(REG_WORK1, REG_WORK1, 1); LSL_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,1); LSL_rrr(d, d, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 16); #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK2, s); #else LSL_rri(REG_WORK2, s, 16); LSR_rri(REG_WORK2, REG_WORK2, 16); #endif ORR_rrrLSRr(d,d,REG_WORK2,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROXL_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROXL_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); CMP_ri(REG_WORK1, 33); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 33); CMP_ri(REG_WORK1, 0); BLE_i(7-1); SUB_rri(REG_WORK1, REG_WORK1, 1); LSL_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,1); LSL_rrr(d, d, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 32); ORR_rrrLSRr(d,d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROXL_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROXL_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 36); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 36); CMP_ri(REG_WORK1, 18); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 18); CMP_ri(REG_WORK1, 9); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 9); CMP_ri(REG_WORK1, 0); #if defined(ARMV6_ASSEMBLY) BLE_i(16-1); // label #else BLE_i(17-1); // label #endif SUB_rri(REG_WORK1, REG_WORK1, 1); LSL_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,1); LSL_rrr(d, d, REG_WORK1); MOV_ri(REG_WORK2, 0x80); LSR_rrr(REG_WORK2, REG_WORK2, REG_WORK1); PUSH(REG_WORK2); RSB_rri(REG_WORK1, REG_WORK1, 8); #if defined(ARMV6_ASSEMBLY) UXTB_rr(REG_WORK2, s); #else ROR_rri(REG_WORK2, s, 8); LSR_rri(REG_WORK2, REG_WORK2, 24); #endif ORR_rrrLSRr(d,d,REG_WORK2,REG_WORK1); POP(REG_WORK2); TST_rr(s, REG_WORK2); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); B_i(0); // label2 // label: MSR_CPSRf_i(0); // label2: raw_sign_extend_8_rr(d,d); TST_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROXL_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROXL_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 34); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 34); CMP_ri(REG_WORK1, 17); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 17); CMP_ri(REG_WORK1, 0); #if defined(ARMV6_ASSEMBLY) BLE_i(16-1); // label #else BLE_i(17-1); // label #endif SUB_rri(REG_WORK1, REG_WORK1, 1); LSL_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,1); LSL_rrr(d, d, REG_WORK1); MOV_ri(REG_WORK2, 0x8000); LSR_rrr(REG_WORK2, REG_WORK2, REG_WORK1); PUSH(REG_WORK2); #if defined(ARMV6_ASSEMBLY) UXTH_rr(REG_WORK2, s); #else LSL_rri(REG_WORK2, s, 16); LSR_rri(REG_WORK2, REG_WORK2, 16); #endif RSB_rri(REG_WORK1, REG_WORK1, 16); ORR_rrrLSRr(d,d,REG_WORK2,REG_WORK1); POP(REG_WORK2); TST_rr(s, REG_WORK2); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); B_i(0); // label2 // label: MSR_CPSRf_i(0); // label2: SIGNED16_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROXL_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROXL_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 33); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 33); CMP_ri(REG_WORK1, 0); BLE_i(13-1); // label SUB_rri(REG_WORK1, REG_WORK1, 1); LSL_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,1); LSL_rrr(d, d, REG_WORK1); MOV_ri(REG_WORK2, 0x80000000); LSR_rrr(REG_WORK2, REG_WORK2, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 32); ORR_rrrLSRr(d,d,s,REG_WORK1); TST_rr(s, REG_WORK2); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); B_i(0);// label2 // label: MSR_CPSRf_i(0); // label2: TST_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } /* * ROXLW * Operand Syntax: * * Operand Size: 16 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. * */ MIDFUNC(2,jnf_ROXLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,1); ADC_rri(d,d,0); unlock2(d); unlock2(s); } MIDFUNC(2,jff_ROXLW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,1); ADC_rri(d,d,0); MSR_CPSRf_i(0); LSLS_rri(d,d,15); LSR_rri(d,d,16); unlock2(d); unlock2(s); } /* * ROR * Operand Syntax: Dx, Dy * #, Dy * * * Operand Size: 8,16,32 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * */ MIDFUNC(3,jnf_ROR_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); ROR_rri(d,d,i); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROR_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); ROR_rri(d,d,i); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROR_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); ROR_rri(d,s,i); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROR_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); RORS_rri(d,d,i); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROR_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); RORS_rrr(d,d,i); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROR_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); MSR_CPSRf_i(0); RORS_rrr(d,s,i); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROR_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROR_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); ROR_rrr(d,d,i); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROR_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROR_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); ROR_rrr(d,d,i); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROR_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROR_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); ROR_rrr(d,s,i); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROR_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROR_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); LSL_rri(d,s,24); ORR_rrrLSRi(d,d,d,8); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); AND_rri(REG_WORK1, i, 63); RORS_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROR_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROR_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); LSL_rri(d,s,16); ORR_rrrLSRi(d,d,d,16); MSR_CPSRf_i(0); AND_rri(REG_WORK1, i, 63); RORS_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROR_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROR_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MSR_CPSRf_i(0); AND_rri(REG_WORK1, i, 63); RORS_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } /* * ROXR * Operand Syntax: Dx, Dy * #, Dy * * Operand Size: 8,16,32 * * X Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. Cleared when the rotate count is zero. * */ MIDFUNC(3,jnf_ROXR_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { LSR_rri(d,s,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (0x80 >> (i - 1))); if (i > 1) ORR_rrrLSLi(d,d,s,(9-i)); } else { MOV_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROXR_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { LSR_rri(d,s,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (0x8000 >> (i - 1))); if (i > 1) ORR_rrrLSLi(d,d,s,(17-i)); } else { MOV_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROXR_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { LSR_rri(d,s,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (0x80000000 >> (i - 1))); if (i > 1) ORR_rrrLSLi(d,d,s,(33-i)); } else { MOV_rr(d,s); } unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROXR_b_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { UNSIGNED8_REG_2_REG(d,s); LSR_rri(d,d,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (0x80 >> (i - 1))); if (i > 1) ORR_rrrLSLi(d,d,s,(9-i)); TST_ri(s, (1<<(i-1))); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); } else { MOV_rr(d,s); MSR_CPSRf_i(0); } SIGNED8_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROXR_w_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { UNSIGNED16_REG_2_REG(d,s); LSR_rri(d,d,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (0x8000 >> (i - 1))); if (i > 1) ORR_rrrLSLi(d,d,s,(17-i)); TST_ri(s, (1<<(i-1))); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); } else { MOV_rr(d,s); MSR_CPSRf_i(0); } SIGNED16_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jff_ROXR_l_imm,(W4 d, RR4 s, IMM i)) { s=readreg(s,4); d=writereg(d,4); if (i > 0) { LSR_rri(d,s,i); CC_ORR_rri(NATIVE_CC_CS, d,d, (0x80000000 >> (i - 1))); if (i > 1) ORR_rrrLSLi(d,d,s,(33-i)); TST_ri(s, (1<<(i-1))); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); } else { MOV_rr(d,s); MSR_CPSRf_i(0); } TST_rr(d,d); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_ROXR_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROXR_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); UNSIGNED8_REG_2_REG(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 36); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 36); CMP_ri(REG_WORK1, 18); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 18); CMP_ri(REG_WORK1, 9); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 9); CMP_ri(REG_WORK1, 0); BLE_i(7-1); SUB_rri(REG_WORK1, REG_WORK1, 1); LSR_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,0x80); LSR_rrr(d, d, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 8); ORR_rrrLSLr(d,d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROXR_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROXR_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); UNSIGNED16_REG_2_REG(d,s); MRS_CPSR(REG_WORK2); CMP_ri(REG_WORK1, 34); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 34); CMP_ri(REG_WORK1, 17); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 17); CMP_ri(REG_WORK1, 0); BLE_i(7-1); SUB_rri(REG_WORK1, REG_WORK1, 1); LSR_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,0x8000); LSR_rrr(d, d, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 16); ORR_rrrLSLr(d,d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jnf_ROXR_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jnf_ROXR_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); CMP_ri(REG_WORK1, 33); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 33); CMP_ri(REG_WORK1, 0); BLE_i(7-1); SUB_rri(REG_WORK1, REG_WORK1, 1); LSR_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,0x80000000); LSR_rrr(d, d, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 32); ORR_rrrLSLr(d,d,s,REG_WORK1); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROXR_b,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROXR_b_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); UNSIGNED8_REG_2_REG(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 36); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 36); CMP_ri(REG_WORK1, 18); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 18); CMP_ri(REG_WORK1, 9); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 9); CMP_ri(REG_WORK1, 0); BLE_i(13-1); // label SUB_rri(REG_WORK1, REG_WORK1, 1); LSR_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,0x80); LSR_rrr(d, d, REG_WORK1); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 8); ORR_rrrLSLr(d,d,s,REG_WORK1); TST_rr(s, REG_WORK2); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); B_i(0);// label2 // label: MSR_CPSRf_i(0); // label2: SIGNED8_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROXR_w,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROXR_w_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); UNSIGNED16_REG_2_REG(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 34); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 34); CMP_ri(REG_WORK1, 17); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 17); CMP_ri(REG_WORK1, 0); BLE_i(13-1); // label SUB_rri(REG_WORK1, REG_WORK1, 1); LSR_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,0x8000); LSR_rrr(d, d, REG_WORK1); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 16); ORR_rrrLSLr(d,d,s,REG_WORK1); TST_rr(s, REG_WORK2); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); B_i(0);// label2 // label: MSR_CPSRf_i(0); // label2: SIGNED16_REG_2_REG(d,d); TST_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } MIDFUNC(3,jff_ROXR_l,(W4 d, RR4 s, RR4 i)) { if (isconst(i)) { COMPCALL(jff_ROXR_l_imm)(d,s,(uae_u8)live.state[i].val); return; } s=readreg(s,4); i=readreg(i,4); d=writereg(d,4); MOV_rr(d,s); MRS_CPSR(REG_WORK2); AND_rri(REG_WORK1, i, 0x3f); CMP_ri(REG_WORK1, 33); CC_SUB_rri(NATIVE_CC_GE, REG_WORK1, REG_WORK1, 33); CMP_ri(REG_WORK1, 0); BLE_i(13-1); // label SUB_rri(REG_WORK1, REG_WORK1, 1); LSR_rri(d, d, 1); MSR_CPSRf_r(REG_WORK2); CC_ORR_rri(NATIVE_CC_CS, d,d,0x80000000); LSR_rrr(d, d, REG_WORK1); MOV_ri(REG_WORK2, 1); LSL_rrr(REG_WORK2, REG_WORK2, REG_WORK1); RSB_rri(REG_WORK1, REG_WORK1, 32); ORR_rrrLSLr(d,d,s,REG_WORK1); TST_rr(s, REG_WORK2); CC_MSR_CPSRf_i(NATIVE_CC_NE, ARM_C_FLAG); CC_MSR_CPSRf_i(NATIVE_CC_EQ, 0); B_i(0);// label2 // label: MSR_CPSRf_i(0); // label2: TST_rr(d,d); unlock2(d); unlock2(s); unlock2(i); } /* * ROXRW * Operand Syntax: * * Operand Size: 16 * * X Not affected. * N Set if the most significant bit of the result is set. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Always cleared. * C Set according to the last bit rotated out of the operand. * */ MIDFUNC(2,jnf_ROXRW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); RRX_rr(d,d); LSR_rri(d,d,16); unlock2(d); unlock2(s); } MIDFUNC(2,jff_ROXRW,(W4 d, RR4 s)) { s=readreg(s,4); d=writereg(d,4); LSL_rri(d,s,16); MSR_CPSRf_i(0); RRXS_rr(d,d); LSR_rri(d,d,16); unlock2(d); unlock2(s); } /* * SUB * Operand Syntax: , Dn * Dn, * * Operand Size: 8,16,32 * * X Set the same as the carry bit. * N Set if the result is negative. Cleared otherwise. * Z Set if the result is zero. Cleared otherwise. * V Set if an overflow is generated. Cleared otherwise. * C Set if a carry is generated. Cleared otherwise. * */ MIDFUNC(3,jnf_SUB_b_imm,(W4 d, RR4 s, IMM v)) { if (isconst(s)) { set_const(d,live.state[s].val-v); return; } s=readreg(s,4); d=writereg(d,4); UNSIGNED8_IMM_2_REG(REG_WORK1, (uint8)v); SUB_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_SUB_b,(W4 d, RR4 s, RR4 v)) { if (isconst(v)) { COMPCALL(jnf_SUB_b_imm)(d,s,live.state[v].val); return; } // d has to be different to s and v v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SUB_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jnf_SUB_w_imm,(W4 d, RR4 s, IMM v)) { if (isconst(s)) { set_const(d,live.state[s].val-v); return; } s=readreg(s,4); d=writereg(d,4); UNSIGNED16_IMM_2_REG(REG_WORK1, (uint16)v); SUB_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_SUB_w,(W4 d, RR4 s, RR4 v)) { if (isconst(v)) { COMPCALL(jnf_SUB_w_imm)(d,s,live.state[v].val); return; } // d has to be different to s and v v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SUB_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jnf_SUB_l_imm,(W4 d, RR4 s, IMM v)) { if (isconst(s)) { set_const(d,live.state[s].val-v); return; } s=readreg(s,4); d=writereg(d,4); compemu_raw_mov_l_ri(REG_WORK1, v); SUB_rrr(d,s,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(3,jnf_SUB_l,(W4 d, RR4 s, RR4 v)) { if (isconst(v)) { COMPCALL(jnf_SUB_l_imm)(d,s,live.state[v].val); return; } // d has to be different to s and v v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SUB_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_SUB_b_imm,(W4 d, RR1 s, IMM v)) { s=readreg(s,4); d=writereg(d,4); SIGNED8_IMM_2_REG(REG_WORK2, (uint8)v); SIGNED8_REG_2_REG(REG_WORK1, s); SUBS_rrr(d,REG_WORK1,REG_WORK2); // Todo: Handle this with inverted carry MRS_CPSR(REG_WORK1);// mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG);// eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1);// msr CPSR_fc, r2 // inverted_carry = true; unlock2(d); unlock2(s); } MIDFUNC(3,jff_SUB_b,(W4 d, RR1 s, RR1 v)) { if (isconst(v)) { COMPCALL(jff_SUB_b_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); SUBS_rrr(d,REG_WORK1,REG_WORK2); // Todo: Handle this with inverted carry MRS_CPSR(REG_WORK1);// mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG);// eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1);// msr CPSR_fc, r2 // inverted_carry = true; unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_SUB_w_imm,(W4 d, RR2 s, IMM v)) { s=readreg(s,4); d=writereg(d,4); SIGNED16_IMM_2_REG(REG_WORK2, (uint16)v); SIGNED16_REG_2_REG(REG_WORK1, s); SUBS_rrr(d,REG_WORK1,REG_WORK2); // Todo: Handle this with inverted carry MRS_CPSR(REG_WORK1);// mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG);// eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1);// msr CPSR_fc, r2 // inverted_carry = true; unlock2(d); unlock2(s); } MIDFUNC(3,jff_SUB_w,(W4 d, RR2 s, RR2 v)) { if (isconst(v)) { COMPCALL(jff_SUB_w_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); SUBS_rrr(d,REG_WORK1,REG_WORK2); // Todo: Handle this with inverted carry MRS_CPSR(REG_WORK1);// mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG);// eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1);// msr CPSR_fc, r2 // inverted_carry = true; unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_SUB_l_imm,(W4 d, RR4 s, IMM v)) { s=readreg(s,4); d=writereg(d,4); compemu_raw_mov_l_ri(REG_WORK2, v); SUBS_rrr(d,s,REG_WORK2); // Todo: Handle this with inverted carry MRS_CPSR(REG_WORK1);// mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG);// eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1);// msr CPSR_fc, r2 // inverted_carry = true; unlock2(d); unlock2(s); } MIDFUNC(3,jff_SUB_l,(W4 d, RR4 s, RR4 v)) { if (isconst(v)) { COMPCALL(jff_SUB_l_imm)(d,s,live.state[v].val); return; } v=readreg(v,4); s=readreg(s,4); d=writereg(d,4); SUBS_rrr(d,s,v); // Todo: Handle this with inverted carry MRS_CPSR(REG_WORK1);// mrs r2, CPSR EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG);// eor r2, r2, #0x20000000 MSR_CPSR_r(REG_WORK1);// msr CPSR_fc, r2 // inverted_carry = true; unlock2(d); unlock2(s); unlock2(v); } /* * SUBA * * Operand Syntax: , Dn * * Operand Size: 16,32 * * Flags: Not affected. * */ MIDFUNC(2,jnf_SUBA_b,(W4 d, RR1 s)) { s=readreg(s,4); d=rmw(d,4,4); SIGNED8_REG_2_REG(REG_WORK1,s); SUB_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_SUBA_w,(W4 d, RR2 s)) { s=readreg(s,4); d=rmw(d,4,4); SIGNED16_REG_2_REG(REG_WORK1,s); SUB_rrr(d,d,REG_WORK1); unlock2(d); unlock2(s); } MIDFUNC(2,jnf_SUBA_l,(W4 d, RR4 s)) { s=readreg(s,4); d=rmw(d,4,4); SUB_rrr(d,d,s); unlock2(d); unlock2(s); } /* * SUBX * Operand Syntax: Dy, Dx * -(Ay), -(Ax) * * Operand Size: 8,16,32 * * X Set the same as the carry bit. * N Set if the result is negative. Cleared otherwise. * Z Cleared if the result is nonzero. Unchanged otherwise. * V Set if an overflow is generated. Cleared otherwise. * C Set if a carry is generated. Cleared otherwise. * * Attention: Z is cleared only if the result is nonzero. Unchanged otherwise * */ MIDFUNC(3,jnf_SUBX,(W4 d, RR4 s, RR4 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); SBC_rrr(d,s,v); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_SUBX_b,(W4 d, RR1 s, RR1 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); MRS_CPSR(REG_WORK1); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK1, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK1, ARM_Z_FLAG); PUSH(REG_WORK1); SIGNED8_REG_2_REG(REG_WORK1, s); SIGNED8_REG_2_REG(REG_WORK2, v); SBCS_rrr(d,REG_WORK1,REG_WORK2); POP(REG_WORK2); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_SUBX_w,(W4 d, RR2 s, RR2 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); MRS_CPSR(REG_WORK1); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK1, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK1, ARM_Z_FLAG); PUSH(REG_WORK1); SIGNED16_REG_2_REG(REG_WORK1, s); SIGNED16_REG_2_REG(REG_WORK2, v); SBCS_rrr(d,REG_WORK1,REG_WORK2); POP(REG_WORK2); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); unlock2(v); } MIDFUNC(3,jff_SUBX_l,(W4 d, RR4 s, RR4 v)) { s=readreg(s,4); v=readreg(v,4); d=writereg(d,4); MRS_CPSR(REG_WORK2); CC_MVN_ri(NATIVE_CC_EQ, REG_WORK2, 0); CC_MVN_ri(NATIVE_CC_NE, REG_WORK2, ARM_Z_FLAG); SBCS_rrr(d,s,v); MRS_CPSR(REG_WORK1); EOR_rri(REG_WORK1, REG_WORK1, ARM_C_FLAG); AND_rrr(REG_WORK1, REG_WORK1, REG_WORK2); MSR_CPSR_r(REG_WORK1); unlock2(d); unlock2(s); unlock2(v); } /* * SWAP * Operand Syntax: Dn * * Operand Size: 16 * * X Not affected. * N Set if the most significant bit of the 32-bit result is set. Cleared otherwise. * Z Set if the 32-bit result is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(1,jnf_SWAP,(RW4 d)) { d=rmw(d,4,4); ROR_rri(d,d,16); unlock2(d); } MIDFUNC(1,jff_SWAP,(RW4 d)) { d=rmw(d,4,4); ROR_rri(d,d,16); MSR_CPSRf_i(0); TST_rr(d,d); unlock2(d); } /* * TST * Operand Syntax: * * Operand Size: 8,16,32 * * X Not affected. * N Set if the operand is negative. Cleared otherwise. * Z Set if the operand is zero. Cleared otherwise. * V Always cleared. * C Always cleared. * */ MIDFUNC(1,jff_TST_b,(RR1 s)) { if (isconst(s)) { SIGNED8_IMM_2_REG(REG_WORK1, (uint8)live.state[s].val); } else { s=readreg(s,4); SIGNED8_REG_2_REG(REG_WORK1, s); unlock2(s); } MSR_CPSRf_i(0); TST_rr(REG_WORK1,REG_WORK1); } MIDFUNC(1,jff_TST_w,(RR2 s)) { if (isconst(s)) { SIGNED16_IMM_2_REG(REG_WORK1, (uint16)live.state[s].val); } else { s=readreg(s,4); SIGNED16_REG_2_REG(REG_WORK1, s); unlock2(s); } MSR_CPSRf_i(0); TST_rr(REG_WORK1,REG_WORK1); } MIDFUNC(1,jff_TST_l,(RR4 s)) { MSR_CPSRf_i(0); if (isconst(s)) { compemu_raw_mov_l_ri(REG_WORK1, live.state[s].val); TST_rr(REG_WORK1,REG_WORK1); } else { s=readreg(s,4); TST_rr(s,s); unlock2(s); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_midfunc_arm2.h000066400000000000000000000260151504763705000271550ustar00rootroot00000000000000/* * compiler/compemu_midfunc_arm2.h - Native MIDFUNCS for ARM (JIT v2) * * Copyright (c) 2014 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Note: * File is included by compemu.h * */ // Arm optimized midfunc extern const uae_u32 ARM_CCR_MAP[]; DECLARE_MIDFUNC(restore_inverted_carry(void)); // ADD DECLARE_MIDFUNC(jnf_ADD(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jnf_ADD_imm(W4 d, RR4 s, IMM v)); DECLARE_MIDFUNC(jff_ADD_b(W4 d, RR1 s, RR1 v)); DECLARE_MIDFUNC(jff_ADD_w(W4 d, RR2 s, RR2 v)); DECLARE_MIDFUNC(jff_ADD_l(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_ADD_b_imm(W4 d, RR1 s, IMM v)); DECLARE_MIDFUNC(jff_ADD_w_imm(W4 d, RR2 s, IMM v)); DECLARE_MIDFUNC(jff_ADD_l_imm(W4 d, RR4 s, IMM v)); // ADDA DECLARE_MIDFUNC(jnf_ADDA_b(W4 d, RR1 s)); DECLARE_MIDFUNC(jnf_ADDA_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jnf_ADDA_l(W4 d, RR4 s)); // ADDX DECLARE_MIDFUNC(jnf_ADDX(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_ADDX_b(W4 d, RR1 s, RR4 v)); DECLARE_MIDFUNC(jff_ADDX_w(W4 d, RR2 s, RR4 v)); DECLARE_MIDFUNC(jff_ADDX_l(W4 d, RR4 s, RR4 v)); // AND DECLARE_MIDFUNC(jnf_AND(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_AND_b(W4 d, RR1 s, RR1 v)); DECLARE_MIDFUNC(jff_AND_w(W4 d, RR2 s, RR2 v)); DECLARE_MIDFUNC(jff_AND_l(W4 d, RR4 s, RR4 v)); // ANDSR DECLARE_MIDFUNC(jff_ANDSR(IMM s, IMM x)); // ASL DECLARE_MIDFUNC(jff_ASL_b_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_ASL_w_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_ASL_l_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_ASL_b_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ASL_w_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ASL_l_reg(W4 d, RR4 s, RR4 i)); // ASLW DECLARE_MIDFUNC(jff_ASLW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_ASLW(W4 d, RR4 s)); // ASR DECLARE_MIDFUNC(jnf_ASR_b_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_ASR_w_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_ASR_l_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_ASR_b_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_ASR_w_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_ASR_l_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_ASR_b_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ASR_w_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ASR_l_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ASR_b_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ASR_w_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ASR_l_reg(W4 d, RR4 s, RR4 i)); // ASRW DECLARE_MIDFUNC(jff_ASRW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_ASRW(W4 d, RR4 s)); // BCHG DECLARE_MIDFUNC(jnf_BCHG_b_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jnf_BCHG_l_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jff_BCHG_b_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jff_BCHG_l_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jnf_BCHG_b(RW4 d, RR4 s)); DECLARE_MIDFUNC(jnf_BCHG_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(jff_BCHG_b(RW4 d, RR4 s)); DECLARE_MIDFUNC(jff_BCHG_l(RW4 d, RR4 s)); // BCLR DECLARE_MIDFUNC(jnf_BCLR_b_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jnf_BCLR_l_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jnf_BCLR_b(RW4 d, RR4 s)); DECLARE_MIDFUNC(jnf_BCLR_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(jff_BCLR_b_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jff_BCLR_l_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jff_BCLR_b(RW4 d, RR4 s)); DECLARE_MIDFUNC(jff_BCLR_l(RW4 d, RR4 s)); // BSET DECLARE_MIDFUNC(jnf_BSET_b_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jnf_BSET_l_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jnf_BSET_b(RW4 d, RR4 s)); DECLARE_MIDFUNC(jnf_BSET_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(jff_BSET_b_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jff_BSET_l_imm(RW4 d, IMM s)); DECLARE_MIDFUNC(jff_BSET_b(RW4 d, RR4 s)); DECLARE_MIDFUNC(jff_BSET_l(RW4 d, RR4 s)); // BTST DECLARE_MIDFUNC(jff_BTST_b_imm(RR4 d, IMM s)); DECLARE_MIDFUNC(jff_BTST_l_imm(RR4 d, IMM s)); DECLARE_MIDFUNC(jff_BTST_b(RR4 d, RR4 s)); DECLARE_MIDFUNC(jff_BTST_l(RR4 d, RR4 s)); // CLR DECLARE_MIDFUNC (jnf_CLR(W4 d)); DECLARE_MIDFUNC (jff_CLR(W4 d)); // CMP DECLARE_MIDFUNC(jff_CMP_b(RR1 d, RR1 s)); DECLARE_MIDFUNC(jff_CMP_w(RR2 d, RR2 s)); DECLARE_MIDFUNC(jff_CMP_l(RR4 d, RR4 s)); // CMPA DECLARE_MIDFUNC(jff_CMPA_b(RR1 d, RR1 s)); DECLARE_MIDFUNC(jff_CMPA_w(RR2 d, RR2 s)); DECLARE_MIDFUNC(jff_CMPA_l(RR4 d, RR4 s)); // EOR DECLARE_MIDFUNC(jnf_EOR(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_EOR_b(W4 d, RR1 s, RR1 v)); DECLARE_MIDFUNC(jff_EOR_w(W4 d, RR2 s, RR2 v)); DECLARE_MIDFUNC(jff_EOR_l(W4 d, RR4 s, RR4 v)); // EORSR DECLARE_MIDFUNC(jff_EORSR(IMM s, IMM x)); // EXT DECLARE_MIDFUNC(jnf_EXT_b(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_EXT_w(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_EXT_l(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_EXT_b(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_EXT_w(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_EXT_l(W4 d, RR4 s)); // LSL DECLARE_MIDFUNC(jnf_LSL_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_LSL_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_LSL_b_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_LSL_w_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_LSL_l_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_LSL_b_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_LSL_w_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_LSL_l_reg(W4 d, RR4 s, RR4 i)); // LSLW DECLARE_MIDFUNC(jff_LSLW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_LSLW(W4 d, RR4 s)); // LSR DECLARE_MIDFUNC(jnf_LSR_b_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_LSR_w_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_LSR_l_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_LSR_b_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_LSR_w_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jff_LSR_l_imm(W4 d, RR4 s, IMM i)); DECLARE_MIDFUNC(jnf_LSR_b_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_LSR_w_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_LSR_l_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_LSR_b_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_LSR_w_reg(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_LSR_l_reg(W4 d, RR4 s, RR4 i)); // LSRW DECLARE_MIDFUNC(jff_LSRW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_LSRW(W4 d, RR4 s)); // MOVE DECLARE_MIDFUNC(jnf_MOVE(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_MOVE_b_imm(W4 d, IMM i)); DECLARE_MIDFUNC(jff_MOVE_w_imm(W4 d, IMM i)); DECLARE_MIDFUNC(jff_MOVE_l_imm(W4 d, IMM i)); DECLARE_MIDFUNC(jff_MOVE_b(W4 d, RR1 s)); DECLARE_MIDFUNC(jff_MOVE_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jff_MOVE_l(W4 d, RR4 s)); // MOVE16 DECLARE_MIDFUNC(jnf_MOVE16(RR4 d, RR4 s)); // MOVEA DECLARE_MIDFUNC(jnf_MOVEA_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jnf_MOVEA_l(W4 d, RR4 s)); // MULS DECLARE_MIDFUNC (jnf_MULS(RW4 d, RR4 s)); DECLARE_MIDFUNC (jff_MULS(RW4 d, RR4 s)); DECLARE_MIDFUNC (jnf_MULS32(RW4 d, RR4 s)); DECLARE_MIDFUNC (jff_MULS32(RW4 d, RR4 s)); DECLARE_MIDFUNC (jnf_MULS64(RW4 d, RW4 s)); DECLARE_MIDFUNC (jff_MULS64(RW4 d, RW4 s)); // MULU DECLARE_MIDFUNC (jnf_MULU(RW4 d, RR4 s)); DECLARE_MIDFUNC (jff_MULU(RW4 d, RR4 s)); DECLARE_MIDFUNC (jnf_MULU32(RW4 d, RR4 s)); DECLARE_MIDFUNC (jff_MULU32(RW4 d, RR4 s)); DECLARE_MIDFUNC (jnf_MULU64(RW4 d, RW4 s)); DECLARE_MIDFUNC (jff_MULU64(RW4 d, RW4 s)); // NEG DECLARE_MIDFUNC(jnf_NEG(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_NEG_b(W4 d, RR1 s)); DECLARE_MIDFUNC(jff_NEG_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jff_NEG_l(W4 d, RR4 s)); // NEGX DECLARE_MIDFUNC(jnf_NEGX(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_NEGX_b(W4 d, RR1 s)); DECLARE_MIDFUNC(jff_NEGX_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jff_NEGX_l(W4 d, RR4 s)); // NOT DECLARE_MIDFUNC(jnf_NOT(W4 d, RR4 s)); DECLARE_MIDFUNC(jff_NOT_b(W4 d, RR1 s)); DECLARE_MIDFUNC(jff_NOT_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jff_NOT_l(W4 d, RR4 s)); // OR DECLARE_MIDFUNC(jnf_OR(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_OR_b(W4 d, RR1 s, RR1 v)); DECLARE_MIDFUNC(jff_OR_w(W4 d, RR2 s, RR2 v)); DECLARE_MIDFUNC(jff_OR_l(W4 d, RR4 s, RR4 v)); // ORSR DECLARE_MIDFUNC(jff_ORSR(IMM s, IMM x)); // ROL DECLARE_MIDFUNC(jnf_ROL_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROL_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROL_l(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROL_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROL_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROL_l(W4 d, RR4 s, RR4 i)); // ROLW DECLARE_MIDFUNC(jff_ROLW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_ROLW(W4 d, RR4 s)); // RORW DECLARE_MIDFUNC(jff_RORW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_RORW(W4 d, RR4 s)); // ROXL DECLARE_MIDFUNC(jnf_ROXL_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROXL_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROXL_l(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROXL_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROXL_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROXL_l(W4 d, RR4 s, RR4 i)); // ROXLW DECLARE_MIDFUNC(jff_ROXLW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_ROXLW(W4 d, RR4 s)); // ROR DECLARE_MIDFUNC(jnf_ROR_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROR_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROR_l(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROR_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROR_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROR_l(W4 d, RR4 s, RR4 i)); // ROXR DECLARE_MIDFUNC(jnf_ROXR_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROXR_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jnf_ROXR_l(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROXR_b(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROXR_w(W4 d, RR4 s, RR4 i)); DECLARE_MIDFUNC(jff_ROXR_l(W4 d, RR4 s, RR4 i)); // ROXRW DECLARE_MIDFUNC(jff_ROXRW(W4 d, RR4 s)); DECLARE_MIDFUNC(jnf_ROXRW(W4 d, RR4 s)); // SUB DECLARE_MIDFUNC(jnf_SUB_b_imm(W4 d, RR4 s, IMM v)); DECLARE_MIDFUNC(jnf_SUB_b(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jnf_SUB_w_imm(W4 d, RR4 s, IMM v)); DECLARE_MIDFUNC(jnf_SUB_w(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jnf_SUB_l_imm(W4 d, RR4 s, IMM v)); DECLARE_MIDFUNC(jnf_SUB_l(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_SUB_b(W4 d, RR1 s, RR1 v)); DECLARE_MIDFUNC(jff_SUB_w(W4 d, RR2 s, RR2 v)); DECLARE_MIDFUNC(jff_SUB_l(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_SUB_b_imm(W4 d, RR1 s, IMM v)); DECLARE_MIDFUNC(jff_SUB_w_imm(W4 d, RR2 s, IMM v)); DECLARE_MIDFUNC(jff_SUB_l_imm(W4 d, RR4 s, IMM v)); // SUBA DECLARE_MIDFUNC(jnf_SUBA_b(W4 d, RR1 s)); DECLARE_MIDFUNC(jnf_SUBA_w(W4 d, RR2 s)); DECLARE_MIDFUNC(jnf_SUBA_l(W4 d, RR4 s)); // SUBX DECLARE_MIDFUNC(jnf_SUBX(W4 d, RR4 s, RR4 v)); DECLARE_MIDFUNC(jff_SUBX_b(W4 d, RR1 s, RR4 v)); DECLARE_MIDFUNC(jff_SUBX_w(W4 d, RR2 s, RR4 v)); DECLARE_MIDFUNC(jff_SUBX_l(W4 d, RR4 s, RR4 v)); // SWAP DECLARE_MIDFUNC (jnf_SWAP(RW4 d)); DECLARE_MIDFUNC (jff_SWAP(RW4 d)); // TST DECLARE_MIDFUNC (jff_TST_b(RR1 s)); DECLARE_MIDFUNC (jff_TST_w(RR2 s)); DECLARE_MIDFUNC (jff_TST_l(RR4 s)); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_midfunc_x86.c000066400000000000000000001361141504763705000267360ustar00rootroot00000000000000/* * compiler/compemu_midfunc_arm.cpp - Native MIDFUNCS for IA-32 and AMD64 * * Copyright (c) 2014 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * * Note: * File is included by compemu_support.cpp * */ static int f_rmw(int r) { int n; f_make_exclusive(r,0); if (f_isinreg(r)) { n=live.fate[r].realreg; } else n=f_alloc_reg(r,0); live.fate[r].status=DIRTY; live.fat[n].locked++; live.fat[n].touched=touchcnt++; return n; } static void fflags_into_flags_internal(uae_u32 tmp) { int r; clobber_flags(); r=f_readreg(FP_RESULT); if (FFLAG_NREG_CLOBBER_CONDITION) { int tmp2=tmp; tmp=writereg_specific(tmp,4,FFLAG_NREG); raw_fflags_into_flags(r); unlock2(tmp); forget_about(tmp2); } else raw_fflags_into_flags(r); f_unlock(r); live_flags(); } /******************************************************************** * CPU functions exposed to gencomp. Both CREATE and EMIT time * ********************************************************************/ /* * RULES FOR HANDLING REGISTERS: * * * In the function headers, order the parameters * - 1st registers written to * - 2nd read/modify/write registers * - 3rd registers read from * * Before calling raw_*, you must call readreg, writereg or rmw for * each register * * The order for this is * - 1st call remove_offset for all registers written to with size<4 * - 2nd call readreg for all registers read without offset * - 3rd call rmw for all rmw registers * - 4th call readreg_offset for all registers that can handle offsets * - 5th call get_offset for all the registers from the previous step * - 6th call writereg for all written-to registers * - 7th call raw_* * - 8th unlock2 all registers that were locked */ MIDFUNC(0,live_flags,(void)) { live.flags_on_stack=TRASH; live.flags_in_flags=VALID; live.flags_are_important=1; } MIDFUNC(0,dont_care_flags,(void)) { live.flags_are_important=0; } /* * store the state of the x86 carry bit into regflags.x, * into the position denoted by FLAGBIT_X */ MIDFUNC(0,duplicate_carry,(void)) { evict(FLAGX); make_flags_live_internal(); COMPCALL(setcc_m)(JITPTR live.state[FLAGX].mem, NATIVE_CC_CS); log_vwrite(FLAGX); } MIDFUNC(0,clear_overflow,(void)) { make_flags_live_internal(); raw_pushfl(); // clear OF flag // and dword [esp],~0x0800 emit_byte(0x81); emit_byte(0x24); emit_byte(0x24); emit_byte(0xff); emit_byte(0xf7); emit_byte(0xff); emit_byte(0xff); raw_popfl(); } // This is complex because x86 shift behavior is different than 680x0. // - shift count 0: does not modify any flags | clears C, modifies Z and N. Does not modify X. // - shift count larger or same than data size : C undefined | C always equals last bit shifted out. // - shift count mask: masked by 31 (except if 64bit data size) | masked by 63. MIDFUNC(6, setcc_for_cntzero, (RR4 /* cnt */, RR4 data, RR4 odata, int obit, int size, int ov)) { uae_u8 *branchadd1, *branchadd2, *branchadd3, *branchadd4; evict(FLAGX); make_flags_live_internal(); raw_pushfl(); if (ov) { // clear OF flag // and dword [esp],~0x0800 emit_byte(0x81); emit_byte(0x24); emit_byte(0x24); emit_byte(0xff); emit_byte(0xf7); emit_byte(0xff); emit_byte(0xff); } // Shift count can only be in CL register; see shrl_b_rr // Zero shift count? raw_test_b_rr(X86_CL, X86_CL); raw_jz_b_oponly(); branchadd4 = get_target(); skip_byte(); // Shift count lower than data size? raw_cmp_b_ri(X86_CL, size == 0 ? 7 : (size == 1 ? 15 : 31)); raw_jcc_b_oponly(NATIVE_CC_LS); branchadd2 = get_target(); skip_byte(); *branchadd4 = JITPTR get_target() - (JITPTR branchadd4 + 1); // Shift count: zero, same or larger than data size // Need to update C, N and Z. raw_popfl(); data = readreg(data, 4); /* Update Z and N (Clears also C). */ switch (size) { case 0: raw_test_b_rr(data, data); break; case 1: raw_test_w_rr(data, data); break; case 2: raw_test_l_rr(data, data); break; } unlock2(data); // Update C (BT does not modify other flags). odata = readreg(odata, 4); raw_bt_l_ri(odata, obit); unlock2(odata); raw_pushfl(); // If zero shift count: X must not be modified. raw_test_b_rr(X86_CL, X86_CL); raw_jz_b_oponly(); branchadd1 = get_target(); skip_byte(); // Non-zero shift count. // Do not modify C, N and Z. // C -> X *branchadd2 = JITPTR get_target() - (JITPTR branchadd2 + 1); raw_popfl(); // Execute "duplicate_carry()" COMPCALL(setcc_m)(JITPTR live.state[FLAGX].mem, NATIVE_CC_CS); log_vwrite(FLAGX); raw_jmp_b_oponly(); branchadd3 = get_target(); skip_byte(); // Zero shift count after CNZ adjustments *branchadd1 = JITPTR get_target() - (JITPTR branchadd1 + 1); raw_popfl(); *branchadd3 = JITPTR get_target() - (JITPTR branchadd3 + 1); } /* * Set the x86 carry flag from regflags.x, from the position * denoted by FLAGBIT_X */ MIDFUNC(0,restore_carry,(void)) { if (!have_rat_stall) { /* Not a P6 core, i.e. no partial stalls */ bt_l_ri_noclobber(FLAGX, FLAGBIT_X); } else { /* Avoid the stall the above creates. This is slow on non-P6, though. */ #if FLAGBIT_X >= 8 COMPCALL(rol_w_ri(FLAGX, 16 - FLAGBIT_X)); #else COMPCALL(rol_b_ri(FLAGX, 8 - FLAGBIT_X)); #endif isclean(FLAGX); } } MIDFUNC(0,start_needflags,(void)) { needflags=1; } MIDFUNC(0,end_needflags,(void)) { needflags=0; } MIDFUNC(0,make_flags_live,(void)) { make_flags_live_internal(); } MIDFUNC(1,fflags_into_flags,(W2 tmp)) { clobber_flags(); fflags_into_flags_internal(tmp); } MIDFUNC(2,bt_l_ri,(RR4 r, IMM i)) /* This is defined as only affecting C */ { int size=4; if (i<16) size=2; CLOBBER_BT; r=readreg(r,size); raw_bt_l_ri(r,i); unlock2(r); } MIDFUNC(2,bt_l_rr,(RR4 r, RR4 b)) /* This is defined as only affecting C */ { CLOBBER_BT; r=readreg(r,4); b=readreg(b,4); raw_bt_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,btc_l_ri,(RW4 r, IMM i)) { int size=4; if (i<16) size=2; CLOBBER_BT; r=rmw(r,size,size); raw_btc_l_ri(r,i); unlock2(r); } MIDFUNC(2,btc_l_rr,(RW4 r, RR4 b)) { CLOBBER_BT; b=readreg(b,4); r=rmw(r,4,4); raw_btc_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,btr_l_ri,(RW4 r, IMM i)) { int size=4; if (i<16) size=2; CLOBBER_BT; r=rmw(r,size,size); raw_btr_l_ri(r,i); unlock2(r); } MIDFUNC(2,btr_l_rr,(RW4 r, RR4 b)) { CLOBBER_BT; b=readreg(b,4); r=rmw(r,4,4); raw_btr_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,bts_l_ri,(RW4 r, IMM i)) { int size=4; if (i<16) size=2; CLOBBER_BT; r=rmw(r,size,size); raw_bts_l_ri(r,i); unlock2(r); } MIDFUNC(2,bts_l_rr,(RW4 r, RR4 b)) { CLOBBER_BT; b=readreg(b,4); r=rmw(r,4,4); raw_bts_l_rr(r,b); unlock2(r); unlock2(b); } MIDFUNC(2,mov_l_rm,(W4 d, IMM s)) { CLOBBER_MOV; d=writereg(d,4); raw_mov_l_rm(d,s); unlock2(d); } MIDFUNC(1,call_r,(RR4 r)) /* Clobbering is implicit */ { r=readreg(r,4); raw_dec_sp(STACK_SHADOW_SPACE); raw_call_r(r); raw_inc_sp(STACK_SHADOW_SPACE); unlock2(r); } MIDFUNC(2,sub_l_mi,(IMM d, IMM s)) { CLOBBER_SUB; raw_sub_l_mi(d,s) ; } MIDFUNC(2,mov_l_mi,(IMM d, IMM s)) { CLOBBER_MOV; raw_mov_l_mi(d,s) ; } MIDFUNC(2,mov_w_mi,(IMM d, IMM s)) { CLOBBER_MOV; raw_mov_w_mi(d,s) ; } MIDFUNC(2,mov_b_mi,(IMM d, IMM s)) { CLOBBER_MOV; raw_mov_b_mi(d,s) ; } MIDFUNC(2,rol_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROL; r=rmw(r,1,1); raw_rol_b_ri(r,i); unlock2(r); } MIDFUNC(2,rol_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROL; r=rmw(r,2,2); raw_rol_w_ri(r,i); unlock2(r); } MIDFUNC(2,rol_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROL; r=rmw(r,4,4); raw_rol_l_ri(r,i); unlock2(r); } MIDFUNC(2,rol_l_rr,(RW4 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(rol_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,4,4); Dif (r!=X86_CL) { jit_abort("Illegal register %d in rol_l_rr",r); } raw_rol_l_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,rol_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(rol_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,2,2); Dif (r!=X86_CL) { jit_abort("Illegal register %d in rol_w_rr",r); } raw_rol_w_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,rol_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(rol_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,1,1); Dif (r!=X86_CL) { jit_abort("Illegal register %d in rol_b_rr",r); } raw_rol_b_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shll_l_rr,(RW4 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shll_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHLL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,4,4); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shll_l_rr",r); } raw_shll_l_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shll_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shll_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHLL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,2,2); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shll_w_rr",r); } raw_shll_w_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shll_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shll_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHLL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,1,1); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shll_b_rr",r); } raw_shll_b_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,ror_b_ri,(RR1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROR; r=rmw(r,1,1); raw_ror_b_ri(r,i); unlock2(r); } MIDFUNC(2,ror_w_ri,(RR2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROR; r=rmw(r,2,2); raw_ror_w_ri(r,i); unlock2(r); } MIDFUNC(2,ror_l_ri,(RR4 r, IMM i)) { if (!i && !needflags) return; CLOBBER_ROR; r=rmw(r,4,4); raw_ror_l_ri(r,i); unlock2(r); } MIDFUNC(2,ror_l_rr,(RR4 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(ror_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROR; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,4,4); raw_ror_l_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,ror_w_rr,(RR2 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(ror_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROR; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,2,2); raw_ror_w_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,ror_b_rr,(RR1 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(ror_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_ROR; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,1,1); raw_ror_b_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shrl_l_rr,(RW4 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shrl_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,4,4); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shrl_l_rr",r); } raw_shrl_l_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shrl_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shrl_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,2,2); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shrl_w_rr",r); } raw_shrl_w_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shrl_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shrl_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRL; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,1,1); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shrl_b_rr",r); } raw_shrl_b_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shll_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; if (isconst(r) && !needflags) { live.state[r].val<<=i; return; } CLOBBER_SHLL; r=rmw(r,4,4); raw_shll_l_ri(r,i); unlock2(r); } MIDFUNC(2,shll_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHLL; r=rmw(r,2,2); raw_shll_w_ri(r,i); unlock2(r); } MIDFUNC(2,shll_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHLL; r=rmw(r,1,1); raw_shll_b_ri(r,i); unlock2(r); } MIDFUNC(2,shrl_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; if (isconst(r) && !needflags) { live.state[r].val>>=i; return; } CLOBBER_SHRL; r=rmw(r,4,4); raw_shrl_l_ri(r,i); unlock2(r); } MIDFUNC(2,shrl_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRL; r=rmw(r,2,2); raw_shrl_w_ri(r,i); unlock2(r); } MIDFUNC(2,shrl_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRL; r=rmw(r,1,1); raw_shrl_b_ri(r,i); unlock2(r); } MIDFUNC(2,shra_l_ri,(RW4 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRA; r=rmw(r,4,4); raw_shra_l_ri(r,i); unlock2(r); } MIDFUNC(2,shra_w_ri,(RW2 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRA; r=rmw(r,2,2); raw_shra_w_ri(r,i); unlock2(r); } MIDFUNC(2,shra_b_ri,(RW1 r, IMM i)) { if (!i && !needflags) return; CLOBBER_SHRA; r=rmw(r,1,1); raw_shra_b_ri(r,i); unlock2(r); } MIDFUNC(2,shra_l_rr,(RW4 d, RR1 r)) { if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shra_l_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRA; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,4,4); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shra_l_rr",r); } raw_shra_l_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shra_w_rr,(RW2 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shra_w_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRA; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,2,2); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shra_w_rr",r); } raw_shra_w_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,shra_b_rr,(RW1 d, RR1 r)) { /* Can only do this with r==1, i.e. cl */ if (isconst(r) && (uae_u8)live.state[r].val != 0) { COMPCALL(shra_b_ri)(d,(uae_u8)live.state[r].val); return; } CLOBBER_SHRA; r=readreg_specific(r,1,SHIFTCOUNT_NREG); d=rmw(d,1,1); Dif (r!=X86_CL) { jit_abort("Illegal register %d in shra_b_rr",r); } raw_shra_b_rr(d,r) ; unlock2(r); unlock2(d); } MIDFUNC(2,setcc,(W1 d, IMM cc)) { CLOBBER_SETCC; d=writereg(d,1); raw_setcc(d,cc); unlock2(d); } MIDFUNC(2,setcc_m,(IMM d, IMM cc)) { CLOBBER_SETCC; raw_setcc_m(d,cc); } MIDFUNC(3,cmov_l_rr,(RW4 d, RR4 s, IMM cc)) { if (d==s) return; CLOBBER_CMOV; s=readreg(s,4); d=rmw(d,4,4); raw_cmov_l_rr(d,s,cc); unlock2(s); unlock2(d); } MIDFUNC(3,cmov_l_rm,(RW4 d, IMM s, IMM cc)) { CLOBBER_CMOV; d=rmw(d,4,4); raw_cmov_l_rm(d,s,cc); unlock2(d); } MIDFUNC(2,bsf_l_rr,(W4 d, RR4 s)) { CLOBBER_BSF; s = readreg(s, 4); d = writereg(d, 4); raw_bsf_l_rr(d, s); unlock2(s); unlock2(d); } /* Set the Z flag depending on the value in s. Note that the value has to be 0 or -1 (or, more precisely, for non-zero values, bit 14 must be set)! */ MIDFUNC(2,simulate_bsf,(W4 tmp, RW4 s)) { CLOBBER_BSF; s=rmw_specific(s,4,4,FLAG_NREG3); tmp=writereg(tmp,4); raw_flags_set_zero(s, tmp); unlock2(tmp); unlock2(s); } MIDFUNC(2,imul_32_32,(RW4 d, RR4 s)) { CLOBBER_MUL; s=readreg(s,4); d=rmw(d,4,4); raw_imul_32_32(d,s); unlock2(s); unlock2(d); } MIDFUNC(2,imul_64_32,(RW4 d, RW4 s)) { CLOBBER_MUL; s=rmw_specific(s,4,4,MUL_NREG2); d=rmw_specific(d,4,4,MUL_NREG1); raw_imul_64_32(d,s); unlock2(s); unlock2(d); } MIDFUNC(2,mul_64_32,(RW4 d, RW4 s)) { CLOBBER_MUL; s=rmw_specific(s,4,4,MUL_NREG2); d=rmw_specific(d,4,4,MUL_NREG1); raw_mul_64_32(d,s); unlock2(s); unlock2(d); } MIDFUNC(2,mul_32_32,(RW4 d, RR4 s)) { CLOBBER_MUL; s=readreg(s,4); d=rmw(d,4,4); raw_mul_32_32(d,s); unlock2(s); unlock2(d); } #if SIZEOF_VOID_P == 8 MIDFUNC(2,sign_extend_32_rr,(W4 d, RR2 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_s32)live.state[s].val); return; } CLOBBER_SE32; isrmw=(s==d); if (!isrmw) { s=readreg(s,4); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,4); } raw_sign_extend_32_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } #endif MIDFUNC(2,sign_extend_16_rr,(W4 d, RR2 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_s32)(uae_s16)live.state[s].val); return; } CLOBBER_SE16; isrmw=(s==d); if (!isrmw) { s=readreg(s,2); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,2); } raw_sign_extend_16_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,sign_extend_8_rr,(W4 d, RR1 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_s32)(uae_s8)live.state[s].val); return; } isrmw=(s==d); CLOBBER_SE8; if (!isrmw) { s=readreg(s,1); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,1); } raw_sign_extend_8_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,zero_extend_16_rr,(W4 d, RR2 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_u32)(uae_u16)live.state[s].val); return; } isrmw=(s==d); CLOBBER_ZE16; if (!isrmw) { s=readreg(s,2); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,2); } raw_zero_extend_16_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,zero_extend_8_rr,(W4 d, RR1 s)) { int isrmw; if (isconst(s)) { set_const(d,(uae_u32)(uae_u8)live.state[s].val); return; } isrmw=(s==d); CLOBBER_ZE8; if (!isrmw) { s=readreg(s,1); d=writereg(d,4); } else { /* If we try to lock this twice, with different sizes, we are int trouble! */ s=d=rmw(s,4,1); } raw_zero_extend_8_rr(d,s); if (!isrmw) { unlock2(d); unlock2(s); } else { unlock2(s); } } MIDFUNC(2,mov_b_rr,(W1 d, RR1 s)) { if (d==s) return; if (isconst(s)) { COMPCALL(mov_b_ri)(d,(uae_u8)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,1); d=writereg(d,1); raw_mov_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,mov_w_rr,(W2 d, RR2 s)) { if (d==s) return; if (isconst(s)) { COMPCALL(mov_w_ri)(d,(uae_u16)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,2); d=writereg(d,2); raw_mov_w_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(4,mov_l_rrm_indexed,(W4 d,RR4 baser, RR4 index, IMM factor)) { CLOBBER_MOV; baser=readreg(baser,4); index=readreg(index,4); d=writereg(d,4); raw_mov_l_rrm_indexed(d,baser,index,factor); unlock2(d); unlock2(baser); unlock2(index); } MIDFUNC(4,mov_w_rrm_indexed,(W2 d, RR4 baser, RR4 index, IMM factor)) { CLOBBER_MOV; baser=readreg(baser,4); index=readreg(index,4); d=writereg(d,2); raw_mov_w_rrm_indexed(d,baser,index,factor); unlock2(d); unlock2(baser); unlock2(index); } MIDFUNC(4,mov_b_rrm_indexed,(W1 d, RR4 baser, RR4 index, IMM factor)) { CLOBBER_MOV; baser=readreg(baser,4); index=readreg(index,4); d=writereg(d,1); raw_mov_b_rrm_indexed(d,baser,index,factor); unlock2(d); unlock2(baser); unlock2(index); } MIDFUNC(4,mov_l_mrr_indexed,(RR4 baser, RR4 index, IMM factor, RR4 s)) { CLOBBER_MOV; baser=readreg(baser,4); index=readreg(index,4); s=readreg(s,4); Dif (baser==s || index==s) jit_abort("mov_l_mrr_indexed"); raw_mov_l_mrr_indexed(baser,index,factor,s); unlock2(s); unlock2(baser); unlock2(index); } MIDFUNC(4,mov_w_mrr_indexed,(RR4 baser, RR4 index, IMM factor, RR2 s)) { CLOBBER_MOV; baser=readreg(baser,4); index=readreg(index,4); s=readreg(s,2); raw_mov_w_mrr_indexed(baser,index,factor,s); unlock2(s); unlock2(baser); unlock2(index); } MIDFUNC(4,mov_b_mrr_indexed,(RR4 baser, RR4 index, IMM factor, RR1 s)) { CLOBBER_MOV; s=readreg(s,1); baser=readreg(baser,4); index=readreg(index,4); raw_mov_b_mrr_indexed(baser,index,factor,s); unlock2(s); unlock2(baser); unlock2(index); } MIDFUNC(5,mov_l_bmrr_indexed,(IMM base, RR4 baser, RR4 index, IMM factor, RR4 s)) { int basereg=baser; int indexreg=index; CLOBBER_MOV; s=readreg(s,4); baser=readreg_offset(baser,4); index=readreg_offset(index,4); base+=get_offset(basereg); base+=factor*get_offset(indexreg); raw_mov_l_bmrr_indexed(base,baser,index,factor,s); unlock2(s); unlock2(baser); unlock2(index); } MIDFUNC(5,mov_w_bmrr_indexed,(IMM base, RR4 baser, RR4 index, IMM factor, RR2 s)) { int basereg=baser; int indexreg=index; CLOBBER_MOV; s=readreg(s,2); baser=readreg_offset(baser,4); index=readreg_offset(index,4); base+=get_offset(basereg); base+=factor*get_offset(indexreg); raw_mov_w_bmrr_indexed(base,baser,index,factor,s); unlock2(s); unlock2(baser); unlock2(index); } MIDFUNC(5,mov_b_bmrr_indexed,(IMM base, RR4 baser, RR4 index, IMM factor, RR1 s)) { int basereg=baser; int indexreg=index; CLOBBER_MOV; s=readreg(s,1); baser=readreg_offset(baser,4); index=readreg_offset(index,4); base+=get_offset(basereg); base+=factor*get_offset(indexreg); raw_mov_b_bmrr_indexed(base,baser,index,factor,s); unlock2(s); unlock2(baser); unlock2(index); } /* Read a long from base+baser+factor*index */ MIDFUNC(5,mov_l_brrm_indexed,(W4 d, IMM base, RR4 baser, RR4 index, IMM factor)) { int basereg=baser; int indexreg=index; CLOBBER_MOV; baser=readreg_offset(baser,4); index=readreg_offset(index,4); base+=get_offset(basereg); base+=factor*get_offset(indexreg); d=writereg(d,4); raw_mov_l_brrm_indexed(d,base,baser,index,factor); unlock2(d); unlock2(baser); unlock2(index); } MIDFUNC(5,mov_w_brrm_indexed,(W2 d, IMM base, RR4 baser, RR4 index, IMM factor)) { int basereg=baser; int indexreg=index; CLOBBER_MOV; remove_offset(d,-1); baser=readreg_offset(baser,4); index=readreg_offset(index,4); base+=get_offset(basereg); base+=factor*get_offset(indexreg); d=writereg(d,2); raw_mov_w_brrm_indexed(d,base,baser,index,factor); unlock2(d); unlock2(baser); unlock2(index); } MIDFUNC(5,mov_b_brrm_indexed,(W1 d, IMM base, RR4 baser, RR4 index, IMM factor)) { int basereg=baser; int indexreg=index; CLOBBER_MOV; remove_offset(d,-1); baser=readreg_offset(baser,4); index=readreg_offset(index,4); base+=get_offset(basereg); base+=factor*get_offset(indexreg); d=writereg(d,1); raw_mov_b_brrm_indexed(d,base,baser,index,factor); unlock2(d); unlock2(baser); unlock2(index); } /* Read a long from base+factor*index */ MIDFUNC(4,mov_l_rm_indexed,(W4 d, IMM base, RR4 index, IMM factor)) { int indexreg=index; if (isconst(index)) { COMPCALL(mov_l_rm)(d,base+factor*live.state[index].val); return; } CLOBBER_MOV; index=readreg_offset(index,4); base+=get_offset(indexreg)*factor; d=writereg(d,4); raw_mov_l_rm_indexed(d,base,index,factor); unlock2(index); unlock2(d); } /* read the long at the address contained in s+offset and store in d */ MIDFUNC(3,mov_l_rR,(W4 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_l_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg(s,4); d=writereg(d,4); raw_mov_l_rR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_w_rR,(W2 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_w_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg(s,4); d=writereg(d,2); raw_mov_w_rR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_b_rR,(W1 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_b_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg(s,4); d=writereg(d,1); raw_mov_b_rR(d,s,offset); unlock2(d); unlock2(s); } /* read the long at the address contained in s+offset and store in d */ MIDFUNC(3,mov_l_brR,(W4 d, RR4 s, IMM offset)) { int sreg=s; if (isconst(s)) { COMPCALL(mov_l_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; s=readreg_offset(s,4); offset+=get_offset(sreg); d=writereg(d,4); raw_mov_l_brR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_w_brR,(W2 d, RR4 s, IMM offset)) { int sreg=s; if (isconst(s)) { COMPCALL(mov_w_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; remove_offset(d,-1); s=readreg_offset(s,4); offset+=get_offset(sreg); d=writereg(d,2); raw_mov_w_brR(d,s,offset); unlock2(d); unlock2(s); } /* read the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_b_brR,(W1 d, RR4 s, IMM offset)) { int sreg=s; if (isconst(s)) { COMPCALL(mov_b_rm)(d,live.state[s].val+offset); return; } CLOBBER_MOV; remove_offset(d,-1); s=readreg_offset(s,4); offset+=get_offset(sreg); d=writereg(d,1); raw_mov_b_brR(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_l_Ri,(RR4 d, IMM i, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_l_mi)(live.state[d].val+offset,i); return; } CLOBBER_MOV; d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_l_Ri(d,i,offset); unlock2(d); } MIDFUNC(3,mov_w_Ri,(RR4 d, IMM i, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_w_mi)(live.state[d].val+offset,i); return; } CLOBBER_MOV; d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_w_Ri(d,i,offset); unlock2(d); } MIDFUNC(3,mov_b_Ri,(RR4 d, IMM i, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_b_mi)(live.state[d].val+offset,i); return; } CLOBBER_MOV; d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_b_Ri(d,i,offset); unlock2(d); } /* Warning! OFFSET is byte sized only! */ MIDFUNC(3,mov_l_Rr,(RR4 d, RR4 s, IMM offset)) { if (isconst(d)) { COMPCALL(mov_l_mr)(live.state[d].val+offset,s); return; } if (isconst(s)) { COMPCALL(mov_l_Ri)(d,live.state[s].val,offset); return; } CLOBBER_MOV; s=readreg(s,4); d=readreg(d,4); raw_mov_l_Rr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_w_Rr,(RR4 d, RR2 s, IMM offset)) { if (isconst(d)) { COMPCALL(mov_w_mr)(live.state[d].val+offset,s); return; } if (isconst(s)) { COMPCALL(mov_w_Ri)(d,(uae_u16)live.state[s].val,offset); return; } CLOBBER_MOV; s=readreg(s,2); d=readreg(d,4); raw_mov_w_Rr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_b_Rr,(RR4 d, RR1 s, IMM offset)) { if (isconst(d)) { COMPCALL(mov_b_mr)(live.state[d].val+offset,s); return; } if (isconst(s)) { COMPCALL(mov_b_Ri)(d,(uae_u8)live.state[s].val,offset); return; } CLOBBER_MOV; s=readreg(s,1); d=readreg(d,4); raw_mov_b_Rr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,lea_l_brr,(W4 d, RR4 s, IMM offset)) { if (isconst(s)) { COMPCALL(mov_l_ri)(d,live.state[s].val+offset); return; } #if USE_OFFSET if (d==s) { add_offset(d,offset); return; } #endif CLOBBER_LEA; s=readreg(s,4); d=writereg(d,4); raw_lea_l_brr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(5,lea_l_brr_indexed,(W4 d, RR4 s, RR4 index, IMM factor, IMM offset)) { if (!offset) { COMPCALL(lea_l_rr_indexed)(d,s,index,factor); return; } CLOBBER_LEA; s=readreg(s,4); index=readreg(index,4); d=writereg(d,4); raw_lea_l_brr_indexed(d,s,index,factor,offset); unlock2(d); unlock2(index); unlock2(s); } MIDFUNC(4,lea_l_rr_indexed,(W4 d, RR4 s, RR4 index, IMM factor)) { CLOBBER_LEA; s=readreg(s,4); index=readreg(index,4); d=writereg(d,4); raw_lea_l_rr_indexed(d,s,index,factor); unlock2(d); unlock2(index); unlock2(s); } /* write d to the long at the address contained in s+offset */ MIDFUNC(3,mov_l_bRr,(RR4 d, RR4 s, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_l_mr)(live.state[d].val+offset,s); return; } CLOBBER_MOV; s=readreg(s,4); d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_l_bRr(d,s,offset); unlock2(d); unlock2(s); } /* write the word at the address contained in s+offset and store in d */ MIDFUNC(3,mov_w_bRr,(RR4 d, RR2 s, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_w_mr)(live.state[d].val+offset,s); return; } CLOBBER_MOV; s=readreg(s,2); d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_w_bRr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(3,mov_b_bRr,(RR4 d, RR1 s, IMM offset)) { int dreg=d; if (isconst(d)) { COMPCALL(mov_b_mr)(live.state[d].val+offset,s); return; } CLOBBER_MOV; s=readreg(s,1); d=readreg_offset(d,4); offset+=get_offset(dreg); raw_mov_b_bRr(d,s,offset); unlock2(d); unlock2(s); } MIDFUNC(1,mid_bswap_32,(RW4 r)) { if (isconst(r)) { uae_u32 oldv=live.state[r].val; live.state[r].val=reverse32(oldv); return; } CLOBBER_SW32; r=rmw(r,4,4); raw_bswap_32(r); unlock2(r); } MIDFUNC(1,mid_bswap_16,(RW2 r)) { if (isconst(r)) { uae_u32 oldv=live.state[r].val; live.state[r].val=((oldv>>8)&0xff) | ((oldv<<8)&0xff00) | (oldv&0xffff0000); return; } CLOBBER_SW16; r=rmw(r,2,2); raw_bswap_16(r); unlock2(r); } MIDFUNC(2,mov_l_rr,(W4 d, RR4 s)) { int olds; if (d==s) { /* How pointless! */ return; } if (isconst(s)) { COMPCALL(mov_l_ri)(d,live.state[s].val); return; } olds=s; disassociate(d); s=readreg_offset(s,4); live.state[d].realreg=s; live.state[d].realind=live.nat[s].nholds; live.state[d].val=live.state[olds].val; live.state[d].validsize=4; live.state[d].dirtysize=4; set_status(d,DIRTY); live.nat[s].holds[live.nat[s].nholds]=d; live.nat[s].nholds++; log_clobberreg(d); jit_log2("Added %d to nreg %d(%d), now holds %d regs", d,s,live.state[d].realind,live.nat[s].nholds); unlock2(s); } MIDFUNC(2,mov_l_mr,(IMM d, RR4 s)) { if (isconst(s)) { COMPCALL(mov_l_mi)(d,live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,4); raw_mov_l_mr(d,s); unlock2(s); } MIDFUNC(2,mov_w_mr,(IMM d, RR2 s)) { if (isconst(s)) { COMPCALL(mov_w_mi)(d,(uae_u16)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,2); raw_mov_w_mr(d,s); unlock2(s); } MIDFUNC(2,mov_w_rm,(W2 d, IMM s)) { CLOBBER_MOV; d=writereg(d,2); raw_mov_w_rm(d,s); unlock2(d); } MIDFUNC(2,mov_b_mr,(IMM d, RR1 s)) { if (isconst(s)) { COMPCALL(mov_b_mi)(d,(uae_u8)live.state[s].val); return; } CLOBBER_MOV; s=readreg(s,1); raw_mov_b_mr(d,s); unlock2(s); } MIDFUNC(2,mov_b_rm,(W1 d, IMM s)) { CLOBBER_MOV; d=writereg(d,1); raw_mov_b_rm(d,s); unlock2(d); } MIDFUNC(2,mov_l_ri,(W4 d, IMM s)) { set_const(d,s); return; } MIDFUNC(2,mov_w_ri,(W2 d, IMM s)) { CLOBBER_MOV; d=writereg(d,2); raw_mov_w_ri(d,s); unlock2(d); } MIDFUNC(2,mov_b_ri,(W1 d, IMM s)) { CLOBBER_MOV; d=writereg(d,1); raw_mov_b_ri(d,s); unlock2(d); } MIDFUNC(2,add_l_mi,(IMM d, IMM s)) { CLOBBER_ADD; raw_add_l_mi(d,s); } MIDFUNC(2,add_w_mi,(IMM d, IMM s)) { CLOBBER_ADD; raw_add_w_mi(d,s); } MIDFUNC(2,add_b_mi,(IMM d, IMM s)) { CLOBBER_ADD; raw_add_b_mi(d,s); } MIDFUNC(2,test_l_ri,(RR4 d, IMM i)) { CLOBBER_TEST; d=readreg(d,4); raw_test_l_ri(d,i); unlock2(d); } MIDFUNC(2,test_l_rr,(RR4 d, RR4 s)) { CLOBBER_TEST; d=readreg(d,4); s=readreg(s,4); raw_test_l_rr(d,s);; unlock2(d); unlock2(s); } MIDFUNC(2,test_w_rr,(RR2 d, RR2 s)) { CLOBBER_TEST; d=readreg(d,2); s=readreg(s,2); raw_test_w_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,test_b_rr,(RR1 d, RR1 s)) { CLOBBER_TEST; d=readreg(d,1); s=readreg(s,1); raw_test_b_rr(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,test_b_mi,(IMM d, IMM s)) { CLOBBER_TEST; raw_test_b_mi(d,s); } MIDFUNC(2,and_l_ri,(RW4 d, IMM i)) { if (isconst(d) && !needflags) { live.state[d].val &= i; return; } CLOBBER_AND; d=rmw(d,4,4); raw_and_l_ri(d,i); unlock2(d); } MIDFUNC(2,and_l,(RW4 d, RR4 s)) { CLOBBER_AND; s=readreg(s,4); d=rmw(d,4,4); raw_and_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,and_w,(RW2 d, RR2 s)) { CLOBBER_AND; s=readreg(s,2); d=rmw(d,2,2); raw_and_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,and_b,(RW1 d, RR1 s)) { CLOBBER_AND; s=readreg(s,1); d=rmw(d,1,1); raw_and_b(d,s); unlock2(d); unlock2(s); } // gb-- used for making an fpcr value in compemu_fpp.cpp MIDFUNC(2,or_l_rm,(RW4 d, IMM s)) { CLOBBER_OR; d=rmw(d,4,4); raw_or_l_rm(d,s); unlock2(d); } MIDFUNC(2,or_l_ri,(RW4 d, IMM i)) { if (isconst(d) && !needflags) { live.state[d].val|=i; return; } CLOBBER_OR; d=rmw(d,4,4); raw_or_l_ri(d,i); unlock2(d); } MIDFUNC(2,or_l,(RW4 d, RR4 s)) { if (isconst(d) && isconst(s) && !needflags) { live.state[d].val|=live.state[s].val; return; } CLOBBER_OR; s=readreg(s,4); d=rmw(d,4,4); raw_or_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,or_w,(RW2 d, RR2 s)) { CLOBBER_OR; s=readreg(s,2); d=rmw(d,2,2); raw_or_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,or_b,(RW1 d, RR1 s)) { CLOBBER_OR; s=readreg(s,1); d=rmw(d,1,1); raw_or_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,adc_l,(RW4 d, RR4 s)) { CLOBBER_ADC; s=readreg(s,4); d=rmw(d,4,4); raw_adc_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,adc_w,(RW2 d, RR2 s)) { CLOBBER_ADC; s=readreg(s,2); d=rmw(d,2,2); raw_adc_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,adc_b,(RW1 d, RR1 s)) { CLOBBER_ADC; s=readreg(s,1); d=rmw(d,1,1); raw_adc_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,add_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(add_l_ri)(d,live.state[s].val); return; } CLOBBER_ADD; s=readreg(s,4); d=rmw(d,4,4); raw_add_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,add_w,(RW2 d, RR2 s)) { if (isconst(s)) { COMPCALL(add_w_ri)(d,(uae_u16)live.state[s].val); return; } CLOBBER_ADD; s=readreg(s,2); d=rmw(d,2,2); raw_add_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,add_b,(RW1 d, RR1 s)) { if (isconst(s)) { COMPCALL(add_b_ri)(d,(uae_u8)live.state[s].val); return; } CLOBBER_ADD; s=readreg(s,1); d=rmw(d,1,1); raw_add_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_l_ri,(RW4 d, IMM i)) { if (!i && !needflags) return; if (isconst(d) && !needflags) { live.state[d].val-=i; return; } #if USE_OFFSET if (!needflags) { add_offset(d,-i); return; } #endif CLOBBER_SUB; d=rmw(d,4,4); raw_sub_l_ri(d,i); unlock2(d); } MIDFUNC(2,sub_w_ri,(RW2 d, IMM i)) { if (!i && !needflags) return; CLOBBER_SUB; d=rmw(d,2,2); raw_sub_w_ri(d,i); unlock2(d); } MIDFUNC(2,sub_b_ri,(RW1 d, IMM i)) { if (!i && !needflags) return; CLOBBER_SUB; d=rmw(d,1,1); raw_sub_b_ri(d,i); unlock2(d); } MIDFUNC(2,add_l_ri,(RW4 d, IMM i)) { if (!i && !needflags) return; if (isconst(d) && !needflags) { live.state[d].val+=i; return; } #if USE_OFFSET if (!needflags) { add_offset(d,i); return; } #endif CLOBBER_ADD; d=rmw(d,4,4); raw_add_l_ri(d,i); unlock2(d); } MIDFUNC(2,add_w_ri,(RW2 d, IMM i)) { if (!i && !needflags) return; CLOBBER_ADD; d=rmw(d,2,2); raw_add_w_ri(d,i); unlock2(d); } MIDFUNC(2,add_b_ri,(RW1 d, IMM i)) { if (!i && !needflags) return; CLOBBER_ADD; d=rmw(d,1,1); raw_add_b_ri(d,i); unlock2(d); } MIDFUNC(2,sbb_l,(RW4 d, RR4 s)) { CLOBBER_SBB; s=readreg(s,4); d=rmw(d,4,4); raw_sbb_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sbb_w,(RW2 d, RR2 s)) { CLOBBER_SBB; s=readreg(s,2); d=rmw(d,2,2); raw_sbb_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sbb_b,(RW1 d, RR1 s)) { CLOBBER_SBB; s=readreg(s,1); d=rmw(d,1,1); raw_sbb_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_l,(RW4 d, RR4 s)) { if (isconst(s)) { COMPCALL(sub_l_ri)(d,live.state[s].val); return; } CLOBBER_SUB; s=readreg(s,4); d=rmw(d,4,4); raw_sub_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_w,(RW2 d, RR2 s)) { if (isconst(s)) { COMPCALL(sub_w_ri)(d,(uae_u16)live.state[s].val); return; } CLOBBER_SUB; s=readreg(s,2); d=rmw(d,2,2); raw_sub_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,sub_b,(RW1 d, RR1 s)) { if (isconst(s)) { COMPCALL(sub_b_ri)(d,(uae_u8)live.state[s].val); return; } CLOBBER_SUB; s=readreg(s,1); d=rmw(d,1,1); raw_sub_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,cmp_l,(RR4 d, RR4 s)) { CLOBBER_CMP; s=readreg(s,4); d=readreg(d,4); raw_cmp_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,cmp_l_ri,(RR4 r, IMM i)) { CLOBBER_CMP; r=readreg(r,4); raw_cmp_l_ri(r,i); unlock2(r); } MIDFUNC(2,cmp_w,(RR2 d, RR2 s)) { CLOBBER_CMP; s=readreg(s,2); d=readreg(d,2); raw_cmp_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,cmp_b,(RR1 d, RR1 s)) { CLOBBER_CMP; s=readreg(s,1); d=readreg(d,1); raw_cmp_b(d,s); unlock2(d); unlock2(s); } MIDFUNC(2, cmp_b_ri, (RR1 r, IMM i)) { CLOBBER_CMP; r = readreg(r, 1); raw_cmp_b_ri(r, i); unlock2(r); } MIDFUNC(2,xor_l,(RW4 d, RR4 s)) { CLOBBER_XOR; s=readreg(s,4); d=rmw(d,4,4); raw_xor_l(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,xor_w,(RW2 d, RR2 s)) { CLOBBER_XOR; s=readreg(s,2); d=rmw(d,2,2); raw_xor_w(d,s); unlock2(d); unlock2(s); } MIDFUNC(2,xor_b,(RW1 d, RR1 s)) { CLOBBER_XOR; s=readreg(s,1); d=rmw(d,1,1); raw_xor_b(d,s); unlock2(d); unlock2(s); } #ifdef UAE MIDFUNC(5,call_r_11,(W4 out1, RR4 r, RR4 in1, IMM osize, IMM isize)) { clobber_flags(); remove_all_offsets(); if (osize==4) { if (out1!=in1 && out1!=r) { COMPCALL(forget_about)(out1); } } else { tomem_c(out1); } in1=readreg_specific(in1,isize,REG_PAR1); r=readreg(r,4); prepare_for_call_1(); /* This should ensure that there won't be any need for swapping nregs in prepare_for_call_2 */ #if USE_NORMAL_CALLING_CONVENTION raw_push_l_r(in1); #endif unlock2(in1); unlock2(r); prepare_for_call_2(); raw_dec_sp(STACK_SHADOW_SPACE); raw_call_r(r); raw_inc_sp(STACK_SHADOW_SPACE); #if USE_NORMAL_CALLING_CONVENTION raw_inc_sp(4); #endif live.nat[REG_RESULT].holds[0]=out1; live.nat[REG_RESULT].nholds=1; live.nat[REG_RESULT].touched=touchcnt++; live.state[out1].realreg=REG_RESULT; live.state[out1].realind=0; live.state[out1].val=0; live.state[out1].validsize=osize; live.state[out1].dirtysize=osize; set_status(out1,DIRTY); } #endif #if defined(UAE) MIDFUNC(5,call_r_02,(RR4 r, RR4 in1, RR4 in2, IMM isize1, IMM isize2)) { clobber_flags(); remove_all_offsets(); in1=readreg_specific(in1,isize1,REG_PAR1); in2=readreg_specific(in2,isize2,REG_PAR2); r=readreg(r,4); prepare_for_call_1(); /* This should ensure that there won't be any need for swapping nregs in prepare_for_call_2 */ #if USE_NORMAL_CALLING_CONVENTION raw_push_l_r(in2); raw_push_l_r(in1); #endif unlock2(r); unlock2(in1); unlock2(in2); prepare_for_call_2(); raw_dec_sp(STACK_SHADOW_SPACE); raw_call_r(r); raw_inc_sp(STACK_SHADOW_SPACE); #if USE_NORMAL_CALLING_CONVENTION raw_inc_sp(8); #endif } #endif /* forget_about() takes a mid-layer register */ MIDFUNC(1,forget_about,(W4 r)) { if (isinreg(r)) disassociate(r); live.state[r].val=0; set_status(r,UNDEF); } MIDFUNC(0,nop,(void)) { raw_emit_nop(); } MIDFUNC(1,f_forget_about,(FW r)) { if (f_isinreg(r)) f_disassociate(r); live.fate[r].status=UNDEF; } MIDFUNC(1,fmov_pi,(FW r)) { r=f_writereg(r); raw_fmov_pi(r); f_unlock(r); } MIDFUNC(1,fmov_log10_2,(FW r)) { r=f_writereg(r); raw_fmov_log10_2(r); f_unlock(r); } MIDFUNC(1,fmov_log2_e,(FW r)) { r=f_writereg(r); raw_fmov_log2_e(r); f_unlock(r); } MIDFUNC(1,fmov_loge_2,(FW r)) { r=f_writereg(r); raw_fmov_loge_2(r); f_unlock(r); } MIDFUNC(1,fmov_1,(FW r)) { r=f_writereg(r); raw_fmov_1(r); f_unlock(r); } MIDFUNC(1,fmov_0,(FW r)) { r=f_writereg(r); raw_fmov_0(r); f_unlock(r); } MIDFUNC(2,fmov_rm,(FW r, MEMPTRR m)) { r=f_writereg(r); raw_fmov_rm(r,m); f_unlock(r); } MIDFUNC(2,fmovi_rm,(FW r, MEMPTRR m)) { r=f_writereg(r); raw_fmovi_rm(r,m); f_unlock(r); } MIDFUNC(2,fmovi_mr,(MEMPTRW m, FR r)) { r=f_readreg(r); raw_fmovi_mr(m,r); f_unlock(r); } MIDFUNC(3,fmovi_mrb,(MEMPTRW m, FR r, double *bounds)) { r=f_readreg(r); raw_fmovi_mrb(m,r,bounds); f_unlock(r); } MIDFUNC(2,fmovs_rm,(FW r, MEMPTRR m)) { r=f_writereg(r); raw_fmovs_rm(r,m); f_unlock(r); } MIDFUNC(2,fmovs_mr,(MEMPTRW m, FR r)) { r=f_readreg(r); raw_fmovs_mr(m,r); f_unlock(r); } MIDFUNC(1,fcuts_r,(FRW r)) { r=f_rmw(r); raw_fcuts_r(r); f_unlock(r); } MIDFUNC(1,fcut_r,(FRW r)) { r=f_rmw(r); raw_fcut_r(r); f_unlock(r); } MIDFUNC(2,fmov_ext_mr,(MEMPTRW m, FR r)) { r=f_readreg(r); raw_fmov_ext_mr(m,r); f_unlock(r); } MIDFUNC(2,fmov_mr,(MEMPTRW m, FR r)) { r=f_readreg(r); raw_fmov_mr(m,r); f_unlock(r); } MIDFUNC(2,fmov_ext_rm,(FW r, MEMPTRR m)) { r=f_writereg(r); raw_fmov_ext_rm(r,m); f_unlock(r); } MIDFUNC(2,fmov_rr,(FW d, FR s)) { if (d==s) { /* How pointless! */ return; } #if USE_F_ALIAS f_disassociate(d); s=f_readreg(s); live.fate[d].realreg=s; live.fate[d].realind=live.fat[s].nholds; live.fate[d].status=DIRTY; live.fat[s].holds[live.fat[s].nholds]=d; live.fat[s].nholds++; f_unlock(s); #else s=f_readreg(s); d=f_writereg(d); raw_fmov_rr(d,s); f_unlock(s); f_unlock(d); #endif } MIDFUNC(2,fldcw_m_indexed,(RR4 index, IMM base)) { index=readreg(index,4); raw_fldcw_m_indexed(index,base); unlock2(index); } MIDFUNC(1,ftst_r,(FR r)) { r=f_readreg(r); raw_ftst_r(r); f_unlock(r); } MIDFUNC(0,dont_care_fflags,(void)) { f_disassociate(FP_RESULT); } MIDFUNC(2,fsqrt_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fsqrt_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fabs_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fabs_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fgetexp_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fgetexp_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fgetman_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fgetman_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fsin_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fsin_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fcos_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fcos_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,ftan_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_ftan_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(3,fsincos_rr,(FW d, FW c, FR s)) { s=f_readreg(s); /* s for source */ d=f_writereg(d); /* d for sine */ c=f_writereg(c); /* c for cosine */ raw_fsincos_rr(d,c,s); f_unlock(s); f_unlock(d); f_unlock(c); } MIDFUNC(2,fscale_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_fscale_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,ftwotox_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_ftwotox_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fetox_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fetox_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,frndint_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_frndint_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fetoxM1_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fetoxM1_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,ftentox_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_ftentox_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,flog2_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_flog2_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,flogN_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_flogN_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,flogNP1_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_flogNP1_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,flog10_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_flog10_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fasin_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fasin_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,facos_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_facos_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fatan_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fatan_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fatanh_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fatanh_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fsinh_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fsinh_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fcosh_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fcosh_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,ftanh_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_ftanh_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fneg_rr,(FW d, FR s)) { s=f_readreg(s); d=f_writereg(d); raw_fneg_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fadd_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_fadd_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fsub_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_fsub_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fcmp_rr,(FR d, FR s)) { d=f_readreg(d); s=f_readreg(s); raw_fcmp_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fdiv_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_fdiv_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,frem_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_frem_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,frem1_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_frem1_rr(d,s); f_unlock(s); f_unlock(d); } MIDFUNC(2,fmul_rr,(FRW d, FR s)) { s=f_readreg(s); d=f_rmw(d); raw_fmul_rr(d,s); f_unlock(s); f_unlock(d); } #ifdef __GNUC__ static inline void mfence(void) { #ifdef CPU_i386 if (!cpuinfo.x86_has_xmm2) __asm__ __volatile__("lock; addl $0,0(%%esp)":::"memory"); else #endif __asm__ __volatile__("mfence":::"memory"); } static inline void clflush(volatile void *__p) { __asm__ __volatile__("clflush %0" : "+m" (*(volatile char *)__p)); } static inline void flush_cpu_icache(void *start, void *stop) { mfence(); if (cpuinfo.x86_clflush_size != 0) { volatile char *vaddr = (volatile char *)(((uintptr)start / cpuinfo.x86_clflush_size) * cpuinfo.x86_clflush_size); volatile char *vend = (volatile char *)((((uintptr)stop + cpuinfo.x86_clflush_size - 1) / cpuinfo.x86_clflush_size) * cpuinfo.x86_clflush_size); while (vaddr < vend) { clflush(vaddr); vaddr += cpuinfo.x86_clflush_size; } } mfence(); } #else static inline void flush_cpu_icache(void *start, void *stop) { UNUSED(start); UNUSED(stop); } #endif static inline void write_jmp_target(uae_u32 *jmpaddr, cpuop_func* a) { uintptr rel = (uintptr) a - ((uintptr) jmpaddr + 4); *(jmpaddr) = (uae_u32) rel; flush_cpu_icache((void *) jmpaddr, (void *) &jmpaddr[1]); } static inline void emit_jmp_target(uae_u32 a) { emit_long(a-(JITPTR target+4)); } void compemu_bkpt(void) { emit_byte(0xcc); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_midfunc_x86.h000066400000000000000000000254461504763705000267500ustar00rootroot00000000000000/* * compiler/compemu_midfunc_x86.h - Native MIDFUNCS for IA-32 and AMD64 * * Copyright (c) 2014 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * * Note: * File is included by compemu.h * */ DECLARE_MIDFUNC(bt_l_ri(RR4 r, IMM i)); DECLARE_MIDFUNC(bt_l_rr(RR4 r, RR4 b)); DECLARE_MIDFUNC(btc_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(btc_l_rr(RW4 r, RR4 b)); DECLARE_MIDFUNC(bts_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(bts_l_rr(RW4 r, RR4 b)); DECLARE_MIDFUNC(btr_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(btr_l_rr(RW4 r, RR4 b)); DECLARE_MIDFUNC(mov_l_rm(W4 d, IMM s)); DECLARE_MIDFUNC(call_r(RR4 r)); DECLARE_MIDFUNC(sub_l_mi(IMM d, IMM s)); DECLARE_MIDFUNC(mov_l_mi(IMM d, IMM s)); DECLARE_MIDFUNC(mov_w_mi(IMM d, IMM s)); DECLARE_MIDFUNC(mov_b_mi(IMM d, IMM s)); DECLARE_MIDFUNC(rol_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(rol_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(rol_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(rol_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(rol_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(rol_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(shll_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(shll_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(shll_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(ror_b_ri(RR1 r, IMM i)); DECLARE_MIDFUNC(ror_w_ri(RR2 r, IMM i)); DECLARE_MIDFUNC(ror_l_ri(RR4 r, IMM i)); DECLARE_MIDFUNC(ror_l_rr(RR4 d, RR1 r)); DECLARE_MIDFUNC(ror_w_rr(RR2 d, RR1 r)); DECLARE_MIDFUNC(ror_b_rr(RR1 d, RR1 r)); DECLARE_MIDFUNC(shrl_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(shrl_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(shrl_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(shra_l_rr(RW4 d, RR1 r)); DECLARE_MIDFUNC(shra_w_rr(RW2 d, RR1 r)); DECLARE_MIDFUNC(shra_b_rr(RW1 d, RR1 r)); DECLARE_MIDFUNC(shll_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shll_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(shll_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(shrl_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shrl_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(shrl_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(shra_l_ri(RW4 r, IMM i)); DECLARE_MIDFUNC(shra_w_ri(RW2 r, IMM i)); DECLARE_MIDFUNC(shra_b_ri(RW1 r, IMM i)); DECLARE_MIDFUNC(setcc(W1 d, IMM cc)); DECLARE_MIDFUNC(setcc_m(IMM d, IMM cc)); DECLARE_MIDFUNC(cmov_l_rr(RW4 d, RR4 s, IMM cc)); DECLARE_MIDFUNC(cmov_l_rm(RW4 d, IMM s, IMM cc)); DECLARE_MIDFUNC(bsf_l_rr(W4 d, RR4 s)); DECLARE_MIDFUNC(pop_m(IMM d)); DECLARE_MIDFUNC(push_m(IMM d)); DECLARE_MIDFUNC(pop_l(W4 d)); DECLARE_MIDFUNC(push_l_i(IMM i)); DECLARE_MIDFUNC(push_l(RR4 s)); DECLARE_MIDFUNC(clear_16(RW4 r)); DECLARE_MIDFUNC(clear_8(RW4 r)); DECLARE_MIDFUNC(sign_extend_32_rr(W4 d, RR2 s)); DECLARE_MIDFUNC(sign_extend_16_rr(W4 d, RR2 s)); DECLARE_MIDFUNC(sign_extend_8_rr(W4 d, RR1 s)); DECLARE_MIDFUNC(zero_extend_16_rr(W4 d, RR2 s)); DECLARE_MIDFUNC(zero_extend_8_rr(W4 d, RR1 s)); DECLARE_MIDFUNC(imul_64_32(RW4 d, RW4 s)); DECLARE_MIDFUNC(mul_64_32(RW4 d, RW4 s)); DECLARE_MIDFUNC(simulate_bsf(W4 tmp, RW4 s)); DECLARE_MIDFUNC(imul_32_32(RW4 d, RR4 s)); DECLARE_MIDFUNC(mul_32_32(RW4 d, RR4 s)); DECLARE_MIDFUNC(mov_b_rr(W1 d, RR1 s)); DECLARE_MIDFUNC(mov_w_rr(W2 d, RR2 s)); DECLARE_MIDFUNC(mov_l_rrm_indexed(W4 d,RR4 baser, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_w_rrm_indexed(W2 d, RR4 baser, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_b_rrm_indexed(W1 d, RR4 baser, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_l_mrr_indexed(RR4 baser, RR4 index, IMM factor, RR4 s)); DECLARE_MIDFUNC(mov_w_mrr_indexed(RR4 baser, RR4 index, IMM factor, RR2 s)); DECLARE_MIDFUNC(mov_b_mrr_indexed(RR4 baser, RR4 index, IMM factor, RR1 s)); DECLARE_MIDFUNC(mov_l_bmrr_indexed(IMM base, RR4 baser, RR4 index, IMM factor, RR4 s)); DECLARE_MIDFUNC(mov_w_bmrr_indexed(IMM base, RR4 baser, RR4 index, IMM factor, RR2 s)); DECLARE_MIDFUNC(mov_b_bmrr_indexed(IMM base, RR4 baser, RR4 index, IMM factor, RR1 s)); DECLARE_MIDFUNC(mov_l_brrm_indexed(W4 d, IMM base, RR4 baser, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_w_brrm_indexed(W2 d, IMM base, RR4 baser, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_b_brrm_indexed(W1 d, IMM base, RR4 baser, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_l_rm_indexed(W4 d, IMM base, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_l_rR(W4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_rR(W2 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_b_rR(W1 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_l_brR(W4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_brR(W2 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_b_brR(W1 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_l_Ri(RR4 d, IMM i, IMM offset)); DECLARE_MIDFUNC(mov_w_Ri(RR4 d, IMM i, IMM offset)); DECLARE_MIDFUNC(mov_b_Ri(RR4 d, IMM i, IMM offset)); DECLARE_MIDFUNC(mov_l_Rr(RR4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_Rr(RR4 d, RR2 s, IMM offset)); DECLARE_MIDFUNC(mov_b_Rr(RR4 d, RR1 s, IMM offset)); DECLARE_MIDFUNC(lea_l_brr(W4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(lea_l_brr_indexed(W4 d, RR4 s, RR4 index, IMM factor, IMM offset)); DECLARE_MIDFUNC(lea_l_rr_indexed(W4 d, RR4 s, RR4 index, IMM factor)); DECLARE_MIDFUNC(mov_l_bRr(RR4 d, RR4 s, IMM offset)); DECLARE_MIDFUNC(mov_w_bRr(RR4 d, RR2 s, IMM offset)); DECLARE_MIDFUNC(mov_b_bRr(RR4 d, RR1 s, IMM offset)); DECLARE_MIDFUNC(mid_bswap_32(RW4 r)); DECLARE_MIDFUNC(mid_bswap_16(RW2 r)); DECLARE_MIDFUNC(mov_l_rr(W4 d, RR4 s)); DECLARE_MIDFUNC(mov_l_mr(IMM d, RR4 s)); DECLARE_MIDFUNC(mov_w_mr(IMM d, RR2 s)); DECLARE_MIDFUNC(mov_w_rm(W2 d, IMM s)); DECLARE_MIDFUNC(mov_b_mr(IMM d, RR1 s)); DECLARE_MIDFUNC(mov_b_rm(W1 d, IMM s)); DECLARE_MIDFUNC(mov_l_ri(W4 d, IMM s)); DECLARE_MIDFUNC(mov_w_ri(W2 d, IMM s)); DECLARE_MIDFUNC(mov_b_ri(W1 d, IMM s)); DECLARE_MIDFUNC(add_l_mi(IMM d, IMM s)); DECLARE_MIDFUNC(add_w_mi(IMM d, IMM s)); DECLARE_MIDFUNC(add_b_mi(IMM d, IMM s)); DECLARE_MIDFUNC(test_l_ri(RR4 d, IMM i)); DECLARE_MIDFUNC(test_l_rr(RR4 d, RR4 s)); DECLARE_MIDFUNC(test_w_rr(RR2 d, RR2 s)); DECLARE_MIDFUNC(test_b_rr(RR1 d, RR1 s)); DECLARE_MIDFUNC(test_b_mi(IMM d, IMM s)); DECLARE_MIDFUNC(and_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(and_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(and_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(and_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(or_l_rm(RW4 d, IMM s)); DECLARE_MIDFUNC(or_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(or_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(or_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(or_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(adc_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(adc_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(adc_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(add_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(add_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(add_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(sub_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(sub_w_ri(RW2 d, IMM i)); DECLARE_MIDFUNC(sub_b_ri(RW1 d, IMM i)); DECLARE_MIDFUNC(add_l_ri(RW4 d, IMM i)); DECLARE_MIDFUNC(add_w_ri(RW2 d, IMM i)); DECLARE_MIDFUNC(add_b_ri(RW1 d, IMM i)); DECLARE_MIDFUNC(sbb_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(sbb_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(sbb_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(sub_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(sub_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(sub_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(cmp_l(RR4 d, RR4 s)); DECLARE_MIDFUNC(cmp_l_ri(RR4 r, IMM i)); DECLARE_MIDFUNC(cmp_w(RR2 d, RR2 s)); DECLARE_MIDFUNC(cmp_b(RR1 d, RR1 s)); DECLARE_MIDFUNC(cmp_b_ri(RR1 r, IMM i)); DECLARE_MIDFUNC(xor_l(RW4 d, RR4 s)); DECLARE_MIDFUNC(xor_w(RW2 d, RR2 s)); DECLARE_MIDFUNC(xor_b(RW1 d, RR1 s)); DECLARE_MIDFUNC(live_flags(void)); DECLARE_MIDFUNC(dont_care_flags(void)); DECLARE_MIDFUNC(duplicate_carry(void)); DECLARE_MIDFUNC(setcc_for_cntzero(RR4 d, RR4 data, RR4 odata, int obit, int size, int ov)); DECLARE_MIDFUNC(clear_overflow(void)); DECLARE_MIDFUNC(restore_carry(void)); DECLARE_MIDFUNC(start_needflags(void)); DECLARE_MIDFUNC(end_needflags(void)); DECLARE_MIDFUNC(make_flags_live(void)); DECLARE_MIDFUNC(call_r_11(RR4 r, W4 out1, RR4 in1, IMM osize, IMM isize)); DECLARE_MIDFUNC(call_r_02(RR4 r, RR4 in1, RR4 in2, IMM isize1, IMM isize2)); DECLARE_MIDFUNC(forget_about(W4 r)); DECLARE_MIDFUNC(nop(void)); DECLARE_MIDFUNC(f_forget_about(FW r)); DECLARE_MIDFUNC(fmov_pi(FW r)); DECLARE_MIDFUNC(fmov_log10_2(FW r)); DECLARE_MIDFUNC(fmov_log2_e(FW r)); DECLARE_MIDFUNC(fmov_loge_2(FW r)); DECLARE_MIDFUNC(fmov_1(FW r)); DECLARE_MIDFUNC(fmov_0(FW r)); DECLARE_MIDFUNC(fmov_rm(FW r, MEMPTRR m)); DECLARE_MIDFUNC(fmov_mr(MEMPTRW m, FR r)); DECLARE_MIDFUNC(fmovi_rm(FW r, MEMPTRR m)); DECLARE_MIDFUNC(fmovi_mr(MEMPTRW m, FR r)); DECLARE_MIDFUNC(fmovi_mrb(MEMPTRW m, FR r, double *bounds)); DECLARE_MIDFUNC(fmovs_rm(FW r, MEMPTRR m)); DECLARE_MIDFUNC(fmovs_mr(MEMPTRW m, FR r)); DECLARE_MIDFUNC(fcuts_r(FRW r)); DECLARE_MIDFUNC(fcut_r(FRW r)); DECLARE_MIDFUNC(fmov_ext_mr(MEMPTRW m, FR r)); DECLARE_MIDFUNC(fmov_ext_rm(FW r, MEMPTRR m)); DECLARE_MIDFUNC(fmov_rr(FW d, FR s)); DECLARE_MIDFUNC(fldcw_m_indexed(RR4 index, IMM base)); DECLARE_MIDFUNC(ftst_r(FR r)); DECLARE_MIDFUNC(dont_care_fflags(void)); DECLARE_MIDFUNC(fsqrt_rr(FW d, FR s)); DECLARE_MIDFUNC(fabs_rr(FW d, FR s)); DECLARE_MIDFUNC(frndint_rr(FW d, FR s)); DECLARE_MIDFUNC(fgetexp_rr(FW d, FR s)); DECLARE_MIDFUNC(fgetman_rr(FW d, FR s)); DECLARE_MIDFUNC(fsin_rr(FW d, FR s)); DECLARE_MIDFUNC(fcos_rr(FW d, FR s)); DECLARE_MIDFUNC(ftan_rr(FW d, FR s)); DECLARE_MIDFUNC(fsincos_rr(FW d, FW c, FR s)); DECLARE_MIDFUNC(fscale_rr(FRW d, FR s)); DECLARE_MIDFUNC(ftwotox_rr(FW d, FR s)); DECLARE_MIDFUNC(fetox_rr(FW d, FR s)); DECLARE_MIDFUNC(fetoxM1_rr(FW d, FR s)); DECLARE_MIDFUNC(ftentox_rr(FW d, FR s)); DECLARE_MIDFUNC(flog2_rr(FW d, FR s)); DECLARE_MIDFUNC(flogN_rr(FW d, FR s)); DECLARE_MIDFUNC(flogNP1_rr(FW d, FR s)); DECLARE_MIDFUNC(flog10_rr(FW d, FR s)); DECLARE_MIDFUNC(fasin_rr(FW d, FR s)); DECLARE_MIDFUNC(facos_rr(FW d, FR s)); DECLARE_MIDFUNC(fatan_rr(FW d, FR s)); DECLARE_MIDFUNC(fatanh_rr(FW d, FR s)); DECLARE_MIDFUNC(fsinh_rr(FW d, FR s)); DECLARE_MIDFUNC(fcosh_rr(FW d, FR s)); DECLARE_MIDFUNC(ftanh_rr(FW d, FR s)); DECLARE_MIDFUNC(fneg_rr(FW d, FR s)); DECLARE_MIDFUNC(fadd_rr(FRW d, FR s)); DECLARE_MIDFUNC(fsub_rr(FRW d, FR s)); DECLARE_MIDFUNC(fmul_rr(FRW d, FR s)); DECLARE_MIDFUNC(frem_rr(FRW d, FR s)); DECLARE_MIDFUNC(frem1_rr(FRW d, FR s)); DECLARE_MIDFUNC(fdiv_rr(FRW d, FR s)); DECLARE_MIDFUNC(fcmp_rr(FR d, FR s)); DECLARE_MIDFUNC(fflags_into_flags(W2 tmp)); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_prefs.c000066400000000000000000000062441504763705000257230ustar00rootroot00000000000000/******************************************************************** * Preferences handling. This is just a convenient place to put it * ********************************************************************/ extern bool have_done_picasso; bool check_prefs_changed_comp (bool checkonly) { bool changed = 0; static int cachesize_prev, comptrust_prev; static bool canbang_prev; special_mem_default = currprefs.comptrustbyte ? (S_READ | S_WRITE | S_N_ADDR) : 0; if (currprefs.comptrustbyte != changed_prefs.comptrustbyte || currprefs.comptrustword != changed_prefs.comptrustword || currprefs.comptrustlong != changed_prefs.comptrustlong || currprefs.comptrustnaddr!= changed_prefs.comptrustnaddr || currprefs.compnf != changed_prefs.compnf || currprefs.comp_hardflush != changed_prefs.comp_hardflush || currprefs.comp_constjump != changed_prefs.comp_constjump || currprefs.compfpu != changed_prefs.compfpu || currprefs.fpu_strict != changed_prefs.fpu_strict || currprefs.cachesize != changed_prefs.cachesize) changed = 1; if (checkonly) return changed; currprefs.comptrustbyte = changed_prefs.comptrustbyte; currprefs.comptrustword = changed_prefs.comptrustword; currprefs.comptrustlong = changed_prefs.comptrustlong; currprefs.comptrustnaddr= changed_prefs.comptrustnaddr; currprefs.compnf = changed_prefs.compnf; currprefs.comp_hardflush = changed_prefs.comp_hardflush; currprefs.comp_constjump = changed_prefs.comp_constjump; currprefs.compfpu = changed_prefs.compfpu; currprefs.fpu_strict = changed_prefs.fpu_strict; if (currprefs.cachesize != changed_prefs.cachesize) { if (currprefs.cachesize && !changed_prefs.cachesize) { cachesize_prev = currprefs.cachesize; comptrust_prev = currprefs.comptrustbyte; canbang_prev = canbang; } else if (!currprefs.cachesize && changed_prefs.cachesize == cachesize_prev) { changed_prefs.comptrustbyte = currprefs.comptrustbyte = comptrust_prev; changed_prefs.comptrustword = currprefs.comptrustword = comptrust_prev; changed_prefs.comptrustlong = currprefs.comptrustlong = comptrust_prev; changed_prefs.comptrustnaddr = currprefs.comptrustnaddr = comptrust_prev; } currprefs.cachesize = changed_prefs.cachesize; alloc_cache(); changed = 1; } // Turn off illegal-mem logging when using JIT... if(currprefs.cachesize) currprefs.illegal_mem = changed_prefs.illegal_mem;// = 0; if ((!canbang || !currprefs.cachesize) && currprefs.comptrustbyte != 1) { // Set all of these to indirect when canbang == 0 currprefs.comptrustbyte = 1; currprefs.comptrustword = 1; currprefs.comptrustlong = 1; currprefs.comptrustnaddr= 1; changed_prefs.comptrustbyte = 1; changed_prefs.comptrustword = 1; changed_prefs.comptrustlong = 1; changed_prefs.comptrustnaddr= 1; changed = 1; if (currprefs.cachesize) write_log (_T("JIT: Reverting to \"indirect\" access, because canbang is zero!\n")); } if (changed) write_log (_T("JIT: cache=%d. b=%d w=%d l=%d fpu=%d nf=%d inline=%d hard=%d\n"), currprefs.cachesize, currprefs.comptrustbyte, currprefs.comptrustword, currprefs.comptrustlong, currprefs.compfpu, currprefs.compnf, currprefs.comp_constjump, currprefs.comp_hardflush); return changed; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/compemu_support.c000066400000000000000000003750251504763705000263260ustar00rootroot00000000000000/* * compiler/compemu_support.cpp - Core dynamic translation engine * * Copyright (c) 2001-2009 Milan Jurik of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * This file is part of the ARAnyM project which builds a new and powerful * TOS/FreeMiNT compatible virtual machine running on almost any hardware. * * JIT compiler m68k -> IA-32 and AMD64 / ARM * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * Adaptation for Basilisk II and improvements, copyright 2000-2004 Gwenole Beauchesne * Portions related to CPU detection come from linux/arch/i386/kernel/setup.c * * ARAnyM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ARAnyM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ARAnyM; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #ifdef UAE #define writemem_special writemem #define readmem_special readmem #else #if !FIXED_ADDRESSING #error "Only Fixed Addressing is supported with the JIT Compiler" #endif #if defined(X86_ASSEMBLY) && !SAHF_SETO_PROFITABLE #error "Only [LS]AHF scheme to [gs]et flags is supported with the JIT Compiler" #endif /* NOTE: support for AMD64 assumes translation cache and other code * buffers are allocated into a 32-bit address space because (i) B2/JIT * code is not 64-bit clean and (ii) it's faster to resolve branches * that way. */ #if !defined(CPU_i386) && !defined(CPU_x86_64) && !defined(CPU_arm) #error "Only IA-32, X86-64 and ARM v6 targets are supported with the JIT Compiler" #endif #endif #define USE_MATCH 0 /* kludge for Brian, so he can compile under MSVC++ */ #define USE_NORMAL_CALLING_CONVENTION 0 #include "sysconfig.h" #include "sysdeps.h" #ifdef JIT #ifdef UAE #define bug write_log #include "options_cpu.h" #include "events.h" #include "memory.h" #include "custom.h" #else #include "cpu_emulation.h" #include "main.h" #include "vm_alloc.h" #include "m68k.h" #include "memory-uae.h" #include "readcpu.h" #endif #include "newcpu.h" #include "comptbl.h" #ifdef UAE #include "compemu.h" #ifdef FSUAE #include "codegen_udis86.h" #endif #else #include "compiler/compemu.h" #include "fpu/fpu.h" #include "fpu/flags.h" #include "parameters.h" static void build_comp(void); #endif #ifndef UAE #include "verify.h" #endif #ifdef UAE #ifdef FSUAE #include "uae/fs.h" #endif #include "uae/log.h" #if defined(__pie__) || defined (__PIE__) #error Position-independent code (PIE) cannot be used with JIT #endif #include "uae/vm.h" #define VM_PAGE_READ UAE_VM_READ #define VM_PAGE_WRITE UAE_VM_WRITE #define VM_PAGE_EXECUTE UAE_VM_EXECUTE #define VM_MAP_FAILED UAE_VM_ALLOC_FAILED #define VM_MAP_DEFAULT 1 #define VM_MAP_32BIT 1 #define vm_protect(address, size, protect) uae_vm_protect(address, size, protect) #define vm_release(address, size) uae_vm_free(address, size) static inline void *vm_acquire(uae_u32 size, int options = VM_MAP_DEFAULT) { assert(options == (VM_MAP_DEFAULT | VM_MAP_32BIT)); return uae_vm_alloc(size, UAE_VM_32BIT, UAE_VM_READ_WRITE); } #define UNUSED(x) #include "uae.h" #include "uae/log.h" #define jit_log(format, ...) \ uae_log("JIT: " format "\n", ##__VA_ARGS__); #define jit_log2(format, ...) #define MEMBaseDiff uae_p32(NATMEM_OFFSET) #ifdef NATMEM_OFFSET #define FIXED_ADDRESSING 1 #endif // %%% BRIAN KING WAS HERE %%% extern bool canbang; #include "compemu_prefs.cpp" #define uint32 uae_u32 #define uint8 uae_u8 static inline int distrust_check(int value) { #ifdef JIT_ALWAYS_DISTRUST return 1; #else int distrust = value; #ifdef FSUAE switch (value) { case 0: distrust = 0; break; case 1: distrust = 1; break; case 2: distrust = ((start_pc & 0xF80000) == 0xF80000); break; case 3: distrust = !have_done_picasso; break; default: abort(); } #endif return distrust; #endif } static inline int distrust_byte(void) { return distrust_check(currprefs.comptrustbyte); } static inline int distrust_word(void) { return distrust_check(currprefs.comptrustword); } static inline int distrust_long(void) { return distrust_check(currprefs.comptrustlong); } static inline int distrust_addr(void) { return distrust_check(currprefs.comptrustnaddr); } #else #define DEBUG 0 #include "debug.h" #define NATMEM_OFFSET MEMBaseDiff #define canbang 1 #define op_illg op_illg_1 #ifdef WINUAE_ARANYM void jit_abort(const char *format, ...) { va_list args; va_start(args, format); ndebug::pdbvprintf(format, args); va_end(args); abort(); } #endif #if DEBUG #define PROFILE_COMPILE_TIME 1 #define PROFILE_UNTRANSLATED_INSNS 1 #endif #endif #include #include #include #include #if defined(CPU_x86_64) && 0 #define RECORD_REGISTER_USAGE 1 #endif #ifdef JIT_DEBUG #undef abort #define abort() do { \ fprintf(stderr, "Abort in file %s at line %d\n", __FILE__, __LINE__); \ compiler_dumpstate(); \ exit(EXIT_FAILURE); \ } while (0) #endif #ifdef RECORD_REGISTER_USAGE static uint64 reg_count[16]; static uint64 reg_count_local[16]; static int reg_count_compare(const void *ap, const void *bp) { const int a = *((int *)ap); const int b = *((int *)bp); return reg_count[b] - reg_count[a]; } #endif #ifdef PROFILE_COMPILE_TIME #include static uae_u32 compile_count = 0; static clock_t compile_time = 0; static clock_t emul_start_time = 0; static clock_t emul_end_time = 0; #endif #ifdef PROFILE_UNTRANSLATED_INSNS static const int untranslated_top_ten = 50; static uae_u32 raw_cputbl_count[65536] = { 0, }; static uae_u16 opcode_nums[65536]; static int __cdecl untranslated_compfn(const void *e1, const void *e2) { int v1 = *(const uae_u16*)e1; int v2 = *(const uae_u16*)e2; return (int)raw_cputbl_count[v2] - (int)raw_cputbl_count[v1]; } #endif static compop_func *compfunctbl[65536]; static compop_func *nfcompfunctbl[65536]; #ifdef NOFLAGS_SUPPORT_GENCOMP static cpuop_func *nfcpufunctbl[65536]; #endif uae_u8* comp_pc_p; #ifdef UAE /* defined in uae.h */ #else // External variables #endif // gb-- Extra data for Basilisk II/JIT #ifdef JIT_DEBUG #define JITDebug bx_options.jit.jitdebug // Enable runtime disassemblers through mon? #else #define JITDebug false // Don't use JIT debug mode at all #endif #if USE_INLINING #ifdef UAE #define follow_const_jumps (currprefs.comp_constjump != 0) #else static bool follow_const_jumps = true; // Flag: translation through constant jumps #endif #else const bool follow_const_jumps = false; #endif static uae_u32 cache_size = 0; // Size of total cache allocated for compiled blocks static uae_u32 current_cache_size = 0; // Cache grows upwards: how much has been consumed already // Flag: compile FPU instructions ? #ifdef UAE #ifdef USE_JIT_FPU #define avoid_fpu (!currprefs.compfpu) #define lazy_flush (!currprefs.comp_hardflush) #else #define avoid_fpu (true) #define lazy_flush (true) #endif #else #ifdef USE_JIT_FPU #define avoid_fpu (!bx_options.jit.jitfpu) #else #define avoid_fpu (true) #endif #endif static bool have_cmov = false; // target has CMOV instructions ? static bool have_rat_stall = true; // target has partial register stalls ? const bool tune_alignment = true; // Tune code alignments for running CPU ? const bool tune_nop_fillers = true; // Tune no-op fillers for architecture static bool setzflg_uses_bsf = false; // setzflg virtual instruction can use native BSF instruction correctly? static int align_loops = 32; // Align the start of loops static int align_jumps = 32; // Align the start of jumps static int optcount[10] = { #ifdef UAE 4, // How often a block has to be executed before it is translated #else 10, // How often a block has to be executed before it is translated #endif 0, // How often to use naive translation 0, 0, 0, 0, -1, -1, -1, -1 }; #ifdef UAE op_properties prop[65536]; #else static op_properties prop[65536]; static inline int end_block(uae_u32 opcode) { return (prop[opcode].cflow & fl_end_block); } #endif static inline bool is_const_jump(uae_u32 opcode) { return (prop[opcode].cflow == fl_const_jump); } #if 0 static inline bool may_trap(uae_u32 opcode) { return (prop[opcode].cflow & fl_trap); } #endif static inline unsigned int cft_map (unsigned int f) { #ifdef UAE #if !defined(HAVE_GET_WORD_UNSWAPPED) return f; #else return do_byteswap_16(f); #endif #else #if !defined(HAVE_GET_WORD_UNSWAPPED) || defined(FULLMMU) return f; #else return ((f >> 8) & 255) | ((f & 255) << 8); #endif #endif } uae_u8* start_pc_p; uae_u32 start_pc; uae_u32 current_block_pc_p; static uintptr current_block_start_target; uae_u32 needed_flags; static uintptr next_pc_p; static uintptr taken_pc_p; static int branch_cc; static int redo_current_block; #ifdef UAE int segvcount=0; #endif static uae_u8* current_compile_p=NULL; static uae_u8* max_compile_start; static uae_u8* compiled_code=NULL; static uae_s32 reg_alloc_run; const int POPALLSPACE_SIZE = 2048; /* That should be enough space */ static uae_u8 *popallspace=NULL; void* pushall_call_handler=NULL; static void* popall_do_nothing=NULL; static void* popall_exec_nostats=NULL; static void* popall_execute_normal=NULL; static void* popall_cache_miss=NULL; static void* popall_recompile_block=NULL; static void* popall_check_checksum=NULL; /* The 68k only ever executes from even addresses. So right now, we * waste half the entries in this array * UPDATE: We now use those entries to store the start of the linked * lists that we maintain for each hash result. */ static cacheline cache_tags[TAGSIZE]; static int cache_enabled=0; static blockinfo* hold_bi[MAX_HOLD_BI]; static blockinfo* active; static blockinfo* dormant; #ifdef NOFLAGS_SUPPORT_GENCOMP /* 68040 */ extern const struct cputbl op_smalltbl_0[]; #endif extern const struct comptbl op_smalltbl_0_comp_nf[]; extern const struct comptbl op_smalltbl_0_comp_ff[]; static void flush_icache_hard(int); static void flush_icache_lazy(int); static void flush_icache_none(int); //void (*flush_icache)(int) = flush_icache_none; static bigstate live; static smallstate empty_ss; static smallstate default_ss; static int optlev; static int writereg(int r, int size); static void unlock2(int r); static void setlock(int r); static int readreg_specific(int r, int size, int spec); static int writereg_specific(int r, int size, int spec); static void inline write_jmp_target(uae_u32 *jmpaddr, cpuop_func* a); uae_u32 m68k_pc_offset; /* Some arithmetic operations can be optimized away if the operands * are known to be constant. But that's only a good idea when the * side effects they would have on the flags are not important. This * variable indicates whether we need the side effects or not */ static uae_u32 needflags=0; /* Flag handling is complicated. * * x86 instructions create flags, which quite often are exactly what we * want. So at times, the "68k" flags are actually in the x86 flags. * * Then again, sometimes we do x86 instructions that clobber the x86 * flags, but don't represent a corresponding m68k instruction. In that * case, we have to save them. * * We used to save them to the stack, but now store them back directly * into the regflags.cznv of the traditional emulation. Thus some odd * names. * * So flags can be in either of two places (used to be three; boy were * things complicated back then!); And either place can contain either * valid flags or invalid trash (and on the stack, there was also the * option of "nothing at all", now gone). A couple of variables keep * track of the respective states. * * To make things worse, we might or might not be interested in the flags. * by default, we are, but a call to dont_care_flags can change that * until the next call to live_flags. If we are not, pretty much whatever * is in the register and/or the native flags is seen as valid. */ static inline blockinfo* get_blockinfo(uae_u32 cl) { return cache_tags[cl+1].bi; } static inline blockinfo* get_blockinfo_addr(void* addr) { blockinfo* bi=get_blockinfo(cacheline(addr)); while (bi) { if (bi->pc_p==addr) return bi; bi=bi->next_same_cl; } return NULL; } #ifdef WINUAE_ARANYM /******************************************************************* * Disassembler support * *******************************************************************/ #define TARGET_M68K 0 #define TARGET_POWERPC 1 #define TARGET_X86 2 #define TARGET_X86_64 3 #define TARGET_ARM 4 #if defined(CPU_i386) #define TARGET_NATIVE TARGET_X86 #endif #if defined(CPU_powerpc) #define TARGET_NATIVE TARGET_POWERPC #endif #if defined(CPU_x86_64) #define TARGET_NATIVE TARGET_X86_64 #endif #if defined(CPU_arm) #define TARGET_NATIVE TARGET_ARM #endif #include "disasm-glue.h" bool disasm_this_inst; #if defined(JIT_DEBUG) || (defined(HAVE_DISASM_NATIVE) && defined(HAVE_DISASM_M68K)) static void disasm_block(int disasm_target, const uint8 *start, size_t length) { UNUSED(start); UNUSED(length); switch (disasm_target) { case TARGET_M68K: #if defined(HAVE_DISASM_M68K) { char buf[256]; disasm_info.memory_vma = ((memptr)((uintptr_t)(start) - MEMBaseDiff)); while (length > 0) { int isize = m68k_disasm_to_buf(&disasm_info, buf, 1); bug("%s", buf); if (isize < 0) break; if ((uintptr)isize > length) break; length -= isize; } } #endif break; case TARGET_X86: case TARGET_X86_64: #if defined(HAVE_DISASM_X86) { const uint8 *end = start + length; char buf[256]; while (start < end) { start = x86_disasm(start, buf, 1); bug("%s", buf); } } #endif break; case TARGET_ARM: #if defined(HAVE_DISASM_ARM) { const uint8 *end = start + length; char buf[256]; while (start < end) { start = arm_disasm(start, buf, 1); bug("%s", buf); } } #endif break; } } static inline void disasm_native_block(const uint8 *start, size_t length) { disasm_block(TARGET_NATIVE, start, length); } static inline void disasm_m68k_block(const uint8 *start, size_t length) { disasm_block(TARGET_M68K, start, length); } #endif #endif /* WINUAE_ARANYM */ /******************************************************************* * All sorts of list related functions for all of the lists * *******************************************************************/ static inline void remove_from_cl_list(blockinfo* bi) { uae_u32 cl=cacheline(bi->pc_p); if (bi->prev_same_cl_p) *(bi->prev_same_cl_p)=bi->next_same_cl; if (bi->next_same_cl) bi->next_same_cl->prev_same_cl_p=bi->prev_same_cl_p; if (cache_tags[cl+1].bi) cache_tags[cl].handler=cache_tags[cl+1].bi->handler_to_use; else cache_tags[cl].handler=(cpuop_func*)popall_execute_normal; } static inline void remove_from_list(blockinfo* bi) { if (bi->prev_p) *(bi->prev_p)=bi->next; if (bi->next) bi->next->prev_p=bi->prev_p; } #if 0 static inline void remove_from_lists(blockinfo* bi) { remove_from_list(bi); remove_from_cl_list(bi); } #endif static inline void add_to_cl_list(blockinfo* bi) { uae_u32 cl=cacheline(bi->pc_p); if (cache_tags[cl+1].bi) cache_tags[cl+1].bi->prev_same_cl_p=&(bi->next_same_cl); bi->next_same_cl=cache_tags[cl+1].bi; cache_tags[cl+1].bi=bi; bi->prev_same_cl_p=&(cache_tags[cl+1].bi); cache_tags[cl].handler=bi->handler_to_use; } static inline void raise_in_cl_list(blockinfo* bi) { remove_from_cl_list(bi); add_to_cl_list(bi); } static inline void add_to_active(blockinfo* bi) { if (active) active->prev_p=&(bi->next); bi->next=active; active=bi; bi->prev_p=&active; } static inline void add_to_dormant(blockinfo* bi) { if (dormant) dormant->prev_p=&(bi->next); bi->next=dormant; dormant=bi; bi->prev_p=&dormant; } static inline void remove_dep(dependency* d) { if (d->prev_p) *(d->prev_p)=d->next; if (d->next) d->next->prev_p=d->prev_p; d->prev_p=NULL; d->next=NULL; } /* This block's code is about to be thrown away, so it no longer depends on anything else */ static inline void remove_deps(blockinfo* bi) { remove_dep(&(bi->dep[0])); remove_dep(&(bi->dep[1])); } static inline void adjust_jmpdep(dependency* d, cpuop_func* a) { write_jmp_target(d->jmp_off, a); } /******************************************************************** * Soft flush handling support functions * ********************************************************************/ static inline void set_dhtu(blockinfo* bi, cpuop_func *dh) { jit_log2("bi is %p",bi); if (dh!=bi->direct_handler_to_use) { dependency* x=bi->deplist; jit_log2("bi->deplist=%p",bi->deplist); while (x) { jit_log2("x is %p",x); jit_log2("x->next is %p",x->next); jit_log2("x->prev_p is %p",x->prev_p); if (x->jmp_off) { adjust_jmpdep(x,dh); } x=x->next; } bi->direct_handler_to_use=dh; } } static inline void invalidate_block(blockinfo* bi) { int i; bi->optlevel=0; bi->count=optcount[0]-1; bi->handler=NULL; bi->handler_to_use=(cpuop_func*)popall_execute_normal; bi->direct_handler=NULL; set_dhtu(bi,bi->direct_pen); bi->needed_flags=0xff; bi->status=BI_INVALID; for (i=0;i<2;i++) { bi->dep[i].jmp_off=NULL; bi->dep[i].target=NULL; } remove_deps(bi); } static inline void create_jmpdep(blockinfo* bi, int i, uae_u32* jmpaddr, uae_u32 target) { blockinfo* tbi=get_blockinfo_addr((void*)(uintptr)target); Dif(!tbi) { jit_abort("Could not create jmpdep!"); } bi->dep[i].jmp_off=jmpaddr; bi->dep[i].source=bi; bi->dep[i].target=tbi; bi->dep[i].next=tbi->deplist; if (bi->dep[i].next) bi->dep[i].next->prev_p=&(bi->dep[i].next); bi->dep[i].prev_p=&(tbi->deplist); tbi->deplist=&(bi->dep[i]); } static inline void block_need_recompile(blockinfo * bi) { uae_u32 cl = cacheline(bi->pc_p); set_dhtu(bi, bi->direct_pen); bi->direct_handler = bi->direct_pen; bi->handler_to_use = (cpuop_func *)popall_execute_normal; bi->handler = (cpuop_func *)popall_execute_normal; if (bi == cache_tags[cl + 1].bi) cache_tags[cl].handler = (cpuop_func *)popall_execute_normal; bi->status = BI_NEED_RECOMP; } #if USE_MATCH static inline void mark_callers_recompile(blockinfo * bi) { dependency *x = bi->deplist; while (x) { dependency *next = x->next; /* This disappears when we mark for * recompilation and thus remove the * blocks from the lists */ if (x->jmp_off) { blockinfo *cbi = x->source; Dif(cbi->status == BI_INVALID) { jit_log("invalid block in dependency list"); // FIXME? // abort(); } if (cbi->status == BI_ACTIVE || cbi->status == BI_NEED_CHECK) { block_need_recompile(cbi); mark_callers_recompile(cbi); } else if (cbi->status == BI_COMPILING) { redo_current_block = 1; } else if (cbi->status == BI_NEED_RECOMP) { /* nothing */ } else { jit_log2("Status %d in mark_callers",cbi->status); // FIXME? } } x = next; } } #endif static inline blockinfo* get_blockinfo_addr_new(void* addr, int /* setstate */) { blockinfo* bi=get_blockinfo_addr(addr); int i; if (!bi) { for (i=0;ipc_p=(uae_u8*)addr; invalidate_block(bi); add_to_active(bi); add_to_cl_list(bi); } } } if (!bi) { jit_abort("Looking for blockinfo, can't find free one"); } return bi; } static void prepare_block(blockinfo* bi); /* Management of blockinfos. A blockinfo struct is allocated whenever a new block has to be compiled. If the list of free blockinfos is empty, we allocate a new pool of blockinfos and link the newly created blockinfos altogether into the list of free blockinfos. Otherwise, we simply pop a structure of the free list. Blockinfo are lazily deallocated, i.e. chained altogether in the list of free blockinfos whenvever a translation cache flush (hard or soft) request occurs. */ template< class T > class LazyBlockAllocator { enum { kPoolSize = 1 + (16384 - sizeof(T) - sizeof(void *)) / sizeof(T) }; struct Pool { T chunk[kPoolSize]; Pool * next; }; Pool * mPools; T * mChunks; public: LazyBlockAllocator() : mPools(0), mChunks(0) { } #ifdef UAE #else ~LazyBlockAllocator(); #endif T * acquire(); void release(T * const); }; #ifdef UAE /* uae_vm_release may do logging, which isn't safe to do when the application * is shutting down. Better to release memory manually with a function call * to a release_all method on shutdown, or even simpler, just let the OS * handle it (we're shutting down anyway). */ #else template< class T > LazyBlockAllocator::~LazyBlockAllocator() { Pool * currentPool = mPools; while (currentPool) { Pool * deadPool = currentPool; currentPool = currentPool->next; vm_release(deadPool, sizeof(Pool)); } } #endif template< class T > T * LazyBlockAllocator::acquire() { if (!mChunks) { // There is no chunk left, allocate a new pool and link the // chunks into the free list Pool * newPool = (Pool *)vm_acquire(sizeof(Pool), VM_MAP_DEFAULT | VM_MAP_32BIT); if (newPool == VM_MAP_FAILED) { jit_abort("Could not allocate block pool!"); } for (T * chunk = &newPool->chunk[0]; chunk < &newPool->chunk[kPoolSize]; chunk++) { chunk->next = mChunks; mChunks = chunk; } newPool->next = mPools; mPools = newPool; } T * chunk = mChunks; mChunks = chunk->next; return chunk; } template< class T > void LazyBlockAllocator::release(T * const chunk) { chunk->next = mChunks; mChunks = chunk; } template< class T > class HardBlockAllocator { public: T * acquire() { T * data = (T *)current_compile_p; current_compile_p += sizeof(T); return data; } void release(T * const ) { // Deallocated on invalidation } }; #if USE_SEPARATE_BIA static LazyBlockAllocator BlockInfoAllocator; static LazyBlockAllocator ChecksumInfoAllocator; #else static HardBlockAllocator BlockInfoAllocator; static HardBlockAllocator ChecksumInfoAllocator; #endif static inline checksum_info *alloc_checksum_info(void) { checksum_info *csi = ChecksumInfoAllocator.acquire(); csi->next = NULL; return csi; } static inline void free_checksum_info(checksum_info *csi) { csi->next = NULL; ChecksumInfoAllocator.release(csi); } static inline void free_checksum_info_chain(checksum_info *csi) { while (csi != NULL) { checksum_info *csi2 = csi->next; free_checksum_info(csi); csi = csi2; } } static inline blockinfo *alloc_blockinfo(void) { blockinfo *bi = BlockInfoAllocator.acquire(); #if USE_CHECKSUM_INFO bi->csi = NULL; #endif return bi; } static inline void free_blockinfo(blockinfo *bi) { #if USE_CHECKSUM_INFO free_checksum_info_chain(bi->csi); bi->csi = NULL; #endif BlockInfoAllocator.release(bi); } static inline void alloc_blockinfos(void) { int i; blockinfo* bi; for (i=0;i data_endpos || get_target_noopt() + codesize - data_writepos > DATA_BUFFER_MAXOFFSET) { // Start new buffer #if DEBUG if(data_writepos < data_endpos) data_wasted += data_endpos - data_writepos; #endif compemu_raw_branch(DATA_BUFFER_SIZE); data_writepos = get_target_noopt(); data_endpos = data_writepos + DATA_BUFFER_SIZE; set_target(get_target_noopt() + DATA_BUFFER_SIZE); } } static inline long data_word_offs(uae_u16 x) { data_check_end(4, 4); #ifdef WORDS_BIGENDIAN *((uae_u16*)data_writepos)=x; data_writepos += 2; *((uae_u16*)data_writepos)=0; data_writepos += 2; #else *((uae_u32*)data_writepos)=x; data_writepos += 4; #endif return (long)data_writepos - (long)get_target_noopt() - 12; } static inline long data_long(uae_u32 x, long codesize) { data_check_end(4, codesize); *((uae_u32*)data_writepos)=x; data_writepos += 4; return (long)data_writepos - 4; } static inline long data_long_offs(uae_u32 x) { data_check_end(4, 4); *((uae_u32*)data_writepos)=x; data_writepos += 4; return (long)data_writepos - (long)get_target_noopt() - 12; } static inline long get_data_offset(long t) { return t - (long)get_target_noopt() - 8; } static inline void reset_data_buffer(void) { data_writepos = 0; data_endpos = 0; } #endif /******************************************************************** * Getting the information about the target CPU * ********************************************************************/ #if defined(CPU_arm) #include "codegen_arm.cpp" #endif #if defined(CPU_i386) || defined(CPU_x86_64) #include "codegen_x86.c" #endif /******************************************************************** * Flags status handling. EMIT TIME! * ********************************************************************/ static void bt_l_ri_noclobber(RR4 r, IMM i); static void make_flags_live_internal(void) { if (live.flags_in_flags==VALID) return; Dif (live.flags_on_stack==TRASH) { jit_abort("Want flags, got something on stack, but it is TRASH"); } if (live.flags_on_stack==VALID) { int tmp; tmp=readreg_specific(FLAGTMP,4,FLAG_NREG2); raw_reg_to_flags(tmp); unlock2(tmp); live.flags_in_flags=VALID; return; } jit_abort("Huh? live.flags_in_flags=%d, live.flags_on_stack=%d, but need to make live", live.flags_in_flags,live.flags_on_stack); } static void flags_to_stack(void) { if (live.flags_on_stack==VALID) return; if (!live.flags_are_important) { live.flags_on_stack=VALID; return; } Dif (live.flags_in_flags!=VALID) jit_abort("flags_to_stack != VALID"); else { int tmp; tmp=writereg_specific(FLAGTMP,4,FLAG_NREG1); raw_flags_to_reg(tmp); unlock2(tmp); } live.flags_on_stack=VALID; } static inline void clobber_flags(void) { if (live.flags_in_flags==VALID && live.flags_on_stack!=VALID) flags_to_stack(); live.flags_in_flags=TRASH; } /* Prepare for leaving the compiled stuff */ static inline void flush_flags(void) { flags_to_stack(); return; } int touchcnt; /******************************************************************** * Partial register flushing for optimized calls * ********************************************************************/ struct regusage { uae_u16 rmask; uae_u16 wmask; }; #if 0 static inline void ru_set(uae_u16 *mask, int reg) { #if USE_OPTIMIZED_CALLS *mask |= 1 << reg; #else UNUSED(mask); UNUSED(reg); #endif } static inline bool ru_get(const uae_u16 *mask, int reg) { #if USE_OPTIMIZED_CALLS return (*mask & (1 << reg)); #else UNUSED(mask); UNUSED(reg); /* Default: instruction reads & write to register */ return true; #endif } static inline void ru_set_read(regusage *ru, int reg) { ru_set(&ru->rmask, reg); } static inline void ru_set_write(regusage *ru, int reg) { ru_set(&ru->wmask, reg); } static inline bool ru_read_p(const regusage *ru, int reg) { return ru_get(&ru->rmask, reg); } static inline bool ru_write_p(const regusage *ru, int reg) { return ru_get(&ru->wmask, reg); } static void ru_fill_ea(regusage *ru, int reg, amodes mode, wordsizes size, int write_mode) { switch (mode) { case Areg: reg += 8; /* fall through */ case Dreg: ru_set(write_mode ? &ru->wmask : &ru->rmask, reg); break; case Ad16: /* skip displacement */ m68k_pc_offset += 2; case Aind: case Aipi: case Apdi: ru_set_read(ru, reg+8); break; case Ad8r: ru_set_read(ru, reg+8); /* fall through */ case PC8r: { uae_u16 dp = comp_get_iword((m68k_pc_offset+=2)-2); reg = (dp >> 12) & 15; ru_set_read(ru, reg); if (dp & 0x100) m68k_pc_offset += (((dp & 0x30) >> 3) & 7) + ((dp & 3) * 2); break; } case PC16: case absw: case imm0: case imm1: m68k_pc_offset += 2; break; case absl: case imm2: m68k_pc_offset += 4; break; case immi: m68k_pc_offset += (size == sz_long) ? 4 : 2; break; } } /* TODO: split into a static initialization part and a dynamic one (instructions depending on extension words) */ static void ru_fill(regusage *ru, uae_u32 opcode) { m68k_pc_offset += 2; /* Default: no register is used or written to */ ru->rmask = 0; ru->wmask = 0; uae_u32 real_opcode = cft_map(opcode); struct instr *dp = &table68k[real_opcode]; bool rw_dest = true; bool handled = false; /* Handle some instructions specifically */ uae_u16 ext; switch (dp->mnemo) { case i_BFCHG: case i_BFCLR: case i_BFEXTS: case i_BFEXTU: case i_BFFFO: case i_BFINS: case i_BFSET: case i_BFTST: ext = comp_get_iword((m68k_pc_offset+=2)-2); if (ext & 0x800) ru_set_read(ru, (ext >> 6) & 7); if (ext & 0x020) ru_set_read(ru, ext & 7); ru_fill_ea(ru, dp->dreg, (amodes)dp->dmode, (wordsizes)dp->size, 1); if (dp->dmode == Dreg) ru_set_read(ru, dp->dreg); switch (dp->mnemo) { case i_BFEXTS: case i_BFEXTU: case i_BFFFO: ru_set_write(ru, (ext >> 12) & 7); break; case i_BFINS: ru_set_read(ru, (ext >> 12) & 7); /* fall through */ case i_BFCHG: case i_BFCLR: case i_BSET: if (dp->dmode == Dreg) ru_set_write(ru, dp->dreg); break; } handled = true; rw_dest = false; break; case i_BTST: rw_dest = false; break; case i_CAS: { ext = comp_get_iword((m68k_pc_offset+=2)-2); int Du = ext & 7; ru_set_read(ru, Du); int Dc = (ext >> 6) & 7; ru_set_read(ru, Dc); ru_set_write(ru, Dc); break; } case i_CAS2: { int Dc1, Dc2, Du1, Du2, Rn1, Rn2; ext = comp_get_iword((m68k_pc_offset+=2)-2); Rn1 = (ext >> 12) & 15; Du1 = (ext >> 6) & 7; Dc1 = ext & 7; ru_set_read(ru, Rn1); ru_set_read(ru, Du1); ru_set_read(ru, Dc1); ru_set_write(ru, Dc1); ext = comp_get_iword((m68k_pc_offset+=2)-2); Rn2 = (ext >> 12) & 15; Du2 = (ext >> 6) & 7; Dc2 = ext & 7; ru_set_read(ru, Rn2); ru_set_read(ru, Du2); ru_set_write(ru, Dc2); break; } case i_DIVL: case i_MULL: m68k_pc_offset += 2; break; case i_LEA: case i_MOVE: case i_MOVEA: case i_MOVE16: rw_dest = false; break; case i_PACK: case i_UNPK: rw_dest = false; m68k_pc_offset += 2; break; case i_TRAPcc: m68k_pc_offset += (dp->size == sz_long) ? 4 : 2; break; case i_RTR: /* do nothing, just for coverage debugging */ break; /* TODO: handle EXG instruction */ } /* Handle A-Traps better */ if ((real_opcode & 0xf000) == 0xa000) { handled = true; } /* Handle EmulOps better */ if ((real_opcode & 0xff00) == 0x7100) { handled = true; ru->rmask = 0xffff; ru->wmask = 0; } if (dp->suse && !handled) ru_fill_ea(ru, dp->sreg, (amodes)dp->smode, (wordsizes)dp->size, 0); if (dp->duse && !handled) ru_fill_ea(ru, dp->dreg, (amodes)dp->dmode, (wordsizes)dp->size, 1); if (rw_dest) ru->rmask |= ru->wmask; handled = handled || dp->suse || dp->duse; /* Mark all registers as used/written if the instruction may trap */ if (may_trap(opcode)) { handled = true; ru->rmask = 0xffff; ru->wmask = 0xffff; } if (!handled) { jit_abort("ru_fill: %04x = { %04x, %04x }", real_opcode, ru->rmask, ru->wmask); } } #endif /******************************************************************** * register allocation per block logging * ********************************************************************/ static uae_s8 vstate[VREGS]; static uae_s8 vwritten[VREGS]; static uae_s8 nstate[N_REGS]; #define L_UNKNOWN -127 #define L_UNAVAIL -1 #define L_NEEDED -2 #define L_UNNEEDED -3 #if USE_MATCH static inline void big_to_small_state(bigstate * /* b */, smallstate * s) { int i; for (i = 0; i < VREGS; i++) s->virt[i] = vstate[i]; for (i = 0; i < N_REGS; i++) s->nat[i] = nstate[i]; } static inline int callers_need_recompile(bigstate * /* b */, smallstate * s) { int i; int reverse = 0; for (i = 0; i < VREGS; i++) { if (vstate[i] != L_UNNEEDED && s->virt[i] == L_UNNEEDED) return 1; if (vstate[i] == L_UNNEEDED && s->virt[i] != L_UNNEEDED) reverse++; } for (i = 0; i < N_REGS; i++) { if (nstate[i] >= 0 && nstate[i] != s->nat[i]) return 1; if (nstate[i] < 0 && s->nat[i] >= 0) reverse++; } if (reverse >= 2 && USE_MATCH) return 1; /* In this case, it might be worth recompiling the * callers */ return 0; } #endif static inline void log_startblock(void) { int i; for (i = 0; i < VREGS; i++) { vstate[i] = L_UNKNOWN; vwritten[i] = 0; } for (i = 0; i < N_REGS; i++) nstate[i] = L_UNKNOWN; } /* Using an n-reg for a temp variable */ static inline void log_isused(int n) { if (nstate[n] == L_UNKNOWN) nstate[n] = L_UNAVAIL; } static inline void log_visused(int r) { if (vstate[r] == L_UNKNOWN) vstate[r] = L_NEEDED; } static inline void do_load_reg(int n, int r) { if (r == FLAGTMP) raw_load_flagreg(n); else if (r == FLAGX) raw_load_flagx(n); else compemu_raw_mov_l_rm(n, JITPTR live.state[r].mem); } #if 0 static inline void check_load_reg(int n, int r) { compemu_raw_mov_l_rm(n, (uintptr) live.state[r].mem); } #endif static inline void log_vwrite(int r) { vwritten[r] = 1; } /* Using an n-reg to hold a v-reg */ static inline void log_isreg(int n, int r) { if (nstate[n] == L_UNKNOWN && r < 16 && !vwritten[r] && USE_MATCH) nstate[n] = r; else { do_load_reg(n, r); if (nstate[n] == L_UNKNOWN) nstate[n] = L_UNAVAIL; } if (vstate[r] == L_UNKNOWN) vstate[r] = L_NEEDED; } static inline void log_clobberreg(int r) { if (vstate[r] == L_UNKNOWN) vstate[r] = L_UNNEEDED; } /* This ends all possibility of clever register allocation */ static inline void log_flush(void) { int i; for (i = 0; i < VREGS; i++) if (vstate[i] == L_UNKNOWN) vstate[i] = L_NEEDED; for (i = 0; i < N_REGS; i++) if (nstate[i] == L_UNKNOWN) nstate[i] = L_UNAVAIL; } static inline void log_dump(void) { int i; return; jit_log("----------------------"); for (i = 0; i < N_REGS; i++) { switch (nstate[i]) { case L_UNKNOWN: jit_log("Nat %d : UNKNOWN", i); break; case L_UNAVAIL: jit_log("Nat %d : UNAVAIL", i); break; default: jit_log("Nat %d : %d", i, nstate[i]); break; } } for (i = 0; i < VREGS; i++) { if (vstate[i] == L_UNNEEDED) { jit_log("Virt %d: UNNEEDED", i); } } } /******************************************************************** * register status handling. EMIT TIME! * ********************************************************************/ static inline void set_status(int r, int status) { if (status == ISCONST) log_clobberreg(r); live.state[r].status=status; } static inline int isinreg(int r) { return live.state[r].status==CLEAN || live.state[r].status==DIRTY; } static inline void adjust_nreg(int r, uae_u32 val) { if (!val) return; compemu_raw_lea_l_brr(r,r,val); } static void tomem(int r) { int rr=live.state[r].realreg; if (isinreg(r)) { if (live.state[r].val && live.nat[rr].nholds==1 && !live.nat[rr].locked) { jit_log2("RemovingA offset %x from reg %d (%d) at %p", live.state[r].val,r,rr,target); adjust_nreg(rr,live.state[r].val); live.state[r].val=0; live.state[r].dirtysize=4; set_status(r,DIRTY); } } if (live.state[r].status==DIRTY) { switch (live.state[r].dirtysize) { case 1: compemu_raw_mov_b_mr(JITPTR live.state[r].mem,rr); break; case 2: compemu_raw_mov_w_mr(JITPTR live.state[r].mem,rr); break; case 4: compemu_raw_mov_l_mr(JITPTR live.state[r].mem,rr); break; default: abort(); } log_vwrite(r); set_status(r,CLEAN); live.state[r].dirtysize=0; } } static inline int isconst(int r) { return live.state[r].status==ISCONST; } int is_const(int r) { return isconst(r); } static inline void writeback_const(int r) { if (!isconst(r)) return; Dif (live.state[r].needflush==NF_HANDLER) { jit_abort("Trying to write back constant NF_HANDLER!"); } compemu_raw_mov_l_mi(JITPTR live.state[r].mem,live.state[r].val); log_vwrite(r); live.state[r].val=0; set_status(r,INMEM); } static inline void tomem_c(int r) { if (isconst(r)) { writeback_const(r); } else tomem(r); } static void evict(int r) { int rr; if (!isinreg(r)) return; tomem(r); rr=live.state[r].realreg; Dif (live.nat[rr].locked && live.nat[rr].nholds==1) { jit_abort("register %d in nreg %d is locked!",r,live.state[r].realreg); } live.nat[rr].nholds--; if (live.nat[rr].nholds!=live.state[r].realind) { /* Was not last */ int topreg=live.nat[rr].holds[live.nat[rr].nholds]; int thisind=live.state[r].realind; live.nat[rr].holds[thisind]=topreg; live.state[topreg].realind=thisind; } live.state[r].realreg=-1; set_status(r,INMEM); } static inline void free_nreg(int r) { int i=live.nat[r].nholds; while (i) { int vr; --i; vr=live.nat[r].holds[i]; evict(vr); } Dif (live.nat[r].nholds!=0) { jit_abort("Failed to free nreg %d, nholds is %d",r,live.nat[r].nholds); } } /* Use with care! */ static inline void isclean(int r) { if (!isinreg(r)) return; live.state[r].validsize=4; live.state[r].dirtysize=0; live.state[r].val=0; set_status(r,CLEAN); } static inline void disassociate(int r) { isclean(r); evict(r); } /* XXFIXME: val may be 64bit address for PC_P */ static inline void set_const(int r, uae_u32 val) { disassociate(r); live.state[r].val=val; set_status(r,ISCONST); } static inline uae_u32 get_offset(int r) { return live.state[r].val; } static int alloc_reg_hinted(int r, int size, int willclobber, int hint) { int bestreg; uae_s32 when; int i; uae_s32 badness=0; /* to shut up gcc */ bestreg=-1; when=2000000000; /* XXX use a regalloc_order table? */ for (i=0;i0) { free_nreg(bestreg); } if (isinreg(r)) { int rr=live.state[r].realreg; /* This will happen if we read a partially dirty register at a bigger size */ Dif (willclobber || live.state[r].validsize>=size) jit_abort("willclobber || live.state[r].validsize>=size"); Dif (live.nat[rr].nholds!=1) jit_abort("live.nat[rr].nholds!=1"); if (size==4 && live.state[r].validsize==2) { log_isused(bestreg); log_visused(r); compemu_raw_mov_l_rm(bestreg, JITPTR live.state[r].mem); compemu_raw_bswap_32(bestreg); compemu_raw_zero_extend_16_rr(rr,rr); compemu_raw_zero_extend_16_rr(bestreg,bestreg); compemu_raw_bswap_32(bestreg); compemu_raw_lea_l_rr_indexed(rr, rr, bestreg, 1); live.state[r].validsize=4; live.nat[rr].touched=touchcnt++; return rr; } if (live.state[r].validsize==1) { /* Nothing yet */ } evict(r); } if (!willclobber) { if (live.state[r].status!=UNDEF) { if (isconst(r)) { compemu_raw_mov_l_ri(bestreg,live.state[r].val); live.state[r].val=0; live.state[r].dirtysize=4; set_status(r,DIRTY); log_isused(bestreg); } else { log_isreg(bestreg, r); /* This will also load it! */ live.state[r].dirtysize=0; set_status(r,CLEAN); } } else { live.state[r].val=0; live.state[r].dirtysize=0; set_status(r,CLEAN); log_isused(bestreg); } live.state[r].validsize=4; } else { /* this is the easiest way, but not optimal. FIXME! */ /* Now it's trickier, but hopefully still OK */ if (!isconst(r) || size==4) { live.state[r].validsize=size; live.state[r].dirtysize=size; live.state[r].val=0; set_status(r,DIRTY); if (size == 4) { log_clobberreg(r); log_isused(bestreg); } else { log_visused(r); log_isused(bestreg); } } else { if (live.state[r].status!=UNDEF) compemu_raw_mov_l_ri(bestreg,live.state[r].val); live.state[r].val=0; live.state[r].validsize=4; live.state[r].dirtysize=4; set_status(r,DIRTY); log_isused(bestreg); } } live.state[r].realreg=bestreg; live.state[r].realind=live.nat[bestreg].nholds; live.nat[bestreg].touched=touchcnt++; live.nat[bestreg].holds[live.nat[bestreg].nholds]=r; live.nat[bestreg].nholds++; return bestreg; } /* static int alloc_reg(int r, int size, int willclobber) { return alloc_reg_hinted(r,size,willclobber,-1); } */ static void unlock2(int r) { Dif (!live.nat[r].locked) jit_abort("unlock2 %d not locked", r); live.nat[r].locked--; } static void setlock(int r) { live.nat[r].locked++; } static void mov_nregs(int d, int s) { int nd=live.nat[d].nholds; int i; if (s==d) return; if (nd>0) free_nreg(d); log_isused(d); compemu_raw_mov_l_rr(d,s); for (i=0;i=size) { n=live.state[r].realreg; switch(size) { case 1: if (live.nat[n].canbyte || spec>=0) { answer=n; } break; case 2: if (live.nat[n].canword || spec>=0) { answer=n; } break; case 4: answer=n; break; default: abort(); } if (answer<0) evict(r); } /* either the value was in memory to start with, or it was evicted and is in memory now */ if (answer<0) { answer=alloc_reg_hinted(r,spec>=0?4:size,0,spec); } if (spec>=0 && spec!=answer) { /* Too bad */ mov_nregs(spec,answer); answer=spec; } live.nat[answer].locked++; live.nat[answer].touched=touchcnt++; return answer; } static int readreg(int r, int size) { return readreg_general(r,size,-1,0); } static int readreg_specific(int r, int size, int spec) { return readreg_general(r,size,spec,0); } static int readreg_offset(int r, int size) { return readreg_general(r,size,-1,1); } /* writereg_general(r, size, spec) * * INPUT * - r : mid-layer register * - size : requested size (1/2/4) * - spec : -1 if find or make a register free, otherwise specifies * the physical register to use in any case * * OUTPUT * - hard (physical, x86 here) register allocated to virtual register r */ static inline int writereg_general(int r, int size, int spec) { int n; int answer=-1; record_register(r); if (size<4) { remove_offset(r,spec); } make_exclusive(r,size,spec); if (isinreg(r)) { int nvsize=size>live.state[r].validsize?size:live.state[r].validsize; int ndsize=size>live.state[r].dirtysize?size:live.state[r].dirtysize; n=live.state[r].realreg; Dif (live.nat[n].nholds!=1) jit_abort("live.nat[%d].nholds!=1", n); switch(size) { case 1: if (live.nat[n].canbyte || spec>=0) { live.state[r].dirtysize=ndsize; live.state[r].validsize=nvsize; answer=n; } break; case 2: if (live.nat[n].canword || spec>=0) { live.state[r].dirtysize=ndsize; live.state[r].validsize=nvsize; answer=n; } break; case 4: live.state[r].dirtysize=ndsize; live.state[r].validsize=nvsize; answer=n; break; default: abort(); } if (answer<0) evict(r); } /* either the value was in memory to start with, or it was evicted and is in memory now */ if (answer<0) { answer=alloc_reg_hinted(r,size,1,spec); } if (spec>=0 && spec!=answer) { mov_nregs(spec,answer); answer=spec; } if (live.state[r].status==UNDEF) live.state[r].validsize=4; live.state[r].dirtysize=size>live.state[r].dirtysize?size:live.state[r].dirtysize; live.state[r].validsize=size>live.state[r].validsize?size:live.state[r].validsize; live.nat[answer].locked++; live.nat[answer].touched=touchcnt++; if (size==4) { live.state[r].val=0; } else { Dif (live.state[r].val) { jit_abort("Problem with val"); } } set_status(r,DIRTY); return answer; } static int writereg(int r, int size) { return writereg_general(r,size,-1); } static int writereg_specific(int r, int size, int spec) { return writereg_general(r,size,spec); } static inline int rmw_general(int r, int wsize, int rsize, int spec) { int n; int answer=-1; record_register(r); if (live.state[r].status==UNDEF) { jit_log("WARNING: Unexpected read of undefined register %d",r); } remove_offset(r,spec); make_exclusive(r,0,spec); Dif (wsize=rsize) { n=live.state[r].realreg; Dif (live.nat[n].nholds!=1) jit_abort("live.nat[%d].nholds!=1", n); switch(rsize) { case 1: if (live.nat[n].canbyte || spec>=0) { answer=n; } break; case 2: if (live.nat[n].canword || spec>=0) { answer=n; } break; case 4: answer=n; break; default: abort(); } if (answer<0) evict(r); } /* either the value was in memory to start with, or it was evicted and is in memory now */ if (answer<0) { answer=alloc_reg_hinted(r,spec>=0?4:rsize,0,spec); } if (spec>=0 && spec!=answer) { /* Too bad */ mov_nregs(spec,answer); answer=spec; } if (wsize>live.state[r].dirtysize) live.state[r].dirtysize=wsize; if (wsize>live.state[r].validsize) live.state[r].validsize=wsize; set_status(r,DIRTY); live.nat[answer].locked++; live.nat[answer].touched=touchcnt++; Dif (live.state[r].val) { jit_abort("Problem with val(rmw)"); } return answer; } static int rmw(int r, int wsize, int rsize) { return rmw_general(r,wsize,rsize,-1); } static int rmw_specific(int r, int wsize, int rsize, int spec) { return rmw_general(r,wsize,rsize,spec); } /* needed for restoring the carry flag on non-P6 cores */ static void bt_l_ri_noclobber(RR4 r, IMM i) { int size=4; if (i<16) size=2; r=readreg(r,size); compemu_raw_bt_l_ri(r,i); unlock2(r); } /******************************************************************** * FPU register status handling. EMIT TIME! * ********************************************************************/ static void f_tomem(int r) { if (live.fate[r].status==DIRTY) { if (use_long_double) { raw_fmov_ext_mr((uintptr)live.fate[r].mem, live.fate[r].realreg); } else { raw_fmov_mr((uintptr)live.fate[r].mem, live.fate[r].realreg); } live.fate[r].status=CLEAN; } } static void f_tomem_drop(int r) { if (live.fate[r].status==DIRTY) { if (use_long_double) { raw_fmov_ext_mr_drop((uintptr)live.fate[r].mem, live.fate[r].realreg); } else { raw_fmov_mr_drop((uintptr)live.fate[r].mem,live.fate[r].realreg); } live.fate[r].status=INMEM; } } static inline int f_isinreg(int r) { return live.fate[r].status==CLEAN || live.fate[r].status==DIRTY; } static void f_evict(int r) { int rr; if (!f_isinreg(r)) return; rr=live.fate[r].realreg; if (live.fat[rr].nholds==1) f_tomem_drop(r); else f_tomem(r); Dif (live.fat[rr].locked && live.fat[rr].nholds==1) { jit_abort("FPU register %d in nreg %d is locked!",r,live.fate[r].realreg); } live.fat[rr].nholds--; if (live.fat[rr].nholds!=live.fate[r].realind) { /* Was not last */ int topreg=live.fat[rr].holds[live.fat[rr].nholds]; int thisind=live.fate[r].realind; live.fat[rr].holds[thisind]=topreg; live.fate[topreg].realind=thisind; } live.fate[r].status=INMEM; live.fate[r].realreg=-1; } static inline void f_free_nreg(int r) { int i=live.fat[r].nholds; while (i) { int vr; --i; vr=live.fat[r].holds[i]; f_evict(vr); } Dif (live.fat[r].nholds!=0) { jit_abort("Failed to free nreg %d, nholds is %d",r,live.fat[r].nholds); } } /* Use with care! */ static inline void f_isclean(int r) { if (!f_isinreg(r)) return; live.fate[r].status=CLEAN; } static inline void f_disassociate(int r) { f_isclean(r); f_evict(r); } static int f_alloc_reg(int r, int willclobber) { int bestreg; uae_s32 when; int i; uae_s32 badness; bestreg=-1; when=2000000000; for (i=N_FREGS;i--;) { badness=live.fat[i].touched; if (live.fat[i].nholds==0) badness=0; if (!live.fat[i].locked && badness0) { f_free_nreg(bestreg); } if (f_isinreg(r)) { f_evict(r); } if (!willclobber) { if (live.fate[r].status!=UNDEF) { if (use_long_double) { raw_fmov_ext_rm(bestreg, (uintptr)live.fate[r].mem); } else { raw_fmov_rm(bestreg,(uintptr)live.fate[r].mem); } } live.fate[r].status=CLEAN; } else { live.fate[r].status=DIRTY; } live.fate[r].realreg=bestreg; live.fate[r].realind=live.fat[bestreg].nholds; live.fat[bestreg].touched=touchcnt++; live.fat[bestreg].holds[live.fat[bestreg].nholds]=r; live.fat[bestreg].nholds++; return bestreg; } static void f_unlock(int r) { Dif (!live.fat[r].locked) jit_abort ("unlock %d", r); live.fat[r].locked--; } static void f_setlock(int r) { live.fat[r].locked++; } static inline int f_readreg(int r) { int n; int answer=-1; if (f_isinreg(r)) { n=live.fate[r].realreg; answer=n; } /* either the value was in memory to start with, or it was evicted and is in memory now */ if (answer<0) answer=f_alloc_reg(r,0); live.fat[answer].locked++; live.fat[answer].touched=touchcnt++; return answer; } static inline void f_make_exclusive(int r, int clobber) { freg_status oldstate; int rr=live.fate[r].realreg; int nr; int nind; int ndirt=0; int i; if (!f_isinreg(r)) return; if (live.fat[rr].nholds==1) return; for (i=0;i= uae_p32(kickmem_bank.baseaddr) && addr < uae_p32(kickmem_bank.baseaddr + 8 * 65536)); #else return ((addr >= (uintptr)ROMBaseHost) && (addr < (uintptr)ROMBaseHost + ROMSize)); #endif } #if defined(UAE) || defined(FLIGHT_RECORDER) static void flush_all(void) { int i; log_flush(); for (i=0;i0) free_nreg(i); for (i=0;i0) f_free_nreg(i); live.flags_in_flags=TRASH; /* Note: We assume we already rescued the flags at the very start of the call_r functions! */ } #endif #if defined(CPU_arm) #include "compemu_midfunc_arm.cpp" #if defined(USE_JIT2) #include "compemu_midfunc_arm2.cpp" #endif #endif #if defined(CPU_i386) || defined(CPU_x86_64) #include "compemu_midfunc_x86.c" #endif /******************************************************************** * Support functions exposed to gencomp. CREATE time * ********************************************************************/ void set_zero(int r, int tmp) { if (setzflg_uses_bsf) bsf_l_rr(r,r); else simulate_bsf(tmp,r); } int kill_rodent(int r) { return KILLTHERAT && have_rat_stall && (live.state[r].status==INMEM || live.state[r].status==CLEAN || live.state[r].status==ISCONST || live.state[r].dirtysize==4); } uae_u32 get_const(int r) { Dif (!isconst(r)) { jit_abort("Register %d should be constant, but isn't",r); } return live.state[r].val; } void sync_m68k_pc(void) { if (m68k_pc_offset) { add_l_ri(PC_P,m68k_pc_offset); comp_pc_p+=m68k_pc_offset; m68k_pc_offset=0; } } /* for building exception frames */ void compemu_exc_make_frame(int format, int sr, int ret, int nr, int tmp) { lea_l_brr(SP_REG, SP_REG, -2); mov_l_ri(tmp, (format << 12) + (nr * 4)); /* format | vector */ writeword(SP_REG, tmp, tmp); lea_l_brr(SP_REG, SP_REG, -4); writelong(SP_REG, ret, tmp); lea_l_brr(SP_REG, SP_REG, -2); writeword_clobber(SP_REG, sr, tmp); remove_offset(SP_REG, -1); if (isinreg(SP_REG)) evict(SP_REG); else flush_reg(SP_REG); } void compemu_make_sr(int sr, int tmp) { flush_flags(); /* low level */ flush_reg(FLAGX); #if defined (OPTIMIZED_FLAGS) || defined(UAE) /* * x86 EFLAGS: (!SAHF_SETO_PROFITABLE) * FEDCBA98 76543210 * ----V--- NZ-----C * * <--AH--> <--AL--> (SAHF_SETO_PROFITABLE) * FEDCBA98 76543210 * NZxxxxxC xxxxxxxV * * arm RFLAGS: * FEDCBA98 76543210 FEDCBA98 76543210 * NZCV---- -------- -------- -------- * * -> m68k SR: * --S--III ---XNZVC * * Master-Bit and traceflags are ignored here, * since they are not emulated in JIT code */ mov_l_rm(sr, uae_p32(live.state[FLAGTMP].mem)); mov_l_ri(tmp, FLAGVAL_N|FLAGVAL_Z|FLAGVAL_V|FLAGVAL_C); and_l(sr, tmp); mov_l_rr(tmp, sr); #if (defined(CPU_i386) && defined(X86_ASSEMBLY)) || (defined(CPU_x86_64) && defined(X86_64_ASSEMBLY)) #ifndef SAHF_SETO_PROFITABLE ror_b_ri(sr, FLAGBIT_N - 3); /* move NZ into position; C->4 */ shrl_w_ri(tmp, FLAGBIT_V - 1); /* move V into position in tmp */ or_l(sr, tmp); /* or V flag to SR */ mov_l_rr(tmp, sr); shrl_b_ri(tmp, (8 - (FLAGBIT_N - 3)) - FLAGBIT_C); /* move C into position in tmp */ or_l(sr, tmp); /* or C flag to SR */ #else ror_w_ri(sr, FLAGBIT_N - 3); /* move NZ in position; V->4, C->12 */ shrl_w_ri(tmp, (16 - (FLAGBIT_N - 3)) - FLAGBIT_V - 1); /* move V into position in tmp; C->9 */ or_l(sr, tmp); /* or V flag to SR */ shrl_w_ri(tmp, FLAGBIT_C + FLAGBIT_V - 1); /* move C into position in tmp */ or_l(sr, tmp); /* or C flag to SR */ #endif mov_l_ri(tmp, 0x0f); and_l(sr, tmp); mov_b_rm(tmp, uae_p32(®flags.x)); and_l_ri(tmp, FLAGVAL_X); shll_l_ri(tmp, 4); or_l(sr, tmp); #elif defined(CPU_arm) && defined(ARM_ASSEMBLY) shrl_l_ri(sr, FLAGBIT_N - 3); /* move NZ into position */ ror_l_ri(tmp, FLAGBIT_C - 1); /* move C into position in tmp; V->31 */ and_l_ri(sr, 0xc); or_l(sr, tmp); /* or C flag to SR */ shrl_l_ri(tmp, 31); /* move V into position in tmp */ or_l(sr, tmp); /* or V flag to SR */ mov_b_rm(tmp, uae_p32(®flags.x)); and_l_ri(tmp, FLAGVAL_X); shrl_l_ri(tmp, FLAGBIT_X - 4); or_l(sr, tmp); #else #error "unknown CPU" #endif #else xor_l(sr, sr); xor_l(tmp, tmp); mov_b_rm(tmp, uae_p32(®s.c)); shll_l_ri(tmp, 0); or_l(sr, tmp); mov_b_rm(tmp, uae_p32(®s.v)); shll_l_ri(tmp, 1); or_l(sr, tmp); mov_b_rm(tmp, uae_p32(®s.z)); shll_l_ri(tmp, 2); or_l(sr, tmp); mov_b_rm(tmp, uae_p32(®s.n)); shll_l_ri(tmp, 3); or_l(sr, tmp); #endif /* OPTIMIZED_FLAGS */ mov_b_rm(tmp, uae_p32(®s.s)); shll_l_ri(tmp, 13); or_l(sr, tmp); mov_l_rm(tmp, uae_p32(®s.intmask)); shll_l_ri(tmp, 8); or_l(sr, tmp); and_l_ri(sr, 0x271f); mov_w_mr(uae_p32(®s.sr), sr); } void compemu_enter_super(int sr) { #if 0 fprintf(stderr, "enter_super: isinreg=%d rr=%d nholds=%d\n", isinreg(SP_REG), live.state[SP_REG].realreg, isinreg(SP_REG) ? live.nat[live.state[SP_REG].realreg].nholds : -1); #endif remove_offset(SP_REG, -1); if (isinreg(SP_REG)) evict(SP_REG); else flush_reg(SP_REG); /* * equivalent to: * if (!regs.s) * { * regs.usp = m68k_areg(regs, 7); * m68k_areg(regs, 7) = regs.isp; * regs.s = 1; * mmu_set_super(1); * } */ test_l_ri(sr, 0x2000); #if defined(CPU_i386) || defined(CPU_x86_64) compemu_raw_jnz_b_oponly(); uae_u8 *branchadd = get_target(); skip_byte(); #elif defined(CPU_arm) compemu_raw_jnz_b_oponly(); uae_u8 *branchadd = get_target(); skip_byte(); #endif mov_l_mr(JITPTR ®s.usp, SP_REG); mov_l_rm(SP_REG, uae_p32(®s.isp)); mov_b_mi(uae_p32(®s.s), 1); #if defined(CPU_i386) || defined(CPU_x86_64) *branchadd = JITPTR get_target() - (JITPTR branchadd + 1); #elif defined(CPU_arm) *((uae_u32 *)branchadd - 3) = get_target() - (branchadd + 1); #endif } /******************************************************************** * Scratch registers management * ********************************************************************/ struct scratch_t { uae_u32 regs[VREGS]; fpu_register fregs[VFREGS]; }; static scratch_t scratch; /******************************************************************** * Support functions exposed to newcpu * ********************************************************************/ static inline const char *str_on_off(bool b) { return b ? "on" : "off"; } void compiler_init(void) { static bool initialized = false; if (initialized) return; flush_icache = flush_icache_none; #ifdef UAE flush_icache = lazy_flush ? flush_icache_lazy : flush_icache_hard; #else jit_log(" : enable runtime disassemblers : %s", JITDebug ? "yes" : "no"); jit_log(" : compile FPU instructions : %s", !avoid_fpu ? "yes" : "no"); // Get size of the translation cache (in KB) cache_size = bx_options.jit.jitcachesize; jit_log(" : requested translation cache size : %d KB", cache_size); setzflg_uses_bsf = target_check_bsf(); jit_log(" : target processor has CMOV instructions : %s", have_cmov ? "yes" : "no"); jit_log(" : target processor can suffer from partial register stalls : %s", have_rat_stall ? "yes" : "no"); jit_log(" : alignment for loops, jumps are %d, %d", align_loops, align_jumps); #if defined(CPU_i386) || defined(CPU_x86_64) jit_log(" : target processor has SSE2 instructions : %s", cpuinfo.x86_has_xmm2 ? "yes" : "no"); jit_log(" : cache linesize is %lu", (unsigned long)cpuinfo.x86_clflush_size); #endif // Translation cache flush mechanism lazy_flush = (bx_options.jit.jitlazyflush == 0) ? false : true; jit_log(" : lazy translation cache invalidation : %s", str_on_off(lazy_flush)); flush_icache = lazy_flush ? flush_icache_lazy : flush_icache_hard; // Compiler features jit_log(" : register aliasing : %s", str_on_off(1)); jit_log(" : FP register aliasing : %s", str_on_off(USE_F_ALIAS)); jit_log(" : lazy constant offsetting : %s", str_on_off(USE_OFFSET)); #if USE_INLINING follow_const_jumps = bx_options.jit.jitinline; #endif jit_log(" : block inlining : %s", str_on_off(follow_const_jumps)); jit_log(" : separate blockinfo allocation : %s", str_on_off(USE_SEPARATE_BIA)); // Build compiler tables init_table68k(); build_comp(); #endif initialized = true; #ifdef PROFILE_UNTRANSLATED_INSNS jit_log(" : gather statistics on untranslated insns count"); #endif #ifdef PROFILE_COMPILE_TIME jit_log(" : gather statistics on translation time"); emul_start_time = clock(); #endif } void compiler_exit(void) { #ifdef PROFILE_COMPILE_TIME emul_end_time = clock(); #endif #ifdef UAE #else #if DEBUG #if defined(USE_DATA_BUFFER) jit_log("data_wasted = %ld bytes", data_wasted); #endif #endif // Deallocate translation cache if (compiled_code) { vm_release(compiled_code, cache_size * 1024); compiled_code = 0; } // Deallocate popallspace if (popallspace) { vm_release(popallspace, POPALLSPACE_SIZE); popallspace = 0; } #endif #ifdef PROFILE_COMPILE_TIME jit_log("### Compile Block statistics"); jit_log("Number of calls to compile_block : %d", compile_count); uae_u32 emul_time = emul_end_time - emul_start_time; jit_log("Total emulation time : %.1f sec", double(emul_time)/double(CLOCKS_PER_SEC)); jit_log("Total compilation time : %.1f sec (%.1f%%)", double(compile_time)/double(CLOCKS_PER_SEC), 100.0*double(compile_time)/double(emul_time)); #endif #ifdef PROFILE_UNTRANSLATED_INSNS uae_u64 untranslated_count = 0; for (int i = 0; i < 65536; i++) { opcode_nums[i] = i; untranslated_count += raw_cputbl_count[i]; } bug("Sorting out untranslated instructions count, total %llu...\n", untranslated_count); qsort(opcode_nums, 65536, sizeof(uae_u16), untranslated_compfn); jit_log("Rank Opc Count Name\n"); for (int i = 0; i < untranslated_top_ten; i++) { uae_u32 count = raw_cputbl_count[opcode_nums[i]]; struct instr *dp; struct mnemolookup *lookup; if (!count) break; dp = table68k + opcode_nums[i]; for (lookup = lookuptab; lookup->mnemo != (instrmnem)dp->mnemo; lookup++) ; bug(_T("%03d: %04x %10u %s\n"), i, opcode_nums[i], count, lookup->name); } #endif #ifdef RECORD_REGISTER_USAGE int reg_count_ids[16]; uint64 tot_reg_count = 0; for (int i = 0; i < 16; i++) { reg_count_ids[i] = i; tot_reg_count += reg_count[i]; } qsort(reg_count_ids, 16, sizeof(int), reg_count_compare); uint64 cum_reg_count = 0; for (int i = 0; i < 16; i++) { int r = reg_count_ids[i]; cum_reg_count += reg_count[r]; jit_log("%c%d : %16ld %2.1f%% [%2.1f]", r < 8 ? 'D' : 'A', r % 8, reg_count[r], 100.0*double(reg_count[r])/double(tot_reg_count), 100.0*double(cum_reg_count)/double(tot_reg_count)); } #endif exit_table68k(); } static void init_comp(void) { int i; uae_s8* cb=can_byte; uae_s8* cw=can_word; uae_s8* au=always_used; #ifdef RECORD_REGISTER_USAGE for (i=0;i<16;i++) reg_count_local[i] = 0; #endif for (i=0;idirect_handler_to_use; } /* This version assumes that it is writing *real* memory, and *will* fail * if that assumption is wrong! No branches, no second chances, just * straight go-for-it attitude */ static void writemem_real(int address, int source, int size, int tmp, int clobber) { int f=tmp; #ifdef NATMEM_OFFSET if (canbang) { /* Woohoo! go directly at the memory! */ if (clobber) f=source; switch(size) { case 1: mov_b_bRr(address,source,MEMBaseDiff); break; case 2: mov_w_rr(f,source); mid_bswap_16(f); mov_w_bRr(address,f,MEMBaseDiff); break; case 4: mov_l_rr(f,source); mid_bswap_32(f); mov_l_bRr(address,f,MEMBaseDiff); break; } forget_about(tmp); forget_about(f); return; } #endif #ifdef UAE mov_l_rr(f,address); shrl_l_ri(f,16); /* The index into the baseaddr table */ mov_l_rm_indexed(f,uae_p32(baseaddr),f,SIZEOF_VOID_P); /* FIXME: is SIZEOF_VOID_P correct? */ if (address==source) { /* IBrowse does this! */ if (size > 1) { add_l(f,address); /* f now holds the final address */ switch (size) { case 2: mid_bswap_16(source); mov_w_Rr(f,source,0); mid_bswap_16(source); return; case 4: mid_bswap_32(source); mov_l_Rr(f,source,0); mid_bswap_32(source); return; } } } switch (size) { /* f now holds the offset */ case 1: mov_b_mrr_indexed(address,f,1,source); break; case 2: mid_bswap_16(source); mov_w_mrr_indexed(address,f,1,source); mid_bswap_16(source); break; /* base, index, source */ case 4: mid_bswap_32(source); mov_l_mrr_indexed(address,f,1,source); mid_bswap_32(source); break; } #endif } #ifdef UAE static inline void writemem(int address, int source, int offset, int size, int tmp) { int f=tmp; mov_l_rr(f,address); shrl_l_ri(f,16); /* The index into the mem bank table */ mov_l_rm_indexed(f,uae_p32(mem_banks),f,SIZEOF_VOID_P); /* FIXME: is SIZEOF_VOID_P correct? */ /* Now f holds a pointer to the actual membank */ mov_l_rR(f,f,offset); /* Now f holds the address of the b/w/lput function */ call_r_02(f,address,source,4,size); forget_about(tmp); } #endif void writebyte(int address, int source, int tmp) { #ifdef UAE if ((special_mem & S_WRITE) || distrust_byte()) writemem_special(address, source, 5 * SIZEOF_VOID_P, 1, tmp); else #endif writemem_real(address,source,1,tmp,0); } static inline void writeword_general(int address, int source, int tmp, int clobber) { #ifdef UAE if ((special_mem & S_WRITE) || distrust_word()) writemem_special(address, source, 4 * SIZEOF_VOID_P, 2, tmp); else #endif writemem_real(address,source,2,tmp,clobber); } void writeword_clobber(int address, int source, int tmp) { writeword_general(address,source,tmp,1); } void writeword(int address, int source, int tmp) { writeword_general(address,source,tmp,0); } static inline void writelong_general(int address, int source, int tmp, int clobber) { #ifdef UAE if ((special_mem & S_WRITE) || distrust_long()) writemem_special(address, source, 3 * SIZEOF_VOID_P, 4, tmp); else #endif writemem_real(address,source,4,tmp,clobber); } void writelong_clobber(int address, int source, int tmp) { writelong_general(address,source,tmp,1); } void writelong(int address, int source, int tmp) { writelong_general(address,source,tmp,0); } /* This version assumes that it is reading *real* memory, and *will* fail * if that assumption is wrong! No branches, no second chances, just * straight go-for-it attitude */ static void readmem_real(int address, int dest, int size, int tmp) { int f=tmp; if (size==4 && address!=dest) f=dest; #ifdef NATMEM_OFFSET if (canbang) { /* Woohoo! go directly at the memory! */ switch(size) { case 1: mov_b_brR(dest,address,MEMBaseDiff); break; case 2: mov_w_brR(dest,address,MEMBaseDiff); mid_bswap_16(dest); break; case 4: mov_l_brR(dest,address,MEMBaseDiff); mid_bswap_32(dest); break; } forget_about(tmp); (void) f; return; } #endif #ifdef UAE mov_l_rr(f,address); shrl_l_ri(f,16); /* The index into the baseaddr table */ mov_l_rm_indexed(f,uae_p32(baseaddr),f,SIZEOF_VOID_P); /* FIXME: is SIZEOF_VOID_P correct? */ /* f now holds the offset */ switch(size) { case 1: mov_b_rrm_indexed(dest,address,f,1); break; case 2: mov_w_rrm_indexed(dest,address,f,1); mid_bswap_16(dest); break; case 4: mov_l_rrm_indexed(dest,address,f,1); mid_bswap_32(dest); break; } forget_about(tmp); #endif } #ifdef UAE static inline void readmem(int address, int dest, int offset, int size, int tmp) { int f=tmp; mov_l_rr(f,address); shrl_l_ri(f,16); /* The index into the mem bank table */ mov_l_rm_indexed(f,uae_p32(mem_banks),f,SIZEOF_VOID_P); /* FIXME: is SIZEOF_VOID_P correct? */ /* Now f holds a pointer to the actual membank */ mov_l_rR(f,f,offset); /* Now f holds the address of the b/w/lget function */ call_r_11(dest,f,address,size,4); forget_about(tmp); } #endif void readbyte(int address, int dest, int tmp) { #ifdef UAE if ((special_mem & S_READ) || distrust_byte()) readmem_special(address, dest, 2 * SIZEOF_VOID_P, 1, tmp); else #endif readmem_real(address,dest,1,tmp); } void readword(int address, int dest, int tmp) { #ifdef UAE if ((special_mem & S_READ) || distrust_word()) readmem_special(address, dest, 1 * SIZEOF_VOID_P, 2, tmp); else #endif readmem_real(address,dest,2,tmp); } void readlong(int address, int dest, int tmp) { #ifdef UAE if ((special_mem & S_READ) || distrust_long()) readmem_special(address, dest, 0 * SIZEOF_VOID_P, 4, tmp); else #endif readmem_real(address,dest,4,tmp); } void get_n_addr(int address, int dest, int tmp) { #ifdef UAE if (special_mem || distrust_addr()) { /* This one might appear a bit odd... */ readmem(address, dest, 6 * SIZEOF_VOID_P, 4, tmp); return; } #endif // a is the register containing the virtual address // after the offset had been fetched int a=tmp; // f is the register that will contain the offset int f=tmp; // a == f == tmp if (address == dest) if (address!=dest) { a=address; f=dest; } #ifdef NATMEM_OFFSET if (canbang) { #if FIXED_ADDRESSING lea_l_brr(dest,address,MEMBaseDiff); #else # error "Only fixed addressing mode supported" #endif forget_about(tmp); (void) f; (void) a; return; } #endif #ifdef UAE mov_l_rr(f,address); mov_l_rr(dest,address); // gb-- nop if dest==address shrl_l_ri(f,16); mov_l_rm_indexed(f,uae_p32(baseaddr),f,SIZEOF_VOID_P); /* FIXME: is SIZEOF_VOID_P correct? */ add_l(dest,f); forget_about(tmp); #endif } void get_n_addr_jmp(int address, int dest, int tmp) { #ifdef WINUAE_ARANYM /* For this, we need to get the same address as the rest of UAE would --- otherwise we end up translating everything twice */ get_n_addr(address,dest,tmp); #else int f=tmp; if (address!=dest) f=dest; mov_l_rr(f,address); shrl_l_ri(f,16); /* The index into the baseaddr bank table */ mov_l_rm_indexed(dest,uae_p32(baseaddr),f,SIZEOF_VOID_P); /* FIXME: is SIZEOF_VOID_P correct? */ add_l(dest,address); and_l_ri (dest, ~1); forget_about(tmp); #endif } /* base is a register, but dp is an actual value. target is a register, as is tmp */ void calc_disp_ea_020(int base, uae_u32 dp, int target, int tmp) { int reg = (dp >> 12) & 15; int regd_shift=(dp >> 9) & 3; if (dp & 0x100) { int ignorebase=(dp&0x80); int ignorereg=(dp&0x40); int addbase=0; int outer=0; if ((dp & 0x30) == 0x20) addbase = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset+=2)-2); if ((dp & 0x30) == 0x30) addbase = comp_get_ilong((m68k_pc_offset+=4)-4); if ((dp & 0x3) == 0x2) outer = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset+=2)-2); if ((dp & 0x3) == 0x3) outer = comp_get_ilong((m68k_pc_offset+=4)-4); if ((dp & 0x4) == 0) { /* add regd *before* the get_long */ if (!ignorereg) { if ((dp & 0x800) == 0) sign_extend_16_rr(target,reg); else mov_l_rr(target,reg); shll_l_ri(target,regd_shift); } else mov_l_ri(target,0); /* target is now regd */ if (!ignorebase) add_l(target,base); add_l_ri(target,addbase); if (dp&0x03) readlong(target,target,tmp); } else { /* do the getlong first, then add regd */ if (!ignorebase) { mov_l_rr(target,base); add_l_ri(target,addbase); } else mov_l_ri(target,addbase); if (dp&0x03) readlong(target,target,tmp); if (!ignorereg) { if ((dp & 0x800) == 0) sign_extend_16_rr(tmp,reg); else mov_l_rr(tmp,reg); shll_l_ri(tmp,regd_shift); /* tmp is now regd */ add_l(target,tmp); } } add_l_ri(target,outer); } else { /* 68000 version */ if ((dp & 0x800) == 0) { /* Sign extend */ sign_extend_16_rr(target,reg); lea_l_brr_indexed(target,base,target,1< : actual translation cache size : %d KB at %p-%p", cache_size, compiled_code, compiled_code + cache_size*1024); #ifdef USE_DATA_BUFFER max_compile_start = compiled_code + cache_size*1024 - BYTES_PER_INST - DATA_BUFFER_SIZE; #else max_compile_start = compiled_code + cache_size*1024 - BYTES_PER_INST; #endif current_compile_p = compiled_code; current_cache_size = 0; #if defined(USE_DATA_BUFFER) reset_data_buffer(); #endif } } static void calc_checksum(blockinfo* bi, uae_u32* c1, uae_u32* c2) { uae_u32 k1 = 0; uae_u32 k2 = 0; #if USE_CHECKSUM_INFO checksum_info *csi = bi->csi; Dif(!csi) abort(); while (csi) { uae_s32 len = csi->length; uintptr tmp = (uintptr)csi->start_p; #else uae_s32 len = bi->len; uintptr tmp = (uintptr)bi->min_pcp; #endif uae_u32* pos; len += (tmp & 3); tmp &= ~((uintptr)3); pos = (uae_u32 *)tmp; if (len >= 0 && len <= MAX_CHECKSUM_LEN) { while (len > 0) { k1 += *pos; k2 ^= *pos; pos++; len -= 4; } } #if USE_CHECKSUM_INFO csi = csi->next; } #endif *c1 = k1; *c2 = k2; } #if 0 static void show_checksum(CSI_TYPE* csi) { uae_u32 k1=0; uae_u32 k2=0; uae_s32 len=CSI_LENGTH(csi); uae_u32 tmp=(uintptr)CSI_START_P(csi); uae_u32* pos; len+=(tmp&3); tmp&=(~3); pos=(uae_u32*)tmp; if (len<0 || len>MAX_CHECKSUM_LEN) { return; } else { while (len>0) { jit_log("%08x ",*pos); pos++; len-=4; } jit_log(" bla"); } } #endif int check_for_cache_miss(void) { blockinfo* bi=get_blockinfo_addr(regs.pc_p); if (bi) { int cl=cacheline(regs.pc_p); if (bi!=cache_tags[cl+1].bi) { raise_in_cl_list(bi); return 1; } } return 0; } static void recompile_block(void) { /* An existing block's countdown code has expired. We need to make sure that execute_normal doesn't refuse to recompile due to a perceived cache miss... */ blockinfo* bi=get_blockinfo_addr(regs.pc_p); Dif (!bi) jit_abort("recompile_block"); raise_in_cl_list(bi); execute_normal(); return; } static void cache_miss(void) { blockinfo* bi=get_blockinfo_addr(regs.pc_p); #if COMP_DEBUG uae_u32 cl=cacheline(regs.pc_p); blockinfo* bi2=get_blockinfo(cl); #endif if (!bi) { execute_normal(); /* Compile this block now */ return; } Dif (!bi2 || bi==bi2) { jit_abort("Unexplained cache miss %p %p",bi,bi2); } raise_in_cl_list(bi); return; } static int called_check_checksum(blockinfo* bi); static inline int block_check_checksum(blockinfo* bi) { uae_u32 c1,c2; bool isgood; if (bi->status!=BI_NEED_CHECK) return 1; /* This block is in a checked state */ if (bi->c1 || bi->c2) calc_checksum(bi,&c1,&c2); else { c1=c2=1; /* Make sure it doesn't match */ } isgood=(c1==bi->c1 && c2==bi->c2); if (isgood) { /* This block is still OK. So we reactivate. Of course, that means we have to move it into the needs-to-be-flushed list */ bi->handler_to_use=bi->handler; set_dhtu(bi,bi->direct_handler); bi->status=BI_CHECKING; isgood=called_check_checksum(bi) != 0; } if (isgood) { jit_log2("reactivate %p/%p (%x %x/%x %x)",bi,bi->pc_p, c1,c2,bi->c1,bi->c2); remove_from_list(bi); add_to_active(bi); raise_in_cl_list(bi); bi->status=BI_ACTIVE; } else { /* This block actually changed. We need to invalidate it, and set it up to be recompiled */ jit_log2("discard %p/%p (%x %x/%x %x)",bi,bi->pc_p, c1,c2,bi->c1,bi->c2); invalidate_block(bi); raise_in_cl_list(bi); } return isgood; } static int called_check_checksum(blockinfo* bi) { int isgood=1; int i; for (i=0;i<2 && isgood;i++) { if (bi->dep[i].jmp_off) { isgood=block_check_checksum(bi->dep[i].target); } } return isgood; } static void check_checksum(void) { blockinfo* bi=get_blockinfo_addr(regs.pc_p); uae_u32 cl=cacheline(regs.pc_p); blockinfo* bi2=get_blockinfo(cl); /* These are not the droids you are looking for... */ if (!bi) { /* Whoever is the primary target is in a dormant state, but calling it was accidental, and we should just compile this new block */ execute_normal(); return; } if (bi!=bi2) { /* The block was hit accidentally, but it does exist. Cache miss */ cache_miss(); return; } if (!block_check_checksum(bi)) execute_normal(); } static inline void match_states(blockinfo* bi) { int i; smallstate* s=&(bi->env); if (bi->status==BI_NEED_CHECK) { block_check_checksum(bi); } if (bi->status==BI_ACTIVE || bi->status==BI_FINALIZING) { /* Deal with the *promises* the block makes (about not using certain vregs) */ for (i=0;i<16;i++) { if (s->virt[i]==L_UNNEEDED) { jit_log2("unneeded reg %d at %p",i,target); COMPCALL(forget_about)(i); // FIXME } } } flush(1); /* And now deal with the *demands* the block makes */ for (i=0;inat[i]; if (v>=0) { // printf("Loading reg %d into %d at %p\n",v,i,target); readreg_specific(v,4,i); // do_load_reg(i,v); // setlock(i); } } for (i=0;inat[i]; if (v>=0) { unlock2(i); } } } static inline void create_popalls(void) { int i,r; if (popallspace == NULL) { if ((popallspace = alloc_code(POPALLSPACE_SIZE)) == NULL) { jit_log("WARNING: Could not allocate popallspace!"); #ifdef UAE if (currprefs.cachesize > 0) #endif { jit_abort("Could not allocate popallspace!"); } #ifdef UAE /* This is not fatal if JIT is not used. If JIT is * turned on, it will crash, but it would have crashed * anyway. */ return; #endif } } vm_protect(popallspace, POPALLSPACE_SIZE, VM_PAGE_READ | VM_PAGE_WRITE); int stack_space = STACK_OFFSET; for (i=0;idirect_pen=(cpuop_func*)get_target(); compemu_raw_mov_l_rm(0, JITPTR &(bi->pc_p)); compemu_raw_mov_l_mr(JITPTR ®s.pc_p,0); compemu_raw_jmp(JITPTR popall_execute_normal); align_target(align_jumps); bi->direct_pcc=(cpuop_func*)get_target(); compemu_raw_mov_l_rm(0, JITPTR &(bi->pc_p)); compemu_raw_mov_l_mr(JITPTR ®s.pc_p,0); compemu_raw_jmp(JITPTR popall_check_checksum); flush_cpu_icache((void *)current_compile_p, (void *)target); current_compile_p=get_target(); bi->deplist=NULL; for (i=0;i<2;i++) { bi->dep[i].prev_p=NULL; bi->dep[i].next=NULL; } bi->env=default_ss; bi->status=BI_INVALID; bi->havestate=0; //bi->env=empty_ss; } #ifdef UAE void compemu_reset(void) { flush_icache = lazy_flush ? flush_icache_lazy : flush_icache_hard; set_cache_state(0); } #endif #ifdef UAE #else // OPCODE is in big endian format, use cft_map() beforehand, if needed. #endif static inline void reset_compop(int opcode) { compfunctbl[opcode] = NULL; nfcompfunctbl[opcode] = NULL; } static int read_opcode(const char *p) { int opcode = 0; for (int i = 0; i < 4; i++) { int op = p[i]; switch (op) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': opcode = (opcode << 4) | (op - '0'); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': opcode = (opcode << 4) | ((op - 'a') + 10); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': opcode = (opcode << 4) | ((op - 'A') + 10); break; default: return -1; } } return opcode; } #ifdef USE_JIT_FPU static struct { const char *name; bool *const disabled; } const jit_opcodes[] = { { "fbcc", &jit_disable.fbcc }, { "fdbcc", &jit_disable.fdbcc }, { "fscc", &jit_disable.fscc }, { "ftrapcc", &jit_disable.ftrapcc }, { "fsave", &jit_disable.fsave }, { "frestore", &jit_disable.frestore }, { "fmove", &jit_disable.fmove }, { "fmovec", &jit_disable.fmovec }, { "fmovem", &jit_disable.fmovem }, { "fmovecr", &jit_disable.fmovecr }, { "fint", &jit_disable.fint }, { "fsinh", &jit_disable.fsinh }, { "fintrz", &jit_disable.fintrz }, { "fsqrt", &jit_disable.fsqrt }, { "flognp1", &jit_disable.flognp1 }, { "fetoxm1", &jit_disable.fetoxm1 }, { "ftanh", &jit_disable.ftanh }, { "fatan", &jit_disable.fatan }, { "fasin", &jit_disable.fasin }, { "fatanh", &jit_disable.fatanh }, { "fsin", &jit_disable.fsin }, { "ftan", &jit_disable.ftan }, { "fetox", &jit_disable.fetox }, { "ftwotox", &jit_disable.ftwotox }, { "ftentox", &jit_disable.ftentox }, { "flogn", &jit_disable.flogn }, { "flog10", &jit_disable.flog10 }, { "flog2", &jit_disable.flog2 }, { "fabs", &jit_disable.fabs }, { "fcosh", &jit_disable.fcosh }, { "fneg", &jit_disable.fneg }, { "facos", &jit_disable.facos }, { "fcos", &jit_disable.fcos }, { "fgetexp", &jit_disable.fgetexp }, { "fgetman", &jit_disable.fgetman }, { "fdiv", &jit_disable.fdiv }, { "fmod", &jit_disable.fmod }, { "fadd", &jit_disable.fadd }, { "fmul", &jit_disable.fmul }, { "fsgldiv", &jit_disable.fsgldiv }, { "frem", &jit_disable.frem }, { "fscale", &jit_disable.fscale }, { "fsglmul", &jit_disable.fsglmul }, { "fsub", &jit_disable.fsub }, { "fsincos", &jit_disable.fsincos }, { "fcmp", &jit_disable.fcmp }, { "ftst", &jit_disable.ftst }, }; static bool read_fpu_opcode(const char *p, size_t len) { unsigned int i; for (i = 0; i < (sizeof(jit_opcodes) / sizeof(jit_opcodes[0])); i++) { if (len == strlen(jit_opcodes[i].name) && strnicmp(jit_opcodes[i].name, p, len) == 0) { *jit_opcodes[i].disabled = true; jit_log(" : disabled %s", jit_opcodes[i].name); return true; } } return false; } #endif static bool merge_blacklist2(const char *blacklist) { #ifdef USE_JIT_FPU for (unsigned int i = 0; i < (sizeof(jit_opcodes) / sizeof(jit_opcodes[0])); i++) *jit_opcodes[i].disabled = false; #endif if (blacklist[0] != '\0') { const char *p = blacklist; for (;;) { size_t len; if (*p == 0) return true; const char *endp = strchr(p, ','); if (endp) { len = endp - p; } else { len = uaestrlen(p); } TCHAR *s = au(p); bool found = false; for (int i = 0; lookuptab[i].name[0]; i++) { if (!_tcsnicmp(s, lookuptab[i].name, len) && _tcslen(lookuptab[i].name) == len) { int mnemo = lookuptab[i].mnemo; for (int opcode = 0; opcode < 0xf000; opcode++) { struct instr *table = &table68k[opcode]; if (table->mnemo == mnemo) { reset_compop(cft_map(opcode)); if (currprefs.cachesize) { jit_log(" : blacklist opcode : %04x", opcode); } found = true; } } if (found) { p += len; if (*p) p++; } } } xfree(s); if (found) continue; #ifdef USE_JIT_FPU if (read_fpu_opcode(p, len)) { p += len; if (*p) p++; continue; } #endif int opcode1 = read_opcode(p); if (opcode1 < 0) { bug(" : invalid opcode %s", p); return false; } p += 4; int opcode2 = opcode1; if (*p == '-') { p++; opcode2 = read_opcode(p); if (opcode2 < 0) { bug(" : invalid opcode %s", p); return false; } p += 4; } if (*p == 0 || *p == ',') { if (currprefs.cachesize) { jit_log(" : blacklist opcodes : %04x-%04x", opcode1, opcode2); } for (int opcode = opcode1; opcode <= opcode2; opcode++) reset_compop(cft_map(opcode)); if (*(p++) == ',') continue; return true; } return false; } } return true; } static bool merge_blacklist(void) { bool ret; #ifdef UAE const char *blacklist = ua(currprefs.jitblacklist); ret = merge_blacklist2(blacklist); xfree((void*)blacklist); #else const char *blacklist = bx_options.jit.jitblacklist; ret = merge_blacklist2(blacklist); #endif return ret; } void build_comp(void) { #ifdef FSUAE if (!g_fs_uae_jit_compiler) { jit_log("JIT: JIT compiler is not enabled"); return; } #endif int i, j; unsigned long opcode; const struct comptbl* tbl=op_smalltbl_0_comp_ff; const struct comptbl* nftbl=op_smalltbl_0_comp_nf; int count; #ifdef WINUAE_ARANYM unsigned int cpu_level = 4; // 68040 const struct cputbl *nfctbl = op_smalltbl_0_nf; #else unsigned int cpu_level = (currprefs.cpu_model - 68000) / 10; if (cpu_level > 4) cpu_level--; #ifdef NOFLAGS_SUPPORT_GENCOMP const struct cputbl *nfctbl = uaegetjitcputbl(); #endif #endif // Initialize target CPU (check for features, e.g. CMOV, rat stalls) raw_init_cpu(); #ifdef NATMEM_OFFSET #ifdef UAE #ifdef JIT_EXCEPTION_HANDLER install_exception_handler(); #endif #endif #endif jit_log(" : building compiler function tables"); for (opcode = 0; opcode < 65536; opcode++) { reset_compop(opcode); #ifdef NOFLAGS_SUPPORT_GENCOMP nfcpufunctbl[opcode] = op_illg; #endif prop[opcode].use_flags = FLAG_ALL; prop[opcode].set_flags = FLAG_ALL; prop[opcode].cflow = fl_trap; // ILLEGAL instructions do trap } for (i = 0; tbl[i].opcode < 65536; i++) { int cflow = table68k[tbl[i].opcode].cflow; if (follow_const_jumps && (tbl[i].specific & COMP_OPCODE_ISCJUMP)) cflow = fl_const_jump; else cflow &= ~fl_const_jump; prop[cft_map(tbl[i].opcode)].cflow = cflow; bool uses_fpu = (tbl[i].specific & COMP_OPCODE_USES_FPU) != 0; if (uses_fpu && avoid_fpu) compfunctbl[cft_map(tbl[i].opcode)] = NULL; else compfunctbl[cft_map(tbl[i].opcode)] = tbl[i].handler; } for (i = 0; nftbl[i].opcode < 65536; i++) { bool uses_fpu = (tbl[i].specific & COMP_OPCODE_USES_FPU) != 0; if (uses_fpu && avoid_fpu) nfcompfunctbl[cft_map(nftbl[i].opcode)] = NULL; else nfcompfunctbl[cft_map(nftbl[i].opcode)] = nftbl[i].handler; for (j = 0; nfctbl[j].handler_ff; j++) { if (nfctbl[j].opcode == nftbl[i].opcode) { #ifdef NOFLAGS_SUPPORT_GENCOMP #ifdef NOFLAGS_SUPPORT_GENCPU nfcpufunctbl[cft_map(nftbl[i].opcode)] = nfctbl[j].handler_nf; #else nfcpufunctbl[cft_map(nftbl[i].opcode)] = nfctbl[j].handler_ff; #endif #endif break; } } if (!nfctbl[j].handler_ff && currprefs.cachesize) { int mnemo = table68k[nftbl[i].opcode].mnemo; struct mnemolookup *lookup; for (lookup = lookuptab; lookup->mnemo != mnemo; lookup++) ; char *s = ua(lookup->name); jit_log("%04x (%s) unavailable", nftbl[i].opcode, s); xfree(s); } } #ifdef NOFLAGS_SUPPORT_GENCOMP #ifdef NOFLAGS_SUPPORT_GENCPU for (i = 0; nfctbl[i].handler_nf; i++) { nfcpufunctbl[cft_map(nfctbl[i].opcode)] = nfctbl[i].handler_nf; } #else for (i = 0; nfctbl[i].handler_ff; i++) { nfcpufunctbl[cft_map(nfctbl[i].opcode)] = nfctbl[i].handler_ff; } #endif #endif for (opcode = 0; opcode < 65536; opcode++) { compop_func *f; compop_func *nff; #ifdef NOFLAGS_SUPPORT_GENCOMP cpuop_func *nfcf; #endif int isaddx; int cflow; if (table68k[opcode].mnemo == i_ILLG || table68k[opcode].clev > cpu_level) continue; if (table68k[opcode].handler != -1) { f = compfunctbl[cft_map(table68k[opcode].handler)]; nff = nfcompfunctbl[cft_map(table68k[opcode].handler)]; #ifdef NOFLAGS_SUPPORT_GENCOMP nfcf = nfcpufunctbl[cft_map(table68k[opcode].handler)]; #endif isaddx = prop[cft_map(table68k[opcode].handler)].is_addx; prop[cft_map(opcode)].is_addx = isaddx; cflow = prop[cft_map(table68k[opcode].handler)].cflow; prop[cft_map(opcode)].cflow = cflow; compfunctbl[cft_map(opcode)] = f; nfcompfunctbl[cft_map(opcode)] = nff; #ifdef NOFLAGS_SUPPORT_GENCOMP Dif (nfcf == op_illg) abort(); nfcpufunctbl[cft_map(opcode)] = nfcf; #endif } prop[cft_map(opcode)].set_flags = table68k[opcode].flagdead; prop[cft_map(opcode)].use_flags = table68k[opcode].flaglive; /* Unconditional jumps don't evaluate condition codes, so they * don't actually use any flags themselves */ if (prop[cft_map(opcode)].cflow & fl_const_jump) prop[cft_map(opcode)].use_flags = 0; } #ifdef NOFLAGS_SUPPORT_GENCOMP #ifdef NOFLAGS_SUPPORT_GENCPU for (i = 0; nfctbl[i].handler_nf != NULL; i++) { if (nfctbl[i].specific) nfcpufunctbl[cft_map(tbl[i].opcode)] = nfctbl[i].handler_nf; } #else for (i = 0; nfctbl[i].handler_ff != NULL; i++) { if (nfctbl[i].specific) nfcpufunctbl[cft_map(tbl[i].opcode)] = nfctbl[i].handler_ff; } #endif #endif /* Merge in blacklist */ if (!merge_blacklist()) { jit_log(" : blacklist merge failure!"); } count=0; for (opcode = 0; opcode < 65536; opcode++) { if (compfunctbl[cft_map(opcode)]) count++; } jit_log(" : supposedly %d compileable opcodes!",count); /* Initialise state */ create_popalls(); alloc_cache(); reset_lists(); for (i=0;ipc_p)].handler=(cpuop_func*)popall_execute_normal; cache_tags[cacheline(bi->pc_p)+1].bi=NULL; dbi=bi; bi=bi->next; free_blockinfo(dbi); } bi=dormant; while(bi) { cache_tags[cacheline(bi->pc_p)].handler=(cpuop_func*)popall_execute_normal; cache_tags[cacheline(bi->pc_p)+1].bi=NULL; dbi=bi; bi=bi->next; free_blockinfo(dbi); } reset_lists(); if (!compiled_code) return; #if defined(USE_DATA_BUFFER) reset_data_buffer(); #endif current_compile_p=compiled_code; #ifdef UAE set_special(0); /* To get out of compiled code */ #else SPCFLAGS_SET( SPCFLAG_JIT_EXEC_RETURN ); /* To get out of compiled code */ #endif } /* "Soft flushing" --- instead of actually throwing everything away, we simply mark everything as "needs to be checked". */ static inline void flush_icache_lazy(int v) { blockinfo* bi; blockinfo* bi2; if (!active) return; bi=active; while (bi) { uae_u32 cl=cacheline(bi->pc_p); if (bi->status==BI_INVALID || bi->status==BI_NEED_RECOMP) { if (bi==cache_tags[cl+1].bi) cache_tags[cl].handler=(cpuop_func*)popall_execute_normal; bi->handler_to_use=(cpuop_func*)popall_execute_normal; set_dhtu(bi,bi->direct_pen); bi->status=BI_INVALID; } else { if (bi==cache_tags[cl+1].bi) cache_tags[cl].handler=(cpuop_func*)popall_check_checksum; bi->handler_to_use=(cpuop_func*)popall_check_checksum; set_dhtu(bi,bi->direct_pcc); bi->status=BI_NEED_CHECK; } bi2=bi; bi=bi->next; } /* bi2 is now the last entry in the active list */ bi2->next=dormant; if (dormant) dormant->prev_p=&(bi2->next); dormant=active; active->prev_p=&dormant; active=NULL; } #if 0 static void flush_icache_range(uae_u32 start, uae_u32 length) { if (!active) return; #if LAZY_FLUSH_ICACHE_RANGE uae_u8 *start_p = get_real_address(start); blockinfo *bi = active; while (bi) { #if USE_CHECKSUM_INFO bool invalidate = false; for (checksum_info *csi = bi->csi; csi && !invalidate; csi = csi->next) invalidate = (((start_p - csi->start_p) < csi->length) || ((csi->start_p - start_p) < length)); #else // Assume system is consistent and would invalidate the right range const bool invalidate = (bi->pc_p - start_p) < length; #endif if (invalidate) { uae_u32 cl = cacheline(bi->pc_p); if (bi == cache_tags[cl + 1].bi) cache_tags[cl].handler = (cpuop_func *)popall_execute_normal; bi->handler_to_use = (cpuop_func *)popall_execute_normal; set_dhtu(bi, bi->direct_pen); bi->status = BI_NEED_RECOMP; } bi = bi->next; } return; #else UNUSED(start); UNUSED(length); #endif flush_icache(); } #endif int failure; #ifdef UAE #if defined(HAVE_GET_WORD_UNSWAPPED) #define DO_GET_OPCODE(a) (do_get_mem_word_unswapped((uae_u16*)(a))) #else static inline unsigned int get_opcode_cft_map(unsigned int f) { return do_byteswap_16(f); } #define DO_GET_OPCODE(a) (get_opcode_cft_map((uae_u16)*(a))) #endif #else #if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU) # define DO_GET_OPCODE(a) (do_get_mem_word_unswapped((uae_u16 *)(a))) #else # define DO_GET_OPCODE(a) (do_get_mem_word((uae_u16 *)(a))) #endif #endif #ifdef JIT_DEBUG static uae_u8 *last_regs_pc_p = 0; static uae_u8 *last_compiled_block_addr = 0; void compiler_dumpstate(void) { if (!JITDebug) return; jit_log("### Host addresses"); jit_log("MEM_BASE : %lx", (unsigned long)MEMBaseDiff); jit_log("PC_P : %p", ®s.pc_p); jit_log("SPCFLAGS : %p", ®s.spcflags); jit_log("D0-D7 : %p-%p", ®s.regs[0], ®s.regs[7]); jit_log("A0-A7 : %p-%p", ®s.regs[8], ®s.regs[15]); jit_log(" "); jit_log("### M68k processor state"); m68k_dumpstate(stderr, 0); jit_log(" "); jit_log("### Block in Atari address space"); jit_log("M68K block : %p", (void *)(uintptr)last_regs_pc_p); if (last_regs_pc_p != 0) { jit_log("Native block : %p (%d bytes)", (void *)last_compiled_block_addr, get_blockinfo_addr(last_regs_pc_p)->direct_handler_size); } jit_log(" "); } #endif #if 0 /* debugging helpers; activate as needed */ static void print_exc_frame(uae_u32 opcode) { int nr = (opcode & 0x0f) + 32; if (nr != 0x45 && /* Timer-C */ nr != 0x1c && /* VBL */ nr != 0x46) /* ACIA */ { memptr sp = m68k_areg(regs, 7); uae_u16 sr = get_word(sp); fprintf(stderr, "Exc:%02x SP: %08x USP: %08x SR: %04x PC: %08x Format: %04x", nr, sp, regs.usp, sr, get_long(sp + 2), get_word(sp + 6)); if (nr >= 32 && nr < 48) { fprintf(stderr, " Opcode: $%04x", sr & 0x2000 ? get_word(sp + 8) : get_word(regs.usp)); } fprintf(stderr, "\n"); } } static void push_all_nat(void) { raw_pushfl(); raw_push_l_r(EAX_INDEX); raw_push_l_r(ECX_INDEX); raw_push_l_r(EDX_INDEX); raw_push_l_r(EBX_INDEX); raw_push_l_r(EBP_INDEX); raw_push_l_r(EDI_INDEX); raw_push_l_r(ESI_INDEX); raw_push_l_r(R8_INDEX); raw_push_l_r(R9_INDEX); raw_push_l_r(R10_INDEX); raw_push_l_r(R11_INDEX); raw_push_l_r(R12_INDEX); raw_push_l_r(R13_INDEX); raw_push_l_r(R14_INDEX); raw_push_l_r(R15_INDEX); } static void pop_all_nat(void) { raw_pop_l_r(R15_INDEX); raw_pop_l_r(R14_INDEX); raw_pop_l_r(R13_INDEX); raw_pop_l_r(R12_INDEX); raw_pop_l_r(R11_INDEX); raw_pop_l_r(R10_INDEX); raw_pop_l_r(R9_INDEX); raw_pop_l_r(R8_INDEX); raw_pop_l_r(ESI_INDEX); raw_pop_l_r(EDI_INDEX); raw_pop_l_r(EBP_INDEX); raw_pop_l_r(EBX_INDEX); raw_pop_l_r(EDX_INDEX); raw_pop_l_r(ECX_INDEX); raw_pop_l_r(EAX_INDEX); raw_popfl(); } #endif #if 0 static void print_inst(void) { disasm_m68k_block(regs.fault_pc + (uint8 *)MEMBaseDiff, 1); } #endif #ifdef UAE void compile_block(cpu_history *pc_hist, int blocklen, int totcycles) { if (cache_enabled && compiled_code && currprefs.cpu_model >= 68020) { #else static void compile_block(cpu_history* pc_hist, int blocklen) { if (cache_enabled && compiled_code) { #endif #ifdef PROFILE_COMPILE_TIME compile_count++; clock_t start_time = clock(); #endif #ifdef JIT_DEBUG bool disasm_block = false; #endif /* OK, here we need to 'compile' a block */ int i; int r; int was_comp=0; uae_u8 liveflags[MAXRUN+1]; #if USE_CHECKSUM_INFO bool trace_in_rom = isinrom((uintptr)pc_hist[0].location) != 0; uintptr max_pcp=(uintptr)pc_hist[blocklen - 1].location; uintptr min_pcp=max_pcp; #else uintptr max_pcp=(uintptr)pc_hist[0].location; uintptr min_pcp=max_pcp; #endif uae_u32 cl=cacheline(pc_hist[0].location); void* specflags=(void*)®s.spcflags; blockinfo* bi=NULL; blockinfo* bi2; int extra_len=0; redo_current_block=0; if (current_compile_p >= MAX_COMPILE_PTR) flush_icache_hard(3); alloc_blockinfos(); bi=get_blockinfo_addr_new(pc_hist[0].location,0); bi2=get_blockinfo(cl); optlev=bi->optlevel; if (bi->status!=BI_INVALID) { Dif (bi!=bi2) { /* I don't think it can happen anymore. Shouldn't, in any case. So let's make sure... */ jit_abort("WOOOWOO count=%d, ol=%d %p %p", bi->count,bi->optlevel,bi->handler_to_use, cache_tags[cl].handler); } Dif (bi->count!=-1 && bi->status!=BI_NEED_RECOMP) { jit_abort("bi->count=%d, bi->status=%d,bi->optlevel=%d",bi->count,bi->status,bi->optlevel); /* What the heck? We are not supposed to be here! */ } } if (bi->count==-1) { optlev++; while (!optcount[optlev]) optlev++; bi->count=optcount[optlev]-1; } current_block_pc_p= JITPTR pc_hist[0].location; remove_deps(bi); /* We are about to create new code */ bi->optlevel=optlev; bi->pc_p=(uae_u8*)pc_hist[0].location; #if USE_CHECKSUM_INFO free_checksum_info_chain(bi->csi); bi->csi = NULL; #endif liveflags[blocklen]=FLAG_ALL; /* All flags needed afterwards */ i=blocklen; while (i--) { uae_u16* currpcp=pc_hist[i].location; uae_u32 op=DO_GET_OPCODE(currpcp); #if USE_CHECKSUM_INFO trace_in_rom = trace_in_rom && isinrom((uintptr)currpcp); if (follow_const_jumps && is_const_jump(op)) { checksum_info *csi = alloc_checksum_info(); csi->start_p = (uae_u8 *)min_pcp; csi->length = JITPTR max_pcp - JITPTR min_pcp + LONGEST_68K_INST; csi->next = bi->csi; bi->csi = csi; max_pcp = (uintptr)currpcp; } min_pcp = (uintptr)currpcp; #else if ((uintptr)currpcpmax_pcp) max_pcp=(uintptr)currpcp; #endif #ifdef UAE if (!currprefs.compnf) { liveflags[i]=FLAG_ALL; } else #endif { liveflags[i] = ((liveflags[i+1] & (~prop[op].set_flags))|prop[op].use_flags); if (prop[op].is_addx && (liveflags[i+1]&FLAG_Z)==0) liveflags[i]&= ~FLAG_Z; } } #if USE_CHECKSUM_INFO checksum_info *csi = alloc_checksum_info(); csi->start_p = (uae_u8 *)min_pcp; csi->length = JITPTR max_pcp - JITPTR min_pcp + LONGEST_68K_INST; csi->next = bi->csi; bi->csi = csi; #endif bi->needed_flags=liveflags[0]; align_target(align_loops); was_comp=0; bi->direct_handler=(cpuop_func*)get_target(); set_dhtu(bi,bi->direct_handler); bi->status=BI_COMPILING; current_block_start_target= JITPTR get_target(); log_startblock(); if (bi->count>=0) { /* Need to generate countdown code */ compemu_raw_mov_l_mi(JITPTR ®s.pc_p, JITPTR pc_hist[0].location); compemu_raw_sub_l_mi(JITPTR &(bi->count),1); compemu_raw_jl(JITPTR popall_recompile_block); } if (optlev==0) { /* No need to actually translate */ /* Execute normally without keeping stats */ compemu_raw_mov_l_mi(JITPTR ®s.pc_p, JITPTR pc_hist[0].location); compemu_raw_jmp(JITPTR popall_exec_nostats); } else { reg_alloc_run=0; next_pc_p=0; taken_pc_p=0; branch_cc=0; // Only to be initialized. Will be set together with next_pc_p comp_pc_p=(uae_u8*)pc_hist[0].location; init_comp(); was_comp=1; #ifdef USE_CPU_EMUL_SERVICES compemu_raw_sub_l_mi((uintptr)&emulated_ticks,blocklen); compemu_raw_jcc_b_oponly(NATIVE_CC_GT); uae_u8 *branchadd=get_target(); skip_byte(); raw_dec_sp(STACK_SHADOW_SPACE); compemu_raw_call((uintptr)cpu_do_check_ticks); raw_inc_sp(STACK_SHADOW_SPACE); *branchadd=get_target()-(branchadd+1); #endif #ifdef JIT_DEBUG if (JITDebug) { compemu_raw_mov_l_mi((uintptr)&last_regs_pc_p,(uintptr)pc_hist[0].location); compemu_raw_mov_l_mi((uintptr)&last_compiled_block_addr,current_block_start_target); } #endif for (i=0;i1) { failure=0; if (!was_comp) { comp_pc_p=(uae_u8*)pc_hist[i].location; init_comp(); } was_comp=1; #if defined(HAVE_DISASM_NATIVE) && defined(HAVE_DISASM_M68K) /* debugging helpers; activate as needed */ #if 1 disasm_this_inst = false; const uae_u8 *start_m68k_thisinst = (const uae_u8 *)pc_hist[i].location; uae_u8 *start_native_thisinst = get_target(); #endif #endif #ifdef WINUAE_ARANYM bool isnop = do_get_mem_word(pc_hist[i].location) == 0x4e71 || ((i + 1) < blocklen && do_get_mem_word(pc_hist[i+1].location) == 0x4e71); if (isnop) compemu_raw_mov_l_mi((uintptr)®s.fault_pc, ((uintptr)(pc_hist[i].location)) - MEMBaseDiff); #endif comptbl[opcode](opcode); freescratch(); if (!(liveflags[i+1] & FLAG_CZNV)) { /* We can forget about flags */ dont_care_flags(); } #if INDIVIDUAL_INST flush(1); nop(); flush(1); was_comp=0; #endif #ifdef WINUAE_ARANYM /* * workaround for buserror handling: on a "nop", write registers back */ if (isnop) { flush(1); nop(); was_comp=0; } #endif #if defined(HAVE_DISASM_NATIVE) && defined(HAVE_DISASM_M68K) /* debugging helpers; activate as needed */ #if 0 disasm_m68k_block(start_m68k_thisinst, 1); push_all_nat(); compemu_raw_mov_l_mi(uae_p32(®s.fault_pc), (uintptr)start_m68k_thisinst - MEMBaseDiff); raw_dec_sp(STACK_SHADOW_SPACE); compemu_raw_call(uae_p32(print_instn)); raw_inc_sp(STACK_SHADOW_SPACE); pop_all_nat(); #endif if (disasm_this_inst) { disasm_m68k_block(start_m68k_thisinst, 1); #if 1 disasm_native_block(start_native_thisinst, get_target() - start_native_thisinst); #endif #if 0 push_all_nat(); raw_dec_sp(STACK_SHADOW_SPACE); compemu_raw_mov_l_ri(REG_PAR1, (uae_u32)cft_map(opcode)); compemu_raw_call((uintptr)print_exc_frame); raw_inc_sp(STACK_SHADOW_SPACE); pop_all_nat(); #endif if (failure) { bug("(discarded)"); target = start_native_thisinst; } } #endif } if (failure) { if (was_comp) { flush(1); was_comp=0; } compemu_raw_mov_l_ri(REG_PAR1,(uae_u32)opcode); #if USE_NORMAL_CALLING_CONVENTION raw_push_l_r(REG_PAR1); #endif compemu_raw_mov_l_mi(JITPTR ®s.pc_p, JITPTR pc_hist[i].location); raw_dec_sp(STACK_SHADOW_SPACE); compemu_raw_call(JITPTR cputbl[opcode]); raw_inc_sp(STACK_SHADOW_SPACE); #ifdef PROFILE_UNTRANSLATED_INSNS // raw_cputbl_count[] is indexed with plain opcode (in m68k order) compemu_raw_add_l_mi((uintptr)&raw_cputbl_count[cft_map(opcode)],1); #endif #if USE_NORMAL_CALLING_CONVENTION raw_inc_sp(4); #endif if (i < blocklen - 1) { uae_u8* branchadd; /* if (SPCFLAGS_TEST(SPCFLAG_ALL)) popall_do_nothing() */ compemu_raw_mov_l_rm(0, JITPTR specflags); compemu_raw_test_l_rr(0,0); #if defined(USE_DATA_BUFFER) data_check_end(8, 64); // just a pessimistic guess... #endif compemu_raw_jz_b_oponly(); branchadd=get_target(); skip_byte(); #ifdef UAE raw_sub_l_mi(uae_p32(&countdown),scaled_cycles(totcycles)); #endif compemu_raw_jmp(JITPTR popall_do_nothing); *branchadd = JITPTR get_target() - (JITPTR branchadd + 1); } } } #if 1 /* This isn't completely kosher yet; It really needs to be be integrated into a general inter-block-dependency scheme */ if (next_pc_p && taken_pc_p && was_comp && taken_pc_p==current_block_pc_p) { blockinfo* bi1=get_blockinfo_addr_new((void*)next_pc_p,0); blockinfo* bi2=get_blockinfo_addr_new((void*)taken_pc_p,0); uae_u8 x=bi1->needed_flags; if (x==0xff || 1) { /* To be on the safe side */ uae_u16* next=(uae_u16*)next_pc_p; uae_u32 op=DO_GET_OPCODE(next); x=FLAG_ALL; x&=(~prop[op].set_flags); x|=prop[op].use_flags; } x|=bi2->needed_flags; if (!(x & FLAG_CZNV)) { /* We can forget about flags */ dont_care_flags(); extra_len+=2; /* The next instruction now is part of this block */ } } #endif log_flush(); if (next_pc_p) { /* A branch was registered */ uintptr t1=next_pc_p; uintptr t2=taken_pc_p; int cc=branch_cc; uae_u32* branchadd; uae_u32* tba; bigstate tmp; blockinfo* tbi; if (taken_pc_penv))) { mark_callers_recompile(bi); } big_to_small_state(&live,&(bi->env)); #endif #if USE_CHECKSUM_INFO remove_from_list(bi); if (trace_in_rom) { // No need to checksum that block trace on cache invalidation free_checksum_info_chain(bi->csi); bi->csi = NULL; add_to_dormant(bi); } else { calc_checksum(bi,&(bi->c1),&(bi->c2)); add_to_active(bi); } #else if (next_pc_p+extra_len>=max_pcp && next_pc_p+extra_lenlen=max_pcp-min_pcp; bi->min_pcp=min_pcp; remove_from_list(bi); if (isinrom(min_pcp) && isinrom(max_pcp)) { add_to_dormant(bi); /* No need to checksum it on cache flush. Please don't start changing ROMs in flight! */ } else { calc_checksum(bi,&(bi->c1),&(bi->c2)); add_to_active(bi); } #endif current_cache_size += JITPTR get_target() - JITPTR current_compile_p; #ifdef JIT_DEBUG bi->direct_handler_size = get_target() - (uae_u8 *)current_block_start_target; if (JITDebug && disasm_block) { uaecptr block_addr = start_pc + ((char *)pc_hist[0].location - (char *)start_pc_p); jit_log("M68K block @ 0x%08x (%d insns)", block_addr, blocklen); uae_u32 block_size = ((uae_u8 *)pc_hist[blocklen - 1].location - (uae_u8 *)pc_hist[0].location) + 1; #ifdef WINUAE_ARANYM disasm_m68k_block((const uae_u8 *)pc_hist[0].location, block_size); #endif jit_log("Compiled block @ %p", pc_hist[0].location); #ifdef WINUAE_ARANYM disasm_native_block((const uae_u8 *)current_block_start_target, bi->direct_handler_size); #endif UNUSED(block_addr); } #endif log_dump(); align_target(align_jumps); #ifdef UAE #ifdef USE_UDIS86 UDISFN(current_block_start_target, target) #endif #endif /* This is the non-direct handler */ bi->handler= bi->handler_to_use=(cpuop_func *)get_target(); compemu_raw_cmp_l_mi(JITPTR ®s.pc_p, JITPTR pc_hist[0].location); compemu_raw_jnz(JITPTR popall_cache_miss); comp_pc_p=(uae_u8*)pc_hist[0].location; bi->status=BI_FINALIZING; init_comp(); match_states(bi); flush(1); compemu_raw_jmp(JITPTR bi->direct_handler); flush_cpu_icache((void *)current_block_start_target, (void *)target); current_compile_p=get_target(); raise_in_cl_list(bi); #ifdef UAE bi->nexthandler=current_compile_p; #endif /* We will flush soon, anyway, so let's do it now */ if (current_compile_p >= MAX_COMPILE_PTR) flush_icache_hard(3); bi->status=BI_ACTIVE; if (redo_current_block) block_need_recompile(bi); #ifdef PROFILE_COMPILE_TIME compile_time += (clock() - start_time); #endif #ifdef UAE /* Account for compilation time */ do_extra_cycles(totcycles); #endif } #ifdef USE_CPU_EMUL_SERVICES /* Account for compilation time */ cpu_do_check_ticks(); #endif } #ifdef UAE /* Slightly different function defined in newcpu.cpp */ #else void do_nothing(void) { /* What did you expect this to do? */ } #endif #ifdef UAE /* Different implementation in newcpu.cpp */ #else void exec_nostats(void) { for (;;) { uae_u32 opcode = GET_OPCODE; #ifdef FLIGHT_RECORDER m68k_record_step(m68k_getpc(), cft_map(opcode)); #endif (*cpufunctbl[opcode])(opcode); cpu_check_ticks(); if (end_block(opcode) || SPCFLAGS_TEST(SPCFLAG_ALL)) { return; /* We will deal with the spcflags in the caller */ } } } #endif #ifdef UAE /* FIXME: check differences against UAE execute_normal (newcpu.cpp) */ #else void execute_normal(void) { if (!check_for_cache_miss()) { cpu_history pc_hist[MAXRUN]; int blocklen = 0; #if 0 && FIXED_ADDRESSING start_pc_p = regs.pc_p; start_pc = get_virtual_address(regs.pc_p); #else start_pc_p = regs.pc_oldp; start_pc = regs.pc; #endif for (;;) { /* Take note: This is the do-it-normal loop */ pc_hist[blocklen++].location = (uae_u16 *)regs.pc_p; uae_u32 opcode = GET_OPCODE; #ifdef FLIGHT_RECORDER m68k_record_step(m68k_getpc(), cft_map(opcode)); #endif (*cpufunctbl[opcode])(opcode); cpu_check_ticks(); if (end_block(opcode) || SPCFLAGS_TEST(SPCFLAG_ALL) || blocklen>=MAXRUN) { compile_block(pc_hist, blocklen); return; /* We will deal with the spcflags in the caller */ } /* No need to check regs.spcflags, because if they were set, we'd have ended up inside that "if" */ } } } #endif typedef void (*compiled_handler)(void); #ifdef UAE /* FIXME: check differences against UAE m68k_do_compile_execute */ #else void m68k_do_compile_execute(void) { for (;;) { ((compiled_handler)(pushall_call_handler))(); /* Whenever we return from that, we should check spcflags */ if (SPCFLAGS_TEST(SPCFLAG_ALL)) { if (m68k_do_specialties ()) return; } } } #endif #ifdef UAE /* FIXME: check differences against UAE m68k_compile_execute */ #else void m68k_compile_execute (void) { setjmpagain: TRY(prb) { for (;;) { if (quit_program > 0) { if (quit_program == 1) { #ifdef FLIGHT_RECORDER dump_flight_recorder(); #endif break; } quit_program = 0; m68k_reset (); } m68k_do_compile_execute(); } } CATCH(prb) { jit_log("m68k_compile_execute: exception %d pc=%08x (%08x+%p-%p) fault_pc=%08x addr=%08x -> %08x sp=%08x", int(prb), m68k_getpc(), regs.pc, regs.pc_p, regs.pc_oldp, regs.fault_pc, regs.mmu_fault_addr, get_long (regs.vbr + 4*prb), regs.regs[15]); flush_icache(); Exception(prb, 0); goto setjmpagain; } } #endif #endif /* JIT */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/exception_handler.c000066400000000000000000000465161504763705000265600ustar00rootroot00000000000000/************************************************************************* * Handling mistaken direct memory access * *************************************************************************/ #ifdef NATMEM_OFFSET #ifdef _WIN32 // %%% BRIAN KING WAS HERE %%% #include #else #ifndef __USE_GNU #define __USE_GNU #endif #include #endif #include #define SIG_READ 1 #define SIG_WRITE 2 static int in_handler = 0; static uae_u8 *veccode; #if defined(JIT_DEBUG) #define DEBUG_ACCESS #endif #if defined(_WIN32) && defined(CPU_x86_64) typedef LPEXCEPTION_POINTERS CONTEXT_T; #define HAVE_CONTEXT_T 1 #define CONTEXT_RIP(context) (context->ContextRecord->Rip) #define CONTEXT_RAX(context) (context->ContextRecord->Rax) #define CONTEXT_RCX(context) (context->ContextRecord->Rcx) #define CONTEXT_RDX(context) (context->ContextRecord->Rdx) #define CONTEXT_RBX(context) (context->ContextRecord->Rbx) #define CONTEXT_RSP(context) (context->ContextRecord->Rsp) #define CONTEXT_RBP(context) (context->ContextRecord->Rbp) #define CONTEXT_RSI(context) (context->ContextRecord->Rsi) #define CONTEXT_RDI(context) (context->ContextRecord->Rdi) #define CONTEXT_R8(context) (context->ContextRecord->R8) #define CONTEXT_R9(context) (context->ContextRecord->R9) #define CONTEXT_R10(context) (context->ContextRecord->R10) #define CONTEXT_R11(context) (context->ContextRecord->R11) #define CONTEXT_R12(context) (context->ContextRecord->R12) #define CONTEXT_R13(context) (context->ContextRecord->R13) #define CONTEXT_R14(context) (context->ContextRecord->R14) #define CONTEXT_R15(context) (context->ContextRecord->R15) #elif defined(_WIN32) && defined(CPU_i386) typedef LPEXCEPTION_POINTERS CONTEXT_T; #define HAVE_CONTEXT_T 1 #define CONTEXT_RIP(context) (context->ContextRecord->Eip) #define CONTEXT_RAX(context) (context->ContextRecord->Eax) #define CONTEXT_RCX(context) (context->ContextRecord->Ecx) #define CONTEXT_RDX(context) (context->ContextRecord->Edx) #define CONTEXT_RBX(context) (context->ContextRecord->Ebx) #define CONTEXT_RSP(context) (context->ContextRecord->Esp) #define CONTEXT_RBP(context) (context->ContextRecord->Ebp) #define CONTEXT_RSI(context) (context->ContextRecord->Esi) #define CONTEXT_RDI(context) (context->ContextRecord->Edi) #elif defined(HAVE_STRUCT_UCONTEXT_UC_MCONTEXT_GREGS) && defined(CPU_x86_64) typedef void *CONTEXT_T; #define HAVE_CONTEXT_T 1 #define CONTEXT_RIP(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RIP]) #define CONTEXT_RAX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RAX]) #define CONTEXT_RCX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RCX]) #define CONTEXT_RDX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RDX]) #define CONTEXT_RBX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RBX]) #define CONTEXT_RSP(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RSP]) #define CONTEXT_RBP(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RBP]) #define CONTEXT_RSI(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RSI]) #define CONTEXT_RDI(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_RDI]) #define CONTEXT_R8(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R8]) #define CONTEXT_R9(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R9]) #define CONTEXT_R10(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R10]) #define CONTEXT_R11(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R11]) #define CONTEXT_R12(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R12]) #define CONTEXT_R13(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R13]) #define CONTEXT_R14(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R14]) #define CONTEXT_R15(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_R15]) #elif defined(HAVE_STRUCT_UCONTEXT_UC_MCONTEXT_GREGS) && defined(CPU_i386) typedef void *CONTEXT_T; #define HAVE_CONTEXT_T 1 #define CONTEXT_RIP(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_EIP]) #define CONTEXT_RAX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_EAX]) #define CONTEXT_RCX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_ECX]) #define CONTEXT_RDX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_EDX]) #define CONTEXT_RBX(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_EBX]) #define CONTEXT_RSP(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_ESP]) #define CONTEXT_RBP(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_EBP]) #define CONTEXT_RSI(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_ESI]) #define CONTEXT_RDI(context) (((struct ucontext *) context)->uc_mcontext.gregs[REG_EDI]) #elif defined(__DARWIN_UNIX03) && defined(CPU_x86_64) typedef void *CONTEXT_T; #define HAVE_CONTEXT_T 1 #define CONTEXT_RIP(context) (*((unsigned long *) &((ucontext_t *) context)->uc_mcontext->__ss.__rip)) #define CONTEXT_RAX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rax) #define CONTEXT_RCX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rcx) #define CONTEXT_RDX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rdx) #define CONTEXT_RBX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rbx) #define CONTEXT_RSP(context) (*((unsigned long *) &((ucontext_t *) context)->uc_mcontext->__ss.__rsp)) #define CONTEXT_RBP(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rbp) #define CONTEXT_RSI(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rsi) #define CONTEXT_RDI(context) (((ucontext_t *) context)->uc_mcontext->__ss.__rdi) #define CONTEXT_R8(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r8) #define CONTEXT_R9(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r9) #define CONTEXT_R10(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r10) #define CONTEXT_R11(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r11) #define CONTEXT_R12(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r12) #define CONTEXT_R13(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r13) #define CONTEXT_R14(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r14) #define CONTEXT_R15(context) (((ucontext_t *) context)->uc_mcontext->__ss.__r15) #elif defined(__DARWIN_UNIX03) && defined(CPU_i386) typedef void *CONTEXT_T; #define HAVE_CONTEXT_T 1 #define CONTEXT_RIP(context) (*((unsigned long *) &((ucontext_t *) context)->uc_mcontext->__ss.__eip)) #define CONTEXT_RAX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__eax) #define CONTEXT_RCX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__ecx) #define CONTEXT_RDX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__edx) #define CONTEXT_RBX(context) (((ucontext_t *) context)->uc_mcontext->__ss.__ebx) #define CONTEXT_RSP(context) (*((unsigned long *) &((ucontext_t *) context)->uc_mcontext->__ss.__esp)) #define CONTEXT_RBP(context) (((ucontext_t *) context)->uc_mcontext->__ss.__ebp) #define CONTEXT_RSI(context) (((ucontext_t *) context)->uc_mcontext->__ss.__esi) #define CONTEXT_RDI(context) (((ucontext_t *) context)->uc_mcontext->__ss.__edi) #endif #define CONTEXT_PC(context) CONTEXT_RIP(context) static int delete_trigger(blockinfo *bi, void *pc) { while (bi) { if (bi->handler && (uae_u8*)bi->direct_handler <= pc && (uae_u8*)bi->nexthandler > pc) { #ifdef DEBUG_ACCESS write_log(_T("JIT: Deleted trigger (%p < %p < %p) %p\n"), bi->handler, pc, bi->nexthandler, bi->pc_p); #endif invalidate_block(bi); raise_in_cl_list(bi); set_special(0); return 1; } bi = bi->next; } return 0; } /* Opcode register id list: * * 8 Bit: 16 Bit: 32 Bit: * * 0: AL 0: AX 0: EAX * 1: CL 1: CX 1: ECX * 2: DL 2: DX 2: EDX * 3: BL 3: BX 3: EBX * 4: AH (SPL, if REX) 4: SP 4: ESP * 5: CH (BPL, if REX) 5: BP 5: EBP * 6: DH (SIL, if REX) 6: SI 6: ESI * 7: BH (DIL, if REX) 7: DI 7: EDI * 8: R8L 8: R8W 8: R8D * 9: R9L 9: R9W 9: R9D * 10: R10L 10: R10W 10: R10D * 11: R11L 11: R11W 11: R11D * 12: R12L 12: R12W 12: R12D * 13: R13L 13: R13W 13: R13D * 14: R14L 14: R14W 14: R14D * 15: R15L 15: R15W 15: R15D */ static bool decode_instruction( uae_u8 *pc, int *r, int *dir, int *size, int *len, int *rex) { *r = -1; *size = 4; *dir = -1; *len = 0; *rex = 0; #ifdef CPU_x86_64 /* Skip address-size override prefix. */ if (*pc == 0x67) { pc += 1; *len += 1; } #endif /* Operand size override prefix. */ if (*pc == 0x66) { pc += 1; *size = 2; *len += 1; } #ifdef CPU_x86_64 /* Handle x86-64 REX prefix. */ if ((pc[0] & 0xf0) == 0x40) { *rex = pc[0]; pc += 1; *len += 1; if (*rex & (1 << 3)) { *size = 8; /* 64-bit operand size not supported. */ return 0; } } #endif switch (pc[0]) { case 0x8a: /* MOV r8, m8 */ if ((pc[1] & 0xc0) == 0x80) { *r = (pc[1] >> 3) & 7; *dir = SIG_READ; *size = 1; *len += 6; break; } break; case 0x88: /* MOV m8, r8 */ if ((pc[1] & 0xc0) == 0x80) { *r = (pc[1] >> 3) & 7; *dir = SIG_WRITE; *size = 1; *len += 6; break; } break; case 0x8b: /* MOV r32, m32 */ switch (pc[1] & 0xc0) { case 0x80: *r = (pc[1] >> 3) & 7; *dir = SIG_READ; *len += 6; break; case 0x40: *r = (pc[1] >> 3) & 7; *dir = SIG_READ; *len += 3; break; case 0x00: *r = (pc[1] >> 3) & 7; *dir = SIG_READ; *len += 2; break; default: break; } break; case 0x89: /* MOV m32, r32 */ switch (pc[1] & 0xc0) { case 0x80: *r = (pc[1] >> 3) & 7; *dir = SIG_WRITE; *len += 6; break; case 0x40: *r = (pc[1] >> 3) & 7; *dir = SIG_WRITE; *len += 3; break; case 0x00: *r = (pc[1] >> 3) & 7; *dir = SIG_WRITE; *len += 2; break; } break; } #ifdef CPU_x86_64 if (*rex & (1 << 2)) { /* Use x86-64 extended registers R8..R15. */ *r += 8; } #endif return *r != -1; } #ifdef HAVE_CONTEXT_T static void *get_pr_from_context(CONTEXT_T context, int r, int size, int rex) { switch (r) { case 0: return &(CONTEXT_RAX(context)); case 1: return &(CONTEXT_RCX(context)); case 2: return &(CONTEXT_RDX(context)); case 3: return &(CONTEXT_RBX(context)); case 4: if (size > 1 || rex) { return NULL; } else { return (((uae_u8 *) &(CONTEXT_RAX(context))) + 1); /* AH */ } case 5: if (size > 1 || rex) { return &(CONTEXT_RBP(context)); } else { return (((uae_u8 *) &(CONTEXT_RCX(context))) + 1); /* CH */ } case 6: if (size > 1 || rex) { return &(CONTEXT_RSI(context)); } else { return (((uae_u8 *) &(CONTEXT_RDX(context))) + 1); /* DH */ } case 7: if (size > 1 || rex) { return &(CONTEXT_RDI(context)); } else { return (((uae_u8 *) &(CONTEXT_RBX(context))) + 1); /* BH */ } #ifdef CPU_x86_64 case 8: return &(CONTEXT_R8(context)); case 9: return &(CONTEXT_R9(context)); case 10: return &(CONTEXT_R10(context)); case 11: return &(CONTEXT_R11(context)); case 12: return &(CONTEXT_R12(context)); case 13: return &(CONTEXT_R13(context)); case 14: return &(CONTEXT_R14(context)); case 15: return &(CONTEXT_R15(context)); #endif default: abort (); } } static void log_unhandled_access(uae_u8 *fault_pc) { write_log(_T("JIT: Can't handle access PC=%p!\n"), fault_pc); if (fault_pc) { write_log(_T("JIT: Instruction bytes")); for (int j = 0; j < 10; j++) { write_log(_T(" %02x"), fault_pc[j]); } write_log(_T("\n")); } } #ifdef WIN32 static int handle_access(uintptr_t fault_addr, CONTEXT_T context) { uae_u8 *fault_pc = (uae_u8 *) CONTEXT_PC(context); #ifdef CPU_64_BIT #if 0 if ((fault_addr & 0xffffffff00000000) == 0xffffffff00000000) { fault_addr &= 0xffffffff; } #endif if (fault_addr > (uintptr_t) 0xffffffff) { return 0; } #endif #ifdef DEBUG_ACCESS write_log(_T("JIT: Fault address is 0x%lx at PC=%p\n"), fault_addr, fault_pc); #endif if (!canbang || !currprefs.cachesize) return 0; if (in_handler) write_log(_T("JIT: Argh --- Am already in a handler. Shouldn't happen!\n")); if (fault_pc < compiled_code || fault_pc > current_compile_p) { return 0; } int r = -1, size = 4, dir = -1, len = 0, rex = 0; decode_instruction(fault_pc, &r, &dir, &size, &len, &rex); if (r == -1) { log_unhandled_access(fault_pc); return 0; } #ifdef DEBUG_ACCESS write_log (_T("JIT: Register was %d, direction was %d, size was %d\n"), r, dir, size); #endif void *pr = get_pr_from_context(context, r, size, rex); if (pr == NULL) { log_unhandled_access(fault_pc); return 0; } uae_u32 addr = uae_p32(fault_addr) - uae_p32(NATMEM_OFFSET); #ifdef DEBUG_ACCESS if (addr >= 0x80000000) { write_log (_T("JIT: Suspicious address 0x%x in SEGV handler.\n"), addr); } addrbank *ab = &get_mem_bank(addr); if (ab) write_log(_T("JIT: Address bank: %s, address %08x\n"), ab->name ? ab->name : _T("NONE"), addr); #endif if (dir == SIG_READ) { switch (size) { case 1: *((uae_u8*)pr) = get_byte(addr); break; case 2: *((uae_u16*)pr) = do_byteswap_16(get_word(addr)); break; case 4: *((uae_u32*)pr) = do_byteswap_32(get_long(addr)); break; default: abort(); } } else { switch (size) { case 1: put_byte(addr, *((uae_u8 *) pr)); break; case 2: put_word(addr, do_byteswap_16(*((uae_u16 *) pr))); break; case 4: put_long(addr, do_byteswap_32(*((uae_u32 *) pr))); break; default: abort(); } } CONTEXT_PC(context) += len; if (delete_trigger(active, fault_pc)) { return 1; } /* Not found in the active list. Might be a rom routine that * is in the dormant list */ if (delete_trigger(dormant, fault_pc)) { return 1; } #ifdef DEBUG_ACCESS // Can happen if MOVEM causes multiple faults write_log (_T("JIT: Huh? Could not find trigger!\n")); #endif set_special(0); return 1; } #else /* * Try to handle faulted memory access in compiled code * * Returns 1 if handled, 0 otherwise */ static int handle_access(uintptr_t fault_addr, CONTEXT_T context) { uae_u8 *fault_pc = (uae_u8 *) CONTEXT_PC(context); #ifdef CPU_64_BIT #if 0 if ((fault_addr & 0xffffffff00000000) == 0xffffffff00000000) { fault_addr &= 0xffffffff; } #endif if (fault_addr > (uintptr_t) 0xffffffff) { return 0; } #endif #ifdef DEBUG_ACCESS write_log(_T("JIT: Fault address is 0x%lx at PC=%p\n"), fault_addr, fault_pc); #endif if (!canbang || !currprefs.cachesize) return 0; if (in_handler) write_log(_T("JIT: Argh --- Am already in a handler. Shouldn't happen!\n")); if (fault_pc < compiled_code || fault_pc > current_compile_p) { return 0; } int r = -1, size = 4, dir = -1, len = 0, rex = 0; decode_instruction(fault_pc, &r, &dir, &size, &len, &rex); if (r == -1) { log_unhandled_access(fault_pc); return 0; } #ifdef DEBUG_ACCESS write_log (_T("JIT: Register was %d, direction was %d, size was %d\n"), r, dir, size); #endif void *pr = get_pr_from_context(context, r, size, rex); if (pr == NULL) { log_unhandled_access(fault_pc); return 0; } uae_u32 addr = uae_p32(fault_addr) - uae_p32(NATMEM_OFFSET); #ifdef DEBUG_ACCESS if (addr >= 0x80000000) { write_log (_T("JIT: Suspicious address 0x%x in SEGV handler.\n"), addr); } addrbank *ab = &get_mem_bank(addr); if (ab) write_log(_T("JIT: Address bank: %s, address %08x\n"), ab->name ? ab->name : _T("NONE"), addr); #endif uae_u8 *original_target = target; target = (uae_u8*) CONTEXT_PC(context); uae_u8 vecbuf[5]; for (int i = 0; i < sizeof(vecbuf); i++) { vecbuf[i] = target[i]; } raw_jmp(uae_p32(veccode)); #ifdef DEBUG_ACCESS write_log(_T("JIT: Create jump to %p\n"), veccode); #endif target = veccode; if (dir == SIG_READ) { switch (size) { case 1: raw_mov_b_ri(r, get_byte(addr)); break; case 2: raw_mov_w_ri(r, do_byteswap_16(get_word(addr))); break; case 4: raw_mov_l_ri(r, do_byteswap_32(get_long(addr))); break; default: abort(); } } else { switch (size) { case 1: put_byte(addr, *((uae_u8 *) pr)); break; case 2: put_word(addr, do_byteswap_16(*((uae_u16 *) pr))); break; case 4: put_long(addr, do_byteswap_32(*((uae_u32 *) pr))); break; default: abort(); } } for (int i = 0; i < sizeof(vecbuf); i++) { raw_mov_b_mi(JITPTR CONTEXT_PC(context) + i, vecbuf[i]); } raw_mov_l_mi(uae_p32(&in_handler), 0); raw_jmp(uae_p32(CONTEXT_PC(context)) + len); in_handler = 1; target = original_target; if (delete_trigger(active, fault_pc)) { return 1; } /* Not found in the active list. Might be a rom routine that * is in the dormant list */ if (delete_trigger(dormant, fault_pc)) { return 1; } #ifdef DEBUG_ACCESS write_log (_T("JIT: Huh? Could not find trigger!\n")); #endif set_special(0); return 1; } #endif #endif /* CONTEXT_T */ #ifdef _WIN32 LONG WINAPI EvalException(LPEXCEPTION_POINTERS info) { DWORD code = info->ExceptionRecord->ExceptionCode; if (code != STATUS_ACCESS_VIOLATION || !canbang || currprefs.cachesize == 0) return EXCEPTION_CONTINUE_SEARCH; uintptr_t address = info->ExceptionRecord->ExceptionInformation[1]; if (handle_access(address, info)) { return EXCEPTION_CONTINUE_EXECUTION; } if (currprefs.comp_catchfault) { // setup fake exception exception2_setup(regs.opcode, uae_p32(address) - uae_p32(NATMEM_OFFSET), info->ExceptionRecord->ExceptionInformation[0] == 0, 1, regs.s ? 4 : 0); return EXCEPTION_EXECUTE_HANDLER; } return EXCEPTION_CONTINUE_SEARCH; } static void *installed_vector_handler; static LONG CALLBACK JITVectoredHandler(PEXCEPTION_POINTERS info) { // write_log(_T("JitVectoredHandler\n")); return EvalException(info); } #elif defined(HAVE_CONTEXT_T) static void sigsegv_handler(int signum, siginfo_t *info, void *context) { uae_u8 *i = (uae_u8 *) CONTEXT_PC(context); uintptr_t address = (uintptr_t) info->si_addr; if (i >= compiled_code) { if (handle_access(address, context)) { return; } } else { write_log ("Caught illegal access to %08lx at eip=%p\n", address, i); } exit (EXIT_FAILURE); } #endif // #define TEST_EXCEPTION_HANDLER #ifdef TEST_EXCEPTION_HANDLER #include "test_exception_handler.cpp" #endif static void install_exception_handler(void) { #ifdef TEST_EXCEPTION_HANDLER test_exception_handler(); #endif #ifdef JIT_EXCEPTION_HANDLER if (veccode == NULL) { veccode = (uae_u8 *) uae_vm_alloc(256, UAE_VM_32BIT, UAE_VM_READ_WRITE_EXECUTE); } #endif #ifdef USE_STRUCTURED_EXCEPTION_HANDLING /* Structured exception handler is installed in main.cpp */ #elif defined(_WIN32) #if 1 write_log(_T("JIT: Installing vectored exception handler\n")); installed_vector_handler = AddVectoredExceptionHandler( 0, JITVectoredHandler); #else write_log(_T("JIT: Installing unhandled exception filter\n")); SetUnhandledExceptionFilter(EvalException); #endif #elif defined(HAVE_CONTEXT_T) write_log (_T("JIT: Installing segfault handler\n")); struct sigaction act; act.sa_sigaction = (void (*)(int, siginfo_t*, void*)) sigsegv_handler; sigemptyset (&act.sa_mask); act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, NULL); #ifdef MACOSX sigaction(SIGBUS, &act, NULL); #endif #else write_log (_T("JIT: No segfault handler installed\n")); #endif } #endif /* NATMEM_OFFSET */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/flags_arm.h000066400000000000000000000032021504763705000250060ustar00rootroot00000000000000/* * compiler/flags_arm.h - Native flags definitions for ARM * * Copyright (c) 2013 Jens Heitmann of ARAnyM dev team (see AUTHORS) * * Inspired by Christian Bauer's Basilisk II * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef NATIVE_FLAGS_ARM_H #define NATIVE_FLAGS_ARM_H /* Native integer code conditions */ enum { NATIVE_CC_EQ = 0, NATIVE_CC_NE = 1, NATIVE_CC_CS = 2, NATIVE_CC_CC = 3, NATIVE_CC_MI = 4, NATIVE_CC_PL = 5, NATIVE_CC_VS = 6, NATIVE_CC_VC = 7, NATIVE_CC_HI = 8, NATIVE_CC_LS = 9, NATIVE_CC_GE = 10, NATIVE_CC_LT = 11, NATIVE_CC_GT = 12, NATIVE_CC_LE = 13, NATIVE_CC_AL = 14 }; #endif /* NATIVE_FLAGS_ARM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/flags_x86.h000066400000000000000000000027671504763705000246730ustar00rootroot00000000000000/* * compiler/flags_x86.h - Native flags definitions for IA-32 * * Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer * * Adaptation for Basilisk II and improvements, copyright 2000-2002 * Gwenole Beauchesne * * Basilisk II (C) 1997-2002 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #ifndef NATIVE_FLAGS_X86_H #define NATIVE_FLAGS_X86_H /* Native integer code conditions */ enum { NATIVE_CC_HI = 7, NATIVE_CC_LS = 6, NATIVE_CC_CC = 3, NATIVE_CC_CS = 2, NATIVE_CC_NE = 5, NATIVE_CC_EQ = 4, NATIVE_CC_VC = 1, NATIVE_CC_VS = 0, NATIVE_CC_PL = 9, NATIVE_CC_MI = 8, NATIVE_CC_GE = 13, NATIVE_CC_LT = 12, NATIVE_CC_GT = 15, NATIVE_CC_LE = 14 }; /* FIXME: include/flags_x86.h in UAE had the following values: NATIVE_CC_VC = 11, NATIVE_CC_VS = 10, */ #endif /* NATIVE_FLAGS_X86_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/gencomp.c000066400000000000000000002654721504763705000245210ustar00rootroot00000000000000/* * compiler/gencomp.c - MC680x0 compilation generator * * Based on work Copyright 1995, 1996 Bernd Schmidt * Changes for UAE-JIT Copyright 2000 Bernd Meyer * * Adaptation for ARAnyM/ARM, copyright 2001-2014 * Milan Jurik, Jens Heitmann * * Adaptation for Basilisk II and improvements, copyright 2000-2005 * Gwenole Beauchesne * * Basilisk II (C) 1997-2005 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #define CC_FOR_BUILD 1 #include "sysconfig.h" #include "sysdeps.h" #include "readcpu.h" #undef NDEBUG #include #include #include #include #include #include #undef abort #ifdef UAE /* #define DISABLE_I_OR_AND_EOR #define DISABLE_I_SUB #define DISABLE_I_SUBA #define DISABLE_I_SUBX #define DISABLE_I_ADD #define DISABLE_I_ADDA #define DISABLE_I_ADDX #define DISABLE_I_NEG #define DISABLE_I_NEGX #define DISABLE_I_CLR #define DISABLE_I_NOT #define DISABLE_I_TST #define DISABLE_I_BCHG_BCLR_BSET_BTST #define DISABLE_I_CMPM_CMP #define DISABLE_I_CMPA #define DISABLE_I_MOVE #define DISABLE_I_MOVEA #define DISABLE_I_SWAP #define DISABLE_I_EXG #define DISABLE_I_EXT #define DISABLE_I_MVMEL #define DISABLE_I_MVMLE #define DISABLE_I_RTD #define DISABLE_I_LINK #define DISABLE_I_UNLK #define DISABLE_I_RTS #define DISABLE_I_JSR #define DISABLE_I_JMP #define DISABLE_I_BSR #define DISABLE_I_BCC #define DISABLE_I_LEA #define DISABLE_I_PEA #define DISABLE_I_DBCC #define DISABLE_I_SCC #define DISABLE_I_MULU #define DISABLE_I_MULS #define DISABLE_I_ASR #define DISABLE_I_ASL #define DISABLE_I_LSR #define DISABLE_I_LSL #define DISABLE_I_ROL #define DISABLE_I_ROR #define DISABLE_I_MULL #define DISABLE_I_FPP #define DISABLE_I_FBCC #define DISABLE_I_FSCC #define DISABLE_I_MOVE16 */ #endif /* UAE */ #ifdef UAE #define JIT_PATH "jit/" #ifdef FSUAE #define GEN_PATH "gen/" #else #define GEN_PATH "jit/" #endif #define RETURN "return 0;" #define RETTYPE "uae_u32" #define NEXT_CPU_LEVEL 5 #else #define JIT_PATH "compiler/" #define GEN_PATH "" #define RETURN "return;" #define RETTYPE "void" #define NEXT_CPU_LEVEL 4 #endif #define BOOL_TYPE "int" #define failure global_failure=1 #define FAILURE global_failure=1 #define isjump global_isjump=1 #define is_const_jump global_iscjump=1 #define isaddx global_isaddx=1 #define uses_cmov global_cmov=1 #define mayfail global_mayfail=1 #define uses_fpu global_fpu=1 int hack_opcode; static int global_failure; static int global_isjump; static int global_iscjump; static int global_isaddx; static int global_cmov; static int long_opcode; static int global_mayfail; static int global_fpu; static char endstr[1000]; #include "flags_x86.h" #ifndef __attribute__ # ifndef __GNUC__ # define __attribute__(x) # endif #endif #define GENA_GETV_NO_FETCH 0 #define GENA_GETV_FETCH 1 #define GENA_GETV_FETCH_ALIGN 2 #define GENA_MOVEM_DO_INC 0 #define GENA_MOVEM_NO_INC 1 #define GENA_MOVEM_MOVE16 2 static int cond_codes[] = { -1, -1, NATIVE_CC_HI, NATIVE_CC_LS, NATIVE_CC_CC, NATIVE_CC_CS, NATIVE_CC_NE, NATIVE_CC_EQ, -1, -1, NATIVE_CC_PL, NATIVE_CC_MI, NATIVE_CC_GE, NATIVE_CC_LT, NATIVE_CC_GT, NATIVE_CC_LE }; static int brace_level; static char outbuffer[30000]; static void comprintf(const char *format, ...) { char outbuf[1000]; va_list parms; va_start(parms, format); _vsnprintf(outbuf, sizeof(outbuf) - 1, format, parms); outbuf[sizeof(outbuf) - 1] = 0; va_end(parms); char *p = outbuf; bool firstspace = true; while (*p) { char v = *p; if (v == '\t' || (v == ' ' && p[1] == ' ') || (firstspace && v == ' ')) { memmove(p, p + 1, strlen(p + 1) + 1); } else { firstspace = false; p++; } } p = outbuf; for (;;) { char *pe = p; int islf = 0; while (*pe != 0 && *pe != '\n') { pe++; } if (*pe == '\n') { islf = 1; *pe = 0; } char outbuf2[1000]; strcpy(outbuf2, p); outbuf2[pe - p] = 0; if (outbuf2[0]) { char *ps = outbuf2; while (*ps) { char v = *ps; if (v == '}') { brace_level--; } ps++; } for (int i = 0; i < brace_level; i++) { strcat(outbuffer, "\t"); } strcat(outbuffer, outbuf2); ps = outbuf2; while (*ps) { char v = *ps; if (v == '{') { brace_level++; } ps++; } } if (islf) { strcat(outbuffer, "\n"); pe++; } if (*pe == 0) break; p = pe; } } static void com_discard(void) { outbuffer[0] = 0; } static void com_flush(void) { printf("%s", outbuffer); com_discard(); } static FILE *headerfile; static FILE *stblfile; static int using_prefetch; static int using_exception_3; static int cpu_level; static int noflags; /* For the current opcode, the next lower level that will have different code. * Initialized to -1 for each opcode. If it remains unchanged, indicates we * are done with that opcode. */ static int next_cpu_level; static int *opcode_map; static int *opcode_next_clev; static int *opcode_last_postfix; static unsigned long *counts; static void read_counts(void) { FILE *file; unsigned long opcode, count, total; char name[20]; int nr = 0; memset(counts, 0, 65536 * sizeof * counts); file = fopen("frequent.68k", "r"); if (file) { if (fscanf(file, "Total: %lu\n", &total) != 1) { assert(0); } while (fscanf(file, "%lx: %lu %s\n", &opcode, &count, name) == 3) { opcode_next_clev[nr] = NEXT_CPU_LEVEL; opcode_last_postfix[nr] = -1; opcode_map[nr++] = opcode; counts[opcode] = count; } fclose(file); } if (nr == nr_cpuop_funcs) return; for (opcode = 0; opcode < 0x10000; opcode++) { if (table68k[opcode].handler == -1 && table68k[opcode].mnemo != i_ILLG && counts[opcode] == 0) { opcode_next_clev[nr] = NEXT_CPU_LEVEL; opcode_last_postfix[nr] = -1; opcode_map[nr++] = opcode; counts[opcode] = count; } } assert(nr == nr_cpuop_funcs); } static int n_braces = 0; static int insn_n_cycles; static void start_brace(void) { n_braces++; comprintf("{\n"); } static void close_brace(void) { assert(n_braces > 0); n_braces--; comprintf("}\n"); } static void finish_braces(void) { while (n_braces > 0) close_brace(); } static inline void gen_update_next_handler(void) { return; /* Can anything clever be done here? */ } static void gen_writebyte(const char *address, const char *source) { comprintf("\twritebyte(%s, %s, scratchie);\n", address, source); } static void gen_writeword(const char *address, const char *source) { comprintf("\twriteword(%s, %s, scratchie);\n", address, source); } static void gen_writelong(const char *address, const char *source) { comprintf("\twritelong(%s, %s, scratchie);\n", address, source); } static void gen_readbyte(const char *address, const char *dest) { comprintf("\treadbyte(%s, %s, scratchie);\n", address, dest); } static void gen_readword(const char *address, const char *dest) { comprintf("\treadword(%s,%s,scratchie);\n", address, dest); } static void gen_readlong(const char *address, const char *dest) { comprintf("\treadlong(%s, %s, scratchie);\n", address, dest); } static const char * gen_nextilong(void) { static char buffer[80]; sprintf(buffer, "comp_get_ilong((m68k_pc_offset+=4)-4)"); insn_n_cycles += 4; long_opcode = 1; return buffer; } static const char * gen_nextiword(void) { static char buffer[80]; sprintf(buffer, "comp_get_iword((m68k_pc_offset+=2)-2)"); insn_n_cycles += 2; long_opcode = 1; return buffer; } static const char * gen_nextibyte(void) { static char buffer[80]; sprintf(buffer, "comp_get_ibyte((m68k_pc_offset+=2)-2)"); insn_n_cycles += 2; long_opcode = 1; return buffer; } static void swap_opcode(void) { #ifdef UAE /* no-op */ #else comprintf("#ifdef USE_JIT_FPU\n"); comprintf("#if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU)\n"); comprintf("\topcode = do_byteswap_16(opcode);\n"); comprintf("#endif\n"); comprintf("#endif\n"); #endif } static void sync_m68k_pc(void) { comprintf("if (m68k_pc_offset > SYNC_PC_OFFSET) {\n"); comprintf("sync_m68k_pc();\n"); comprintf("}\n"); } static void gen_set_fault_pc(void) { start_brace(); comprintf("\tsync_m68k_pc();\n"); comprintf("\tuae_u32 retadd=start_pc+((char *)comp_pc_p-(char *)start_pc_p)+m68k_pc_offset;\n"); comprintf("\tint ret=scratchie++;\n" "\tmov_l_ri(ret,retadd);\n" "\tmov_l_mr((uintptr)®s.fault_pc,ret);\n"); } static void make_sr(void) { start_brace(); comprintf("\tint sr = scratchie++;\n"); comprintf("\tint tmp = scratchie++;\n"); comprintf("\tcompemu_make_sr(sr, tmp);\n"); } static void disasm_this_inst(void) { comprintf("\tdisasm_this_inst = true;\n"); } /* getv == 1: fetch data; getv != 0: check for odd address. If movem != 0, * the calling routine handles Apdi and Aipi modes. * gb-- movem == 2 means the same thing but for a MOVE16 instruction */ static void genamode(amodes mode, const char *reg, wordsizes size, const char *name, int getv, int movem) { start_brace(); switch (mode) { case Dreg: /* Do we need to check dodgy here? */ assert(movem == GENA_MOVEM_DO_INC); if (getv == GENA_GETV_FETCH || getv == GENA_GETV_FETCH_ALIGN) { /* We generate the variable even for getv==2, so we can use it as a destination for MOVE */ comprintf("\tint %s = %s;\n", name, reg); } return; case Areg: assert(movem == GENA_MOVEM_DO_INC); if (getv == GENA_GETV_FETCH || getv == GENA_GETV_FETCH_ALIGN) { /* see above */ comprintf("\tint %s = dodgy ? scratchie++ : %s + 8;\n", name, reg); if (getv == GENA_GETV_FETCH) { comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%s, %s + 8);\n", name, reg); comprintf("}\n"); } } return; case Aind: comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%sa, %s + 8);\n", name, reg); comprintf("}\n"); break; case Aipi: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_rr(%sa, %s + 8);\n", name, reg); break; case Apdi: switch (size) { case sz_byte: if (movem != GENA_MOVEM_DO_INC) { comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("}\n"); } else { start_brace(); comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tlea_l_brr(%s + 8, %s + 8, (uae_s32)-areg_byteinc[%s]);\n", reg, reg, reg); comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("}\n"); } break; case sz_word: if (movem != GENA_MOVEM_DO_INC) { comprintf("\tint %sa=dodgy?scratchie++:%s+8;\n", name, reg); comprintf("\tif (dodgy) {\n"); comprintf("\tmov_l_rr(%sa,8+%s);\n", name, reg); comprintf("}\n"); } else { start_brace(); comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tlea_l_brr(%s + 8, %s + 8, -2);\n", reg, reg); comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("}\n"); } break; case sz_long: if (movem != GENA_MOVEM_DO_INC) { comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("}\n"); } else { start_brace(); comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tlea_l_brr(%s + 8, %s + 8, -4);\n", reg, reg); comprintf("\tif (dodgy) {\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("}\n"); } break; default: assert(0); break; } break; case Ad16: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("\tlea_l_brr(%sa, %sa, (uae_s32)(uae_s16)%s);\n", name, name, gen_nextiword()); break; case Ad8r: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tcalc_disp_ea_020(%s + 8, %s, %sa, scratchie);\n", reg, gen_nextiword(), name); break; case PC16: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tuae_u32 address = (uae_u32)(start_pc + ((char *)comp_pc_p - (char *)start_pc_p) + m68k_pc_offset);\n"); comprintf("\tuae_s32 PC16off = (uae_s32)(uae_s16)%s;\n", gen_nextiword()); comprintf("\tmov_l_ri(%sa, address + PC16off);\n", name); break; case PC8r: comprintf("\tint pctmp = scratchie++;\n"); comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tuae_u32 address = (uae_u32)(start_pc + ((char *)comp_pc_p - (char *)start_pc_p) + m68k_pc_offset);\n"); start_brace(); comprintf("\tmov_l_ri(pctmp,address);\n"); comprintf("\tcalc_disp_ea_020(pctmp, %s, %sa, scratchie);\n", gen_nextiword(), name); break; case absw: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_ri(%sa, (uae_s32)(uae_s16)%s);\n", name, gen_nextiword()); break; case absl: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_ri(%sa, %s); /* absl */\n", name, gen_nextilong()); break; case imm: assert(getv == GENA_GETV_FETCH); switch (size) { case sz_byte: comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s8)%s);\n", name, gen_nextibyte()); break; case sz_word: comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s16)%s);\n", name, gen_nextiword()); break; case sz_long: comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, %s);\n", name, gen_nextilong()); break; default: assert(0); break; } return; case imm0: assert(getv == GENA_GETV_FETCH); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s8)%s);\n", name, gen_nextibyte()); return; case imm1: assert(getv == GENA_GETV_FETCH); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s16)%s);\n", name, gen_nextiword()); return; case imm2: assert(getv == GENA_GETV_FETCH); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, %s);\n", name, gen_nextilong()); return; case immi: assert(getv == GENA_GETV_FETCH); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, %s);\n", name, reg); return; default: assert(0); break; } /* We get here for all non-reg non-immediate addressing modes to * actually fetch the value. */ if (getv == GENA_GETV_FETCH) { char astring[80]; sprintf(astring, "%sa", name); switch (size) { case sz_byte: insn_n_cycles += 2; break; case sz_word: insn_n_cycles += 2; break; case sz_long: insn_n_cycles += 4; break; default: assert(0); break; } start_brace(); comprintf("\tint %s = scratchie++;\n", name); switch (size) { case sz_byte: gen_readbyte(astring, name); break; case sz_word: gen_readword(astring, name); break; case sz_long: gen_readlong(astring, name); break; default: assert(0); break; } } /* We now might have to fix up the register for pre-dec or post-inc * addressing modes. */ if (movem == GENA_MOVEM_DO_INC) { switch (mode) { case Aipi: switch (size) { case sz_byte: comprintf("\tlea_l_brr(%s + 8,%s + 8, areg_byteinc[%s]);\n", reg, reg, reg); break; case sz_word: comprintf("\tlea_l_brr(%s + 8, %s + 8, 2);\n", reg, reg); break; case sz_long: comprintf("\tlea_l_brr(%s + 8, %s + 8, 4);\n", reg, reg); break; default: assert(0); break; } break; case Apdi: break; default: break; } } } static void genastore(const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { switch (mode) { case Dreg: switch (size) { case sz_byte: comprintf("\tif(%s != %s) {\n", reg, from); comprintf("\t\tmov_b_rr(%s, %s);\n", reg, from); comprintf("}\n"); break; case sz_word: comprintf("\tif(%s != %s) {\n", reg, from); comprintf("\t\tmov_w_rr(%s, %s);\n", reg, from); comprintf("}\n"); break; case sz_long: comprintf("\tif(%s != %s) {\n", reg, from); comprintf("\t\tmov_l_rr(%s, %s);\n", reg, from); comprintf("}\n"); break; default: assert(0); break; } break; case Areg: switch (size) { case sz_word: comprintf("\tif(%s + 8 != %s) {\n", reg, from); comprintf("\t\tmov_w_rr(%s + 8, %s);\n", reg, from); comprintf("}\n"); break; case sz_long: comprintf("\tif(%s + 8 != %s) {\n", reg, from); comprintf("\t\tmov_l_rr(%s + 8, %s);\n", reg, from); comprintf("}\n"); break; default: assert(0); break; } break; case Apdi: case absw: case PC16: case PC8r: case Ad16: case Ad8r: case Aipi: case Aind: case absl: { char astring[80]; sprintf(astring, "%sa", to); switch (size) { case sz_byte: insn_n_cycles += 2; gen_writebyte(astring, from); break; case sz_word: insn_n_cycles += 2; gen_writeword(astring, from); break; case sz_long: insn_n_cycles += 4; gen_writelong(astring, from); break; default: assert(0); break; } } break; case imm: case imm0: case imm1: case imm2: case immi: assert(0); break; default: assert(0); break; } } static void genmov16(uae_u32 opcode, struct instr *curi) { comprintf("\tint src=scratchie++;\n"); comprintf("\tint dst=scratchie++;\n"); if ((opcode & 0xfff8) == 0xf620) { /* MOVE16 (Ax)+,(Ay)+ */ comprintf("\tuae_u16 dstreg=((%s)>>12)&0x07;\n", gen_nextiword()); comprintf("\tmov_l_rr(src,8+srcreg);\n"); comprintf("\tmov_l_rr(dst,8+dstreg);\n"); } else { /* Other variants */ genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_NO_FETCH, GENA_MOVEM_MOVE16); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_NO_FETCH, GENA_MOVEM_MOVE16); comprintf("\tmov_l_rr(src,srca);\n"); comprintf("\tmov_l_rr(dst,dsta);\n"); } /* Align on 16-byte boundaries */ comprintf("\tand_l_ri(src,~15);\n"); comprintf("\tand_l_ri(dst,~15);\n"); if ((opcode & 0xfff8) == 0xf620) { comprintf("\tif (srcreg != dstreg) {\n"); comprintf("\tadd_l_ri(srcreg+8,16);\n"); comprintf("}\n"); comprintf("\tadd_l_ri(dstreg+8,16);\n"); } else if ((opcode & 0xfff8) == 0xf600) comprintf("\tadd_l_ri(srcreg+8,16);\n"); else if ((opcode & 0xfff8) == 0xf608) comprintf("\tadd_l_ri(dstreg+8,16);\n"); #ifdef UAE comprintf("\tif (special_mem || jit_n_addr_unsafe) {\n"); comprintf("\t\tint tmp=scratchie;\n"); comprintf("\tscratchie+=4;\n" "\treadlong(src,tmp,scratchie);\n" "\twritelong_clobber(dst,tmp,scratchie);\n" "\tadd_l_ri(src,4);\n" "\tadd_l_ri(dst,4);\n" "\treadlong(src,tmp,scratchie);\n" "\twritelong_clobber(dst,tmp,scratchie);\n" "\tadd_l_ri(src,4);\n" "\tadd_l_ri(dst,4);\n" "\treadlong(src,tmp,scratchie);\n" "\twritelong_clobber(dst,tmp,scratchie);\n" "\tadd_l_ri(src,4);\n" "\tadd_l_ri(dst,4);\n" "\treadlong(src,tmp,scratchie);\n" "\twritelong_clobber(dst,tmp,scratchie);\n"); comprintf("\t} else\n"); #endif start_brace(); comprintf("\tint tmp=scratchie;\n"); comprintf("\tscratchie+=4;\n" "\tget_n_addr(src,src,scratchie);\n" "\tget_n_addr(dst,dst,scratchie);\n" "\tmov_l_rR(tmp+0,src,0);\n" "\tmov_l_rR(tmp+1,src,4);\n" "\tmov_l_rR(tmp+2,src,8);\n" "\tmov_l_rR(tmp+3,src,12);\n" "\tmov_l_Rr(dst,tmp+0,0);\n" "\tforget_about(tmp+0);\n" "\tmov_l_Rr(dst,tmp+1,4);\n" "\tforget_about(tmp+1);\n" "\tmov_l_Rr(dst,tmp+2,8);\n" "\tforget_about(tmp+2);\n" "\tmov_l_Rr(dst,tmp+3,12);\n"); close_brace(); } static void genmovemel(uae_u16 opcode) { comprintf("\tuae_u16 mask = %s;\n", gen_nextiword()); comprintf("\tint native=scratchie++;\n"); comprintf("\tint i;\n"); comprintf("\tsigned char offset=0;\n"); genamode(table68k[opcode].dmode, "dstreg", table68k[opcode].size, "src", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_NO_INC); #ifdef UAE if (table68k[opcode].size == sz_long) comprintf("\tif (1 && !special_mem && !jit_n_addr_unsafe) {\n"); else comprintf("\tif (1 && !special_mem && !jit_n_addr_unsafe) {\n"); #endif /* Fast but unsafe... */ comprintf("\tget_n_addr(srca,native,scratchie);\n"); comprintf("\tfor (i=0;i<16;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\tmov_l_rR(i,native,offset);\n" "\t\t\tmid_bswap_32(i);\n" "\t\t\toffset+=4;\n"); break; case sz_word: comprintf("\t\t\tmov_w_rR(i,native,offset);\n" "\t\t\tmid_bswap_16(i);\n" "\t\t\tsign_extend_16_rr(i,i);\n" "\t\t\toffset+=2;\n"); break; default: assert(0); } comprintf("\t\t}\n"); comprintf("\t}\n"); if (table68k[opcode].dmode == Aipi) { comprintf("\t\t\tlea_l_brr(8+dstreg,srca,offset);\n"); } /* End fast but unsafe. */ #ifdef UAE comprintf("\t} else {\n"); comprintf("\t\tint tmp=scratchie++;\n"); comprintf("\t\tmov_l_rr(tmp,srca);\n"); comprintf("\t\tfor (i=0;i<16;i++) {\n" "\t\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\t\treadlong(tmp,i,scratchie);\n" "\t\t\t\tadd_l_ri(tmp,4);\n"); break; case sz_word: comprintf("\t\t\t\treadword(tmp,i,scratchie);\n" "\t\t\t\tsign_extend_16_rr(i,i);\n" "\t\t\t\tadd_l_ri(tmp,2);\n"); break; default: assert(0); } comprintf("\t\t\t}\n"); comprintf("\t\t}\n"); if (table68k[opcode].dmode == Aipi) { comprintf("\t\tmov_l_rr(8+dstreg,tmp);\n"); } comprintf("\t}\n"); #endif } static void genmovemle(uae_u16 opcode) { comprintf("\tuae_u16 mask = %s;\n", gen_nextiword()); comprintf("\tint native=scratchie++;\n"); comprintf("\tint i;\n"); comprintf("\tint tmp=scratchie++;\n"); comprintf("\tsigned char offset=0;\n"); genamode(table68k[opcode].dmode, "dstreg", table68k[opcode].size, "src", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_NO_INC); #ifdef UAE /* *Sigh* Some clever geek realized that the fastest way to copy a buffer from main memory to the gfx card is by using movmle. Good on her, but unfortunately, gfx mem isn't "real" mem, and thus that act of cleverness means that movmle must pay attention to special_mem, or Genetic Species is a rather boring-looking game ;-) */ if (table68k[opcode].dmode != Apdi) { comprintf("\tif (1 && !special_mem && !jit_n_addr_unsafe) {\n"); } else { // if Apdi and dstreg is included with mask: use indirect mode. comprintf("\tif (1 && !special_mem && !jit_n_addr_unsafe && !(mask & (1 << (7 - dstreg)))) {\n"); } #endif comprintf("\tget_n_addr(srca,native,scratchie);\n"); if (table68k[opcode].dmode != Apdi) { comprintf("\tfor (i=0;i<16 && mask;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\tmov_l_rr(tmp,i);\n" "\t\t\tmid_bswap_32(tmp);\n" "\t\t\tmov_l_Rr(native,tmp,offset);\n" "\t\t\toffset+=4;\n"); break; case sz_word: comprintf("\t\t\tmov_l_rr(tmp,i);\n" "\t\t\tmid_bswap_16(tmp);\n" "\t\t\tmov_w_Rr(native,tmp,offset);\n" "\t\t\toffset+=2;\n"); break; default: assert(0); } } else { /* Pre-decrement */ comprintf("\tfor (i=0;i<16 && mask;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\toffset-=4;\n" "\t\t\tmov_l_rr(tmp,15-i);\n" "\t\t\tmid_bswap_32(tmp);\n" "\t\t\tmov_l_Rr(native,tmp,offset);\n" ); break; case sz_word: comprintf("\t\t\toffset-=2;\n" "\t\t\tmov_l_rr(tmp,15-i);\n" "\t\t\tmid_bswap_16(tmp);\n" "\t\t\tmov_w_Rr(native,tmp,offset);\n" ); break; default: assert(0); } } comprintf("\t\t}\n"); comprintf("\t}\n"); if (table68k[opcode].dmode == Apdi) { comprintf("\t\t\tlea_l_brr(8+dstreg,srca,(uae_s32)offset);\n"); } #ifdef UAE comprintf("\t} else {\n"); if (table68k[opcode].dmode != Apdi) { comprintf("\tmov_l_rr(tmp,srca);\n"); comprintf("\tfor (i=0;i<16 && mask;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\twritelong(tmp,i,scratchie);\n" "\t\t\tadd_l_ri(tmp,4);\n"); break; case sz_word: comprintf("\t\t\twriteword(tmp,i,scratchie);\n" "\t\t\tadd_l_ri(tmp,2);\n"); break; default: assert(0); } } else { /* Pre-decrement */ comprintf("\tfor (i=0;i<16 && mask;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\tsub_l_ri(srca,4);\n" "\t\t\twritelong(srca,15-i,scratchie);\n"); break; case sz_word: comprintf("\t\t\tsub_l_ri(srca,2);\n" "\t\t\twriteword(srca,15-i,scratchie);\n"); break; default: assert(0); } } comprintf("\t\t}\n"); comprintf("\t}\n"); if (table68k[opcode].dmode == Apdi) { comprintf("\t\t\tmov_l_rr(8+dstreg,srca);\n"); } comprintf("\t}\n"); #endif } static void duplicate_carry(void) { comprintf("\tif (needed_flags&FLAG_X) {\n"); comprintf("\tduplicate_carry();\n"); comprintf("}\n"); } typedef enum { flag_logical_noclobber, flag_logical, flag_add, flag_sub, flag_cmp, flag_addx, flag_subx, flag_zn, flag_av, flag_sv, flag_and, flag_or, flag_eor, flag_mov } flagtypes; static void genflags(flagtypes type, wordsizes size, const char *value, const char *src, const char *dst) { if (noflags) { switch (type) { case flag_cmp: comprintf("\tdont_care_flags();\n"); comprintf("/* Weird --- CMP with noflags ;-) */\n"); return; case flag_add: case flag_sub: comprintf("\tdont_care_flags();\n"); { const char *op; switch (type) { case flag_add: op = "add"; break; case flag_sub: op = "sub"; break; default: assert(0); } switch (size) { case sz_byte: comprintf("\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\t%s_l(%s,%s);\n", op, dst, src); break; } return; } break; case flag_and: comprintf("\tdont_care_flags();\n"); switch (size) { case sz_byte: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_8_rr(scratchie,%s);\n", src); comprintf("\tor_l_ri(scratchie,0xffffff00);\n"); comprintf("\tand_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tand_b(%s,%s);\n", dst, src); break; case sz_word: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_16_rr(scratchie,%s);\n", src); comprintf("\tor_l_ri(scratchie,0xffff0000);\n"); comprintf("\tand_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tand_w(%s,%s);\n", dst, src); break; case sz_long: comprintf("\tand_l(%s,%s);\n", dst, src); break; } return; case flag_mov: comprintf("\tdont_care_flags();\n"); switch (size) { case sz_byte: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_8_rr(scratchie,%s);\n", src); comprintf("\tand_l_ri(%s,0xffffff00);\n", dst); comprintf("\tor_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tmov_b_rr(%s,%s);\n", dst, src); break; case sz_word: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_16_rr(scratchie,%s);\n", src); comprintf("\tand_l_ri(%s,0xffff0000);\n", dst); comprintf("\tor_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tmov_w_rr(%s,%s);\n", dst, src); break; case sz_long: comprintf("\tmov_l_rr(%s,%s);\n", dst, src); break; } return; case flag_or: case flag_eor: comprintf("\tdont_care_flags();\n"); start_brace(); { const char *op; switch (type) { case flag_or: op = "or"; break; case flag_eor: op = "xor"; break; default: assert(0); } switch (size) { case sz_byte: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_8_rr(scratchie,%s);\n", src); comprintf("\t%s_l(%s,scratchie);\n", op, dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_16_rr(scratchie,%s);\n", src); comprintf("\t%s_l(%s,scratchie);\n", op, dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\t%s_l(%s,%s);\n", op, dst, src); break; } close_brace(); return; } case flag_addx: case flag_subx: comprintf("\tdont_care_flags();\n"); { const char *op; switch (type) { case flag_addx: op = "adc"; break; case flag_subx: op = "sbb"; break; default: assert(0); } comprintf("\trestore_carry();\n"); /* Reload the X flag into C */ switch (size) { case sz_byte: comprintf("\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\t%s_l(%s,%s);\n", op, dst, src); break; } return; } break; default: return; } } /* Need the flags, but possibly not all of them */ switch (type) { case flag_logical_noclobber: failure; /* fall through */ case flag_and: case flag_or: case flag_eor: comprintf("\tdont_care_flags();\n"); start_brace(); { const char *op; switch (type) { case flag_and: op = "and"; break; case flag_or: op = "or"; break; case flag_eor: op = "xor"; break; default: assert(0); } switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\tstart_needflags();\n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tstart_needflags();\n" "\t%s_l(%s,%s);\n", op, dst, src); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); close_brace(); return; } case flag_mov: comprintf("\tdont_care_flags();\n"); start_brace(); { switch (size) { case sz_byte: comprintf("\tif (%s!=%s) {\n", src, dst); comprintf("\tmov_b_ri(%s,0);\n" "\tstart_needflags();\n", dst); comprintf("\tor_b(%s,%s);\n", dst, src); comprintf("\t} else {\n"); comprintf("\tmov_b_rr(%s,%s);\n", dst, src); comprintf("\ttest_b_rr(%s,%s);\n", dst, dst); comprintf("\t}\n"); break; case sz_word: comprintf("\tif (%s!=%s) {\n", src, dst); comprintf("\tmov_w_ri(%s,0);\n" "\tstart_needflags();\n", dst); comprintf("\tor_w(%s,%s);\n", dst, src); comprintf("\t} else {\n"); comprintf("\tmov_w_rr(%s,%s);\n", dst, src); comprintf("\ttest_w_rr(%s,%s);\n", dst, dst); comprintf("\t}\n"); break; case sz_long: comprintf("\tif (%s!=%s) {\n", src, dst); comprintf("\tmov_l_ri(%s,0);\n" "\tstart_needflags();\n", dst); comprintf("\tor_l(%s,%s);\n", dst, src); comprintf("\t} else {\n"); comprintf("\tmov_l_rr(%s,%s);\n", dst, src); comprintf("\ttest_l_rr(%s,%s);\n", dst, dst); comprintf("\t}\n"); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); close_brace(); return; } case flag_logical: comprintf("\tdont_care_flags();\n"); start_brace(); switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\ttest_b_rr(%s,%s);\n", value, value); break; case sz_word: comprintf("\tstart_needflags();\n" "\ttest_w_rr(%s,%s);\n", value, value); break; case sz_long: comprintf("\tstart_needflags();\n" "\ttest_l_rr(%s,%s);\n", value, value); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); close_brace(); return; case flag_add: case flag_sub: case flag_cmp: comprintf("\tdont_care_flags();\n"); { const char *op; switch (type) { case flag_add: op = "add"; break; case flag_sub: op = "sub"; break; case flag_cmp: op = "cmp"; break; default: assert(0); } switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\tstart_needflags();\n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tstart_needflags();\n" "\t%s_l(%s,%s);\n", op, dst, src); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); if (type != flag_cmp) { duplicate_carry(); } comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); return; } case flag_addx: case flag_subx: uses_cmov; comprintf("\tdont_care_flags();\n"); { const char *op; switch (type) { case flag_addx: op = "adc"; break; case flag_subx: op = "sbb"; break; default: assert(0); } start_brace(); comprintf("\tint zero=scratchie++;\n" "\tint one=scratchie++;\n" "\tif (needed_flags&FLAG_Z) {\n" "\tmov_l_ri(zero,0);\n" "\tmov_l_ri(one,-1);\n" "\tmake_flags_live();\n" "\tcmov_l_rr(zero,one,%d);\n" "\t}\n", NATIVE_CC_NE); comprintf("\trestore_carry();\n"); /* Reload the X flag into C */ switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\tstart_needflags();\n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tstart_needflags();\n" "\t%s_l(%s,%s);\n", op, dst, src); break; } comprintf("\tlive_flags();\n"); comprintf("\tif (needed_flags&FLAG_Z) {\n"); comprintf("\tcmov_l_rr(zero,one,%d);\n", NATIVE_CC_NE); comprintf("\tset_zero(zero, one);\n"); /* No longer need one */ comprintf("\tlive_flags();\n"); comprintf("\t}\n"); comprintf("\tend_needflags();\n"); duplicate_carry(); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); return; } default: failure; break; } } static int /* returns zero for success, non-zero for failure */ gen_opcode(unsigned int opcode) { struct instr *curi = table68k + opcode; const char *ssize = NULL; insn_n_cycles = 2; global_failure = 0; long_opcode = 0; global_isjump = 0; global_iscjump = 0; global_isaddx = 0; global_cmov = 0; global_fpu = 0; global_mayfail = 0; hack_opcode = opcode; endstr[0] = 0; start_brace(); comprintf("\tuae_u8 scratchie=S1;\n"); switch (curi->plev) { case 0: /* not privileged */ break; case 1: /* unprivileged only on 68000 */ if (cpu_level == 0) break; if (next_cpu_level < 0) next_cpu_level = 0; /* fall through */ case 2: /* priviledged */ failure; /* Easy ones first */ break; case 3: /* privileged if size == word */ if (curi->size == sz_byte) break; failure; break; } switch (curi->size) { case sz_byte: ssize = "b"; break; case sz_word: ssize = "w"; break; case sz_long: ssize = "l"; break; default: assert(0); } (void)ssize; switch (curi->mnemo) { case i_OR: case i_AND: case i_EOR: #ifdef DISABLE_I_OR_AND_EOR failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); switch (curi->mnemo) { case i_OR: genflags(flag_or, curi->size, "", "src", "dst"); break; case i_AND: genflags(flag_and, curi->size, "", "src", "dst"); break; case i_EOR: genflags(flag_eor, curi->size, "", "src", "dst"); break; } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; case i_ORSR: case i_EORSR: failure; isjump; break; case i_ANDSR: failure; isjump; break; case i_SUB: #ifdef DISABLE_I_SUB failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genflags(flag_sub, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; case i_SUBA: #ifdef DISABLE_I_SUBA failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", sz_long, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint tmp=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(tmp,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(tmp,src);\n"); break; case sz_long: comprintf("\ttmp=src;\n"); break; default: assert(0); } comprintf("\tsub_l(dst,tmp);\n"); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); break; case i_SUBX: #ifdef DISABLE_I_SUBX failure; #endif isaddx; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genflags(flag_subx, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; case i_SBCD: failure; /* I don't think so! */ break; case i_ADD: #ifdef DISABLE_I_ADD failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genflags(flag_add, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; case i_ADDA: #ifdef DISABLE_I_ADDA failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", sz_long, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint tmp=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(tmp,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(tmp,src);\n"); break; case sz_long: comprintf("\ttmp=src;\n"); break; default: assert(0); } comprintf("\tadd_l(dst,tmp);\n"); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); break; case i_ADDX: #ifdef DISABLE_I_ADDX failure; #endif isaddx; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); genflags(flag_addx, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; case i_ABCD: failure; /* No BCD maths for me.... */ break; case i_NEG: #ifdef DISABLE_I_NEG failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0);\n"); genflags(flag_sub, curi->size, "", "src", "dst"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); break; case i_NEGX: #ifdef DISABLE_I_NEGX failure; #endif isaddx; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0);\n"); genflags(flag_subx, curi->size, "", "src", "dst"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); break; case i_NBCD: failure; /* Nope! */ break; case i_CLR: #ifdef DISABLE_I_CLR failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0);\n"); genflags(flag_logical, curi->size, "dst", "", ""); genastore("dst", curi->smode, "srcreg", curi->size, "src"); break; case i_NOT: #ifdef DISABLE_I_NOT failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0xffffffff);\n"); genflags(flag_eor, curi->size, "", "src", "dst"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); break; case i_TST: #ifdef DISABLE_I_TST failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genflags(flag_logical, curi->size, "src", "", ""); break; case i_BCHG: case i_BCLR: case i_BSET: case i_BTST: #ifdef DISABLE_I_BCHG_BCLR_BSET_BTST failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint s=scratchie++;\n" "\tint tmp=scratchie++;\n" "\tmov_l_rr(s,src);\n"); if (curi->size == sz_byte) comprintf("\tand_l_ri(s,7);\n"); else comprintf("\tand_l_ri(s,31);\n"); { const char *op; int need_write = 1; switch (curi->mnemo) { case i_BCHG: op = "btc"; break; case i_BCLR: op = "btr"; break; case i_BSET: op = "bts"; break; case i_BTST: op = "bt"; need_write = 0; break; default: op = ""; assert(0); } comprintf("\t%s_l_rr(dst,s);\n" /* Answer now in C */ "\tsbb_l(s,s);\n" /* s is 0 if bit was 0, -1 otherwise */ "\tmake_flags_live();\n" /* Get the flags back */ "\tdont_care_flags();\n", op); if (!noflags) { comprintf("\tstart_needflags();\n" "\tset_zero(s,tmp);\n" "\tlive_flags();\n" "\tend_needflags();\n"); } if (need_write) genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); } break; case i_CMPM: case i_CMP: #ifdef DISABLE_I_CMPM_CMP failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); genflags(flag_cmp, curi->size, "", "src", "dst"); break; case i_CMPA: #ifdef DISABLE_I_CMPA failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", sz_long, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint tmps=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(tmps,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(tmps,src);\n"); break; case sz_long: comprintf("tmps=src;\n"); break; default: assert(0); } genflags(flag_cmp, sz_long, "", "tmps", "dst"); break; /* The next two are coded a little unconventional, but they are doing * weird things... */ case i_MVPRM: isjump; failure; break; case i_MVPMR: isjump; failure; break; case i_MOVE: #ifdef DISABLE_I_MOVE failure; #endif switch (curi->dmode) { case Dreg: case Areg: genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); genflags(flag_mov, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; default: /* It goes to memory, not a register */ genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); genflags(flag_logical, curi->size, "src", "", ""); genastore("src", curi->dmode, "dstreg", curi->size, "dst"); break; } break; case i_MOVEA: #ifdef DISABLE_I_MOVEA failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint tmps=scratchie++;\n"); switch (curi->size) { case sz_word: comprintf("\tsign_extend_16_rr(dst,src);\n"); break; case sz_long: comprintf("\tmov_l_rr(dst,src);\n"); break; default: assert(0); } genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); break; case i_MVSR2: isjump; failure; break; case i_MV2SR: isjump; failure; break; case i_SWAP: #ifdef DISABLE_I_SWAP failure; #endif genamode(curi->smode, "srcreg", sz_long, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); comprintf("\tdont_care_flags();\n"); comprintf("\trol_l_ri(src,16);\n"); genflags(flag_logical, sz_long, "src", "", ""); genastore("src", curi->smode, "srcreg", sz_long, "src"); break; case i_EXG: #ifdef DISABLE_I_EXG failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint tmp=scratchie++;\n" "\tmov_l_rr(tmp,src);\n"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); break; case i_EXT: #ifdef DISABLE_I_EXT failure; #endif genamode(curi->smode, "srcreg", sz_long, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); comprintf("\tdont_care_flags();\n"); start_brace(); switch (curi->size) { case sz_byte: comprintf("\tint dst = src;\n" "\tsign_extend_8_rr(src,src);\n"); break; case sz_word: comprintf("\tint dst = scratchie++;\n" "\tsign_extend_8_rr(dst,src);\n"); break; case sz_long: comprintf("\tint dst = src;\n" "\tsign_extend_16_rr(src,src);\n"); break; default: assert(0); } genflags(flag_logical, curi->size == sz_word ? sz_word : sz_long, "dst", "", ""); genastore("dst", curi->smode, "srcreg", curi->size == sz_word ? sz_word : sz_long, "src"); break; case i_MVMEL: #ifdef DISABLE_I_MVMEL failure; #endif genmovemel(opcode); break; case i_MVMLE: #ifdef DISABLE_I_MVMLE failure; #endif genmovemle(opcode); break; case i_TRAP: #ifdef DISABLE_I_TRAP failure; #endif isjump; mayfail; start_brace(); comprintf(" int trapno = srcreg + 32;\n"); gen_set_fault_pc(); make_sr(); comprintf(" compemu_enter_super(sr);\n"); comprintf(" compemu_exc_make_frame(0, sr, ret, trapno, scratchie);\n"); comprintf(" forget_about(ret);\n"); /* m68k_setpc (get_long (regs.vbr + 4*nr)); */ start_brace(); comprintf(" int srca = scratchie++;\n"); comprintf(" mov_l_rm(srca, (uintptr)®s.vbr);\n"); comprintf(" mov_l_brR(srca, srca, MEMBaseDiff + trapno * 4); mid_bswap_32(srca);\n"); comprintf(" mov_l_mr(JITPTR ®s.pc, srca);\n"); comprintf(" get_n_addr_jmp(srca, PC_P, scratchie);\n"); comprintf(" mov_l_mr(JITPTR ®s.pc_oldp, PC_P);\n"); gen_update_next_handler(); disasm_this_inst(); /* for debugging only */ /* * this currently deactivates this feature, since it does not work yet */ failure; break; case i_MVR2USP: isjump; failure; break; case i_MVUSP2R: isjump; failure; break; case i_RESET: isjump; failure; break; case i_NOP: break; case i_STOP: isjump; failure; break; case i_RTE: isjump; failure; break; case i_RTD: #ifdef DISABLE_I_RTD failure; #endif genamode(curi->smode, "srcreg", curi->size, "offs", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); /* offs is constant */ comprintf("\tadd_l_ri(offs,4);\n"); start_brace(); comprintf("\tint newad=scratchie++;\n" "\treadlong(SP_REG,newad,scratchie);\n" "\tmov_l_mr(JITPTR ®s.pc,newad);\n" "\tget_n_addr_jmp(newad,PC_P,scratchie);\n" "\tmov_l_mr(JITPTR ®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n" "\tadd_l(SP_REG,offs);\n"); gen_update_next_handler(); isjump; break; case i_LINK: #ifdef DISABLE_I_LINK failure; #endif genamode(curi->smode, "srcreg", sz_long, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "offs", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); comprintf("\tsub_l_ri(SP_REG,4);\n" "\twritelong_clobber(SP_REG,src,scratchie);\n" "\tmov_l_rr(src,SP_REG);\n"); if (curi->size == sz_word) comprintf("\tsign_extend_16_rr(offs,offs);\n"); comprintf("\tadd_l(SP_REG,offs);\n"); genastore("src", curi->smode, "srcreg", sz_long, "src"); break; case i_UNLK: #ifdef DISABLE_I_UNLK failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); comprintf("\tmov_l_rr(SP_REG,src);\n" "\treadlong(SP_REG,src,scratchie);\n" "\tadd_l_ri(SP_REG,4);\n"); genastore("src", curi->smode, "srcreg", curi->size, "src"); break; case i_RTS: #ifdef DISABLE_I_RTS failure; #endif comprintf("\tint newad=scratchie++;\n" "\treadlong(SP_REG,newad,scratchie);\n" "\tmov_l_mr(JITPTR ®s.pc,newad);\n" "\tget_n_addr_jmp(newad,PC_P,scratchie);\n" "\tmov_l_mr(JITPTR ®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n" "\tlea_l_brr(SP_REG,SP_REG,4);\n"); gen_update_next_handler(); isjump; break; case i_TRAPV: isjump; failure; break; case i_RTR: isjump; failure; break; case i_JSR: #ifdef DISABLE_I_JSR failure; #endif isjump; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_NO_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tuae_u32 retadd=(uae_u32)(start_pc+((char *)comp_pc_p-(char *)start_pc_p)+m68k_pc_offset);\n"); comprintf("\tint ret=scratchie++;\n" "\tmov_l_ri(ret,retadd);\n" "\tsub_l_ri(SP_REG,4);\n" "\twritelong_clobber(SP_REG,ret,scratchie);\n"); comprintf("\tmov_l_mr(JITPTR ®s.pc,srca);\n" "\tget_n_addr_jmp(srca,PC_P,scratchie);\n" "\tmov_l_mr(JITPTR ®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n"); gen_update_next_handler(); break; case i_JMP: #ifdef DISABLE_I_JMP failure; #endif isjump; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_NO_FETCH, GENA_MOVEM_DO_INC); comprintf("\tmov_l_mr(JITPTR ®s.pc,srca);\n" "\tget_n_addr_jmp(srca,PC_P,scratchie);\n" "\tmov_l_mr(JITPTR ®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n"); gen_update_next_handler(); break; case i_BSR: #ifdef DISABLE_I_BSR failure; #endif is_const_jump; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tuae_u32 retadd=(uae_u32)(start_pc+((char *)comp_pc_p-(char *)start_pc_p)+m68k_pc_offset);\n"); comprintf("\tint ret=scratchie++;\n" "\tmov_l_ri(ret,retadd);\n" "\tsub_l_ri(SP_REG,4);\n" "\twritelong_clobber(SP_REG,ret,scratchie);\n"); comprintf("\tadd_l_ri(src,m68k_pc_offset_thisinst+2);\n"); comprintf("\tm68k_pc_offset=0;\n"); comprintf("\tadd_l(PC_P,src);\n"); comprintf("\tcomp_pc_p=(uae_u8*)(uintptr)get_const(PC_P);\n"); gen_update_next_handler(); break; case i_Bcc: #ifdef DISABLE_I_BCC failure; #endif comprintf("\tuae_u32 v,v1,v2;\n"); genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); /* That source is an immediate, so we can clobber it with abandon */ switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(src,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(src,src);\n"); break; case sz_long: break; } comprintf("\tsub_l_ri(src,m68k_pc_offset-m68k_pc_offset_thisinst-2);\n"); /* Leave the following as "add" --- it will allow it to be optimized away due to src being a constant ;-) */ comprintf("\tadd_l_ri(src,JITPTR comp_pc_p);\n"); comprintf("\tmov_l_ri(PC_P,JITPTR comp_pc_p);\n"); /* Now they are both constant. Might as well fold in m68k_pc_offset */ comprintf("\tadd_l_ri(src,m68k_pc_offset);\n"); comprintf("\tadd_l_ri(PC_P,m68k_pc_offset);\n"); comprintf("\tm68k_pc_offset=0;\n"); if (curi->cc >= 2) { comprintf("\tuae_u32 v1=get_const(PC_P);\n" "\tuae_u32 v2=get_const(src);\n" "\tregister_branch(v1,v2,%d);\n", cond_codes[curi->cc]); comprintf("\tmake_flags_live();\n"); /* Load the flags */ isjump; } else { is_const_jump; } switch (curi->cc) { case 0: /* Unconditional jump */ comprintf("\tmov_l_rr(PC_P,src);\n"); comprintf("\tcomp_pc_p=(uae_u8*)(uintptr)get_const(PC_P);\n"); break; case 1: break; /* This is silly! */ case 8: failure; break; /* Work out details! FIXME */ case 9: failure; break; /* Not critical, though! */ case 2: case 3: case 4: case 5: case 6: case 7: case 10: case 11: case 12: case 13: case 14: case 15: break; default: assert(0); } break; case i_LEA: #ifdef DISABLE_I_LEA failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_NO_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); genastore("srca", curi->dmode, "dstreg", curi->size, "dst"); break; case i_PEA: #ifdef DISABLE_I_PEA failure; #endif if (table68k[opcode].smode == Areg || table68k[opcode].smode == Aind || table68k[opcode].smode == Aipi || table68k[opcode].smode == Apdi || table68k[opcode].smode == Ad16 || table68k[opcode].smode == Ad8r) comprintf("if (srcreg==7) dodgy=1;\n"); genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_NO_FETCH, GENA_MOVEM_DO_INC); genamode(Apdi, "7", sz_long, "dst", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); genastore("srca", Apdi, "7", sz_long, "dst"); break; case i_DBcc: #ifdef DISABLE_I_DBCC failure; #endif isjump; uses_cmov; genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "offs", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); /* That offs is an immediate, so we can clobber it with abandon */ switch (curi->size) { case sz_word: comprintf("\tsign_extend_16_rr(offs,offs);\n"); break; default: assert(0); /* Seems this only comes in word flavour */ } comprintf("\tsub_l_ri(offs,m68k_pc_offset-m68k_pc_offset_thisinst-2);\n"); comprintf("\tadd_l_ri(offs,JITPTR comp_pc_p);\n"); /* New PC, once the offset_68k is * also added */ /* Let's fold in the m68k_pc_offset at this point */ comprintf("\tadd_l_ri(offs,m68k_pc_offset);\n"); comprintf("\tadd_l_ri(PC_P,m68k_pc_offset);\n"); comprintf("\tm68k_pc_offset=0;\n"); start_brace(); comprintf("\tint nsrc=scratchie++;\n"); if (curi->cc >= 2) { comprintf("\tmake_flags_live();\n"); /* Load the flags */ } assert(curi->size == sz_word); switch (curi->cc) { case 0: /* This is an elaborate nop? */ break; case 1: comprintf("\tstart_needflags();\n"); comprintf("\tsub_w_ri(src,1);\n"); comprintf("\tend_needflags();\n"); start_brace(); comprintf("\tuae_u32 v2;\n" "\tuae_u32 v1=get_const(PC_P);\n"); comprintf("\tv2=get_const(offs);\n" "\tregister_branch(v1,v2,%d);\n", NATIVE_CC_CC); break; case 8: failure; break; /* Work out details! FIXME */ case 9: failure; break; /* Not critical, though! */ case 2: case 3: case 4: case 5: case 6: case 7: case 10: case 11: case 12: case 13: case 14: case 15: comprintf("\tmov_l_rr(nsrc,src);\n"); comprintf("\tlea_l_brr(scratchie,src,(uae_s32)-1);\n" "\tmov_w_rr(src,scratchie);\n"); comprintf("\tcmov_l_rr(offs,PC_P,%d);\n", cond_codes[curi->cc]); comprintf("\tcmov_l_rr(src,nsrc,%d);\n", cond_codes[curi->cc]); /* OK, now for cc=true, we have src==nsrc and offs==PC_P, so whether we move them around doesn't matter. However, if cc=false, we have offs==jump_pc, and src==nsrc-1 */ comprintf("\tstart_needflags();\n"); comprintf("\ttest_w_rr(nsrc,nsrc);\n"); comprintf("\tend_needflags();\n"); comprintf("\tcmov_l_rr(PC_P,offs,%d);\n", NATIVE_CC_NE); break; default: assert(0); } genastore("src", curi->smode, "srcreg", curi->size, "src"); gen_update_next_handler(); break; case i_Scc: #ifdef DISABLE_I_SCC failure; #endif genamode(curi->smode, "srcreg", curi->size, "src", GENA_GETV_FETCH_ALIGN, GENA_MOVEM_DO_INC); start_brace(); comprintf("\tint val = scratchie++;\n"); /* We set val to 0 if we really should use 255, and to 1 for real 0 */ switch (curi->cc) { case 0: /* Unconditional set */ comprintf("\tmov_l_ri(val,0);\n"); break; case 1: /* Unconditional not-set */ comprintf("\tmov_l_ri(val,1);\n"); break; case 8: failure; break; /* Work out details! FIXME */ case 9: failure; break; /* Not critical, though! */ case 2: case 3: case 4: case 5: case 6: case 7: case 10: case 11: case 12: case 13: case 14: case 15: comprintf("\tmake_flags_live();\n"); /* Load the flags */ /* All condition codes can be inverted by changing the LSB */ comprintf("\tsetcc(val,%d);\n", cond_codes[curi->cc] ^ 1); break; default: assert(0); } comprintf("\tsub_b_ri(val,1);\n"); genastore("val", curi->smode, "srcreg", curi->size, "src"); break; case i_DIVU: isjump; failure; break; case i_DIVS: isjump; failure; break; case i_MULU: #ifdef DISABLE_I_MULU failure; #endif comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", sz_word, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", sz_word, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); /* To do 16x16 unsigned multiplication, we actually use 32x32 signed, and zero-extend the registers first. That solves the problem of MUL needing dedicated registers on the x86 */ comprintf("\tzero_extend_16_rr(scratchie,src);\n" "\tzero_extend_16_rr(dst,dst);\n" "\timul_32_32(dst,scratchie);\n"); genflags(flag_logical, sz_long, "dst", "", ""); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); break; case i_MULS: #ifdef DISABLE_I_MULS failure; #endif comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", sz_word, "src", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", sz_word, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); comprintf("\tsign_extend_16_rr(scratchie,src);\n" "\tsign_extend_16_rr(dst,dst);\n" "\timul_32_32(dst,scratchie);\n"); genflags(flag_logical, sz_long, "dst", "", ""); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); break; case i_CHK: isjump; failure; break; case i_CHK2: isjump; failure; break; case i_ASR: #ifdef DISABLE_I_ASR failure; #endif mayfail; if (curi->smode == Dreg) { comprintf( " if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "data", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); if (!noflags) comprintf("\tstart_needflags();\n"); if (curi->smode != immi) { uses_cmov; start_brace(); comprintf("\tint cdata = scratchie++;\n"); comprintf("\tint tmpcnt = scratchie++;\n"); comprintf("\tint setval = scratchie++;\n"); if (!noflags) { comprintf("\tint odata = scratchie++;\n"); } comprintf("\tmov_l_ri(cdata, 0);\n"); comprintf("\tmov_l_ri(setval, 0xffffffff);\n"); // if high bit = 0: setval = 0x00000000, else setval = 0xffffffff comprintf("\ttest_l_ri(data, 0x%08x);\n", curi->size == sz_byte ? 0x80 : (curi->size == sz_word ? 0x8000 : 0x80000000)); comprintf("\tcmov_l_rr(setval, cdata, NATIVE_CC_EQ);\n"); comprintf("\tmov_l_rr(cdata, setval);\n"); if (!noflags) { // setval -> odata comprintf("\tmov_l_rr(odata, setval);\n"); } comprintf("\tmov_l_rr(tmpcnt, cnt);\n"); comprintf("\tand_l_ri(tmpcnt, 63);\n"); if (!noflags) { // shift == 0: tmpcnt (0) -> odata (C is always zero) comprintf("\tcmov_l_rr(odata, tmpcnt, NATIVE_CC_EQ);\n"); } switch (curi->size) { case sz_byte: comprintf("\tcmp_b_ri(tmpcnt, 0x08);\n"); // shift > 8: setval -> cdata comprintf("\tcmov_l_rr(cdata, setval, NATIVE_CC_HI);\n"); // shift <= 8: data -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshra_b_rr(cdata, tmpcnt);\n"); break; case sz_word: comprintf("\tcmp_b_ri(tmpcnt, 0x10);\n"); // shift > 16: setval -> cdata comprintf("\tcmov_l_rr(cdata, setval, NATIVE_CC_HI);\n"); // shift <= 16: data -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshra_w_rr(cdata, tmpcnt);\n"); break; case sz_long: comprintf("\tcmp_b_ri(tmpcnt, 0x20);\n"); // shift > 32: setval -> cdata comprintf("\tcmov_l_rr(cdata, setval, NATIVE_CC_HI);\n"); // shift == 32? 0 -> cdata (x86 masks count by 31, 680x0 uses mask 63) comprintf("\tcmov_l_rr(data, setval, NATIVE_CC_EQ);\n"); // shift <= 32: data -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshra_l_rr(cdata, tmpcnt);\n"); break; default: assert(0); } /* Result of shift is now in data. */ } else { switch (curi->size) { case sz_byte: comprintf("\tshra_b_ri(data,srcreg);\n"); break; case sz_word: comprintf("\tshra_w_ri(data,srcreg);\n"); break; case sz_long: comprintf("\tshra_l_ri(data,srcreg);\n"); break; default: assert(0); } } /* And create the flags */ if (!noflags) { comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); if (curi->smode != immi) { comprintf("\tsetcc_for_cntzero(tmpcnt, cdata, odata, 0, %d, 0);\n", curi->size); } else { comprintf("\tduplicate_carry();\n"); } comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } if (curi->smode != immi) { switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data, cdata);\n"); break; case sz_word: comprintf("\tmov_w_rr(data, cdata);\n"); break; case sz_long: comprintf("\tmov_l_rr(data, cdata);\n"); break; default: assert(0); } } genastore("data", curi->dmode, "dstreg", curi->size, "data"); break; case i_ASL: #ifdef DISABLE_I_ASL failure; #endif mayfail; if (curi->smode == Dreg) { comprintf( " if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); start_brace(); } comprintf("\tdont_care_flags();\n"); /* Except for the handling of the V flag, this is identical to LSL. The handling of V is, uhm, unpleasant, so if it's needed, let the normal emulation handle it. Shoulders of giants kinda thing ;-) */ comprintf( " if (needed_flags & FLAG_V) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "data", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); if (!noflags) comprintf("\tstart_needflags();\n"); if (curi->smode != immi) { uses_cmov; start_brace(); comprintf("\tint cdata = scratchie++;\n"); comprintf("\tint tmpcnt = scratchie++;\n"); if (!noflags) { comprintf("\tint odata = scratchie++;\n"); comprintf("\tmov_l_rr(odata, data);\n"); } comprintf("\tmov_l_ri(cdata, 0);\n"); comprintf("\tmov_l_rr(tmpcnt, cnt);\n"); comprintf("\tand_l_ri(tmpcnt, 63);\n"); if (!noflags) { // shift == 0? cdata (0) -> odata (C is always zero) comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_EQ);\n"); } switch (curi->size) { case sz_byte: // shift > 8? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x08);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift <= 8? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshll_b_rr(cdata, tmpcnt);\n"); break; case sz_word: // shift > 16? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x10);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift <= 16? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshll_w_rr(cdata, tmpcnt);\n"); break; case sz_long: // shift > 32? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x20);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift == 32? 0 -> cdata (x86 masks count by 31, 680x0 uses mask 63) comprintf("\tcmov_l_rr(data, cdata, NATIVE_CC_EQ);\n"); // shift <= 32? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshll_l_rr(cdata, tmpcnt);\n"); break; default: assert(0); } /* Result of shift is now in data. */ } else { switch (curi->size) { case sz_byte: comprintf("\tshll_b_ri(data,srcreg);\n"); break; case sz_word: comprintf("\tshll_w_ri(data,srcreg);\n"); break; case sz_long: comprintf("\tshll_l_ri(data,srcreg);\n"); break; default: assert(0); } } /* And create the flags */ if (!noflags) { comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); if (curi->smode != immi) { comprintf("\tsetcc_for_cntzero(tmpcnt, cdata, odata, 0, %d, 1);\n", curi->size); } else { comprintf("\tclear_overflow();\n"); comprintf("\tduplicate_carry();\n"); } comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } if (curi->smode != immi) { switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data, cdata);\n"); break; case sz_word: comprintf("\tmov_w_rr(data, cdata);\n"); break; case sz_long: comprintf("\tmov_l_rr(data, cdata);\n"); break; default: assert(0); } } genastore("data", curi->dmode, "dstreg", curi->size, "data"); break; case i_LSR: #ifdef DISABLE_I_LSR failure; #endif mayfail; if (curi->smode == Dreg) { comprintf( " if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "data", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); if (!noflags) comprintf("\tstart_needflags();\n"); if (curi->smode != immi) { uses_cmov; start_brace(); comprintf("\tint cdata = scratchie++;\n"); comprintf("\tint tmpcnt = scratchie++;\n"); if (!noflags) { comprintf("\tint odata = scratchie++;\n"); comprintf("\tmov_l_rr(odata, data);\n"); } comprintf("\tmov_l_ri(cdata, 0);\n"); comprintf("\tmov_l_rr(tmpcnt, cnt);\n"); comprintf("\tand_l_ri(tmpcnt, 63);\n"); if (!noflags) { // shift == 0? cdata (0) -> odata (C is always zero) comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_EQ);\n"); } switch (curi->size) { case sz_byte: // shift > 8? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x08);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift <= 8? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshrl_b_rr(cdata, tmpcnt);\n"); break; case sz_word: // shift > 16? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x10);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift <= 16? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshrl_w_rr(cdata, tmpcnt);\n"); break; case sz_long: // shift > 32? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x20);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift == 32? 0 -> cdata (x86 masks count by 31, 680x0 uses mask 63) comprintf("\tcmov_l_rr(data, cdata, NATIVE_CC_EQ);\n"); // shift <= 32? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshrl_l_rr(cdata, tmpcnt);\n"); break; default: assert(0); } /* Result of shift is now in data. */ } else { switch (curi->size) { case sz_byte: comprintf("\tshrl_b_ri(data,srcreg);\n"); break; case sz_word: comprintf("\tshrl_w_ri(data,srcreg);\n"); break; case sz_long: comprintf("\tshrl_l_ri(data,srcreg);\n"); break; default: assert(0); } } /* And create the flags */ if (!noflags) { comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); if (curi->smode != immi) { comprintf("\tsetcc_for_cntzero(tmpcnt, cdata, odata, %d, %d, 1);\n", curi->size == sz_byte ? 7 : curi->size == sz_word ? 15 : 31, curi->size); } else { comprintf("\tduplicate_carry();\n"); } comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } if (curi->smode != immi) { switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data, cdata);\n"); break; case sz_word: comprintf("\tmov_w_rr(data, cdata);\n"); break; case sz_long: comprintf("\tmov_l_rr(data, cdata);\n"); break; default: assert(0); } } genastore("data", curi->dmode, "dstreg", curi->size, "data"); break; case i_LSL: #ifdef DISABLE_I_LSL failure; #endif mayfail; if (curi->smode == Dreg) { comprintf( " if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "data", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); if (!noflags) comprintf("\tstart_needflags();\n"); if (curi->smode != immi) { uses_cmov; start_brace(); comprintf("\tint cdata = scratchie++;\n"); comprintf("\tint tmpcnt = scratchie++;\n"); if (!noflags) { comprintf("\tint odata = scratchie++;\n"); comprintf("\tmov_l_rr(odata, data);\n"); } comprintf("\tmov_l_ri(cdata, 0);\n"); comprintf("\tmov_l_rr(tmpcnt, cnt);\n"); comprintf("\tand_l_ri(tmpcnt, 63);\n"); if (!noflags) { // shift == 0? cdata (0) -> odata (C is always zero) comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_EQ);\n"); } switch (curi->size) { case sz_byte: // shift > 8? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x08);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift <= 8? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshll_b_rr(cdata, tmpcnt);\n"); break; case sz_word: // shift > 16? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x10);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift <= 16? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshll_w_rr(cdata, tmpcnt);\n"); break; case sz_long: // shift > 32? 0 -> odata (C is always zero) comprintf("\tcmp_b_ri(tmpcnt, 0x20);\n"); if (!noflags) { comprintf("\tcmov_l_rr(odata, cdata, NATIVE_CC_HI);\n"); } // shift == 32? 0 -> cdata (x86 masks count by 31, 680x0 uses mask 63) comprintf("\tcmov_l_rr(data, cdata, NATIVE_CC_EQ);\n"); // shift <= 32? cdata -> cdata ("normal" shift) comprintf("\tcmov_l_rr(cdata, data, NATIVE_CC_LS);\n"); comprintf("\tshll_l_rr(cdata, tmpcnt);\n"); break; default: assert(0); } /* Result of shift is now in data. */ } else { switch (curi->size) { case sz_byte: comprintf("\tshll_b_ri(data,srcreg);\n"); break; case sz_word: comprintf("\tshll_w_ri(data,srcreg);\n"); break; case sz_long: comprintf("\tshll_l_ri(data,srcreg);\n"); break; default: assert(0); } } /* And create the flags */ if (!noflags) { comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); if (curi->smode != immi) { comprintf("\tsetcc_for_cntzero(tmpcnt, cdata, odata, 0, %d, 1);\n", curi->size); } else { comprintf("\tclear_overflow();\n"); comprintf("\tduplicate_carry();\n"); } comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } if (curi->smode != immi) { switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data, cdata);\n"); break; case sz_word: comprintf("\tmov_w_rr(data, cdata);\n"); break; case sz_long: comprintf("\tmov_l_rr(data, cdata);\n"); break; default: assert(0); } } genastore("data", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROL: #ifdef DISABLE_I_ROL failure; #endif mayfail; if (curi->smode == Dreg) { comprintf( " if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "data", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); switch (curi->size) { case sz_long: comprintf("\trol_l_rr(data,cnt);\n"); break; case sz_word: comprintf("\trol_w_rr(data,cnt);\n"); break; case sz_byte: comprintf("\trol_b_rr(data,cnt);\n"); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tint cdata = scratchie++;\n"); comprintf("\tint zero = scratchie++;\n"); comprintf("\tint tmpcnt = scratchie++;\n"); comprintf("\tmov_l_rr(cdata,data);\n"); comprintf("\tmov_l_rr(tmpcnt,cnt);\n"); comprintf("\tmov_l_ri(zero,0);\n"); // if shift count is zero: C is always cleared comprintf("\tand_l_ri(tmpcnt,63);\n"); comprintf("\tcmov_l_rr(cdata,zero,NATIVE_CC_EQ);\n"); /* * x86 ROL instruction does not set ZF/SF, so we need extra checks here */ comprintf("\tif (needed_flags & FLAG_ZNV) {\n"); switch (curi->size) { case sz_byte: comprintf("\ttest_b_rr(data,data);\n"); break; case sz_word: comprintf("\ttest_w_rr(data,data);\n"); break; case sz_long: comprintf("\ttest_l_rr(data,data);\n"); break; } comprintf("}\n"); comprintf("\tbt_l_ri(cdata,0x00);\n"); /* Set C */ comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROR: #ifdef DISABLE_I_ROR failure; #endif mayfail; if (curi->smode == Dreg) { comprintf( " if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " " RETURN "\n" " }\n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); genamode(curi->dmode, "dstreg", curi->size, "data", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); start_brace(); switch (curi->size) { case sz_long: comprintf("\tror_l_rr(data,cnt);\n"); break; case sz_word: comprintf("\tror_w_rr(data,cnt);\n"); break; case sz_byte: comprintf("\tror_b_rr(data,cnt);\n"); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tint cdata = scratchie++;\n"); comprintf("\tint zero = scratchie++;\n"); comprintf("\tint tmpcnt = scratchie++;\n"); comprintf("\tmov_l_rr(cdata,data);\n"); comprintf("\tmov_l_rr(tmpcnt,cnt);\n"); comprintf("\tmov_l_ri(zero,0);\n"); // if shift count is zero: C is always cleared comprintf("\tand_l_ri(tmpcnt,63);\n"); comprintf("\tcmov_l_rr(cdata,zero,NATIVE_CC_EQ);\n"); /* * x86 ROR instruction does not set ZF/SF, so we need extra checks here */ comprintf("\tif (needed_flags & FLAG_ZNV) {\n"); switch (curi->size) { case sz_byte: comprintf("\ttest_b_rr(data,data);\n"); break; case sz_word: comprintf("\ttest_w_rr(data,data);\n"); break; case sz_long: comprintf("\ttest_l_rr(data,data);\n"); break; } comprintf("}\n"); switch (curi->size) { case sz_byte: comprintf("\tbt_l_ri(cdata,0x07);\n"); break; case sz_word: comprintf("\tbt_l_ri(cdata,0x0f);\n"); break; case sz_long: comprintf("\tbt_l_ri(cdata,0x1f);\n"); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); break; case i_ROXL: failure; break; case i_ROXR: failure; break; case i_ASRW: failure; break; case i_ASLW: failure; break; case i_LSRW: failure; break; case i_LSLW: failure; break; case i_ROLW: failure; break; case i_RORW: failure; break; case i_ROXLW: failure; break; case i_ROXRW: failure; break; case i_MOVEC2: isjump; failure; break; case i_MOVE2C: isjump; failure; break; case i_CAS: failure; break; case i_CAS2: failure; break; case i_MOVES: /* ignore DFC and SFC because we have no MMU */ isjump; failure; break; case i_BKPT: /* only needed for hardware emulators */ isjump; failure; break; case i_CALLM: /* not present in 68030 */ isjump; failure; break; case i_RTM: /* not present in 68030 */ isjump; failure; break; case i_TRAPcc: isjump; failure; break; case i_DIVL: isjump; failure; break; case i_MULL: #ifdef DISABLE_I_MULL failure; #endif if (!noflags) { failure; break; } comprintf("\tuae_u16 extra=%s;\n", gen_nextiword()); comprintf("\tint r2=(extra>>12)&7;\n" "\tint tmp=scratchie++;\n"); genamode(curi->dmode, "dstreg", curi->size, "dst", GENA_GETV_FETCH, GENA_MOVEM_DO_INC); /* The two operands are in dst and r2 */ comprintf("\tif (extra&0x0400) {\n" /* Need full 64 bit result */ "\tint r3=(extra&7);\n" "\tmov_l_rr(r3,dst);\n"); /* operands now in r3 and r2 */ comprintf("\tif (extra&0x0800) { \n" /* signed */ "\t\timul_64_32(r2,r3);\n" "\t} else { \n" "\t\tmul_64_32(r2,r3);\n" "\t} \n"); /* The result is in r2/tmp, with r2 holding the lower 32 bits */ comprintf("\t} else {\n"); /* Only want 32 bit result */ /* operands in dst and r2, result foes into r2 */ /* shouldn't matter whether it's signed or unsigned?!? */ comprintf("\timul_32_32(r2,dst);\n" "\t}\n"); break; case i_BFTST: case i_BFEXTU: case i_BFCHG: case i_BFEXTS: case i_BFCLR: case i_BFFFO: case i_BFSET: case i_BFINS: failure; break; case i_PACK: failure; break; case i_UNPK: failure; break; case i_TAS: failure; break; case i_FPP: #ifdef DISABLE_I_FPP failure; #endif uses_fpu; mayfail; comprintf("#ifdef USE_JIT_FPU\n"); comprintf("\tuae_u16 extra=%s;\n", gen_nextiword()); swap_opcode(); comprintf("\tcomp_fpp_opp(opcode,extra);\n"); comprintf("#else\n"); comprintf("\tfailure = 1;\n"); comprintf("#endif\n"); break; case i_FBcc: #ifdef DISABLE_I_FBCC failure; #endif uses_fpu; isjump; uses_cmov; mayfail; comprintf("#ifdef USE_JIT_FPU\n"); swap_opcode(); comprintf("\tcomp_fbcc_opp(opcode);\n"); comprintf("#else\n"); comprintf("\tfailure = 1;\n"); comprintf("#endif\n"); break; case i_FDBcc: uses_fpu; isjump; failure; break; case i_FScc: #ifdef DISABLE_I_FSCC failure; #endif uses_fpu; mayfail; uses_cmov; comprintf("#ifdef USE_JIT_FPU\n"); comprintf("\tuae_u16 extra=%s;\n", gen_nextiword()); swap_opcode(); comprintf("\tcomp_fscc_opp(opcode,extra);\n"); comprintf("#else\n"); comprintf("\tfailure = 1;\n"); comprintf("#endif\n"); break; case i_FTRAPcc: uses_fpu; isjump; failure; break; case i_FSAVE: uses_fpu; failure; break; case i_FRESTORE: uses_fpu; failure; break; case i_CINVL: case i_CINVP: case i_CINVA: isjump; /* Not really, but it's probably a good idea to stop translating at this point */ failure; comprintf("\tflush_icache();\n"); /* Differentiate a bit more? */ break; case i_CPUSHL: case i_CPUSHP: case i_CPUSHA: isjump; /* Not really, but it's probably a good idea to stop translating at this point */ failure; break; case i_MOVE16: #ifdef DISABLE_I_MOVE16 failure; #endif genmov16(opcode, curi); break; #ifdef UAE case i_MMUOP030: case i_PFLUSHN: case i_PFLUSH: case i_PFLUSHAN: case i_PFLUSHA: case i_PLPAR: case i_PLPAW: case i_PTESTR: case i_PTESTW: case i_LPSTOP: case i_PULSE: case i_HALT: isjump; failure; break; #endif #ifdef WINUAE_ARANYM case i_EMULOP_RETURN: isjump; failure; break; case i_EMULOP: failure; break; case i_NATFEAT_ID: case i_NATFEAT_CALL: failure; break; case i_MMUOP: isjump; failure; break; #endif default: assert(0); break; } comprintf("%s", endstr); finish_braces(); sync_m68k_pc(); if (global_mayfail) { comprintf("if (failure) {\n"); comprintf("m68k_pc_offset = m68k_pc_offset_thisinst;\n"); comprintf("}\n"); } return global_failure; } static void generate_includes(FILE *f) { fprintf(f, "#include \"sysconfig.h\"\n"); fprintf(f, "#if defined(JIT)\n"); fprintf(f, "#include \"sysdeps.h\"\n"); #ifdef UAE fprintf(f, "#include \"options.h\"\n"); fprintf(f, "#include \"uae/memory.h\"\n"); #else fprintf(f, "#include \"m68k.h\"\n"); fprintf(f, "#include \"memory-uae.h\"\n"); #endif fprintf(f, "#include \"readcpu.h\"\n"); fprintf(f, "#include \"newcpu.h\"\n"); fprintf(f, "#include \"comptbl.h\"\n"); fprintf(f, "#include \"debug.h\"\n"); } static int postfix; static char *decodeEA(amodes mode, wordsizes size) { static char buffer[80]; buffer[0] = 0; switch (mode) { case Dreg: strcpy(buffer, "Dn"); break; case Areg: strcpy(buffer, "An"); break; case Aind: strcpy(buffer, "(An)"); break; case Aipi: strcpy(buffer, "(An)+"); break; case Apdi: strcpy(buffer, "-(An)"); break; case Ad16: strcpy(buffer, "(d16,An)"); break; case Ad8r: strcpy(buffer, "(d8,An,Xn)"); break; case PC16: strcpy(buffer, "(d16,PC)"); break; case PC8r: strcpy(buffer, "(d8,PC,Xn)"); break; case absw: strcpy(buffer, "(xxx).W"); break; case absl: strcpy(buffer, "(xxx).L"); break; case imm: switch (size) { case sz_byte: strcpy(buffer, "#.B"); break; case sz_word: strcpy(buffer, "#.W"); break; case sz_long: strcpy(buffer, "#.L"); break; default: break; } break; case imm0: strcpy(buffer, "#.B"); break; case imm1: strcpy(buffer, "#.W"); break; case imm2: strcpy(buffer, "#.L"); break; case immi: strcpy(buffer, "#"); break; default: break; } return buffer; } static char *outopcode(const char *name, int opcode) { static char out[100]; struct instr *ins; ins = &table68k[opcode]; strcpy(out, name); if (ins->smode == immi) strcat(out, "Q"); if (ins->size == sz_byte) strcat(out, ".B"); if (ins->size == sz_word) strcat(out, ".W"); if (ins->size == sz_long) strcat(out, ".L"); strcat(out, " "); if (ins->suse) strcat(out, decodeEA(ins->smode, ins->size)); if (ins->duse) { if (ins->suse) strcat(out, ","); strcat(out, decodeEA(ins->dmode, ins->size)); } return out; } static void generate_one_opcode(int rp, int noflags) { int i; uae_u16 smsk, dmsk; unsigned int opcode = opcode_map[rp]; int aborted = 0; int have_srcreg = 0; int have_dstreg = 0; const char *name; const char *tbl = noflags ? "nf" : "ff"; if (table68k[opcode].mnemo == i_ILLG || table68k[opcode].clev > cpu_level) return; for (i = 0; lookuptab[i].name[0]; i++) { if (table68k[opcode].mnemo == lookuptab[i].mnemo) break; } if (table68k[opcode].handler != -1) return; name = ua(lookuptab[i].name); comprintf("/* %s */\n", outopcode(name, opcode)); comprintf(RETTYPE " REGPARAM2 op_%x_%d_comp_%s(uae_u32 opcode) /* %s */\n{\n", opcode, postfix, tbl, name); switch (table68k[opcode].stype) { case 0: smsk = 7; break; case 1: smsk = 255; break; case 2: smsk = 15; break; case 3: smsk = 7; break; case 4: smsk = 7; break; case 5: smsk = 63; break; #ifndef UAE case 6: smsk = 255; break; #endif case 7: smsk = 3; break; default: smsk = 0; assert(0); } dmsk = 7; next_cpu_level = -1; if (table68k[opcode].suse && table68k[opcode].smode != imm && table68k[opcode].smode != imm0 && table68k[opcode].smode != imm1 && table68k[opcode].smode != imm2 && table68k[opcode].smode != absw && table68k[opcode].smode != absl && table68k[opcode].smode != PC8r && table68k[opcode].smode != PC16) { have_srcreg = 1; if (table68k[opcode].spos == -1) { if (((int)table68k[opcode].sreg) >= 128) comprintf("\tuae_s32 srcreg = (uae_s32)(uae_s8)%d;\n", (int)table68k[opcode].sreg); else comprintf("\tuae_s32 srcreg = %d;\n", (int)table68k[opcode].sreg); } else { char source[100]; int pos = table68k[opcode].spos; #ifndef UAE comprintf("#if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU)\n"); #else comprintf("#if defined(HAVE_GET_WORD_UNSWAPPED)\n"); #endif if (pos < 8 && (smsk >> (8 - pos)) != 0) sprintf(source, "(((opcode >> %d) | (opcode << %d)) & %d)", pos ^ 8, 8 - pos, dmsk); else if (pos != 8) sprintf(source, "((opcode >> %d) & %d)", pos ^ 8, smsk); else sprintf(source, "(opcode & %d)", smsk); if (table68k[opcode].stype == 3) comprintf("\tuae_u32 srcreg = imm8_table[%s];\n", source); else if (table68k[opcode].stype == 1) comprintf("\tuae_u32 srcreg = (uae_s32)(uae_s8)%s;\n", source); else comprintf("\tuae_u32 srcreg = %s;\n", source); comprintf("#else\n"); if (pos) sprintf(source, "((opcode >> %d) & %d)", pos, smsk); else sprintf(source, "(opcode & %d)", smsk); if (table68k[opcode].stype == 3) comprintf("\tuae_s32 srcreg = imm8_table[%s];\n", source); else if (table68k[opcode].stype == 1) comprintf("\tuae_s32 srcreg = (uae_s32)(uae_s8)%s;\n", source); else comprintf("\tuae_s32 srcreg = %s;\n", source); comprintf("#endif\n"); } } if (table68k[opcode].duse /* Yes, the dmode can be imm, in case of LINK or DBcc */ && table68k[opcode].dmode != imm && table68k[opcode].dmode != imm0 && table68k[opcode].dmode != imm1 && table68k[opcode].dmode != imm2 && table68k[opcode].dmode != absw && table68k[opcode].dmode != absl) { have_dstreg = 1; if (table68k[opcode].dpos == -1) { if (((int)table68k[opcode].dreg) >= 128) comprintf("\tuae_s32 dstreg = (uae_s32)(uae_s8)%d;\n", (int)table68k[opcode].dreg); else comprintf("\tuae_s32 dstreg = %d;\n", (int)table68k[opcode].dreg); } else { int pos = table68k[opcode].dpos; #ifndef UAE comprintf("#if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU)\n"); if (pos < 8 && (dmsk >> (8 - pos)) != 0) comprintf("\tuae_u32 dstreg = ((opcode >> %d) | (opcode << %d)) & %d;\n", pos ^ 8, 8 - pos, dmsk); else if (pos != 8) comprintf("\tuae_u32 dstreg = (opcode >> %d) & %d;\n", pos ^ 8, dmsk); else comprintf("\tuae_u32 dstreg = opcode & %d;\n", dmsk); comprintf("#else\n"); #endif if (pos) comprintf("\tuae_u32 dstreg = (opcode >> %d) & %d;\n", pos, dmsk); else comprintf("\tuae_u32 dstreg = opcode & %d;\n", dmsk); #ifndef UAE comprintf("#endif\n"); #endif } } if (have_srcreg && have_dstreg && (table68k[opcode].dmode == Areg || table68k[opcode].dmode == Aind || table68k[opcode].dmode == Aipi || table68k[opcode].dmode == Apdi || table68k[opcode].dmode == Ad16 || table68k[opcode].dmode == Ad8r) && (table68k[opcode].smode == Areg || table68k[opcode].smode == Aind || table68k[opcode].smode == Aipi || table68k[opcode].smode == Apdi || table68k[opcode].smode == Ad16 || table68k[opcode].smode == Ad8r) ) { comprintf("\tuae_u32 dodgy=(srcreg==(uae_s32)dstreg);\n"); } else { comprintf("\tuae_u32 dodgy=0;\n"); } comprintf("\tuae_u32 m68k_pc_offset_thisinst=m68k_pc_offset;\n"); comprintf("\tm68k_pc_offset+=2;\n"); aborted = gen_opcode(opcode); { char flags[64 * 6]; *flags = '\0'; if (global_isjump) strcat(flags, "COMP_OPCODE_ISJUMP|"); if (long_opcode) strcat(flags, "COMP_OPCODE_LONG_OPCODE|"); if (global_cmov) strcat(flags, "COMP_OPCODE_CMOV|"); if (global_isaddx) strcat(flags, "COMP_OPCODE_ISADDX|"); if (global_iscjump) strcat(flags, "COMP_OPCODE_ISCJUMP|"); if (global_fpu) strcat(flags, "COMP_OPCODE_USES_FPU|"); if (*flags) flags[strlen(flags) - 1] = '\0'; else strcpy(flags, "0"); #ifdef UAE /* RETTYPE != void */ comprintf("return 0;\n"); #endif comprintf("}\n"); if (aborted) { fprintf(stblfile, "{ NULL, %u, %s }, /* %s */\n", opcode, flags, name); com_discard(); } else { fprintf(stblfile, "{ op_%x_%d_comp_%s, %u, %s }, /* %s */\n", opcode, postfix, tbl, opcode, flags, name); fprintf(headerfile, "extern compop_func op_%x_%d_comp_%s;\n", opcode, postfix, tbl); com_flush(); } } opcode_next_clev[rp] = next_cpu_level; opcode_last_postfix[rp] = postfix; } static void generate_func(int noflags) { int i, j, rp; const char *tbl = noflags ? "nf" : "ff"; using_prefetch = 0; using_exception_3 = 0; for (i = 0; i < 1; i++) /* We only do one level! */ { cpu_level = NEXT_CPU_LEVEL - i; postfix = i; fprintf(stblfile, "const struct comptbl op_smalltbl_%d_comp_%s[] = {\n", postfix, tbl); /* sam: this is for people with low memory (eg. me :)) */ printf("\n" "#if !defined(PART_1) && !defined(PART_2) && " "!defined(PART_3) && !defined(PART_4) && " "!defined(PART_5) && !defined(PART_6) && " "!defined(PART_7) && !defined(PART_8)" "\n" "#define PART_1 1\n" "#define PART_2 1\n" "#define PART_3 1\n" "#define PART_4 1\n" "#define PART_5 1\n" "#define PART_6 1\n" "#define PART_7 1\n" "#define PART_8 1\n" "#endif\n\n"); #ifdef UAE printf("#ifdef USE_JIT_FPU\n"); printf("extern void comp_fpp_opp();\n" "extern void comp_fscc_opp();\n" "extern void comp_fbcc_opp();\n"); printf("#endif\n"); printf("\n"); #endif rp = 0; for (j = 1; j <= 8; ++j) { int k = (j * nr_cpuop_funcs) / 8; printf("#ifdef PART_%d\n", j); for (; rp < k; rp++) generate_one_opcode(rp, noflags); printf("#endif\n\n"); } fprintf(stblfile, "{ 0, 65536, 0 }};\n"); } } #if (defined(OS_cygwin) || defined(OS_mingw)) && defined(EXTENDED_SIGSEGV) void cygwin_mingw_abort() { #undef abort abort(); } #endif #if defined(FSUAE) && defined (WINDOWS) #include "windows.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) #else int main(void) #endif { init_table68k(); opcode_map = (int *)malloc(sizeof(int) * nr_cpuop_funcs); opcode_last_postfix = (int *)malloc(sizeof(int) * nr_cpuop_funcs); opcode_next_clev = (int *)malloc(sizeof(int) * nr_cpuop_funcs); counts = (unsigned long *)malloc(65536 * sizeof(unsigned long)); read_counts(); /* It would be a lot nicer to put all in one file (we'd also get rid of * cputbl.h that way), but cpuopti can't cope. That could be fixed, but * I don't dare to touch the 68k version. */ headerfile = fopen(GEN_PATH "comptbl.h", "wb"); fprintf(headerfile, "" "extern const struct comptbl op_smalltbl_0_comp_nf[];\n" "extern const struct comptbl op_smalltbl_0_comp_ff[];\n" ""); stblfile = fopen(GEN_PATH "compstbl.cpp", "wb"); if (freopen(GEN_PATH "compemu.cpp", "wb", stdout) == NULL) { abort(); } generate_includes(stdout); generate_includes(stblfile); printf("#include \"" JIT_PATH "compemu.h\"\n"); printf("#include \"" JIT_PATH "flags_x86.h\"\n"); noflags = 0; generate_func(noflags); free(opcode_map); free(opcode_last_postfix); free(opcode_next_clev); free(counts); opcode_map = (int *)malloc(sizeof(int) * nr_cpuop_funcs); opcode_last_postfix = (int *)malloc(sizeof(int) * nr_cpuop_funcs); opcode_next_clev = (int *)malloc(sizeof(int) * nr_cpuop_funcs); counts = (unsigned long *)malloc(65536 * sizeof(unsigned long)); read_counts(); noflags = 1; generate_func(noflags); printf("#endif\n"); fprintf(stblfile, "#endif\n"); free(opcode_map); free(opcode_last_postfix); free(opcode_next_clev); free(counts); free(table68k); fclose(stblfile); fclose(headerfile); (void)disasm_this_inst; return 0; } #ifdef UAE void write_log(const TCHAR *format, ...) { } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/gencomp_arm.c000066400000000000000000003754751504763705000253650ustar00rootroot00000000000000/* * compiler/gencomp_arm2.c - MC680x0 compilation generator (ARM Adaption JIT v1 & JIT v2) * * Based on work Copyright 1995, 1996 Bernd Schmidt * Changes for UAE-JIT Copyright 2000 Bernd Meyer * * Adaptation for ARAnyM/ARM, copyright 2001-2015 * Milan Jurik, Jens Heitmann * * Basilisk II (C) 1997-2005 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Notes * ===== * * Advantages of JIT v2 * - Processor independent style * - Reduced overhead * - Easier to understand / read * - Easier to optimize * - More precise flag handling * - Better optimization for different CPU version ARM, ARMv6 etc.. * * Disadvantages of JIT v2 * - Less generated * - Requires more code implementation by hand (MidFunc) * - MIDFUNCS are more CPU minded (closer to raw) * - Separate code for each instruction (but this could be also an advantage, because you can concentrate on it) * * Additional note: * - current using jnf_xxx calls for non-flag operations and * jff_xxx for flag operations * * Still todo: * - Optimize genamode, genastore, gen_writeXXX, gen_readXXX, genmovemXXX * */ #define CC_FOR_BUILD 1 #include "sysconfig.h" #include "sysdeps.h" #include "readcpu.h" #include #include #include #include #include #include #undef abort #define BOOL_TYPE "int" #define failure global_failure=1 #define FAILURE global_failure=1 #define isjump global_isjump=1 #define is_const_jump global_iscjump=1 #define isaddx global_isaddx=1 #define uses_cmov global_cmov=1 #define mayfail global_mayfail=1 #define uses_fpu global_fpu=1 int hack_opcode; static int global_failure; static int global_isjump; static int global_iscjump; static int global_isaddx; static int global_cmov; static int long_opcode; static int global_mayfail; static int global_fpu; static char endstr[1000]; static char lines[100000]; static int comp_index = 0; #include "flags_arm.h" #ifndef __attribute__ # ifndef __GNUC__ # define __attribute__(x) # endif #endif static int cond_codes[] = { // NATIVE_CC_AL, -1, // NATIVE_CC_HI, NATIVE_CC_LS, // NATIVE_CC_CC, NATIVE_CC_CS, // NATIVE_CC_NE, NATIVE_CC_EQ, // NATIVE_CC_VC, NATIVE_CC_VS, // NATIVE_CC_PL, NATIVE_CC_MI, // NATIVE_CC_GE, NATIVE_CC_LT, // NATIVE_CC_GT, NATIVE_CC_LE // }; __attribute__((format(printf, 1, 2))) static void comprintf(const char *format, ...) { va_list args; va_start(args, format); comp_index += vsprintf(lines + comp_index, format, args); va_end(args); } static void com_discard(void) { comp_index = 0; } static void com_flush(void) { int i; for (i = 0; i < comp_index; i++) putchar(lines[i]); com_discard(); } static FILE *headerfile; static FILE *stblfile; static int using_prefetch; static int using_exception_3; static int cpu_level; static int noflags; /* For the current opcode, the next lower level that will have different code. * Initialized to -1 for each opcode. If it remains unchanged, indicates we * are done with that opcode. */ static int next_cpu_level; static int *opcode_map; static int *opcode_next_clev; static int *opcode_last_postfix; static unsigned long *counts; static void read_counts(void) { FILE *file; unsigned long opcode, count, total; char name[20]; int nr = 0; memset(counts, 0, 65536 * sizeof *counts); file = fopen("frequent.68k", "r"); if (file) { if (fscanf(file, "Total: %lu\n", &total) != 1) { assert(0); } while (fscanf(file, "%lx: %lu %s\n", &opcode, &count, name) == 3) { opcode_next_clev[nr] = 4; opcode_last_postfix[nr] = -1; opcode_map[nr++] = opcode; counts[opcode] = count; } fclose(file); } if (nr == nr_cpuop_funcs) return; for (opcode = 0; opcode < 0x10000; opcode++) { if (table68k[opcode].handler == -1 && table68k[opcode].mnemo != i_ILLG && counts[opcode] == 0) { opcode_next_clev[nr] = 4; opcode_last_postfix[nr] = -1; opcode_map[nr++] = opcode; counts[opcode] = count; } } assert (nr == nr_cpuop_funcs); } static int n_braces = 0; static int insn_n_cycles; static void start_brace(void) { n_braces++; comprintf("{"); } static void close_brace(void) { assert(n_braces > 0); n_braces--; comprintf("}"); } static void finish_braces(void) { while (n_braces > 0) close_brace(); } static inline void gen_update_next_handler(void) { return; /* Can anything clever be done here? */ } static void gen_writebyte(const char *address, const char *source) { comprintf("\twritebyte(%s, %s, scratchie);\n", address, source); } static void gen_writeword(const char *address, const char *source) { comprintf("\twriteword(%s, %s, scratchie);\n", address, source); } static void gen_writelong(const char *address, const char *source) { comprintf("\twritelong(%s, %s, scratchie);\n", address, source); } static void gen_readbyte(const char *address, const char* dest) { comprintf("\treadbyte(%s, %s, scratchie);\n", address, dest); } static void gen_readword(const char *address, const char *dest) { comprintf("\treadword(%s,%s,scratchie);\n", address, dest); } static void gen_readlong(const char *address, const char *dest) { comprintf("\treadlong(%s, %s, scratchie);\n", address, dest); } static const char * gen_nextilong(void) { static char buffer[80]; sprintf(buffer, "comp_get_ilong((m68k_pc_offset+=4)-4)"); insn_n_cycles += 4; long_opcode = 1; return buffer; } static const char * gen_nextiword(void) { static char buffer[80]; sprintf(buffer, "comp_get_iword((m68k_pc_offset+=2)-2)"); insn_n_cycles += 2; long_opcode = 1; return buffer; } static const char * gen_nextibyte(void) { static char buffer[80]; sprintf(buffer, "comp_get_ibyte((m68k_pc_offset+=2)-2)"); insn_n_cycles += 2; long_opcode = 1; return buffer; } #if defined(USE_JIT_FPU) // Only used by FPU (future), get rid of unused warning static void swap_opcode (void) { comprintf("#if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU)\n"); comprintf("\topcode = do_byteswap_16(opcode);\n"); comprintf("#endif\n"); } #endif static void sync_m68k_pc(void) { comprintf("\t if (m68k_pc_offset>SYNC_PC_OFFSET) sync_m68k_pc();\n"); } /* getv == 1: fetch data; getv != 0: check for odd address. If movem != 0, * the calling routine handles Apdi and Aipi modes. * gb-- movem == 2 means the same thing but for a MOVE16 instruction */ static void genamode(amodes mode, const char *reg, wordsizes size, const char *name, int getv, int movem) { start_brace(); switch (mode) { case Dreg: /* Do we need to check dodgy here? */ assert (!movem); if (getv == 1 || getv == 2) { /* We generate the variable even for getv==2, so we can use it as a destination for MOVE */ comprintf("\tint %s = %s;\n", name, reg); } return; case Areg: assert (!movem); if (getv == 1 || getv == 2) { /* see above */ comprintf("\tint %s = dodgy ? scratchie++ : %s + 8;\n", name, reg); if (getv == 1) { comprintf("\tif (dodgy) \n"); comprintf("\t\tmov_l_rr(%s, %s + 8);\n", name, reg); } } return; case Aind: comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tif (dodgy)\n"); comprintf("\t\tmov_l_rr(%sa, %s + 8);\n", name, reg); break; case Aipi: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_rr(%sa, %s + 8);\n", name, reg); break; case Apdi: switch (size) { case sz_byte: if (movem) { comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tif (dodgy)\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); } else { start_brace(); comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tlea_l_brr(%s + 8, %s + 8, (uae_s32)-areg_byteinc[%s]);\n", reg, reg, reg); comprintf("\tif (dodgy)\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); } break; case sz_word: if (movem) { comprintf("\tint %sa=dodgy?scratchie++:%s+8;\n", name, reg); comprintf("\tif (dodgy) \n"); comprintf("\tmov_l_rr(%sa,8+%s);\n", name, reg); } else { start_brace(); comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tlea_l_brr(%s + 8, %s + 8, -2);\n", reg, reg); comprintf("\tif (dodgy)\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); } break; case sz_long: if (movem) { comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tif (dodgy)\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); } else { start_brace(); comprintf("\tint %sa = dodgy ? scratchie++ : %s + 8;\n", name, reg); comprintf("\tlea_l_brr(%s + 8, %s + 8, -4);\n", reg, reg); comprintf("\tif (dodgy)\n"); comprintf("\t\tmov_l_rr(%sa, 8 + %s);\n", name, reg); } break; default: assert(0); break; } break; case Ad16: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_rr(%sa, 8 + %s);\n", name, reg); comprintf("\tlea_l_brr(%sa, %sa, (uae_s32)(uae_s16)%s);\n", name, name, gen_nextiword()); break; case Ad8r: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tcalc_disp_ea_020(%s + 8, %s, %sa, scratchie);\n", reg, gen_nextiword(), name); break; case PC16: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tuae_u32 address = start_pc + ((char *)comp_pc_p - (char *)start_pc_p) + m68k_pc_offset;\n"); comprintf("\tuae_s32 PC16off = (uae_s32)(uae_s16)%s;\n", gen_nextiword()); comprintf("\tmov_l_ri(%sa, address + PC16off);\n", name); break; case PC8r: comprintf("\tint pctmp = scratchie++;\n"); comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tuae_u32 address = start_pc + ((char *)comp_pc_p - (char *)start_pc_p) + m68k_pc_offset;\n"); start_brace(); comprintf("\tmov_l_ri(pctmp,address);\n"); comprintf("\tcalc_disp_ea_020(pctmp, %s, %sa, scratchie);\n", gen_nextiword(), name); break; case absw: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_ri(%sa, (uae_s32)(uae_s16)%s);\n", name, gen_nextiword()); break; case absl: comprintf("\tint %sa = scratchie++;\n", name); comprintf("\tmov_l_ri(%sa, %s); /* absl */\n", name, gen_nextilong()); break; case imm: assert (getv == 1); switch (size) { case sz_byte: comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s8)%s);\n", name, gen_nextibyte()); break; case sz_word: comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s16)%s);\n", name, gen_nextiword()); break; case sz_long: comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, %s);\n", name, gen_nextilong()); break; default: assert(0); break; } return; case imm0: assert (getv == 1); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s8)%s);\n", name, gen_nextibyte()); return; case imm1: assert (getv == 1); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, (uae_s32)(uae_s16)%s);\n", name, gen_nextiword()); return; case imm2: assert (getv == 1); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, %s);\n", name, gen_nextilong()); return; case immi: assert (getv == 1); comprintf("\tint %s = scratchie++;\n", name); comprintf("\tmov_l_ri(%s, %s);\n", name, reg); return; default: assert(0); break; } /* We get here for all non-reg non-immediate addressing modes to * actually fetch the value. */ if (getv == 1) { char astring[80]; sprintf(astring, "%sa", name); switch (size) { case sz_byte: insn_n_cycles += 2; break; case sz_word: insn_n_cycles += 2; break; case sz_long: insn_n_cycles += 4; break; default: assert(0); break; } start_brace(); comprintf("\tint %s = scratchie++;\n", name); switch (size) { case sz_byte: gen_readbyte(astring, name); break; case sz_word: gen_readword(astring, name); break; case sz_long: gen_readlong(astring, name); break; default: assert(0); break; } } /* We now might have to fix up the register for pre-dec or post-inc * addressing modes. */ if (!movem) { switch (mode) { case Aipi: switch (size) { case sz_byte: comprintf("\tlea_l_brr(%s + 8,%s + 8, areg_byteinc[%s]);\n", reg, reg, reg); break; case sz_word: comprintf("\tlea_l_brr(%s + 8, %s + 8, 2);\n", reg, reg); break; case sz_long: comprintf("\tlea_l_brr(%s + 8, %s + 8, 4);\n", reg, reg); break; default: assert(0); break; } break; case Apdi: break; default: break; } } } static void genastore(const char *from, amodes mode, const char *reg, wordsizes size, const char *to) { switch (mode) { case Dreg: switch (size) { case sz_byte: comprintf("\tif(%s != %s)\n", reg, from); comprintf("\t\tmov_b_rr(%s, %s);\n", reg, from); break; case sz_word: comprintf("\tif(%s != %s)\n", reg, from); comprintf("\t\tmov_w_rr(%s, %s);\n", reg, from); break; case sz_long: comprintf("\tif(%s != %s)\n", reg, from); comprintf("\t\tmov_l_rr(%s, %s);\n", reg, from); break; default: assert(0); break; } break; case Areg: switch (size) { case sz_word: comprintf("\tif(%s + 8 != %s)\n", reg, from); comprintf("\t\tmov_w_rr(%s + 8, %s);\n", reg, from); break; case sz_long: comprintf("\tif(%s + 8 != %s)\n", reg, from); comprintf("\t\tmov_l_rr(%s + 8, %s);\n", reg, from); break; default: assert(0); break; } break; case Apdi: case absw: case PC16: case PC8r: case Ad16: case Ad8r: case Aipi: case Aind: case absl: { char astring[80]; sprintf(astring, "%sa", to); switch (size) { case sz_byte: insn_n_cycles += 2; gen_writebyte(astring, from); break; case sz_word: insn_n_cycles += 2; gen_writeword(astring, from); break; case sz_long: insn_n_cycles += 4; gen_writelong(astring, from); break; default: assert(0); break; } } break; case imm: case imm0: case imm1: case imm2: case immi: assert(0); break; default: assert(0); break; } } static void gen_move16(uae_u32 opcode, struct instr *curi) { #if defined(USE_JIT2) comprintf("\tint src=scratchie++;\n"); comprintf("\tint dst=scratchie++;\n"); uae_u32 masked_op = (opcode & 0xfff8); if (masked_op == 0xf620) { // POSTINCREMENT SOURCE AND DESTINATION version comprintf("\t uae_u16 dstreg = ((%s)>>12) & 0x07;\n", gen_nextiword()); comprintf("\t jnf_MOVE(src, srcreg + 8);"); comprintf("\t jnf_MOVE(dst, dstreg + 8);"); comprintf("\t if (srcreg != dstreg)\n"); comprintf("\t jnf_ADD_imm(srcreg + 8, srcreg + 8, 16);"); comprintf("\t jnf_ADD_imm(dstreg + 8, dstreg + 8, 16);"); } else { /* Other variants */ genamode(curi->smode, "srcreg", curi->size, "src", 0, 2); genamode(curi->dmode, "dstreg", curi->size, "dst", 0, 2); switch (masked_op) { case 0xf600: comprintf("\t jnf_ADD_imm(srcreg + 8, srcreg + 8, 16);"); break; case 0xf608: comprintf("\t jnf_ADD_imm(dstreg + 8, dstreg + 8, 16);"); break; } } comprintf("\t jnf_MOVE16(dst, src);"); #else comprintf("\tint src=scratchie++;\n"); comprintf("\tint dst=scratchie++;\n"); if ((opcode & 0xfff8) == 0xf620) { /* MOVE16 (Ax)+,(Ay)+ */ comprintf("\tuae_u16 dstreg=((%s)>>12)&0x07;\n", gen_nextiword()); comprintf("\tmov_l_rr(src,8+srcreg);\n"); comprintf("\tmov_l_rr(dst,8+dstreg);\n"); } else { /* Other variants */ genamode(curi->smode, "srcreg", curi->size, "src", 0, 2); genamode(curi->dmode, "dstreg", curi->size, "dst", 0, 2); comprintf("\tmov_l_rr(src,srca);\n"); comprintf("\tmov_l_rr(dst,dsta);\n"); } /* Align on 16-byte boundaries */ comprintf("\tand_l_ri(src,~15);\n"); comprintf("\tand_l_ri(dst,~15);\n"); if ((opcode & 0xfff8) == 0xf620) { comprintf("\tif (srcreg != dstreg)\n"); comprintf("\tarm_ADD_l_ri8(srcreg+8,16);\n"); comprintf("\tarm_ADD_l_ri8(dstreg+8,16);\n"); } else if ((opcode & 0xfff8) == 0xf600) comprintf("\tarm_ADD_l_ri8(srcreg+8,16);\n"); else if ((opcode & 0xfff8) == 0xf608) comprintf("\tarm_ADD_l_ri8(dstreg+8,16);\n"); start_brace(); comprintf("\tint tmp=scratchie;\n"); comprintf("\tscratchie+=4;\n"); comprintf("\tget_n_addr(src,src,scratchie);\n" "\tget_n_addr(dst,dst,scratchie);\n" "\tmov_l_rR(tmp+0,src,0);\n" "\tmov_l_rR(tmp+1,src,4);\n" "\tmov_l_rR(tmp+2,src,8);\n" "\tmov_l_rR(tmp+3,src,12);\n" "\tmov_l_Rr(dst,tmp+0,0);\n" "\tforget_about(tmp+0);\n" "\tmov_l_Rr(dst,tmp+1,4);\n" "\tforget_about(tmp+1);\n" "\tmov_l_Rr(dst,tmp+2,8);\n" "\tforget_about(tmp+2);\n" "\tmov_l_Rr(dst,tmp+3,12);\n"); close_brace(); #endif } static void genmovemel(uae_u16 opcode) { comprintf("\tuae_u16 mask = %s;\n", gen_nextiword()); comprintf("\tint native=scratchie++;\n"); comprintf("\tint i;\n"); comprintf("\tsigned char offset=0;\n"); genamode(table68k[opcode].dmode, "dstreg", table68k[opcode].size, "src", 2, 1); comprintf("\tget_n_addr(srca,native,scratchie);\n"); comprintf("\tfor (i=0;i<16;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\tmov_l_rR(i,native,offset);\n" "\t\t\tmid_bswap_32(i);\n" "\t\t\toffset+=4;\n"); break; case sz_word: comprintf("\t\t\tmov_w_rR(i,native,offset);\n" "\t\t\tmid_bswap_16(i);\n" "\t\t\tsign_extend_16_rr(i,i);\n" "\t\t\toffset+=2;\n"); break; default: assert(0); break; } comprintf("\t\t}\n" "\t}"); if (table68k[opcode].dmode == Aipi) { comprintf("\t\t\tlea_l_brr(8+dstreg,srca,offset);\n"); } } static void genmovemle(uae_u16 opcode) { comprintf("\tuae_u16 mask = %s;\n", gen_nextiword()); comprintf("\tint native=scratchie++;\n"); comprintf("\tint i;\n"); comprintf("\tint tmp=scratchie++;\n"); comprintf("\tsigned char offset=0;\n"); genamode(table68k[opcode].dmode, "dstreg", table68k[opcode].size, "src", 2, 1); comprintf("\tget_n_addr(srca,native,scratchie);\n"); if (table68k[opcode].dmode != Apdi) { comprintf("\tfor (i=0;i<16;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\tmov_l_rr(tmp,i);\n" "\t\t\tmid_bswap_32(tmp);\n" "\t\t\tmov_l_Rr(native,tmp,offset);\n" "\t\t\toffset+=4;\n"); break; case sz_word: comprintf("\t\t\tmov_l_rr(tmp,i);\n" "\t\t\tmid_bswap_16(tmp);\n" "\t\t\tmov_w_Rr(native,tmp,offset);\n" "\t\t\toffset+=2;\n"); break; default: assert(0); break; } } else { /* Pre-decrement */ comprintf("\tfor (i=0;i<16;i++) {\n" "\t\tif ((mask>>i)&1) {\n"); switch (table68k[opcode].size) { case sz_long: comprintf("\t\t\toffset-=4;\n" "\t\t\tmov_l_rr(tmp,15-i);\n" "\t\t\tmid_bswap_32(tmp);\n" "\t\t\tmov_l_Rr(native,tmp,offset);\n"); break; case sz_word: comprintf("\t\t\toffset-=2;\n" "\t\t\tmov_l_rr(tmp,15-i);\n" "\t\t\tmid_bswap_16(tmp);\n" "\t\t\tmov_w_Rr(native,tmp,offset);\n"); break; default: assert(0); break; } } comprintf("\t\t}\n" "\t}"); if (table68k[opcode].dmode == Apdi) { comprintf("\t\t\tlea_l_brr(8+dstreg,srca,(uae_s32)offset);\n"); } } static void duplicate_carry(void) { comprintf("\tif (needed_flags&FLAG_X) duplicate_carry();\n"); } typedef enum { flag_logical_noclobber, flag_logical, flag_add, flag_sub, flag_cmp, flag_addx, flag_subx, flag_zn, flag_av, flag_sv, flag_and, flag_or, flag_eor, flag_mov } flagtypes; #if !defined(USE_JIT2) static void genflags(flagtypes type, wordsizes size, const char *value, const char *src, const char *dst) { if (noflags) { switch (type) { case flag_cmp: comprintf("\tdont_care_flags();\n"); comprintf("/* Weird --- CMP with noflags ;-) */\n"); return; case flag_add: case flag_sub: comprintf("\tdont_care_flags();\n"); { const char* op; switch (type) { case flag_add: op = "add"; break; // nf case flag_sub: op = "sub"; break; // nf default: assert(0); break; } switch (size) { case sz_byte: comprintf("\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\t%s_l(%s,%s);\n", op, dst, src); break; } return; } break; case flag_and: comprintf("\tdont_care_flags();\n"); switch (size) { case sz_byte: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_8_rr(scratchie,%s);\n", src); comprintf("\tor_l_ri(scratchie,0xffffff00);\n"); // nf comprintf("\tarm_AND_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tarm_AND_b(%s,%s);\n", dst, src); break; case sz_word: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_16_rr(scratchie,%s);\n", src); comprintf("\tor_l_ri(scratchie,0xffff0000);\n"); // nf comprintf("\tarm_AND_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tarm_AND_w(%s,%s);\n", dst, src); break; case sz_long: comprintf("\tarm_AND_l(%s,%s);\n", dst, src); break; } return; case flag_mov: comprintf("\tdont_care_flags();\n"); switch (size) { case sz_byte: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_8_rr(scratchie,%s);\n", src); comprintf("\tand_l_ri(%s,0xffffff00);\n", dst); // nf comprintf("\tarm_ORR_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tmov_b_rr(%s,%s);\n", dst, src); break; case sz_word: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_16_rr(scratchie,%s);\n", src); comprintf("\tand_l_ri(%s,0xffff0000);\n", dst); // nf comprintf("\tarm_ORR_l(%s,scratchie);\n", dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tmov_w_rr(%s,%s);\n", dst, src); break; case sz_long: comprintf("\tmov_l_rr(%s,%s);\n", dst, src); break; } return; case flag_or: case flag_eor: comprintf("\tdont_care_flags();\n"); start_brace(); { const char* op; switch (type) { case flag_or: op = "ORR"; break; // nf case flag_eor: op = "EOR"; break; // nf default: assert(0); break; } switch (size) { case sz_byte: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_8_rr(scratchie,%s);\n", src); comprintf("\tarm_%s_l(%s,scratchie);\n", op, dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tarm_%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("if (kill_rodent(dst)) {\n"); comprintf("\tzero_extend_16_rr(scratchie,%s);\n", src); comprintf("\tarm_%s_l(%s,scratchie);\n", op, dst); comprintf("\tforget_about(scratchie);\n"); comprintf("\t} else \n" "\tarm_%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tarm_%s_l(%s,%s);\n", op, dst, src); break; } close_brace(); return; } case flag_addx: case flag_subx: comprintf("\tdont_care_flags();\n"); { const char* op; switch (type) { case flag_addx: op = "adc"; break; case flag_subx: op = "sbb"; break; default: assert(0); break; } comprintf("\trestore_carry();\n"); /* Reload the X flag into C */ switch (size) { case sz_byte: comprintf("\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\t%s_l(%s,%s);\n", op, dst, src); break; } return; } break; default: return; } } /* Need the flags, but possibly not all of them */ switch (type) { case flag_logical_noclobber: failure; /* fall through */ case flag_and: case flag_or: case flag_eor: comprintf("\tdont_care_flags();\n"); start_brace(); { const char* op; switch (type) { case flag_and: op = "and"; break; case flag_or: op = "or"; break; case flag_eor: op = "xor"; break; default: assert(0); break; } switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\tstart_needflags();\n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tstart_needflags();\n" "\t%s_l(%s,%s);\n", op, dst, src); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); close_brace(); return; } case flag_mov: comprintf("\tdont_care_flags();\n"); start_brace(); { switch (size) { case sz_byte: comprintf("\tif (%s!=%s) {\n", src, dst); comprintf("\tmov_b_ri(%s,0);\n" "\tstart_needflags();\n", dst); comprintf("\tor_b(%s,%s);\n", dst, src); comprintf("\t} else {\n"); comprintf("\tmov_b_rr(%s,%s);\n", dst, src); comprintf("\ttest_b_rr(%s,%s);\n", dst, dst); comprintf("\t}\n"); break; case sz_word: comprintf("\tif (%s!=%s) {\n", src, dst); comprintf("\tmov_w_ri(%s,0);\n" "\tstart_needflags();\n", dst); comprintf("\tor_w(%s,%s);\n", dst, src); comprintf("\t} else {\n"); comprintf("\tmov_w_rr(%s,%s);\n", dst, src); comprintf("\ttest_w_rr(%s,%s);\n", dst, dst); comprintf("\t}\n"); break; case sz_long: comprintf("\tif (%s!=%s) {\n", src, dst); comprintf("\tmov_l_ri(%s,0);\n" "\tstart_needflags();\n", dst); comprintf("\tor_l(%s,%s);\n", dst, src); comprintf("\t} else {\n"); comprintf("\tmov_l_rr(%s,%s);\n", dst, src); comprintf("\ttest_l_rr(%s,%s);\n", dst, dst); comprintf("\t}\n"); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); close_brace(); return; } case flag_logical: comprintf("\tdont_care_flags();\n"); start_brace(); switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\ttest_b_rr(%s,%s);\n", value, value); break; case sz_word: comprintf("\tstart_needflags();\n" "\ttest_w_rr(%s,%s);\n", value, value); break; case sz_long: comprintf("\tstart_needflags();\n" "\ttest_l_rr(%s,%s);\n", value, value); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); close_brace(); return; case flag_add: case flag_sub: case flag_cmp: comprintf("\tdont_care_flags();\n"); { const char* op; switch (type) { case flag_add: op = "add"; break; case flag_sub: op = "sub"; break; case flag_cmp: op = "cmp"; break; default: assert(0); break; } switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\tstart_needflags();\n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tstart_needflags();\n" "\t%s_l(%s,%s);\n", op, dst, src); break; } comprintf("\tlive_flags();\n"); comprintf("\tend_needflags();\n"); if (type != flag_cmp) { duplicate_carry(); } comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); return; } case flag_addx: case flag_subx: uses_cmov; comprintf("\tdont_care_flags();\n"); { const char* op; switch (type) { case flag_addx: op = "adc"; break; case flag_subx: op = "sbb"; break; default: assert(0); break; } start_brace(); comprintf("\tint zero=scratchie++;\n" "\tint one=scratchie++;\n" "\tif (needed_flags&FLAG_Z) {\n" "\tmov_l_ri(zero,0);\n" "\tmov_l_ri(one,-1);\n" "\tmake_flags_live();\n" "\tcmov_l_rr(zero,one,%d);\n" "\t}\n", NATIVE_CC_NE); comprintf("\trestore_carry();\n"); /* Reload the X flag into C */ switch (size) { case sz_byte: comprintf("\tstart_needflags();\n" "\t%s_b(%s,%s);\n", op, dst, src); break; case sz_word: comprintf("\tstart_needflags();\n" "\t%s_w(%s,%s);\n", op, dst, src); break; case sz_long: comprintf("\tstart_needflags();\n" "\t%s_l(%s,%s);\n", op, dst, src); break; } comprintf("\tlive_flags();\n"); comprintf("\tif (needed_flags&FLAG_Z) {\n" "\tcmov_l_rr(zero,one,%d);\n" "\tset_zero(zero, one);\n" /* No longer need one */ "\tlive_flags();\n" "\t}\n", NATIVE_CC_NE); comprintf("\tend_needflags();\n"); duplicate_carry(); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); return; } default: failure; break; } } #endif static void gen_abcd(uae_u32 opcode, struct instr *curi, const char* ssize) { #if 0 #else (void) opcode; (void) curi; (void) ssize; failure; /* No BCD maths for me.... */ #endif } static void gen_add(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); // Use tmp register to avoid destroying upper part in .B., .W cases if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_ADD_%s(tmp,dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_ADD(tmp,dst,src);\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_add, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_adda(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_long, "dst", 1, 0); start_brace(); comprintf("\t jnf_ADDA_%s(dst, src);\n", ssize); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_long, "dst", 1, 0); start_brace(); comprintf("\tint tmp=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(tmp,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(tmp,src);\n"); break; case sz_long: comprintf("\ttmp=src;\n"); break; default: assert(0); break; } comprintf("\tarm_ADD_l(dst,tmp);\n"); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #endif } static void gen_addx(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) isaddx; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); // Use tmp register to avoid destroying upper part in .B., .W cases comprintf("\t dont_care_flags();\n"); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_ADDX_%s(tmp,dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); comprintf("\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_ADDX(tmp,dst,src);\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; isaddx; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); genflags(flag_addx, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_and(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); comprintf("\t dont_care_flags();\n"); comprintf("\t int tmp=scratchie++;\n"); start_brace(); if (!noflags) { comprintf("\t jff_AND_%s(tmp,dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_AND(tmp,dst,src);\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_and, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_andsr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_ANDSR(ARM_CCR_MAP[src & 0xF], (src & 0x10));\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } #else (void) curi; failure; isjump; #endif } static void gen_asl(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\t dont_care_flags();\n"); comprintf("\t int tmp=scratchie++;\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); if (curi->smode != immi) { if (!noflags) { start_brace(); comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_ASL_%s_reg(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { start_brace(); comprintf("\t jnf_LSL_reg(tmp,data,cnt);\n"); } } else { start_brace(); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_ASL_%s_imm(tmp,data,srcreg);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_LSL_imm(tmp,data,srcreg);\n"); } } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) ssize; mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); /* Except for the handling of the V flag, this is identical to LSL. The handling of V is, uhm, unpleasant, so if it's needed, let the normal emulation handle it. Shoulders of giants kinda thing ;-) */ comprintf("if (needed_flags & FLAG_V) {\n" " FAIL(1);\n" " return;\n" "} \n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); if (curi->smode != immi) { if (!noflags) { uses_cmov; start_brace(); comprintf("\tint highmask;\n" "\tint cdata=scratchie++;\n" "\tint tmpcnt=scratchie++;\n"); comprintf("\tmov_l_rr(tmpcnt,cnt);\n" "\tand_l_ri(tmpcnt,63);\n" "\tmov_l_ri(cdata,0);\n" "\tcmov_l_rr(cdata,data,%d);\n", NATIVE_CC_NE); /* cdata is now either data (for shift count!=0) or 0 (for shift count==0) */ switch (curi->size) { case sz_byte: comprintf("\tshll_b_rr(data,cnt);\n" "\thighmask=0x38;\n"); break; case sz_word: comprintf("\tshll_w_rr(data,cnt);\n" "\thighmask=0x30;\n"); break; case sz_long: comprintf("\tshll_l_rr(data,cnt);\n" "\thighmask=0x20;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(scratchie,data,%d);\n", NATIVE_CC_EQ); switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data,scratchie);\n"); break; case sz_word: comprintf("\tmov_w_rr(data,scratchie);\n"); break; case sz_long: comprintf("\tmov_l_rr(data,scratchie);\n"); break; default: assert(0); break; } /* Result of shift is now in data. Now we need to determine the carry by shifting cdata one less */ comprintf("\tsub_l_ri(tmpcnt,1);\n"); switch (curi->size) { case sz_byte: comprintf("\tshll_b_rr(cdata,tmpcnt);\n"); break; case sz_word: comprintf("\tshll_w_rr(cdata,tmpcnt);\n"); break; case sz_long: comprintf("\tshll_l_rr(cdata,tmpcnt);\n"); break; default: assert(0); break; } comprintf("test_l_ri(tmpcnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(cdata,scratchie,%d);\n", NATIVE_CC_NE); /* And create the flags */ comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); comprintf("\t bt_l_ri(cdata,7);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); comprintf("\t bt_l_ri(cdata,15);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); comprintf("\t bt_l_ri(cdata,31);\n"); break; } comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); genastore("data", curi->dmode, "dstreg", curi->size, "data"); } else { uses_cmov; start_brace(); comprintf("\tint highmask;\n"); switch (curi->size) { case sz_byte: comprintf("\tshll_b_rr(data,cnt);\n" "\thighmask=0x38;\n"); break; case sz_word: comprintf("\tshll_w_rr(data,cnt);\n" "\thighmask=0x30;\n"); break; case sz_long: comprintf("\tshll_l_rr(data,cnt);\n" "\thighmask=0x20;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(scratchie,data,%d);\n", NATIVE_CC_EQ); switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data,scratchie);\n"); break; case sz_word: comprintf("\tmov_w_rr(data,scratchie);\n"); break; case sz_long: comprintf("\tmov_l_rr(data,scratchie);\n"); break; default: assert(0); break; } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } } else { start_brace(); comprintf("\tint tmp=scratchie++;\n" "\tint bp;\n" "\tmov_l_rr(tmp,data);\n"); switch (curi->size) { case sz_byte: comprintf("\tshll_b_ri(data,srcreg);\n" "\tbp=8-srcreg;\n"); break; case sz_word: comprintf("\tshll_w_ri(data,srcreg);\n" "\tbp=16-srcreg;\n"); break; case sz_long: comprintf("\tshll_l_ri(data,srcreg);\n" "\tbp=32-srcreg;\n"); break; default: assert(0); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(tmp,bp);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } #endif } static void gen_aslw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_ASLW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_ASLW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_asr(uae_u32 opcode, struct instr *curi, const char* ssize) { #if defined(USE_JIT2) (void)opcode; mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (curi->smode != immi) { if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_ASR_%s_reg(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_ASR_%s_reg(tmp,data,cnt);\n", ssize); } } else { char *op; if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); op = "ff"; } else op = "nf"; comprintf("\t j%s_ASR_%s_imm(tmp,data,srcreg);\n", op, ssize); if (!noflags) { comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) opcode; (void) ssize; mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); if (curi->smode != immi) { if (!noflags) { uses_cmov; start_brace(); comprintf("\tint highmask;\n" "\tint width;\n" "\tint cdata=scratchie++;\n" "\tint tmpcnt=scratchie++;\n" "\tint highshift=scratchie++;\n"); comprintf("\tmov_l_rr(tmpcnt,cnt);\n" "\tand_l_ri(tmpcnt,63);\n" "\tmov_l_ri(cdata,0);\n" "\tcmov_l_rr(cdata,data,%d);\n", NATIVE_CC_NE); /* cdata is now either data (for shift count!=0) or 0 (for shift count==0) */ switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(data,cnt);\n" "\thighmask=0x38;\n" "\twidth=8;\n"); break; case sz_word: comprintf("\tshra_w_rr(data,cnt);\n" "\thighmask=0x30;\n" "\twidth=16;\n"); break; case sz_long: comprintf("\tshra_l_rr(data,cnt);\n" "\thighmask=0x20;\n" "\twidth=32;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(highshift,0);\n" "mov_l_ri(scratchie,width/2);\n" "cmov_l_rr(highshift,scratchie,%d);\n", NATIVE_CC_NE); /* The x86 masks out bits, so we now make sure that things really get shifted as much as planned */ switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(data,highshift);\n"); break; case sz_word: comprintf("\tshra_w_rr(data,highshift);\n"); break; case sz_long: comprintf("\tshra_l_rr(data,highshift);\n"); break; default: assert(0); break; } /* And again */ switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(data,highshift);\n"); break; case sz_word: comprintf("\tshra_w_rr(data,highshift);\n"); break; case sz_long: comprintf("\tshra_l_rr(data,highshift);\n"); break; default: assert(0); break; } /* Result of shift is now in data. Now we need to determine the carry by shifting cdata one less */ comprintf("\tsub_l_ri(tmpcnt,1);\n"); switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(cdata,tmpcnt);\n"); break; case sz_word: comprintf("\tshra_w_rr(cdata,tmpcnt);\n"); break; case sz_long: comprintf("\tshra_l_rr(cdata,tmpcnt);\n"); break; default: assert(0); break; } /* If the shift count was higher than the width, we need to pick up the sign from data */ comprintf("test_l_ri(tmpcnt,highmask);\n" "cmov_l_rr(cdata,data,%d);\n", NATIVE_CC_NE); /* And create the flags */ comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(cdata,0);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); genastore("data", curi->dmode, "dstreg", curi->size, "data"); } else { uses_cmov; start_brace(); comprintf("\tint highmask;\n" "\tint width;\n" "\tint highshift=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(data,cnt);\n" "\thighmask=0x38;\n" "\twidth=8;\n"); break; case sz_word: comprintf("\tshra_w_rr(data,cnt);\n" "\thighmask=0x30;\n" "\twidth=16;\n"); break; case sz_long: comprintf("\tshra_l_rr(data,cnt);\n" "\thighmask=0x20;\n" "\twidth=32;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(highshift,0);\n" "mov_l_ri(scratchie,width/2);\n" "cmov_l_rr(highshift,scratchie,%d);\n", NATIVE_CC_NE); /* The x86 masks out bits, so we now make sure that things really get shifted as much as planned */ switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(data,highshift);\n"); break; case sz_word: comprintf("\tshra_w_rr(data,highshift);\n"); break; case sz_long: comprintf("\tshra_l_rr(data,highshift);\n"); break; default: assert(0); break; } /* And again */ switch (curi->size) { case sz_byte: comprintf("\tshra_b_rr(data,highshift);\n"); break; case sz_word: comprintf("\tshra_w_rr(data,highshift);\n"); break; case sz_long: comprintf("\tshra_l_rr(data,highshift);\n"); break; default: assert(0); break; } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } } else { start_brace(); comprintf("\tint tmp=scratchie++;\n" "\tint bp;\n" "\tmov_l_rr(tmp,data);\n"); switch (curi->size) { case sz_byte: comprintf("\tshra_b_ri(data,srcreg);\n" "\tbp=srcreg-1;\n"); break; case sz_word: comprintf("\tshra_w_ri(data,srcreg);\n" "\tbp=srcreg-1;\n"); break; case sz_long: comprintf("\tshra_l_ri(data,srcreg);\n" "\tbp=srcreg-1;\n"); break; default: assert(0); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(tmp,bp);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } #endif } static void gen_asrw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp = scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_ASRW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_ASRW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_bchg(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_BCHG_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_BCHG_%s(dst,src);\n", ssize); comprintf("\t dont_care_flags();\n"); } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\tint s=scratchie++;\n" "\tint tmp=scratchie++;\n" "\tmov_l_rr(s,src);\n"); if (curi->size == sz_byte) comprintf("\tand_l_ri(s,7);\n"); else comprintf("\tand_l_ri(s,31);\n"); comprintf("\tbtc_l_rr(dst,s);\n" /* Answer now in C */ "\tsbb_l(s,s);\n" /* s is 0 if bit was 0, -1 otherwise */ "\tmake_flags_live();\n" /* Get the flags back */ "\tdont_care_flags();\n"); if (!noflags) { comprintf("\tstart_needflags();\n" "\tset_zero(s,tmp);\n" "\tlive_flags();\n" "\tend_needflags();\n"); } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_bclr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_BCLR_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_BCLR_%s(dst,src);\n", ssize); comprintf("\t dont_care_flags();\n"); } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\tint s=scratchie++;\n" "\tint tmp=scratchie++;\n" "\tmov_l_rr(s,src);\n"); if (curi->size == sz_byte) comprintf("\tand_l_ri(s,7);\n"); else comprintf("\tand_l_ri(s,31);\n"); comprintf("\tbtr_l_rr(dst,s);\n" /* Answer now in C */ "\tsbb_l(s,s);\n" /* s is 0 if bit was 0, -1 otherwise */ "\tmake_flags_live();\n" /* Get the flags back */ "\tdont_care_flags();\n"); if (!noflags) { comprintf("\tstart_needflags();\n" "\tset_zero(s,tmp);\n" "\tlive_flags();\n" "\tend_needflags();\n"); } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_bset(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_BSET_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_BSET_%s(dst,src);\n", ssize); comprintf("\t dont_care_flags();\n"); } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\tint s=scratchie++;\n" "\tint tmp=scratchie++;\n" "\tmov_l_rr(s,src);\n"); if (curi->size == sz_byte) comprintf("\tand_l_ri(s,7);\n"); else comprintf("\tand_l_ri(s,31);\n"); comprintf("\tbts_l_rr(dst,s);\n" /* Answer now in C */ "\tsbb_l(s,s);\n" /* s is 0 if bit was 0, -1 otherwise */ "\tmake_flags_live();\n" /* Get the flags back */ "\tdont_care_flags();\n"); if (!noflags) { comprintf("\tstart_needflags();\n" "\tset_zero(s,tmp);\n" "\tlive_flags();\n" "\tend_needflags();\n"); } genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_btst(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); // If we are not interested in flags it is not necessary to do // anything with the data if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_BTST_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t dont_care_flags();\n"); } #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\tint s=scratchie++;\n" "\tint tmp=scratchie++;\n" "\tmov_l_rr(s,src);\n"); if (curi->size == sz_byte) comprintf("\tand_l_ri(s,7);\n"); else comprintf("\tand_l_ri(s,31);\n"); comprintf("\tbt_l_rr(dst,s);\n" /* Answer now in C */ "\tsbb_l(s,s);\n" /* s is 0 if bit was 0, -1 otherwise */ "\tmake_flags_live();\n" /* Get the flags back */ "\tdont_care_flags();\n"); if (!noflags) { comprintf("\tstart_needflags();\n" "\tset_zero(s,tmp);\n" "\tlive_flags();\n" "\tend_needflags();\n"); } #endif } static void gen_clr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 2, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_CLR(tmp);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_CLR(tmp);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else genamode(curi->smode, "srcreg", curi->size, "src", 2, 0); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0);\n"); genflags(flag_logical, curi->size, "dst", "", ""); genastore("dst", curi->smode, "srcreg", curi->size, "src"); #endif } static void gen_cmp(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\t dont_care_flags();\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_CMP_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("/* Weird --- CMP with noflags ;-) */\n"); } #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); genflags(flag_cmp, curi->size, "", "src", "dst"); #endif } static void gen_cmpa(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_long, "dst", 1, 0); start_brace(); if (!noflags) { comprintf("\t dont_care_flags();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_CMPA_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\tdont_care_flags();\n"); comprintf("/* Weird --- CMP with noflags ;-) */\n"); } #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_long, "dst", 1, 0); start_brace(); comprintf("\tint tmps=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(tmps,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(tmps,src);\n"); break; case sz_long: comprintf("tmps=src;\n"); break; default: assert(0); break; } genflags(flag_cmp, sz_long, "", "tmps", "dst"); #endif } static void gen_dbcc(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if 0 isjump; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "offs", 1, 0); comprintf("uae_u32 voffs;\n"); comprintf("voffs = get_const(offs);\n"); /* That offs is an immediate, so we can clobber it with abandon */ switch (curi->size) { case sz_word: comprintf("\t voffs = (uae_s32)((uae_s16)voffs);\n"); break; default: assert(0); /* Seems this only comes in word flavour */ break; } comprintf("\t voffs -= m68k_pc_offset - m68k_pc_offset_thisinst - 2;\n"); comprintf("\t voffs += (uintptr)comp_pc_p + m68k_pc_offset;\n"); comprintf("\t add_const_v(PC_P, m68k_pc_offset);\n"); comprintf("\t m68k_pc_offset = 0;\n"); start_brace(); if (curi->cc >= 2) { comprintf("\t make_flags_live();\n"); /* Load the flags */ } assert(curi->size == sz_word); switch (curi->cc) { case 0: /* This is an elaborate nop? */ break; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: comprintf("\t start_needflags();\n"); comprintf("\t jnf_DBcc(src,voffs,%d);\n", curi->cc); comprintf("\t end_needflags();\n"); break; default: assert(0); break; } genastore("src", curi->smode, "srcreg", curi->size, "src"); gen_update_next_handler(); #else isjump; uses_cmov; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "offs", 1, 0); /* That offs is an immediate, so we can clobber it with abandon */ switch (curi->size) { case sz_word: comprintf("\tsign_extend_16_rr(offs,offs);\n"); break; default: assert(0); /* Seems this only comes in word flavour */ break; } comprintf("\tsub_l_ri(offs,m68k_pc_offset-m68k_pc_offset_thisinst-2);\n"); comprintf("\tarm_ADD_l_ri(offs,(uintptr)comp_pc_p);\n"); /* New PC, once the offset_68k is * also added */ /* Let's fold in the m68k_pc_offset at this point */ comprintf("\tarm_ADD_l_ri(offs,m68k_pc_offset);\n"); comprintf("\tarm_ADD_l_ri(PC_P,m68k_pc_offset);\n"); comprintf("\tm68k_pc_offset=0;\n"); start_brace(); comprintf("\tint nsrc=scratchie++;\n"); if (curi->cc >= 2) { comprintf("\tmake_flags_live();\n"); /* Load the flags */ } assert (curi->size == sz_word); switch (curi->cc) { case 0: /* This is an elaborate nop? */ break; case 1: comprintf("\tstart_needflags();\n"); comprintf("\tsub_w_ri(src,1);\n"); comprintf("\t end_needflags();\n"); start_brace(); comprintf("\tuae_u32 v2,v;\n" "\tuae_u32 v1=get_const(PC_P);\n"); comprintf("\tv2=get_const(offs);\n" "\tregister_branch(v1,v2,%d);\n", NATIVE_CC_CC); break; case 8: failure; break; /* Work out details! FIXME */ case 9: failure; break; /* Not critical, though! */ case 2: case 3: case 4: case 5: case 6: case 7: case 10: case 11: case 12: case 13: case 14: case 15: comprintf("\tmov_l_rr(nsrc,src);\n"); comprintf("\tlea_l_brr(scratchie,src,(uae_s32)-1);\n" "\tmov_w_rr(src,scratchie);\n"); comprintf("\tcmov_l_rr(offs,PC_P,%d);\n", cond_codes[curi->cc]); comprintf("\tcmov_l_rr(src,nsrc,%d);\n", cond_codes[curi->cc]); /* OK, now for cc=true, we have src==nsrc and offs==PC_P, so whether we move them around doesn't matter. However, if cc=false, we have offs==jump_pc, and src==nsrc-1 */ comprintf("\t start_needflags();\n"); comprintf("\ttest_w_rr(nsrc,nsrc);\n"); comprintf("\t end_needflags();\n"); comprintf("\tcmov_l_rr(PC_P,offs,%d);\n", NATIVE_CC_NE); break; default: assert(0); break; } genastore("src", curi->smode, "srcreg", curi->size, "src"); gen_update_next_handler(); #endif } static void gen_eor(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t jff_EOR_%s(tmp,dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_EOR(tmp,dst,src);\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_eor, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_eorsr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_EORSR(ARM_CCR_MAP[src & 0xF], ((src & 0x10) >> 4));\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } #else (void) curi; failure; isjump; #endif } static void gen_exg(uae_u32 opcode, struct instr *curi, const char* ssize) { #if 0 #else (void) opcode; (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\tint tmp=scratchie++;\n" "\tmov_l_rr(tmp,src);\n"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_ext(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", sz_long, "src", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_EXT_%s(tmp,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_EXT_%s(tmp,src);\n", ssize); } genastore("tmp", curi->smode, "srcreg", curi->size == sz_word ? sz_word : sz_long, "src"); #else (void) ssize; genamode(curi->smode, "srcreg", sz_long, "src", 1, 0); comprintf("\tdont_care_flags();\n"); start_brace(); switch (curi->size) { case sz_byte: comprintf("\tint dst = src;\n" "\tsign_extend_8_rr(src,src);\n"); break; case sz_word: comprintf("\tint dst = scratchie++;\n" "\tsign_extend_8_rr(dst,src);\n"); break; case sz_long: comprintf("\tint dst = src;\n" "\tsign_extend_16_rr(src,src);\n"); break; default: assert(0); break; } genflags(flag_logical, curi->size == sz_word ? sz_word : sz_long, "dst", "", ""); genastore("dst", curi->smode, "srcreg", curi->size == sz_word ? sz_word : sz_long, "src"); #endif } static void gen_lsl(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); comprintf("\t int tmp=scratchie++;\n"); if (curi->smode != immi) { if (!noflags) { start_brace(); comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_LSL_%s_reg(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { start_brace(); comprintf("\t jnf_LSL_reg(tmp,data,cnt);\n"); } } else { start_brace(); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_LSL_%s_imm(tmp,data,srcreg);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_LSL_imm(tmp,data,srcreg);\n"); } } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); if (curi->smode != immi) { if (!noflags) { uses_cmov; start_brace(); comprintf("\tint highmask;\n" "\tint cdata=scratchie++;\n" "\tint tmpcnt=scratchie++;\n"); comprintf("\tmov_l_rr(tmpcnt,cnt);\n" "\tand_l_ri(tmpcnt,63);\n" "\tmov_l_ri(cdata,0);\n" "\tcmov_l_rr(cdata,data,%d);\n", NATIVE_CC_NE); /* cdata is now either data (for shift count!=0) or 0 (for shift count==0) */ switch (curi->size) { case sz_byte: comprintf("\tshll_b_rr(data,cnt);\n" "\thighmask=0x38;\n"); break; case sz_word: comprintf("\tshll_w_rr(data,cnt);\n" "\thighmask=0x30;\n"); break; case sz_long: comprintf("\tshll_l_rr(data,cnt);\n" "\thighmask=0x20;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(scratchie,data,%d);\n", NATIVE_CC_EQ); switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data,scratchie);\n"); break; case sz_word: comprintf("\tmov_w_rr(data,scratchie);\n"); break; case sz_long: comprintf("\tmov_l_rr(data,scratchie);\n"); break; default: assert(0); break; } /* Result of shift is now in data. Now we need to determine the carry by shifting cdata one less */ comprintf("\tsub_l_ri(tmpcnt,1);\n"); switch (curi->size) { case sz_byte: comprintf("\tshll_b_rr(cdata,tmpcnt);\n"); break; case sz_word: comprintf("\tshll_w_rr(cdata,tmpcnt);\n"); break; case sz_long: comprintf("\tshll_l_rr(cdata,tmpcnt);\n"); break; default: assert(0); break; } comprintf("test_l_ri(tmpcnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(cdata,scratchie,%d);\n", NATIVE_CC_NE); /* And create the flags */ comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); comprintf("\t bt_l_ri(cdata,7);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); comprintf("\t bt_l_ri(cdata,15);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); comprintf("\t bt_l_ri(cdata,31);\n"); break; } comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); genastore("data", curi->dmode, "dstreg", curi->size, "data"); } else { uses_cmov; start_brace(); comprintf("\tint highmask;\n"); switch (curi->size) { case sz_byte: comprintf("\tshll_b_rr(data,cnt);\n" "\thighmask=0x38;\n"); break; case sz_word: comprintf("\tshll_w_rr(data,cnt);\n" "\thighmask=0x30;\n"); break; case sz_long: comprintf("\tshll_l_rr(data,cnt);\n" "\thighmask=0x20;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(scratchie,data,%d);\n", NATIVE_CC_EQ); switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data,scratchie);\n"); break; case sz_word: comprintf("\tmov_w_rr(data,scratchie);\n"); break; case sz_long: comprintf("\tmov_l_rr(data,scratchie);\n"); break; default: assert(0); break; } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } } else { start_brace(); comprintf("\tint tmp=scratchie++;\n" "\tint bp;\n" "\tmov_l_rr(tmp,data);\n"); switch (curi->size) { case sz_byte: comprintf("\tshll_b_ri(data,srcreg);\n" "\tbp=8-srcreg;\n"); break; case sz_word: comprintf("\tshll_w_ri(data,srcreg);\n" "\tbp=16-srcreg;\n"); break; case sz_long: comprintf("\tshll_l_ri(data,srcreg);\n" "\tbp=32-srcreg;\n"); break; default: assert(0); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(tmp,bp);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } #endif } static void gen_lslw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_LSLW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_LSLW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_lsr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); comprintf("\t int tmp=scratchie++;\n"); if (curi->smode != immi) { if (!noflags) { start_brace(); comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_LSR_%s_reg(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { start_brace(); comprintf("\t jnf_LSR_%s_reg(tmp,data,cnt);\n", ssize); } } else { start_brace(); char *op; if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); op = "ff"; } else op = "nf"; comprintf("\t j%s_LSR_%s_imm(tmp,data,srcreg);\n", op, ssize); if (!noflags) { comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) ssize; mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); if (curi->smode != immi) { if (!noflags) { uses_cmov; start_brace(); comprintf("\tint highmask;\n" "\tint cdata=scratchie++;\n" "\tint tmpcnt=scratchie++;\n"); comprintf("\tmov_l_rr(tmpcnt,cnt);\n" "\tand_l_ri(tmpcnt,63);\n" "\tmov_l_ri(cdata,0);\n" "\tcmov_l_rr(cdata,data,%d);\n", NATIVE_CC_NE); /* cdata is now either data (for shift count!=0) or 0 (for shift count==0) */ switch (curi->size) { case sz_byte: comprintf("\tshrl_b_rr(data,cnt);\n" "\thighmask=0x38;\n"); break; case sz_word: comprintf("\tshrl_w_rr(data,cnt);\n" "\thighmask=0x30;\n"); break; case sz_long: comprintf("\tshrl_l_rr(data,cnt);\n" "\thighmask=0x20;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(scratchie,data,%d);\n", NATIVE_CC_EQ); switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data,scratchie);\n"); break; case sz_word: comprintf("\tmov_w_rr(data,scratchie);\n"); break; case sz_long: comprintf("\tmov_l_rr(data,scratchie);\n"); break; default: assert(0); break; } /* Result of shift is now in data. Now we need to determine the carry by shifting cdata one less */ comprintf("\tsub_l_ri(tmpcnt,1);\n"); switch (curi->size) { case sz_byte: comprintf("\tshrl_b_rr(cdata,tmpcnt);\n"); break; case sz_word: comprintf("\tshrl_w_rr(cdata,tmpcnt);\n"); break; case sz_long: comprintf("\tshrl_l_rr(cdata,tmpcnt);\n"); break; default: assert(0); break; } comprintf("test_l_ri(tmpcnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(cdata,scratchie,%d);\n", NATIVE_CC_NE); /* And create the flags */ comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(cdata,0);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); genastore("data", curi->dmode, "dstreg", curi->size, "data"); } else { uses_cmov; start_brace(); comprintf("\tint highmask;\n"); switch (curi->size) { case sz_byte: comprintf("\tshrl_b_rr(data,cnt);\n" "\thighmask=0x38;\n"); break; case sz_word: comprintf("\tshrl_w_rr(data,cnt);\n" "\thighmask=0x30;\n"); break; case sz_long: comprintf("\tshrl_l_rr(data,cnt);\n" "\thighmask=0x20;\n"); break; default: assert(0); break; } comprintf("test_l_ri(cnt,highmask);\n" "mov_l_ri(scratchie,0);\n" "cmov_l_rr(scratchie,data,%d);\n", NATIVE_CC_EQ); switch (curi->size) { case sz_byte: comprintf("\tmov_b_rr(data,scratchie);\n"); break; case sz_word: comprintf("\tmov_w_rr(data,scratchie);\n"); break; case sz_long: comprintf("\tmov_l_rr(data,scratchie);\n"); break; default: assert(0); break; } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } } else { start_brace(); comprintf("\tint tmp=scratchie++;\n" "\tint bp;\n" "\tmov_l_rr(tmp,data);\n"); switch (curi->size) { case sz_byte: comprintf("\tshrl_b_ri(data,srcreg);\n" "\tbp=srcreg-1;\n"); break; case sz_word: comprintf("\tshrl_w_ri(data,srcreg);\n" "\tbp=srcreg-1;\n"); break; case sz_long: comprintf("\tshrl_l_ri(data,srcreg);\n" "\tbp=srcreg-1;\n"); break; default: assert(0); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(tmp,bp);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("\t duplicate_carry();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); } #endif } static void gen_lsrw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp = scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_LSRW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_LSRW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_move(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) switch (curi->dmode) { case Dreg: case Areg: genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags && curi->dmode == Dreg) { comprintf("\t start_needflags();\n"); comprintf("\t jff_MOVE_%s(tmp, src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t tmp = src;\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); break; default: /* It goes to memory, not a register */ genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); comprintf("\t dont_care_flags();\n"); start_brace(); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_TST_%s(src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } genastore("src", curi->dmode, "dstreg", curi->size, "dst"); break; } #else (void) ssize; switch (curi->dmode) { case Dreg: case Areg: genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); genflags(flag_mov, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); break; default: /* It goes to memory, not a register */ genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); genflags(flag_logical, curi->size, "src", "", ""); genastore("src", curi->dmode, "dstreg", curi->size, "dst"); break; } #endif } static void gen_movea(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); start_brace(); comprintf("\t jnf_MOVEA_%s(dst, src);\n", ssize); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); start_brace(); comprintf("\tint tmps=scratchie++;\n"); switch (curi->size) { case sz_word: comprintf("\tsign_extend_16_rr(dst,src);\n"); break; case sz_long: comprintf("\tmov_l_rr(dst,src);\n"); break; default: assert(0); break; } genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #endif } static void gen_mull(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t uae_u16 extra=%s;\n", gen_nextiword()); comprintf("\t int r2=(extra>>12)&7;\n" "\t int tmp=scratchie++;\n"); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); /* The two operands are in dst and r2 */ if (!noflags) { comprintf("\t if (extra & 0x0400) {\n"); /* Need full 64 bit result */ comprintf("\t int r3=(extra & 7);\n"); comprintf("\t mov_l_rr(r3,dst);\n"); /* operands now in r3 and r2 */ comprintf("\t if (extra & 0x0800) { \n"); /* signed */ comprintf("\t\t jff_MULS64(r2,r3);\n"); comprintf("\t } else { \n"); comprintf("\t\t jff_MULU64(r2,r3);\n"); comprintf("\t } \n"); /* The result is in r2/r3, with r2 holding the lower 32 bits */ comprintf("\t } else {\n"); /* Only want 32 bit result */ /* operands in dst and r2, result goes into r2 */ /* shouldn't matter whether it's signed or unsigned?!? */ comprintf("\t if (extra & 0x0800) { \n"); /* signed */ comprintf("\t jff_MULS32(r2,dst);\n"); comprintf("\t } else { \n"); comprintf("\t\t jff_MULU32(r2,dst);\n"); comprintf("\t } \n"); /* The result is in r2, with r2 holding the lower 32 bits */ comprintf("\t }\n"); } else { comprintf("\t if (extra & 0x0400) {\n"); /* Need full 64 bit result */ comprintf("\t int r3=(extra & 7);\n"); comprintf("\t mov_l_rr(r3,dst);\n"); /* operands now in r3 and r2 */ comprintf("\t if (extra & 0x0800) { \n"); /* signed */ comprintf("\t\t jnf_MULS64(r2,r3);\n"); comprintf("\t } else { \n"); comprintf("\t\t jnf_MULU64(r2,r3);\n"); comprintf("\t } \n"); /* The result is in r2/r3, with r2 holding the lower 32 bits */ comprintf("\t } else {\n"); /* Only want 32 bit result */ /* operands in dst and r2, result foes into r2 */ /* shouldn't matter whether it's signed or unsigned?!? */ comprintf("\t if (extra & 0x0800) { \n"); /* signed */ comprintf("\t jnf_MULS32(r2,dst);\n"); comprintf("\t } else { \n"); comprintf("\t\t jnf_MULU32(r2,dst);\n"); comprintf("\t } \n"); /* The result is in r2, with r2 holding the lower 32 bits */ comprintf("\t }\n"); } #else if (!noflags) { failure; return; } comprintf("\tuae_u16 extra=%s;\n", gen_nextiword()); comprintf("\tint r2=(extra>>12)&7;\n" "\tint tmp=scratchie++;\n"); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); /* The two operands are in dst and r2 */ comprintf("\tif (extra&0x0400) {\n" /* Need full 64 bit result */ "\tint r3=(extra&7);\n" "\tmov_l_rr(r3,dst);\n"); /* operands now in r3 and r2 */ comprintf("\tif (extra&0x0800) { \n" /* signed */ "\t\timul_64_32(r2,r3);\n" "\t} else { \n" "\t\tmul_64_32(r2,r3);\n" "\t} \n"); /* The result is in r2/tmp, with r2 holding the lower 32 bits */ comprintf("\t} else {\n"); /* Only want 32 bit result */ /* operands in dst and r2, result foes into r2 */ /* shouldn't matter whether it's signed or unsigned?!? */ comprintf("\timul_32_32(r2,dst);\n" "\t}\n"); #endif } static void gen_muls(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", sz_word, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_word, "dst", 1, 0); start_brace(); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_MULS(dst,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_MULS(dst,src);\n"); } genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #else comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", sz_word, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_word, "dst", 1, 0); comprintf("\tsign_extend_16_rr(scratchie,src);\n" "\tsign_extend_16_rr(dst,dst);\n" "\timul_32_32(dst,scratchie);\n"); genflags(flag_logical, sz_long, "dst", "", ""); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #endif } static void gen_mulu(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", sz_word, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_word, "dst", 1, 0); start_brace(); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_MULU(dst,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_MULU(dst,src);\n"); } genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #else comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", sz_word, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_word, "dst", 1, 0); /* To do 16x16 unsigned multiplication, we actually use 32x32 signed, and zero-extend the registers first. That solves the problem of MUL needing dedicated registers on the x86 */ comprintf("\tzero_extend_16_rr(scratchie,src);\n" "\tzero_extend_16_rr(dst,dst);\n" "\timul_32_32(dst,scratchie);\n"); genflags(flag_logical, sz_long, "dst", "", ""); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #endif } static void gen_nbcd(uae_u32 opcode, struct instr *curi, const char* ssize) { #if 0 #else (void) opcode; (void) curi; (void) ssize; failure; /* Nope! */ #endif } static void gen_neg(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_NEG_%s(tmp,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); comprintf("\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_NEG(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0);\n"); genflags(flag_sub, curi->size, "", "src", "dst"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); #endif } static void gen_negx(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) isaddx; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int dst=scratchie++;\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_inverted_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_NEGX_%s(dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); comprintf("\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t restore_inverted_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_NEGX(dst,src);\n"); } genastore("dst", curi->smode, "srcreg", curi->size, "src"); #else (void) ssize; isaddx; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0);\n"); genflags(flag_subx, curi->size, "", "src", "dst"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); #endif } static void gen_not(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_NOT_%s(tmp,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_NOT(tmp,src);\n", ssize); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\tint dst=scratchie++;\n"); comprintf("\tmov_l_ri(dst,0xffffffff);\n"); genflags(flag_eor, curi->size, "", "src", "dst"); genastore("dst", curi->smode, "srcreg", curi->size, "src"); #endif } static void gen_or(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t jff_OR_%s(tmp, dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_OR(tmp, dst,src);\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_or, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_orsr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t start_needflags();\n"); comprintf("\t jff_ORSR(ARM_CCR_MAP[src & 0xF], ((src & 0x10) >> 4));\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } #else (void) curi; failure; isjump; #endif } static void gen_rol(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_ROL_%s(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_ROL_%s(tmp,data,cnt);\n", ssize); } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) ssize; mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); switch (curi->size) { case sz_long: comprintf("\t rol_l_rr(data,cnt);\n"); break; case sz_word: comprintf("\t rol_w_rr(data,cnt);\n"); break; case sz_byte: comprintf("\t rol_b_rr(data,cnt);\n"); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } comprintf("\t bt_l_ri(data,0x00);\n"); /* Set C */ comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); #endif } static void gen_rolw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp = scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_ROLW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_ROLW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_ror(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_ROR_%s(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_ROR_%s(tmp,data,cnt);\n", ssize); } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) ssize; mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } comprintf("\tdont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); switch (curi->size) { case sz_long: comprintf("\t ror_l_rr(data,cnt);\n"); break; case sz_word: comprintf("\t ror_w_rr(data,cnt);\n"); break; case sz_byte: comprintf("\t ror_b_rr(data,cnt);\n"); break; } if (!noflags) { comprintf("\tstart_needflags();\n"); comprintf("\tif (needed_flags & FLAG_ZNV)\n"); switch (curi->size) { case sz_byte: comprintf("\t test_b_rr(data,data);\n"); break; case sz_word: comprintf("\t test_w_rr(data,data);\n"); break; case sz_long: comprintf("\t test_l_rr(data,data);\n"); break; } switch (curi->size) { case sz_byte: comprintf("\t bt_l_ri(data,0x07);\n"); break; case sz_word: comprintf("\t bt_l_ri(data,0x0f);\n"); break; case sz_long: comprintf("\t bt_l_ri(data,0x1f);\n"); break; } comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } genastore("data", curi->dmode, "dstreg", curi->size, "data"); #endif } static void gen_rorw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp = scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_RORW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } else { comprintf("\t jnf_RORW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_roxl(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } isaddx; comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_ROXL_%s(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); } else { comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_ROXL_%s(tmp,data,cnt);\n", ssize); } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) curi; (void) ssize; failure; #endif } static void gen_roxlw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) isaddx; comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp = scratchie++;\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_ROXLW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); } else { comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_ROXLW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_roxr(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) mayfail; if (curi->smode == Dreg) { comprintf("if ((uae_u32)srcreg==(uae_u32)dstreg) {\n" " FAIL(1);\n" " return;\n" "} \n"); start_brace(); } isaddx; comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "cnt", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "data", 1, 0); start_brace(); comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_ROXR_%s(tmp,data,cnt);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); } else { comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_ROXR_%s(tmp,data,cnt);\n", ssize); } genastore("tmp", curi->dmode, "dstreg", curi->size, "data"); #else (void) curi; failure; #endif } static void gen_roxrw(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) isaddx; comprintf("\t dont_care_flags();\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf("\t int tmp = scratchie++;\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_ROXRW(tmp,src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); } else { comprintf("\t restore_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_ROXRW(tmp,src);\n"); } genastore("tmp", curi->smode, "srcreg", curi->size, "src"); #else (void) curi; failure; #endif } static void gen_sbcd(uae_u32 opcode, struct instr *curi, const char* ssize) { #if 0 #else (void) opcode; (void) curi; (void) ssize; failure; /* I don't think so! */ #endif } static void gen_scc(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if 0 genamode(curi->smode, "srcreg", curi->size, "src", 2, 0); start_brace(); comprintf("\t int val = scratchie++;\n"); switch (curi->cc) { case 0: /* Unconditional set */ case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: comprintf("\t make_flags_live();\n"); /* Load the flags */ comprintf("\t jnf_Scc_ri(val,%d);\n", curi->cc); break; default: assert(0); break; } genastore("val", curi->smode, "srcreg", curi->size, "src"); #else genamode(curi->smode, "srcreg", curi->size, "src", 2, 0); start_brace(); comprintf("\tint val = scratchie++;\n"); /* We set val to 0 if we really should use 255, and to 1 for real 0 */ switch (curi->cc) { case 0: /* Unconditional set */ comprintf("\tmov_l_ri(val,0);\n"); break; case 1: /* Unconditional not-set */ comprintf("\tmov_l_ri(val,1);\n"); break; case 8: failure; break; /* Work out details! FIXME */ case 9: failure; break; /* Not critical, though! */ case 2: case 3: case 4: case 5: case 6: case 7: case 10: case 11: case 12: case 13: case 14: case 15: comprintf("\tmake_flags_live();\n"); /* Load the flags */ /* All condition codes can be inverted by changing the LSB */ comprintf("\tsetcc(val,%d);\n", cond_codes[curi->cc] ^ 1); break; default: assert(0); break; } comprintf("\tsub_b_ri(val,1);\n"); genastore("val", curi->smode, "srcreg", curi->size, "src"); #endif } static void gen_sub(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); // Use tmp register to avoid destroying upper part in .B., .W cases comprintf("\t int tmp=scratchie++;\n"); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_SUB_%s(tmp,dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); comprintf( "\t if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_SUB_%s(tmp,dst,src);\n", ssize); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_sub, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_suba(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_long, "dst", 1, 0); start_brace(); comprintf("\t jnf_SUBA_%s(dst, src);\n", ssize); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #else (void) ssize; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", sz_long, "dst", 1, 0); start_brace(); comprintf("\tint tmp=scratchie++;\n"); switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(tmp,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(tmp,src);\n"); break; case sz_long: comprintf("\ttmp=src;\n"); break; default: assert(0); break; } comprintf("\tsub_l(dst,tmp);\n"); genastore("dst", curi->dmode, "dstreg", sz_long, "dst"); #endif } static void gen_subx(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; #if defined(USE_JIT2) isaddx; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); start_brace(); comprintf("\tint tmp=scratchie++;\n"); comprintf("\tdont_care_flags();\n"); if (!noflags) { comprintf("\t make_flags_live();\n"); comprintf("\t restore_inverted_carry();\n"); /* Reload the X flag into C */ comprintf("\t start_needflags();\n"); comprintf("\t jff_SUBX_%s(tmp,dst,src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); duplicate_carry(); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t restore_inverted_carry();\n"); /* Reload the X flag into C */ comprintf("\t jnf_SUBX(tmp,dst,src);\n"); } genastore("tmp", curi->dmode, "dstreg", curi->size, "dst"); #else (void) ssize; isaddx; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 1, 0); genflags(flag_subx, curi->size, "", "src", "dst"); genastore("dst", curi->dmode, "dstreg", curi->size, "dst"); #endif } static void gen_swap(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", sz_long, "src", 1, 0); comprintf("\t dont_care_flags();\n"); start_brace(); if (!noflags) { comprintf("\t start_needflags();\n"); comprintf("\t jff_SWAP(src);\n"); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); comprintf("if (!(needed_flags & FLAG_CZNV)) dont_care_flags();\n"); } else { comprintf("\t jnf_SWAP(src);\n"); } genastore("src", curi->smode, "srcreg", sz_long, "src"); #else genamode(curi->smode, "srcreg", sz_long, "src", 1, 0); comprintf("\tdont_care_flags();\n"); comprintf("\tarm_ROR_l_ri8(src,16);\n"); genflags(flag_logical, sz_long, "src", "", ""); genastore("src", curi->smode, "srcreg", sz_long, "src"); #endif } static void gen_tst(uae_u32 opcode, struct instr *curi, const char* ssize) { (void) opcode; (void) ssize; #if defined(USE_JIT2) genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); comprintf("\t dont_care_flags();\n"); if (!noflags) { start_brace(); comprintf("\t start_needflags();\n"); comprintf("\t jff_TST_%s(src);\n", ssize); comprintf("\t live_flags();\n"); comprintf("\t end_needflags();\n"); } #else genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); genflags(flag_logical, curi->size, "src", "", ""); #endif } static int /* returns zero for success, non-zero for failure */ gen_opcode(unsigned long int opcode) { struct instr *curi = table68k + opcode; const char* ssize = NULL; insn_n_cycles = 2; global_failure = 0; long_opcode = 0; global_isjump = 0; global_iscjump = 0; global_isaddx = 0; global_cmov = 0; global_fpu = 0; global_mayfail = 0; hack_opcode = opcode; endstr[0] = 0; start_brace(); comprintf("\tuae_u8 scratchie=S1;\n"); switch (curi->plev) { case 0: /* not privileged */ break; case 1: /* unprivileged only on 68000 */ if (cpu_level == 0) break; if (next_cpu_level < 0) next_cpu_level = 0; /* fall through */ case 2: /* priviledged */ failure; /* Easy ones first */ break; case 3: /* privileged if size == word */ if (curi->size == sz_byte) break; failure; break; } switch (curi->size) { case sz_byte: ssize = "b"; break; case sz_word: ssize = "w"; break; case sz_long: ssize = "l"; break; default: assert(0); break; } (void) ssize; switch (curi->mnemo) { case i_AND: gen_and(opcode, curi, ssize); break; case i_OR: gen_or(opcode, curi, ssize); break; case i_EOR: gen_eor(opcode, curi, ssize); break; case i_ORSR: gen_orsr(opcode, curi, ssize); break; case i_EORSR: gen_eorsr(opcode, curi, ssize); break; case i_ANDSR: gen_andsr(opcode, curi, ssize); break; case i_SUB: gen_sub(opcode, curi, ssize); break; case i_SUBA: gen_suba(opcode, curi, ssize); break; case i_SUBX: gen_subx(opcode, curi, ssize); break; case i_SBCD: gen_sbcd(opcode, curi, ssize); break; case i_ADD: gen_add(opcode, curi, ssize); break; case i_ADDA: gen_adda(opcode, curi, ssize); break; case i_ADDX: gen_addx(opcode, curi, ssize); break; case i_ABCD: gen_abcd(opcode, curi, ssize); break; case i_NEG: gen_neg(opcode, curi, ssize); break; case i_NEGX: gen_negx(opcode, curi, ssize); break; case i_NBCD: gen_nbcd(opcode, curi, ssize); break; case i_CLR: gen_clr(opcode, curi, ssize); break; case i_NOT: gen_not(opcode, curi, ssize); break; case i_TST: gen_tst(opcode, curi, ssize); break; case i_BCHG: gen_bchg(opcode, curi, ssize); break; case i_BCLR: gen_bclr(opcode, curi, ssize); break; case i_BSET: gen_bset(opcode, curi, ssize); break; case i_BTST: gen_btst(opcode, curi, ssize); break; case i_CMPM: case i_CMP: gen_cmp(opcode, curi, ssize); break; case i_CMPA: gen_cmpa(opcode, curi, ssize); break; /* The next two are coded a little unconventional, but they are doing * weird things... */ case i_MVPRM: isjump; failure; break; case i_MVPMR: isjump; failure; break; case i_MOVE: gen_move(opcode, curi, ssize); break; case i_MOVEA: gen_movea(opcode, curi, ssize); break; case i_MVSR2: isjump; failure; break; case i_MV2SR: isjump; failure; break; case i_SWAP: gen_swap(opcode, curi, ssize); break; case i_EXG: gen_exg(opcode, curi, ssize); break; case i_EXT: gen_ext(opcode, curi, ssize); break; case i_MVMEL: genmovemel(opcode); break; case i_MVMLE: genmovemle(opcode); break; case i_TRAP: isjump; failure; break; case i_MVR2USP: isjump; failure; break; case i_MVUSP2R: isjump; failure; break; case i_RESET: isjump; failure; break; case i_NOP: break; case i_STOP: isjump; failure; break; case i_RTE: isjump; failure; break; case i_RTD: genamode(curi->smode, "srcreg", curi->size, "offs", 1, 0); /* offs is constant */ comprintf("\tarm_ADD_l_ri8(offs,4);\n"); start_brace(); comprintf("\tint newad=scratchie++;\n" "\treadlong(15,newad,scratchie);\n" "\tmov_l_mr((uintptr)®s.pc,newad);\n" "\tget_n_addr_jmp(newad,PC_P,scratchie);\n" "\tmov_l_mr((uintptr)®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n" "\tarm_ADD_l(15,offs);\n"); gen_update_next_handler(); isjump; break; case i_LINK: genamode(curi->smode, "srcreg", sz_long, "src", 1, 0); genamode(curi->dmode, "dstreg", curi->size, "offs", 1, 0); comprintf("\tsub_l_ri(15,4);\n" "\twritelong_clobber(15,src,scratchie);\n" "\tmov_l_rr(src,15);\n"); if (curi->size == sz_word) comprintf("\tsign_extend_16_rr(offs,offs);\n"); comprintf("\tarm_ADD_l(15,offs);\n"); genastore("src", curi->smode, "srcreg", sz_long, "src"); break; case i_UNLK: genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); comprintf("\tmov_l_rr(15,src);\n" "\treadlong(15,src,scratchie);\n" "\tarm_ADD_l_ri8(15,4);\n"); genastore("src", curi->smode, "srcreg", curi->size, "src"); break; case i_RTS: comprintf("\tint newad=scratchie++;\n" "\treadlong(15,newad,scratchie);\n" "\tmov_l_mr((uintptr)®s.pc,newad);\n" "\tget_n_addr_jmp(newad,PC_P,scratchie);\n" "\tmov_l_mr((uintptr)®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n" "\tlea_l_brr(15,15,4);\n"); gen_update_next_handler(); isjump; break; case i_TRAPV: isjump; failure; break; case i_RTR: isjump; failure; break; case i_JSR: isjump; genamode(curi->smode, "srcreg", curi->size, "src", 0, 0); start_brace(); comprintf( "\tuae_u32 retadd=start_pc+((char *)comp_pc_p-(char *)start_pc_p)+m68k_pc_offset;\n"); comprintf("\tint ret=scratchie++;\n" "\tmov_l_ri(ret,retadd);\n" "\tsub_l_ri(15,4);\n" "\twritelong_clobber(15,ret,scratchie);\n"); comprintf("\tmov_l_mr((uintptr)®s.pc,srca);\n" "\tget_n_addr_jmp(srca,PC_P,scratchie);\n" "\tmov_l_mr((uintptr)®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n"); gen_update_next_handler(); break; case i_JMP: isjump; genamode(curi->smode, "srcreg", curi->size, "src", 0, 0); comprintf("\tmov_l_mr((uintptr)®s.pc,srca);\n" "\tget_n_addr_jmp(srca,PC_P,scratchie);\n" "\tmov_l_mr((uintptr)®s.pc_oldp,PC_P);\n" "\tm68k_pc_offset=0;\n"); gen_update_next_handler(); break; case i_BSR: is_const_jump; genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); start_brace(); comprintf( "\tuae_u32 retadd=start_pc+((char *)comp_pc_p-(char *)start_pc_p)+m68k_pc_offset;\n"); comprintf("\tint ret=scratchie++;\n" "\tmov_l_ri(ret,retadd);\n" "\tsub_l_ri(15,4);\n" "\twritelong_clobber(15,ret,scratchie);\n"); comprintf("\tarm_ADD_l_ri(src,m68k_pc_offset_thisinst+2);\n"); comprintf("\tm68k_pc_offset=0;\n"); comprintf("\tarm_ADD_l(PC_P,src);\n"); comprintf("\tcomp_pc_p=(uae_u8*)get_const(PC_P);\n"); break; case i_Bcc: comprintf("\tuae_u32 v,v1,v2;\n"); genamode(curi->smode, "srcreg", curi->size, "src", 1, 0); /* That source is an immediate, so we can clobber it with abandon */ switch (curi->size) { case sz_byte: comprintf("\tsign_extend_8_rr(src,src);\n"); break; case sz_word: comprintf("\tsign_extend_16_rr(src,src);\n"); break; case sz_long: break; } comprintf( "\tsub_l_ri(src,m68k_pc_offset-m68k_pc_offset_thisinst-2);\n"); /* Leave the following as "add" --- it will allow it to be optimized away due to src being a constant ;-) */ comprintf("\tarm_ADD_l_ri(src,(uintptr)comp_pc_p);\n"); comprintf("\tmov_l_ri(PC_P,(uintptr)comp_pc_p);\n"); /* Now they are both constant. Might as well fold in m68k_pc_offset */ comprintf("\tarm_ADD_l_ri(src,m68k_pc_offset);\n"); comprintf("\tarm_ADD_l_ri(PC_P,m68k_pc_offset);\n"); comprintf("\tm68k_pc_offset=0;\n"); if (curi->cc >= 2) { comprintf("\tv1=get_const(PC_P);\n" "\tv2=get_const(src);\n" "\tregister_branch(v1,v2,%d);\n", cond_codes[curi->cc]); comprintf("\tmake_flags_live();\n"); /* Load the flags */ isjump; } else { is_const_jump; } switch (curi->cc) { case 0: /* Unconditional jump */ comprintf("\tmov_l_rr(PC_P,src);\n"); comprintf("\tcomp_pc_p=(uae_u8*)get_const(PC_P);\n"); break; case 1: break; /* This is silly! */ case 8: failure; break; /* Work out details! FIXME */ case 9: failure; break; /* Not critical, though! */ case 2: case 3: case 4: case 5: case 6: case 7: case 10: case 11: case 12: case 13: case 14: case 15: break; default: assert(0); break; } break; case i_LEA: genamode(curi->smode, "srcreg", curi->size, "src", 0, 0); genamode(curi->dmode, "dstreg", curi->size, "dst", 2, 0); genastore("srca", curi->dmode, "dstreg", curi->size, "dst"); break; case i_PEA: if (table68k[opcode].smode == Areg || table68k[opcode].smode == Aind || table68k[opcode].smode == Aipi || table68k[opcode].smode == Apdi || table68k[opcode].smode == Ad16 || table68k[opcode].smode == Ad8r) comprintf("if (srcreg==7) dodgy=1;\n"); genamode(curi->smode, "srcreg", curi->size, "src", 0, 0); genamode(Apdi, "7", sz_long, "dst", 2, 0); genastore("srca", Apdi, "7", sz_long, "dst"); break; case i_DBcc: gen_dbcc(opcode, curi, ssize); break; case i_Scc: gen_scc(opcode, curi, ssize); break; case i_DIVU: isjump; failure; break; case i_DIVS: isjump; failure; break; case i_MULU: gen_mulu(opcode, curi, ssize); break; case i_MULS: gen_muls(opcode, curi, ssize); break; case i_CHK: isjump; failure; break; case i_CHK2: isjump; failure; break; case i_ASR: gen_asr(opcode, curi, ssize); break; case i_ASL: gen_asl(opcode, curi, ssize); break; case i_LSR: gen_lsr(opcode, curi, ssize); break; case i_LSL: gen_lsl(opcode, curi, ssize); break; case i_ROL: gen_rol(opcode, curi, ssize); break; case i_ROR: gen_ror(opcode, curi, ssize); break; case i_ROXL: gen_roxl(opcode, curi, ssize); break; case i_ROXR: gen_roxr(opcode, curi, ssize); break; case i_ASRW: gen_asrw(opcode, curi, ssize); break; case i_ASLW: gen_aslw(opcode, curi, ssize); break; case i_LSRW: gen_lsrw(opcode, curi, ssize); break; case i_LSLW: gen_lslw(opcode, curi, ssize); break; case i_ROLW: gen_rolw(opcode, curi, ssize); break; case i_RORW: gen_rorw(opcode, curi, ssize); break; case i_ROXLW: gen_roxlw(opcode, curi, ssize); break; case i_ROXRW: gen_roxrw(opcode, curi, ssize); break; case i_MOVEC2: isjump; failure; break; case i_MOVE2C: isjump; failure; break; case i_CAS: failure; break; case i_CAS2: failure; break; case i_MOVES: /* ignore DFC and SFC because we have no MMU */ isjump; failure; break; case i_BKPT: /* only needed for hardware emulators */ isjump; failure; break; case i_CALLM: /* not present in 68030 */ isjump; failure; break; case i_RTM: /* not present in 68030 */ isjump; failure; break; case i_TRAPcc: isjump; failure; break; case i_DIVL: isjump; failure; break; case i_MULL: gen_mull(opcode, curi, ssize); break; case i_BFTST: case i_BFEXTU: case i_BFCHG: case i_BFEXTS: case i_BFCLR: case i_BFFFO: case i_BFSET: case i_BFINS: failure; break; case i_PACK: failure; break; case i_UNPK: failure; break; case i_TAS: failure; break; case i_FPP: uses_fpu; #ifdef USE_JIT_FPU mayfail; comprintf("\tuae_u16 extra=%s;\n",gen_nextiword()); swap_opcode(); comprintf("\tcomp_fpp_opp(opcode,extra);\n"); #else failure; #endif break; case i_FBcc: uses_fpu; #ifdef USE_JIT_FPU isjump; uses_cmov; mayfail; swap_opcode(); comprintf("\tcomp_fbcc_opp(opcode);\n"); #else isjump; failure; #endif break; case i_FDBcc: uses_fpu; isjump; failure; break; case i_FScc: uses_fpu; #ifdef USE_JIT_FPU mayfail; uses_cmov; comprintf("\tuae_u16 extra=%s;\n",gen_nextiword()); swap_opcode(); comprintf("\tcomp_fscc_opp(opcode,extra);\n"); #else failure; #endif break; case i_FTRAPcc: uses_fpu; isjump; failure; break; case i_FSAVE: uses_fpu; failure; break; case i_FRESTORE: uses_fpu; failure; break; case i_CINVL: case i_CINVP: case i_CINVA: isjump; /* Not really, but it's probably a good idea to stop translating at this point */ failure; comprintf("\tflush_icache();\n"); /* Differentiate a bit more? */ break; case i_CPUSHL: case i_CPUSHP: case i_CPUSHA: isjump; /* Not really, but it's probably a good idea to stop translating at this point */ failure; break; case i_MOVE16: gen_move16(opcode, curi); break; case i_EMULOP_RETURN: isjump; failure; break; case i_EMULOP: failure; break; case i_NATFEAT_ID: case i_NATFEAT_CALL: failure; break; case i_MMUOP: isjump; failure; break; default: assert(0); break; } comprintf("%s", endstr); finish_braces(); sync_m68k_pc(); if (global_mayfail) comprintf("\tif (failure) m68k_pc_offset=m68k_pc_offset_thisinst;\n"); return global_failure; } static void generate_includes(FILE * f) { fprintf(f, "#include \"sysdeps.h\"\n"); fprintf(f, "#include \"m68k.h\"\n"); fprintf(f, "#include \"memory-uae.h\"\n"); fprintf(f, "#include \"readcpu.h\"\n"); fprintf(f, "#include \"newcpu.h\"\n"); fprintf(f, "#include \"comptbl.h\"\n"); fprintf(f, "#include \"debug.h\"\n"); } static int postfix; static char *decodeEA (amodes mode, wordsizes size) { static char buffer[80]; buffer[0] = 0; switch (mode){ case Dreg: strcpy (buffer,"Dn"); break; case Areg: strcpy (buffer,"An"); break; case Aind: strcpy (buffer,"(An)"); break; case Aipi: strcpy (buffer,"(An)+"); break; case Apdi: strcpy (buffer,"-(An)"); break; case Ad16: strcpy (buffer,"(d16,An)"); break; case Ad8r: strcpy (buffer,"(d8,An,Xn)"); break; case PC16: strcpy (buffer,"(d16,PC)"); break; case PC8r: strcpy (buffer,"(d8,PC,Xn)"); break; case absw: strcpy (buffer,"(xxx).W"); break; case absl: strcpy (buffer,"(xxx).L"); break; case imm: switch (size){ case sz_byte: strcpy (buffer,"#.B"); break; case sz_word: strcpy (buffer,"#.W"); break; case sz_long: strcpy (buffer,"#.L"); break; default: break; } break; case imm0: strcpy (buffer,"#.B"); break; case imm1: strcpy (buffer,"#.W"); break; case imm2: strcpy (buffer,"#.L"); break; case immi: strcpy (buffer,"#"); break; default: break; } return buffer; } static char *outopcode (const char *name, int opcode) { static char out[100]; struct instr *ins; ins = &table68k[opcode]; strcpy (out, name); if (ins->smode == immi) strcat (out, "Q"); if (ins->size == sz_byte) strcat (out,".B"); if (ins->size == sz_word) strcat (out,".W"); if (ins->size == sz_long) strcat (out,".L"); strcat (out," "); if (ins->suse) strcat (out, decodeEA (ins->smode, ins->size)); if (ins->duse) { if (ins->suse) strcat (out,","); strcat (out, decodeEA (ins->dmode, ins->size)); } return out; } static void generate_one_opcode(int rp, int noflags) { int i; uae_u16 smsk, dmsk; int opcode = opcode_map[rp]; int aborted = 0; int have_srcreg = 0; int have_dstreg = 0; const char *name; if (table68k[opcode].mnemo == i_ILLG || table68k[opcode].clev > cpu_level) return; for (i = 0; lookuptab[i].name[0]; i++) { if (table68k[opcode].mnemo == lookuptab[i].mnemo) break; } if (table68k[opcode].handler != -1) return; switch (table68k[opcode].stype) { case 0: smsk = 7; break; case 1: smsk = 255; break; case 2: smsk = 15; break; case 3: smsk = 7; break; case 4: smsk = 7; break; case 5: smsk = 63; break; case 6: smsk = 255; break; case 7: smsk = 3; break; default: assert(0); break; } dmsk = 7; next_cpu_level = -1; if (table68k[opcode].suse && table68k[opcode].smode != imm && table68k[opcode].smode != imm0 && table68k[opcode].smode != imm1 && table68k[opcode].smode != imm2 && table68k[opcode].smode != absw && table68k[opcode].smode != absl && table68k[opcode].smode != PC8r && table68k[opcode].smode != PC16) { have_srcreg = 1; if (table68k[opcode].spos == -1) { if (((int) table68k[opcode].sreg) >= 128) comprintf("\tuae_s32 srcreg = (uae_s32)(uae_s8)%d;\n", (int) table68k[opcode].sreg); else comprintf("\tuae_s32 srcreg = %d;\n", (int) table68k[opcode].sreg); } else { char source[100]; int pos = table68k[opcode].spos; comprintf( "#if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU)\n"); if (pos < 8 && (smsk >> (8 - pos)) != 0) sprintf(source, "(((opcode >> %d) | (opcode << %d)) & %d)", pos ^ 8, 8 - pos, dmsk); else if (pos != 8) sprintf(source, "((opcode >> %d) & %d)", pos ^ 8, smsk); else sprintf(source, "(opcode & %d)", smsk); if (table68k[opcode].stype == 3) comprintf("\tuae_u32 srcreg = imm8_table[%s];\n", source); else if (table68k[opcode].stype == 1) comprintf("\tuae_u32 srcreg = (uae_s32)(uae_s8)%s;\n", source); else comprintf("\tuae_u32 srcreg = %s;\n", source); comprintf("#else\n"); if (pos) sprintf(source, "((opcode >> %d) & %d)", pos, smsk); else sprintf(source, "(opcode & %d)", smsk); if (table68k[opcode].stype == 3) comprintf("\tuae_s32 srcreg = imm8_table[%s];\n", source); else if (table68k[opcode].stype == 1) comprintf("\tuae_s32 srcreg = (uae_s32)(uae_s8)%s;\n", source); else comprintf("\tuae_s32 srcreg = %s;\n", source); comprintf("#endif\n"); } } if (table68k[opcode].duse /* Yes, the dmode can be imm, in case of LINK or DBcc */ && table68k[opcode].dmode != imm && table68k[opcode].dmode != imm0 && table68k[opcode].dmode != imm1 && table68k[opcode].dmode != imm2 && table68k[opcode].dmode != absw && table68k[opcode].dmode != absl) { have_dstreg = 1; if (table68k[opcode].dpos == -1) { if (((int) table68k[opcode].dreg) >= 128) comprintf("\tuae_s32 dstreg = (uae_s32)(uae_s8)%d;\n", (int) table68k[opcode].dreg); else comprintf("\tuae_s32 dstreg = %d;\n", (int) table68k[opcode].dreg); } else { int pos = table68k[opcode].dpos; comprintf( "#if defined(HAVE_GET_WORD_UNSWAPPED) && !defined(FULLMMU)\n"); if (pos < 8 && (dmsk >> (8 - pos)) != 0) comprintf( "\tuae_u32 dstreg = ((opcode >> %d) | (opcode << %d)) & %d;\n", pos ^ 8, 8 - pos, dmsk); else if (pos != 8) comprintf("\tuae_u32 dstreg = (opcode >> %d) & %d;\n", pos ^ 8, dmsk); else comprintf("\tuae_u32 dstreg = opcode & %d;\n", dmsk); comprintf("#else\n"); if (pos) comprintf("\tuae_u32 dstreg = (opcode >> %d) & %d;\n", pos, dmsk); else comprintf("\tuae_u32 dstreg = opcode & %d;\n", dmsk); comprintf("#endif\n"); } } if (have_srcreg && have_dstreg && (table68k[opcode].dmode == Areg || table68k[opcode].dmode == Aind || table68k[opcode].dmode == Aipi || table68k[opcode].dmode == Apdi || table68k[opcode].dmode == Ad16 || table68k[opcode].dmode == Ad8r) && (table68k[opcode].smode == Areg || table68k[opcode].smode == Aind || table68k[opcode].smode == Aipi || table68k[opcode].smode == Apdi || table68k[opcode].smode == Ad16 || table68k[opcode].smode == Ad8r)) { comprintf("\tuae_u32 dodgy=(srcreg==(uae_s32)dstreg);\n"); } else { comprintf("\tuae_u32 dodgy=0;\n"); } comprintf("\tuae_u32 m68k_pc_offset_thisinst=m68k_pc_offset;\n"); comprintf("\tm68k_pc_offset+=2;\n"); aborted = gen_opcode(opcode); { int flags = 0; if (global_isjump) flags |= 1; if (long_opcode) flags |= 2; if (global_cmov) flags |= 4; if (global_isaddx) flags |= 8; if (global_iscjump) flags |= 16; if (global_fpu) flags |= 32; comprintf("}\n"); name = lookuptab[i].name; if (aborted) { fprintf(stblfile, "{ NULL, 0x%08x, %d }, /* %s */\n", opcode, flags, name); com_discard(); } else { const char *tbl = noflags ? "nf" : "ff"; fprintf(stblfile, "{ op_%x_%d_comp_%s, %d, 0x%08x }, /* %s */\n", opcode, postfix, tbl, opcode, flags, name); fprintf(headerfile, "extern compop_func op_%x_%d_comp_%s;\n", opcode, postfix, tbl); printf ("/* %s */\n", outopcode (name, opcode)); printf( "void REGPARAM2 op_%x_%d_comp_%s(uae_u32 opcode) /* %s */\n{\n", opcode, postfix, tbl, name); com_flush(); } } opcode_next_clev[rp] = next_cpu_level; opcode_last_postfix[rp] = postfix; } static void generate_func(int noflags) { int i, j, rp; const char *tbl = noflags ? "nf" : "ff"; using_prefetch = 0; using_exception_3 = 0; for (i = 0; i < 1; i++) /* We only do one level! */ { cpu_level = 4 - i; postfix = i; fprintf(stblfile, "const struct comptbl op_smalltbl_%d_comp_%s[] = {\n", postfix, tbl); /* sam: this is for people with low memory (eg. me :)) */ printf("\n" "#if !defined(PART_1) && !defined(PART_2) && " "!defined(PART_3) && !defined(PART_4) && " "!defined(PART_5) && !defined(PART_6) && " "!defined(PART_7) && !defined(PART_8)" "\n" "#define PART_1 1\n" "#define PART_2 1\n" "#define PART_3 1\n" "#define PART_4 1\n" "#define PART_5 1\n" "#define PART_6 1\n" "#define PART_7 1\n" "#define PART_8 1\n" "#endif\n\n"); rp = 0; for (j = 1; j <= 8; ++j) { int k = (j * nr_cpuop_funcs) / 8; printf("#ifdef PART_%d\n", j); for (; rp < k; rp++) generate_one_opcode(rp, noflags); printf("#endif\n\n"); } fprintf(stblfile, "{ 0, 65536, 0 }};\n"); } } #if (defined(OS_cygwin) || defined(OS_mingw)) && defined(EXTENDED_SIGSEGV) void cygwin_mingw_abort() { #undef abort abort(); } #endif int main(void) { init_table68k(); opcode_map = (int *) malloc(sizeof(int) * nr_cpuop_funcs); opcode_last_postfix = (int *) malloc(sizeof(int) * nr_cpuop_funcs); opcode_next_clev = (int *) malloc(sizeof(int) * nr_cpuop_funcs); counts = (unsigned long *) malloc(65536 * sizeof(unsigned long)); read_counts(); /* It would be a lot nicer to put all in one file (we'd also get rid of * cputbl.h that way), but cpuopti can't cope. That could be fixed, but * I don't dare to touch the 68k version. */ headerfile = fopen("comptbl.h", "wb"); fprintf (headerfile, "" "extern const struct comptbl op_smalltbl_0_comp_nf[];\n" "extern const struct comptbl op_smalltbl_0_comp_ff[];\n" ""); stblfile = fopen("compstbl.cpp", "wb"); if (freopen("compemu.cpp", "wb", stdout) == NULL) { assert(0); } generate_includes(stdout); generate_includes(stblfile); printf("#include \"compiler/compemu.h\"\n"); noflags = 0; generate_func(noflags); free(opcode_map); free(opcode_last_postfix); free(opcode_next_clev); free(counts); opcode_map = (int *) malloc(sizeof(int) * nr_cpuop_funcs); opcode_last_postfix = (int *) malloc(sizeof(int) * nr_cpuop_funcs); opcode_next_clev = (int *) malloc(sizeof(int) * nr_cpuop_funcs); counts = (unsigned long *) malloc(65536 * sizeof(unsigned long)); read_counts(); noflags = 1; generate_func(noflags); free(opcode_map); free(opcode_last_postfix); free(opcode_next_clev); free(counts); free(table68k); fclose(stblfile); fclose(headerfile); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/jit/memory-uae.h000066400000000000000000000000511504763705000251320ustar00rootroot00000000000000#include "options.h" #include "memory.h" hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/maccess.h000066400000000000000000000050711504763705000237110ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator - CPU core * * Big endian memory access functions. * * Copyright 1996 Bernd Schmidt * * Adaptation to Hatari by Thomas Huth, Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef UAE_MACCESS_H #define UAE_MACCESS_H #include "sysdeps.h" #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #define be_swap32(x) ((uint32_t)(x)) #define be_swap16(x) ((uint16_t)(x)) #else #define be_swap32(x) bswap_32(x) #define be_swap16(x) bswap_16(x) #endif /* Can the actual CPU access unaligned memory? */ #ifndef CPU_CAN_ACCESS_UNALIGNED # if defined(__i386__) || defined(__x86_64__) || defined(__mc68020__) || \ defined(powerpc) || defined(__ppc__) || defined(__ppc64__) # define CPU_CAN_ACCESS_UNALIGNED 1 # else # define CPU_CAN_ACCESS_UNALIGNED 0 # endif #endif /* If the CPU can access unaligned memory, use these accelerated functions: */ #if CPU_CAN_ACCESS_UNALIGNED && !defined(ENABLE_UBSAN) static inline uae_u32 do_get_mem_long(void *a) { return be_swap32(*(uae_u32 *)a); } static inline uae_u16 do_get_mem_word(void *a) { return be_swap16(*(uae_u16 *)a); } static inline void do_put_mem_long(void *a, uae_u32 v) { *(uae_u32 *)a = be_swap32(v); } static inline void do_put_mem_word(void *a, uae_u16 v) { *(uae_u16 *)a = be_swap16(v); } #else /* Cpu can not access unaligned memory: */ static inline uae_u32 do_get_mem_long(void *a) { uae_u8 *b = (uae_u8 *)a; return ((uae_u32)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; } static inline uae_u16 do_get_mem_word(void *a) { uae_u8 *b = (uae_u8 *)a; return (b[0] << 8) | b[1]; } static inline void do_put_mem_long(void *a, uae_u32 v) { uae_u8 *b = (uae_u8 *)a; b[0] = v >> 24; b[1] = v >> 16; b[2] = v >> 8; b[3] = v; } static inline void do_put_mem_word(void *a, uae_u16 v) { uae_u8 *b = (uae_u8 *)a; b[0] = v >> 8; b[1] = v; } #endif /* CPU_CAN_ACCESS_UNALIGNED */ /* These are same for all architectures: */ static inline uae_u8 do_get_mem_byte(uae_u8 *a) { return *a; } static inline void do_put_mem_byte(uae_u8 *a, uae_u8 v) { *a = v; } STATIC_INLINE uae_u32 do_byteswap_32(uae_u32 v) { return bswap_32(v); } STATIC_INLINE uae_u16 do_byteswap_16(uae_u16 v) { return bswap_16(v); } STATIC_INLINE uae_u32 do_get_mem_word_unswapped(uae_u16 *a) { return *a; } #define call_mem_get_func(func, addr) ((*func)(addr)) #define call_mem_put_func(func, addr, v) ((*func)(addr, v)) #endif /* UAE_MACCESS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/machdep/000077500000000000000000000000001504763705000235205ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/machdep/m68k.c000066400000000000000000000117321504763705000244550ustar00rootroot00000000000000 #include "sysconfig.h" #include "sysdeps.h" #include "m68k.h" /* * Test CCR condition */ #ifndef SAHF_SETO_PROFITABLE int cctrue(int cc) { uae_u32 cznv = regflags.cznv; switch (cc) { case 0: return 1; /* T */ case 1: return 0; /* F */ case 2: return (cznv & (FLAGVAL_C | FLAGVAL_Z)) == 0; /* !CFLG && !ZFLG HI */ case 3: return (cznv & (FLAGVAL_C | FLAGVAL_Z)) != 0; /* CFLG || ZFLG LS */ case 4: return (cznv & FLAGVAL_C) == 0; /* !CFLG CC */ case 5: return (cznv & FLAGVAL_C) != 0; /* CFLG CS */ case 6: return (cznv & FLAGVAL_Z) == 0; /* !ZFLG NE */ case 7: return (cznv & FLAGVAL_Z) != 0; /* ZFLG EQ */ case 8: return (cznv & FLAGVAL_V) == 0; /* !VFLG VC */ case 9: return (cznv & FLAGVAL_V) != 0; /* VFLG VS */ case 10: return (cznv & FLAGVAL_N) == 0; /* !NFLG PL */ case 11: return (cznv & FLAGVAL_N) != 0; /* NFLG MI */ #if FLAGBIT_N > FLAGBIT_V case 12: return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & FLAGVAL_N) == 0; /* NFLG == VFLG GE */ case 13: return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & FLAGVAL_N) != 0; /* NFLG != VFLG LT */ case 14: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* !ZFLG && (NFLG == VFLG) GT */ return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & (FLAGVAL_N | FLAGVAL_Z)) == 0; case 15: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* ZFLG || (NFLG != VFLG) LE */ return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & (FLAGVAL_N | FLAGVAL_Z)) != 0; #else case 12: return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & FLAGVAL_V) == 0; /* NFLG == VFLG GE */ case 13: return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & FLAGVAL_V) != 0; /* NFLG != VFLG LT */ case 14: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* !ZFLG && (NFLG == VFLG) GT */ return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & (FLAGVAL_V | FLAGVAL_Z)) == 0; case 15: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* ZFLG || (NFLG != VFLG) LE */ return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & (FLAGVAL_V | FLAGVAL_Z)) != 0; #endif } return 0; } #else /* !SAHF_SETO_PROFITABLE */ int cctrue(int cc) { uae_u32 cznv = regflags.cznv; switch (cc) { case 0: return 1; /* T */ case 1: return 0; /* F */ case 2: return (cznv & (FLAGVAL_C | FLAGVAL_Z)) == 0; /* !CFLG && !ZFLG HI */ case 3: return (cznv & (FLAGVAL_C | FLAGVAL_Z)) != 0; /* CFLG || ZFLG LS */ case 4: return (cznv & FLAGVAL_C) == 0; /* !CFLG CC */ case 5: return (cznv & FLAGVAL_C) != 0; /* CFLG CS */ case 6: return (cznv & FLAGVAL_Z) == 0; /* !ZFLG NE */ case 7: return (cznv & FLAGVAL_Z) != 0; /* ZFLG EQ */ case 8: return (cznv & FLAGVAL_V) == 0; /* !VFLG VC */ case 9: return (cznv & FLAGVAL_V) != 0; /* VFLG VS */ case 10: return (cznv & FLAGVAL_N) == 0; /* !NFLG PL */ case 11: return (cznv & FLAGVAL_N) != 0; /* NFLG MI */ #if FLAGBIT_N > FLAGBIT_V case 12: return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & FLAGVAL_N) == 0; /* NFLG == VFLG GE */ case 13: return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & FLAGVAL_N) != 0; /* NFLG != VFLG LT */ case 14: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* !ZFLG && (NFLG == VFLG) GT */ return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & (FLAGVAL_N | FLAGVAL_Z)) == 0; case 15: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* ZFLG || (NFLG != VFLG) LE */ return (((cznv << (FLAGBIT_N - FLAGBIT_V)) ^ cznv) & (FLAGVAL_N | FLAGVAL_Z)) != 0; #else case 12: return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & FLAGVAL_V) == 0; /* NFLG == VFLG GE */ case 13: return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & FLAGVAL_V) != 0; /* NFLG != VFLG LT */ case 14: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* !ZFLG && (NFLG == VFLG) GT */ return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & (FLAGVAL_V | FLAGVAL_Z)) == 0; case 15: cznv &= (FLAGVAL_N | FLAGVAL_Z | FLAGVAL_V); /* ZFLG || (NFLG != VFLG) LE */ return (((cznv << (FLAGBIT_V - FLAGBIT_N)) ^ cznv) & (FLAGVAL_V | FLAGVAL_Z)) != 0; #endif } return 0; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/machdep/m68k.h000066400000000000000000000076541504763705000244720ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68000 emulation - machine dependent bits * * Copyright 1996 Bernd Schmidt * Copyright 2004-2005 Richard Drummond */ /* * Machine dependent structure for holding the 68k CCR flags */ extern int cctrue(int cc); #ifndef SAHF_SETO_PROFITABLE struct flag_struct { #if defined(CPU_x86_64) uae_u64 cznv; uae_u64 x; #else uae_u32 cznv; uae_u32 x; #endif }; extern struct flag_struct regflags; /* * The bits in the cznv field in the above structure are assigned to * allow the easy mirroring of the x86 rFLAGS register. * * The 68k CZNV flags are thus assigned in cznv as: * * 76543210 FEDCBA98 --------- --------- * SZxxxxxC xxxxVxxx xxxxxxxxx xxxxxxxxx */ #define FLAGBIT_N 7 #define FLAGBIT_Z 6 #define FLAGBIT_C 0 #define FLAGBIT_V 11 #define FLAGBIT_X 0 #define FLAGVAL_N (1 << FLAGBIT_N) #define FLAGVAL_Z (1 << FLAGBIT_Z) #define FLAGVAL_C (1 << FLAGBIT_C) #define FLAGVAL_V (1 << FLAGBIT_V) #define FLAGVAL_X (1 << FLAGBIT_X) #define SET_ZFLG(y) (regflags.cznv = (regflags.cznv & ~FLAGVAL_Z) | (((y) & 1) << FLAGBIT_Z)) #define SET_CFLG(y) (regflags.cznv = (regflags.cznv & ~FLAGVAL_C) | (((y) & 1) << FLAGBIT_C)) #define SET_VFLG(y) (regflags.cznv = (regflags.cznv & ~FLAGVAL_V) | (((y) & 1) << FLAGBIT_V)) #define SET_NFLG(y) (regflags.cznv = (regflags.cznv & ~FLAGVAL_N) | (((y) & 1) << FLAGBIT_N)) #define SET_XFLG(y) (regflags.x = ((y) & 1) << FLAGBIT_X) #define GET_ZFLG() ((regflags.cznv >> FLAGBIT_Z) & 1) #define GET_CFLG() ((regflags.cznv >> FLAGBIT_C) & 1) #define GET_VFLG() ((regflags.cznv >> FLAGBIT_V) & 1) #define GET_NFLG() ((regflags.cznv >> FLAGBIT_N) & 1) #define GET_XFLG() ((regflags.x >> FLAGBIT_X) & 1) #define CLEAR_CZNV() (regflags.cznv = 0) #define GET_CZNV() (regflags.cznv) #define IOR_CZNV(X) (regflags.cznv |= (X)) #define SET_CZNV(X) (regflags.cznv = (X)) #define COPY_CARRY() (regflags.x = regflags.cznv >> (FLAGBIT_C - FLAGBIT_X)) #else /* !SAHF_SETO_PROFITABLE */ /* * Machine dependent structure for holding the 68k CCR flags */ struct flag_struct { uae_u32 cznv; uae_u32 x; }; extern struct flag_struct regflags; /* * The bits in the cznv field in the above structure are assigned to * allow the easy mirroring of the x86 condition flags. (For example, * from the AX register - the x86 overflow flag can be copied to AL * with a setto %AL instr and the other flags copied to AH with an * lahf instr). * * The 68k CZNV flags are thus assigned in cznv as: * * <--AL--> <--AH--> * 76543210 FEDCBA98 --------- --------- * xxxxxxxV NZxxxxxC xxxxxxxxx xxxxxxxxx */ #define FLAGBIT_N 15 #define FLAGBIT_Z 14 #define FLAGBIT_C 8 #define FLAGBIT_V 0 #define FLAGBIT_X 0 /* must be in position 0 for duplicate_carry() to work */ #define FLAGVAL_N (1 << FLAGBIT_N) #define FLAGVAL_Z (1 << FLAGBIT_Z) #define FLAGVAL_C (1 << FLAGBIT_C) #define FLAGVAL_V (1 << FLAGBIT_V) #define FLAGVAL_X (1 << FLAGBIT_X) #define SET_ZFLG(y) (regflags.cznv = (((uae_u32)regflags.cznv) & ~FLAGVAL_Z) | (((y) & 1) << FLAGBIT_Z)) #define SET_CFLG(y) (regflags.cznv = (((uae_u32)regflags.cznv) & ~FLAGVAL_C) | (((y) & 1) << FLAGBIT_C)) #define SET_VFLG(y) (regflags.cznv = (((uae_u32)regflags.cznv) & ~FLAGVAL_V) | (((y) & 1) << FLAGBIT_V)) #define SET_NFLG(y) (regflags.cznv = (((uae_u32)regflags.cznv) & ~FLAGVAL_N) | (((y) & 1) << FLAGBIT_N)) #define SET_XFLG(y) (regflags.x = ((y) & 1) << FLAGBIT_X) #define GET_ZFLG() ((regflags.cznv >> FLAGBIT_Z) & 1) #define GET_CFLG() ((regflags.cznv >> FLAGBIT_C) & 1) #define GET_VFLG() ((regflags.cznv >> FLAGBIT_V) & 1) #define GET_NFLG() ((regflags.cznv >> FLAGBIT_N) & 1) #define GET_XFLG() ((regflags.x >> FLAGBIT_X) & 1) #define CLEAR_CZNV() (regflags.cznv = 0) #define GET_CZNV() (regflags.cznv) #define IOR_CZNV(X) (regflags.cznv |= (X)) #define SET_CZNV(X) (regflags.cznv = (X)) #define COPY_CARRY() (regflags.x = regflags.cznv >> (FLAGBIT_C - FLAGBIT_X)) #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/machdep/rpt.h000066400000000000000000000000261504763705000244740ustar00rootroot00000000000000#include "uae/time.h" hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/memory.c000066400000000000000000001615471504763705000236110ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator - CPU core * * Memory management * * (c) 1995 Bernd Schmidt * * Adaptation to Hatari by Thomas Huth * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ const char Memory_fileid[] = "Hatari memory.c"; #include #include "main.h" #include "sysdeps.h" #include "hatari-glue.h" #include "maccess.h" #include "memory.h" #include "tos.h" #include "ide.h" #include "ioMem.h" #include "reset.h" #include "screen.h" #include "stMemory.h" #include "m68000.h" #include "configuration.h" #include "video.h" #include "newcpu.h" /* Set illegal_mem to 1 for debug output: */ #define illegal_mem 1 static int illegal_count; static uae_u32 STmem_size; uae_u32 TTmem_size = 0; static uae_u32 TTmem_mask; #define STmem_start 0x00000000 #define ROMmem_start 0x00E00000 #define IdeMem_start 0x00F00000 #define IOmem_start 0x00FF0000 #define TTmem_start 0x01000000 /* TOS 3 and TOS 4 always expect extra RAM at this address */ #define TTmem_end 0x80000000 /* Max value for end of TT ram, which gives 2047 MB */ #define VMEmem_start_TT 0xFE000000 /* MegaSTE and TT support a VME bus. This returns a bus error */ #define VMEmem_end_TT 0xFF000000 /* when no board is plugged. Regions are different between MegaSTE and TT */ #define VMEmem_start_MegaSTE 0x00A00000 #define VMEmem_end_MegaSTE 0x00E00000 #define IdeMem_size 65536 #define IOmem_size 65536 #define ROMmem_size (0x00FF0000 - 0x00E00000) /* So we cover both possible ROM regions + cartridge */ #define STmem_mask 0x00ffffff #define ROMmem_mask 0x00ffffff #define IdeMem_mask (IdeMem_size - 1) #define IOmem_mask (IOmem_size - 1) /* Some prototypes: */ static int REGPARAM3 STmem_check (uaecptr addr, uae_u32 size) REGPARAM; static uae_u8 * REGPARAM3 STmem_xlate (uaecptr addr) REGPARAM; #ifdef WINUAE_FOR_HATARI #undef NATMEM_OFFSET /* Don't use shm in Hatari */ #endif #ifdef NATMEM_OFFSET bool canbang; int candirect = -1; #endif #ifdef JIT /* Set by each memory handler that does not simply access real memory. */ int special_mem, special_mem_default; /* do not use get_n_addr */ int jit_n_addr_unsafe; #endif #ifdef NATMEM_OFFSET static bool isdirectjit (void) { return currprefs.cachesize && !currprefs.comptrustbyte; } static bool canjit (void) { if (currprefs.cpu_model < 68020 || currprefs.address_space_24) return false; return true; } static bool needmman (void) { if (!currprefs.jit_direct_compatible_memory) return false; #ifdef _WIN32 return true; #endif if (canjit ()) return true; return false; } static void nocanbang (void) { if (canbang) { write_log(_T("Switching JIT direct off!\n")); } canbang = 0; } #endif uae_u8 ce_banktype[65536]; uae_u8 ce_cachable[65536]; /* The address space setting used during the last reset. */ static bool last_address_space_24; addrbank *mem_banks[MEMORY_BANKS]; /* This has two functions. It either holds a host address that, when added to the 68k address, gives the host address corresponding to that 68k address (in which case the value in this array is even), OR it holds the same value as mem_banks, for those banks that have baseaddr==0. In that case, bit 0 is set (the memory access routines will take care of it). */ uae_u8 *baseaddr[MEMORY_BANKS]; #ifdef NO_INLINE_MEMORY_ACCESS __inline__ uae_u32 longget (uaecptr addr) { return call_mem_get_func (get_mem_bank (addr).lget, addr); } __inline__ uae_u32 wordget (uaecptr addr) { return call_mem_get_func (get_mem_bank (addr).wget, addr); } __inline__ uae_u32 byteget (uaecptr addr) { return call_mem_get_func (get_mem_bank (addr).bget, addr); } __inline__ void longput (uaecptr addr, uae_u32 l) { call_mem_put_func (get_mem_bank (addr).lput, addr, l); } __inline__ void wordput (uaecptr addr, uae_u32 w) { call_mem_put_func (get_mem_bank (addr).wput, addr, w); } __inline__ void byteput (uaecptr addr, uae_u32 b) { call_mem_put_func (get_mem_bank (addr).bput, addr, b); } #endif int addr_valid (const TCHAR *txt, uaecptr addr, uae_u32 len) { addrbank *ab = &get_mem_bank(addr); if (ab == 0 || !(ab->flags & (ABFLAG_RAM | ABFLAG_ROM)) || addr < 0x100 || len > 16777215 || !valid_address (addr, len)) { write_log (_T("corrupt %s pointer %x (%d) detected!\n"), txt, addr, len); return 0; } return 1; } static uae_u32 REGPARAM3 dummy_lget (uaecptr) REGPARAM; static uae_u32 REGPARAM3 dummy_wget (uaecptr) REGPARAM; static uae_u32 REGPARAM3 dummy_bget (uaecptr) REGPARAM; static void REGPARAM3 dummy_lput (uaecptr, uae_u32) REGPARAM; static void REGPARAM3 dummy_wput (uaecptr, uae_u32) REGPARAM; static void REGPARAM3 dummy_bput (uaecptr, uae_u32) REGPARAM; static int REGPARAM3 dummy_check (uaecptr addr, uae_u32 size) REGPARAM; #define MAX_ILG 200 #define NONEXISTINGDATA 0 //#define NONEXISTINGDATA 0xffffffff static void print_illegal_counted(const char *txt, uaecptr addr) { if (!illegal_mem || illegal_count >= MAX_ILG) return; write_log("%s at %08lx\n", txt, (long)addr); if (++illegal_count == MAX_ILG) write_log("Suppressing further messages about illegal memory accesses.\n"); } /* **** A dummy bank that only contains zeros **** */ /* TODO [NP] : in many cases, we should not return 0 but a value depending on the data */ /* last accessed on the bus */ static void dummylog(int rw, uaecptr addr, int size, uae_u32 val, int ins) { if (illegal_count >= MAX_ILG && MAX_ILG > 0) return; #ifndef WINUAE_FOR_HATARI /* ignore Zorro3 expansion space */ if (addr >= 0xff000000 && addr <= 0xff000200) return; /* autoconfig and extended rom */ if (addr >= 0xe00000 && addr <= 0xf7ffff) return; /* motherboard ram */ if (addr >= 0x08000000 && addr <= 0x08000007) return; if (addr >= 0x07f00000 && addr <= 0x07f00007) return; if (addr >= 0x07f7fff0 && addr <= 0x07ffffff) return; #endif if (MAX_ILG >= 0) illegal_count++; if (ins) { write_log (_T("WARNING: Illegal opcode %cget at %08x PC=%x\n"), size == sz_word ? 'w' : 'l', addr, M68K_GETPC); } else if (rw) { write_log (_T("Illegal %cput at %08x=%08x PC=%x\n"), size == sz_byte ? 'b' : size == sz_word ? 'w' : 'l', addr, val, M68K_GETPC); } else { write_log (_T("Illegal %cget at %08x PC=%x\n"), size == sz_byte ? 'b' : size == sz_word ? 'w' : 'l', addr, M68K_GETPC); } } void dummy_put (uaecptr addr, int size, uae_u32 val) { #ifndef WINUAE_FOR_HATARI #if FLASHEMU if (addr >= 0xf00000 && addr < 0xf80000 && size < 2) flash_write(addr, val); #endif if (gary_nonrange(addr) || (size > sz_byte && gary_nonrange(addr + (1 << size) - 1))) { if (gary_timeout) gary_wait(addr, size, true); if (gary_toenb && currprefs.mmu_model) hardware_exception2(addr, val, false, false, size); } #else /* Hatari : do nothing in case of dummy_put */ #endif } static uae_u32 nonexistingdata(void) { #ifndef WINUAE_FOR_HATARI if (currprefs.cs_unmapped_space == 1) return 0x00000000; if (currprefs.cs_unmapped_space == 2) return 0xffffffff; #endif return NONEXISTINGDATA; } uae_u32 dummy_get (uaecptr addr, int size, bool inst, uae_u32 defvalue) { uae_u32 v = defvalue; #ifndef WINUAE_FOR_HATARI #if FLASHEMU if (addr >= 0xf00000 && addr < 0xf80000 && size < 2) { if (addr < 0xf60000) return flash_read(addr); return 8; } #endif #if ACA500x_DEVELOPMENT if (addr == 0xb03000) { return 0xffff; } if (addr == 0xb07000) { return 0x0000; } if (addr == 0xb2f800) { return 0xffff; } if (addr == 0xb3b800) { return 0x0000; } if (addr == 0xb3f800) { return currprefs.cpu_model > 68000 ? 0x0000 : 0xffff; } if (addr == 0xb0b000) { return isideint() ? 0xffff : 0x0000; } #endif if (gary_nonrange(addr) || (size > 1 && gary_nonrange(addr + size - 1))) { if (gary_timeout) gary_wait (addr, size, false); if (gary_toenb) exception2 (addr, false, size, (regs.s ? 4 : 0) | (inst ? 0 : 1)); return v; } if (currprefs.cpu_model >= 68040) return v; if (!currprefs.cpu_compatible) return v; if (currprefs.address_space_24) addr &= 0x00ffffff; if (addr >= 0x10000000) return v; if ((currprefs.cpu_model <= 68010) || (currprefs.cpu_model == 68020 && (currprefs.chipset_mask & CSMASK_AGA) && currprefs.address_space_24)) { if (size == sz_long) { v = regs.irc & 0xffff; if (addr & 1) v = (v << 8) | (v >> 8); v = (v << 16) | v; } else if (size == sz_word) { v = regs.irc & 0xffff; if (addr & 1) v = (v << 8) | (v >> 8); } else { v = regs.irc; v = (addr & 1) ? (v & 0xff) : ((v >> 8) & 0xff); } } #if 0 if (addr >= 0x10000000) write_log (_T("%08X %d = %08x\n"), addr, size, v); #endif #else /* Hatari : TODO returns 0 for now, but we should use last databus value */ v = 0; #endif return v; } static uae_u32 REGPARAM2 dummy_lget (uaecptr addr) { if (illegal_mem) dummylog (0, addr, 4, 0, 0); return dummy_get(addr, sz_long, false, nonexistingdata()); } uae_u32 REGPARAM2 dummy_lgeti (uaecptr addr) { if (illegal_mem) dummylog(0, addr, sz_long, 0, 1); return dummy_get(addr, sz_long, true, nonexistingdata()); } static uae_u32 REGPARAM2 dummy_wget (uaecptr addr) { if (illegal_mem) dummylog(0, addr, sz_word, 0, 0); return dummy_get(addr, sz_word, false, nonexistingdata()); } uae_u32 REGPARAM2 dummy_wgeti (uaecptr addr) { if (illegal_mem) dummylog(0, addr, sz_word, 0, 1); return dummy_get(addr, sz_word, true, nonexistingdata()); } static uae_u32 REGPARAM2 dummy_bget (uaecptr addr) { if (illegal_mem) dummylog(0, addr, sz_byte, 0, 0); return dummy_get(addr, sz_byte, false, nonexistingdata()); } static void REGPARAM2 dummy_lput (uaecptr addr, uae_u32 l) { if (illegal_mem) dummylog(1, addr, sz_long, l, 0); dummy_put(addr, sz_long, l); } static void REGPARAM2 dummy_wput (uaecptr addr, uae_u32 w) { if (illegal_mem) dummylog(1, addr, sz_word, w, 0); dummy_put(addr, sz_word, w); } static void REGPARAM2 dummy_bput (uaecptr addr, uae_u32 b) { if (illegal_mem) dummylog(1, addr, sz_byte, b, 0); dummy_put(addr, sz_byte, b); } static int REGPARAM2 dummy_check (uaecptr addr, uae_u32 size) { return 0; } static uae_u8 * REGPARAM3 dummy_xlate(uaecptr addr) { write_log("Your Atari program just did something terribly stupid:" " dummy_xlate($%x)\n", addr); /*Reset_Warm();*/ return STmem_xlate(addr); /* So we don't crash. */ } #ifndef WINUAE_FOR_HATARI static void REGPARAM2 none_put (uaecptr addr, uae_u32 v) { } static uae_u32 REGPARAM2 ones_get (uaecptr addr) { return 0xffffffff; } addrbank *get_sub_bank(uaecptr *paddr) { int i; uaecptr addr = *paddr; addrbank *ab = &get_mem_bank(addr); struct addrbank_sub *sb = ab->sub_banks; if (!sb) return &dummy_bank; for (i = 0; sb[i].bank; i++) { int offset = addr & 65535; if (offset < sb[i + 1].offset) { uae_u32 mask = sb[i].mask; uae_u32 maskval = sb[i].maskval; if ((offset & mask) == maskval) { *paddr = addr - sb[i].suboffset; return sb[i].bank; } } } *paddr = addr - sb[i - 1].suboffset; return sb[i - 1].bank; } uae_u32 REGPARAM3 sub_bank_lget (uaecptr addr) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->lget(addr); } uae_u32 REGPARAM3 sub_bank_wget(uaecptr addr) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->wget(addr); } uae_u32 REGPARAM3 sub_bank_bget(uaecptr addr) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->bget(addr); } void REGPARAM3 sub_bank_lput(uaecptr addr, uae_u32 v) REGPARAM { addrbank *ab = get_sub_bank(&addr); ab->lput(addr, v); } void REGPARAM3 sub_bank_wput(uaecptr addr, uae_u32 v) REGPARAM { addrbank *ab = get_sub_bank(&addr); ab->wput(addr, v); } void REGPARAM3 sub_bank_bput(uaecptr addr, uae_u32 v) REGPARAM { addrbank *ab = get_sub_bank(&addr); /* last accessed on the bus */ab->bput(addr, v); } uae_u32 REGPARAM3 sub_bank_lgeti(uaecptr addr) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->lgeti(addr); } uae_u32 REGPARAM3 sub_bank_wgeti(uaecptr addr) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->wgeti(addr); } int REGPARAM3 sub_bank_check(uaecptr addr, uae_u32 size) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->check(addr, size); } uae_u8 *REGPARAM3 sub_bank_xlate(uaecptr addr) REGPARAM { addrbank *ab = get_sub_bank(&addr); return ab->xlateaddr(addr); } #endif /* **** This memory bank only generates bus errors **** */ static uae_u32 REGPARAM3 BusErrMem_lget(uaecptr addr) { print_illegal_counted("Bus error lget", addr); M68000_BusError(addr, BUS_ERROR_READ, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, 0); return 0; } static uae_u32 REGPARAM3 BusErrMem_wget(uaecptr addr) { print_illegal_counted("Bus error wget", addr); M68000_BusError(addr, BUS_ERROR_READ, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, 0); return 0; } static uae_u32 REGPARAM3 BusErrMem_bget(uaecptr addr) { print_illegal_counted("Bus error bget", addr); M68000_BusError(addr, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return 0; } static void REGPARAM3 BusErrMem_lput(uaecptr addr, uae_u32 l) { print_illegal_counted("Bus error lput", addr); M68000_BusError(addr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, l); } static void REGPARAM3 BusErrMem_wput(uaecptr addr, uae_u32 w) { print_illegal_counted("Bus error wput", addr); M68000_BusError(addr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, w); } static void REGPARAM3 BusErrMem_bput(uaecptr addr, uae_u32 b) { print_illegal_counted("Bus error bput", addr); M68000_BusError(addr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, b); } static int REGPARAM3 BusErrMem_check(uaecptr addr, uae_u32 size) { if (illegal_mem) write_log ("Bus error check at %08lx\n", (long)addr); return 0; } static uae_u8 * REGPARAM3 BusErrMem_xlate (uaecptr addr) { write_log("Your Atari program just did something terribly stupid:" " BusErrMem_xlate($%x)\n", addr); /*M68000_BusError(addr);*/ return STmem_xlate(addr); /* So we don't crash. */ } /* **** ST RAM memory **** */ /*static uae_u8 *STmemory;*/ #define STmemory STRam static uae_u32 REGPARAM3 STmem_lget(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; return do_get_mem_long(STmemory + addr); } static uae_u32 REGPARAM3 STmem_wget(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; return do_get_mem_word(STmemory + addr); } static uae_u32 REGPARAM3 STmem_bget(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; return STmemory[addr]; } static void REGPARAM3 STmem_lput(uaecptr addr, uae_u32 l) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; do_put_mem_long(STmemory + addr, l); } static void REGPARAM3 STmem_wput(uaecptr addr, uae_u32 w) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; do_put_mem_word(STmemory + addr, w); } static void REGPARAM3 STmem_bput(uaecptr addr, uae_u32 b) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; STmemory[addr] = b; } static int REGPARAM3 STmem_check(uaecptr addr, uae_u32 size) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; return (addr + size) <= STmem_size; } static uae_u8 * REGPARAM3 STmem_xlate(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; return STmemory + addr; } /* Same functions for ST RAM but with MMU/MCU enabled to translate addresses */ static uae_u32 REGPARAM3 STmem_lget_MMU(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; addr = STMemory_MMU_Translate_Addr ( addr ); return do_get_mem_long(STmemory + addr); } static uae_u32 REGPARAM3 STmem_wget_MMU(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; addr = STMemory_MMU_Translate_Addr ( addr ); return do_get_mem_word(STmemory + addr); } static uae_u32 REGPARAM3 STmem_bget_MMU(uaecptr addr) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; addr = STMemory_MMU_Translate_Addr ( addr ); return STmemory[addr]; } static void REGPARAM3 STmem_lput_MMU(uaecptr addr, uae_u32 l) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; addr = STMemory_MMU_Translate_Addr ( addr ); do_put_mem_long(STmemory + addr, l); } static void REGPARAM3 STmem_wput_MMU(uaecptr addr, uae_u32 w) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; addr = STMemory_MMU_Translate_Addr ( addr ); do_put_mem_word(STmemory + addr, w); } static void REGPARAM3 STmem_bput_MMU(uaecptr addr, uae_u32 b) { addr -= STmem_start & STmem_mask; addr &= STmem_mask; addr = STMemory_MMU_Translate_Addr ( addr ); STmemory[addr] = b; } /* * **** ST RAM system memory **** * We need a separate mem bank for this region since the first 0x800 bytes on * the ST can only be accessed in supervisor mode. Note that the very first * 8 bytes of the ST memory are also a mirror of the TOS ROM, so they are write * protected! */ static uae_u32 REGPARAM3 SysMem_lget(uaecptr addr) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; /* Only CPU will trigger bus error if bit S=0, not the blitter or the debugger */ if(addr < 0x800 && !is_super_access(true) && BusMode == BUS_MODE_CPU) { M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, 0); return 0; } return do_get_mem_long(STmemory + addr); } static uae_u32 REGPARAM3 SysMem_wget(uaecptr addr) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; /* Only CPU will trigger bus error if bit S=0, not the blitter or the debugger */ if(addr < 0x800 && !is_super_access(true) && BusMode == BUS_MODE_CPU) { M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, 0); return 0; } return do_get_mem_word(STmemory + addr); } static uae_u32 REGPARAM3 SysMem_bget(uaecptr addr) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; /* Only CPU will trigger bus error if bit S=0, not the blitter or the debugger */ if(addr < 0x800 && !is_super_access(true) && BusMode == BUS_MODE_CPU) { M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return 0; } return STmemory[addr]; } static void REGPARAM3 SysMem_lput(uaecptr addr, uae_u32 l) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; if(addr < 0x8 || (addr < 0x800 && !is_super_access(false))) { M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, l); return; } do_put_mem_long(STmemory + addr, l); } static void REGPARAM3 SysMem_wput(uaecptr addr, uae_u32 w) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; /* Only CPU will trigger bus error if bit S=0, not the blitter */ if (addr < 0x8 || (addr < 0x800 && !is_super_access(false))) { if (BusMode == BUS_MODE_CPU) { M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, w); return; } /* If blitter writes < 0x8 then it should be ignored, else the write should be made */ else if ( ( BusMode == BUS_MODE_BLITTER ) && ( addr < 0x8 ) ) { return; } } do_put_mem_word(STmemory + addr, w); } static void REGPARAM3 SysMem_bput(uaecptr addr, uae_u32 b) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; if (addr < 0x8 || (addr < 0x800 && !is_super_access(false))) { M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, b); return; } STmemory[addr] = b; } /* Same functions for ST RAM system but with MMU/MCU enabled to translate addresses */ static uae_u32 REGPARAM3 SysMem_lget_MMU(uaecptr addr) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; if (addr < 0x800 && !is_super_access(true)) { M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, 0); return 0; } addr = STMemory_MMU_Translate_Addr ( addr ); return do_get_mem_long(STmemory + addr); } static uae_u32 REGPARAM3 SysMem_wget_MMU(uaecptr addr) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; /* Only CPU will trigger bus error if bit S=0, not the blitter */ if (addr < 0x800 && !is_super_access(true) && BusMode == BUS_MODE_CPU) { M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, 0); return 0; } addr = STMemory_MMU_Translate_Addr ( addr ); return do_get_mem_word(STmemory + addr); } static uae_u32 REGPARAM3 SysMem_bget_MMU(uaecptr addr) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; if (addr < 0x800 && !is_super_access(true)) { M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return 0; } addr = STMemory_MMU_Translate_Addr ( addr ); return STmemory[addr]; } static void REGPARAM3 SysMem_lput_MMU(uaecptr addr, uae_u32 l) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; if (addr < 0x8 || (addr < 0x800 && !is_super_access(false))) { M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, l); return; } addr = STMemory_MMU_Translate_Addr ( addr ); do_put_mem_long(STmemory + addr, l); } static void REGPARAM3 SysMem_wput_MMU(uaecptr addr, uae_u32 w) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; /* Only CPU will trigger bus error if bit S=0, not the blitter */ if (addr < 0x8 || (addr < 0x800 && !is_super_access(false))) { if ( BusMode == BUS_MODE_CPU ) { M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, w); return; } /* If blitter writes < 0x8 then it should be ignored, else the write should be made */ else if ( ( BusMode == BUS_MODE_BLITTER ) && ( addr < 0x8 ) ) { return; } } addr = STMemory_MMU_Translate_Addr ( addr ); do_put_mem_word(STmemory + addr, w); } static void REGPARAM3 SysMem_bput_MMU(uaecptr addr, uae_u32 b) { uaecptr addr_in = addr; addr -= STmem_start & STmem_mask; addr &= STmem_mask; if (addr < 0x8 || (addr < 0x800 && !is_super_access(false))) { M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, b); return; } addr = STMemory_MMU_Translate_Addr ( addr ); STmemory[addr] = b; } /* * **** Void memory **** * Between the ST-RAM end and the 4 MB barrier, there is a void memory space: * Reading depends on current activity on the bus and writing does nothing at all. * * [NP] : When there's no memory, it will return the latest data that was read on the bus. * In many cases, this will return the word that was just read in the 68000's * prefetch register to decode the next opcode (tested on a real STF) * * STF : this seems to always return the last data word on the bus read by the CPU (tested on real STF) * STE : result seems also related to what the shifter displays, it can be data read by the CPU or by * the shifter ; for now do the same as STF [TODO improve this ? No program are known to depend on it] */ static uae_u32 REGPARAM3 VoidMem_lget(uaecptr addr) { return ( regs.db<<16 ) | regs.db; } static uae_u32 REGPARAM3 VoidMem_wget(uaecptr addr) { return regs.db; } static uae_u32 REGPARAM3 VoidMem_bget(uaecptr addr) { return regs.db; } static void REGPARAM3 VoidMem_lput(uaecptr addr, uae_u32 l) { } static void REGPARAM3 VoidMem_wput(uaecptr addr, uae_u32 w) { } static void REGPARAM3 VoidMem_bput (uaecptr addr, uae_u32 b) { } static int REGPARAM3 VoidMem_check(uaecptr addr, uae_u32 size) { if (illegal_mem) write_log ("Void memory check at %08lx\n", (long)addr); return 0; } static uae_u8 * REGPARAM3 VoidMem_xlate (uaecptr addr) { write_log("Your Atari program just did something terribly stupid:" " VoidMem_xlate($%x)\n", addr); return STmem_xlate(addr); /* So we don't crash. */ } /* **** TT fast memory **** */ uae_u8 *TTmemory; static uae_u32 REGPARAM3 TTmem_lget(uaecptr addr) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; return do_get_mem_long(TTmemory + addr); } static uae_u32 REGPARAM3 TTmem_wget(uaecptr addr) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; return do_get_mem_word(TTmemory + addr); } static uae_u32 REGPARAM3 TTmem_bget(uaecptr addr) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; return TTmemory[addr]; } static void REGPARAM3 TTmem_lput(uaecptr addr, uae_u32 l) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; do_put_mem_long(TTmemory + addr, l); } static void REGPARAM3 TTmem_wput(uaecptr addr, uae_u32 w) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; do_put_mem_word(TTmemory + addr, w); } static void REGPARAM3 TTmem_bput(uaecptr addr, uae_u32 b) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; TTmemory[addr] = b; } static int REGPARAM3 TTmem_check(uaecptr addr, uae_u32 size) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; return (addr + size) <= TTmem_size; } static uae_u8 * REGPARAM3 TTmem_xlate(uaecptr addr) { addr -= TTmem_start & TTmem_mask; addr &= TTmem_mask; return TTmemory + addr; } /* **** ROM memory **** */ uae_u8 *ROMmemory; static uae_u32 REGPARAM3 ROMmem_lget(uaecptr addr) { addr -= ROMmem_start & ROMmem_mask; addr &= ROMmem_mask; return do_get_mem_long(ROMmemory + addr); } static uae_u32 REGPARAM3 ROMmem_wget(uaecptr addr) { addr -= ROMmem_start & ROMmem_mask; addr &= ROMmem_mask; return do_get_mem_word(ROMmemory + addr); } static uae_u32 REGPARAM3 ROMmem_bget(uaecptr addr) { addr -= ROMmem_start & ROMmem_mask; addr &= ROMmem_mask; return ROMmemory[addr]; } static void REGPARAM3 ROMmem_lput(uaecptr addr, uae_u32 l) { print_illegal_counted("Illegal ROMmem lput", addr); M68000_BusError(addr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, l); } static void REGPARAM3 ROMmem_wput(uaecptr addr, uae_u32 w) { print_illegal_counted("Illegal ROMmem wput", addr); M68000_BusError(addr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, w); } static void REGPARAM3 ROMmem_bput(uaecptr addr, uae_u32 b) { print_illegal_counted("Illegal ROMmem bput", addr); M68000_BusError(addr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, b); } static int REGPARAM3 ROMmem_check(uaecptr addr, uae_u32 size) { addr -= ROMmem_start & ROMmem_mask; addr &= ROMmem_mask; return (addr + size) <= ROMmem_size; } static uae_u8 * REGPARAM3 ROMmem_xlate(uaecptr addr) { addr -= ROMmem_start & ROMmem_mask; addr &= ROMmem_mask; return ROMmemory + addr; } /* IDE controller IO memory */ /* see also ide.c */ static uae_u8 *IdeMemory; static int REGPARAM3 IdeMem_check(uaecptr addr, uae_u32 size) { addr -= IdeMem_start; addr &= IdeMem_mask; return (addr + size) <= IdeMem_size; } static uae_u8 * REGPARAM3 IdeMem_xlate(uaecptr addr) { addr -= IdeMem_start; addr &= IdeMem_mask; return IdeMemory + addr; } /* Hardware IO memory */ /* see also ioMem.c */ uae_u8 *IOmemory; static int REGPARAM3 IOmem_check(uaecptr addr, uae_u32 size) { addr -= IOmem_start; addr &= IOmem_mask; return (addr + size) <= IOmem_size; } static uae_u8 * REGPARAM3 IOmem_xlate(uaecptr addr) { addr -= IOmem_start; addr &= IOmem_mask; return IOmemory + addr; } /* **** Address banks **** */ /* * NOTE : if ABFLAG_DIRECTACCESS is set for a bank we don't use * the functions get/put/xlate/check defined for this bank but * we use the more generic memory_get/memory_put/mgemory_get_real/memory_valid_address * functions which bypass the common memory handlers and are slightly faster. * This flag can only be set for memory region where content is "fixed" (ie RAM or ROM) */ addrbank dummy_bank = { dummy_lget, dummy_wget, dummy_bget, dummy_lput, dummy_wput, dummy_bput, dummy_xlate, dummy_check, NULL, NULL, NULL, dummy_lget, dummy_wget, ABFLAG_NONE }; static addrbank BusErrMem_bank = { BusErrMem_lget, BusErrMem_wget, BusErrMem_bget, BusErrMem_lput, BusErrMem_wput, BusErrMem_bput, BusErrMem_xlate, BusErrMem_check, NULL, "bus_err_mem" , "BusError memory", BusErrMem_lget, BusErrMem_wget, ABFLAG_NONE }; static addrbank STmem_bank = { STmem_lget, STmem_wget, STmem_bget, STmem_lput, STmem_wput, STmem_bput, STmem_xlate, STmem_check, NULL, "st_mem" , "ST memory", STmem_lget, STmem_wget, ABFLAG_RAM | ABFLAG_DIRECTACCESS }; static addrbank SysMem_bank = { SysMem_lget, SysMem_wget, SysMem_bget, SysMem_lput, SysMem_wput, SysMem_bput, STmem_xlate, STmem_check, NULL, "sys_mem" , "Sys memory", SysMem_lget, SysMem_wget, ABFLAG_RAM }; static addrbank STmem_bank_MMU = /* similar to STmem_bank with MMU/MCU enabled */ { STmem_lget_MMU, STmem_wget_MMU, STmem_bget_MMU, STmem_lput_MMU, STmem_wput_MMU, STmem_bput_MMU, STmem_xlate, STmem_check, NULL, "st_mem" , "ST memory + MMU", STmem_lget_MMU, STmem_wget_MMU, ABFLAG_RAM }; static addrbank SysMem_bank_MMU = /* similar to STmem_bank with MMU/MCU enabled */ { SysMem_lget_MMU, SysMem_wget_MMU, SysMem_bget_MMU, SysMem_lput_MMU, SysMem_wput_MMU, SysMem_bput_MMU, STmem_xlate, STmem_check, NULL, "sys_mem" , "Sys memory + MMU", SysMem_lget_MMU, SysMem_wget_MMU, ABFLAG_RAM }; static addrbank VoidMem_bank = { VoidMem_lget, VoidMem_wget, VoidMem_bget, VoidMem_lput, VoidMem_wput, VoidMem_bput, VoidMem_xlate, VoidMem_check, NULL, "void_mem" , "Void memory", VoidMem_lget, VoidMem_wget, ABFLAG_NONE }; static addrbank TTmem_bank = { TTmem_lget, TTmem_wget, TTmem_bget, TTmem_lput, TTmem_wput, TTmem_bput, TTmem_xlate, TTmem_check, NULL, "tt_mem" , "TT memory", TTmem_lget, TTmem_wget, ABFLAG_RAM | ABFLAG_DIRECTACCESS /* NP TODO : use ABFLAG_RAM_TT for non DMA RAM */ }; static addrbank ROMmem_bank = { ROMmem_lget, ROMmem_wget, ROMmem_bget, ROMmem_lput, ROMmem_wput, ROMmem_bput, ROMmem_xlate, ROMmem_check, NULL, "rom_mem" , "ROM memory", ROMmem_lget, ROMmem_wget, ABFLAG_ROM | ABFLAG_DIRECTACCESS }; static addrbank IdeMem_bank = { Ide_Mem_lget, Ide_Mem_wget, Ide_Mem_bget, Ide_Mem_lput, Ide_Mem_wput, Ide_Mem_bput, IdeMem_xlate, IdeMem_check, NULL, "ide_mem" , "IDE memory", Ide_Mem_lget, Ide_Mem_wget, ABFLAG_IO }; static addrbank IOmem_bank = { IoMem_lget, IoMem_wget, IoMem_bget, IoMem_lput, IoMem_wput, IoMem_bput, IOmem_xlate, IOmem_check, NULL, "io_mem" , "IO memory", IoMem_lget, IoMem_wget, ABFLAG_IO }; static void set_direct_memory(addrbank *ab) { if (!(ab->flags & ABFLAG_DIRECTACCESS)) return; ab->baseaddr_direct_r = ab->baseaddr; if (!(ab->flags & ABFLAG_ROM)) ab->baseaddr_direct_w = ab->baseaddr; } #ifndef NATMEM_OFFSET //extern uae_u8 *natmem_offset, *natmem_offset_end; bool mapped_malloc (addrbank *ab) { ab->startmask = ab->start; ab->startaccessmask = ab->start & ab->mask; ab->baseaddr = xcalloc (uae_u8, ab->reserved_size + 4); ab->allocated_size = ab->baseaddr != NULL ? ab->reserved_size : 0; ab->baseaddr_direct_r = NULL; ab->baseaddr_direct_w = NULL; ab->flags &= ~ABFLAG_MAPPED; set_direct_memory(ab); return ab->baseaddr != NULL; } void mapped_free (addrbank *ab) { xfree(ab->baseaddr); ab->flags &= ~ABFLAG_MAPPED; ab->allocated_size = 0; ab->baseaddr = NULL; } #else #include #include #include #include shmpiece *shm_start; static void dumplist (void) { shmpiece *x = shm_start; write_log (_T("Start Dump:\n")); while (x) { write_log (_T("this=%p,Native %p,id %d,prev=%p,next=%p,size=0x%08x\n"), x, x->native_address, x->id, x->prev, x->next, x->size); x = x->next; } write_log (_T("End Dump:\n")); } static shmpiece *find_shmpiece (uae_u8 *base, bool safe) { shmpiece *x = shm_start; while (x && x->native_address != base) x = x->next; if (!x) { #ifndef WINUAE_FOR_HATARI if (safe || bogomem_aliasing) #else if (safe) #endif return 0; write_log (_T("NATMEM: Failure to find mapping at %08lx, %p\n"), base - NATMEM_OFFSET, base); nocanbang (); return 0; } return x; } static void delete_shmmaps (uae_u32 start, uae_u32 size) { if (!needmman ()) return; while (size) { uae_u8 *base = mem_banks[bankindex (start)]->baseaddr; if (base) { shmpiece *x; //base = ((uae_u8*)NATMEM_OFFSET)+start; x = find_shmpiece (base, true); if (!x) return; if (x->size > size) { if (isdirectjit ()) write_log (_T("NATMEM WARNING: size mismatch mapping at %08x (size %08x, delsize %08x)\n"),start,x->size,size); size = x->size; } uae_shmdt (x->native_address); size -= x->size; start += x->size; if (x->next) x->next->prev = x->prev; /* remove this one from the list */ if (x->prev) x->prev->next = x->next; else shm_start = x->next; xfree (x); } else { size -= 0x10000; start += 0x10000; } } } static void add_shmmaps (uae_u32 start, addrbank *what) { shmpiece *x = shm_start; shmpiece *y; uae_u8 *base = what->baseaddr; if (!needmman ()) return; if (!base) return; if (what->jit_read_flag && what->jit_write_flag) return; x = find_shmpiece (base, false); if (!x) return; y = xmalloc (shmpiece, 1); *y = *x; base = ((uae_u8 *) NATMEM_OFFSET) + start; y->native_address = (uae_u8*)uae_shmat (what, y->id, base, 0, NULL); if (y->native_address == (void *) -1) { write_log (_T("NATMEM: Failure to map existing at %08x (%p)\n"), start, base); dumplist (); nocanbang (); return; } y->next = shm_start; y->prev = NULL; if (y->next) y->next->prev = y; shm_start = y; } #define MAPPED_MALLOC_DEBUG 0 bool mapped_malloc (addrbank *ab) { int id; void *answer; shmpiece *x; bool rtgmem = (ab->flags & ABFLAG_RTG) != 0; static int recurse; if (ab->allocated_size) { write_log(_T("mapped_malloc with memory bank '%s' already allocated!?\n"), ab->name); } ab->allocated_size = 0; ab->baseaddr_direct_r = NULL; ab->baseaddr_direct_w = NULL; ab->flags &= ~ABFLAG_MAPPED; if (ab->label && ab->label[0] == '*') { if (ab->start == 0 || ab->start == 0xffffffff) { write_log(_T("mapped_malloc(*) without start address!\n")); return false; } } struct uae_mman_data md = { 0 }; uaecptr start = ab->start; if (uae_mman_info(ab, &md)) { start = md.start; } ab->startmask = start; ab->startaccessmask = start & ab->mask; if (!md.directsupport || (ab->flags & ABFLAG_ALLOCINDIRECT)) { if (!(ab->flags & ABFLAG_ALLOCINDIRECT)) { if (canbang) { write_log(_T("JIT direct switched off: %s\n"), ab->name); } nocanbang(); } ab->flags &= ~ABFLAG_DIRECTMAP; if (ab->flags & ABFLAG_NOALLOC) { ab->allocated_size = ab->reserved_size; #if MAPPED_MALLOC_DEBUG write_log(_T("mapped_malloc noalloc %s\n"), ab->name); #endif return true; } ab->baseaddr = xcalloc (uae_u8, ab->reserved_size + 4); if (ab->baseaddr) { // fill end of ram with ILLEGAL to catch direct PC falling out of RAM. put_long_host(ab->baseaddr + ab->reserved_size, 0x4afc4afc); ab->allocated_size = ab->reserved_size; } set_direct_memory(ab); #if MAPPED_MALLOC_DEBUG write_log(_T("mapped_malloc nodirect %s %p\n"), ab->name, ab->baseaddr); #endif return ab->baseaddr != NULL; } id = uae_shmget (UAE_IPC_PRIVATE, ab, 0x1ff); if (id == -1) { nocanbang (); if (recurse) return NULL; recurse++; mapped_malloc (ab); recurse--; return ab->baseaddr != NULL; } if (!(ab->flags & ABFLAG_NOALLOC)) { answer = uae_shmat (iab, id, NULL, 0, &md); uae_shmctl (id, UAE_IPC_RMID, NULL); } else { write_log(_T("MMAN: mapped_malloc using existing baseaddr %p\n"), ab->baseaddr); answer = ab->baseaddr; } if (answer != (void *) -1) { x = xmalloc (shmpiece, 1); x->native_address = (uae_u8*)answer; x->id = id; x->size = ab->reserved_size; x->name = ab->label; x->next = shm_start; x->prev = NULL; if (x->next) x->next->prev = x; shm_start = x; ab->baseaddr = x->native_address; if (ab->baseaddr) { if (md.hasbarrier) { // fill end of ram with ILLEGAL to catch direct PC falling out of RAM. put_long_host(ab->baseaddr + ab->reserved_size, 0x4afc4afc); ab->barrier = true; } ab->allocated_size = ab->reserved_size; } ab->flags |= ABFLAG_DIRECTMAP; set_direct_memory(ab); #if MAPPED_MALLOC_DEBUG write_log(_T("mapped_malloc direct %s %p\n"), ab->name, ab->baseaddr); #endif return ab->baseaddr != NULL; } if (recurse) return NULL; nocanbang (); recurse++; mapped_malloc (ab); recurse--; set_direct_memory(ab); #if MAPPED_MALLOC_DEBUG write_log(_T("mapped_malloc indirect %s %p\n"), ab->name, ab->baseaddr); #endif return ab->baseaddr != NULL;} } #endif static void init_mem_banks (void) { // unsigned so i << 16 won't overflow to negative when i >= 32768 for (unsigned int i = 0; i < MEMORY_BANKS; i++) put_mem_bank (i << 16, &dummy_bank, 0); #ifdef NATMEM_OFFSET delete_shmmaps (0, 0xFFFF0000); #endif } static void init_bank (addrbank *ab , uae_u32 size) { ab->allocated_size = size; ab->startmask = ab->start; ab->startaccessmask = ab->start & ab->mask; ab->baseaddr_direct_r = NULL; ab->baseaddr_direct_w = NULL; set_direct_memory ( ab ); } #ifdef WINUAE_FOR_HATARI /* * Check if an address points to a memory region that causes bus error * Returns true if region gives bus error */ bool memory_region_bus_error ( uaecptr addr ) { return mem_banks[bankindex(addr)] == &BusErrMem_bank; } /* * Check if an address points to the IO memory region * Returns true if it's the case */ bool memory_region_iomem ( uaecptr addr ) { return mem_banks[bankindex(addr)] == &IOmem_bank; } #endif /* * Initialize some extra parameters for the memory banks in CE mode * By default, we set all banks to CHIP16 and not cacheable * * Possible values for ce_banktype : * CE_MEMBANK_CHIP16 shared between CPU and DMA, bus width = 16 bits * CE_MEMBANK_CHIP32 shared between CPU and DMA, bus width = 32 bits (AGA chipset) * CE_MEMBANK_FAST16 accessible only to the CPU, bus width = 16 bits * CE_MEMBANK_FAST32 accessible only to the CPU, bus width = 32 bits * CE_MEMBANK_CIA Amiga only, for CIA chips * * Possible values for ce_cachable : * bit 0 : cacheable yes/no for data * bit 1 : burst mode allowed when caching data yes/no * bit 7 : cacheable yes/no for instructions * bit 6 : burst mode allowed when caching instructions yes/no */ static void init_ce_banks (void) { /* Default to CHIP16 */ memset (ce_banktype, CE_MEMBANK_CHIP16, sizeof ce_banktype); /* Default to not cacheable */ memset (ce_cachable, 0, sizeof ce_cachable); } /* * For CE mode, set banktype and cacheable for a memory region */ static void fill_ce_banks (int start, int size, int banktype, int cachable ) { int i; for ( i=start ; i> 16, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); /* Space between 4MB barrier and TOS ROM causes a bus error */ map_banks_ce(&BusErrMem_bank, 0x400000 >> 16, 0xA0, 0 , CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); /* Now map main ST RAM, overwriting the void and bus error regions if necessary */ /* - Map the ST system RAM from 0 to 0x10000 (required for supervisor check between 0x0 and 0x800) */ /* - Map rest of the ST RAM from 0x10000 to STmem_size */ /* If possible/needed we enable MMU/MCU translation for maximum accuracy (when sizes of banks differ between MMU and RAM) */ if ( ( Config_IsMachineST() || Config_IsMachineSTE() ) && !ConfigureParams.System.bFastBoot && !ConfigureParams.Screen.bUseExtVdiResolutions && STmem_size <= 0x400000 && ( ( MMU_Bank0_Size != RAM_Bank0_Size ) || ( ( RAM_Bank1_Size > 0 ) && ( MMU_Bank1_Size != RAM_Bank1_Size ) ) ) && !bRamTosImage ) { /* We map memory according to the logical MMU configuration and we will translate addresses on each memory access. */ /* RAM bank 0 can never be empty, but RAM bank 1 can be empty. If there's no RAM bank 1, then we must */ /* use 'Void' region directly, we don't map it to STmem_bank_MMU */ Log_Printf(LOG_DEBUG, "memory_map_Standard_RAM - enable MMU %d %d %d\n", STmem_size, MMU_Bank0_Size, MMU_Bank1_Size); /* Map RAM bank 0 to MMU bank 0 */ map_banks_ce(&SysMem_bank_MMU, 0x00, 0x10000 >> 16, 0, CE_MEMBANK_CHIP16, CACHE_ENABLE_BOTH); map_banks_ce(&STmem_bank_MMU, 0x10000 >> 16, ( MMU_Bank0_Size - 0x10000 ) >> 16, 0, CE_MEMBANK_CHIP16, CACHE_ENABLE_BOTH); /* If RAM bank 1 exists, we map it after MMU bank 0 ; else we keep void region */ if ( RAM_Bank1_Size > 0 ) map_banks_ce(&STmem_bank_MMU, MMU_Bank0_Size >> 16, MMU_Bank1_Size >> 16, 0, CE_MEMBANK_CHIP16, CACHE_ENABLE_BOTH); /* [NP] There's a special case when bank0=128 and bank1=2048 : addresses between $40000 and $80000 */ /* are returning 'void' region too, which creates a "hole" in the memory mapping */ /* (this might be a bug / forgotten case by Atari in the STF non-IMP MMU as this memory */ /* configuration was certainly never used in real machines) */ if ( ( MMU_Bank0_Size == MEM_BANK_SIZE_128 ) && ( MMU_Bank1_Size == MEM_BANK_SIZE_2048 ) ) { map_banks_ce(&VoidMem_bank, 0x40000 >> 16, 0x40000 >> 16, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); } /* When enabling MMU address translation in the memory banks for the CPU, we must */ /* also enable MMU address translation for the shifter, as fetching data words to display */ /* will depend on how the MMU is configured */ Video_Set_Memcpy ( true ); } else { /* Don't enable MMU address translation */ map_banks_ce(&SysMem_bank, 0x00, 0x10000 >> 16, 0, CE_MEMBANK_CHIP16, CACHE_ENABLE_BOTH); map_banks_ce(&STmem_bank, 0x10000 >> 16, ( STmem_size - 0x10000 ) >> 16, 0, CE_MEMBANK_CHIP16, CACHE_ENABLE_BOTH); /* Use normal memcpy functions for video rendering */ Video_Set_Memcpy ( false ); } } /* * Initialize all the memory banks */ void memory_init(uae_u32 NewSTMemSize, uae_u32 NewTTMemSize, uae_u32 NewRomMemStart) { int addr; last_address_space_24 = ConfigureParams.System.bAddressSpace24; /* Round to next multiple of 65536 bytes */ STmem_size = (NewSTMemSize + 65535) & 0xFFFF0000; TTmem_size = (NewTTMemSize + 65535) & 0xFFFF0000; //fprintf ( stderr , "memory_init: STmem_size=$%x, TTmem_size=$%x, ROM-Start=$%x,\n", STmem_size, TTmem_size, NewRomMemStart); /*write_log("memory_init: STmem_size=$%x, TTmem_size=$%x, ROM-Start=$%x,\n", STmem_size, TTmem_size, NewRomMemStart);*/ /* Allocate memory for normal ST RAM. Note that we always allocate * either 4 MiB, 8 MiB or the full 16 MiB, since the functions that * might access the memory directly (via "DMA" like the shifter, see * the Video_CopyScreenLine*() functions) might also try to access * the memory beyond the end of the RAM in case the base address has * been wrongly set up by the Atari program (the accesses are only * limited by the value returned from DMA_MaskAddressHigh()). To * compensate for reads beyond the end, we also add a "runaway ramp" * buffer with the size of the maximum ST screen, so that the function * Video_CopyScreenLineColor() should never go out of bounds. */ int alloc_size = NUM_VISIBLE_LINE_PIXELS * NUM_VISIBLE_LINES / 2; if (STmem_size > 0x800000) alloc_size += 0x1000000; else if (STmem_size > 0x400000) alloc_size += 0x800000; else alloc_size += 0x400000; STmemory = malloc(alloc_size); if (!STmemory) { Main_ErrorExit("Virtual memory exhausted (STmemory)", NULL, 1); } memset(STmemory, 0, alloc_size); /* Set up memory for ROM areas, IDE and IO memory space (0xE00000 - 0xFFFFFF) */ if (alloc_size >= 0x1000000) { ROMmemory = STmemory + ROMmem_start; } else { ROMmemory = malloc(2*1024*1024); if (!ROMmemory) { Main_ErrorExit("Out of memory (ROM/IO mem)", NULL, 1); } } IdeMemory = ROMmemory + 0x100000; IOmemory = ROMmemory + 0x1f0000; init_mem_banks(); init_ce_banks(); /* Set the infos about memory pointers for each mem bank, used for direct memory access in stMemory.c */ STmem_bank.baseaddr = STmemory; STmem_bank.mask = STmem_mask; STmem_bank.start = STmem_start; init_bank ( &STmem_bank , STmem_size ); SysMem_bank.baseaddr = STmemory; SysMem_bank.mask = STmem_mask; SysMem_bank.start = STmem_start; init_bank ( &SysMem_bank , STmem_size ); STmem_bank_MMU.baseaddr = STmemory; STmem_bank_MMU.mask = STmem_mask; STmem_bank_MMU.start = STmem_start; init_bank ( &STmem_bank_MMU , STmem_size ); SysMem_bank_MMU.baseaddr = STmemory; SysMem_bank_MMU.mask = STmem_mask; SysMem_bank_MMU.start = STmem_start; init_bank ( &SysMem_bank_MMU , STmem_size ); dummy_bank.baseaddr = NULL; /* No real memory allocated for this region */ init_bank ( &dummy_bank , 0 ); VoidMem_bank.baseaddr = NULL; /* No real memory allocated for this region */ init_bank ( &VoidMem_bank , 0 ); BusErrMem_bank.baseaddr = NULL; /* No real memory allocated for this region */ init_bank ( &BusErrMem_bank , 0 ); /* Map the standard RAM (Max is 4 MB on unmodified STF/STE) */ memory_map_Standard_RAM ( MMU_Bank0_Size , MMU_Bank1_Size ); /* Handle extra RAM on TT and Falcon starting at 0x1000000 and up to 0x80000000 */ /* This requires the CPU to use 32 bit addressing */ TTmemory = NULL; if (!ConfigureParams.System.bAddressSpace24) { /* If there's no Fast-RAM, region 0x01000000 - 0x80000000 (2047 MB) must return bus errors */ map_banks_ce ( &BusErrMem_bank, TTmem_start >> 16, ( TTmem_end - TTmem_start ) >> 16, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); if (TTmem_size > 0) { TTmemory = (uae_u8 *)malloc ( TTmem_size ); if (TTmemory != NULL) { /* 32 bit RAM for CPU only + cache/burst allowed */ map_banks_ce ( &TTmem_bank, TTmem_start >> 16, TTmem_size >> 16, 0, CE_MEMBANK_FAST32, CACHE_ENABLE_ALL); TTmem_mask = 0xffffffff; TTmem_bank.baseaddr = TTmemory; TTmem_bank.mask = TTmem_mask; TTmem_bank.start = TTmem_start; init_bank ( &TTmem_bank , TTmem_size ); } else { write_log ("can't allocate %d MB for TT RAM\n" , TTmem_size / ( 1024*1024 ) ); TTmem_size = 0; } } } /* ROM memory: */ /* Depending on which ROM version we are using, the other ROM region is illegal! */ if(NewRomMemStart == 0xFC0000) { map_banks_ce(&ROMmem_bank, 0xFC0000 >> 16, 0x3, 0, CE_MEMBANK_FAST16, CACHE_ENABLE_BOTH); /* [NP] tested on real STF, no bus wait from ROM */ map_banks_ce(&BusErrMem_bank, 0xE00000 >> 16, 0x10, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); } else if(NewRomMemStart == 0xE00000) { map_banks_ce(&ROMmem_bank, 0xE00000 >> 16, 0x10, 0, CE_MEMBANK_FAST16, CACHE_ENABLE_BOTH); /* [NP] tested on real STF, no bus wait from ROM */ map_banks_ce(&BusErrMem_bank, 0xFC0000 >> 16, 0x3, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); } else { write_log("Illegal ROM memory start!\n"); } /* Cartridge memory: */ map_banks_ce(&ROMmem_bank, 0xFA0000 >> 16, 0x2, 0, CE_MEMBANK_FAST16, CACHE_ENABLE_BOTH); /* [NP] tested on real STF, no bus wait from cartridge */ ROMmem_bank.baseaddr = ROMmemory; ROMmem_bank.mask = ROMmem_mask; ROMmem_bank.start = ROMmem_start; init_bank ( &ROMmem_bank , ROMmem_size ); /* IO memory: */ map_banks_ce(&IOmem_bank, IOmem_start>>16, 0x1, 0, CE_MEMBANK_FAST16, CE_MEMBANK_NOT_CACHABLE); /* [NP] tested on real STF, no bus wait for IO memory */ IOmem_bank.baseaddr = IOmemory; /* except for some shifter registers */ IOmem_bank.mask = IOmem_mask; IOmem_bank.start = IOmem_start; init_bank ( &IOmem_bank , IOmem_size ); /* IDE controller memory region at 0xF00000 (for Falcon or can be forced for other machines, else it's a bus error region) */ if (Ide_IsAvailable()) { map_banks_ce(&IdeMem_bank, IdeMem_start >> 16, 0x1, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); /* IDE controller on the Falcon */ IdeMem_bank.baseaddr = IdeMemory; IdeMem_bank.mask = IdeMem_mask; IdeMem_bank.start = IdeMem_start ; init_bank ( &IdeMem_bank , IdeMem_size ); } else { map_banks_ce(&BusErrMem_bank, IdeMem_start >> 16, 0x1, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); } /* VME regions on TT : 0xFE000000 to 0xFEFEFFFF (A24/D16) and 0xFEFF0000 to 0xFEFFFFFF (A16/D16) : total is 16 MB */ /* VME regions on MegaSTE : 0x00A00000 to 0x00DEFFFF (A24/D16) and 0x00DF0000 to 0x00DFFFFF (A16/D16) : total is 4 MB */ /* Hatari doesn't emulate VME for now, so these regions should return bus errors */ if ( Config_IsMachineTT() ) { map_banks_ce(&BusErrMem_bank, VMEmem_start_TT >> 16, ( VMEmem_end_TT - VMEmem_start_TT ) >> 16 , 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); } else if ( Config_IsMachineMegaSTE() ) { map_banks_ce(&BusErrMem_bank, VMEmem_start_MegaSTE >> 16, ( VMEmem_end_MegaSTE - VMEmem_start_MegaSTE ) >> 16 , 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); } /* Illegal memory regions cause a bus error on the ST: */ map_banks_ce(&BusErrMem_bank, 0xF10000 >> 16, 0x9, 0, CE_MEMBANK_CHIP16, CE_MEMBANK_NOT_CACHABLE); /* According to the "Atari TT030 Hardware Reference Manual", the * lowest 16 MBs (i.e. the 24-bit address space) are always mirrored * to 0xff000000, so we remap memory 00xxxxxx to FFxxxxxx here. If not, * we'd get some crashes when booting TOS 3 and 4 (e.g. both TOS 3.06 * and TOS 4.04 touch 0xffff8606 before setting up the MMU tables) */ if (!ConfigureParams.System.bAddressSpace24) { /* Copy all 256 banks 0x0000-0x00FF to banks 0xFF00-0xFFFF */ for ( addr=0x0 ; addr<=0x00ffffff ; addr+=0x10000 ) { //printf ( "put mem %x %x\n" , addr , addr|0xff000000 ); put_mem_bank ( 0xff000000|addr , &get_mem_bank ( addr ) , 0 ); /* Copy the CE parameters */ ce_banktype[ (0xff000000|addr)>>16 ] = ce_banktype[ addr>>16 ]; ce_cachable[ (0xff000000|addr)>>16 ] = ce_cachable[ addr>>16 ]; } } illegal_count = 0; } /* * Uninitialize the memory banks. */ void memory_uninit (void) { /* Here, we free allocated memory from memory_init */ if (TTmemory) { free(TTmemory); TTmemory = NULL; } if (STmemory) { free(STmemory); STmemory = NULL; } if (STmem_size <= 0x800000 && ROMmemory) { free(ROMmemory); } ROMmemory = NULL; } static void map_banks2 (addrbank *bank, int start, int size, int realsize, int quick) { #ifndef WINUAE_FOR_HATARI int bnr, old; unsigned long int hioffs = 0, endhioffs = 0x100; addrbank *orgbank = bank; uae_u32 realstart = start; #else int bnr; unsigned long int hioffs = 0, endhioffs = 0x100; NOWARN_UNUSED(uae_u32 realstart) = start; #endif //printf ( "map %x %x 24=%d\n" , start<<16 , size<<16 , currprefs.address_space_24 ); #ifndef WINUAE_FOR_HATARI if (quick <= 0) old = debug_bankchange (-1); #endif flush_icache(3); /* Sure don't want to keep any old mappings around! */ #ifdef NATMEM_OFFSET if (!quick) delete_shmmaps (start << 16, size << 16); #endif if (!realsize) realsize = size << 16; if ((size << 16) < realsize) { write_log (_T("Broken mapping, size=%x, realsize=%x\nStart is %x\n"), size, realsize, start); } #ifdef JIT if ((bank->jit_read_flag | bank->jit_write_flag) & S_N_ADDR) { jit_n_addr_unsafe = 1; } #endif #ifndef ADDRESS_SPACE_24BIT if (start >= 0x100) { int real_left = 0; for (bnr = start; bnr < start + size; bnr++) { if (!real_left) { realstart = bnr; real_left = realsize >> 16; #ifdef NATMEM_OFFSET if (!quick) add_shmmaps (realstart << 16, bank); #endif } put_mem_bank (bnr << 16, bank, realstart << 16); real_left--; } #ifndef WINUAE_FOR_HATARI if (quick <= 0) debug_bankchange (old); #endif return; } #endif if (last_address_space_24) endhioffs = 0x10000; #ifdef ADDRESS_SPACE_24BIT endhioffs = 0x100; #endif for (hioffs = 0; hioffs < endhioffs; hioffs += 0x100) { int real_left = 0; for (bnr = start; bnr < start + size; bnr++) { if (!real_left) { realstart = bnr + hioffs; real_left = realsize >> 16; #ifdef NATMEM_OFFSET if (!quick) add_shmmaps (realstart << 16, bank); #endif } put_mem_bank ((bnr + hioffs) << 16, bank, realstart << 16); /* Copy the CE parameters for bank/start */ ce_banktype[ (bnr + hioffs) ] = ce_banktype[ start ]; ce_cachable[ (bnr + hioffs) ] = ce_cachable[ start ]; //printf ( "ce copy %x %x\n" , ce_banktype[ (bnr + hioffs) ] , ce_cachable[ (bnr + hioffs) ] ); real_left--; } } #ifndef WINUAE_FOR_HATARI if (quick <= 0) debug_bankchange (old); fill_ce_banks (); #endif } void map_banks (addrbank *bank, int start, int size, int realsize) { map_banks2 (bank, start, size, realsize, 0); } void map_banks_quick (addrbank *bank, int start, int size, int realsize) { map_banks2 (bank, start, size, realsize, 1); } void map_banks_nojitdirect (addrbank *bank, int start, int size, int realsize) { map_banks2 (bank, start, size, realsize, -1); } void map_banks_ce (addrbank *bank, int start, int size, int realsize , int banktype, int cachable ) { fill_ce_banks (start, size, banktype, cachable ); map_banks2 (bank, start, size, realsize, 0); } void memory_hardreset (void) { } /* * Called from newcpu.c / m68k_go() when cpu_hardreset==true * Clear RAM at STmemory and TTmemory */ void memory_clear (void) { // [NP] Don't clear memory for now, because it would erase memory after restoring a memory snapshot // TODO : handle memstate save/restore from m68_go as in winuae ? #if 0 if ( STmemory ) memset ( STmemory , 0 , STmem_size ); if ( TTmemory ) memset ( TTmemory , 0 , TTmem_size ); #endif } uae_u32 memory_get_longi(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return call_mem_get_func(ab->lgeti, addr); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_r + addr; return do_get_mem_long((uae_u32 *)m); } } uae_u32 memory_get_wordi(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return call_mem_get_func(ab->wgeti, addr); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_r + addr; return do_get_mem_word((uae_u16*)m); } } uae_u32 memory_get_long(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return call_mem_get_func(ab->lget, addr); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_r + addr; return do_get_mem_long((uae_u32*)m); } } uae_u32 memory_get_word(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return call_mem_get_func(ab->wget, addr); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_r + addr; return do_get_mem_word((uae_u16*)m); } } uae_u32 memory_get_byte(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return call_mem_get_func(ab->bget, addr); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_r + addr; return *m; } } void memory_put_long(uaecptr addr, uae_u32 v) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_w) { call_mem_put_func(ab->lput, addr, v); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_w + addr; do_put_mem_long((uae_u32*)m, v); } } void memory_put_word(uaecptr addr, uae_u32 v) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_w) { call_mem_put_func(ab->wput, addr, v); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_w + addr; do_put_mem_word((uae_u16*)m, v); } } void memory_put_byte(uaecptr addr, uae_u32 v) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_w) { call_mem_put_func(ab->bput, addr, v); } else { uae_u8 *m; addr -= ab->startaccessmask; addr &= ab->mask; m = ab->baseaddr_direct_w + addr; *m = (uae_u8)v; } } uae_u8 *memory_get_real_address(uaecptr addr) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return get_mem_bank(addr).xlateaddr(addr); } else { addr -= ab->startaccessmask; addr &= ab->mask; return ab->baseaddr_direct_r + addr; } } int memory_valid_address(uaecptr addr, uae_u32 size) { addrbank *ab = &get_mem_bank(addr); if (!ab->baseaddr_direct_r) { return get_mem_bank(addr).check(addr, size); } addr -= ab->startaccessmask; addr &= ab->mask; return addr + size <= ab->allocated_size; } void dma_put_word(uaecptr addr, uae_u16 v) { addrbank* ab = &get_mem_bank(addr); if (ab->flags & ABFLAG_NODMA) return; put_word(addr, v); } void dma_put_byte(uaecptr addr, uae_u8 v) { addrbank* ab = &get_mem_bank(addr); if (ab->flags & ABFLAG_NODMA) return; put_byte(addr, v); } uae_u16 dma_get_word(uaecptr addr) { addrbank* ab = &get_mem_bank(addr); if (ab->flags & ABFLAG_NODMA) return 0xffff; return get_word(addr); } uae_u8 dma_get_byte(uaecptr addr) { addrbank* ab = &get_mem_bank(addr); if (ab->flags & ABFLAG_NODMA) return 0xff; return get_byte(addr); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/memory.h000066400000000000000000000707331504763705000236120ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * memory management * * Copyright 1995 Bernd Schmidt */ #ifndef UAE_MEMORY_H #define UAE_MEMORY_H extern void memory_reset(void); #ifdef JIT extern int special_mem; extern int special_mem_default; extern int jit_n_addr_unsafe; #endif #define S_READ 1 #define S_WRITE 2 #define S_N_ADDR 4 bool init_shm (void); void free_shm (void); bool preinit_shm (void); extern bool canbang; extern bool jit_direct_compatible_memory; #define Z3BASE_UAE 0x10000000 #define Z3BASE_REAL 0x40000000 #define AUTOCONFIG_Z2 0x00e80000 #define AUTOCONFIG_Z2_MEM 0x00200000 #define AUTOCONFIG_Z3 0xff000000 #ifdef ADDRESS_SPACE_24BIT #define MEMORY_BANKS 256 #define MEMORY_RANGE_MASK ((1<<24)-1) #else #define MEMORY_BANKS 65536 #define MEMORY_RANGE_MASK (~0) #endif typedef uae_u32 (REGPARAM3 *mem_get_func)(uaecptr) REGPARAM; typedef void (REGPARAM3 *mem_put_func)(uaecptr, uae_u32) REGPARAM; typedef uae_u8 *(REGPARAM3 *xlate_func)(uaecptr) REGPARAM; typedef int (REGPARAM3 *check_func)(uaecptr, uae_u32) REGPARAM; extern uae_u32 max_z3fastmem; extern uae_u32 wait_cpu_cycle_read (uaecptr addr, int mode); extern void wait_cpu_cycle_write (uaecptr addr, int mode, uae_u32 v); extern uae_u32 wait_cpu_cycle_read_ce020 (uaecptr addr, int mode); extern void wait_cpu_cycle_write_ce020 (uaecptr addr, int mode, uae_u32 v); #undef DIRECT_MEMFUNCS_SUCCESSFUL #include "maccess.h" #define chipmem_start_addr 0x00000000 #define bogomem_start_addr 0x00C00000 #define ROM_SIZE_512 524288 #define ROM_SIZE_256 262144 #define ROM_SIZE_128 131072 extern bool ersatzkickfile; extern bool cloanto_rom, kickstart_rom; extern uae_u16 kickstart_version; extern int uae_boot_rom_type; extern int uae_boot_rom_size; extern uaecptr rtarea_base; extern uaecptr uaeboard_base; extern uae_u8* baseaddr[]; #define CACHE_ENABLE_DATA 0x01 #define CACHE_ENABLE_DATA_BURST 0x02 #define CACHE_ENABLE_COPYBACK 0x020 #define CACHE_ENABLE_INS 0x80 #define CACHE_ENABLE_INS_BURST 0x40 #define CACHE_ENABLE_BOTH (CACHE_ENABLE_DATA | CACHE_ENABLE_INS) #define CACHE_ENABLE_ALL (CACHE_ENABLE_BOTH | CACHE_ENABLE_INS_BURST | CACHE_ENABLE_DATA_BURST) #define CACHE_DISABLE_ALLOCATE 0x08 #define CACHE_DISABLE_MMU 0x10 extern uae_u8 ce_banktype[65536], ce_cachable[65536]; #define ABFLAG_CACHE_SHIFT 24 enum { ABFLAG_UNK = 0, ABFLAG_RAM = 1, ABFLAG_ROM = 2, ABFLAG_ROMIN = 4, ABFLAG_IO = 8, ABFLAG_NONE = 16, ABFLAG_SAFE = 32, ABFLAG_INDIRECT = 64, ABFLAG_NOALLOC = 128, ABFLAG_RTG = 256, ABFLAG_THREADSAFE = 512, ABFLAG_DIRECTMAP = 1024, ABFLAG_ALLOCINDIRECT = 2048, ABFLAG_CHIPRAM = 4096, ABFLAG_CIA = 8192, ABFLAG_PPCIOSPACE = 16384, ABFLAG_MAPPED = 32768, ABFLAG_DIRECTACCESS = 65536, ABFLAG_NODMA = 131072, ABFLAG_CACHE_ENABLE_DATA = CACHE_ENABLE_DATA << ABFLAG_CACHE_SHIFT, ABFLAG_CACHE_ENABLE_DATA_BURST = CACHE_ENABLE_DATA_BURST << ABFLAG_CACHE_SHIFT, ABFLAG_CACHE_ENABLE_INS = CACHE_ENABLE_INS << ABFLAG_CACHE_SHIFT, ABFLAG_CACHE_ENABLE_INS_BURST = CACHE_ENABLE_INS_BURST << ABFLAG_CACHE_SHIFT, }; #define ABFLAG_CACHE_ENABLE_BOTH (ABFLAG_CACHE_ENABLE_DATA | ABFLAG_CACHE_ENABLE_INS) #define ABFLAG_CACHE_ENABLE_ALL (ABFLAG_CACHE_ENABLE_BOTH | ABFLAG_CACHE_ENABLE_INS_BURST | ABFLAG_CACHE_ENABLE_DATA_BURST) typedef struct addrbank { /* These ones should be self-explanatory... * Do not move. JIT depends on it */ mem_get_func lget, wget, bget; mem_put_func lput, wput, bput; /* Use xlateaddr to translate an Amiga address to a uae_u8 * that can * be used to address memory without calling the wget/wput functions. * This doesn't work for all memory banks, so this function may call * abort(). */ xlate_func xlateaddr; /* To prevent calls to abort(), use check before calling xlateaddr. * It checks not only that the memory bank can do xlateaddr, but also * that the pointer points to an area of at least the specified size. * This is used for example to translate bitplane pointers in custom.c */ check_func check; /* For those banks that refer to real memory, we can save the whole trouble of going through function calls, and instead simply grab the memory ourselves. This holds the memory address where the start of memory is for this particular bank. */ uae_u8 *baseaddr; const TCHAR *label; const TCHAR *name; /* for instruction opcode/operand fetches */ mem_get_func lgeti, wgeti; int flags; int jit_read_flag, jit_write_flag; struct addrbank_sub *sub_banks; uae_u32 mask; uae_u32 startmask; uae_u32 start; // if RAM: size of allocated RAM. Zero if failed. uae_u32 allocated_size; // size of bank (if IO or before RAM allocation) uae_u32 reserved_size; /* non-NULL if xget/xput can be bypassed */ uae_u8 *baseaddr_direct_r; uae_u8 *baseaddr_direct_w; uae_u32 startaccessmask; bool barrier; uae_u32 protectmode; } addrbank; #define MEMORY_MIN_SUBBANK 1024 struct addrbank_sub { addrbank *bank; uae_u32 offset; uae_u32 suboffset; uae_u32 mask; uae_u32 maskval; }; #ifndef WINUAE_FOR_HATARI struct autoconfig_info { struct uae_prefs *prefs; bool doinit; bool postinit; int devnum; uae_u8 autoconfig_raw[128]; uae_u8 autoconfig_bytes[16]; TCHAR name[128]; const uae_u8 *autoconfigp; bool autoconfig_automatic; uae_u32 start; uae_u32 size; int zorro; const TCHAR *label; struct addrbank *addrbank; uaecptr write_bank_address; struct romconfig *rc; uae_u32 last_high_ram; const struct cpuboardsubtype *cst; const struct expansionromtype *ert; struct autoconfig_info *parent; const int *parent_romtype; bool parent_of_previous; bool parent_address_space; bool direct_vram; const TCHAR *parent_name; bool can_sort; bool hardwired; bool (*get_params)(struct uae_prefs*, struct expansion_params*); bool (*set_params)(struct uae_prefs*, struct expansion_params*); void *userdata; }; #endif #define CE_MEMBANK_FAST32 0 #define CE_MEMBANK_CHIP16 1 #define CE_MEMBANK_CHIP32 2 #define CE_MEMBANK_CIA 3 #define CE_MEMBANK_FAST16 4 //#define CE_MEMBANK_FAST16_EXTRA_ACCURACY 5 #ifdef WINUAE_FOR_HATARI #define CE_MEMBANK_NOT_CACHABLE CACHE_DISABLE_ALLOCATE #endif #define MEMORY_LGETI(name) \ static uae_u32 REGPARAM3 name ## _lgeti (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## _lgeti (uaecptr addr) \ { \ uae_u8 *m; \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ m = name ## _bank.baseaddr + addr; \ return do_get_mem_long ((uae_u32 *)m); \ } #define MEMORY_WGETI(name) \ static uae_u32 REGPARAM3 name ## _wgeti (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## _wgeti (uaecptr addr) \ { \ uae_u8 *m; \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ m = name ## _bank.baseaddr + addr; \ return do_get_mem_word ((uae_u16 *)m); \ } #define MEMORY_LGET(name) \ static uae_u32 REGPARAM3 name ## _lget (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## _lget (uaecptr addr) \ { \ uae_u8 *m; \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ m = name ## _bank.baseaddr + addr; \ return do_get_mem_long ((uae_u32 *)m); \ } #define MEMORY_WGET(name) \ static uae_u32 REGPARAM3 name ## _wget (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## _wget (uaecptr addr) \ { \ uae_u8 *m; \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ m = name ## _bank.baseaddr + addr; \ return do_get_mem_word ((uae_u16 *)m); \ } #define MEMORY_BGET(name) \ static uae_u32 REGPARAM3 name ## _bget (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## _bget (uaecptr addr) \ { \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ return name ## _bank.baseaddr[addr]; \ } #define MEMORY_LPUT(name) \ static void REGPARAM3 name ## _lput (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM2 name ## _lput (uaecptr addr, uae_u32 l) \ { \ uae_u8 *m; \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ m = name ## _bank.baseaddr + addr; \ do_put_mem_long ((uae_u32 *)m, l); \ } #define MEMORY_WPUT(name) \ static void REGPARAM3 name ## _wput (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM2 name ## _wput (uaecptr addr, uae_u32 w) \ { \ uae_u8 *m; \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ m = name ## _bank.baseaddr + addr; \ do_put_mem_word ((uae_u16 *)m, w); \ } #define MEMORY_BPUT(name) \ static void REGPARAM3 name ## _bput (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM2 name ## _bput (uaecptr addr, uae_u32 b) \ { \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ name ## _bank.baseaddr[addr] = b; \ } #define MEMORY_CHECK(name) \ static int REGPARAM3 name ## _check (uaecptr addr, uae_u32 size) REGPARAM; \ static int REGPARAM2 name ## _check (uaecptr addr, uae_u32 size) \ { \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ return (addr + size) <= name ## _bank.allocated_size; \ } #define MEMORY_XLATE(name) \ static uae_u8 *REGPARAM3 name ## _xlate (uaecptr addr) REGPARAM; \ static uae_u8 *REGPARAM2 name ## _xlate (uaecptr addr) \ { \ addr -= name ## _bank.startaccessmask; \ addr &= name ## _bank.mask; \ return name ## _bank.baseaddr + addr; \ } #define DECLARE_MEMORY_FUNCTIONS(name) \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _lget) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _lgeti) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _wget) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _wgeti) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _bget) (uaecptr) REGPARAM; \ static void REGPARAM3 NOWARN_UNUSED(name ## _lput) (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM3 NOWARN_UNUSED(name ## _wput) (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM3 NOWARN_UNUSED(name ## _bput) (uaecptr, uae_u32) REGPARAM; \ static int REGPARAM3 NOWARN_UNUSED(name ## _check) (uaecptr addr, uae_u32 size) REGPARAM; \ static uae_u8 *REGPARAM3 NOWARN_UNUSED(name ## _xlate) (uaecptr addr) REGPARAM; #define DECLARE_MEMORY_FUNCTIONS_WITH_SUFFIX(name, suffix) \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _lget_ ## suffix) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _lgeti_ ## suffix) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _wget_ ## suffix) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _wgeti_ ## suffix) (uaecptr) REGPARAM; \ static uae_u32 REGPARAM3 NOWARN_UNUSED(name ## _bget_ ## suffix) (uaecptr) REGPARAM; \ static void REGPARAM3 NOWARN_UNUSED(name ## _lput_ ## suffix) (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM3 NOWARN_UNUSED(name ## _wput_ ## suffix) (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM3 NOWARN_UNUSED(name ## _bput_ ## suffix) (uaecptr, uae_u32) REGPARAM; \ static int REGPARAM3 NOWARN_UNUSED(name ## _check_ ## suffix) (uaecptr addr, uae_u32 size) REGPARAM; \ static uae_u8 *REGPARAM3 NOWARN_UNUSED(name ## _xlate_ ## suffix) (uaecptr addr) REGPARAM; #define MEMORY_FUNCTIONS(name) \ MEMORY_LGET(name); \ MEMORY_WGET(name); \ MEMORY_BGET(name); \ MEMORY_LPUT(name); \ MEMORY_WPUT(name); \ MEMORY_BPUT(name); \ MEMORY_CHECK(name); \ MEMORY_XLATE(name); #define MEMORY_ARRAY_LGET(name, index) \ static uae_u32 REGPARAM3 name ## index ## _lget (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## index ## _lget (uaecptr addr) \ { \ uae_u8 *m; \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ m = name ## _bank[index].baseaddr + addr; \ return do_get_mem_long ((uae_u32 *)m); \ } #define MEMORY_ARRAY_WGET(name, index) \ static uae_u32 REGPARAM3 name ## index ## _wget (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## index ## _wget (uaecptr addr) \ { \ uae_u8 *m; \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ m = name ## _bank[index].baseaddr + addr; \ return do_get_mem_word ((uae_u16 *)m); \ } #define MEMORY_ARRAY_BGET(name, index) \ static uae_u32 REGPARAM3 name ## index ## _bget (uaecptr) REGPARAM; \ static uae_u32 REGPARAM2 name ## index ## _bget (uaecptr addr) \ { \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ return name ## _bank[index].baseaddr[addr]; \ } #define MEMORY_ARRAY_LPUT(name, index) \ static void REGPARAM3 name ## index ## _lput (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM2 name ## index ## _lput (uaecptr addr, uae_u32 l) \ { \ uae_u8 *m; \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ m = name ## _bank[index].baseaddr + addr; \ do_put_mem_long ((uae_u32 *)m, l); \ } #define MEMORY_ARRAY_WPUT(name, index) \ static void REGPARAM3 name ## index ## _wput (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM2 name ## index ## _wput (uaecptr addr, uae_u32 w) \ { \ uae_u8 *m; \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ m = name ## _bank[index].baseaddr + addr; \ do_put_mem_word ((uae_u16 *)m, w); \ } #define MEMORY_ARRAY_BPUT(name, index) \ static void REGPARAM3 name ## index ## _bput (uaecptr, uae_u32) REGPARAM; \ static void REGPARAM2 name ## index ## _bput (uaecptr addr, uae_u32 b) \ { \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ name ## _bank[index].baseaddr[addr] = b; \ } #define MEMORY_ARRAY_CHECK(name, index) \ static int REGPARAM3 name ## index ## _check (uaecptr addr, uae_u32 size) REGPARAM; \ static int REGPARAM2 name ## index ## _check (uaecptr addr, uae_u32 size) \ { \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ return (addr + size) <= name ## _bank[index].allocated_size; \ } #define MEMORY_ARRAY_XLATE(name, index) \ static uae_u8 *REGPARAM3 name ## index ## _xlate (uaecptr addr) REGPARAM; \ static uae_u8 *REGPARAM2 name ## index ## _xlate (uaecptr addr) \ { \ addr -= name ## _bank[index].start & name ## _bank[index].mask; \ addr &= name ## _bank[index].mask; \ return name ## _bank[index].baseaddr + addr; \ } #define MEMORY_ARRAY_FUNCTIONS(name, index) \ MEMORY_ARRAY_LGET(name, index); \ MEMORY_ARRAY_WGET(name, index); \ MEMORY_ARRAY_BGET(name, index); \ MEMORY_ARRAY_LPUT(name, index); \ MEMORY_ARRAY_WPUT(name, index); \ MEMORY_ARRAY_BPUT(name, index); \ MEMORY_ARRAY_CHECK(name, index); \ MEMORY_ARRAY_XLATE(name, index); #ifndef WINUAE_FOR_HATARI extern addrbank chipmem_bank; extern addrbank chipmem_agnus_bank; extern addrbank chipmem_bank_ce2; extern addrbank kickmem_bank; extern addrbank custom_bank; extern addrbank clock_bank; extern addrbank cia_bank; extern addrbank rtarea_bank; extern addrbank filesys_bank; extern addrbank uaeboard_bank; extern addrbank expamem_bank; extern addrbank expamem_null, expamem_none; extern addrbank fastmem_bank[MAX_RAM_BOARDS]; extern addrbank fastmem_nojit_bank[MAX_RAM_BOARDS]; extern addrbank *gfxmem_banks[MAX_RTG_BOARDS]; extern addrbank gayle_bank; extern addrbank gayle2_bank; extern addrbank mbres_bank; extern addrbank akiko_bank; extern addrbank cardmem_bank; extern addrbank bogomem_bank; extern addrbank z3fastmem_bank[MAX_RAM_BOARDS]; extern addrbank z3chipmem_bank; extern addrbank mem25bit_bank; extern addrbank debugmem_bank; extern addrbank a3000lmem_bank; extern addrbank a3000hmem_bank; extern addrbank extendedkickmem_bank; extern addrbank extendedkickmem2_bank; extern addrbank custmem1_bank; extern addrbank custmem2_bank; extern addrbank romboardmem_bank[MAX_ROM_BOARDS]; extern void rtarea_init(void); extern void rtarea_free(void); extern void rtarea_init_mem(void); extern void rtarea_setup(void); extern void expamem_reset(int); extern void expamem_next(addrbank *mapped, addrbank *next); extern void expamem_shutup(addrbank *mapped); extern bool expamem_z3hack(struct uae_prefs*); extern void expansion_cpu_fallback(void); extern void set_expamem_z3_hack_mode(int); extern uaecptr expamem_board_pointer, expamem_highmem_pointer; extern uaecptr expamem_z3_pointer_real, expamem_z3_pointer_uae; extern uae_u32 expamem_z3_highram_real, expamem_z3_highram_uae; extern uae_u32 expamem_board_size; extern uae_u32 last_custom_value1; #endif /* Default memory access functions */ extern void dummy_put (uaecptr addr, int size, uae_u32 val); extern uae_u32 dummy_get (uaecptr addr, int size, bool inst, uae_u32 defvalue); extern uae_u32 dummy_get_safe(uaecptr addr, int size, bool inst, uae_u32 defvalue); extern int REGPARAM3 default_check(uaecptr addr, uae_u32 size) REGPARAM; extern uae_u8 *REGPARAM3 default_xlate(uaecptr addr) REGPARAM; /* 680x0 opcode fetches */ extern uae_u32 REGPARAM3 dummy_lgeti (uaecptr addr) REGPARAM; extern uae_u32 REGPARAM3 dummy_wgeti (uaecptr addr) REGPARAM; /* sub bank support */ extern uae_u32 REGPARAM3 sub_bank_lget (uaecptr) REGPARAM; extern uae_u32 REGPARAM3 sub_bank_wget(uaecptr) REGPARAM; extern uae_u32 REGPARAM3 sub_bank_bget(uaecptr) REGPARAM; extern void REGPARAM3 sub_bank_lput(uaecptr, uae_u32) REGPARAM; extern void REGPARAM3 sub_bank_wput(uaecptr, uae_u32) REGPARAM; extern void REGPARAM3 sub_bank_bput(uaecptr, uae_u32) REGPARAM; extern uae_u32 REGPARAM3 sub_bank_lgeti(uaecptr) REGPARAM; extern uae_u32 REGPARAM3 sub_bank_wgeti(uaecptr) REGPARAM; extern int REGPARAM3 sub_bank_check(uaecptr addr, uae_u32 size) REGPARAM; extern uae_u8 *REGPARAM3 sub_bank_xlate(uaecptr addr) REGPARAM; extern addrbank *get_sub_bank(uaecptr *addr); #define bankindex(addr) (((uaecptr)(addr)) >> 16) extern addrbank *mem_banks[MEMORY_BANKS]; #ifdef JIT extern uae_u8 *baseaddr[MEMORY_BANKS]; #endif #define get_mem_bank(addr) (*mem_banks[bankindex(addr)]) extern addrbank *get_mem_bank_real(uaecptr); #ifdef JIT #define put_mem_bank(addr, b, realstart) do { \ (mem_banks[bankindex(addr)] = (b)); \ if ((b)->baseaddr) \ baseaddr[bankindex(addr)] = (b)->baseaddr - (realstart); \ else \ baseaddr[bankindex(addr)] = (uae_u8*)(((uae_u8*)b)+1); \ } while (0) #else #define put_mem_bank(addr, b, realstart) \ (mem_banks[bankindex(addr)] = (b)); #endif #ifdef WINUAE_FOR_HATARI extern bool memory_region_bus_error ( uaecptr addr ); extern bool memory_region_iomem ( uaecptr addr ); extern void memory_map_Standard_RAM ( uint32_t MMU_Bank0_Size , uint32_t MMU_Bank1_Size ); #endif extern void memory_init(uae_u32 NewSTMemSize, uae_u32 NewTTMemSize, uae_u32 NewRomMemStart); extern void memory_uninit (void); extern void map_banks (addrbank *bank, int first, int count, int realsize); extern void map_banks_z2(addrbank *bank, int first, int count); extern uae_u32 map_banks_z2_autosize(addrbank *bank, int first); extern void map_banks_z3(addrbank *bank, int first, int count); extern bool validate_banks_z2(addrbank *bank, int start, int size); extern bool validate_banks_z3(addrbank *bank, int start, int size); extern void map_banks_quick (addrbank *bank, int first, int count, int realsize); extern void map_banks_nojitdirect (addrbank *bank, int first, int count, int realsize); extern void map_banks_cond (addrbank *bank, int first, int count, int realsize); extern void map_overlay (int chip); #ifdef WINUAE_FOR_HATARI extern void map_banks_ce (addrbank *bank, int first, int count, int realsize, int banktype, int cachable); #endif extern void memory_hardreset (void); extern void memory_clear (void); #ifndef WINUAE_FOR_HATARI extern void free_fastmemory (int); extern void set_roms_modified (void); extern void reload_roms(void); extern bool read_kickstart_version(struct uae_prefs *p); extern void chipmem_setindirect(void); extern void initramboard(addrbank *ab, struct ramboard *rb); extern void loadboardfile(addrbank *ab, struct boardloadfile *lf); extern void mman_set_barriers(bool); #endif uae_u32 memory_get_long(uaecptr); uae_u32 memory_get_word(uaecptr); uae_u32 memory_get_byte(uaecptr); uae_u32 memory_get_longi(uaecptr); uae_u32 memory_get_wordi(uaecptr); STATIC_INLINE uae_u32 get_long(uaecptr addr) { return memory_get_long(addr); } STATIC_INLINE uae_u32 get_word (uaecptr addr) { return memory_get_word(addr); } STATIC_INLINE uae_u32 get_byte (uaecptr addr) { return memory_get_byte(addr); } STATIC_INLINE uae_u32 get_longi(uaecptr addr) { return memory_get_longi(addr); } STATIC_INLINE uae_u32 get_wordi(uaecptr addr) { return memory_get_wordi(addr); } // do split memory access if it can cross memory banks STATIC_INLINE uae_u32 get_long_compatible(uaecptr addr) { if ((addr &0xffff) < 0xfffd) { return memory_get_long(addr); } else if (addr & 1) { uae_u8 v0 = memory_get_byte(addr + 0); uae_u16 v1 = memory_get_word(addr + 1); uae_u8 v3 = memory_get_byte(addr + 3); return (v0 << 24) | (v1 << 8) | (v3 << 0); } else { uae_u16 v0 = memory_get_word(addr + 0); uae_u16 v1 = memory_get_word(addr + 2); return (v0 << 16) | (v1 << 0); } } STATIC_INLINE uae_u32 get_word_compatible(uaecptr addr) { if ((addr & 0xffff) < 0xffff) { return memory_get_word(addr); } else { uae_u8 v0 = memory_get_byte(addr + 0); uae_u8 v1 = memory_get_byte(addr + 1); return (v0 << 8) | (v1 << 0); } } STATIC_INLINE uae_u32 get_byte_compatible(uaecptr addr) { return memory_get_byte(addr); } STATIC_INLINE uae_u32 get_longi_compatible(uaecptr addr) { if ((addr & 0xffff) < 0xfffd) { return memory_get_longi(addr); } else { uae_u16 v0 = memory_get_wordi(addr + 0); uae_u16 v1 = memory_get_wordi(addr + 2); return (v0 << 16) | (v1 << 0); } } STATIC_INLINE uae_u32 get_wordi_compatible(uaecptr addr) { return memory_get_wordi(addr); } STATIC_INLINE uae_u32 get_long_jit(uaecptr addr) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_read_flag; #endif return memory_get_long(addr); } STATIC_INLINE uae_u32 get_word_jit(uaecptr addr) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_read_flag; #endif return memory_get_word(addr); } STATIC_INLINE uae_u32 get_byte_jit(uaecptr addr) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_read_flag; #endif return memory_get_byte(addr); } STATIC_INLINE uae_u32 get_longi_jit(uaecptr addr) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_read_flag; #endif return memory_get_longi(addr); } STATIC_INLINE uae_u32 get_wordi_jit(uaecptr addr) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_read_flag; #endif return memory_get_wordi(addr); } /* * Read a host pointer from addr */ #if SIZEOF_VOID_P == 4 # define get_pointer(addr) ((void *)get_long (addr)) #else # if SIZEOF_VOID_P == 8 STATIC_INLINE void *get_pointer (uaecptr addr) { const unsigned int n = SIZEOF_VOID_P / 4; union { void *ptr; uae_u32 longs[SIZEOF_VOID_P / 4]; } p; unsigned int i; for (i = 0; i < n; i++) { #ifdef WORDS_BIGENDIAN p.longs[i] = get_long (addr + i * 4); #else p.longs[n - 1 - i] = get_long (addr + i * 4); #endif } return p.ptr; } # else # error "Unknown or unsupported pointer size." # endif #endif void dma_put_word(uaecptr addr, uae_u16 v); uae_u16 dma_get_word(uaecptr addr); void dma_put_byte(uaecptr addr, uae_u8 v); uae_u8 dma_get_byte(uaecptr addr); void memory_put_long(uaecptr, uae_u32); void memory_put_word(uaecptr, uae_u32); void memory_put_byte(uaecptr, uae_u32); STATIC_INLINE void put_long (uaecptr addr, uae_u32 l) { memory_put_long(addr, l); } STATIC_INLINE void put_word (uaecptr addr, uae_u32 w) { memory_put_word(addr, w); } STATIC_INLINE void put_byte (uaecptr addr, uae_u32 b) { memory_put_byte(addr, b); } // do split memory access if it can cross memory banks STATIC_INLINE void put_long_compatible(uaecptr addr, uae_u32 l) { if ((addr & 0xffff) < 0xfffd) { memory_put_long(addr, l); } else if (addr & 1) { memory_put_byte(addr + 0, l >> 24); memory_put_word(addr + 1, l >> 8); memory_put_byte(addr + 3, l >> 0); } else { memory_put_word(addr + 0, l >> 16); memory_put_word(addr + 2, l >> 0); } } STATIC_INLINE void put_word_compatible(uaecptr addr, uae_u32 w) { if ((addr & 0xffff) < 0xffff) { memory_put_word(addr, w); } else { memory_put_byte(addr + 0, w >> 8); memory_put_byte(addr + 1, w >> 0); } } STATIC_INLINE void put_byte_compatible(uaecptr addr, uae_u32 b) { memory_put_byte(addr, b); } STATIC_INLINE void put_long_jit(uaecptr addr, uae_u32 l) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_write_flag; #endif memory_put_long(addr, l); } STATIC_INLINE void put_word_jit(uaecptr addr, uae_u32 l) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_write_flag; #endif memory_put_word(addr, l); } STATIC_INLINE void put_byte_jit(uaecptr addr, uae_u32 l) { #ifdef JIT addrbank *bank = &get_mem_bank(addr); special_mem |= bank->jit_write_flag; #endif memory_put_byte(addr, l); } /* * Store host pointer v at addr */ #if SIZEOF_VOID_P == 4 # define put_pointer(addr, p) (put_long ((addr), (uae_u32)(p))) #else # if SIZEOF_VOID_P == 8 STATIC_INLINE void put_pointer (uaecptr addr, void *v) { const unsigned int n = SIZEOF_VOID_P / 4; union { void *ptr; uae_u32 longs[SIZEOF_VOID_P / 4]; } p; unsigned int i; p.ptr = v; for (i = 0; i < n; i++) { #ifdef WORDS_BIGENDIAN put_long (addr + i * 4, p.longs[i]); #else put_long (addr + i * 4, p.longs[n - 1 - i]); #endif } } # endif #endif bool real_address_allowed(void); uae_u8 *memory_get_real_address(uaecptr); int memory_valid_address(uaecptr, uae_u32); STATIC_INLINE uae_u8 *get_real_address (uaecptr addr) { return memory_get_real_address(addr); } STATIC_INLINE int valid_address (uaecptr addr, uae_u32 size) { return memory_valid_address(addr, size); } STATIC_INLINE void put_quad_host(void *addr, uae_u64 v) { do_put_mem_long((uae_u32*)addr, v >> 32); do_put_mem_long(((uae_u32*)addr) + 1, (uae_u32)v); } STATIC_INLINE void put_long_host(void *addr, uae_u32 v) { do_put_mem_long((uae_u32*)addr, v); } STATIC_INLINE void put_word_host(void *addr, uae_u16 v) { do_put_mem_word((uae_u16*)addr, v); } STATIC_INLINE void put_byte_host(void *addr, uae_u8 v) { *((uae_u8*)addr) = v; } STATIC_INLINE uae_u64 get_quad_host(void *addr) { uae_u64 v = ((uae_u64)do_get_mem_long((uae_u32*)addr)) << 32; v |= do_get_mem_long(((uae_u32*)addr) + 1); return v; } STATIC_INLINE uae_u32 get_long_host(void *addr) { return do_get_mem_long((uae_u32*)addr); } STATIC_INLINE uae_u16 get_word_host(void *addr) { return do_get_mem_word((uae_u16*)addr); } STATIC_INLINE uae_u32 get_byte_host(void *addr) { return *((uae_u8*)addr); } extern int addr_valid (const TCHAR*, uaecptr,uae_u32); /* For faster access in custom chip emulation. */ extern void REGPARAM3 chipmem_lput (uaecptr, uae_u32) REGPARAM; extern void REGPARAM3 chipmem_wput (uaecptr, uae_u32) REGPARAM; extern void REGPARAM3 chipmem_bput (uaecptr, uae_u32) REGPARAM; extern uae_u32 REGPARAM3 chipmem_agnus_wget (uaecptr) REGPARAM; extern void REGPARAM3 chipmem_agnus_wput (uaecptr, uae_u32) REGPARAM; extern addrbank dummy_bank; /* 68020+ Chip RAM DMA contention emulation */ extern void REGPARAM3 chipmem_bput_c2 (uaecptr, uae_u32) REGPARAM; extern uae_u32 (REGPARAM3 *chipmem_lget_indirect)(uaecptr) REGPARAM; extern uae_u32 (REGPARAM3 *chipmem_wget_indirect)(uaecptr) REGPARAM; extern uae_u32 (REGPARAM3 *chipmem_bget_indirect)(uaecptr) REGPARAM; extern void (REGPARAM3 *chipmem_lput_indirect)(uaecptr, uae_u32) REGPARAM; extern void (REGPARAM3 *chipmem_wput_indirect)(uaecptr, uae_u32) REGPARAM; extern void (REGPARAM3 *chipmem_bput_indirect)(uaecptr, uae_u32) REGPARAM; extern int (REGPARAM3 *chipmem_check_indirect)(uaecptr, uae_u32) REGPARAM; extern uae_u8 *(REGPARAM3 *chipmem_xlate_indirect)(uaecptr) REGPARAM; #ifdef NATMEM_OFFSET typedef struct shmpiece_reg { uae_u8 *native_address; int id; uae_u32 size; const TCHAR *name; struct shmpiece_reg *next; struct shmpiece_reg *prev; } shmpiece; extern shmpiece *shm_start; extern uae_u8* natmem_offset; extern uae_u8 *natmem_reserved; extern uae_u32 natmem_reserved_size; #endif extern bool mapped_malloc (addrbank*); extern void mapped_free (addrbank*); extern void a3000_fakekick (int); extern uaecptr strcpyha_safe (uaecptr dst, const uae_char *src); extern uae_char *strcpyah_safe (uae_char *dst, uaecptr src, int maxsize); extern void memcpyha_safe (uaecptr dst, const uae_u8 *src, int size); extern void memcpyha (uaecptr dst, const uae_u8 *src, int size); extern void memcpyah_safe (uae_u8 *dst, uaecptr src, int size); extern void memcpyah (uae_u8 *dst, uaecptr src, int size); #define UAE_MEMORY_REGIONS_MAX 64 #define UAE_MEMORY_REGION_NAME_LENGTH 64 #define UAE_MEMORY_REGION_RAM (1 << 0) #define UAE_MEMORY_REGION_ALIAS (1 << 1) #define UAE_MEMORY_REGION_MIRROR (1 << 2) /* Get a list of memory regions in the Amiga address space */ typedef struct UaeMemoryRegion { uaecptr start; uae_u32 size; TCHAR name[UAE_MEMORY_REGION_NAME_LENGTH]; TCHAR rom_name[UAE_MEMORY_REGION_NAME_LENGTH]; uaecptr alias; int flags; uae_u8 *memory; } UaeMemoryRegion; typedef struct UaeMemoryMap { UaeMemoryRegion regions[UAE_MEMORY_REGIONS_MAX]; int num_regions; } UaeMemoryMap; void uae_memory_map(UaeMemoryMap *map); #endif /* UAE_MEMORY_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/mmu_common.h000066400000000000000000000105741504763705000244450ustar00rootroot00000000000000#ifndef UAE_MMU_COMMON_H #define UAE_MMU_COMMON_H #include "uae/types.h" #include "uae/likely.h" #define MMUDEBUG 0 #define MMUINSDEBUG 0 #define MMUDEBUGMISC 0 #ifdef __cplusplus struct m68k_exception { int prb; m68k_exception (int exc) : prb (exc) {} operator int() { return prb; } }; #define SAVE_EXCEPTION #define RESTORE_EXCEPTION #define TRY(var) try #define CATCH(var) catch([[maybe_unused]] m68k_exception &var) #define THROW(n) throw m68k_exception(n) #define THROW_AGAIN(var) throw #define ENDTRY #define STOPTRY #else /* we are in plain C, just use a stack of long jumps */ #include extern jmp_buf __exbuf; extern int __exvalue; #define TRY(DUMMY) __exvalue=setjmp(__exbuf); \ if (__exvalue==0) { __pushtry(&__exbuf); #define CATCH(x) __poptry(); } else {m68k_exception x=__exvalue; x=x; #define ENDTRY __poptry();} #define STOPTRY __poptry() #define THROW(x) if (__is_catched()) {longjmp(__exbuf,x);} #define THROW_AGAIN(var) if (__is_catched()) longjmp(*__poptry(),__exvalue) #define SAVE_EXCEPTION #define RESTORE_EXCEPTION jmp_buf* __poptry(void); void __pushtry(jmp_buf *j); int __is_catched(void); typedef int m68k_exception; #endif /* special status word (access error stack frame) */ /* 68060 */ #define MMU_FSLW_MA 0x08000000 #define MMU_FSLW_LK 0x02000000 #define MMU_FSLW_R 0x01000000 #define MMU_FSLW_W 0x00800000 #define MMU_FSLW_SIZE_L 0x00000000 /* Note: wrong in mc68060 manual! */ #define MMU_FSLW_SIZE_B 0x00200000 #define MMU_FSLW_SIZE_W 0x00400000 #define MMU_FSLW_SIZE_D 0x00600000 #define MMU_FSLW_TT 0x00180000 #define MMU_FSLW_TT_N 0x00000000 /* Normal access */ #define MMU_FSLW_TT_16 0x00080000 /* MOVE16 */ #define MMU_FSLW_TM 0x00070000 /* = function code */ #define MMU_FSLW_IO 0x00008000 #define MMU_FSLW_PBE 0x00004000 #define MMU_FSLW_SBE 0x00002000 #define MMU_FSLW_PTA 0x00001000 #define MMU_FSLW_PTB 0x00000800 #define MMU_FSLW_IL 0x00000400 #define MMU_FSLW_PF 0x00000200 #define MMU_FSLW_SP 0x00000100 #define MMU_FSLW_WP 0x00000080 #define MMU_FSLW_TWE 0x00000040 #define MMU_FSLW_RE 0x00000020 #define MMU_FSLW_WE 0x00000010 #define MMU_FSLW_TTR 0x00000008 #define MMU_FSLW_BPE 0x00000004 #define MMU_FSLW_SEE 0x00000001 /* 68040 */ #define MMU_SSW_TM 0x0007 #define MMU_SSW_TT 0x0018 #define MMU_SSW_TT1 0x0010 #define MMU_SSW_TT0 0x0008 #define MMU_SSW_SIZE 0x0060 #define MMU_SSW_SIZE_B 0x0020 #define MMU_SSW_SIZE_W 0x0040 #define MMU_SSW_SIZE_L 0x0000 #define MMU_SSW_SIZE_CL 0x0060 #define MMU_SSW_RW 0x0100 #define MMU_SSW_LK 0x0200 #define MMU_SSW_ATC 0x0400 #define MMU_SSW_MA 0x0800 #define MMU_SSW_CM 0x1000 #define MMU_SSW_CT 0x2000 #define MMU_SSW_CU 0x4000 #define MMU_SSW_CP 0x8000 /* 68030 */ #define MMU030_SSW_FC 0x8000 #define MMU030_SSW_FB 0x4000 #define MMU030_SSW_RC 0x2000 #define MMU030_SSW_RB 0x1000 #define MMU030_SSW_DF 0x0100 #define MMU030_SSW_RM 0x0080 #define MMU030_SSW_RW 0x0040 #define MMU030_SSW_SIZE_MASK 0x0030 #define MMU030_SSW_SIZE_B 0x0010 #define MMU030_SSW_SIZE_W 0x0020 #define MMU030_SSW_SIZE_L 0x0000 #define MMU030_SSW_FC_MASK 0x0007 #define ALWAYS_INLINE __inline // take care of 2 kinds of alignment, bus size and page static ALWAYS_INLINE bool is_unaligned_page(uaecptr addr, int size) { return unlikely((addr & (size - 1)) && (addr ^ (addr + size - 1)) & regs.mmu_page_size); } static ALWAYS_INLINE bool is_unaligned_bus(uaecptr addr, int size) { return (addr & (size - 1)); } static ALWAYS_INLINE void phys_put_long(uaecptr addr, uae_u32 l) { put_long(addr, l); } static ALWAYS_INLINE void phys_put_word(uaecptr addr, uae_u32 w) { put_word(addr, w); } static ALWAYS_INLINE void phys_put_byte(uaecptr addr, uae_u32 b) { put_byte(addr, b); } static ALWAYS_INLINE uae_u32 phys_get_long(uaecptr addr) { return get_long(addr); } static ALWAYS_INLINE uae_u32 phys_get_word(uaecptr addr) { return get_word(addr); } static ALWAYS_INLINE uae_u32 phys_get_byte(uaecptr addr) { return get_byte(addr); } extern uae_u32(*x_phys_get_iword)(uaecptr); extern uae_u32(*x_phys_get_ilong)(uaecptr); extern uae_u32(*x_phys_get_byte)(uaecptr); extern uae_u32(*x_phys_get_word)(uaecptr); extern uae_u32(*x_phys_get_long)(uaecptr); extern void(*x_phys_put_byte)(uaecptr, uae_u32); extern void(*x_phys_put_word)(uaecptr, uae_u32); extern void(*x_phys_put_long)(uaecptr, uae_u32); #endif /* UAE_MMU_COMMON_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/newcpu.c000066400000000000000000011262261504763705000235760ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68000 emulation * * (c) 1995 Bernd Schmidt */ #define MMUOP_DEBUG 0 #define DEBUG_CD32CDTVIO 0 #define EXCEPTION3_DEBUGGER 0 #define CPUTRACE_DEBUG 0 #define VALIDATE_68030_DATACACHE 0 #define VALIDATE_68040_DATACACHE 0 #define DISABLE_68040_COPYBACK 0 #define MORE_ACCURATE_68020_PIPELINE 1 #include /* Needed for PRIX64 */ #include "main.h" #include "compat.h" #include "sysconfig.h" #include "sysdeps.h" #include "hatari-glue.h" #include "options_cpu.h" #include "events.h" #include "memory.h" #include "custom.h" #include "newcpu.h" #include "disasm.h" #include "cpummu.h" #include "cpummu030.h" #include "cputbl.h" #include "cpu_prefetch.h" #include "debugmem.h" #include "savestate.h" #include "fpp.h" #ifdef WINUAE_FOR_HATARI #include "debug.h" #include "m68000.h" #include "reset.h" #include "cycInt.h" #include "mfp.h" #include "tos.h" #include "vdi.h" #include "cart.h" #include "dialog.h" #include "bios.h" #include "xbios.h" #include "video.h" #include "options.h" #include "dsp.h" #include "log.h" #include "debugui.h" #include "debugcpu.h" #include "stMemory.h" #include "blitter.h" #include "scc.h" #endif #ifdef JIT #include "jit/compemu.h" #include #else /* Need to have these somewhere */ //#ifndef WINUAE_FOR_HATARI bool check_prefs_changed_comp (bool checkonly) { return false; } //#endif #endif /* Opcode of faulting instruction */ static uae_u32 last_op_for_exception_3; /* PC at fault time */ static uaecptr last_addr_for_exception_3; /* Address that generated the exception */ static uaecptr last_fault_for_exception_3; /* read (0) or write (1) access */ static bool last_writeaccess_for_exception_3; /* size */ static bool last_size_for_exception_3; /* FC */ static int last_fc_for_exception_3; /* Data (1) or instruction fetch (0) */ static int last_di_for_exception_3; /* not instruction */ static bool last_notinstruction_for_exception_3; /* set when writing exception stack frame */ static int exception_in_exception; /* secondary SR for handling 68040 bug */ static uae_u16 last_sr_for_exception3; static void exception3_read_special(uae_u32 opcode, uaecptr addr, int size, int fc); int mmu_enabled, mmu_triggered; int cpu_cycles; int hardware_bus_error; #ifndef WINUAE_FOR_HATARI static int baseclock; #endif int m68k_pc_indirect; bool m68k_interrupt_delay; static bool m68k_accurate_ipl; static bool m68k_reset_delay; static bool ismoves_nommu; static bool need_opcode_swap; #ifndef WINUAE_FOR_HATARI static volatile uae_atomic uae_interrupt; static volatile uae_atomic uae_interrupts2[IRQ_SOURCE_MAX]; static volatile uae_atomic uae_interrupts6[IRQ_SOURCE_MAX]; #endif static int cacheisets04060, cacheisets04060mask, cacheitag04060mask; static int cachedsets04060, cachedsets04060mask, cachedtag04060mask; static int cpu_prefs_changed_flag; int cpuipldelay2, cpuipldelay4; int cpucycleunit; int cpu_tracer; bool cpu_bus_rmw; const int areg_byteinc[] = { 1, 1, 1, 1, 1, 1, 1, 2 }; const int imm8_table[] = { 8, 1, 2, 3, 4, 5, 6, 7 }; int movem_index1[256]; int movem_index2[256]; int movem_next[256]; cpuop_func *cpufunctbl[65536]; cpuop_func_noret *cpufunctbl_noret[65536]; cpuop_func *loop_mode_table[65536]; struct cputbl_data { uae_s16 length; uae_s8 disp020[2]; uae_s8 branch; }; static struct cputbl_data cpudatatbl[65536]; struct mmufixup mmufixup[2]; #define COUNT_INSTRS 0 #define MC68060_PCR 0x04300000 #define MC68EC060_PCR 0x04310000 static uae_u64 fake_srp_030, fake_crp_030; static uae_u32 fake_tt0_030, fake_tt1_030, fake_tc_030; static uae_u16 fake_mmusr_030; static struct cache020 caches020[CACHELINES020]; static struct cache030 icaches030[CACHELINES030]; static struct cache030 dcaches030[CACHELINES030]; static int icachelinecnt, icachehalfline; static int dcachelinecnt; static struct cache040 icaches040[CACHESETS060]; static struct cache040 dcaches040[CACHESETS060]; static int cache_lastline; static int fallback_cpu_model, fallback_mmu_model, fallback_fpu_model; static bool fallback_cpu_compatible, fallback_cpu_address_space_24; static struct regstruct fallback_regs; static int fallback_new_cpu_model; #ifdef WINUAE_FOR_HATARI int OpcodeFamily; int BusCyclePenalty = 0; FILE *console_out_FILE = NULL; int quit_program = 0; /* from main.cpp */ #endif void (*flush_icache)(int); #if COUNT_INSTRS static unsigned long int instrcount[65536]; static uae_u16 opcodenums[65536]; static int compfn (const void *el1, const void *el2) { return instrcount[*(const uae_u16 *)el1] < instrcount[*(const uae_u16 *)el2]; } static TCHAR *icountfilename (void) { TCHAR *name = getenv ("INSNCOUNT"); if (name) return name; return COUNT_INSTRS == 2 ? "frequent.68k" : "insncount"; } void dump_counts (void) { FILE *f = fopen (icountfilename (), "w"); unsigned long int total; int i; write_log (_T("Writing instruction count file...\n")); for (i = 0; i < 65536; i++) { opcodenums[i] = i; total += instrcount[i]; } qsort (opcodenums, 65536, sizeof (uae_u16), compfn); fprintf (f, "Total: %lu\n", total); for (i=0; i < 65536; i++) { unsigned long int cnt = instrcount[opcodenums[i]]; struct instr *dp; struct mnemolookup *lookup; if (!cnt) break; dp = table68k + opcodenums[i]; for (lookup = lookuptab;lookup->mnemo != dp->mnemo; lookup++) ; fprintf (f, "%04x: %lu %s\n", opcodenums[i], cnt, lookup->name); } fclose (f); } #else void dump_counts (void) { } #endif /* ok, all this to "record" current instruction state for later 100% cycle-exact restoring */ static uae_u32 (*x2_prefetch)(int); static uae_u32 (*x2_prefetch_long)(int); static uae_u32 (*x2_next_iword)(void); static uae_u32 (*x2_next_ilong)(void); static uae_u32 (*x2_get_ilong)(int); static uae_u32 (*x2_get_iword)(int); static uae_u32 (*x2_get_ibyte)(int); static uae_u32 (*x2_get_long)(uaecptr); static uae_u32 (*x2_get_word)(uaecptr); static uae_u32 (*x2_get_byte)(uaecptr); static void (*x2_put_long)(uaecptr,uae_u32); static void (*x2_put_word)(uaecptr,uae_u32); static void (*x2_put_byte)(uaecptr,uae_u32); static void (*x2_do_cycles)(int); static void (*x2_do_cycles_pre)(int); static void (*x2_do_cycles_post)(int, uae_u32); uae_u32 (*x_prefetch)(int); uae_u32 (*x_next_iword)(void); uae_u32 (*x_next_ilong)(void); uae_u32 (*x_get_ilong)(int); uae_u32 (*x_get_iword)(int); uae_u32 (*x_get_ibyte)(int); uae_u32 (*x_get_long)(uaecptr); uae_u32 (*x_get_word)(uaecptr); uae_u32 (*x_get_byte)(uaecptr); void (*x_put_long)(uaecptr,uae_u32); void (*x_put_word)(uaecptr,uae_u32); void (*x_put_byte)(uaecptr,uae_u32); uae_u32 (*x_cp_next_iword)(void); uae_u32 (*x_cp_next_ilong)(void); uae_u32 (*x_cp_get_long)(uaecptr); uae_u32 (*x_cp_get_word)(uaecptr); uae_u32 (*x_cp_get_byte)(uaecptr); void (*x_cp_put_long)(uaecptr,uae_u32); void (*x_cp_put_word)(uaecptr,uae_u32); void (*x_cp_put_byte)(uaecptr,uae_u32); uae_u32 (REGPARAM3 *x_cp_get_disp_ea_020)(uae_u32 base, int idx) REGPARAM; void (*x_do_cycles)(int); void (*x_do_cycles_pre)(int); void (*x_do_cycles_post)(int, uae_u32); uae_u32(*x_phys_get_iword)(uaecptr); uae_u32(*x_phys_get_ilong)(uaecptr); uae_u32(*x_phys_get_byte)(uaecptr); uae_u32(*x_phys_get_word)(uaecptr); uae_u32(*x_phys_get_long)(uaecptr); void(*x_phys_put_byte)(uaecptr, uae_u32); void(*x_phys_put_word)(uaecptr, uae_u32); void(*x_phys_put_long)(uaecptr, uae_u32); bool(*is_super_access)(bool); static void set_x_cp_funcs(void) { x_cp_put_long = x_put_long; x_cp_put_word = x_put_word; x_cp_put_byte = x_put_byte; x_cp_get_long = x_get_long; x_cp_get_word = x_get_word; x_cp_get_byte = x_get_byte; x_cp_next_iword = x_next_iword; x_cp_next_ilong = x_next_ilong; x_cp_get_disp_ea_020 = x_get_disp_ea_020; if (currprefs.mmu_model == 68030) { if (currprefs.cpu_compatible) { x_cp_put_long = put_long_mmu030c_state; x_cp_put_word = put_word_mmu030c_state; x_cp_put_byte = put_byte_mmu030c_state; x_cp_get_long = get_long_mmu030c_state; x_cp_get_word = get_word_mmu030c_state; x_cp_get_byte = get_byte_mmu030c_state; x_cp_next_iword = next_iword_mmu030c_state; x_cp_next_ilong = next_ilong_mmu030c_state; x_cp_get_disp_ea_020 = get_disp_ea_020_mmu030c; } else { x_cp_put_long = put_long_mmu030_state; x_cp_put_word = put_word_mmu030_state; x_cp_put_byte = put_byte_mmu030_state; x_cp_get_long = get_long_mmu030_state; x_cp_get_word = get_word_mmu030_state; x_cp_get_byte = get_byte_mmu030_state; x_cp_next_iword = next_iword_mmu030_state; x_cp_next_ilong = next_ilong_mmu030_state; x_cp_get_disp_ea_020 = get_disp_ea_020_mmu030; } } } static struct cputracestruct cputrace; #if CPUTRACE_DEBUG static void validate_trace (void) { for (int i = 0; i < cputrace.memoryoffset; i++) { struct cputracememory *ctm = &cputrace.ctm[i]; if (ctm->data == 0xdeadf00d) { write_log (_T("unfinished write operation %d %08x\n"), i, ctm->addr); } } } #endif static void debug_trace (void) { if (cputrace.writecounter > 10000 || cputrace.readcounter > 10000) write_log (_T("cputrace.readcounter=%d cputrace.writecounter=%d\n"), cputrace.readcounter, cputrace.writecounter); } STATIC_INLINE void clear_trace (void) { #if CPUTRACE_DEBUG validate_trace (); #endif if (cputrace.memoryoffset == MAX_CPUTRACESIZE) return; struct cputracememory *ctm = &cputrace.ctm[cputrace.memoryoffset++]; if (cputrace.memoryoffset == MAX_CPUTRACESIZE) { write_log(_T("CPUTRACE overflow, stopping tracing.\n")); return; } ctm->mode = 0; cputrace.cyclecounter = 0; cputrace.cyclecounter_pre = cputrace.cyclecounter_post = 0; } static void set_trace (uaecptr addr, int accessmode, int size) { #if CPUTRACE_DEBUG validate_trace (); #endif if (cputrace.memoryoffset == MAX_CPUTRACESIZE) return; struct cputracememory *ctm = &cputrace.ctm[cputrace.memoryoffset++]; if (cputrace.memoryoffset == MAX_CPUTRACESIZE) { write_log(_T("CPUTRACE overflow, stopping tracing.\n")); return; } ctm->addr = addr; ctm->data = 0xdeadf00d; ctm->mode = accessmode | (size << 4); ctm->flags = 1; cputrace.cyclecounter_pre = -1; if (accessmode == 1) cputrace.writecounter++; else cputrace.readcounter++; debug_trace (); } static void add_trace (uaecptr addr, uae_u32 val, int accessmode, int size) { if (cputrace.memoryoffset < 1) { #if CPUTRACE_DEBUG write_log (_T("add_trace memoryoffset=%d!\n"), cputrace.memoryoffset); #endif return; } int mode = accessmode | (size << 4); struct cputracememory *ctm = &cputrace.ctm[cputrace.memoryoffset - 1]; ctm->addr = addr; ctm->data = val; if (!ctm->mode) { ctm->mode = mode; if (accessmode == 1) cputrace.writecounter++; else cputrace.readcounter++; } ctm->flags = 0; debug_trace (); cputrace.cyclecounter_pre = cputrace.cyclecounter_post = 0; } static void check_trace2 (void) { if (cputrace.readcounter || cputrace.writecounter || cputrace.cyclecounter || cputrace.cyclecounter_pre || cputrace.cyclecounter_post) write_log (_T("CPU tracer invalid state during playback!\n")); } static bool check_trace (void) { if (!cpu_tracer) return true; if (!cputrace.readcounter && !cputrace.writecounter && !cputrace.cyclecounter) { if (cpu_tracer != -2) { write_log (_T("CPU trace: dma_cycle() enabled. %08x %08x NOW=%08llx\n"), cputrace.cyclecounter_pre, cputrace.cyclecounter_post, get_cycles ()); cpu_tracer = -2; // dma_cycle() allowed to work now } } if (cputrace.readcounter || cputrace.writecounter || cputrace.cyclecounter || cputrace.cyclecounter_pre || cputrace.cyclecounter_post) return false; x_prefetch = x2_prefetch; x_get_ilong = x2_get_ilong; x_get_iword = x2_get_iword; x_get_ibyte = x2_get_ibyte; x_next_iword = x2_next_iword; x_next_ilong = x2_next_ilong; x_put_long = x2_put_long; x_put_word = x2_put_word; x_put_byte = x2_put_byte; x_get_long = x2_get_long; x_get_word = x2_get_word; x_get_byte = x2_get_byte; x_do_cycles = x2_do_cycles; x_do_cycles_pre = x2_do_cycles_pre; x_do_cycles_post = x2_do_cycles_post; set_x_cp_funcs(); write_log(_T("CPU tracer playback complete. STARTCYCLES=%016llx NOWCYCLES=%016llx\n"), cputrace.startcycles, get_cycles()); cputrace.needendcycles = 1; cpu_tracer = 0; return true; } static bool get_trace(uaecptr addr, int accessmode, int size, uae_u32 *data) { int mode = accessmode | (size << 4); for (int i = 0; i < cputrace.memoryoffset; i++) { struct cputracememory *ctm = &cputrace.ctm[i]; if (ctm->addr == addr && ctm->mode == mode) { ctm->mode = 0; write_log(_T("CPU trace: GET %d: PC=%08x %08x=%08x %d %d %08x/%08x/%08x %d/%d (%08llx)\n"), i, cputrace.pc, addr, ctm->data, accessmode, size, cputrace.cyclecounter, cputrace.cyclecounter_pre, cputrace.cyclecounter_post, cputrace.readcounter, cputrace.writecounter, get_cycles ()); if (accessmode == 1) cputrace.writecounter--; else cputrace.readcounter--; if (cputrace.writecounter == 0 && cputrace.readcounter == 0) { if (ctm->flags & 4) { if (cputrace.cyclecounter_post) { int c = cputrace.cyclecounter_post; cputrace.cyclecounter_post = 0; x_do_cycles(c); check_trace(); *data = ctm->data; if ((ctm->flags & 1) && !(ctm->flags & 2)) { return true; // argh, need to rerun the memory access.. } } // if cyclecounter_pre > 0, it gets handled during memory access rerun check_trace(); *data = ctm->data; if ((ctm->flags & 1) && !(ctm->flags & 2)) { return true; // argh, need to rerun the memory access.. } } else { if (cputrace.cyclecounter_post) { int c = cputrace.cyclecounter_post; cputrace.cyclecounter_post = 0; x_do_cycles(c); } else if (cputrace.cyclecounter_pre) { check_trace(); *data = ctm->data; return true; // argh, need to rerun the memory access.. } } } check_trace(); *data = ctm->data; return false; } } if (cputrace.cyclecounter_post) { int c = cputrace.cyclecounter_post; cputrace.cyclecounter_post = 0; check_trace(); check_trace2(); x_do_cycles(c); return false; } if ((cputrace.writecounter > 0 || cputrace.readcounter > 0) && cputrace.cyclecounter_pre) { gui_message(_T("CPU trace: GET %08x %d %d (%d %d %d) NOT FOUND!\n"), addr, accessmode, size, cputrace.readcounter, cputrace.writecounter, cputrace.cyclecounter_pre); } check_trace(); *data = 0; return true; } static uae_u32 cputracefunc_x_prefetch (int o) { uae_u32 pc = m68k_getpc (); set_trace (pc + o, 2, 2); uae_u32 v = x2_prefetch (o); add_trace (pc + o, v, 2, 2); return v; } static uae_u32 cputracefunc2_x_prefetch (int o) { uae_u32 v; if (get_trace (m68k_getpc () + o, 2, 2, &v)) { v = x2_prefetch (o); check_trace2 (); } return v; } static uae_u32 cputracefunc_x_next_iword (void) { uae_u32 pc = m68k_getpc (); set_trace (pc, 2, 2); uae_u32 v = x2_next_iword (); add_trace (pc, v, 2, 2); return v; } static uae_u32 cputracefunc_x_next_ilong (void) { uae_u32 pc = m68k_getpc (); set_trace (pc, 2, 4); uae_u32 v = x2_next_ilong (); add_trace (pc, v, 2, 4); return v; } static uae_u32 cputracefunc2_x_next_iword (void) { uae_u32 v; if (get_trace (m68k_getpc (), 2, 2, &v)) { v = x2_next_iword (); check_trace2 (); } return v; } static uae_u32 cputracefunc2_x_next_ilong (void) { uae_u32 v; if (get_trace (m68k_getpc (), 2, 4, &v)) { v = x2_next_ilong (); check_trace2 (); } return v; } static uae_u32 cputracefunc_x_get_ilong (int o) { uae_u32 pc = m68k_getpc (); set_trace (pc + o, 2, 4); uae_u32 v = x2_get_ilong (o); add_trace (pc + o, v, 2, 4); return v; } static uae_u32 cputracefunc_x_get_iword (int o) { uae_u32 pc = m68k_getpc (); set_trace (pc + o, 2, 2); uae_u32 v = x2_get_iword (o); add_trace (pc + o, v, 2, 2); return v; } static uae_u32 cputracefunc_x_get_ibyte (int o) { uae_u32 pc = m68k_getpc (); set_trace (pc + o, 2, 1); uae_u32 v = x2_get_ibyte (o); add_trace (pc + o, v, 2, 1); return v; } static uae_u32 cputracefunc2_x_get_ilong (int o) { uae_u32 v; if (get_trace (m68k_getpc () + o, 2, 4, &v)) { v = x2_get_ilong (o); check_trace2 (); } return v; } static uae_u32 cputracefunc2_x_get_iword (int o) { uae_u32 v; if (get_trace (m68k_getpc () + o, 2, 2, &v)) { v = x2_get_iword (o); check_trace2 (); } return v; } static uae_u32 cputracefunc2_x_get_ibyte (int o) { uae_u32 v; if (get_trace (m68k_getpc () + o, 2, 1, &v)) { v = x2_get_ibyte (o); check_trace2 (); } return v; } static uae_u32 cputracefunc_x_get_long (uaecptr o) { set_trace (o, 0, 4); uae_u32 v = x2_get_long (o); add_trace (o, v, 0, 4); return v; } static uae_u32 cputracefunc_x_get_word (uaecptr o) { set_trace (o, 0, 2); uae_u32 v = x2_get_word (o); add_trace (o, v, 0, 2); return v; } static uae_u32 cputracefunc_x_get_byte (uaecptr o) { set_trace (o, 0, 1); uae_u32 v = x2_get_byte (o); add_trace (o, v, 0, 1); return v; } static uae_u32 cputracefunc2_x_get_long (uaecptr o) { uae_u32 v; if (get_trace (o, 0, 4, &v)) { v = x2_get_long (o); check_trace2 (); } return v; } static uae_u32 cputracefunc2_x_get_word (uaecptr o) { uae_u32 v; if (get_trace (o, 0, 2, &v)) { v = x2_get_word (o); check_trace2 (); } return v; } static uae_u32 cputracefunc2_x_get_byte (uaecptr o) { uae_u32 v; if (get_trace (o, 0, 1, &v)) { v = x2_get_byte (o); check_trace2 (); } return v; } static void cputracefunc_x_put_long (uaecptr o, uae_u32 val) { clear_trace (); add_trace (o, val, 1, 4); x2_put_long (o, val); } static void cputracefunc_x_put_word (uaecptr o, uae_u32 val) { clear_trace (); add_trace (o, val, 1, 2); x2_put_word (o, val); } static void cputracefunc_x_put_byte (uaecptr o, uae_u32 val) { clear_trace (); add_trace (o, val, 1, 1); x2_put_byte (o, val); } static void cputracefunc2_x_put_long (uaecptr o, uae_u32 val) { uae_u32 v; if (get_trace (o, 1, 4, &v)) { x2_put_long (o, val); check_trace2 (); } if (v != val) write_log (_T("cputracefunc2_x_put_long %d <> %d\n"), v, val); } static void cputracefunc2_x_put_word (uaecptr o, uae_u32 val) { uae_u32 v; if (get_trace (o, 1, 2, &v)) { x2_put_word (o, val); check_trace2 (); } if (v != val) write_log (_T("cputracefunc2_x_put_word %d <> %d\n"), v, val); } static void cputracefunc2_x_put_byte (uaecptr o, uae_u32 val) { uae_u32 v; if (get_trace (o, 1, 1, &v)) { x2_put_byte (o, val); check_trace2 (); } if (v != val) write_log (_T("cputracefunc2_x_put_byte %d <> %d\n"), v, val); } static void cputracefunc_x_do_cycles(int cycles) { while (cycles >= CYCLE_UNIT) { cputrace.cyclecounter += CYCLE_UNIT; cycles -= CYCLE_UNIT; x2_do_cycles(CYCLE_UNIT); } if (cycles > 0) { cputrace.cyclecounter += cycles; x2_do_cycles(cycles); } } static void cputracefunc2_x_do_cycles(int cycles) { if (cputrace.cyclecounter > (long)cycles) { cputrace.cyclecounter -= cycles; return; } cycles -= cputrace.cyclecounter; cputrace.cyclecounter = 0; check_trace(); x_do_cycles = x2_do_cycles; if (cycles > 0) x_do_cycles(cycles); } static void cputracefunc_x_do_cycles_pre(int cycles) { cputrace.cyclecounter_post = 0; cputrace.cyclecounter_pre = 0; while (cycles >= CYCLE_UNIT) { cycles -= CYCLE_UNIT; cputrace.cyclecounter_pre += CYCLE_UNIT; x2_do_cycles_pre(CYCLE_UNIT); } if (cycles > 0) { x2_do_cycles_pre(cycles); cputrace.cyclecounter_pre += cycles; } cputrace.cyclecounter_pre = 0; } // cyclecounter_pre = how many cycles we need to SWALLOW // -1 = rerun whole access static void cputracefunc2_x_do_cycles_pre (int cycles) { if (cputrace.cyclecounter_pre == -1) { cputrace.cyclecounter_pre = 0; check_trace (); check_trace2 (); x_do_cycles (cycles); return; } if (cputrace.cyclecounter_pre > (long)cycles) { cputrace.cyclecounter_pre -= cycles; return; } cycles -= cputrace.cyclecounter_pre; cputrace.cyclecounter_pre = 0; check_trace (); if (cycles > 0) x_do_cycles (cycles); } static void cputracefunc_x_do_cycles_post(int cycles, uae_u32 v) { if (cputrace.memoryoffset < 1) { #if CPUTRACE_DEBUG write_log (_T("cputracefunc_x_do_cycles_post memoryoffset=%d!\n"), cputrace.memoryoffset); #endif x2_do_cycles_post(cycles, v); return; } struct cputracememory *ctm = &cputrace.ctm[cputrace.memoryoffset - 1]; ctm->data = v; ctm->flags = 0; cputrace.cyclecounter_post = cycles; cputrace.cyclecounter_pre = 0; while (cycles >= CYCLE_UNIT) { cycles -= CYCLE_UNIT; cputrace.cyclecounter_post -= CYCLE_UNIT; x2_do_cycles_post(CYCLE_UNIT, v); } if (cycles > 0) { cputrace.cyclecounter_post -= cycles; x2_do_cycles_post(CYCLE_UNIT, v); } cputrace.cyclecounter_post = 0; } // cyclecounter_post = how many cycles we need to WAIT static void cputracefunc2_x_do_cycles_post (int cycles, uae_u32 v) { int c; if (cputrace.cyclecounter_post) { c = cputrace.cyclecounter_post; cputrace.cyclecounter_post = 0; } else { c = cycles; } check_trace (); if (c > 0) x_do_cycles (c); } static void do_cycles_post (int cycles, uae_u32 v) { do_cycles(cycles); } static void do_cycles_ce_post (int cycles, uae_u32 v) { do_cycles_ce (cycles); } static void do_cycles_ce020_post (int cycles, uae_u32 v) { do_cycles_ce020 (cycles); } static uae_u8 dcache_check_nommu(uaecptr addr, bool write, uae_u32 size) { return ce_cachable[addr >> 16]; } static void mem_access_delay_long_write_ce030_cicheck(uaecptr addr, uae_u32 v) { mem_access_delay_long_write_ce020(addr, v); mmu030_cache_state = ce_cachable[addr >> 16]; } static void mem_access_delay_word_write_ce030_cicheck(uaecptr addr, uae_u32 v) { mem_access_delay_word_write_ce020(addr, v); mmu030_cache_state = ce_cachable[addr >> 16]; } static void mem_access_delay_byte_write_ce030_cicheck(uaecptr addr, uae_u32 v) { mem_access_delay_byte_write_ce020(addr, v); mmu030_cache_state = ce_cachable[addr >> 16]; } static void put_long030_cicheck(uaecptr addr, uae_u32 v) { put_long(addr, v); mmu030_cache_state = ce_cachable[addr >> 16]; } static void put_word030_cicheck(uaecptr addr, uae_u32 v) { put_word(addr, v); mmu030_cache_state = ce_cachable[addr >> 16]; } static void put_byte030_cicheck(uaecptr addr, uae_u32 v) { put_byte(addr, v); mmu030_cache_state = ce_cachable[addr >> 16]; } static uae_u32 (*icache_fetch)(uaecptr); static uae_u16 (*icache_fetch_word)(uaecptr); static uae_u32 (*dcache_lget)(uaecptr); static uae_u32 (*dcache_wget)(uaecptr); static uae_u32 (*dcache_bget)(uaecptr); static uae_u8 (*dcache_check)(uaecptr, bool, uae_u32); static void (*dcache_lput)(uaecptr, uae_u32); static void (*dcache_wput)(uaecptr, uae_u32); static void (*dcache_bput)(uaecptr, uae_u32); uae_u32(*read_data_030_bget)(uaecptr); uae_u32(*read_data_030_wget)(uaecptr); uae_u32(*read_data_030_lget)(uaecptr); void(*write_data_030_bput)(uaecptr,uae_u32); void(*write_data_030_wput)(uaecptr,uae_u32); void(*write_data_030_lput)(uaecptr,uae_u32); uae_u32(*read_data_030_fc_bget)(uaecptr, uae_u32); uae_u32(*read_data_030_fc_wget)(uaecptr, uae_u32); uae_u32(*read_data_030_fc_lget)(uaecptr, uae_u32); void(*write_data_030_fc_bput)(uaecptr, uae_u32, uae_u32); void(*write_data_030_fc_wput)(uaecptr, uae_u32, uae_u32); void(*write_data_030_fc_lput)(uaecptr, uae_u32, uae_u32); static void set_x_ifetches(void) { if (m68k_pc_indirect) { if (currprefs.cachesize) { // indirect via addrbank x_get_ilong = get_iilong_jit; x_get_iword = get_iiword_jit; x_get_ibyte = get_iibyte_jit; x_next_iword = next_iiword_jit; x_next_ilong = next_iilong_jit; } else { // indirect via addrbank x_get_ilong = get_iilong; x_get_iword = get_iiword; x_get_ibyte = get_iibyte; x_next_iword = next_iiword; x_next_ilong = next_iilong; } } else { // direct to memory x_get_ilong = get_dilong; x_get_iword = get_diword; x_get_ibyte = get_dibyte; x_next_iword = next_diword; x_next_ilong = next_dilong; } } #ifdef WINUAE_FOR_HATARI static void trace_cpu_disasm(void) { if (LOG_TRACE_LEVEL(TRACE_CPU_VIDEO_CYCLES)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles, &HblCounterVideo, &LineCycles ); LOG_TRACE_DIRECT_INIT (); LOG_TRACE_DIRECT ( "cpu video_cyc=%6d %3d@%3d %"PRIu64" : ", FrameCycles, LineCycles, HblCounterVideo, CyclesGlobalClockCounter ); } m68k_disasm_file(TraceFile, m68k_getpc (), NULL, m68k_getpc (), 1); } static void trace_cpu_disasm_mmu030(void) { uaecptr new_addr = mmu030_translate(m68k_getpc(), regs.s != 0, false, false); if (LOG_TRACE_LEVEL(TRACE_CPU_VIDEO_CYCLES)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles, &HblCounterVideo, &LineCycles ); LOG_TRACE_DIRECT_INIT (); LOG_TRACE_DIRECT ( "cpu video_cyc=%6d %3d@%3d %"PRIu64" : ", FrameCycles, LineCycles, HblCounterVideo, CyclesGlobalClockCounter ); } if ( new_addr != m68k_getpc() ) f_out(TraceFile , "(%08x) " , m68k_getpc() ); m68k_disasm_file(TraceFile, new_addr, NULL, new_addr, 1); } void (*x_do_cycles_hatari_blitter_save)(int); void (*x_do_cycles_pre_hatari_blitter_save)(int); void (*x_do_cycles_post_hatari_blitter_save)(int, uae_u32); static void do_cycles_ce_post_hatari_blitter (int cycles, uae_u32 v) { do_cycles_ce_hatari_blitter (cycles); } void set_x_funcs_hatari_blitter (int flag) { if ( flag == 0 ) { //fprintf ( stderr , "restore blitter x_funcs\n" ); /* disable blitter, restore functions if needed */ if ( x_do_cycles_hatari_blitter_save ) { x_do_cycles = x_do_cycles_hatari_blitter_save; x_do_cycles_pre = x_do_cycles_pre_hatari_blitter_save; x_do_cycles_post = x_do_cycles_post_hatari_blitter_save; } } else { //fprintf ( stderr , "save/set blitter x_funcs\n" ); /* save current functions */ x_do_cycles_hatari_blitter_save = x_do_cycles; x_do_cycles_pre_hatari_blitter_save = x_do_cycles_pre; x_do_cycles_post_hatari_blitter_save = x_do_cycles_post; /* set blitter specific functions */ x_do_cycles = do_cycles_ce_hatari_blitter; x_do_cycles_pre = do_cycles_ce_hatari_blitter; x_do_cycles_post = do_cycles_ce_post_hatari_blitter; } } #endif static bool is_super_access_68000(bool read) { return regs.s; } static bool nommu_is_super_access(bool read) { if (!ismoves_nommu) { return regs.s; } else { uae_u32 fc = read ? regs.sfc : regs.dfc; return (fc & 4) != 0; } } // indirect memory access functions static void set_x_funcs (void) { if (currprefs.cpu_model >= 68010) { if (currprefs.mmu_model == 68030) { is_super_access = mmu030_is_super_access; } else if (currprefs.mmu_model >= 68040) { is_super_access = mmu_is_super_access; } else { is_super_access = nommu_is_super_access; } } else { is_super_access = is_super_access_68000; } if (currprefs.mmu_model) { if (currprefs.cpu_model == 68060) { x_prefetch = get_iword_mmu060; x_get_ilong = get_ilong_mmu060; x_get_iword = get_iword_mmu060; x_get_ibyte = get_ibyte_mmu060; x_next_iword = next_iword_mmu060; x_next_ilong = next_ilong_mmu060; x_put_long = put_long_mmu060; x_put_word = put_word_mmu060; x_put_byte = put_byte_mmu060; x_get_long = get_long_mmu060; x_get_word = get_word_mmu060; x_get_byte = get_byte_mmu060; } else if (currprefs.cpu_model == 68040) { x_prefetch = get_iword_mmu040; x_get_ilong = get_ilong_mmu040; x_get_iword = get_iword_mmu040; x_get_ibyte = get_ibyte_mmu040; x_next_iword = next_iword_mmu040; x_next_ilong = next_ilong_mmu040; x_put_long = put_long_mmu040; x_put_word = put_word_mmu040; x_put_byte = put_byte_mmu040; x_get_long = get_long_mmu040; x_get_word = get_word_mmu040; x_get_byte = get_byte_mmu040; } else { if (currprefs.cpu_memory_cycle_exact) { x_prefetch = get_iword_mmu030c_state; x_get_ilong = get_ilong_mmu030c_state; x_get_iword = get_iword_mmu030c_state; x_get_ibyte = NULL; x_next_iword = next_iword_mmu030c_state; x_next_ilong = next_ilong_mmu030c_state; } else if (currprefs.cpu_compatible) { x_prefetch = get_iword_mmu030c_state; x_get_ilong = get_ilong_mmu030c_state; x_get_iword = get_iword_mmu030c_state; x_get_ibyte = NULL; x_next_iword = next_iword_mmu030c_state; x_next_ilong = next_ilong_mmu030c_state; } else { x_prefetch = get_iword_mmu030; x_get_ilong = get_ilong_mmu030; x_get_iword = get_iword_mmu030; x_get_ibyte = get_ibyte_mmu030; x_next_iword = next_iword_mmu030; x_next_ilong = next_ilong_mmu030; } x_put_long = put_long_mmu030; x_put_word = put_word_mmu030; x_put_byte = put_byte_mmu030; x_get_long = get_long_mmu030; x_get_word = get_word_mmu030; x_get_byte = get_byte_mmu030; if (currprefs.cpu_data_cache) { x_put_long = put_long_dc030; x_put_word = put_word_dc030; x_put_byte = put_byte_dc030; x_get_long = get_long_dc030; x_get_word = get_word_dc030; x_get_byte = get_byte_dc030; } } if (currprefs.cpu_cycle_exact) { x_do_cycles = do_cycles_ce020; x_do_cycles_pre = do_cycles_ce020; x_do_cycles_post = do_cycles_ce020_post; } else { x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } } else if (currprefs.cpu_model < 68020) { // 68000/010 if (currprefs.cpu_cycle_exact) { x_prefetch = get_word_ce000_prefetch; x_get_ilong = NULL; x_get_iword = get_wordi_ce000; x_get_ibyte = NULL; x_next_iword = NULL; x_next_ilong = NULL; x_put_long = put_long_ce000; x_put_word = put_word_ce000; x_put_byte = put_byte_ce000; x_get_long = get_long_ce000; x_get_word = get_word_ce000; x_get_byte = get_byte_ce000; x_do_cycles = do_cycles_ce; x_do_cycles_pre = do_cycles_ce; x_do_cycles_post = do_cycles_ce_post; } else if (currprefs.cpu_memory_cycle_exact) { // cpu_memory_cycle_exact + cpu_compatible x_prefetch = get_word_000_prefetch; x_get_ilong = NULL; x_get_iword = get_iiword; x_get_ibyte = get_iibyte; x_next_iword = NULL; x_next_ilong = NULL; x_put_long = put_long_ce000; x_put_word = put_word_ce000; x_put_byte = put_byte_ce000; x_get_long = get_long_ce000; x_get_word = get_word_ce000; x_get_byte = get_byte_ce000; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else if (currprefs.cpu_compatible) { // cpu_compatible only x_prefetch = get_word_000_prefetch; x_get_ilong = NULL; x_get_iword = get_iiword; x_get_ibyte = get_iibyte; x_next_iword = NULL; x_next_ilong = NULL; x_put_long = put_long_compatible; x_put_word = put_word_compatible; x_put_byte = put_byte_compatible; x_get_long = get_long_compatible; x_get_word = get_word_compatible; x_get_byte = get_byte_compatible; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else { x_prefetch = NULL; x_get_ilong = get_dilong; x_get_iword = get_diword; x_get_ibyte = get_dibyte; x_next_iword = next_diword; x_next_ilong = next_dilong; x_put_long = put_long; x_put_word = put_word; x_put_byte = put_byte; x_get_long = get_long; x_get_word = get_word; x_get_byte = get_byte; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } } else if (!currprefs.cpu_cycle_exact) { // 68020+ no ce if (currprefs.cpu_memory_cycle_exact) { // cpu_memory_cycle_exact + cpu_compatible if (currprefs.cpu_model == 68020 && !currprefs.cachesize) { x_prefetch = get_word_020_prefetch; x_get_ilong = get_long_020_prefetch; x_get_iword = get_word_020_prefetch; x_get_ibyte = NULL; x_next_iword = next_iword_020_prefetch; x_next_ilong = next_ilong_020_prefetch; x_put_long = put_long_ce020; x_put_word = put_word_ce020; x_put_byte = put_byte_ce020; x_get_long = get_long_ce020; x_get_word = get_word_ce020; x_get_byte = get_byte_ce020; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else if (currprefs.cpu_model == 68030 && !currprefs.cachesize) { x_prefetch = get_word_030_prefetch; x_get_ilong = get_long_030_prefetch; x_get_iword = get_word_030_prefetch; x_get_ibyte = NULL; x_next_iword = next_iword_030_prefetch; x_next_ilong = next_ilong_030_prefetch; x_put_long = put_long_ce030; x_put_word = put_word_ce030; x_put_byte = put_byte_ce030; x_get_long = get_long_ce030; x_get_word = get_word_ce030; x_get_byte = get_byte_ce030; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else if (currprefs.cpu_model < 68040) { // JIT or 68030+ does not have real prefetch only emulation x_prefetch = NULL; set_x_ifetches(); x_put_long = put_long; x_put_word = put_word; x_put_byte = put_byte; x_get_long = get_long; x_get_word = get_word; x_get_byte = get_byte; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else { // 68040+ (same as below) x_prefetch = NULL; x_get_ilong = get_ilong_cache_040; x_get_iword = get_iword_cache_040; x_get_ibyte = NULL; x_next_iword = next_iword_cache040; x_next_ilong = next_ilong_cache040; if (currprefs.cpu_data_cache) { x_put_long = put_long_cache_040; x_put_word = put_word_cache_040; x_put_byte = put_byte_cache_040; x_get_long = get_long_cache_040; x_get_word = get_word_cache_040; x_get_byte = get_byte_cache_040; } else { x_get_byte = mem_access_delay_byte_read_c040; x_get_word = mem_access_delay_word_read_c040; x_get_long = mem_access_delay_long_read_c040; x_put_byte = mem_access_delay_byte_write_c040; x_put_word = mem_access_delay_word_write_c040; x_put_long = mem_access_delay_long_write_c040; } x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } } else if (currprefs.cpu_compatible) { // cpu_compatible only if (currprefs.cpu_model == 68020 && !currprefs.cachesize) { x_prefetch = get_word_020_prefetch; x_get_ilong = get_long_020_prefetch; x_get_iword = get_word_020_prefetch; x_get_ibyte = NULL; x_next_iword = next_iword_020_prefetch; x_next_ilong = next_ilong_020_prefetch; x_put_long = put_long_compatible; x_put_word = put_word_compatible; x_put_byte = put_byte_compatible; x_get_long = get_long_compatible; x_get_word = get_word_compatible; x_get_byte = get_byte_compatible; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else if (currprefs.cpu_model == 68030 && !currprefs.cachesize) { x_prefetch = get_word_030_prefetch; x_get_ilong = get_long_030_prefetch; x_get_iword = get_word_030_prefetch; x_get_ibyte = NULL; x_next_iword = next_iword_030_prefetch; x_next_ilong = next_ilong_030_prefetch; x_put_long = put_long_030; x_put_word = put_word_030; x_put_byte = put_byte_030; x_get_long = get_long_030; x_get_word = get_word_030; x_get_byte = get_byte_030; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else if (currprefs.cpu_model < 68040) { // JIT or 68030+ does not have real prefetch only emulation x_prefetch = NULL; set_x_ifetches(); x_put_long = put_long; x_put_word = put_word; x_put_byte = put_byte; x_get_long = get_long; x_get_word = get_word; x_get_byte = get_byte; x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } else { x_prefetch = NULL; x_get_ilong = get_ilong_cache_040; x_get_iword = get_iword_cache_040; x_get_ibyte = NULL; x_next_iword = next_iword_cache040; x_next_ilong = next_ilong_cache040; if (currprefs.cpu_data_cache) { x_put_long = put_long_cache_040; x_put_word = put_word_cache_040; x_put_byte = put_byte_cache_040; x_get_long = get_long_cache_040; x_get_word = get_word_cache_040; x_get_byte = get_byte_cache_040; } else { x_put_long = put_long; x_put_word = put_word; x_put_byte = put_byte; x_get_long = get_long; x_get_word = get_word; x_get_byte = get_byte; } x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } } else { x_prefetch = NULL; set_x_ifetches(); if (currprefs.cachesize) { x_put_long = put_long_jit; x_put_word = put_word_jit; x_put_byte = put_byte_jit; x_get_long = get_long_jit; x_get_word = get_word_jit; x_get_byte = get_byte_jit; } else { x_put_long = put_long; x_put_word = put_word; x_put_byte = put_byte; x_get_long = get_long; x_get_word = get_word; x_get_byte = get_byte; } x_do_cycles = do_cycles; x_do_cycles_pre = do_cycles; x_do_cycles_post = do_cycles_post; } // 68020+ cycle exact } else if (currprefs.cpu_model == 68020) { x_prefetch = get_word_ce020_prefetch; x_get_ilong = get_long_ce020_prefetch; x_get_iword = get_word_ce020_prefetch; x_get_ibyte = NULL; x_next_iword = next_iword_020ce; x_next_ilong = next_ilong_020ce; x_put_long = put_long_ce020; x_put_word = put_word_ce020; x_put_byte = put_byte_ce020; x_get_long = get_long_ce020; x_get_word = get_word_ce020; x_get_byte = get_byte_ce020; x_do_cycles = do_cycles_ce020; x_do_cycles_pre = do_cycles_ce020; x_do_cycles_post = do_cycles_ce020_post; } else if (currprefs.cpu_model == 68030) { x_prefetch = get_word_ce030_prefetch; x_get_ilong = get_long_ce030_prefetch; x_get_iword = get_word_ce030_prefetch; x_get_ibyte = NULL; x_next_iword = next_iword_030ce; x_next_ilong = next_ilong_030ce; if (currprefs.cpu_data_cache) { x_put_long = put_long_dc030; x_put_word = put_word_dc030; x_put_byte = put_byte_dc030; x_get_long = get_long_dc030; x_get_word = get_word_dc030; x_get_byte = get_byte_dc030; } else { x_put_long = put_long_ce030; x_put_word = put_word_ce030; x_put_byte = put_byte_ce030; x_get_long = get_long_ce030; x_get_word = get_word_ce030; x_get_byte = get_byte_ce030; } x_do_cycles = do_cycles_ce020; x_do_cycles_pre = do_cycles_ce020; x_do_cycles_post = do_cycles_ce020_post; } else if (currprefs.cpu_model >= 68040) { x_prefetch = NULL; x_get_ilong = get_ilong_cache_040; x_get_iword = get_iword_cache_040; x_get_ibyte = NULL; x_next_iword = next_iword_cache040; x_next_ilong = next_ilong_cache040; if (currprefs.cpu_data_cache) { x_put_long = put_long_cache_040; x_put_word = put_word_cache_040; x_put_byte = put_byte_cache_040; x_get_long = get_long_cache_040; x_get_word = get_word_cache_040; x_get_byte = get_byte_cache_040; } else { x_get_byte = mem_access_delay_byte_read_c040; x_get_word = mem_access_delay_word_read_c040; x_get_long = mem_access_delay_long_read_c040; x_put_byte = mem_access_delay_byte_write_c040; x_put_word = mem_access_delay_word_write_c040; x_put_long = mem_access_delay_long_write_c040; } x_do_cycles = do_cycles_ce020; x_do_cycles_pre = do_cycles_ce020; x_do_cycles_post = do_cycles_ce020_post; } x2_prefetch = x_prefetch; x2_get_ilong = x_get_ilong; x2_get_iword = x_get_iword; x2_get_ibyte = x_get_ibyte; x2_next_iword = x_next_iword; x2_next_ilong = x_next_ilong; x2_put_long = x_put_long; x2_put_word = x_put_word; x2_put_byte = x_put_byte; x2_get_long = x_get_long; x2_get_word = x_get_word; x2_get_byte = x_get_byte; x2_do_cycles = x_do_cycles; x2_do_cycles_pre = x_do_cycles_pre; x2_do_cycles_post = x_do_cycles_post; if (cpu_tracer > 0) { x_prefetch = cputracefunc_x_prefetch; x_get_ilong = cputracefunc_x_get_ilong; x_get_iword = cputracefunc_x_get_iword; x_get_ibyte = cputracefunc_x_get_ibyte; x_next_iword = cputracefunc_x_next_iword; x_next_ilong = cputracefunc_x_next_ilong; x_put_long = cputracefunc_x_put_long; x_put_word = cputracefunc_x_put_word; x_put_byte = cputracefunc_x_put_byte; x_get_long = cputracefunc_x_get_long; x_get_word = cputracefunc_x_get_word; x_get_byte = cputracefunc_x_get_byte; x_do_cycles = cputracefunc_x_do_cycles; x_do_cycles_pre = cputracefunc_x_do_cycles_pre; x_do_cycles_post = cputracefunc_x_do_cycles_post; } else if (cpu_tracer < 0) { if (!check_trace ()) { x_prefetch = cputracefunc2_x_prefetch; x_get_ilong = cputracefunc2_x_get_ilong; x_get_iword = cputracefunc2_x_get_iword; x_get_ibyte = cputracefunc2_x_get_ibyte; x_next_iword = cputracefunc2_x_next_iword; x_next_ilong = cputracefunc2_x_next_ilong; x_put_long = cputracefunc2_x_put_long; x_put_word = cputracefunc2_x_put_word; x_put_byte = cputracefunc2_x_put_byte; x_get_long = cputracefunc2_x_get_long; x_get_word = cputracefunc2_x_get_word; x_get_byte = cputracefunc2_x_get_byte; x_do_cycles = cputracefunc2_x_do_cycles; x_do_cycles_pre = cputracefunc2_x_do_cycles_pre; x_do_cycles_post = cputracefunc2_x_do_cycles_post; } } set_x_cp_funcs(); mmu_set_funcs(); mmu030_set_funcs(); dcache_lput = put_long; dcache_wput = put_word; dcache_bput = put_byte; dcache_lget = get_long; dcache_wget = get_word; dcache_bget = get_byte; dcache_check = dcache_check_nommu; icache_fetch = get_longi; icache_fetch_word = NULL; if (currprefs.cpu_cycle_exact) { icache_fetch = mem_access_delay_longi_read_ce020; } if (currprefs.cpu_model >= 68040 && currprefs.cpu_memory_cycle_exact) { icache_fetch = mem_access_delay_longi_read_c040; dcache_bget = mem_access_delay_byte_read_c040; dcache_wget = mem_access_delay_word_read_c040; dcache_lget = mem_access_delay_long_read_c040; dcache_bput = mem_access_delay_byte_write_c040; dcache_wput = mem_access_delay_word_write_c040; dcache_lput = mem_access_delay_long_write_c040; } if (currprefs.cpu_model == 68030) { if (currprefs.cpu_data_cache) { read_data_030_bget = read_dcache030_mmu_bget; read_data_030_wget = read_dcache030_mmu_wget; read_data_030_lget = read_dcache030_mmu_lget; write_data_030_bput = write_dcache030_mmu_bput; write_data_030_wput = write_dcache030_mmu_wput; write_data_030_lput = write_dcache030_mmu_lput; read_data_030_fc_bget = read_dcache030_bget; read_data_030_fc_wget = read_dcache030_wget; read_data_030_fc_lget = read_dcache030_lget; write_data_030_fc_bput = write_dcache030_bput; write_data_030_fc_wput = write_dcache030_wput; write_data_030_fc_lput = write_dcache030_lput; } else { read_data_030_bget = dcache_bget; read_data_030_wget = dcache_wget; read_data_030_lget = dcache_lget; write_data_030_bput = dcache_bput; write_data_030_wput = dcache_wput; write_data_030_lput = dcache_lput; read_data_030_fc_bget = mmu030_get_fc_byte; read_data_030_fc_wget = mmu030_get_fc_word; read_data_030_fc_lget = mmu030_get_fc_long; write_data_030_fc_bput = mmu030_put_fc_byte; write_data_030_fc_wput = mmu030_put_fc_word; write_data_030_fc_lput = mmu030_put_fc_long; } if (currprefs.mmu_model) { if (currprefs.cpu_compatible) { icache_fetch = uae_mmu030_get_ilong_fc; icache_fetch_word = uae_mmu030_get_iword_fc; } else { icache_fetch = uae_mmu030_get_ilong; icache_fetch_word = uae_mmu030_get_iword_fc; } dcache_lput = uae_mmu030_put_long_fc; dcache_wput = uae_mmu030_put_word_fc; dcache_bput = uae_mmu030_put_byte_fc; dcache_lget = uae_mmu030_get_long_fc; dcache_wget = uae_mmu030_get_word_fc; dcache_bget = uae_mmu030_get_byte_fc; if (currprefs.cpu_data_cache) { read_data_030_bget = read_dcache030_mmu_bget; read_data_030_wget = read_dcache030_mmu_wget; read_data_030_lget = read_dcache030_mmu_lget; write_data_030_bput = write_dcache030_mmu_bput; write_data_030_wput = write_dcache030_mmu_wput; write_data_030_lput = write_dcache030_mmu_lput; dcache_check = uae_mmu030_check_fc; } else { read_data_030_bget = uae_mmu030_get_byte; read_data_030_wget = uae_mmu030_get_word; read_data_030_lget = uae_mmu030_get_long; write_data_030_bput = uae_mmu030_put_byte; write_data_030_wput = uae_mmu030_put_word; write_data_030_lput = uae_mmu030_put_long; } } else if (currprefs.cpu_memory_cycle_exact) { icache_fetch = mem_access_delay_longi_read_ce020; dcache_lget = mem_access_delay_long_read_ce020; dcache_wget = mem_access_delay_word_read_ce020; dcache_bget = mem_access_delay_byte_read_ce020; if (currprefs.cpu_data_cache) { dcache_lput = mem_access_delay_long_write_ce030_cicheck; dcache_wput = mem_access_delay_word_write_ce030_cicheck; dcache_bput = mem_access_delay_byte_write_ce030_cicheck; } else { dcache_lput = mem_access_delay_long_write_ce020; dcache_wput = mem_access_delay_word_write_ce020; dcache_bput = mem_access_delay_byte_write_ce020; } } else if (currprefs.cpu_data_cache) { dcache_lput = put_long030_cicheck; dcache_wput = put_word030_cicheck; dcache_bput = put_byte030_cicheck; } } } bool can_cpu_tracer (void) { return (currprefs.cpu_model == 68000 || currprefs.cpu_model == 68020) && currprefs.cpu_memory_cycle_exact; } bool is_cpu_tracer (void) { return cpu_tracer > 0; } bool set_cpu_tracer (bool state) { if (cpu_tracer < 0) return false; int old = cpu_tracer; #ifndef WINUAE_FOR_HATARI if (input_record) state = true; #endif cpu_tracer = 0; if (state && can_cpu_tracer ()) { cpu_tracer = 1; set_x_funcs (); if (old != cpu_tracer) write_log (_T("CPU tracer enabled\n")); } if (old > 0 && state == false) { set_x_funcs (); write_log (_T("CPU tracer disabled\n")); } return is_cpu_tracer (); } void invalidate_cpu_data_caches(void) { if (currprefs.cpu_model == 68030) { for (int i = 0; i < CACHELINES030; i++) { dcaches030[i].valid[0] = 0; dcaches030[i].valid[1] = 0; dcaches030[i].valid[2] = 0; dcaches030[i].valid[3] = 0; } } else if (currprefs.cpu_model >= 68040) { dcachelinecnt = 0; for (int i = 0; i < CACHESETS060; i++) { for (int j = 0; j < CACHELINES040; j++) { dcaches040[i].valid[j] = false; } } } } #ifdef DEBUGGER static void flush_cpu_cache_debug(uaecptr addr, int size) { debugmem_flushcache(0, -1); #ifndef WINUAE_FOR_HATARI debug_smc_clear(0, -1); #endif } #endif void flush_cpu_caches(bool force) { bool doflush = currprefs.cpu_compatible || currprefs.cpu_memory_cycle_exact; if (currprefs.cpu_model == 68020) { if ((regs.cacr & 0x08) || force) { // clear instr cache for (int i = 0; i < CACHELINES020; i++) caches020[i].valid = 0; regs.cacr &= ~0x08; #ifdef DEBUGGER flush_cpu_cache_debug(0, -1); #endif } if (regs.cacr & 0x04) { // clear entry in instr cache caches020[(regs.caar >> 2) & (CACHELINES020 - 1)].valid = 0; regs.cacr &= ~0x04; #ifdef DEBUGGER flush_cpu_cache_debug(regs.caar, CACHELINES020); #endif } } else if (currprefs.cpu_model == 68030) { if ((regs.cacr & 0x08) || force) { // clear instr cache if (doflush) { for (int i = 0; i < CACHELINES030; i++) { icaches030[i].valid[0] = 0; icaches030[i].valid[1] = 0; icaches030[i].valid[2] = 0; icaches030[i].valid[3] = 0; } } regs.cacr &= ~0x08; #ifdef DEBUGGER flush_cpu_cache_debug(0, -1); #endif } if (regs.cacr & 0x04) { // clear entry in instr cache icaches030[(regs.caar >> 4) & (CACHELINES030 - 1)].valid[(regs.caar >> 2) & 3] = 0; regs.cacr &= ~0x04; #ifdef DEBUGGER flush_cpu_cache_debug(regs.caar, CACHELINES030); #endif } if ((regs.cacr & 0x800) || force) { // clear data cache if (doflush) { for (int i = 0; i < CACHELINES030; i++) { dcaches030[i].valid[0] = 0; dcaches030[i].valid[1] = 0; dcaches030[i].valid[2] = 0; dcaches030[i].valid[3] = 0; } } regs.cacr &= ~0x800; } if (regs.cacr & 0x400) { // clear entry in data cache dcaches030[(regs.caar >> 4) & (CACHELINES030 - 1)].valid[(regs.caar >> 2) & 3] = 0; regs.cacr &= ~0x400; } } else if (currprefs.cpu_model >= 68040) { if (doflush && force) { mmu_flush_cache(); icachelinecnt = 0; icachehalfline = 0; for (int i = 0; i < CACHESETS060; i++) { for (int j = 0; j < CACHELINES040; j++) { icaches040[i].valid[j] = false; } } #ifdef DEBUGGER flush_cpu_cache_debug(0, -1); #endif } } } #if VALIDATE_68040_DATACACHE > 1 static void validate_dcache040(void) { for (int i = 0; i < cachedsets04060; i++) { struct cache040 *c = &dcaches040[i]; for (int j = 0; j < CACHELINES040; j++) { if (c->valid[j]) { uae_u32 addr = (c->tag[j] & cachedtag04060mask) | (i << 4); if (addr < 0x200000 || (addr >= 0xd80000 && addr < 0xe00000) || (addr >= 0xe80000 && addr < 0xf00000) || (addr >= 0xa00000 && addr < 0xc00000)) { write_log(_T("Chip RAM or IO address cached! %08x\n"), addr); } for (int k = 0; k < 4; k++) { if (!c->dirty[j][k]) { uae_u32 v = get_long(addr + k * 4); if (v != c->data[j][k]) { write_log(_T("Address %08x data cache mismatch %08x != %08x\n"), addr, v, c->data[j][k]); } } } } } } } #endif static void dcache040_push_line(int index, int line, bool writethrough, bool invalidate) { struct cache040 *c = &dcaches040[index]; #if VALIDATE_68040_DATACACHE if (!c->valid[line]) { write_log("dcache040_push_line pushing invalid line!\n"); } #endif if (c->gdirty[line]) { uae_u32 addr = (c->tag[line] & cachedtag04060mask) | (index << 4); for (int i = 0; i < 4; i++) { if (c->dirty[line][i] || (!writethrough && currprefs.cpu_model == 68060)) { dcache_lput(addr + i * 4, c->data[line][i]); c->dirty[line][i] = false; } } c->gdirty[line] = false; } if (invalidate) c->valid[line] = false; #if VALIDATE_68040_DATACACHE > 1 validate_dcache040(); #endif } static void flush_cpu_caches_040_2(int cache, int scope, uaecptr addr, bool push, bool pushinv) { #if VALIDATE_68040_DATACACHE write_log(_T("push %d %d %d %08x %d %d\n"), cache, scope, areg, addr, push, pushinv); #endif if ((cache & 1) && !currprefs.cpu_data_cache) { cache &= ~1; } if (cache & 2) { regs.prefetch020addr = 0xffffffff; } for (int k = 0; k < 2; k++) { if (cache & (1 << k)) { if (scope == 3) { // all if (!k) { // data for (int i = 0; i < cachedsets04060; i++) { struct cache040 *c = &dcaches040[i]; for (int j = 0; j < CACHELINES040; j++) { if (c->valid[j]) { if (push) { dcache040_push_line(i, j, false, pushinv); } else { c->valid[j] = false; } } } } dcachelinecnt = 0; } else { // instruction flush_cpu_caches(true); } } else { uae_u32 pagesize; if (scope == 2) { // page pagesize = mmu_pagesize_8k ? 8192 : 4096; } else { // line pagesize = 16; } addr &= ~(pagesize - 1); for (uae_u32 j = 0; j < pagesize; j += 16, addr += 16) { int index; uae_u32 tag; uae_u32 tagmask; struct cache040 *c; if (k) { tagmask = cacheitag04060mask; index = (addr >> 4) & cacheisets04060mask; c = &icaches040[index]; #ifdef DEBUGGER flush_cpu_cache_debug(addr, 16); #endif } else { tagmask = cachedtag04060mask; index = (addr >> 4) & cachedsets04060mask; c = &dcaches040[index]; } tag = addr & tagmask; for (int i = 0; i < CACHELINES040; i++) { if (c->valid[i] && c->tag[i] == tag) { if (push) { dcache040_push_line(index, i, false, pushinv); } else { c->valid[i] = false; } } } } } } } } void flush_cpu_caches_040(uae_u16 opcode) { // 0 (1) = data, 1 (2) = instruction int cache = (opcode >> 6) & 3; int scope = (opcode >> 3) & 3; int areg = opcode & 7; uaecptr addr = m68k_areg(regs, areg); bool push = (opcode & 0x20) != 0; bool pushinv = (regs.cacr & 0x01000000) == 0; // 68060 DPI flush_cpu_caches_040_2(cache, scope, addr, push, pushinv); mmu_flush_cache(); } void cpu_invalidate_cache(uaecptr addr, int size) { if (!currprefs.cpu_data_cache) return; if (currprefs.cpu_model == 68030) { uaecptr end = addr + size; addr &= ~3; while (addr < end) { dcaches030[(addr >> 4) & (CACHELINES030 - 1)].valid[(addr >> 2) & 3] = 0; addr += 4; } } else if (currprefs.cpu_model >= 68040) { uaecptr end = addr + size; while (addr < end) { flush_cpu_caches_040_2(0, 1, addr, true, true); addr += 16; } } } void set_cpu_caches (bool flush) { regs.prefetch020addr = 0xffffffff; regs.cacheholdingaddr020 = 0xffffffff; cache_default_data &= ~CACHE_DISABLE_ALLOCATE; // 68060 FIC 1/2 instruction cache cacheisets04060 = currprefs.cpu_model == 68060 && !(regs.cacr & 0x00002000) ? CACHESETS060 : CACHESETS040; cacheisets04060mask = cacheisets04060 - 1; cacheitag04060mask = ~((cacheisets04060 << 4) - 1); // 68060 FOC 1/2 data cache cachedsets04060 = currprefs.cpu_model == 68060 && !(regs.cacr & 0x08000000) ? CACHESETS060 : CACHESETS040; cachedsets04060mask = cachedsets04060 - 1; cachedtag04060mask = ~((cachedsets04060 << 4) - 1); cache_lastline = 0; #ifdef JIT if (currprefs.cachesize) { if (currprefs.cpu_model < 68040) { set_cache_state (regs.cacr & 1); if (regs.cacr & 0x08) { flush_icache (3); } } else { set_cache_state ((regs.cacr & 0x8000) ? 1 : 0); } } #endif flush_cpu_caches(flush); } STATIC_INLINE void count_instr (uae_u32 opcode) { } static uae_u32 opcode_swap(uae_u16 opcode) { if (!need_opcode_swap) return opcode; return do_byteswap_16(opcode); } uae_u32 REGPARAM2 op_illg_1(uae_u32 opcode) { opcode = opcode_swap(opcode); op_illg(opcode); return 4; } void REGPARAM2 op_illg_1_noret(uae_u32 opcode) { op_illg_1(opcode); } uae_u32 REGPARAM2 op_unimpl_1 (uae_u32 opcode) { opcode = opcode_swap(opcode); if ((opcode & 0xf000) == 0xf000 || currprefs.cpu_model < 68060) op_illg(opcode); else op_unimpl(opcode); return 4; } void REGPARAM2 op_unimpl_1_noret(uae_u32 opcode) { op_unimpl_1(opcode); } // generic+direct, generic+direct+jit, generic+indirect, more compatible, cycle-exact, mmu, mmu+more compatible, mmu+mc+ce static const struct cputbl *cputbls[6][8] = { // 68000 { op_smalltbl_5, op_smalltbl_45, op_smalltbl_55, op_smalltbl_12, op_smalltbl_14, NULL, NULL, NULL }, // 68010 { op_smalltbl_4, op_smalltbl_44, op_smalltbl_54, op_smalltbl_11, op_smalltbl_13, NULL, NULL, NULL }, // 68020 { op_smalltbl_3, op_smalltbl_43, op_smalltbl_53, op_smalltbl_20, op_smalltbl_21, NULL, NULL, NULL }, // 68030 { op_smalltbl_2, op_smalltbl_42, op_smalltbl_52, op_smalltbl_22, op_smalltbl_23, op_smalltbl_32, op_smalltbl_34, op_smalltbl_35 }, // 68040 { op_smalltbl_1, op_smalltbl_41, op_smalltbl_51, op_smalltbl_25, op_smalltbl_25, op_smalltbl_31, op_smalltbl_31, op_smalltbl_31 }, // 68060 { op_smalltbl_0, op_smalltbl_40, op_smalltbl_50, op_smalltbl_24, op_smalltbl_24, op_smalltbl_33, op_smalltbl_33, op_smalltbl_33 } }; #ifdef JIT const struct cputbl *uaegetjitcputbl(void) { int lvl = (currprefs.cpu_model - 68000) / 10; if (lvl > 4) lvl--; int index = currprefs.comptrustbyte ? 0 : 1; return cputbls[lvl][index]; } const struct cputbl *getjitcputbl(int cpulvl, int direct) { return cputbls[cpulvl][1 + direct]; } #endif static void build_cpufunctbl (void) { int i, opcnt; uae_u32 opcode; const struct cputbl *tbl = NULL; int lvl, mode, jit; jit = 0; if (!currprefs.cachesize) { if (currprefs.mmu_model) { if (currprefs.cpu_cycle_exact) mode = 7; else if (currprefs.cpu_compatible) mode = 6; else mode = 5; } else if (currprefs.cpu_cycle_exact) { mode = 4; } else if (currprefs.cpu_compatible) { mode = 3; } else { mode = 0; } m68k_pc_indirect = mode != 0 ? 1 : 0; } else { mode = 1; m68k_pc_indirect = 0; jit = 1; if (currprefs.comptrustbyte) { mode = 2; m68k_pc_indirect = -1; } } lvl = (currprefs.cpu_model - 68000) / 10; if (lvl == 6) lvl = 5; tbl = cputbls[lvl][mode]; if (tbl == NULL) { write_log (_T("no CPU emulation cores available CPU=%d!"), currprefs.cpu_model); abort (); } for (opcode = 0; opcode < 65536; opcode++) { cpufunctbl[opcode] = op_illg_1; cpufunctbl_noret[opcode] = op_illg_1_noret; } for (i = 0; tbl[i].handler_ff != NULL || tbl[i].handler_ff_noret != NULL; i++) { opcode = tbl[i].opcode; cpufunctbl[opcode] = tbl[i].handler_ff; cpufunctbl_noret[opcode] = tbl[i].handler_ff_noret; cpudatatbl[opcode].length = tbl[i].length; cpudatatbl[opcode].disp020[0] = tbl[i].disp020[0]; cpudatatbl[opcode].disp020[1] = tbl[i].disp020[1]; cpudatatbl[opcode].branch = tbl[i].branch; } /* hack fpu to 68000/68010 mode */ if (currprefs.fpu_model && currprefs.cpu_model < 68020) { tbl = op_smalltbl_3; for (i = 0; tbl[i].handler_ff != NULL || tbl[i].handler_ff_noret != NULL; i++) { if ((tbl[i].opcode & 0xfe00) == 0xf200) { cpufunctbl[tbl[i].opcode] = tbl[i].handler_ff; cpufunctbl_noret[tbl[i].opcode] = tbl[i].handler_ff_noret; cpudatatbl[tbl[i].opcode].length = tbl[i].length; cpudatatbl[tbl[i].opcode].disp020[0] = tbl[i].disp020[0]; cpudatatbl[tbl[i].opcode].disp020[1] = tbl[i].disp020[1]; cpudatatbl[tbl[i].opcode].branch = tbl[i].branch; } } } opcnt = 0; for (opcode = 0; opcode < 65536; opcode++) { struct instr *table = &table68k[opcode]; if (table->mnemo == i_ILLG) continue; /* unimplemented opcode? */ if (table->unimpclev > 0 && lvl >= table->unimpclev) { if (currprefs.cpu_model == 68060) { // remove unimplemented integer instructions // unimpclev == 5: not implemented in 68060, // generates unimplemented instruction exception. if (currprefs.int_no_unimplemented && table->unimpclev == 5) { cpufunctbl[opcode] = op_unimpl_1; cpufunctbl_noret[opcode] = op_unimpl_1_noret; continue; } // remove unimplemented instruction that were removed in previous models, // generates normal illegal instruction exception. // unimplclev < 5: instruction was removed in 68040 or previous model. // clev=4: implemented in 68040 or later. unimpclev=5: not in 68060 if (table->unimpclev < 5 || (table->clev == 4 && table->unimpclev == 5)) { cpufunctbl[opcode] = op_illg_1; cpufunctbl_noret[opcode] = op_illg_1_noret; continue; } } else { cpufunctbl[opcode] = op_illg_1; cpufunctbl_noret[opcode] = op_illg_1_noret; continue; } } if (currprefs.fpu_model && currprefs.cpu_model < 68020) { /* more hack fpu to 68000/68010 mode */ if (table->clev > lvl && (opcode & 0xfe00) != 0xf200) continue; } else if (table->clev > lvl) { continue; } if (table->handler != -1) { int idx = table->handler; if (cpufunctbl[idx] == op_illg_1 || cpufunctbl_noret[idx] == op_illg_1_noret) abort (); cpufunctbl[opcode] = cpufunctbl[idx]; cpufunctbl_noret[opcode] = cpufunctbl_noret[idx]; memcpy(&cpudatatbl[opcode], &cpudatatbl[idx], sizeof(struct cputbl_data)); opcnt++; } if (opcode_loop_mode(opcode)) { loop_mode_table[opcode] = cpufunctbl[opcode]; } } need_opcode_swap = 0; #ifdef HAVE_GET_WORD_UNSWAPPED if (jit) { cpuop_func **tmp = xmalloc(cpuop_func*, 65536); memcpy(tmp, cpufunctbl, sizeof(cpuop_func*) * 65536); for (int i = 0; i < 65536; i++) { int offset = do_byteswap_16(i); cpufunctbl[offset] = tmp[i]; } xfree(tmp); need_opcode_swap = 1; } #endif write_log (_T("Building CPU, %d opcodes (%d %d %d)\n"), opcnt, lvl, currprefs.cpu_cycle_exact ? -2 : currprefs.cpu_memory_cycle_exact ? -1 : currprefs.cpu_compatible ? 1 : 0, currprefs.address_space_24); #ifdef JIT write_log(_T("JIT: &countdown = %p\n"), &countdown); write_log(_T("JIT: &build_comp = %p\n"), &build_comp); build_comp (); #endif write_log(_T("CPU=%d, FPU=%d%s, MMU=%d, JIT%s=%d."), currprefs.cpu_model, currprefs.fpu_model, currprefs.fpu_model ? (currprefs.fpu_mode > 0 ? _T(" (softfloat)") : (currprefs.fpu_mode < 0 ? _T(" (host 80b)") : _T(" (host 64b)"))) : _T(""), currprefs.mmu_model, currprefs.cachesize ? (currprefs.compfpu ? _T("=CPU/FPU") : _T("=CPU")) : _T(""), currprefs.cachesize); regs.address_space_mask = 0xffffffff; #ifndef WINUAE_FOR_HATARI if (currprefs.cpu_compatible) { if (currprefs.address_space_24 && currprefs.cpu_model >= 68040) currprefs.address_space_24 = false; } #else /* Hatari : don't force address_space_24=0 for 68030, as the Falcon has a 68030 LC with only 24 bits */ /* TODO ? Force address_space_24=0 for 68040 ? */ #endif m68k_interrupt_delay = false; m68k_accurate_ipl = false; if (currprefs.cpu_cycle_exact) { if (tbl == op_smalltbl_14 || tbl == op_smalltbl_13 || tbl == op_smalltbl_21 || tbl == op_smalltbl_23) m68k_interrupt_delay = true; if (tbl == op_smalltbl_14 || tbl == op_smalltbl_13) m68k_accurate_ipl = true; } else if (currprefs.cpu_compatible) { if (currprefs.cpu_model <= 68010 && currprefs.m68k_speed == 0) { m68k_interrupt_delay = true; } } if (currprefs.cpu_cycle_exact) { if (currprefs.cpu_model == 68000) write_log(_T(" prefetch and cycle-exact")); else write_log(_T(" ~cycle-exact")); } else if (currprefs.cpu_memory_cycle_exact) { write_log(_T(" ~memory-cycle-exact")); } else if (currprefs.cpu_compatible) { if (currprefs.cpu_model <= 68020) { write_log(_T(" prefetch")); } else { write_log(_T(" fake prefetch")); } } if (currprefs.m68k_speed < 0) write_log(_T(" fast")); if (currprefs.int_no_unimplemented && currprefs.cpu_model == 68060) { write_log(_T(" no unimplemented integer instructions")); } if (currprefs.fpu_no_unimplemented && currprefs.fpu_model) { write_log(_T(" no unimplemented floating point instructions")); } if (currprefs.address_space_24) { regs.address_space_mask = 0x00ffffff; write_log(_T(" 24-bit")); } write_log(_T("\n")); cpuipldelay2 = 2 * cpucycleunit; cpuipldelay4 = 4 * cpucycleunit; set_cpu_caches (true); #ifndef WINUAE_FOR_HATARI target_cpu_speed(); #endif } #define CYCLES_DIV 8192 static uae_u32 cycles_mult; static void update_68k_cycles (void) { #ifdef WINUAE_FOR_HATARI /* Don't adjust cycles_mult in Hatari and ignore m68k_speed (forced to 0) */ Log_Printf(LOG_DEBUG, "update cyc speed %d throttle %f clock_mult %d\n", currprefs.m68k_speed, currprefs.m68k_speed_throttle, changed_prefs.cpu_clock_multiplier); cycles_mult = CYCLES_DIV; #else cycles_mult = 0; if (currprefs.m68k_speed == 0) { // approximate cycles_mult = CYCLES_DIV; if (currprefs.cpu_model >= 68040) { if (currprefs.mmu_model) { cycles_mult = CYCLES_DIV / 24; } else { cycles_mult = CYCLES_DIV / 16; } } else if (currprefs.cpu_model >= 68020) { if (currprefs.mmu_model) { cycles_mult = CYCLES_DIV / 12; } else { cycles_mult = CYCLES_DIV / 8; } } if (!currprefs.cpu_cycle_exact) { if (currprefs.m68k_speed_throttle < 0) { cycles_mult = (uae_u32)((cycles_mult * 1000) / (1000 + currprefs.m68k_speed_throttle)); } else if (currprefs.m68k_speed_throttle > 0) { cycles_mult = (uae_u32)((cycles_mult * 1000) / (1000 + currprefs.m68k_speed_throttle)); } } } else if (currprefs.m68k_speed < 0) { cycles_mult = CYCLES_DIV / 21; } else { if (currprefs.m68k_speed >= 0 && !currprefs.cpu_cycle_exact && !currprefs.cpu_compatible) { if (currprefs.m68k_speed_throttle < 0) { cycles_mult = (uae_u32)(CYCLES_DIV * 1000 / (1000 + currprefs.m68k_speed_throttle)); } else if (currprefs.m68k_speed_throttle > 0) { cycles_mult = (uae_u32)(CYCLES_DIV * 1000 / (1000 + currprefs.m68k_speed_throttle)); } } } #endif currprefs.cpu_clock_multiplier = changed_prefs.cpu_clock_multiplier; currprefs.cpu_frequency = changed_prefs.cpu_frequency; #ifndef WINUAE_FOR_HATARI baseclock = (currprefs.ntscmode ? CHIPSET_CLOCK_NTSC : CHIPSET_CLOCK_PAL) * 8; #endif cpucycleunit = CYCLE_UNIT / 2; if (currprefs.cpu_clock_multiplier) { if (currprefs.cpu_clock_multiplier >= 256) { cpucycleunit = CYCLE_UNIT / (currprefs.cpu_clock_multiplier >> 8); } else { cpucycleunit = CYCLE_UNIT * currprefs.cpu_clock_multiplier; } if (currprefs.cpu_model >= 68040) { cpucycleunit /= 2; } #ifndef WINUAE_FOR_HATARI /* [NP] TODO : handle any cpu frequency, not just mulltiplier ? */ } else if (currprefs.cpu_frequency) { cpucycleunit = CYCLE_UNIT * baseclock / currprefs.cpu_frequency; #endif } else if (currprefs.cpu_memory_cycle_exact && currprefs.cpu_clock_multiplier == 0) { if (currprefs.cpu_model >= 68040) { cpucycleunit = CYCLE_UNIT / 16; } if (currprefs.cpu_model == 68030) { cpucycleunit = CYCLE_UNIT / 8; } else if (currprefs.cpu_model == 68020) { cpucycleunit = CYCLE_UNIT / 4; } else { cpucycleunit = CYCLE_UNIT / 2; } } if (cpucycleunit < 1) cpucycleunit = 1; write_log (_T("CPU cycleunit: %d (%.3f)\n"), cpucycleunit, (float)cpucycleunit / CYCLE_UNIT); #ifndef WINUAE_FOR_HATARI set_config_changed (); #endif } static void prefs_changed_cpu (void) { fixup_cpu (&changed_prefs); check_prefs_changed_comp(false); currprefs.cpu_model = changed_prefs.cpu_model; currprefs.fpu_model = changed_prefs.fpu_model; currprefs.fpu_revision = changed_prefs.fpu_revision; if (currprefs.mmu_model != changed_prefs.mmu_model) { int oldmmu = currprefs.mmu_model; currprefs.mmu_model = changed_prefs.mmu_model; if (currprefs.mmu_model >= 68040) { uae_u32 tcr = regs.tcr; mmu_reset(); mmu_set_tc(tcr); mmu_set_super(regs.s != 0); mmu_tt_modified(); } else if (currprefs.mmu_model == 68030) { mmu030_reset(-1); mmu030_flush_atc_all(); tc_030 = fake_tc_030; tt0_030 = fake_tt0_030; tt1_030 = fake_tt1_030; srp_030 = fake_srp_030; crp_030 = fake_crp_030; mmu030_decode_tc(tc_030, false); } else if (oldmmu == 68030) { fake_tc_030 = tc_030; fake_tt0_030 = tt0_030; fake_tt1_030 = tt1_030; fake_srp_030 = srp_030; fake_crp_030 = crp_030; } } currprefs.mmu_ec = changed_prefs.mmu_ec; if (currprefs.cpu_compatible != changed_prefs.cpu_compatible) { currprefs.cpu_compatible = changed_prefs.cpu_compatible; flush_cpu_caches(true); invalidate_cpu_data_caches(); } if (currprefs.cpu_data_cache != changed_prefs.cpu_data_cache) { currprefs.cpu_data_cache = changed_prefs.cpu_data_cache; invalidate_cpu_data_caches(); } currprefs.address_space_24 = changed_prefs.address_space_24; currprefs.cpu_cycle_exact = changed_prefs.cpu_cycle_exact; currprefs.cpu_memory_cycle_exact = changed_prefs.cpu_memory_cycle_exact; currprefs.int_no_unimplemented = changed_prefs.int_no_unimplemented; currprefs.fpu_no_unimplemented = changed_prefs.fpu_no_unimplemented; currprefs.blitter_cycle_exact = changed_prefs.blitter_cycle_exact; #ifndef WINUAE_FOR_HATARI mman_set_barriers(false); #endif } static int check_prefs_changed_cpu2(void) { int changed = 0; #ifdef JIT changed = check_prefs_changed_comp(true) ? 1 : 0; #endif if (changed || currprefs.cpu_model != changed_prefs.cpu_model || currprefs.fpu_model != changed_prefs.fpu_model || currprefs.fpu_revision != changed_prefs.fpu_revision || currprefs.mmu_model != changed_prefs.mmu_model || currprefs.mmu_ec != changed_prefs.mmu_ec || currprefs.cpu_data_cache != changed_prefs.cpu_data_cache || currprefs.address_space_24 != changed_prefs.address_space_24 /* WINUAE_FOR_HATARI */ || currprefs.int_no_unimplemented != changed_prefs.int_no_unimplemented || currprefs.fpu_no_unimplemented != changed_prefs.fpu_no_unimplemented || currprefs.cpu_compatible != changed_prefs.cpu_compatible || currprefs.cpu_cycle_exact != changed_prefs.cpu_cycle_exact || currprefs.cpu_memory_cycle_exact != changed_prefs.cpu_memory_cycle_exact || currprefs.fpu_mode != changed_prefs.fpu_mode) { cpu_prefs_changed_flag |= 1; } if (changed || currprefs.m68k_speed != changed_prefs.m68k_speed || currprefs.m68k_speed_throttle != changed_prefs.m68k_speed_throttle || currprefs.cpu_clock_multiplier != changed_prefs.cpu_clock_multiplier || currprefs.reset_delay != changed_prefs.reset_delay || currprefs.cpu_frequency != changed_prefs.cpu_frequency) { cpu_prefs_changed_flag |= 2; } return cpu_prefs_changed_flag; } void check_prefs_changed_cpu(void) { #ifndef WINUAE_FOR_HATARI /* [NP] TODO : handle cpu change on the fly ? */ if (!config_changed) return; #endif currprefs.cpu_idle = changed_prefs.cpu_idle; currprefs.ppc_cpu_idle = changed_prefs.ppc_cpu_idle; currprefs.reset_delay = changed_prefs.reset_delay; #ifndef WINUAE_FOR_HATARI currprefs.cpuboard_settings = changed_prefs.cpuboard_settings; #endif if (check_prefs_changed_cpu2()) { set_special(SPCFLAG_MODE_CHANGE); reset_frame_rate_hack(); } } void init_m68k (void) { prefs_changed_cpu (); update_68k_cycles (); for (int i = 0 ; i < 256 ; i++) { int j; for (j = 0 ; j < 8 ; j++) { if (i & (1 << j)) break; } movem_index1[i] = j; movem_index2[i] = 7 - j; movem_next[i] = i & (~(1 << j)); } cycles_mult &= ~0x7f; if (cycles_mult < 0x80) { cycles_mult = 0x80; } #if COUNT_INSTRS { FILE *f = fopen (icountfilename (), "r"); memset (instrcount, 0, sizeof instrcount); if (f) { uae_u32 opcode, count, total; TCHAR name[20]; write_log (_T("Reading instruction count file...\n")); fscanf (f, "Total: %lu\n", &total); while (fscanf (f, "%x: %lu %s\n", &opcode, &count, name) == 3) { instrcount[opcode] = count; } fclose (f); } } #endif init_table68k(); write_log (_T("%d CPU functions\n"), nr_cpuop_funcs); } struct regstruct regs, mmu_backup_regs; struct flag_struct regflags; static int m68kpc_offset; #ifndef WINUAE_FOR_HATARI STATIC_INLINE int in_rom (uaecptr pc) { return (munge24 (pc) & 0xFFF80000) == 0xF80000; } STATIC_INLINE int in_rtarea (uaecptr pc) { return (munge24 (pc) & 0xFFFF0000) == rtarea_base && (uae_boot_rom_type || currprefs.uaeboard > 0); } #endif STATIC_INLINE void wait_memory_cycles (void) { if (regs.memory_waitstate_cycles) { x_do_cycles(regs.memory_waitstate_cycles); regs.memory_waitstate_cycles = 0; } if (regs.ce020extracycles >= 16) { regs.ce020extracycles = 0; x_do_cycles(2 * cpucycleunit); } } STATIC_INLINE int adjust_cycles (int cycles) { #ifndef WINUAE_FOR_HATARI int mc = regs.memory_waitstate_cycles; regs.memory_waitstate_cycles = 0; cycles *= cycles_mult; cycles /= CYCLES_DIV; return cycles + mc; #else /* Don't adjust cycles in Hatari, return the same value */ return cycles; #endif } void m68k_cancel_idle(void) { #ifndef WINUAE_FOR_HATARI cpu_last_stop_vpos = -1; #endif } static void m68k_set_stop(int stoptype) { if (regs.stopped) return; regs.stopped = stoptype; #ifndef WINUAE_FOR_HATARI if (cpu_last_stop_vpos >= 0) { cpu_last_stop_vpos = vpos; } #endif } static void m68k_unset_stop(void) { regs.stopped = 0; #ifndef WINUAE_FOR_HATARI if (cpu_last_stop_vpos >= 0) { cpu_stopped_lines += vpos - cpu_last_stop_vpos; cpu_last_stop_vpos = vpos; } #endif } static void activate_trace(void) { unset_special (SPCFLAG_TRACE); set_special (SPCFLAG_DOTRACE); } // make sure interrupt is checked immediately after current instruction void checkint(void) { doint(); if (!m68k_accurate_ipl && !currprefs.cachesize && !(regs.spcflags & SPCFLAG_INT) && (regs.spcflags & SPCFLAG_DOINT)) set_special(SPCFLAG_INT); } void REGPARAM2 MakeSR(void) { regs.sr = ((regs.t1 << 15) | (regs.t0 << 14) | (regs.s << 13) | (regs.m << 12) | (regs.intmask << 8) | (GET_XFLG() << 4) | (GET_NFLG() << 3) | (GET_ZFLG() << 2) | (GET_VFLG() << 1) | GET_CFLG()); } static void SetSR(uae_u16 sr) { regs.sr &= 0xff00; regs.sr |= sr; SET_XFLG((regs.sr >> 4) & 1); SET_NFLG((regs.sr >> 3) & 1); SET_ZFLG((regs.sr >> 2) & 1); SET_VFLG((regs.sr >> 1) & 1); SET_CFLG(regs.sr & 1); } static void MakeFromSR_x(int t0trace) { int oldm = regs.m; int olds = regs.s; int oldt0 = regs.t0; int oldt1 = regs.t1; SET_XFLG((regs.sr >> 4) & 1); SET_NFLG((regs.sr >> 3) & 1); SET_ZFLG((regs.sr >> 2) & 1); SET_VFLG((regs.sr >> 1) & 1); SET_CFLG(regs.sr & 1); if (regs.t1 == ((regs.sr >> 15) & 1) && regs.t0 == ((regs.sr >> 14) & 1) && regs.s == ((regs.sr >> 13) & 1) && regs.m == ((regs.sr >> 12) & 1) && regs.intmask == ((regs.sr >> 8) & 7)) return; regs.t1 = (regs.sr >> 15) & 1; regs.t0 = (regs.sr >> 14) & 1; regs.s = (regs.sr >> 13) & 1; regs.m = (regs.sr >> 12) & 1; if (regs.intmask != ((regs.sr >> 8) & 7)) { int newimask = (regs.sr >> 8) & 7; if (m68k_accurate_ipl) { // STOP intmask change enabling already active interrupt: delay it by 1 STOP round if (t0trace < 0 && regs.ipl[0] <= regs.intmask && regs.ipl[0] > newimask && regs.ipl[0] < 7) { regs.ipl[0] = 0; } } else { if (regs.ipl_pin <= regs.intmask && regs.ipl_pin > newimask) { if (!currprefs.cachesize) { set_special(SPCFLAG_INT); } else { set_special(SPCFLAG_DOINT); } } } regs.intmask = newimask; } if (currprefs.cpu_model >= 68020) { /* 68060 does not have MSP but does have M-bit.. */ if (currprefs.cpu_model >= 68060) regs.msp = regs.isp; if (olds != regs.s) { if (olds) { if (oldm) regs.msp = m68k_areg (regs, 7); else regs.isp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.usp; } else { regs.usp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.m ? regs.msp : regs.isp; } } else if (olds && oldm != regs.m) { if (oldm) { regs.msp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.isp; } else { regs.isp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.msp; } } if (currprefs.cpu_model >= 68060) regs.t0 = 0; } else { regs.t0 = regs.m = 0; if (olds != regs.s) { if (olds) { regs.isp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.usp; } else { regs.usp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.isp; } } } if (currprefs.mmu_model) mmu_set_super (regs.s != 0); #ifdef JIT // if JIT enabled and T1, T0 or M changes: end compile. if (currprefs.cachesize && (oldt0 != regs.t0 || oldt1 != regs.t1 || oldm != regs.m)) { set_special(SPCFLAG_END_COMPILE); } #endif if (regs.t1 || regs.t0) { set_special (SPCFLAG_TRACE); } else { /* Keep SPCFLAG_DOTRACE, we still want a trace exception for SR-modifying instructions (including STOP). */ unset_special (SPCFLAG_TRACE); } // Stop SR-modification does not generate T0 // If this SR modification set Tx bit, no trace until next instruction. if (!regs.stopped && ((oldt0 && t0trace && currprefs.cpu_model >= 68020) || oldt1)) { // Always trace if Tx bits were already set, even if this SR modification cleared them. activate_trace(); } } void REGPARAM2 MakeFromSR_T0(void) { MakeFromSR_x(1); } void REGPARAM2 MakeFromSR(void) { MakeFromSR_x(0); } void REGPARAM2 MakeFromSR_STOP(void) { MakeFromSR_x(-1); } static bool internalexception(int nr) { return nr == 5 || nr == 6 || nr == 7 || (nr >= 32 && nr <= 47); } static void exception_check_trace (int nr) { unset_special (SPCFLAG_TRACE | SPCFLAG_DOTRACE); if (regs.t1) { /* trace stays pending if exception is div by zero, chk, * trapv or trap #x. Except if 68040 or 68060. */ if (currprefs.cpu_model < 68040 && internalexception(nr)) { set_special(SPCFLAG_DOTRACE); } // 68010 and RTE format error: trace is not cleared if (nr == 14 && currprefs.cpu_model == 68010) { set_special(SPCFLAG_DOTRACE); } } regs.t1 = regs.t0 = 0; } static void exception_debug (int nr) { #ifdef WINUAE_FOR_HATARI if (unlikely(ExceptionDebugMask & EXCEPT_NOHANDLER) && STMemory_ReadLong(regs.vbr + 4*nr) == 0) { fprintf(stderr,"Uninitialized exception handler #%i!\n", nr); DebugUI(REASON_CPU_EXCEPTION); } else { DebugUI_Exceptions(nr, M68K_GETPC); } #else #ifdef DEBUGGER if (!exception_debugging) return; console_out_f (_T("Exception %d, PC=%08X\n"), nr, M68K_GETPC); #endif #endif } #ifdef CPUEMU_13 /* cycle-exact exception handler, 68000 only */ /* 68000 Address/Bus Error: - [memory access causing bus/address error] - 8 idle cycles (+4 if bus error) - write PC low word - write SR - write PC high word - write instruction word - write fault address low word - write status code - write fault address high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch 68010 Address/Bus Error: - [memory access causing bus/address error] - 8 idle cycles (+4 if bus error) - write word 28 - write word 26 - write word 27 - write word 25 - write word 23 - write word 24 - write word 22 - write word 21 - write word 20 - write word 19 - write word 18 - write word 17 - write word 16 - write word 15 - write word 13 - write word 14 - write instruction buffer - (skipped) - write data input buffer - (skipped) - write data output buffer - (skipped) - write fault address low word - write fault address high word - write special status word - write PC low word - write SR - write PC high word - write frame format - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch Division by Zero: - 4 idle cycles (EA + 4 cycles in cpuemu) - write PC low word - write SR - write PC high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch Traps: - 4 idle cycles - write PC low word - write SR - write PC high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch TrapV: (- normal prefetch done by TRAPV) - write PC low word - write SR - write PC high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch CHK: - 4 idle cycles (EA + 4/6 cycles in cpuemu) - write PC low word - write SR - write PC high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch Illegal Instruction: Privilege violation: Trace: Line A: Line F: - 4 idle cycles - write PC low word - write SR - write PC high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch Interrupt: - 6 idle cycles - write PC low word - read exception number byte from (0xfffff1 | (interrupt number << 1)) - 4 idle cycles - write SR - write PC high word - read exception address high word - read exception address low word - prefetch - 2 idle cycles - prefetch 68010: ... - write SR - write PC high word - write frame format - read exception address high word ... */ static int iack_cycle(int nr) { int vector = nr; #ifndef WINUAE_FOR_HATARI if (1) { // non-autovectored if (currprefs.cpu_model >= 68020) { return vector; } // this is basically normal memory access and takes 4 cycles (without wait states). vector = x_get_byte(0x00fffff1 | ((nr - 24) << 1)); x_do_cycles(4 * cpucycleunit); } else { // autovectored } #else int e_cycles; int cycle_exact = currprefs.cpu_cycle_exact && !currprefs.mmu_model; // TODO/CHECK HATARI : use CpuRunCycleExact instead ? /* * Pending bits / vector number can change before the start of the IACK sequence : * * Interrupt processing takes 12 cycles (CPU_IACK_CYCLES_START in non CE mode) * before doing the IACK sequence. During these 12 cycles another interrupt (MFP, video, ...) * could happen with a higher priority and replace the vector that would normally be used * To update pending interrupts, we call CycInt_Process() just before the IACK sequence * * We need to handle MFP/DSP and HBL/VBL cases for this : * - Level 6 (MFP/DSP) uses vectored interrupts * - Level 5 (SCC) uses vectored interrupts * - Level 2 (HBL) and 4 (VBL) use auto-vectored interrupts and require sync with E-clock */ vector = nr; if ( nr == 30 ) /* MFP or DSP (level 6) */ { vector = -1; if (bDspEnabled) /* Check DSP first */ { /* TODO : For DSP, we just get the vector, we don't add IACK cycles */ vector = DSP_ProcessIACK (); } if ( vector < 0 ) /* No DSP, check MFP */ { /* Update cycles counter before the IACK sequence */ if (cycle_exact) { /* In CE mode, the cycles before reaching IACK are already counted, no need to call x_do_cycles() */ /* Flush all CE cycles so far to update PendingInterruptCount */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle=0; } else M68000_AddCycles ( CPU_IACK_CYCLES_START ); /* This will be compensated in add_approximate_exception_cycles() */ CPU_IACK = true; /* Update pending interrupts just before doing the IACK sequence */ CycInt_Process(); vector = MFP_ProcessIACK ( nr ); /* Add the cycles used by the IACK sequence (IACK to DTACK transition) */ if (cycle_exact) x_do_cycles ( CPU_IACK_CYCLES_MFP_CE * cpucycleunit ); else M68000_AddCycles ( CPU_IACK_CYCLES_MFP ); CPU_IACK = false; } if ( vector >= 0 ) /* We have a valid vector for level 6 */ { /* If a DSP IRQ is pending, we don't clear level 6 pending bit, else the DSP IRQ */ /* will never be processed. If there's no DSP IRQ, we clear level 6 pending bit now */ /* and if there's a lower MFP pending int, level 6 will be set again at the next instruction */ if ( DSP_GetHREQ() == 0 ) M68000_ClearIRQ ( 6 ); } } if ( nr == 29 ) /* SCC (level 5) */ { vector = SCC_Process_IACK (); } else if ( ( nr == 26 ) || ( nr == 28 ) ) /* HBL (level 2) or VBL (level 6) */ { /* Update cycles counter before the IACK sequence */ if (cycle_exact) { /* In CE mode, the cycles before reaching IACK are already counted, no need to call x_do_cycles() */ /* Flush all CE cycles so far before calling M68000_WaitEClock() */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle = 0; } else M68000_AddCycles ( CPU_IACK_CYCLES_START ); e_cycles = M68000_WaitEClock (); // fprintf ( stderr , "wait e clock %d\n" , e_cycles); if ( nr == 26 ) { LOG_TRACE ( TRACE_VIDEO_HBL , "HBL=%d clock=%"PRIu64" e_clock_jitter=%d\n" , nHBL , Cycles_GetClockCounterImmediate() , e_cycles ); } else if ( nr == 28 ) { LOG_TRACE ( TRACE_VIDEO_VBL , "VBL=%d clock=%"PRIu64" e_clock_jitter=%d\n" , nVBLs , Cycles_GetClockCounterImmediate() , e_cycles ); } if (cycle_exact) { x_do_cycles ( e_cycles * cpucycleunit ); /* Flush all CE cycles so far to update PendingInterruptCount */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle = 0; } else M68000_AddCycles ( e_cycles ); CPU_IACK = true; /* Update pending interrupts just before doing the IACK sequence */ CycInt_Process(); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); /* update MFP's state if some internal timers related to MFP expired */ M68000_ClearIRQ ( nr - 24 ); /* clear HBL or VBL pending bit (even if an MFP timer occurred during IACK) */ CPU_IACK = false; /* Add the cycles used by the IACK sequence (IACK to DTACK transition) */ if (cycle_exact) x_do_cycles ( CPU_IACK_CYCLES_VIDEO_CE * cpucycleunit ); else M68000_AddCycles ( CPU_IACK_CYCLES_VIDEO ); CPU_IACK = false; } /* If none of DSP, MFP or SCC returned a vector during the IACK sequence, then we have a spurious interrupt */ /* In that case, we use vector 24 and we jump to $60 */ if ( vector < 0 ) { vector = 24; } /* Add 4 idle cycles for CE mode. For non-CE mode, this will be counted in add_approximate_exception_cycles() */ if (cycle_exact) x_do_cycles( 4 * cpucycleunit ); #endif return vector; } static void Exception_ce000 (int nr) { uae_u32 currpc = m68k_getpc (), newpc; int sv = regs.s; int start, interrupt; int vector_nr = nr; int frame_id = 0; //fprintf ( stderr , "ex in %d %ld %ld\n" , nr , currcycle , CyclesGlobalClockCounter ); start = 6; interrupt = nr >= 24 && nr < 24 + 8; if (!interrupt) { start = 4; if (nr == 7) { // TRAPV start = 0; } else if (nr == 3) { if (currprefs.cpu_model == 68000) start = 8; else start = 4; } else if (nr == 2) { if (currprefs.cpu_model == 68000) start = 12; else start = 8; } } if (start) x_do_cycles (start * cpucycleunit); #ifdef WINUAE_FOR_HATARI LOG_TRACE(TRACE_CPU_EXCEPTION, "cpu exception %d currpc %x buspc %x newpc %x fault_e3 %x op_e3 %x addr_e3 %x SR %x\n", nr, currpc, regs.instruction_pc, STMemory_ReadLong (regs.vbr + 4*nr), last_fault_for_exception_3, last_op_for_exception_3, last_addr_for_exception_3, regs.sr); #endif exception_debug (nr); MakeSR (); bool g1 = generates_group1_exception(regs.ir); if (!regs.s) { regs.usp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.isp; regs.s = 1; } if (nr == 2 || nr == 3) { /* 2=bus error, 3=address error */ if ((m68k_areg(regs, 7) & 1) || exception_in_exception < 0) { cpu_halt (CPU_HALT_DOUBLE_FAULT); return; } #ifndef WINUAE_FOR_HATARI write_log (_T("Exception %d (%08x %x) at %x -> %x!\n"), nr, last_op_for_exception_3, last_addr_for_exception_3, currpc, get_long_debug (4 * nr)); #else if (nr != 2 || M68000_IsVerboseBusError(currpc, last_fault_for_exception_3)) Log_Printf(LOG_WARN, "%s Error %s at address $%x, PC=$%x addr_e3=%x op_e3=%x\n", nr == 2 ? "Bus" : "Address", last_writeaccess_for_exception_3 ? "writing" : "reading", last_fault_for_exception_3, currpc, last_addr_for_exception_3 , last_op_for_exception_3); #endif if (currprefs.cpu_model == 68000) { // 68000 bus/address error uae_u16 mode = (sv ? 4 : 0) | last_fc_for_exception_3; mode |= last_writeaccess_for_exception_3 ? 0 : 16; mode |= last_notinstruction_for_exception_3 ? 8 : 0; // undocumented bits contain opcode mode |= last_op_for_exception_3 & ~31; m68k_areg(regs, 7) -= 7 * 2; exception_in_exception = -1; x_put_word(m68k_areg(regs, 7) + 12, last_addr_for_exception_3); x_put_word(m68k_areg(regs, 7) + 8, regs.sr); x_put_word(m68k_areg(regs, 7) + 10, last_addr_for_exception_3 >> 16); x_put_word(m68k_areg(regs, 7) + 6, last_op_for_exception_3); x_put_word(m68k_areg(regs, 7) + 4, last_fault_for_exception_3); x_put_word(m68k_areg(regs, 7) + 0, mode); x_put_word(m68k_areg(regs, 7) + 2, last_fault_for_exception_3 >> 16); goto kludge_me_do; } else { // 68010 bus/address error (partially implemented only) uae_u16 in = regs.read_buffer; uae_u16 out = regs.write_buffer; uae_u16 ssw = (sv ? 4 : 0) | last_fc_for_exception_3; ssw |= last_di_for_exception_3 > 0 ? 0x0000 : (last_di_for_exception_3 < 0 ? (0x2000 | 0x1000) : 0x2000); ssw |= (!last_writeaccess_for_exception_3 && last_di_for_exception_3) ? 0x1000 : 0x000; // DF ssw |= (last_op_for_exception_3 & 0x10000) ? 0x0400 : 0x0000; // HB ssw |= last_size_for_exception_3 == 0 ? 0x0200 : 0x0000; // BY ssw |= last_writeaccess_for_exception_3 ? 0 : 0x0100; // RW if (last_op_for_exception_3 & 0x20000) ssw &= 0x00ff; m68k_areg(regs, 7) -= (29 - 4) * 2; exception_in_exception = -1; frame_id = 8; for (int i = 0; i < 15; i++) { x_put_word(m68k_areg(regs, 7) + 20 + i * 2, ((i + 1) << 8) | ((i + 2) << 0)); } x_put_word(m68k_areg(regs, 7) + 18, 0); // version x_put_word(m68k_areg(regs, 7) + 16, regs.irc); // instruction input buffer x_put_word(m68k_areg(regs, 7) + 12, in); // data input buffer x_put_word(m68k_areg(regs, 7) + 8, out); // data output buffer x_put_word(m68k_areg(regs, 7) + 4, last_fault_for_exception_3); // fault addr x_put_word(m68k_areg(regs, 7) + 2, last_fault_for_exception_3 >> 16); x_put_word(m68k_areg(regs, 7) + 0, ssw); // ssw } } if (currprefs.cpu_model == 68010) { // 68010 creates only format 0 and 8 stack frames m68k_areg (regs, 7) -= 4 * 2; if (m68k_areg(regs, 7) & 1) { exception3_notinstruction(regs.ir, m68k_areg(regs, 7) + 4); return; } exception_in_exception = 1; x_put_word (m68k_areg (regs, 7) + 4, currpc); // write low address if (interrupt) { vector_nr = iack_cycle(nr); } x_put_word (m68k_areg (regs, 7) + 0, regs.sr); // write SR x_put_word (m68k_areg (regs, 7) + 2, currpc >> 16); // write high address x_put_word (m68k_areg (regs, 7) + 6, (frame_id << 12) | (vector_nr * 4)); } else { m68k_areg (regs, 7) -= 3 * 2; if (m68k_areg(regs, 7) & 1) { exception3_notinstruction(regs.ir, m68k_areg(regs, 7) + 4); return; } exception_in_exception = 1; x_put_word (m68k_areg (regs, 7) + 4, currpc); // write low address //fprintf ( stderr , "ex iack1 %d %ld\n" , nr , currcycle ); if (interrupt) { vector_nr = iack_cycle(nr); //fprintf ( stderr , "ex iack2 %d %ld\n" , nr , currcycle ); } x_put_word (m68k_areg (regs, 7) + 0, regs.sr); // write SR x_put_word (m68k_areg (regs, 7) + 2, currpc >> 16); // write high address } kludge_me_do: if ((regs.vbr & 1) && currprefs.cpu_model <= 68010) { cpu_halt(CPU_HALT_DOUBLE_FAULT); return; } if (interrupt) regs.intmask = nr - 24; newpc = x_get_word (regs.vbr + 4 * vector_nr) << 16; // read high address newpc |= x_get_word (regs.vbr + 4 * vector_nr + 2); // read low address exception_in_exception = 0; if (newpc & 1) { if (nr == 2 || nr == 3) { cpu_halt(CPU_HALT_DOUBLE_FAULT); return; } if (currprefs.cpu_model == 68000) { // if exception vector is odd: // opcode is last opcode executed, address is address of exception vector // pc is last prefetch address regs.t1 = 0; MakeSR(); m68k_setpc(regs.vbr + 4 * vector_nr); if (interrupt) { regs.ir = nr; exception3_read_access(regs.ir | 0x20000 | 0x10000, newpc, sz_word, 2); } else { exception3_read_access(regs.ir | 0x40000 | 0x20000 | (g1 ? 0x10000 : 0), newpc, sz_word, 2); } } else if (currprefs.cpu_model == 68010) { // offset, not vbr + offset regs.t1 = 0; MakeSR(); regs.write_buffer = 4 * vector_nr; regs.read_buffer = newpc; regs.irc = regs.read_buffer; exception3_read_access(regs.opcode, newpc, sz_word, 2); } else { exception_check_trace(nr); exception3_notinstruction(regs.ir, newpc); } return; } m68k_setpc(newpc); #ifdef DEBUGGER branch_stack_push(currpc, currpc); #endif regs.ir = x_get_word(m68k_getpc()); // prefetch 1 if (hardware_bus_error) { if (nr == 2 || nr == 3) { cpu_halt(CPU_HALT_DOUBLE_FAULT); return; } exception2_fetch(regs.irc, 0, 0); return; } regs.ird = regs.ir; x_do_cycles (2 * cpucycleunit); // TODO : check 2022/10/19, ifdef tjs necessaire ? #ifdef WINUAE_FOR_HATARI /* [NP] IPL should be updated just before the last x_get_word for irc */ /* (4 cycles before end of exception), so we need to add 2 cycles now */ /* to be aligned on 4 cycles (else the 2 cycles will be added in x_get_word */ /* and IPL will be updated 2 cycles too soon) */ x_do_cycles (2 * cpucycleunit); /* Add all cycles needed for the exception so far */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle = 0; /* Update IPL / interrupts state, in case a new interrupt happened during this exception */ CycInt_Process(); #endif if (m68k_accurate_ipl) { ipl_fetch_next(); } regs.irc = x_get_word(m68k_getpc() + 2); // prefetch 2 if (hardware_bus_error) { if (nr == 2 || nr == 3) { cpu_halt(CPU_HALT_DOUBLE_FAULT); return; } exception2_fetch(regs.ir, 0, 2); return; } #ifdef JIT if (currprefs.cachesize) { set_special(SPCFLAG_END_COMPILE); } #endif exception_check_trace(nr); //fprintf ( stderr , "ex out %d %ld\n" , nr , currcycle ); #ifdef WINUAE_FOR_HATARI /* Add all cycles needed for the exception */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle = 0; #endif } #endif // 68030 MMU static void Exception_mmu030 (int nr, uaecptr oldpc) { uae_u32 currpc = m68k_getpc (), newpc; int interrupt, vector_nr = nr; interrupt = nr >= 24 && nr < 24 + 8; if (interrupt) vector_nr = iack_cycle(nr); #ifdef WINUAE_FOR_HATARI LOG_TRACE(TRACE_CPU_EXCEPTION, "cpu exception %d currpc %x buspc %x newpc %x fault_e3 %x op_e3 %x addr_e3 %x SR %x\n", nr, currpc, regs.instruction_pc, STMemory_ReadLong (regs.vbr + 4*vector_nr), last_fault_for_exception_3, last_op_for_exception_3, last_addr_for_exception_3, regs.sr); #endif exception_debug (vector_nr); MakeSR (); if (!regs.s) { regs.usp = m68k_areg (regs, 7); m68k_areg(regs, 7) = regs.m ? regs.msp : regs.isp; regs.s = 1; mmu_set_super (1); } newpc = x_get_long (regs.vbr + 4 * vector_nr); if (regs.m && interrupt) { /* M + Interrupt */ Exception_build_stack_frame (oldpc, currpc, regs.mmu_ssw, vector_nr, 0x0); MakeSR (); regs.m = 0; regs.msp = m68k_areg (regs, 7); m68k_areg (regs, 7) = regs.isp; Exception_build_stack_frame (oldpc, currpc, regs.mmu_ssw, vector_nr, 0x1); } else if (nr == 2) { if (1 && (mmu030_state[1] & MMU030_STATEFLAG1_LASTWRITE)) { Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0xA); } else { Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0xB); } } else if (nr == 3) { regs.mmu_fault_addr = last_fault_for_exception_3; mmu030_state[0] = mmu030_state[1] = 0; mmu030_data_buffer_out = 0; Exception_build_stack_frame(last_fault_for_exception_3, currpc, MMU030_SSW_RW | MMU030_SSW_SIZE_W | (regs.s ? 6 : 2), vector_nr, 0xB); } else { Exception_build_stack_frame_common(oldpc, currpc, regs.mmu_ssw, nr, vector_nr); } if (newpc & 1) { if (nr == 2 || nr == 3) cpu_halt (CPU_HALT_DOUBLE_FAULT); else exception3_read_special(regs.ir, newpc, 1, 1); return; } if (interrupt) regs.intmask = nr - 24; m68k_setpci (newpc); fill_prefetch (); exception_check_trace (nr); } // 68040/060 MMU static void Exception_mmu (int nr, uaecptr oldpc) { uae_u32 currpc = m68k_getpc (), newpc; int interrupt; int vector_nr = nr; interrupt = nr >= 24 && nr < 24 + 8; if (interrupt) vector_nr = iack_cycle(nr); // exception vector fetch and exception stack frame // operations don't allocate new cachelines cache_default_data |= CACHE_DISABLE_ALLOCATE; #ifdef WINUAE_FOR_HATARI LOG_TRACE(TRACE_CPU_EXCEPTION, "cpu exception %d currpc %x buspc %x newpc %x fault_e3 %x op_e3 %x addr_e3 %x SR %x\n", nr, currpc, regs.instruction_pc, STMemory_ReadLong (regs.vbr + 4*vector_nr), last_fault_for_exception_3, last_op_for_exception_3, last_addr_for_exception_3, regs.sr); #endif exception_debug (vector_nr); MakeSR (); if (!regs.s) { regs.usp = m68k_areg (regs, 7); if (currprefs.cpu_model >= 68020 && currprefs.cpu_model < 68060) { m68k_areg (regs, 7) = regs.m ? regs.msp : regs.isp; } else { m68k_areg (regs, 7) = regs.isp; } regs.s = 1; mmu_set_super (1); } newpc = x_get_long (regs.vbr + 4 * vector_nr); #if 0 write_log (_T("Exception %d: %08x -> %08x\n"), nr, currpc, newpc); #endif if (nr == 2) { // bus error //write_log (_T("Exception_mmu %08x %08x %08x\n"), currpc, oldpc, regs.mmu_fault_addr); if (currprefs.mmu_model == 68040) Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0x7); else Exception_build_stack_frame(regs.mmu_fault_addr, currpc, regs.mmu_fslw, vector_nr, 0x4); } else if (nr == 3) { // address error Exception_build_stack_frame(last_fault_for_exception_3, currpc, 0, vector_nr, 0x2); write_log (_T("Exception %d (%x) at %x -> %x!\n"), nr, last_fault_for_exception_3, currpc, get_long_debug (regs.vbr + 4 * vector_nr)); } else if (regs.m && interrupt) { /* M + Interrupt */ Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0x0); MakeSR(); regs.m = 0; if (currprefs.cpu_model < 68060) { regs.msp = m68k_areg(regs, 7); Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0x1); } } else { Exception_build_stack_frame_common(oldpc, currpc, regs.mmu_ssw, nr, vector_nr); } if (newpc & 1) { if (nr == 2 || nr == 3) cpu_halt (CPU_HALT_DOUBLE_FAULT); else exception3_read_special(regs.ir, newpc, 2, 1); return; } cache_default_data &= ~CACHE_DISABLE_ALLOCATE; m68k_setpci (newpc); if (interrupt) regs.intmask = nr - 24; fill_prefetch (); exception_check_trace (nr); } static void add_approximate_exception_cycles(int nr) { int cycles; if (currprefs.cpu_model == 68000) { if (nr >= 24 && nr <= 31) { #ifndef WINUAE_FOR_HATARI /* Interrupts */ cycles = 44; #else /* Atari's specific interrupts take 56 cycles instead of 44 due to iack sequence */ /* We must subtract CPU_IACK_CYCLES_START cycles already counted into iack_cycle() */ if ( nr == 30 ) /* MFP/DSP */ cycles = 44-CPU_IACK_CYCLES_START; else if ( nr == 28 ) /* VBL */ cycles = 44-CPU_IACK_CYCLES_START; else if ( nr == 26 ) /* HBL */ cycles = 44-CPU_IACK_CYCLES_START; else cycles = 44; /* Other interrupts (not used in Atari machines) */ #endif } else if (nr >= 32 && nr <= 47) { /* Trap */ cycles = 34; } else { switch (nr) { case 2: cycles = 58; break; /* Bus error */ case 3: cycles = 54; break; /* Address error */ case 4: cycles = 34; break; /* Illegal instruction */ case 5: cycles = 34; break; /* Division by zero */ case 6: cycles = 34; break; /* CHK */ case 7: cycles = 30; break; /* TRAPV */ case 8: cycles = 34; break; /* Privilege violation */ case 9: cycles = 34; break; /* Trace */ case 10: cycles = 34; break; /* Line-A */ case 11: cycles = 34; break; /* Line-F */ default: cycles = 4; break; } } } else if (currprefs.cpu_model == 68010) { if (nr >= 24 && nr <= 31) { /* Interrupts */ cycles = 48; } else if (nr >= 32 && nr <= 47) { /* Trap */ cycles = 38; } else { switch (nr) { case 2: cycles = 140; break; /* Bus error */ case 3: cycles = 136; break; /* Address error */ case 4: cycles = 38; break; /* Illegal instruction */ case 5: cycles = 38; break; /* Division by zero */ case 6: cycles = 38; break; /* CHK */ case 7: cycles = 40; break; /* TRAPV */ case 8: cycles = 38; break; /* Privilege violation */ case 9: cycles = 38; break; /* Trace */ case 10: cycles = 38; break; /* Line-A */ case 11: cycles = 38; break; /* Line-F */ case 14: cycles = 38; break; /* RTE frame error */ default: cycles = 4; break; } } } else { return; } #ifdef WINUAE_FOR_HATARI M68000_AddCycles ( cycles ); #endif cycles = adjust_cycles(cycles * CYCLE_UNIT / 2); x_do_cycles(cycles); } static void Exception_normal (int nr) { uae_u32 newpc; uae_u32 currpc = m68k_getpc(); uae_u32 nextpc; int sv = regs.s; int interrupt; int vector_nr = nr; bool g1 = false; cache_default_data |= CACHE_DISABLE_ALLOCATE; interrupt = nr >= 24 && nr < 24 + 8; #ifndef WINUAE_FOR_HATARI if (currprefs.cpu_model <= 68010) { g1 = generates_group1_exception(regs.ir); if (interrupt) vector_nr = iack_cycle(nr); } #else g1 = generates_group1_exception(regs.ir); if (interrupt) vector_nr = iack_cycle(nr); #endif exception_debug (vector_nr); MakeSR (); if (!regs.s) { regs.usp = m68k_areg (regs, 7); if (currprefs.cpu_model >= 68020 && currprefs.cpu_model < 68060) { m68k_areg (regs, 7) = regs.m ? regs.msp : regs.isp; } else { m68k_areg (regs, 7) = regs.isp; } regs.s = 1; if (currprefs.mmu_model) mmu_set_super (regs.s != 0); } if ((m68k_areg(regs, 7) & 1) && currprefs.cpu_model < 68020) { if (nr == 2 || nr == 3) cpu_halt (CPU_HALT_DOUBLE_FAULT); else exception3_notinstruction(regs.ir, m68k_areg(regs, 7)); return; } if ((nr == 2 || nr == 3) && exception_in_exception < 0) { cpu_halt (CPU_HALT_DOUBLE_FAULT); return; } #ifndef WINUAE_FOR_HATARI if (!currprefs.cpu_compatible) { addrbank *ab = &get_mem_bank(m68k_areg(regs, 7) - 4); // Not plain RAM check because some CPU type tests that // don't need to return set stack to ROM.. if (!ab || ab == &dummy_bank || (ab->flags & ABFLAG_IO)) { cpu_halt(CPU_HALT_SSP_IN_NON_EXISTING_ADDRESS); return; } } #endif bool used_exception_build_stack_frame = false; if (currprefs.cpu_model > 68000) { uae_u32 oldpc = regs.instruction_pc; nextpc = exception_pc (nr); #ifdef WINUAE_FOR_HATARI LOG_TRACE(TRACE_CPU_EXCEPTION, "cpu exception %d vector %x currpc %x buspc %x newpc %x fault_e3 %x op_e3 %x addr_e3 %x SR %x\n", nr, 4*vector_nr , currpc, regs.instruction_pc, STMemory_ReadLong (regs.vbr + 4*vector_nr), last_fault_for_exception_3, last_op_for_exception_3, last_addr_for_exception_3, regs.sr); #endif if (nr == 2 || nr == 3) { int i; if (currprefs.cpu_model >= 68040) { if (nr == 2) { if (currprefs.mmu_model) { // 68040/060 mmu bus error for (i = 0 ; i < 7 ; i++) { m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), 0); } m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.wb3_data); m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.mmu_fault_addr); m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.mmu_fault_addr); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), regs.wb3_status); regs.wb3_status = 0; m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), regs.mmu_ssw); m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.mmu_fault_addr); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0x7000 + vector_nr * 4); m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.instruction_pc); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), regs.sr); newpc = x_get_long (regs.vbr + 4 * vector_nr); if (newpc & 1) { if (nr == 2 || nr == 3) cpu_halt (CPU_HALT_DOUBLE_FAULT); else exception3_read_special(regs.ir, newpc, 2, 1); return; } m68k_setpc (newpc); #ifdef JIT if (currprefs.cachesize) { set_special(SPCFLAG_END_COMPILE); } #endif exception_check_trace (nr); return; } else { // 68040 bus error (not really, some garbage?) for (i = 0 ; i < 18 ; i++) { m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); } m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), last_fault_for_exception_3); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0x0140 | (sv ? 6 : 2)); /* SSW */ m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), last_addr_for_exception_3); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0x7000 + vector_nr * 4); m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.instruction_pc); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), regs.sr); goto kludge_me_do; } } else { // 68040/060 odd PC address error Exception_build_stack_frame(last_fault_for_exception_3, currpc, 0, vector_nr, 0x02); used_exception_build_stack_frame = true; } } else if (currprefs.cpu_model >= 68020) { // 68020/030 odd PC address error (partially implemented only) // annoyingly this generates frame B, not A. uae_u16 ssw = (sv ? 4 : 0) | last_fc_for_exception_3; ssw |= MMU030_SSW_RW | MMU030_SSW_SIZE_W; regs.mmu_fault_addr = last_fault_for_exception_3; mmu030_state[0] = mmu030_state[1] = 0; mmu030_data_buffer_out = 0; Exception_build_stack_frame(last_fault_for_exception_3, currpc, ssw, vector_nr, 0x0b); used_exception_build_stack_frame = true; } else { // 68010 bus/address error (partially implemented only) uae_u16 ssw = (sv ? 4 : 0) | last_fc_for_exception_3; ssw |= last_di_for_exception_3 > 0 ? 0x0000 : (last_di_for_exception_3 < 0 ? (0x2000 | 0x1000) : 0x2000); ssw |= (!last_writeaccess_for_exception_3 && last_di_for_exception_3) ? 0x1000 : 0x000; // DF ssw |= (last_op_for_exception_3 & 0x10000) ? 0x0400 : 0x0000; // HB ssw |= last_size_for_exception_3 == 0 ? 0x0200 : 0x0000; // BY ssw |= last_writeaccess_for_exception_3 ? 0x0000 : 0x0100; // RW if (last_op_for_exception_3 & 0x20000) ssw &= 0x00ff; regs.mmu_fault_addr = last_fault_for_exception_3; Exception_build_stack_frame(oldpc, currpc, ssw, vector_nr, 0x08); used_exception_build_stack_frame = true; } #ifndef WINUAE_FOR_HATARI write_log (_T("Exception %d (%x) at %x -> %x!\n"), nr, regs.instruction_pc, currpc, get_long_debug (regs.vbr + 4 * vector_nr)); #else if (nr != 2 || M68000_IsVerboseBusError(currpc, last_fault_for_exception_3)) Log_Printf(LOG_WARN, "%s Error %s at address $%x, PC=$%x addr_e3=%x op_e3=%x\n", nr == 2 ? "Bus" : "Address", last_writeaccess_for_exception_3 ? "writing" : "reading", last_fault_for_exception_3, currpc, last_addr_for_exception_3, last_op_for_exception_3); #endif } else if (regs.m && interrupt) { /* M + Interrupt */ m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), vector_nr * 4); if (currprefs.cpu_model < 68060) { m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), currpc); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), regs.sr); regs.sr |= (1 << 13); regs.msp = m68k_areg(regs, 7); regs.m = 0; m68k_areg(regs, 7) = regs.isp; m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0x1000 + vector_nr * 4); } } else { Exception_build_stack_frame_common(oldpc, currpc, regs.mmu_ssw, nr, vector_nr); used_exception_build_stack_frame = true; } } else { nextpc = m68k_getpc (); #ifdef WINUAE_FOR_HATARI LOG_TRACE(TRACE_CPU_EXCEPTION, "cpu exception %d vector %x currpc %x buspc %x newpc %x fault_e3 %x op_e3 %x addr_e3 %x SR %x\n", nr, 4*vector_nr , currpc, regs.instruction_pc, STMemory_ReadLong (regs.vbr + 4*vector_nr), last_fault_for_exception_3, last_op_for_exception_3, last_addr_for_exception_3, regs.sr); #endif if (nr == 2 || nr == 3) { // 68000 bus/address error uae_u16 mode = (sv ? 4 : 0) | last_fc_for_exception_3; mode |= last_writeaccess_for_exception_3 ? 0 : 16; mode |= last_notinstruction_for_exception_3 ? 8 : 0; exception_in_exception = -1; Exception_build_68000_address_error_stack_frame(mode, last_op_for_exception_3, last_fault_for_exception_3, last_addr_for_exception_3); #ifndef WINUAE_FOR_HATARI write_log (_T("Exception %d (%x) at %x -> %x!\n"), nr, last_fault_for_exception_3, currpc, get_long_debug (regs.vbr + 4 * vector_nr)); #else if (nr != 2 || M68000_IsVerboseBusError(currpc, last_fault_for_exception_3)) Log_Printf(LOG_WARN, "%s Error %s at address $%x, PC=$%x addr_e3=%x op_e3=%x\n", nr == 2 ? "Bus" : "Address", last_writeaccess_for_exception_3 ? "writing" : "reading", last_fault_for_exception_3, currpc, last_addr_for_exception_3, last_op_for_exception_3); #endif goto kludge_me_do; } } if (!used_exception_build_stack_frame) { m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), nextpc); m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), regs.sr); } if (currprefs.cpu_model == 68060 && interrupt) { regs.m = 0; } if (currprefs.cpu_model == 68040 && nr == 3 && (last_op_for_exception_3 & 0x10000)) { // Weird 68040 bug with RTR and RTE. New SR when exception starts. Stacked SR is different! // Just replace it in stack, it is safe enough because we are in address error exception // any other exception would halt the CPU. x_put_word(m68k_areg(regs, 7), last_sr_for_exception3); } kludge_me_do: if ((regs.vbr & 1) && currprefs.cpu_model <= 68010) { cpu_halt(CPU_HALT_DOUBLE_FAULT); return; } if (interrupt) regs.intmask = nr - 24; newpc = x_get_long (regs.vbr + 4 * vector_nr); exception_in_exception = 0; if (newpc & 1) { if (nr == 2 || nr == 3) { cpu_halt(CPU_HALT_DOUBLE_FAULT); return; } // 4 idle, write pc low, write sr, write pc high, read vector high, read vector low x_do_cycles(adjust_cycles(6 * 4 * CYCLE_UNIT / 2)); if (currprefs.cpu_model == 68000) { regs.t1 = 0; MakeSR(); m68k_setpc(regs.vbr + 4 * vector_nr); if (interrupt) { regs.ir = nr; exception3_read_access(regs.ir | 0x20000 | 0x10000, newpc, sz_word, 2); } else { exception3_read_access(regs.ir | 0x40000 | 0x20000 | (g1 ? 0x10000 : 0), newpc, sz_word, 2); } } else if (currprefs.cpu_model == 68010) { regs.t1 = 0; MakeSR(); regs.write_buffer = 4 * vector_nr; regs.read_buffer = newpc; regs.irc = regs.read_buffer; exception3_read_access(regs.ir, newpc, sz_word, 2); } else { exception_check_trace(nr); exception3_notinstruction(regs.ir, newpc); } return; } add_approximate_exception_cycles(nr); m68k_setpc (newpc); cache_default_data &= ~CACHE_DISABLE_ALLOCATE; #ifdef WINUAE_FOR_HATARI /* Update IPL / interrupts state, in case a new interrupt happened during this exception */ CycInt_Process(); #endif #ifdef JIT if (currprefs.cachesize) { set_special(SPCFLAG_END_COMPILE); } #endif #ifdef DEBUGGER branch_stack_push(currpc, nextpc); #endif regs.ipl_pin = intlev(); ipl_fetch_now(); fill_prefetch (); exception_check_trace (nr); } // address = format $2 stack frame address field static void ExceptionX (int nr, uaecptr address, uaecptr oldpc) { uaecptr pc = m68k_getpc(); regs.exception = nr; regs.loop_mode = 0; #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_dma) { record_dma_event_data(DMA_EVENT_CPUINS, 0x20000); } #endif #endif if (cpu_tracer) { cputrace.state = nr; } if (!regs.s) { regs.instruction_pc_user_exception = pc; } #ifdef WINUAE_FOR_HATARI /* Handle Hatari GEM and BIOS traps */ if (nr == 0x22) { /* Intercept VDI & AES exceptions (Trap #2) */ if (bVdiAesIntercept && VDI_AES_Entry()) { /* Set 'PC' to address of 'VDI_OPCODE' illegal instruction. * This will call OpCode_VDI() after completion of Trap call! * Used to modify specific VDI return vectors contents. */ VDI_OldPC = m68k_getpc(); m68k_setpc(CART_VDI_OPCODE_ADDR); } } else if (nr == 0x2d) { /* Intercept BIOS (Trap #13) calls */ if (Bios()) { fill_prefetch (); regs.exception = 0; return; } } else if (nr == 0x2e) { /* Intercept XBIOS (Trap #14) calls */ if (XBios()) { fill_prefetch (); regs.exception = 0; return; } } #endif if (oldpc != 0xffffffff) { regs.instruction_pc = oldpc; } #ifdef DEBUGGER debug_exception(nr); #endif m68k_resumestopped(); #ifdef CPUEMU_13 if (currprefs.cpu_cycle_exact && currprefs.cpu_model <= 68010) Exception_ce000 (nr); else #endif { if (currprefs.cpu_model == 68060) { regs.buscr &= 0xa0000000; regs.buscr |= regs.buscr >> 1; } if (currprefs.mmu_model) { if (currprefs.cpu_model == 68030) Exception_mmu030(nr, m68k_getpc()); else Exception_mmu(nr, m68k_getpc()); } else { Exception_normal(nr); } } regs.exception = 0; if (cpu_tracer) { cputrace.state = 0; } } void REGPARAM2 Exception_cpu_oldpc(int nr, uaecptr oldpc) { bool t0 = currprefs.cpu_model >= 68020 && regs.t0 && !regs.t1; ExceptionX(nr, 0xffffffff, oldpc); // Check T0 trace // RTE format error ignores T0 trace if (nr != 14) { if (currprefs.cpu_model >= 68040 && internalexception(nr)) { t0 = false; } if (t0) { activate_trace(); } } } void REGPARAM2 Exception_cpu(int nr) { Exception_cpu_oldpc(nr, 0xffffffff); } void REGPARAM2 Exception(int nr) { ExceptionX(nr, 0xffffffff, 0xffffffff); } void REGPARAM2 ExceptionL(int nr, uaecptr address) { ExceptionX(nr, address, 0xffffffff); } static void bus_error(void) { TRY (prb2) { Exception (2); } CATCH (prb2) { cpu_halt (CPU_HALT_BUS_ERROR_DOUBLE_FAULT); } ENDTRY } static int get_ipl(void) { return regs.ipl[0]; } static void do_interrupt (int nr) { #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_dma) record_dma_event(DMA_EVENT_CPUIRQ); #endif if (inputrecord_debug & 2) { if (input_record > 0) inprec_recorddebug_cpu(2, 0); else if (input_play > 0) inprec_playdebug_cpu(2, 0); } assert (nr < 8 && nr >= 0); intlev_ack(nr); #endif for (;;) { Exception (nr + 24); if (!currprefs.cpu_compatible || currprefs.cpu_model == 68060) break; if (m68k_interrupt_delay) nr = get_ipl(); else nr = intlev(); if (nr <= 0 || regs.intmask >= nr) break; } doint (); } void NMI (void) { do_interrupt (7); } static void cpu_halt_clear(void) { regs.halted = 0; #ifndef WINUAE_FOR_HATARI if (gui_data.cpu_halted) { gui_data.cpu_halted = 0; gui_led(LED_CPU, 0, -1); } #endif } static void maybe_disable_fpu(void) { #ifndef WINUAE_FOR_HATARI if (currprefs.cpu_model == 68060 && currprefs.cpuboard_type == 0 && (rtarea_base != 0xf00000 || !need_uae_boot_rom(&currprefs))) { // disable FPU at reset if no 68060 accelerator board and no $f0 ROM. regs.pcr |= 2; } #endif if (!currprefs.fpu_model) { regs.pcr |= 2; } } static void m68k_reset_sr(void) { SET_XFLG ((regs.sr >> 4) & 1); SET_NFLG ((regs.sr >> 3) & 1); SET_ZFLG ((regs.sr >> 2) & 1); SET_VFLG ((regs.sr >> 1) & 1); SET_CFLG (regs.sr & 1); regs.t1 = (regs.sr >> 15) & 1; regs.t0 = (regs.sr >> 14) & 1; regs.s = (regs.sr >> 13) & 1; regs.m = (regs.sr >> 12) & 1; regs.intmask = (regs.sr >> 8) & 7; /* set stack pointer */ if (regs.s) m68k_areg (regs, 7) = regs.isp; else m68k_areg (regs, 7) = regs.usp; } static void m68k_reset2(bool hardreset) { uae_u32 v; //fprintf ( stderr,"m68k_reset2 hard=%d in pc=%x\n" , hardreset , regs.pc ); cpu_halt_clear(); regs.spcflags = 0; m68k_reset_delay = 0; regs.ipl[0] = regs.ipl[1] = regs.ipl_pin = 0; #ifndef WINUAE_FOR_HATARI for (int i = 0; i < IRQ_SOURCE_MAX; i++) { uae_interrupts2[i] = 0; uae_interrupts6[i] = 0; uae_interrupt = 0; } #endif // Force config changes (CPU speed) into effect on hard reset update_68k_cycles(); #ifdef SAVESTATE if (isrestore()) { m68k_reset_sr(); m68k_setpc_normal(regs.pc); return; } else { m68k_reset_delay = currprefs.reset_delay; set_special(SPCFLAG_CHECK); } #endif regs.s = 1; #ifndef WINUAE_FOR_HATARI if (currprefs.cpuboard_type) { uaecptr stack; v = cpuboard_get_reset_pc(&stack); m68k_areg(regs, 7) = stack; } else { v = get_long(4); m68k_areg(regs, 7) = get_long(0); } #else v = get_long (4); m68k_areg (regs, 7) = get_long (0); #endif m68k_setpc_normal(v); regs.m = 0; regs.stopped = 0; regs.t1 = 0; regs.t0 = 0; SET_ZFLG(0); SET_XFLG(0); SET_CFLG(0); SET_VFLG(0); SET_NFLG(0); regs.intmask = 7; regs.lastipl = 0; regs.vbr = regs.sfc = regs.dfc = 0; regs.irc = 0xffff; #ifdef FPUEMU fpu_reset(); #endif regs.caar = regs.cacr = 0; regs.itt0 = regs.itt1 = regs.dtt0 = regs.dtt1 = 0; regs.tcr = regs.mmusr = regs.urp = regs.srp = regs.buscr = 0; mmu_tt_modified(); if (currprefs.cpu_model == 68020) { regs.cacr |= 8; set_cpu_caches (false); } mmufixup[0].reg = -1; mmufixup[1].reg = -1; mmu030_cache_state = CACHE_ENABLE_ALL; mmu_cache_state = CACHE_ENABLE_ALL; if (currprefs.cpu_model >= 68040) { set_cpu_caches(false); } if (currprefs.mmu_model >= 68040) { mmu_reset(); mmu_set_tc(regs.tcr); mmu_set_super(regs.s != 0); } else if (currprefs.mmu_model == 68030) { mmu030_reset(hardreset || regs.halted); } else { #ifndef WINUAE_FOR_HATARI a3000_fakekick (0); #endif /* only (E)nable bit is zeroed when CPU is reset, A3000 SuperKickstart expects this */ fake_tc_030 &= ~0x80000000; fake_tt0_030 &= ~0x80000000; fake_tt1_030 &= ~0x80000000; if (hardreset || regs.halted) { fake_srp_030 = fake_crp_030 = 0; fake_tt0_030 = fake_tt1_030 = fake_tc_030 = 0; } fake_mmusr_030 = 0; } /* 68060 FPU is not compatible with 68040, * 68060 accelerators' boot ROM disables the FPU */ regs.pcr = 0; if (currprefs.cpu_model == 68060) { regs.pcr = currprefs.fpu_model == 68060 ? MC68060_PCR : MC68EC060_PCR; regs.pcr |= (currprefs.cpu060_revision & 0xff) << 8; maybe_disable_fpu(); } // regs.ce020memcycles = 0; regs.ce020startcycle = regs.ce020endcycle = 0; fill_prefetch(); //fprintf ( stderr,"m68k_reset2 out pc=%x\n" , regs.pc ); } void m68k_reset(void) { m68k_reset2(false); } #ifndef WINUAE_FOR_HATARI void cpu_change(int newmodel) { if (newmodel == currprefs.cpu_model) return; fallback_new_cpu_model = newmodel; cpu_halt(CPU_HALT_ACCELERATOR_CPU_FALLBACK); } void cpu_fallback(int mode) { int fallbackmodel; if (currprefs.chipset_mask & CSMASK_AGA) { fallbackmodel = 68020; } else { fallbackmodel = 68000; } if (mode < 0) { if (currprefs.cpu_model > fallbackmodel) { cpu_change(fallbackmodel); } else if (fallback_new_cpu_model) { cpu_change(fallback_new_cpu_model); } } else if (mode == 0) { cpu_change(fallbackmodel); } else if (mode) { if (fallback_cpu_model) { cpu_change(fallback_cpu_model); } } } static void cpu_do_fallback(void) { bool fallbackmode = false; if ((fallback_new_cpu_model < 68020 && !(currprefs.chipset_mask & CSMASK_AGA)) || (fallback_new_cpu_model == 68020 && (currprefs.chipset_mask & CSMASK_AGA))) { // -> 68000/68010 or 68EC020 fallback_cpu_model = currprefs.cpu_model; fallback_fpu_model = currprefs.fpu_model; fallback_mmu_model = currprefs.mmu_model; fallback_cpu_compatible = currprefs.cpu_compatible; fallback_cpu_address_space_24 = currprefs.address_space_24; changed_prefs.cpu_model = currprefs.cpu_model_fallback && fallback_new_cpu_model <= 68020 ? currprefs.cpu_model_fallback : fallback_new_cpu_model; changed_prefs.fpu_model = 0; changed_prefs.mmu_model = 0; changed_prefs.cpu_compatible = true; changed_prefs.address_space_24 = true; memcpy(&fallback_regs, ®s, sizeof(struct regstruct)); fallback_regs.pc = M68K_GETPC; fallbackmode = true; } else { // -> 68020+ changed_prefs.cpu_model = fallback_cpu_model; changed_prefs.fpu_model = fallback_fpu_model; changed_prefs.mmu_model = fallback_mmu_model; changed_prefs.cpu_compatible = fallback_cpu_compatible; changed_prefs.address_space_24 = fallback_cpu_address_space_24; fallback_cpu_model = 0; } init_m68k(); build_cpufunctbl(); m68k_reset2(false); if (!fallbackmode) { // restore original 68020+ memcpy(®s, &fallback_regs, sizeof(regs)); restore_banks(); memory_restore(); memory_map_dump(); m68k_setpc(fallback_regs.pc); } else { // 68000/010/EC020 memory_restore(); expansion_cpu_fallback(); memory_map_dump(); } } #endif static void m68k_reset_restore(void) { #ifndef WINUAE_FOR_HATARI // hardreset and 68000/68020 fallback mode? Restore original mode. if (fallback_cpu_model) { fallback_new_cpu_model = fallback_cpu_model; fallback_regs.pc = 0; cpu_do_fallback(); } #endif } void REGPARAM2 op_unimpl (uae_u32 opcode) { static int warned; if (warned < 1000) { write_log (_T("68060 unimplemented opcode %04X, PC=%08x SP=%08x\n"), opcode, regs.instruction_pc, regs.regs[15]); warned++; } ExceptionL (61, regs.instruction_pc); } uae_u32 REGPARAM2 op_illg (uae_u32 opcode) { uaecptr pc = m68k_getpc (); static int warned; #ifndef WINUAE_FOR_HATARI int inrom = in_rom (pc); int inrt = in_rtarea (pc); if (opcode == 0x4afc || opcode == 0xfc4a) { if (!valid_address(pc, 4) && valid_address(pc - 4, 4)) { // PC fell off the end of RAM bus_error(); return 4; } } // BKPT? if (opcode >= 0x4848 && opcode <= 0x484f && currprefs.cpu_model >= 68020) { // some boards hang because there is no break point cycle acknowledge if (currprefs.cs_bkpthang) { cpu_halt(CPU_HALT_BKPT); return 4; } } #ifdef DEBUGGER if (debugmem_illg(opcode)) { m68k_incpc_normal(2); return 4; } #endif if (cloanto_rom && (opcode & 0xF100) == 0x7100) { m68k_dreg (regs, (opcode >> 9) & 7) = (uae_s8)(opcode & 0xFF); m68k_incpc_normal (2); fill_prefetch (); return 4; } if (opcode == 0x4E7B && inrom) { if (get_long (0x10) == 0) { notify_user (NUMSG_KS68020); uae_restart (&currprefs, NULL); m68k_setstopped(1); return 4; } } #ifdef AUTOCONFIG if (opcode == 0xFF0D && inrt) { /* User-mode STOP replacement */ m68k_setstopped(1); return 4; } if ((opcode & 0xF000) == 0xA000 && inrt) { /* Calltrap. */ m68k_incpc_normal (2); m68k_handle_trap(opcode & 0xFFF); fill_prefetch (); return 4; } #endif #endif if ((opcode & 0xF000) == 0xF000) { // Missing MMU or FPU cpSAVE/cpRESTORE privilege check if (privileged_copro_instruction(opcode)) { Exception(8); } else { #ifndef WINUAE_FOR_HATARI if (warned < 20) { write_log(_T("B-Trap %04X at %08X -> %08X\n"), opcode, pc, get_long_debug(regs.vbr + 0x2c)); warned++; } #endif Exception(0xB); } //activate_debugger_new(); return 4; } if ((opcode & 0xF000) == 0xA000) { #ifndef WINUAE_FOR_HATARI if (warned < 20) { write_log(_T("A-Trap %04X at %08X -> %08X\n"), opcode, pc, get_long_debug(regs.vbr + 0x28)); warned++; } #endif Exception (0xA); //activate_debugger_new(); return 4; } if (warned < 20) { write_log (_T("Illegal instruction: %04x at %08X -> %08X\n"), opcode, pc, get_long_debug(regs.vbr + 0x10)); warned++; //activate_debugger_new(); } Exception (4); return 4; } void REGPARAM2 op_illg_noret(uae_u32 opcode) { op_illg(opcode); } #ifdef CPUEMU_0 static bool mmu_op30_invea(uae_u32 opcode) { int eamode = (opcode >> 3) & 7; int rreg = opcode & 7; // Dn, An, (An)+, -(An), immediate and PC-relative not allowed if (eamode == 0 || eamode == 1 || eamode == 3 || eamode == 4 || (eamode == 7 && rreg > 1)) return true; return false; } static bool mmu_op30fake_pmove (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int preg = (next >> 10) & 31; int rw = (next >> 9) & 1; int fd = (next >> 8) & 1; int unused = (next & 0xff); const TCHAR *reg = NULL; #ifndef WINUAE_FOR_HATARI uae_u32 otc = fake_tc_030; #endif int siz; if (mmu_op30_invea(opcode)) return true; // unused low 8 bits must be zeroed if (unused) return true; // read and fd set? if (rw && fd) return true; switch (preg) { case 0x10: // TC reg = _T("TC"); siz = 4; if (rw) x_put_long (extra, fake_tc_030); else fake_tc_030 = x_get_long (extra); break; case 0x12: // SRP reg = _T("SRP"); siz = 8; if (rw) { x_put_long (extra, fake_srp_030 >> 32); x_put_long (extra + 4, (uae_u32)fake_srp_030); } else { fake_srp_030 = (uae_u64)x_get_long (extra) << 32; fake_srp_030 |= x_get_long (extra + 4); } break; case 0x13: // CRP reg = _T("CRP"); siz = 8; if (rw) { x_put_long (extra, fake_crp_030 >> 32); x_put_long (extra + 4, (uae_u32)fake_crp_030); } else { fake_crp_030 = (uae_u64)x_get_long (extra) << 32; fake_crp_030 |= x_get_long (extra + 4); } break; case 0x18: // MMUSR if (fd) { // FD must be always zero when MMUSR read or write return true; } reg = _T("MMUSR"); siz = 2; if (rw) x_put_word (extra, fake_mmusr_030); else fake_mmusr_030 = x_get_word (extra); break; case 0x02: // TT0 reg = _T("TT0"); siz = 4; if (rw) x_put_long (extra, fake_tt0_030); else fake_tt0_030 = x_get_long (extra); break; case 0x03: // TT1 reg = _T("TT1"); siz = 4; if (rw) x_put_long (extra, fake_tt1_030); else fake_tt1_030 = x_get_long (extra); break; } if (!reg) return true; #if MMUOP_DEBUG > 0 { uae_u32 val; if (siz == 8) { uae_u32 val2 = x_get_long (extra); val = x_get_long (extra + 4); if (rw) write_log (_T("PMOVE %s,%08X%08X"), reg, val2, val); else write_log (_T("PMOVE %08X%08X,%s"), val2, val, reg); } else { if (siz == 4) val = x_get_long (extra); else val = x_get_word (extra); if (rw) write_log (_T("PMOVE %s,%08X"), reg, val); else write_log (_T("PMOVE %08X,%s"), val, reg); } write_log (_T(" PC=%08X\n"), pc); } #endif #ifndef WINUAE_FOR_HATARI if ((currprefs.cs_mbdmac & 1) && currprefs.mbresmem_low.size > 0) { if (otc != fake_tc_030) { a3000_fakekick (fake_tc_030 & 0x80000000); } } #endif return false; } static bool mmu_op30fake_ptest (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int eamode = (opcode >> 3) & 7; int rreg = opcode & 7; int level = (next&0x1C00)>>10; int a = (next >> 8) & 1; if (mmu_op30_invea(opcode)) return true; if (!level && a) return true; #if MMUOP_DEBUG > 0 TCHAR tmp[10]; tmp[0] = 0; if ((next >> 8) & 1) _stprintf (tmp, _T(",A%d"), (next >> 4) & 15); write_log (_T("PTEST%c %02X,%08X,#%X%s PC=%08X\n"), ((next >> 9) & 1) ? 'W' : 'R', (next & 15), extra, (next >> 10) & 7, tmp, pc); #endif fake_mmusr_030 = 0; return false; } static bool mmu_op30fake_pload (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int unused = (next & (0x100 | 0x80 | 0x40 | 0x20)); if (mmu_op30_invea(opcode)) return true; if (unused) return true; write_log(_T("PLOAD\n")); return false; } static bool mmu_op30fake_pflush (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int flushmode = (next >> 8) & 31; int fc = next & 31; int mask = (next >> 5) & 3; int fc_bits = next & 0x7f; TCHAR fname[100]; switch (flushmode) { case 0x00: case 0x02: return mmu_op30fake_pload(pc, opcode, next, extra); case 0x18: if (mmu_op30_invea(opcode)) return true; _stprintf (fname, _T("FC=%x MASK=%x EA=%08x"), fc, mask, 0); break; case 0x10: _stprintf (fname, _T("FC=%x MASK=%x"), fc, mask); break; case 0x04: if (fc_bits) return true; _tcscpy (fname, _T("ALL")); break; default: return true; } #if MMUOP_DEBUG > 0 write_log (_T("PFLUSH %s PC=%08X\n"), fname, pc); #endif return false; } // 68030 (68851) MMU instructions only bool mmu_op30(uaecptr pc, uae_u32 opcode, uae_u16 extra, uaecptr extraa) { int type = extra >> 13; int fline = 0; switch (type) { case 0: case 2: case 3: if (currprefs.mmu_model) fline = mmu_op30_pmove(pc, opcode, extra, extraa); else fline = mmu_op30fake_pmove(pc, opcode, extra, extraa); break; case 1: if (currprefs.mmu_model) fline = mmu_op30_pflush(pc, opcode, extra, extraa); else fline = mmu_op30fake_pflush(pc, opcode, extra, extraa); break; case 4: if (currprefs.mmu_model) fline = mmu_op30_ptest(pc, opcode, extra, extraa); else fline = mmu_op30fake_ptest(pc, opcode, extra, extraa); break; } if (fline > 0) { m68k_setpc(pc); op_illg(opcode); } return fline != 0; } /* check if an address matches a ttr */ static int fake_mmu_do_match_ttr(uae_u32 ttr, uaecptr addr, bool super) { if (ttr & MMU_TTR_BIT_ENABLED) { /* TTR enabled */ uae_u8 msb, mask; msb = ((addr ^ ttr) & MMU_TTR_LOGICAL_BASE) >> 24; mask = (ttr & MMU_TTR_LOGICAL_MASK) >> 16; if (!(msb & ~mask)) { if ((ttr & MMU_TTR_BIT_SFIELD_ENABLED) == 0) { if (((ttr & MMU_TTR_BIT_SFIELD_SUPER) == 0) != (super == 0)) { return TTR_NO_MATCH; } } return (ttr & MMU_TTR_BIT_WRITE_PROTECT) ? TTR_NO_WRITE : TTR_OK_MATCH; } } return TTR_NO_MATCH; } static int fake_mmu_match_ttr(uaecptr addr, bool super, bool data) { int res; if (data) { res = fake_mmu_do_match_ttr(regs.dtt0, addr, super); if (res == TTR_NO_MATCH) res = fake_mmu_do_match_ttr(regs.dtt1, addr, super); } else { res = fake_mmu_do_match_ttr(regs.itt0, addr, super); if (res == TTR_NO_MATCH) res = fake_mmu_do_match_ttr(regs.itt1, addr, super); } return res; } // 68040+ MMU instructions only void mmu_op (uae_u32 opcode, uae_u32 extra) { if (currprefs.mmu_model) { mmu_op_real (opcode, extra); return; } #if MMUOP_DEBUG > 1 write_log (_T("mmu_op %04X PC=%08X\n"), opcode, m68k_getpc ()); #endif if ((opcode & 0xFE0) == 0x0500) { /* PFLUSH */ regs.mmusr = 0; #if MMUOP_DEBUG > 0 write_log (_T("PFLUSH\n")); #endif return; } else if ((opcode & 0x0FD8) == 0x0548) { if (currprefs.cpu_model < 68060) { /* PTEST not in 68060 */ /* PTEST */ int regno = opcode & 7; uae_u32 addr = m68k_areg(regs, regno); bool write = (opcode & 32) == 0; bool super = (regs.dfc & 4) != 0; bool data = (regs.dfc & 3) != 2; regs.mmusr = 0; if (fake_mmu_match_ttr(addr, super, data) != TTR_NO_MATCH) { regs.mmusr = MMU_MMUSR_T | MMU_MMUSR_R; } regs.mmusr |= addr & 0xfffff000; #if MMUOP_DEBUG > 0 write_log (_T("PTEST%c %08x\n"), write ? 'W' : 'R', addr); #endif return; } } else if ((opcode & 0x0FB8) == 0x0588) { /* PLPA */ if (currprefs.cpu_model == 68060) { int regno = opcode & 7; uae_u32 addr = m68k_areg (regs, regno); int write = (opcode & 0x40) == 0; bool data = (regs.dfc & 3) != 2; bool super = (regs.dfc & 4) != 0; if (fake_mmu_match_ttr(addr, super, data) == TTR_NO_MATCH) { m68k_areg (regs, regno) = addr; } #if MMUOP_DEBUG > 0 write_log (_T("PLPA\n")); #endif return; } } #if MMUOP_DEBUG > 0 write_log (_T("Unknown MMU OP %04X\n"), opcode); #endif m68k_setpc_normal (m68k_getpc () - 2); op_illg (opcode); } #endif static void do_trace(void) { if (regs.stopped) { return; } // need to store PC because of branch instructions regs.trace_pc = m68k_getpc(); if (regs.t0 && !regs.t1 && currprefs.cpu_model >= 68020) { // this is obsolete return; } if (regs.t1) { activate_trace(); } } #ifndef WINUAE_FOR_HATARI static void int_request_do(bool i6) { if (i6) { if (currprefs.cs_compatible == CP_DRACO || currprefs.cs_compatible == CP_CASABLANCA) { draco_ext_interrupt(true); } else { INTREQ_f(0x8000 | 0x2000); } } else { if (currprefs.cs_compatible == CP_DRACO || currprefs.cs_compatible == CP_CASABLANCA) { draco_ext_interrupt(false); } else { INTREQ_f(0x8000 | 0x0008); } } } static void check_uae_int_request(void) { bool irq2 = false; bool irq6 = false; if (atomic_and(&uae_interrupt, 0)) { for (int i = 0; i < IRQ_SOURCE_MAX; i++) { if (!irq2 && uae_interrupts2[i]) { uae_atomic v = atomic_and(&uae_interrupts2[i], 0); if (v) { int_request_do(false); irq2 = true; } } if (!irq6 && uae_interrupts6[i]) { uae_atomic v = atomic_and(&uae_interrupts6[i], 0); if (v) { int_request_do(true); irq6 = true; } } } } if (uae_int_requested) { if (!irq2 && (uae_int_requested & 0x00ff)) { int_request_do(false); irq2 = true; } if (!irq6 && (uae_int_requested & 0xff00)) { int_request_do(true); irq6 = true; } if (uae_int_requested & 0xff0000) { #ifdef WITH_PPC if (!cpuboard_is_ppcboard_irq()) { #endif atomic_and(&uae_int_requested, ~0x010000); #ifdef WITH_PPC } #endif } } if (irq2 || irq6) { doint(); } } void safe_interrupt_set(int num, int id, bool i6) { if (!is_mainthread()) { set_special_exter(SPCFLAG_UAEINT); volatile uae_atomic *p; if (i6) p = &uae_interrupts6[num]; else p = &uae_interrupts2[num]; atomic_or(p, 1 << id); atomic_or(&uae_interrupt, 1); } else { if (currprefs.cs_compatible == CP_DRACO || currprefs.cs_compatible == CP_CASABLANCA) { draco_ext_interrupt(i6); } else { int inum = i6 ? 13 : 3; uae_u16 v = 1 << inum; if (currprefs.cpu_cycle_exact || currprefs.cpu_compatible) { INTREQ_INT(inum, 0); } else if (!(intreq & v)) { INTREQ_0(0x8000 | v); } } } } #endif int cpu_sleep_millis(int ms) { int ret = 0; #ifdef WITH_PPC int state = ppc_state; if (state) uae_ppc_spinlock_release(); #endif #ifdef WITH_X86 // if (x86_turbo_on) { // execute_other_cpu(read_processor_time() + vsynctimebase / 20); // } else { ret = sleep_millis_main(ms); // } #endif #ifdef WITH_PPC if (state) uae_ppc_spinlock_get(); #endif return ret; } #define PPC_HALTLOOP_SCANLINES 25 // ppc_cpu_idle // 0 = busy // 1-9 = wait, levels // 10 = max wait #ifndef WINUAE_FOR_HATARI static bool haltloop_do(int vsynctimeline, frame_time_t rpt_end, int lines) { int ovpos = vpos; while (lines-- >= 0) { ovpos = vpos; while (ovpos == vpos) { x_do_cycles(8 * CYCLE_UNIT); unset_special(SPCFLAG_UAEINT); check_uae_int_request(); #ifdef WITH_PPC ppc_interrupt(intlev()); uae_ppc_execute_check(); #endif if (regs.spcflags & (SPCFLAG_BRK | SPCFLAG_MODE_CHANGE)) { if (regs.spcflags & SPCFLAG_BRK) { unset_special(SPCFLAG_BRK); #ifdef DEBUGGER if (debugging) debug(); #endif } return true; } } // sync chipset with real time for (;;) { check_uae_int_request(); #ifdef WITH_PPC ppc_interrupt(intlev()); uae_ppc_execute_check(); #endif if (event_wait) break; frame_time_t d = read_processor_time() - rpt_end; if (d < -2 * vsynctimeline || d >= 0) break; } } return false; } #endif static bool haltloop(void) { #ifndef WINUAE_FOR_HATARI #ifdef WITH_PPC if (regs.halted < 0) { int rpt_end = 0; int ovpos = vpos; while (regs.halted) { int vsynctimeline = (int)(vsynctimebase / (maxvpos_display + 1)); int lines; frame_time_t rpt_scanline = read_processor_time(); frame_time_t rpt_end = rpt_scanline + vsynctimeline; // See expansion handling. // Dialog must be opened from main thread. if (regs.halted == -2) { regs.halted = -1; notify_user (NUMSG_UAEBOOTROM_PPC); } if (currprefs.ppc_cpu_idle) { int maxlines = 100 - (currprefs.ppc_cpu_idle - 1) * 10; int i; event_wait = false; for (i = 0; i < ev_max; i++) { if (i == ev_audio) continue; if (!eventtab[i].active) continue; if (eventtab[i].evtime - currcycle < maxlines * maxhpos * CYCLE_UNIT) break; } if (currprefs.ppc_cpu_idle >= 10 || (i == ev_max && vpos > 0 && vpos < maxvpos - maxlines)) { cpu_sleep_millis(1); } check_uae_int_request(); uae_ppc_execute_check(); lines = (int)(read_processor_time() - rpt_scanline) / vsynctimeline + 1; } else { event_wait = true; lines = 0; } if (lines > maxvpos / 2) lines = maxvpos / 2; if (haltloop_do(vsynctimeline, rpt_end, lines)) return true; } } else { #endif while (regs.halted) { static int prevvpos; if (vpos == 0 && prevvpos) { prevvpos = 0; cpu_sleep_millis(8); } if (vpos) prevvpos = 1; x_do_cycles(8 * CYCLE_UNIT); if (regs.spcflags) { if ((regs.spcflags & (SPCFLAG_BRK | SPCFLAG_MODE_CHANGE))) return true; } } #ifdef WITH_PPC } #endif return false; #else /* In Hatari, we don't use the halt state, we do a reset */ return false; #endif } #ifdef WITH_PPC static bool uae_ppc_poll_check_halt(void) { if (regs.halted) { if (haltloop()) return true; } return false; } #endif // check if interrupt active static int time_for_interrupt(void) { int ipl = get_ipl(); if (ipl > regs.intmask || ipl == 7) { return ipl; } return 0; } // ipl check mid next memory cycle void ipl_fetch_next_pre(void) { ipl_fetch_next(); regs.ipl_evt_pre = get_cycles(); regs.ipl_evt_pre_mode = 1; } void ipl_fetch_now_pre(void) { ipl_fetch_now(); regs.ipl_evt_pre = get_cycles(); regs.ipl_evt_pre_mode = 0; } // ipl check was early enough, interrupt possible after current instruction void ipl_fetch_now(void) { evt_t c = get_cycles(); regs.ipl_evt = c; regs.ipl[0] = regs.ipl_pin; regs.ipl[1] = 0; } // ipl check max 4 cycles before end of instruction. // interrupt starts after current instruction if IPL was changed earlier. // if not early enough: interrupt starts after following instruction. void ipl_fetch_next(void) { evt_t c = get_cycles(); evt_t cd = c - regs.ipl_pin_change_evt; evt_t cdp = c - regs.ipl_pin_change_evt_p; if (cd >= cpuipldelay4) { regs.ipl[0] = regs.ipl_pin; regs.ipl[1] = 0; } else if (cdp >= cpuipldelay2) { regs.ipl[0] = regs.ipl_pin_p; regs.ipl[1] = 0; } else { regs.ipl[1] = regs.ipl_pin; } } void intlev_load(void) { if (m68k_accurate_ipl) { ipl_fetch_now(); } doint(); } static void update_ipl(int ipl) { evt_t c = get_cycles(); regs.ipl_pin_change_evt_p = regs.ipl_pin_change_evt; regs.ipl_pin_p = regs.ipl_pin; regs.ipl_pin_change_evt = c; regs.ipl_pin = ipl; if (m68k_accurate_ipl) { // check if 68000/010 interrupt was detected mid memory access, // 2 cycles from start of memory cycle if (ipl > 0 && c == regs.ipl_evt_pre + cpuipldelay2) { if (regs.ipl_evt_pre_mode) { ipl_fetch_next(); } else { ipl_fetch_now(); } } } #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_dma) { record_dma_ipl(); } #endif #endif } static void doint_delayed(uae_u32 v) { update_ipl(v); } void doint(void) { #ifdef WITH_PPC if (ppc_state) { if (!ppc_interrupt(intlev())) return; } #endif int ipl = intlev(); if (regs.ipl_pin != ipl) { #ifndef WINUAE_FOR_HATARI // Paula does low to high IPL changes about 1.5 CPU clocks later than high to low. // -> CPU detects IPL change 1 CCK later if any IPL pin has high to low transition. // (In real world IPL is active low and delay is added if 0 to 1 transition) if (currprefs.cs_ipldelay && m68k_accurate_ipl && regs.ipl_pin >= 0 && ipl >= 0 && ( ((regs.ipl_pin & 1) && !(ipl & 1)) || ((regs.ipl_pin & 2) && !(ipl & 2)) || ((regs.ipl_pin & 4) && !(ipl & 4)) )) { event2_newevent_xx(-1, CYCLE_UNIT, ipl, doint_delayed); return; } #endif update_ipl(ipl); } //fprintf ( stderr , "doint1 %d ipl=%x ipl_pin=%x intmask=%x spcflags=%x\n" , m68k_interrupt_delay,regs.ipl, regs.ipl_pin , regs.intmask, regs.spcflags ); if (m68k_interrupt_delay) { //fprintf ( stderr , "doint2 %d ipl=%x ipl_pin=%x intmask=%x spcflags=%x\n" , m68k_interrupt_delay,regs.ipl, regs.ipl_pin , regs.intmask, regs.spcflags ); if (!m68k_accurate_ipl && regs.ipl_pin > regs.intmask) { set_special(SPCFLAG_INT); } return; } if (regs.ipl_pin > regs.intmask || currprefs.cachesize) { if (currprefs.cpu_compatible && currprefs.cpu_model < 68020) set_special(SPCFLAG_INT); else set_special(SPCFLAG_DOINT); } } static void check_debugger(void) { if (regs.spcflags & SPCFLAG_BRK) { unset_special(SPCFLAG_BRK); #ifdef DEBUGGER if (debugging) { debug(); } #endif } } static void debug_cpu_stop(void) { #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER record_dma_event(DMA_EVENT_CPUSTOP); if (time_for_interrupt()) { record_dma_event(DMA_EVENT_CPUSTOPIPL); } #endif #endif } static int do_specialties (int cycles) { uaecptr pc = m68k_getpc(); uae_atomic spcflags = regs.spcflags; if (spcflags & SPCFLAG_MODE_CHANGE) return 1; while (spcflags & SPCFLAG_CPUINRESET) { cpu_halt_clear(); x_do_cycles(4 * CYCLE_UNIT); spcflags = regs.spcflags; if (!(spcflags & SPCFLAG_CPUINRESET) || (spcflags & SPCFLAG_BRK) || (spcflags & SPCFLAG_MODE_CHANGE)) { break; } } #ifndef WINUAE_FOR_HATARI if (spcflags & SPCFLAG_CHECK) { if (regs.halted) { if (regs.halted == CPU_HALT_ACCELERATOR_CPU_FALLBACK) { return 1; } unset_special(SPCFLAG_CHECK); if (haltloop()) return 1; } if (m68k_reset_delay) { int vsynccnt = 60; int vsyncstate = -1; while (vsynccnt > 0 && !quit_program) { x_do_cycles(8 * CYCLE_UNIT); spcflags = regs.spcflags; if (vsync_counter != vsyncstate) { vsyncstate = vsync_counter; vsynccnt--; } } } m68k_reset_delay = 0; unset_special(SPCFLAG_CHECK); } #endif #ifdef ACTION_REPLAY #ifdef ACTION_REPLAY_HRTMON if ((spcflags & SPCFLAG_ACTION_REPLAY) && hrtmon_flag != ACTION_REPLAY_INACTIVE) { int isinhrt = pc >= hrtmem_start && pc < hrtmem_start + hrtmem_size; /* exit from HRTMon? */ if (hrtmon_flag == ACTION_REPLAY_ACTIVE && !isinhrt) hrtmon_hide(); /* HRTMon breakpoint? (not via IRQ7) */ if (hrtmon_flag == ACTION_REPLAY_IDLE && isinhrt) hrtmon_breakenter(); if (hrtmon_flag == ACTION_REPLAY_ACTIVATE) hrtmon_enter(); } #endif if ((spcflags & SPCFLAG_ACTION_REPLAY) && action_replay_flag != ACTION_REPLAY_INACTIVE) { /*if (action_replay_flag == ACTION_REPLAY_ACTIVE && !is_ar_pc_in_rom ())*/ /* write_log (_T("PC:%p\n"), m68k_getpc ());*/ if (action_replay_flag == ACTION_REPLAY_ACTIVATE || action_replay_flag == ACTION_REPLAY_DORESET) action_replay_enter(); if ((action_replay_flag == ACTION_REPLAY_HIDE || action_replay_flag == ACTION_REPLAY_ACTIVE) && !is_ar_pc_in_rom()) { action_replay_hide(); unset_special (SPCFLAG_ACTION_REPLAY); } if (action_replay_flag == ACTION_REPLAY_WAIT_PC) { /*write_log (_T("Waiting for PC: %p, current PC= %p\n"), wait_for_pc, m68k_getpc ());*/ if (pc == wait_for_pc) { action_replay_flag = ACTION_REPLAY_ACTIVATE; /* Activate after next instruction. */ } } } #endif #ifdef JIT if (spcflags & SPCFLAG_END_COMPILE) { unset_special(SPCFLAG_END_COMPILE); } #endif #ifndef WINUAE_FOR_HATARI while ((spcflags & SPCFLAG_BLTNASTY) && dmaen (DMA_BLITTER) && cycles > 0 && ((currprefs.waiting_blits && currprefs.cpu_model >= 68020) || !currprefs.blitter_cycle_exact)) { int c = blitnasty(); if (c < 0) { break; } else if (c > 0) { cycles -= c * CYCLE_UNIT * 2; if (cycles < CYCLE_UNIT) cycles = 0; } else { c = 4; } x_do_cycles(c * CYCLE_UNIT); spcflags = regs.spcflags; #ifdef WITH_PPC if (ppc_state) { if (uae_ppc_poll_check_halt()) return true; uae_ppc_execute_check(); } #endif } #endif if (spcflags & SPCFLAG_MMURESTART) { // can't have interrupt when 040/060 CPU reruns faulted instruction unset_special(SPCFLAG_MMURESTART); if (spcflags & SPCFLAG_TRACE) { do_trace(); } } else { #ifdef WINUAE_FOR_HATARI if (spcflags & SPCFLAG_BUSERROR) { /* We can not execute bus errors directly in the memory handler * functions since the PC should point to the address of the next * instruction, so we're executing the bus errors here: */ unset_special(SPCFLAG_BUSERROR); Exception(2); } #endif if (spcflags & SPCFLAG_DOTRACE) { Exception(9); } #ifndef WINUAE_FOR_HATARI if (spcflags & SPCFLAG_TRAP) { unset_special (SPCFLAG_TRAP); Exception(3); } #endif if (regs.spcflags & SPCFLAG_TRACE) do_trace(); #ifndef WINUAE_FOR_HATARI if (spcflags & SPCFLAG_UAEINT) { check_uae_int_request(); unset_special(SPCFLAG_UAEINT); } #endif #ifdef WINUAE_FOR_HATARI if (spcflags & SPCFLAG_MFP) { MFP_DelayIRQ (); /* Handle IRQ propagation */ M68000_Update_intlev (); /* Refresh the list of pending interrupts */ } #endif //fprintf ( stderr , "dospec1 %d %d spcflags=%x ipl=%x ipl_pin=%x intmask=%x\n" , m68k_interrupt_delay,time_for_interrupt() , spcflags , regs.ipl , regs.ipl_pin, regs.intmask ); if (m68k_interrupt_delay) { int ipl = time_for_interrupt(); if (ipl) { unset_special(SPCFLAG_INT); do_interrupt(ipl); } } else { if (spcflags & SPCFLAG_INT) { int intr = intlev(); unset_special (SPCFLAG_INT | SPCFLAG_DOINT); if (intr > regs.intmask || (intr == 7 && intr > regs.lastipl)) { do_interrupt(intr); } regs.lastipl = intr; } } //fprintf ( stderr , "dospec2 %d %d spcflags=%x ipl=%x ipl_pin=%x intmask=%x\n" , m68k_interrupt_delay,time_for_interrupt() , spcflags , regs.ipl , regs.ipl_pin, regs.intmask ); if (spcflags & SPCFLAG_DOINT) { unset_special(SPCFLAG_DOINT); set_special(SPCFLAG_INT); } //fprintf ( stderr , "dospec3 %d %d spcflags=%x ipl=%x ipl_pin=%x intmask=%x\n" , m68k_interrupt_delay,time_for_interrupt() , spcflags , regs.ipl , regs.ipl_pin, regs.intmask ); } #ifdef WINUAE_FOR_HATARI if (spcflags & SPCFLAG_DEBUGGER) DebugCpu_Check(); #endif if (spcflags & SPCFLAG_BRK) { unset_special(SPCFLAG_BRK); #ifdef DEBUGGER if (debugging) { debug(); } #endif #ifdef WINUAE_FOR_HATARI return 1; /* Exit the upper run_xxx() function */ #endif } return 0; } //static uae_u32 pcs[1000]; #if DEBUG_CD32CDTVIO static uae_u32 cd32nextpc, cd32request; static void out_cd32io2 (void) { uae_u32 request = cd32request; write_log (_T("%08x returned\n"), request); //write_log (_T("ACTUAL=%d ERROR=%d\n"), get_long (request + 32), get_byte (request + 31)); cd32nextpc = 0; cd32request = 0; } static void out_cd32io (uae_u32 pc) { TCHAR out[100]; int ioreq = 0; uae_u32 request = m68k_areg (regs, 1); if (pc == cd32nextpc) { out_cd32io2 (); return; } out[0] = 0; switch (pc) { case 0xe57cc0: case 0xf04c34: _stprintf (out, _T("opendevice")); break; case 0xe57ce6: case 0xf04c56: _stprintf (out, _T("closedevice")); break; case 0xe57e44: case 0xf04f2c: _stprintf (out, _T("beginio")); ioreq = 1; break; case 0xe57ef2: case 0xf0500e: _stprintf (out, _T("abortio")); ioreq = -1; break; } if (out[0] == 0) return; if (cd32request) write_log (_T("old request still not returned!\n")); cd32request = request; cd32nextpc = get_long (m68k_areg (regs, 7)); write_log (_T("%s A1=%08X\n"), out, request); if (ioreq) { static int cnt = 0; int cmd = get_word (request + 28); #if 0 if (cmd == 33) { uaecptr data = get_long (request + 40); write_log (_T("CD_CONFIG:\n")); for (int i = 0; i < 16; i++) { write_log (_T("%08X=%08X\n"), get_long (data), get_long (data + 4)); data += 8; } } #endif #if 0 if (cmd == 37) { cnt--; if (cnt <= 0) activate_debugger (); } #endif write_log (_T("CMD=%d DATA=%08X LEN=%d %OFF=%d PC=%x\n"), cmd, get_long (request + 40), get_long (request + 36), get_long (request + 44), M68K_GETPC); } if (ioreq < 0) ;//activate_debugger (); } #endif #ifndef CPUEMU_11 static void m68k_run_1 (void) { } #else /* It's really sad to have two almost identical functions for this, but we do it all for performance... :( This version emulates 68000's prefetch "cache" */ static void m68k_run_1 (void) { struct regstruct *r = ®s; bool exit = false; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_1\n"); CpuRunFuncNoret = false; #endif while (!exit) { check_debugger(); TRY (prb) { while (!exit) { r->opcode = r->ir; count_instr (r->opcode); #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif #if DEBUG_CD32CDTVIO out_cd32io (m68k_getpc ()); #endif #if 0 int pc = m68k_getpc (); if (pc == 0xdff002) write_log (_T("hip\n")); if (pc != pcs[0] && (pc < 0xd00000 || pc > 0x1000000)) { memmove (pcs + 1, pcs, 998 * 4); pcs[0] = pc; //write_log (_T("%08X-%04X "), pc, r->opcode); } #endif #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif r->instruction_pc = m68k_getpc (); cpu_cycles = (*cpufunctbl[r->opcode])(r->opcode) & 0xffff; if (!regs.loop_mode) regs.ird = regs.opcode; cpu_cycles = adjust_cycles (cpu_cycles); do_cycles(cpu_cycles); regs.instruction_cnt++; #ifdef WINUAE_FOR_HATARI /* Also add some extra cycles to simulate some wait state */ /* TODO [NP] do this in all m68k_run_xx() */ M68000_AddCyclesWithPairing(cpu_cycles * 2 / CYCLE_UNIT + WaitStateCycles); WaitStateCycles = 0; /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (r->spcflags || regs.ipl[0] > 0) { if (do_specialties (cpu_cycles)) exit = true; } regs.ipl[0] = regs.ipl_pin; #ifdef WINUAE_FOR_HATARI if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif if (!currprefs.cpu_compatible || (currprefs.cpu_cycle_exact && currprefs.cpu_model <= 68010)) exit = true; } } CATCH (prb) { bus_error(); if (r->spcflags) { if (do_specialties(cpu_cycles)) exit = true; } regs.ipl[0] = regs.ipl[1] = regs.ipl_pin; } ENDTRY } } #endif /* CPUEMU_11 */ #ifndef CPUEMU_13 static void m68k_run_1_ce (void) { } #else /* cycle-exact m68k_run () */ static void m68k_run_1_ce (void) { struct regstruct *r = ®s; bool first = true; bool exit = false; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_1_ce\n"); CpuRunCycleExact = true; CpuRunFuncNoret = true; #endif while (!exit) { check_debugger(); TRY (prb) { if (first) { if (cpu_tracer < 0) { memcpy (&r->regs, &cputrace.regs, 16 * sizeof (uae_u32)); r->ir = cputrace.ir; r->irc = cputrace.irc; r->ird = cputrace.ird; r->sr = cputrace.sr; r->usp = cputrace.usp; r->isp = cputrace.isp; r->intmask = cputrace.intmask; r->stopped = cputrace.stopped; r->read_buffer = cputrace.read_buffer; r->write_buffer = cputrace.write_buffer; m68k_setpc (cputrace.pc); if (!r->stopped) { if (cputrace.state > 1) { write_log (_T("CPU TRACE: EXCEPTION %d\n"), cputrace.state); Exception (cputrace.state); } else if (cputrace.state == 1) { write_log (_T("CPU TRACE: %04X\n"), cputrace.opcode); (*cpufunctbl_noret[cputrace.opcode])(cputrace.opcode); } } else { write_log (_T("CPU TRACE: STOPPED\n")); } set_cpu_tracer (false); goto cont; } set_cpu_tracer (false); first = false; } while (!exit) { r->opcode = r->ir; #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif #if DEBUG_CD32CDTVIO out_cd32io (m68k_getpc ()); #endif if (cpu_tracer) { memcpy (&cputrace.regs, &r->regs, 16 * sizeof (uae_u32)); cputrace.opcode = r->opcode; cputrace.ir = r->ir; cputrace.irc = r->irc; cputrace.ird = r->ird; cputrace.sr = r->sr; cputrace.usp = r->usp; cputrace.isp = r->isp; cputrace.intmask = r->intmask; cputrace.stopped = r->stopped; cputrace.read_buffer = r->read_buffer; cputrace.write_buffer = r->write_buffer; cputrace.state = 1; cputrace.pc = m68k_getpc (); cputrace.startcycles = get_cycles (); cputrace.memoryoffset = 0; cputrace.cyclecounter = cputrace.cyclecounter_pre = cputrace.cyclecounter_post = 0; cputrace.readcounter = cputrace.writecounter = 0; } #ifndef WINUAE_FOR_HATARI if (inputrecord_debug & 4) { if (input_record > 0) inprec_recorddebug_cpu (1); else if (input_play > 0) inprec_playdebug_cpu (1); } #endif #ifdef WINUAE_FOR_HATARI currcycle = 0; #endif #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif r->instruction_pc = m68k_getpc (); #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_dma) { record_dma_event_data(DMA_EVENT_CPUINS, r->opcode); } #endif #endif (*cpufunctbl_noret[r->opcode])(r->opcode); if (!regs.loop_mode) regs.ird = regs.opcode; regs.instruction_cnt++; wait_memory_cycles(); // TODO NP : ici, ou plus bas ? #ifdef WINUAE_FOR_HATARI //fprintf ( stderr, "cyc_1ce %d\n" , currcycle ); /* Flush all CE cycles so far to update PendingInterruptCount */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); currcycle = 0; CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif // wait_memory_cycles(); // TODO NP : plus haut ou ici ? if (cpu_tracer) { cputrace.state = 0; } cont: if (cputrace.needendcycles) { cputrace.needendcycles = 0; write_log(_T("STARTCYCLES=%016llx ENDCYCLES=%016llx\n"), cputrace.startcycles, get_cycles()); #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER log_dma_record (); #endif #endif } if (r->spcflags || regs.ipl[0] > 0) { if (do_specialties (0)) exit = true; } regs.ipl[0] = regs.ipl[1]; regs.ipl[1] = 0; #ifdef WINUAE_FOR_HATARI if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif if (!currprefs.cpu_cycle_exact || currprefs.cpu_model > 68010) exit = true; } } CATCH (prb) { bus_error(); if (r->spcflags) { if (do_specialties(0)) exit = true; } } ENDTRY } } #endif #if defined(CPUEMU_20) && defined(JIT) // emulate simple prefetch static uae_u16 get_word_020_prefetchf (uae_u32 pc) { if (pc == regs.prefetch020addr) { uae_u16 v = regs.prefetch020[0]; regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; regs.prefetch020[2] = x_get_word (pc + 6); regs.prefetch020addr += 2; return v; } else if (pc == regs.prefetch020addr + 2) { uae_u16 v = regs.prefetch020[1]; regs.prefetch020[0] = regs.prefetch020[2]; regs.prefetch020[1] = x_get_word (pc + 4); regs.prefetch020[2] = x_get_word (pc + 6); regs.prefetch020addr = pc + 2; return v; } else if (pc == regs.prefetch020addr + 4) { uae_u16 v = regs.prefetch020[2]; regs.prefetch020[0] = x_get_word (pc + 2); regs.prefetch020[1] = x_get_word (pc + 4); regs.prefetch020[2] = x_get_word (pc + 6); regs.prefetch020addr = pc + 2; return v; } else { regs.prefetch020addr = pc + 2; regs.prefetch020[0] = x_get_word (pc + 2); regs.prefetch020[1] = x_get_word (pc + 4); regs.prefetch020[2] = x_get_word (pc + 6); return x_get_word (pc); } } #endif #ifdef WITH_THREADED_CPU static volatile int cpu_thread_active; static uae_sem_t cpu_in_sema, cpu_out_sema, cpu_wakeup_sema; static volatile int cpu_thread_ilvl; static volatile uae_u32 cpu_thread_indirect_mode; static volatile uae_u32 cpu_thread_indirect_addr; static volatile uae_u32 cpu_thread_indirect_val; static volatile uae_u32 cpu_thread_indirect_size; static volatile uae_u32 cpu_thread_reset; static uae_thread_id cpu_thread_tid; static bool m68k_cs_initialized; static int do_specialties_thread(void) { uae_atomic spcflags = regs.spcflags; if (spcflags & SPCFLAG_MODE_CHANGE) return 1; #ifdef JIT if (currprefs.cachesize) { unset_special(SPCFLAG_END_COMPILE); } #endif if (spcflags & SPCFLAG_DOTRACE) Exception(9); if (spcflags & SPCFLAG_TRAP) { unset_special(SPCFLAG_TRAP); Exception(3); } if (spcflags & SPCFLAG_TRACE) do_trace(); for (;;) { if (spcflags & (SPCFLAG_BRK | SPCFLAG_MODE_CHANGE)) { return 1; } int ilvl = cpu_thread_ilvl; if (ilvl > 0 && (ilvl > regs.intmask || ilvl == 7)) { do_interrupt(ilvl); } break; } return 0; } static void init_cpu_thread(void) { if (!currprefs.cpu_thread) return; if (m68k_cs_initialized) return; uae_sem_init(&cpu_in_sema, 0, 0); uae_sem_init(&cpu_out_sema, 0, 0); uae_sem_init(&cpu_wakeup_sema, 0, 0); m68k_cs_initialized = true; } extern addrbank *thread_mem_banks[MEMORY_BANKS]; uae_u32 process_cpu_indirect_memory_read(uae_u32 addr, int size) { // Do direct access if call is from filesystem etc thread if (cpu_thread_tid != uae_thread_get_id()) { uae_u32 data = 0; addrbank *ab = thread_mem_banks[bankindex(addr)]; switch (size) { case 0: data = ab->bget(addr) & 0xff; break; case 1: data = ab->wget(addr) & 0xffff; break; case 2: data = ab->lget(addr); break; } return data; } cpu_thread_indirect_mode = 2; cpu_thread_indirect_addr = addr; cpu_thread_indirect_size = size; uae_sem_post(&cpu_out_sema); uae_sem_wait(&cpu_in_sema); cpu_thread_indirect_mode = 0xfe; return cpu_thread_indirect_val; } void process_cpu_indirect_memory_write(uae_u32 addr, uae_u32 data, int size) { if (cpu_thread_tid != uae_thread_get_id()) { addrbank *ab = thread_mem_banks[bankindex(addr)]; switch (size) { case 0: ab->bput(addr, data & 0xff); break; case 1: ab->wput(addr, data & 0xffff); break; case 2: ab->lput(addr, data); break; } return; } cpu_thread_indirect_mode = 1; cpu_thread_indirect_addr = addr; cpu_thread_indirect_size = size; cpu_thread_indirect_val = data; uae_sem_post(&cpu_out_sema); uae_sem_wait(&cpu_in_sema); cpu_thread_indirect_mode = 0xff; } static void run_cpu_thread(void (*f)(void *)) { int framecnt = -1; int vp = 0; int intlev_prev = 0; cpu_thread_active = 0; uae_sem_init(&cpu_in_sema, 0, 0); uae_sem_init(&cpu_out_sema, 0, 0); uae_sem_init(&cpu_wakeup_sema, 0, 0); if (!uae_start_thread(_T("cpu"), f, NULL, NULL)) return; while (!cpu_thread_active) { sleep_millis(1); } while (!(regs.spcflags & SPCFLAG_MODE_CHANGE)) { int maxperloop = 10; while (!uae_sem_trywait(&cpu_out_sema)) { uae_u32 addr, data, size, mode; addr = cpu_thread_indirect_addr; data = cpu_thread_indirect_val; size = cpu_thread_indirect_size; mode = cpu_thread_indirect_mode; switch(mode) { case 1: { addrbank *ab = thread_mem_banks[bankindex(addr)]; switch (size) { case 0: ab->bput(addr, data & 0xff); break; case 1: ab->wput(addr, data & 0xffff); break; case 2: ab->lput(addr, data); break; } uae_sem_post(&cpu_in_sema); break; } case 2: { addrbank *ab = thread_mem_banks[bankindex(addr)]; switch (size) { case 0: data = ab->bget(addr) & 0xff; break; case 1: data = ab->wget(addr) & 0xffff; break; case 2: data = ab->lget(addr); break; } cpu_thread_indirect_val = data; uae_sem_post(&cpu_in_sema); break; } default: write_log(_T("cpu_thread_indirect_mode=%08x!\n"), mode); break; } if (maxperloop-- < 0) break; } if (framecnt != vsync_counter) { framecnt = vsync_counter; } if (cpu_thread_reset) { bool hardreset = cpu_thread_reset & 2; bool keyboardreset = cpu_thread_reset & 4; custom_reset(hardreset, keyboardreset); cpu_thread_reset = 0; uae_sem_post(&cpu_in_sema); } if (regs.spcflags & SPCFLAG_BRK) { unset_special(SPCFLAG_BRK); #ifdef DEBUGGER if (debugging) { debug(); } #endif } if (vp == vpos) { do_cycles((maxhpos / 2) * CYCLE_UNIT); check_uae_int_request(); if (regs.spcflags & (SPCFLAG_INT | SPCFLAG_DOINT)) { int intr = intlev(); unset_special(SPCFLAG_INT | SPCFLAG_DOINT); if (intr > 0) { cpu_thread_ilvl = intr; cycles_do_special(); uae_sem_post(&cpu_wakeup_sema); } else { cpu_thread_ilvl = 0; } } continue; } frame_time_t next = vsyncmintimepre + (vsynctimebase * vpos / (maxvpos + 1)); frame_time_t c = read_processor_time(); if (next - c > 0 && next - c < vsyncmaxtime * 2) continue; vp = vpos; } while (cpu_thread_active) { uae_sem_post(&cpu_in_sema); uae_sem_post(&cpu_wakeup_sema); sleep_millis(1); } } #endif #ifndef WINUAE_FOR_HATARI static void custom_reset_cpu(bool hardreset, bool keyboardreset) { #ifdef WITH_THREADED_CPU if (cpu_thread_tid != uae_thread_get_id()) { custom_reset(hardreset, keyboardreset); return; } cpu_thread_reset = 1 | (hardreset ? 2 : 0) | (keyboardreset ? 4 : 0); uae_sem_post(&cpu_wakeup_sema); uae_sem_wait(&cpu_in_sema); #else custom_reset(hardreset, keyboardreset); #endif } #endif #ifdef JIT /* Completely different run_2 replacement */ void do_nothing (void) { if (!currprefs.cpu_thread) { /* What did you expect this to do? */ do_cycles (0); /* I bet you didn't expect *that* ;-) */ } } static uae_u32 get_jit_opcode(void) { uae_u32 opcode; if (currprefs.cpu_compatible) { opcode = get_word_020_prefetchf(m68k_getpc()); #ifdef HAVE_GET_WORD_UNSWAPPED opcode = do_byteswap_16(opcode); #endif } else { #ifdef HAVE_GET_WORD_UNSWAPPED opcode = do_get_mem_word_unswapped((uae_u16 *)get_real_address(m68k_getpc())); #else opcode = x_get_iword(0); #endif } return opcode; } void exec_nostats (void) { struct regstruct *r = ®s; for (;;) { r->opcode = get_jit_opcode(); (*cpufunctbl[r->opcode])(r->opcode); cpu_cycles = 4 * CYCLE_UNIT; // adjust_cycles(cpu_cycles); if (!currprefs.cpu_thread) { do_cycles(cpu_cycles); #ifdef WITH_PPC if (ppc_state) ppc_interrupt(intlev()); #endif } #ifdef WINUAE_FOR_HATARI if (end_block(r->opcode) || r->spcflags) #else if (end_block(r->opcode) || r->spcflags || uae_int_requested) #endif return; /* We will deal with the spcflags in the caller */ } } void execute_normal(void) { struct regstruct *r = ®s; int blocklen; cpu_history pc_hist[MAXRUN]; int total_cycles; if (check_for_cache_miss ()) return; total_cycles = 0; blocklen = 0; start_pc_p = r->pc_oldp; start_pc = r->pc; for (;;) { /* Take note: This is the do-it-normal loop */ r->opcode = get_jit_opcode(); special_mem = special_mem_default; pc_hist[blocklen].location = (uae_u16*)r->pc_p; (*cpufunctbl[r->opcode])(r->opcode); cpu_cycles = 4 * CYCLE_UNIT; // cpu_cycles = adjust_cycles(cpu_cycles); if (!currprefs.cpu_thread) { do_cycles(cpu_cycles); } total_cycles += cpu_cycles; pc_hist[blocklen].specmem = special_mem; blocklen++; #ifdef WINUAE_FOR_HATARI if (end_block (r->opcode) || blocklen >= MAXRUN || r->spcflags) { #else if (end_block (r->opcode) || blocklen >= MAXRUN || r->spcflags || uae_int_requested) { #endif compile_block (pc_hist, blocklen, total_cycles); return; /* We will deal with the spcflags in the caller */ } /* No need to check regs.spcflags, because if they were set, we'd have ended up inside that "if" */ #ifdef WITH_PPC if (ppc_state) ppc_interrupt(intlev()); #endif } } typedef void compiled_handler (void); #ifdef WITH_THREADED_CPU static void cpu_thread_run_jit(void *v) { cpu_thread_tid = uae_thread_get_id(); cpu_thread_active = 1; #ifdef USE_STRUCTURED_EXCEPTION_HANDLING __try #endif { for (;;) { ((compiled_handler*)(pushall_call_handler))(); /* Whenever we return from that, we should check spcflags */ if (regs.spcflags || cpu_thread_ilvl > 0) { if (do_specialties_thread()) { break; } } } } #ifdef USE_STRUCTURED_EXCEPTION_HANDLING #ifdef JIT __except (EvalException(GetExceptionInformation())) #else __except (DummyException(GetExceptionInformation(), GetExceptionCode())) #endif { // EvalException does the good stuff... } #endif cpu_thread_active = 0; } #endif static void m68k_run_jit(void) { #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_jit\n"); CpuRunFuncNoret = false; #endif #ifdef WITH_THREADED_CPU if (currprefs.cpu_thread) { run_cpu_thread(cpu_thread_run_jit); return; } #endif if (regs.spcflags) { if (do_specialties(0)) { return; } } for (;;) { #ifdef USE_STRUCTURED_EXCEPTION_HANDLING __try { #endif for (;;) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif ((compiled_handler*)(pushall_call_handler))(); /* Whenever we return from that, we should check spcflags */ check_uae_int_request(); if (regs.spcflags) { if (do_specialties(0)) { STOPTRY; return; } } // If T0, T1 or M got set: run normal emulation loop if (regs.t0 || regs.t1 || regs.m) { flush_icache(3); struct regstruct *r = ®s; bool exit = false; check_debugger(); while (!exit && (regs.t0 || regs.t1 || regs.m)) { r->instruction_pc = m68k_getpc(); r->opcode = x_get_iword(0); (*cpufunctbl[r->opcode])(r->opcode); count_instr(r->opcode); do_cycles(4 * CYCLE_UNIT); if (r->spcflags) { if (do_specialties(cpu_cycles)) exit = true; } } unset_special(SPCFLAG_END_COMPILE); } } #ifdef USE_STRUCTURED_EXCEPTION_HANDLING } __except (EvalException(GetExceptionInformation())) { // Something very bad happened, generate fake bus error exception // Either emulation continues normally or crashes. // Without this it would have crashed in any case.. uaecptr pc = M68K_GETPC; write_log(_T("Unhandled JIT exception! PC=%08x\n"), pc); if (pc & 1) Exception(3); else Exception(2); } #endif } } #endif /* JIT */ #ifndef CPUEMU_0 static void m68k_run_2 (void) { } #else static void opcodedebug (uae_u32 pc, uae_u16 opcode, bool full) { struct mnemolookup *lookup; struct instr *dp; uae_u32 addr; int fault; if (cpufunctbl[opcode] == op_illg_1) opcode = 0x4AFC; dp = table68k + opcode; for (lookup = lookuptab;lookup->mnemo != dp->mnemo; lookup++) ; fault = 0; TRY(prb) { addr = mmu_translate (pc, 0, (regs.mmu_ssw & 4) ? 1 : 0, 0, 0, sz_word); } CATCH (prb) { fault = 1; } ENDTRY if (!fault) { #ifdef DEBUGGER TCHAR buf[100]; if (full) write_log (_T("mmufixup=%d %04x %04x\n"), mmufixup[0].reg, regs.wb3_status, regs.mmu_ssw); m68k_disasm_2(buf, sizeof buf / sizeof (TCHAR), addr, NULL, 0, NULL, 1, NULL, NULL, 0xffffffff, 0); write_log (_T("%s\n"), buf); if (full) m68k_dumpstate(NULL, 0xffffffff); #endif } } static void check_halt(void) { if (regs.halted) do_specialties (0); } void cpu_inreset(void) { set_special(SPCFLAG_CPUINRESET); regs.s = 1; regs.intmask = 7; MakeSR(); } void cpu_halt(int id) { #ifndef WINUAE_FOR_HATARI // id < 0: m68k halted, PPC active. // id > 0: emulation halted. if (!regs.halted) { write_log(_T("CPU halted: reason = %d PC=%08x\n"), id, M68K_GETPC); if (currprefs.crash_auto_reset) { write_log(_T("Forcing hard reset\n")); uae_reset(true, false); quit_program = -quit_program; set_special(SPCFLAG_BRK | SPCFLAG_MODE_CHANGE); return; } regs.halted = id; gui_data.cpu_halted = id; gui_led(LED_CPU, 0, -1); if (id >= 0) { regs.intmask = 7; MakeSR(); audio_deactivate(); #ifdef DEBUGGER if (debugging) activate_debugger(); #endif } } set_special(SPCFLAG_CHECK); #else Dialog_HaltDlg(); #endif } #ifdef CPUEMU_33 /* [NP] TODO : use 68060 in Hatari ? with DSP ? */ /* MMU 68060 */ static void m68k_run_mmu060 (void) { struct flag_struct f; int halt = 0; check_halt(); #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_mmu060\n"); CpuRunFuncNoret = false; #endif while (!halt) { check_debugger(); TRY (prb) { for (;;) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif f.cznv = regflags.cznv; f.x = regflags.x; regs.instruction_pc = m68k_getpc (); do_cycles(cpu_cycles); mmu_opcode = -1; mmu060_state = 0; mmu_opcode = regs.opcode = x_prefetch (0); mmu060_state = 1; count_instr (regs.opcode); cpu_cycles = (*cpufunctbl[regs.opcode])(regs.opcode); cpu_cycles = adjust_cycles(cpu_cycles); regs.instruction_cnt++; #ifdef WINUAE_FOR_HATARI M68000_AddCycles(cpu_cycles * 2 / CYCLE_UNIT); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (regs.spcflags) { if (do_specialties(cpu_cycles)) { STOPTRY; return; } } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run(2 * cpu_cycles * 2 / CYCLE_UNIT); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH (prb) { m68k_setpci (regs.instruction_pc); regflags.cznv = f.cznv; regflags.x = f.x; cpu_restore_fixup(); TRY (prb2) { Exception (prb); } CATCH (prb2) { halt = 1; } ENDTRY } ENDTRY } cpu_halt(halt); } #endif #ifdef CPUEMU_31 /* Aranym MMU 68040 */ static void m68k_run_mmu040 (void) { struct flag_struct f; int halt = 0; check_halt(); #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_mmu040\n"); CpuRunFuncNoret = false; #endif while (!halt) { check_debugger(); TRY (prb) { for (;;) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif f.cznv = regflags.cznv; f.x = regflags.x; mmu_restart = true; regs.instruction_pc = m68k_getpc (); do_cycles(cpu_cycles); mmu_opcode = -1; mmu_opcode = regs.opcode = x_prefetch (0); count_instr (regs.opcode); cpu_cycles = (*cpufunctbl[regs.opcode])(regs.opcode); cpu_cycles = adjust_cycles(cpu_cycles); regs.instruction_cnt++; regs.ce020extracycles++; #ifdef WINUAE_FOR_HATARI M68000_AddCycles(cpu_cycles * 2 / CYCLE_UNIT); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (regs.spcflags) { if (do_specialties(cpu_cycles)) { STOPTRY; return; } } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run(2 * cpu_cycles * 2 / CYCLE_UNIT); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH (prb) { if (mmu_restart) { /* restore state if instruction restart */ regflags.cznv = f.cznv; regflags.x = f.x; m68k_setpci (regs.instruction_pc); } cpu_restore_fixup(); TRY (prb2) { Exception (prb); } CATCH (prb2) { halt = 1; } ENDTRY } ENDTRY } cpu_halt(halt); } #endif #ifdef CPUEMU_32 // Previous MMU 68030 static void m68k_run_mmu030 (void) { struct flag_struct f; int halt = 0; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_mmu030\n"); CpuRunFuncNoret = false; if ( currprefs.cpu_cycle_exact ) /* m68k_run_mmu030 can run with CE mode ON or OFF */ { CpuRunCycleExact = true; CpuRunFuncNoret = true; } #endif mmu030_opcode_stageb = -1; mmu030_fake_prefetch = -1; check_halt(); while(!halt) { check_debugger(); TRY (prb) { for (;;) { int cnt; insretry: regs.instruction_pc = m68k_getpc (); f.cznv = regflags.cznv; f.x = regflags.x; mmu030_state[0] = mmu030_state[1] = mmu030_state[2] = 0; mmu030_opcode = -1; if (mmu030_fake_prefetch >= 0) { // use fake prefetch opcode only if mapping changed uaecptr new_addr = mmu030_translate(regs.instruction_pc, regs.s != 0, false, false); if (mmu030_fake_prefetch_addr != new_addr) { regs.opcode = mmu030_fake_prefetch; write_log(_T("MMU030 fake prefetch remap: %04x, %08x -> %08x\n"), mmu030_fake_prefetch, mmu030_fake_prefetch_addr, new_addr); } else { if (mmu030_opcode_stageb < 0) { regs.opcode = x_prefetch (0); } else { regs.opcode = mmu030_opcode_stageb; mmu030_opcode_stageb = -1; } } mmu030_fake_prefetch = -1; } else if (mmu030_opcode_stageb < 0) { if (currprefs.cpu_compatible) regs.opcode = regs.irc; else regs.opcode = x_prefetch (0); } else { regs.opcode = mmu030_opcode_stageb; mmu030_opcode_stageb = -1; } mmu030_opcode = regs.opcode; mmu030_idx_done = 0; cnt = 50; for (;;) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm_mmu030(); } #endif regs.opcode = regs.irc = mmu030_opcode; mmu030_idx = 0; mmu030_retry = false; if (!currprefs.cpu_cycle_exact) { count_instr (regs.opcode); do_cycles(cpu_cycles); cpu_cycles = (*cpufunctbl[regs.opcode])(regs.opcode); } else { #ifdef WINUAE_FOR_HATARI currcycle = 0; #endif (*cpufunctbl_noret[regs.opcode])(regs.opcode); wait_memory_cycles(); } cnt--; // so that we don't get in infinite loop if things go horribly wrong if (!mmu030_retry) break; if (cnt < 0) { cpu_halt (CPU_HALT_CPU_STUCK); break; } if (mmu030_retry && mmu030_opcode == -1) goto insretry; // urgh } mmu030_opcode = -1; if (!currprefs.cpu_cycle_exact) { cpu_cycles = adjust_cycles (cpu_cycles); regs.instruction_cnt++; #ifdef WINUAE_FOR_HATARI M68000_AddCycles(cpu_cycles * 2 / CYCLE_UNIT); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (regs.spcflags) { if (do_specialties (cpu_cycles)) { STOPTRY; return; } } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run(2 * cpu_cycles * 2 / CYCLE_UNIT); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } #endif } else { #ifdef WINUAE_FOR_HATARI //fprintf ( stderr, "cyc_2ce %d\n" , currcycle ); /* Flush all CE cycles so far to update PendingInterruptCount */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); int dsp_cycles = 2 * currcycle * 2 / CYCLE_UNIT; // FIXME : remove this when using DSP_CyclesGlobalClockCounter in DSP_Run currcycle = 0; /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif regs.instruction_cnt++; regs.ipl[0] = regs.ipl_pin; if (regs.spcflags || time_for_interrupt ()) { if (do_specialties(0)) { STOPTRY; return; } } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { //fprintf ( stderr, "dsp cyc_2ce %d\n" , currcycle ); DSP_Run( dsp_cycles ); //fprintf ( stderr, "dsp cyc_2ce %d - %d\n" , currcycle * 2 / CYCLE_UNIT , (CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter) ); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } #endif } #ifdef WINUAE_FOR_HATARI if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH (prb) { if (mmu030_opcode == -1) { // full prefetch fill access fault mmufixup[0].reg = -1; mmufixup[1].reg = -1; } else if (mmu030_state[1] & MMU030_STATEFLAG1_LASTWRITE) { mmufixup[0].reg = -1; mmufixup[1].reg = -1; } else { regflags.cznv = f.cznv; regflags.x = f.x; cpu_restore_fixup(); } m68k_setpci (regs.instruction_pc); TRY (prb2) { Exception (prb); } CATCH (prb2) { halt = 1; } ENDTRY } ENDTRY } cpu_halt (halt); } #endif /* "cycle exact" 68040/060 */ static void m68k_run_3ce (void) { struct regstruct *r = ®s; bool exit = false; int extracycles = 0; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_3ce\n"); CpuRunCycleExact = true; CpuRunFuncNoret = true; #endif while (!exit) { check_debugger(); TRY(prb) { while (!exit) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } currcycle = CYCLE_UNIT / 2; /* Assume at least 1 cycle per instruction */ #endif evt_t c = get_cycles(); r->instruction_pc = m68k_getpc(); r->opcode = get_iword_cache_040(0); // "prefetch" if (regs.cacr & 0x8000) fill_icache040(r->instruction_pc + 16); #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif (*cpufunctbl_noret[r->opcode])(r->opcode); #ifdef WINUAE_FOR_HATARI //fprintf ( stderr, "cyc_3ce %ld\n" , currcycle ); /* Flush all CE cycles so far to update PendingInterruptCount */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); int dsp_cycles = 2 * currcycle * 2 / CYCLE_UNIT; // FIXME : remove this when using DSP_CyclesGlobalClockCounter in DSP_Run currcycle = 0; /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (r->spcflags) { if (do_specialties (0)) exit = true; } // workaround for situation when all accesses are cached if (c == get_cycles()) { extracycles++; if (extracycles >= 4) { extracycles = 0; #ifdef WINUAE_FOR_HATARI x_do_cycles(CYCLE_UNIT); #else M68000_AddCycles_CE ( 2 ); #endif } } regs.instruction_cnt++; #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run( dsp_cycles ); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH(prb) { bus_error(); if (r->spcflags) { if (do_specialties(0)) exit = true; } } ENDTRY } } /* "prefetch" 68040/060 */ static void m68k_run_3p(void) { struct regstruct *r = ®s; bool exit = false; int cycles; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_3p\n"); CpuRunFuncNoret = true; #endif while (!exit) { check_debugger(); TRY(prb) { while (!exit) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif r->instruction_pc = m68k_getpc(); r->opcode = get_iword_cache_040(0); // "prefetch" if (regs.cacr & 0x8000) fill_icache040(r->instruction_pc + 16); #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif (*cpufunctbl_noret[r->opcode])(r->opcode); #ifndef WINUAE_FOR_HATARI cpu_cycles = 2 * CYCLE_UNIT; cycles = adjust_cycles(cpu_cycles); regs.instruction_cnt++; x_do_cycles(cycles); #else cycles = cpu_cycles = 2 * CYCLE_UNIT; M68000_AddCycles_CE(cycles * 2 / CYCLE_UNIT); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } int dsp_cycles = 2 * cycles * 2 / CYCLE_UNIT; // FIXME : remove this when using DSP_CyclesGlobalClockCounter in DSP_Run currcycle = 0; /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (r->spcflags) { if (do_specialties(0)) exit = true; } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run( dsp_cycles ); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH(prb) { bus_error(); if (r->spcflags) { if (do_specialties(0)) exit = true; } } ENDTRY } } /* "cycle exact" 68020/030 */ STATIC_INLINE struct cache030 *getcache030 (struct cache030 *cp, uaecptr addr, uae_u32 *tagp, int *lwsp); static void m68k_run_2ce (void) { struct regstruct *r = ®s; bool exit = false; bool first = true; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_2ce\n"); CpuRunCycleExact = true; CpuRunFuncNoret = true; #endif while (!exit) { check_debugger(); TRY(prb) { if (first) { if (cpu_tracer < 0) { memcpy (&r->regs, &cputrace.regs, 16 * sizeof (uae_u32)); r->ir = cputrace.ir; r->irc = cputrace.irc; r->sr = cputrace.sr; r->usp = cputrace.usp; r->isp = cputrace.isp; r->intmask = cputrace.intmask; r->stopped = cputrace.stopped; r->msp = cputrace.msp; r->vbr = cputrace.vbr; r->caar = cputrace.caar; r->cacr = cputrace.cacr; r->cacheholdingdata020 = cputrace.cacheholdingdata020; r->cacheholdingaddr020 = cputrace.cacheholdingaddr020; r->prefetch020addr = cputrace.prefetch020addr; memcpy(&r->prefetch020, &cputrace.prefetch020, CPU_PIPELINE_MAX * sizeof(uae_u16)); memcpy(&r->prefetch020_valid, &cputrace.prefetch020_valid, CPU_PIPELINE_MAX * sizeof(uae_u8)); memcpy(&caches020, &cputrace.caches020, sizeof caches020); m68k_setpc(cputrace.pc); if (!r->stopped) { if (cputrace.state > 1) Exception (cputrace.state); else if (cputrace.state == 1) (*cpufunctbl_noret[cputrace.opcode])(cputrace.opcode); } set_cpu_tracer (false); goto cont; } set_cpu_tracer (false); first = false; } while (!exit) { #if 0 static int prevopcode; #endif #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); #if 0 // logs to debug data cache issues struct cache030 *c1 ,*c2; int lws1, lws2; uae_u32 tag1, tag2; c1 = getcache030 (dcaches030, (uaecptr)0x27ece, &tag1, &lws1); c2 = getcache030 (dcaches030, (uaecptr)0x7f8192+4, &tag2, &lws2); fprintf ( stderr , "cache valid %d tag1 %x lws1 %x ctag %x data %x mem=%x\n" , c1->valid[lws1] , tag1 , lws1 , c1->tag , c1->data[lws1] , get_long(0x27ece) ); //fprintf ( stderr , "cache valid %d tag2 %x lws2 %x ctag %x data %x mem=%x\n" , c2->valid[lws2] , tag2 , lws2 , c2->tag , c2->data[lws2] , get_long(0x7f8192+4) ); #endif } currcycle = 0; #endif r->instruction_pc = m68k_getpc (); #if 0 if (regs.irc == 0xfffb) { gui_message (_T("OPCODE %04X HAS FAULTY PREFETCH! PC=%08X"), prevopcode, r->instruction_pc); } #endif //write_log (_T("%x %04x\n"), r->instruction_pc, regs.irc); r->opcode = regs.irc; #if 0 prevopcode = r->opcode; regs.irc = 0xfffb; #endif //write_log (_T("%08x %04x\n"), r->instruction_pc, opcode); #if DEBUG_CD32CDTVIO out_cd32io (r->instruction_pc); #endif if (cpu_tracer) { #if CPUTRACE_DEBUG validate_trace (); #endif memcpy (&cputrace.regs, &r->regs, 16 * sizeof (uae_u32)); cputrace.opcode = r->opcode; cputrace.ir = r->ir; cputrace.irc = r->irc; cputrace.sr = r->sr; cputrace.usp = r->usp; cputrace.isp = r->isp; cputrace.intmask = r->intmask; cputrace.stopped = r->stopped; cputrace.state = 1; cputrace.pc = m68k_getpc (); cputrace.msp = r->msp; cputrace.vbr = r->vbr; cputrace.caar = r->caar; cputrace.cacr = r->cacr; cputrace.cacheholdingdata020 = r->cacheholdingdata020; cputrace.cacheholdingaddr020 = r->cacheholdingaddr020; cputrace.prefetch020addr = r->prefetch020addr; memcpy(&cputrace.prefetch020, &r->prefetch020, CPU_PIPELINE_MAX * sizeof (uae_u16)); memcpy(&cputrace.prefetch020_valid, &r->prefetch020_valid, CPU_PIPELINE_MAX * sizeof(uae_u8)); memcpy(&cputrace.caches020, &caches020, sizeof caches020); cputrace.memoryoffset = 0; cputrace.cyclecounter = cputrace.cyclecounter_pre = cputrace.cyclecounter_post = 0; cputrace.readcounter = cputrace.writecounter = 0; } #ifndef WINUAE_FOR_HATARI if (inputrecord_debug & 4) { if (input_record > 0) inprec_recorddebug_cpu(1, r->opcode); else if (input_play > 0) inprec_playdebug_cpu(1, r->opcode); } #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif (*cpufunctbl_noret[r->opcode])(r->opcode); wait_memory_cycles(); regs.instruction_cnt++; #ifdef WINUAE_FOR_HATARI //fprintf ( stderr, "cyc_2ce %d\n" , currcycle ); /* Flush all CE cycles so far to update PendingInterruptCount */ M68000_AddCycles_CE ( currcycle * 2 / CYCLE_UNIT ); int dsp_cycles = 2 * currcycle * 2 / CYCLE_UNIT; // FIXME : remove this when using DSP_CyclesGlobalClockCounter in DSP_Run currcycle = 0; /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif cont: if (r->spcflags || regs.ipl[0] > 0) { if (do_specialties (0)) exit = true; } ipl_fetch_now(); #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { //fprintf ( stderr, "dsp cyc_2ce %d\n" , currcycle ); DSP_Run( dsp_cycles ); //fprintf ( stderr, "dsp cyc_2ce %d - %d\n" , currcycle * 2 / CYCLE_UNIT , (CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter) ); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH(prb) { bus_error(); ipl_fetch_now(); if (r->spcflags || time_for_interrupt()) { if (do_specialties(0)) exit = true; } } ENDTRY } } #ifdef CPUEMU_20 // full prefetch 020 (more compatible) static void m68k_run_2p (void) { struct regstruct *r = ®s; bool exit = false; bool first = true; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_2p\n"); CpuRunFuncNoret = false; #endif while (!exit) { check_debugger(); TRY(prb) { if (first) { if (cpu_tracer < 0) { memcpy (&r->regs, &cputrace.regs, 16 * sizeof (uae_u32)); r->ir = cputrace.ir; r->irc = cputrace.irc; r->sr = cputrace.sr; r->usp = cputrace.usp; r->isp = cputrace.isp; r->intmask = cputrace.intmask; r->stopped = cputrace.stopped; r->msp = cputrace.msp; r->vbr = cputrace.vbr; r->caar = cputrace.caar; r->cacr = cputrace.cacr; r->cacheholdingdata020 = cputrace.cacheholdingdata020; r->cacheholdingaddr020 = cputrace.cacheholdingaddr020; r->prefetch020addr = cputrace.prefetch020addr; memcpy(&r->prefetch020, &cputrace.prefetch020, CPU_PIPELINE_MAX * sizeof(uae_u16)); memcpy(&r->prefetch020_valid, &cputrace.prefetch020_valid, CPU_PIPELINE_MAX * sizeof(uae_u8)); memcpy(&caches020, &cputrace.caches020, sizeof caches020); m68k_setpc (cputrace.pc); if (!r->stopped) { if (cputrace.state > 1) Exception (cputrace.state); else if (cputrace.state == 1) (*cpufunctbl[cputrace.opcode])(cputrace.opcode); } set_cpu_tracer (false); goto cont; } set_cpu_tracer (false); first = false; } while (!exit) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif r->instruction_pc = m68k_getpc (); r->opcode = regs.irc; #if DEBUG_CD32CDTVIO out_cd32io (m68k_getpc ()); #endif if (cpu_tracer) { #if CPUTRACE_DEBUG validate_trace (); #endif memcpy (&cputrace.regs, &r->regs, 16 * sizeof (uae_u32)); cputrace.opcode = r->opcode; cputrace.ir = r->ir; cputrace.irc = r->irc; cputrace.sr = r->sr; cputrace.usp = r->usp; cputrace.isp = r->isp; cputrace.intmask = r->intmask; cputrace.stopped = r->stopped; cputrace.state = 1; cputrace.pc = m68k_getpc (); cputrace.msp = r->msp; cputrace.vbr = r->vbr; cputrace.caar = r->caar; cputrace.cacr = r->cacr; cputrace.cacheholdingdata020 = r->cacheholdingdata020; cputrace.cacheholdingaddr020 = r->cacheholdingaddr020; cputrace.prefetch020addr = r->prefetch020addr; memcpy(&cputrace.prefetch020, &r->prefetch020, CPU_PIPELINE_MAX * sizeof(uae_u16)); memcpy(&cputrace.prefetch020_valid, &r->prefetch020_valid, CPU_PIPELINE_MAX * sizeof(uae_u8)); memcpy(&cputrace.caches020, &caches020, sizeof caches020); cputrace.memoryoffset = 0; cputrace.cyclecounter = cputrace.cyclecounter_pre = cputrace.cyclecounter_post = 0; cputrace.readcounter = cputrace.writecounter = 0; } #ifndef WINUAE_FOR_HATARI if (inputrecord_debug & 4) { if (input_record > 0) inprec_recorddebug_cpu(1, r->opcode); else if (input_play > 0) inprec_playdebug_cpu(1, r->opcode); } #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif if (currprefs.cpu_memory_cycle_exact) { evt_t c = get_cycles(); (*cpufunctbl[r->opcode])(r->opcode); c = get_cycles() - c; cpu_cycles = 0; if (c <= cpucycleunit) { cpu_cycles = cpucycleunit; } regs.instruction_cnt++; } else { cpu_cycles = (*cpufunctbl[r->opcode])(r->opcode); cpu_cycles = adjust_cycles (cpu_cycles); regs.instruction_cnt++; } if (cpu_cycles > 0) x_do_cycles(cpu_cycles); cont: #ifdef WINUAE_FOR_HATARI //fprintf ( stderr , "waits %d %d %ld\n" , cpu_cycles*2/CYCLE_UNIT , WaitStateCycles , CyclesGlobalClockCounter ); M68000_AddCycles(cpu_cycles * 2 / CYCLE_UNIT); //fprintf ( stderr , "waits %d %d %ld\n" , cpu_cycles*2/CYCLE_UNIT , WaitStateCycles , CyclesGlobalClockCounter ); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } //fprintf ( stderr , "waits %d %d %ld\n" , cpu_cycles*2/CYCLE_UNIT , WaitStateCycles , CyclesGlobalClockCounter ); int dsp_cycles = 2 * cpu_cycles * 2 / CYCLE_UNIT; // FIXME : remove this when using DSP_CyclesGlobalClockCounter in DSP_Run currcycle = 0; /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (r->spcflags || regs.ipl[0] > 0) { if (do_specialties (cpu_cycles)) exit = true; } ipl_fetch_now(); #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { //if ( DSP_CPU_FREQ_RATIO * ( (CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter) << nCpuFreqShift ) - 2 * cpu_cycles * 2 / CYCLE_UNIT >= 8 ) //fprintf ( stderr , "dsp %d %d\n" , 2 * cpu_cycles * 2 / CYCLE_UNIT , DSP_CPU_FREQ_RATIO * ( (CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter) << nCpuFreqShift ) ); DSP_Run( dsp_cycles ); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH(prb) { bus_error(); if (r->spcflags) { if (do_specialties(cpu_cycles)) exit = true; } ipl_fetch_now(); } ENDTRY } } #endif #ifdef WITH_THREADED_CPU static void cpu_thread_run_2(void *v) { bool exit = false; struct regstruct *r = ®s; cpu_thread_tid = uae_thread_get_id(); cpu_thread_active = 1; while (!exit) { TRY(prb) { while (!exit) { r->instruction_pc = m68k_getpc(); r->opcode = x_get_iword(0); (*cpufunctbl[r->opcode])(r->opcode); if (regs.spcflags || cpu_thread_ilvl > 0) { if (do_specialties_thread()) exit = true; } } } CATCH(prb) { bus_error(); if (r->spcflags) { if (do_specialties_thread()) exit = true; } } ENDTRY } cpu_thread_active = 0; return 0; } #endif /* Same thing, but don't use prefetch to get opcode. */ static void m68k_run_2_000(void) { struct regstruct *r = ®s; bool exit = false; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_2_000\n"); CpuRunFuncNoret = false; #endif while (!exit) { check_debugger(); TRY(prb) { while (!exit) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif r->instruction_pc = m68k_getpc (); r->opcode = x_get_iword(0); count_instr (r->opcode); #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif cpu_cycles = (*cpufunctbl[r->opcode])(r->opcode) & 0xffff; cpu_cycles = adjust_cycles (cpu_cycles); do_cycles(cpu_cycles); #ifdef WINUAE_FOR_HATARI //fprintf ( stderr , "cyc_2 %d\n" , cpu_cycles ); M68000_AddCyclesWithPairing(cpu_cycles * 2 / CYCLE_UNIT); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (r->spcflags) { if (do_specialties (cpu_cycles)) exit = true; } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run(2 * cpu_cycles * 2 / CYCLE_UNIT); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH(prb) { bus_error(); if (r->spcflags) { if (do_specialties(cpu_cycles)) exit = true; } } ENDTRY } } static void m68k_run_2_020(void) { #ifdef WITH_THREADED_CPU if (currprefs.cpu_thread) { run_cpu_thread(cpu_thread_run_2); return; } #endif struct regstruct *r = ®s; bool exit = false; #ifdef WINUAE_FOR_HATARI Log_Printf(LOG_DEBUG, "m68k_run_2_020\n"); CpuRunFuncNoret = false; #endif while (!exit) { check_debugger(); TRY(prb) { while (!exit) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif r->instruction_pc = m68k_getpc(); r->opcode = x_get_iword(0); count_instr(r->opcode); #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_opcode_watch) { debug_trainer_match(); } #endif #endif cpu_cycles = (*cpufunctbl[r->opcode])(r->opcode) >> 16; cpu_cycles = adjust_cycles(cpu_cycles); do_cycles(cpu_cycles); #ifdef WINUAE_FOR_HATARI //fprintf ( stderr , "cyc_2 %d\n" , cpu_cycles ); M68000_AddCyclesWithPairing(cpu_cycles * 2 / CYCLE_UNIT); if ( WaitStateCycles ) { /* Add some extra cycles to simulate a wait state */ M68000_AddCycles(WaitStateCycles); WaitStateCycles = 0; } /* We can have several interrupts at the same time before the next CPU instruction */ /* We must check for pending interrupt and call do_specialties() only */ /* if the cpu is not in the STOP state. Else, the int could be acknowledged now */ /* and prevent exiting the STOP state when calling do_specialties() after. */ /* For performance, we first test PendingInterruptCount, then regs.spcflags */ CycInt_Process_stop(regs.spcflags & SPCFLAG_STOP); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ_All ( 0 ); #endif if (r->spcflags) { if (do_specialties(cpu_cycles)) exit = true; } #ifdef WINUAE_FOR_HATARI /* Run DSP 56k code if necessary */ if (bDspEnabled) { DSP_Run(2 * cpu_cycles * 2 / CYCLE_UNIT); // DSP_Run ( DSP_CPU_FREQ_RATIO * ( CyclesGlobalClockCounter - DSP_CyclesGlobalClockCounter ) ); } if ( savestate_state == STATE_SAVE ) save_state ( NULL , NULL ); #endif } } CATCH(prb) { bus_error(); if (r->spcflags) { if (do_specialties(cpu_cycles)) exit = true; } } ENDTRY } } /* fake MMU 68k */ #if 0 static void m68k_run_mmu (void) { for (;;) { #ifdef WINUAE_FOR_HATARI //m68k_dumpstate_file(stderr, NULL, 0xffffffff); if ((LOG_TRACE_LEVEL(TRACE_CPU_DISASM)) && (!regs.stopped)) { trace_cpu_disasm(); } #endif regs.opcode = get_iiword (0); do_cycles (cpu_cycles); mmu_backup_regs = regs; cpu_cycles = (*cpufunctbl[regs.opcode])(regs.opcode); cpu_cycles = adjust_cycles (cpu_cycles); if (mmu_triggered) mmu_do_hit (); if (regs.spcflags) { if (do_specialties (cpu_cycles)) return; } } } #endif #endif /* CPUEMU_0 */ int in_m68k_go = 0; #if 0 static void exception2_handle (uaecptr addr, uaecptr fault) { last_addr_for_exception_3 = addr; last_fault_for_exception_3 = fault; last_writeaccess_for_exception_3 = 0; last_fc_for_exception_3 = 0; Exception (2); } #endif static bool cpu_hardreset, cpu_keyboardreset; bool is_hardreset(void) { return cpu_hardreset; } bool is_keyboardreset(void) { return cpu_keyboardreset; } #ifndef WINUAE_FOR_HATARI static void warpmode_reset(void) { if (currprefs.turbo_boot && currprefs.turbo_emulation < 2) { warpmode(1); currprefs.turbo_emulation = changed_prefs.turbo_emulation = 2; } } #endif void m68k_go (int may_quit) { int hardboot = 1; #ifdef WITH_THREADED_CPU init_cpu_thread(); #endif if (in_m68k_go || !may_quit) { write_log (_T("Bug! m68k_go is not reentrant.\n")); abort (); } reset_frame_rate_hack (); update_68k_cycles (); #ifndef WINUAE_FOR_HATARI start_cycles = 0; #endif set_cpu_tracer (false); cpu_prefs_changed_flag = 0; in_m68k_go++; for (;;) { int restored = 0; void (*run_func)(void); #ifdef WINUAE_FOR_HATARI /* Exit hatari ? */ if (bQuitProgram == true) break; #endif cputrace.state = -1; #ifndef WINUAE_FOR_HATARI if (regs.halted == CPU_HALT_ACCELERATOR_CPU_FALLBACK) { cpu_halt_clear(); cpu_do_fallback(); } if (currprefs.inprecfile[0] && input_play) { inprec_open (currprefs.inprecfile, NULL); changed_prefs.inprecfile[0] = currprefs.inprecfile[0] = 0; quit_program = UAE_RESET; } if (input_play || input_record) inprec_startup (); #endif if (quit_program > 0) { cpu_keyboardreset = quit_program == UAE_RESET_KEYBOARD; cpu_hardreset = ((quit_program == UAE_RESET_HARD ? 1 : 0) || hardboot) != 0; hardboot |= quit_program == UAE_RESET_HARD ? 1 : 0; if (quit_program == UAE_QUIT) break; hsync_counter = 0; vsync_counter = 0; quit_program = 0; #ifdef SAVESTATE if (savestate_state == STATE_DORESTORE) { savestate_state = STATE_RESTORE; } if (savestate_state == STATE_RESTORE) { restore_state (savestate_fname); cpu_hardreset = 1; } else if (savestate_state == STATE_REWIND) { #ifndef WINUAE_FOR_HATARI savestate_rewind (); #endif } #endif if (cpu_hardreset) { m68k_reset_restore(); } prefs_changed_cpu(); build_cpufunctbl(); set_x_funcs(); #ifndef WINUAE_FOR_HATARI set_cycles (start_cycles); #endif custom_reset (cpu_hardreset != 0, cpu_keyboardreset); m68k_reset2 (cpu_hardreset != 0); if (cpu_hardreset) { memory_clear (); write_log (_T("hardreset, memory cleared\n")); } #ifdef DEBUGGER #ifndef WINUAE_FOR_HATARI if (debug_dma) { record_dma_reset(1); record_dma_reset(1); } #endif #endif #ifdef SAVESTATE /* We may have been restoring state, but we're done now. */ if (isrestore ()) { restored = savestate_restore_finish (); #ifndef WINUAE_FOR_HATARI memory_map_dump (); #endif if (currprefs.mmu_model == 68030) { mmu030_decode_tc (tc_030, true); } else if (currprefs.mmu_model >= 68040) { mmu_set_tc (regs.tcr); } hardboot = 1; } #endif #ifndef WINUAE_FOR_HATARI if (currprefs.produce_sound == 0) eventtab[ev_audio].active = 0; m68k_setpc_normal (regs.pc); check_prefs_changed_audio (); if (!restored || hsync_counter == 0) savestate_check (); if (input_record == INPREC_RECORD_START) input_record = INPREC_RECORD_NORMAL; statusline_clear(); #else m68k_setpc_normal (regs.pc); #endif } else { #ifndef WINUAE_FOR_HATARI if (input_record == INPREC_RECORD_START) { input_record = INPREC_RECORD_NORMAL; savestate_init (); hsync_counter = 0; vsync_counter = 0; savestate_check (); } #endif } #ifndef WINUAE_FOR_HATARI if (changed_prefs.inprecfile[0] && input_record) inprec_prepare_record (savestate_fname[0] ? savestate_fname : NULL); #ifdef DEBUGGER if (changed_prefs.trainerfile[0]) debug_init_trainer(changed_prefs.trainerfile); #endif #endif set_cpu_tracer (false); #ifdef DEBUGGER if (debugging) debug (); #endif if (regs.spcflags & SPCFLAG_MODE_CHANGE) { if (cpu_prefs_changed_flag & 1) { uaecptr pc = m68k_getpc(); prefs_changed_cpu(); fpu_modechange(); #ifndef WINUAE_FOR_HATARI custom_cpuchange(); #endif build_cpufunctbl(); m68k_setpc_normal(pc); fill_prefetch(); update_68k_cycles(); } if (cpu_prefs_changed_flag & 2) { fixup_cpu(&changed_prefs); currprefs.m68k_speed = changed_prefs.m68k_speed; currprefs.m68k_speed_throttle = changed_prefs.m68k_speed_throttle; update_68k_cycles(); #ifndef WINUAE_FOR_HATARI target_cpu_speed(); #endif } cpu_prefs_changed_flag = 0; } set_x_funcs(); #ifndef WINUAE_FOR_HATARI if (hardboot) { custom_prepare (); mman_set_barriers(false); protect_roms (true); } if ((cpu_keyboardreset || hardboot) && !restored) { warpmode_reset(); } cpu_hardreset = false; cpu_keyboardreset = false; event_wait = true; #endif unset_special(SPCFLAG_MODE_CHANGE); #ifndef WINUAE_FOR_HATARI if (!restored && hardboot) { uaerandomizeseed(); uae_u32 s = uaerandgetseed(); uaesetrandseed(s); write_log("rndseed = %08x (%u)\n", s, s); // add random delay before CPU starts int t = uaerand() & 0x7fff; while (t > 255) { x_do_cycles(255 * CYCLE_UNIT); t -= 255; } x_do_cycles(t * CYCLE_UNIT); } #endif hardboot = 0; #ifdef SAVESTATE if (restored) { restored = 0; savestate_restore_final(); } #endif if (!regs.halted) { // check that PC points to something that looks like memory. uaecptr pc = m68k_getpc(); #ifndef WINUAE_FOR_HATARI addrbank *ab = get_mem_bank_real(pc); #else addrbank *ab = &get_mem_bank(pc); #endif if (ab == NULL || ab == &dummy_bank || (!currprefs.cpu_compatible && !valid_address(pc, 2)) || (pc & 1)) { cpu_halt(CPU_HALT_INVALID_START_ADDRESS); } } if (regs.halted) { cpu_halt (regs.halted); if (regs.halted < 0) { haltloop(); continue; } } #ifdef WINUAE_FOR_HATARI /* Apply patches for gemdos HD if needed (we need to do it after */ /* cpu tables for all opcodes were rebuilt in build_cpufunctbl() ) */ M68000_PatchCpuTables(); /* Restore debugger state (breakpoints) after a reset */ M68000_RestoreDebugger(); /* Set cycle exact mode to false by default */ CpuRunCycleExact = false; #endif #if 0 if (mmu_enabled && !currprefs.cachesize) { run_func = m68k_run_mmu; } else { #endif run_func = currprefs.cpu_cycle_exact && currprefs.cpu_model <= 68010 ? m68k_run_1_ce : currprefs.cpu_compatible && currprefs.cpu_model <= 68010 ? m68k_run_1 : #ifdef JIT currprefs.cpu_model >= 68020 && currprefs.cachesize ? m68k_run_jit : #endif currprefs.cpu_model == 68030 && currprefs.mmu_model ? m68k_run_mmu030 : currprefs.cpu_model == 68040 && currprefs.mmu_model ? m68k_run_mmu040 : currprefs.cpu_model == 68060 && currprefs.mmu_model ? m68k_run_mmu060 : currprefs.cpu_model >= 68040 && currprefs.cpu_cycle_exact ? m68k_run_3ce : currprefs.cpu_model >= 68020 && currprefs.cpu_cycle_exact ? m68k_run_2ce : currprefs.cpu_model <= 68020 && currprefs.cpu_compatible ? m68k_run_2p : currprefs.cpu_model == 68030 && currprefs.cpu_compatible ? m68k_run_2p : currprefs.cpu_model >= 68040 && currprefs.cpu_compatible ? m68k_run_3p : currprefs.cpu_model < 68020 ? m68k_run_2_000 : m68k_run_2_020; #if 0 } #endif run_func(); if (quit_program < 0) { quit_program = -quit_program; } if (quit_program == UAE_QUIT) break; Log_Printf(LOG_DEBUG, "exit m68k_run\n"); } #ifndef WINUAE_FOR_HATARI protect_roms (false); mman_set_barriers(false); #endif in_m68k_go--; } void m68k_disasm_ea (uaecptr addr, uaecptr *nextpc, int cnt, uae_u32 *seaddr, uae_u32 *deaddr, uaecptr lastpc) { TCHAR *buf; if (!cnt) return; int pcnt = cnt > 0 ? cnt : -cnt; buf = xcalloc (TCHAR, (MAX_LINEWIDTH + 1) * pcnt); if (!buf) return; #ifdef DEBUGGER m68k_disasm_2(buf, MAX_LINEWIDTH * pcnt, addr, NULL, 0, nextpc, cnt, seaddr, deaddr, lastpc, 1); #endif xfree (buf); } void m68k_disasm (uaecptr addr, uaecptr *nextpc, uaecptr lastpc, int cnt) { TCHAR *buf; if (!cnt) return; int pcnt = cnt > 0 ? cnt : -cnt; buf = xcalloc (TCHAR, (MAX_LINEWIDTH + 1) * pcnt); if (!buf) return; #ifdef DEBUGGER m68k_disasm_2(buf, MAX_LINEWIDTH * pcnt, addr, NULL, 0, nextpc, cnt, NULL, NULL, lastpc, 0); #endif console_out_f (_T("%s"), buf); xfree (buf); } void m68k_disasm_file (FILE *f, uaecptr addr, uaecptr *nextpc, uaecptr lastpc, int cnt) { TCHAR *buf; buf = xmalloc (TCHAR, (MAX_LINEWIDTH + 1) * cnt); if (!buf) return; console_out_FILE = f; m68k_disasm_2(buf, MAX_LINEWIDTH * cnt, addr, NULL, 0, nextpc, cnt, NULL, NULL, lastpc, 0); f_out (f, _T("%s"), buf); xfree (buf); console_out_FILE = NULL; } #ifdef WINUAE_FOR_HATARI /* * Functions called from debug/68Disass.c, we need to check if MMU is enabled to do some address * translations on 'addr' if needed, depending on the CPU/MMU family */ void m68k_disasm_file_wrapper (FILE *f, uaecptr addr, uaecptr *nextpc, uaecptr lastpc, int cnt) { uaecptr new_addr = addr; if ( currprefs.cpu_model == 68030 && currprefs.mmu_model ) /* 68030 with MMU */ new_addr = mmu030_translate(addr, regs.s != 0, false, false); m68k_disasm_file(TraceFile, new_addr, nextpc, lastpc, cnt); } #endif void m68k_dumpstate(uaecptr *nextpc, uaecptr prevpc) { int i, j; uaecptr pc = M68K_GETPC; MakeSR(); for (i = 0; i < 8; i++){ console_out_f (_T(" D%d %08X "), i, m68k_dreg (regs, i)); if ((i & 3) == 3) console_out_f (_T("\n")); } for (i = 0; i < 8; i++){ console_out_f (_T(" A%d %08X "), i, m68k_areg (regs, i)); if ((i & 3) == 3) console_out_f (_T("\n")); } if (regs.s == 0) regs.usp = m68k_areg (regs, 7); if (regs.s && regs.m) regs.msp = m68k_areg (regs, 7); if (regs.s && regs.m == 0) regs.isp = m68k_areg (regs, 7); j = 2; console_out_f (_T("USP %08X ISP %08X "), regs.usp, regs.isp); #ifdef DEBUGGER for (i = 0; m2cregs[i].regno>= 0; i++) { if (!movec_illg (m2cregs[i].regno)) { if (!_tcscmp (m2cregs[i].regname, _T("USP")) || !_tcscmp (m2cregs[i].regname, _T("ISP"))) continue; if (j > 0 && (j % 4) == 0) console_out_f (_T("\n")); console_out_f (_T("%-4s %08X "), m2cregs[i].regname, val_move2c (m2cregs[i].regno)); j++; } } #endif if (j > 0) console_out_f (_T("\n")); console_out_f (_T("SR=%04X T=%d%d S=%d M=%d X=%d N=%d Z=%d V=%d C=%d IM=%d STP=%d\n"), regs.sr, regs.t1, regs.t0, regs.s, regs.m, GET_XFLG(), GET_NFLG(), GET_ZFLG(), GET_VFLG(), GET_CFLG(), regs.intmask, regs.stopped); #ifdef FPUEMU if (currprefs.fpu_model) { uae_u32 fpsr; for (i = 0; i < 8; i++) { console_out_f(_T("%d: "), i); console_out_f (_T("%s "), fpp_print(®s.fp[i], -1)); console_out_f (_T("%s "), fpp_print(®s.fp[i], 0)); console_out_f (_T("\n")); } fpsr = fpp_get_fpsr (); console_out_f (_T("FPSR: %08X FPCR: %08x FPIAR: %08x N=%d Z=%d I=%d NAN=%d\n"), fpsr, regs.fpcr, regs.fpiar, (fpsr & 0x8000000) != 0, (fpsr & 0x4000000) != 0, (fpsr & 0x2000000) != 0, (fpsr & 0x1000000) != 0); } #endif if (currprefs.mmu_model == 68030) { #ifndef WINUAE_FOR_HATARI console_out_f (_T("SRP: %llX CRP: %llX\n"), srp_030, crp_030); #else /* Use PRIX64 since MinGW on Windows does not know about %llx (?) */ console_out_f (_T("SRP: %"PRIX64" CRP: %"PRIX64"\n"), (uint64_t)srp_030, (uint64_t)crp_030); #endif console_out_f (_T("TT0: %08X TT1: %08X TC: %08X\n"), tt0_030, tt1_030, tc_030); } if (currprefs.cpu_compatible) { console_out_f(_T("Prefetch")); if (currprefs.cpu_model == 68020 || currprefs.cpu_model == 68030) { console_out_f(_T(" %08x %08x (%d)"), regs.cacheholdingaddr020, regs.cacheholdingdata020, regs.cacheholdingdata_valid); } for (int i = 0; i < 3; i++) { uae_u16 w; #ifdef DEBUGGER if (!debug_get_prefetch(i, &w)) break; #endif struct instr *dp; struct mnemolookup *lookup; dp = table68k + w; for (lookup = lookuptab; lookup->mnemo != dp->mnemo; lookup++) ; console_out_f(_T(" %04x (%s)"), w, lookup->name); } console_out_f (_T(" Chip latch %08X\n"), regs.chipset_latch_rw); } if (prevpc != 0xffffffff && pc - prevpc < 100) { while (prevpc < pc) { m68k_disasm(prevpc, &prevpc, 0xffffffff, 1); } } m68k_disasm (pc, nextpc, pc, 1); if (nextpc) console_out_f (_T("Next PC: %08x\n"), *nextpc); } #ifdef WINUAE_FOR_HATARI void m68k_dumpstate_file (FILE *f, uaecptr *nextpc, uaecptr prevpc) { console_out_FILE = f; m68k_dumpstate(nextpc, prevpc); console_out_FILE = NULL; } #endif void m68k_dumpcache (bool dc) { if (!currprefs.cpu_compatible) return; if (currprefs.cpu_model == 68020) { for (int i = 0; i < CACHELINES020; i += 4) { for (int j = 0; j < 4; j++) { int s = i + j; uaecptr addr; int fc; struct cache020 *c = &caches020[s]; fc = c->tag & 1; addr = c->tag & ~1; addr |= s << 2; console_out_f (_T("%08X%c:%08X%c"), addr, fc ? 'S' : 'U', c->data, c->valid ? '*' : ' '); } console_out_f (_T("\n")); } } else if (currprefs.cpu_model == 68030) { for (int i = 0; i < CACHELINES030; i++) { struct cache030 *c = dc ? &dcaches030[i] : &icaches030[i]; int fc; uaecptr addr; if (!dc) { fc = (c->tag & 1) ? 6 : 2; } else { fc = c->fc; } addr = c->tag & ~1; addr |= i << 4; console_out_f (_T("%08X %d: "), addr, fc); for (int j = 0; j < 4; j++) { console_out_f (_T("%08X%c "), c->data[j], c->valid[j] ? '*' : ' '); } console_out_f (_T("\n")); } } else if (currprefs.cpu_model >= 68040) { uae_u32 tagmask = dc ? cachedtag04060mask : cacheitag04060mask; for (int i = 0; i < cachedsets04060; i++) { struct cache040 *c = dc ? &dcaches040[i] : &icaches040[i]; for (int j = 0; j < CACHELINES040; j++) { if (c->valid[j]) { uae_u32 addr = (c->tag[j] & tagmask) | (i << 4); write_log(_T("%02d:%d %08x = %08x%c %08x%c %08x%c %08x%c\n"), i, j, addr, c->data[j][0], c->dirty[j][0] ? '*' : ' ', c->data[j][1], c->dirty[j][1] ? '*' : ' ', c->data[j][2], c->dirty[j][2] ? '*' : ' ', c->data[j][3], c->dirty[j][3] ? '*' : ' '); } } } } } #ifdef SAVESTATE /* CPU save/restore code */ #define CPUTYPE_EC 1 #define CPUMODE_HALT 1 #define CPUMODE_HALT2 2 uae_u8 *restore_cpu (uae_u8 *src) { int flags, model; uae_u32 l; changed_prefs.fpu_model = currprefs.fpu_model = 0; changed_prefs.mmu_model = currprefs.mmu_model = 0; currprefs.cpu_model = changed_prefs.cpu_model = model = restore_u32 (); flags = restore_u32 (); changed_prefs.address_space_24 = 0; if (flags & CPUTYPE_EC) changed_prefs.address_space_24 = 1; currprefs.address_space_24 = changed_prefs.address_space_24; currprefs.cpu_compatible = changed_prefs.cpu_compatible; currprefs.cpu_cycle_exact = changed_prefs.cpu_cycle_exact; currprefs.cpu_memory_cycle_exact = changed_prefs.cpu_memory_cycle_exact; currprefs.blitter_cycle_exact = changed_prefs.blitter_cycle_exact; currprefs.cpu_frequency = changed_prefs.cpu_frequency = 0; currprefs.cpu_clock_multiplier = changed_prefs.cpu_clock_multiplier = 0; for (int i = 0; i < 15; i++) regs.regs[i] = restore_u32 (); regs.pc = restore_u32 (); regs.irc = restore_u16 (); regs.ir = restore_u16 (); regs.usp = restore_u32 (); regs.isp = restore_u32 (); regs.sr = restore_u16 (); l = restore_u32 (); if (l & CPUMODE_HALT) { regs.stopped = (l & CPUMODE_HALT2) ? 2 : 1; } else { regs.stopped = 0; } if (model >= 68010) { regs.dfc = restore_u32 (); regs.sfc = restore_u32 (); regs.vbr = restore_u32 (); } if (model >= 68020) { regs.caar = restore_u32 (); regs.cacr = restore_u32 (); regs.msp = restore_u32 (); } if (model >= 68030) { crp_030 = fake_crp_030 = restore_u64 (); srp_030 = fake_srp_030 = restore_u64 (); tt0_030 = fake_tt0_030 = restore_u32 (); tt1_030 = fake_tt1_030 = restore_u32 (); tc_030 = fake_tc_030 = restore_u32 (); mmusr_030 = fake_mmusr_030 = restore_u16 (); } if (model >= 68040) { regs.itt0 = restore_u32 (); regs.itt1 = restore_u32 (); regs.dtt0 = restore_u32 (); regs.dtt1 = restore_u32 (); regs.tcr = restore_u32 (); regs.urp = restore_u32 (); regs.srp = restore_u32 (); } if (model >= 68060) { regs.buscr = restore_u32 (); regs.pcr = restore_u32 (); } if (flags & 0x80000000) { int khz = restore_u32 (); restore_u32 (); if (khz > 0 && khz < 800000) currprefs.m68k_speed = changed_prefs.m68k_speed = 0; } set_cpu_caches (true); if (flags & 0x40000000) { if (model == 68020) { for (int i = 0; i < CACHELINES020; i++) { caches020[i].data = restore_u32 (); caches020[i].tag = restore_u32 (); caches020[i].valid = restore_u8 () != 0; } regs.prefetch020addr = restore_u32 (); regs.cacheholdingaddr020 = restore_u32 (); regs.cacheholdingdata020 = restore_u32 (); if (flags & 0x20000000) { if (flags & 0x4000000) { // 3.6 new (back to 16 bits) for (int i = 0; i < CPU_PIPELINE_MAX; i++) { uae_u32 v = restore_u32(); regs.prefetch020[i] = v >> 16; regs.prefetch020_valid[i] = (v & 1) != 0; } } else { // old uae_u32 v = restore_u32(); regs.prefetch020[0] = v >> 16; regs.prefetch020[1] = (uae_u16)v; v = restore_u32(); regs.prefetch020[2] = v >> 16; regs.prefetch020[3] = (uae_u16)v; restore_u32(); restore_u32(); regs.prefetch020_valid[0] = true; regs.prefetch020_valid[1] = true; regs.prefetch020_valid[2] = true; regs.prefetch020_valid[3] = true; } } } else if (model == 68030) { for (int i = 0; i < CACHELINES030; i++) { for (int j = 0; j < 4; j++) { icaches030[i].data[j] = restore_u32 (); icaches030[i].valid[j] = restore_u8 () != 0; } icaches030[i].tag = restore_u32 (); } for (int i = 0; i < CACHELINES030; i++) { for (int j = 0; j < 4; j++) { dcaches030[i].data[j] = restore_u32 (); dcaches030[i].valid[j] = restore_u8 () != 0; } dcaches030[i].tag = restore_u32 (); } regs.prefetch020addr = restore_u32 (); regs.cacheholdingaddr020 = restore_u32 (); regs.cacheholdingdata020 = restore_u32 (); if (flags & 0x4000000) { for (int i = 0; i < CPU_PIPELINE_MAX; i++) { uae_u32 v = restore_u32(); regs.prefetch020[i] = v >> 16; regs.prefetch020_valid[i] = (v & 1) != 0; } } else { for (int i = 0; i < CPU_PIPELINE_MAX; i++) { regs.prefetch020[i] = restore_u32 (); regs.prefetch020_valid[i] = false; } } } else if (model == 68040) { if (flags & 0x8000000) { for (int i = 0; i < ((model == 68060 && (flags & 0x4000000)) ? CACHESETS060 : CACHESETS040); i++) { for (int j = 0; j < CACHELINES040; j++) { struct cache040 *c = &icaches040[i]; c->data[j][0] = restore_u32(); c->data[j][1] = restore_u32(); c->data[j][2] = restore_u32(); c->data[j][3] = restore_u32(); c->tag[j] = restore_u32(); c->valid[j] = restore_u16() & 1; } } regs.prefetch020addr = restore_u32(); regs.cacheholdingaddr020 = restore_u32(); regs.cacheholdingdata020 = restore_u32(); for (int i = 0; i < CPU_PIPELINE_MAX; i++) regs.prefetch040[i] = restore_u32(); if (flags & 0x4000000) { for (int i = 0; i < (model == 68060 ? CACHESETS060 : CACHESETS040); i++) { for (int j = 0; j < CACHELINES040; j++) { struct cache040 *c = &dcaches040[i]; c->data[j][0] = restore_u32(); c->data[j][1] = restore_u32(); c->data[j][2] = restore_u32(); c->data[j][3] = restore_u32(); c->tag[j] = restore_u32(); uae_u16 v = restore_u16(); c->valid[j] = (v & 1) != 0; c->dirty[j][0] = (v & 0x10) != 0; c->dirty[j][1] = (v & 0x20) != 0; c->dirty[j][2] = (v & 0x40) != 0; c->dirty[j][3] = (v & 0x80) != 0; c->gdirty[j] = c->dirty[j][0] || c->dirty[j][1] || c->dirty[j][2] || c->dirty[j][3]; } } } } } if (model >= 68020) { restore_u32 (); // regs.ce020memcycles regs.ce020startcycle = regs.ce020endcycle = 0; restore_u32 (); } } if (flags & 0x10000000) { regs.chipset_latch_rw = restore_u32 (); regs.chipset_latch_read = restore_u32 (); regs.chipset_latch_write = restore_u32 (); } regs.pipeline_pos = -1; regs.pipeline_stop = 0; if ((flags & 0x4000000) && currprefs.cpu_model == 68020) { regs.pipeline_pos = restore_u16(); regs.pipeline_r8[0] = restore_u16(); regs.pipeline_r8[1] = restore_u16(); regs.pipeline_stop = restore_u16(); } if ((flags & 0x2000000) && currprefs.cpu_model <= 68010) { int v = restore_u32(); regs.ird = restore_u16(); regs.read_buffer = restore_u16(); regs.write_buffer = restore_u16(); if (v & 1) { regs.ipl[0] = restore_s8(); regs.ipl[1] = restore_s8(); regs.ipl_pin = restore_s8(); regs.ipl_pin_p = restore_s8(); regs.ipl_evt = restore_u64(); regs.ipl_evt_pre = restore_u64(); regs.ipl_pin_change_evt = restore_u64(); regs.ipl_pin_change_evt_p = restore_u64(); } } m68k_reset_sr(); write_log (_T("CPU: %d%s%03d, PC=%08X\n"), model / 1000, flags & 1 ? _T("EC") : _T(""), model % 1000, regs.pc); return src; } static void fill_prefetch_quick (void) { if (currprefs.cpu_model >= 68020) { fill_prefetch (); return; } // old statefile compatibility, this needs to done, // even in 68000 cycle-exact mode regs.ir = get_word (m68k_getpc ()); regs.ird = regs.ir; regs.irc = get_word (m68k_getpc () + 2); } void restore_cpu_finish (void) { if (!currprefs.fpu_model) fpu_reset(); init_m68k (); m68k_setpc_normal (regs.pc); doint (); fill_prefetch_quick (); #ifndef WINUAE_FOR_HATARI set_cycles (start_cycles); events_schedule (); #endif //activate_debugger (); } uae_u8 *save_cpu_trace(size_t *len, uae_u8 *dstptr) { uae_u8 *dstbak, *dst; if (cputrace.state <= 0) return NULL; if (dstptr) dstbak = dst = dstptr; else dstbak = dst = xmalloc (uae_u8, 10000); save_u32 (2 | 4 | 16 | 32 | 64 | 128); save_u16 (cputrace.opcode); for (int i = 0; i < 16; i++) save_u32 (cputrace.regs[i]); save_u32 (cputrace.pc); save_u16 (cputrace.irc); save_u16 (cputrace.ir); save_u32 (cputrace.usp); save_u32 (cputrace.isp); save_u16 (cputrace.sr); save_u16 (cputrace.intmask); save_u16 ((cputrace.stopped ? 1 : 0) | (regs.stopped ? 2 : 0)); save_u16 (cputrace.state); save_u32 (cputrace.cyclecounter); save_u32 (cputrace.cyclecounter_pre); save_u32 (cputrace.cyclecounter_post); save_u32 (cputrace.readcounter); save_u32 (cputrace.writecounter); save_u32 (cputrace.memoryoffset); write_log (_T("CPUT SAVE: PC=%08x C=%016llX %08x %08x %08x %d %d %d\n"), cputrace.pc, cputrace.startcycles, cputrace.cyclecounter, cputrace.cyclecounter_pre, cputrace.cyclecounter_post, cputrace.readcounter, cputrace.writecounter, cputrace.memoryoffset); for (int i = 0; i < cputrace.memoryoffset; i++) { save_u32 (cputrace.ctm[i].addr); save_u32 (cputrace.ctm[i].data); save_u32 (cputrace.ctm[i].mode); write_log (_T("CPUT%d: %08x %08x %08x %08x\n"), i, cputrace.ctm[i].addr, cputrace.ctm[i].data, cputrace.ctm[i].mode, cputrace.ctm[i].flags); } save_u32 ((uae_u32)cputrace.startcycles); if (currprefs.cpu_model == 68020) { for (int i = 0; i < CACHELINES020; i++) { save_u32 (cputrace.caches020[i].data); save_u32 (cputrace.caches020[i].tag); save_u8 (cputrace.caches020[i].valid ? 1 : 0); } save_u32 (cputrace.prefetch020addr); save_u32 (cputrace.cacheholdingaddr020); save_u32 (cputrace.cacheholdingdata020); for (int i = 0; i < CPU_PIPELINE_MAX; i++) { save_u16 (cputrace.prefetch020[i]); } for (int i = 0; i < CPU_PIPELINE_MAX; i++) { save_u32 (cputrace.prefetch020[i]); } for (int i = 0; i < CPU_PIPELINE_MAX; i++) { save_u8 (cputrace.prefetch020_valid[i]); } save_u16(cputrace.pipeline_pos); save_u16(cputrace.pipeline_r8[0]); save_u16(cputrace.pipeline_r8[1]); save_u16(cputrace.pipeline_stop); } save_u16(cputrace.ird); save_u16(cputrace.read_buffer); save_u16(cputrace.writecounter); save_u32(cputrace.startcycles >> 32); for (int i = 0; i < cputrace.memoryoffset; i++) { save_u32(cputrace.ctm[i].flags | 4); } *len = dst - dstbak; cputrace.needendcycles = 1; return dstbak; } uae_u8 *restore_cpu_trace(uae_u8 *src) { cpu_tracer = 0; cputrace.state = 0; uae_u32 v = restore_u32 (); if (!(v & 2)) return src; cputrace.opcode = restore_u16 (); for (int i = 0; i < 16; i++) cputrace.regs[i] = restore_u32 (); cputrace.pc = restore_u32 (); cputrace.irc = restore_u16 (); cputrace.ir = restore_u16 (); cputrace.usp = restore_u32 (); cputrace.isp = restore_u32 (); cputrace.sr = restore_u16 (); cputrace.intmask = restore_u16 (); cputrace.stopped = restore_u16 (); cputrace.state = restore_u16 (); cputrace.cyclecounter = restore_u32 (); cputrace.cyclecounter_pre = restore_u32 (); cputrace.cyclecounter_post = restore_u32 (); cputrace.readcounter = restore_u32 (); cputrace.writecounter = restore_u32 (); cputrace.memoryoffset = restore_u32 (); for (int i = 0; i < cputrace.memoryoffset; i++) { cputrace.ctm[i].addr = restore_u32 (); cputrace.ctm[i].data = restore_u32 (); cputrace.ctm[i].mode = restore_u32 (); } cputrace.startcycles = restore_u32(); if (v & 4) { if (currprefs.cpu_model == 68020) { for (int i = 0; i < CACHELINES020; i++) { cputrace.caches020[i].data = restore_u32 (); cputrace.caches020[i].tag = restore_u32 (); cputrace.caches020[i].valid = restore_u8 () != 0; } cputrace.prefetch020addr = restore_u32 (); cputrace.cacheholdingaddr020 = restore_u32 (); cputrace.cacheholdingdata020 = restore_u32 (); for (int i = 0; i < CPU_PIPELINE_MAX; i++) { cputrace.prefetch020[i] = restore_u16 (); } if (v & 8) { // backwards compatibility uae_u32 v2 = restore_u32(); cputrace.prefetch020[0] = v2 >> 16; cputrace.prefetch020[1] = (uae_u16)v2; v2 = restore_u32(); cputrace.prefetch020[2] = v2 >> 16; cputrace.prefetch020[3] = (uae_u16)v2; restore_u32(); restore_u32(); cputrace.prefetch020_valid[0] = true; cputrace.prefetch020_valid[1] = true; cputrace.prefetch020_valid[2] = true; cputrace.prefetch020_valid[3] = true; cputrace.prefetch020[0] = cputrace.prefetch020[1]; cputrace.prefetch020[1] = cputrace.prefetch020[2]; cputrace.prefetch020[2] = cputrace.prefetch020[3]; cputrace.prefetch020_valid[3] = false; } if (v & 16) { if ((v & 32) && !(v & 8)) { restore_u32(); restore_u32(); restore_u32(); restore_u32(); } for (int i = 0; i < CPU_PIPELINE_MAX; i++) { cputrace.prefetch020_valid[i] = restore_u8() != 0; } } if (v & 32) { cputrace.pipeline_pos = restore_u16(); cputrace.pipeline_r8[0] = restore_u16(); cputrace.pipeline_r8[1] = restore_u16(); cputrace.pipeline_stop = restore_u16(); } } if (v & 64) { cputrace.ird = restore_u16(); cputrace.read_buffer = restore_u16(); cputrace.write_buffer = restore_u16(); } if (v & 128) { cputrace.startcycles |= ((uae_u64)restore_u32()) << 32; for (int i = 0; i < cputrace.memoryoffset; i++) { cputrace.ctm[i].flags = restore_u32(); } } } cputrace.needendcycles = 1; if (v && cputrace.state) { if (currprefs.cpu_model > 68000) { if (v & 4) cpu_tracer = -1; // old format? if ((v & (4 | 8)) != (4 | 8) && (v & (32 | 16 | 8 | 4)) != (32 | 16 | 4)) cpu_tracer = 0; } else { cpu_tracer = -1; } } return src; } uae_u8 *restore_cpu_extra(uae_u8 *src) { restore_u32 (); uae_u32 flags = restore_u32 (); currprefs.cpu_cycle_exact = changed_prefs.cpu_cycle_exact = (flags & 1) ? true : false; currprefs.cpu_memory_cycle_exact = changed_prefs.cpu_memory_cycle_exact = currprefs.cpu_cycle_exact; if ((flags & 32) && !(flags & 1)) currprefs.cpu_memory_cycle_exact = changed_prefs.cpu_memory_cycle_exact = true; currprefs.blitter_cycle_exact = changed_prefs.blitter_cycle_exact = currprefs.cpu_cycle_exact; currprefs.cpu_compatible = changed_prefs.cpu_compatible = (flags & 2) ? true : false; currprefs.cpu_frequency = changed_prefs.cpu_frequency = restore_u32 (); currprefs.cpu_clock_multiplier = changed_prefs.cpu_clock_multiplier = restore_u32 (); //currprefs.cachesize = changed_prefs.cachesize = (flags & 8) ? 8192 : 0; currprefs.m68k_speed = changed_prefs.m68k_speed = 0; if (flags & 4) currprefs.m68k_speed = changed_prefs.m68k_speed = -1; if (flags & 16) currprefs.m68k_speed = changed_prefs.m68k_speed = (flags >> 24) * CYCLE_UNIT; currprefs.cpu060_revision = changed_prefs.cpu060_revision = restore_u8 (); currprefs.fpu_revision = changed_prefs.fpu_revision = restore_u8 (); return src; } uae_u8 *save_cpu_extra(size_t *len, uae_u8 *dstptr) { uae_u8 *dstbak, *dst; uae_u32 flags; if (dstptr) dstbak = dst = dstptr; else dstbak = dst = xmalloc (uae_u8, 1000); save_u32 (0); // version flags = 0; flags |= currprefs.cpu_cycle_exact ? 1 : 0; flags |= currprefs.cpu_compatible ? 2 : 0; flags |= currprefs.m68k_speed < 0 ? 4 : 0; flags |= currprefs.cachesize > 0 ? 8 : 0; flags |= currprefs.m68k_speed > 0 ? 16 : 0; flags |= currprefs.cpu_memory_cycle_exact ? 32 : 0; if (currprefs.m68k_speed > 0) flags |= (currprefs.m68k_speed / CYCLE_UNIT) << 24; save_u32 (flags); save_u32 (currprefs.cpu_frequency); save_u32 (currprefs.cpu_clock_multiplier); save_u8 (currprefs.cpu060_revision); save_u8 (currprefs.fpu_revision); *len = dst - dstbak; return dstbak; } uae_u8 *save_cpu(size_t *len, uae_u8 *dstptr) { uae_u8 *dstbak, *dst; int model, khz; if (dstptr) dstbak = dst = dstptr; else dstbak = dst = xmalloc (uae_u8, 1000 + 30000); model = currprefs.cpu_model; save_u32 (model); /* MODEL */ save_u32(0x80000000 | 0x40000000 | 0x20000000 | 0x10000000 | 0x8000000 | 0x4000000 | 0x2000000 | (currprefs.address_space_24 ? 1 : 0)); /* FLAGS */ for (int i = 0;i < 15; i++) save_u32 (regs.regs[i]); /* D0-D7 A0-A6 */ save_u32 (m68k_getpc ()); /* PC */ save_u16 (regs.irc); /* prefetch */ save_u16 (regs.ir); /* instruction prefetch */ MakeSR (); save_u32 (!regs.s ? regs.regs[15] : regs.usp); /* USP */ save_u32 (regs.s ? regs.regs[15] : regs.isp); /* ISP */ save_u16 (regs.sr); /* SR/CCR */ save_u32 (regs.stopped == 1 ? CPUMODE_HALT : (regs.stopped == 2 ? (CPUMODE_HALT | CPUMODE_HALT2) : 0)); /* flags */ if (model >= 68010) { save_u32 (regs.dfc); /* DFC */ save_u32 (regs.sfc); /* SFC */ save_u32 (regs.vbr); /* VBR */ } if (model >= 68020) { save_u32 (regs.caar); /* CAAR */ save_u32 (regs.cacr); /* CACR */ save_u32 (regs.msp); /* MSP */ } if (model >= 68030) { if (currprefs.mmu_model) { save_u64 (crp_030); /* CRP */ save_u64 (srp_030); /* SRP */ save_u32 (tt0_030); /* TT0/AC0 */ save_u32 (tt1_030); /* TT1/AC1 */ save_u32 (tc_030); /* TCR */ save_u16 (mmusr_030); /* MMUSR/ACUSR */ } else { save_u64 (fake_crp_030); /* CRP */ save_u64 (fake_srp_030); /* SRP */ save_u32 (fake_tt0_030); /* TT0/AC0 */ save_u32 (fake_tt1_030); /* TT1/AC1 */ save_u32 (fake_tc_030); /* TCR */ save_u16 (fake_mmusr_030); /* MMUSR/ACUSR */ } } if (model >= 68040) { save_u32 (regs.itt0); /* ITT0 */ save_u32 (regs.itt1); /* ITT1 */ save_u32 (regs.dtt0); /* DTT0 */ save_u32 (regs.dtt1); /* DTT1 */ save_u32 (regs.tcr); /* TCR */ save_u32 (regs.urp); /* URP */ save_u32 (regs.srp); /* SRP */ } if (model >= 68060) { save_u32 (regs.buscr); /* BUSCR */ save_u32 (regs.pcr); /* PCR */ } khz = -1; if (currprefs.m68k_speed == 0) { khz = currprefs.ntscmode ? 715909 : 709379; if (currprefs.cpu_model >= 68020) khz *= 2; } save_u32 (khz); // clock rate in KHz: -1 = fastest possible save_u32 (0); // spare if (model == 68020) { for (int i = 0; i < CACHELINES020; i++) { save_u32 (caches020[i].data); save_u32 (caches020[i].tag); save_u8 (caches020[i].valid ? 1 : 0); } save_u32 (regs.prefetch020addr); save_u32 (regs.cacheholdingaddr020); save_u32 (regs.cacheholdingdata020); for (int i = 0; i < CPU_PIPELINE_MAX; i++) save_u32 ((regs.prefetch020[i] << 16) | (regs.prefetch020_valid[i] ? 1 : 0)); } else if (model == 68030) { for (int i = 0; i < CACHELINES030; i++) { for (int j = 0; j < 4; j++) { save_u32 (icaches030[i].data[j]); save_u8 (icaches030[i].valid[j] ? 1 : 0); } save_u32 (icaches030[i].tag); } for (int i = 0; i < CACHELINES030; i++) { for (int j = 0; j < 4; j++) { save_u32 (dcaches030[i].data[j]); save_u8 (dcaches030[i].valid[j] ? 1 : 0); } save_u32 (dcaches030[i].tag); } save_u32 (regs.prefetch020addr); save_u32 (regs.cacheholdingaddr020); save_u32 (regs.cacheholdingdata020); for (int i = 0; i < CPU_PIPELINE_MAX; i++) save_u32 (regs.prefetch020[i]); } else if (model >= 68040) { for (int i = 0; i < (model == 68060 ? CACHESETS060 : CACHESETS040); i++) { for (int j = 0; j < CACHELINES040; j++) { struct cache040 *c = &icaches040[i]; save_u32(c->data[j][0]); save_u32(c->data[j][1]); save_u32(c->data[j][2]); save_u32(c->data[j][3]); save_u32(c->tag[j]); save_u16(c->valid[j] ? 1 : 0); } } save_u32(regs.prefetch020addr); save_u32(regs.cacheholdingaddr020); save_u32(regs.cacheholdingdata020); for (int i = 0; i < CPU_PIPELINE_MAX; i++) { save_u32(regs.prefetch040[i]); } for (int i = 0; i < (model == 68060 ? CACHESETS060 : CACHESETS040); i++) { for (int j = 0; j < CACHELINES040; j++) { struct cache040 *c = &dcaches040[i]; save_u32(c->data[j][0]); save_u32(c->data[j][1]); save_u32(c->data[j][2]); save_u32(c->data[j][3]); save_u32(c->tag[j]); uae_u16 v = c->valid[j] ? 1 : 0; v |= c->dirty[j][0] ? 0x10 : 0; v |= c->dirty[j][1] ? 0x20 : 0; v |= c->dirty[j][2] ? 0x40 : 0; v |= c->dirty[j][3] ? 0x80 : 0; save_u16(v); } } } if (currprefs.cpu_model >= 68020) { save_u32 (0); //save_u32 (regs.ce020memcycles); save_u32 (0); } save_u32 (regs.chipset_latch_rw); save_u32 (regs.chipset_latch_read); save_u32 (regs.chipset_latch_write); if (currprefs.cpu_model == 68020) { save_u16(regs.pipeline_pos); save_u16(regs.pipeline_r8[0]); save_u16(regs.pipeline_r8[1]); save_u16(regs.pipeline_stop); } if (currprefs.cpu_model <= 68010) { save_u32(1); save_u16(regs.ird); save_u16(regs.read_buffer); save_u16(regs.write_buffer); save_s8(regs.ipl[0]); save_s8(regs.ipl[1]); save_s8(regs.ipl_pin); save_s8(regs.ipl_pin_p); save_u64(regs.ipl_evt); save_u64(regs.ipl_evt_pre); save_u64(regs.ipl_pin_change_evt); save_u64(regs.ipl_pin_change_evt_p); } *len = dst - dstbak; return dstbak; } uae_u8 *save_mmu(size_t *len, uae_u8 *dstptr) { uae_u8 *dstbak, *dst; int model; model = currprefs.mmu_model; #ifndef WINUAE_FOR_HATARI /* Under Hatari, we save all MMU variables, even if mmu_model==0 */ if (model != 68030 && model != 68040 && model != 68060) return NULL; #endif if (dstptr) dstbak = dst = dstptr; else dstbak = dst = xmalloc (uae_u8, 1000); save_u32 (model); /* MODEL */ save_u32 (0); /* FLAGS */ *len = dst - dstbak; return dstbak; } uae_u8 *restore_mmu(uae_u8 *src) { int flags, model; changed_prefs.mmu_model = model = restore_u32 (); flags = restore_u32 (); if ( model == 68030 ) restore_mmu030_finish(); write_log (_T("MMU: %d\n"), model); return src; } #endif /* SAVESTATE */ static void exception3f(uae_u32 opcode, uaecptr addr, bool writeaccess, bool instructionaccess, bool notinstruction, uaecptr pc, int size, int fc, uae_u16 secondarysr) { if (currprefs.cpu_model >= 68040) addr &= ~1; if (currprefs.cpu_model >= 68020) { if (pc == 0xffffffff) last_addr_for_exception_3 = regs.instruction_pc; else last_addr_for_exception_3 = pc; } else if (pc == 0xffffffff) { last_addr_for_exception_3 = m68k_getpc(); } else { last_addr_for_exception_3 = pc; } last_fault_for_exception_3 = addr; last_op_for_exception_3 = opcode; last_writeaccess_for_exception_3 = writeaccess; last_fc_for_exception_3 = fc >= 0 ? fc : (instructionaccess ? 2 : 1); last_notinstruction_for_exception_3 = notinstruction; last_size_for_exception_3 = size; last_sr_for_exception3 = secondarysr; Exception (3); #if EXCEPTION3_DEBUGGER activate_debugger(); #endif } void exception3_notinstruction(uae_u32 opcode, uaecptr addr) { last_di_for_exception_3 = 1; exception3f (opcode, addr, true, false, true, 0xffffffff, 1, -1, 0); } static void exception3_read_special(uae_u32 opcode, uaecptr addr, int size, int fc) { exception3f(opcode, addr, false, 0, false, 0xffffffff, size, fc, 0); } // 68010 special prefetch handling void exception3_read_prefetch_only(uae_u32 opcode, uaecptr addr) { if (currprefs.cpu_model == 68010) { uae_u16 prev = regs.read_buffer; x_get_word(addr & ~1); regs.irc = regs.read_buffer; } else { x_do_cycles(4 * cpucycleunit); } last_di_for_exception_3 = 0; exception3f(opcode, addr, false, true, false, m68k_getpc(), sz_word, -1, 0); } // Some hardware accepts address error aborted reads or writes as normal reads/writes. void exception3_read_prefetch(uae_u32 opcode, uaecptr addr) { x_do_cycles(4 * cpucycleunit); last_di_for_exception_3 = 0; if (currprefs.cpu_model == 68000) { m68k_incpci(2); } exception3f(opcode, addr, false, true, false, m68k_getpc(), sz_word, -1, 0); } void exception3_read_prefetch_68040bug(uae_u32 opcode, uaecptr addr, uae_u16 secondarysr) { x_do_cycles(4 * cpucycleunit); last_di_for_exception_3 = 0; exception3f(opcode | 0x10000, addr, false, true, false, m68k_getpc(), sz_word, -1, secondarysr); } void exception3_read_access(uae_u32 opcode, uaecptr addr, int size, int fc) { x_do_cycles(4 * cpucycleunit); exception3_read(opcode, addr, size, fc); } void exception3_read_access2(uae_u32 opcode, uaecptr addr, int size, int fc) { // (An), -(An) and (An)+ and 68010: read happens twice! x_do_cycles(8 * cpucycleunit); exception3_read(opcode, addr, size, fc); } void exception3_write_access(uae_u32 opcode, uaecptr addr, int size, uae_u32 val, int fc) { x_do_cycles(4 * cpucycleunit); exception3_write(opcode, addr, size, val, fc); } void exception3_read(uae_u32 opcode, uaecptr addr, int size, int fc) { bool ni = false; bool ia = false; if (currprefs.cpu_model == 68000 && currprefs.cpu_compatible) { if (generates_group1_exception(regs.ir) && !(opcode & 0x20000)) { ni = true; fc = -1; } if (opcode & 0x10000) ni = true; if (opcode & 0x40000) ia = true; opcode = regs.ir; } last_di_for_exception_3 = 1; exception3f(opcode, addr, false, ia, ni, 0xffffffff, size & 15, fc, 0); } void exception3_write(uae_u32 opcode, uaecptr addr, int size, uae_u32 val, int fc) { bool ni = false; bool ia = false; if (currprefs.cpu_model == 68000 && currprefs.cpu_compatible) { if (generates_group1_exception(regs.ir) && !(opcode & 0x20000)) { ni = true; fc = -1; } if (opcode & 0x10000) ni = true; if (opcode & 0x40000) ia = true; opcode = regs.ir; } last_di_for_exception_3 = 1; regs.write_buffer = val; exception3f(opcode, addr, true, ia, ni, 0xffffffff, size & 15, fc, 0); } void exception2_setup(uae_u32 opcode, uaecptr addr, bool read, int size, uae_u32 fc) { last_addr_for_exception_3 = m68k_getpc(); last_fault_for_exception_3 = addr; last_writeaccess_for_exception_3 = read == 0; last_op_for_exception_3 = opcode; last_fc_for_exception_3 = fc; last_notinstruction_for_exception_3 = exception_in_exception != 0; last_size_for_exception_3 = size & 15; last_di_for_exception_3 = 1; hardware_bus_error = 0; if (currprefs.cpu_model == 68000 && currprefs.cpu_compatible) { if (generates_group1_exception(regs.ir) && !(opcode & 0x20000)) { last_notinstruction_for_exception_3 = true; fc = -1; } if (opcode & 0x10000) last_notinstruction_for_exception_3 = true; if (!(opcode & 0x20000)) last_op_for_exception_3 = regs.ir; } } // Common hardware bus error entry point. Both for MMU and non-MMU emulation. void hardware_exception2(uaecptr addr, uae_u32 v, bool read, bool ins, int size) { if (currprefs.cpu_model <= 68010 && currprefs.cpu_compatible && HARDWARE_BUS_ERROR_EMULATION) { hardware_bus_error = 1; } else if (currprefs.mmu_model) { if (currprefs.mmu_model == 68030) { mmu030_hardware_bus_error(addr, v, read, ins, size); } else { mmu_hardware_bus_error(addr, v, read, ins, size); } return; } else { int fc = (regs.s ? 4 : 0) | (ins ? 2 : 1); if (ismoves_nommu) { ismoves_nommu = false; fc = read ? regs.sfc : regs.dfc; } // Non-MMU exception2_setup(regs.opcode, addr, read, size, fc); THROW(2); } } void exception2_read(uae_u32 opcode, uaecptr addr, int size, int fc) { exception2_setup(opcode, addr, true, size & 15, fc); Exception(2); } void exception2_write(uae_u32 opcode, uaecptr addr, int size, uae_u32 val, int fc) { exception2_setup(opcode, addr, false, size & 15, fc); if (size & 0x100) { regs.write_buffer = val; } else { if (size == sz_byte) { regs.write_buffer &= 0xff00; regs.write_buffer |= val & 0xff; } else { regs.write_buffer = val; } } Exception(2); } static void exception2_fetch_common(uae_u32 opcode, int offset) { last_fault_for_exception_3 = m68k_getpc() + offset; // this is not yet fully correct last_addr_for_exception_3 = last_fault_for_exception_3; last_writeaccess_for_exception_3 = 0; last_op_for_exception_3 = opcode; last_fc_for_exception_3 = 2; last_notinstruction_for_exception_3 = exception_in_exception != 0; last_size_for_exception_3 = sz_word; last_di_for_exception_3 = 0; hardware_bus_error = 0; if (currprefs.cpu_model == 68000 && currprefs.cpu_compatible) { if (generates_group1_exception(regs.ir) && !(opcode & 0x20000)) { last_fc_for_exception_3 |= 8; // set N/I } if (opcode & 0x10000) last_fc_for_exception_3 |= 8; } } void exception2_fetch_opcode(uae_u32 opcode, int offset, int pcoffset) { exception2_fetch_common(opcode, offset); last_addr_for_exception_3 += pcoffset; if (currprefs.cpu_model == 68010) { last_di_for_exception_3 = -1; } Exception(2); } void exception2_fetch(uae_u32 opcode, int offset, int pcoffset) { exception2_fetch_common(opcode, offset); last_addr_for_exception_3 += pcoffset; Exception(2); } bool cpureset (void) { /* RESET hasn't increased PC yet, 1 word offset */ uaecptr pc; #ifndef WINUAE_FOR_HATARI uaecptr ksboot = 0xf80002 - 2; uae_u16 ins; #endif addrbank *ab; bool extreset = false; maybe_disable_fpu(); m68k_reset_delay = currprefs.reset_delay; set_special(SPCFLAG_CHECK); unset_special(SPCFLAG_CPUINRESET); #ifndef WINUAE_FOR_HATARI send_internalevent(INTERNALEVENT_CPURESET); warpmode_reset(); if (cpuboard_forced_hardreset()) { custom_reset_cpu(false, false); m68k_reset(); return true; } if (currprefs.cpu_compatible || currprefs.cpu_memory_cycle_exact) { custom_reset_cpu(false, false); return false; } #endif pc = m68k_getpc () + 2; ab = &get_mem_bank (pc); if (ab->check (pc, 2)) { write_log (_T("CPU reset PC=%x (%s)..\n"), pc - 2, ab->name); #ifndef WINUAE_FOR_HATARI ins = get_word (pc); custom_reset_cpu(false, false); // did memory disappear under us? if (ab == &get_mem_bank (pc)) return false; // it did if ((ins & ~7) == 0x4ed0) { int reg = ins & 7; uae_u32 addr = m68k_areg (regs, reg); if (addr < 0x80000) addr += 0xf80000; write_log (_T("reset/jmp (ax) combination at %08x emulated -> %x\n"), pc, addr); m68k_setpc_normal (addr - 2); return false; } #else customreset (); /* From hatari-glue.c */ return false; #endif } // the best we can do, jump directly to ROM entrypoint // (which is probably what program wanted anyway) #ifndef WINUAE_FOR_HATARI write_log (_T("CPU Reset PC=%x (%s), invalid memory -> %x.\n"), pc, ab->name, ksboot + 2); custom_reset_cpu(false, false); m68k_setpc_normal (ksboot); #else write_log (_T("CPU Reset PC=%x (%s), invalid memory\n"), pc, ab->name); customreset (); /* From hatari-glue.c */ #endif return false; } void do_cycles_stop(int c) { c *= cpucycleunit; if (!currprefs.cpu_compatible) { do_cycles(c); } else { #ifndef WINUAE_FOR_HATARI #ifdef DEBUGGER if (debug_dma) { while (c > 0) { debug_cpu_stop(); x_do_cycles(c > CYCLE_UNIT ? CYCLE_UNIT : c); c -= CYCLE_UNIT; } } else { #endif x_do_cycles(c); #ifdef DEBUGGER } #endif #else x_do_cycles(c); #endif } } void m68k_setstopped(int stoptype) { m68k_set_stop(stoptype); } void m68k_resumestopped(void) { if (!regs.stopped) { return; } if (regs.stopped == 1) { // STOP m68k_incpci(4); } else if (regs.stopped == 2) { // LPSTOP m68k_incpci(6); } m68k_unset_stop(); } uae_u32 mem_access_delay_word_read (uaecptr addr) { uae_u32 v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); // WINUAE_FOR_HATARI switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: v = wait_cpu_cycle_read (addr, 1); break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: v = get_word (addr); x_do_cycles_post (4 * cpucycleunit, v); break; default: v = get_word (addr); break; } regs.db = v; regs.read_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return v; } uae_u32 mem_access_delay_wordi_read (uaecptr addr) { uae_u32 v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); // WINUAE_FOR_HATARI switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: v = wait_cpu_cycle_read (addr, 2); break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: v = get_wordi (addr); x_do_cycles_post (4 * cpucycleunit, v); break; default: v = get_wordi (addr); break; } regs.db = v; regs.read_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return v; } uae_u32 mem_access_delay_byte_read (uaecptr addr) { uae_u32 v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); // WINUAE_FOR_HATARI switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: v = wait_cpu_cycle_read (addr, 0); break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: v = get_byte (addr); x_do_cycles_post (4 * cpucycleunit, v); break; default: v = get_byte (addr); break; } regs.db = (v << 8) | v; regs.read_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return v; } void mem_access_delay_byte_write (uaecptr addr, uae_u32 v) { regs.db = (v << 8) | v; regs.write_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); // WINUAE_FOR_HATARI switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: wait_cpu_cycle_write (addr, 0, v); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: put_byte (addr, v); x_do_cycles_post (4 * cpucycleunit, v); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return; } put_byte (addr, v); } void mem_access_delay_word_write (uaecptr addr, uae_u32 v) { if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); // WINUAE_FOR_HATARI regs.db = v; regs.write_buffer = v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: wait_cpu_cycle_write (addr, 1, v); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: put_word (addr, v); x_do_cycles_post (4 * cpucycleunit, v); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); // WINUAE_FOR_HATARI return; } put_word (addr, v); } static void start_020_cycle(void) { regs.ce020startcycle = get_cycles(); } static void start_020_cycle_prefetch(bool opcode) { regs.ce020startcycle = get_cycles(); // back to back prefetch cycles require 2 extra cycles (maybe) if (opcode && regs.ce020startcycle == regs.ce020prefetchendcycle && currprefs.cpu_cycle_exact) { x_do_cycles(2 * cpucycleunit); regs.ce020startcycle = get_cycles(); } } static void end_020_cycle(void) { regs.ce020endcycle = get_cycles(); } static void end_020_cycle_prefetch(bool opcode) { regs.ce020endcycle = get_cycles(); if (opcode) { regs.ce020prefetchendcycle = regs.ce020endcycle; } else { regs.ce020prefetchendcycle = regs.ce020startcycle; } } // this one is really simple and easy static void fill_icache020 (uae_u32 addr, bool opcode) { int index; uae_u32 tag; uae_u32 data; struct cache020 *c; regs.fc030 = (regs.s ? 4 : 0) | 2; addr &= ~3; if (regs.cacheholdingaddr020 == addr) return; index = (addr >> 2) & (CACHELINES020 - 1); tag = regs.s | (addr & ~((CACHELINES020 << 2) - 1)); c = &caches020[index]; if ((regs.cacr & 1) && c->valid && c->tag == tag) { // cache hit regs.cacheholdingaddr020 = addr; regs.cacheholdingdata020 = c->data; regs.cacheholdingdata_valid = 1; #ifdef WINUAE_FOR_HATARI CpuInstruction.I_Cache_hit++; #endif return; } // cache miss #if 0 // Prefetch apparently can be queued by bus controller // even if bus controller is currently processing // previous data access. // Other combinations are not possible. if (!regs.ce020memcycle_data) { if (regs.ce020memcycles > 0) x_do_cycles (regs.ce020memcycles); regs.ce020memcycles = 0; } #endif start_020_cycle_prefetch(opcode); data = icache_fetch(addr); end_020_cycle_prefetch(opcode); // enabled and not frozen if ((regs.cacr & 1) && !(regs.cacr & 2)) { c->tag = tag; c->valid = true; c->data = data; } regs.cacheholdingaddr020 = addr; regs.cacheholdingdata020 = data; regs.cacheholdingdata_valid = 1; #ifdef WINUAE_FOR_HATARI CpuInstruction.I_Cache_miss++; #endif } #if MORE_ACCURATE_68020_PIPELINE #define PIPELINE_DEBUG 0 #if PIPELINE_DEBUG static uae_u16 pipeline_opcode; #endif static void pipeline_020(uaecptr pc) { uae_u16 w = regs.prefetch020[1]; if (regs.prefetch020_valid[1] == 0) { regs.pipeline_stop = -1; return; } if (regs.pipeline_pos < 0) return; if (regs.pipeline_pos > 0) { // handle annoying 68020+ addressing modes if (regs.pipeline_pos == regs.pipeline_r8[0]) { regs.pipeline_r8[0] = 0; if (w & 0x100) { int extra = 0; if ((w & 0x30) == 0x20) extra += 2; if ((w & 0x30) == 0x30) extra += 4; if ((w & 0x03) == 0x02) extra += 2; if ((w & 0x03) == 0x03) extra += 4; regs.pipeline_pos += extra; } return; } if (regs.pipeline_pos == regs.pipeline_r8[1]) { regs.pipeline_r8[1] = 0; if (w & 0x100) { int extra = 0; if ((w & 0x30) == 0x20) extra += 2; if ((w & 0x30) == 0x30) extra += 4; if ((w & 0x03) == 0x02) extra += 2; if ((w & 0x03) == 0x03) extra += 4; regs.pipeline_pos += extra; } return; } } if (regs.pipeline_pos > 2) { regs.pipeline_pos -= 2; // If stop set, prefetches stop 1 word early. if (regs.pipeline_stop > 0 && regs.pipeline_pos == 2) regs.pipeline_stop = -1; return; } if (regs.pipeline_stop) { regs.pipeline_stop = -1; return; } #if PIPELINE_DEBUG pipeline_opcode = w; #endif regs.pipeline_r8[0] = cpudatatbl[w].disp020[0]; regs.pipeline_r8[1] = cpudatatbl[w].disp020[1]; regs.pipeline_pos = cpudatatbl[w].length; #if PIPELINE_DEBUG if (!regs.pipeline_pos) { write_log(_T("Opcode %04x has no size PC=%08x!\n"), w, pc); } #endif // illegal instructions, TRAP, TRAPV, A-line, F-line don't stop prefetches int branch = cpudatatbl[w].branch; if (regs.pipeline_pos > 0 && branch > 0) { // Short branches (Bcc.s) still do one more prefetch. #if 0 // RTS and other unconditional single opcode instruction stop immediately. if (branch == 2) { // Immediate stop regs.pipeline_stop = -1; } else { // Stop 1 word early than normally regs.pipeline_stop = 1; } #else regs.pipeline_stop = 1; #endif } } #endif static uae_u32 get_word_ce020_prefetch_2 (int o, bool opcode) { uae_u32 pc = m68k_getpc () + o; uae_u32 v; v = regs.prefetch020[0]; regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif if (pc & 2) { // branch instruction detected in pipeline: stop fetches until branch executed. if (!MORE_ACCURATE_68020_PIPELINE || regs.pipeline_stop >= 0) { fill_icache020 (pc + 2 + 4, opcode); } regs.prefetch020[2] = regs.cacheholdingdata020 >> 16; } else { regs.prefetch020[2] = (uae_u16)regs.cacheholdingdata020; } regs.db = regs.prefetch020[0]; do_cycles_ce020_internal(2); return v; } uae_u32 get_word_ce020_prefetch (int o) { return get_word_ce020_prefetch_2(o, false); } uae_u32 get_word_ce020_prefetch_opcode (int o) { return get_word_ce020_prefetch_2(o, true); } uae_u32 get_word_020_prefetch (int o) { uae_u32 pc = m68k_getpc () + o; uae_u32 v; v = regs.prefetch020[0]; regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif if (pc & 2) { // branch instruction detected in pipeline: stop fetches until branch executed. if (!MORE_ACCURATE_68020_PIPELINE || regs.pipeline_stop >= 0) { fill_icache020 (pc + 2 + 4, false); } regs.prefetch020[2] = regs.cacheholdingdata020 >> 16; } else { regs.prefetch020[2] = (uae_u16)regs.cacheholdingdata020; } regs.db = regs.prefetch020[0]; //if ( ( v & 0xffff ) != ( get_word(pc) & 0xffff ) ) // fprintf ( stderr , "prefetch mismatch pc=%x prefetch=%x != mem=%x, i-cache error ?\n" , pc , v&0xffff , get_word(pc)&0xffff ); return v; } // these are also used by 68030. #if 0 #define RESET_CE020_CYCLES \ regs.ce020memcycles = 0; \ regs.ce020memcycle_data = true; #define STORE_CE020_CYCLES \ unsigned long cycs = get_cycles () #define ADD_CE020_CYCLES \ regs.ce020memcycles += get_cycles () - cycs #endif uae_u32 mem_access_delay_long_read_ce020 (uaecptr addr) { uae_u32 v; //fprintf ( stderr , "long read ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: v = wait_cpu_cycle_read_ce020 (addr + 0, 1) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 1) << 0; break; case CE_MEMBANK_CHIP32: if ((addr & 3) != 0) { v = wait_cpu_cycle_read_ce020 (addr + 0, 1) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 1) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, -1); } break; case CE_MEMBANK_FAST32: v = get_long (addr); if ((addr & 3) != 0) do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); else do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; case CE_MEMBANK_FAST16: v = get_long (addr); do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); break; default: v = get_long (addr); break; } //fprintf ( stderr , "long read2 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); end_020_cycle(); //fprintf ( stderr , "long read3 ce020 %lu %lu\n" , currcycle / cpucycleunit , currcycle ); return v; } uae_u32 mem_access_delay_longi_read_ce020 (uaecptr addr) { uae_u32 v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: v = wait_cpu_cycle_read_ce020 (addr + 0, 2) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 2) << 0; break; case CE_MEMBANK_CHIP32: if ((addr & 3) != 0) { v = wait_cpu_cycle_read_ce020 (addr + 0, 2) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 2) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, -2); } break; case CE_MEMBANK_FAST32: v = get_longi (addr); if ((addr & 3) != 0) do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); else do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; case CE_MEMBANK_FAST16: v = get_longi (addr); do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); break; default: v = get_longi (addr); break; } return v; } uae_u32 mem_access_delay_wordi_read_ce020 (uaecptr addr) { uae_u32 v; start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ((addr & 3) == 3) { v = wait_cpu_cycle_read_ce020 (addr + 0, 0) << 8; v |= wait_cpu_cycle_read_ce020 (addr + 1, 0) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, 1); } break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: v = get_wordi (addr); if ((addr & 3) == 3) do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); else do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; default: v = get_wordi (addr); break; } end_020_cycle(); return v; } uae_u32 mem_access_delay_word_read_ce020 (uaecptr addr) { uae_u32 v; start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ((addr & 3) == 3) { v = wait_cpu_cycle_read_ce020 (addr + 0, 0) << 8; v |= wait_cpu_cycle_read_ce020 (addr + 1, 0) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, 1); } break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: v = get_word (addr); if ((addr & 3) == 3) do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); else do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; default: v = get_word (addr); break; } end_020_cycle(); return v; } uae_u32 mem_access_delay_byte_read_ce020 (uaecptr addr) { uae_u32 v; start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: v = wait_cpu_cycle_read_ce020 (addr, 0); break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: v = get_byte (addr); do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; default: v = get_byte (addr); break; } end_020_cycle(); return v; } void mem_access_delay_byte_write_ce020 (uaecptr addr, uae_u32 v) { start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: wait_cpu_cycle_write_ce020 (addr, 0, v); break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: put_byte (addr, v); do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; default: put_byte (addr, v); break; } end_020_cycle(); } void mem_access_delay_word_write_ce020 (uaecptr addr, uae_u32 v) { start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ((addr & 3) == 3) { wait_cpu_cycle_write_ce020 (addr + 0, 0, (v >> 8) & 0xff); wait_cpu_cycle_write_ce020 (addr + 1, 0, (v >> 0) & 0xff); } else { wait_cpu_cycle_write_ce020 (addr + 0, 1, v); } break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: put_word (addr, v); if ((addr & 3) == 3) do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); else do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; default: put_word (addr, v); break; } end_020_cycle(); } void mem_access_delay_long_write_ce020 (uaecptr addr, uae_u32 v) { start_020_cycle(); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: wait_cpu_cycle_write_ce020 (addr + 0, 1, (v >> 16) & 0xffff); wait_cpu_cycle_write_ce020 (addr + 2, 1, (v >> 0) & 0xffff); break; case CE_MEMBANK_CHIP32: if ((addr & 3) != 0) { wait_cpu_cycle_write_ce020 (addr + 0, 1, (v >> 16) & 0xffff); wait_cpu_cycle_write_ce020 (addr + 2, 1, (v >> 0) & 0xffff); } else { wait_cpu_cycle_write_ce020 (addr + 0, -1, v); } break; case CE_MEMBANK_FAST32: put_long (addr, v); if ((addr & 3) != 0) do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); else do_cycles_ce020_mem (1 * CPU020_MEM_CYCLE, v); break; case CE_MEMBANK_FAST16: put_long (addr, v); do_cycles_ce020_mem (2 * CPU020_MEM_CYCLE, v); break; default: put_long (addr, v); break; } end_020_cycle(); } // 68030 caches aren't so simple as 68020 cache.. STATIC_INLINE struct cache030 *geticache030 (struct cache030 *cp, uaecptr addr, uae_u32 *tagp, int *lwsp) { int index, lws; uae_u32 tag; struct cache030 *c; addr &= ~3; index = (addr >> 4) & (CACHELINES030 - 1); tag = regs.s | (addr & ~((CACHELINES030 << 4) - 1)); lws = (addr >> 2) & 3; c = &cp[index]; *tagp = tag; *lwsp = lws; //fprintf ( stderr , "getcache addr=%x index=%x tag=%x lws=%x\n" , addr , index , tag , lws ); return c; } STATIC_INLINE void update_icache030 (struct cache030 *c, uae_u32 val, uae_u32 tag, int lws) { if (c->tag != tag) c->valid[0] = c->valid[1] = c->valid[2] = c->valid[3] = false; c->tag = tag; c->valid[lws] = true; c->data[lws] = val; } STATIC_INLINE struct cache030 *getdcache030 (struct cache030 *cp, uaecptr addr, uae_u32 *tagp, int *lwsp) { int index, lws; uae_u32 tag; struct cache030 *c; addr &= ~3; index = (addr >> 4) & (CACHELINES030 - 1); tag = addr & ~((CACHELINES030 << 4) - 1); lws = (addr >> 2) & 3; c = &cp[index]; *tagp = tag; *lwsp = lws; return c; } STATIC_INLINE void update_dcache030 (struct cache030 *c, uae_u32 val, uae_u32 tag, uae_u8 fc, int lws) { if (c->tag != tag) c->valid[0] = c->valid[1] = c->valid[2] = c->valid[3] = false; c->tag = tag; c->fc = fc; c->valid[lws] = true; c->data[lws] = val; } static bool maybe_icache030(uae_u32 addr) { int lws; uae_u32 tag; struct cache030 *c; regs.fc030 = (regs.s ? 4 : 0) | 2; addr &= ~3; if (regs.cacheholdingaddr020 == addr || regs.cacheholdingdata_valid == 0) return true; c = geticache030(icaches030, addr, &tag, &lws); if ((regs.cacr & 1) && c->valid[lws] && c->tag == tag) { // cache hit regs.cacheholdingaddr020 = addr; regs.cacheholdingdata020 = c->data[lws]; return true; } return false; } static void fill_icache030(uae_u32 addr) { int lws; uae_u32 tag; uae_u32 data; struct cache030 *c; //fprintf ( stderr , "fill icache030 %x %x %x\n" , addr , regs.cacr , regs.cacheholdingaddr020 ); regs.fc030 = (regs.s ? 4 : 0) | 2; addr &= ~3; if (regs.cacheholdingaddr020 == addr || regs.cacheholdingdata_valid == 0) return; c = geticache030 (icaches030, addr, &tag, &lws); if ((regs.cacr & 1) && c->valid[lws] && c->tag == tag) { // cache hit regs.cacheholdingaddr020 = addr; regs.cacheholdingdata020 = c->data[lws]; //fprintf ( stderr , "fill ica %x -> hit %x %x\n" , addr , regs.cacheholdingdata020 , regs.cacr ); #ifdef WINUAE_FOR_HATARI CpuInstruction.I_Cache_hit++; #endif return; } TRY (prb2) { // cache miss if (currprefs.cpu_cycle_exact) { regs.ce020memcycle_data = false; start_020_cycle_prefetch(false); data = icache_fetch(addr); end_020_cycle_prefetch(false); } else { data = icache_fetch(addr); } } CATCH (prb2) { // Bus error/MMU access fault: Delayed exception. regs.cacheholdingdata_valid = 0; regs.cacheholdingaddr020 = 0xffffffff; regs.cacheholdingdata020 = 0xffffffff; end_020_cycle_prefetch(false); STOPTRY; return; } ENDTRY if (mmu030_cache_state & CACHE_ENABLE_INS) { if ((regs.cacr & 0x03) == 0x01) { // instruction cache not frozen and enabled //fprintf ( stderr , "fill ica %x -> update %x\n" , addr , data ); update_icache030 (c, data, tag, lws); } // Do burst fetch if enabled, cache is not frozen, all line slots invalid, and 32-bit CPU local bus (no chip ram). // // Burst cycles 2-4 are handled by the 030 bus controller rather than the sequencing unit. // The MMU only translates the first cache fetch (above) and the following 3 fetches increment // address lines A2-A3 (optionally via external hardware). If a bus error occurs, no exception // is generated and the remaining cache line slots are left invalid. // if ((mmu030_cache_state & CACHE_ENABLE_INS_BURST) && (regs.cacr & 0x11) == 0x11) { if (c->valid[0] + c->valid[1] + c->valid[2] + c->valid[3] == 1) { //fprintf ( stderr , "fill ica %x -> burst %x\n" , addr , data ); uaecptr physaddr = addr; if (currprefs.mmu_model) { physaddr = mmu030_translate(addr, regs.s != 0, false, false); } if (ce_banktype[physaddr >> 16] == CE_MEMBANK_FAST32) { int i; for (i = 0; i < 4; i++) { if (c->valid[i]) break; } uaecptr baddr = physaddr & ~15; if (currprefs.mmu_model) { TRY (prb) { // TODO: Need memory functions for burst row and burst column access. for (int j = 0; j < 3; j++) { i++; i &= 3; c->data[i] = get_longi(baddr + i * 4); c->valid[i] = true; if (currprefs.cpu_cycle_exact) do_cycles_ce020_mem(1 * (CPU020_MEM_CYCLE - 1), c->data[i]); } } CATCH (prb) { ; // abort burst fetch if bus error, do not report it. } ENDTRY } else { for (int j = 0; j < 3; j++) { i++; i &= 3; c->data[i] = get_longi(baddr + i * 4); c->valid[i] = true; if (currprefs.cpu_cycle_exact) do_cycles_ce020_mem(1 * (CPU020_MEM_CYCLE - 1), c->data[i]); } } } } } } regs.cacheholdingaddr020 = addr; regs.cacheholdingdata020 = data; //fprintf ( stderr , "fill ica %x -> miss %x\n" , addr , regs.cacheholdingdata020 ); #ifdef WINUAE_FOR_HATARI CpuInstruction.I_Cache_miss++; #endif } #if VALIDATE_68030_DATACACHE static void validate_dcache030(void) { #ifdef WINUAE_FOR_HATARI int BusMode_old = BusMode; BusMode = BUS_MODE_DEBUGGER; /* Don't trigger bus error when reading RAM 0-$7FF */ #endif for (int i = 0; i < CACHELINES030; i++) { struct cache030 *c = &dcaches030[i]; uae_u32 addr = c->tag & ~((CACHELINES030 << 4) - 1); addr |= i << 4; for (int j = 0; j < 4; j++) { if (c->valid[j]) { uae_u32 v = get_long(addr); if (v != c->data[j]) { write_log(_T("Address %08x data cache mismatch %08x != %08x\n"), addr, v, c->data[j]); } } addr += 4; } } #ifdef WINUAE_FOR_HATARI BusMode = BusMode_old; #endif } static void validate_dcache030_read(uae_u32 addr, uae_u32 ov, int size) { #ifdef WINUAE_FOR_HATARI int BusMode_old = BusMode; BusMode = BUS_MODE_DEBUGGER; /* Don't trigger bus error when reading RAM 0-$7FF */ #endif uae_u32 ov2; if (size == 2) { ov2 = get_long(addr); } else if (size == 1) { ov2 = get_word(addr); ov &= 0xffff; } else { ov2 = get_byte(addr); ov &= 0xff; } if (ov2 != ov) { write_log(_T("Address read %08x data cache mismatch %08x != %08x\n"), addr, ov2, ov); } #ifdef WINUAE_FOR_HATARI BusMode = BusMode_old; #endif } #endif // and finally the worst part, 68030 data cache.. static void write_dcache030x(uaecptr addr, uae_u32 val, uae_u32 size, uae_u32 fc) { if (regs.cacr & 0x100) { static const uae_u32 mask[3] = { 0xff000000, 0xffff0000, 0xffffffff }; struct cache030 *c1, *c2; int lws1, lws2; uae_u32 tag1, tag2; int aligned = addr & 3; int wa = regs.cacr & 0x2000; int width = 8 << size; int offset = 8 * aligned; int hit; c1 = getdcache030(dcaches030, addr, &tag1, &lws1); hit = c1->tag == tag1 && c1->fc == fc && c1->valid[lws1]; // Write-allocate can create new valid cache entry if // long aligned long write and MMU CI is not active. // All writes ignore external CIIN signal. // CACHE_DISABLE_ALLOCATE = emulation only method to disable WA caching completely. if (width == 32 && offset == 0 && wa) { if (!(mmu030_cache_state & CACHE_DISABLE_MMU) && !(mmu030_cache_state & CACHE_DISABLE_ALLOCATE)) { update_dcache030(c1, val, tag1, fc, lws1); //fprintf ( stderr , "write cache1 %x %x %d tag1 %x lws1 %x tag2 %x lws2 %x ctag %x data %x\n", addr, val, size, tag1, lws1, tag2, lws2, c1->tag , c1->data[lws1] ); #if VALIDATE_68030_DATACACHE validate_dcache030(); #endif } else if (hit) { // Does real 68030 do this if MMU cache inhibited? c1->valid[lws1] = false; } return; } if (hit || wa) { if (hit) { uae_u32 val_left_aligned = val << (32 - width); c1->data[lws1] &= ~(mask[size] >> offset); c1->data[lws1] |= val_left_aligned >> offset; } else { c1->valid[lws1] = false; } } // do we need to update a 2nd cache entry ? if (width + offset > 32) { c2 = getdcache030(dcaches030, addr + 4, &tag2, &lws2); hit = c2->tag == tag2 && c2->fc == fc && c2->valid[lws2]; if (hit || wa) { if (hit) { c2->data[lws2] &= 0xffffffff >> (width + offset - 32); c2->data[lws2] |= val << (32 - (width + offset - 32)); } else { c2->valid[lws2] = false; } } } #if VALIDATE_68030_DATACACHE validate_dcache030(); #endif } } void write_dcache030_bput(uaecptr addr, uae_u32 v,uae_u32 fc) { //fprintf ( stderr , "write dcache %x %x %d\n" , addr , v , size ); regs.fc030 = fc; dcache_bput(addr, v); write_dcache030x(addr, v, 0, fc); } void write_dcache030_wput(uaecptr addr, uae_u32 v,uae_u32 fc) { regs.fc030 = fc; dcache_wput(addr, v); write_dcache030x(addr, v, 1, fc); } void write_dcache030_lput(uaecptr addr, uae_u32 v,uae_u32 fc) { regs.fc030 = fc; dcache_lput(addr, v); write_dcache030x(addr, v, 2, fc); } // 68030 MMU bus fault retry case, direct write, store to cache if enabled void write_dcache030_retry(uaecptr addr, uae_u32 v, uae_u32 fc, int size, int flags) { regs.fc030 = fc; mmu030_put_generic(addr, v, fc, size, flags); write_dcache030x(addr, v, size, fc); } static void dcache030_maybe_burst(uaecptr addr, struct cache030 *c, int lws) { // Do burst fetch if enabled, cache not frozen, all line slots invalid, and 32-bit CPU local bus (no chip ram). // (See notes about burst fetches in icache routines) if (c->valid[0] + c->valid[1] + c->valid[2] + c->valid[3] == 1) { uaecptr physaddr = addr; if (currprefs.mmu_model) { physaddr = mmu030_translate(addr, regs.s != 0, false, false); } if (ce_banktype[physaddr >> 16] == CE_MEMBANK_FAST32) { int i; for (i = 0; i < 4; i++) { if (c->valid[i]) break; } uaecptr baddr = physaddr & ~15; if (currprefs.mmu_model) { TRY (prb) { // TODO: Need memory functions for burst row and burst column access. for (int j = 0; j < 3; j++) { i++; i &= 3; c->data[i] = get_long(baddr + i * 4); c->valid[i] = true; if (currprefs.cpu_cycle_exact) do_cycles_ce020_mem(1 * (CPU020_MEM_CYCLE - 1), c->data[i]); } } CATCH (prb) { ; // abort burst fetch if bus error } ENDTRY } else { for (int j = 0; j < 3; j++) { i++; i &= 3; c->data[i] = get_long(baddr + i * 4); c->valid[i] = true; if (currprefs.cpu_cycle_exact) do_cycles_ce020_mem(1 * (CPU020_MEM_CYCLE - 1), c->data[i]); } } } #if VALIDATE_68030_DATACACHE validate_dcache030(); #endif } } #ifdef DEBUGGER static uae_u32 read_dcache030_debug(uaecptr addr, uae_u32 size, uae_u32 fc, bool *cached) { static const uae_u32 mask[3] = { 0x000000ff, 0x0000ffff, 0xffffffff }; struct cache030 *c1, *c2; int lws1, lws2; uae_u32 tag1, tag2; int aligned = addr & 3; uae_u32 v1, v2; int width = 8 << size; int offset = 8 * aligned; uae_u32 out; *cached = false; if (!currprefs.cpu_data_cache) { if (size == 0) return get_byte_debug(addr); if (size == 1) return get_word_debug(addr); return get_long_debug(addr); } c1 = getdcache030(dcaches030, addr, &tag1, &lws1); addr &= ~3; if (!c1->valid[lws1] || c1->tag != tag1 || c1->fc != fc) { v1 = get_long_debug(addr); } else { // Cache hit, inhibited caching do not prevent read hits. v1 = c1->data[lws1]; *cached = true; } // only one long fetch needed? if (width + offset <= 32) { out = v1 >> (32 - (offset + width)); out &= mask[size]; return out; } // no, need another one addr += 4; c2 = getdcache030(dcaches030, addr, &tag2, &lws2); if (!c2->valid[lws2] || c2->tag != tag2 || c2->fc != fc) { v2 = get_long_debug(addr); } else { v2 = c2->data[lws2]; *cached = true; } uae_u64 v64 = ((uae_u64)v1 << 32) | v2; out = (uae_u32)(v64 >> (64 - (offset + width))); out &= mask[size]; return out; } #endif // [HATARI] Define next line to check for 68030 data cache mismatch after every write //#define WINUAE_FOR_HATARI_DEBUG_CACHE #ifdef WINUAE_FOR_HATARI_DEBUG_CACHE bool read_dcache030_2_real (uaecptr addr, uae_u32 size, uae_u32 *valp); static bool read_dcache030_2(uaecptr addr, uae_u32 size, uae_u32 *valp) { bool b; int BusMode_old = BusMode; b = read_dcache030_2_real ( addr , size , valp ); if ( b==false) return false; BusMode = BUS_MODE_DEBUGGER; /* Don't trigger bus error when reading RAM 0-$7FF */ if ( ( ( size==2 ) && ( *valp != get_long ( addr ) ) ) || ( ( size==1 ) && ( (*valp&0xffff) != (get_word ( addr ) & 0xffff) ) ) || ( ( size==0 ) && ( (*valp&0xff) != (get_byte ( addr ) & 0xff ) ) ) ) fprintf ( stderr , "d-cache mismatch pc=%x addr=%x size=%d cache=%x != mem=%x, d-cache error ?\n" , m68k_getpc(), addr, size, *valp , get_long(addr) ); BusMode = BusMode_old; return true; } bool read_dcache030_2_real(uaecptr addr, uae_u32 size, uae_u32 *valp) #else static bool read_dcache030_2(uaecptr addr, uae_u32 size, uae_u32 *valp) #endif { // data cache enabled? if (!(regs.cacr & 0x100)) return false; uae_u32 addr_o = addr; uae_u32 fc = regs.fc030; static const uae_u32 mask[3] = { 0x000000ff, 0x0000ffff, 0xffffffff }; struct cache030 *c1, *c2; int lws1, lws2; uae_u32 tag1, tag2; int aligned = addr & 3; uae_u32 v1, v2; int width = 8 << size; int offset = 8 * aligned; uae_u32 out; c1 = getdcache030(dcaches030, addr, &tag1, &lws1); addr &= ~3; if (!c1->valid[lws1] || c1->tag != tag1 || c1->fc != fc) { // MMU validate address, returns zero if valid but uncacheable // throws bus error if invalid uae_u8 cs = dcache_check(addr_o, false, size); if (!(cs & CACHE_ENABLE_DATA)) return false; v1 = dcache_lget(addr); update_dcache030(c1, v1, tag1, fc, lws1); if ((cs & CACHE_ENABLE_DATA_BURST) && (regs.cacr & 0x1100) == 0x1100) dcache030_maybe_burst(addr, c1, lws1); #if VALIDATE_68030_DATACACHE validate_dcache030(); #endif //fprintf ( stderr , "read cache %x %x %d tag1 %x lws1 %x tag2 %x lws2 %x ref %x\n", addr, v1, size, tag1, lws1, tag2, lws2 , get_long (0x1f81ec) ); #ifdef WINUAE_FOR_HATARI CpuInstruction.D_Cache_miss++; #endif } else { // Cache hit, inhibited caching do not prevent read hits. v1 = c1->data[lws1]; #ifdef WINUAE_FOR_HATARI CpuInstruction.D_Cache_hit++; #endif } // only one long fetch needed? if (width + offset <= 32) { out = v1 >> (32 - (offset + width)); out &= mask[size]; #if VALIDATE_68030_DATACACHE validate_dcache030_read(addr_o, out, size); #endif *valp = out; return true; } // no, need another one addr += 4; c2 = getdcache030(dcaches030, addr, &tag2, &lws2); if (!c2->valid[lws2] || c2->tag != tag2 || c2->fc != fc) { uae_u8 cs = dcache_check(addr, false, 2); if (!(cs & CACHE_ENABLE_DATA)) return false; v2 = dcache_lget(addr); update_dcache030(c2, v2, tag2, fc, lws2); if ((cs & CACHE_ENABLE_DATA_BURST) && (regs.cacr & 0x1100) == 0x1100) dcache030_maybe_burst(addr, c2, lws2); #if VALIDATE_68030_DATACACHE validate_dcache030(); #endif //fprintf ( stderr , "read cache %x %x %d tag1 %x lws1 %x tag2 %x lws2 %x\n", addr, v1, size, tag1, lws1, tag2, lws2 ); #ifdef WINUAE_FOR_HATARI CpuInstruction.D_Cache_miss++; #endif } else { v2 = c2->data[lws2]; #ifdef WINUAE_FOR_HATARI CpuInstruction.D_Cache_hit++; #endif } uae_u64 v64 = ((uae_u64)v1 << 32) | v2; out = (uae_u32)(v64 >> (64 - (offset + width))); out &= mask[size]; #if VALIDATE_68030_DATACACHE validate_dcache030_read(addr_o, out, size); #endif *valp = out; return true; } static uae_u32 read_dcache030 (uaecptr addr, uae_u32 size, uae_u32 fc) { uae_u32 val; regs.fc030 = fc; if (!read_dcache030_2(addr, size, &val)) { // read from memory, data cache is disabled or inhibited. if (size == 2) return dcache_lget(addr); else if (size == 1) return dcache_wget(addr); else return dcache_bget(addr); } return val; } // 68030 MMU bus fault retry case, either read from cache or use direct reads uae_u32 read_dcache030_retry(uaecptr addr, uae_u32 fc, int size, int flags) { uae_u32 val; regs.fc030 = fc; if (!read_dcache030_2(addr, size, &val)) { return mmu030_get_generic(addr, fc, size, flags); } return val; } uae_u32 read_dcache030_bget(uaecptr addr, uae_u32 fc) { return read_dcache030(addr, 0, fc); } uae_u32 read_dcache030_wget(uaecptr addr, uae_u32 fc) { return read_dcache030(addr, 1, fc); } uae_u32 read_dcache030_lget(uaecptr addr, uae_u32 fc) { return read_dcache030(addr, 2, fc); } uae_u32 read_dcache030_mmu_bget(uaecptr addr) { return read_dcache030_bget(addr, (regs.s ? 4 : 0) | 1); } uae_u32 read_dcache030_mmu_wget(uaecptr addr) { return read_dcache030_wget(addr, (regs.s ? 4 : 0) | 1); } uae_u32 read_dcache030_mmu_lget(uaecptr addr) { return read_dcache030_lget(addr, (regs.s ? 4 : 0) | 1); } void write_dcache030_mmu_bput(uaecptr addr, uae_u32 val) { write_dcache030_bput(addr, val, (regs.s ? 4 : 0) | 1); } void write_dcache030_mmu_wput(uaecptr addr, uae_u32 val) { write_dcache030_wput(addr, val, (regs.s ? 4 : 0) | 1); } void write_dcache030_mmu_lput(uaecptr addr, uae_u32 val) { write_dcache030_lput(addr, val, (regs.s ? 4 : 0) | 1); } uae_u32 read_dcache030_lrmw_mmu_fcx(uaecptr addr, uae_u32 size, int fc) { if (currprefs.cpu_data_cache) { mmu030_cache_state = CACHE_DISABLE_MMU; if (size == 0) return read_dcache030_bget(addr, fc); if (size == 1) return read_dcache030_wget(addr, fc); return read_dcache030_lget(addr, fc); } else { if (size == 0) return read_data_030_bget(addr); if (size == 1) return read_data_030_wget(addr); return read_data_030_lget(addr); } } uae_u32 read_dcache030_lrmw_mmu(uaecptr addr, uae_u32 size) { return read_dcache030_lrmw_mmu_fcx(addr, size, (regs.s ? 4 : 0) | 1); } void write_dcache030_lrmw_mmu_fcx(uaecptr addr, uae_u32 val, uae_u32 size, int fc) { if (currprefs.cpu_data_cache) { mmu030_cache_state = CACHE_DISABLE_MMU; if (size == 0) write_dcache030_bput(addr, val, fc); else if (size == 1) write_dcache030_wput(addr, val, fc); else write_dcache030_lput(addr, val, fc); } else { if (size == 0) write_data_030_bput(addr, val); else if (size == 1) write_data_030_wput(addr, val); else write_data_030_lput(addr, val); } } void write_dcache030_lrmw_mmu(uaecptr addr, uae_u32 val, uae_u32 size) { write_dcache030_lrmw_mmu_fcx(addr, val, size, (regs.s ? 4 : 0) | 1); } static void do_access_or_bus_error(uaecptr pc, uaecptr pcnow) { // TODO: handle external bus errors if (!currprefs.mmu_model) return; if (pc != 0xffffffff) regs.instruction_pc = pc; mmu030_opcode = -1; mmu030_page_fault(pcnow, true, -1, 0); } static uae_u32 get_word_ce030_prefetch_2 (int o) { uae_u32 pc = m68k_getpc () + o; uae_u32 v; //fprintf ( stderr , "get_word_ce030_prefetch_2 %d pc=%x\n" , currcycle , pc); v = regs.prefetch020[0]; regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif if (pc & 2) { // branch instruction detected in pipeline: stop fetches until branch executed. if (!MORE_ACCURATE_68020_PIPELINE || regs.pipeline_stop >= 0) { fill_icache030 (pc + 2 + 4); } else { if (regs.cacheholdingdata_valid > 0) regs.cacheholdingdata_valid++; } regs.prefetch020[2] = regs.cacheholdingdata020 >> 16; } else { pc += 4; // cacheholdingdata020 may be invalid if RTE from bus error if ((!MORE_ACCURATE_68020_PIPELINE || regs.pipeline_stop >= 0) && regs.cacheholdingaddr020 != pc) { fill_icache030 (pc); } regs.prefetch020[2] = (uae_u16)regs.cacheholdingdata020; } regs.db = regs.prefetch020[0]; do_cycles_ce020_internal(2); return v; } uae_u32 get_word_ce030_prefetch (int o) { return get_word_ce030_prefetch_2(o); } uae_u32 get_word_ce030_prefetch_opcode (int o) { return get_word_ce030_prefetch_2(o); } // [HATARI] Define next line to check for 68030 prefetch mismatch //#define WINUAE_FOR_HATARI_DEBUG_PREFETCH_030 #ifdef WINUAE_FOR_HATARI_DEBUG_PREFETCH_030 uae_u32 get_word_030_prefetch_real (int o); uae_u32 get_word_030_prefetch (int o) { uae_u32 v; v = get_word_030_prefetch_real(o); if ( ( v & 0xffff ) != ( get_iword_mmu030(o) & 0xffff ) ) fprintf ( stderr , "prefetch mismatch m68k_getpc=%x o=%d prefetch=%04x != mem=%04x, i-cache error ?\n" , m68k_getpc() , o , v&0xffff , get_iword_mmu030(o)&0xffff ); return v; } uae_u32 get_word_030_prefetch_real (int o) #else uae_u32 get_word_030_prefetch (int o) #endif { uae_u32 pc = m68k_getpc () + o; uae_u32 v; v = regs.prefetch020[0]; regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; regs.prefetch020_valid[0] = regs.prefetch020_valid[1]; regs.prefetch020_valid[1] = regs.prefetch020_valid[2]; regs.prefetch020_valid[2] = false; if (!regs.prefetch020_valid[1]) { if (regs.pipeline_stop) { regs.db = regs.prefetch020[0]; return v; } do_access_or_bus_error(0xffffffff, pc + 4); } #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif if (pc & 2) { // branch instruction detected in pipeline: stop fetches until branch executed. if (!MORE_ACCURATE_68020_PIPELINE || regs.pipeline_stop >= 0) { fill_icache030 (pc + 2 + 4); } else { if (regs.cacheholdingdata_valid > 0) regs.cacheholdingdata_valid++; } regs.prefetch020[2] = regs.cacheholdingdata020 >> 16; } else { pc += 4; // cacheholdingdata020 may be invalid if RTE from bus error if ((!MORE_ACCURATE_68020_PIPELINE || regs.pipeline_stop >= 0) && regs.cacheholdingaddr020 != pc) { fill_icache030 (pc); } regs.prefetch020[2] = (uae_u16)regs.cacheholdingdata020; } regs.prefetch020_valid[2] = regs.cacheholdingdata_valid; regs.db = regs.prefetch020[0]; //if ( ( v & 0xffff ) != ( get_word(pc) & 0xffff ) ) // fprintf ( stderr , "prefetch mismatch pc=%x prefetch=%x != mem=%x, i-cache error ?\n" , pc , v&0xffff , get_word(pc)&0xffff ); return v; } uae_u32 get_word_icache030(uaecptr addr) { fill_icache030(addr); return regs.cacheholdingdata020 >> ((addr & 2) ? 0 : 16); } uae_u32 get_long_icache030(uaecptr addr) { uae_u32 v; fill_icache030(addr); if ((addr & 2) == 0) return regs.cacheholdingdata020; v = regs.cacheholdingdata020 << 16; fill_icache030(addr + 4); v |= regs.cacheholdingdata020 >> 16; return v; } uae_u32 fill_icache040(uae_u32 addr) { int index, lws; uae_u32 tag, addr2; struct cache040 *c; int line; addr2 = addr & ~15; lws = (addr >> 2) & 3; if (regs.prefetch020addr == addr2) { return regs.prefetch040[lws]; } if (regs.cacr & 0x8000) { uae_u8 cs = mmu_cache_state; if (!(ce_cachable[addr >> 16] & CACHE_ENABLE_INS)) cs = CACHE_DISABLE_MMU; index = (addr >> 4) & cacheisets04060mask; tag = addr & cacheitag04060mask; c = &icaches040[index]; for (int i = 0; i < CACHELINES040; i++) { if (c->valid[cache_lastline] && c->tag[cache_lastline] == tag) { // cache hit if (!(cs & CACHE_ENABLE_INS) || (cs & CACHE_DISABLE_MMU)) { c->valid[cache_lastline] = false; goto end; } if ((lws & 1) != icachehalfline) { icachehalfline ^= 1; icachelinecnt++; #ifdef WINUAE_FOR_HATARI CpuInstruction.I_Cache_hit++; #endif } return c->data[cache_lastline][lws]; } cache_lastline++; cache_lastline &= (CACHELINES040 - 1); } // cache miss regs.prefetch020addr = 0xffffffff; regs.prefetch040[0] = icache_fetch(addr2 + 0); regs.prefetch040[1] = icache_fetch(addr2 + 4); regs.prefetch040[2] = icache_fetch(addr2 + 8); regs.prefetch040[3] = icache_fetch(addr2 + 12); regs.prefetch020addr = addr2; if (!(cs & CACHE_ENABLE_INS) || (cs & CACHE_DISABLE_MMU)) goto end; if (regs.cacr & 0x00004000) // 68060 NAI goto end; if (c->valid[0] && c->valid[1] && c->valid[2] && c->valid[3]) { line = icachelinecnt & (CACHELINES040 - 1); icachehalfline = (lws & 1) ? 0 : 1; } else { for (line = 0; line < CACHELINES040; line++) { if (c->valid[line] == false) break; } } c->tag[line] = tag; c->valid[line] = true; c->data[line][0] = regs.prefetch040[0]; c->data[line][1] = regs.prefetch040[1]; c->data[line][2] = regs.prefetch040[2]; c->data[line][3] = regs.prefetch040[3]; if ((lws & 1) != icachehalfline) { icachehalfline ^= 1; icachelinecnt++; } #ifdef WINUAE_FOR_HATARI CpuInstruction.I_Cache_miss++; #endif return c->data[line][lws]; } end: if (regs.prefetch020addr == addr2) return regs.prefetch040[lws]; regs.prefetch020addr = addr2; regs.prefetch040[0] = icache_fetch(addr2 + 0); regs.prefetch040[1] = icache_fetch(addr2 + 4); regs.prefetch040[2] = icache_fetch(addr2 + 8); regs.prefetch040[3] = icache_fetch(addr2 + 12); return regs.prefetch040[lws]; } STATIC_INLINE void do_cycles_c040_mem (int clocks, uae_u32 val) { x_do_cycles_post (clocks * cpucycleunit, val); } uae_u32 mem_access_delay_longi_read_c040 (uaecptr addr) { uae_u32 v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: v = wait_cpu_cycle_read_ce020 (addr + 0, 2) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 2) << 0; break; case CE_MEMBANK_CHIP32: if ((addr & 3) != 0) { v = wait_cpu_cycle_read_ce020 (addr + 0, 2) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 2) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, -2); } break; case CE_MEMBANK_FAST16: v = get_longi (addr); do_cycles_c040_mem(1, v); break; case CE_MEMBANK_FAST32: v = get_longi (addr); break; default: v = get_longi (addr); break; } return v; } uae_u32 mem_access_delay_long_read_c040 (uaecptr addr) { uae_u32 v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: v = wait_cpu_cycle_read_ce020 (addr + 0, 1) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 1) << 0; break; case CE_MEMBANK_CHIP32: if ((addr & 3) != 0) { v = wait_cpu_cycle_read_ce020 (addr + 0, 1) << 16; v |= wait_cpu_cycle_read_ce020 (addr + 2, 1) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, -1); } break; case CE_MEMBANK_FAST16: v = get_long (addr); do_cycles_c040_mem(1, v); break; case CE_MEMBANK_FAST32: v = get_long (addr); break; default: v = get_long (addr); break; } return v; } uae_u32 mem_access_delay_word_read_c040 (uaecptr addr) { uae_u32 v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ((addr & 3) == 3) { v = wait_cpu_cycle_read_ce020 (addr + 0, 0) << 8; v |= wait_cpu_cycle_read_ce020 (addr + 1, 0) << 0; } else { v = wait_cpu_cycle_read_ce020 (addr, 1); } break; case CE_MEMBANK_FAST16: v = get_word (addr); do_cycles_c040_mem (2, v); break; case CE_MEMBANK_FAST32: v = get_word (addr); break; default: v = get_word (addr); break; } return v; } uae_u32 mem_access_delay_byte_read_c040 (uaecptr addr) { uae_u32 v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: v = wait_cpu_cycle_read_ce020 (addr, 0); break; case CE_MEMBANK_FAST16: v = get_byte (addr); do_cycles_c040_mem (1, v); break; case CE_MEMBANK_FAST32: v = get_byte (addr); break; default: v = get_byte (addr); break; } return v; } void mem_access_delay_byte_write_c040 (uaecptr addr, uae_u32 v) { switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: wait_cpu_cycle_write_ce020 (addr, 0, v); break; case CE_MEMBANK_FAST16: put_byte (addr, v); do_cycles_c040_mem (1, v); break; case CE_MEMBANK_FAST32: put_byte (addr, v); break; default: put_byte (addr, v); break; } } void mem_access_delay_word_write_c040 (uaecptr addr, uae_u32 v) { switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ((addr & 3) == 3) { wait_cpu_cycle_write_ce020 (addr + 0, 0, (v >> 8) & 0xff); wait_cpu_cycle_write_ce020 (addr + 1, 0, (v >> 0) & 0xff); } else { wait_cpu_cycle_write_ce020 (addr + 0, 1, v); } break; case CE_MEMBANK_FAST16: put_word (addr, v); if ((addr & 3) == 3) do_cycles_c040_mem(2, v); else do_cycles_c040_mem(1, v); break; case CE_MEMBANK_FAST32: put_word (addr, v); break; default: put_word (addr, v); break; } } void mem_access_delay_long_write_c040 (uaecptr addr, uae_u32 v) { switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: wait_cpu_cycle_write_ce020 (addr + 0, 1, (v >> 16) & 0xffff); wait_cpu_cycle_write_ce020 (addr + 2, 1, (v >> 0) & 0xffff); break; case CE_MEMBANK_CHIP32: if ((addr & 3) != 0) { wait_cpu_cycle_write_ce020 (addr + 0, 1, (v >> 16) & 0xffff); wait_cpu_cycle_write_ce020 (addr + 2, 1, (v >> 0) & 0xffff); } else { wait_cpu_cycle_write_ce020 (addr + 0, -1, v); } break; case CE_MEMBANK_FAST16: put_long (addr, v); do_cycles_c040_mem(1, v); break; case CE_MEMBANK_FAST32: put_long (addr, v); break; default: put_long (addr, v); break; } } static uae_u32 dcache040_get_data(uaecptr addr, struct cache040 *c, int line, int size) { static const uae_u32 mask[3] = { 0x000000ff, 0x0000ffff, 0xffffffff }; int offset = (addr & 15) * 8; int offset32 = offset & 31; int slot = offset / 32; int width = 8 << size; uae_u32 vv; if (offset32 + width <= 32) { uae_u32 v = c->data[line][slot]; v >>= 32 - (offset32 + width); v &= mask[size]; vv = v; } else { #if VALIDATE_68040_DATACACHE if (slot >= 3) { write_log(_T("invalid dcache040_get_data!\n")); return 0; } #endif uae_u64 v = c->data[line][slot]; v <<= 32; v |= c->data[line][slot + 1]; v >>= 64 - (offset32 + width); vv = v & mask[size]; } return vv; } static void dcache040_update(uaecptr addr, struct cache040 *c, int line, uae_u32 val, int size) { static const uae_u64 mask64[3] = { 0xff, 0xffff, 0xffffffff }; static const uae_u32 mask32[3] = { 0xff, 0xffff, 0xffffffff }; int offset = (addr & 15) * 8; int offset32 = offset & 31; int slot = offset / 32; int width = 8 << size; #if VALIDATE_68040_DATACACHE > 1 validate_dcache040(); #endif if (offset32 + width <= 32) { int shift = 32 - (offset32 + width); uae_u32 v = c->data[line][slot]; v &= ~(mask32[size] << shift); v |= val << shift; c->data[line][slot] = v; c->dirty[line][slot] = true; } else { #if VALIDATE_68040_DATACACHE if (slot >= 3) { write_log(_T("invalid dcache040_update!\n")); return; } #endif int shift = 64 - (offset32 + width); uae_u64 v = c->data[line][slot]; v <<= 32; v |= c->data[line][slot + 1]; v &= ~(mask64[size] << shift); v |= ((uae_u64)val) << shift; c->data[line][slot] = v >> 32; c->dirty[line][slot] = true; c->data[line][slot + 1] = (uae_u32)v; c->dirty[line][slot + 1] = true; } c->gdirty[line] = true; } static int dcache040_fill_line(int index, uae_u32 tag, uaecptr addr) { // cache miss struct cache040 *c = &dcaches040[index]; int line; if (c->valid[0] && c->valid[1] && c->valid[2] && c->valid[3]) { // all lines allocated, choose one, push and invalidate. line = dcachelinecnt & (CACHELINES040 - 1); dcachelinecnt++; dcache040_push_line(index, line, false, true); } else { // at least one invalid for (line = 0; line < CACHELINES040; line++) { if (c->valid[line] == false) break; } } c->tag[line] = tag; c->dirty[line][0] = false; c->dirty[line][1] = false; c->dirty[line][2] = false; c->dirty[line][3] = false; c->gdirty[line] = false; c->data[line][0] = dcache_lget(addr + 0); c->data[line][1] = dcache_lget(addr + 4); c->data[line][2] = dcache_lget(addr + 8); c->data[line][3] = dcache_lget(addr + 12); c->valid[line] = true; return line; } #ifdef DEBUGGER static uae_u32 read_dcache040_debug(uae_u32 addr, int size, bool *cached) { int index; uae_u32 tag; struct cache040 *c; int line; uae_u32 addr_o = addr; uae_u8 cs = mmu_cache_state; *cached = false; if (!currprefs.cpu_data_cache) goto nocache; if (!(regs.cacr & 0x80000000)) goto nocache; addr &= ~15; index = (addr >> 4) & cachedsets04060mask; tag = addr & cachedtag04060mask; c = &dcaches040[index]; for (line = 0; line < CACHELINES040; line++) { if (c->valid[line] && c->tag[line] == tag) { // cache hit return dcache040_get_data(addr_o, c, line, size); } } nocache: if (size == 0) return get_byte_debug(addr); if (size == 1) return get_word_debug(addr); return get_long_debug(addr); } #endif static uae_u32 read_dcache040(uae_u32 addr, int size, uae_u32 (*fetch)(uaecptr)) { int index; uae_u32 tag; struct cache040 *c; int line; uae_u32 addr_o = addr; uae_u8 cs = mmu_cache_state; if (!(regs.cacr & 0x80000000)) goto nocache; #if VALIDATE_68040_DATACACHE > 1 validate_dcache040(); #endif // Simple because 68040+ caches physical addresses (68030 caches logical addresses) if (!(ce_cachable[addr >> 16] & CACHE_ENABLE_DATA)) cs = CACHE_DISABLE_MMU; addr &= ~15; index = (addr >> 4) & cachedsets04060mask; tag = addr & cachedtag04060mask; c = &dcaches040[index]; for (line = 0; line < CACHELINES040; line++) { if (c->valid[line] && c->tag[line] == tag) { // cache hit dcachelinecnt++; // Cache hit but MMU disabled: do not cache, push and invalidate possible existing line if (cs & CACHE_DISABLE_MMU) { dcache040_push_line(index, line, false, true); goto nocache; } return dcache040_get_data(addr_o, c, line, size); } } // Cache miss // 040+ always caches whole line if ((cs & CACHE_DISABLE_MMU) || !(cs & CACHE_ENABLE_DATA) || (cs & CACHE_DISABLE_ALLOCATE) || (regs.cacr & 0x40000000)) { nocache: return fetch(addr_o); } // Allocate new cache line, return requested data. line = dcache040_fill_line(index, tag, addr); return dcache040_get_data(addr_o, c, line, size); } static void write_dcache040(uae_u32 addr, uae_u32 val, int size, void (*store)(uaecptr, uae_u32)) { static const uae_u32 mask[3] = { 0x000000ff, 0x0000ffff, 0xffffffff }; int index; uae_u32 tag; struct cache040 *c; int line; uae_u32 addr_o = addr; uae_u8 cs = mmu_cache_state; val &= mask[size]; if (!(regs.cacr & 0x80000000)) goto nocache; if (!(ce_cachable[addr >> 16] & CACHE_ENABLE_DATA)) cs = CACHE_DISABLE_MMU; addr &= ~15; index = (addr >> 4) & cachedsets04060mask; tag = addr & cachedtag04060mask; c = &dcaches040[index]; for (line = 0; line < CACHELINES040; line++) { if (c->valid[line] && c->tag[line] == tag) { // cache hit dcachelinecnt++; // Cache hit but MMU disabled: do not cache, push and invalidate possible existing line if (cs & CACHE_DISABLE_MMU) { dcache040_push_line(index, line, false, true); goto nocache; } dcache040_update(addr_o, c, line, val, size); // If not copyback mode: push modifications immediately (write-through) if (!(cs & CACHE_ENABLE_COPYBACK) || DISABLE_68040_COPYBACK) { dcache040_push_line(index, line, true, false); } return; } } // Cache miss // 040+ always caches whole line // Writes misses in write-through mode don't allocate new cache lines if (!(cs & CACHE_ENABLE_DATA) || (cs & CACHE_DISABLE_MMU) || (cs & CACHE_DISABLE_ALLOCATE) || !(cs & CACHE_ENABLE_COPYBACK) || (regs.cacr & 0x40000000)) { nocache: store(addr_o, val); return; } // Allocate new cache line and update it with new data. line = dcache040_fill_line(index, tag, addr); dcache040_update(addr_o, c, line, val, size); if (DISABLE_68040_COPYBACK) { dcache040_push_line(index, line, true, false); } } // really unoptimized uae_u32 get_word_icache040(uaecptr addr) { uae_u32 v = fill_icache040(addr); return v >> ((addr & 2) ? 0 : 16); } uae_u32 get_long_icache040(uaecptr addr) { uae_u32 v1, v2; v1 = fill_icache040(addr); if ((addr & 2) == 0) return v1; v2 = fill_icache040(addr + 4); return (v2 >> 16) | (v1 << 16); } uae_u32 get_ilong_cache_040(int o) { return get_long_icache040(m68k_getpci() + o); } uae_u32 get_iword_cache_040(int o) { return get_word_icache040(m68k_getpci() + o); } void put_long_cache_040(uaecptr addr, uae_u32 v) { int offset = addr & 15; // access must not cross cachelines if (offset < 13) { write_dcache040(addr, v, 2, dcache_lput); } else if (offset == 13 || offset == 15) { write_dcache040(addr + 0, v >> 24, 0, dcache_bput); write_dcache040(addr + 1, v >> 8, 1, dcache_wput); write_dcache040(addr + 3, v >> 0, 0, dcache_bput); } else if (offset == 14) { write_dcache040(addr + 0, v >> 16, 1, dcache_wput); write_dcache040(addr + 2, v >> 0, 1, dcache_wput); } } void put_word_cache_040(uaecptr addr, uae_u32 v) { int offset = addr & 15; if (offset < 15) { write_dcache040(addr, v, 1, dcache_wput); } else { write_dcache040(addr + 0, v >> 8, 0, dcache_bput); write_dcache040(addr + 1, v >> 0, 0, dcache_bput); } } void put_byte_cache_040(uaecptr addr, uae_u32 v) { write_dcache040(addr, v, 0, dcache_bput); } uae_u32 get_long_cache_040(uaecptr addr) { uae_u32 v; int offset = addr & 15; if (offset < 13) { v = read_dcache040(addr, 2, dcache_lget); } else if (offset == 13 || offset == 15) { v = read_dcache040(addr + 0, 0, dcache_bget) << 24; v |= read_dcache040(addr + 1, 1, dcache_wget) << 8; v |= read_dcache040(addr + 3, 0, dcache_bget) << 0; } else /* if (offset == 14) */ { v = read_dcache040(addr + 0, 1, dcache_wget) << 16; v |= read_dcache040(addr + 2, 1, dcache_wget) << 0; } return v; } uae_u32 get_word_cache_040(uaecptr addr) { uae_u32 v; int offset = addr & 15; if (offset < 15) { v = read_dcache040(addr, 1, dcache_wget); } else { v = read_dcache040(addr + 0, 0, dcache_bget) << 8; v |= read_dcache040(addr + 1, 0, dcache_bget) << 0; } return v; } uae_u32 get_byte_cache_040(uaecptr addr) { return read_dcache040(addr, 0, dcache_bget); } uae_u32 next_iword_cache040(void) { uae_u32 r = get_word_icache040(m68k_getpci()); m68k_incpci(2); return r; } uae_u32 next_ilong_cache040(void) { uae_u32 r = get_long_icache040(m68k_getpci()); m68k_incpci(4); return r; } #ifdef DEBUGGER uae_u32 get_byte_cache_debug(uaecptr addr, bool *cached) { *cached = false; if (currprefs.cpu_model == 68030) { return read_dcache030_debug(addr, 0, regs.s ? 5 : 1, cached); } else if (currprefs.cpu_model >= 68040) { return read_dcache040_debug(addr, 0, cached); } return get_byte_debug(addr); } uae_u32 get_word_cache_debug(uaecptr addr, bool *cached) { *cached = false; if (currprefs.cpu_model == 68030) { return read_dcache030_debug(addr, 1, regs.s ? 5 : 1, cached); } else if (currprefs.cpu_model >= 68040) { return read_dcache040_debug(addr, 1, cached); } return get_word_debug(addr); } uae_u32 get_long_cache_debug(uaecptr addr, bool *cached) { *cached = false; if (currprefs.cpu_model == 68030) { return read_dcache030_debug(addr, 2, regs.s ? 5 : 1, cached); } else if (currprefs.cpu_model >= 68040) { return read_dcache040_debug(addr, 2, cached); } return get_long_debug(addr); } #endif void check_t0_trace(void) { if (regs.t0 && !regs.t1 && currprefs.cpu_model >= 68020) { unset_special (SPCFLAG_TRACE); set_special (SPCFLAG_DOTRACE); } } static void reset_pipeline_state(void) { #if MORE_ACCURATE_68020_PIPELINE regs.pipeline_pos = 0; regs.pipeline_stop = 0; regs.pipeline_r8[0] = regs.pipeline_r8[1] = -1; #endif } static int add_prefetch_030(int idx, uae_u16 w, uaecptr pc) { regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020_valid[0] = regs.prefetch020_valid[1]; regs.prefetch020[1] = regs.prefetch020[2]; regs.prefetch020_valid[1] = regs.prefetch020_valid[2]; regs.prefetch020[2] = w; regs.prefetch020_valid[2] = regs.cacheholdingdata_valid; #if MORE_ACCURATE_68020_PIPELINE if (idx >= 1) { pipeline_020(pc); } #endif if (!regs.prefetch020_valid[2]) { if (idx == 0 || !regs.pipeline_stop) { // Pipeline refill and first opcode word is invalid? // Generate previously detected bus error/MMU fault do_access_or_bus_error(pc, pc + idx * 2); } } return idx + 1; } void fill_prefetch_030_ntx(void) { //fprintf ( stderr , "fill_prefetch_030_ntx %d\n" , currcycle); uaecptr pc = m68k_getpc (); uaecptr pc2 = pc; int idx = 0; pc &= ~3; mmu030_idx = mmu030_idx_done = 0; reset_pipeline_state(); regs.cacheholdingdata_valid = 1; regs.cacheholdingaddr020 = 0xffffffff; regs.prefetch020_valid[0] = regs.prefetch020_valid[1] = regs.prefetch020_valid[2] = 0; fill_icache030(pc); if (pc2 & 2) { idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc2); } else { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc2); idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc2); } fill_icache030(pc + 4); if (pc2 & 2) { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc2); idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc2); } else { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc2); } ipl_fetch_now(); if (currprefs.cpu_cycle_exact) regs.irc = get_word_ce030_prefetch_opcode (0); else regs.irc = get_word_030_prefetch (0); } void fill_prefetch_030_ntx_continue (void) { uaecptr pc = m68k_getpc (); uaecptr pc_orig = pc; int idx = 0; mmu030_idx = mmu030_idx_done = 0; reset_pipeline_state(); regs.cacheholdingdata_valid = 1; regs.cacheholdingaddr020 = 0xffffffff; if (regs.prefetch020_valid[0] && regs.prefetch020_valid[1] && regs.prefetch020_valid[2]) { for (int i = 2; i >= 0; i--) { regs.prefetch020[i + 1] = regs.prefetch020[i]; } for (int i = 1; i <= 3; i++) { #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif regs.prefetch020[i - 1] = regs.prefetch020[i]; pc += 2; idx++; } } else if (regs.prefetch020_valid[2] && !regs.prefetch020_valid[1]) { regs.prefetch020_valid[1] = regs.prefetch020_valid[2]; regs.prefetch020[1] = regs.prefetch020[2]; regs.prefetch020_valid[2] = 0; pc += 2; idx++; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif if (!regs.pipeline_stop) { if (maybe_icache030(pc)) { regs.prefetch020[2] = regs.cacheholdingdata020 >> (regs.cacheholdingaddr020 == pc ? 16 : 0); } else { regs.prefetch020[2] = icache_fetch_word(pc); } regs.prefetch020_valid[2] = 1; pc += 2; idx++; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif } } else if (regs.prefetch020_valid[2] && regs.prefetch020_valid[1]) { pc += 2; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif pc += 2; #if MORE_ACCURATE_68020_PIPELINE pipeline_020(pc); #endif idx += 2; } while (idx < 2) { regs.prefetch020[0] = regs.prefetch020[1]; regs.prefetch020[1] = regs.prefetch020[2]; regs.prefetch020_valid[0] = regs.prefetch020_valid[1]; regs.prefetch020_valid[1] = regs.prefetch020_valid[2]; regs.prefetch020_valid[2] = false; idx++; } if (idx < 3 && !regs.pipeline_stop) { uaecptr pc2 = pc; pc &= ~3; fill_icache030(pc); if (pc2 & 2) { idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc_orig); } else { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc_orig); if (idx < 3) idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc_orig); } if (idx < 3) { fill_icache030(pc + 4); if (pc2 & 2) { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc_orig); if (idx < 3) idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc_orig); } else { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc_orig); } } } ipl_fetch_now(); if (currprefs.cpu_cycle_exact) regs.irc = get_word_ce030_prefetch_opcode(0); else regs.irc = get_word_030_prefetch(0); } void fill_prefetch_020_ntx(void) { uaecptr pc = m68k_getpc (); uaecptr pc2 = pc; int idx = 0; pc &= ~3; reset_pipeline_state(); fill_icache020 (pc, true); if (pc2 & 2) { idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc); } else { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc); idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc); } fill_icache020 (pc + 4, true); if (pc2 & 2) { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc); idx = add_prefetch_030(idx, regs.cacheholdingdata020, pc); } else { idx = add_prefetch_030(idx, regs.cacheholdingdata020 >> 16, pc); } ipl_fetch_now(); if (currprefs.cpu_cycle_exact) regs.irc = get_word_ce020_prefetch_opcode (0); else regs.irc = get_word_020_prefetch (0); } // Not exactly right, requires logic analyzer checks. void continue_ce020_prefetch(void) { fill_prefetch_020_ntx(); } void continue_020_prefetch(void) { fill_prefetch_020_ntx(); } void continue_ce030_prefetch(void) { fill_prefetch_030_ntx(); } void continue_030_prefetch(void) { fill_prefetch_030_ntx(); } void fill_prefetch_020(void) { fill_prefetch_020_ntx(); check_t0_trace(); } void fill_prefetch_030(void) { fill_prefetch_030_ntx(); check_t0_trace(); } void fill_prefetch (void) { if (currprefs.cachesize) return; if (!currprefs.cpu_compatible) return; reset_pipeline_state(); if (currprefs.cpu_model >= 68040) { if (currprefs.cpu_compatible || currprefs.cpu_memory_cycle_exact) { fill_icache040(m68k_getpc() + 16); fill_icache040(m68k_getpc()); } } else if (currprefs.cpu_model == 68020) { fill_prefetch_020 (); } else if (currprefs.cpu_model == 68030) { fill_prefetch_030 (); } else if (currprefs.cpu_model <= 68010) { uaecptr pc = m68k_getpc (); regs.ir = x_get_word (pc); regs.ird = regs.ir; regs.irc = x_get_word (pc + 2); regs.read_buffer = regs.irc; } } #ifdef WINUAE_FOR_HATARI STATIC_INLINE bool cpuboard_fc_check(uaecptr addr, uae_u32 *v, int size, bool write) { return false; } #endif uae_u32 sfc_nommu_get_byte(uaecptr addr) { uae_u32 v; ismoves_nommu = true; if (!cpuboard_fc_check(addr, &v, 0, false)) v = x_get_byte(addr); ismoves_nommu = false; return v; } uae_u32 sfc_nommu_get_word(uaecptr addr) { uae_u32 v; ismoves_nommu = true; if (!cpuboard_fc_check(addr, &v, 1, false)) v = x_get_word(addr); ismoves_nommu = false; return v; } uae_u32 sfc_nommu_get_long(uaecptr addr) { uae_u32 v; ismoves_nommu = true; if (!cpuboard_fc_check(addr, &v, 2, false)) v = x_get_long(addr); ismoves_nommu = false; return v; } void dfc_nommu_put_byte(uaecptr addr, uae_u32 v) { ismoves_nommu = true; if (!cpuboard_fc_check(addr, &v, 0, true)) x_put_byte(addr, v); ismoves_nommu = false; } void dfc_nommu_put_word(uaecptr addr, uae_u32 v) { ismoves_nommu = true; if (!cpuboard_fc_check(addr, &v, 1, true)) x_put_word(addr, v); ismoves_nommu = false; } void dfc_nommu_put_long(uaecptr addr, uae_u32 v) { ismoves_nommu = true; if (!cpuboard_fc_check(addr, &v, 2, true)) x_put_long(addr, v); ismoves_nommu = false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/newcpu.h000066400000000000000000000704421504763705000236000ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * MC68000 emulation * * Copyright 1995 Bernd Schmidt */ #ifndef UAE_NEWCPU_H #define UAE_NEWCPU_H #include "uae/types.h" #include "readcpu.h" #include "machdep/m68k.h" #include "events.h" #include #ifdef WINUAE_FOR_HATARI #include "compat.h" #include "maccess.h" #include "memory.h" #include "custom.h" /* Possible exceptions sources for M68000_Exception() and Exception() */ // TODO : remove when not used anymore in m68000.c #define M68000_EXC_SRC_CPU 1 /* Direct CPU exception */ #define M68000_EXC_SRC_AUTOVEC 2 /* Auto-vector exception (e.g. VBL) */ #define M68000_EXC_SRC_INT_MFP 3 /* MFP interrupt exception */ #define M68000_EXC_SRC_INT_DSP 4 /* DSP interrupt exception */ #define M68000_EXC_SRC_INT_SCC 5 /* SCC interrupt exception */ #endif #ifndef SET_CFLG #define SET_CFLG(x) (CFLG() = (x)) #define SET_NFLG(x) (NFLG() = (x)) #define SET_VFLG(x) (VFLG() = (x)) #define SET_ZFLG(x) (ZFLG() = (x)) #define SET_XFLG(x) (XFLG() = (x)) #define GET_CFLG() CFLG() #define GET_NFLG() NFLG() #define GET_VFLG() VFLG() #define GET_ZFLG() ZFLG() #define GET_XFLG() XFLG() #define CLEAR_CZNV() do { \ SET_CFLG (0); \ SET_ZFLG (0); \ SET_NFLG (0); \ SET_VFLG (0); \ } while (0) #define COPY_CARRY() (SET_XFLG (GET_CFLG ())) #endif extern const int areg_byteinc[]; extern const int imm8_table[]; extern int movem_index1[256]; extern int movem_index2[256]; extern int movem_next[256]; #ifdef FPUEMU extern int fpp_movem_index1[256]; extern int fpp_movem_index2[256]; extern int fpp_movem_next[256]; #endif extern int hardware_bus_error; typedef uae_u32 REGPARAM3 cpuop_func(uae_u32) REGPARAM; typedef void REGPARAM3 cpuop_func_noret(uae_u32) REGPARAM; struct cputbl { cpuop_func *handler_ff; #ifdef NOFLAGS_SUPPORT_GENCPU cpuop_func_ret *handler_nf; #endif cpuop_func_noret *handler_ff_noret; #ifdef NOFLAGS_SUPPORT_GENCPU cpuop_func_ret *handler_nf_noret; #endif uae_u16 opcode; uae_s8 length; uae_s8 disp020[2]; uae_s8 branch; #ifdef JIT uae_u16 specific; #endif }; #ifdef JIT #define MIN_JIT_CACHE 128 #define MAX_JIT_CACHE 16384 typedef uae_u32 REGPARAM3 compop_func (uae_u32) REGPARAM; #define COMP_OPCODE_ISJUMP 0x0001 #define COMP_OPCODE_LONG_OPCODE 0x0002 #define COMP_OPCODE_CMOV 0x0004 #define COMP_OPCODE_ISADDX 0x0008 #define COMP_OPCODE_ISCJUMP 0x0010 #define COMP_OPCODE_USES_FPU 0x0020 struct comptbl { compop_func *handler; uae_u32 opcode; int specific; }; #endif extern cpuop_func *loop_mode_table[]; extern uae_u32 REGPARAM3 op_illg(uae_u32) REGPARAM; extern void REGPARAM3 op_illg_noret(uae_u32) REGPARAM; void REGPARAM3 op_illg_1_noret(uae_u32 opcode) REGPARAM; extern void REGPARAM3 op_unimpl(uae_u32) REGPARAM; void REGPARAM3 op_unimpl_1_noret(uae_u32 opcode) REGPARAM; typedef uae_u8 flagtype; #ifdef FPUEMU #ifdef USE_LONG_DOUBLE typedef long double fptype; #else typedef double fptype; #endif #endif #define MAX68020CYCLES 4 #define CPU_PIPELINE_MAX 4 #define CPU000_MEM_CYCLE 4 #define CPU000_CLOCK_MULT 2 #define CPU020_MEM_CYCLE 3 #define CPU020_CLOCK_MULT 4 #define CACHELINES020 64 struct cache020 { uae_u32 data; uae_u32 tag; bool valid; }; #define CACHELINES030 16 struct cache030 { uae_u32 data[4]; bool valid[4]; uae_u32 tag; uae_u8 fc; }; #define CACHESETS040 64 #define CACHESETS060 128 #define CACHELINES040 4 struct cache040 { uae_u32 data[CACHELINES040][4]; bool dirty[CACHELINES040][4]; bool gdirty[CACHELINES040]; bool valid[CACHELINES040]; uae_u32 tag[CACHELINES040]; }; struct mmufixup { int reg; uae_u32 value; }; extern struct mmufixup mmufixup[2]; #ifdef MSVC_LONG_DOUBLE typedef struct { uae_u64 m; uae_u16 e; uae_u16 dummy; } fprawtype; #endif typedef struct { floatx80 fpx; #ifdef MSVC_LONG_DOUBLE union { fptype fp; fprawtype rfp; }; #else fptype fp; #endif } fpdata; struct regstruct { uae_u32 regs[16]; uae_u32 pc; uae_u8 *pc_p; uae_u8 *pc_oldp; uae_u16 opcode; uae_u32 instruction_pc; uae_u32 instruction_pc_user_exception; uae_u32 trace_pc; uae_u16 irc, ir, ird; volatile uae_atomic spcflags; uae_u32 last_prefetch; uae_u32 chipset_latch_rw; uae_u32 chipset_latch_read; uae_u32 chipset_latch_write; uae_u16 db, write_buffer, read_buffer; int loop_mode; int instruction_cnt; uaecptr usp, isp, msp; uae_u16 sr; flagtype t1; flagtype t0; flagtype s; flagtype m; flagtype stopped; int halted; int exception; int intmask; int ipl[2], ipl_pin, ipl_pin_p; int lastipl; evt_t ipl_pin_change_evt, ipl_pin_change_evt_p; evt_t ipl_evt, ipl_evt_pre; int ipl_evt_pre_mode; uae_u32 vbr, sfc, dfc; #ifdef FPUEMU fpdata fp[8]; fpdata fp_result; uae_u32 fpcr, fpsr, fpiar; uae_u32 fpu_state; uae_u32 fpu_exp_state; uae_u16 fp_opword; uaecptr fp_ea; bool fp_ea_set; uae_u32 fp_exp_pend, fp_unimp_pend; bool fpu_exp_pre; bool fp_unimp_ins; bool fp_exception; bool fp_branch; #endif #ifndef CPUEMU_68000_ONLY uae_u32 cacr, caar; uae_u32 itt0, itt1, dtt0, dtt1; uae_u32 tcr, mmusr, urp, srp, buscr; uae_u32 mmu_fslw; uae_u32 mmu_fault_addr, mmu_effective_addr; uae_u16 mmu_ssw; uae_u32 wb2_address; uae_u32 wb3_data; uae_u8 wb3_status, wb2_status; int mmu_enabled; int mmu_page_size; #endif uae_u32 pcr; uae_u32 address_space_mask; uae_u16 prefetch020[CPU_PIPELINE_MAX]; uae_u8 prefetch020_valid[CPU_PIPELINE_MAX]; uae_u32 prefetch020addr; uae_u32 cacheholdingdata020; uae_u32 cacheholdingaddr020; uae_u8 cacheholdingdata_valid; int pipeline_pos; int pipeline_r8[2]; int pipeline_stop; uae_u8 fc030; uae_u32 prefetch040[CPU_PIPELINE_MAX]; evt_t ce020endcycle; evt_t ce020startcycle; evt_t ce020prefetchendcycle; evt_t ce020extracycles; bool ce020memcycle_data; int ce020_tail; evt_t ce020_tail_cycles; int memory_waitstate_cycles; }; extern struct regstruct regs; #define MAX_CPUTRACESIZE 128 struct cputracememory { uae_u32 addr; uae_u32 data; int mode; uae_u32 flags; }; struct cputracestruct { uae_u32 regs[16]; uae_u32 usp, isp, pc; uae_u16 ir, irc, ird, sr, opcode; int intmask, stopped, state; uae_u32 msp, vbr; uae_u32 cacr, caar; uae_u16 prefetch020[CPU_PIPELINE_MAX]; uae_u8 prefetch020_valid[CPU_PIPELINE_MAX]; uae_u32 prefetch020addr; uae_u32 cacheholdingdata020; uae_u32 cacheholdingaddr020; struct cache020 caches020[CACHELINES020]; int pipeline_pos; int pipeline_r8[2]; int pipeline_stop; uae_u16 read_buffer, write_buffer; evt_t startcycles; int needendcycles; int memoryoffset; int cyclecounter, cyclecounter_pre, cyclecounter_post; int readcounter, writecounter; struct cputracememory ctm[MAX_CPUTRACESIZE]; }; STATIC_INLINE uae_u32 munge24 (uae_u32 x) { return x & regs.address_space_mask; } extern int mmu_enabled, mmu_triggered; extern int cpu_cycles; extern int cpucycleunit, cpuipldelay2, cpuipldelay4; extern int m68k_pc_indirect; extern bool m68k_interrupt_delay; extern bool cpu_bus_rmw; // WINUAE_FOR_HATARI extern void safe_interrupt_set(int, int, bool); #define SPCFLAG_CPUINRESET 2 #define SPCFLAG_INT 8 #define SPCFLAG_BRK 16 #define SPCFLAG_UAEINT 32 #define SPCFLAG_TRACE 64 #define SPCFLAG_DOTRACE 128 #define SPCFLAG_DOINT 256 /* arg, JIT fails without this.. */ //#define SPCFLAG_BLTNASTY 512 #define SPCFLAG_EXEC 1024 //#define SPCFLAG_ACTION_REPLAY 2048 //#define SPCFLAG_TRAP 4096 /* enforcer-hack */ #define SPCFLAG_MODE_CHANGE 8192 #ifdef JIT #define SPCFLAG_END_COMPILE 16384 #endif #define SPCFLAG_CHECK 32768 #define SPCFLAG_MMURESTART 65536 #ifdef WINUAE_FOR_HATARI // Hatari's specific flags #define SPCFLAG_DEBUGGER 0x1000000 #define SPCFLAG_STOP 0x2000000 #define SPCFLAG_BUSERROR 0x4000000 #define SPCFLAG_EXTRA_CYCLES 0x8000000 #define SPCFLAG_MFP 0x10000000 #define SPCFLAG_DSP 0x20000000 // TODO : remove with do_specialties_interrupt() #endif #ifndef WINUAE_FOR_HATARI STATIC_INLINE void set_special_exter(uae_u32 x) { atomic_or(®s.spcflags, x); } STATIC_INLINE void set_special (uae_u32 x) { atomic_or(®s.spcflags, x); cycles_do_special(); } STATIC_INLINE void unset_special (uae_u32 x) { atomic_and(®s.spcflags, ~x); } #else STATIC_INLINE void set_special (uae_u32 x) { regs.spcflags |= x; cycles_do_special (); } STATIC_INLINE void unset_special (uae_u32 x) { regs.spcflags &= ~x; } #endif #define m68k_dreg(r,num) ((r).regs[(num)]) #define m68k_areg(r,num) (((r).regs + 8)[(num)]) // JIT only #ifdef HAVE_GET_WORD_UNSWAPPED #define GET_OPCODE (do_get_mem_word_unswapped((uae_u16*)(pc + pc_offset))); #else #define GET_OPCODE (do_get_mem_word((uae_u16*)(pc + pc_offset))); #endif extern uae_u32(*x_prefetch)(int); extern uae_u32(*x_get_byte)(uaecptr addr); extern uae_u32(*x_get_word)(uaecptr addr); extern uae_u32(*x_get_long)(uaecptr addr); extern void(*x_put_byte)(uaecptr addr, uae_u32 v); extern void(*x_put_word)(uaecptr addr, uae_u32 v); extern void(*x_put_long)(uaecptr addr, uae_u32 v); extern uae_u32(*x_next_iword)(void); extern uae_u32(*x_next_ilong)(void); extern uae_u32(*x_get_ilong)(int); extern uae_u32(*x_get_iword)(int); extern uae_u32(*x_get_ibyte)(int); extern uae_u32(*x_cp_get_byte)(uaecptr addr); extern uae_u32(*x_cp_get_word)(uaecptr addr); extern uae_u32(*x_cp_get_long)(uaecptr addr); extern void(*x_cp_put_byte)(uaecptr addr, uae_u32 v); extern void(*x_cp_put_word)(uaecptr addr, uae_u32 v); extern void(*x_cp_put_long)(uaecptr addr, uae_u32 v); extern uae_u32(*x_cp_next_iword)(void); extern uae_u32(*x_cp_next_ilong)(void); void mem_access_delay_long_write_ce020 (uaecptr addr, uae_u32 v); void mem_access_delay_word_write_ce020 (uaecptr addr, uae_u32 v); void mem_access_delay_byte_write_ce020 (uaecptr addr, uae_u32 v); uae_u32 mem_access_delay_byte_read_ce020 (uaecptr addr); uae_u32 mem_access_delay_word_read_ce020 (uaecptr addr); uae_u32 mem_access_delay_long_read_ce020 (uaecptr addr); uae_u32 mem_access_delay_longi_read_ce020 (uaecptr addr); uae_u32 mem_access_delay_wordi_read_ce020 (uaecptr addr); void mem_access_delay_long_write_c040 (uaecptr addr, uae_u32 v); void mem_access_delay_word_write_c040 (uaecptr addr, uae_u32 v); void mem_access_delay_byte_write_c040 (uaecptr addr, uae_u32 v); uae_u32 mem_access_delay_byte_read_c040 (uaecptr addr); uae_u32 mem_access_delay_word_read_c040 (uaecptr addr); uae_u32 mem_access_delay_long_read_c040 (uaecptr addr); uae_u32 mem_access_delay_longi_read_c040 (uaecptr addr); extern uae_u32(REGPARAM3 *x_cp_get_disp_ea_020)(uae_u32 base, int idx) REGPARAM; #ifndef WINUAE_FOR_HATARI extern bool debugmem_trace; extern void branch_stack_push(uaecptr, uaecptr); extern void branch_stack_pop_rte(uaecptr); extern void branch_stack_pop_rts(uaecptr); #endif /* direct (regs.pc_p) access */ STATIC_INLINE void m68k_setpc(uaecptr newpc) { regs.pc_p = regs.pc_oldp = get_real_address(newpc); regs.instruction_pc = regs.pc = newpc; } STATIC_INLINE void m68k_setpc_j(uaecptr newpc) { regs.pc_p = regs.pc_oldp = get_real_address(newpc); regs.pc = newpc; } STATIC_INLINE uaecptr m68k_getpc(void) { return (uaecptr)(regs.pc + ((uae_u8*)regs.pc_p - (uae_u8*)regs.pc_oldp)); } #define M68K_GETPC m68k_getpc() STATIC_INLINE uaecptr m68k_getpc_p(uae_u8 *p) { return (uaecptr)(regs.pc + ((uae_u8*)p - (uae_u8*)regs.pc_oldp)); } STATIC_INLINE void m68k_incpc(int o) { regs.pc_p += o; } STATIC_INLINE uae_u32 get_dibyte(int o) { return do_get_mem_byte((uae_u8 *)((regs).pc_p + (o) + 1)); } STATIC_INLINE uae_u32 get_diword(int o) { return do_get_mem_word((uae_u16 *)((regs).pc_p + (o))); } STATIC_INLINE uae_u32 get_dilong(int o) { return do_get_mem_long((uae_u32 *)((regs).pc_p + (o))); } STATIC_INLINE uae_u32 next_diword(void) { uae_u32 r = do_get_mem_word((uae_u16 *)((regs).pc_p)); m68k_incpc(2); return r; } STATIC_INLINE uae_u32 next_dilong(void) { uae_u32 r = do_get_mem_long((uae_u32 *)((regs).pc_p)); m68k_incpc(4); return r; } STATIC_INLINE void m68k_do_bsr(uaecptr oldpc, uae_s32 offset) { m68k_areg(regs, 7) -= 4; put_long(m68k_areg(regs, 7), oldpc); m68k_incpc(offset); } STATIC_INLINE void m68k_do_rts(void) { uae_u32 newpc = get_long(m68k_areg(regs, 7)); m68k_setpc(newpc); m68k_areg(regs, 7) += 4; } /* indirect (regs.pc) access */ STATIC_INLINE void m68k_setpci(uaecptr newpc) { regs.instruction_pc = regs.pc = newpc; } STATIC_INLINE void m68k_setpci_j(uaecptr newpc) { regs.pc = newpc; } STATIC_INLINE uaecptr m68k_getpci(void) { return regs.pc; } STATIC_INLINE void m68k_incpci(int o) { regs.pc += o; } STATIC_INLINE uae_u32 get_iibyte(int o) { return get_wordi(m68k_getpci() + (o)) & 0xff; } STATIC_INLINE uae_u32 get_iiword(int o) { return get_wordi(m68k_getpci() + (o)); } STATIC_INLINE uae_u32 get_iilong(int o) { return get_longi(m68k_getpci () + (o)); } STATIC_INLINE uae_u32 next_iibyte (void) { uae_u32 r = get_iibyte (0); m68k_incpci (2); return r; } STATIC_INLINE uae_u32 next_iiword (void) { uae_u32 r = get_iiword (0); m68k_incpci (2); return r; } STATIC_INLINE uae_u32 next_iiwordi (void) { uae_u32 r = get_wordi(m68k_getpci()); m68k_incpci (2); return r; } STATIC_INLINE uae_u32 next_iilong (void) { uae_u32 r = get_iilong(0); m68k_incpci (4); return r; } STATIC_INLINE uae_u32 next_iilongi (void) { uae_u32 r = get_longi (m68k_getpci ()); m68k_incpci (4); return r; } STATIC_INLINE void m68k_do_bsri(uaecptr oldpc, uae_s32 offset) { m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), oldpc); m68k_incpci(offset); } STATIC_INLINE void m68k_do_rtsi(void) { uae_u32 newpc = x_get_long(m68k_areg(regs, 7)); m68k_setpci(newpc); m68k_areg(regs, 7) += 4; } /* indirect jit friendly versions */ STATIC_INLINE uae_u32 get_iibyte_jit(int o) { return get_wordi(m68k_getpc() + (o)) & 0xff; } STATIC_INLINE uae_u32 get_iiword_jit(int o) { return get_wordi(m68k_getpc() + (o)); } STATIC_INLINE uae_u32 get_iilong_jit(int o) { return get_longi(m68k_getpc() + (o)); } STATIC_INLINE uae_u32 next_iiword_jit(void) { uae_u32 r = get_wordi(m68k_getpc()); m68k_incpc(2); return r; } STATIC_INLINE uae_u32 next_iilong_jit(void) { uae_u32 r = get_longi(m68k_getpc()); m68k_incpc(4); return r; } STATIC_INLINE void m68k_do_bsri_jit(uaecptr oldpc, uae_s32 offset) { m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), oldpc); m68k_incpc(offset); } STATIC_INLINE void m68k_do_rtsi_jit(void) { uae_u32 newpc = x_get_long(m68k_areg(regs, 7)); m68k_setpc(newpc); m68k_areg(regs, 7) += 4; } /* common access */ STATIC_INLINE void m68k_incpc_normal(int o) { if (m68k_pc_indirect > 0) m68k_incpci(o); else m68k_incpc(o); } STATIC_INLINE void m68k_setpc_normal(uaecptr pc) { if (m68k_pc_indirect > 0) { regs.pc_p = regs.pc_oldp = 0; m68k_setpci(pc); } else { m68k_setpc(pc); } } extern void cpu_invalidate_cache(uaecptr, int); extern bool(*is_super_access)(bool); extern uae_u32(*read_data_030_bget)(uaecptr); extern uae_u32(*read_data_030_wget)(uaecptr); extern uae_u32(*read_data_030_lget)(uaecptr); extern void(*write_data_030_bput)(uaecptr,uae_u32); extern void(*write_data_030_wput)(uaecptr,uae_u32); extern void(*write_data_030_lput)(uaecptr,uae_u32); extern uae_u32(*read_data_030_fc_bget)(uaecptr, uae_u32); extern uae_u32(*read_data_030_fc_wget)(uaecptr, uae_u32); extern uae_u32(*read_data_030_fc_lget)(uaecptr, uae_u32); extern void(*write_data_030_fc_bput)(uaecptr, uae_u32, uae_u32); extern void(*write_data_030_fc_wput)(uaecptr, uae_u32, uae_u32); extern void(*write_data_030_fc_lput)(uaecptr, uae_u32, uae_u32); extern void write_dcache030_bput(uaecptr, uae_u32, uae_u32); extern void write_dcache030_wput(uaecptr, uae_u32, uae_u32); extern void write_dcache030_lput(uaecptr, uae_u32, uae_u32); extern void write_dcache030_retry(uaecptr addr, uae_u32 v, uae_u32 fc, int size, int flags); extern uae_u32 read_dcache030_bget(uaecptr, uae_u32); extern uae_u32 read_dcache030_wget(uaecptr, uae_u32); extern uae_u32 read_dcache030_lget(uaecptr, uae_u32); extern uae_u32 read_dcache030_retry(uaecptr addr, uae_u32 fc, int size, int flags); extern void write_dcache030_mmu_bput(uaecptr, uae_u32); extern void write_dcache030_mmu_wput(uaecptr, uae_u32); extern void write_dcache030_mmu_lput(uaecptr, uae_u32); extern uae_u32 read_dcache030_mmu_bget(uaecptr); extern uae_u32 read_dcache030_mmu_wget(uaecptr); extern uae_u32 read_dcache030_mmu_lget(uaecptr); extern void write_dcache030_lrmw_mmu(uaecptr, uae_u32, uae_u32); extern void write_dcache030_lrmw_mmu_fcx(uaecptr, uae_u32, uae_u32, int); extern uae_u32 read_dcache030_lrmw_mmu(uaecptr, uae_u32); extern uae_u32 read_dcache030_lrmw_mmu_fcx(uaecptr, uae_u32, int); extern void check_t0_trace(void); extern uae_u32 get_word_icache030(uaecptr addr); extern uae_u32 get_long_icache030(uaecptr addr); uae_u32 fill_icache040(uae_u32 addr); extern void put_long_cache_040(uaecptr, uae_u32); extern void put_word_cache_040(uaecptr, uae_u32); extern void put_byte_cache_040(uaecptr, uae_u32); extern uae_u32 get_ilong_cache_040(int); extern uae_u32 get_iword_cache_040(int); extern uae_u32 get_long_cache_040(uaecptr); extern uae_u32 get_word_cache_040(uaecptr); extern uae_u32 get_byte_cache_040(uaecptr); extern uae_u32 next_iword_cache040(void); extern uae_u32 next_ilong_cache040(void); extern uae_u32 get_word_icache040(uaecptr addr); extern uae_u32 get_long_icache040(uaecptr addr); extern uae_u32 sfc_nommu_get_byte(uaecptr); extern uae_u32 sfc_nommu_get_word(uaecptr); extern uae_u32 sfc_nommu_get_long(uaecptr); extern void dfc_nommu_put_byte(uaecptr, uae_u32); extern void dfc_nommu_put_word(uaecptr, uae_u32); extern void dfc_nommu_put_long(uaecptr, uae_u32); extern void (*x_do_cycles)(int); extern void (*x_do_cycles_pre)(int); extern void (*x_do_cycles_post)(int, uae_u32); #ifdef WINUAE_FOR_HATARI void set_x_funcs_hatari_blitter (int flag); #endif extern uae_u32 REGPARAM3 x_get_disp_ea_020 (uae_u32 base, int idx) REGPARAM; extern uae_u32 REGPARAM3 x_get_disp_ea_ce020 (uae_u32 base, int idx) REGPARAM; extern uae_u32 REGPARAM3 x_get_disp_ea_ce030 (uae_u32 base, int idx) REGPARAM; extern uae_u32 REGPARAM3 x_get_disp_ea_040(uae_u32 base, int idx) REGPARAM; extern uae_u32 REGPARAM3 x_get_bitfield (uae_u32 src, uae_u32 bdata[2], uae_s32 offset, int width) REGPARAM; extern void REGPARAM3 x_put_bitfield (uae_u32 dst, uae_u32 bdata[2], uae_u32 val, uae_s32 offset, int width) REGPARAM; extern void m68k_setstopped(int stoptype); extern void m68k_resumestopped(void); extern void m68k_cancel_idle(void); extern void do_cycles_stop(int); extern uae_u32 REGPARAM3 get_disp_ea_020 (uae_u32 base, int idx) REGPARAM; extern uae_u32 REGPARAM3 get_bitfield (uae_u32 src, uae_u32 bdata[2], uae_s32 offset, int width) REGPARAM; extern void REGPARAM3 put_bitfield (uae_u32 dst, uae_u32 bdata[2], uae_u32 val, uae_s32 offset, int width) REGPARAM; extern void m68k_disasm_ea (uaecptr addr, uaecptr *nextpc, int cnt, uae_u32 *seaddr, uae_u32 *deaddr, uaecptr lastpc); extern void m68k_disasm (uaecptr addr, uaecptr *nextpc, uaecptr lastpc, int cnt); extern uae_u32 m68k_disasm_2(TCHAR *buf, int bufsize, uaecptr pc, uae_u16 *bufpc, int bufpccount, uaecptr *nextpc, int cnt, uae_u32 *seaddr, uae_u32 *deaddr, uaecptr lastpc, int safemode); #ifdef WINUAE_FOR_HATARI extern void m68k_disasm_file (FILE *f, uaecptr addr, uaecptr *nextpc, uaecptr lastpc, int cnt); extern void m68k_disasm_file_wrapper (FILE *f, uaecptr addr, uaecptr *nextpc, uaecptr lastpc, int cnt); #endif extern void sm68k_disasm (TCHAR*, TCHAR*, uaecptr addr, uaecptr *nextpc, uaecptr lastpc); extern int m68k_asm(TCHAR *buf, uae_u16 *out, uaecptr pc); extern uaecptr ShowEA(void *f, uaecptr pc, uae_u16 opcode, int reg, amodes mode, wordsizes size, TCHAR *buf, uae_u32 *eaddr, int *actualea, int safemode); extern int get_cpu_model (void); extern void set_cpu_caches (bool flush); #ifdef WINUAE_FOR_HATARI extern void invalidate_cpu_data_caches(void); #endif extern void flush_cpu_caches(bool flush); extern void flush_cpu_caches_040(uae_u16 opcode); extern void REGPARAM3 MakeSR (void) REGPARAM; extern void REGPARAM3 MakeFromSR(void) REGPARAM; extern void REGPARAM3 MakeFromSR_T0(void) REGPARAM; extern void REGPARAM3 MakeFromSR_STOP(void) REGPARAM; extern void REGPARAM3 Exception (int) REGPARAM; extern void REGPARAM3 Exception_cpu(int) REGPARAM; extern void REGPARAM3 Exception_cpu_oldpc(int, uaecptr) REGPARAM; extern void REGPARAM3 ExceptionL (int, uaecptr) REGPARAM; extern void NMI (void); extern void IRQ_forced(int, int); extern void prepare_interrupt (uae_u32); extern void doint(void); extern void checkint(void); extern void intlev_load(void); extern void ipl_fetch_now_pre(void); extern void ipl_fetch_next_pre(void); extern void ipl_fetch_now(void); extern void ipl_fetch_next(void); extern void dump_counts (void); extern int m68k_move2c (int, uae_u32 *); extern int m68k_movec2 (int, uae_u32 *); extern int m68k_divl (uae_u32, uae_u32, uae_u16, uaecptr); extern int m68k_mull (uae_u32, uae_u32, uae_u16); extern void init_m68k (void); extern void m68k_go (int); extern void m68k_dumpstate(uaecptr *, uaecptr); #ifdef WINUAE_FOR_HATARI extern void m68k_dumpstate_file (FILE *f, uaecptr *nextpc, uaecptr prevpc); #endif extern void m68k_dumpcache(bool); extern int getMulu68kCycles(uae_u16 src); extern int getMuls68kCycles(uae_u16 src); extern int getDivu68kCycles (uae_u32 dividend, uae_u16 divisor); extern int getDivs68kCycles (uae_s32 dividend, uae_s16 divisor, int *extra); extern void divbyzero_special(bool issigned, uae_s32 dst); extern void setdivuflags(uae_u32 dividend, uae_u16 divisor); extern void setdivsflags(uae_s32 dividend, uae_s16 divisor); extern void setchkundefinedflags(uae_s32 src, uae_s32 dst, int size); extern void setchk2undefinedflags(uae_s32 lower, uae_s32 upper, uae_s32 val, int size); extern void protect_roms (bool); extern void unprotect_maprom (void); extern bool is_hardreset(void); extern bool is_keyboardreset(void); extern void Exception_build_stack_frame_common(uae_u32 oldpc, uae_u32 currpc, uae_u32 ssw, int nr, int vector_nr); extern void Exception_build_stack_frame(uae_u32 oldpc, uae_u32 currpc, uae_u32 ssw, int nr, int format); extern void Exception_build_68000_address_error_stack_frame(uae_u16 mode, uae_u16 opcode, uaecptr fault_addr, uaecptr pc); extern uae_u32 exception_pc(int nr); extern void cpu_restore_fixup(void); extern bool privileged_copro_instruction(uae_u16 opcode); extern bool generates_group1_exception(uae_u16 opcode); void ccr_68000_long_move_ae_LZN(uae_s32 src); void ccr_68000_long_move_ae_LN(uae_s32 src); void ccr_68000_long_move_ae_HNZ(uae_s32 src); void ccr_68000_long_move_ae_normal(uae_s32 src); void ccr_68000_word_move_ae_normal(uae_s16 src); void dreg_68000_long_replace_low(int reg, uae_u16 v); void areg_68000_long_replace_low(int reg, uae_u16 v); extern void mmu_op (uae_u32, uae_u32); extern bool mmu_op30 (uaecptr, uae_u32, uae_u16, uaecptr); extern void fpuop_arithmetic(uae_u32, uae_u16); extern void fpuop_dbcc(uae_u32, uae_u16); extern void fpuop_scc(uae_u32, uae_u16); extern void fpuop_trapcc(uae_u32, uaecptr, uae_u16); extern void fpuop_bcc(uae_u32, uaecptr, uae_u32); extern void fpuop_save(uae_u32); extern void fpuop_restore(uae_u32); extern uae_u32 fpp_get_fpsr (void); extern void fpu_reset (void); extern void fpux_save (int*); extern void fpux_restore (int*); extern bool fpu_get_constant(fpdata *fp, int cr); extern int fpp_cond(int condition); extern void exception3_read(uae_u32 opcode, uaecptr addr, int size, int fc); extern void exception3_write(uae_u32 opcode, uaecptr addr, int size, uae_u32 val, int fc); extern void exception3_read_access(uae_u32 opcode, uaecptr addr, int size, int fc); extern void exception3_read_access2(uae_u32 opcode, uaecptr addr, int size, int fc); extern void exception3_write_access(uae_u32 opcode, uaecptr addr, int size, uae_u32 val, int fc); extern void exception3_read_prefetch(uae_u32 opcode, uaecptr addr); extern void exception3_read_prefetch_68040bug(uae_u32 opcode, uaecptr addr, uae_u16 secondarysr); extern void exception3_read_prefetch_only(uae_u32 opcode, uaecptr addr); extern void exception3_notinstruction(uae_u32 opcode, uaecptr addr); extern void hardware_exception2(uaecptr addr, uae_u32 v, bool read, bool ins, int size); extern void exception2_setup(uae_u32 opcode, uaecptr addr, bool read, int size, uae_u32 fc); extern void exception2_read(uae_u32 opcode, uaecptr addr, int size, int fc); extern void exception2_write(uae_u32 opcode, uaecptr addr, int size, uae_u32 val, int fc); extern void exception2_fetch_opcode(uae_u32 opcode, int offset, int pcoffset); extern void exception2_fetch(uae_u32 opcode, int offset, int pcoffset); extern void m68k_reset(void); extern bool cpureset(void); extern void cpu_halt(int id); extern void cpu_inreset(void); extern int cpu_sleep_millis(int ms); extern void cpu_change(int newmodel); extern void cpu_fallback(int mode); extern void fill_prefetch(void); extern void fill_prefetch_020_ntx(void); extern void fill_prefetch_030_ntx(void); extern void fill_prefetch_030_ntx_continue(void); extern void fill_prefetch_020(void); extern void fill_prefetch_030(void); #define CPU_OP_NAME(a) op ## a /* 68060 */ extern const struct cputbl op_smalltbl_0[]; extern const struct cputbl op_smalltbl_40[]; extern const struct cputbl op_smalltbl_50[]; extern const struct cputbl op_smalltbl_24[]; // CE extern const struct cputbl op_smalltbl_33[]; // MMU /* 68040 */ extern const struct cputbl op_smalltbl_1[]; extern const struct cputbl op_smalltbl_41[]; extern const struct cputbl op_smalltbl_51[]; extern const struct cputbl op_smalltbl_25[]; // CE extern const struct cputbl op_smalltbl_31[]; // MMU /* 68030 */ extern const struct cputbl op_smalltbl_2[]; extern const struct cputbl op_smalltbl_42[]; extern const struct cputbl op_smalltbl_52[]; extern const struct cputbl op_smalltbl_22[]; // prefetch extern const struct cputbl op_smalltbl_23[]; // CE extern const struct cputbl op_smalltbl_32[]; // MMU extern const struct cputbl op_smalltbl_34[]; // MMU + cache extern const struct cputbl op_smalltbl_35[]; // MMU + CE + cache /* 68020 */ extern const struct cputbl op_smalltbl_3[]; extern const struct cputbl op_smalltbl_43[]; extern const struct cputbl op_smalltbl_53[]; extern const struct cputbl op_smalltbl_20[]; // prefetch extern const struct cputbl op_smalltbl_21[]; // CE /* 68010 */ extern const struct cputbl op_smalltbl_4[]; extern const struct cputbl op_smalltbl_44[]; extern const struct cputbl op_smalltbl_54[]; extern const struct cputbl op_smalltbl_11[]; // prefetch extern const struct cputbl op_smalltbl_13[]; // CE /* 68000 */ extern const struct cputbl op_smalltbl_5[]; extern const struct cputbl op_smalltbl_45[]; extern const struct cputbl op_smalltbl_55[]; extern const struct cputbl op_smalltbl_12[]; // prefetch extern const struct cputbl op_smalltbl_14[]; // CE extern cpuop_func_noret *cpufunctbl_noret[65536] ASM_SYM_FOR_FUNC("cpufunctbl_noret"); extern cpuop_func *cpufunctbl[65536] ASM_SYM_FOR_FUNC("cpufunctbl"); #ifdef JIT extern void (*flush_icache)(int); extern void compemu_reset(void); #else #define flush_icache(int) do {} while (0) #define flush_icache_hard(int) do {} while (0) #endif bool check_prefs_changed_comp (bool); #ifdef WINUAE_FOR_HATARI extern void flush_instr_cache (uaecptr, int); #endif extern void flush_mmu (uaecptr, int); extern int movec_illg (int regno); extern uae_u32 val_move2c (int regno); extern void val_move2c2 (int regno, uae_u32 val); struct cpum2c { int regno; int flags; const TCHAR *regname; }; extern struct cpum2c m2cregs[]; extern bool is_cpu_tracer (void); extern bool set_cpu_tracer (bool force); extern bool can_cpu_tracer (void); #define CPU_HALT_PPC_ONLY -1 #define CPU_HALT_BUS_ERROR_DOUBLE_FAULT 1 #define CPU_HALT_DOUBLE_FAULT 2 #define CPU_HALT_OPCODE_FETCH_FROM_NON_EXISTING_ADDRESS 3 #define CPU_HALT_ACCELERATOR_CPU_FALLBACK 4 #define CPU_HALT_ALL_CPUS_STOPPED 5 #define CPU_HALT_FAKE_DMA 6 #define CPU_HALT_AUTOCONFIG_CONFLICT 7 #define CPU_HALT_PCI_CONFLICT 8 #define CPU_HALT_CPU_STUCK 9 #define CPU_HALT_SSP_IN_NON_EXISTING_ADDRESS 10 #define CPU_HALT_INVALID_START_ADDRESS 11 #define CPU_HALT_68060_HALT 12 #define CPU_HALT_BKPT 13 uae_u32 process_cpu_indirect_memory_read(uae_u32 addr, int size); void process_cpu_indirect_memory_write(uae_u32 addr, uae_u32 data, int size); /* From uae.h */ #define UAE_QUIT 1 #define UAE_RESET 2 #define UAE_RESET_KEYBOARD 3 #define UAE_RESET_HARD 4 extern int quit_program; /* From uae.h */ #ifdef WINUAE_FOR_HATARI /*** Hatari ***/ /* Family of the latest instruction executed (to check for pairing) */ extern int OpcodeFamily; /* see instrmnem in readcpu.h */ /* How many cycles to add to the current instruction in case a "misaligned" bus access is made */ /* (e.g. used when addressing mode is d8(an,ix)) */ extern int BusCyclePenalty; /* To redirect WinUAE's prints to our own file */ extern FILE *console_out_FILE; /*** Hatari ***/ #endif const struct cputbl *uaegetjitcputbl(void); const struct cputbl *getjitcputbl(int cpulvl, int direct); #endif /* UAE_NEWCPU_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/newcpu_common.c000066400000000000000000001253671504763705000251520ustar00rootroot00000000000000 #include "sysconfig.h" #include "sysdeps.h" #define MOVEC_DEBUG 0 #include "main.h" #include "hatari-glue.h" #include "log.h" #include "options_cpu.h" #include "memory.h" #include "newcpu.h" #include "cpummu.h" #include "cpummu030.h" #include "cpu_prefetch.h" int get_cpu_model(void) { return currprefs.cpu_model; } void val_move2c2 (int regno, uae_u32 val) { switch (regno) { case 0: regs.sfc = val; break; case 1: regs.dfc = val; break; case 2: regs.cacr = val; break; case 3: regs.tcr = val; break; case 4: regs.itt0 = val; break; case 5: regs.itt1 = val; break; case 6: regs.dtt0 = val; break; case 7: regs.dtt1 = val; break; case 8: regs.buscr = val; break; case 0x800: regs.usp = val; break; case 0x801: regs.vbr = val; break; case 0x802: regs.caar = val; break; case 0x803: regs.msp = val; break; case 0x804: regs.isp = val; break; case 0x805: regs.mmusr = val; break; case 0x806: regs.urp = val; break; case 0x807: regs.srp = val; break; case 0x808: regs.pcr = val; break; } } uae_u32 val_move2c (int regno) { switch (regno) { case 0: return regs.sfc; case 1: return regs.dfc; case 2: return regs.cacr; case 3: return regs.tcr; case 4: return regs.itt0; case 5: return regs.itt1; case 6: return regs.dtt0; case 7: return regs.dtt1; case 8: return regs.buscr; case 0x800: return regs.usp; case 0x801: return regs.vbr; case 0x802: return regs.caar; case 0x803: return regs.msp; case 0x804: return regs.isp; case 0x805: return regs.mmusr; case 0x806: return regs.urp; case 0x807: return regs.srp; case 0x808: return regs.pcr; default: return 0; } } #ifndef CPUEMU_68000_ONLY int movec_illg (int regno) { int regno2 = regno & 0x7ff; if (currprefs.cpu_model == 68060) { if (regno <= 8) return 0; if (regno == 0x800 || regno == 0x801 || regno == 0x806 || regno == 0x807 || regno == 0x808) return 0; return 1; } else if (currprefs.cpu_model == 68010) { if (regno2 < 2) return 0; return 1; } else if (currprefs.cpu_model == 68020) { if (regno == 3) return 1; /* 68040/060 only */ /* 4 is >=68040, but 0x804 is in 68020 */ if (regno2 < 4 || regno == 0x804) return 0; return 1; } else if (currprefs.cpu_model == 68030) { if (regno2 <= 2) return 0; if (regno == 0x803 || regno == 0x804) return 0; return 1; } else if (currprefs.cpu_model == 68040) { if (regno == 0x802) return 1; /* 68020/030 only */ if (regno2 < 8) return 0; return 1; } return 1; } int m68k_move2c (int regno, uae_u32 *regp) { #if MOVEC_DEBUG > 0 write_log (_T("move2c %04X <- %08X PC=%x\n"), regno, *regp, M68K_GETPC); #endif if (movec_illg(regno)) { if (currprefs.cpu_model < 68060 && !regs.s) { Exception(8); return 0; } op_illg(0x4E7B); return 0; } else { if (!regs.s) { Exception(8); return 0; } switch (regno) { case 0: regs.sfc = *regp & 7; break; case 1: regs.dfc = *regp & 7; break; case 2: { uae_u32 cacr_mask = 0; if (currprefs.cpu_model == 68020) cacr_mask = 0x0000000f; else if (currprefs.cpu_model == 68030) cacr_mask = 0x00003f1f; else if (currprefs.cpu_model == 68040) cacr_mask = 0x80008000; else if (currprefs.cpu_model == 68060) cacr_mask = 0xf8e0e000; regs.cacr = *regp & cacr_mask; set_cpu_caches (false); } break; /* 68040/060 only */ case 3: regs.tcr = *regp & (currprefs.cpu_model == 68060 ? 0xfffe : 0xc000); if (currprefs.mmu_model) regs.tcr = mmu_set_tc (regs.tcr); break; /* no differences between 68040 and 68060 */ case 4: regs.itt0 = *regp & 0xffffe364; mmu_tt_modified (); break; case 5: regs.itt1 = *regp & 0xffffe364; mmu_tt_modified (); break; case 6: regs.dtt0 = *regp & 0xffffe364; mmu_tt_modified (); break; case 7: regs.dtt1 = *regp & 0xffffe364; mmu_tt_modified (); break; /* 68060 only */ case 8: regs.buscr &= 0x50000000; regs.buscr |= *regp & 0xa0000000; break; case 0x800: regs.usp = *regp; break; case 0x801: regs.vbr = *regp; break; case 0x802: regs.caar = *regp; break; case 0x803: regs.msp = *regp; if (regs.m == 1) m68k_areg (regs, 7) = regs.msp; break; case 0x804: regs.isp = *regp; if (regs.m == 0) m68k_areg (regs, 7) = regs.isp; break; /* 68040 only */ case 0x805: regs.mmusr = *regp; break; /* 68040 stores all bits, 68060 zeroes low 9 bits */ case 0x806: regs.urp = *regp & (currprefs.cpu_model == 68060 ? 0xfffffe00 : 0xffffffff); break; case 0x807: regs.srp = *regp & (currprefs.cpu_model == 68060 ? 0xfffffe00 : 0xffffffff); break; /* 68060 only */ case 0x808: { uae_u32 opcr = regs.pcr; regs.pcr &= ~(0x40 | 2 | 1); regs.pcr |= (*regp) & (0x40 | 2 | 1); if (currprefs.fpu_model <= 0) regs.pcr |= 2; if (((opcr ^ regs.pcr) & 2) == 2) { write_log (_T("68060 FPU state: %s\n"), regs.pcr & 2 ? _T("disabled") : _T("enabled")); /* flush possible already translated FPU instructions */ flush_icache (3); } } break; default: op_illg (0x4E7B); return 0; } } return 1; } int m68k_movec2 (int regno, uae_u32 *regp) { #if MOVEC_DEBUG > 0 write_log (_T("movec2 %04X PC=%x\n"), regno, M68K_GETPC); #endif if (movec_illg(regno)) { if (currprefs.cpu_model < 68060 && !regs.s) { Exception(8); return 0; } op_illg(0x4E7A); return 0; } else { if (!regs.s) { Exception(8); return 0; } switch (regno) { case 0: *regp = regs.sfc; break; case 1: *regp = regs.dfc; break; case 2: { uae_u32 v = regs.cacr; uae_u32 cacr_mask = 0; if (currprefs.cpu_model == 68020) cacr_mask = 0x00000003; else if (currprefs.cpu_model == 68030) cacr_mask = 0x00003313; else if (currprefs.cpu_model == 68040) cacr_mask = 0x80008000; else if (currprefs.cpu_model == 68060) cacr_mask = 0xf880e000; *regp = v & cacr_mask; } break; case 3: *regp = regs.tcr; break; case 4: *regp = regs.itt0; break; case 5: *regp = regs.itt1; break; case 6: *regp = regs.dtt0; break; case 7: *regp = regs.dtt1; break; case 8: *regp = regs.buscr; break; case 0x800: *regp = regs.usp; break; case 0x801: *regp = regs.vbr; break; case 0x802: *regp = regs.caar; break; case 0x803: *regp = regs.m == 1 ? m68k_areg (regs, 7) : regs.msp; break; case 0x804: *regp = regs.m == 0 ? m68k_areg (regs, 7) : regs.isp; break; case 0x805: *regp = regs.mmusr; break; case 0x806: *regp = regs.urp; break; case 0x807: *regp = regs.srp; break; case 0x808: *regp = regs.pcr; break; default: op_illg (0x4E7A); return 0; } } #if MOVEC_DEBUG > 0 write_log (_T("-> %08X\n"), *regp); #endif return 1; } #endif /* * extract bitfield data from memory and return it in the MSBs * bdata caches the unmodified data for put_bitfield() */ uae_u32 REGPARAM2 get_bitfield (uae_u32 src, uae_u32 bdata[2], uae_s32 offset, int width) { uae_u32 tmp, res, mask; offset &= 7; mask = 0xffffffffu << (32 - width); switch ((offset + width + 7) >> 3) { case 1: tmp = get_byte (src); res = tmp << (24 + offset); bdata[0] = tmp & ~(mask >> (24 + offset)); break; case 2: tmp = get_word (src); res = tmp << (16 + offset); bdata[0] = tmp & ~(mask >> (16 + offset)); break; case 3: tmp = get_word (src); res = tmp << (16 + offset); bdata[0] = tmp & ~(mask >> (16 + offset)); tmp = get_byte (src + 2); res |= tmp << (8 + offset); bdata[1] = tmp & ~(mask >> (8 + offset)); break; case 4: tmp = get_long (src); res = tmp << offset; bdata[0] = tmp & ~(mask >> offset); break; case 5: tmp = get_long (src); res = tmp << offset; bdata[0] = tmp & ~(mask >> offset); tmp = get_byte (src + 4); res |= tmp >> (8 - offset); bdata[1] = tmp & ~(mask << (8 - offset)); break; default: /* Panic? */ write_log (_T("get_bitfield() can't happen %d\n"), (offset + width + 7) >> 3); res = 0; break; } return res; } /* * write bitfield data (in the LSBs) back to memory, upper bits * must be cleared already. */ void REGPARAM2 put_bitfield (uae_u32 dst, uae_u32 bdata[2], uae_u32 val, uae_s32 offset, int width) { offset = (offset & 7) + width; switch ((offset + 7) >> 3) { case 1: put_byte (dst, bdata[0] | (val << (8 - offset))); break; case 2: put_word (dst, bdata[0] | (val << (16 - offset))); break; case 3: put_word (dst, bdata[0] | (val >> (offset - 16))); put_byte (dst + 2, bdata[1] | (val << (24 - offset))); break; case 4: put_long (dst, bdata[0] | (val << (32 - offset))); break; case 5: put_long (dst, bdata[0] | (val >> (offset - 32))); put_byte (dst + 4, bdata[1] | (val << (40 - offset))); break; default: write_log (_T("put_bitfield() can't happen %d\n"), (offset + 7) >> 3); break; } } uae_u32 REGPARAM2 x_get_bitfield (uae_u32 src, uae_u32 bdata[2], uae_s32 offset, int width) { uae_u32 tmp1, tmp2, res, mask; offset &= 7; mask = 0xffffffffu << (32 - width); switch ((offset + width + 7) >> 3) { case 1: tmp1 = x_cp_get_byte (src); res = tmp1 << (24 + offset); bdata[0] = tmp1 & ~(mask >> (24 + offset)); break; case 2: tmp1 = x_cp_get_word (src); res = tmp1 << (16 + offset); bdata[0] = tmp1 & ~(mask >> (16 + offset)); break; case 3: tmp1 = x_cp_get_word (src); tmp2 = x_cp_get_byte (src + 2); res = tmp1 << (16 + offset); bdata[0] = tmp1 & ~(mask >> (16 + offset)); res |= tmp2 << (8 + offset); bdata[1] = tmp2 & ~(mask >> (8 + offset)); break; case 4: tmp1 = x_cp_get_long (src); res = tmp1 << offset; bdata[0] = tmp1 & ~(mask >> offset); break; case 5: tmp1 = x_cp_get_long (src); tmp2 = x_cp_get_byte (src + 4); res = tmp1 << offset; bdata[0] = tmp1 & ~(mask >> offset); res |= tmp2 >> (8 - offset); bdata[1] = tmp2 & ~(mask << (8 - offset)); break; default: /* Panic? */ write_log (_T("x_get_bitfield() can't happen %d\n"), (offset + width + 7) >> 3); res = 0; break; } return res; } void REGPARAM2 x_put_bitfield (uae_u32 dst, uae_u32 bdata[2], uae_u32 val, uae_s32 offset, int width) { offset = (offset & 7) + width; switch ((offset + 7) >> 3) { case 1: x_cp_put_byte (dst, bdata[0] | (val << (8 - offset))); break; case 2: x_cp_put_word (dst, bdata[0] | (val << (16 - offset))); break; case 3: x_cp_put_word (dst, bdata[0] | (val >> (offset - 16))); x_cp_put_byte (dst + 2, bdata[1] | (val << (24 - offset))); break; case 4: x_cp_put_long (dst, bdata[0] | (val << (32 - offset))); break; case 5: x_cp_put_long (dst, bdata[0] | (val >> (offset - 32))); x_cp_put_byte (dst + 4, bdata[1] | (val << (40 - offset))); break; default: write_log (_T("x_put_bitfield() can't happen %d\n"), (offset + 7) >> 3); break; } } uae_u32 REGPARAM2 get_disp_ea_020 (uae_u32 base, int idx) { uae_u16 dp = next_diword (); int reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) base += (uae_s32)(uae_s16) next_diword (); if ((dp & 0x30) == 0x30) base += next_dilong (); if ((dp & 0x3) == 0x2) outer = (uae_s32)(uae_s16) next_diword (); if ((dp & 0x3) == 0x3) outer = next_dilong (); if ((dp & 0x4) == 0) base += regd; if (dp & 0x3) base = get_long (base); if (dp & 0x4) base += regd; return base + outer; } else { return base + (uae_s32)((uae_s8)dp) + regd; } } uae_u32 REGPARAM2 x_get_disp_ea_020 (uae_u32 base, int idx) { uae_u16 dp = x_next_iword (); int reg = (dp >> 12) & 15; int cycles = 0; uae_u32 v; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) x_next_iword (); cycles++; } if ((dp & 0x30) == 0x30) { base += x_next_ilong (); cycles++; } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) x_next_iword (); cycles++; } if ((dp & 0x3) == 0x3) { outer = x_next_ilong (); cycles++; } if ((dp & 0x4) == 0) { base += regd; cycles++; } if (dp & 0x3) { base = x_get_long (base); cycles++; } if (dp & 0x4) { base += regd; cycles++; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } #if 0 if (cycles && currprefs.cpu_cycle_exact) x_do_cycles (cycles * cpucycleunit); #endif return v; } #ifndef CPU_TESTER uae_u32 REGPARAM2 x_get_disp_ea_ce030 (uae_u32 base, int idx) { uae_u16 dp = next_iword_030ce (); int reg = (dp >> 12) & 15; uae_u32 v; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) next_iword_030ce (); } if ((dp & 0x30) == 0x30) { base += next_ilong_030ce (); } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) next_iword_030ce (); } if ((dp & 0x3) == 0x3) { outer = next_ilong_030ce (); } if ((dp & 0x4) == 0) { base += regd; } if (dp & 0x3) { base = x_get_long (base); } if (dp & 0x4) { base += regd; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } return v; } uae_u32 REGPARAM2 x_get_disp_ea_ce020 (uae_u32 base, int idx) { uae_u16 dp = next_iword_020ce (); int reg = (dp >> 12) & 15; uae_u32 v; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) next_iword_020ce (); } if ((dp & 0x30) == 0x30) { base += next_ilong_020ce (); } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) next_iword_020ce (); } if ((dp & 0x3) == 0x3) { outer = next_ilong_020ce (); } if ((dp & 0x4) == 0) { base += regd; } if (dp & 0x3) { base = x_get_long (base); } if (dp & 0x4) { base += regd; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } return v; } uae_u32 REGPARAM2 x_get_disp_ea_040(uae_u32 base, int idx) { uae_u16 dp = next_iword_cache040(); int reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) base += (uae_s32)(uae_s16)next_iword_cache040(); if ((dp & 0x30) == 0x30) base += next_ilong_cache040(); if ((dp & 0x3) == 0x2) outer = (uae_s32)(uae_s16)next_iword_cache040(); if ((dp & 0x3) == 0x3) outer = next_ilong_cache040(); if ((dp & 0x4) == 0) base += regd; if (dp & 0x3) base = x_get_long(base); if (dp & 0x4) base += regd; return base + outer; } else { return base + (uae_s32)((uae_s8)dp) + regd; } } #endif int getMulu68kCycles(uae_u16 src) { int cycles = 0; if (currprefs.cpu_model == 68000) { cycles = 38 - 4; for (int bits = 0; bits < 16 && src; bits++, src >>= 1) { if (src & 1) cycles += 2; } } else { cycles = 40 - 4; } return cycles; } int getMuls68kCycles(uae_u16 src) { int cycles; if (currprefs.cpu_model == 68000) { cycles = 38 - 4; uae_u32 usrc = ((uae_u32)src) << 1; for (int bits = 0; bits < 16 && usrc; bits++, usrc >>= 1) { if ((usrc & 3) == 1 || (usrc & 3) == 2) { cycles += 2; } } } else { cycles = 40 - 4; // 2 extra cycles added if source is negative if (src & 0x8000) cycles += 2; } return cycles; } /* * Compute exact number of CPU cycles taken * by DIVU and DIVS on a 68000 processor. * * Copyright (c) 2005 by Jorge Cwik, pasti@fxatari.com * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ /* The routines below take dividend and divisor as parameters. They return 0 if division by zero, or exact number of cycles otherwise. The number of cycles returned assumes a register operand. Effective address time must be added if memory operand. For 68000 only (not 68010, 68012, 68020, etc). Probably valid for 68008 after adding the extra prefetch cycle. Best and worst cases for register operand: (Note the difference with the documented range.) DIVU 68000: Overflow (always): 10 cycles. Worst case: 136 cycles. Best case: 76 cycles. DIVU 68010: Overflow (always): 8 cycles. Wost case: 108 cycles. Best case: 78 cycles. DIVS 68000: Absolute overflow: 16-18 cycles. Signed overflow is not detected prematurely. Worst case: 156 cycles. Best case without signed overflow: 122 cycles. Best case with signed overflow: 120 cycles DIVS 68010: Absolute overflow: 16 cycles. Signed overflow is not detected prematurely. Worst case: 122 cycles. Best case: 120 cycles. */ int getDivu68kCycles (uae_u32 dividend, uae_u16 divisor) { int mcycles; uae_u32 hdivisor; int i; if (divisor == 0) return 0; if (currprefs.cpu_model == 68010) { // Overflow if ((dividend >> 16) >= divisor) { return 4; } mcycles = 74; hdivisor = divisor << 16; for (i = 0; i < 15; i++) { uae_u32 temp; temp = dividend; dividend <<= 1; // If carry from shift if ((uae_s32)temp < 0) { dividend -= hdivisor; } else { mcycles += 2; if (dividend >= hdivisor) { dividend -= hdivisor; } } } return mcycles; } else { // Overflow if ((dividend >> 16) >= divisor) return (mcycles = 5) * 2 - 4; mcycles = 38; hdivisor = divisor << 16; for (i = 0; i < 15; i++) { uae_u32 temp; temp = dividend; dividend <<= 1; // If carry from shift if ((uae_s32)temp < 0) { dividend -= hdivisor; } else { mcycles += 2; if (dividend >= hdivisor) { dividend -= hdivisor; mcycles--; } } } // -4 = remove prefetch cycle return mcycles * 2 - 4; } } int getDivs68kCycles (uae_s32 dividend, uae_s16 divisor, int *extra) { int mcycles; uae_u32 aquot; int i; if (divisor == 0) { return 0; } if (currprefs.cpu_model == 68010) { // Check for absolute overflow if (((uae_u32)abs(dividend) >> 16) >= (uae_u16)abs(divisor)) return 12; mcycles = 116; // add 2 extra cycles if negative dividend if (dividend < 0) { mcycles += 2; *extra = 1; } return mcycles; } mcycles = 6; if (dividend < 0) mcycles++; // Check for absolute overflow if (((uae_u32)abs (dividend) >> 16) >= (uae_u16)abs (divisor)) return (mcycles + 2) * 2 - 4; // report special case where IPL check is 2 cycles earlier *extra = (divisor < 0 && dividend >= 0) ? 1 : 0; // Absolute quotient aquot = (uae_u32) abs (dividend) / (uae_u16)abs (divisor); mcycles += 55; if (divisor >= 0) { if (dividend >= 0) mcycles--; else mcycles++; } // Count 15 msbits in absolute of quotient for (i = 0; i < 15; i++) { if ((uae_s16)aquot >= 0) mcycles++; aquot <<= 1; } return mcycles * 2 - 4; } /* DIV divide by zero * * 68000 Signed: NVC=0 Z=1. Unsigned: VC=0 N=(dst>>16)<0 Z=(dst>>16)==0 * 68020 and 68030: Signed: Z=1 NC=0. V=? (sometimes random!) Unsigned: V=1, N=(dst>>16)<0 Z=(dst>>16)==0, C=0. * 68040/68060 C=0. * */ void divbyzero_special (bool issigned, uae_s32 dst) { if (currprefs.cpu_model == 68020 || currprefs.cpu_model == 68030) { CLEAR_CZNV(); if (issigned) { SET_ZFLG(1); } else { uae_s16 d = dst >> 16; if (d < 0) SET_NFLG(1); else if (d == 0) SET_ZFLG(1); SET_VFLG(1); } } else if (currprefs.cpu_model == 68040) { SET_CFLG(0); } else if (currprefs.cpu_model == 68060) { SET_CFLG (0); } else { // 68000/010 CLEAR_CZNV (); if (issigned) { SET_ZFLG(1); } else { uae_s16 d = dst >> 16; if (d < 0) SET_NFLG(1); else if (d == 0) SET_ZFLG(1); } } } /* DIVU overflow * * 68000: V=1, N=1, C=0, Z=0 * 68010: V=1, N=(dividend >=0 or divisor >= 0), C=0, Z=0 * 68020: V=1, C=0, Z=0, N=X * 68040: V=1, C=0, NZ not modified. * 68060: V=1, C=0, NZ not modified. * * X) N is set if original 32-bit destination value is negative. * */ void setdivuflags(uae_u32 dividend, uae_u16 divisor) { if (currprefs.cpu_model == 68060) { SET_VFLG(1); SET_CFLG(0); } else if (currprefs.cpu_model == 68040) { SET_VFLG(1); SET_CFLG(0); } else if (currprefs.cpu_model >= 68020) { SET_VFLG(1); if ((uae_s32)dividend < 0) SET_NFLG(1); } else if (currprefs.cpu_model == 68010) { SET_VFLG(1); if ((uae_s32)dividend < 0 && (uae_s16)divisor < 0) { SET_NFLG(0); } else { SET_NFLG(1); } SET_ZFLG(0); SET_CFLG(0); } else { // 68000 SET_VFLG(1); SET_NFLG(1); SET_ZFLG(0); SET_CFLG(0); } } /* * DIVS overflow * * 68000: V=1, C=0, N=1, Z=0 * 68010: V=1, C=0, N=0, Z= * 68020: V=1, C=0, ZN = X * 68040: V=1, C=0. NZ not modified. * 68060: V=1, C=0, NZ not modified. * * X) if absolute overflow(Check getDivs68kCycles for details) : Z = 0, N = 0 * if not absolute overflow : N is set if internal result BYTE is negative, Z is set if it is zero! * */ void setdivsflags(uae_s32 dividend, uae_s16 divisor) { if (currprefs.cpu_model == 68060) { SET_VFLG(1); SET_CFLG(0); } else if (currprefs.cpu_model == 68040) { SET_VFLG(1); SET_CFLG(0); } else if (currprefs.cpu_model >= 68020) { CLEAR_CZNV(); SET_VFLG(1); // absolute overflow? if (((uae_u32)abs(dividend) >> 16) >= (uae_u16)abs(divisor)) return; uae_u32 aquot = (uae_u32)abs(dividend) / (uae_u16)abs(divisor); if ((uae_s8)aquot == 0) SET_ZFLG(1); if ((uae_s8)aquot < 0) SET_NFLG(1); } else if (currprefs.cpu_model == 68010) { CLEAR_CZNV(); SET_VFLG(1); SET_NFLG(1); } else { // 68000 CLEAR_CZNV(); SET_VFLG(1); SET_NFLG(1); } } /* * CHK undefined flags * * 68000: CV=0. Z: dst==0. N: dst < 0. !N: dst > src. * 68020: Z: dst==0. N: dst < 0. V: src-dst overflow. C: if dst < 0: (dst > src || src >= 0), if dst > src: (src >= 0). * 68040: C=0. C=1 if exception and (dst < 0 && src >= 0) || (src >= 0 && dst >= src) || (dst < 0 && src < dst) * 68060: N=0. If exception: N=dst < 0 * */ void setchkundefinedflags(uae_s32 src, uae_s32 dst, int size) { if (currprefs.cpu_model < 68020) { CLEAR_CZNV(); if (dst == 0) SET_ZFLG(1); if (dst < 0) SET_NFLG(1); else if (dst > src) SET_NFLG(0); } else if (currprefs.cpu_model == 68020 || currprefs.cpu_model == 68030) { CLEAR_CZNV(); if (dst == 0) SET_ZFLG(1); SET_NFLG(dst < 0); if (dst < 0 || dst > src) { if (size == sz_word) { int flgs = ((uae_s16)(dst)) < 0; int flgo = ((uae_s16)(src)) < 0; uae_s16 val = (uae_s16)src - (uae_s16)dst; int flgn = val < 0; SET_VFLG((flgs ^ flgo) & (flgn ^ flgo)); } else { int flgs = dst < 0; int flgo = src < 0; uae_s32 val = src - dst; int flgn = val < 0; SET_VFLG((flgs ^ flgo) & (flgn ^ flgo)); } if (dst < 0) { SET_CFLG(dst > src || src >= 0); } else { SET_CFLG(src >= 0); } } } else if (currprefs.cpu_model == 68040) { SET_CFLG(0); if (dst < 0 || dst > src) { if (dst < 0 && src >= 0) { SET_CFLG(1); } else if (src >= 0 && dst >= src) { SET_CFLG(1); } else if (dst < 0 && src < dst) { SET_CFLG(1); } } SET_NFLG(dst < 0); } else if (currprefs.cpu_model == 68060) { SET_NFLG(0); if (dst < 0 || dst > src) { SET_NFLG(dst < 0); } } } /* * CHK2/CMP2 undefined flags * * 68020-68030: See below.. * 68040: NV not modified. * 68060: N: val<0 V=0 * */ // This is the complex one. // Someone else can attempt to simplify this.. void setchk2undefinedflags(uae_s32 lower, uae_s32 upper, uae_s32 val, int size) { if (currprefs.cpu_model == 68060) { SET_VFLG(0); SET_NFLG(val < 0); return; } else if (currprefs.cpu_model == 68040) { return; } SET_NFLG(0); SET_VFLG(0); if (val == lower || val == upper) return; if (lower < 0 && upper >= 0) { if (val < lower) { SET_NFLG(1); } if (val >= 0 && val < upper) { SET_NFLG(1); } if (val >= 0 && lower - val >= 0) { SET_VFLG(1); SET_NFLG(0); if (val > upper) { SET_NFLG(1); } } } else if (lower >= 0 && upper < 0) { if (val >= 0) { SET_NFLG(1); } if (val > upper) { SET_NFLG(1); } if (val > lower && upper - val >= 0) { SET_VFLG(1); SET_NFLG(0); } } else if (lower >= 0 && upper >= 0 && lower > upper) { if (val > upper && val < lower) { SET_NFLG(1); } if (val < 0 && lower - val < 0) { SET_VFLG(1); } if (val < 0 && lower - val >= 0) { SET_NFLG(1); } } else if (lower >= 0 && upper >= 0 && lower <= upper) { if (val >= 0 && val < lower) { SET_NFLG(1); } if (val > upper) { SET_NFLG(1); } if (val < 0 && upper - val < 0) { SET_VFLG(1); SET_NFLG(1); } } else if (lower < 0 && upper < 0 && lower > upper) { if (val >= 0) { SET_NFLG(1); } if (val > upper && val < lower) { SET_NFLG(1); } if (val >= 0 && val - lower < 0) { SET_NFLG(0); SET_VFLG(1); } } else if (lower < 0 && upper < 0 && lower <= upper) { if (val < lower) { SET_NFLG(1); } if (val < 0 && val > upper) { SET_NFLG(1); } if (val >= 0 && val - lower < 0) { SET_NFLG(1); SET_VFLG(1); } } } #ifndef CPUEMU_68000_ONLY static void divsl_overflow(uae_u16 extra, uae_s64 a, uae_s32 divider) { if (currprefs.cpu_model >= 68040) { SET_VFLG(1); SET_CFLG(0); } else { uae_s32 a32 = (uae_s32)a; bool neg64 = a < 0; bool neg32 = a32 < 0; SET_VFLG(1); if (extra & 0x0400) { // this is still missing condition where Z is set // without none of input parameters being zero. uae_s32 ahigh = a >> 32; if (ahigh == 0) { SET_ZFLG(1); SET_NFLG(0); } else if (ahigh < 0 && divider < 0 && ahigh > divider) { SET_ZFLG(0); SET_NFLG(0); } else { if (a32 == 0) { SET_ZFLG(1); SET_NFLG(0); } else { SET_ZFLG(0); SET_NFLG(neg32 ^ neg64); } } } else { if (a32 == 0) { SET_ZFLG(1); SET_NFLG(0); } else { SET_NFLG(neg32); SET_ZFLG(0); } } SET_CFLG(0); } } static void divul_overflow(uae_u16 extra, uae_s64 a) { if (currprefs.cpu_model >= 68040) { SET_VFLG(1); SET_CFLG(0); } else { uae_s32 a32 = (uae_s32)a; bool neg32 = a32 < 0; SET_VFLG(1); SET_NFLG(neg32); SET_ZFLG(a32 == 0); SET_CFLG(0); } } static void divsl_divbyzero(uae_u16 extra, uae_s64 a, uaecptr oldpc) { if (currprefs.cpu_model >= 68040) { SET_CFLG(0); } else { SET_NFLG(0); SET_ZFLG(1); SET_CFLG(0); } Exception_cpu_oldpc(5, oldpc); } static void divul_divbyzero(uae_u16 extra, uae_s64 a, uaecptr oldpc) { if (currprefs.cpu_model >= 68040) { SET_CFLG(0); } else { uae_s32 a32 = (uae_s32)a; bool neg32 = a32 < 0; SET_NFLG(neg32); SET_ZFLG(a32 == 0); SET_VFLG(1); SET_CFLG(0); } Exception_cpu_oldpc(5, oldpc); } int m68k_divl(uae_u32 opcode, uae_u32 src, uae_u16 extra, uaecptr oldpc) { if ((extra & 0x400) && currprefs.int_no_unimplemented && currprefs.cpu_model == 68060) { return -1; } if (extra & 0x800) { /* signed variant */ uae_s64 a = (uae_s64)(uae_s32)m68k_dreg (regs, (extra >> 12) & 7); uae_s64 quot, rem; if (extra & 0x400) { a &= 0xffffffffu; a |= (uae_s64)m68k_dreg (regs, extra & 7) << 32; } if (src == 0) { divsl_divbyzero(extra, a, oldpc); return 0; } if ((uae_u64)a == 0x8000000000000000UL && src == ~0u) { divsl_overflow(extra, a, src); } else { rem = a % (uae_s64)(uae_s32)src; quot = a / (uae_s64)(uae_s32)src; if ((quot & UVAL64 (0xffffffff80000000)) != 0 && (quot & UVAL64 (0xffffffff80000000)) != UVAL64 (0xffffffff80000000)) { divsl_overflow(extra, a, src); } else { if (((uae_s32)rem < 0) != ((uae_s64)a < 0)) rem = -rem; SET_VFLG (0); SET_CFLG (0); SET_ZFLG (((uae_s32)quot) == 0); SET_NFLG (((uae_s32)quot) < 0); m68k_dreg (regs, extra & 7) = (uae_u32)rem; m68k_dreg (regs, (extra >> 12) & 7) = (uae_u32)quot; } } } else { /* unsigned */ uae_u64 a = (uae_u64)(uae_u32)m68k_dreg (regs, (extra >> 12) & 7); uae_u64 quot, rem; if (extra & 0x400) { a &= 0xffffffffu; a |= (uae_u64)m68k_dreg (regs, extra & 7) << 32; } if (src == 0) { divul_divbyzero(extra, a, oldpc); return 0; } rem = a % (uae_u64)src; quot = a / (uae_u64)src; if (quot > 0xffffffffu) { divul_overflow(extra, a); } else { SET_VFLG (0); SET_CFLG (0); SET_ZFLG (((uae_s32)quot) == 0); SET_NFLG (((uae_s32)quot) < 0); m68k_dreg (regs, extra & 7) = (uae_u32)rem; m68k_dreg (regs, (extra >> 12) & 7) = (uae_u32)quot; } } return 1; } int m68k_mull (uae_u32 opcode, uae_u32 src, uae_u16 extra) { if ((extra & 0x400) && currprefs.int_no_unimplemented && currprefs.cpu_model == 68060) { return -1; } if (extra & 0x800) { /* signed */ uae_s64 a = (uae_s64)(uae_s32)m68k_dreg (regs, (extra >> 12) & 7); a *= (uae_s64)(uae_s32)src; SET_VFLG (0); SET_CFLG (0); if (extra & 0x400) { // 32 * 32 = 64 // 68040 is different. if (currprefs.cpu_model >= 68040) { m68k_dreg(regs, extra & 7) = (uae_u32)(a >> 32); m68k_dreg(regs, (extra >> 12) & 7) = (uae_u32)a; } else { // 020/030 m68k_dreg(regs, (extra >> 12) & 7) = (uae_u32)a; m68k_dreg(regs, extra & 7) = (uae_u32)(a >> 32); } SET_ZFLG(a == 0); SET_NFLG(a < 0); } else { // 32 * 32 = 32 uae_s32 b = (uae_s32)a; m68k_dreg(regs, (extra >> 12) & 7) = (uae_u32)a; if ((a & UVAL64(0xffffffff80000000)) != 0 && (a & UVAL64(0xffffffff80000000)) != UVAL64(0xffffffff80000000)) { SET_VFLG(1); } SET_ZFLG(b == 0); SET_NFLG(b < 0); } } else { /* unsigned */ uae_u64 a = (uae_u64)(uae_u32)m68k_dreg (regs, (extra >> 12) & 7); a *= (uae_u64)src; SET_VFLG (0); SET_CFLG (0); if (extra & 0x400) { // 32 * 32 = 64 // 68040 is different. if (currprefs.cpu_model >= 68040) { m68k_dreg(regs, extra & 7) = (uae_u32)(a >> 32); m68k_dreg(regs, (extra >> 12) & 7) = (uae_u32)a; } else { // 020/030 m68k_dreg(regs, (extra >> 12) & 7) = (uae_u32)a; m68k_dreg(regs, extra & 7) = (uae_u32)(a >> 32); } SET_ZFLG(a == 0); SET_NFLG(((uae_s64)a) < 0); } else { // 32 * 32 = 32 uae_s32 b = (uae_s32)a; m68k_dreg(regs, (extra >> 12) & 7) = (uae_u32)a; if ((a & UVAL64(0xffffffff00000000)) != 0) { SET_VFLG(1); } SET_ZFLG(b == 0); SET_NFLG(b < 0); } } return 1; } #endif uae_u32 exception_pc(int nr) { // bus error, address error, illegal instruction, privilege violation, a-line, f-line if (nr == 2 || nr == 3 || nr == 4 || nr == 8 || nr == 10 || nr == 11) return regs.instruction_pc; return m68k_getpc(); } void Exception_build_stack_frame(uae_u32 oldpc, uae_u32 currpc, uae_u32 ssw, int nr, int format) { int i; switch (format) { case 0x0: // four word stack frame case 0x1: // throwaway four word stack frame break; case 0x2: // six word stack frame m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), oldpc); break; case 0x3: // floating point post-instruction stack frame (68040) m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.fp_ea); break; case 0x4: // floating point unimplemented stack frame (68LC040, 68EC040) // or 68060 bus access fault stack frame m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), ssw); m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), oldpc); break; case 0x7: // access error stack frame (68040) for (i = 3; i >= 0; i--) { // WB1D/PD0,PD1,PD2,PD3 m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), mmu040_move16[i]); } m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), 0); // WB1A m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), 0); // WB2D m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.wb2_address); // WB2A m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.wb3_data); // WB3D m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.mmu_fault_addr); // WB3A m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.mmu_fault_addr); // FA m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), 0); m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.wb2_status); regs.wb2_status = 0; m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.wb3_status); regs.wb3_status = 0; m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), ssw); m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.mmu_effective_addr); break; case 0x8: // bus/address error (68010) { uae_u16 in = regs.read_buffer; uae_u16 out = regs.write_buffer; for (i = 0; i < 15; i++) { m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), 0); } m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), 0x0000); // version (probably bits 12 to 15 only because other bits change) m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.irc); // instruction input buffer m68k_areg(regs, 7) -= 2; // unused not written m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), in); // data input buffer m68k_areg(regs, 7) -= 2; // unused not written m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), out); // data output buffer m68k_areg(regs, 7) -= 2; // unused not written m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.mmu_fault_addr); // fault addr m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.mmu_fault_addr >> 16); // fault addr m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), ssw); // ssw break; } case 0x9: // coprocessor mid-instruction stack frame (68020, 68030) m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.fp_ea); m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.fp_opword); m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), oldpc); break; case 0xB: // long bus cycle fault stack frame (68020, 68030) // Store state information to internal register space #if MMU030_DEBUG if (mmu030_idx >= MAX_MMU030_ACCESS) { write_log(_T("mmu030_idx out of bounds! %d >= %d\n"), mmu030_idx, MAX_MMU030_ACCESS); } #endif if (!(ssw & MMU030_SSW_RW)) { // written value that caused fault but was not yet written to memory // this value is used in cpummu030 when write is retried. mmu030_ad[mmu030_idx_done].val = regs.wb3_data; } for (i = 0; i < mmu030_idx_done + 1; i++) { m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), mmu030_ad[i].val); } while (i < MAX_MMU030_ACCESS) { uae_u32 v = 0; m68k_areg(regs, 7) -= 4; // mmu030_idx is always small enough if instruction is FMOVEM. if (mmu030_state[1] & MMU030_STATEFLAG1_FMOVEM) { #if MMU030_DEBUG if (mmu030_idx >= MAX_MMU030_ACCESS - 2) { write_log(_T("mmu030_idx (FMOVEM) out of bounds! %d >= %d\n"), mmu030_idx, MAX_MMU030_ACCESS - 2); } #endif if (i == MAX_MMU030_ACCESS - 2) v = mmu030_fmovem_store[0]; else if (i == MAX_MMU030_ACCESS - 1) v = mmu030_fmovem_store[1]; } x_put_long(m68k_areg(regs, 7), v); i++; } // version & internal information (We store index here) m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), (mmu030_idx & 0xf) | ((mmu030_idx_done & 0xf) << 4) | (regs.wb2_status << 8)); // 3* internal registers m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), mmu030_state[2] | (regs.wb3_status << 8)); m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.wb2_address); // = mmu030_state[1] m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), mmu030_state[0]); // data input buffer = fault address m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.mmu_fault_addr); // 2xinternal { uae_u32 ps = (regs.prefetch020_valid[0] ? 1 : 0) | (regs.prefetch020_valid[1] ? 2 : 0) | (regs.prefetch020_valid[2] ? 4 : 0); ps |= ((regs.pipeline_r8[0] & 7) << 8); ps |= ((regs.pipeline_r8[1] & 7) << 11); ps |= ((regs.pipeline_pos & 15) << 16); ps |= ((regs.pipeline_stop & 15) << 20); if (mmu030_opcode == -1) ps |= 1 << 31; m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), ps); } // stage b address m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), mm030_stageb_address); // 2xinternal m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), mmu030_disp_store[1]); /* fall through */ case 0xA: // short bus cycle fault stack frame (68020, 68030) // used when instruction's last write causes bus fault m68k_areg(regs, 7) -= 4; if (format == 0xb) { x_put_long(m68k_areg(regs, 7), mmu030_disp_store[0]); // 28 0x1c } else { uae_u32 ps = (regs.prefetch020_valid[0] ? 1 : 0) | (regs.prefetch020_valid[1] ? 2 : 0) | (regs.prefetch020_valid[2] ? 4 : 0); ps |= ((regs.pipeline_r8[0] & 7) << 8); ps |= ((regs.pipeline_r8[1] & 7) << 11); ps |= ((regs.pipeline_pos & 15) << 16); ps |= ((regs.pipeline_stop & 15) << 20); x_put_long(m68k_areg(regs, 7), ps); // 28 0x1c } m68k_areg(regs, 7) -= 4; // Data output buffer = value that was going to be written x_put_long(m68k_areg(regs, 7), regs.wb3_data); // 24 0x18 m68k_areg(regs, 7) -= 4; if (format == 0xb) { x_put_long(m68k_areg(regs, 7), (mmu030_opcode & 0xffff) | (regs.prefetch020[0] << 16)); // Internal register (opcode storage) 20 0x14 } else { x_put_long(m68k_areg(regs, 7), regs.irc | (regs.prefetch020[0] << 16)); // Internal register (opcode storage) 20 0x14 } m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), regs.mmu_fault_addr); // data cycle fault address 16 0x10 m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.prefetch020[2]); // Instr. pipe stage B 14 0x0e m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.prefetch020[1]); // Instr. pipe stage C 12 0x0c m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), ssw); // 10 0x0a m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.wb2_address); // = mmu030_state[1]); 8 0x08 break; default: write_log(_T("Unknown exception stack frame format: %X\n"), format); return; } m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), (format << 12) | (nr * 4)); m68k_areg(regs, 7) -= 4; x_put_long(m68k_areg(regs, 7), currpc); m68k_areg(regs, 7) -= 2; x_put_word(m68k_areg(regs, 7), regs.sr); } void Exception_build_stack_frame_common(uae_u32 oldpc, uae_u32 currpc, uae_u32 ssw, int nr, int vector_nr) { if (nr == 5 || nr == 6 || nr == 7 || nr == 9) { if (nr == 9) oldpc = regs.trace_pc; if (currprefs.cpu_model <= 68010) Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0x0); else Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0x2); } else if (nr == 60 || nr == 61) { Exception_build_stack_frame(oldpc, regs.instruction_pc, regs.mmu_ssw, vector_nr, 0x0); } else if (nr >= 48 && nr <= 55) { if (regs.fpu_exp_pre) { if (currprefs.cpu_model == 68060 && nr == 55 && regs.fp_unimp_pend == 2) { // packed decimal real Exception_build_stack_frame(regs.fp_ea, regs.instruction_pc, 0, vector_nr, 0x2); } else { Exception_build_stack_frame(oldpc, regs.instruction_pc, 0, vector_nr, 0x0); } } else { /* post-instruction */ if (currprefs.cpu_model == 68060 && nr == 55 && regs.fp_unimp_pend == 2) { // packed decimal real Exception_build_stack_frame(regs.fp_ea, currpc, 0, vector_nr, 0x2); } else { Exception_build_stack_frame(oldpc, currpc, 0, vector_nr, 0x3); } } } else if (nr == 11 && regs.fp_unimp_ins) { regs.fp_unimp_ins = false; if ((currprefs.cpu_model == 68060 && (currprefs.fpu_model == 0 || (regs.pcr & 2))) || (currprefs.cpu_model == 68040 && currprefs.fpu_model == 0)) { Exception_build_stack_frame(regs.fp_ea, currpc, regs.instruction_pc, vector_nr, 0x4); } else { Exception_build_stack_frame(regs.fp_ea, currpc, regs.mmu_ssw, vector_nr, 0x2); } } else { Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, vector_nr, 0x0); } } void Exception_build_68000_address_error_stack_frame(uae_u16 mode, uae_u16 opcode, uaecptr fault_addr, uaecptr pc) { // undocumented bits seem to contain opcode mode |= opcode & ~31; m68k_areg(regs, 7) -= 14; x_put_word(m68k_areg(regs, 7) + 0, mode); x_put_long(m68k_areg(regs, 7) + 2, fault_addr); x_put_word(m68k_areg(regs, 7) + 6, opcode); x_put_word(m68k_areg(regs, 7) + 8, regs.sr); x_put_long(m68k_areg(regs, 7) + 10, pc); } void cpu_restore_fixup(void) { if (mmufixup[0].reg >= 0) { m68k_areg(regs, mmufixup[0].reg & 15) = mmufixup[0].value; mmufixup[0].reg = -1; } if (mmufixup[1].reg >= 0) { m68k_areg(regs, mmufixup[1].reg & 15) = mmufixup[1].value; mmufixup[1].reg = -1; } } // Low word: Clear + Z and N void ccr_68000_long_move_ae_LZN(uae_s32 src) { CLEAR_CZNV(); uae_s16 vsrc = (uae_s16)(src & 0xffff); SET_ZFLG(vsrc == 0); SET_NFLG(vsrc < 0); } // Low word: Clear + N only void ccr_68000_long_move_ae_LN(uae_s32 src) { CLEAR_CZNV(); uae_s16 vsrc = (uae_s16)(src & 0xffff); SET_NFLG(vsrc < 0); } // High word: N and Z clear. void ccr_68000_long_move_ae_HNZ(uae_s32 src) { uae_s16 vsrc = (uae_s16)(src >> 16); if(vsrc < 0) { SET_NFLG(1); SET_ZFLG(0); } else if (vsrc) { SET_NFLG(0); SET_ZFLG(0); } else { SET_NFLG(0); } } // Set normally. void ccr_68000_long_move_ae_normal(uae_s32 src) { CLEAR_CZNV(); SET_ZFLG(src == 0); SET_NFLG(src < 0); } void ccr_68000_word_move_ae_normal(uae_s16 src) { CLEAR_CZNV(); SET_ZFLG(src == 0); SET_NFLG(src < 0); } void dreg_68000_long_replace_low(int reg, uae_u16 v) { m68k_dreg(regs, reg) = (m68k_dreg(regs, reg) & 0xffff0000) | v; } void areg_68000_long_replace_low(int reg, uae_u16 v) { m68k_areg(regs, reg) = (m68k_areg(regs, reg) & 0xffff0000) | v; } // Change F-line to privilege violation if missing co-pro // 68040 and 68060 always return F-line bool privileged_copro_instruction(uae_u16 opcode) { if (currprefs.cpu_model >= 68020 && !regs.s) { int reg = opcode & 7; int mode = (opcode >> 3) & 7; int id = (opcode >> 9) & 7; // cpSAVE and cpRESTORE: privilege violation if user mode. if ((opcode & 0xf1c0) == 0xf100) { // cpSAVE // check if valid EA if (mode == 2 || (mode >= 4 && mode <= 6) || (mode == 7 && (reg == 0 || reg == 1))) { if (currprefs.cpu_model < 68040 || (currprefs.cpu_model >= 68040 && id == 1)) return true; } } else if ((opcode & 0xf1c0) == 0xf140) { // cpRESTORE // check if valid EA if (mode == 2 || mode == 3 || (mode >= 5 && mode <= 6) || (mode == 7 && reg <= 3)) { if (currprefs.cpu_model < 68040 || (currprefs.cpu_model >= 68040 && id == 1)) return true; } } } return false; } bool generates_group1_exception(uae_u16 opcode) { struct instr *table = &table68k[opcode]; // illegal/a-line/f-line? if (table->mnemo == i_ILLG) return true; // privilege violation? if (!regs.s) { if (table->plev == 1 && currprefs.cpu_model > 68000) return true; if (table->plev == 2) return true; if (table->plev == 3 && table->size == sz_word) return true; } return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/options_cpu.h000066400000000000000000000712721504763705000246430ustar00rootroot00000000000000/* Hatari - options_cpu.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef UAE_OPTIONS_H #define UAE_OPTIONS_H #include "uae/types.h" //#include "traps.h" #ifndef WINUAE_FOR_HATARI #define UAEMAJOR 6 #define UAEMINOR 0 #define UAESUBREV 0 #define MAX_AMIGADISPLAYS 4 typedef enum { KBD_LANG_US, KBD_LANG_DK, KBD_LANG_DE, KBD_LANG_SE, KBD_LANG_FR, KBD_LANG_IT, KBD_LANG_ES } KbdLang; extern long int version; #define MAX_PATHS 8 struct multipath { TCHAR path[MAX_PATHS][PATH_MAX]; }; #define PATH_NONE -1 #define PATH_FLOPPY 0 #define PATH_CD 1 #define PATH_DIR 2 #define PATH_HDF 3 #define PATH_FS 4 #define PATH_TAPE 5 #define PATH_GENLOCK_IMAGE 6 #define PATH_GENLOCK_VIDEO 7 #define PATH_GEO 8 #define PATH_ROM 9 struct strlist { struct strlist *next; TCHAR *option, *value; int unknown; }; #define MAX_TOTAL_SCSI_DEVICES 8 /* maximum number native input devices supported (single type) */ #define MAX_INPUT_DEVICES 20 /* maximum number of native input device's buttons and axles supported */ #define MAX_INPUT_DEVICE_EVENTS 256 /* 4 different customization settings */ #define MAX_INPUT_SETTINGS 4 #define GAMEPORT_INPUT_SETTINGS 3 // last slot is for gameport panel mappings #define MAX_INPUT_SUB_EVENT 8 #define MAX_INPUT_SUB_EVENT_ALL 9 #define SPARE_SUB_EVENT 8 #define INTERNALEVENT_COUNT 1 struct uae_input_device { TCHAR *name; TCHAR *configname; uae_s16 eventid[MAX_INPUT_DEVICE_EVENTS][MAX_INPUT_SUB_EVENT_ALL]; TCHAR *custom[MAX_INPUT_DEVICE_EVENTS][MAX_INPUT_SUB_EVENT_ALL]; uae_u64 flags[MAX_INPUT_DEVICE_EVENTS][MAX_INPUT_SUB_EVENT_ALL]; uae_s8 port[MAX_INPUT_DEVICE_EVENTS][MAX_INPUT_SUB_EVENT_ALL]; uae_s16 extra[MAX_INPUT_DEVICE_EVENTS]; uae_s8 enabled; }; #define MAX_JPORTS_CUSTOM 6 #define MAX_JPORTS 4 #define NORMAL_JPORTS 2 #define MAX_JPORT_NAME 128 #define MAX_JPORT_CONFIG 256 struct jport_custom { TCHAR custom[MAX_DPATH]; }; struct inputdevconfig { TCHAR name[MAX_JPORT_NAME]; TCHAR configname[MAX_JPORT_CONFIG]; TCHAR shortid[16]; }; struct jport_dev { int id; int mode; // 0=def,1=mouse,2=joy,3=anajoy,4=lightpen int submode; int autofire; struct inputdevconfig idc; }; #define MAX_JPORT_DEVS 8 struct jport { struct jport_dev jd[MAX_JPORT_DEVS]; bool nokeyboardoverride; bool changed; }; #define JPORT_UNPLUGGED -2 #define JPORT_NONE -1 #define JPORT_AF_NORMAL 1 #define JPORT_AF_TOGGLE 2 #define JPORT_AF_ALWAYS 3 #define JPORT_AF_TOGGLENOAF 4 #define KBTYPE_AMIGA 0 #define KBTYPE_PC1 1 #define KBTYPE_PC2 2 #define MAX_SPARE_DRIVES 20 #define MAX_CUSTOM_MEMORY_ADDRS 2 #define CONFIG_TYPE_ALL -1 #define CONFIG_TYPE_DEFAULT 0 #define CONFIG_TYPE_HARDWARE 1 #define CONFIG_TYPE_HOST 2 #define CONFIG_TYPE_NORESET 4 #define CONFIG_BLEN 2560 #define MOUSEUNTRAP_NONE 0 #define MOUSEUNTRAP_MIDDLEBUTTON 1 #define MOUSEUNTRAP_MAGIC 2 #define MOUSEUNTRAP_BOTH 3 #define TABLET_OFF 0 #define TABLET_MOUSEHACK 1 #define TABLET_REAL 2 #ifdef WITH_SLIRP #define MAX_SLIRP_REDIRS 32 struct slirp_redir { int proto; int srcport; int dstport; unsigned long addr; }; #endif struct cdslot { TCHAR name[MAX_DPATH]; bool inuse; bool delayed; bool temporary; int type; }; struct floppyslot { TCHAR df[MAX_DPATH]; int dfxtype; int dfxsubtype; TCHAR dfxsubtypeid[32]; int dfxclick; TCHAR dfxclickexternal[256]; bool forcedwriteprotect; }; #define ASPECTMULT 1024 #define WH_NATIVE 1 struct wh { int x, y; int width, height; int special; }; #define MOUNT_CONFIG_SIZE 30 #define UAEDEV_DIR 0 #define UAEDEV_HDF 1 #define UAEDEV_CD 2 #define UAEDEV_TAPE 3 #define HD_LEVEL_SCSI_1 0 #define HD_LEVEL_SCSI_2 1 #define HD_LEVEL_SASI 2 #define HD_LEVEL_SASI_ENHANCED 2 #define HD_LEVEL_SASI_CHS 3 #define HD_LEVEL_ATA_1 0 #define HD_LEVEL_ATA_2 1 #define HD_LEVEL_ATA_2S 2 #define BOOTPRI_NOAUTOBOOT -128 #define BOOTPRI_NOAUTOMOUNT -129 #define ISAUTOBOOT(ci) ((ci)->bootpri > BOOTPRI_NOAUTOBOOT) #define ISAUTOMOUNT(ci) ((ci)->bootpri > BOOTPRI_NOAUTOMOUNT) #define MAX_UAEDEV_BADBLOCKS 8 struct uaedev_badblock { uae_u32 first; uae_u32 last; }; struct uaedev_config_info { int type; TCHAR devname[MAX_DPATH]; TCHAR volname[MAX_DPATH]; TCHAR rootdir[MAX_DPATH]; bool readonly; bool lock; bool loadidentity; int bootpri; TCHAR filesys[MAX_DPATH]; TCHAR geometry[MAX_DPATH]; int lowcyl; int highcyl; // zero if detected from size int cyls; // calculated/corrected highcyl int surfaces; int sectors; int reserved; int blocksize; bool chs; uae_u64 max_lba; int controller_type; int controller_type_unit; int controller_unit; int controller_media_type; // 1 = CF IDE, 0 = normal int unit_feature_level; int unit_special_flags; bool physical_geometry; // if false: use defaults int pcyls, pheads, psecs; int flags; int buffers; int bufmemtype; int stacksize; int priority; uae_u32 mask; int maxtransfer; uae_u32 dostype; int unit; int interleave; int sectorsperblock; int forceload; int device_emu_unit; bool inject_icons; int badblock_num; struct uaedev_badblock badblocks[MAX_UAEDEV_BADBLOCKS]; int uae_unitnum; // mountunit nr }; struct uaedev_config_data { struct uaedev_config_info ci; int configoffset; // HD config entry index int unitnum; // scsi unit number (if tape currently) }; enum { CP_GENERIC = 1, CP_CDTV, CP_CDTVCR, CP_CD32, CP_A500, CP_A500P, CP_A600, CP_A1000, CP_A1200, CP_A2000, CP_A3000, CP_A3000T, CP_A4000, CP_A4000T, CP_VELVET, CP_CASABLANCA, CP_DRACO }; #define IDE_A600A1200 1 #define IDE_A4000 2 #define GFX_WINDOW 0 #define GFX_FULLSCREEN 1 #define GFX_FULLWINDOW 2 #define AUTOSCALE_NONE 0 #define AUTOSCALE_STATIC_AUTO 1 #define AUTOSCALE_STATIC_NOMINAL 2 #define AUTOSCALE_STATIC_MAX 3 #define AUTOSCALE_NORMAL 4 #define AUTOSCALE_RESIZE 5 #define AUTOSCALE_CENTER 6 #define AUTOSCALE_MANUAL 7 // use gfx_xcenter_pos and gfx_ycenter_pos #define AUTOSCALE_INTEGER 8 #define AUTOSCALE_INTEGER_AUTOSCALE 9 #define AUTOSCALE_SEPARATOR 10 #define AUTOSCALE_OVERSCAN_BLANK 11 #define MONITOREMU_NONE 0 #define MONITOREMU_AUTO 1 #define MONITOREMU_A2024 2 #define MONITOREMU_GRAFFITI 3 #define MONITOREMU_HAM_E 4 #define MONITOREMU_HAM_E_PLUS 5 #define MONITOREMU_VIDEODAC18 6 #define MONITOREMU_AVIDEO12 7 #define MONITOREMU_AVIDEO24 8 #define MONITOREMU_FIRECRACKER24 9 #define MONITOREMU_DCTV 10 #define MONITOREMU_OPALVISION 11 #define MONITOREMU_COLORBURST 12 #define OVERSCANMODE_OVERSCAN 3 #define OVERSCANMODE_BROADCAST 4 #define OVERSCANMODE_EXTREME 5 #define OVERSCANMODE_ULTRA 6 #define MAX_FILTERSHADERS 4 #define MAX_CHIPSET_REFRESH 10 #define MAX_CHIPSET_REFRESH_TOTAL (MAX_CHIPSET_REFRESH + 2) #define CHIPSET_REFRESH_PAL (MAX_CHIPSET_REFRESH + 0) #define CHIPSET_REFRESH_NTSC (MAX_CHIPSET_REFRESH + 1) struct chipset_refresh { bool inuse; int index; bool locked; bool rtg; bool exit; bool defaultdata; int horiz; int vert; int lace; int resolution; int resolution_pct; int ntsc; int vsync; int framelength; float rate; TCHAR label[16]; TCHAR commands[256]; TCHAR filterprofile[64]; }; #define APMODE_NATIVE 0 #define APMODE_RTG 1 struct apmode { int gfx_fullscreen; int gfx_display; int gfx_vsync; // 0 = immediate flip // -1 = wait for flip, before frame ends // 1 = wait for flip, after new frame has started int gfx_vflip; // doubleframemode strobo bool gfx_strobo; int gfx_vsyncmode; int gfx_backbuffers; bool gfx_interlaced; int gfx_refreshrate; }; #define MAX_LUA_STATES 16 #define MAX_FILTERDATA 3 #define GF_NORMAL 0 #define GF_RTG 1 #define GF_INTERLACE 2 struct gfx_filterdata { int enable; int gfx_filter; TCHAR gfx_filtershader[2 * MAX_FILTERSHADERS + 1][MAX_DPATH]; TCHAR gfx_filtermask[2 * MAX_FILTERSHADERS + 1][MAX_DPATH]; TCHAR gfx_filteroverlay[MAX_DPATH]; struct wh gfx_filteroverlay_pos; int gfx_filteroverlay_overscan; int gfx_filter_scanlines; int gfx_filter_scanlineratio; int gfx_filter_scanlinelevel; int gfx_filter_scanlineoffset; float gfx_filter_horiz_zoom, gfx_filter_vert_zoom; float gfx_filter_horiz_zoom_mult, gfx_filter_vert_zoom_mult; float gfx_filter_horiz_offset, gfx_filter_vert_offset; int gfx_filter_left_border, gfx_filter_right_border; int gfx_filter_top_border, gfx_filter_bottom_border; int gfx_filter_filtermodeh, gfx_filter_filtermodev; int gfx_filter_bilinear; int gfx_filter_noise, gfx_filter_blur; int gfx_filter_saturation, gfx_filter_luminance, gfx_filter_contrast; int gfx_filter_gamma, gfx_filter_gamma_ch[3]; int gfx_filter_keep_aspect, gfx_filter_aspect; int gfx_filter_autoscale; int gfx_filter_integerscalelimit; int gfx_filter_keep_autoscale_aspect; int gfx_filter_rotation; bool changed; }; #define MAX_DUPLICATE_EXPANSION_BOARDS 5 #define MAX_AVAILABLE_DUPLICATE_EXPANSION_BOARDS 4 #define MAX_EXPANSION_BOARDS 20 #define ROMCONFIG_CONFIGTEXT_LEN 256 struct boardromconfig; struct romconfig { TCHAR romfile[MAX_DPATH]; TCHAR romident[256]; uae_u32 board_ram_size; bool autoboot_disabled; bool inserted; bool dma24bit; int device_id; int device_settings; int subtype; void *unitdata; TCHAR configtext[ROMCONFIG_CONFIGTEXT_LEN]; uae_u16 manufacturer; uae_u8 product; uae_u8 autoconfig[16]; struct boardromconfig *back; }; #define MAX_BOARD_ROMS 2 struct boardromconfig { int device_type; int device_num; int device_order; struct romconfig roms[MAX_BOARD_ROMS]; }; #define MAX_RTG_BOARDS 4 struct rtgboardconfig { int rtg_index; int rtgmem_type; uae_u32 rtgmem_size; int device_order; int monitor_id; bool autoswitch; }; struct boardloadfile { uae_u32 loadoffset; uae_u32 fileoffset, filesize; TCHAR loadfile[MAX_DPATH]; }; #define MAX_ROM_BOARDS 4 struct romboard { uae_u32 size; uae_u32 start_address; uae_u32 end_address; struct boardloadfile lf; }; #define MAX_RAM_BOARDS 4 struct ramboard { uae_u32 size; uae_u16 manufacturer; uae_u8 product; uae_u8 autoconfig[16]; bool autoconfig_inuse; bool manual_config; bool no_reset_unmap; int device_order; uae_u32 start_address; uae_u32 end_address; uae_u32 write_address; bool readonly; bool nodma; bool force16bit; bool chipramtiming; int fault; struct boardloadfile lf; }; struct expansion_params { int device_order; }; #define Z3MAPPING_AUTO 0 #define Z3MAPPING_UAE 1 #define Z3MAPPING_REAL 2 #define GFX_SIZE_EXTRA_NUM 6 struct monconfig { struct wh gfx_size_win; struct wh gfx_size_fs; struct wh gfx_size; struct wh gfx_size_win_xtra[GFX_SIZE_EXTRA_NUM]; struct wh gfx_size_fs_xtra[GFX_SIZE_EXTRA_NUM]; }; #endif /* !WINUAE_FOR_HATARI */ struct uae_prefs { #ifndef WINUAE_FOR_HATARI struct strlist *all_lines; TCHAR description[256]; TCHAR category[256]; TCHAR tags[256]; TCHAR info[256]; int config_version; TCHAR config_hardware_path[MAX_DPATH]; TCHAR config_host_path[MAX_DPATH]; TCHAR config_all_path[MAX_DPATH]; TCHAR config_path[MAX_DPATH]; TCHAR config_window_title[256]; int got_fs2_hdf2; bool illegal_mem; bool debug_mem; bool use_serial; bool serial_demand; bool serial_hwctsrts; bool serial_rtsctsdtrdtecd; bool serial_ri; bool serial_direct; int serial_stopbits; int serial_crlf; bool parallel_demand; int parallel_matrix_emulation; bool parallel_postscript_emulation; bool parallel_postscript_detection; int parallel_autoflush_time; TCHAR ghostscript_parameters[256]; bool use_gfxlib; bool socket_emu; bool start_debugger; int debugging_features; TCHAR debugging_options[MAX_DPATH]; bool start_gui; KbdLang keyboard_lang; int produce_sound; int sound_stereo; int sound_stereo_separation; int sound_mixed_stereo_delay; int sound_freq; int sound_maxbsiz; int sound_interpol; int sound_filter; int sound_filter_type; int sound_volume_master; int sound_volume_paula; int sound_volume_cd; int sound_volume_board; int sound_volume_midi; int sound_volume_genlock; bool sound_stereo_swap_paula; bool sound_stereo_swap_ahi; bool sound_auto; bool sound_cdaudio; bool sound_volcnt; int sampler_freq; int sampler_buffer; bool sampler_stereo; #endif int comptrustbyte; int comptrustword; int comptrustlong; int comptrustnaddr; bool compnf; bool compfpu; bool comp_hardflush; bool comp_constjump; bool comp_catchfault; int cachesize; bool cachesize_inhibit; TCHAR jitblacklist[MAX_DPATH]; bool fpu_strict; int fpu_mode; #ifndef WINUAE_FOR_HATARI struct monconfig gfx_monitor[MAX_AMIGADISPLAYS]; int gfx_framerate, gfx_autoframerate; bool gfx_autoresolution_vga; int gfx_autoresolution; int gfx_autoresolution_delay; int gfx_autoresolution_minv, gfx_autoresolution_minh; bool gfx_scandoubler; struct apmode gfx_apmode[2]; int gfx_resolution; int gfx_vresolution; int gfx_lores_mode; int gfx_pscanlines, gfx_iscanlines; int gfx_xcenter, gfx_ycenter; int gfx_xcenter_pos, gfx_ycenter_pos; int gfx_xcenter_size, gfx_ycenter_size; int gfx_max_horizontal, gfx_max_vertical; int gfx_saturation, gfx_luminance, gfx_contrast, gfx_gamma, gfx_gamma_ch[3]; bool gfx_blackerthanblack; int gfx_threebitcolors; int gfx_api; int gfx_api_options; int color_mode; int gfx_extrawidth; int gfx_extraheight; bool lightboost_strobo; int lightboost_strobo_ratio; bool gfx_grayscale; bool lightpen_crosshair; int lightpen_offset[2]; int gfx_display_sections; int gfx_variable_sync; bool gfx_windowed_resize; int gfx_overscanmode; int gfx_monitorblankdelay; int gfx_rotation; int gfx_ntscpixels; uae_u32 gfx_bordercolor; struct gfx_filterdata gf[3]; float rtg_horiz_zoom_mult; float rtg_vert_zoom_mult; bool immediate_blits; int waiting_blits; float blitter_speed_throttle; unsigned int chipset_mask; bool chipset_hr; bool keyboard_connected; #endif bool ntscmode; #ifndef WINUAE_FOR_HATARI bool genlock; int genlock_image; int genlock_mix; int genlock_scale; int genlock_aspect; int genlock_effects; int genlock_offset_x, genlock_offset_y; uae_u64 ecs_genlock_features_colorkey_mask[4]; uae_u8 ecs_genlock_features_plane_mask; bool genlock_alpha; TCHAR genlock_image_file[MAX_DPATH]; TCHAR genlock_video_file[MAX_DPATH]; int monitoremu; int monitoremu_mon; float chipset_refreshrate; struct chipset_refresh cr[MAX_CHIPSET_REFRESH + 2]; int cr_selected; int collision_level; int leds_on_screen; int leds_on_screen_mask[2]; int leds_on_screen_multiplier[2]; int power_led_dim; struct wh osd_pos; int keyboard_leds[3]; bool keyboard_leds_in_use; int scsi; bool sana2; bool uaeserial; int catweasel; #endif int cpu_idle; int ppc_cpu_idle; bool cpu_cycle_exact; int cpu_clock_multiplier; int cpu_frequency; bool blitter_cycle_exact; bool cpu_memory_cycle_exact; #ifndef WINUAE_FOR_HATARI int floppy_speed; int floppy_write_length; int floppy_random_bits_min; int floppy_random_bits_max; int floppy_auto_ext2; int cd_speed; bool tod_hack; uae_u32 maprom; int boot_rom; bool rom_readwrite; int turbo_emulation; int turbo_emulation_limit; bool turbo_boot; int turbo_boot_delay; bool headless; int filesys_limit; int filesys_max_name; int filesys_max_file_size; bool filesys_inject_icons; TCHAR filesys_inject_icons_tool[MAX_DPATH]; TCHAR filesys_inject_icons_project[MAX_DPATH]; TCHAR filesys_inject_icons_drawer[MAX_DPATH]; int uaescsidevmode; #endif bool reset_delay; bool crash_auto_reset; int monitorblankdelay; #ifndef WINUAE_FOR_HATARI int cs_compatible; int cs_ciaatod; int cs_rtc; int cs_rtc_adjust; int cs_rtc_adjust_mode; bool cs_ksmirror_e0; bool cs_ksmirror_a8; bool cs_ciaoverlay; bool cs_cd32cd; bool cs_cd32c2p; bool cs_cd32nvram; bool cs_cd32fmv; int cs_cd32nvram_size; bool cs_cdtvcd; bool cs_cdtvram; int cs_ide; bool cs_pcmcia; bool cs_a1000ram; int cs_fatgaryrev; int cs_ramseyrev; int cs_agnusrev; int cs_deniserev; int cs_mbdmac; bool cs_cdtvcr; bool cs_df0idhw; bool cs_resetwarning; bool cs_denisenoehb; bool cs_dipagnus; bool cs_agnusbltbusybug; bool cs_ciatodbug; bool cs_z3autoconfig; bool cs_1mchipjumper; bool cs_cia6526; bool cs_bytecustomwritebug; bool cs_color_burst; bool cs_romisslow; bool cs_toshibagary; bool cs_bkpthang; int cs_unmapped_space; int cs_hacks; int cs_ciatype[2]; int cs_kbhandshake; int cs_hvcsync; int cs_eclockphase; int cs_eclocksync; bool cs_memorypatternfill; bool cs_ipldelay; bool cs_floppydatapullup; uae_u32 seed; struct boardromconfig expansionboard[MAX_EXPANSION_BOARDS]; TCHAR romfile[MAX_DPATH]; TCHAR romident[256]; TCHAR romextfile[MAX_DPATH]; uae_u32 romextfile2addr; TCHAR romextfile2[MAX_DPATH]; TCHAR romextident[256]; TCHAR flashfile[MAX_DPATH]; TCHAR rtcfile[MAX_DPATH]; TCHAR cartfile[MAX_DPATH]; TCHAR cartident[256]; int cart_internal; TCHAR pci_devices[256]; TCHAR prtname[256]; TCHAR sername[256]; TCHAR a2065name[MAX_DPATH]; TCHAR ne2000pciname[MAX_DPATH]; TCHAR ne2000pcmcianame[MAX_DPATH]; TCHAR picassoivromfile[MAX_DPATH]; struct cdslot cdslots[MAX_TOTAL_SCSI_DEVICES]; TCHAR quitstatefile[MAX_DPATH]; TCHAR statefile[MAX_DPATH]; TCHAR statefile_path[MAX_DPATH]; TCHAR inprecfile[MAX_DPATH]; TCHAR trainerfile[MAX_DPATH]; bool inprec_autoplay; bool refresh_indicator; struct multipath path_floppy; struct multipath path_hardfile; struct multipath path_rom; struct multipath path_cd; #endif int m68k_speed; float m68k_speed_throttle; float x86_speed_throttle; int cpu_model; int mmu_model; bool mmu_ec; int cpu060_revision; int fpu_model; int fpu_revision; int ppc_mode; TCHAR ppc_model[32]; bool cpu_compatible; bool cpu_thread; bool int_no_unimplemented; bool fpu_no_unimplemented; bool address_space_24; bool cpu_data_cache; bool picasso96_nocustom; int picasso96_modeflags; int cpu_model_fallback; #ifndef WINUAE_FOR_HATARI uae_u32 z3autoconfig_start; struct ramboard z3fastmem[MAX_RAM_BOARDS]; struct ramboard fastmem[MAX_RAM_BOARDS]; struct romboard romboards[MAX_ROM_BOARDS]; struct ramboard z3chipmem; struct ramboard chipmem; struct ramboard bogomem; struct ramboard mbresmem_low; struct ramboard mbresmem_high; struct ramboard mem25bit; uae_u32 debugmem_start; uae_u32 debugmem_size; int cpuboard_type; int cpuboard_subtype; int cpuboard_settings; struct ramboard cpuboardmem1; struct ramboard cpuboardmem2; int ppc_implementation; bool rtg_hardwareinterrupt; bool rtg_hardwaresprite; bool rtg_more_compatible; bool rtg_multithread; bool rtg_overlay; bool rtg_vgascreensplit; bool rtg_paletteswitch; bool rtg_dacswitch; struct rtgboardconfig rtgboards[MAX_RTG_BOARDS]; uae_u32 custom_memory_addrs[MAX_CUSTOM_MEMORY_ADDRS]; uae_u32 custom_memory_sizes[MAX_CUSTOM_MEMORY_ADDRS]; uae_u32 custom_memory_mask[MAX_CUSTOM_MEMORY_ADDRS]; int uaeboard; bool uaeboard_nodiag; int uaeboard_order; bool kickshifter; bool scsidevicedisable; bool filesys_no_uaefsdb; bool filesys_custom_uaefsdb; bool mmkeyboard; int uae_hide; bool clipboard_sharing; bool native_code; bool uae_hide_autoconfig; int z3_mapping_mode; bool autoconfig_custom_sort; bool obs_sound_toccata; bool obs_sound_toccata_mixer; bool obs_sound_es1370; bool obs_sound_fm801; bool cputester; int mountitems; struct uaedev_config_data mountconfig[MOUNT_CONFIG_SIZE]; int nr_floppies; struct floppyslot floppyslots[4]; bool floppy_read_only; bool harddrive_read_only; TCHAR dfxlist[MAX_SPARE_DRIVES][MAX_DPATH]; int dfxclickvolume_disk[4]; int dfxclickvolume_empty[4]; int dfxclickchannelmask; TCHAR luafiles[MAX_LUA_STATES][MAX_DPATH]; /* Target specific options */ bool win32_logfile; bool win32_notaskbarbutton; bool win32_nonotificationicon; bool win32_gui_alwaysontop; bool win32_main_alwaysontop; bool win32_powersavedisabled; bool win32_minimize_inactive; bool win32_capture_always; int win32_statusbar; bool win32_start_minimized; bool win32_start_uncaptured; int win32_active_capture_priority; bool win32_active_nocapture_pause; bool win32_active_nocapture_nosound; int win32_active_input; int win32_inactive_priority; bool win32_inactive_pause; bool win32_inactive_nosound; int win32_inactive_input; int win32_iconified_priority; bool win32_iconified_pause; bool win32_iconified_nosound; int win32_iconified_input; bool win32_rtgmatchdepth; bool win32_rtgallowscaling; int win32_rtgscaleaspectratio; int win32_rtgvblankrate; bool win32_borderless; bool win32_ctrl_F11_is_quit; bool win32_automount_removable; bool win32_automount_drives; bool win32_automount_cddrives; bool win32_automount_netdrives; bool win32_automount_removabledrives; int win32_midioutdev; int win32_midiindev; bool win32_midirouter; int win32_uaescsimode; int win32_soundcard; int win32_samplersoundcard; bool win32_norecyclebin; int win32_guikey; int win32_kbledmode; bool win32_blankmonitors; TCHAR win32_commandpathstart[MAX_DPATH]; TCHAR win32_commandpathend[MAX_DPATH]; TCHAR win32_parjoyport0[MAX_DPATH]; TCHAR win32_parjoyport1[MAX_DPATH]; TCHAR win32_guipage[32]; TCHAR win32_guiactivepage[32]; bool win32_filesystem_mangle_reserved_names; bool win32_shutdown_notification; bool win32_warn_exit; win32_gui_control; win32_videograb_balance; bool right_control_is_right_win_key; #ifdef WITH_SLIRP struct slirp_redir slirp_redirs[MAX_SLIRP_REDIRS]; #endif int statecapturerate, statecapturebuffersize; int aviout_width, aviout_height, aviout_xoffset, aviout_yoffset; int screenshot_width, screenshot_height, screenshot_xoffset, screenshot_yoffset; int screenshot_min_width, screenshot_min_height; int screenshot_max_width, screenshot_max_height; int screenshot_output_width, screenshot_output_height; int screenshot_xmult, screenshot_ymult; /* input */ struct jport jports[MAX_JPORTS]; struct jport_custom jports_custom[MAX_JPORTS_CUSTOM]; int input_selected_setting; int input_joymouse_multiplier; int input_joymouse_deadzone; int input_joystick_deadzone; int input_joymouse_speed; int input_analog_joystick_mult; int input_analog_joystick_offset; int input_autofire_linecnt; int input_mouse_speed; int input_tablet; bool tablet_library; int input_mouse_untrap; int input_magic_mouse_cursor; int input_keyboard_type; bool input_autoswitch; bool input_autoswitchleftright; bool input_advancedmultiinput; struct uae_input_device joystick_settings[MAX_INPUT_SETTINGS][MAX_INPUT_DEVICES]; struct uae_input_device mouse_settings[MAX_INPUT_SETTINGS][MAX_INPUT_DEVICES]; struct uae_input_device keyboard_settings[MAX_INPUT_SETTINGS][MAX_INPUT_DEVICES]; struct uae_input_device internalevent_settings[MAX_INPUT_SETTINGS][INTERNALEVENT_COUNT]; TCHAR input_config_name[GAMEPORT_INPUT_SETTINGS][256]; int dongle; int input_contact_bounce; int input_device_match_mask; #endif }; #ifndef WINUAE_FOR_HATARI extern int config_changed; extern void config_check_vsync (void); extern void set_config_changed (void); /* Contains the filename of .uaerc */ extern TCHAR optionsfile[]; extern void cfgfile_write (struct zfile *, const TCHAR *option, const TCHAR *format,...); extern void cfgfile_dwrite (struct zfile *, const TCHAR *option, const TCHAR *format,...); extern void cfgfile_target_write (struct zfile *, const TCHAR *option, const TCHAR *format,...); extern void cfgfile_target_dwrite (struct zfile *, const TCHAR *option, const TCHAR *format,...); extern void cfgfile_write_bool (struct zfile *f, const TCHAR *option, bool b); extern void cfgfile_dwrite_bool (struct zfile *f,const TCHAR *option, bool b); extern void cfgfile_target_write_bool (struct zfile *f, const TCHAR *option, bool b); extern void cfgfile_target_dwrite_bool (struct zfile *f, const TCHAR *option, bool b); extern void cfgfile_write_str(struct zfile *f, const TCHAR *option, const TCHAR *value); extern void cfgfile_write_str_escape(struct zfile *f, const TCHAR *option, const TCHAR *value); extern void cfgfile_dwrite_str(struct zfile *f, const TCHAR *option, const TCHAR *value); extern void cfgfile_target_write_str(struct zfile *f, const TCHAR *option, const TCHAR *value); extern void cfgfile_target_dwrite_str(struct zfile *f, const TCHAR *option, const TCHAR *value); extern void cfgfile_target_dwrite_str_escape(struct zfile *f, const TCHAR *option, const TCHAR *value); extern void cfgfile_backup (const TCHAR *path); extern struct uaedev_config_data *add_filesys_config (struct uae_prefs *p, int index, struct uaedev_config_info*); extern bool get_hd_geometry (struct uaedev_config_info *); extern void uci_set_defaults (struct uaedev_config_info *uci, bool rdb); extern void error_log (const TCHAR*, ...); extern TCHAR *get_error_log (void); extern bool is_error_log (void); extern void default_prefs (struct uae_prefs *, bool, int); extern void discard_prefs (struct uae_prefs *, int); extern void copy_prefs(struct uae_prefs *src, struct uae_prefs *dst); extern void copy_inputdevice_prefs(struct uae_prefs *src, struct uae_prefs *dst); int parse_cmdline_option (struct uae_prefs *, TCHAR, const TCHAR*); extern int cfgfile_yesno(const TCHAR *option, const TCHAR *value, const TCHAR *name, bool *location); extern int cfgfile_intval(const TCHAR *option, const TCHAR *value, const TCHAR *name, int *location, int scale); extern int cfgfile_strval(const TCHAR *option, const TCHAR *value, const TCHAR *name, int *location, const TCHAR *table[], int more); extern int cfgfile_string(const TCHAR *option, const TCHAR *value, const TCHAR *name, TCHAR *location, int maxsz); extern int cfgfile_string_escape(const TCHAR *option, const TCHAR *value, const TCHAR *name, TCHAR *location, int maxsz); extern bool cfgfile_option_find(const TCHAR *s, const TCHAR *option); extern TCHAR *cfgfile_option_get(const TCHAR *s, const TCHAR *option); extern TCHAR *cfgfile_subst_path(const TCHAR *path, const TCHAR *subst, const TCHAR *file); extern TCHAR *target_expand_environment (const TCHAR *path, TCHAR *out, int maxlen); extern int target_parse_option (struct uae_prefs *, const TCHAR *option, const TCHAR *value, int type); extern void target_save_options (struct zfile*, struct uae_prefs *); extern void target_default_options (struct uae_prefs *, int type); extern void target_fixup_options (struct uae_prefs *); extern int target_cfgfile_load (struct uae_prefs *, const TCHAR *filename, int type, int isdefault); extern void cfgfile_save_options (struct zfile *f, struct uae_prefs *p, int type); extern int target_get_display (const TCHAR*); extern const TCHAR *target_get_display_name (int, bool); extern void target_multipath_modified(struct uae_prefs *); extern void cfgfile_resolve_path_out_load(const TCHAR *path, TCHAR *out, int size, int type); extern void cfgfile_resolve_path_load(TCHAR *path, int size, int type); extern void cfgfile_resolve_path_out_save(const TCHAR *path, TCHAR *out, int size, int type); extern void cfgfile_resolve_path_save(TCHAR *path, int size, int type); extern struct uae_prefs *cfgfile_open(const TCHAR *filename, int *type); extern void cfgfile_close(struct uae_prefs *p); extern int cfgfile_load (struct uae_prefs *p, const TCHAR *filename, int *type, int ignorelink, int userconfig); extern int cfgfile_save (struct uae_prefs *p, const TCHAR *filename, int); extern void cfgfile_parse_line (struct uae_prefs *p, TCHAR *, int); extern void cfgfile_parse_lines (struct uae_prefs *p, const TCHAR *, int); extern int cfgfile_parse_option (struct uae_prefs *p, const TCHAR *option, TCHAR *value, int); extern int cfgfile_get_description (struct uae_prefs *p, const TCHAR *filename, TCHAR *description, TCHAR *category, TCHAR *tags, TCHAR *hostlink, TCHAR *hardwarelink, int *type); extern void cfgfile_show_usage (void); extern int cfgfile_searchconfig(const TCHAR *in, int index, TCHAR *out, int outsize); extern uae_u32 cfgfile_uaelib(TrapContext *ctx, int mode, uae_u32 name, uae_u32 dst, uae_u32 maxlen); extern uae_u32 cfgfile_uaelib_modify(TrapContext *ctx, uae_u32 mode, uae_u32 parms, uae_u32 size, uae_u32 out, uae_u32 outsize); extern uae_u32 cfgfile_modify (uae_u32 index, const TCHAR *parms, uae_u32 size, TCHAR *out, uae_u32 outsize); extern void cfgfile_addcfgparam (TCHAR *); extern int built_in_prefs (struct uae_prefs *p, int model, int config, int compa, int romcheck); extern int built_in_chipset_prefs (struct uae_prefs *p); extern int cmdlineparser (const TCHAR *s, TCHAR *outp[], int max); extern void fixup_prefs_dimensions (struct uae_prefs *prefs); extern void fixup_prefs (struct uae_prefs *prefs, bool userconfig); extern void fixup_cpu (struct uae_prefs *prefs); extern void cfgfile_compatibility_romtype(struct uae_prefs *p); extern void cfgfile_compatibility_rtg(struct uae_prefs *p); extern bool cfgfile_detect_art(struct uae_prefs *p, TCHAR *path); extern const TCHAR *cfgfile_getconfigdata(size_t *len); extern bool cfgfile_createconfigstore(struct uae_prefs *p); extern void cfgfile_get_shader_config(struct uae_prefs *p, int rtg); extern void check_prefs_changed_custom (void); extern void check_prefs_changed_cpu (void); extern void check_prefs_changed_audio (void); extern void check_prefs_changed_cd (void); extern int check_prefs_changed_gfx (void); extern struct uae_prefs currprefs, changed_prefs; extern int machdep_init (void); extern void machdep_free (void); #else /* !WINUAE_FOR_HATARI */ extern struct uae_prefs currprefs, changed_prefs; extern void fixup_cpu (struct uae_prefs *prefs); extern void check_prefs_changed_cpu (void); extern void error_log (const TCHAR*, ...); #endif #endif /* UAE_OPTIONS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/readcpu.c000066400000000000000000000635771504763705000237300ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * Read 68000 CPU specs from file "table68k" * * Copyright 1995,1996 Bernd Schmidt */ #include "sysconfig.h" #include "sysdeps.h" #include #include #include "readcpu.h" int nr_cpuop_funcs; struct mnemolookup lookuptab[] = { { i_ILLG, _T("ILLEGAL"), NULL, 0 }, { i_OR, _T("OR"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_CHK, _T("CHK"), NULL, 0 }, { i_CHK2, _T("CHK2"), NULL, 0 }, { i_AND, _T("AND"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_EOR, _T("EOR"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_ORSR, _T("ORSR"), _T("OR"), 0 }, { i_ANDSR, _T("ANDSR"), _T("AND"), 0 }, { i_EORSR, _T("EORSR"), _T("EOR"), 0 }, { i_SUB, _T("SUB"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_SUBA, _T("SUBA"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_SUBX, _T("SUBX"), NULL, 0 | MNEMOFLAG_LOOPMODE }, { i_SBCD, _T("SBCD"), NULL, 0 | MNEMOFLAG_LOOPMODE }, { i_ADD, _T("ADD"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_ADDA, _T("ADDA"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_ADDX, _T("ADDX"), NULL, 0 | MNEMOFLAG_LOOPMODE }, { i_ABCD, _T("ABCD"), NULL, 0 | MNEMOFLAG_LOOPMODE }, { i_NEG, _T("NEG"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_NEGX, _T("NEGX"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_NBCD, _T("NBCD"), NULL, 0 | MNEMOFLAG_LOOPMODE }, { i_CLR, _T("CLR"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_NOT, _T("NOT"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_TST, _T("TST"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_BTST, _T("BTST"), NULL, 1 }, { i_BCHG, _T("BCHG"), NULL, 1 }, { i_BCLR, _T("BCLR"), NULL, 1 }, { i_BSET, _T("BSET"), NULL, 1 }, { i_CMP, _T("CMP"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_CMPM, _T("CMPM"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_CMPA, _T("CMPA"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_MVPRM, _T("MVPRM"), _T("MOVEP"), 0 }, { i_MVPMR, _T("MVPMR"), _T("MOVEP"), 0 }, { i_MOVE, _T("MOVE"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_MOVEA, _T("MOVEA"), NULL, 1 | MNEMOFLAG_LOOPMODE }, { i_MVSR2, _T("MVSR2"), _T("MOVE"), 0 }, { i_MV2SR, _T("MV2SR"), _T("MOVE"), 0 }, { i_SWAP, _T("SWAP"), NULL, 0 }, { i_EXG, _T("EXG"), NULL, 0 }, { i_EXT, _T("EXT"), NULL, 0 }, { i_MVMEL, _T("MVMEL"), _T("MOVEM"), 0 }, { i_MVMLE, _T("MVMLE"), _T("MOVEM"), 0 }, { i_TRAP, _T("TRAP"), NULL, 0 }, { i_MVR2USP, _T("MVR2USP"), _T("MOVE"), 0 }, { i_MVUSP2R, _T("MVUSP2R"), _T("MOVE"), 0 }, { i_NOP, _T("NOP"), NULL, 0 }, { i_RESET, _T("RESET"), NULL, 0 }, { i_RTE, _T("RTE"), NULL, 0 }, { i_RTD, _T("RTD"), NULL, 0 }, { i_LINK, _T("LINK"), NULL, 0 }, { i_UNLK, _T("UNLK"), NULL, 0 }, { i_RTS, _T("RTS"), NULL, 0 }, { i_STOP, _T("STOP"), NULL, 0 }, { i_TRAPV, _T("TRAPV"), NULL, 0 }, { i_RTR, _T("RTR"), NULL, 0 }, { i_JSR, _T("JSR"), NULL, 0 }, { i_JMP, _T("JMP"), NULL, 0 }, { i_BSR, _T("BSR"), NULL, 0 }, { i_Bcc, _T("Bcc"), NULL, 0 }, { i_LEA, _T("LEA"), NULL, 0 }, { i_PEA, _T("PEA"), NULL, 0 }, { i_DBcc, _T("DBcc"), NULL, 0 }, { i_Scc, _T("Scc"), NULL, 1 }, { i_DIVU, _T("DIVU"), NULL, 1 }, { i_DIVS, _T("DIVS"), NULL, 1 }, { i_MULU, _T("MULU"), NULL, 1 }, { i_MULS, _T("MULS"), NULL, 1 }, { i_ASR, _T("ASR"), NULL, 0 }, { i_ASL, _T("ASL"), NULL, 0 }, { i_LSR, _T("LSR"), NULL, 0 }, { i_LSL, _T("LSL"), NULL, 0 }, { i_ROL, _T("ROL"), NULL, 0 }, { i_ROR, _T("ROR"), NULL, 0 }, { i_ROXL, _T("ROXL"), NULL, 1 }, { i_ROXR, _T("ROXR"), NULL, 1 }, { i_ASRW, _T("ASRW"), _T("ASR"), 1 | MNEMOFLAG_LOOPMODE }, { i_ASLW, _T("ASLW"), _T("ASL"), 1 | MNEMOFLAG_LOOPMODE }, { i_LSRW, _T("LSRW"), _T("LSR"), 1 | MNEMOFLAG_LOOPMODE }, { i_LSLW, _T("LSLW"), _T("LSL"), 1 | MNEMOFLAG_LOOPMODE }, { i_ROLW, _T("ROLW"), _T("ROL"), 1 | MNEMOFLAG_LOOPMODE }, { i_RORW, _T("RORW"), _T("ROR"), 1 | MNEMOFLAG_LOOPMODE }, { i_ROXLW, _T("ROXLW"), _T("ROXL"), 1 | MNEMOFLAG_LOOPMODE }, { i_ROXRW, _T("ROXRW"), _T("ROXR"), 1 | MNEMOFLAG_LOOPMODE }, { i_MOVE2C, _T("MOVE2C"), _T("MOVEC"), 0 }, { i_MOVEC2, _T("MOVEC2"), _T("MOVEC"), 0 }, { i_CAS, _T("CAS"), NULL, 1 }, { i_CAS2, _T("CAS2"), NULL, 1 }, { i_MULL, _T("MULL"), NULL, 0 }, { i_DIVL, _T("DIVL"), NULL, 0 }, { i_BFTST, _T("BFTST"), NULL, 0 }, { i_BFEXTU, _T("BFEXTU"), NULL, 0 }, { i_BFCHG, _T("BFCHG"), NULL, 0 }, { i_BFEXTS, _T("BFEXTS"), NULL, 0 }, { i_BFCLR, _T("BFCLR"), NULL, 0 }, { i_BFFFO, _T("BFFFO"), NULL, 0 }, { i_BFSET, _T("BFSET"), NULL, 0 }, { i_BFINS, _T("BFINS"), NULL, 0 }, { i_PACK, _T("PACK"), NULL, 0 }, { i_UNPK, _T("UNPK"), NULL, 0 }, { i_TAS, _T("TAS"), NULL, 1 }, { i_BKPT, _T("BKPT"), NULL, 0 }, { i_CALLM, _T("CALLM"), NULL, 0 }, { i_RTM, _T("RTM"), NULL, 0 }, { i_TRAPcc, _T("TRAPcc"), NULL, 0 }, { i_MOVES, _T("MOVES"), NULL, 1 }, { i_FPP, _T("FPP"), NULL, 0 }, { i_FDBcc, _T("FDBcc"), NULL, 0 }, { i_FScc, _T("FScc"), NULL, 0 }, { i_FTRAPcc, _T("FTRAPcc"), NULL, 0 }, { i_FBcc, _T("FBcc"), NULL, 0 }, { i_FSAVE, _T("FSAVE"), NULL, 0 }, { i_FRESTORE, _T("FRESTORE"), NULL, 0 }, { i_CINVL, _T("CINVL"), NULL, 0 }, { i_CINVP, _T("CINVP"), NULL, 0 }, { i_CINVA, _T("CINVA"), NULL, 0 }, { i_CPUSHL, _T("CPUSHL"), NULL, 0 }, { i_CPUSHP, _T("CPUSHP"), NULL, 0 }, { i_CPUSHA, _T("CPUSHA"), NULL, 0 }, { i_MOVE16, _T("MOVE16"), NULL, 0 }, { i_MMUOP030, _T("MMUOP030"), NULL, 0 }, { i_PFLUSHN, _T("PFLUSHN"), NULL, 0 }, { i_PFLUSH, _T("PFLUSH"), NULL, 0 }, { i_PFLUSHAN, _T("PFLUSHAN"), NULL, 0 }, { i_PFLUSHA, _T("PFLUSHA"), NULL, 0 }, { i_PLPAR, _T("PLPAR"), NULL, 0 }, { i_PLPAW, _T("PLPAW"), NULL, 0 }, { i_PTESTR, _T("PTESTR"), NULL, 0 }, { i_PTESTW, _T("PTESTW"), NULL, 0 }, { i_LPSTOP, _T("LPSTOP"), NULL, 0 }, { i_HALT, _T("HALT"), NULL, 0 }, { i_PULSE, _T("PULSE"), NULL, 0 }, { i_ILLG, _T(""), NULL, 0 }, }; struct instr *table68k; static amodes mode_from_str (const TCHAR *str) { if (_tcsncmp (str, _T("Dreg"), 4) == 0) return Dreg; if (_tcsncmp (str, _T("Areg"), 4) == 0) return Areg; if (_tcsncmp (str, _T("Aind"), 4) == 0) return Aind; if (_tcsncmp (str, _T("Apdi"), 4) == 0) return Apdi; if (_tcsncmp (str, _T("Aipi"), 4) == 0) return Aipi; if (_tcsncmp (str, _T("Ad16"), 4) == 0) return Ad16; if (_tcsncmp (str, _T("Ad8r"), 4) == 0) return Ad8r; if (_tcsncmp (str, _T("absw"), 4) == 0) return absw; if (_tcsncmp (str, _T("absl"), 4) == 0) return absl; if (_tcsncmp (str, _T("PC16"), 4) == 0) return PC16; if (_tcsncmp (str, _T("PC8r"), 4) == 0) return PC8r; if (_tcsncmp (str, _T("Immd"), 4) == 0) return imm; abort (); return Dreg; } STATIC_INLINE amodes mode_from_mr (int mode, int reg) { switch (mode) { case 0: return Dreg; case 1: return Areg; case 2: return Aind; case 3: return Aipi; case 4: return Apdi; case 5: return Ad16; case 6: return Ad8r; case 7: switch (reg) { case 0: return absw; case 1: return absl; case 2: return PC16; case 3: return PC8r; case 4: return imm; case 5: case 6: case 7: return am_illg; } } abort (); return Dreg; } static void build_insn (int insn) { int find = -1; int variants; struct instr_def id; const TCHAR *opcstr; int i, n; int flaglive = 0, flagdead = 0; int cflow = 0; id = defs68k[insn]; // Control flow information cflow = id.cflow; // Mask of flags set/used unsigned char flags_set = 0, flags_used = 0; for (i = 0, n = 4; i < 5; i++, n--) { switch (id.flaginfo[i].flagset) { case fa_unset: case fa_isjmp: break; default: flags_set |= (1 << n); } switch (id.flaginfo[i].flaguse) { case fu_unused: case fu_isjmp: break; default: flags_used |= (1 << n); } } for (i = 0; i < 5; i++) { switch (id.flaginfo[i].flagset) { case fa_unset: break; case fa_isjmp: break; case fa_zero: flagdead |= 1 << i; break; case fa_one: flagdead |= 1 << i; break; case fa_dontcare: flagdead |= 1 << i; break; case fa_unknown: flagdead = -1; goto out1; case fa_set: flagdead |= 1 << i; break; } } out1: for (i = 0; i < 5; i++) { switch (id.flaginfo[i].flaguse) { case fu_unused: break; case fu_isjmp: flaglive |= 1 << i; break; case fu_maybecc: flaglive |= 1 << i; break; case fu_unknown: flaglive = -1; goto out2; case fu_used: flaglive |= 1 << i; break; } } out2: opcstr = id.opcstr; for (variants = 0; variants < (1 << id.n_variable); variants++) { int bitcnt[lastbit]; int bitval[lastbit]; int bitpos[lastbit]; int i; uae_u16 opc = id.bits; uae_u16 msk, vmsk; int pos = 0; int mnp = 0; int bitno = 0; int unsized = 1; TCHAR mnemonic[10]; int mnemo; wordsizes sz = sz_long; int srcgather = 0, dstgather = 0; int usesrc = 0, usedst = 0; int srctype = 0; int srcpos = -1, dstpos = -1; int usecc = 0; amodes srcmode = am_unknown, destmode = am_unknown; int srcreg = -1, destreg = -1; for (i = 0; i < lastbit; i++) { bitcnt[i] = bitval[i] = 0; } vmsk = 1 << id.n_variable; for (i = 0, msk = 0x8000; i < 16; i++, msk >>= 1) { if (!(msk & id.mask)) { int currbit = id.bitpos[bitno++]; int bit_set; vmsk >>= 1; bit_set = variants & vmsk ? 1 : 0; if (bit_set) opc |= msk; bitpos[currbit] = 15 - i; bitcnt[currbit]++; bitval[currbit] <<= 1; bitval[currbit] |= bit_set; if (currbit == bitC || currbit == bitc) usecc = 1; } } if (bitval[bitj] == 0) bitval[bitj] = 8; /* first check whether this one does not match after all */ if (bitval[bitz] == 3 || bitval[bitC] == 1) continue; if (bitcnt[bitI] && (bitval[bitI] == 0x00 || bitval[bitI] == 0xff)) continue; /* bitI and bitC get copied to biti and bitc */ if (bitcnt[bitI]) { bitval[biti] = bitval[bitI]; bitpos[biti] = bitpos[bitI]; } if (bitcnt[bitC]) bitval[bitc] = bitval[bitC]; pos = 0; while (opcstr[pos] && !_istspace(opcstr[pos])) { if (opcstr[pos] == '.') { pos++; unsized = 0; switch (opcstr[pos]) { case 'B': sz = sz_byte; break; case 'W': sz = sz_word; break; case 'L': sz = sz_long; break; case 'z': switch (bitval[bitz]) { case 0: sz = sz_byte; break; case 1: sz = sz_word; break; case 2: sz = sz_long; break; default: abort(); } break; default: abort(); } } else { mnemonic[mnp] = opcstr[pos]; if (mnemonic[mnp] == 'f') { find = -1; switch (bitval[bitf]) { case 0: mnemonic[mnp] = 'R'; break; case 1: mnemonic[mnp] = 'L'; break; default: abort(); } } mnp++; } pos++; } mnemonic[mnp] = 0; /* now, we have read the mnemonic and the size */ while (opcstr[pos] && _istspace(opcstr[pos])) pos++; /* A goto a day keeps the D******a away. */ if (opcstr[pos] == 0) goto endofline; /* parse the source address */ usesrc = 1; switch (opcstr[pos++]) { case 'D': srcmode = Dreg; switch (opcstr[pos++]) { case 'r': srcreg = bitval[bitr]; srcgather = 1; srcpos = bitpos[bitr]; break; case 'R': srcreg = bitval[bitR]; srcgather = 1; srcpos = bitpos[bitR]; break; default: abort(); } break; case 'A': srcmode = Areg; switch (opcstr[pos++]) { case 'l': srcmode = absl; break; case 'r': srcreg = bitval[bitr]; srcgather = 1; srcpos = bitpos[bitr]; break; case 'R': srcreg = bitval[bitR]; srcgather = 1; srcpos = bitpos[bitR]; break; default: abort(); } switch (opcstr[pos]) { case 'p': srcmode = Apdi; pos++; break; case 'P': srcmode = Aipi; pos++; break; case 'a': srcmode = Aind; pos++; break; } break; case '#': switch (opcstr[pos++]) { case 'z': srcmode = imm; break; case '0': srcmode = imm0; break; case '1': srcmode = imm1; break; case '2': srcmode = imm2; break; case 'i': srcmode = immi; srcreg = (uae_s32)(uae_s8)bitval[biti]; if (CPU_EMU_SIZE < 4) { /* Used for branch instructions */ srctype = 1; srcgather = 1; srcpos = bitpos[biti]; } break; case 'j': srcmode = immi; srcreg = bitval[bitj]; if (CPU_EMU_SIZE < 3) { /* 1..8 for ADDQ/SUBQ and rotshi insns */ srcgather = 1; srctype = 3; srcpos = bitpos[bitj]; } break; case 'J': srcmode = immi; srcreg = bitval[bitJ]; if (CPU_EMU_SIZE < 5) { /* 0..15 */ srcgather = 1; srctype = 2; srcpos = bitpos[bitJ]; } break; case 'k': srcmode = immi; srcreg = bitval[bitk]; if (CPU_EMU_SIZE < 3) { srcgather = 1; srctype = 4; srcpos = bitpos[bitk]; } break; case 'K': srcmode = immi; srcreg = bitval[bitK]; if (CPU_EMU_SIZE < 5) { /* 0..15 */ srcgather = 1; srctype = 5; srcpos = bitpos[bitK]; } break; case 'E': srcmode = immi; srcreg = bitval[bitE]; if (CPU_EMU_SIZE < 5) { // gb-- what is CPU_EMU_SIZE used for ?? /* 1..255 */ srcgather = 1; srctype = 6; srcpos = bitpos[bitE]; } break; case 'p': srcmode = immi; srcreg = bitval[bitK]; if (CPU_EMU_SIZE < 5) { // gb-- what is CPU_EMU_SIZE used for ?? /* 0..3 */ srcgather = 1; srctype = 7; srcpos = bitpos[bitp]; } break; default: abort(); } break; case 'd': srcreg = bitval[bitD]; srcmode = mode_from_mr(bitval[bitd],bitval[bitD]); if (srcmode == am_illg) continue; if (CPU_EMU_SIZE < 2 && (srcmode == Areg || srcmode == Dreg || srcmode == Aind || srcmode == Ad16 || srcmode == Ad8r || srcmode == Aipi || srcmode == Apdi)) { srcgather = 1; srcpos = bitpos[bitD]; } if (opcstr[pos] == '[') { pos++; if (opcstr[pos] == '!') { /* exclusion */ do { pos++; if (mode_from_str(opcstr+pos) == srcmode) goto nomatch; pos += 4; } while (opcstr[pos] == ','); pos++; } else { if (opcstr[pos+4] == '-') { /* replacement */ if (mode_from_str(opcstr+pos) == srcmode) srcmode = mode_from_str(opcstr+pos+5); else goto nomatch; pos += 10; } else { /* normal */ while(mode_from_str(opcstr+pos) != srcmode) { pos += 4; if (opcstr[pos] == ']') goto nomatch; pos++; } while(opcstr[pos] != ']') pos++; pos++; break; } } } /* Some addressing modes are invalid as destination */ if (srcmode == imm || srcmode == PC16 || srcmode == PC8r) goto nomatch; break; case 's': srcreg = bitval[bitS]; srcmode = mode_from_mr(bitval[bits],bitval[bitS]); if (srcmode == am_illg) continue; if (CPU_EMU_SIZE < 2 && (srcmode == Areg || srcmode == Dreg || srcmode == Aind || srcmode == Ad16 || srcmode == Ad8r || srcmode == Aipi || srcmode == Apdi)) { srcgather = 1; srcpos = bitpos[bitS]; } if (opcstr[pos] == '[') { pos++; if (opcstr[pos] == '!') { /* exclusion */ do { pos++; if (mode_from_str(opcstr+pos) == srcmode) goto nomatch; pos += 4; } while (opcstr[pos] == ','); pos++; } else { if (opcstr[pos+4] == '-') { /* replacement */ if (mode_from_str(opcstr+pos) == srcmode) srcmode = mode_from_str(opcstr+pos+5); else goto nomatch; pos += 10; } else { /* normal */ while(mode_from_str(opcstr+pos) != srcmode) { pos += 4; if (opcstr[pos] == ']') goto nomatch; pos++; } while(opcstr[pos] != ']') pos++; pos++; } } } break; default: abort(); } /* safety check - might have changed */ if (srcmode != Areg && srcmode != Dreg && srcmode != Aind && srcmode != Ad16 && srcmode != Ad8r && srcmode != Aipi && srcmode != Apdi && srcmode != immi) { srcgather = 0; } if (srcmode == Areg && sz == sz_byte) goto nomatch; if (opcstr[pos] != ',') goto endofline; pos++; /* parse the destination address */ usedst = 1; switch (opcstr[pos++]) { case 'D': destmode = Dreg; switch (opcstr[pos++]) { case 'r': destreg = bitval[bitr]; dstgather = 1; dstpos = bitpos[bitr]; break; case 'R': destreg = bitval[bitR]; dstgather = 1; dstpos = bitpos[bitR]; break; default: abort(); } if (dstpos < 0 || dstpos >= 32) abort (); break; case 'A': destmode = Areg; switch (opcstr[pos++]) { case 'l': destmode = absl; break; case 'r': destreg = bitval[bitr]; dstgather = 1; dstpos = bitpos[bitr]; break; case 'R': destreg = bitval[bitR]; dstgather = 1; dstpos = bitpos[bitR]; break; case 'x': destreg = 0; dstgather = 0; dstpos = 0; break; default: abort(); } switch (opcstr[pos]) { case 'p': destmode = Apdi; pos++; break; case 'P': destmode = Aipi; pos++; break; } break; case '#': switch (opcstr[pos++]) { case 'z': destmode = imm; break; case '0': destmode = imm0; break; case '1': destmode = imm1; break; case '2': destmode = imm2; break; case 'i': destmode = immi; destreg = (uae_s32)(uae_s8)bitval[biti]; break; case 'j': destmode = immi; destreg = bitval[bitj]; break; case 'J': destmode = immi; destreg = bitval[bitJ]; break; case 'k': destmode = immi; destreg = bitval[bitk]; break; case 'K': destmode = immi; destreg = bitval[bitK]; break; default: abort(); } break; case 'd': destreg = bitval[bitD]; destmode = mode_from_mr(bitval[bitd],bitval[bitD]); if (destmode == am_illg) continue; if (CPU_EMU_SIZE < 1 && (destmode == Areg || destmode == Dreg || destmode == Aind || destmode == Ad16 || destmode == Ad8r || destmode == Aipi || destmode == Apdi)) { dstgather = 1; dstpos = bitpos[bitD]; } if (opcstr[pos] == '[') { pos++; if (opcstr[pos] == '!') { /* exclusion */ do { pos++; if (mode_from_str(opcstr+pos) == destmode) goto nomatch; pos += 4; } while (opcstr[pos] == ','); pos++; } else { if (opcstr[pos+4] == '-') { /* replacement */ if (mode_from_str(opcstr+pos) == destmode) destmode = mode_from_str(opcstr+pos+5); else goto nomatch; pos += 10; } else { /* normal */ while(mode_from_str(opcstr+pos) != destmode) { pos += 4; if (opcstr[pos] == ']') goto nomatch; pos++; } while(opcstr[pos] != ']') pos++; pos++; break; } } } /* Some addressing modes are invalid as destination */ if (destmode == imm || destmode == PC16 || destmode == PC8r) goto nomatch; break; case 's': destreg = bitval[bitS]; destmode = mode_from_mr(bitval[bits],bitval[bitS]); if (destmode == am_illg) continue; if (CPU_EMU_SIZE < 1 && (destmode == Areg || destmode == Dreg || destmode == Aind || destmode == Ad16 || destmode == Ad8r || destmode == Aipi || destmode == Apdi)) { dstgather = 1; dstpos = bitpos[bitS]; } if (opcstr[pos] == '[') { pos++; if (opcstr[pos] == '!') { /* exclusion */ do { pos++; if (mode_from_str(opcstr+pos) == destmode) goto nomatch; pos += 4; } while (opcstr[pos] == ','); pos++; } else { if (opcstr[pos+4] == '-') { /* replacement */ if (mode_from_str(opcstr+pos) == destmode) destmode = mode_from_str(opcstr+pos+5); else goto nomatch; pos += 10; } else { /* normal */ while(mode_from_str(opcstr+pos) != destmode) { pos += 4; if (opcstr[pos] == ']') goto nomatch; pos++; } while(opcstr[pos] != ']') pos++; pos++; } } } break; default: abort(); } /* safety check - might have changed */ if (destmode != Areg && destmode != Dreg && destmode != Aind && destmode != Ad16 && destmode != Ad8r && destmode != Aipi && destmode != Apdi) { dstgather = 0; } if (destmode == Areg && sz == sz_byte) goto nomatch; #if 0 if (sz == sz_byte && (destmode == Aipi || destmode == Apdi)) { dstgather = 0; } #endif endofline: /* now, we have a match */ //if (table68k[opc].mnemo != i_ILLG) // write_log (_T("Double match: %x: %s\n"), opc, opcstr); if (find == -1) { for (find = 0;; find++) { if (_tcscmp (mnemonic, lookuptab[find].name) == 0) { table68k[opc].mnemo = lookuptab[find].mnemo; break; } if (lookuptab[find].name[0] == '\0') abort(); } } else { table68k[opc].mnemo = lookuptab[find].mnemo; } table68k[opc].cc = bitval[bitc]; table68k[opc].ccuse = usecc != 0; mnemo = table68k[opc].mnemo; if (mnemo == i_BTST || mnemo == i_BSET || mnemo == i_BCLR || mnemo == i_BCHG) { sz = destmode == Dreg ? sz_long : sz_byte; unsized = 0; } if (mnemo == i_JSR || mnemo == i_JMP) { unsized = 1; } table68k[opc].size = sz; table68k[opc].unsized = unsized; table68k[opc].sduse = id.sduse; table68k[opc].sreg = srcreg; table68k[opc].dreg = destreg; table68k[opc].smode = srcmode; table68k[opc].dmode = destmode; table68k[opc].spos = srcgather ? srcpos : -1; table68k[opc].dpos = dstgather ? dstpos : -1; table68k[opc].suse = usesrc; table68k[opc].duse = usedst; table68k[opc].stype = srctype; table68k[opc].plev = id.plevel; table68k[opc].clev = id.cpulevel; table68k[opc].unimpclev = id.unimpcpulevel; table68k[opc].head = id.head; table68k[opc].tail = id.tail; table68k[opc].clocks = id.clocks; table68k[opc].fetchmode = id.fetchmode; #if 0 for (i = 0; i < 5; i++) { table68k[opc].flaginfo[i].flagset = id.flaginfo[i].flagset; table68k[opc].flaginfo[i].flaguse = id.flaginfo[i].flaguse; } #endif // Fix flags used information for Scc, Bcc, TRAPcc, DBcc instructions if (table68k[opc].mnemo == i_Scc || table68k[opc].mnemo == i_Bcc || table68k[opc].mnemo == i_DBcc || table68k[opc].mnemo == i_TRAPcc ) { switch (table68k[opc].cc) { // CC mask: XNZVC // 8421 case 0: flags_used = 0x00; break; /* T */ case 1: flags_used = 0x00; break; /* F */ case 2: flags_used = 0x05; break; /* HI */ case 3: flags_used = 0x05; break; /* LS */ case 4: flags_used = 0x01; break; /* CC */ case 5: flags_used = 0x01; break; /* CS */ case 6: flags_used = 0x04; break; /* NE */ case 7: flags_used = 0x04; break; /* EQ */ case 8: flags_used = 0x02; break; /* VC */ case 9: flags_used = 0x02; break; /* VS */ case 10:flags_used = 0x08; break; /* PL */ case 11:flags_used = 0x08; break; /* MI */ case 12:flags_used = 0x0A; break; /* GE */ case 13:flags_used = 0x0A; break; /* LT */ case 14:flags_used = 0x0E; break; /* GT */ case 15:flags_used = 0x0E; break; /* LE */ } } #if 1 /* gb-- flagdead and flaglive would not have correct information */ table68k[opc].flagdead = flags_set; table68k[opc].flaglive = flags_used; #else table68k[opc].flagdead = flagdead; table68k[opc].flaglive = flaglive; #endif table68k[opc].cflow = cflow; nomatch: /* FOO! */; } } static int imismatch; static void handle_merges (long int opcode) { uae_u16 smsk; uae_u16 dmsk; int sbitdst, dstend; int srcreg, dstreg; if (table68k[opcode].spos == -1) { sbitdst = 1; smsk = 0; } else { switch (table68k[opcode].stype) { case 0: smsk = 7; sbitdst = 8; break; case 1: smsk = 255; sbitdst = 256; break; case 2: smsk = 15; sbitdst = 16; break; case 3: smsk = 7; sbitdst = 8; break; case 4: smsk = 7; sbitdst = 8; break; case 5: smsk = 63; sbitdst = 64; break; case 6: smsk = 255; sbitdst = 256; break; case 7: smsk = 3; sbitdst = 4; break; default: smsk = 0; sbitdst = 0; abort(); break; } smsk <<= table68k[opcode].spos; } if (table68k[opcode].dpos == -1) { dstend = 1; dmsk = 0; } else { dmsk = 7 << table68k[opcode].dpos; dstend = 8; } for (srcreg=0; srcreg < sbitdst; srcreg++) { for (dstreg=0; dstreg < dstend; dstreg++) { uae_u16 code = (uae_u16)opcode; uae_u8 spos = (table68k[opcode].spos < 0) ? 0 : table68k[opcode].spos; uae_u8 dpos = (table68k[opcode].dpos < 0) ? 0 : table68k[opcode].dpos; code = (code & ~smsk) | (srcreg << spos); code = (code & ~dmsk) | (dstreg << dpos); /* Check whether this is in fact the same instruction. * The instructions should never differ, except for the * Bcc.(BW) case. */ if (table68k[code].mnemo != table68k[opcode].mnemo || table68k[code].size != table68k[opcode].size || table68k[code].suse != table68k[opcode].suse || table68k[code].duse != table68k[opcode].duse) { imismatch++; continue; } if (table68k[opcode].suse && (table68k[opcode].spos != table68k[code].spos || table68k[opcode].smode != table68k[code].smode || table68k[opcode].stype != table68k[code].stype)) { imismatch++; continue; } if (table68k[opcode].duse && (table68k[opcode].dpos != table68k[code].dpos || table68k[opcode].dmode != table68k[code].dmode)) { imismatch++; continue; } if (code != opcode) table68k[code].handler = opcode; } } } void do_merges (void) { long int opcode; int nr = 0; imismatch = 0; for (opcode = 0; opcode < 65536; opcode++) { if (table68k[opcode].handler != -1 || table68k[opcode].mnemo == i_ILLG) continue; nr++; handle_merges (opcode); } nr_cpuop_funcs = nr; } int get_no_mismatches (void) { return imismatch; } static int isreg(amodes mode) { if (mode == Dreg || mode == Areg) return 1; return 0; } bool opcode_loop_mode(uae_u16 opcode) { struct instr *c = &table68k[opcode]; bool loopmode = false; int i; for (i = 0; lookuptab[i].name[0]; i++) { if (c->mnemo == lookuptab[i].mnemo) break; } if (lookuptab[i].flags & MNEMOFLAG_LOOPMODE) { // Source is Dn,An,(An),(An)+,-(An) // Destination is Dn,An,(An),(An)+,-(An) // Both source and destination must not be Dn or An. // RMW instruction must not be Dn or An if (((isreg(c->smode) || c->smode == Aind || c->smode == Apdi || c->smode == Aipi)) && ((!c->duse && !isreg(c->smode)) || (c->duse && (isreg(c->dmode) || c->dmode == Aind || c->dmode == Apdi || c->dmode == Aipi))) && (!c->duse || (isreg(c->smode) && !isreg(c->dmode)) || (!isreg(c->smode) && isreg(c->dmode)) || (!isreg(c->smode) && !isreg(c->dmode)))) { loopmode = true; } if (c->mnemo == i_MOVE || c->mnemo == i_MOVEA) { // move x,reg: not supported if (isreg(c->dmode)) loopmode = false; // move reg,-(an): not supported if (isreg(c->smode) && c->dmode == Apdi) loopmode = false; } } return loopmode; } void init_table68k(void) { int i; free(table68k); table68k = xmalloc(struct instr, 65536); for (i = 0; i < 65536; i++) { table68k[i].mnemo = i_ILLG; table68k[i].handler = -1; } for (i = 0; i < n_defs68k; i++) { build_insn(i); } do_merges(); } void exit_table68k(void) { free(table68k); table68k = NULL; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/readcpu.h000066400000000000000000000072121504763705000237150ustar00rootroot00000000000000#ifndef UAE_READCPU_H #define UAE_READCPU_H #include "uae/types.h" ENUMDECL { Dreg, Areg, Aind, Aipi, Apdi, Ad16, Ad8r, absw, absl, PC16, PC8r, imm, imm0, imm1, imm2, immi, am_unknown, am_illg } ENUMNAME (amodes); ENUMDECL { i_ILLG, i_OR, i_AND, i_EOR, i_ORSR, i_ANDSR, i_EORSR, i_SUB, i_SUBA, i_SUBX, i_SBCD, i_ADD, i_ADDA, i_ADDX, i_ABCD, i_NEG, i_NEGX, i_NBCD, i_CLR, i_NOT, i_TST, i_BTST, i_BCHG, i_BCLR, i_BSET, i_CMP, i_CMPM, i_CMPA, i_MVPRM, i_MVPMR, i_MOVE, i_MOVEA, i_MVSR2, i_MV2SR, i_SWAP, i_EXG, i_EXT, i_MVMEL, i_MVMLE, i_TRAP, i_MVR2USP, i_MVUSP2R, i_RESET, i_NOP, i_STOP, i_RTE, i_RTD, i_LINK, i_UNLK, i_RTS, i_TRAPV, i_RTR, i_JSR, i_JMP, i_BSR, i_Bcc, i_LEA, i_PEA, i_DBcc, i_Scc, i_DIVU, i_DIVS, i_MULU, i_MULS, i_ASR, i_ASL, i_LSR, i_LSL, i_ROL, i_ROR, i_ROXL, i_ROXR, i_ASRW, i_ASLW, i_LSRW, i_LSLW, i_ROLW, i_RORW, i_ROXLW, i_ROXRW, i_CHK,i_CHK2, i_MOVEC2, i_MOVE2C, i_CAS, i_CAS2, i_DIVL, i_MULL, i_BFTST,i_BFEXTU,i_BFCHG,i_BFEXTS,i_BFCLR,i_BFFFO,i_BFSET,i_BFINS, i_PACK, i_UNPK, i_TAS, i_BKPT, i_CALLM, i_RTM, i_TRAPcc, i_MOVES, i_FPP, i_FDBcc, i_FScc, i_FTRAPcc, i_FBcc, i_FSAVE, i_FRESTORE, i_CINVL, i_CINVP, i_CINVA, i_CPUSHL, i_CPUSHP, i_CPUSHA, i_MOVE16, i_MMUOP030, i_PFLUSHN, i_PFLUSH, i_PFLUSHAN, i_PFLUSHA, i_PLPAR, i_PLPAW, i_PTESTR, i_PTESTW, i_LPSTOP, i_HALT, i_PULSE, MAX_OPCODE_FAMILY } ENUMNAME (instrmnem); #define MNEMOFLAG_LOOPMODE 2 struct mnemolookup { instrmnem mnemo; const TCHAR *name; const TCHAR *friendlyname; uae_u32 flags; }; extern struct mnemolookup lookuptab[]; ENUMDECL { sz_byte, sz_word, sz_long, sz_single, sz_double, sz_extended, sz_packed } ENUMNAME (wordsizes); ENUMDECL { fa_set, fa_unset, fa_zero, fa_one, fa_dontcare, fa_unknown, fa_isjmp, fa_isbranch } ENUMNAME (flagaffect); ENUMDECL { fu_used, fu_unused, fu_maybecc, fu_unknown, fu_isjmp } ENUMNAME (flaguse); ENUMDECL { fl_normal = 0, fl_branch = 1, fl_jump = 2, fl_return = 3, fl_trap = 4, fl_const_jump = 8, #ifdef UAE fl_end_block = 7 #else /* Instructions that can trap don't mark the end of a block */ fl_end_block = 3 #endif } ENUMNAME (cflow_t); #ifndef HATARI_NO_ENUM_BITVALS ENUMDECL { bit0, bit1, bitc, bitC, bitf, biti, bitI, bitj, bitJ, bitk, bitK, bits, bitS, bitd, bitD, bitr, bitR, bitz, bitE, bitp, lastbit } ENUMNAME (bitvals); #endif struct instr_def { unsigned int bits; int n_variable; uae_u8 bitpos[16]; unsigned int mask; int cpulevel; int unimpcpulevel; int plevel; struct { unsigned int flaguse:3; unsigned int flagset:3; } flaginfo[5]; uae_u8 cflow; uae_u8 sduse; const TCHAR *opcstr; // 68020/030 timing int head, tail, clocks, fetchmode; }; extern struct instr_def defs68k[]; extern int n_defs68k; extern struct instr { long int handler; unsigned char dreg; unsigned char sreg; signed char dpos; signed char spos; unsigned char sduse; int flagdead:8, flaglive:8; unsigned int mnemo:8; unsigned int cc:4; unsigned int plev:2; wordsizes size; unsigned int unsized:1; amodes smode; unsigned int stype:3; amodes dmode; unsigned int suse:1; unsigned int duse:1; unsigned int ccuse:1; unsigned int clev:3, unimpclev:3; unsigned int cflow:3; unsigned int unused2:1; char head, tail, clocks, fetchmode; } *table68k; extern void do_merges(void); extern void init_table68k(void); extern void exit_table68k(void); extern int get_no_mismatches(void); extern int nr_cpuop_funcs; extern bool opcode_loop_mode(uae_u16 opcode); #endif /* UAE_READCPU_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/savestate.h000066400000000000000000000253701504763705000242760ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator * * Save/restore emulator state * * (c) 1999-2001 Toni Wilen */ #ifndef UAE_SAVESTATE_H #define UAE_SAVESTATE_H #include "uae/types.h" /* functions to save byte,word or long word * independent of CPU's endianness */ #ifdef WINUAE_FOR_HATARI extern void save_u64(uae_u64 data); extern void save_u32(uae_u32 data); extern void save_u16(uae_u16 data); extern void save_u8(uae_u8 data); extern void save_s8(uae_s8 data); extern uae_u64 restore_u64(void); extern uae_u32 restore_u32(void); extern uae_u16 restore_u16(void); extern uae_u8 restore_u8(void); extern uae_s8 restore_s8(void); #else extern void save_store_pos_func (uae_u8 **); extern void save_store_size_func (uae_u8 **); extern void restore_store_pos_func (uae_u8 **); extern void restore_store_size_func (uae_u8 **); #define save_store_pos() save_store_pos_func (&dst) #define save_store_size() save_store_size_func (&dst) #define restore_store_pos() restore_store_pos_func (&src) #define restore_store_size() restore_store_size_func (&src) extern void save_u64_func(uae_u8 **, uae_u64); extern void save_u32t_func(uae_u8 **, size_t); extern void save_u32_func(uae_u8 **, uae_u32); extern void save_u16_func(uae_u8 **, uae_u16); extern void save_u8_func(uae_u8 **, uae_u8); extern uae_u64 restore_u64_func(uae_u8 **); extern uae_u32 restore_u32_func(uae_u8 **); extern uae_u16 restore_u16_func(uae_u8 **); extern uae_u8 restore_u8_func(uae_u8 **); #endif extern void save_string_func (uae_u8 **, const TCHAR*); extern TCHAR *restore_string_func (uae_u8 **); #define SAVESTATE_PATH 0 #define SAVESTATE_PATH_FLOPPY 1 #define SAVESTATE_PATH_VDIR 2 #define SAVESTATE_PATH_HDF 3 #define SAVESTATE_PATH_HD 4 #define SAVESTATE_PATH_CD 5 extern void save_path_func (uae_u8 **, const TCHAR*, int type); extern void save_path_full_func(uae_u8 **, const TCHAR*, int type); extern TCHAR *restore_path_func(uae_u8 **, int type); extern TCHAR *restore_path_full_func(uae_u8 **); #ifndef WINUAE_FOR_HATARI #define save_u64(x) save_u64_func(&dst, (x)) #define save_u32(x) save_u32_func(&dst, (x)) #define save_u32t(x) save_u32t_func(&dst, (x)) #define save_u16(x) save_u16_func(&dst, (x)) #define save_u8(x) save_u8_func(&dst, (x)) #define save_s8(x) save_u8_func(&dst, (uae_u8)(x)) #define restore_u64() restore_u64_func(&src) #define restore_u64to32() (uae_u32)restore_u64_func(&src) #define restore_u32() restore_u32_func(&src) #define restore_u16() restore_u16_func(&src) #define restore_u8() restore_u8_func(&src) #define restore_s8() ((uae_s8)restore_u8_func(&src)) #endif #define save_string(x) save_string_func(&dst, (x)) #define restore_string() restore_string_func(&src) #define save_path(x, p) save_path_func(&dst, (x), p) #define save_path_full(x, p) save_path_full_func(&dst, (x), p) #define restore_path(p) restore_path_func(&src, p) #define restore_path_full() restore_path_full_func(&src) /* save, restore and initialize routines for Amiga's subsystems */ extern uae_u8 *restore_cpu(uae_u8 *); extern void restore_cpu_finish(void); extern uae_u8 *save_cpu(size_t *, uae_u8 *); extern uae_u8 *restore_cpu_extra(uae_u8 *); extern uae_u8 *save_cpu_extra(size_t *, uae_u8 *); extern uae_u8 *save_cpu_trace(size_t *, uae_u8 *); extern uae_u8 *restore_cpu_trace(uae_u8 *); extern uae_u8 *restore_mmu(uae_u8 *); extern uae_u8 *save_mmu(size_t *, uae_u8 *); extern uae_u8 *restore_fpu(uae_u8 *); extern uae_u8 *save_fpu(size_t *, uae_u8 *); extern uae_u8 *restore_disk(int, uae_u8 *); extern uae_u8 *save_disk(int, size_t *, uae_u8 *, bool); extern uae_u8 *restore_floppy(uae_u8 *src); extern uae_u8 *save_floppy(size_t *len, uae_u8 *); extern uae_u8 *save_disk2(int num, size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_disk2(int num, uae_u8 *src); extern void DISK_save_custom (uae_u32 *pdskpt, uae_u16 *pdsklen, uae_u16 *pdsksync, uae_u16 *pdskbytr); extern void DISK_restore_custom (uae_u32 pdskpt, uae_u16 pdsklength, uae_u16 pdskbytr); extern void restore_disk_finish(void); extern uae_u8 *restore_custom(uae_u8 *); extern uae_u8 *save_custom(size_t *, uae_u8 *, int); extern uae_u8 *restore_custom_extra(uae_u8 *); extern uae_u8 *save_custom_extra(size_t *, uae_u8 *); extern void restore_custom_finish(void); extern void restore_custom_start(void); extern uae_u8 *restore_custom_sprite(int num, uae_u8 *src); extern uae_u8 *save_custom_sprite(int num, size_t *len, uae_u8 *); uae_u8 *save_custom_sprite_denise(int num, uae_u8 *dst); uae_u8 *restore_custom_sprite_denise(int num, uae_u8 *src, uae_u16 pos, uae_u16 ctl); uae_u8 *save_custom_bpl(size_t *len, uae_u8 *dstptr); uae_u8 *restore_custom_bpl(uae_u8 *src); uae_u16 save_custom_bpl_dat(int num); void restore_custom_bpl_dat(int num, uae_u16 dat); extern uae_u8 *restore_custom_agacolors (uae_u8 *src); extern uae_u8 *save_custom_agacolors(size_t *len, uae_u8 *); extern uae_u8 *restore_custom_event_delay (uae_u8 *src); extern uae_u8 *save_custom_event_delay(size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_custom_slots(uae_u8 *src); extern uae_u8 *save_custom_slots(size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_blitter (uae_u8 *src); extern uae_u8 *save_blitter (size_t *len, uae_u8 *, bool); extern uae_u8 *restore_blitter_new (uae_u8 *src); extern uae_u8 *save_blitter_new (size_t *len, uae_u8 *); extern void restore_blitter_finish (void); extern uae_u8 *restore_audio(int, uae_u8 *); extern uae_u8 *save_audio(int, size_t *, uae_u8 *); extern void restore_audio_finish(void); extern void restore_audio_start(void); extern uae_u8 *restore_cia(int, uae_u8 *); extern uae_u8 *save_cia(int, size_t *, uae_u8 *); extern void restore_cia_finish(void); extern void restore_cia_start(void); extern uae_u8 *restore_expansion(uae_u8 *); extern uae_u8 *save_expansion(size_t *, uae_u8 *); extern uae_u8 *restore_p96(uae_u8 *); extern uae_u8 *save_p96(size_t *, uae_u8 *); extern void restore_p96_finish(void); extern uae_u8 *restore_keyboard(uae_u8 *); extern uae_u8 *save_keyboard(size_t *,uae_u8*); extern uae_u8 *restore_akiko(uae_u8 *src); extern uae_u8 *save_akiko(size_t *len, uae_u8*); extern void restore_akiko_finish(void); extern void restore_akiko_final(void); extern uae_u8 *restore_cdtv(uae_u8 *src); extern uae_u8 *save_cdtv(size_t *len, uae_u8*); extern void restore_cdtv_finish(void); extern void restore_cdtv_final(void); extern uae_u8 *restore_cdtv_dmac(uae_u8 *src); extern uae_u8 *save_cdtv_dmac(size_t *len, uae_u8*); extern uae_u8 *restore_scsi_dmac(int wdtype, uae_u8 *src); extern uae_u8 *save_scsi_dmac(int wdtype, int *len, uae_u8*); extern uae_u8 *save_scsi_device(int wdtype, int num, size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_scsi_device(int wdtype, uae_u8 *src); extern uae_u8 *save_scsidev(int num, size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_scsidev(uae_u8 *src); extern uae_u8 *restore_filesys(uae_u8 *src); extern uae_u8 *save_filesys(int num, size_t *len); extern uae_u8 *restore_filesys_common(uae_u8 *src); extern uae_u8 *save_filesys_common(size_t *len); extern uae_u8 *restore_filesys_paths(uae_u8 *src); extern uae_u8 *save_filesys_paths(int num, size_t *len); extern int save_filesys_cando(void); extern uae_u8 *restore_gayle(uae_u8 *src); extern uae_u8 *save_gayle(size_t *len, uae_u8*); extern uae_u8 *restore_gayle_ide(uae_u8 *src); extern uae_u8 *save_gayle_ide(int num, size_t *len, uae_u8*); extern uae_u8 *save_cd(int num, size_t *len); extern uae_u8 *restore_cd(int, uae_u8 *src); extern uae_u8 *save_configuration(size_t *len, bool fullconfig); extern uae_u8 *restore_configuration(uae_u8 *src); extern uae_u8 *save_log(int, size_t *len); //extern uae_u8 *restore_log (uae_u8 *src); extern uae_u8 *restore_input(uae_u8 *src); extern uae_u8 *save_input(size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_inputstate(uae_u8 *src); extern uae_u8 *save_inputstate(size_t *len, uae_u8 *dstptr); extern void clear_inputstate(void); extern uae_u8 *save_a2065(size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_a2065(uae_u8 *src); extern void restore_a2065_finish(void); extern uae_u8 *restore_debug_memwatch(uae_u8 *src); extern uae_u8 *save_debug_memwatch(size_t *len, uae_u8 *dstptr); extern void restore_debug_memwatch_finish(void); extern uae_u8 *save_screenshot(int monid, size_t *len); extern uae_u8 *save_cycles(size_t *len, uae_u8 *dstptr); extern uae_u8 *restore_cycles(uae_u8 *src); extern void restore_cram(int, size_t); extern void restore_bram(int, size_t); extern void restore_fram(int, size_t, int); extern void restore_zram(int, size_t, int); extern void restore_bootrom(int, size_t); extern void restore_pram(int, size_t); extern void restore_a3000lram(int, size_t); extern void restore_a3000hram(int, size_t); extern void restore_ram (size_t, uae_u8*); extern uae_u8 *save_cram(size_t *); extern uae_u8 *save_bram(size_t *); extern uae_u8 *save_fram(size_t *, int); extern uae_u8 *save_zram(size_t *, int); extern uae_u8 *save_bootrom(size_t *); extern uae_u8 *save_pram(size_t *); extern uae_u8 *save_a3000lram (size_t *); extern uae_u8 *save_a3000hram (size_t *); extern uae_u8 *restore_rom(uae_u8 *); extern uae_u8 *save_rom(int, size_t *, uae_u8 *); extern uae_u8 *save_expansion_boards(size_t *, uae_u8*, int); extern uae_u8 *restore_expansion_boards(uae_u8*); #if 0 extern uae_u8 *save_expansion_info_old(int*, uae_u8*); extern uae_u8 *restore_expansion_info_old(uae_u8*); #endif extern void restore_expansion_finish(void); extern uae_u8 *restore_action_replay(uae_u8 *); extern uae_u8 *save_action_replay(size_t *, uae_u8 *); extern uae_u8 *restore_hrtmon(uae_u8 *); extern uae_u8 *save_hrtmon(size_t *, uae_u8 *); extern void restore_ar_finish(void); extern void savestate_initsave(const TCHAR *filename, int docompress, int nodialogs, bool save); extern int save_state(const TCHAR *filename, const TCHAR *description); extern void restore_state(const TCHAR *filename); extern bool savestate_restore_finish(void); extern void savestate_restore_final(void); extern void savestate_memorysave(void); extern bool is_savestate_incompatible(void); extern void custom_prepare_savestate(void); extern bool savestate_check(void); #define STATE_SAVE 1 #define STATE_RESTORE 2 #define STATE_DOSAVE 4 #define STATE_DORESTORE 8 #define STATE_REWIND 16 #define STATE_DOREWIND 32 #define STATE_SAVE_DESCRIPTION _T("Description!") extern int savestate_state; extern TCHAR savestate_fname[MAX_DPATH]; extern TCHAR path_statefile[MAX_DPATH]; extern struct zfile *savestate_file; STATIC_INLINE bool isrestore(void) { return savestate_state == STATE_RESTORE || savestate_state == STATE_REWIND; } extern void savestate_quick(int slot, int save); extern void savestate_capture(int); extern void savestate_free(void); extern void savestate_init(void); extern void savestate_rewind(void); extern int savestate_dorewind(int); extern void savestate_listrewind(void); extern void statefile_save_recording(const TCHAR*); extern void savestate_capture_request(void); #endif /* UAE_SAVESTATE_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/000077500000000000000000000000001504763705000241205ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/softfloat-macros.h000066400000000000000000000660011504763705000275570ustar00rootroot00000000000000/* * QEMU float support macros * * The code in this source file is derived from release 2a of the SoftFloat * IEC/IEEE Floating-point Arithmetic Package. Those parts of the code (and * some later contributions) are provided under that license, as detailed below. * It has subsequently been modified by contributors to the QEMU Project, * so some portions are provided under: * the SoftFloat-2a license * the BSD license * GPL-v2-or-later * * Any future contributions to this file after December 1st 2014 will be * taken to be licensed under the Softfloat-2a license unless specifically * indicated otherwise. */ /* =============================================================================== This C source fragment is part of the SoftFloat IEC/IEEE Floating-point Arithmetic Package, Release 2a. Written by John R. Hauser. This work was made possible in part by the International Computer Science Institute, located at Suite 600, 1947 Center Street, Berkeley, California 94704. Funding was partially provided by the National Science Foundation under grant MIP-9311980. The original version of this code was written as part of a project to build a fixed-point vector processor in collaboration with the University of California at Berkeley, overseen by Profs. Nelson Morgan and John Wawrzynek. More information is available through the Web page `http://HTTP.CS.Berkeley.EDU/~jhauser/ arithmetic/SoftFloat.html'. THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ANY AND ALL LOSSES, COSTS, OR OTHER PROBLEMS ARISING FROM ITS USE. Derivative works are acceptable, even for commercial purposes, so long as (1) they include prominent notice that the work is derivative, and (2) they include prominent notice akin to these four paragraphs for those parts of this code that are retained. =============================================================================== */ /* BSD licensing: * Copyright (c) 2006, Fabrice Bellard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* Portions of this work are licensed under the terms of the GNU GPL, * version 2 or later. See the COPYING file in the top-level directory. */ /*---------------------------------------------------------------------------- | This macro tests for minimum version of the GNU C compiler. *----------------------------------------------------------------------------*/ #if defined(__GNUC__) && defined(__GNUC_MINOR__) # define SOFTFLOAT_GNUC_PREREQ(maj, min) \ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) #else # define SOFTFLOAT_GNUC_PREREQ(maj, min) 0 #endif /*---------------------------------------------------------------------------- | Shifts `a' right by the number of bits given in `count'. If any nonzero | bits are shifted off, they are ``jammed'' into the least significant bit of | the result by setting the least significant bit to 1. The value of `count' | can be arbitrarily large; in particular, if `count' is greater than 32, the | result will be either 0 or 1, depending on whether `a' is zero or nonzero. | The result is stored in the location pointed to by `zPtr'. *----------------------------------------------------------------------------*/ static inline void shift32RightJamming(uint32_t a, int count, uint32_t *zPtr) { uint32_t z; if ( count == 0 ) { z = a; } else if ( count < 32 ) { z = ( a>>count ) | ( ( a<<( ( - count ) & 31 ) ) != 0 ); } else { z = ( a != 0 ); } *zPtr = z; } /*---------------------------------------------------------------------------- | Shifts `a' right by the number of bits given in `count'. If any nonzero | bits are shifted off, they are ``jammed'' into the least significant bit of | the result by setting the least significant bit to 1. The value of `count' | can be arbitrarily large; in particular, if `count' is greater than 64, the | result will be either 0 or 1, depending on whether `a' is zero or nonzero. | The result is stored in the location pointed to by `zPtr'. *----------------------------------------------------------------------------*/ static inline void shift64RightJamming(uint64_t a, int count, uint64_t *zPtr) { uint64_t z; if ( count == 0 ) { z = a; } else if ( count < 64 ) { z = ( a>>count ) | ( ( a<<( ( - count ) & 63 ) ) != 0 ); } else { z = ( a != 0 ); } *zPtr = z; } /*---------------------------------------------------------------------------- | Shifts the 128-bit value formed by concatenating `a0' and `a1' right by 64 | _plus_ the number of bits given in `count'. The shifted result is at most | 64 nonzero bits; this is stored at the location pointed to by `z0Ptr'. The | bits shifted off form a second 64-bit result as follows: The _last_ bit | shifted off is the most-significant bit of the extra result, and the other | 63 bits of the extra result are all zero if and only if _all_but_the_last_ | bits shifted off were all zero. This extra result is stored in the location | pointed to by `z1Ptr'. The value of `count' can be arbitrarily large. | (This routine makes more sense if `a0' and `a1' are considered to form a | fixed-point value with binary point between `a0' and `a1'. This fixed-point | value is shifted right by the number of bits given in `count', and the | integer part of the result is returned at the location pointed to by | `z0Ptr'. The fractional part of the result may be slightly corrupted as | described above, and is returned at the location pointed to by `z1Ptr'.) *----------------------------------------------------------------------------*/ static inline void shift64ExtraRightJamming( uint64_t a0, uint64_t a1, int count, uint64_t *z0Ptr, uint64_t *z1Ptr) { uint64_t z0, z1; int8_t negCount = ( - count ) & 63; if ( count == 0 ) { z1 = a1; z0 = a0; } else if ( count < 64 ) { z1 = ( a0<>count; } else { if ( count == 64 ) { z1 = a0 | ( a1 != 0 ); } else { z1 = ( ( a0 | a1 ) != 0 ); } z0 = 0; } *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Shifts the 128-bit value formed by concatenating `a0' and `a1' right by the | number of bits given in `count'. Any bits shifted off are lost. The value | of `count' can be arbitrarily large; in particular, if `count' is greater | than 128, the result will be 0. The result is broken into two 64-bit pieces | which are stored at the locations pointed to by `z0Ptr' and `z1Ptr'. *----------------------------------------------------------------------------*/ static inline void shift128Right( uint64_t a0, uint64_t a1, int count, uint64_t *z0Ptr, uint64_t *z1Ptr) { uint64_t z0, z1; int8_t negCount = ( - count ) & 63; if ( count == 0 ) { z1 = a1; z0 = a0; } else if ( count < 64 ) { z1 = ( a0<>count ); z0 = a0>>count; } else { z1 = (count < 128) ? (a0 >> (count & 63)) : 0; z0 = 0; } *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Shifts the 128-bit value formed by concatenating `a0' and `a1' right by the | number of bits given in `count'. If any nonzero bits are shifted off, they | are ``jammed'' into the least significant bit of the result by setting the | least significant bit to 1. The value of `count' can be arbitrarily large; | in particular, if `count' is greater than 128, the result will be either | 0 or 1, depending on whether the concatenation of `a0' and `a1' is zero or | nonzero. The result is broken into two 64-bit pieces which are stored at | the locations pointed to by `z0Ptr' and `z1Ptr'. *----------------------------------------------------------------------------*/ static inline void shift128RightJamming( uint64_t a0, uint64_t a1, int count, uint64_t *z0Ptr, uint64_t *z1Ptr) { uint64_t z0, z1; int8_t negCount = ( - count ) & 63; if ( count == 0 ) { z1 = a1; z0 = a0; } else if ( count < 64 ) { z1 = ( a0<>count ) | ( ( a1<>count; } else { if ( count == 64 ) { z1 = a0 | ( a1 != 0 ); } else if ( count < 128 ) { z1 = ( a0>>( count & 63 ) ) | ( ( ( a0<>count ); z0 = a0>>count; } else { if ( count == 64 ) { z2 = a1; z1 = a0; } else { a2 |= a1; if ( count < 128 ) { z2 = a0<>( count & 63 ); } else { z2 = ( count == 128 ) ? a0 : ( a0 != 0 ); z1 = 0; } } z0 = 0; } z2 |= ( a2 != 0 ); } *z2Ptr = z2; *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Shifts the 128-bit value formed by concatenating `a0' and `a1' left by the | number of bits given in `count'. Any bits shifted off are lost. The value | of `count' must be less than 64. The result is broken into two 64-bit | pieces which are stored at the locations pointed to by `z0Ptr' and `z1Ptr'. *----------------------------------------------------------------------------*/ static inline void shortShift128Left( uint64_t a0, uint64_t a1, int count, uint64_t *z0Ptr, uint64_t *z1Ptr) { *z1Ptr = a1<>( ( - count ) & 63 ) ); } /*---------------------------------------------------------------------------- | Shifts the 192-bit value formed by concatenating `a0', `a1', and `a2' left | by the number of bits given in `count'. Any bits shifted off are lost. | The value of `count' must be less than 64. The result is broken into three | 64-bit pieces which are stored at the locations pointed to by `z0Ptr', | `z1Ptr', and `z2Ptr'. *----------------------------------------------------------------------------*/ static inline void shortShift192Left( uint64_t a0, uint64_t a1, uint64_t a2, int count, uint64_t *z0Ptr, uint64_t *z1Ptr, uint64_t *z2Ptr ) { uint64_t z0, z1, z2; int8_t negCount; z2 = a2<>negCount; z0 |= a1>>negCount; } *z2Ptr = z2; *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Adds the 128-bit value formed by concatenating `a0' and `a1' to the 128-bit | value formed by concatenating `b0' and `b1'. Addition is modulo 2^128, so | any carry out is lost. The result is broken into two 64-bit pieces which | are stored at the locations pointed to by `z0Ptr' and `z1Ptr'. *----------------------------------------------------------------------------*/ static inline void add128( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1, uint64_t *z0Ptr, uint64_t *z1Ptr ) { uint64_t z1; z1 = a1 + b1; *z1Ptr = z1; *z0Ptr = a0 + b0 + ( z1 < a1 ); } /*---------------------------------------------------------------------------- | Adds the 192-bit value formed by concatenating `a0', `a1', and `a2' to the | 192-bit value formed by concatenating `b0', `b1', and `b2'. Addition is | modulo 2^192, so any carry out is lost. The result is broken into three | 64-bit pieces which are stored at the locations pointed to by `z0Ptr', | `z1Ptr', and `z2Ptr'. *----------------------------------------------------------------------------*/ static inline void add192( uint64_t a0, uint64_t a1, uint64_t a2, uint64_t b0, uint64_t b1, uint64_t b2, uint64_t *z0Ptr, uint64_t *z1Ptr, uint64_t *z2Ptr ) { uint64_t z0, z1, z2; uint8_t carry0, carry1; z2 = a2 + b2; carry1 = ( z2 < a2 ); z1 = a1 + b1; carry0 = ( z1 < a1 ); z0 = a0 + b0; z1 += carry1; z0 += ( z1 < carry1 ); z0 += carry0; *z2Ptr = z2; *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Subtracts the 128-bit value formed by concatenating `b0' and `b1' from the | 128-bit value formed by concatenating `a0' and `a1'. Subtraction is modulo | 2^128, so any borrow out (carry out) is lost. The result is broken into two | 64-bit pieces which are stored at the locations pointed to by `z0Ptr' and | `z1Ptr'. *----------------------------------------------------------------------------*/ static inline void sub128( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1, uint64_t *z0Ptr, uint64_t *z1Ptr ) { *z1Ptr = a1 - b1; *z0Ptr = a0 - b0 - ( a1 < b1 ); } /*---------------------------------------------------------------------------- | Subtracts the 192-bit value formed by concatenating `b0', `b1', and `b2' | from the 192-bit value formed by concatenating `a0', `a1', and `a2'. | Subtraction is modulo 2^192, so any borrow out (carry out) is lost. The | result is broken into three 64-bit pieces which are stored at the locations | pointed to by `z0Ptr', `z1Ptr', and `z2Ptr'. *----------------------------------------------------------------------------*/ static inline void sub192( uint64_t a0, uint64_t a1, uint64_t a2, uint64_t b0, uint64_t b1, uint64_t b2, uint64_t *z0Ptr, uint64_t *z1Ptr, uint64_t *z2Ptr ) { uint64_t z0, z1, z2; uint8_t borrow0, borrow1; z2 = a2 - b2; borrow1 = ( a2 < b2 ); z1 = a1 - b1; borrow0 = ( a1 < b1 ); z0 = a0 - b0; z0 -= ( z1 < borrow1 ); z1 -= borrow1; z0 -= borrow0; *z2Ptr = z2; *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Multiplies `a' by `b' to obtain a 128-bit product. The product is broken | into two 64-bit pieces which are stored at the locations pointed to by | `z0Ptr' and `z1Ptr'. *----------------------------------------------------------------------------*/ static inline void mul64To128( uint64_t a, uint64_t b, uint64_t *z0Ptr, uint64_t *z1Ptr ) { uint32_t aHigh, aLow, bHigh, bLow; uint64_t z0, zMiddleA, zMiddleB, z1; aLow = (uint32_t)a; aHigh = a>>32; bLow = (uint32_t)b; bHigh = b>>32; z1 = ( (uint64_t) aLow ) * bLow; zMiddleA = ( (uint64_t) aLow ) * bHigh; zMiddleB = ( (uint64_t) aHigh ) * bLow; z0 = ( (uint64_t) aHigh ) * bHigh; zMiddleA += zMiddleB; z0 += ( ( (uint64_t) ( zMiddleA < zMiddleB ) )<<32 ) + ( zMiddleA>>32 ); zMiddleA <<= 32; z1 += zMiddleA; z0 += ( z1 < zMiddleA ); *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Multiplies the 128-bit value formed by concatenating `a0' and `a1' by | `b' to obtain a 192-bit product. The product is broken into three 64-bit | pieces which are stored at the locations pointed to by `z0Ptr', `z1Ptr', and | `z2Ptr'. *----------------------------------------------------------------------------*/ static inline void mul128By64To192( uint64_t a0, uint64_t a1, uint64_t b, uint64_t *z0Ptr, uint64_t *z1Ptr, uint64_t *z2Ptr ) { uint64_t z0, z1, z2, more1; mul64To128( a1, b, &z1, &z2 ); mul64To128( a0, b, &z0, &more1 ); add128( z0, more1, 0, z1, &z0, &z1 ); *z2Ptr = z2; *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Multiplies the 128-bit value formed by concatenating `a0' and `a1' to the | 128-bit value formed by concatenating `b0' and `b1' to obtain a 256-bit | product. The product is broken into four 64-bit pieces which are stored at | the locations pointed to by `z0Ptr', `z1Ptr', `z2Ptr', and `z3Ptr'. *----------------------------------------------------------------------------*/ static inline void mul128To256( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1, uint64_t *z0Ptr, uint64_t *z1Ptr, uint64_t *z2Ptr, uint64_t *z3Ptr ) { uint64_t z0, z1, z2, z3; uint64_t more1, more2; mul64To128( a1, b1, &z2, &z3 ); mul64To128( a1, b0, &z1, &more2 ); add128( z1, more2, 0, z2, &z1, &z2 ); mul64To128( a0, b0, &z0, &more1 ); add128( z0, more1, 0, z1, &z0, &z1 ); mul64To128( a0, b1, &more1, &more2 ); add128( more1, more2, 0, z2, &more1, &z2 ); add128( z0, z1, 0, more1, &z0, &z1 ); *z3Ptr = z3; *z2Ptr = z2; *z1Ptr = z1; *z0Ptr = z0; } /*---------------------------------------------------------------------------- | Returns an approximation to the 64-bit integer quotient obtained by dividing | `b' into the 128-bit value formed by concatenating `a0' and `a1'. The | divisor `b' must be at least 2^63. If q is the exact quotient truncated | toward zero, the approximation returned lies between q and q + 2 inclusive. | If the exact quotient q is larger than 64 bits, the maximum positive 64-bit | unsigned integer is returned. *----------------------------------------------------------------------------*/ static inline uint64_t estimateDiv128To64( uint64_t a0, uint64_t a1, uint64_t b ) { uint64_t b0, b1; uint64_t rem0, rem1, term0, term1; uint64_t z; if ( b <= a0 ) return LIT64( 0xFFFFFFFFFFFFFFFF ); b0 = b>>32; z = ( b0<<32 <= a0 ) ? LIT64( 0xFFFFFFFF00000000 ) : ( a0 / b0 )<<32; mul64To128( b, z, &term0, &term1 ); sub128( a0, a1, term0, term1, &rem0, &rem1 ); while ( ( (int64_t) rem0 ) < 0 ) { z -= LIT64( 0x100000000 ); b1 = b<<32; add128( rem0, rem1, b0, b1, &rem0, &rem1 ); } rem0 = ( rem0<<32 ) | ( rem1>>32 ); z |= ( b0<<32 <= rem0 ) ? 0xFFFFFFFF : rem0 / b0; return z; } /*---------------------------------------------------------------------------- | Returns an approximation to the square root of the 32-bit significand given | by `a'. Considered as an integer, `a' must be at least 2^31. If bit 0 of | `aExp' (the least significant bit) is 1, the integer returned approximates | 2^31*sqrt(`a'/2^31), where `a' is considered an integer. If bit 0 of `aExp' | is 0, the integer returned approximates 2^31*sqrt(`a'/2^30). In either | case, the approximation returned lies strictly within +/-2 of the exact | value. *----------------------------------------------------------------------------*/ static inline uint32_t estimateSqrt32(int aExp, uint32_t a) { static const uint16_t sqrtOddAdjustments[] = { 0x0004, 0x0022, 0x005D, 0x00B1, 0x011D, 0x019F, 0x0236, 0x02E0, 0x039C, 0x0468, 0x0545, 0x0631, 0x072B, 0x0832, 0x0946, 0x0A67 }; static const uint16_t sqrtEvenAdjustments[] = { 0x0A2D, 0x08AF, 0x075A, 0x0629, 0x051A, 0x0429, 0x0356, 0x029E, 0x0200, 0x0179, 0x0109, 0x00AF, 0x0068, 0x0034, 0x0012, 0x0002 }; int8_t index; uint32_t z; index = ( a>>27 ) & 15; if ( aExp & 1 ) { z = 0x4000 + ( a>>17 ) - sqrtOddAdjustments[ (int)index ]; z = ( ( a / z )<<14 ) + ( z<<15 ); a >>= 1; } else { z = 0x8000 + ( a>>17 ) - sqrtEvenAdjustments[ (int)index ]; z = a / z + z; z = ( 0x20000 <= z ) ? 0xFFFF8000 : ( z<<15 ); if ( z <= a ) return (uint32_t) ( ( (int32_t) a )>>1 ); } return ( (uint32_t) ( ( ( (uint64_t) a )<<31 ) / z ) ) + ( z>>1 ); } /*---------------------------------------------------------------------------- | Returns the number of leading 0 bits before the most-significant 1 bit of | `a'. If `a' is zero, 32 is returned. *----------------------------------------------------------------------------*/ static inline int8_t countLeadingZeros32( uint32_t a ) { #if SOFTFLOAT_GNUC_PREREQ(3, 4) if (a) { return __builtin_clz(a); } else { return 32; } #else static const int8_t countLeadingZerosHigh[] = { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int8_t shiftCount; shiftCount = 0; if ( a < 0x10000 ) { shiftCount += 16; a <<= 16; } if ( a < 0x1000000 ) { shiftCount += 8; a <<= 8; } shiftCount += countLeadingZerosHigh[ a>>24 ]; return shiftCount; #endif } /*---------------------------------------------------------------------------- | Returns the number of leading 0 bits before the most-significant 1 bit of | `a'. If `a' is zero, 64 is returned. *----------------------------------------------------------------------------*/ static inline int8_t countLeadingZeros64( uint64_t a ) { #if SOFTFLOAT_GNUC_PREREQ(3, 4) if (a) { return __builtin_clzll(a); } else { return 64; } #else int8_t shiftCount; shiftCount = 0; if ( a < ( (uint64_t) 1 )<<32 ) { shiftCount += 32; } else { a >>= 32; } shiftCount += countLeadingZeros32( (uint32_t)a ); return shiftCount; #endif } /*---------------------------------------------------------------------------- | Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' | is equal to the 128-bit value formed by concatenating `b0' and `b1'. | Otherwise, returns 0. *----------------------------------------------------------------------------*/ static inline flag eq128( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1 ) { return ( a0 == b0 ) && ( a1 == b1 ); } /*---------------------------------------------------------------------------- | Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' is less | than or equal to the 128-bit value formed by concatenating `b0' and `b1'. | Otherwise, returns 0. *----------------------------------------------------------------------------*/ static inline flag le128( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1 ) { return ( a0 < b0 ) || ( ( a0 == b0 ) && ( a1 <= b1 ) ); } /*---------------------------------------------------------------------------- | Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' is less | than the 128-bit value formed by concatenating `b0' and `b1'. Otherwise, | returns 0. *----------------------------------------------------------------------------*/ static inline flag lt128( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1 ) { return ( a0 < b0 ) || ( ( a0 == b0 ) && ( a1 < b1 ) ); } /*---------------------------------------------------------------------------- | Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' is | not equal to the 128-bit value formed by concatenating `b0' and `b1'. | Otherwise, returns 0. *----------------------------------------------------------------------------*/ static inline flag ne128( uint64_t a0, uint64_t a1, uint64_t b0, uint64_t b1 ) { return ( a0 != b0 ) || ( a1 != b1 ); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/softfloat-specialize.h000066400000000000000000000377661504763705000304430ustar00rootroot00000000000000/* * QEMU float support * * The code in this source file is derived from release 2a of the SoftFloat * IEC/IEEE Floating-point Arithmetic Package. Those parts of the code (and * some later contributions) are provided under that license, as detailed below. * It has subsequently been modified by contributors to the QEMU Project, * so some portions are provided under: * the SoftFloat-2a license * the BSD license * GPL-v2-or-later * * Any future contributions to this file after December 1st 2014 will be * taken to be licensed under the Softfloat-2a license unless specifically * indicated otherwise. */ /* =============================================================================== This C source fragment is part of the SoftFloat IEC/IEEE Floating-point Arithmetic Package, Release 2a. Written by John R. Hauser. This work was made possible in part by the International Computer Science Institute, located at Suite 600, 1947 Center Street, Berkeley, California 94704. Funding was partially provided by the National Science Foundation under grant MIP-9311980. The original version of this code was written as part of a project to build a fixed-point vector processor in collaboration with the University of California at Berkeley, overseen by Profs. Nelson Morgan and John Wawrzynek. More information is available through the Web page `http://HTTP.CS.Berkeley.EDU/~jhauser/ arithmetic/SoftFloat.html'. THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ANY AND ALL LOSSES, COSTS, OR OTHER PROBLEMS ARISING FROM ITS USE. Derivative works are acceptable, even for commercial purposes, so long as (1) they include prominent notice that the work is derivative, and (2) they include prominent notice akin to these four paragraphs for those parts of this code that are retained. =============================================================================== */ /* BSD licensing: * Copyright (c) 2006, Fabrice Bellard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* Portions of this work are licensed under the terms of the GNU GPL, * version 2 or later. See the COPYING file in the top-level directory. */ /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is a | NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_nan( floatx80 a ) { return ( ( a.high & 0x7FFF ) == 0x7FFF ) && (uint64_t) ( a.low<<1 ); } /*---------------------------------------------------------------------------- | The pattern for a default generated extended double-precision NaN. *----------------------------------------------------------------------------*/ static inline floatx80 floatx80_default_nan(float_status *status) { floatx80 r; r.high = 0x7FFF; r.low = LIT64( 0xFFFFFFFFFFFFFFFF ); return r; } /*---------------------------------------------------------------------------- | Raises the exceptions specified by `flags'. Floating-point traps can be | defined here if desired. It is currently not possible for such a trap | to substitute a result value. If traps are not implemented, this routine | should be simply `float_exception_flags |= flags;'. *----------------------------------------------------------------------------*/ static inline void float_raise(uint8_t flags, float_status *status) { status->float_exception_flags |= flags; } /*---------------------------------------------------------------------------- | Internal canonical NaN format. *----------------------------------------------------------------------------*/ typedef struct { flag sign; uint64_t high, low; } commonNaNT; /*---------------------------------------------------------------------------- | Returns 1 if the single-precision floating-point value `a' is a NaN; | otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag float32_is_nan( float32 a ) { return ( 0xFF000000 < (uint32_t) ( a<<1 ) ); } /*---------------------------------------------------------------------------- | Returns 1 if the single-precision floating-point value `a' is a signaling | NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag float32_is_signaling_nan( float32 a ) { return ( ( ( a>>22 ) & 0x1FF ) == 0x1FE ) && ( a & 0x003FFFFF ); } /*---------------------------------------------------------------------------- | Returns the result of converting the single-precision floating-point NaN | `a' to the canonical NaN format. If `a' is a signaling NaN, the invalid | exception is raised. *----------------------------------------------------------------------------*/ static inline commonNaNT float32ToCommonNaN( float32 a, float_status *status ) { commonNaNT z; if ( float32_is_signaling_nan( a ) ) float_raise( float_flag_signaling, status ); z.sign = a>>31; z.low = 0; z.high = ( (uint64_t) a )<<41; return z; } /*---------------------------------------------------------------------------- | Returns the result of converting the canonical NaN `a' to the single- | precision floating-point format. *----------------------------------------------------------------------------*/ static inline float32 commonNaNToFloat32( commonNaNT a ) { return ( ( (uint32_t) a.sign )<<31 ) | 0x7FC00000 | ( a.high>>41 ); } /*---------------------------------------------------------------------------- | Takes two single-precision floating-point values `a' and `b', one of which | is a NaN, and returns the appropriate NaN result. If either `a' or `b' is a | signaling NaN, the invalid exception is raised. *----------------------------------------------------------------------------*/ static inline float32 propagateFloat32NaN( float32 a, float32 b, float_status *status ) { flag aIsNaN, aIsSignalingNaN, bIsNaN, bIsSignalingNaN; aIsNaN = float32_is_nan( a ); aIsSignalingNaN = float32_is_signaling_nan( a ); bIsNaN = float32_is_nan( b ); bIsSignalingNaN = float32_is_signaling_nan( b ); a |= 0x00400000; b |= 0x00400000; if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_signaling, status ); if ( aIsNaN ) { return ( aIsSignalingNaN & bIsNaN ) ? b : a; } else { return b; } } /*---------------------------------------------------------------------------- | Returns 1 if the double-precision floating-point value `a' is a NaN; | otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag float64_is_nan( float64 a ) { return ( LIT64( 0xFFE0000000000000 ) < (uint64_t) ( a<<1 ) ); } /*---------------------------------------------------------------------------- | Returns 1 if the double-precision floating-point value `a' is a signaling | NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag float64_is_signaling_nan( float64 a ) { return ( ( ( a>>51 ) & 0xFFF ) == 0xFFE ) && ( a & LIT64( 0x0007FFFFFFFFFFFF ) ); } /*---------------------------------------------------------------------------- | Returns the result of converting the double-precision floating-point NaN | `a' to the canonical NaN format. If `a' is a signaling NaN, the invalid | exception is raised. *----------------------------------------------------------------------------*/ static inline commonNaNT float64ToCommonNaN(float64 a, float_status *status) { commonNaNT z; if (float64_is_signaling_nan(a)) { float_raise(float_flag_invalid, status); } z.sign = float64_val(a) >> 63; z.low = 0; z.high = float64_val(a) << 12; return z; } /*---------------------------------------------------------------------------- | Returns the result of converting the canonical NaN `a' to the double- | precision floating-point format. *----------------------------------------------------------------------------*/ static inline float64 commonNaNToFloat64(commonNaNT a, float_status *status) { return ( ( (uint64_t) a.sign )<<63 ) | LIT64( 0x7FF8000000000000 ) | ( a.high>>12 ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is a | signaling NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_signaling_nan( floatx80 a ) { uint64_t aLow; aLow = a.low & ~ LIT64( 0x4000000000000000 ); return ( ( a.high & 0x7FFF ) == 0x7FFF ) && (uint64_t) ( aLow<<1 ) && ( a.low == aLow ); } /*---------------------------------------------------------------------------- | Returns the result of converting the extended double-precision floating- | point NaN `a' to the canonical NaN format. If `a' is a signaling NaN, the | invalid exception is raised. *----------------------------------------------------------------------------*/ static inline commonNaNT floatx80ToCommonNaN( floatx80 a, float_status *status ) { commonNaNT z; if ( floatx80_is_signaling_nan( a ) ) float_raise( float_flag_signaling, status ); z.sign = a.high>>15; z.low = 0; z.high = a.low<<1; return z; } /*---------------------------------------------------------------------------- | Returns the result of converting the canonical NaN `a' to the extended | double-precision floating-point format. *----------------------------------------------------------------------------*/ static inline floatx80 commonNaNToFloatx80(commonNaNT a, float_status *status) { floatx80 z; #ifdef SOFTFLOAT_68K z.low = LIT64( 0x4000000000000000 ) | ( a.high>>1 ); #else z.low = LIT64( 0xC000000000000000 ) | ( a.high>>1 ); #endif z.high = ( ( (int16_t) a.sign )<<15 ) | 0x7FFF; return z; } /*---------------------------------------------------------------------------- | Takes two extended double-precision floating-point values `a' and `b', one | of which is a NaN, and returns the appropriate NaN result. If either `a' or | `b' is a signaling NaN, the invalid exception is raised. *----------------------------------------------------------------------------*/ static inline floatx80 propagateFloatx80NaN( floatx80 a, floatx80 b, float_status *status ) { flag aIsNaN, aIsSignalingNaN, bIsSignalingNaN; #ifndef SOFTFLOAT_68K flag bIsNaN; #endif aIsNaN = floatx80_is_nan( a ); aIsSignalingNaN = floatx80_is_signaling_nan( a ); bIsSignalingNaN = floatx80_is_signaling_nan( b ); #ifdef SOFTFLOAT_68K a.low |= LIT64( 0x4000000000000000 ); b.low |= LIT64( 0x4000000000000000 ); if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_signaling, status ); return aIsNaN ? a : b; #else bIsNaN = floatx80_is_nan( b ); a.low |= LIT64( 0xC000000000000000 ); b.low |= LIT64( 0xC000000000000000 ); if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_signaling, status ); if ( aIsNaN ) { return ( aIsSignalingNaN & bIsNaN ) ? b : a; } else { return b; } #endif } #ifdef SOFTFLOAT_68K /*---------------------------------------------------------------------------- | Takes extended double-precision floating-point NaN `a' and returns the | appropriate NaN result. If `a' is a signaling NaN, the invalid exception | is raised. *----------------------------------------------------------------------------*/ static inline floatx80 propagateFloatx80NaNOneArg(floatx80 a, float_status *status) { if ( floatx80_is_signaling_nan( a ) ) float_raise( float_flag_signaling, status ); a.low |= LIT64( 0x4000000000000000 ); return a; } #endif // 28-12-2016: Added for Previous: /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | zero; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_zero( floatx80 a ) { return ( ( a.high & 0x7FFF ) < 0x7FFF ) && ( a.low == 0 ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | infinity; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_infinity( floatx80 a ) { return ( ( a.high & 0x7FFF ) == 0x7FFF ) && ( (uint64_t) ( a.low<<1 ) == 0 ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | negative; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_negative( floatx80 a ) { return ( ( a.high & 0x8000 ) == 0x8000 ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | unnormal; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_unnormal( floatx80 a ) { return ( ( a.high & 0x7FFF ) > 0 ) && ( ( a.high & 0x7FFF ) < 0x7FFF) && ( (uint64_t) ( a.low & LIT64( 0x8000000000000000 ) ) == LIT64( 0x0000000000000000 ) ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | denormal; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_denormal( floatx80 a ) { return ( ( a.high & 0x7FFF ) == 0 ) && ( (uint64_t) ( a.low & LIT64( 0x8000000000000000 ) ) == LIT64( 0x0000000000000000 ) ) && (uint64_t) ( a.low<<1 ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | normal; otherwise returns 0. *----------------------------------------------------------------------------*/ static inline flag floatx80_is_normal( floatx80 a ) { return ( ( a.high & 0x7FFF ) < 0x7FFF ) && ( (uint64_t) ( a.low & LIT64( 0x8000000000000000 ) ) == LIT64( 0x8000000000000000 ) ); } // End of addition for Previous hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/softfloat.c000066400000000000000000003571361504763705000263040ustar00rootroot00000000000000 #define SOFTFLOAT_68K #include #include #include "softfloat/softfloat.h" /* * QEMU float support * * The code in this source file is derived from release 2a of the SoftFloat * IEC/IEEE Floating-point Arithmetic Package. Those parts of the code (and * some later contributions) are provided under that license, as detailed below. * It has subsequently been modified by contributors to the QEMU Project, * so some portions are provided under: * the SoftFloat-2a license * the BSD license * GPL-v2-or-later * * Any future contributions to this file after December 1st 2014 will be * taken to be licensed under the Softfloat-2a license unless specifically * indicated otherwise. */ /* =============================================================================== This C source file is part of the SoftFloat IEC/IEEE Floating-point Arithmetic Package, Release 2a. Written by John R. Hauser. This work was made possible in part by the International Computer Science Institute, located at Suite 600, 1947 Center Street, Berkeley, California 94704. Funding was partially provided by the National Science Foundation under grant MIP-9311980. The original version of this code was written as part of a project to build a fixed-point vector processor in collaboration with the University of California at Berkeley, overseen by Profs. Nelson Morgan and John Wawrzynek. More information is available through the Web page `http://HTTP.CS.Berkeley.EDU/~jhauser/ arithmetic/SoftFloat.html'. THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ANY AND ALL LOSSES, COSTS, OR OTHER PROBLEMS ARISING FROM ITS USE. Derivative works are acceptable, even for commercial purposes, so long as (1) they include prominent notice that the work is derivative, and (2) they include prominent notice akin to these four paragraphs for those parts of this code that are retained. =============================================================================== */ /* BSD licensing: * Copyright (c) 2006, Fabrice Bellard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* Portions of this work are licensed under the terms of the GNU GPL, * version 2 or later. See the COPYING file in the top-level directory. */ /* We only need stdlib for abort() */ /*---------------------------------------------------------------------------- | Primitive arithmetic functions, including multi-word arithmetic, and | division and square root approximations. (Can be specialized to target if | desired.) *----------------------------------------------------------------------------*/ #include "softfloat-macros.h" /*---------------------------------------------------------------------------- | Variables for storing sign, exponent and significand of internal extended | double-precision floating-point value for external use. *----------------------------------------------------------------------------*/ flag floatx80_internal_sign = 0; int32_t floatx80_internal_exp = 0; uint64_t floatx80_internal_sig = 0; int32_t floatx80_internal_exp0 = 0; uint64_t floatx80_internal_sig0 = 0; uint64_t floatx80_internal_sig1 = 0; int8_t floatx80_internal_precision = 80; int8_t floatx80_internal_mode = float_round_nearest_even; /*---------------------------------------------------------------------------- | Functions for storing sign, exponent and significand of extended | double-precision floating-point intermediate result for external use. *----------------------------------------------------------------------------*/ floatx80 roundSaveFloatx80Internal( int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status ) { uint64_t roundMask, roundBits; uint64_t roundIncrement; flag increment; if ( roundingPrecision == 80 ) { goto precision80; } else if ( roundingPrecision == 64 ) { roundIncrement = LIT64( 0x0000000000000400 ); roundMask = LIT64( 0x00000000000007FF ); } else if ( roundingPrecision == 32 ) { roundIncrement = LIT64( 0x0000008000000000 ); roundMask = LIT64( 0x000000FFFFFFFFFF ); } else { goto precision80; } zSig0 |= ( zSig1 != 0 ); if ( status->float_rounding_mode != float_round_nearest_even ) { if ( status->float_rounding_mode == float_round_to_zero ) { roundIncrement = 0; } else { roundIncrement = roundMask; if ( zSign ) { if ( status->float_rounding_mode == float_round_up ) roundIncrement = 0; } else { if ( status->float_rounding_mode == float_round_down ) roundIncrement = 0; } } } roundBits = zSig0 & roundMask; zSig0 += roundIncrement; if ( zSig0 < roundIncrement ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } roundIncrement = roundMask + 1; if ( status->float_rounding_mode == float_round_nearest_even && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; if ( zSig0 == 0 ) zExp = 0; return packFloatx80( zSign, zExp, zSig0 ); precision80: increment = ( (int64_t) zSig1 < 0 ); if ( status->float_rounding_mode != float_round_nearest_even ) { if ( status->float_rounding_mode == float_round_to_zero ) { increment = 0; } else { if ( zSign ) { increment = ( status->float_rounding_mode == float_round_down ) && zSig1; } else { increment = ( status->float_rounding_mode == float_round_up ) && zSig1; } } } if ( increment ) { ++zSig0; if ( zSig0 == 0 ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } else { if ((zSig1 << 1) == 0 && status->float_rounding_mode == float_round_nearest_even) zSig0 &= ~1; } } else { if ( zSig0 == 0 ) zExp = 0; } return packFloatx80( zSign, zExp, zSig0 ); } static void saveFloatx80Internal( int8_t prec, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status ) { floatx80_internal_sign = zSign; floatx80_internal_exp = zExp; floatx80_internal_sig0 = zSig0; floatx80_internal_sig1 = zSig1; floatx80_internal_precision = prec; floatx80_internal_mode = status->float_rounding_mode; } static void saveFloat64Internal( flag zSign, int16_t zExp, uint64_t zSig, float_status *status ) { floatx80_internal_sign = zSign; floatx80_internal_exp = zExp + 0x3C01; floatx80_internal_sig0 = zSig<<1; floatx80_internal_sig1 = 0; floatx80_internal_precision = 64; floatx80_internal_mode = status->float_rounding_mode; } static void saveFloat32Internal( flag zSign, int16_t zExp, uint32_t zSig, float_status *status ) { floatx80 z = roundSaveFloatx80Internal( 32, zSign, zExp + 0x3F81, ( (uint64_t) zSig )<<33, 0, status ); floatx80_internal_sign = zSign; floatx80_internal_exp = extractFloatx80Exp( z ); floatx80_internal_sig = extractFloatx80Frac( z ); floatx80_internal_exp0 = zExp + 0x3F81; floatx80_internal_sig0 = ( (uint64_t) zSig )<<33; floatx80_internal_sig1 = 0; } /*---------------------------------------------------------------------------- | Functions for returning sign, exponent and significand of extended | double-precision floating-point intermediate result for external use. *----------------------------------------------------------------------------*/ void getRoundedFloatInternal( int8_t roundingPrecision, flag *pzSign, int32_t *pzExp, uint64_t *pzSig ) { uint64_t roundMask, roundBits; uint64_t roundIncrement; flag increment; flag zSign = floatx80_internal_sign; int32_t zExp = floatx80_internal_exp; uint64_t zSig0 = floatx80_internal_sig0; uint64_t zSig1 = floatx80_internal_sig1; if ( roundingPrecision == 80 ) { goto precision80; } else if ( roundingPrecision == 64 ) { roundIncrement = LIT64( 0x0000000000000400 ); roundMask = LIT64( 0x00000000000007FF ); } else if ( roundingPrecision == 32 ) { roundIncrement = LIT64( 0x0000008000000000 ); roundMask = LIT64( 0x000000FFFFFFFFFF ); } else { goto precision80; } zSig0 |= ( zSig1 != 0 ); if ( floatx80_internal_mode != float_round_nearest_even ) { if ( floatx80_internal_mode == float_round_to_zero ) { roundIncrement = 0; } else { roundIncrement = roundMask; if ( zSign ) { if ( floatx80_internal_mode == float_round_up ) roundIncrement = 0; } else { if ( floatx80_internal_mode == float_round_down ) roundIncrement = 0; } } } roundBits = zSig0 & roundMask; zSig0 += roundIncrement; if ( zSig0 < roundIncrement ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } roundIncrement = roundMask + 1; if ( floatx80_internal_mode == float_round_nearest_even && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; if ( zSig0 == 0 ) zExp = 0; *pzSign = zSign; *pzExp = zExp; *pzSig = zSig0; return; precision80: increment = ( (int64_t) zSig1 < 0 ); if ( floatx80_internal_mode != float_round_nearest_even ) { if ( floatx80_internal_mode == float_round_to_zero ) { increment = 0; } else { if ( zSign ) { increment = ( floatx80_internal_mode == float_round_down ) && zSig1; } else { increment = ( floatx80_internal_mode == float_round_up ) && zSig1; } } } if ( increment ) { ++zSig0; if ( zSig0 == 0 ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } else { if ((zSig1 << 1) == 0 && floatx80_internal_mode == float_round_nearest_even) zSig0 &= ~1; } } else { if ( zSig0 == 0 ) zExp = 0; } *pzSign = zSign; *pzExp = zExp; *pzSig = zSig0; } floatx80 getFloatInternalOverflow( void ) { flag zSign; int32_t zExp; uint64_t zSig; getRoundedFloatInternal( floatx80_internal_precision, &zSign, &zExp, &zSig ); if (zExp > (0x7fff + 0x6000)) { // catastrophic zExp = 0; } else { zExp -= 0x6000; } return packFloatx80( zSign, zExp, zSig ); } floatx80 getFloatInternalUnderflow( void ) { flag zSign; int32_t zExp; uint64_t zSig; getRoundedFloatInternal( floatx80_internal_precision, &zSign, &zExp, &zSig ); if (zExp < (0x0000 - 0x6000)) { // catastrophic zExp = 0; } else { zExp += 0x6000; } return packFloatx80( zSign, zExp, zSig ); } floatx80 getFloatInternalRoundedAll( void ) { flag zSign; int32_t zExp; uint64_t zSig, zSig32, zSig64, zSig80; if (floatx80_internal_precision == 80) { getRoundedFloatInternal( 80, &zSign, &zExp, &zSig80 ); zSig = zSig80; } else if (floatx80_internal_precision == 64) { getRoundedFloatInternal( 80, &zSign, &zExp, &zSig80 ); getRoundedFloatInternal( 64, &zSign, &zExp, &zSig64 ); zSig = zSig64; zSig |= zSig80 & LIT64( 0x00000000000007FF ); } else { getRoundedFloatInternal( 80, &zSign, &zExp, &zSig80 ); getRoundedFloatInternal( 64, &zSign, &zExp, &zSig64 ); getRoundedFloatInternal( 32, &zSign, &zExp, &zSig32 ); zSig = zSig32; zSig |= zSig64 & LIT64( 0x000000FFFFFFFFFF ); zSig |= zSig80 & LIT64( 0x00000000000007FF ); } return packFloatx80( zSign, zExp & 0x7FFF, zSig ); } floatx80 getFloatInternalRoundedSome( void ) { flag zSign; int32_t zExp; uint64_t zSig, zSig32, zSig64, zSig80; if (floatx80_internal_precision == 80) { getRoundedFloatInternal( 80, &zSign, &zExp, &zSig80 ); zSig = zSig80; } else if (floatx80_internal_precision == 64) { getRoundedFloatInternal( 64, &zSign, &zExp, &zSig64 ); zSig80 = floatx80_internal_sig0; if (zSig64 != (zSig80 & LIT64( 0xFFFFFFFFFFFFF800 ))) { zSig80++; } zSig = zSig64; zSig |= zSig80 & LIT64( 0x00000000000007FF ); } else { getRoundedFloatInternal( 32, &zSign, &zExp, &zSig32 ); zSig80 = floatx80_internal_sig0; if (zSig32 != (zSig80 & LIT64( 0xFFFFFF0000000000 ))) { zSig80++; } zSig = zSig32; zSig |= zSig80 & LIT64( 0x000000FFFFFFFFFF ); } return packFloatx80( zSign, zExp & 0x7FFF, zSig ); } floatx80 getFloatInternalFloatx80( void ) { flag zSign; int32_t zExp; uint64_t zSig; getRoundedFloatInternal( 80, &zSign, &zExp, &zSig ); return packFloatx80( zSign, zExp & 0x7FFF, zSig ); } floatx80 getFloatInternalUnrounded( void ) { flag zSign = floatx80_internal_sign; int32_t zExp = floatx80_internal_exp; uint64_t zSig = floatx80_internal_sig0; return packFloatx80( zSign, zExp & 0x7FFF, zSig ); } uint64_t getFloatInternalGRS( void ) { #if 1 if (floatx80_internal_sig1) return 5; if (floatx80_internal_precision == 64 && floatx80_internal_sig0 & LIT64( 0x00000000000007FF )) { return 1; } if (floatx80_internal_precision == 32 && floatx80_internal_sig0 & LIT64( 0x000000FFFFFFFFFF )) { return 1; } return 0; #else uint64_t roundbits; shift64RightJamming(floatx80_internal_sig1, 61, &roundbits); return roundbits; #endif } /*---------------------------------------------------------------------------- | Functions and definitions to determine: (1) whether tininess for underflow | is detected before or after rounding by default, (2) what (if anything) | happens when exceptions are raised, (3) how signaling NaNs are distinguished | from quiet NaNs, (4) the default generated quiet NaNs, and (5) how NaNs | are propagated from function inputs to output. These details are target- | specific. *----------------------------------------------------------------------------*/ #include "softfloat-specialize.h" /*---------------------------------------------------------------------------- | Takes a 64-bit fixed-point value `absZ' with binary point between bits 6 | and 7, and returns the properly rounded 32-bit integer corresponding to the | input. If `zSign' is 1, the input is negated before being converted to an | integer. Bit 63 of `absZ' must be zero. Ordinarily, the fixed-point input | is simply rounded to an integer, with the inexact exception raised if the | input cannot be represented exactly as an integer. However, if the fixed- | point input is too large, the invalid exception is raised and the largest | positive or negative integer is returned. *----------------------------------------------------------------------------*/ static int32_t roundAndPackInt32(flag zSign, uint64_t absZ, float_status *status) { int8_t roundingMode; flag roundNearestEven; int8_t roundIncrement, roundBits; int32_t z; roundingMode = status->float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: roundIncrement = 0x40; break; case float_round_to_zero: roundIncrement = 0; break; case float_round_up: roundIncrement = zSign ? 0 : 0x7f; break; case float_round_down: roundIncrement = zSign ? 0x7f : 0; break; default: abort(); } roundBits = absZ & 0x7F; absZ = ( absZ + roundIncrement )>>7; absZ &= ~ ( ( ( roundBits ^ 0x40 ) == 0 ) & roundNearestEven ); z = (int32_t)absZ; if ( zSign ) z = - z; if ( ( absZ>>32 ) || ( z && ( ( z < 0 ) ^ zSign ) ) ) { float_raise(float_flag_invalid, status); return zSign ? (int32_t) 0x80000000 : 0x7FFFFFFF; } if (roundBits) { status->float_exception_flags |= float_flag_inexact; } return z; } #ifdef SOFTFLOAT_68K // 30-01-2017: Added for Previous static int16_t roundAndPackInt16( flag zSign, uint64_t absZ, float_status *status ) { int8_t roundingMode; flag roundNearestEven; int8_t roundIncrement, roundBits; int16_t z; roundingMode = status->float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); roundIncrement = 0x40; if ( ! roundNearestEven ) { if ( roundingMode == float_round_to_zero ) { roundIncrement = 0; } else { roundIncrement = 0x7F; if ( zSign ) { if ( roundingMode == float_round_up ) roundIncrement = 0; } else { if ( roundingMode == float_round_down ) roundIncrement = 0; } } } roundBits = absZ & 0x7F; absZ = ( absZ + roundIncrement )>>7; absZ &= ~ ( ( ( roundBits ^ 0x40 ) == 0 ) & roundNearestEven ); z = (int16_t)absZ; if ( zSign ) z = - z; z = (int16_t) z; if ( ( absZ>>16 ) || ( z && ( ( z < 0 ) ^ zSign ) ) ) { float_raise( float_flag_invalid, status ); return zSign ? (int16_t) 0x8000 : 0x7FFF; } if ( roundBits ) status->float_exception_flags |= float_flag_inexact; return z; } static int8_t roundAndPackInt8( flag zSign, uint64_t absZ, float_status *status ) { int8_t roundingMode; flag roundNearestEven; int8_t roundIncrement, roundBits; int8_t z; roundingMode = status->float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); roundIncrement = 0x40; if ( ! roundNearestEven ) { if ( roundingMode == float_round_to_zero ) { roundIncrement = 0; } else { roundIncrement = 0x7F; if ( zSign ) { if ( roundingMode == float_round_up ) roundIncrement = 0; } else { if ( roundingMode == float_round_down ) roundIncrement = 0; } } } roundBits = absZ & 0x7F; absZ = ( absZ + roundIncrement )>>7; absZ &= ~ ( ( ( roundBits ^ 0x40 ) == 0 ) & roundNearestEven ); z = (int8_t)absZ; if ( zSign ) z = - z; z = (int8_t) z; if ( ( absZ>>8 ) || ( z && ( ( z < 0 ) ^ zSign ) ) ) { float_raise( float_flag_invalid, status ); return zSign ? (int8_t) 0x80 : 0x7F; } if ( roundBits ) status->float_exception_flags |= float_flag_inexact; return z; } #endif // End of addition for Previous /*---------------------------------------------------------------------------- | Takes the 128-bit fixed-point value formed by concatenating `absZ0' and | `absZ1', with binary point between bits 63 and 64 (between the input words), | and returns the properly rounded 64-bit integer corresponding to the input. | If `zSign' is 1, the input is negated before being converted to an integer. | Ordinarily, the fixed-point input is simply rounded to an integer, with | the inexact exception raised if the input cannot be represented exactly as | an integer. However, if the fixed-point input is too large, the invalid | exception is raised and the largest positive or negative integer is | returned. *----------------------------------------------------------------------------*/ static int64_t roundAndPackInt64(flag zSign, uint64_t absZ0, uint64_t absZ1, float_status *status) { int8_t roundingMode; flag roundNearestEven, increment; int64_t z; roundingMode = status->float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: increment = ((int64_t) absZ1 < 0); break; case float_round_to_zero: increment = 0; break; case float_round_up: increment = !zSign && absZ1; break; case float_round_down: increment = zSign && absZ1; break; default: abort(); } if ( increment ) { ++absZ0; if ( absZ0 == 0 ) goto overflow; absZ0 &= ~ ( ( (uint64_t) ( absZ1<<1 ) == 0 ) & roundNearestEven ); } z = absZ0; if ( zSign ) z = - z; if ( z && ( ( z < 0 ) ^ zSign ) ) { overflow: float_raise(float_flag_invalid, status); return zSign ? (int64_t) LIT64( 0x8000000000000000 ) : LIT64( 0x7FFFFFFFFFFFFFFF ); } if (absZ1) { status->float_exception_flags |= float_flag_inexact; } return z; } /*---------------------------------------------------------------------------- | Returns the fraction bits of the single-precision floating-point value `a'. *----------------------------------------------------------------------------*/ static inline uint32_t extractFloat32Frac( float32 a ) { return float32_val(a) & 0x007FFFFF; } /*---------------------------------------------------------------------------- | Returns the exponent bits of the single-precision floating-point value `a'. *----------------------------------------------------------------------------*/ static inline int extractFloat32Exp(float32 a) { return ( float32_val(a)>>23 ) & 0xFF; } /*---------------------------------------------------------------------------- | Returns the sign bit of the single-precision floating-point value `a'. *----------------------------------------------------------------------------*/ static inline flag extractFloat32Sign( float32 a ) { return float32_val(a)>>31; } /*---------------------------------------------------------------------------- | Normalizes the subnormal single-precision floating-point value represented | by the denormalized significand `aSig'. The normalized exponent and | significand are stored at the locations pointed to by `zExpPtr' and | `zSigPtr', respectively. *----------------------------------------------------------------------------*/ static void normalizeFloat32Subnormal(uint32_t aSig, int *zExpPtr, uint32_t *zSigPtr) { int8_t shiftCount; shiftCount = countLeadingZeros32( aSig ) - 8; *zSigPtr = aSig<float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: roundIncrement = 0x40; break; case float_round_to_zero: roundIncrement = 0; break; case float_round_up: roundIncrement = zSign ? 0 : 0x7f; break; case float_round_down: roundIncrement = zSign ? 0x7f : 0; break; default: abort(); break; } roundBits = zSig & 0x7F; if ( 0xFD <= (uint16_t) zExp ) { if ( ( 0xFD < zExp ) || ( ( zExp == 0xFD ) && ( (int32_t) ( zSig + roundIncrement ) < 0 ) ) ) { #ifdef SOFTFLOAT_68K float_raise( float_flag_overflow, status ); saveFloat32Internal( zSign, zExp, zSig, status ); if ( roundBits ) float_raise( float_flag_inexact, status ); #else float_raise(float_flag_overflow | float_flag_inexact, status); #endif return packFloat32( zSign, 0xFF, - ( roundIncrement == 0 )); } if ( zExp < 0 ) { if (status->flush_to_zero) { //float_raise(float_flag_output_denormal, status); return packFloat32(zSign, 0, 0); } isTiny = (status->float_detect_tininess == float_tininess_before_rounding) || ( zExp < -1 ) || ( zSig + roundIncrement < 0x80000000 ); #ifdef SOFTFLOAT_68K if ( isTiny ) { float_raise( float_flag_underflow, status ); saveFloat32Internal( zSign, zExp, zSig, status ); } #endif shift32RightJamming( zSig, - zExp, &zSig ); zExp = 0; roundBits = zSig & 0x7F; #ifndef SOFTFLOAT_68K if (isTiny && roundBits) float_raise(float_flag_underflow, status); #endif } } if (roundBits) { status->float_exception_flags |= float_flag_inexact; } zSig = ( zSig + roundIncrement )>>7; zSig &= ~ ( ( ( roundBits ^ 0x40 ) == 0 ) & roundNearestEven ); if ( zSig == 0 ) zExp = 0; return packFloat32( zSign, zExp, zSig ); } /*---------------------------------------------------------------------------- | Returns the fraction bits of the double-precision floating-point value `a'. *----------------------------------------------------------------------------*/ static inline uint64_t extractFloat64Frac( float64 a ) { return float64_val(a) & LIT64( 0x000FFFFFFFFFFFFF ); } /*---------------------------------------------------------------------------- | Returns the exponent bits of the double-precision floating-point value `a'. *----------------------------------------------------------------------------*/ static inline int extractFloat64Exp(float64 a) { return ( float64_val(a)>>52 ) & 0x7FF; } /*---------------------------------------------------------------------------- | Returns the sign bit of the double-precision floating-point value `a'. *----------------------------------------------------------------------------*/ static inline flag extractFloat64Sign( float64 a ) { return float64_val(a)>>63; } /*---------------------------------------------------------------------------- | If `a' is denormal and we are in flush-to-zero mode then set the | input-denormal exception and return zero. Otherwise just return the value. *----------------------------------------------------------------------------*/ float64 float64_squash_input_denormal(float64 a, float_status *status) { if (status->flush_inputs_to_zero) { if (extractFloat64Exp(a) == 0 && extractFloat64Frac(a) != 0) { //float_raise(float_flag_input_denormal, status); return make_float64(float64_val(a) & (1ULL << 63)); } } return a; } /*---------------------------------------------------------------------------- | Normalizes the subnormal double-precision floating-point value represented | by the denormalized significand `aSig'. The normalized exponent and | significand are stored at the locations pointed to by `zExpPtr' and | `zSigPtr', respectively. *----------------------------------------------------------------------------*/ static void normalizeFloat64Subnormal(uint64_t aSig, int *zExpPtr, uint64_t *zSigPtr) { int8_t shiftCount; shiftCount = countLeadingZeros64( aSig ) - 11; *zSigPtr = aSig<float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: roundIncrement = 0x200; break; case float_round_to_zero: roundIncrement = 0; break; case float_round_up: roundIncrement = zSign ? 0 : 0x3ff; break; case float_round_down: roundIncrement = zSign ? 0x3ff : 0; break; default: abort(); } roundBits = zSig & 0x3FF; if ( 0x7FD <= (uint16_t) zExp ) { if ( ( 0x7FD < zExp ) || ( ( zExp == 0x7FD ) && ( (int64_t) ( zSig + roundIncrement ) < 0 ) ) ) { #ifdef SOFTFLOAT_68K float_raise( float_flag_overflow, status ); saveFloat64Internal( zSign, zExp, zSig, status ); if ( roundBits ) float_raise( float_flag_inexact, status ); #else float_raise(float_flag_overflow | float_flag_inexact, status); #endif return packFloat64( zSign, 0x7FF, - ( roundIncrement == 0 )); } if ( zExp < 0 ) { if (status->flush_to_zero) { //float_raise(float_flag_output_denormal, status); return packFloat64(zSign, 0, 0); } isTiny = (status->float_detect_tininess == float_tininess_before_rounding) || ( zExp < -1 ) || ( zSig + roundIncrement < LIT64( 0x8000000000000000 ) ); #ifdef SOFTFLOAT_68K if ( isTiny ) { float_raise( float_flag_underflow, status ); saveFloat64Internal( zSign, zExp, zSig, status ); } #endif shift64RightJamming( zSig, - zExp, &zSig ); zExp = 0; roundBits = zSig & 0x3FF; #ifndef SOFTFLOAT_68K if (isTiny && roundBits) float_raise(float_flag_underflow, status); #endif } } if (roundBits) { status->float_exception_flags |= float_flag_inexact; } zSig = ( zSig + roundIncrement )>>10; zSig &= ~ ( ( ( roundBits ^ 0x200 ) == 0 ) & roundNearestEven ); if ( zSig == 0 ) zExp = 0; return packFloat64( zSign, zExp, zSig ); } /*---------------------------------------------------------------------------- | Returns the fraction bits of the extended double-precision floating-point | value `a'. *----------------------------------------------------------------------------*/ uint64_t extractFloatx80Frac( floatx80 a ) { return a.low; } /*---------------------------------------------------------------------------- | Returns the exponent bits of the extended double-precision floating-point | value `a'. *----------------------------------------------------------------------------*/ int32_t extractFloatx80Exp( floatx80 a ) { return a.high & 0x7FFF; } /*---------------------------------------------------------------------------- | Returns the sign bit of the extended double-precision floating-point value | `a'. *----------------------------------------------------------------------------*/ flag extractFloatx80Sign( floatx80 a ) { return a.high>>15; } /*---------------------------------------------------------------------------- | Normalizes the subnormal extended double-precision floating-point value | represented by the denormalized significand `aSig'. The normalized exponent | and significand are stored at the locations pointed to by `zExpPtr' and | `zSigPtr', respectively. *----------------------------------------------------------------------------*/ void normalizeFloatx80Subnormal( uint64_t aSig, int32_t *zExpPtr, uint64_t *zSigPtr ) { int8_t shiftCount; shiftCount = countLeadingZeros64( aSig ); *zSigPtr = aSig<float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); if ( roundingPrecision == 80 ) goto precision80; if ( roundingPrecision == 64 ) { roundIncrement = LIT64( 0x0000000000000400 ); roundMask = LIT64( 0x00000000000007FF ); } else if ( roundingPrecision == 32 ) { roundIncrement = LIT64( 0x0000008000000000 ); roundMask = LIT64( 0x000000FFFFFFFFFF ); } else { goto precision80; } zSig0 |= ( zSig1 != 0 ); switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: break; case float_round_to_zero: roundIncrement = 0; break; case float_round_up: roundIncrement = zSign ? 0 : roundMask; break; case float_round_down: roundIncrement = zSign ? roundMask : 0; break; default: abort(); } roundBits = zSig0 & roundMask; #ifdef SOFTFLOAT_68K if ( 0x7FFE <= (uint32_t) zExp ) { #else if ( 0x7FFD <= (uint32_t) ( zExp - 1 ) ) { #endif if ( ( 0x7FFE < zExp ) || ( ( zExp == 0x7FFE ) && ( zSig0 + roundIncrement < zSig0 ) ) ) { goto overflow; } #ifdef SOFTFLOAT_68K if ( zExp < 0 ) { #else if ( zExp <= 0 ) { #endif if (status->flush_to_zero) { //float_raise(float_flag_output_denormal, status); return packFloatx80(zSign, 0, 0); } isTiny = (status->float_detect_tininess == float_tininess_before_rounding) #ifdef SOFTFLOAT_68K || ( zExp < -1 ) #else || ( zExp < 0 ) #endif || ( zSig0 <= zSig0 + roundIncrement ); #ifdef SOFTFLOAT_68K if ( isTiny ) { float_raise( float_flag_underflow, status ); saveFloatx80Internal( zSign, zExp, zSig0, zSig1, status ); } shift64RightJamming( zSig0, -zExp, &zSig0 ); #else shift64RightJamming( zSig0, 1 - zExp, &zSig0 ); #endif zExp = 0; roundBits = zSig0 & roundMask; #ifdef SOFTFLOAT_68K if ( isTiny ) float_raise( float_flag_underflow, status ); #else if (isTiny && roundBits) { float_raise(float_flag_underflow, status); } #endif if (roundBits) { status->float_exception_flags |= float_flag_inexact; } zSig0 += roundIncrement; #ifndef SOFTFLOAT_68K if ( (int64_t) zSig0 < 0 ) zExp = 1; #endif roundIncrement = roundMask + 1; if ( roundNearestEven && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; return packFloatx80( zSign, zExp, zSig0 ); } } if (roundBits) { status->float_exception_flags |= float_flag_inexact; } zSig0 += roundIncrement; if ( zSig0 < roundIncrement ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } roundIncrement = roundMask + 1; if ( roundNearestEven && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; if ( zSig0 == 0 ) zExp = 0; return packFloatx80( zSign, zExp, zSig0 ); precision80: switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: increment = ((int64_t)zSig1 < 0); break; case float_round_to_zero: increment = 0; break; case float_round_up: increment = !zSign && zSig1; break; case float_round_down: increment = zSign && zSig1; break; default: abort(); } #ifdef SOFTFLOAT_68K if ( 0x7FFE <= (uint32_t) zExp ) { #else if ( 0x7FFD <= (uint32_t) ( zExp - 1 ) ) { #endif if ( ( 0x7FFE < zExp ) || ( ( zExp == 0x7FFE ) && ( zSig0 == LIT64( 0xFFFFFFFFFFFFFFFF ) ) && increment ) ) { roundMask = 0; overflow: #ifndef SOFTFLOAT_68K float_raise(float_flag_overflow | float_flag_inexact, status); #else float_raise( float_flag_overflow, status ); saveFloatx80Internal( zSign, zExp, zSig0, zSig1, status ); if ( ( zSig0 & roundMask ) || zSig1 ) float_raise( float_flag_inexact, status ); #endif if ( ( roundingMode == float_round_to_zero ) || ( zSign && ( roundingMode == float_round_up ) ) || ( ! zSign && ( roundingMode == float_round_down ) ) ) { return packFloatx80( zSign, 0x7FFE, ~ roundMask ); } return packFloatx80( zSign, 0x7FFF, floatx80_default_infinity_low ); } #ifdef SOFTFLOAT_68K if ( zExp < 0 ) { #else if ( zExp <= 0 ) { #endif isTiny = (status->float_detect_tininess == float_tininess_before_rounding) #ifdef SOFTFLOAT_68K || ( zExp < -1 ) #else || ( zExp < 0 ) #endif || ! increment || ( zSig0 < LIT64( 0xFFFFFFFFFFFFFFFF ) ); #ifdef SOFTFLOAT_68K if ( isTiny ) { float_raise( float_flag_underflow, status ); saveFloatx80Internal( zSign, zExp, zSig0, zSig1, status ); } shift64ExtraRightJamming( zSig0, zSig1, -zExp, &zSig0, &zSig1 ); #else shift64ExtraRightJamming( zSig0, zSig1, 1 - zExp, &zSig0, &zSig1 ); #endif zExp = 0; #ifndef SOFTFLOAT_68K if ( isTiny && zSig1 ) float_raise( float_flag_underflow, status ); #endif if (zSig1) float_raise(float_flag_inexact, status); switch (roundingMode) { case float_round_nearest_even: case float_round_ties_away: increment = ((int64_t)zSig1 < 0); break; case float_round_to_zero: increment = 0; break; case float_round_up: increment = !zSign && zSig1; break; case float_round_down: increment = zSign && zSig1; break; default: abort(); } if ( increment ) { ++zSig0; zSig0 &= ~ ( ( (uint64_t) ( zSig1<<1 ) == 0 ) & roundNearestEven ); #ifndef SOFTFLOAT_68K if ( (int64_t) zSig0 < 0 ) zExp = 1; #endif } return packFloatx80( zSign, zExp, zSig0 ); } } if (zSig1) { status->float_exception_flags |= float_flag_inexact; } if ( increment ) { ++zSig0; if ( zSig0 == 0 ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } else { zSig0 &= ~ ( ( (uint64_t) ( zSig1<<1 ) == 0 ) & roundNearestEven ); } } else { if ( zSig0 == 0 ) zExp = 0; } return packFloatx80( zSign, zExp, zSig0 ); } #else // SOFTFLOAT_68K floatx80 roundAndPackFloatx80( int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status ) { int8_t roundingMode; flag roundNearestEven, increment; uint64_t roundMask, roundBits; uint64_t roundIncrement; int32_t expOffset; roundingMode = status->float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); if ( roundingPrecision == 80 ) goto precision80; if ( roundingPrecision == 64 ) { roundIncrement = LIT64( 0x0000000000000400 ); roundMask = LIT64( 0x00000000000007FF ); expOffset = 0x3C00; } else if ( roundingPrecision == 32 ) { roundIncrement = LIT64( 0x0000008000000000 ); roundMask = LIT64( 0x000000FFFFFFFFFF ); expOffset = 0x3F80; } else { goto precision80; } zSig0 |= ( zSig1 != 0 ); if ( ! roundNearestEven ) { if ( roundingMode == float_round_to_zero ) { roundIncrement = 0; } else { roundIncrement = roundMask; if ( zSign ) { if ( roundingMode == float_round_up ) roundIncrement = 0; } else { if ( roundingMode == float_round_down ) roundIncrement = 0; } } } roundBits = zSig0 & roundMask; if ( ( ( 0x7FFE - expOffset ) < zExp ) || ( ( zExp == ( 0x7FFE - expOffset ) ) && ( zSig0 + roundIncrement < zSig0 ) ) ) { float_raise( float_flag_overflow, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status ); if ( zSig0 & roundMask ) float_raise( float_flag_inexact, status ); if ( ( roundingMode == float_round_to_zero ) || ( zSign && ( roundingMode == float_round_up ) ) || ( ! zSign && ( roundingMode == float_round_down ) ) ) { return packFloatx80( zSign, 0x7FFE - expOffset, ~ roundMask ); } return packFloatx80( zSign, 0x7FFF, floatx80_default_infinity_low ); } if ( zExp < ( expOffset + 1 ) ) { float_raise( float_flag_underflow, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status ); shift64RightJamming( zSig0, -( zExp - ( expOffset + 1 ) ), &zSig0 ); zExp = expOffset + 1; roundBits = zSig0 & roundMask; if ( roundBits ) float_raise( float_flag_inexact, status ); zSig0 += roundIncrement; roundIncrement = roundMask + 1; if ( roundNearestEven && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; return packFloatx80( zSign, zExp, zSig0 ); } if ( roundBits ) { float_raise( float_flag_inexact, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status); } zSig0 += roundIncrement; if ( zSig0 < roundIncrement ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } roundIncrement = roundMask + 1; if ( roundNearestEven && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; if ( zSig0 == 0 ) zExp = 0; return packFloatx80( zSign, zExp, zSig0 ); precision80: increment = ( (int64_t) zSig1 < 0 ); if ( ! roundNearestEven ) { if ( roundingMode == float_round_to_zero ) { increment = 0; } else { if ( zSign ) { increment = ( roundingMode == float_round_down ) && zSig1; } else { increment = ( roundingMode == float_round_up ) && zSig1; } } } if ( 0x7FFE <= (uint32_t) zExp ) { if ( ( 0x7FFE < zExp ) || ( ( zExp == 0x7FFE ) && ( zSig0 == LIT64( 0xFFFFFFFFFFFFFFFF ) ) && increment ) ) { roundMask = 0; float_raise( float_flag_overflow, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status ); if ( ( zSig0 & roundMask ) || zSig1 ) float_raise( float_flag_inexact, status ); if ( ( roundingMode == float_round_to_zero ) || ( zSign && ( roundingMode == float_round_up ) ) || ( ! zSign && ( roundingMode == float_round_down ) ) ) { return packFloatx80( zSign, 0x7FFE, ~ roundMask ); } return packFloatx80( zSign, 0x7FFF, floatx80_default_infinity_low ); } if ( zExp < 0 ) { float_raise( float_flag_underflow, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status); shift64ExtraRightJamming( zSig0, zSig1, -zExp, &zSig0, &zSig1 ); zExp = 0; if ( zSig1 ) float_raise( float_flag_inexact, status ); if ( roundNearestEven ) { increment = ( (int64_t) zSig1 < 0 ); } else { if ( zSign ) { increment = ( roundingMode == float_round_down ) && zSig1; } else { increment = ( roundingMode == float_round_up ) && zSig1; } } if ( increment ) { ++zSig0; zSig0 &= ~ ( ( (uint64_t) ( zSig1<<1 ) == 0 ) & roundNearestEven ); } return packFloatx80( zSign, zExp, zSig0 ); } } if ( zSig1 ) { float_raise( float_flag_inexact, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status ); } if ( increment ) { ++zSig0; if ( zSig0 == 0 ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } else { zSig0 &= ~ ( ( (uint64_t) ( zSig1<<1 ) == 0 ) & roundNearestEven ); } } else { if ( zSig0 == 0 ) zExp = 0; } return packFloatx80( zSign, zExp, zSig0 ); } #endif #ifdef SOFTFLOAT_68K // 21-01-2017: Added for Previous floatx80 roundSigAndPackFloatx80( int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status ) { int8_t roundingMode; flag roundNearestEven, isTiny; uint64_t roundMask, roundBits; uint64_t roundIncrement; roundingMode = status->float_rounding_mode; roundNearestEven = ( roundingMode == float_round_nearest_even ); if ( roundingPrecision == 32 ) { roundIncrement = LIT64( 0x0000008000000000 ); roundMask = LIT64( 0x000000FFFFFFFFFF ); } else if ( roundingPrecision == 64 ) { roundIncrement = LIT64( 0x0000000000000400 ); roundMask = LIT64( 0x00000000000007FF ); } else { return roundAndPackFloatx80( 80, zSign, zExp, zSig0, zSig1, status ); } zSig0 |= ( zSig1 != 0 ); if ( ! roundNearestEven ) { if ( roundingMode == float_round_to_zero ) { roundIncrement = 0; } else { roundIncrement = roundMask; if ( zSign ) { if ( roundingMode == float_round_up ) roundIncrement = 0; } else { if ( roundingMode == float_round_down ) roundIncrement = 0; } } } roundBits = zSig0 & roundMask; if ( 0x7FFE <= (uint32_t) zExp ) { if ( ( 0x7FFE < zExp ) || ( ( zExp == 0x7FFE ) && ( zSig0 + roundIncrement < zSig0 ) ) ) { float_raise( float_flag_overflow, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status); if ( zSig0 & roundMask ) float_raise( float_flag_inexact, status ); if ( ( roundingMode == float_round_to_zero ) || ( zSign && ( roundingMode == float_round_up ) ) || ( ! zSign && ( roundingMode == float_round_down ) ) ) { return packFloatx80( zSign, 0x7FFE, LIT64( 0xFFFFFFFFFFFFFFFF ) ); } return packFloatx80( zSign, 0x7FFF, floatx80_default_infinity_low ); } if ( zExp < 0 ) { isTiny = ( status->float_detect_tininess == float_tininess_before_rounding ) || ( zExp < -1 ) || ( zSig0 <= zSig0 + roundIncrement ); if ( isTiny ) { float_raise( float_flag_underflow, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status ); } shift64RightJamming( zSig0, -zExp, &zSig0 ); zExp = 0; roundBits = zSig0 & roundMask; if ( roundBits ) float_raise ( float_flag_inexact, status ); zSig0 += roundIncrement; if ( roundNearestEven && ( roundBits == roundIncrement ) ) { roundMask |= roundIncrement<<1; } zSig0 &= ~roundMask; return packFloatx80( zSign, zExp, zSig0 ); } } if ( roundBits ) { float_raise( float_flag_inexact, status ); saveFloatx80Internal( roundingPrecision, zSign, zExp, zSig0, zSig1, status ); } zSig0 += roundIncrement; if ( zSig0 < roundIncrement ) { ++zExp; zSig0 = LIT64( 0x8000000000000000 ); } roundIncrement = roundMask + 1; if ( roundNearestEven && ( roundBits<<1 == roundIncrement ) ) { roundMask |= roundIncrement; } zSig0 &= ~ roundMask; if ( zSig0 == 0 ) zExp = 0; return packFloatx80( zSign, zExp, zSig0 ); } #endif // End of Addition for Previous /*---------------------------------------------------------------------------- | Takes an abstract floating-point value having sign `zSign', exponent | `zExp', and significand formed by the concatenation of `zSig0' and `zSig1', | and returns the proper extended double-precision floating-point value | corresponding to the abstract input. This routine is just like | `roundAndPackFloatx80' except that the input significand does not have to be | normalized. *----------------------------------------------------------------------------*/ static floatx80 normalizeRoundAndPackFloatx80(int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status) { int8_t shiftCount; if ( zSig0 == 0 ) { zSig0 = zSig1; zSig1 = 0; zExp -= 64; } shiftCount = countLeadingZeros64( zSig0 ); shortShift128Left( zSig0, zSig1, shiftCount, &zSig0, &zSig1 ); zExp -= shiftCount; return roundAndPackFloatx80(roundingPrecision, zSign, zExp, zSig0, zSig1, status); } /*---------------------------------------------------------------------------- | Returns the result of converting the 32-bit two's complement integer `a' | to the extended double-precision floating-point format. The conversion | is performed according to the IEC/IEEE Standard for Binary Floating-Point | Arithmetic. *----------------------------------------------------------------------------*/ floatx80 int32_to_floatx80(int32_t a) { flag zSign; uint32_t absA; int8_t shiftCount; uint64_t zSig; if ( a == 0 ) return packFloatx80( 0, 0, 0 ); zSign = ( a < 0 ); absA = zSign ? - a : a; shiftCount = countLeadingZeros32( absA ) + 32; zSig = absA; return packFloatx80( zSign, 0x403E - shiftCount, zSig<>32); } float_raise( float_flag_invalid, status ); return aSign ? (int32_t) 0x80000000 : 0x7FFFFFFF; } #else if ( ( aExp == 0x7FFF ) && (uint64_t) ( aSig<<1 ) ) aSign = 0; #endif shiftCount = 0x4037 - aExp; if ( shiftCount <= 0 ) shiftCount = 1; shift64RightJamming( aSig, shiftCount, &aSig ); return roundAndPackInt32(aSign, aSig, status); } #ifdef SOFTFLOAT_68K // 30-01-2017: Addition for Previous int16_t floatx80_to_int16( floatx80 a, float_status *status) { flag aSign; int32_t aExp, shiftCount; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { float_raise( float_flag_invalid, status ); if ( (uint64_t) ( aSig<<1 ) ) { a = propagateFloatx80NaNOneArg( a, status ); if ( a.low == aSig ) float_raise( float_flag_invalid, status ); return (int16_t)(a.low>>48); } return aSign ? (int16_t) 0x8000 : 0x7FFF; } shiftCount = 0x4037 - aExp; if ( shiftCount <= 0 ) shiftCount = 1; shift64RightJamming( aSig, shiftCount, &aSig ); return roundAndPackInt16( aSign, aSig, status ); } int8_t floatx80_to_int8( floatx80 a, float_status *status) { flag aSign; int32_t aExp, shiftCount; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) { a = propagateFloatx80NaNOneArg( a, status ); if ( a.low == aSig ) float_raise( float_flag_invalid, status ); return (int8_t)(a.low>>56); } float_raise( float_flag_invalid, status ); return aSign ? (int8_t) 0x80 : 0x7F; } shiftCount = 0x4037 - aExp; if ( shiftCount <= 0 ) shiftCount = 1; shift64RightJamming( aSig, shiftCount, &aSig ); return roundAndPackInt8( aSign, aSig, status ); } #endif // End of addition for Previous /*---------------------------------------------------------------------------- | Returns the result of converting the extended double-precision floating- | point value `a' to the 32-bit two's complement integer format. The | conversion is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic, except that the conversion is always rounded | toward zero. If `a' is a NaN, the largest positive integer is returned. | Otherwise, if the conversion overflows, the largest integer with the same | sign as `a' is returned. *----------------------------------------------------------------------------*/ int32_t floatx80_to_int32_round_to_zero(floatx80 a, float_status *status) { flag aSign; int32_t aExp, shiftCount; uint64_t aSig, savedASig; int32_t z; if (floatx80_invalid_encoding(a)) { float_raise(float_flag_invalid, status); return 1 << 31; } aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( 0x401E < aExp ) { if ( ( aExp == 0x7FFF ) && (uint64_t) ( aSig<<1 ) ) aSign = 0; goto invalid; } else if ( aExp < 0x3FFF ) { if (aExp || aSig) { status->float_exception_flags |= float_flag_inexact; } return 0; } shiftCount = 0x403E - aExp; savedASig = aSig; aSig >>= shiftCount; z = (int32_t)aSig; if ( aSign ) z = - z; if ( ( z < 0 ) ^ aSign ) { invalid: float_raise(float_flag_invalid, status); return aSign ? (int32_t) 0x80000000 : 0x7FFFFFFF; } if ( ( aSig<float_exception_flags |= float_flag_inexact; } return z; } /*---------------------------------------------------------------------------- | Returns the result of converting the extended double-precision floating- | point value `a' to the 64-bit two's complement integer format. The | conversion is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic---which means in particular that the conversion | is rounded according to the current rounding mode. If `a' is a NaN, | the largest positive integer is returned. Otherwise, if the conversion | overflows, the largest integer with the same sign as `a' is returned. *----------------------------------------------------------------------------*/ int64_t floatx80_to_int64(floatx80 a, float_status *status) { flag aSign; int32_t aExp, shiftCount; uint64_t aSig, aSigExtra; if (floatx80_invalid_encoding(a)) { float_raise(float_flag_invalid, status); return 1ULL << 63; } aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); shiftCount = 0x403E - aExp; if ( shiftCount <= 0 ) { if ( shiftCount ) { float_raise(float_flag_invalid, status); if ( ! aSign || ( ( aExp == 0x7FFF ) && ( aSig != LIT64( 0x8000000000000000 ) ) ) ) { return LIT64( 0x7FFFFFFFFFFFFFFF ); } return (int64_t) LIT64( 0x8000000000000000 ); } aSigExtra = 0; } else { shift64ExtraRightJamming( aSig, 0, shiftCount, &aSig, &aSigExtra ); } return roundAndPackInt64(aSign, aSig, aSigExtra, status); } /*---------------------------------------------------------------------------- | Returns the result of converting the extended double-precision floating- | point value `a' to the single-precision floating-point format. The | conversion is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ float32 floatx80_to_float32(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) { return commonNaNToFloat32(floatx80ToCommonNaN(a, status)); } return packFloat32( aSign, 0xFF, 0 ); } #ifdef SOFTFLOAT_68K if ( aExp == 0 ) { if ( aSig == 0) return packFloat32( aSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } shift64RightJamming( aSig, 33, &aSig ); aExp -= 0x3F81; #else shift64RightJamming( aSig, 33, &aSig ); if ( aExp || aSig ) aExp -= 0x3F81; #endif return roundAndPackFloat32(aSign, aExp, (uint32_t)aSig, status); } /*---------------------------------------------------------------------------- | Returns the result of converting the extended double-precision floating- | point value `a' to the double-precision floating-point format. The | conversion is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ float64 floatx80_to_float64(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig, zSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) { return commonNaNToFloat64(floatx80ToCommonNaN(a, status), status); } return packFloat64( aSign, 0x7FF, 0 ); } #ifdef SOFTFLOAT_68K if ( aExp == 0 ) { if ( aSig == 0) return packFloat64( aSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } shift64RightJamming( aSig, 1, &zSig ); aExp -= 0x3C01; #else shift64RightJamming( aSig, 1, &zSig ); if ( aExp || aSig ) aExp -= 0x3C01; #endif return roundAndPackFloat64(aSign, aExp, zSig, status); } #ifdef SOFTFLOAT_68K // 31-01-2017 /*---------------------------------------------------------------------------- | Returns the result of converting the extended double-precision floating- | point value `a' to the extended double-precision floating-point format. | The conversion is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_to_floatx80( floatx80 a, float_status *status ) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF && (uint64_t) ( aSig<<1 ) ) { return propagateFloatx80NaNOneArg( a, status ); } if ( aExp == 0 && aSig != 0 ) { return normalizeRoundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status ); } return a; } #endif #ifdef SOFTFLOAT_68K // 30-01-2016: Added for Previous floatx80 floatx80_round32( floatx80 a, float_status *status ) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF || aSig == 0 ) { return a; } if ( aExp == 0 ) { normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return roundSigAndPackFloatx80( 32, aSign, aExp, aSig, 0, status ); } floatx80 floatx80_round64( floatx80 a, float_status *status ) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF || aSig == 0 ) { return a; } if ( aExp == 0 ) { normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return roundSigAndPackFloatx80( 64, aSign, aExp, aSig, 0, status ); } floatx80 floatx80_round_to_float32( floatx80 a, float_status *status ) { flag aSign; int32_t aExp; uint64_t aSig; aSign = extractFloatx80Sign( a ); aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); return a; } if ( aExp == 0 ) { if ( aSig == 0 ) return a; normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return roundAndPackFloatx80( 32, aSign, aExp, aSig, 0, status ); } floatx80 floatx80_round_to_float64( floatx80 a, float_status *status ) { flag aSign; int32_t aExp; uint64_t aSig; aSign = extractFloatx80Sign( a ); aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); return a; } if ( aExp == 0 ) { if ( aSig == 0 ) return a; normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return roundAndPackFloatx80( 64, aSign, aExp, aSig, 0, status ); } floatx80 floatx80_normalize( floatx80 a ) { flag aSign; int16_t aExp; uint64_t aSig; int8_t shiftCount; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF || aExp == 0 ) return a; if ( aSig == 0 ) return packFloatx80(aSign, 0, 0); shiftCount = countLeadingZeros64( aSig ); if ( shiftCount > aExp ) shiftCount = (int8_t)aExp; aExp -= shiftCount; aSig <<= shiftCount; return packFloatx80( aSign, aExp, aSig ); } #endif // end of addition for Previous /*---------------------------------------------------------------------------- | Rounds the extended double-precision floating-point value `a' to an integer, | and returns the result as an extended quadruple-precision floating-point | value. The operation is performed according to the IEC/IEEE Standard for | Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_round_to_int(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t lastBitMask, roundBitsMask; floatx80 z; aSign = extractFloatx80Sign(a); aExp = extractFloatx80Exp( a ); if ( 0x403E <= aExp ) { if ( aExp == 0x7FFF ) { if ((uint64_t) ( extractFloatx80Frac( a )<<1 ) ) return propagateFloatx80NaNOneArg(a, status); return inf_clear_intbit(status) ? packFloatx80(aSign, aExp, 0) : a; } return a; } if ( aExp < 0x3FFF ) { if ( ( aExp == 0 ) #ifdef SOFTFLOAT_68K && ( (uint64_t) extractFloatx80Frac( a ) == 0 ) ) { #else && ( (uint64_t) ( extractFloatx80Frac( a )<<1 ) == 0 ) ) { #endif return a; } status->float_exception_flags |= float_flag_inexact; switch (status->float_rounding_mode) { case float_round_nearest_even: if ( ( aExp == 0x3FFE ) && (uint64_t) ( extractFloatx80Frac( a )<<1 ) ) { return packFloatx80( aSign, 0x3FFF, LIT64( 0x8000000000000000 ) ); } break; case float_round_ties_away: if (aExp == 0x3FFE) { return packFloatx80(aSign, 0x3FFF, LIT64(0x8000000000000000)); } break; case float_round_down: return aSign ? packFloatx80( 1, 0x3FFF, LIT64( 0x8000000000000000 ) ) : packFloatx80( 0, 0, 0 ); case float_round_up: return aSign ? packFloatx80( 1, 0, 0 ) : packFloatx80( 0, 0x3FFF, LIT64( 0x8000000000000000 ) ); } return packFloatx80( aSign, 0, 0 ); } lastBitMask = 1; lastBitMask <<= 0x403E - aExp; roundBitsMask = lastBitMask - 1; z = a; switch (status->float_rounding_mode) { case float_round_nearest_even: z.low += lastBitMask>>1; if ((z.low & roundBitsMask) == 0) { z.low &= ~lastBitMask; } break; case float_round_ties_away: z.low += lastBitMask >> 1; break; case float_round_to_zero: break; case float_round_up: if (!extractFloatx80Sign(z)) { z.low += roundBitsMask; } break; case float_round_down: if (extractFloatx80Sign(z)) { z.low += roundBitsMask; } break; default: abort(); } z.low &= ~ roundBitsMask; if ( z.low == 0 ) { ++z.high; z.low = LIT64( 0x8000000000000000 ); } if (z.low != a.low) { status->float_exception_flags |= float_flag_inexact; } return z; } #ifdef SOFTFLOAT_68K // 09-01-2017: Added for Previous floatx80 floatx80_round_to_int_toward_zero( floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t lastBitMask, roundBitsMask; floatx80 z; aSign = extractFloatx80Sign(a); aExp = extractFloatx80Exp( a ); if ( 0x403E <= aExp ) { if ( aExp == 0x7FFF ) { if ( (uint64_t) ( extractFloatx80Frac( a )<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); return inf_clear_intbit(status) ? packFloatx80(aSign, aExp, 0) : a; } return a; } if ( aExp < 0x3FFF ) { if ( ( aExp == 0 ) #ifdef SOFTFLOAT_68K && ( (uint64_t) extractFloatx80Frac( a ) == 0 ) ) { #else && ( (uint64_t) ( extractFloatx80Frac( a )<<1 ) == 0 ) ) { #endif return a; } status->float_exception_flags |= float_flag_inexact; return packFloatx80( aSign, 0, 0 ); } lastBitMask = 1; lastBitMask <<= 0x403E - aExp; roundBitsMask = lastBitMask - 1; z = a; z.low &= ~ roundBitsMask; if ( z.low == 0 ) { ++z.high; z.low = LIT64( 0x8000000000000000 ); } if ( z.low != a.low ) status->float_exception_flags |= float_flag_inexact; return z; } #endif // End of addition for Previous /*---------------------------------------------------------------------------- | Returns the result of adding the absolute values of the extended double- | precision floating-point values `a' and `b'. If `zSign' is 1, the sum is | negated before being returned. `zSign' is ignored if the result is a NaN. | The addition is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ static floatx80 addFloatx80Sigs(floatx80 a, floatx80 b, flag zSign, float_status *status) { int32_t aExp, bExp, zExp; uint64_t aSig, bSig, zSig0, zSig1; int32_t expDiff; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); #ifdef SOFTFLOAT_68K if ( aExp == 0 ) { if (aSig == 0) { aExp = -64; } else { normalizeFloatx80Subnormal(aSig, &aExp, &aSig); } } if ( bExp == 0 ) { if (bSig == 0) { bExp = -64; } else { normalizeFloatx80Subnormal(bSig, &bExp, &bSig); } } #endif expDiff = aExp - bExp; if ( 0 < expDiff ) { if ( aExp == 0x7FFF ) { if ((uint64_t)(aSig << 1)) return propagateFloatx80NaN(a, b, status); return inf_clear_intbit(status) ? packFloatx80(extractFloatx80Sign(a), aExp, 0) : a; } #ifndef SOFTFLOAT_68K if ( bExp == 0 ) --expDiff; #endif shift64ExtraRightJamming( bSig, 0, expDiff, &bSig, &zSig1 ); zExp = aExp; } else if ( expDiff < 0 ) { if ( bExp == 0x7FFF ) { if ((uint64_t)(bSig << 1)) return propagateFloatx80NaN(a, b, status); if (inf_clear_intbit(status)) bSig = 0; return packFloatx80( zSign, bExp, bSig ); } #ifndef SOFTFLOAT_68K if ( aExp == 0 ) ++expDiff; #endif shift64ExtraRightJamming( aSig, 0, - expDiff, &aSig, &zSig1 ); zExp = bExp; } else { if ( aExp == 0x7FFF ) { if ( (uint64_t) ( ( aSig | bSig )<<1 ) ) { return propagateFloatx80NaN(a, b, status); } if (inf_clear_intbit(status)) return packFloatx80(extractFloatx80Sign(a), aExp, 0); return faddsub_swap_inf(status) ? b : a; } zSig1 = 0; zSig0 = aSig + bSig; #ifndef SOFTFLOAT_68K if ( aExp == 0 ) { normalizeFloatx80Subnormal( zSig0, &zExp, &zSig0 ); goto roundAndPack; } #endif zExp = aExp; #ifdef SOFTFLOAT_68K if ( aSig == 0 && bSig == 0 ) return packFloatx80( zSign, 0, 0 ); if ( aSig == 0 || bSig == 0 ) goto roundAndPack; #endif goto shiftRight1; } zSig0 = aSig + bSig; if ( (int64_t) zSig0 < 0 ) goto roundAndPack; shiftRight1: shift64ExtraRightJamming( zSig0, zSig1, 1, &zSig0, &zSig1 ); zSig0 |= LIT64( 0x8000000000000000 ); ++zExp; roundAndPack: return roundAndPackFloatx80(status->floatx80_rounding_precision, zSign, zExp, zSig0, zSig1, status); } /*---------------------------------------------------------------------------- | Returns the result of subtracting the absolute values of the extended | double-precision floating-point values `a' and `b'. If `zSign' is 1, the | difference is negated before being returned. `zSign' is ignored if the | result is a NaN. The subtraction is performed according to the IEC/IEEE | Standard for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ static floatx80 subFloatx80Sigs(floatx80 a, floatx80 b, flag zSign, float_status *status) { int32_t aExp, bExp, zExp; uint64_t aSig, bSig, zSig0, zSig1; int32_t expDiff; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); expDiff = aExp - bExp; if ( 0 < expDiff ) goto aExpBigger; if ( expDiff < 0 ) goto bExpBigger; if ( aExp == 0x7FFF ) { if ( (uint64_t) ( ( aSig | bSig )<<1 ) ) { return propagateFloatx80NaN(a, b, status); } float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } #ifndef SOFTFLOAT_68K if ( aExp == 0 ) { aExp = 1; bExp = 1; } #endif zSig1 = 0; if ( bSig < aSig ) goto aBigger; if ( aSig < bSig ) goto bBigger; return packFloatx80(status->float_rounding_mode == float_round_down, 0, 0); bExpBigger: if ( bExp == 0x7FFF ) { if ((uint64_t)(bSig << 1)) return propagateFloatx80NaN(a, b, status); if (inf_clear_intbit(status)) bSig = 0; return packFloatx80(zSign ^ 1, bExp, bSig); } #ifndef SOFTFLOAT_68K if ( aExp == 0 ) ++expDiff; #endif shift128RightJamming( aSig, 0, - expDiff, &aSig, &zSig1 ); bBigger: sub128( bSig, 0, aSig, zSig1, &zSig0, &zSig1 ); zExp = bExp; zSign ^= 1; goto normalizeRoundAndPack; aExpBigger: if ( aExp == 0x7FFF ) { if ((uint64_t)(aSig << 1)) return propagateFloatx80NaN(a, b, status); return inf_clear_intbit(status) ? packFloatx80(extractFloatx80Sign(a), aExp, 0) : a; } #ifndef SOFTFLOAT_68K if ( bExp == 0 ) --expDiff; #endif shift128RightJamming( bSig, 0, expDiff, &bSig, &zSig1 ); aBigger: sub128( aSig, 0, bSig, zSig1, &zSig0, &zSig1 ); zExp = aExp; normalizeRoundAndPack: return normalizeRoundAndPackFloatx80(status->floatx80_rounding_precision, zSign, zExp, zSig0, zSig1, status); } /*---------------------------------------------------------------------------- | Returns the result of adding the extended double-precision floating-point | values `a' and `b'. The operation is performed according to the IEC/IEEE | Standard for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_add(floatx80 a, floatx80 b, float_status *status) { flag aSign, bSign; if (floatx80_invalid_encoding(a) || floatx80_invalid_encoding(b)) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } aSign = extractFloatx80Sign( a ); bSign = extractFloatx80Sign( b ); if ( aSign == bSign ) { return addFloatx80Sigs(a, b, aSign, status); } else { return subFloatx80Sigs(a, b, aSign, status); } } /*---------------------------------------------------------------------------- | Returns the result of subtracting the extended double-precision floating- | point values `a' and `b'. The operation is performed according to the | IEC/IEEE Standard for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_sub(floatx80 a, floatx80 b, float_status *status) { flag aSign, bSign; if (floatx80_invalid_encoding(a) || floatx80_invalid_encoding(b)) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } aSign = extractFloatx80Sign( a ); bSign = extractFloatx80Sign( b ); if ( aSign == bSign ) { return subFloatx80Sigs(a, b, aSign, status); } else { return addFloatx80Sigs(a, b, aSign, status); } } /*---------------------------------------------------------------------------- | Returns the result of multiplying the extended double-precision floating- | point values `a' and `b'. The operation is performed according to the | IEC/IEEE Standard for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_mul(floatx80 a, floatx80 b, float_status *status) { flag aSign, bSign, zSign; int32_t aExp, bExp, zExp; uint64_t aSig, bSig, zSig0, zSig1; if (floatx80_invalid_encoding(a) || floatx80_invalid_encoding(b)) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); bSign = extractFloatx80Sign( b ); zSign = aSign ^ bSign; if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) || ( ( bExp == 0x7FFF ) && (uint64_t) ( bSig<<1 ) ) ) { return propagateFloatx80NaN(a, b, status); } if ( ( bExp | bSig ) == 0 ) goto invalid; if (inf_clear_intbit(status)) aSig = 0; return packFloatx80(zSign, aExp, aSig); } if ( bExp == 0x7FFF ) { if ((uint64_t)(bSig << 1)) { return propagateFloatx80NaN(a, b, status); } if ( ( aExp | aSig ) == 0 ) { invalid: float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } if (inf_clear_intbit(status)) bSig = 0; return packFloatx80(zSign, bExp, bSig); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( zSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } if ( bExp == 0 ) { if ( bSig == 0 ) return packFloatx80( zSign, 0, 0 ); normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); } zExp = aExp + bExp - 0x3FFE; mul64To128( aSig, bSig, &zSig0, &zSig1 ); if ( 0 < (int64_t) zSig0 ) { shortShift128Left( zSig0, zSig1, 1, &zSig0, &zSig1 ); --zExp; } return roundAndPackFloatx80(status->floatx80_rounding_precision, zSign, zExp, zSig0, zSig1, status); } #ifdef SOFTFLOAT_68K // 21-01-2017: Added for Previous floatx80 floatx80_sglmul( floatx80 a, floatx80 b, float_status *status ) { flag aSign, bSign, zSign; int32_t aExp, bExp, zExp; uint64_t aSig, bSig, zSig0, zSig1; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); bSign = extractFloatx80Sign( b ); zSign = aSign ^ bSign; if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) || ( ( bExp == 0x7FFF ) && (uint64_t) ( bSig<<1 ) ) ) { return propagateFloatx80NaN( a, b, status ); } if ( ( bExp | bSig ) == 0 ) goto invalid; if (inf_clear_intbit(status)) aSig = 0; return packFloatx80(zSign, aExp, aSig); } if ( bExp == 0x7FFF ) { if ( (uint64_t) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b, status ); if ( ( aExp | aSig ) == 0 ) { invalid: float_raise( float_flag_invalid, status ); return floatx80_default_nan(status); } if (inf_clear_intbit(status)) bSig = 0; return packFloatx80(zSign, bExp, bSig); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( zSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } if ( bExp == 0 ) { if ( bSig == 0 ) return packFloatx80( zSign, 0, 0 ); normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); } aSig &= LIT64( 0xFFFFFF0000000000 ); bSig &= LIT64( 0xFFFFFF0000000000 ); zExp = aExp + bExp - 0x3FFE; mul64To128( aSig, bSig, &zSig0, &zSig1 ); if ( 0 < (int64_t) zSig0 ) { shortShift128Left( zSig0, zSig1, 1, &zSig0, &zSig1 ); --zExp; } return roundSigAndPackFloatx80( 32, zSign, zExp, zSig0, zSig1, status); } #endif // End of addition for Previous /*---------------------------------------------------------------------------- | Returns the result of dividing the extended double-precision floating-point | value `a' by the corresponding value `b'. The operation is performed | according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_div(floatx80 a, floatx80 b, float_status *status) { flag aSign, bSign, zSign; int32_t aExp, bExp, zExp; uint64_t aSig, bSig, zSig0, zSig1; uint64_t rem0, rem1, rem2, term0, term1, term2; if (floatx80_invalid_encoding(a) || floatx80_invalid_encoding(b)) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); bSign = extractFloatx80Sign( b ); zSign = aSign ^ bSign; if ( aExp == 0x7FFF ) { if ((uint64_t)(aSig << 1)) { return propagateFloatx80NaN(a, b, status); } if ( bExp == 0x7FFF ) { if ((uint64_t)(bSig << 1)) { return propagateFloatx80NaN(a, b, status); } goto invalid; } if (inf_clear_intbit(status)) aSig = 0; return packFloatx80(zSign, aExp, aSig); } if ( bExp == 0x7FFF ) { if ((uint64_t)(bSig << 1)) { return propagateFloatx80NaN(a, b, status); } return packFloatx80( zSign, 0, 0 ); } if ( bExp == 0 ) { if ( bSig == 0 ) { if ( ( aExp | aSig ) == 0 ) { invalid: float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } float_raise(float_flag_divbyzero, status); return packFloatx80( zSign, 0x7FFF, floatx80_default_infinity_low ); } normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( zSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } zExp = aExp - bExp + 0x3FFE; rem1 = 0; if ( bSig <= aSig ) { shift128Right( aSig, 0, 1, &aSig, &rem1 ); ++zExp; } zSig0 = estimateDiv128To64( aSig, rem1, bSig ); mul64To128( bSig, zSig0, &term0, &term1 ); sub128( aSig, rem1, term0, term1, &rem0, &rem1 ); while ( (int64_t) rem0 < 0 ) { --zSig0; add128( rem0, rem1, 0, bSig, &rem0, &rem1 ); } zSig1 = estimateDiv128To64( rem1, 0, bSig ); if ( (uint64_t) ( zSig1<<1 ) <= 8 ) { mul64To128( bSig, zSig1, &term1, &term2 ); sub128( rem1, 0, term1, term2, &rem1, &rem2 ); while ( (int64_t) rem1 < 0 ) { --zSig1; add128( rem1, rem2, 0, bSig, &rem1, &rem2 ); } zSig1 |= ( ( rem1 | rem2 ) != 0 ); } return roundAndPackFloatx80(status->floatx80_rounding_precision, zSign, zExp, zSig0, zSig1, status); } #ifdef SOFTFLOAT_68K // 21-01-2017: Addition for Previous floatx80 floatx80_sgldiv( floatx80 a, floatx80 b, float_status *status ) { flag aSign, bSign, zSign; int32_t aExp, bExp, zExp; uint64_t aSig, bSig, zSig0, zSig1; uint64_t rem0, rem1, rem2, term0, term1, term2; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); bSign = extractFloatx80Sign( b ); zSign = aSign ^ bSign; if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaN( a, b, status ); if ( bExp == 0x7FFF ) { if ( (uint64_t) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b, status ); goto invalid; } if (inf_clear_intbit(status)) aSig = 0; return packFloatx80(zSign, aExp, aSig); } if ( bExp == 0x7FFF ) { if ( (uint64_t) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b, status ); return packFloatx80( zSign, 0, 0 ); } if ( bExp == 0 ) { if ( bSig == 0 ) { if ( ( aExp | aSig ) == 0 ) { invalid: float_raise( float_flag_invalid, status ); return floatx80_default_nan(status); } float_raise( float_flag_divbyzero, status ); return packFloatx80( zSign, 0x7FFF, floatx80_default_infinity_low ); } normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( zSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } zExp = aExp - bExp + 0x3FFE; rem1 = 0; if ( bSig <= aSig ) { shift128Right( aSig, 0, 1, &aSig, &rem1 ); ++zExp; } zSig0 = estimateDiv128To64( aSig, rem1, bSig ); mul64To128( bSig, zSig0, &term0, &term1 ); sub128( aSig, rem1, term0, term1, &rem0, &rem1 ); while ( (int64_t) rem0 < 0 ) { --zSig0; add128( rem0, rem1, 0, bSig, &rem0, &rem1 ); } zSig1 = estimateDiv128To64( rem1, 0, bSig ); if ( (uint64_t) ( zSig1<<1 ) <= 8 ) { mul64To128( bSig, zSig1, &term1, &term2 ); sub128( rem1, 0, term1, term2, &rem1, &rem2 ); while ( (int64_t) rem1 < 0 ) { --zSig1; add128( rem1, rem2, 0, bSig, &rem1, &rem2 ); } zSig1 |= ( ( rem1 | rem2 ) != 0 ); } return roundSigAndPackFloatx80( 32, zSign, zExp, zSig0, zSig1, status); } #endif // End of addition for Previous /*---------------------------------------------------------------------------- | Returns the remainder of the extended double-precision floating-point value | `a' with respect to the corresponding value `b'. The operation is performed | according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ #ifndef SOFTFLOAT_68K floatx80 floatx80_rem(floatx80 a, floatx80 b, float_status *status) { flag aSign, zSign; int32_t aExp, bExp, expDiff; uint64_t aSig0, aSig1, bSig; uint64_t q, term0, term1, alternateASig0, alternateASig1; if (floatx80_invalid_encoding(a) || floatx80_invalid_encoding(b)) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } aSig0 = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig0<<1 ) || ( ( bExp == 0x7FFF ) && (uint64_t) ( bSig<<1 ) ) ) { return propagateFloatx80NaN(a, b, status); } goto invalid; } if ( bExp == 0x7FFF ) { if ((uint64_t)(bSig << 1)) { return propagateFloatx80NaN(a, b, status); } return a; } if ( bExp == 0 ) { if ( bSig == 0 ) { invalid: float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); } if ( aExp == 0 ) { if ( (uint64_t) ( aSig0<<1 ) == 0 ) return a; normalizeFloatx80Subnormal( aSig0, &aExp, &aSig0 ); } bSig |= LIT64( 0x8000000000000000 ); zSign = aSign; expDiff = aExp - bExp; aSig1 = 0; if ( expDiff < 0 ) { if ( expDiff < -1 ) return a; shift128Right( aSig0, 0, 1, &aSig0, &aSig1 ); expDiff = 0; } q = ( bSig <= aSig0 ); if ( q ) aSig0 -= bSig; expDiff -= 64; while ( 0 < expDiff ) { q = estimateDiv128To64( aSig0, aSig1, bSig ); q = ( 2 < q ) ? q - 2 : 0; mul64To128( bSig, q, &term0, &term1 ); sub128( aSig0, aSig1, term0, term1, &aSig0, &aSig1 ); shortShift128Left( aSig0, aSig1, 62, &aSig0, &aSig1 ); expDiff -= 62; } expDiff += 64; if ( 0 < expDiff ) { q = estimateDiv128To64( aSig0, aSig1, bSig ); q = ( 2 < q ) ? q - 2 : 0; q >>= 64 - expDiff; mul64To128( bSig, q<<( 64 - expDiff ), &term0, &term1 ); sub128( aSig0, aSig1, term0, term1, &aSig0, &aSig1 ); shortShift128Left( 0, bSig, 64 - expDiff, &term0, &term1 ); while ( le128( term0, term1, aSig0, aSig1 ) ) { ++q; sub128( aSig0, aSig1, term0, term1, &aSig0, &aSig1 ); } } else { term1 = 0; term0 = bSig; } sub128( term0, term1, aSig0, aSig1, &alternateASig0, &alternateASig1 ); if ( lt128( alternateASig0, alternateASig1, aSig0, aSig1 ) || ( eq128( alternateASig0, alternateASig1, aSig0, aSig1 ) && ( q & 1 ) ) ) { aSig0 = alternateASig0; aSig1 = alternateASig1; zSign = ! zSign; } return normalizeRoundAndPackFloatx80( 80, zSign, bExp + expDiff, aSig0, aSig1, status); } #else // 09-01-2017: Modified version for Previous floatx80 floatx80_rem(floatx80 a, floatx80 b, uint64_t *q, flag *s, float_status *status) { flag aSign, bSign, zSign; int32_t aExp, bExp, expDiff; uint64_t aSig0, aSig1, bSig; uint64_t qTemp, term0, term1, alternateASig0, alternateASig1; aSig0 = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); bSig = extractFloatx80Frac(b); bExp = extractFloatx80Exp(b); bSign = extractFloatx80Sign(b); *s = 0; *q = 0; if (aExp == 0x7FFF) { if ((uint64_t)(aSig0 << 1) || ((bExp == 0x7FFF) && (uint64_t)(bSig << 1))) { return propagateFloatx80NaN(a, b, status); } goto invalid; } if (bExp == 0x7FFF) { if ((uint64_t)(bSig << 1)) return propagateFloatx80NaN(a, b, status); *s = (aSign != bSign); return normalizeRoundAndPackFloatx80(status->floatx80_rounding_precision, aSign, aExp, aSig0, 0, status); } if (bExp == 0) { if (bSig == 0) { invalid: float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } normalizeFloatx80Subnormal(bSig, &bExp, &bSig); } if (aExp == 0) { if (aSig0 == 0) { *s = (aSign != bSign); return a; } normalizeFloatx80Subnormal(aSig0, &aExp, &aSig0); } bSig |= LIT64(0x8000000000000000); zSign = aSign; expDiff = aExp - bExp; *s = (aSign != bSign); aSig1 = 0; if (expDiff < 0) { if (expDiff < -1) return a; shift128Right(aSig0, 0, 1, &aSig0, &aSig1); expDiff = 0; } qTemp = (bSig <= aSig0); if (qTemp) aSig0 -= bSig; *q = (expDiff > 63) ? 0 : (qTemp << expDiff); expDiff -= 64; while (0 < expDiff) { qTemp = estimateDiv128To64(aSig0, aSig1, bSig); qTemp = (2 < qTemp) ? qTemp - 2 : 0; mul64To128(bSig, qTemp, &term0, &term1); sub128(aSig0, aSig1, term0, term1, &aSig0, &aSig1); shortShift128Left(aSig0, aSig1, 62, &aSig0, &aSig1); *q = (expDiff > 63) ? 0 : (qTemp << expDiff); expDiff -= 62; } expDiff += 64; if (0 < expDiff) { qTemp = estimateDiv128To64(aSig0, aSig1, bSig); qTemp = (2 < qTemp) ? qTemp - 2 : 0; qTemp >>= 64 - expDiff; mul64To128(bSig, qTemp << (64 - expDiff), &term0, &term1); sub128(aSig0, aSig1, term0, term1, &aSig0, &aSig1); shortShift128Left(0, bSig, 64 - expDiff, &term0, &term1); while (le128(term0, term1, aSig0, aSig1)) { ++qTemp; sub128(aSig0, aSig1, term0, term1, &aSig0, &aSig1); } *q += qTemp; } else { term1 = 0; term0 = bSig; } sub128(term0, term1, aSig0, aSig1, &alternateASig0, &alternateASig1); if (lt128(alternateASig0, alternateASig1, aSig0, aSig1) || (eq128(alternateASig0, alternateASig1, aSig0, aSig1) && (qTemp & 1)) ) { aSig0 = alternateASig0; aSig1 = alternateASig1; zSign = !zSign; ++*q; } return normalizeRoundAndPackFloatx80(status->floatx80_rounding_precision, zSign, bExp + expDiff, aSig0, aSig1, status); } #endif // End of modification #ifdef SOFTFLOAT_68K // 08-01-2017: Added for Previous /*---------------------------------------------------------------------------- | Returns the modulo remainder of the extended double-precision floating-point | value `a' with respect to the corresponding value `b'. *----------------------------------------------------------------------------*/ floatx80 floatx80_mod(floatx80 a, floatx80 b, uint64_t *q, flag *s, float_status *status) { flag aSign, bSign, zSign; int32_t aExp, bExp, expDiff; uint64_t aSig0, aSig1, bSig; uint64_t qTemp, term0, term1; aSig0 = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); bSig = extractFloatx80Frac(b); bExp = extractFloatx80Exp(b); bSign = extractFloatx80Sign(b); *s = 0; *q = 0; if (aExp == 0x7FFF) { if ((uint64_t)(aSig0 << 1) || ((bExp == 0x7FFF) && (uint64_t)(bSig << 1))) { return propagateFloatx80NaN(a, b, status); } goto invalid; } if (bExp == 0x7FFF) { if ((uint64_t)(bSig << 1)) return propagateFloatx80NaN(a, b, status); *s = (aSign != bSign); return normalizeRoundAndPackFloatx80(status->floatx80_rounding_precision, aSign, aExp, aSig0, 0, status); } if (bExp == 0) { if (bSig == 0) { invalid: float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } normalizeFloatx80Subnormal(bSig, &bExp, &bSig); } if (aExp == 0) { if (aSig0 == 0) { *s = (aSign != bSign); return a; } normalizeFloatx80Subnormal(aSig0, &aExp, &aSig0); } bSig |= LIT64(0x8000000000000000); zSign = aSign; expDiff = aExp - bExp; *s = (aSign != bSign); aSig1 = 0; if (expDiff < 0) return roundAndPackFloatx80(status->floatx80_rounding_precision, aSign, aExp, aSig0, 0, status); qTemp = (bSig <= aSig0); if (qTemp) aSig0 -= bSig; *q = (expDiff > 63) ? 0 : (qTemp << expDiff); expDiff -= 64; while (0 < expDiff) { qTemp = estimateDiv128To64(aSig0, aSig1, bSig); qTemp = (2 < qTemp) ? qTemp - 2 : 0; mul64To128(bSig, qTemp, &term0, &term1); sub128(aSig0, aSig1, term0, term1, &aSig0, &aSig1); shortShift128Left(aSig0, aSig1, 62, &aSig0, &aSig1); *q = (expDiff > 63) ? 0 : (qTemp << expDiff); expDiff -= 62; } expDiff += 64; if (0 < expDiff) { qTemp = estimateDiv128To64(aSig0, aSig1, bSig); qTemp = (2 < qTemp) ? qTemp - 2 : 0; qTemp >>= 64 - expDiff; mul64To128(bSig, qTemp << (64 - expDiff), &term0, &term1); sub128(aSig0, aSig1, term0, term1, &aSig0, &aSig1); shortShift128Left(0, bSig, 64 - expDiff, &term0, &term1); while (le128(term0, term1, aSig0, aSig1)) { ++qTemp; sub128(aSig0, aSig1, term0, term1, &aSig0, &aSig1); } *q += qTemp; } return normalizeRoundAndPackFloatx80(status->floatx80_rounding_precision, zSign, bExp + expDiff, aSig0, aSig1, status); } #endif // end of addition for Previous /*---------------------------------------------------------------------------- | Returns the square root of the extended double-precision floating-point | value `a'. The operation is performed according to the IEC/IEEE Standard | for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_sqrt(floatx80 a, float_status *status) { flag aSign; int32_t aExp, zExp; uint64_t aSig0, aSig1, zSig0, zSig1, doubleZSig0; uint64_t rem0, rem1, rem2, rem3, term0, term1, term2, term3; if (floatx80_invalid_encoding(a)) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } aSig0 = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ((uint64_t)(aSig0 << 1)) return propagateFloatx80NaNOneArg(a, status); if (!aSign) return inf_clear_intbit(status) ? packFloatx80(aSign, aExp, 0) : a; goto invalid; } if ( aSign ) { if ( ( aExp | aSig0 ) == 0 ) return a; invalid: float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } if ( aExp == 0 ) { if ( aSig0 == 0 ) return packFloatx80( 0, 0, 0 ); normalizeFloatx80Subnormal( aSig0, &aExp, &aSig0 ); } zExp = ( ( aExp - 0x3FFF )>>1 ) + 0x3FFF; zSig0 = estimateSqrt32( aExp, aSig0>>32 ); shift128Right( aSig0, 0, 2 + ( aExp & 1 ), &aSig0, &aSig1 ); zSig0 = estimateDiv128To64( aSig0, aSig1, zSig0<<32 ) + ( zSig0<<30 ); doubleZSig0 = zSig0<<1; mul64To128( zSig0, zSig0, &term0, &term1 ); sub128( aSig0, aSig1, term0, term1, &rem0, &rem1 ); while ( (int64_t) rem0 < 0 ) { --zSig0; doubleZSig0 -= 2; add128( rem0, rem1, zSig0>>63, doubleZSig0 | 1, &rem0, &rem1 ); } zSig1 = estimateDiv128To64( rem1, 0, doubleZSig0 ); if ( ( zSig1 & LIT64( 0x3FFFFFFFFFFFFFFF ) ) <= 5 ) { if ( zSig1 == 0 ) zSig1 = 1; mul64To128( doubleZSig0, zSig1, &term1, &term2 ); sub128( rem1, 0, term1, term2, &rem1, &rem2 ); mul64To128( zSig1, zSig1, &term2, &term3 ); sub192( rem1, rem2, 0, 0, term2, term3, &rem1, &rem2, &rem3 ); while ( (int64_t) rem1 < 0 ) { --zSig1; shortShift128Left( 0, zSig1, 1, &term2, &term3 ); term3 |= 1; term2 |= doubleZSig0; add192( rem1, rem2, rem3, 0, term2, term3, &rem1, &rem2, &rem3 ); } zSig1 |= ( ( rem1 | rem2 | rem3 ) != 0 ); } shortShift128Left( 0, zSig1, 1, &zSig0, &zSig1 ); zSig0 |= doubleZSig0; return roundAndPackFloatx80(status->floatx80_rounding_precision, 0, zExp, zSig0, zSig1, status); } #ifdef SOFTFLOAT_68K // 07-01-2017: Added for Previous /*---------------------------------------------------------------------------- | Returns the mantissa of the extended double-precision floating-point | value `a'. *----------------------------------------------------------------------------*/ floatx80 floatx80_getman( floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); float_raise( float_flag_invalid, status ); return floatx80_default_nan(status); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( aSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return packFloatx80(aSign, 0x3fff, aSig); } /*---------------------------------------------------------------------------- | Returns the exponent of the extended double-precision floating-point | value `a' as an extended double-precision value. *----------------------------------------------------------------------------*/ floatx80 floatx80_getexp( floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); float_raise( float_flag_invalid, status ); return floatx80_default_nan(status); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( aSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return int32_to_floatx80(aExp - 0x3FFF); } /*---------------------------------------------------------------------------- | Scales extended double-precision floating-point value in operand `a' by | value `b'. The function truncates the value in the second operand 'b' to | an integral value and adds that value to the exponent of the operand 'a'. | The operation performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_scale(floatx80 a, floatx80 b, float_status *status) { flag aSign, bSign; int32_t aExp, bExp, shiftCount; uint64_t aSig, bSig; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); bSig = extractFloatx80Frac(b); bExp = extractFloatx80Exp(b); bSign = extractFloatx80Sign(b); if ( bExp == 0x7FFF ) { if ( (uint64_t) ( bSig<<1 ) || ( ( aExp == 0x7FFF ) && (uint64_t) ( aSig<<1 ) ) ) { return propagateFloatx80NaN( a, b, status ); } float_raise( float_flag_invalid, status ); return floatx80_default_nan(status); } if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaN( a, b, status ); return a; } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( aSign, 0, 0); if (bExp < 0x3FFF) return normalizeRoundAndPackFloatx80(status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } if (bExp < 0x3FFF) { return roundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status); } if ( 0x400F < bExp ) { aExp = bSign ? -0x6001 : 0xE000; return roundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status ); } shiftCount = 0x403E - bExp; bSig >>= shiftCount; aExp = (int32_t)(bSign ? ( aExp - bSig ) : ( aExp + bSig )); return roundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status); } /*----------------------------------------------------------------------------- | Calculates the absolute value of the extended double-precision floating-point | value `a'. The operation is performed according to the IEC/IEEE Standard | for Binary Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_abs(floatx80 a, float_status *status) { int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); if (inf_clear_intbit(status)) aSig = 0; return packFloatx80(0, aExp, aSig); } if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( 0, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return roundAndPackFloatx80( status->floatx80_rounding_precision, 0, aExp, aSig, 0, status ); } /*----------------------------------------------------------------------------- | Changes the sign of the extended double-precision floating-point value 'a'. | The operation is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ floatx80 floatx80_neg(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); if (inf_clear_intbit(status)) aSig = 0; return packFloatx80(!aSign, aExp, aSig); } aSign = !aSign; if ( aExp == 0 ) { if ( aSig == 0 ) return packFloatx80( aSign, 0, 0 ); normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); } return roundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status ); } /*---------------------------------------------------------------------------- | Returns the result of comparing the extended double-precision floating- | point values `a' and `b'. The result is abstracted for matching the | corresponding condition codes. *----------------------------------------------------------------------------*/ floatx80 floatx80_cmp( floatx80 a, floatx80 b, float_status *status ) { flag aSign, bSign; int32_t aExp, bExp; uint64_t aSig, bSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); bSig = extractFloatx80Frac( b ); bExp = extractFloatx80Exp( b ); bSign = extractFloatx80Sign( b ); if ( ( aExp == 0x7FFF && (uint64_t) ( aSig<<1 ) ) || ( bExp == 0x7FFF && (uint64_t) ( bSig<<1 ) ) ) { // 68040 FCMP -NaN return N flag set if (fcmp_signed_nan(status)) return propagateFloatx80NaN(a, b, status ); return propagateFloatx80NaN(packFloatx80(0, aExp, aSig), packFloatx80(0, bExp, bSig), status); } if ( bExp < aExp ) return packFloatx80( aSign, 0x3FFF, LIT64( 0x8000000000000000 ) ); if ( aExp < bExp ) return packFloatx80( bSign ^ 1, 0x3FFF, LIT64( 0x8000000000000000 ) ); if ( aExp == 0x7FFF ) { if ( aSign == bSign ) return packFloatx80( aSign, 0, 0 ); return packFloatx80( aSign, 0x3FFF, LIT64( 0x8000000000000000 ) ); } if ( bSig < aSig ) return packFloatx80( aSign, 0x3FFF, LIT64( 0x8000000000000000 ) ); if ( aSig < bSig ) return packFloatx80( bSign ^ 1, 0x3FFF, LIT64( 0x8000000000000000 ) ); if ( aSig == 0 ) return packFloatx80( aSign, 0, 0 ); if ( aSign == bSign ) return packFloatx80( 0, 0, 0 ); return packFloatx80( aSign, 0x3FFF, LIT64( 0x8000000000000000 ) ); } floatx80 floatx80_tst( floatx80 a, float_status *status ) { int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); if ( aExp == 0x7FFF && (uint64_t) ( aSig<<1 ) ) return propagateFloatx80NaNOneArg( a, status ); return a; } floatx80 floatx80_move( floatx80 a, float_status *status ) { flag aSign; int32_t aExp; uint64_t aSig; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ((uint64_t)(aSig << 1)) return propagateFloatx80NaNOneArg(a, status); return inf_clear_intbit(status) ? packFloatx80(aSign, aExp, 0) : a; } if ( aExp == 0 ) { if ( aSig == 0 ) return a; return normalizeRoundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status ); } return roundAndPackFloatx80( status->floatx80_rounding_precision, aSign, aExp, aSig, 0, status ); } floatx80 floatx80_denormalize( floatx80 a, flag eSign) { flag aSign; int32_t aExp; uint64_t aSig; int32_t shiftCount; aSig = extractFloatx80Frac( a ); aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( eSign ) { shiftCount = 0x8000 - aExp; aExp = 0; if (shiftCount > 63) { aSig = 0; } else { aSig >>= shiftCount; } } return packFloatx80(aSign, aExp, aSig); } #endif // End of addition for Previous /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | equal to the corresponding value `b', and 0 otherwise. The comparison is | performed according to the IEC/IEEE Standard for Binary Floating-Point | Arithmetic. *----------------------------------------------------------------------------*/ flag floatx80_eq( floatx80 a, floatx80 b, float_status *status ) { if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) && (uint64_t) ( extractFloatx80Frac( a )<<1 ) ) || ( ( extractFloatx80Exp( b ) == 0x7FFF ) && (uint64_t) ( extractFloatx80Frac( b )<<1 ) ) ) { if ( floatx80_is_signaling_nan( a ) || floatx80_is_signaling_nan( b ) ) { float_raise( float_flag_invalid, status ); } return 0; } return ( a.low == b.low ) && ( ( a.high == b.high ) || ( ( a.low == 0 ) && ( (uint16_t) ( ( a.high | b.high )<<1 ) == 0 ) ) ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | less than or equal to the corresponding value `b', and 0 otherwise. The | comparison is performed according to the IEC/IEEE Standard for Binary | Floating-Point Arithmetic. *----------------------------------------------------------------------------*/ flag floatx80_le( floatx80 a, floatx80 b, float_status *status ) { flag aSign, bSign; if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) && (uint64_t) ( extractFloatx80Frac( a )<<1 ) ) || ( ( extractFloatx80Exp( b ) == 0x7FFF ) && (uint64_t) ( extractFloatx80Frac( b )<<1 ) ) ) { float_raise( float_flag_invalid, status ); return 0; } aSign = extractFloatx80Sign( a ); bSign = extractFloatx80Sign( b ); if ( aSign != bSign ) { return aSign || ( ( ( (uint16_t) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) == 0 ); } return aSign ? le128( b.high, b.low, a.high, a.low ) : le128( a.high, a.low, b.high, b.low ); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is | less than the corresponding value `b', and 0 otherwise. The comparison | is performed according to the IEC/IEEE Standard for Binary Floating-Point | Arithmetic. *----------------------------------------------------------------------------*/ flag floatx80_lt( floatx80 a, floatx80 b, float_status *status ) { flag aSign, bSign; if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) && (uint64_t) ( extractFloatx80Frac( a )<<1 ) ) || ( ( extractFloatx80Exp( b ) == 0x7FFF ) && (uint64_t) ( extractFloatx80Frac( b )<<1 ) ) ) { float_raise( float_flag_invalid, status ); return 0; } aSign = extractFloatx80Sign( a ); bSign = extractFloatx80Sign( b ); if ( aSign != bSign ) { return aSign && ( ( ( (uint16_t) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) != 0 ); } return aSign ? lt128( b.high, b.low, a.high, a.low ) : lt128( a.high, a.low, b.high, b.low ); } /*---------------------------------------------------------------------------- | Returns the result of converting the 64-bit two's complement integer `a' | to the extended double-precision floating-point format. The conversion | is performed according to the IEC/IEEE Standard for Binary Floating-Point | Arithmetic. *----------------------------------------------------------------------------*/ floatx80 int64_to_floatx80( int64_t a ) { flag zSign; uint64_t absA; int8_t shiftCount; if ( a == 0 ) return packFloatx80( 0, 0, 0 ); zSign = ( a < 0 ); absA = zSign ? - a : a; shiftCount = countLeadingZeros64( absA ); return packFloatx80( zSign, 0x403E - shiftCount, absA< #if defined(CONFIG_SOLARIS) && defined(CONFIG_NEEDS_LIBSUNMATH) #include #endif /* This 'flag' type must be able to hold at least 0 and 1. It should * probably be replaced with 'bool' but the uses would need to be audited * to check that they weren't accidentally relying on it being a larger type. */ typedef uint8_t flag; #define LIT64( a ) a##ULL /*---------------------------------------------------------------------------- | Software IEC/IEEE floating-point ordering relations *----------------------------------------------------------------------------*/ enum { float_relation_less = -1, float_relation_equal = 0, float_relation_greater = 1, float_relation_unordered = 2 }; /*---------------------------------------------------------------------------- | Software IEC/IEEE floating-point types. *----------------------------------------------------------------------------*/ /* Use structures for soft-float types. This prevents accidentally mixing them with native int/float types. A sufficiently clever compiler and sane ABI should be able to see though these structs. However x86/gcc 3.x seems to struggle a bit, so leave them disabled by default. */ //#define USE_SOFTFLOAT_STRUCT_TYPES #ifdef USE_SOFTFLOAT_STRUCT_TYPES typedef struct { uint16_t v; } float16; #define float16_val(x) (((float16)(x)).v) #define make_float16(x) __extension__ ({ float16 f16_val = {x}; f16_val; }) #define const_float16(x) { x } typedef struct { uint32_t v; } float32; /* The cast ensures an error if the wrong type is passed. */ #define float32_val(x) (((float32)(x)).v) #define make_float32(x) __extension__ ({ float32 f32_val = {x}; f32_val; }) #define const_float32(x) { x } typedef struct { uint64_t v; } float64; #define float64_val(x) (((float64)(x)).v) #define make_float64(x) __extension__ ({ float64 f64_val = {x}; f64_val; }) #define const_float64(x) { x } #else typedef uint16_t float16; typedef uint32_t float32; typedef uint64_t float64; #define float16_val(x) (x) #define float32_val(x) (x) #define float64_val(x) (x) #define make_float16(x) (x) #define make_float32(x) (x) #define make_float64(x) (x) #define const_float16(x) (x) #define const_float32(x) (x) #define const_float64(x) (x) #endif typedef struct { uint16_t high; uint64_t low; } floatx80; typedef struct { #ifdef HOST_WORDS_BIGENDIAN uint64_t high, low; #else uint64_t low, high; #endif } float128; /*---------------------------------------------------------------------------- | Software IEC/IEEE floating-point underflow tininess-detection mode. *----------------------------------------------------------------------------*/ enum { float_tininess_after_rounding = 0, float_tininess_before_rounding = 1 }; /*---------------------------------------------------------------------------- | Software IEC/IEEE floating-point rounding mode. *----------------------------------------------------------------------------*/ enum { float_round_nearest_even = 0, float_round_down = 1, float_round_up = 2, float_round_to_zero = 3, float_round_ties_away = 4, }; /*---------------------------------------------------------------------------- | Software IEC/IEEE floating-point exception flags. *----------------------------------------------------------------------------*/ enum { float_flag_invalid = 0x01, float_flag_denormal = 0x02, float_flag_divbyzero = 0x04, float_flag_overflow = 0x08, float_flag_underflow = 0x10, float_flag_inexact = 0x20, float_flag_signaling = 0x40, float_flag_decimal = 0x80 }; /*---------------------------------------------------------------------------- | Variables for storing sign, exponent and significand of overflowed or | underflowed extended double-precision floating-point value. | Variables for storing sign, exponent and significand of internal extended | double-precision floating-point value for external use. *----------------------------------------------------------------------------*/ extern flag floatx80_internal_sign; extern int32_t floatx80_internal_exp; extern uint64_t floatx80_internal_sig; extern int32_t floatx80_internal_exp0; extern uint64_t floatx80_internal_sig0; extern uint64_t floatx80_internal_sig1; extern int8_t floatx80_internal_precision; extern int8_t floatx80_internal_mode; typedef struct float_status { signed char float_detect_tininess; signed char float_rounding_mode; uint8_t float_exception_flags; signed char floatx80_rounding_precision; /* should denormalised results go to zero and set the inexact flag? */ flag flush_to_zero; /* should denormalised inputs go to zero and set the input_denormal flag? */ flag flush_inputs_to_zero; flag default_nan_mode; flag snan_bit_is_one; flag floatx80_special_flags; } float_status; /*---------------------------------------------------------------------------- | Function for getting sign, exponent and significand of extended | double-precision floating-point intermediate result for external use. *----------------------------------------------------------------------------*/ floatx80 getFloatInternalOverflow( void ); floatx80 getFloatInternalUnderflow( void ); floatx80 getFloatInternalRoundedAll( void ); floatx80 getFloatInternalRoundedSome( void ); floatx80 getFloatInternalUnrounded( void ); floatx80 getFloatInternalFloatx80( void ); uint64_t getFloatInternalGRS( void ); static inline void set_float_detect_tininess(int val, float_status *status) { status->float_detect_tininess = val; } static inline void set_float_rounding_mode(int val, float_status *status) { status->float_rounding_mode = val; } static inline void set_float_exception_flags(int val, float_status *status) { status->float_exception_flags = val; } static inline void set_floatx80_rounding_precision(int val, float_status *status) { status->floatx80_rounding_precision = val; } static inline void set_flush_to_zero(flag val, float_status *status) { status->flush_to_zero = val; } static inline void set_flush_inputs_to_zero(flag val, float_status *status) { status->flush_inputs_to_zero = val; } static inline void set_default_nan_mode(flag val, float_status *status) { status->default_nan_mode = val; } static inline void set_snan_bit_is_one(flag val, float_status *status) { status->snan_bit_is_one = val; } static inline int get_float_detect_tininess(float_status *status) { return status->float_detect_tininess; } static inline int get_float_rounding_mode(float_status *status) { return status->float_rounding_mode; } static inline int get_float_exception_flags(float_status *status) { return status->float_exception_flags; } static inline int get_floatx80_rounding_precision(float_status *status) { return status->floatx80_rounding_precision; } static inline flag get_flush_to_zero(float_status *status) { return status->flush_to_zero; } static inline flag get_flush_inputs_to_zero(float_status *status) { return status->flush_inputs_to_zero; } static inline flag get_default_nan_mode(float_status *status) { return status->default_nan_mode; } /*---------------------------------------------------------------------------- | Special flags for indicating some unique behavior is required. *----------------------------------------------------------------------------*/ enum { cmp_signed_nan = 0x01, addsub_swap_inf = 0x02, infinity_clear_intbit = 0x04 }; static inline void set_special_flags(uint8_t flags, float_status *status) { status->floatx80_special_flags = flags; } static inline int8_t fcmp_signed_nan(float_status *status) { return status->floatx80_special_flags & cmp_signed_nan; } static inline int8_t faddsub_swap_inf(float_status *status) { return status->floatx80_special_flags & addsub_swap_inf; } static inline int8_t inf_clear_intbit(float_status *status) { return status->floatx80_special_flags & infinity_clear_intbit; } /*---------------------------------------------------------------------------- | Routine to raise any or all of the software IEC/IEEE floating-point | exception flags. *----------------------------------------------------------------------------*/ //void float_raise(uint8_t flags, float_status *status); /*---------------------------------------------------------------------------- | The pattern for a default generated single-precision NaN. *----------------------------------------------------------------------------*/ #define float32_default_nan 0x7FFFFFFF /*---------------------------------------------------------------------------- | The pattern for a default generated double-precision NaN. *----------------------------------------------------------------------------*/ #define float64_default_nan LIT64( 0x7FFFFFFFFFFFFFFF ) /*---------------------------------------------------------------------------- | The pattern for a default generated extended double-precision NaN. The | `high' and `low' values hold the most- and least-significant bits, | respectively. *----------------------------------------------------------------------------*/ #define floatx80_default_nan_high 0x7FFF #define floatx80_default_nan_low LIT64( 0xFFFFFFFFFFFFFFFF ) /*---------------------------------------------------------------------------- | The pattern for a default generated extended double-precision infinity. *----------------------------------------------------------------------------*/ #define floatx80_default_infinity_low LIT64( 0x0000000000000000 ) /*---------------------------------------------------------------------------- | If `a' is denormal and we are in flush-to-zero mode then set the | input-denormal exception and return zero. Otherwise just return the value. *----------------------------------------------------------------------------*/ float64 float64_squash_input_denormal(float64 a, float_status *status); /*---------------------------------------------------------------------------- | Options to indicate which negations to perform in float*_muladd() | Using these differs from negating an input or output before calling | the muladd function in that this means that a NaN doesn't have its | sign bit inverted before it is propagated. | We also support halving the result before rounding, as a special | case to support the ARM fused-sqrt-step instruction FRSQRTS. *----------------------------------------------------------------------------*/ enum { float_muladd_negate_c = 1, float_muladd_negate_product = 2, float_muladd_negate_result = 4, float_muladd_halve_result = 8, }; /*---------------------------------------------------------------------------- | Software IEC/IEEE integer-to-floating-point conversion routines. *----------------------------------------------------------------------------*/ floatx80 int32_to_floatx80(int32_t); floatx80 int64_to_floatx80(int64_t); /*---------------------------------------------------------------------------- | Software IEC/IEEE single-precision conversion routines. *----------------------------------------------------------------------------*/ floatx80 float32_to_floatx80(float32, float_status *status); floatx80 float32_to_floatx80_allowunnormal(float32, float_status *status); /*---------------------------------------------------------------------------- | Software IEC/IEEE double-precision conversion routines. *----------------------------------------------------------------------------*/ floatx80 float64_to_floatx80(float64, float_status *status); floatx80 float64_to_floatx80_allowunnormal( float64 a, float_status *status ); /*---------------------------------------------------------------------------- | Software IEC/IEEE extended double-precision conversion routines. *----------------------------------------------------------------------------*/ int32_t floatx80_to_int32(floatx80, float_status *status); #ifdef SOFTFLOAT_68K int16_t floatx80_to_int16(floatx80, float_status *status); int8_t floatx80_to_int8(floatx80, float_status *status); #endif int32_t floatx80_to_int32_round_to_zero(floatx80, float_status *status); int64_t floatx80_to_int64(floatx80, float_status *status); float32 floatx80_to_float32(floatx80, float_status *status); float64 floatx80_to_float64(floatx80, float_status *status); #ifdef SOFTFLOAT_68K floatx80 floatx80_to_floatx80( floatx80, float_status *status); floatx80 floatdecimal_to_floatx80(floatx80, float_status *status); floatx80 floatx80_to_floatdecimal(floatx80, int32_t*, float_status *status); #endif uint64_t extractFloatx80Frac( floatx80 a ); int32_t extractFloatx80Exp( floatx80 a ); flag extractFloatx80Sign( floatx80 a ); floatx80 floatx80_round_to_int_toward_zero( floatx80 a, float_status *status); floatx80 floatx80_round_to_float32( floatx80, float_status *status ); floatx80 floatx80_round_to_float64( floatx80, float_status *status ); floatx80 floatx80_round32( floatx80, float_status *status); floatx80 floatx80_round64( floatx80, float_status *status); flag floatx80_eq( floatx80, floatx80, float_status *status); flag floatx80_le( floatx80, floatx80, float_status *status); flag floatx80_lt( floatx80, floatx80, float_status *status); #ifdef SOFTFLOAT_68K // functions are in softfloat.c floatx80 roundSaveFloatx80Internal( int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status ); void getRoundedFloatInternal( int8_t roundingPrecision, flag *pzSign, int32_t *pzExp, uint64_t *pzSig ); floatx80 roundSigAndPackFloatx80( int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status ); floatx80 floatx80_move( floatx80 a, float_status *status ); floatx80 floatx80_abs( floatx80 a, float_status *status ); floatx80 floatx80_neg( floatx80 a, float_status *status ); floatx80 floatx80_getexp( floatx80 a, float_status *status ); floatx80 floatx80_getman( floatx80 a, float_status *status ); floatx80 floatx80_scale(floatx80 a, floatx80 b, float_status *status ); floatx80 floatx80_rem( floatx80 a, floatx80 b, uint64_t *q, flag *s, float_status *status ); floatx80 floatx80_mod( floatx80 a, floatx80 b, uint64_t *q, flag *s, float_status *status ); floatx80 floatx80_sglmul( floatx80 a, floatx80 b, float_status *status ); floatx80 floatx80_sgldiv( floatx80 a, floatx80 b, float_status *status ); floatx80 floatx80_cmp( floatx80 a, floatx80 b, float_status *status ); floatx80 floatx80_tst( floatx80 a, float_status *status ); // functions are in softfloat_fpsp.c floatx80 floatx80_acos(floatx80 a, float_status *status); floatx80 floatx80_asin(floatx80 a, float_status *status); floatx80 floatx80_atan(floatx80 a, float_status *status); floatx80 floatx80_atanh(floatx80 a, float_status *status); floatx80 floatx80_cos(floatx80 a, float_status *status); floatx80 floatx80_cosh(floatx80 a, float_status *status); floatx80 floatx80_etox(floatx80 a, float_status *status); floatx80 floatx80_etoxm1(floatx80 a, float_status *status); floatx80 floatx80_log10(floatx80 a, float_status *status); floatx80 floatx80_log2(floatx80 a, float_status *status); floatx80 floatx80_logn(floatx80 a, float_status *status); floatx80 floatx80_lognp1(floatx80 a, float_status *status); floatx80 floatx80_sin(floatx80 a, float_status *status); floatx80 floatx80_sinh(floatx80 a, float_status *status); floatx80 floatx80_tan(floatx80 a, float_status *status); floatx80 floatx80_tanh(floatx80 a, float_status *status); floatx80 floatx80_tentox(floatx80 a, float_status *status); floatx80 floatx80_twotox(floatx80 a, float_status *status); floatx80 floatx80_sincos(floatx80 a, floatx80 *c, float_status *status); #endif // functions originally internal to softfloat.c void normalizeFloatx80Subnormal( uint64_t aSig, int32_t *zExpPtr, uint64_t *zSigPtr ); floatx80 packFloatx80( flag zSign, int32_t zExp, uint64_t zSig ); floatx80 roundAndPackFloatx80(int8_t roundingPrecision, flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status); /*---------------------------------------------------------------------------- | Software IEC/IEEE extended double-precision operations. *----------------------------------------------------------------------------*/ floatx80 floatx80_round_to_int(floatx80, float_status *status); floatx80 floatx80_add(floatx80, floatx80, float_status *status); floatx80 floatx80_sub(floatx80, floatx80, float_status *status); floatx80 floatx80_mul(floatx80, floatx80, float_status *status); floatx80 floatx80_div(floatx80, floatx80, float_status *status); floatx80 floatx80_sqrt(floatx80, float_status *status); floatx80 floatx80_normalize(floatx80); floatx80 floatx80_denormalize(floatx80, flag); static inline int floatx80_is_zero_or_denormal(floatx80 a) { return (a.high & 0x7fff) == 0; } static inline int floatx80_is_any_nan(floatx80 a) { return ((a.high & 0x7fff) == 0x7fff) && (a.low<<1); } /*---------------------------------------------------------------------------- | Return whether the given value is an invalid floatx80 encoding. | Invalid floatx80 encodings arise when the integer bit is not set, but | the exponent is not zero. The only times the integer bit is permitted to | be zero is in subnormal numbers and the value zero. | This includes what the Intel software developer's manual calls pseudo-NaNs, | pseudo-infinities and un-normal numbers. It does not include | pseudo-denormals, which must still be correctly handled as inputs even | if they are never generated as outputs. *----------------------------------------------------------------------------*/ static inline bool floatx80_invalid_encoding(floatx80 a) { return (a.low & (1ULL << 63)) == 0 && (a.high & 0x7FFF) != 0 && (a.high & 0x7FFF) != 0x7FFF; } #define floatx80_zero make_floatx80(0x0000, 0x0000000000000000LL) #define floatx80_one make_floatx80(0x3fff, 0x8000000000000000LL) #define floatx80_ln2 make_floatx80(0x3ffe, 0xb17217f7d1cf79acLL) #define floatx80_pi make_floatx80(0x4000, 0xc90fdaa22168c235LL) #define floatx80_half make_floatx80(0x3ffe, 0x8000000000000000LL) #define floatx80_infinity make_floatx80(0x7fff, 0x8000000000000000LL) #endif /* SOFTFLOAT_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/softfloat_decimal.c000066400000000000000000000270361504763705000277530ustar00rootroot00000000000000/*============================================================================ This C source file is an extension to the SoftFloat IEC/IEEE Floating-point Arithmetic Package, Release 2a. =============================================================================*/ #include #include "sysconfig.h" #include "sysdeps.h" #define DECIMAL_LOG 0 #if DECIMAL_LOG #define decimal_log write_log #else #define decimal_log(fmt, ...) #endif #include "softfloat.h" #include "softfloat-macros.h" #include "softfloat/softfloat-specialize.h" /*---------------------------------------------------------------------------- | Methods for converting decimal floats to binary extended precision floats. *----------------------------------------------------------------------------*/ static void round128to64(flag aSign, int32_t *aExp, uint64_t *aSig0, uint64_t *aSig1, float_status *status) { flag increment; int32_t zExp; uint64_t zSig0, zSig1; zExp = *aExp; zSig0 = *aSig0; zSig1 = *aSig1; increment = ( (int64_t) zSig1 < 0 ); if (status->float_rounding_mode != float_round_nearest_even) { if (status->float_rounding_mode == float_round_to_zero) { increment = 0; } else { if (aSign) { increment = (status->float_rounding_mode == float_round_down) && zSig1; } else { increment = (status->float_rounding_mode == float_round_up) && zSig1; } } } if (increment) { ++zSig0; if (zSig0 == 0) { ++zExp; zSig0 = LIT64(0x8000000000000000); } else { if ((zSig1 << 1) == 0 && status->float_rounding_mode == float_round_nearest_even) zSig0 &= ~1; } } else { if ( zSig0 == 0 ) zExp = 0; } *aExp = zExp; *aSig0 = zSig0; *aSig1 = 0; } static void mul128by128round(int32_t *aExp, uint64_t *aSig0, uint64_t *aSig1, int32_t bExp, uint64_t bSig0, uint64_t bSig1, float_status *status) { int32_t zExp; uint64_t zSig0, zSig1, zSig2, zSig3; zExp = *aExp; zSig0 = *aSig0; zSig1 = *aSig1; round128to64(0, &bExp, &bSig0, &bSig1, status); zExp += bExp - 0x3FFE; mul128To256(zSig0, zSig1, bSig0, bSig1, &zSig0, &zSig1, &zSig2, &zSig3); zSig1 |= (zSig2 | zSig3) != 0; if ( 0 < (int64_t) zSig0 ) { shortShift128Left( zSig0, zSig1, 1, &zSig0, &zSig1 ); --zExp; } *aExp = zExp; *aSig0 = zSig0; *aSig1 = zSig1; round128to64(0, aExp, aSig0, aSig1, status); } static void mul128by128(int32_t *aExp, uint64_t *aSig0, uint64_t *aSig1, int32_t bExp, uint64_t bSig0, uint64_t bSig1) { int32_t zExp; uint64_t zSig0, zSig1, zSig2, zSig3; zExp = *aExp; zSig0 = *aSig0; zSig1 = *aSig1; zExp += bExp - 0x3FFE; mul128To256(zSig0, zSig1, bSig0, bSig1, &zSig0, &zSig1, &zSig2, &zSig3); zSig1 |= (zSig2 | zSig3) != 0; if ( 0 < (int64_t) zSig0 ) { shortShift128Left( zSig0, zSig1, 1, &zSig0, &zSig1 ); --zExp; } *aExp = zExp; *aSig0 = zSig0; *aSig1 = zSig1; } static void div128by128(int32_t *paExp, uint64_t *paSig0, uint64_t *paSig1, int32_t bExp, uint64_t bSig0, uint64_t bSig1) { int32_t zExp, aExp; uint64_t zSig0, zSig1, aSig0, aSig1; uint64_t rem0, rem1, rem2, rem3, term0, term1, term2, term3; aExp = *paExp; aSig0 = *paSig0; aSig1 = *paSig1; zExp = aExp - bExp + 0x3FFE; if ( le128( bSig0, bSig1, aSig0, aSig1 ) ) { shift128Right( aSig0, aSig1, 1, &aSig0, &aSig1 ); ++zExp; } zSig0 = estimateDiv128To64( aSig0, aSig1, bSig0 ); mul128By64To192( bSig0, bSig1, zSig0, &term0, &term1, &term2 ); sub192( aSig0, aSig1, 0, term0, term1, term2, &rem0, &rem1, &rem2 ); while ( (int64_t) rem0 < 0 ) { --zSig0; add192( rem0, rem1, rem2, 0, bSig0, bSig1, &rem0, &rem1, &rem2 ); } zSig1 = estimateDiv128To64( rem1, rem2, bSig0 ); if ( ( zSig1 & 0x3FFF ) <= 4 ) { mul128By64To192( bSig0, bSig1, zSig1, &term1, &term2, &term3 ); sub192( rem1, rem2, 0, term1, term2, term3, &rem1, &rem2, &rem3 ); while ( (int64_t) rem1 < 0 ) { --zSig1; add192( rem1, rem2, rem3, 0, bSig0, bSig1, &rem1, &rem2, &rem3 ); } zSig1 |= ( ( rem1 | rem2 | rem3 ) != 0 ); } *paExp = zExp; *paSig0 = zSig0; *paSig1 = zSig1; } #if 0 void tentoint128(flag mSign, flag eSign, int32_t *aExp, uint64_t *aSig0, uint64_t *aSig1, int32_t scale, float_status *status) { int32_t mExp; uint64_t mSig0, mSig1; *aExp = 0x3FFF; *aSig0 = LIT64(0x8000000000000000); *aSig1 = 0; mExp = 0x4002; mSig0 = LIT64(0xA000000000000000); mSig1 = 0; while (scale) { if (scale & 1) { mul128by128round(aExp, aSig0, aSig1, mExp, mSig0, mSig1, status); } mul128by128(&mExp, &mSig0, &mSig1, mExp, mSig0, mSig1); scale >>= 1; } } #else static void tentoint128(flag mSign, flag eSign, int32_t *aExp, uint64_t *aSig0, uint64_t *aSig1, int32_t scale, float_status *status) { int8_t save_rounding_mode; int32_t mExp; uint64_t mSig0, mSig1; save_rounding_mode = status->float_rounding_mode; switch (status->float_rounding_mode) { case float_round_nearest_even: break; case float_round_down: if (mSign != eSign) { set_float_rounding_mode(float_round_up, status); } break; case float_round_up: if (mSign != eSign) { set_float_rounding_mode(float_round_down, status); } break; case float_round_to_zero: if (eSign == 0) { set_float_rounding_mode(float_round_down, status); } else { set_float_rounding_mode(float_round_up, status); } break; default: break; } *aExp = 0x3FFF; *aSig0 = LIT64(0x8000000000000000); *aSig1 = 0; mExp = 0x4002; mSig0 = LIT64(0xA000000000000000); mSig1 = 0; while (scale) { if (scale & 1) { mul128by128round(aExp, aSig0, aSig1, mExp, mSig0, mSig1, status); } mul128by128(&mExp, &mSig0, &mSig1, mExp, mSig0, mSig1); scale >>= 1; } set_float_rounding_mode(save_rounding_mode, status); } #endif static int64_t tentointdec(int32_t scale) { uint64_t decM, decX; decX = 1; decM = 10; while (scale) { if (scale & 1) { decX *= decM; } decM *= decM; scale >>= 1; } return decX; } static int64_t float128toint64(flag zSign, int32_t zExp, uint64_t zSig0, uint64_t zSig1, float_status *status) { int8_t roundingMode; flag roundNearestEven, increment; int64_t z; shift128RightJamming(zSig0, zSig1, 0x403E - zExp, &zSig0, &zSig1); roundingMode = status->float_rounding_mode; roundNearestEven = (roundingMode == float_round_nearest_even); increment = ((int64_t)zSig1 < 0); if (!roundNearestEven) { if (roundingMode == float_round_to_zero) { increment = 0; } else { if (zSign) { increment = (roundingMode == float_round_down ) && zSig1; } else { increment = (roundingMode == float_round_up ) && zSig1; } } } if (increment) { ++zSig0; zSig0 &= ~ (((uint64_t)(zSig1<<1) == 0) & roundNearestEven); } z = zSig0; if (zSig1) float_raise(float_flag_inexact, status); return z; } static int32_t getDecimalExponent(int32_t aExp, uint64_t aSig) { flag zSign; int32_t zExp, shiftCount; uint64_t zSig0, zSig1; if (aSig == 0 || aExp == 0x3FFF) { return 0; } if (aExp < 0) { return -4932; } aSig ^= LIT64(0x8000000000000000); aExp -= 0x3FFF; zSign = (aExp < 0); aExp = zSign ? -aExp : aExp; shiftCount = 31 - countLeadingZeros32(aExp); zExp = 0x3FFF + shiftCount; if (shiftCount < 0) { shortShift128Left(aSig, 0, -shiftCount, &zSig0, &zSig1); } else { shift128Right(aSig, 0, shiftCount, &zSig0, &zSig1); aSig = (uint64_t)aExp << (63 - shiftCount); if (zSign) { sub128(aSig, 0, zSig0, zSig1, &zSig0, &zSig1); } else { add128(aSig, 0, zSig0, zSig1, &zSig0, &zSig1); } } shiftCount = countLeadingZeros64(zSig0); shortShift128Left(zSig0, zSig1, shiftCount, &zSig0, &zSig1); zExp -= shiftCount; mul128by128(&zExp, &zSig0, &zSig1, 0x3FFD, LIT64(0x9A209A84FBCFF798), LIT64(0x8F8959AC0B7C9178)); shiftCount = 0x403E - zExp; shift128RightJamming(zSig0, zSig1, shiftCount, &zSig0, &zSig1); if ((int64_t)zSig1 < 0) { ++zSig0; zSig0 &= ~(((int64_t)(zSig1<<1) == 0) & 1); } zExp = (int32_t)(zSign ? (0 - zSig0) : zSig0); return zExp; } /*---------------------------------------------------------------------------- | Decimal to binary *----------------------------------------------------------------------------*/ floatx80 floatdecimal_to_floatx80(floatx80 a, float_status *status) { flag decSign, zSign, decExpSign; int32_t decExp, zExp, xExp, shiftCount; uint64_t decSig, zSig0, zSig1, xSig0, xSig1; decSign = extractFloatx80Sign(a); decExp = extractFloatx80Exp(a); decSig = extractFloatx80Frac(a); if (decExp == 0x7FFF) return a; if (decExp == 0 && decSig == 0) return a; decExpSign = (decExp >> 14) & 1; decExp &= 0x3FFF; shiftCount = countLeadingZeros64( decSig ); zExp = 0x403E - shiftCount; zSig0 = decSig << shiftCount; zSig1 = 0; zSign = decSign; tentoint128(decSign, decExpSign, &xExp, &xSig0, &xSig1, decExp, status); if (decExpSign) { div128by128(&zExp, &zSig0, &zSig1, xExp, xSig0, xSig1); } else { mul128by128(&zExp, &zSig0, &zSig1, xExp, xSig0, xSig1); } if (zSig1) float_raise(float_flag_decimal, status); round128to64(zSign, &zExp, &zSig0, &zSig1, status); return packFloatx80( zSign, zExp, zSig0 ); } /*---------------------------------------------------------------------------- | Binary to decimal *----------------------------------------------------------------------------*/ floatx80 floatx80_to_floatdecimal(floatx80 a, int32_t *k, float_status *status) { flag aSign, decSign; int32_t aExp, decExp, zExp, xExp; uint64_t aSig, decSig, decX, zSig0, zSig1, xSig0, xSig1; flag ictr, lambda; int32_t kfactor, ilog, iscale, len; aSign = extractFloatx80Sign(a); aExp = extractFloatx80Exp(a); aSig = extractFloatx80Frac(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); return a; } if (aExp == 0) { if (aSig == 0) return packFloatx80(aSign, 0, 0); normalizeFloatx80Subnormal(aSig, &aExp, &aSig); } kfactor = *k; ilog = getDecimalExponent(aExp, aSig); ictr = 0; try_again: decimal_log(_T("ILOG = %i\n"), ilog); if (kfactor > 0) { if (kfactor > 17) { kfactor = 17; float_raise(float_flag_invalid, status); } len = kfactor; } else { len = ilog + 1 - kfactor; if (len > 17) { len = 17; } if (len < 1) { len = 1; } if (kfactor > ilog) { ilog = kfactor; decimal_log(_T("ILOG is kfactor = %i\n"), ilog); } } decimal_log(_T("LEN = %i\n"),len); lambda = 0; iscale = ilog + 1 - len; if (iscale < 0) { lambda = 1; iscale = -iscale; } decimal_log(_T("ISCALE = %i, LAMBDA = %i\n"),iscale, lambda); tentoint128(lambda, 0, &xExp, &xSig0, &xSig1, iscale, status); decimal_log(_T("AFTER tentoint128: zExp = %04x, zSig0 = %16llx, zSig1 = %16llx\n"), xExp, xSig0, xSig1); zExp = aExp; zSig0 = aSig; zSig1 = 0; if (lambda) { mul128by128(&zExp, &zSig0, &zSig1, xExp, xSig0, xSig1); } else { div128by128(&zExp, &zSig0, &zSig1, xExp, xSig0, xSig1); } decimal_log(_T("BEFORE: zExp = %04x, zSig0 = %16llx, zSig1 = %16llx\n"),zExp,zSig0,zSig1); decSig = float128toint64(aSign, zExp, zSig0, zSig1, status); decimal_log(_T("AFTER: decSig = %llu\n"),decSig); if (ictr == 0) { decX = tentointdec(len - 1); if (decSig < decX) { // z < x ilog -= 1; ictr = 1; goto try_again; } decX *= 10; if (decSig > decX) { // z > x ilog += 1; ictr = 1; goto try_again; } } decSign = aSign; decExp = (ilog < 0) ? -ilog : ilog; if (decExp > 999) { float_raise(float_flag_invalid, status); } if (ilog < 0) decExp |= 0x4000; *k = len; return packFloatx80(decSign, decExp, decSig); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/softfloat_fpsp.c000066400000000000000000002361571504763705000273330ustar00rootroot00000000000000 /*============================================================================ This C source file is an extension to the SoftFloat IEC/IEEE Floating-point Arithmetic Package, Release 2a. Written by Andreas Grabher for Previous, NeXT Computer Emulator. =============================================================================*/ #include #include #include "softfloat.h" #include "softfloat-specialize.h" #include "softfloat_fpsp_tables.h" /*---------------------------------------------------------------------------- | Algorithms for transcendental functions supported by MC68881 and MC68882 | mathematical coprocessors. The functions are derived from FPSP library. *----------------------------------------------------------------------------*/ #define pi_sig LIT64(0xc90fdaa22168c235) #define pi_sig0 LIT64(0xc90fdaa22168c234) #define pi_sig1 LIT64(0xc4c6628b80dc1cd1) #define pi_exp 0x4000 #define piby2_exp 0x3FFF #define piby4_exp 0x3FFE #define one_exp 0x3FFF #define one_sig LIT64(0x8000000000000000) #define SET_PREC \ int8_t user_rnd_mode, user_rnd_prec; \ user_rnd_mode = status->float_rounding_mode; \ user_rnd_prec = status->floatx80_rounding_precision; \ status->float_rounding_mode = float_round_nearest_even; \ status->floatx80_rounding_precision = 80 #define RESET_PREC \ status->float_rounding_mode = user_rnd_mode; \ status->floatx80_rounding_precision = user_rnd_prec /*---------------------------------------------------------------------------- | Function for compactifying extended double-precision floating point values. *----------------------------------------------------------------------------*/ static int32_t floatx80_make_compact(int32_t aExp, uint64_t aSig) { return (aExp<<16)|(aSig>>48); } /*---------------------------------------------------------------------------- | Arc cosine *----------------------------------------------------------------------------*/ floatx80 floatx80_acos(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact; floatx80 fp0, fp1, one; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF && (uint64_t) (aSig<<1)) { return propagateFloatx80NaNOneArg(a, status); } if (aExp == 0 && aSig == 0) { float_raise(float_flag_inexact, status); return roundAndPackFloatx80(status->floatx80_rounding_precision, 0, piby2_exp, pi_sig, 0, status); } compact = floatx80_make_compact(aExp, aSig); if (compact >= 0x3FFF8000) { // |X| >= 1 if (aExp == one_exp && aSig == one_sig) { // |X| == 1 if (aSign) { // X == -1 a = packFloatx80(0, pi_exp, pi_sig); float_raise(float_flag_inexact, status); return floatx80_move(a, status); } else { // X == +1 return packFloatx80(0, 0, 0); } } else { // |X| > 1 float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } } // |X| < 1 SET_PREC; one = packFloatx80(0, one_exp, one_sig); fp0 = a; fp1 = floatx80_add(one, fp0, status); // 1 + X fp0 = floatx80_sub(one, fp0, status); // 1 - X fp0 = floatx80_div(fp0, fp1, status); // (1-X)/(1+X) fp0 = floatx80_sqrt(fp0, status); // SQRT((1-X)/(1+X)) fp0 = floatx80_atan(fp0, status); // ATAN(SQRT((1-X)/(1+X))) RESET_PREC; a = floatx80_add(fp0, fp0, status); // 2 * ATAN(SQRT((1-X)/(1+X))) float_raise(float_flag_inexact, status); return a; } /*---------------------------------------------------------------------------- | Arc sine *----------------------------------------------------------------------------*/ floatx80 floatx80_asin(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact; floatx80 fp0, fp1, fp2, one; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF && (uint64_t) (aSig<<1)) { return propagateFloatx80NaNOneArg(a, status); } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } compact = floatx80_make_compact(aExp, aSig); if (compact >= 0x3FFF8000) { // |X| >= 1 if (aExp == one_exp && aSig == one_sig) { // |X| == 1 float_raise(float_flag_inexact, status); a = packFloatx80(aSign, piby2_exp, pi_sig); return floatx80_move(a, status); } else { // |X| > 1 float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } } // |X| < 1 SET_PREC; one = packFloatx80(0, one_exp, one_sig); fp0 = a; fp1 = floatx80_sub(one, fp0, status); // 1 - X fp2 = floatx80_add(one, fp0, status); // 1 + X fp1 = floatx80_mul(fp2, fp1, status); // (1+X)*(1-X) fp1 = floatx80_sqrt(fp1, status); // SQRT((1+X)*(1-X)) fp0 = floatx80_div(fp0, fp1, status); // X/SQRT((1+X)*(1-X)) RESET_PREC; a = floatx80_atan(fp0, status); // ATAN(X/SQRT((1+X)*(1-X))) float_raise(float_flag_inexact, status); return a; } /*---------------------------------------------------------------------------- | Arc tangent *----------------------------------------------------------------------------*/ floatx80 floatx80_atan(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact, tbl_index; floatx80 fp0, fp1, fp2, fp3, xsave; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); a = packFloatx80(aSign, piby2_exp, pi_sig); float_raise(float_flag_inexact, status); return floatx80_move(a, status); } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } compact = floatx80_make_compact(aExp, aSig); SET_PREC; if (compact < 0x3FFB8000 || compact > 0x4002FFFF) { // |X| >= 16 or |X| < 1/16 if (compact > 0x3FFF8000) { // |X| >= 16 if (compact > 0x40638000) { // |X| > 2^(100) fp0 = packFloatx80(aSign, piby2_exp, pi_sig); fp1 = packFloatx80(aSign, 0x0001, one_sig); RESET_PREC; a = floatx80_sub(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } else { fp0 = a; fp1 = packFloatx80(1, one_exp, one_sig); // -1 fp1 = floatx80_div(fp1, fp0, status); // X' = -1/X xsave = fp1; fp0 = floatx80_mul(fp1, fp1, status); // Y = X'*X' fp1 = floatx80_mul(fp0, fp0, status); // Z = Y*Y fp3 = float64_to_floatx80(LIT64(0xBFB70BF398539E6A), status); // C5 fp2 = float64_to_floatx80(LIT64(0x3FBC7187962D1D7D), status); // C4 fp3 = floatx80_mul(fp3, fp1, status); // Z*C5 fp2 = floatx80_mul(fp2, fp1, status); // Z*C4 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBFC24924827107B8), status), status); // C3+Z*C5 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FC999999996263E), status), status); // C2+Z*C4 fp1 = floatx80_mul(fp1, fp3, status); // Z*(C3+Z*C5) fp2 = floatx80_mul(fp2, fp0, status); // Y*(C2+Z*C4) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0xBFD5555555555536), status), status); // C1+Z*(C3+Z*C5) fp0 = floatx80_mul(fp0, xsave, status); // X'*Y fp1 = floatx80_add(fp1, fp2, status); // [Y*(C2+Z*C4)]+[C1+Z*(C3+Z*C5)] fp0 = floatx80_mul(fp0, fp1, status); // X'*Y*([B1+Z*(B3+Z*B5)]+[Y*(B2+Z*(B4+Z*B6))]) ?? fp0 = floatx80_add(fp0, xsave, status); fp1 = packFloatx80(aSign, piby2_exp, pi_sig); RESET_PREC; a = floatx80_add(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } } else { // |X| < 1/16 if (compact < 0x3FD78000) { // |X| < 2^(-40) RESET_PREC; a = floatx80_move(a, status); float_raise(float_flag_inexact, status); return a; } else { fp0 = a; xsave = a; fp0 = floatx80_mul(fp0, fp0, status); // Y = X*X fp1 = floatx80_mul(fp0, fp0, status); // Z = Y*Y fp2 = float64_to_floatx80(LIT64(0x3FB344447F876989), status); // B6 fp3 = float64_to_floatx80(LIT64(0xBFB744EE7FAF45DB), status); // B5 fp2 = floatx80_mul(fp2, fp1, status); // Z*B6 fp3 = floatx80_mul(fp3, fp1, status); // Z*B5 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FBC71C646940220), status), status); // B4+Z*B6 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBFC24924921872F9), status), status); // B3+Z*B5 fp2 = floatx80_mul(fp2, fp1, status); // Z*(B4+Z*B6) fp1 = floatx80_mul(fp1, fp3, status); // Z*(B3+Z*B5) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FC9999999998FA9), status), status); // B2+Z*(B4+Z*B6) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0xBFD5555555555555), status), status); // B1+Z*(B3+Z*B5) fp2 = floatx80_mul(fp2, fp0, status); // Y*(B2+Z*(B4+Z*B6)) fp0 = floatx80_mul(fp0, xsave, status); // X*Y fp1 = floatx80_add(fp1, fp2, status); // [B1+Z*(B3+Z*B5)]+[Y*(B2+Z*(B4+Z*B6))] fp0 = floatx80_mul(fp0, fp1, status); // X*Y*([B1+Z*(B3+Z*B5)]+[Y*(B2+Z*(B4+Z*B6))]) RESET_PREC; a = floatx80_add(fp0, xsave, status); float_raise(float_flag_inexact, status); return a; } } } else { aSig &= LIT64(0xF800000000000000); aSig |= LIT64(0x0400000000000000); xsave = packFloatx80(aSign, aExp, aSig); // F fp0 = a; fp1 = a; // X fp2 = packFloatx80(0, one_exp, one_sig); // 1 fp1 = floatx80_mul(fp1, xsave, status); // X*F fp0 = floatx80_sub(fp0, xsave, status); // X-F fp1 = floatx80_add(fp1, fp2, status); // 1 + X*F fp0 = floatx80_div(fp0, fp1, status); // U = (X-F)/(1+X*F) tbl_index = compact; tbl_index &= 0x7FFF0000; tbl_index -= 0x3FFB0000; tbl_index >>= 1; tbl_index += compact&0x00007800; tbl_index >>= 11; fp3 = atan_tbl[tbl_index]; fp3.high |= aSign ? 0x8000 : 0; // ATAN(F) fp1 = floatx80_mul(fp0, fp0, status); // V = U*U fp2 = float64_to_floatx80(LIT64(0xBFF6687E314987D8), status); // A3 fp2 = floatx80_add(fp2, fp1, status); // A3+V fp2 = floatx80_mul(fp2, fp1, status); // V*(A3+V) fp1 = floatx80_mul(fp1, fp0, status); // U*V fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x4002AC6934A26DB3), status), status); // A2+V*(A3+V) fp1 = floatx80_mul(fp1, float64_to_floatx80(LIT64(0xBFC2476F4E1DA28E), status), status); // A1+U*V fp1 = floatx80_mul(fp1, fp2, status); // A1*U*V*(A2+V*(A3+V)) fp0 = floatx80_add(fp0, fp1, status); // ATAN(U) RESET_PREC; a = floatx80_add(fp0, fp3, status); // ATAN(X) float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | Hyperbolic arc tangent *----------------------------------------------------------------------------*/ floatx80 floatx80_atanh(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact; floatx80 fp0, fp1, fp2, one; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF && (uint64_t) (aSig<<1)) { return propagateFloatx80NaNOneArg(a, status); } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } compact = floatx80_make_compact(aExp, aSig); if (compact >= 0x3FFF8000) { // |X| >= 1 if (aExp == one_exp && aSig == one_sig) { // |X| == 1 float_raise(float_flag_divbyzero, status); return packFloatx80(aSign, 0x7FFF, floatx80_default_infinity_low); } else { // |X| > 1 float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } } // |X| < 1 SET_PREC; one = packFloatx80(0, one_exp, one_sig); fp2 = packFloatx80(aSign, 0x3FFE, one_sig); // SIGN(X) * (1/2) fp0 = packFloatx80(0, aExp, aSig); // Y = |X| fp1 = packFloatx80(1, aExp, aSig); // -Y fp0 = floatx80_add(fp0, fp0, status); // 2Y fp1 = floatx80_add(fp1, one, status); // 1-Y fp0 = floatx80_div(fp0, fp1, status); // Z = 2Y/(1-Y) fp0 = floatx80_lognp1(fp0, status); // LOG1P(Z) RESET_PREC; a = floatx80_mul(fp0, fp2, status); // ATANH(X) = SIGN(X) * (1/2) * LOG1P(Z) float_raise(float_flag_inexact, status); return a; } /*---------------------------------------------------------------------------- | Cosine *----------------------------------------------------------------------------*/ floatx80 floatx80_cos(floatx80 a, float_status *status) { flag aSign, xSign; int32_t aExp, xExp; uint64_t aSig, xSig; int32_t compact, l, n, j; floatx80 fp0, fp1, fp2, fp3, fp4, fp5, x, invtwopi, twopi1, twopi2; float32 posneg1, twoto63; flag adjn, endflag; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } if (aExp == 0 && aSig == 0) { return packFloatx80(0, one_exp, one_sig); } adjn = 1; SET_PREC; compact = floatx80_make_compact(aExp, aSig); fp0 = a; if (compact < 0x3FD78000 || compact > 0x4004BC7E) { // 2^(-40) > |X| > 15 PI if (compact > 0x3FFF8000) { // |X| >= 15 PI // REDUCEX fp1 = packFloatx80(0, 0, 0); if (compact == 0x7FFEFFFF) { twopi1 = packFloatx80(aSign ^ 1, 0x7FFE, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(aSign ^ 1, 0x7FDC, LIT64(0x85A308D300000000)); fp0 = floatx80_add(fp0, twopi1, status); fp1 = fp0; fp0 = floatx80_add(fp0, twopi2, status); fp1 = floatx80_sub(fp1, fp0, status); fp1 = floatx80_add(fp1, twopi2, status); } loop: xSign = extractFloatx80Sign(fp0); xExp = extractFloatx80Exp(fp0); xExp -= 0x3FFF; if (xExp <= 28) { l = 0; endflag = 1; } else { l = xExp - 27; endflag = 0; } invtwopi = packFloatx80(0, 0x3FFE - l, LIT64(0xA2F9836E4E44152A)); // INVTWOPI twopi1 = packFloatx80(0, 0x3FFF + l, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(0, 0x3FDD + l, LIT64(0x85A308D300000000)); twoto63 = 0x5F000000; twoto63 |= xSign ? 0x80000000 : 0x00000000; // SIGN(INARG)*2^63 IN SGL fp2 = floatx80_mul(fp0, invtwopi, status); fp2 = floatx80_add(fp2, float32_to_floatx80(twoto63, status), status); // THE FRACTIONAL PART OF FP2 IS ROUNDED fp2 = floatx80_sub(fp2, float32_to_floatx80(twoto63, status), status); // FP2 is N fp4 = floatx80_mul(twopi1, fp2, status); // W = N*P1 fp5 = floatx80_mul(twopi2, fp2, status); // w = N*P2 fp3 = floatx80_add(fp4, fp5, status); // FP3 is P fp4 = floatx80_sub(fp4, fp3, status); // W-P fp0 = floatx80_sub(fp0, fp3, status); // FP0 is A := R - P fp4 = floatx80_add(fp4, fp5, status); // FP4 is p = (W-P)+w fp3 = fp0; // FP3 is A fp1 = floatx80_sub(fp1, fp4, status); // FP1 is a := r - p fp0 = floatx80_add(fp0, fp1, status); // FP0 is R := A+a if (endflag > 0) { n = floatx80_to_int32(fp2, status); goto sincont; } fp3 = floatx80_sub(fp3, fp0, status); // A-R fp1 = floatx80_add(fp1, fp3, status); // FP1 is r := (A-R)+a goto loop; } else { // SINSM fp0 = float32_to_floatx80(0x3F800000, status); // 1 RESET_PREC; if (adjn) { // COSTINY a = floatx80_sub(fp0, float32_to_floatx80(0x00800000, status), status); } else { // SINTINY a = floatx80_move(a, status); } float_raise(float_flag_inexact, status); return a; } } else { fp1 = floatx80_mul(fp0, float64_to_floatx80(LIT64(0x3FE45F306DC9C883), status), status); // X*2/PI n = floatx80_to_int32(fp1, status); j = 32 + n; fp0 = floatx80_sub(fp0, pi_tbl[j], status); // X-Y1 fp0 = floatx80_sub(fp0, float32_to_floatx80(pi_tbl2[j], status), status); // FP0 IS R = (X-Y1)-Y2 sincont: if ((n + adjn) & 1) { // COSPOLY fp0 = floatx80_mul(fp0, fp0, status); // FP0 IS S fp1 = floatx80_mul(fp0, fp0, status); // FP1 IS T fp2 = float64_to_floatx80(LIT64(0x3D2AC4D0D6011EE3), status); // B8 fp3 = float64_to_floatx80(LIT64(0xBDA9396F9F45AC19), status); // B7 xSign = extractFloatx80Sign(fp0); // X IS S xExp = extractFloatx80Exp(fp0); xSig = extractFloatx80Frac(fp0); if (((n + adjn) >> 1) & 1) { xSign ^= 1; posneg1 = 0xBF800000; // -1 } else { xSign ^= 0; posneg1 = 0x3F800000; // 1 } // X IS NOW R'= SGN*R fp2 = floatx80_mul(fp2, fp1, status); // TB8 fp3 = floatx80_mul(fp3, fp1, status); // TB7 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3E21EED90612C972), status), status); // B6+TB8 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBE927E4FB79D9FCF), status), status); // B5+TB7 fp2 = floatx80_mul(fp2, fp1, status); // T(B6+TB8) fp3 = floatx80_mul(fp3, fp1, status); // T(B5+TB7) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EFA01A01A01D423), status), status); // B4+T(B6+TB8) fp4 = packFloatx80(1, 0x3FF5, LIT64(0xB60B60B60B61D438)); fp3 = floatx80_add(fp3, fp4, status); // B3+T(B5+TB7) fp2 = floatx80_mul(fp2, fp1, status); // T(B4+T(B6+TB8)) fp1 = floatx80_mul(fp1, fp3, status); // T(B3+T(B5+TB7)) fp4 = packFloatx80(0, 0x3FFA, LIT64(0xAAAAAAAAAAAAAB5E)); fp2 = floatx80_add(fp2, fp4, status); // B2+T(B4+T(B6+TB8)) fp1 = floatx80_add(fp1, float32_to_floatx80(0xBF000000, status), status); // B1+T(B3+T(B5+TB7)) fp0 = floatx80_mul(fp0, fp2, status); // S(B2+T(B4+T(B6+TB8))) fp0 = floatx80_add(fp0, fp1, status); // [B1+T(B3+T(B5+TB7))]+[S(B2+T(B4+T(B6+TB8)))] x = packFloatx80(xSign, xExp, xSig); fp0 = floatx80_mul(fp0, x, status); RESET_PREC; a = floatx80_add(fp0, float32_to_floatx80(posneg1, status), status); float_raise(float_flag_inexact, status); return a; } else { // SINPOLY xSign = extractFloatx80Sign(fp0); // X IS R xExp = extractFloatx80Exp(fp0); xSig = extractFloatx80Frac(fp0); xSign ^= ((n + adjn) >> 1) & 1; // X IS NOW R'= SGN*R fp0 = floatx80_mul(fp0, fp0, status); // FP0 IS S fp1 = floatx80_mul(fp0, fp0, status); // FP1 IS T fp3 = float64_to_floatx80(LIT64(0xBD6AAA77CCC994F5), status); // A7 fp2 = float64_to_floatx80(LIT64(0x3DE612097AAE8DA1), status); // A6 fp3 = floatx80_mul(fp3, fp1, status); // T*A7 fp2 = floatx80_mul(fp2, fp1, status); // T*A6 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBE5AE6452A118AE4), status), status); // A5+T*A7 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EC71DE3A5341531), status), status); // A4+T*A6 fp3 = floatx80_mul(fp3, fp1, status); // T(A5+TA7) fp2 = floatx80_mul(fp2, fp1, status); // T(A4+TA6) fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBF2A01A01A018B59), status), status); // A3+T(A5+TA7) fp4 = packFloatx80(0, 0x3FF8, LIT64(0x88888888888859AF)); fp2 = floatx80_add(fp2, fp4, status); // A2+T(A4+TA6) fp1 = floatx80_mul(fp1, fp3, status); // T(A3+T(A5+TA7)) fp2 = floatx80_mul(fp2, fp0, status); // S(A2+T(A4+TA6)) fp4 = packFloatx80(1, 0x3FFC, LIT64(0xAAAAAAAAAAAAAA99)); fp1 = floatx80_add(fp1, fp4, status); // A1+T(A3+T(A5+TA7)) fp1 = floatx80_add(fp1, fp2, status); // [A1+T(A3+T(A5+TA7))]+[S(A2+T(A4+TA6))] x = packFloatx80(xSign, xExp, xSig); fp0 = floatx80_mul(fp0, x, status); // R'*S fp0 = floatx80_mul(fp0, fp1, status); // SIN(R')-R' RESET_PREC; a = floatx80_add(fp0, x, status); float_raise(float_flag_inexact, status); return a; } } } /*---------------------------------------------------------------------------- | Hyperbolic cosine *----------------------------------------------------------------------------*/ floatx80 floatx80_cosh(floatx80 a, float_status *status) { int32_t aExp; uint64_t aSig; int32_t compact; floatx80 fp0, fp1; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); return packFloatx80(0, aExp, aSig); } if (aExp == 0 && aSig == 0) { return packFloatx80(0, one_exp, one_sig); } SET_PREC; compact = floatx80_make_compact(aExp, aSig); if (compact > 0x400CB167) { if (compact > 0x400CB2B3) { RESET_PREC; a = roundAndPackFloatx80(status->floatx80_rounding_precision, 0, 0x8000, one_sig, 0, status); float_raise(float_flag_inexact, status); return a; } else { fp0 = packFloatx80(0, aExp, aSig); fp0 = floatx80_sub(fp0, float64_to_floatx80(LIT64(0x40C62D38D3D64634), status), status); fp0 = floatx80_sub(fp0, float64_to_floatx80(LIT64(0x3D6F90AEB1E75CC7), status), status); fp0 = floatx80_etox(fp0, status); fp1 = packFloatx80(0, 0x7FFB, one_sig); RESET_PREC; a = floatx80_mul(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } } fp0 = packFloatx80(0, aExp, aSig); // |X| fp0 = floatx80_etox(fp0, status); // EXP(|X|) fp0 = floatx80_mul(fp0, float32_to_floatx80(0x3F000000, status), status); // (1/2)*EXP(|X|) fp1 = float32_to_floatx80(0x3E800000, status); // 1/4 fp1 = floatx80_div(fp1, fp0, status); // 1/(2*EXP(|X|)) RESET_PREC; a = floatx80_add(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } /*---------------------------------------------------------------------------- | e to x *----------------------------------------------------------------------------*/ floatx80 floatx80_etox(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact, n, j, k, m, m1; floatx80 fp0, fp1, fp2, fp3, l2, scale, adjscale; flag adjflag; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign) return packFloatx80(0, 0, 0); return a; } if (aExp == 0 && aSig == 0) { return packFloatx80(0, one_exp, one_sig); } SET_PREC; if (aExp >= 0x3FBE) { // |X| >= 2^(-65) compact = floatx80_make_compact(aExp, aSig); if (compact < 0x400CB167) { // |X| < 16380 log2 fp0 = a; fp1 = a; fp0 = floatx80_mul(fp0, float32_to_floatx80(0x42B8AA3B, status), status); // 64/log2 * X adjflag = 0; n = floatx80_to_int32(fp0, status); // int(64/log2*X) fp0 = int32_to_floatx80(n); j = n & 0x3F; // J = N mod 64 m = n / 64; // NOTE: this is really arithmetic right shift by 6 if (n < 0 && j) { // arithmetic right shift is division and round towards minus infinity m--; } m += 0x3FFF; // biased exponent of 2^(M) expcont1: fp2 = fp0; // N fp0 = floatx80_mul(fp0, float32_to_floatx80(0xBC317218, status), status); // N * L1, L1 = lead(-log2/64) l2 = packFloatx80(0, 0x3FDC, LIT64(0x82E308654361C4C6)); fp2 = floatx80_mul(fp2, l2, status); // N * L2, L1+L2 = -log2/64 fp0 = floatx80_add(fp0, fp1, status); // X + N*L1 fp0 = floatx80_add(fp0, fp2, status); // R fp1 = floatx80_mul(fp0, fp0, status); // S = R*R fp2 = float32_to_floatx80(0x3AB60B70, status); // A5 fp2 = floatx80_mul(fp2, fp1, status); // fp2 is S*A5 fp3 = floatx80_mul(float32_to_floatx80(0x3C088895, status), fp1, status); // fp3 is S*A4 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FA5555555554431), status), status); // fp2 is A3+S*A5 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0x3FC5555555554018), status), status); // fp3 is A2+S*A4 fp2 = floatx80_mul(fp2, fp1, status); // fp2 is S*(A3+S*A5) fp3 = floatx80_mul(fp3, fp1, status); // fp3 is S*(A2+S*A4) fp2 = floatx80_add(fp2, float32_to_floatx80(0x3F000000, status), status); // fp2 is A1+S*(A3+S*A5) fp3 = floatx80_mul(fp3, fp0, status); // fp3 IS R*S*(A2+S*A4) fp2 = floatx80_mul(fp2, fp1, status); // fp2 IS S*(A1+S*(A3+S*A5)) fp0 = floatx80_add(fp0, fp3, status); // fp0 IS R+R*S*(A2+S*A4) fp0 = floatx80_add(fp0, fp2, status); // fp0 IS EXP(R) - 1 fp1 = exp_tbl[j]; fp0 = floatx80_mul(fp0, fp1, status); // 2^(J/64)*(Exp(R)-1) fp0 = floatx80_add(fp0, float32_to_floatx80(exp_tbl2[j], status), status); // accurate 2^(J/64) fp0 = floatx80_add(fp0, fp1, status); // 2^(J/64) + 2^(J/64)*(Exp(R)-1) scale = packFloatx80(0, m, one_sig); if (adjflag) { adjscale = packFloatx80(0, m1, one_sig); fp0 = floatx80_mul(fp0, adjscale, status); } RESET_PREC; a = floatx80_mul(fp0, scale, status); float_raise(float_flag_inexact, status); return a; } else { // |X| >= 16380 log2 if (compact > 0x400CB27C) { // |X| >= 16480 log2 RESET_PREC; if (aSign) { a = roundAndPackFloatx80(status->floatx80_rounding_precision, 0, -0x1000, aSig, 0, status); } else { a = roundAndPackFloatx80(status->floatx80_rounding_precision, 0, 0x8000, aSig, 0, status); } float_raise(float_flag_inexact, status); return a; } else { fp0 = a; fp1 = a; fp0 = floatx80_mul(fp0, float32_to_floatx80(0x42B8AA3B, status), status); // 64/log2 * X adjflag = 1; n = floatx80_to_int32(fp0, status); // int(64/log2*X) fp0 = int32_to_floatx80(n); j = n & 0x3F; // J = N mod 64 k = n / 64; // NOTE: this is really arithmetic right shift by 6 if (n < 0 && j) { // arithmetic right shift is division and round towards minus infinity k--; } m1 = k / 2; // NOTE: this is really arithmetic right shift by 1 if (k < 0 && (k & 1)) { // arithmetic right shift is division and round towards minus infinity m1--; } m = k - m1; m1 += 0x3FFF; // biased exponent of 2^(M1) m += 0x3FFF; // biased exponent of 2^(M) goto expcont1; } } } else { // |X| < 2^(-65) RESET_PREC; a = floatx80_add(a, float32_to_floatx80(0x3F800000, status), status); // 1 + X float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | e to x minus 1 *----------------------------------------------------------------------------*/ floatx80 floatx80_etoxm1(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact, n, j, m, m1; floatx80 fp0, fp1, fp2, fp3, l2, sc, onebysc; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign) return packFloatx80(aSign, one_exp, one_sig); return a; } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } SET_PREC; if (aExp >= 0x3FFD) { // |X| >= 1/4 compact = floatx80_make_compact(aExp, aSig); if (compact <= 0x4004C215) { // |X| <= 70 log2 fp0 = a; fp1 = a; fp0 = floatx80_mul(fp0, float32_to_floatx80(0x42B8AA3B, status), status); // 64/log2 * X n = floatx80_to_int32(fp0, status); // int(64/log2*X) fp0 = int32_to_floatx80(n); j = n & 0x3F; // J = N mod 64 m = n / 64; // NOTE: this is really arithmetic right shift by 6 if (n < 0 && j) { // arithmetic right shift is division and round towards minus infinity m--; } m1 = -m; //m += 0x3FFF; // biased exponent of 2^(M) //m1 += 0x3FFF; // biased exponent of -2^(-M) fp2 = fp0; // N fp0 = floatx80_mul(fp0, float32_to_floatx80(0xBC317218, status), status); // N * L1, L1 = lead(-log2/64) l2 = packFloatx80(0, 0x3FDC, LIT64(0x82E308654361C4C6)); fp2 = floatx80_mul(fp2, l2, status); // N * L2, L1+L2 = -log2/64 fp0 = floatx80_add(fp0, fp1, status); // X + N*L1 fp0 = floatx80_add(fp0, fp2, status); // R fp1 = floatx80_mul(fp0, fp0, status); // S = R*R fp2 = float32_to_floatx80(0x3950097B, status); // A6 fp2 = floatx80_mul(fp2, fp1, status); // fp2 is S*A6 fp3 = floatx80_mul(float32_to_floatx80(0x3AB60B6A, status), fp1, status); // fp3 is S*A5 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3F81111111174385), status), status); // fp2 IS A4+S*A6 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0x3FA5555555554F5A), status), status); // fp3 is A3+S*A5 fp2 = floatx80_mul(fp2, fp1, status); // fp2 IS S*(A4+S*A6) fp3 = floatx80_mul(fp3, fp1, status); // fp3 IS S*(A3+S*A5) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FC5555555555555), status), status); // fp2 IS A2+S*(A4+S*A6) fp3 = floatx80_add(fp3, float32_to_floatx80(0x3F000000, status), status); // fp3 IS A1+S*(A3+S*A5) fp2 = floatx80_mul(fp2, fp1, status); // fp2 IS S*(A2+S*(A4+S*A6)) fp1 = floatx80_mul(fp1, fp3, status); // fp1 IS S*(A1+S*(A3+S*A5)) fp2 = floatx80_mul(fp2, fp0, status); // fp2 IS R*S*(A2+S*(A4+S*A6)) fp0 = floatx80_add(fp0, fp1, status); // fp0 IS R+S*(A1+S*(A3+S*A5)) fp0 = floatx80_add(fp0, fp2, status); // fp0 IS EXP(R) - 1 fp0 = floatx80_mul(fp0, exp_tbl[j], status); // 2^(J/64)*(Exp(R)-1) if (m >= 64) { fp1 = float32_to_floatx80(exp_tbl2[j], status); onebysc = packFloatx80(1, m1 + 0x3FFF, one_sig); // -2^(-M) fp1 = floatx80_add(fp1, onebysc, status); fp0 = floatx80_add(fp0, fp1, status); fp0 = floatx80_add(fp0, exp_tbl[j], status); } else if (m < -3) { fp0 = floatx80_add(fp0, float32_to_floatx80(exp_tbl2[j], status), status); fp0 = floatx80_add(fp0, exp_tbl[j], status); onebysc = packFloatx80(1, m1 + 0x3FFF, one_sig); // -2^(-M) fp0 = floatx80_add(fp0, onebysc, status); } else { // -3 <= m <= 63 fp1 = exp_tbl[j]; fp0 = floatx80_add(fp0, float32_to_floatx80(exp_tbl2[j], status), status); onebysc = packFloatx80(1, m1 + 0x3FFF, one_sig); // -2^(-M) fp1 = floatx80_add(fp1, onebysc, status); fp0 = floatx80_add(fp0, fp1, status); } sc = packFloatx80(0, m + 0x3FFF, one_sig); RESET_PREC; a = floatx80_mul(fp0, sc, status); float_raise(float_flag_inexact, status); return a; } else { // |X| > 70 log2 if (aSign) { fp0 = float32_to_floatx80(0xBF800000, status); // -1 RESET_PREC; a = floatx80_add(fp0, float32_to_floatx80(0x00800000, status), status); // -1 + 2^(-126) float_raise(float_flag_inexact, status); return a; } else { RESET_PREC; return floatx80_etox(a, status); } } } else { // |X| < 1/4 if (aExp >= 0x3FBE) { fp0 = a; fp0 = floatx80_mul(fp0, fp0, status); // S = X*X fp1 = float32_to_floatx80(0x2F30CAA8, status); // B12 fp1 = floatx80_mul(fp1, fp0, status); // S * B12 fp2 = float32_to_floatx80(0x310F8290, status); // B11 fp1 = floatx80_add(fp1, float32_to_floatx80(0x32D73220, status), status); // B10 fp2 = floatx80_mul(fp2, fp0, status); fp1 = floatx80_mul(fp1, fp0, status); fp2 = floatx80_add(fp2, float32_to_floatx80(0x3493F281, status), status); // B9 fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3EC71DE3A5774682), status), status); // B8 fp2 = floatx80_mul(fp2, fp0, status); fp1 = floatx80_mul(fp1, fp0, status); fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EFA01A019D7CB68), status), status); // B7 fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3F2A01A01A019DF3), status), status); // B6 fp2 = floatx80_mul(fp2, fp0, status); fp1 = floatx80_mul(fp1, fp0, status); fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3F56C16C16C170E2), status), status); // B5 fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3F81111111111111), status), status); // B4 fp2 = floatx80_mul(fp2, fp0, status); fp1 = floatx80_mul(fp1, fp0, status); fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FA5555555555555), status), status); // B3 fp3 = packFloatx80(0, 0x3FFC, LIT64(0xAAAAAAAAAAAAAAAB)); fp1 = floatx80_add(fp1, fp3, status); // B2 fp2 = floatx80_mul(fp2, fp0, status); fp1 = floatx80_mul(fp1, fp0, status); fp2 = floatx80_mul(fp2, fp0, status); fp1 = floatx80_mul(fp1, a, status); fp0 = floatx80_mul(fp0, float32_to_floatx80(0x3F000000, status), status); // S*B1 fp1 = floatx80_add(fp1, fp2, status); // Q fp0 = floatx80_add(fp0, fp1, status); // S*B1+Q RESET_PREC; a = floatx80_add(fp0, a, status); float_raise(float_flag_inexact, status); return a; } else { // |X| < 2^(-65) sc = packFloatx80(1, 1, one_sig); fp0 = a; if (aExp < 0x0033) { // |X| < 2^(-16382) fp0 = floatx80_mul(fp0, float64_to_floatx80(LIT64(0x48B0000000000000), status), status); fp0 = floatx80_add(fp0, sc, status); RESET_PREC; a = floatx80_mul(fp0, float64_to_floatx80(LIT64(0x3730000000000000), status), status); } else { RESET_PREC; a = floatx80_add(fp0, sc, status); } float_raise(float_flag_inexact, status); return a; } } } /*---------------------------------------------------------------------------- | Log base 10 *----------------------------------------------------------------------------*/ floatx80 floatx80_log10(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; floatx80 fp0, fp1; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign == 0) return a; } if (aExp == 0 && aSig == 0) { float_raise(float_flag_divbyzero, status); return packFloatx80(1, 0x7FFF, floatx80_default_infinity_low); } if (aSign) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } SET_PREC; fp0 = floatx80_logn(a, status); fp1 = packFloatx80(0, 0x3FFD, LIT64(0xDE5BD8A937287195)); // INV_L10 RESET_PREC; a = floatx80_mul(fp0, fp1, status); // LOGN(X)*INV_L10 float_raise(float_flag_inexact, status); return a; } /*---------------------------------------------------------------------------- | Log base 2 *----------------------------------------------------------------------------*/ floatx80 floatx80_log2(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; floatx80 fp0, fp1; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign == 0) return a; } if (aExp == 0) { if (aSig == 0) { float_raise(float_flag_divbyzero, status); return packFloatx80(1, 0x7FFF, floatx80_default_infinity_low); } normalizeFloatx80Subnormal(aSig, &aExp, &aSig); } if (aSign) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } SET_PREC; if (aSig == one_sig) { // X is 2^k RESET_PREC; a = int32_to_floatx80(aExp-0x3FFF); } else { fp0 = floatx80_logn(a, status); fp1 = packFloatx80(0, 0x3FFF, LIT64(0xB8AA3B295C17F0BC)); // INV_L2 RESET_PREC; a = floatx80_mul(fp0, fp1, status); // LOGN(X)*INV_L2 } float_raise(float_flag_inexact, status); return a; } /*---------------------------------------------------------------------------- | Log base e *----------------------------------------------------------------------------*/ floatx80 floatx80_logn(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig, fSig; int32_t compact, j, k, adjk; floatx80 fp0, fp1, fp2, fp3, f, logof2, klog2, saveu; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign == 0) return a; } adjk = 0; if (aExp == 0) { if (aSig == 0) { // zero float_raise(float_flag_divbyzero, status); return packFloatx80(1, 0x7FFF, floatx80_default_infinity_low); } #if 1 if ((aSig & one_sig) == 0) { // denormal normalizeFloatx80Subnormal(aSig, &aExp, &aSig); adjk = -100; aExp += 100; a = packFloatx80(aSign, aExp, aSig); } #else normalizeFloatx80Subnormal(aSig, &aExp, &aSig); #endif } if (aSign) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } SET_PREC; compact = floatx80_make_compact(aExp, aSig); if (compact < 0x3FFEF07D || compact > 0x3FFF8841) { // |X| < 15/16 or |X| > 17/16 k = aExp - 0x3FFF; k += adjk; fp1 = int32_to_floatx80(k); fSig = (aSig & LIT64(0xFE00000000000000)) | LIT64(0x0100000000000000); j = (fSig >> 56) & 0x7E; // DISPLACEMENT FOR 1/F f = packFloatx80(0, 0x3FFF, fSig); // F fp0 = packFloatx80(0, 0x3FFF, aSig); // Y fp0 = floatx80_sub(fp0, f, status); // Y-F // LP1CONT1 fp0 = floatx80_mul(fp0, log_tbl[j], status); // FP0 IS U = (Y-F)/F logof2 = packFloatx80(0, 0x3FFE, LIT64(0xB17217F7D1CF79AC)); klog2 = floatx80_mul(fp1, logof2, status); // FP1 IS K*LOG2 fp2 = floatx80_mul(fp0, fp0, status); // FP2 IS V=U*U fp3 = fp2; fp1 = fp2; fp1 = floatx80_mul(fp1, float64_to_floatx80(LIT64(0x3FC2499AB5E4040B), status), status); // V*A6 fp2 = floatx80_mul(fp2, float64_to_floatx80(LIT64(0xBFC555B5848CB7DB), status), status); // V*A5 fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3FC99999987D8730), status), status); // A4+V*A6 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBFCFFFFFFF6F7E97), status), status); // A3+V*A5 fp1 = floatx80_mul(fp1, fp3, status); // V*(A4+V*A6) fp2 = floatx80_mul(fp2, fp3, status); // V*(A3+V*A5) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3FD55555555555A4), status), status); // A2+V*(A4+V*A6) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBFE0000000000008), status), status); // A1+V*(A3+V*A5) fp1 = floatx80_mul(fp1, fp3, status); // V*(A2+V*(A4+V*A6)) fp2 = floatx80_mul(fp2, fp3, status); // V*(A1+V*(A3+V*A5)) fp1 = floatx80_mul(fp1, fp0, status); // U*V*(A2+V*(A4+V*A6)) fp0 = floatx80_add(fp0, fp2, status); // U+V*(A1+V*(A3+V*A5)) fp1 = floatx80_add(fp1, log_tbl[j+1], status); // LOG(F)+U*V*(A2+V*(A4+V*A6)) fp0 = floatx80_add(fp0, fp1, status); // FP0 IS LOG(F) + LOG(1+U) RESET_PREC; a = floatx80_add(fp0, klog2, status); float_raise(float_flag_inexact, status); return a; } else { // |X-1| >= 1/16 fp0 = a; fp1 = a; fp1 = floatx80_sub(fp1, float32_to_floatx80(0x3F800000, status), status); // FP1 IS X-1 fp0 = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // FP0 IS X+1 fp1 = floatx80_add(fp1, fp1, status); // FP1 IS 2(X-1) // LP1CONT2 fp1 = floatx80_div(fp1, fp0, status); // U saveu = fp1; fp0 = floatx80_mul(fp1, fp1, status); // FP0 IS V = U*U fp1 = floatx80_mul(fp0, fp0, status); // FP1 IS W = V*V fp3 = float64_to_floatx80(LIT64(0x3F175496ADD7DAD6), status); // B5 fp2 = float64_to_floatx80(LIT64(0x3F3C71C2FE80C7E0), status); // B4 fp3 = floatx80_mul(fp3, fp1, status); // W*B5 fp2 = floatx80_mul(fp2, fp1, status); // W*B4 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0x3F624924928BCCFF), status), status); // B3+W*B5 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3F899999999995EC), status), status); // B2+W*B4 fp1 = floatx80_mul(fp1, fp3, status); // W*(B3+W*B5) fp2 = floatx80_mul(fp2, fp0, status); // V*(B2+W*B4) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3FB5555555555555), status), status); // B1+W*(B3+W*B5) fp0 = floatx80_mul(fp0, saveu, status); // FP0 IS U*V fp1 = floatx80_add(fp1, fp2, status); // B1+W*(B3+W*B5) + V*(B2+W*B4) fp0 = floatx80_mul(fp0, fp1, status); // U*V*( [B1+W*(B3+W*B5)] + [V*(B2+W*B4)] ) RESET_PREC; a = floatx80_add(fp0, saveu, status); float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | Log base e of x plus 1 *----------------------------------------------------------------------------*/ floatx80 floatx80_lognp1(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig, fSig; int32_t compact, j, k; floatx80 fp0, fp1, fp2, fp3, f, logof2, klog2, saveu; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign) { float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } return a; } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } if (aSign && aExp >= one_exp) { if (aExp == one_exp && aSig == one_sig) { float_raise(float_flag_divbyzero, status); return packFloatx80(aSign, 0x7FFF, floatx80_default_infinity_low); } float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } if (aExp < 0x3f99 || (aExp == 0x3f99 && aSig == one_sig)) { // <= min threshold float_raise(float_flag_inexact, status); return floatx80_move(a, status); } SET_PREC; fp0 = a; // Z fp1 = a; fp0 = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // X = (1+Z) aExp = extractFloatx80Exp(fp0); aSig = extractFloatx80Frac(fp0); compact = floatx80_make_compact(aExp, aSig); if (compact < 0x3FFE8000 || compact > 0x3FFFC000) { // |X| < 1/2 or |X| > 3/2 k = aExp - 0x3FFF; fp1 = int32_to_floatx80(k); fSig = (aSig & LIT64(0xFE00000000000000)) | LIT64(0x0100000000000000); j = (fSig >> 56) & 0x7E; // DISPLACEMENT FOR 1/F f = packFloatx80(0, 0x3FFF, fSig); // F fp0 = packFloatx80(0, 0x3FFF, aSig); // Y fp0 = floatx80_sub(fp0, f, status); // Y-F lp1cont1: // LP1CONT1 fp0 = floatx80_mul(fp0, log_tbl[j], status); // FP0 IS U = (Y-F)/F logof2 = packFloatx80(0, 0x3FFE, LIT64(0xB17217F7D1CF79AC)); klog2 = floatx80_mul(fp1, logof2, status); // FP1 IS K*LOG2 fp2 = floatx80_mul(fp0, fp0, status); // FP2 IS V=U*U fp3 = fp2; fp1 = fp2; fp1 = floatx80_mul(fp1, float64_to_floatx80(LIT64(0x3FC2499AB5E4040B), status), status); // V*A6 fp2 = floatx80_mul(fp2, float64_to_floatx80(LIT64(0xBFC555B5848CB7DB), status), status); // V*A5 fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3FC99999987D8730), status), status); // A4+V*A6 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBFCFFFFFFF6F7E97), status), status); // A3+V*A5 fp1 = floatx80_mul(fp1, fp3, status); // V*(A4+V*A6) fp2 = floatx80_mul(fp2, fp3, status); // V*(A3+V*A5) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3FD55555555555A4), status), status); // A2+V*(A4+V*A6) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBFE0000000000008), status), status); // A1+V*(A3+V*A5) fp1 = floatx80_mul(fp1, fp3, status); // V*(A2+V*(A4+V*A6)) fp2 = floatx80_mul(fp2, fp3, status); // V*(A1+V*(A3+V*A5)) fp1 = floatx80_mul(fp1, fp0, status); // U*V*(A2+V*(A4+V*A6)) fp0 = floatx80_add(fp0, fp2, status); // U+V*(A1+V*(A3+V*A5)) fp1 = floatx80_add(fp1, log_tbl[j+1], status); // LOG(F)+U*V*(A2+V*(A4+V*A6)) fp0 = floatx80_add(fp0, fp1, status); // FP0 IS LOG(F) + LOG(1+U) RESET_PREC; a = floatx80_add(fp0, klog2, status); float_raise(float_flag_inexact, status); return a; } else if (compact < 0x3FFEF07D || compact > 0x3FFF8841) { // |X| < 1/16 or |X| > -1/16 // LP1CARE fSig = (aSig & LIT64(0xFE00000000000000)) | LIT64(0x0100000000000000); f = packFloatx80(0, 0x3FFF, fSig); // F j = (fSig >> 56) & 0x7E; // DISPLACEMENT FOR 1/F if (compact >= 0x3FFF8000) { // 1+Z >= 1 // KISZERO fp0 = floatx80_sub(float32_to_floatx80(0x3F800000, status), f, status); // 1-F fp0 = floatx80_add(fp0, fp1, status); // FP0 IS Y-F = (1-F)+Z fp1 = packFloatx80(0, 0, 0); // K = 0 } else { // KISNEG fp0 = floatx80_sub(float32_to_floatx80(0x40000000, status), f, status); // 2-F fp1 = floatx80_add(fp1, fp1, status); // 2Z fp0 = floatx80_add(fp0, fp1, status); // FP0 IS Y-F = (2-F)+2Z fp1 = packFloatx80(1, one_exp, one_sig); // K = -1 } goto lp1cont1; } else { // LP1ONE16 fp1 = floatx80_add(fp1, fp1, status); // FP1 IS 2Z fp0 = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // FP0 IS 1+X // LP1CONT2 fp1 = floatx80_div(fp1, fp0, status); // U saveu = fp1; fp0 = floatx80_mul(fp1, fp1, status); // FP0 IS V = U*U fp1 = floatx80_mul(fp0, fp0, status); // FP1 IS W = V*V fp3 = float64_to_floatx80(LIT64(0x3F175496ADD7DAD6), status); // B5 fp2 = float64_to_floatx80(LIT64(0x3F3C71C2FE80C7E0), status); // B4 fp3 = floatx80_mul(fp3, fp1, status); // W*B5 fp2 = floatx80_mul(fp2, fp1, status); // W*B4 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0x3F624924928BCCFF), status), status); // B3+W*B5 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3F899999999995EC), status), status); // B2+W*B4 fp1 = floatx80_mul(fp1, fp3, status); // W*(B3+W*B5) fp2 = floatx80_mul(fp2, fp0, status); // V*(B2+W*B4) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3FB5555555555555), status), status); // B1+W*(B3+W*B5) fp0 = floatx80_mul(fp0, saveu, status); // FP0 IS U*V fp1 = floatx80_add(fp1, fp2, status); // B1+W*(B3+W*B5) + V*(B2+W*B4) fp0 = floatx80_mul(fp0, fp1, status); // U*V*( [B1+W*(B3+W*B5)] + [V*(B2+W*B4)] ) RESET_PREC; a = floatx80_add(fp0, saveu, status); float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | Sine *----------------------------------------------------------------------------*/ floatx80 floatx80_sin(floatx80 a, float_status *status) { flag aSign, xSign; int32_t aExp, xExp; uint64_t aSig, xSig; int32_t compact, l, n, j; floatx80 fp0, fp1, fp2, fp3, fp4, fp5, x, invtwopi, twopi1, twopi2; float32 posneg1, twoto63; flag adjn, endflag; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } adjn = 0; SET_PREC; compact = floatx80_make_compact(aExp, aSig); fp0 = a; if (compact < 0x3FD78000 || compact > 0x4004BC7E) { // 2^(-40) > |X| > 15 PI if (compact > 0x3FFF8000) { // |X| >= 15 PI // REDUCEX fp1 = packFloatx80(0, 0, 0); if (compact == 0x7FFEFFFF) { twopi1 = packFloatx80(aSign ^ 1, 0x7FFE, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(aSign ^ 1, 0x7FDC, LIT64(0x85A308D300000000)); fp0 = floatx80_add(fp0, twopi1, status); fp1 = fp0; fp0 = floatx80_add(fp0, twopi2, status); fp1 = floatx80_sub(fp1, fp0, status); fp1 = floatx80_add(fp1, twopi2, status); } loop: xSign = extractFloatx80Sign(fp0); xExp = extractFloatx80Exp(fp0); xExp -= 0x3FFF; if (xExp <= 28) { l = 0; endflag = 1; } else { l = xExp - 27; endflag = 0; } invtwopi = packFloatx80(0, 0x3FFE - l, LIT64(0xA2F9836E4E44152A)); // INVTWOPI twopi1 = packFloatx80(0, 0x3FFF + l, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(0, 0x3FDD + l, LIT64(0x85A308D300000000)); twoto63 = 0x5F000000; twoto63 |= xSign ? 0x80000000 : 0x00000000; // SIGN(INARG)*2^63 IN SGL fp2 = floatx80_mul(fp0, invtwopi, status); fp2 = floatx80_add(fp2, float32_to_floatx80(twoto63, status), status); // THE FRACTIONAL PART OF FP2 IS ROUNDED fp2 = floatx80_sub(fp2, float32_to_floatx80(twoto63, status), status); // FP2 is N fp4 = floatx80_mul(twopi1, fp2, status); // W = N*P1 fp5 = floatx80_mul(twopi2, fp2, status); // w = N*P2 fp3 = floatx80_add(fp4, fp5, status); // FP3 is P fp4 = floatx80_sub(fp4, fp3, status); // W-P fp0 = floatx80_sub(fp0, fp3, status); // FP0 is A := R - P fp4 = floatx80_add(fp4, fp5, status); // FP4 is p = (W-P)+w fp3 = fp0; // FP3 is A fp1 = floatx80_sub(fp1, fp4, status); // FP1 is a := r - p fp0 = floatx80_add(fp0, fp1, status); // FP0 is R := A+a if (endflag > 0) { n = floatx80_to_int32(fp2, status); goto sincont; } fp3 = floatx80_sub(fp3, fp0, status); // A-R fp1 = floatx80_add(fp1, fp3, status); // FP1 is r := (A-R)+a goto loop; } else { // SINSM fp0 = float32_to_floatx80(0x3F800000, status); // 1 RESET_PREC; if (adjn) { // COSTINY a = floatx80_sub(fp0, float32_to_floatx80(0x00800000, status), status); } else { // SINTINY a = floatx80_move(a, status); } float_raise(float_flag_inexact, status); return a; } } else { fp1 = floatx80_mul(fp0, float64_to_floatx80(LIT64(0x3FE45F306DC9C883), status), status); // X*2/PI n = floatx80_to_int32(fp1, status); j = 32 + n; fp0 = floatx80_sub(fp0, pi_tbl[j], status); // X-Y1 fp0 = floatx80_sub(fp0, float32_to_floatx80(pi_tbl2[j], status), status); // FP0 IS R = (X-Y1)-Y2 sincont: if ((n + adjn) & 1) { // COSPOLY fp0 = floatx80_mul(fp0, fp0, status); // FP0 IS S fp1 = floatx80_mul(fp0, fp0, status); // FP1 IS T fp2 = float64_to_floatx80(LIT64(0x3D2AC4D0D6011EE3), status); // B8 fp3 = float64_to_floatx80(LIT64(0xBDA9396F9F45AC19), status); // B7 xSign = extractFloatx80Sign(fp0); // X IS S xExp = extractFloatx80Exp(fp0); xSig = extractFloatx80Frac(fp0); if (((n + adjn) >> 1) & 1) { xSign ^= 1; posneg1 = 0xBF800000; // -1 } else { xSign ^= 0; posneg1 = 0x3F800000; // 1 } // X IS NOW R'= SGN*R fp2 = floatx80_mul(fp2, fp1, status); // TB8 fp3 = floatx80_mul(fp3, fp1, status); // TB7 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3E21EED90612C972), status), status); // B6+TB8 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBE927E4FB79D9FCF), status), status); // B5+TB7 fp2 = floatx80_mul(fp2, fp1, status); // T(B6+TB8) fp3 = floatx80_mul(fp3, fp1, status); // T(B5+TB7) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EFA01A01A01D423), status), status); // B4+T(B6+TB8) fp4 = packFloatx80(1, 0x3FF5, LIT64(0xB60B60B60B61D438)); fp3 = floatx80_add(fp3, fp4, status); // B3+T(B5+TB7) fp2 = floatx80_mul(fp2, fp1, status); // T(B4+T(B6+TB8)) fp1 = floatx80_mul(fp1, fp3, status); // T(B3+T(B5+TB7)) fp4 = packFloatx80(0, 0x3FFA, LIT64(0xAAAAAAAAAAAAAB5E)); fp2 = floatx80_add(fp2, fp4, status); // B2+T(B4+T(B6+TB8)) fp1 = floatx80_add(fp1, float32_to_floatx80(0xBF000000, status), status); // B1+T(B3+T(B5+TB7)) fp0 = floatx80_mul(fp0, fp2, status); // S(B2+T(B4+T(B6+TB8))) fp0 = floatx80_add(fp0, fp1, status); // [B1+T(B3+T(B5+TB7))]+[S(B2+T(B4+T(B6+TB8)))] x = packFloatx80(xSign, xExp, xSig); fp0 = floatx80_mul(fp0, x, status); RESET_PREC; a = floatx80_add(fp0, float32_to_floatx80(posneg1, status), status); float_raise(float_flag_inexact, status); return a; } else { // SINPOLY xSign = extractFloatx80Sign(fp0); // X IS R xExp = extractFloatx80Exp(fp0); xSig = extractFloatx80Frac(fp0); xSign ^= ((n + adjn) >> 1) & 1; // X IS NOW R'= SGN*R fp0 = floatx80_mul(fp0, fp0, status); // FP0 IS S fp1 = floatx80_mul(fp0, fp0, status); // FP1 IS T fp3 = float64_to_floatx80(LIT64(0xBD6AAA77CCC994F5), status); // A7 fp2 = float64_to_floatx80(LIT64(0x3DE612097AAE8DA1), status); // A6 fp3 = floatx80_mul(fp3, fp1, status); // T*A7 fp2 = floatx80_mul(fp2, fp1, status); // T*A6 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBE5AE6452A118AE4), status), status); // A5+T*A7 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EC71DE3A5341531), status), status); // A4+T*A6 fp3 = floatx80_mul(fp3, fp1, status); // T(A5+TA7) fp2 = floatx80_mul(fp2, fp1, status); // T(A4+TA6) fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBF2A01A01A018B59), status), status); // A3+T(A5+TA7) fp4 = packFloatx80(0, 0x3FF8, LIT64(0x88888888888859AF)); fp2 = floatx80_add(fp2, fp4, status); // A2+T(A4+TA6) fp1 = floatx80_mul(fp1, fp3, status); // T(A3+T(A5+TA7)) fp2 = floatx80_mul(fp2, fp0, status); // S(A2+T(A4+TA6)) fp4 = packFloatx80(1, 0x3FFC, LIT64(0xAAAAAAAAAAAAAA99)); fp1 = floatx80_add(fp1, fp4, status); // A1+T(A3+T(A5+TA7)) fp1 = floatx80_add(fp1, fp2, status); // [A1+T(A3+T(A5+TA7))]+[S(A2+T(A4+TA6))] x = packFloatx80(xSign, xExp, xSig); fp0 = floatx80_mul(fp0, x, status); // R'*S fp0 = floatx80_mul(fp0, fp1, status); // SIN(R')-R' RESET_PREC; a = floatx80_add(fp0, x, status); float_raise(float_flag_inexact, status); return a; } } } /*---------------------------------------------------------------------------- | Sine and cosine *----------------------------------------------------------------------------*/ floatx80 floatx80_sincos(floatx80 a, floatx80 *c, float_status *status) { flag aSign, xSign, rSign, sSign; int32_t aExp, xExp, rExp, sExp; uint64_t aSig, rSig, sSig; int32_t compact, l, n, i, j1, j2; floatx80 fp0, fp1, fp2, fp3, fp4, fp5, r, s, invtwopi, twopi1, twopi2; float32 posneg1, twoto63; flag endflag; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t)(aSig << 1)) { *c = propagateFloatx80NaNOneArg(a, status); return *c; } float_raise(float_flag_invalid, status); *c = floatx80_default_nan(status); return *c; } if (aExp == 0 && aSig == 0) { *c = packFloatx80(0, one_exp, one_sig); return packFloatx80(aSign, 0, 0); } SET_PREC; compact = floatx80_make_compact(aExp, aSig); fp0 = a; if (compact < 0x3FD78000 || compact > 0x4004BC7E) { // 2^(-40) > |X| > 15 PI if (compact > 0x3FFF8000) { // |X| >= 15 PI // REDUCEX fp1 = packFloatx80(0, 0, 0); if (compact == 0x7FFEFFFF) { twopi1 = packFloatx80(aSign ^ 1, 0x7FFE, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(aSign ^ 1, 0x7FDC, LIT64(0x85A308D300000000)); fp0 = floatx80_add(fp0, twopi1, status); fp1 = fp0; fp0 = floatx80_add(fp0, twopi2, status); fp1 = floatx80_sub(fp1, fp0, status); fp1 = floatx80_add(fp1, twopi2, status); } loop: xSign = extractFloatx80Sign(fp0); xExp = extractFloatx80Exp(fp0); xExp -= 0x3FFF; if (xExp <= 28) { l = 0; endflag = 1; } else { l = xExp - 27; endflag = 0; } invtwopi = packFloatx80(0, 0x3FFE - l, LIT64(0xA2F9836E4E44152A)); // INVTWOPI twopi1 = packFloatx80(0, 0x3FFF + l, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(0, 0x3FDD + l, LIT64(0x85A308D300000000)); twoto63 = 0x5F000000; twoto63 |= xSign ? 0x80000000 : 0x00000000; // SIGN(INARG)*2^63 IN SGL fp2 = floatx80_mul(fp0, invtwopi, status); fp2 = floatx80_add(fp2, float32_to_floatx80(twoto63, status), status); // THE FRACTIONAL PART OF FP2 IS ROUNDED fp2 = floatx80_sub(fp2, float32_to_floatx80(twoto63, status), status); // FP2 is N fp4 = floatx80_mul(twopi1, fp2, status); // W = N*P1 fp5 = floatx80_mul(twopi2, fp2, status); // w = N*P2 fp3 = floatx80_add(fp4, fp5, status); // FP3 is P fp4 = floatx80_sub(fp4, fp3, status); // W-P fp0 = floatx80_sub(fp0, fp3, status); // FP0 is A := R - P fp4 = floatx80_add(fp4, fp5, status); // FP4 is p = (W-P)+w fp3 = fp0; // FP3 is A fp1 = floatx80_sub(fp1, fp4, status); // FP1 is a := r - p fp0 = floatx80_add(fp0, fp1, status); // FP0 is R := A+a if (endflag > 0) { n = floatx80_to_int32(fp2, status); goto sccont; } fp3 = floatx80_sub(fp3, fp0, status); // A-R fp1 = floatx80_add(fp1, fp3, status); // FP1 is r := (A-R)+a goto loop; } else { // SCSM fp0 = float32_to_floatx80(0x3F800000, status); // 1 RESET_PREC; // COSTINY *c = floatx80_sub(fp0, float32_to_floatx80(0x00800000, status), status); // SINTINY a = floatx80_move(a, status); float_raise(float_flag_inexact, status); return a; } } else { fp1 = floatx80_mul(fp0, float64_to_floatx80(LIT64(0x3FE45F306DC9C883), status), status); // X*2/PI n = floatx80_to_int32(fp1, status); i = 32 + n; fp0 = floatx80_sub(fp0, pi_tbl[i], status); // X-Y1 fp0 = floatx80_sub(fp0, float32_to_floatx80(pi_tbl2[i], status), status); // FP0 IS R = (X-Y1)-Y2 sccont: n &= 3; // k = N mod 4 if (n & 1) { // NODD j1 = n >> 1; // j1 = (k-1)/2 j2 = j1 ^ (n & 1); // j2 = j1 EOR (k mod 2) rSign = extractFloatx80Sign(fp0); // R rExp = extractFloatx80Exp(fp0); rSig = extractFloatx80Frac(fp0); rSign ^= j2; fp0 = floatx80_mul(fp0, fp0, status); // FP0 IS S = R*R fp1 = float64_to_floatx80(LIT64(0xBD6AAA77CCC994F5), status); // A7 fp2 = float64_to_floatx80(LIT64(0x3D2AC4D0D6011EE3), status); // B8 fp1 = floatx80_mul(fp1, fp0, status); // FP1 IS SA7 fp2 = floatx80_mul(fp2, fp0, status); // FP2 IS SB8 fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3DE612097AAE8DA1), status), status); // A6+SA7 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBDA9396F9F45AC19), status), status); // B7+SB8 fp1 = floatx80_mul(fp1, fp0, status); // S(A6+SA7) fp2 = floatx80_mul(fp2, fp0, status); // S(B7+SB8) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0xBE5AE6452A118AE4), status), status); // A5+S(A6+SA7) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3E21EED90612C972), status), status); // B6+S(B7+SB8) fp1 = floatx80_mul(fp1, fp0, status); // S(A5+S(A6+SA7)) fp2 = floatx80_mul(fp2, fp0, status); // S(B6+S(B7+SB8)) sSign = extractFloatx80Sign(fp0); // S sExp = extractFloatx80Exp(fp0); sSig = extractFloatx80Frac(fp0); sSign ^= j1; posneg1 = 0x3F800000; posneg1 |= j1 ? 0x80000000 : 0; fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3EC71DE3A5341531), status), status); // A4+S(A5+S(A6+SA7)) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBE927E4FB79D9FCF), status), status); // B5+S(B6+S(B7+SB8)) fp1 = floatx80_mul(fp1, fp0, status); // S(A4+...) fp2 = floatx80_mul(fp2, fp0, status); // S(B5+...) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0xBF2A01A01A018B59), status), status); // A3+S(A4+...) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EFA01A01A01D423), status), status); // B4+S(B5+...) fp1 = floatx80_mul(fp1, fp0, status); // S(A3+...) fp2 = floatx80_mul(fp2, fp0, status); // S(B4+...) fp3 = packFloatx80(0, 0x3FF8, LIT64(0x88888888888859AF)); fp4 = packFloatx80(1, 0x3FF5, LIT64(0xB60B60B60B61D438)); fp1 = floatx80_add(fp1, fp3, status); // A2+S(A3+...) fp2 = floatx80_add(fp2, fp4, status); // B3+S(B4+...) fp1 = floatx80_mul(fp1, fp0, status); // S(A2+...) fp2 = floatx80_mul(fp2, fp0, status); // S(B3+...) fp3 = packFloatx80(1, 0x3FFC, LIT64(0xAAAAAAAAAAAAAA99)); fp4 = packFloatx80(0, 0x3FFA, LIT64(0xAAAAAAAAAAAAAB5E)); fp1 = floatx80_add(fp1, fp3, status); // A1+S(A2+...) fp2 = floatx80_add(fp2, fp4, status); // B2+S(B3+...) fp1 = floatx80_mul(fp1, fp0, status); // S(A1+...) fp0 = floatx80_mul(fp0, fp2, status); // S(B2+...) r = packFloatx80(rSign, rExp, rSig); fp1 = floatx80_mul(fp1, r, status); // R'S(A1+...) fp0 = floatx80_add(fp0, float32_to_floatx80(0xBF000000, status), status); // B1+S(B2...) s = packFloatx80(sSign, sExp, sSig); fp0 = floatx80_mul(fp0, s, status); // S'(B1+S(B2+...)) RESET_PREC; *c = floatx80_add(fp1, r, status); // COS(X) a = floatx80_add(fp0, float32_to_floatx80(posneg1, status), status); // SIN(X) float_raise(float_flag_inexact, status); return a; } else { // NEVEN j1 = n >> 1; // j1 = k/2 rSign = extractFloatx80Sign(fp0); // R rExp = extractFloatx80Exp(fp0); rSig = extractFloatx80Frac(fp0); rSign ^= j1; fp0 = floatx80_mul(fp0, fp0, status); // FP0 IS S = R*R fp1 = float64_to_floatx80(LIT64(0x3D2AC4D0D6011EE3), status); // B8 fp2 = float64_to_floatx80(LIT64(0xBD6AAA77CCC994F5), status); // A7 fp1 = floatx80_mul(fp1, fp0, status); // SB8 fp2 = floatx80_mul(fp2, fp0, status); // SA7 sSign = extractFloatx80Sign(fp0); // S sExp = extractFloatx80Exp(fp0); sSig = extractFloatx80Frac(fp0); sSign ^= j1; posneg1 = 0x3F800000; posneg1 |= j1 ? 0x80000000 : 0; fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0xBDA9396F9F45AC19), status), status); // B7+SB8 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3DE612097AAE8DA1), status), status); // A6+SA7 fp1 = floatx80_mul(fp1, fp0, status); // S(B7+SB8) fp2 = floatx80_mul(fp2, fp0, status); // S(A6+SA7) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3E21EED90612C972), status), status); // B6+S(B7+SB8) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBE5AE6452A118AE4), status), status); // A5+S(A6+SA7) fp1 = floatx80_mul(fp1, fp0, status); // S(B6+S(B7+SB8)) fp2 = floatx80_mul(fp2, fp0, status); // S(A5+S(A6+SA7)) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0xBE927E4FB79D9FCF), status), status); // B5+S(B6+S(B7+SB8)) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3EC71DE3A5341531), status), status); // A4+S(A5+S(A6+SA7)) fp1 = floatx80_mul(fp1, fp0, status); // S(B5+...) fp2 = floatx80_mul(fp2, fp0, status); // S(A4+...) fp1 = floatx80_add(fp1, float64_to_floatx80(LIT64(0x3EFA01A01A01D423), status), status); // B4+S(B5+...) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0xBF2A01A01A018B59), status), status); // A3+S(A4+...) fp1 = floatx80_mul(fp1, fp0, status); // S(B4+...) fp2 = floatx80_mul(fp2, fp0, status); // S(A3+...) fp3 = packFloatx80(1, 0x3FF5, LIT64(0xB60B60B60B61D438)); fp4 = packFloatx80(0, 0x3FF8, LIT64(0x88888888888859AF)); fp1 = floatx80_add(fp1, fp3, status); // B3+S(B4+...) fp2 = floatx80_add(fp2, fp4, status); // A2+S(A3+...) fp1 = floatx80_mul(fp1, fp0, status); // S(B3+...) fp2 = floatx80_mul(fp2, fp0, status); // S(A2+...) fp3 = packFloatx80(0, 0x3FFA, LIT64(0xAAAAAAAAAAAAAB5E)); fp4 = packFloatx80(1, 0x3FFC, LIT64(0xAAAAAAAAAAAAAA99)); fp1 = floatx80_add(fp1, fp3, status); // B2+S(B3+...) fp2 = floatx80_add(fp2, fp4, status); // A1+S(A2+...) fp1 = floatx80_mul(fp1, fp0, status); // S(B2+...) fp0 = floatx80_mul(fp0, fp2, status); // S(A1+...) fp1 = floatx80_add(fp1, float32_to_floatx80(0xBF000000, status), status); // B1+S(B2...) r = packFloatx80(rSign, rExp, rSig); fp0 = floatx80_mul(fp0, r, status); // R'S(A1+...) s = packFloatx80(sSign, sExp, sSig); fp1 = floatx80_mul(fp1, s, status); // S'(B1+S(B2+...)) RESET_PREC; *c = floatx80_add(fp1, float32_to_floatx80(posneg1, status), status); // COS(X) a = floatx80_add(fp0, r, status); // SIN(X) float_raise(float_flag_inexact, status); return a; } } } /*---------------------------------------------------------------------------- | Hyperbolic sine *----------------------------------------------------------------------------*/ floatx80 floatx80_sinh(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact; floatx80 fp0, fp1, fp2; float32 fact; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); return a; } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } SET_PREC; compact = floatx80_make_compact(aExp, aSig); if (compact > 0x400CB167) { // SINHBIG if (compact > 0x400CB2B3) { RESET_PREC; a = roundAndPackFloatx80(status->floatx80_rounding_precision, aSign, 0x8000, aSig, 0, status); float_raise(float_flag_inexact, status); return a; } else { fp0 = floatx80_abs(a, status); // Y = |X| fp0 = floatx80_sub(fp0, float64_to_floatx80(LIT64(0x40C62D38D3D64634), status), status); // (|X|-16381LOG2_LEAD) fp0 = floatx80_sub(fp0, float64_to_floatx80(LIT64(0x3D6F90AEB1E75CC7), status), status); // |X| - 16381 LOG2, ACCURATE fp0 = floatx80_etox(fp0, status); fp2 = packFloatx80(aSign, 0x7FFB, one_sig); RESET_PREC; a = floatx80_mul(fp0, fp2, status); float_raise(float_flag_inexact, status); return a; } } else { // |X| < 16380 LOG2 fp0 = floatx80_abs(a, status); // Y = |X| fp0 = floatx80_etoxm1(fp0, status); // FP0 IS Z = EXPM1(Y) fp1 = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // 1+Z fp2 = fp0; fp0 = floatx80_div(fp0, fp1, status); // Z/(1+Z) fp0 = floatx80_add(fp0, fp2, status); fact = 0x3F000000; fact |= aSign ? 0x80000000 : 0x00000000; RESET_PREC; a = floatx80_mul(fp0, float32_to_floatx80(fact, status), status); float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | Tangent *----------------------------------------------------------------------------*/ floatx80 floatx80_tan(floatx80 a, float_status *status) { flag aSign, xSign; int32_t aExp, xExp; uint64_t aSig, xSig; int32_t compact, l, n, j; floatx80 fp0, fp1, fp2, fp3, fp4, fp5, invtwopi, twopi1, twopi2; float32 twoto63; flag endflag; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } SET_PREC; compact = floatx80_make_compact(aExp, aSig); fp0 = a; if (compact < 0x3FD78000 || compact > 0x4004BC7E) { // 2^(-40) > |X| > 15 PI if (compact > 0x3FFF8000) { // |X| >= 15 PI // REDUCEX fp1 = packFloatx80(0, 0, 0); if (compact == 0x7FFEFFFF) { twopi1 = packFloatx80(aSign ^ 1, 0x7FFE, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(aSign ^ 1, 0x7FDC, LIT64(0x85A308D300000000)); fp0 = floatx80_add(fp0, twopi1, status); fp1 = fp0; fp0 = floatx80_add(fp0, twopi2, status); fp1 = floatx80_sub(fp1, fp0, status); fp1 = floatx80_add(fp1, twopi2, status); } loop: xSign = extractFloatx80Sign(fp0); xExp = extractFloatx80Exp(fp0); xExp -= 0x3FFF; if (xExp <= 28) { l = 0; endflag = 1; } else { l = xExp - 27; endflag = 0; } invtwopi = packFloatx80(0, 0x3FFE - l, LIT64(0xA2F9836E4E44152A)); // INVTWOPI twopi1 = packFloatx80(0, 0x3FFF + l, LIT64(0xC90FDAA200000000)); twopi2 = packFloatx80(0, 0x3FDD + l, LIT64(0x85A308D300000000)); twoto63 = 0x5F000000; twoto63 |= xSign ? 0x80000000 : 0x00000000; // SIGN(INARG)*2^63 IN SGL fp2 = floatx80_mul(fp0, invtwopi, status); fp2 = floatx80_add(fp2, float32_to_floatx80(twoto63, status), status); // THE FRACTIONAL PART OF FP2 IS ROUNDED fp2 = floatx80_sub(fp2, float32_to_floatx80(twoto63, status), status); // FP2 is N fp4 = floatx80_mul(twopi1, fp2, status); // W = N*P1 fp5 = floatx80_mul(twopi2, fp2, status); // w = N*P2 fp3 = floatx80_add(fp4, fp5, status); // FP3 is P fp4 = floatx80_sub(fp4, fp3, status); // W-P fp0 = floatx80_sub(fp0, fp3, status); // FP0 is A := R - P fp4 = floatx80_add(fp4, fp5, status); // FP4 is p = (W-P)+w fp3 = fp0; // FP3 is A fp1 = floatx80_sub(fp1, fp4, status); // FP1 is a := r - p fp0 = floatx80_add(fp0, fp1, status); // FP0 is R := A+a if (endflag > 0) { n = floatx80_to_int32(fp2, status); goto tancont; } fp3 = floatx80_sub(fp3, fp0, status); // A-R fp1 = floatx80_add(fp1, fp3, status); // FP1 is r := (A-R)+a goto loop; } else { RESET_PREC; a = floatx80_move(a, status); float_raise(float_flag_inexact, status); return a; } } else { fp1 = floatx80_mul(fp0, float64_to_floatx80(LIT64(0x3FE45F306DC9C883), status), status); // X*2/PI n = floatx80_to_int32(fp1, status); j = 32 + n; fp0 = floatx80_sub(fp0, pi_tbl[j], status); // X-Y1 fp0 = floatx80_sub(fp0, float32_to_floatx80(pi_tbl2[j], status), status); // FP0 IS R = (X-Y1)-Y2 tancont: if (n & 1) { // NODD fp1 = fp0; // R fp0 = floatx80_mul(fp0, fp0, status); // S = R*R fp3 = float64_to_floatx80(LIT64(0x3EA0B759F50F8688), status); // Q4 fp2 = float64_to_floatx80(LIT64(0xBEF2BAA5A8924F04), status); // P3 fp3 = floatx80_mul(fp3, fp0, status); // SQ4 fp2 = floatx80_mul(fp2, fp0, status); // SP3 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBF346F59B39BA65F), status), status); // Q3+SQ4 fp4 = packFloatx80(0, 0x3FF6, LIT64(0xE073D3FC199C4A00)); fp2 = floatx80_add(fp2, fp4, status); // P2+SP3 fp3 = floatx80_mul(fp3, fp0, status); // S(Q3+SQ4) fp2 = floatx80_mul(fp2, fp0, status); // S(P2+SP3) fp4 = packFloatx80(0, 0x3FF9, LIT64(0xD23CD68415D95FA1)); fp3 = floatx80_add(fp3, fp4, status); // Q2+S(Q3+SQ4) fp4 = packFloatx80(1, 0x3FFC, LIT64(0x8895A6C5FB423BCA)); fp2 = floatx80_add(fp2, fp4, status); // P1+S(P2+SP3) fp3 = floatx80_mul(fp3, fp0, status); // S(Q2+S(Q3+SQ4)) fp2 = floatx80_mul(fp2, fp0, status); // S(P1+S(P2+SP3)) fp4 = packFloatx80(1, 0x3FFD, LIT64(0xEEF57E0DA84BC8CE)); fp3 = floatx80_add(fp3, fp4, status); // Q1+S(Q2+S(Q3+SQ4)) fp2 = floatx80_mul(fp2, fp1, status); // RS(P1+S(P2+SP3)) fp0 = floatx80_mul(fp0, fp3, status); // S(Q1+S(Q2+S(Q3+SQ4))) fp1 = floatx80_add(fp1, fp2, status); // R+RS(P1+S(P2+SP3)) fp0 = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // 1+S(Q1+S(Q2+S(Q3+SQ4))) xSign = extractFloatx80Sign(fp1); xExp = extractFloatx80Exp(fp1); xSig = extractFloatx80Frac(fp1); xSign ^= 1; fp1 = packFloatx80(xSign, xExp, xSig); RESET_PREC; a = floatx80_div(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } else { fp1 = floatx80_mul(fp0, fp0, status); // S = R*R fp3 = float64_to_floatx80(LIT64(0x3EA0B759F50F8688), status); // Q4 fp2 = float64_to_floatx80(LIT64(0xBEF2BAA5A8924F04), status); // P3 fp3 = floatx80_mul(fp3, fp1, status); // SQ4 fp2 = floatx80_mul(fp2, fp1, status); // SP3 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0xBF346F59B39BA65F), status), status); // Q3+SQ4 fp4 = packFloatx80(0, 0x3FF6, LIT64(0xE073D3FC199C4A00)); fp2 = floatx80_add(fp2, fp4, status); // P2+SP3 fp3 = floatx80_mul(fp3, fp1, status); // S(Q3+SQ4) fp2 = floatx80_mul(fp2, fp1, status); // S(P2+SP3) fp4 = packFloatx80(0, 0x3FF9, LIT64(0xD23CD68415D95FA1)); fp3 = floatx80_add(fp3, fp4, status); // Q2+S(Q3+SQ4) fp4 = packFloatx80(1, 0x3FFC, LIT64(0x8895A6C5FB423BCA)); fp2 = floatx80_add(fp2, fp4, status); // P1+S(P2+SP3) fp3 = floatx80_mul(fp3, fp1, status); // S(Q2+S(Q3+SQ4)) fp2 = floatx80_mul(fp2, fp1, status); // S(P1+S(P2+SP3)) fp4 = packFloatx80(1, 0x3FFD, LIT64(0xEEF57E0DA84BC8CE)); fp3 = floatx80_add(fp3, fp4, status); // Q1+S(Q2+S(Q3+SQ4)) fp2 = floatx80_mul(fp2, fp0, status); // RS(P1+S(P2+SP3)) fp1 = floatx80_mul(fp1, fp3, status); // S(Q1+S(Q2+S(Q3+SQ4))) fp0 = floatx80_add(fp0, fp2, status); // R+RS(P1+S(P2+SP3)) fp1 = floatx80_add(fp1, float32_to_floatx80(0x3F800000, status), status); // 1+S(Q1+S(Q2+S(Q3+SQ4))) RESET_PREC; a = floatx80_div(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } } } /*---------------------------------------------------------------------------- | Hyperbolic tangent *----------------------------------------------------------------------------*/ floatx80 floatx80_tanh(floatx80 a, float_status *status) { flag aSign, vSign; int32_t aExp, vExp; uint64_t aSig, vSig; int32_t compact; floatx80 fp0, fp1; float32 sign; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); return packFloatx80(aSign, one_exp, one_sig); } if (aExp == 0 && aSig == 0) { return packFloatx80(aSign, 0, 0); } SET_PREC; compact = floatx80_make_compact(aExp, aSig); if (compact < 0x3FD78000 || compact > 0x3FFFDDCE) { // TANHBORS if (compact < 0x3FFF8000) { // TANHSM RESET_PREC; a = floatx80_move(a, status); float_raise(float_flag_inexact, status); return a; } else { if (compact > 0x40048AA1) { // TANHHUGE sign = 0x3F800000; sign |= aSign ? 0x80000000 : 0x00000000; fp0 = float32_to_floatx80(sign, status); sign &= 0x80000000; sign ^= 0x80800000; // -SIGN(X)*EPS RESET_PREC; a = floatx80_add(fp0, float32_to_floatx80(sign, status), status); float_raise(float_flag_inexact, status); return a; } else { fp0 = packFloatx80(0, aExp+1, aSig); // Y = 2|X| fp0 = floatx80_etox(fp0, status); // FP0 IS EXP(Y) fp0 = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // EXP(Y)+1 sign = aSign ? 0x80000000 : 0x00000000; fp1 = floatx80_div(float32_to_floatx80(sign^0xC0000000, status), fp0, status); // -SIGN(X)*2 / [EXP(Y)+1] fp0 = float32_to_floatx80(sign | 0x3F800000, status); // SIGN RESET_PREC; a = floatx80_add(fp1, fp0, status); float_raise(float_flag_inexact, status); return a; } } } else { // 2**(-40) < |X| < (5/2)LOG2 fp0 = packFloatx80(0, aExp+1, aSig); // Y = 2|X| fp0 = floatx80_etoxm1(fp0, status); // FP0 IS Z = EXPM1(Y) fp1 = floatx80_add(fp0, float32_to_floatx80(0x40000000, status), status); // Z+2 vSign = extractFloatx80Sign(fp1); vExp = extractFloatx80Exp(fp1); vSig = extractFloatx80Frac(fp1); fp1 = packFloatx80(vSign ^ aSign, vExp, vSig); RESET_PREC; a = floatx80_div(fp0, fp1, status); float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | 10 to x *----------------------------------------------------------------------------*/ floatx80 floatx80_tentox(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact, n, j, l, m, m1; floatx80 fp0, fp1, fp2, fp3, adjfact, fact1, fact2; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign) return packFloatx80(0, 0, 0); return a; } if (aExp == 0 && aSig == 0) { return packFloatx80(0, one_exp, one_sig); } SET_PREC; fp0 = a; compact = floatx80_make_compact(aExp, aSig); if (compact < 0x3FB98000 || compact > 0x400B9B07) { // |X| > 16480 LOG2/LOG10 or |X| < 2^(-70) if (compact > 0x3FFF8000) { // |X| > 16480 RESET_PREC; if (aSign) { return roundAndPackFloatx80(status->floatx80_rounding_precision, 0, -0x1000, aSig, 0, status); } else { return roundAndPackFloatx80(status->floatx80_rounding_precision, 0, 0x8000, aSig, 0, status); } } else { // |X| < 2^(-70) RESET_PREC; a = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // 1 + X float_raise(float_flag_inexact, status); return a; } } else { // 2^(-70) <= |X| <= 16480 LOG 2 / LOG 10 fp1 = fp0; // X fp1 = floatx80_mul(fp1, float64_to_floatx80(LIT64(0x406A934F0979A371), status), status); // X*64*LOG10/LOG2 n = floatx80_to_int32(fp1, status); // N=INT(X*64*LOG10/LOG2) fp1 = int32_to_floatx80(n); j = n & 0x3F; l = n / 64; // NOTE: this is really arithmetic right shift by 6 if (n < 0 && j) { // arithmetic right shift is division and round towards minus infinity l--; } m = l / 2; // NOTE: this is really arithmetic right shift by 1 if (l < 0 && (l & 1)) { // arithmetic right shift is division and round towards minus infinity m--; } m1 = l - m; m1 += 0x3FFF; // ADJFACT IS 2^(M') adjfact = packFloatx80(0, m1, one_sig); fact1 = exp2_tbl[j]; fact1.high += m; fact2.high = exp2_tbl2[j]>>16; fact2.high += m; fact2.low = (uint64_t)(exp2_tbl2[j] & 0xFFFF); fact2.low <<= 48; fp2 = fp1; // N fp1 = floatx80_mul(fp1, float64_to_floatx80(LIT64(0x3F734413509F8000), status), status); // N*(LOG2/64LOG10)_LEAD fp3 = packFloatx80(1, 0x3FCD, LIT64(0xC0219DC1DA994FD2)); fp2 = floatx80_mul(fp2, fp3, status); // N*(LOG2/64LOG10)_TRAIL fp0 = floatx80_sub(fp0, fp1, status); // X - N L_LEAD fp0 = floatx80_sub(fp0, fp2, status); // X - N L_TRAIL fp2 = packFloatx80(0, 0x4000, LIT64(0x935D8DDDAAA8AC17)); // LOG10 fp0 = floatx80_mul(fp0, fp2, status); // R // EXPR fp1 = floatx80_mul(fp0, fp0, status); // S = R*R fp2 = float64_to_floatx80(LIT64(0x3F56C16D6F7BD0B2), status); // A5 fp3 = float64_to_floatx80(LIT64(0x3F811112302C712C), status); // A4 fp2 = floatx80_mul(fp2, fp1, status); // S*A5 fp3 = floatx80_mul(fp3, fp1, status); // S*A4 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FA5555555554CC1), status), status); // A3+S*A5 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0x3FC5555555554A54), status), status); // A2+S*A4 fp2 = floatx80_mul(fp2, fp1, status); // S*(A3+S*A5) fp3 = floatx80_mul(fp3, fp1, status); // S*(A2+S*A4) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FE0000000000000), status), status); // A1+S*(A3+S*A5) fp3 = floatx80_mul(fp3, fp0, status); // R*S*(A2+S*A4) fp2 = floatx80_mul(fp2, fp1, status); // S*(A1+S*(A3+S*A5)) fp0 = floatx80_add(fp0, fp3, status); // R+R*S*(A2+S*A4) fp0 = floatx80_add(fp0, fp2, status); // EXP(R) - 1 fp0 = floatx80_mul(fp0, fact1, status); fp0 = floatx80_add(fp0, fact2, status); fp0 = floatx80_add(fp0, fact1, status); RESET_PREC; a = floatx80_mul(fp0, adjfact, status); float_raise(float_flag_inexact, status); return a; } } /*---------------------------------------------------------------------------- | 2 to x *----------------------------------------------------------------------------*/ floatx80 floatx80_twotox(floatx80 a, float_status *status) { flag aSign; int32_t aExp; uint64_t aSig; int32_t compact, n, j, l, m, m1; floatx80 fp0, fp1, fp2, fp3, adjfact, fact1, fact2; aSig = extractFloatx80Frac(a); aExp = extractFloatx80Exp(a); aSign = extractFloatx80Sign(a); if (aExp == 0x7FFF) { if ((uint64_t) (aSig<<1)) return propagateFloatx80NaNOneArg(a, status); if (aSign) return packFloatx80(0, 0, 0); return a; } if (aExp == 0 && aSig == 0) { return packFloatx80(0, one_exp, one_sig); } SET_PREC; fp0 = a; compact = floatx80_make_compact(aExp, aSig); if (compact < 0x3FB98000 || compact > 0x400D80C0) { // |X| > 16480 or |X| < 2^(-70) if (compact > 0x3FFF8000) { // |X| > 16480 RESET_PREC;; if (aSign) { return roundAndPackFloatx80(status->floatx80_rounding_precision, 0, -0x1000, aSig, 0, status); } else { return roundAndPackFloatx80(status->floatx80_rounding_precision, 0, 0x8000, aSig, 0, status); } } else { // |X| < 2^(-70) RESET_PREC;; a = floatx80_add(fp0, float32_to_floatx80(0x3F800000, status), status); // 1 + X float_raise(float_flag_inexact, status); return a; } } else { // 2^(-70) <= |X| <= 16480 fp1 = fp0; // X fp1 = floatx80_mul(fp1, float32_to_floatx80(0x42800000, status), status); // X * 64 n = floatx80_to_int32(fp1, status); fp1 = int32_to_floatx80(n); j = n & 0x3F; l = n / 64; // NOTE: this is really arithmetic right shift by 6 if (n < 0 && j) { // arithmetic right shift is division and round towards minus infinity l--; } m = l / 2; // NOTE: this is really arithmetic right shift by 1 if (l < 0 && (l & 1)) { // arithmetic right shift is division and round towards minus infinity m--; } m1 = l - m; m1 += 0x3FFF; // ADJFACT IS 2^(M') adjfact = packFloatx80(0, m1, one_sig); fact1 = exp2_tbl[j]; fact1.high += m; fact2.high = exp2_tbl2[j]>>16; fact2.high += m; fact2.low = (uint64_t)(exp2_tbl2[j] & 0xFFFF); fact2.low <<= 48; fp1 = floatx80_mul(fp1, float32_to_floatx80(0x3C800000, status), status); // (1/64)*N fp0 = floatx80_sub(fp0, fp1, status); // X - (1/64)*INT(64 X) fp2 = packFloatx80(0, 0x3FFE, LIT64(0xB17217F7D1CF79AC)); // LOG2 fp0 = floatx80_mul(fp0, fp2, status); // R // EXPR fp1 = floatx80_mul(fp0, fp0, status); // S = R*R fp2 = float64_to_floatx80(LIT64(0x3F56C16D6F7BD0B2), status); // A5 fp3 = float64_to_floatx80(LIT64(0x3F811112302C712C), status); // A4 fp2 = floatx80_mul(fp2, fp1, status); // S*A5 fp3 = floatx80_mul(fp3, fp1, status); // S*A4 fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FA5555555554CC1), status), status); // A3+S*A5 fp3 = floatx80_add(fp3, float64_to_floatx80(LIT64(0x3FC5555555554A54), status), status); // A2+S*A4 fp2 = floatx80_mul(fp2, fp1, status); // S*(A3+S*A5) fp3 = floatx80_mul(fp3, fp1, status); // S*(A2+S*A4) fp2 = floatx80_add(fp2, float64_to_floatx80(LIT64(0x3FE0000000000000), status), status); // A1+S*(A3+S*A5) fp3 = floatx80_mul(fp3, fp0, status); // R*S*(A2+S*A4) fp2 = floatx80_mul(fp2, fp1, status); // S*(A1+S*(A3+S*A5)) fp0 = floatx80_add(fp0, fp3, status); // R+R*S*(A2+S*A4) fp0 = floatx80_add(fp0, fp2, status); // EXP(R) - 1 fp0 = floatx80_mul(fp0, fact1, status); fp0 = floatx80_add(fp0, fact2, status); fp0 = floatx80_add(fp0, fact1, status); RESET_PREC; a = floatx80_mul(fp0, adjfact, status); float_raise(float_flag_inexact, status); return a; } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/softfloat/softfloat_fpsp_tables.h000066400000000000000000000464641504763705000306720ustar00rootroot00000000000000 static const floatx80 atan_tbl[128] = { {0x3FFB, LIT64(0x83D152C5060B7A51)}, {0x3FFB, LIT64(0x8BC8544565498B8B)}, {0x3FFB, LIT64(0x93BE406017626B0D)}, {0x3FFB, LIT64(0x9BB3078D35AEC202)}, {0x3FFB, LIT64(0xA3A69A525DDCE7DE)}, {0x3FFB, LIT64(0xAB98E94362765619)}, {0x3FFB, LIT64(0xB389E502F9C59862)}, {0x3FFB, LIT64(0xBB797E436B09E6FB)}, {0x3FFB, LIT64(0xC367A5C739E5F446)}, {0x3FFB, LIT64(0xCB544C61CFF7D5C6)}, {0x3FFB, LIT64(0xD33F62F82488533E)}, {0x3FFB, LIT64(0xDB28DA8162404C77)}, {0x3FFB, LIT64(0xE310A4078AD34F18)}, {0x3FFB, LIT64(0xEAF6B0A8188EE1EB)}, {0x3FFB, LIT64(0xF2DAF1949DBE79D5)}, {0x3FFB, LIT64(0xFABD581361D47E3E)}, {0x3FFC, LIT64(0x8346AC210959ECC4)}, {0x3FFC, LIT64(0x8B232A08304282D8)}, {0x3FFC, LIT64(0x92FB70B8D29AE2F9)}, {0x3FFC, LIT64(0x9ACF476F5CCD1CB4)}, {0x3FFC, LIT64(0xA29E76304954F23F)}, {0x3FFC, LIT64(0xAA68C5D08AB85230)}, {0x3FFC, LIT64(0xB22DFFFD9D539F83)}, {0x3FFC, LIT64(0xB9EDEF453E900EA5)}, {0x3FFC, LIT64(0xC1A85F1CC75E3EA5)}, {0x3FFC, LIT64(0xC95D1BE828138DE6)}, {0x3FFC, LIT64(0xD10BF300840D2DE4)}, {0x3FFC, LIT64(0xD8B4B2BA6BC05E7A)}, {0x3FFC, LIT64(0xE0572A6BB42335F6)}, {0x3FFC, LIT64(0xE7F32A70EA9CAA8F)}, {0x3FFC, LIT64(0xEF88843264ECEFAA)}, {0x3FFC, LIT64(0xF7170A28ECC06666)}, {0x3FFD, LIT64(0x812FD288332DAD32)}, {0x3FFD, LIT64(0x88A8D1B1218E4D64)}, {0x3FFD, LIT64(0x9012AB3F23E4AEE8)}, {0x3FFD, LIT64(0x976CC3D411E7F1B9)}, {0x3FFD, LIT64(0x9EB689493889A227)}, {0x3FFD, LIT64(0xA5EF72C34487361B)}, {0x3FFD, LIT64(0xAD1700BAF07A7227)}, {0x3FFD, LIT64(0xB42CBCFAFD37EFB7)}, {0x3FFD, LIT64(0xBB303A940BA80F89)}, {0x3FFD, LIT64(0xC22115C6FCAEBBAF)}, {0x3FFD, LIT64(0xC8FEF3E686331221)}, {0x3FFD, LIT64(0xCFC98330B4000C70)}, {0x3FFD, LIT64(0xD6807AA1102C5BF9)}, {0x3FFD, LIT64(0xDD2399BC31252AA3)}, {0x3FFD, LIT64(0xE3B2A8556B8FC517)}, {0x3FFD, LIT64(0xEA2D764F64315989)}, {0x3FFD, LIT64(0xF3BF5BF8BAD1A21D)}, {0x3FFE, LIT64(0x801CE39E0D205C9A)}, {0x3FFE, LIT64(0x8630A2DADA1ED066)}, {0x3FFE, LIT64(0x8C1AD445F3E09B8C)}, {0x3FFE, LIT64(0x91DB8F1664F350E2)}, {0x3FFE, LIT64(0x97731420365E538C)}, {0x3FFE, LIT64(0x9CE1C8E6A0B8CDBA)}, {0x3FFE, LIT64(0xA22832DBCADAAE09)}, {0x3FFE, LIT64(0xA746F2DDB7602294)}, {0x3FFE, LIT64(0xAC3EC0FB997DD6A2)}, {0x3FFE, LIT64(0xB110688AEBDC6F6A)}, {0x3FFE, LIT64(0xB5BCC49059ECC4B0)}, {0x3FFE, LIT64(0xBA44BC7DD470782F)}, {0x3FFE, LIT64(0xBEA94144FD049AAC)}, {0x3FFE, LIT64(0xC2EB4ABB661628B6)}, {0x3FFE, LIT64(0xC70BD54CE602EE14)}, {0x3FFE, LIT64(0xCD000549ADEC7159)}, {0x3FFE, LIT64(0xD48457D2D8EA4EA3)}, {0x3FFE, LIT64(0xDB948DA712DECE3B)}, {0x3FFE, LIT64(0xE23855F969E8096A)}, {0x3FFE, LIT64(0xE8771129C4353259)}, {0x3FFE, LIT64(0xEE57C16E0D379C0D)}, {0x3FFE, LIT64(0xF3E10211A87C3779)}, {0x3FFE, LIT64(0xF919039D758B8D41)}, {0x3FFE, LIT64(0xFE058B8F64935FB3)}, {0x3FFF, LIT64(0x8155FB497B685D04)}, {0x3FFF, LIT64(0x83889E3549D108E1)}, {0x3FFF, LIT64(0x859CFA76511D724B)}, {0x3FFF, LIT64(0x87952ECFFF8131E7)}, {0x3FFF, LIT64(0x89732FD19557641B)}, {0x3FFF, LIT64(0x8B38CAD101932A35)}, {0x3FFF, LIT64(0x8CE7A8D8301EE6B5)}, {0x3FFF, LIT64(0x8F46A39E2EAE5281)}, {0x3FFF, LIT64(0x922DA7D791888487)}, {0x3FFF, LIT64(0x94D19FCBDEDF5241)}, {0x3FFF, LIT64(0x973AB94419D2A08B)}, {0x3FFF, LIT64(0x996FF00E08E10B96)}, {0x3FFF, LIT64(0x9B773F9512321DA7)}, {0x3FFF, LIT64(0x9D55CC320F935624)}, {0x3FFF, LIT64(0x9F100575006CC571)}, {0x3FFF, LIT64(0xA0A9C290D97CC06C)}, {0x3FFF, LIT64(0xA22659EBEBC0630A)}, {0x3FFF, LIT64(0xA388B4AFF6EF0EC9)}, {0x3FFF, LIT64(0xA4D35F1061D292C4)}, {0x3FFF, LIT64(0xA60895DCFBE3187E)}, {0x3FFF, LIT64(0xA72A51DC7367BEAC)}, {0x3FFF, LIT64(0xA83A51530956168F)}, {0x3FFF, LIT64(0xA93A20077539546E)}, {0x3FFF, LIT64(0xAA9E7245023B2605)}, {0x3FFF, LIT64(0xAC4C84BA6FE4D58F)}, {0x3FFF, LIT64(0xADCE4A4A606B9712)}, {0x3FFF, LIT64(0xAF2A2DCD8D263C9C)}, {0x3FFF, LIT64(0xB0656F81F22265C7)}, {0x3FFF, LIT64(0xB18465150F71496A)}, {0x3FFF, LIT64(0xB28AAA156F9ADA35)}, {0x3FFF, LIT64(0xB37B44FF3766B895)}, {0x3FFF, LIT64(0xB458C3DCE9630433)}, {0x3FFF, LIT64(0xB525529D562246BD)}, {0x3FFF, LIT64(0xB5E2CCA95F9D88CC)}, {0x3FFF, LIT64(0xB692CADA7ACA1ADA)}, {0x3FFF, LIT64(0xB736AEA7A6925838)}, {0x3FFF, LIT64(0xB7CFAB287E9F7B36)}, {0x3FFF, LIT64(0xB85ECC66CB219835)}, {0x3FFF, LIT64(0xB8E4FD5A20A593DA)}, {0x3FFF, LIT64(0xB99F41F64AFF9BB5)}, {0x3FFF, LIT64(0xBA7F1E17842BBE7B)}, {0x3FFF, LIT64(0xBB4712857637E17D)}, {0x3FFF, LIT64(0xBBFABE8A4788DF6F)}, {0x3FFF, LIT64(0xBC9D0FAD2B689D79)}, {0x3FFF, LIT64(0xBD306A39471ECD86)}, {0x3FFF, LIT64(0xBDB6C731856AF18A)}, {0x3FFF, LIT64(0xBE31CAC502E80D70)}, {0x3FFF, LIT64(0xBEA2D55CE33194E2)}, {0x3FFF, LIT64(0xBF0B10B7C03128F0)}, {0x3FFF, LIT64(0xBF6B7A18DACB778D)}, {0x3FFF, LIT64(0xBFC4EA4663FA18F6)}, {0x3FFF, LIT64(0xC0181BDE8B89A454)}, {0x3FFF, LIT64(0xC065B066CFBF6439)}, {0x3FFF, LIT64(0xC0AE345F56340AE6)}, {0x3FFF, LIT64(0xC0F222919CB9E6A7)} }; static const floatx80 exp_tbl[64] = { {0x3FFF, LIT64(0x8000000000000000)}, {0x3FFF, LIT64(0x8164D1F3BC030774)}, {0x3FFF, LIT64(0x82CD8698AC2BA1D8)}, {0x3FFF, LIT64(0x843A28C3ACDE4048)}, {0x3FFF, LIT64(0x85AAC367CC487B14)}, {0x3FFF, LIT64(0x871F61969E8D1010)}, {0x3FFF, LIT64(0x88980E8092DA8528)}, {0x3FFF, LIT64(0x8A14D575496EFD9C)}, {0x3FFF, LIT64(0x8B95C1E3EA8BD6E8)}, {0x3FFF, LIT64(0x8D1ADF5B7E5BA9E4)}, {0x3FFF, LIT64(0x8EA4398B45CD53C0)}, {0x3FFF, LIT64(0x9031DC431466B1DC)}, {0x3FFF, LIT64(0x91C3D373AB11C338)}, {0x3FFF, LIT64(0x935A2B2F13E6E92C)}, {0x3FFF, LIT64(0x94F4EFA8FEF70960)}, {0x3FFF, LIT64(0x96942D3720185A00)}, {0x3FFF, LIT64(0x9837F0518DB8A970)}, {0x3FFF, LIT64(0x99E0459320B7FA64)}, {0x3FFF, LIT64(0x9B8D39B9D54E5538)}, {0x3FFF, LIT64(0x9D3ED9A72CFFB750)}, {0x3FFF, LIT64(0x9EF5326091A111AC)}, {0x3FFF, LIT64(0xA0B0510FB9714FC4)}, {0x3FFF, LIT64(0xA27043030C496818)}, {0x3FFF, LIT64(0xA43515AE09E680A0)}, {0x3FFF, LIT64(0xA5FED6A9B15138EC)}, {0x3FFF, LIT64(0xA7CD93B4E9653568)}, {0x3FFF, LIT64(0xA9A15AB4EA7C0EF8)}, {0x3FFF, LIT64(0xAB7A39B5A93ED338)}, {0x3FFF, LIT64(0xAD583EEA42A14AC8)}, {0x3FFF, LIT64(0xAF3B78AD690A4374)}, {0x3FFF, LIT64(0xB123F581D2AC2590)}, {0x3FFF, LIT64(0xB311C412A9112488)}, {0x3FFF, LIT64(0xB504F333F9DE6484)}, {0x3FFF, LIT64(0xB6FD91E328D17790)}, {0x3FFF, LIT64(0xB8FBAF4762FB9EE8)}, {0x3FFF, LIT64(0xBAFF5AB2133E45FC)}, {0x3FFF, LIT64(0xBD08A39F580C36C0)}, {0x3FFF, LIT64(0xBF1799B67A731084)}, {0x3FFF, LIT64(0xC12C4CCA66709458)}, {0x3FFF, LIT64(0xC346CCDA24976408)}, {0x3FFF, LIT64(0xC5672A115506DADC)}, {0x3FFF, LIT64(0xC78D74C8ABB9B15C)}, {0x3FFF, LIT64(0xC9B9BD866E2F27A4)}, {0x3FFF, LIT64(0xCBEC14FEF2727C5C)}, {0x3FFF, LIT64(0xCE248C151F8480E4)}, {0x3FFF, LIT64(0xD06333DAEF2B2594)}, {0x3FFF, LIT64(0xD2A81D91F12AE45C)}, {0x3FFF, LIT64(0xD4F35AABCFEDFA20)}, {0x3FFF, LIT64(0xD744FCCAD69D6AF4)}, {0x3FFF, LIT64(0xD99D15C278AFD7B4)}, {0x3FFF, LIT64(0xDBFBB797DAF23754)}, {0x3FFF, LIT64(0xDE60F4825E0E9124)}, {0x3FFF, LIT64(0xE0CCDEEC2A94E110)}, {0x3FFF, LIT64(0xE33F8972BE8A5A50)}, {0x3FFF, LIT64(0xE5B906E77C8348A8)}, {0x3FFF, LIT64(0xE8396A503C4BDC68)}, {0x3FFF, LIT64(0xEAC0C6E7DD243930)}, {0x3FFF, LIT64(0xED4F301ED9942B84)}, {0x3FFF, LIT64(0xEFE4B99BDCDAF5CC)}, {0x3FFF, LIT64(0xF281773C59FFB138)}, {0x3FFF, LIT64(0xF5257D152486CC2C)}, {0x3FFF, LIT64(0xF7D0DF730AD13BB8)}, {0x3FFF, LIT64(0xFA83B2DB722A033C)}, {0x3FFF, LIT64(0xFD3E0C0CF486C174)} }; static const float32 exp_tbl2[64] = { 0x00000000, 0x9F841A9B, 0x9FC1D5B9, 0xA0728369, 0x1FC5C95C, 0x1EE85C9F, 0x9FA20729, 0xA07BF9AF, 0xA0020DCF, 0x205A63DA, 0x1EB70051, 0x1F6EB029, 0xA0781494, 0x9EB319B0, 0x2017457D, 0x1F11D537, 0x9FB952DD, 0x1FE43087, 0x1FA2A818, 0x1FDE494D, 0x20504890, 0xA073691C, 0x1F9B7A05, 0xA0797126, 0xA071A140, 0x204F62DA, 0x1F283C4A, 0x9F9A7FDC, 0xA05B3FAC, 0x1FDF2610, 0x9F705F90, 0x201F678A, 0x1F32FB13, 0x20038B30, 0x200DC3CC, 0x9F8B2AE6, 0xA02BBF70, 0xA00BF518, 0xA041DD41, 0x9FDF137B, 0x201F1568, 0x1FC13A2E, 0xA03F8F03, 0x1FF4907D, 0x9E6E53E4, 0x1FD6D45C, 0xA076EDB9, 0x9FA6DE21, 0x1EE69A2F, 0x207F439F, 0x201EC207, 0x9E8BE175, 0x20032C4B, 0x2004DFF5, 0x1E72F47A, 0x1F722F22, 0xA017E945, 0x1F401A5B, 0x9FB9A9E3, 0x20744C05, 0x1F773A19, 0x1FFE90D5, 0xA041ED22, 0x1F853F3A }; static const floatx80 exp2_tbl[64] = { {0x3FFF, LIT64(0x8000000000000000)}, {0x3FFF, LIT64(0x8164D1F3BC030773)}, {0x3FFF, LIT64(0x82CD8698AC2BA1D7)}, {0x3FFF, LIT64(0x843A28C3ACDE4046)}, {0x3FFF, LIT64(0x85AAC367CC487B15)}, {0x3FFF, LIT64(0x871F61969E8D1010)}, {0x3FFF, LIT64(0x88980E8092DA8527)}, {0x3FFF, LIT64(0x8A14D575496EFD9A)}, {0x3FFF, LIT64(0x8B95C1E3EA8BD6E7)}, {0x3FFF, LIT64(0x8D1ADF5B7E5BA9E6)}, {0x3FFF, LIT64(0x8EA4398B45CD53C0)}, {0x3FFF, LIT64(0x9031DC431466B1DC)}, {0x3FFF, LIT64(0x91C3D373AB11C336)}, {0x3FFF, LIT64(0x935A2B2F13E6E92C)}, {0x3FFF, LIT64(0x94F4EFA8FEF70961)}, {0x3FFF, LIT64(0x96942D3720185A00)}, {0x3FFF, LIT64(0x9837F0518DB8A96F)}, {0x3FFF, LIT64(0x99E0459320B7FA65)}, {0x3FFF, LIT64(0x9B8D39B9D54E5539)}, {0x3FFF, LIT64(0x9D3ED9A72CFFB751)}, {0x3FFF, LIT64(0x9EF5326091A111AE)}, {0x3FFF, LIT64(0xA0B0510FB9714FC2)}, {0x3FFF, LIT64(0xA27043030C496819)}, {0x3FFF, LIT64(0xA43515AE09E6809E)}, {0x3FFF, LIT64(0xA5FED6A9B15138EA)}, {0x3FFF, LIT64(0xA7CD93B4E965356A)}, {0x3FFF, LIT64(0xA9A15AB4EA7C0EF8)}, {0x3FFF, LIT64(0xAB7A39B5A93ED337)}, {0x3FFF, LIT64(0xAD583EEA42A14AC6)}, {0x3FFF, LIT64(0xAF3B78AD690A4375)}, {0x3FFF, LIT64(0xB123F581D2AC2590)}, {0x3FFF, LIT64(0xB311C412A9112489)}, {0x3FFF, LIT64(0xB504F333F9DE6484)}, {0x3FFF, LIT64(0xB6FD91E328D17791)}, {0x3FFF, LIT64(0xB8FBAF4762FB9EE9)}, {0x3FFF, LIT64(0xBAFF5AB2133E45FB)}, {0x3FFF, LIT64(0xBD08A39F580C36BF)}, {0x3FFF, LIT64(0xBF1799B67A731083)}, {0x3FFF, LIT64(0xC12C4CCA66709456)}, {0x3FFF, LIT64(0xC346CCDA24976407)}, {0x3FFF, LIT64(0xC5672A115506DADD)}, {0x3FFF, LIT64(0xC78D74C8ABB9B15D)}, {0x3FFF, LIT64(0xC9B9BD866E2F27A3)}, {0x3FFF, LIT64(0xCBEC14FEF2727C5D)}, {0x3FFF, LIT64(0xCE248C151F8480E4)}, {0x3FFF, LIT64(0xD06333DAEF2B2595)}, {0x3FFF, LIT64(0xD2A81D91F12AE45A)}, {0x3FFF, LIT64(0xD4F35AABCFEDFA1F)}, {0x3FFF, LIT64(0xD744FCCAD69D6AF4)}, {0x3FFF, LIT64(0xD99D15C278AFD7B6)}, {0x3FFF, LIT64(0xDBFBB797DAF23755)}, {0x3FFF, LIT64(0xDE60F4825E0E9124)}, {0x3FFF, LIT64(0xE0CCDEEC2A94E111)}, {0x3FFF, LIT64(0xE33F8972BE8A5A51)}, {0x3FFF, LIT64(0xE5B906E77C8348A8)}, {0x3FFF, LIT64(0xE8396A503C4BDC68)}, {0x3FFF, LIT64(0xEAC0C6E7DD24392F)}, {0x3FFF, LIT64(0xED4F301ED9942B84)}, {0x3FFF, LIT64(0xEFE4B99BDCDAF5CB)}, {0x3FFF, LIT64(0xF281773C59FFB13A)}, {0x3FFF, LIT64(0xF5257D152486CC2C)}, {0x3FFF, LIT64(0xF7D0DF730AD13BB9)}, {0x3FFF, LIT64(0xFA83B2DB722A033A)}, {0x3FFF, LIT64(0xFD3E0C0CF486C175)} }; static const float32 exp2_tbl2[64] = { 0x3F738000, 0x3FBEF7CA, 0x3FBDF8A9, 0x3FBCD7C9, 0xBFBDE8DA, 0x3FBDE85C, 0x3FBEBBF1, 0x3FBB80CA, 0xBFBA8373, 0xBFBE9670, 0x3FBDB700, 0x3FBEEEB0, 0x3FBBFD6D, 0xBFBDB319, 0x3FBDBA2B, 0x3FBE91D5, 0x3FBE8D5A, 0xBFBCDE7B, 0xBFBEBAAF, 0xBFBD86DA, 0xBFBEBEDD, 0x3FBCC96E, 0xBFBEC90B, 0x3FBBD1DB, 0x3FBCE5EB, 0xBFBEC274, 0x3FBEA83C, 0x3FBECB00, 0x3FBE9301, 0xBFBD8367, 0xBFBEF05F, 0x3FBDFB3C, 0x3FBEB2FB, 0x3FBAE2CB, 0x3FBCDC3C, 0x3FBEE9AA, 0xBFBEAEFD, 0xBFBCBF51, 0x3FBEF88A, 0x3FBD83B2, 0x3FBDF8AB, 0xBFBDFB17, 0xBFBEFE3C, 0xBFBBB6F8, 0xBFBCEE53, 0xBFBDA4AE, 0x3FBC9124, 0x3FBEB243, 0x3FBDE69A, 0xBFB8BC61, 0x3FBDF610, 0xBFBD8BE1, 0x3FBACB12, 0x3FBB9BFE, 0x3FBCF2F4, 0x3FBEF22F, 0xBFBDBF4A, 0x3FBEC01A, 0x3FBE8CAC, 0xBFBCBB3F, 0x3FBEF73A, 0xBFB8B795, 0x3FBEF84B, 0xBFBEF581 }; static const floatx80 log_tbl[128] = { {0x3FFE, LIT64(0xFE03F80FE03F80FE)}, {0x3FF7, LIT64(0xFF015358833C47E2)}, {0x3FFE, LIT64(0xFA232CF252138AC0)}, {0x3FF9, LIT64(0xBDC8D83EAD88D549)}, {0x3FFE, LIT64(0xF6603D980F6603DA)}, {0x3FFA, LIT64(0x9CF43DCFF5EAFD48)}, {0x3FFE, LIT64(0xF2B9D6480F2B9D65)}, {0x3FFA, LIT64(0xDA16EB88CB8DF614)}, {0x3FFE, LIT64(0xEF2EB71FC4345238)}, {0x3FFB, LIT64(0x8B29B7751BD70743)}, {0x3FFE, LIT64(0xEBBDB2A5C1619C8C)}, {0x3FFB, LIT64(0xA8D839F830C1FB49)}, {0x3FFE, LIT64(0xE865AC7B7603A197)}, {0x3FFB, LIT64(0xC61A2EB18CD907AD)}, {0x3FFE, LIT64(0xE525982AF70C880E)}, {0x3FFB, LIT64(0xE2F2A47ADE3A18AF)}, {0x3FFE, LIT64(0xE1FC780E1FC780E2)}, {0x3FFB, LIT64(0xFF64898EDF55D551)}, {0x3FFE, LIT64(0xDEE95C4CA037BA57)}, {0x3FFC, LIT64(0x8DB956A97B3D0148)}, {0x3FFE, LIT64(0xDBEB61EED19C5958)}, {0x3FFC, LIT64(0x9B8FE100F47BA1DE)}, {0x3FFE, LIT64(0xD901B2036406C80E)}, {0x3FFC, LIT64(0xA9372F1D0DA1BD17)}, {0x3FFE, LIT64(0xD62B80D62B80D62C)}, {0x3FFC, LIT64(0xB6B07F38CE90E46B)}, {0x3FFE, LIT64(0xD3680D3680D3680D)}, {0x3FFC, LIT64(0xC3FD032906488481)}, {0x3FFE, LIT64(0xD0B69FCBD2580D0B)}, {0x3FFC, LIT64(0xD11DE0FF15AB18CA)}, {0x3FFE, LIT64(0xCE168A7725080CE1)}, {0x3FFC, LIT64(0xDE1433A16C66B150)}, {0x3FFE, LIT64(0xCB8727C065C393E0)}, {0x3FFC, LIT64(0xEAE10B5A7DDC8ADD)}, {0x3FFE, LIT64(0xC907DA4E871146AD)}, {0x3FFC, LIT64(0xF7856E5EE2C9B291)}, {0x3FFE, LIT64(0xC6980C6980C6980C)}, {0x3FFD, LIT64(0x82012CA5A68206D7)}, {0x3FFE, LIT64(0xC4372F855D824CA6)}, {0x3FFD, LIT64(0x882C5FCD7256A8C5)}, {0x3FFE, LIT64(0xC1E4BBD595F6E947)}, {0x3FFD, LIT64(0x8E44C60B4CCFD7DE)}, {0x3FFE, LIT64(0xBFA02FE80BFA02FF)}, {0x3FFD, LIT64(0x944AD09EF4351AF6)}, {0x3FFE, LIT64(0xBD69104707661AA3)}, {0x3FFD, LIT64(0x9A3EECD4C3EAA6B2)}, {0x3FFE, LIT64(0xBB3EE721A54D880C)}, {0x3FFD, LIT64(0xA0218434353F1DE8)}, {0x3FFE, LIT64(0xB92143FA36F5E02E)}, {0x3FFD, LIT64(0xA5F2FCABBBC506DA)}, {0x3FFE, LIT64(0xB70FBB5A19BE3659)}, {0x3FFD, LIT64(0xABB3B8BA2AD362A5)}, {0x3FFE, LIT64(0xB509E68A9B94821F)}, {0x3FFD, LIT64(0xB1641795CE3CA97B)}, {0x3FFE, LIT64(0xB30F63528917C80B)}, {0x3FFD, LIT64(0xB70475515D0F1C61)}, {0x3FFE, LIT64(0xB11FD3B80B11FD3C)}, {0x3FFD, LIT64(0xBC952AFEEA3D13E1)}, {0x3FFE, LIT64(0xAF3ADDC680AF3ADE)}, {0x3FFD, LIT64(0xC2168ED0F458BA4A)}, {0x3FFE, LIT64(0xAD602B580AD602B6)}, {0x3FFD, LIT64(0xC788F439B3163BF1)}, {0x3FFE, LIT64(0xAB8F69E28359CD11)}, {0x3FFD, LIT64(0xCCECAC08BF04565D)}, {0x3FFE, LIT64(0xA9C84A47A07F5638)}, {0x3FFD, LIT64(0xD24204872DD85160)}, {0x3FFE, LIT64(0xA80A80A80A80A80B)}, {0x3FFD, LIT64(0xD78949923BC3588A)}, {0x3FFE, LIT64(0xA655C4392D7B73A8)}, {0x3FFD, LIT64(0xDCC2C4B49887DACC)}, {0x3FFE, LIT64(0xA4A9CF1D96833751)}, {0x3FFD, LIT64(0xE1EEBD3E6D6A6B9E)}, {0x3FFE, LIT64(0xA3065E3FAE7CD0E0)}, {0x3FFD, LIT64(0xE70D785C2F9F5BDC)}, {0x3FFE, LIT64(0xA16B312EA8FC377D)}, {0x3FFD, LIT64(0xEC1F392C5179F283)}, {0x3FFE, LIT64(0x9FD809FD809FD80A)}, {0x3FFD, LIT64(0xF12440D3E36130E6)}, {0x3FFE, LIT64(0x9E4CAD23DD5F3A20)}, {0x3FFD, LIT64(0xF61CCE92346600BB)}, {0x3FFE, LIT64(0x9CC8E160C3FB19B9)}, {0x3FFD, LIT64(0xFB091FD38145630A)}, {0x3FFE, LIT64(0x9B4C6F9EF03A3CAA)}, {0x3FFD, LIT64(0xFFE97042BFA4C2AD)}, {0x3FFE, LIT64(0x99D722DABDE58F06)}, {0x3FFE, LIT64(0x825EFCED49369330)}, {0x3FFE, LIT64(0x9868C809868C8098)}, {0x3FFE, LIT64(0x84C37A7AB9A905C9)}, {0x3FFE, LIT64(0x97012E025C04B809)}, {0x3FFE, LIT64(0x87224C2E8E645FB7)}, {0x3FFE, LIT64(0x95A02568095A0257)}, {0x3FFE, LIT64(0x897B8CAC9F7DE298)}, {0x3FFE, LIT64(0x9445809445809446)}, {0x3FFE, LIT64(0x8BCF55DEC4CD05FE)}, {0x3FFE, LIT64(0x92F113840497889C)}, {0x3FFE, LIT64(0x8E1DC0FB89E125E5)}, {0x3FFE, LIT64(0x91A2B3C4D5E6F809)}, {0x3FFE, LIT64(0x9066E68C955B6C9B)}, {0x3FFE, LIT64(0x905A38633E06C43B)}, {0x3FFE, LIT64(0x92AADE74C7BE59E0)}, {0x3FFE, LIT64(0x8F1779D9FDC3A219)}, {0x3FFE, LIT64(0x94E9BFF615845643)}, {0x3FFE, LIT64(0x8DDA520237694809)}, {0x3FFE, LIT64(0x9723A1B720134203)}, {0x3FFE, LIT64(0x8CA29C046514E023)}, {0x3FFE, LIT64(0x995899C890EB8990)}, {0x3FFE, LIT64(0x8B70344A139BC75A)}, {0x3FFE, LIT64(0x9B88BDAA3A3DAE2F)}, {0x3FFE, LIT64(0x8A42F8705669DB46)}, {0x3FFE, LIT64(0x9DB4224FFFE1157C)}, {0x3FFE, LIT64(0x891AC73AE9819B50)}, {0x3FFE, LIT64(0x9FDADC268B7A12DA)}, {0x3FFE, LIT64(0x87F78087F78087F8)}, {0x3FFE, LIT64(0xA1FCFF17CE733BD4)}, {0x3FFE, LIT64(0x86D905447A34ACC6)}, {0x3FFE, LIT64(0xA41A9E8F5446FB9F)}, {0x3FFE, LIT64(0x85BF37612CEE3C9B)}, {0x3FFE, LIT64(0xA633CD7E6771CD8B)}, {0x3FFE, LIT64(0x84A9F9C8084A9F9D)}, {0x3FFE, LIT64(0xA8489E600B435A5E)}, {0x3FFE, LIT64(0x839930523FBE3368)}, {0x3FFE, LIT64(0xAA59233CCCA4BD49)}, {0x3FFE, LIT64(0x828CBFBEB9A020A3)}, {0x3FFE, LIT64(0xAC656DAE6BCC4985)}, {0x3FFE, LIT64(0x81848DA8FAF0D277)}, {0x3FFE, LIT64(0xAE6D8EE360BB2468)}, {0x3FFE, LIT64(0x8080808080808081)}, {0x3FFE, LIT64(0xB07197A23C46C654)} }; static const floatx80 pi_tbl[65] = { {0xC004, LIT64(0xC90FDAA22168C235)}, {0xC004, LIT64(0xC2C75BCD105D7C23)}, {0xC004, LIT64(0xBC7EDCF7FF523611)}, {0xC004, LIT64(0xB6365E22EE46F000)}, {0xC004, LIT64(0xAFEDDF4DDD3BA9EE)}, {0xC004, LIT64(0xA9A56078CC3063DD)}, {0xC004, LIT64(0xA35CE1A3BB251DCB)}, {0xC004, LIT64(0x9D1462CEAA19D7B9)}, {0xC004, LIT64(0x96CBE3F9990E91A8)}, {0xC004, LIT64(0x9083652488034B96)}, {0xC004, LIT64(0x8A3AE64F76F80584)}, {0xC004, LIT64(0x83F2677A65ECBF73)}, {0xC003, LIT64(0xFB53D14AA9C2F2C2)}, {0xC003, LIT64(0xEEC2D3A087AC669F)}, {0xC003, LIT64(0xE231D5F66595DA7B)}, {0xC003, LIT64(0xD5A0D84C437F4E58)}, {0xC003, LIT64(0xC90FDAA22168C235)}, {0xC003, LIT64(0xBC7EDCF7FF523611)}, {0xC003, LIT64(0xAFEDDF4DDD3BA9EE)}, {0xC003, LIT64(0xA35CE1A3BB251DCB)}, {0xC003, LIT64(0x96CBE3F9990E91A8)}, {0xC003, LIT64(0x8A3AE64F76F80584)}, {0xC002, LIT64(0xFB53D14AA9C2F2C2)}, {0xC002, LIT64(0xE231D5F66595DA7B)}, {0xC002, LIT64(0xC90FDAA22168C235)}, {0xC002, LIT64(0xAFEDDF4DDD3BA9EE)}, {0xC002, LIT64(0x96CBE3F9990E91A8)}, {0xC001, LIT64(0xFB53D14AA9C2F2C2)}, {0xC001, LIT64(0xC90FDAA22168C235)}, {0xC001, LIT64(0x96CBE3F9990E91A8)}, {0xC000, LIT64(0xC90FDAA22168C235)}, {0xBFFF, LIT64(0xC90FDAA22168C235)}, {0x0000, LIT64(0x0000000000000000)}, {0x3FFF, LIT64(0xC90FDAA22168C235)}, {0x4000, LIT64(0xC90FDAA22168C235)}, {0x4001, LIT64(0x96CBE3F9990E91A8)}, {0x4001, LIT64(0xC90FDAA22168C235)}, {0x4001, LIT64(0xFB53D14AA9C2F2C2)}, {0x4002, LIT64(0x96CBE3F9990E91A8)}, {0x4002, LIT64(0xAFEDDF4DDD3BA9EE)}, {0x4002, LIT64(0xC90FDAA22168C235)}, {0x4002, LIT64(0xE231D5F66595DA7B)}, {0x4002, LIT64(0xFB53D14AA9C2F2C2)}, {0x4003, LIT64(0x8A3AE64F76F80584)}, {0x4003, LIT64(0x96CBE3F9990E91A8)}, {0x4003, LIT64(0xA35CE1A3BB251DCB)}, {0x4003, LIT64(0xAFEDDF4DDD3BA9EE)}, {0x4003, LIT64(0xBC7EDCF7FF523611)}, {0x4003, LIT64(0xC90FDAA22168C235)}, {0x4003, LIT64(0xD5A0D84C437F4E58)}, {0x4003, LIT64(0xE231D5F66595DA7B)}, {0x4003, LIT64(0xEEC2D3A087AC669F)}, {0x4003, LIT64(0xFB53D14AA9C2F2C2)}, {0x4004, LIT64(0x83F2677A65ECBF73)}, {0x4004, LIT64(0x8A3AE64F76F80584)}, {0x4004, LIT64(0x9083652488034B96)}, {0x4004, LIT64(0x96CBE3F9990E91A8)}, {0x4004, LIT64(0x9D1462CEAA19D7B9)}, {0x4004, LIT64(0xA35CE1A3BB251DCB)}, {0x4004, LIT64(0xA9A56078CC3063DD)}, {0x4004, LIT64(0xAFEDDF4DDD3BA9EE)}, {0x4004, LIT64(0xB6365E22EE46F000)}, {0x4004, LIT64(0xBC7EDCF7FF523611)}, {0x4004, LIT64(0xC2C75BCD105D7C23)}, {0x4004, LIT64(0xC90FDAA22168C235)} }; static const float32 pi_tbl2[65] = { 0x21800000, 0xA0D00000, 0xA1E80000, 0x21480000, 0xA1200000, 0x21FC0000, 0x21100000, 0xA1580000, 0x21E00000, 0x20B00000, 0xA1880000, 0x21C40000, 0x20000000, 0x21380000, 0xA1300000, 0x9FC00000, 0x21000000, 0xA1680000, 0xA0A00000, 0x20900000, 0x21600000, 0xA1080000, 0x1F800000, 0xA0B00000, 0x20800000, 0xA0200000, 0x20E00000, 0x1F000000, 0x20000000, 0x20600000, 0x1F800000, 0x1F000000, 0x00000000, 0x9F000000, 0x9F800000, 0xA0600000, 0xA0000000, 0x9F000000, 0xA0E00000, 0x20200000, 0xA0800000, 0x20B00000, 0x9F800000, 0x21080000, 0xA1600000, 0xA0900000, 0x20A00000, 0x21680000, 0xA1000000, 0x1FC00000, 0x21300000, 0xA1380000, 0xA0000000, 0xA1C40000, 0x21880000, 0xA0B00000, 0xA1E00000, 0x21580000, 0xA1100000, 0xA1FC0000, 0x21200000, 0xA1480000, 0x21E80000, 0x20D00000, 0xA1800000 }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/sysconfig.h000066400000000000000000000367451504763705000243130ustar00rootroot00000000000000#ifndef WINUAE_SYSCONFIG_H #define WINUAE_SYSCONFIG_H /* This define disable compilation of Amiga's specific parts in the cpu core */ /* when compiling Hatari */ #define WINUAE_FOR_HATARI #ifndef WINUAE_FOR_HATARI #pragma warning (disable : 4761) #pragma warning (disable : 4996) #pragma warning (disable : 4018) #define DIRECTINPUT_VERSION 0x0800 #define DIRECT3D_VERSION 0x0900 #define SUPPORT_THREADS #define MAX_DPATH 1000 #define DRIVESOUND #define GFXFILTER #if defined(_M_ARM64) || defined(_M_ARM64EC) #define __arm__ #define MSVC_LONG_DOUBLE #else #define X86_MSVC_ASSEMBLY //#define OPTIMIZED_FLAGS #define MSVC_LONG_DOUBLE #ifndef __i386__ #define __i386__ #endif #endif #define WINDOWS #define ZLIB_WINAPI #define PACKAGE_STRING "WinUAE" #else /* ! WINUAE_FOR_HATARI */ #define MAX_DPATH 1000 #endif /* ! WINUAE_FOR_HATARI */ #ifndef UAE_MINI #if !defined(_M_ARM64) && !defined(_M_ARM64EC) && !defined(__aarch64__) //#define JIT /* JIT compiler support */ //#define USE_JIT_FPU #endif #define DEBUGGER //#define GDBSERVER #define FILESYS /* filesys emulation */ #define UAE_FILESYS_THREADS //#define AUTOCONFIG /* autoconfig support, fast ram, harddrives etc.. */ //#define NOFLAGS_SUPPORT_GENCPU #define NOFLAGS_SUPPORT_GENCOMP //#define HAVE_GET_WORD_UNSWAPPED #define NATMEM_OFFSET natmem_offset #define USE_NORMAL_CALLING_CONVENTION 0 #define USE_X86_FPUCW 1 #define WINDDK /* Windows DDK available, keyboard leds and harddrive support */ #define CATWEASEL /* Catweasel MK2/3 support */ #define AHI /* AHI sound emulation */ #define ENFORCER /* UAE Enforcer */ #define ECS_DENISE /* ECS DENISE new features */ #define AGA /* AGA chipset emulation (ECS_DENISE must be enabled) */ #define CD32 /* CD32 emulation */ #define CDTV /* CDTV emulation */ #define D3D /* D3D display filter support */ //#define OPENGL /* OpenGL display filter support */ #define PARALLEL_PORT /* parallel port emulation */ #define PARALLEL_DIRECT /* direct parallel port emulation */ #define SERIAL_PORT /* serial port emulation */ #define SERIAL_ENET /* serial port UDP transport */ #define SCSIEMU /* uaescsi.device emulation */ #define UAESERIAL /* uaeserial.device emulation */ #define FPUEMU /* FPU emulation */ #define FPU_UAE #define MMUEMU /* Aranym 68040 MMU */ #define FULLMMU /* Aranym 68040 MMU */ #define CPUEMU_0 /* generic 680x0 emulation */ #define CPUEMU_11 /* 68000/68010 prefetch emulation */ #define CPUEMU_13 /* 68000/68010 cycle-exact cpu&blitter */ #define CPUEMU_20 /* 68020 prefetch */ #define CPUEMU_21 /* 68020 "cycle-exact" + blitter */ #define CPUEMU_22 /* 68030 prefetch */ #define CPUEMU_23 /* 68030 "cycle-exact" + blitter */ #define CPUEMU_24 /* 68060 "cycle-exact" + blitter */ #define CPUEMU_25 /* 68040 "cycle-exact" + blitter */ #define CPUEMU_31 /* Aranym 68040 MMU */ #define CPUEMU_32 /* Previous 68030 MMU */ #define CPUEMU_33 /* 68060 MMU */ #define CPUEMU_34 /* 68030 MMU + cache */ #define CPUEMU_35 /* 68030 MMU + cache + CE */ #define CPUEMU_40 /* generic 680x0 with JIT direct memory access */ #define CPUEMU_50 /* generic 680x0 with indirect memory access */ //#define ACTION_REPLAY /* Action Replay 1/2/3 support */ #define PICASSO96 /* Picasso96 display card emulation */ #define UAEGFX_INTERNAL /* built-in libs:picasso96/uaegfx.card */ #define BSDSOCKET /* bsdsocket.library emulation */ #define CAPS /* CAPS-image support */ #define SCP /* SuperCardPro */ #define FDI2RAW /* FDI 1.0 and 2.x image support */ #define AVIOUTPUT /* Avioutput support */ #define PROWIZARD /* Pro-Wizard module ripper */ #define ARCADIA /* Arcadia arcade system */ #define ARCHIVEACCESS /* ArchiveAccess decompression library */ #define LOGITECHLCD /* Logitech G15 LCD */ #define SAVESTATE /* State file support */ #define A2091 /* A590/A2091 SCSI */ #define A2065 /* A2065 Ethernet card */ #define GFXBOARD /* Hardware graphics board */ #define NCR /* A4000T/A4091, 53C710/53C770 SCSI */ #define NCR9X /* 53C9X SCSI */ #define SANA2 /* SANA2 network driver */ #define AMAX /* A-Max ROM adapater emulation */ #define RETROPLATFORM /* Cloanto RetroPlayer support */ #ifndef WINUAE_FOR_HATARI #define WITH_CHD #define WITH_LUA /* lua scripting */ #define WITH_UAENATIVE #define WITH_SLIRP #define WITH_BUILTIN_SLIRP #define WITH_TABLETLIBRARY #define WITH_UAENET_PCAP #define WITH_PPC #define WITH_QEMU_CPU #define WITH_TOCCATA #define WITH_PCI #define WITH_X86 #define WITH_THREADED_CPU #endif /* ! WINUAE_FOR_HATARI */ #define WITH_SOFTFLOAT #else /* #define SINGLEFILE */ #define CUSTOM_SIMPLE /* simplified custom chipset emulation */ #define CPUEMU_0 #define CPUEMU_68000_ONLY /* drop 68010+ commands from CPUEMU_0 */ #define ADDRESS_SPACE_24BIT #ifndef UAE_NOGUI #define D3D #define OPENGL #endif #define CAPS #define CPUEMU_13 #define CPUEMU_11 #endif #define WITH_SCSI_IOCTL #define WITH_SCSI_SPTI #define A_ZIP #define A_RAR #define A_7Z #define A_LHA #define A_LZX #define A_DMS #define A_WRP #ifndef WINUAE_FOR_HATARI #ifndef PATH_MAX #define PATH_MAX 256 #endif #endif /* ! WINUAE_FOR_HATARI */ #define UAE_RAND_MAX RAND_MAX #ifndef GFXFILTER #undef OPENGL #undef D3D #endif #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC #include #include #endif #include #ifdef WIN64 #undef X86_MSVC_ASSEMBLY_MEMACCESS #undef X86_MSVC_ASSEMBLY #define X64_MSVC_ASSEMBLY #define SIZEOF_VOID_P 8 #else #define SIZEOF_VOID_P 4 #endif #if !defined(AHI) #undef ENFORCER #endif typedef long uae_atomic; #ifndef WINUAE_FOR_HATARI /* src/sysconfig.h. Generated automatically by configure. */ /* src/sysconfig.h.in. Generated automatically from configure.in by autoheader. */ /* Define if on AIX 3. System headers sometimes define this. We just want to avoid a redefinition error message. */ #ifndef _ALL_SOURCE /* #undef _ALL_SOURCE */ #endif /* Define to empty if the keyword does not work. */ /* #undef const */ /* Define if you have the getmntent function. */ /* #undef HAVE_GETMNTENT */ /* Define if your struct stat has st_blocks. */ /* #undef HAVE_ST_BLOCKS */ #endif /* ! WINUAE_FOR_HATARI */ /* Define as __inline if that's what the C compiler calls it. */ /* #undef inline */ #define __inline__ __inline #define __volatile__ volatile #ifndef WINUAE_FOR_HATARI /* Define to `int' if doesn't define. */ /* #undef mode_t */ #define mode_t int /* Define to `long' if doesn't define. */ /* #undef off_t */ /* Define to `int' if doesn't define. */ /* #undef pid_t */ #endif /* ! WINUAE_FOR_HATARI */ /* Define as the return type of signal handlers (int or void). */ #define RETSIGTYPE void /* Define if you can safely include both and . */ #ifdef __GNUC__ #define TIME_WITH_SYS_TIME 1 #endif #ifdef _WIN32_WCE #define NO_TIME_H 1 #endif /* Define if your declares struct tm. */ /* #undef TM_IN_SYS_TIME */ #ifndef WINUAE_FOR_HATARI /* Define if you have the Andrew File System. */ /* #undef AFS */ /* Define if there is no specific function for reading the list of mounted filesystems. fread will be used to read /etc/mnttab. [SVR2] */ /* #undef MOUNTED_FREAD */ /* Define if (like SVR2) there is no specific function for reading the list of mounted filesystems, and your system has these header files: and . [SVR3] */ /* #undef MOUNTED_FREAD_FSTYP */ /* Define if there is a function named getfsstat for reading the list of mounted filesystems. [DEC Alpha running OSF/1] */ /* #undef MOUNTED_GETFSSTAT */ /* Define if there is a function named getmnt for reading the list of mounted filesystems. [Ultrix] */ /* #undef MOUNTED_GETMNT */ /* Define if there is a function named getmntent for reading the list of mounted filesystems, and that function takes a single argument. [4.3BSD, SunOS, HP-UX, Dynix, Irix] */ /* #undef MOUNTED_GETMNTENT1 */ /* Define if there is a function named getmntent for reading the list of mounted filesystems, and that function takes two arguments. [SVR4] */ /* #undef MOUNTED_GETMNTENT2 */ /* Define if there is a function named getmntinfo for reading the list of mounted filesystems. [4.4BSD] */ /* #undef MOUNTED_GETMNTINFO */ /* Define if there is a function named listmntent that can be used to list all mounted filesystems. [UNICOS] */ /* #undef MOUNTED_LISTMNTENT */ /* Define if there is a function named mntctl that can be used to read the list of mounted filesystems, and there is a system header file that declares `struct vmount.' [AIX] */ /* #undef MOUNTED_VMOUNT */ /* Define if statfs takes 3 args. [DEC Alpha running OSF/1] */ /* #undef STAT_STATFS3_OSF1 */ /* Define if there is no specific function for reading filesystems usage information and you have the header file. [SVR2] */ /* #undef STAT_READ_FILSYS */ /* Define if statfs takes 2 args and struct statfs has a field named f_bsize. [4.3BSD, SunOS 4, HP-UX, AIX PS/2] */ /* #undef STAT_STATFS2_BSIZE */ /* Define if statfs takes 2 args and struct statfs has a field named f_fsize. [4.4BSD, NetBSD] */ /* #undef STAT_STATFS2_FSIZE */ /* Define if statfs takes 2 args and the second argument has type struct fs_data. [Ultrix] */ /* #undef STAT_STATFS2_FS_DATA */ /* Define if statfs takes 4 args. [SVR3, Dynix, Irix, Dolphin] */ /* #undef STAT_STATFS4 */ /* Define if there is a function named statvfs. [SVR4] */ /* #undef STAT_STATVFS */ /* Define if the block counts reported by statfs may be truncated to 2GB and the correct values may be stored in the f_spare array. [SunOS 4.1.2, 4.1.3, and 4.1.3_U1 are reported to have this problem. SunOS 4.1.1 seems not to be affected.] */ /* #undef STATFS_TRUNCATES_BLOCK_COUNTS */ #endif /* ! WINUAE_FOR_HATARI */ /* The number of bytes in a __int64. */ #define SIZEOF___INT64 8 /* The number of bytes in a char. */ #define SIZEOF_CHAR 1 /* The number of bytes in a int. */ #define SIZEOF_INT 4 /* The number of bytes in a long. */ #define SIZEOF_LONG 4 /* The number of bytes in a long long. */ #define SIZEOF_LONG_LONG 8 /* The number of bytes in a short. */ #define SIZEOF_SHORT 2 #define SIZEOF_FLOAT 4 #define SIZEOF_DOUBLE 8 #define HAVE_ISNAN #undef HAVE_ISINF //#define isnan _isnan #ifndef WINUAE_FOR_HATARI #ifndef LT_MODULE_EXT #define LT_MODULE_EXT _T(".dll") #endif #endif /* ! WINUAE_FOR_HATARI */ /* Define if you have the bcopy function. */ /* #undef HAVE_BCOPY */ /* Define if you have the cfmakeraw function. */ /* #undef HAVE_CFMAKERAW */ /* Define if you have the endgrent function. */ /* #undef HAVE_ENDGRENT */ /* Define if you have the endpwent function. */ /* #undef HAVE_ENDPWENT */ /* Define if you have the fchdir function. */ /* #undef HAVE_FCHDIR */ /* Define if you have the ftime function. */ /* #undef HAVE_FTIME */ /* Define if you have the ftruncate function. */ /* #undef HAVE_FTRUNCATE */ /* Define if you have the getcwd function. */ #define HAVE_GETCWD 1 /* Define if you have the getmntinfo function. */ /* #undef HAVE_GETMNTINFO */ /* Define if you have the getopt function. */ /* #undef HAVE_GETOPT */ /* Define if you have the gettimeofday function. */ #ifndef _WIN32_WCE //#define HAVE_GETTIMEOFDAY #endif /* Define if you have the isascii function. */ /* #undef HAVE_ISASCII */ /* Define if you have the lchown function. */ /* #undef HAVE_LCHOWN */ /* Define if you have the listmntent function. */ /* #undef HAVE_LISTMNTENT */ /* Define if you have the memcpy function. */ /* #undef HAVE_MEMCPY */ /* Define if you have the mkdir function. */ #define HAVE_MKDIR 1 /* Define if you have the mkfifo function. */ /* #undef HAVE_MKFIFO */ /* Define if you have the rmdir function. */ #define HAVE_RMDIR 1 /* Define if you have the select function. */ /* #undef HAVE_SELECT */ /* Define if you have the strchr function. */ /* #undef HAVE_STRCHR */ /* Define if you have the strdup function. */ #define HAVE_STRDUP 1 /* Define if you have the strerror function. */ #define HAVE_STRERROR 1 /* Define if you have the strrchr function. */ /* #undef HAVE_STRRCHR */ /* Define if you have the strstr function. */ #define HAVE_STRSTR 1 /* Define if you have the tcgetattr function. */ /* #undef HAVE_TCGETATTR */ /* Define if you have the vfprintf function. */ #define HAVE_VFPRINTF 1 /* Define if you have the vprintf function. */ #define HAVE_VPRINTF 1 /* Define if you have the vsprintf function. */ #define HAVE_VSPRINTF 1 /* Define if you have the header file. */ #define HAVE_DIRENT_H 1 /* Define if you have the header file. */ #ifndef _WIN32_WCE #define HAVE_FCNTL_H 1 #endif /* Define if you have the header file. */ /* #undef HAVE_GGI_LIBGGI_H */ /* Define if you have the header file. */ /* #undef HAVE_LIBRARIES_CYBERGRAPHICS_H */ /* Define if you have the header file. */ /* #undef HAVE_MACHINE_JOYSTICK_H */ /* Define if you have the header file. */ /* #undef HAVE_MACHINE_SOUNDCARD_H */ /* Define if you have the header file. */ /* #undef HAVE_MNTENT_H */ /* Define if you have the header file. */ /* #undef HAVE_MNTTAB_H */ /* Define if you have the header file. */ /* #undef HAVE_NCURSES_H */ /* Define if you have the header file. */ /* #undef HAVE_NDIR_H */ /* Define if you have the header file. */ /* #undef HAVE_POSIX_OPT_H */ #ifndef _WIN32_WCE /* Define if you have the header file. */ #define HAVE_STRING_H 1 #endif /* Define if you have the header file. */ /* #undef HAVE_STRINGS_H */ /* Define if you have the header file. */ /* #undef HAVE_SUN_AUDIOIO_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_AUDIOIO_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_DIR_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_FILSYS_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_FS_S5PARAM_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_IOCTL_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_IPC_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_MOUNT_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_NDIR_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_PARAM_H */ /* Define if you have the header file. */ #ifndef _WIN32_WCE #define HAVE_SYS_STAT_H 1 #endif /* Define if you have the header file. */ /* #undef HAVE_SYS_STATFS_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_STATVFS_H */ /* Define if you have the header file. */ /* #undef HAVE_SYS_TERMIOS_H */ /* Define if you have the header file. */ #ifdef __GNUC__ #define HAVE_SYS_TIME_H 1 #endif /* Define if you have the header file. */ #ifndef _WIN32_WCE #define HAVE_SYS_TYPES_H 1 #endif /* Define if you have the header file. */ #ifndef _WIN32_WCE //#define HAVE_SYS_UTIME_H 1 #endif /* Define if you have the header file. */ /* #undef HAVE_SYS_VFS_H */ /* Define if you have the header file. */ #ifdef __GNUC__ #define HAVE_UNISTD_H 1 #endif /* Define if you have the header file. */ /* #undef HAVE_UTIME_H */ /* Define if you have the header file. */ #define HAVE_WINDOWS_H 1 #define FSDB_DIR_SEPARATOR '\\' #define FSDB_DIR_SEPARATOR_S _T("\\") /* Define to 1 if `S_un' is a member of `struct in_addr'. */ #define HAVE_STRUCT_IN_ADDR_S_UN 1 #endif /* WINUAE_SYSCONFIG_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/sysdeps.h000066400000000000000000000367341504763705000237770ustar00rootroot00000000000000/* * UAE - The Un*x Amiga Emulator * * Try to include the right system headers and get other system-specific * stuff right & other collected kludges. * * If you think about modifying this, think twice. Some systems rely on * the exact order of the #include statements. That's also the reason * why everything gets included unconditionally regardless of whether * it's actually needed by the .c file. * * Copyright 1996, 1997 Bernd Schmidt */ #ifndef UAE_SYSDEPS_H #define UAE_SYSDEPS_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "sysconfig.h" #ifdef WINUAE_FOR_HATARI #include "compat.h" #ifdef __GNUC__ #define HAVE_VAR_ATTRIBUTE_UNUSED 1 #endif #if defined(_MSC_VER) #include "../includes/vs-fix.h" #endif #endif #ifndef UAE #define UAE #endif #ifdef __cplusplus #include using namespace std; #else #include #include #endif #include #include #include #include #include #ifndef UAE #define UAE #endif #if defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__aarch64__) #define CPU_arm 1 #define ARM_ASSEMBLY 1 #define CPU_64_BIT 1 #elif defined(__arm__) || defined(_M_ARM) #define CPU_arm 1 #define ARM_ASSEMBLY 1 #elif defined(__x86_64__) || defined(_M_AMD64) #define CPU_x86_64 1 #define CPU_64_BIT 1 #define X86_64_ASSEMBLY 1 #define SAHF_SETO_PROFITABLE #elif defined(__i386__) || defined(_M_IX86) #define CPU_i386 1 #define X86_ASSEMBLY 1 #define SAHF_SETO_PROFITABLE #elif defined(__powerpc__) || defined(_M_PPC) || defined(__ppc__) || defined(__ppc64__) #define CPU_powerpc 1 #elif defined(__mips__) || defined(mips) || defined(__mips64) #define CPU_mips 1 #elif defined(JIT) #error unrecognized CPU type #endif #ifdef _WIN32 /* Parameters are passed in ECX, EDX for both x86 and x86-64 (RCX, RDX). * For x86-64, __fastcall is the default, so it isn't really required. */ #define JITCALL __fastcall #elif defined(CPU_x86_64) /* Parameters are passed in RDI, RSI by default (System V AMD64 ABI). */ #define JITCALL #elif defined(HAVE_FUNC_ATTRIBUTE_REGPARM) /* Parameters are passed in EAX, EDX on x86 with regparm(2). */ #define JITCALL __attribute__((regparm(2))) /* This was originally regparm(3), but as far as I can see only two register * params are supported by the JIT code. It probably just worked anyway * if all functions used max two arguments. */ #elif !defined(JIT) #define JITCALL #endif #define REGPARAM #define REGPARAM2 JITCALL #define REGPARAM3 JITCALL #ifdef WINUAE_FOR_HATARI #include "uae/types.h" #else #include #endif #if CPU_64_BIT #define addrdiff(a, b) ((int)((a) - (b))) #else #define addrdiff(a, b) ((a) - (b)) #endif #ifndef __STDC__ #ifndef _MSC_VER #error "Your compiler is not ANSI. Get a real one." #endif #endif #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_VALUES_H #include #endif #ifdef HAVE_STRINGS_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_UTIME_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #if HAVE_DIRENT_H # include #else # define dirent direct # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif #ifdef HAVE_SYS_UTIME_H # include #endif #include #include #ifdef __NeXT__ #define S_IRUSR S_IREAD #define S_IWUSR S_IWRITE #define S_IXUSR S_IEXEC #define S_ISDIR(val) (S_IFDIR & val) struct utimbuf { time_t actime; time_t modtime; }; #endif #ifdef WINUAE_FOR_HATARI /* Types are provided by uae/types.h already */ #if SIZEOF_LONG_LONG == 8 #define VAL64(a) (a ## LL) #define UVAL64(a) (a ## uLL) #elif SIZEOF___INT64 == 8 #define VAL64(a) (a) #define UVAL64(a) (a) #elif SIZEOF_LONG == 8 #define VAL64(a) (a ## l) #define UVAL64(a) (a ## ul) #endif #define uae_s64 uae_s64 #define uae_u64 uae_u64 #else /* WINUAE_FOR_HATARI */ /* If char has more then 8 bits, good night. */ typedef unsigned char uae_u8; typedef signed char uae_s8; typedef char uae_char; typedef struct { uae_u8 RGB[3]; } RGB; #if SIZEOF_SHORT == 2 typedef unsigned short uae_u16; typedef short uae_s16; #elif SIZEOF_INT == 2 typedef unsigned int uae_u16; typedef int uae_s16; #else #error No 2 byte type, you lose. #endif #if SIZEOF_INT == 4 typedef unsigned int uae_u32; typedef int uae_s32; #elif SIZEOF_LONG == 4 typedef unsigned long uae_u32; typedef long uae_s32; #else #error No 4 byte type, you lose. #endif typedef uae_u32 uaecptr; #undef uae_s64 #undef uae_u64 #if SIZEOF_LONG_LONG == 8 #define uae_s64 long long #define uae_u64 unsigned long long #define VAL64(a) (a ## LL) #define UVAL64(a) (a ## uLL) #elif SIZEOF___INT64 == 8 #define uae_s64 __int64 #define uae_u64 unsigned __int64 #define VAL64(a) (a) #define UVAL64(a) (a) #elif SIZEOF_LONG == 8 #define uae_s64 long; #define uae_u64 unsigned long; #define VAL64(a) (a ## l) #define UVAL64(a) (a ## ul) #endif #endif /* WINUAE_FOR_HATARI */ uae_atomic atomic_and(volatile uae_atomic *p, uae_u32 v); uae_atomic atomic_or(volatile uae_atomic *p, uae_u32 v); uae_atomic atomic_inc(volatile uae_atomic *p); uae_atomic atomic_dec(volatile uae_atomic *p); uae_u32 atomic_bit_test_and_reset(volatile uae_atomic *p, uae_u32 v); #ifdef HAVE_STRDUP #define my_strdup _tcsdup #else extern TCHAR *my_strdup (const TCHAR*s); #endif extern TCHAR *my_strdup_ansi (const char*); extern void my_trim (TCHAR*); extern TCHAR *my_strdup_trim (const TCHAR*); extern TCHAR *au (const char*); extern char *ua (const TCHAR*); extern TCHAR *aucp (const char *s, unsigned int cp); extern char *uacp (const TCHAR *s, unsigned int cp); extern TCHAR *au_fs (const char*); extern char *ua_fs (const TCHAR*, int); extern char *ua_copy (char *dst, int maxlen, const TCHAR *src); extern TCHAR *au_copy (TCHAR *dst, int maxlen, const char *src); extern char *ua_fs_copy (char *dst, int maxlen, const TCHAR *src, int defchar); extern TCHAR *au_fs_copy (TCHAR *dst, int maxlen, const char *src); extern char *uutf8 (const TCHAR *s); extern TCHAR *utf8u (const char *s); extern void unicode_init (void); extern void to_lower (TCHAR *s, int len); extern void to_upper (TCHAR *s, int len); extern int uaestrlen(const char*); extern int uaetcslen(const TCHAR*); #define ENUMDECL typedef enum #define ENUMNAME(name) name /* * Porters to weird systems, look! This is the preferred way to get * filesys.c (and other stuff) running on your system. Define the * appropriate macros and implement wrappers in a machine-specific file. * * I guess the Mac port could use this (Ernesto?) */ #undef DONT_HAVE_POSIX #undef DONT_HAVE_REAL_POSIX /* define if open+delete doesn't do what it should */ #undef DONT_HAVE_STDIO #undef DONT_HAVE_MALLOC #if defined(WARPUP) #define DONT_HAVE_POSIX #endif #if !defined(FSUAE) && defined _WIN32 //#ifdef FSUAE //#error _WIN32 should not be defined here //#endif #if defined __WATCOMC__ #define O_NDELAY 0 #include #define dirent direct #define mkdir(a,b) mkdir(a) #define strcasecmp stricmp #elif defined __MINGW32__ #include #define O_NDELAY 0 #define FILEFLAG_DIR 0x1 #define FILEFLAG_ARCHIVE 0x2 #define FILEFLAG_WRITE 0x4 #define FILEFLAG_READ 0x8 #define FILEFLAG_EXECUTE 0x10 #define FILEFLAG_SCRIPT 0x20 #define FILEFLAG_PURE 0x40 #define mkdir(a,b) mkdir(a) #elif defined _MSC_VER #ifdef HAVE_GETTIMEOFDAY #include // for 'struct timeval' definition extern void gettimeofday( struct timeval *tv, void *blah ); #endif #define O_NDELAY 0 #define FILEFLAG_DIR 0x1 #define FILEFLAG_ARCHIVE 0x2 #define FILEFLAG_WRITE 0x4 #define FILEFLAG_READ 0x8 #define FILEFLAG_EXECUTE 0x10 #define FILEFLAG_SCRIPT 0x20 #define FILEFLAG_PURE 0x40 #include #define O_BINARY _O_BINARY #define O_WRONLY _O_WRONLY #define O_RDONLY _O_RDONLY #define O_RDWR _O_RDWR #define O_CREAT _O_CREAT #define O_TRUNC _O_TRUNC #ifndef WINUAE_FOR_HATARI #define strcasecmp _tcsicmp #define strncasecmp _tcsncicmp #define W_OK 0x2 #define R_OK 0x4 #define STAT struct stat #define DIR struct DIR struct direct { TCHAR d_name[1]; }; #endif /* WINUAE_FOR_HATARI */ #include #define utimbuf __utimbuf64 #define USE_ZFILE #undef S_ISDIR #undef S_IWUSR #undef S_IRUSR #undef S_IXUSR #define S_ISDIR(a) (a&FILEFLAG_DIR) #define S_ISARC(a) (a&FILEFLAG_ARCHIVE) #define S_IWUSR FILEFLAG_WRITE #define S_IRUSR FILEFLAG_READ #define S_IXUSR FILEFLAG_EXECUTE #endif #endif /* _WIN32 */ #ifdef DONT_HAVE_POSIX #define access posixemu_access extern int posixemu_access (const TCHAR *, int); #define open posixemu_open extern int posixemu_open (const TCHAR *, int, int); #define close posixemu_close extern void posixemu_close (int); #define read posixemu_read extern int posixemu_read (int, TCHAR *, int); #define write posixemu_write extern int posixemu_write (int, const TCHAR *, int); #undef lseek #define lseek posixemu_seek extern int posixemu_seek (int, int, int); #define stat(a,b) posixemu_stat ((a), (b)) extern int posixemu_stat (const TCHAR *, STAT *); #define mkdir posixemu_mkdir extern int mkdir (const TCHAR *, int); #define rmdir posixemu_rmdir extern int posixemu_rmdir (const TCHAR *); #define unlink posixemu_unlink extern int posixemu_unlink (const TCHAR *); #define truncate posixemu_truncate extern int posixemu_truncate (const TCHAR *, long int); #define rename posixemu_rename extern int posixemu_rename (const TCHAR *, const TCHAR *); #define chmod posixemu_chmod extern int posixemu_chmod (const TCHAR *, int); #define tmpnam posixemu_tmpnam extern void posixemu_tmpnam (TCHAR *); #define utime posixemu_utime extern int posixemu_utime (const TCHAR *, struct utimbuf *); #define opendir posixemu_opendir extern DIR * posixemu_opendir (const TCHAR *); #define readdir posixemu_readdir extern struct dirent* readdir (DIR *); #define closedir posixemu_closedir extern void closedir (DIR *); /* This isn't the best place for this, but it fits reasonably well. The logic * is that you probably don't have POSIX errnos if you don't have the above * functions. */ extern long dos_errno (void); #endif #ifdef DONT_HAVE_STDIO extern FILE *stdioemu_fopen (const TCHAR *, const TCHAR *); #define fopen(a,b) stdioemu_fopen(a, b) extern int stdioemu_fseek (FILE *, int, int); #define fseek(a,b,c) stdioemu_fseek(a, b, c) extern int stdioemu_fread (TCHAR *, int, int, FILE *); #define fread(a,b,c,d) stdioemu_fread(a, b, c, d) extern int stdioemu_fwrite (const TCHAR *, int, int, FILE *); #define fwrite(a,b,c,d) stdioemu_fwrite(a, b, c, d) extern int stdioemu_ftell (FILE *); #define ftell(a) stdioemu_ftell(a) extern int stdioemu_fclose (FILE *); #define fclose(a) stdioemu_fclose(a) #endif #ifdef DONT_HAVE_MALLOC #define malloc(a) mallocemu_malloc(a) extern void *mallocemu_malloc (int size); #define free(a) mallocemu_free(a) extern void mallocemu_free (void *ptr); #endif #ifdef X86_ASSEMBLY //#define ASM_SYM_FOR_FUNC(a) __asm__(a) #define ASM_SYM_FOR_FUNC(a) #else #define ASM_SYM_FOR_FUNC(a) #endif //#include "target.h" #ifdef UAE_CONSOLE #undef write_log #define write_log write_log_standard #endif #ifdef WINUAE_FOR_HATARI #define write_log(...) Log_Printf(LOG_DEBUG, __VA_ARGS__) #elif __GNUC__ - 1 > 1 || __GNUC_MINOR__ - 1 > 6 extern void write_log(const TCHAR *, ...); extern void write_logx(const TCHAR *, ...); extern void write_log(const char *, ...) __attribute__ ((format (printf, 1, 2))); #else extern void write_log(const TCHAR *, ...); extern void write_logx(const TCHAR *, ...); extern void write_log(const char *, ...); #endif extern void write_dlog (const TCHAR *, ...); extern int read_log(void); extern void flush_log (void); extern TCHAR *setconsolemode (TCHAR *buffer, int maxlen); extern void close_console (void); extern void open_console(void); extern void reopen_console(void); extern void activate_console(void); extern void deactivate_console(void); //extern void console_out (const TCHAR *); //extern void console_out_f (const TCHAR *, ...); extern void console_flush (void); extern int console_get (TCHAR *, int); extern bool console_isch (void); extern TCHAR console_getch (void); extern void f_out (void *, const TCHAR *, ...); extern TCHAR* buf_out (TCHAR *buffer, int *bufsize, const TCHAR *format, ...); //extern void gui_message (const TCHAR *,...); extern int gui_message_multibutton (int flags, const TCHAR *format,...); #define write_log_err write_log extern void logging_init (void); extern FILE *log_open (const TCHAR *name, int append, int bootlog, TCHAR*); extern void log_close (FILE *f); extern TCHAR *write_log_get_ts(void); extern bool is_console_open(void); extern bool use_long_double; #ifndef O_BINARY #define O_BINARY 0 #endif #ifndef STATIC_INLINE #if __GNUC__ - 1 > 2 || (__GNUC__ - 1 == 2 && __GNUC_MINOR__ - 1 >= 0) #define STATIC_INLINE static __inline__ __attribute__ ((always_inline)) #define NOINLINE __attribute__ ((noinline)) #define NORETURN __attribute__ ((noreturn)) #elif _MSC_VER #define STATIC_INLINE static __forceinline #define NOINLINE __declspec(noinline) #define NORETURN __declspec(noreturn) #else #define STATIC_INLINE static __inline__ #define NOINLINE #define NORETURN #endif #endif /* Every Amiga hardware clock cycle takes this many "virtual" cycles. This used to be hardcoded as 1, but using higher values allows us to time some stuff more precisely. 512 is the official value from now on - it can't change, unless we want _another_ config option "finegrain2_m68k_speed". We define this value here rather than in events.h so that gencpu.c sees it. */ #define CYCLE_UNIT 512 /* This one is used by cfgfile.c. We could reduce the CYCLE_UNIT back to 1, I'm not 100% sure this code is bug free yet. */ #define OFFICIAL_CYCLE_UNIT 512 /* * You can specify numbers from 0 to 5 here. It is possible that higher * numbers will make the CPU emulation slightly faster, but if the setting * is too high, you will run out of memory while compiling. * Best to leave this as it is. */ #define CPU_EMU_SIZE 0 /* * Byte-swapping functions */ /* Try to use system bswap_16/bswap_32 functions. */ #if defined HAVE_BSWAP_16 && defined HAVE_BSWAP_32 # ifdef HAVE_BYTESWAP_H # include # endif #else /* Else, if using SDL, try SDL's endian functions. */ # ifdef USE_SDL # include # ifndef bswap_16 # define bswap_16(x) SDL_Swap16(x) # endif # ifndef bswap_32 # define bswap_32(x) SDL_Swap32(x) # endif # else /* Otherwise, we'll roll our own. */ # ifndef bswap_16 # define bswap_16(x) (((x) >> 8) | (((x) & 0xFF) << 8)) # endif # ifndef bswap_32 # define bswap_32(x) (((x) << 24) | (((x) << 8) & 0x00FF0000) | (((x) >> 8) & 0x0000FF00) | ((x) >> 24)) # endif # endif #endif #ifndef __cplusplus #define xmalloc(T, N) malloc(sizeof (T) * (N)) #define xcalloc(T, N) calloc(sizeof (T), N) #define xfree(T) free(T) #define xrealloc(T, TP, N) realloc(TP, sizeof (T) * (N)) #if 0 extern void *xmalloc (size_t); extern void *xcalloc (size_t, size_t); extern void xfree (const void*); #endif #else #define xmalloc(T, N) static_cast(malloc (sizeof (T) * (N))) #define xcalloc(T, N) static_cast(calloc (sizeof (T), N)) #define xrealloc(T, TP, N) static_cast(realloc (TP, sizeof (T) * (N))) #define xfree(T) free(T) #endif #define DBLEQU(f, i) (abs ((f) - (i)) < 0.000001) #ifdef HAVE_VAR_ATTRIBUTE_UNUSED #define NOWARN_UNUSED(x) __attribute__((unused)) x #else #define NOWARN_UNUSED(x) x #endif #endif /* UAE_SYSDEPS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/table68k000066400000000000000000000456551504763705000235010ustar00rootroot00000000000000% 0: bit 0 % 1: bit 1 % c: condition code % C: condition codes, except F % f: direction % i: immediate % E: immediate, except 00 (Aranym only, EmulOp instruction) % I: immediate, except 00 and ff % j: immediate 1..8 % J: immediate 0..15 % k: immediate 0..7 % K: immediate 0..63 % p: immediate 0..3 (CINV and CPUSH: cache field) % s: source mode % S: source reg % d: dest mode % D: dest reg % r: reg % z: size % % Actually, a sssSSS may appear as a destination, and % vice versa. The only difference between sssSSS and % dddDDD are the valid addressing modes. There is % no match for immediate and pc-rel. addressing modes % in case of dddDDD. % % Arp: --> -(Ar) % ArP: --> (Ar)+ % Ara: --> (Ar) % L: (xxx.L) % % Fields on a line: % 16 chars bitpattern : % CPU level / privilege level : % CPU level 0: 68000 % 1: 68010 % 2: 68020 % 3: 68030 % 4: 68040 % 5: 68060 % [Everything from 68020 possibly allows for FPU emulation] % Unimplemented after: % 0: Normal % 3: Not implemented in 68030 and later % 4: Not implemented in 68040 and later % 5: Not implemented in 68060 % privilege level 0: not privileged % 1: unprivileged only on 68000 (check regs.s) % 2: privileged (check regs.s) % 3: privileged if size == word (check regs.s) % Flags set by instruction: XNZVC : % Flags used by instruction: XNZVC : % - means flag unaffected / unused % 0 means flag reset % 1 means flag set % ? means programmer was too lazy to check or instruction may trap % + means instruction is conditional branch (ignored, only for sync) % / means instruction is unconditional branch/call (ignored, only for sync) % x means flag is unknown and well-behaved programs shouldn't check it % everything else means flag set/used % % Control flow % two letters, combination of % - nothing % T the instruction may trap or cause an exception % B branch instruction % J jump instruction % R return instruction % % srcaddr status destaddr status : % bitmasks of % 1 means fetched % 2 means stored % 4 means jump offset % 8 means jump address % % instruction % optional line feed and 68030 Head/Tail/Cycles/ea calculation % 0000 0000 0011 1100:000:XNZVC:XNZVC:--:10: ORSR.B #1 0000 0000 0111 1100:002:XNZVC:XNZVC:T-:10: ORSR.W #1 0000 0zz0 11ss sSSS:250:-?Z?C:-----:T-:11: CHK2.z #1,s[!Dreg,Areg,Aipi,Apdi,Immd] 0000 0000 zzdd dDDD:000:-NZ00:-----:--:13: OR.z #z,d[Dreg] - 2 0 2 fiea 0000 0000 zzdd dDDD:000:-NZ00:-----:--:13: OR.z #z,d[!Areg,Dreg] - 0 1 3 fiea 0000 0010 0011 1100:000:XNZVC:XNZVC:--:10: ANDSR.B #1 0000 0010 0111 1100:002:XNZVC:XNZVC:T-:10: ANDSR.W #1 0000 0010 zzdd dDDD:000:-NZ00:-----:--:13: AND.z #z,d[Dreg] - 2 0 2 fiea 0000 0010 zzdd dDDD:000:-NZ00:-----:--:13: AND.z #z,d[!Areg,Dreg] - 0 1 3 fiea 0000 0100 zzdd dDDD:000:XNZVC:-----:--:13: SUB.z #z,d[Dreg] - 2 0 2 fiea 0000 0100 zzdd dDDD:000:XNZVC:-----:--:13: SUB.z #z,d[!Areg,Dreg] - 0 1 3 fiea 0000 0110 zzdd dDDD:000:XNZVC:-----:--:13: ADD.z #z,d[Dreg] - 2 0 2 fiea 0000 0110 zzdd dDDD:000:XNZVC:-----:--:13: ADD.z #z,d[!Areg,Dreg] - 0 1 3 fiea 0000 0110 11ss sSSS:230:-----:XNZVC:--:10: CALLM s[!Dreg,Areg,Aipi,Apdi,Immd] 0000 0110 11ss sSSS:230:XNZVC:-----:-R:10: RTM s[Dreg,Areg] 0000 1000 00ss sSSS:000:--Z--:-----:--:11: BTST #1,s[Dreg] - 4 0 4 0000 1000 00ss sSSS:000:--Z--:-----:--:11: BTST #1,s[!Areg,Dreg,Immd] - 0 0 4 fiea 0000 1000 01ss sSSS:000:--Z--:-----:--:13: BCHG #1,s[Dreg] - 6 0 6 0000 1000 01ss sSSS:000:--Z--:-----:--:13: BCHG #1,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 6 fiea 0000 1000 10ss sSSS:000:--Z--:-----:--:13: BCLR #1,s[Dreg] - 6 0 6 0000 1000 10ss sSSS:000:--Z--:-----:--:13: BCLR #1,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 6 fiea 0000 1000 11ss sSSS:000:--Z--:-----:--:13: BSET #1,s[Dreg] - 6 0 6 0000 1000 11ss sSSS:000:--Z--:-----:--:13: BSET #1,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 6 fiea 0000 1010 0011 1100:000:XNZVC:XNZVC:--:10: EORSR.B #1 0000 1010 0111 1100:002:XNZVC:XNZVC:T-:10: EORSR.W #1 0000 1010 zzdd dDDD:000:-NZ00:-----:--:13: EOR.z #z,d[Dreg] - 2 0 2 fiea 0000 1010 zzdd dDDD:000:-NZ00:-----:--:13: EOR.z #z,d[!Areg,Dreg] - 0 1 3 fiea 0000 1100 zzss sSSS:000:-NZVC:-----:--:11: CMP.z #z,s[Dreg] - 2 0 2 fiea 0000 1100 zzss sSSS:000:-NZVC:-----:--:11: CMP.z #z,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 2 fiea 0000 1100 zzss sSSS:200:-NZVC:-----:--:11: CMP.z #z,s[PC8r,PC16] - 0 0 2 fiea 0000 1010 11ss sSSS:200:-NZVC:-----:--:13: CAS.B #1,s[!Dreg,Areg,Immd,PC8r,PC16] 0000 1100 11ss sSSS:200:-NZVC:-----:--:13: CAS.W #1,s[!Dreg,Areg,Immd,PC8r,PC16] 0000 1100 1111 1100:250:-NZVC:-----:--:10: CAS2.W #2 0000 1110 zzss sSSS:102:-----:-----:T-:13: MOVES.z #1,s[!Dreg,Areg,Immd,PC8r,PC16] 0000 1110 11ss sSSS:200:-NZVC:-----:--:13: CAS.L #1,s[!Dreg,Areg,Immd,PC8r,PC16] 0000 1110 1111 1100:250:-NZVC:-----:--:10: CAS2.L #2 0000 rrr1 00dd dDDD:050:-----:-----:--:12: MVPMR.W d[Areg-Ad16],Dr 0000 rrr1 01dd dDDD:050:-----:-----:--:12: MVPMR.L d[Areg-Ad16],Dr 0000 rrr1 10dd dDDD:050:-----:-----:--:12: MVPRM.W Dr,d[Areg-Ad16] 0000 rrr1 11dd dDDD:050:-----:-----:--:12: MVPRM.L Dr,d[Areg-Ad16] 0000 rrr1 00ss sSSS:000:--Z--:-----:--:11: BTST Dr,s[Dreg] - 4 0 4 0000 rrr1 00ss sSSS:000:--Z--:-----:--:11: BTST Dr,s[!Areg,Dreg] - 0 0 4 fea 0000 rrr1 01ss sSSS:000:--Z--:-----:--:13: BCHG Dr,s[Dreg] - 6 0 6 0000 rrr1 01ss sSSS:000:--Z--:-----:--:13: BCHG Dr,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 6 fea 0000 rrr1 10ss sSSS:000:--Z--:-----:--:13: BCLR Dr,s[Dreg] - 6 0 6 0000 rrr1 10ss sSSS:000:--Z--:-----:--:13: BCLR Dr,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 6 fea 0000 rrr1 11ss sSSS:000:--Z--:-----:--:13: BSET Dr,s[Dreg] - 6 0 6 0000 rrr1 11ss sSSS:000:--Z--:-----:--:13: BSET Dr,s[!Areg,Dreg,Immd,PC8r,PC16] - 0 0 6 fea % Move cycles are special cased in gencpu.c 0001 DDDd ddss sSSS:000:-NZ00:-----:--:12: MOVE.B s,d[!Areg] 0011 DDDd ddss sSSS:000:-----:-----:--:12: MOVEA.W s,d[Areg] 0011 DDDd ddss sSSS:000:-NZ00:-----:--:12: MOVE.W s,d[!Areg] 0010 DDDd ddss sSSS:000:-----:-----:--:12: MOVEA.L s,d[Areg] 0010 DDDd ddss sSSS:000:-NZ00:-----:--:12: MOVE.L s,d[!Areg] 0100 0000 zzdd dDDD:000:XNZVC:X-Z--:--:30: NEGX.z d[Dreg] - 2 0 2 0100 0000 zzdd dDDD:000:XNZVC:X-Z--:--:30: NEGX.z d[!Areg,Dreg] - 0 1 3 fea 0100 0000 11dd dDDD:001:-----:XNZVC:T-:10: MVSR2.W d[Dreg] - 2 0 4 0100 0000 11dd dDDD:001:-----:XNZVC:T-:10: MVSR2.W d[!Areg,Dreg] - 2 0 4 cea 0100 0010 zzdd dDDD:000:-0100:-----:--:20: CLR.z d[Dreg] - 2 0 2 0100 0010 zzdd dDDD:000:-0100:-----:--:20: CLR.z d[!Areg,Dreg] - 0 1 3 cea 0100 0010 11dd dDDD:100:-----:XNZVC:--:10: MVSR2.B d[Dreg] - 2 0 4 0100 0010 11dd dDDD:100:-----:XNZVC:--:10: MVSR2.B d[!Areg,Dreg] - 2 0 4 cea 0100 0100 zzdd dDDD:000:XNZVC:-----:--:30: NEG.z d[Dreg] - 2 0 2 0100 0100 zzdd dDDD:000:XNZVC:-----:--:30: NEG.z d[!Areg,Dreg] - 0 1 3 fea 0100 0100 11ss sSSS:000:XNZVC:-----:--:10: MV2SR.B s[Dreg] - 4 0 4 0100 0100 11ss sSSS:000:XNZVC:-----:--:10: MV2SR.B s[!Areg,Dreg] - 0 0 4 fea 0100 0110 zzdd dDDD:000:-NZ00:-----:--:30: NOT.z d[Dreg] - 2 0 2 0100 0110 zzdd dDDD:000:-NZ00:-----:--:30: NOT.z d[!Areg,Dreg] - 0 1 3 fea 0100 0110 11ss sSSS:002:XNZVC:XNZVC:T-:10: MV2SR.W s[!Areg] - 0 0 8 fea 0100 1000 0000 1rrr:200:-----:-----:--:31: LINK.L Ar,#2 - 2 0 6 0100 1000 00dd dDDD:000:X?Z?C:X-Z--:--:30: NBCD.B d[!Areg] - 0 0 6 0100 1000 0100 1kkk:100:-----:-----:T-:10: BKPT #k 0100 1000 01ss sSSS:000:-NZ00:-----:--:30: SWAP.W s[Dreg] - 4 0 4 0100 1000 01ss sSSS:000:-----:-----:--:00: PEA.L s[!Dreg,Areg,Aipi,Apdi,Immd] - 0 2 4 cea 0100 1000 10dd dDDD:000:-NZ00:-----:--:30: EXT.W d[Dreg] - 4 0 4 0100 1000 10dd dDDD:000:-----:-----:--:02: MVMLE.W #1,d[!Dreg,Areg,Aipi] 0100 1000 11dd dDDD:000:-NZ00:-----:--:30: EXT.L d[Dreg] - 4 0 4 0100 1000 11dd dDDD:000:-----:-----:--:02: MVMLE.L #1,d[!Dreg,Areg,Aipi] 0100 1001 11dd dDDD:200:-NZ00:-----:--:30: EXT.B d[Dreg] - 4 0 4 0100 1010 zzss sSSS:000:-NZ00:-----:--:10: TST.z s[Dreg] - 0 0 2 0100 1010 zzss sSSS:000:-NZ00:-----:--:10: TST.z s[!Areg,Dreg,PC16,PC8r,Immd] - 0 0 2 fea 0100 1010 zzss sSSS:200:-NZ00:-----:--:10: TST.z s[Areg,PC16,PC8r,Immd] - 0 0 2 fea 0100 1010 11dd dDDD:000:-NZ00:-----:--:30: TAS.B d[Dreg] - 0 0 2 0100 1010 11dd dDDD:000:-NZ00:-----:--:30: TAS.B d[!Areg,Dreg] - 0 0 2 fea 0100 1010 1111 1100:000:-----:-----:T-:00: ILLEGAL 0100 1100 00ss sSSS:200:-NZVC:-----:--:13: MULL.L #1,s[!Areg] - 2 0 30 fiea 0100 1100 01ss sSSS:200:-NZV0:-----:T-:13: DIVL.L #1,s[!Areg] - 0 0 50 fiea 0100 1100 10ss sSSS:000:-----:-----:--:01: MVMEL.W #1,s[!Dreg,Areg,Apdi,Immd] 0100 1100 11ss sSSS:000:-----:-----:--:01: MVMEL.L #1,s[!Dreg,Areg,Apdi,Immd] 0100 1110 0100 JJJJ:000:-----:XNZVC:--:10: TRAP #J 0100 1110 0101 0rrr:000:-----:-----:--:31: LINK.W Ar,#1 - 0 0 4 0100 1110 0101 1rrr:000:-----:-----:--:30: UNLK.L Ar - 0 0 5 0100 1110 0110 0rrr:002:-----:-----:T-:10: MVR2USP.L Ar - 4 0 4 0100 1110 0110 1rrr:002:-----:-----:T-:20: MVUSP2R.L Ar - 4 0 4 0100 1110 0111 0000:002:-----:-----:T-:00: RESET - 0 0 518 0100 1110 0111 0001:000:-----:-----:--:00: NOP - 0 0 2 0100 1110 0111 0010:002:XNZVC:-----:T-:10: STOP #1 - 0 0 8 0100 1110 0111 0011:002:XNZVC:-----:TR:00: RTE - 1 9 18 0100 1110 0111 0100:100:-----:-----:-R:10: RTD #1 - 2 0 10 0100 1110 0111 0101:000:-----:-----:-R:00: RTS - 1 0 9 0100 1110 0111 0110:000:-----:XNZVC:T-:00: TRAPV 0100 1110 0111 0111:000:XNZVC:-----:-R:00: RTR - 1 0 12 0100 1110 0111 1010:100:-----:-----:T-:10: MOVEC2 #1 - 6 0 6 0100 1110 0111 1011:100:-----:-----:T-:10: MOVE2C #1 - 6 0 6 0100 1110 10ss sSSS:000://///://///:-J:80: JSR.L s[!Dreg,Areg,Aipi,Apdi,Immd] - 0 0 4 jea 0100 rrr1 00ss sSSS:200:-N???:-----:T-:11: CHK.L s[!Areg],Dr 0100 rrr1 10ss sSSS:000:-N???:-----:T-:11: CHK.W s[!Areg],Dr 0100 1110 11ss sSSS:000://///://///:-J:80: JMP.L s[!Dreg,Areg,Aipi,Apdi,Immd] - 4 0 4 jea 0100 rrr1 11ss sSSS:000:-----:-----:--:02: LEA.L s[!Dreg,Areg,Aipi,Apdi,Immd],Ar - 2 0 2 cea % This variant of ADDQ is word and long sized only 0101 jjj0 01dd dDDD:000:-----:-----:--:13: ADDA.W #j,d[Areg] - 2 0 2 0101 jjj0 10dd dDDD:000:-----:-----:--:13: ADDA.L #j,d[Areg] - 2 0 2 0101 jjj0 zzdd dDDD:000:XNZVC:-----:--:13: ADD.z #j,d[Dreg] - 2 0 2 0101 jjj0 zzdd dDDD:000:XNZVC:-----:--:13: ADD.z #j,d[!Areg,Dreg] - 0 1 3 fea % This variant of SUBQ is word and long sized only 0101 jjj1 01dd dDDD:000:-----:-----:--:13: SUBA.W #j,d[Areg] - 2 0 2 0101 jjj1 10dd dDDD:000:-----:-----:--:13: SUBA.L #j,d[Areg] - 2 0 2 0101 jjj1 zzdd dDDD:000:XNZVC:-----:--:13: SUB.z #j,d[Dreg] - 2 0 2 0101 jjj1 zzdd dDDD:000:XNZVC:-----:--:13: SUB.z #j,d[!Areg,Dreg] - 0 1 3 fea 0101 cccc 1100 1rrr:000:-----:-++++:-B:31: DBcc.W Dr,#1 - -1 0 0 0101 cccc 11dd dDDD:000:-----:-++++:--:20: Scc.B d[Dreg] - 0 0 2 0101 cccc 11dd dDDD:000:-----:-++++:--:20: Scc.B d[!Areg,Dreg] - 0 0 2 cea 0101 cccc 1111 1010:200:-----:-????:T-:10: TRAPcc #1 0101 cccc 1111 1011:200:-----:-????:T-:10: TRAPcc #2 0101 cccc 1111 1100:200:-----:-????:T-:00: TRAPcc % Bxx.L is 68020 only, but setting the CPU level to 2 would give illegal % instruction exceptions when compiling a 68000 only emulation, which isn't % what we want either. 0110 0001 0000 0000:000://///://///:-B:40: BSR.W #1 - 2 0 6 0110 0001 IIII IIII:000://///://///:-B:40: BSR.B #i - 2 0 6 0110 0001 1111 1111:000://///://///:-B:40: BSR.L #2 - 2 0 6 0110 CCCC 0000 0000:000:-----:-++++:-B:40: Bcc.W #1 - -1 0 0 0110 CCCC IIII IIII:000:-----:-++++:-B:40: Bcc.B #i - -1 0 0 0110 CCCC 1111 1111:000:-----:-++++:-B:40: Bcc.L #2 - -1 0 0 0111 rrr0 iiii iiii:000:-NZ00:-----:--:12: MOVE.L #i,Dr 1000 rrr0 zzss sSSS:000:-NZ00:-----:--:13: OR.z s[Dreg],Dr - 2 0 2 1000 rrr0 zzss sSSS:000:-NZ00:-----:--:13: OR.z s[!Areg,Dreg],Dr - 0 0 2 fea 1000 rrr0 11ss sSSS:000:-NZV0:-----:T-:13: DIVU.W s[Dreg],Dr - 2 0 20 1000 rrr0 11ss sSSS:000:-NZV0:-----:T-:13: DIVU.W s[!Areg,Dreg],Dr - 0 0 20 fea 1000 rrr1 00dd dDDD:000:X?Z?C:X-Z--:--:13: SBCD.B d[Dreg],Dr - 0 0 4 1000 rrr1 00dd dDDD:000:X?Z?C:X-Z--:--:13: SBCD.B d[Areg-Apdi],Arp - 2 1 13 1000 rrr1 zzdd dDDD:000:-NZ00:-----:--:13: OR.z Dr,d[!Areg,Dreg] - 0 1 3 fea 1000 rrr1 01dd dDDD:200:-----:-----:--:12: PACK d[Dreg],Dr - 6 0 6 1000 rrr1 01dd dDDD:200:-----:-----:--:12: PACK d[Areg-Apdi],Arp - 2 1 11 1000 rrr1 10dd dDDD:200:-----:-----:--:12: UNPK d[Dreg],Dr - 8 0 8 1000 rrr1 10dd dDDD:200:-----:-----:--:12: UNPK d[Areg-Apdi],Arp - 2 1 11 1000 rrr1 11ss sSSS:000:-NZV0:-----:T-:13: DIVS.W s[Dreg],Dr - 2 0 20 1000 rrr1 11ss sSSS:000:-NZV0:-----:T-:13: DIVS.W s[!Areg,Dreg],Dr - 0 0 20 fea 1001 rrr0 zzss sSSS:000:XNZVC:-----:--:13: SUB.z s[Areg,Dreg],Dr - 2 0 2 1001 rrr0 zzss sSSS:000:XNZVC:-----:--:13: SUB.z s[!Areg,Dreg],Dr - 0 0 2 fea 1001 rrr0 11ss sSSS:000:-----:-----:--:13: SUBA.W s[Areg,Dreg],Ar - 4 0 4 1001 rrr0 11ss sSSS:000:-----:-----:--:13: SUBA.W s[!Areg,Dreg],Ar - 0 0 4 fea 1001 rrr1 zzdd dDDD:000:XNZVC:X-Z--:--:13: SUBX.z d[Dreg],Dr - 2 0 2 1001 rrr1 zzdd dDDD:000:XNZVC:X-Z--:--:13: SUBX.z d[Areg-Apdi],Arp - 2 1 9 1001 rrr1 zzdd dDDD:000:XNZVC:-----:--:13: SUB.z Dr,d[!Areg,Dreg] - 0 1 3 fea 1001 rrr1 11ss sSSS:000:-----:-----:--:13: SUBA.L s[Areg,Dreg],Ar - 2 0 2 1001 rrr1 11ss sSSS:000:-----:-----:--:13: SUBA.L s[!Areg,Dreg],Ar - 0 0 2 fea 1011 rrr0 zzss sSSS:000:-NZVC:-----:--:11: CMP.z s[Areg,Dreg],Dr - 2 0 2 1011 rrr0 zzss sSSS:000:-NZVC:-----:--:11: CMP.z s[!Areg,Dreg],Dr - 0 0 2 fea 1011 rrr0 11ss sSSS:000:-NZVC:-----:--:11: CMPA.W s[Areg,Dreg],Ar - 4 0 4 1011 rrr0 11ss sSSS:000:-NZVC:-----:--:11: CMPA.W s[!Areg,Dreg],Ar - 0 0 4 fea 1011 rrr1 11ss sSSS:000:-NZVC:-----:--:11: CMPA.L s[Areg,Dreg],Ar - 4 0 4 1011 rrr1 11ss sSSS:000:-NZVC:-----:--:11: CMPA.L s[!Areg,Dreg],Ar - 0 0 4 fea 1011 rrr1 zzdd dDDD:000:-NZVC:-----:--:11: CMPM.z d[Areg-Aipi],ArP - 0 0 8 1011 rrr1 zzdd dDDD:000:-NZ00:-----:--:13: EOR.z Dr,d[Dreg] - 2 0 2 1011 rrr1 zzdd dDDD:000:-NZ00:-----:--:13: EOR.z Dr,d[!Areg,Dreg] - 0 1 3 fea 1100 rrr0 zzss sSSS:000:-NZ00:-----:--:13: AND.z s[Dreg],Dr - 2 0 2 fea 1100 rrr0 zzss sSSS:000:-NZ00:-----:--:13: AND.z s[!Areg,Dreg],Dr - 0 1 3 fea 1100 rrr0 11ss sSSS:000:-NZ00:-----:--:13: MULU.W s[!Areg],Dr - 2 0 12 fea 1100 rrr1 00dd dDDD:000:X?Z?C:X-Z--:--:13: ABCD.B d[Dreg],Dr - 0 0 4 1100 rrr1 00dd dDDD:000:X?Z?C:X-Z--:--:13: ABCD.B d[Areg-Apdi],Arp - 2 1 13 1100 rrr1 zzdd dDDD:000:-NZ00:-----:--:13: AND.z Dr,d[!Areg,Dreg] - 0 1 3 fea 1100 rrr1 01dd dDDD:000:-----:-----:--:33: EXG.L Dr,d[Dreg] - 4 0 4 1100 rrr1 01dd dDDD:000:-----:-----:--:33: EXG.L Ar,d[Areg] - 4 0 4 1100 rrr1 10dd dDDD:000:-----:-----:--:33: EXG.L Dr,d[Areg] - 4 0 4 1100 rrr1 11ss sSSS:000:-NZ00:-----:--:13: MULS.W s[!Areg],Dr - 2 0 12 fea 1101 rrr0 zzss sSSS:000:XNZVC:-----:--:13: ADD.z s[Areg,Dreg],Dr - 2 0 2 1101 rrr0 zzss sSSS:000:XNZVC:-----:--:13: ADD.z s[!Areg,Dreg],Dr - 0 0 2 fea 1101 rrr0 11ss sSSS:000:-----:-----:--:13: ADDA.W s[Areg,Dreg],Ar - 0 0 4 1101 rrr0 11ss sSSS:000:-----:-----:--:13: ADDA.W s[!Areg,Dreg],Ar - 4 0 4 fea 1101 rrr1 zzdd dDDD:000:XNZVC:X-Z--:--:13: ADDX.z d[Dreg],Dr - 2 0 2 1101 rrr1 zzdd dDDD:000:XNZVC:X-Z--:--:13: ADDX.z d[Areg-Apdi],Arp - 2 1 9 1101 rrr1 zzdd dDDD:000:XNZVC:-----:--:13: ADD.z Dr,d[!Areg,Dreg] - 0 1 3 fea 1101 rrr1 11ss sSSS:000:-----:-----:--:13: ADDA.L s[Areg,Dreg],Ar - 2 0 2 1101 rrr1 11ss sSSS:000:-----:-----:--:13: ADDA.L s[!Areg,Dreg],Ar - 0 0 2 fea 1110 jjjf zz00 0RRR:000:XNZVC:-----:--:13: ASf.z #j,DR - 2 0 6 1110 jjjf zz00 1RRR:000:XNZ0C:-----:--:13: LSf.z #j,DR - 4 0 4 1110 jjjf zz01 0RRR:000:XNZ0C:X----:--:13: ROXf.z #j,DR - 10 0 12 1110 jjjf zz01 1RRR:000:-NZ0C:-----:--:13: ROf.z #j,DR - 4 0 6 1110 rrrf zz10 0RRR:000:XNZVC:X----:--:13: ASf.z Dr,DR - 4 0 6 1110 rrrf zz10 1RRR:000:XNZ0C:X----:--:13: LSf.z Dr,DR - 6 0 6 1110 rrrf zz11 0RRR:000:XNZ0C:X----:--:13: ROXf.z Dr,DR - 10 0 12 1110 rrrf zz11 1RRR:000:-NZ0C:-----:--:13: ROf.z Dr,DR - 6 0 8 1110 000f 11dd dDDD:000:XNZVC:-----:--:13: ASfW.W d[!Dreg,Areg] - 0 0 4 fea 1110 001f 11dd dDDD:000:XNZ0C:-----:--:13: LSfW.W d[!Dreg,Areg] - 0 0 4 fea 1110 010f 11dd dDDD:000:XNZ0C:X----:--:13: ROXfW.W d[!Dreg,Areg] - 0 0 4 fea 1110 011f 11dd dDDD:000:-NZ0C:-----:--:13: ROfW.W d[!Dreg,Areg] - 0 0 6 fea 1110 1000 11ss sSSS:200:-NZ00:-----:--:11: BFTST #1,s[!Areg,Apdi,Aipi,Immd] 1110 1001 11ss sSSS:200:-NZ00:-----:--:11: BFEXTU #1,s[!Areg,Apdi,Aipi,Immd] 1110 1010 11ss sSSS:200:-NZ00:-----:--:13: BFCHG #1,s[!Areg,Apdi,Aipi,Immd,PC8r,PC16] 1110 1011 11ss sSSS:200:-NZ00:-----:--:11: BFEXTS #1,s[!Areg,Apdi,Aipi,Immd] 1110 1100 11ss sSSS:200:-NZ00:-----:--:13: BFCLR #1,s[!Areg,Apdi,Aipi,Immd,PC8r,PC16] 1110 1101 11ss sSSS:200:-NZ00:-----:--:11: BFFFO #1,s[!Areg,Apdi,Aipi,Immd] 1110 1110 11ss sSSS:200:-NZ00:-----:--:13: BFSET #1,s[!Areg,Apdi,Aipi,Immd,PC8r,PC16] 1110 1111 11ss sSSS:200:-NZ00:-----:--:13: BFINS #1,s[!Areg,Apdi,Aipi,Immd,PC8r,PC16] % floating point co processor 1111 0010 00ss sSSS:200:-----:-----:--:11: FPP #1,s 1111 0010 01ss sSSS:200:-----:-----:-B:11: FDBcc #1,s[Areg-Dreg] 1111 0010 01ss sSSS:200:-----:-----:--:11: FScc #1,s[!Areg,Immd,PC8r,PC16] 1111 0010 0111 1010:200:-----:-----:T-:10: FTRAPcc #1 1111 0010 0111 1011:200:-----:-----:T-:10: FTRAPcc #2 1111 0010 0111 1100:200:-----:-----:T-:00: FTRAPcc 1111 0010 10KK KKKK:200:-----:-----:-B:11: FBcc #K,#1 1111 0010 11KK KKKK:200:-----:-----:-B:11: FBcc #K,#2 1111 0011 00ss sSSS:202:-----:-----:--:20: FSAVE s[!Dreg,Areg,Aipi,Immd,PC8r,PC16] 1111 0011 01ss sSSS:202:-----:-----:--:10: FRESTORE s[!Dreg,Areg,Apdi,Immd] % 68030 MMU (allowed addressing modes not checked!) 1111 0000 00ss sSSS:342:-----:-----:T-:11: MMUOP030 s[Dreg,Areg,Apdi,Aipi,Aind,Ad16,Ad8r,absl,absw],#1 % 68040/68060 instructions 1111 0100 pp00 1rrr:402:-----:-----:T-:02: CINVL #p,Ar 1111 0100 pp01 0rrr:402:-----:-----:T-:02: CINVP #p,Ar 1111 0100 pp01 1rrr:402:-----:-----:T-:00: CINVA #p 1111 0100 pp10 1rrr:402:-----:-----:T-:02: CPUSHL #p,Ar 1111 0100 pp11 0rrr:402:-----:-----:T-:02: CPUSHP #p,Ar 1111 0100 pp11 1rrr:402:-----:-----:T-:00: CPUSHA #p 1111 0101 0000 0rrr:402:-----:-----:T-:00: PFLUSHN Ara 1111 0101 0000 1rrr:402:-----:-----:T-:00: PFLUSH Ara 1111 0101 0001 0rrr:402:-----:-----:T-:00: PFLUSHAN Ara 1111 0101 0001 1rrr:402:-----:-----:T-:00: PFLUSHA Ara % 68040 only 1111 0101 0100 1rrr:452:-----:-----:T-:00: PTESTW Ara 1111 0101 0110 1rrr:452:-----:-----:T-:00: PTESTR Ara % destination register number is encoded in the following word 1111 0110 0010 0rrr:400:-----:-----:--:12: MOVE16 ArP,AxP 1111 0110 00ss sSSS:400:-----:-----:--:12: MOVE16 s[Dreg-Aipi],Al 1111 0110 00dd dDDD:400:-----:-----:--:12: MOVE16 Al,d[Areg-Aipi] 1111 0110 00ss sSSS:400:-----:-----:--:12: MOVE16 s[Aind],Al 1111 0110 00dd dDDD:400:-----:-----:--:12: MOVE16 Al,d[Aipi-Aind] % 68060 1111 1000 0000 0000:500:XNZVC:-----:T-:10: LPSTOP #1 1111 0101 1000 1rrr:502:-----:-----:T-:00: PLPAW Ara 1111 0101 1100 1rrr:502:-----:-----:T-:00: PLPAR Ara % "Debug Pipe Control Mode Commands" 0100 1010 1100 1000:502:-----:-----:--:00: HALT 0100 1010 1100 1100:500:-----:-----:--:00: PULSE hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/000077500000000000000000000000001504763705000226715ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/attributes.h000066400000000000000000000015611504763705000252330ustar00rootroot00000000000000/* * Defines useful functions and variable attributes for UAE * Copyright (C) 2014 Frode Solheim * * Licensed under the terms of the GNU General Public License version 2. * See the file 'COPYING' for full license text. */ #ifndef UAE_ATTRIBUTES_H #define UAE_ATTRIBUTES_H /* This file is intended to be included by external libraries as well, * so don't pull in too much UAE-specific stuff. */ #ifdef _WIN32 #define uae_cdecl __cdecl #elif defined(__GNUC__) && defined(__i386__) #define uae_cdecl __attribute__((cdecl)) #else #define uae_cdecl #endif /* This attribute allows (some) compiles to emit warnings when incorrect * arguments are used with the format string. */ #ifdef __GNUC__ #define UAE_PRINTF_FORMAT(f, a) __attribute__((format(printf, f, a))) #else #define UAE_PRINTF_FORMAT(f, a) #endif #define UAE_WPRINTF_FORMAT(f, a) #endif /* UAE_ATTRIBUTES_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/likely.h000066400000000000000000000005601504763705000243340ustar00rootroot00000000000000#ifndef UAE_LIKELY_H #define UAE_LIKELY_H #ifndef WINUAE_FOR_HATARI /* not necessary for Hatari, already done in main.h */ #ifdef HAVE___BUILTIN_EXPECT #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #else #define likely(x) x #define unlikely(x) x #endif #endif /* !WINUAE_FOR_HATARI */ #endif /* UAE_LIKELY_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/string.h000066400000000000000000000037631504763705000243610ustar00rootroot00000000000000#ifndef UAE_STRING_H #define UAE_STRING_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "uae/types.h" #include #ifdef _WIN32 /* Make sure the real _tcs* functions are already declared before we * re-define them below. */ #include #include #include #endif #ifdef _WIN32 /* Using the real _tcs* functions */ #else #define _istdigit isdigit #define _istspace isspace #define _istupper isupper #define _sntprintf snprintf #define _stprintf sprintf #define _strtoui64 strtoll #define _tcscat strcat #define _tcschr strchr #define _tcscmp strcmp #define _tcscpy strcpy #define _tcscspn strcspn #define _tcsdup strdup #define _tcsftime strftime #define _tcsftime strftime #define _tcsicmp strcasecmp #define _tcslen strlen #define _tcsncat strncat #define _tcsncmp strncmp #define _tcsncpy strncpy #define _tcsnicmp strncasecmp #define _tcsrchr strrchr #define _tcsspn strspn #define _tcsstr strstr #define _tcstod strtod #define _tcstok strtok #define _tcstol strtol #define _tcstoul strtoul #define _totlower tolower #define _totupper toupper #define _tprintf printf #define _tstof atof #define _tstoi64 atoll #define _tstoi atoi #define _tstol atol #define _vsnprintf vsnprintf #define _vsntprintf vsnprintf #endif static inline size_t uae_tcslcpy(TCHAR *dst, const TCHAR *src, size_t size) { if (size == 0) { return 0; } size_t src_len = _tcslen(src); size_t cpy_len = src_len; if (cpy_len >= size) { cpy_len = size - 1; } memcpy(dst, src, cpy_len * sizeof(TCHAR)); dst[cpy_len] = _T('\0'); return src_len; } static inline size_t uae_strlcpy(char *dst, const char *src, size_t size) { if (size == 0) { return 0; } size_t src_len = strlen(src); size_t cpy_len = src_len; if (cpy_len >= size) { cpy_len = size - 1; } memcpy(dst, src, cpy_len); dst[cpy_len] = '\0'; return src_len; } static inline int uaestrlen(const char* s) { return (int)strlen(s); } static inline int uaetcslen(const TCHAR* s) { return (int)_tcslen(s); } #endif /* UAE_STRING_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/time.h000066400000000000000000000010131504763705000237730ustar00rootroot00000000000000#ifndef UAE_TIME_H #define UAE_TIME_H #include "uae/types.h" /* frame_time_t is often cast to int in the code so we use int for now... */ typedef uae_s64 uae_time_t; void uae_time_init(void); void uae_time_calibrate(void); uae_time_t uae_time(void); #ifdef _WIN32 void uae_time_use_rdtsc(bool enable); uae_s64 read_system_time(void); uae_s64 read_processor_time_rdtsc(void); #endif typedef uae_time_t frame_time_t; static inline frame_time_t read_processor_time(void) { return uae_time(); } #endif /* UAE_TIME_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/types.h000066400000000000000000000037331504763705000242140ustar00rootroot00000000000000/* * Common types used throughout the UAE source code * Copyright (C) 2014 Frode Solheim * * Licensed under the terms of the GNU General Public License version 2. * See the file 'COPYING' for full license text. */ #ifndef UAE_TYPES_H #define UAE_TYPES_H /* This file is intended to be included by external libraries as well, * so don't pull in too much UAE-specific stuff. */ #if 0 #include "config.h" #endif /* Define uae_ integer types. Prefer long long int for uae_x64 since we can * then use the %lld format specifier for both 32-bit and 64-bit instead of * the ugly PRIx64 macros. */ #include typedef int8_t uae_s8; typedef uint8_t uae_u8; typedef int16_t uae_s16; typedef uint16_t uae_u16; typedef int32_t uae_s32; typedef uint32_t uae_u32; #ifndef uae_s64 typedef long long int uae_s64; #endif #ifndef uae_u64 typedef unsigned long long int uae_u64; #endif #ifdef HAVE___UINT128_T #define HAVE_UAE_U128 typedef __uint128_t uae_u128; #endif /* Parts of the UAE/WinUAE code uses the bool type (from C++). * Including stdbool.h lets this be valid also when compiling with C. */ #ifndef __cplusplus #include #endif /* Use uaecptr to represent 32-bit (or 24-bit) addresses into the Amiga * address space. This is a 32-bit unsigned int regardless of host arch. */ typedef uae_u32 uaecptr; /* Define UAE character types. WinUAE uses TCHAR (typedef for wchar_t) for * many strings. FS-UAE always uses char-based strings in UTF-8 format. * Defining SIZEOF_TCHAR lets us determine whether to include overloaded * functions in some cases or not. */ typedef char uae_char; #ifdef _WIN32 #include #ifdef UNICODE #define SIZEOF_TCHAR 2 #else #define SIZEOF_TCHAR 1 #endif #else typedef char TCHAR; #define SIZEOF_TCHAR 1 #endif #ifndef _T #if SIZEOF_TCHAR == 1 #define _T(x) x #else #define _T(x) Lx #endif #endif #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE (!FALSE) #endif typedef signed long long evt_t; #endif /* UAE_TYPES_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/uae/vm.h000066400000000000000000000027721504763705000234740ustar00rootroot00000000000000/* * Multi-platform virtual memory functions for UAE. * Copyright (C) 2015 Frode Solheim * * Licensed under the terms of the GNU General Public License version 2. * See the file 'COPYING' for full license text. */ #ifndef UAE_VM_H #define UAE_VM_H #include "uae/types.h" #define UAE_VM_WRITE 2 #define UAE_VM_EXECUTE 4 #define UAE_VM_32BIT (1 << 8) #define UAE_VM_WRITE_WATCH (1 << 9) #define UAE_VM_ALLOC_FAILED NULL /* Even though it looks like you can OR together vm protection values, * do not do this. Not all combinations are supported (on Windows), and only * a few combinations are implemented. Only use the following predefined * constants to be safe. */ #define UAE_VM_NO_ACCESS 0 #define UAE_VM_READ 1 #define UAE_VM_READ_WRITE (UAE_VM_READ | UAE_VM_WRITE) #define UAE_VM_READ_EXECUTE (UAE_VM_READ | UAE_VM_EXECUTE) #define UAE_VM_READ_WRITE_EXECUTE (UAE_VM_READ | UAE_VM_WRITE | UAE_VM_EXECUTE) #if 0 void *uae_vm_alloc(uae_u32 size); void *uae_vm_alloc(uae_u32 size, int flags); #endif void *uae_vm_alloc(uae_u32 size, int flags, int protect); bool uae_vm_protect(void *address, int size, int protect); bool uae_vm_free(void *address, int size); void *uae_vm_reserve(uae_u32 size, int flags); void *uae_vm_reserve_fixed(void *address, uae_u32 size, int flags); void *uae_vm_commit(void *address, uae_u32 size, int protect); bool uae_vm_decommit(void *address, uae_u32 size); int uae_vm_page_size(void); // void *uae_vm_alloc_with_flags(uae_u32 size, int protect, int flags); #endif /* UAE_VM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/winuae_readme.txt000066400000000000000000000013411504763705000254640ustar00rootroot00000000000000 The CPU core in this directory is based on WinUAE 6.0.1 (2025/08/13) To update to a newer WinUAE's version, a diff should be made between WinUAE 6.0.1 sources and the newer sources, then the resulting patch should be applied to the files in this directory. Most files are similar to WinUAE's ones, so patches should apply in most cases ; except custom.c and memory.c which contain too much Amiga specific code and were trimmed down and might need manual editing to apply patches. newcpu.c contains many Hatari specific code, so patches might need manual editing too. If you update to a newer WinUAE's version, also update the version at the top of this readme file to keep track of the reference version used for the CPU core. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cpu/writelog.c000066400000000000000000000010611504763705000241150ustar00rootroot00000000000000 /* * UAE - The Un*x Amiga Emulator * * Standard write_log that writes to the console * * Copyright 2001 Bernd Schmidt */ #include "sysconfig.h" #include "sysdeps.h" #ifndef WINUAE_FOR_HATARI bool bCpuWriteLog = true; void write_log (const char *fmt, ...) { va_list ap; if (!bCpuWriteLog) return; va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); } #endif void f_out (void *f, const TCHAR *format, ...) { va_list parms; if (f == NULL) return; va_start (parms, format); vfprintf (f, format, parms); va_end (parms); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/createBlankImage.c000066400000000000000000000143231504763705000246550ustar00rootroot00000000000000/* Hatari - createBlankImage.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Create blank .ST/.MSA disk images. */ const char CreateBlankImage_fileid[] = "Hatari createBlankImage.c"; #include "main.h" #include "configuration.h" #include "dim.h" #include "file.h" #include "floppy.h" #include "log.h" #include "msa.h" #include "st.h" #include "createBlankImage.h" #include "gemdos_defines.h" #include "utils.h" /*-----------------------------------------------------------------------*/ /* 40 track SS 40 track DS 80 track SS 80 track DS 0- 1 Branch instruction to boot program if executable 2- 7 'Loader' 8-10 24-bit serial number 11-12 BPS 512 512 512 512 13 SPC 1 2 2 2 14-15 RES 1 1 1 1 16 FAT 2 2 2 2 17-18 DIR 64 112 112 112 19-20 SEC 360 720 720 1440 21 MEDIA $FC $FD $F8 $F9 (isn't used by ST-BIOS) 22-23 SPF 2 2 5 5 24-25 SPT 9 9 9 9 26-27 SIDE 1 2 1 2 28-29 HID 0 0 0 0 510-511 CHECKSUM */ /*-----------------------------------------------------------------------*/ /** * Calculate the size of a disk in dialog. */ static int CreateBlankImage_GetDiskImageCapacity(int nTracks, int nSectors, int nSides) { /* Find size of disk image */ return nTracks*nSectors*nSides*NUMBYTESPERSECTOR; } /*-----------------------------------------------------------------------*/ /** * Write a short integer to addr using little endian byte order * (needed for 16 bit values in the bootsector of the disk image). */ static inline void WriteShortLE(void *addr, uint16_t val) { uint8_t *p = (uint8_t *)addr; p[0] = (uint8_t)val; p[1] = (uint8_t)(val >> 8); } /*-----------------------------------------------------------------------*/ /** * Create .ST/.MSA disk image according to 'Tracks,Sector,Sides' and save * it under given filename. * If VolumeLabel != NULL, use this 8+3 char text as the name of the disk image. * Return true if saving succeeded, false otherwise. */ bool CreateBlankImage_CreateFile(const char *pszFileName, int nTracks, int nSectors, int nSides, const char *VolumeLabel) { uint8_t *pDiskFile; unsigned long nDiskSize; unsigned short int SPC, nDir, MediaByte, SPF; bool bRet = false; int drive; int LabelSize; uint8_t *pDirStart; /* HD/ED disks are all double sided */ if (nSectors >= 18) nSides = 2; /* Calculate size of disk image */ nDiskSize = CreateBlankImage_GetDiskImageCapacity(nTracks, nSectors, nSides); /* Allocate space for our 'file', and blank */ pDiskFile = malloc(nDiskSize); if (pDiskFile == NULL) { perror("Error while creating blank disk image"); return false; } memset(pDiskFile, 0, nDiskSize); /* Clear buffer */ /* Fill in boot-sector */ pDiskFile[0] = 0xE9; /* Needed for MS-DOS compatibility */ memset(pDiskFile+2, 0x4e, 6); /* 2-7 'Loader' */ WriteShortLE(pDiskFile+8, Hatari_rand()); /* 8-10 24-bit serial number */ pDiskFile[10] = Hatari_rand(); WriteShortLE(pDiskFile+11, NUMBYTESPERSECTOR); /* 11-12 BPS */ if ((nTracks == 40) && (nSides == 1)) SPC = 1; else SPC = 2; pDiskFile[13] = SPC; /* 13 SPC */ WriteShortLE(pDiskFile+14, 1); /* 14-15 RES */ pDiskFile[16] = 2; /* 16 FAT */ if (SPC==1) nDir = 64; else if (nSectors < 18) nDir = 112; else nDir = 224; WriteShortLE(pDiskFile+17, nDir); /* 17-18 DIR */ WriteShortLE(pDiskFile+19, nTracks*nSectors*nSides); /* 19-20 SEC */ if (nSectors >= 18) MediaByte = 0xF0; else { if (nTracks <= 42) MediaByte = 0xFC; else MediaByte = 0xF8; if (nSides == 2) MediaByte |= 0x01; } pDiskFile[21] = MediaByte; /* 21 MEDIA */ if (nSectors >= 18) SPF = 9; else if (nTracks >= 80) SPF = 5; else SPF = 2; WriteShortLE(pDiskFile+22, SPF); /* 22-23 SPF */ WriteShortLE(pDiskFile+24, nSectors); /* 24-25 SPT */ WriteShortLE(pDiskFile+26, nSides); /* 26-27 SIDE */ WriteShortLE(pDiskFile+28, 0); /* 28-29 HID */ /* Set correct media bytes in the 1st FAT: */ pDiskFile[512] = MediaByte; pDiskFile[513] = pDiskFile[514] = 0xFF; /* Set correct media bytes in the 2nd FAT: */ pDiskFile[512 + SPF * 512] = MediaByte; pDiskFile[513 + SPF * 512] = pDiskFile[514 + SPF * 512] = 0xFF; /* Set volume label if needed (in 1st entry of the directory) */ if ( VolumeLabel != NULL ) { /* Set 1st dir entry as 'volume label' */ pDirStart = pDiskFile + ( 1 + SPF * 2 ) * 512; memset ( pDirStart , ' ' , 8+3 ); LabelSize = strlen ( VolumeLabel ); if ( LabelSize <= 8+3 ) memcpy ( pDirStart , VolumeLabel , LabelSize ); else memcpy ( pDirStart , VolumeLabel , 8+3 ); pDirStart[ 8+3 ] = GEMDOS_FILE_ATTRIB_VOLUME_LABEL; } /* Ask if OK to overwrite, if exists? */ if (File_QueryOverwrite(pszFileName)) { drive = 0; /* drive is not used for ST/MSA/DIM, set it to 0 */ /* Save image to file */ if (MSA_FileNameIsMSA(pszFileName, true)) bRet = MSA_WriteDisk(drive, pszFileName, pDiskFile, nDiskSize); else if (ST_FileNameIsST(pszFileName, true)) bRet = ST_WriteDisk(drive, pszFileName, pDiskFile, nDiskSize); else if (DIM_FileNameIsDIM(pszFileName, true)) bRet = DIM_WriteDisk(drive, pszFileName, pDiskFile, nDiskSize); else Log_AlertDlg(LOG_ERROR, "Unknown floppy image filename extension!"); /* Did create successfully? */ if (bRet) { /* Say OK */ Log_AlertDlg(LOG_INFO, "Disk image '%s' created.", pszFileName); } else { /* Warn user we were unable to create image */ Log_AlertDlg(LOG_ERROR, "Unable to create disk image '%s'!", pszFileName); } } /* Free image */ free(pDiskFile); return bRet; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cycInt.c000066400000000000000000000505031504763705000227300ustar00rootroot00000000000000/* Hatari - cycInt.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This code handles our table with callbacks for cycle accurate program interruption. We add any pending callback handler into a table so that we do not need to test for every possible interrupt event. We then scan the list if used entries in the table and copy the one with the least cycle count into the global 'PendingInterruptCount' variable. This is then decremented by the execution loop - rather than decrement each and every entry (as the others cannot occur before this one). We have two methods of adding interrupts; Absolute and Relative. Absolute will set values from the time of the previous interrupt (e.g., add HBL every 512 cycles), and Relative will add from the current cycle time. Note that interrupt may occur 'late'. I.e., if an interrupt is due in 4 cycles time but the current instruction takes 20 cycles we will be 16 cycles late - this is handled in the adjust functions. In order to handle both CPU and MFP interrupt events, we don't convert MFP cycles to CPU cycles, because it requires some floating points approximations and accumulates some errors that could lead to bad results. Instead, CPU and MFP cycles are converted to 'internal' cycles with the following rule : - 1 CPU cycle gives 9600 internal cycles - 1 MFP cycle gives 31333 internal cycle All interrupt events are then handled in the 'internal' units and are converted back to cpu or mfp units when needed. This allows very good synchronisation between CPU and MFP, without the rounding errors of floating points math. Thanks to Arnaud Carre (Leonard / Oxygene) for sharing this method used in Saint (and also used in sc68). Conversions are based on these values : real MFP frequency is 2457600 Hz real CPU frequency is 8021247 Hz (PAL european STF), which we round to 8021248. Then : 8021248 = ( 2^8 * 31333 ) 2457600 = ( 2^15 * 3 * 5^2 ) So, the ratio 8021248 / 2457600 can be expressed as 31333 / 9600 */ const char CycInt_fileid[] = "Hatari cycInt.c"; #include #include #include #include "main.h" #include "configuration.h" #include "blitter.h" #include "dmaSnd.h" #include "crossbar.h" #include "fdc.h" #include "ikbd.h" #include "cycles.h" #include "cycInt.h" #include "m68000.h" #include "mfp.h" #include "midi.h" #include "memorySnapShot.h" #include "sound.h" #include "video.h" #include "acia.h" #include "scc.h" #include "clocks_timings.h" //#define CYCINT_DEBUG void (*PendingInterruptFunction)(void); // TODO rename to CycInt_ActiveInt_Function int PendingInterruptCount; static int CycInt_DelayedCycles; /* List of possible interrupt handlers to be stored in 'InterruptHandlers[]' * The list should be in the same order than the enum type 'interrupt_id' */ static void (* const pIntHandlerFunctions[MAX_INTERRUPTS])(void) = { NULL, Video_InterruptHandler_VBL, Video_InterruptHandler_HBL, Video_InterruptHandler_EndLine, MFP_Main_InterruptHandler_TimerA, MFP_Main_InterruptHandler_TimerB, MFP_Main_InterruptHandler_TimerC, MFP_Main_InterruptHandler_TimerD, MFP_TT_InterruptHandler_TimerA, MFP_TT_InterruptHandler_TimerB, MFP_TT_InterruptHandler_TimerC, MFP_TT_InterruptHandler_TimerD, ACIA_InterruptHandler_IKBD, IKBD_InterruptHandler_ResetTimer, IKBD_InterruptHandler_AutoSend, DmaSnd_InterruptHandler_Microwire, /* Used for both STE and Falcon Microwire emulation */ Crossbar_InterruptHandler_25Mhz, Crossbar_InterruptHandler_32Mhz, FDC_InterruptHandler_Update, Blitter_InterruptHandler, Midi_InterruptHandler_Update, SCC_InterruptHandler_BRG_A, SCC_InterruptHandler_TX_RX_A, SCC_InterruptHandler_RX_A, SCC_InterruptHandler_BRG_B, SCC_InterruptHandler_TX_RX_B, SCC_InterruptHandler_RX_B }; /* Event timer structure - keeps next timer to occur in structure so don't need * to check all entries */ typedef struct { bool Active; /* Is interrupt active? */ uint64_t Cycles; void (*pFunction)(void); int IntList_Prev; /* Number of previous interrupt sorted by 'Cycles' value (or -1 if none) */ int IntList_Next; /* Number of next interrupt sorted by 'Cycles' value (or -1 if none) */ /* NOTE : type should be 'int' not 'interrupt_id' else compiler might internally */ /* use 'unsigned int' which will fail when storing value '-1' */ } INTERRUPTHANDLER; static INTERRUPTHANDLER InterruptHandlers[MAX_INTERRUPTS]; static interrupt_id CycInt_ActiveInt = 0; uint64_t CycInt_ActiveInt_Cycles; static void CycInt_InsertInt ( interrupt_id IntId ); /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ /* This is a temporary case needed to handle updating CYCLES_COUNTER_VIDEO */ /* when cycint handler is called while processing an opcode (see MFP_UpdateTimers() ) */ /* This should be removed once we replace CYCLES_COUNTER_VIDEO with CyclesGlobalClockCounter */ bool CycInt_From_Opcode = false; /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ /*-----------------------------------------------------------------------*/ /** * Reset interrupts, handlers */ void CycInt_Reset(void) { int i; /* Reset counts */ PendingInterruptCount = 0; CycInt_DelayedCycles = 0; /* Reset interrupt table */ for (i=0; i= 0 ); #endif /* Search for the position to insert IntId in the linked list ; we insert just before interrupt 'n' */ n = CycInt_ActiveInt; prev = InterruptHandlers[ n ].IntList_Prev; while (InterruptHandlers[ IntId ].Cycles > InterruptHandlers[ n ].Cycles) { n = InterruptHandlers[ n ].IntList_Next; assert (n >= 0); prev = InterruptHandlers[ n ].IntList_Prev; } InterruptHandlers[ IntId ].IntList_Next = n; InterruptHandlers[ n ].IntList_Prev = IntId; if ( n == (int)CycInt_ActiveInt ) /* Add as the first entry from list */ { /* Set the new ActiveInt */ CycInt_ActiveInt = IntId; CycInt_ActiveInt_Cycles = InterruptHandlers[ CycInt_ActiveInt ].Cycles; /* New ActiveInt is first of the list */ InterruptHandlers[ CycInt_ActiveInt ].IntList_Prev = -1; } else /* Insert in middle of the list */ { InterruptHandlers[ IntId ].IntList_Prev = prev; InterruptHandlers[ prev ].IntList_Next = IntId; } #ifdef CYCINT_DEBUG fprintf ( stderr , "int after active=%02d active_cyc=%"PRIu64" new=%02d cyc=%"PRIu64" clock=%"PRIu64"\n" , CycInt_ActiveInt , CycInt_ActiveInt_Cycles , IntId , InterruptHandlers[ IntId ].Cycles , Cycles_GetClockCounterImmediate() ); n = CycInt_ActiveInt; do { fprintf ( stderr , " int %02d prev=%02d next=%02d cyc=%"PRIu64"\n" , n , InterruptHandlers[ n ].IntList_Prev , InterruptHandlers[ n ].IntList_Next , InterruptHandlers[ n ].Cycles ); n = InterruptHandlers[ n ].IntList_Next; } while ( n >= 0 ); #endif } /*-----------------------------------------------------------------------*/ /** * As 'CycInt_ActiveInt' has occurred, we remove it from active list * and set a new value for CycInt_ActiveInt */ void CycInt_AcknowledgeInterrupt(void) { /* Disable interrupt's entry which has just occurred */ InterruptHandlers[ CycInt_ActiveInt ].Active = false; /* Set the new ActiveInt as the next in list (it can be INTERRUPT_NULL (=0) ) */ CycInt_ActiveInt = InterruptHandlers[ CycInt_ActiveInt ].IntList_Next; CycInt_ActiveInt_Cycles = InterruptHandlers[ CycInt_ActiveInt ].Cycles; /* New ActiveInt is first of the list */ InterruptHandlers[ CycInt_ActiveInt ].IntList_Prev = -1; LOG_TRACE(TRACE_INT, "int ack video_cyc=%d active_int=%d clock=%"PRIu64" active_cyc=%"PRIu64" pending_count=%d\n", Video_GetCyclesSinceVbl(), CycInt_ActiveInt, Cycles_GetClockCounterImmediate() , InterruptHandlers[CycInt_ActiveInt].Cycles, PendingInterruptCount ); } /*-----------------------------------------------------------------------*/ /** * Add interrupt from the time last one should have occurred. * We take into account CycInt_DelayedCycles (<=0) which can be =0 if the * interrupt could be processed at exactly InterruptHandlers[].Cycles or can be < 0 * if the interrupt was delayed by some cycles */ void CycInt_AddAbsoluteInterrupt(int CycleTime, int CycleType, interrupt_id Handler) { /* Check interrupt is not already enabled ; if so, remove it first */ if ( InterruptHandlers[ Handler ].Active == true ) CycInt_RemovePendingInterrupt ( Handler ); /* Enable interrupt with new Cycles value */ InterruptHandlers[ Handler ].Active = true; InterruptHandlers[ Handler ].Cycles = INT_CONVERT_TO_INTERNAL((int64_t)CycleTime , CycleType) + CycInt_DelayedCycles; InterruptHandlers[ Handler ].Cycles += INT_CONVERT_TO_INTERNAL(Cycles_GetClockCounterImmediate(),INT_CPU_CYCLE); CycInt_InsertInt ( Handler ); LOG_TRACE(TRACE_INT, "int add abs video_cyc=%d handler=%d clock=%"PRIu64" handler_cyc=%"PRIu64" pending_count=%d\n", Video_GetCyclesSinceVbl(), Handler, Cycles_GetClockCounterImmediate() , InterruptHandlers[Handler].Cycles, PendingInterruptCount ); } /*-----------------------------------------------------------------------*/ /** * Add interrupt to occur from now. */ void CycInt_AddRelativeInterrupt(int CycleTime, int CycleType, interrupt_id Handler) { CycInt_AddRelativeInterruptWithOffset(CycleTime, CycleType, Handler, 0); } /*-----------------------------------------------------------------------*/ /** * Add interrupt to occur after CycleTime/CycleType + CycleOffset. * CycleOffset can be used to add another delay to the resulting * number of internal cycles (should be 0 most of the time, except in * the MFP emulation to start timers precisely based on the number of * cycles of the current instruction). * This allows to restart an MFP timer just after it expired. */ void CycInt_AddRelativeInterruptWithOffset(int CycleTime, int CycleType, interrupt_id Handler, int CycleOffset) { //fprintf ( stderr , "int add rel %d type %d handler %d offset %d\n" , CycleTime,CycleType,Handler,CycleOffset ); /* Check interrupt is not already enabled ; if so, remove it first */ if ( InterruptHandlers[ Handler ].Active == true ) CycInt_RemovePendingInterrupt ( Handler ); /* Enable interrupt with new Cycles value */ InterruptHandlers[ Handler ].Active = true; InterruptHandlers[ Handler ].Cycles = INT_CONVERT_TO_INTERNAL((int64_t)CycleTime , CycleType) + CycleOffset; InterruptHandlers[ Handler ].Cycles += INT_CONVERT_TO_INTERNAL(Cycles_GetClockCounterImmediate(),INT_CPU_CYCLE); CycInt_InsertInt ( Handler ); LOG_TRACE(TRACE_INT, "int add rel offset video_cyc=%d handler=%d clock=%"PRIu64" handler_cyc=%"PRIu64" offset_cyc=%d pending_count=%d\n", Video_GetCyclesSinceVbl(), Handler, Cycles_GetClockCounterImmediate() , InterruptHandlers[Handler].Cycles, CycleOffset, PendingInterruptCount); } /*-----------------------------------------------------------------------*/ /** * Modify interrupt's Cycles to make it happen earlier or later. * This will not restart the interrupt, but add CycleTime cycles to the * current value of the counter. * CycleTime can be <0 or >0 */ void CycInt_ModifyInterrupt(int CycleTime, int CycleType, interrupt_id Handler) { /* First, we remove the interrupt from the list */ CycInt_RemovePendingInterrupt ( Handler ); /* Enable interrupt with new Cycles value */ InterruptHandlers[ Handler ].Active = true; InterruptHandlers[ Handler ].Cycles += INT_CONVERT_TO_INTERNAL((int64_t)CycleTime , CycleType); CycInt_InsertInt ( Handler ); LOG_TRACE(TRACE_INT, "int modify video_cyc=%d handler=%d clock=%"PRIu64" handler_cyc=%"PRIu64" pending_count=%d\n", Video_GetCyclesSinceVbl(), Handler, Cycles_GetClockCounterImmediate() , InterruptHandlers[Handler].Cycles, PendingInterruptCount ); } /*-----------------------------------------------------------------------*/ /** * Remove a pending interrupt from our table * If Handler== CycInt_ActiveInt, we also set a new value for CycInt_ActiveInt */ void CycInt_RemovePendingInterrupt(interrupt_id Handler) { /* Check interrupt is not already disabled ; if so, don't do anything */ if ( InterruptHandlers[ Handler ].Active == false ) { LOG_TRACE(TRACE_INT, "int remove pending already disabled video_cyc=%d handler=%d clock=%"PRIu64" handler_cyc=%"PRIu64" pending_count=%d\n", Video_GetCyclesSinceVbl(), Handler, Cycles_GetClockCounterImmediate() , InterruptHandlers[Handler].Cycles, PendingInterruptCount); return; } /* Disable interrupt's entry */ InterruptHandlers[Handler].Active = false; if ( Handler == CycInt_ActiveInt ) /* Remove first entry from list */ { /* Set the new ActiveInt as the next in list (it can be INTERRUPT_NULL) */ CycInt_ActiveInt = InterruptHandlers[ CycInt_ActiveInt ].IntList_Next; CycInt_ActiveInt_Cycles = InterruptHandlers[ CycInt_ActiveInt ].Cycles; /* New ActiveInt is first of the list */ InterruptHandlers[ CycInt_ActiveInt ].IntList_Prev = -1; } else /* Remove an entry 'n' in middle of the list */ { /* Update prev/next for the entries n-1 and n+1 */ InterruptHandlers[ InterruptHandlers[Handler].IntList_Prev ].IntList_Next = InterruptHandlers[ Handler ].IntList_Next; InterruptHandlers[ InterruptHandlers[Handler].IntList_Next ].IntList_Prev = InterruptHandlers[ Handler ].IntList_Prev; } LOG_TRACE(TRACE_INT, "int remove pending video_cyc=%d handler=%d clock=%"PRIu64" handler_cyc=%"PRIu64" pending_count=%d\n", Video_GetCyclesSinceVbl(), Handler, Cycles_GetClockCounterImmediate() , InterruptHandlers[Handler].Cycles, PendingInterruptCount); #ifdef CYCINT_DEBUG fprintf ( stderr , "int remove after active=%02d active_cyc=%"PRIu64" clock=%"PRIu64"\n" , CycInt_ActiveInt , CycInt_ActiveInt_Cycles , Cycles_GetClockCounterImmediate() ); int n = CycInt_ActiveInt; do { fprintf ( stderr , " int %02d prev=%02d next=%02d cyc=%"PRIu64"\n" , n , InterruptHandlers[ n ].IntList_Prev , InterruptHandlers[ n ].IntList_Next , InterruptHandlers[ n ].Cycles ); n = InterruptHandlers[ n ].IntList_Next; } while ( n >= 0 ); #endif } /*-----------------------------------------------------------------------*/ /** * Return cycles remaining for an interrupt handler * Remaining cycles are counted from current clock CyclesGlobalClockCounter, * with a possible extra CPU cycles delay in AddCpuCycles */ int CycInt_FindCyclesRemaining(interrupt_id Handler, int CycleType) { int64_t Cycles; Cycles = InterruptHandlers[Handler].Cycles - INT_CONVERT_TO_INTERNAL( ( Cycles_GetClockCounterImmediate() ) , INT_CPU_CYCLE ); LOG_TRACE(TRACE_INT, "int find passed cyc video_cyc=%d handler=%d clock=%"PRIu64" int_cyc=%"PRIu64" remain_cyc=%"PRIu64"\n", Video_GetCyclesSinceVbl(), Handler, Cycles_GetClockCounterImmediate() , InterruptHandlers[Handler].Cycles, Cycles); return INT_CONVERT_FROM_INTERNAL ( Cycles , CycleType ) ; } /*-----------------------------------------------------------------------*/ /** * Return true if interrupt is active in list */ bool CycInt_InterruptActive(interrupt_id Handler) { /* Is timer active? */ if (InterruptHandlers[Handler].Active) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Return the number of the active interrupt (0 means no active int) */ int CycInt_GetActiveInt(void) { return CycInt_ActiveInt; } /*-----------------------------------------------------------------------*/ /** * Call the handler associated with the active interrupt (it should never be NULL) * Clock is the time when the active interrupt triggered and it's used to * compute PendingInterruptCount */ void CycInt_CallActiveHandler(uint64_t Clock) { #ifdef CYCINT_DEBUG fprintf ( stderr , "int remove after active=%02d active_cyc=%"PRIu64" clock=%"PRIu64"\n" , CycInt_ActiveInt , CycInt_ActiveInt_Cycles , Clock ); int n = CycInt_ActiveInt; do { fprintf ( stderr , " int %02d prev=%02d next=%02d cyc=%"PRIu64"\n" , n , InterruptHandlers[ n ].IntList_Prev , InterruptHandlers[ n ].IntList_Next , InterruptHandlers[ n ].Cycles ); n = InterruptHandlers[ n ].IntList_Next; } while ( n >= 0 ); #endif /* For compatibility with old cycInt code, we compute a value of PendingInterruptCount */ /* at the time the interrupt happens. PendingInterruptCount will be <= 0 */ /* A value <0 indicates that the interrupt was delayed by some cycles */ /* TODO : rename this variable later to sthg more explicit when old cycInt code is removed */ /* (keep only CycInt_DelayedCycles for example) */ PendingInterruptCount = CycInt_ActiveInt_Cycles - INT_CONVERT_TO_INTERNAL(Clock,INT_CPU_CYCLE); CycInt_DelayedCycles = PendingInterruptCount; //fprintf ( stderr , "int call handler pending=%d\n" , PendingInterruptCount ); CALL_VAR ( InterruptHandlers[CycInt_ActiveInt].pFunction ); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/cycles.c000066400000000000000000000256741504763705000227740ustar00rootroot00000000000000/* Hatari - cycles.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Here we take care of cycle counters. For performance reasons we don't increase all counters after each 68k instruction, but only one main counter. When we need to read one of the normal counters (currently only for video and sound cycles), we simply update these counters with the main counter before returning the current counter value. */ /* 2007/03/xx [NP] Use 'CurrentInstrCycles' to get a good approximation for */ /* Cycles_GetCounterOnReadAccess and Cycles_GetCounterOnWriteAccess*/ /* (this should work correctly with 'move' instruction). */ /* 2008/04/14 [NP] Take WaitStateCycles into account when computing the value of */ /* Cycles_GetCounterOnReadAccess and Cycles_GetCounterOnWriteAccess*/ /* 2008/12/21 [NP] Use BusMode to adjust Cycles_GetCounterOnReadAccess and */ /* Cycles_GetCounterOnWriteAccess depending on who is owning the */ /* bus (cpu, blitter). */ /* 2011/03/26 [NP] In Cycles_GetCounterOnReadAccess, add a special case for opcode */ /* $11f8 'move.b xxx.w,xxx.w' (fix MOVE.B $ffff8209.w,$26.w in */ /* 'Bird Mad Girl Show' demo's loader/protection) */ /* 2012/08/19 [NP] Add a global counter CyclesGlobalClockCounter to count cycles */ /* since the last reset. */ /* 2015/10/04 [NP] In Cycles_GetInternalCycleOnReadAccess / WriteAccess, use the */ /* sub-cycles provided by WinUAE cpu core when using cycle exact */ /* mode (instead of using heuristics for the most common opcodes). */ const char Cycles_fileid[] = "Hatari cycles.c"; #include "main.h" #include "m68000.h" #include "memorySnapShot.h" #include "cycles.h" #include "ioMem.h" #include "hatari-glue.h" int nCyclesMainCounter; /* Main cycles counter since previous Cycles_UpdateCounters() */ static int nCyclesCounter[CYCLES_COUNTER_MAX]; /* Array with all counters */ uint64_t CyclesGlobalClockCounter = 0; /* Global clock counter since starting Hatari (it's never reset afterwards) */ int CurrentInstrCycles; static void Cycles_UpdateCounters(void); /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void Cycles_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&nCyclesMainCounter, sizeof(nCyclesMainCounter)); MemorySnapShot_Store(nCyclesCounter, sizeof(nCyclesCounter)); MemorySnapShot_Store(&CyclesGlobalClockCounter, sizeof(CyclesGlobalClockCounter)); MemorySnapShot_Store(&CurrentInstrCycles, sizeof(CurrentInstrCycles)); } /*-----------------------------------------------------------------------*/ /** * Update all cycles counters with the current value of nCyclesMainCounter. */ static void Cycles_UpdateCounters(void) { int i; for (i = 0; i < CYCLES_COUNTER_MAX; i++) { nCyclesCounter[i] += nCyclesMainCounter; } nCyclesMainCounter = 0; } /*-----------------------------------------------------------------------*/ /** * Set a counter to a new value. */ void Cycles_SetCounter(int nId, int nValue) { /* Update counters first (nCyclesMainCounter must be 0 afterwards) */ Cycles_UpdateCounters(); /* Now set the new value: */ nCyclesCounter[nId] = nValue; } /*-----------------------------------------------------------------------*/ /** * Read a counter. */ int Cycles_GetCounter(int nId) { /* Update counters first so we read an up-to-date value */ Cycles_UpdateCounters(); return nCyclesCounter[nId]; } /*-----------------------------------------------------------------------*/ /** * Compute the cycles where a read actually happens inside a specific * instruction type. We use some common cases, this should be handled more * accurately in the cpu emulation for each opcode. */ int Cycles_GetInternalCycleOnReadAccess(void) { int AddCycles; int Opcode; if ( BusMode == BUS_MODE_BLITTER ) { AddCycles = 4 + WaitStateCycles; } /* When using WinUAE CPU in CE mode, 'currcycle' will be the number of cycles */ /* inside the current opcode just before accessing memory. */ /* As memory accesses take 4 cycles, we just need to add 4 cycles to get */ /* the number of cycles when the read will be completed. */ /* (see mem_access_delay_XXX_read() in cpu_prefetch.h and wait_cpu_cycle_read() in custom.c) */ else if ( CpuRunCycleExact ) { AddCycles = currcycle*2/CYCLE_UNIT + 4; } else /* BUS_MODE_CPU */ { /* TODO: Find proper cycles count depending on the opcode/family of the current instruction */ /* (e.g. movem is not correctly handled) */ Opcode = M68000_CurrentOpcode; /* Assume we use 'move src,dst' : access cycle depends on dst mode */ if ( Opcode == 0x11f8 ) /* move.b xxx.w,xxx.w (eg MOVE.B $ffff8209.w,$26.w in Bird Mad Girl Show) */ AddCycles = 8 + WaitStateCycles; /* read is effective after 8 cycles */ else if ( OpcodeFamily == i_MVPRM ) /* movep.l d0,$ffc3(a1) in E605 (STE) or movep.l d1,$fffb(a2) in RGBeast (STE) */ AddCycles = 4 + IoAccessInstrCount * 4 + WaitStateCycles; /* [NP] FIXME, it works with RGBeast, but not with E605 */ /* something must be wrong in video.c */ else /* assume the behaviour of a 'move' to Dn */ AddCycles = CurrentInstrCycles - 4 + WaitStateCycles; /* read is effective 4 cycles before the end of the instr */ } return AddCycles; } /*-----------------------------------------------------------------------*/ /** * Compute the cycles where a write actually happens inside a specific * instruction type. We use some common cases, this should be handled more * accurately in the cpu emulation for each opcode. */ int Cycles_GetInternalCycleOnWriteAccess(void) { int AddCycles; if ( BusMode == BUS_MODE_BLITTER ) { AddCycles = 4 + WaitStateCycles; } /* When using WinUAE CPU in CE mode, 'currcycle' will be the number of cycles */ /* inside the current opcode just before accessing memory. */ /* As memory accesses take 4 cycles, we just need to add 4 cycles to get */ /* the number of cycles when the write will be completed. */ /* (see mem_access_delay_XXX_write() in cpu_prefetch.h and wait_cpu_cycle_write() in custom.c) */ else if ( CpuRunCycleExact ) { AddCycles = currcycle*2/CYCLE_UNIT + 4; } else /* BUS_MODE_CPU */ { /* TODO: Find proper cycles count depending on the type of the current instruction */ /* (e.g. movem is not correctly handled) */ AddCycles = CurrentInstrCycles + WaitStateCycles; if ( ( OpcodeFamily == i_CLR ) || ( OpcodeFamily == i_NEG ) || ( OpcodeFamily == i_NEGX ) || ( OpcodeFamily == i_NOT ) ) ; /* Do nothing, the write is done during the last 4 cycles */ /* (e.g i_CLR for bottom border removal in No Scroll / Delirious Demo 4) */ else if ( ( OpcodeFamily == i_ADD ) || ( OpcodeFamily == i_SUB ) ) ; /* Do nothing, the write is done during the last 4 cycles */ /* (eg 'add d1,(a0)' in rasters.prg by TOS Crew */ else if ( ( OpcodeFamily == i_AND ) || ( OpcodeFamily == i_OR ) || ( OpcodeFamily == i_EOR ) ) ; /* Do nothing, the write is done during the last 4 cycles */ else if ( ( OpcodeFamily == i_BCHG ) || ( OpcodeFamily == i_BCLR ) || ( OpcodeFamily == i_BSET ) ) ; /* Do nothing, the write is done during the last 4 cycles */ else if ( OpcodeFamily == i_MVPRM ) /* movep.l d0,$ffc3(a1) in E605 (STE) or movep.l d1,$fffb(a2) in RGBeast (STE) */ AddCycles = 4 + IoAccessInstrCount * 4 + WaitStateCycles; /* [NP] FIXME, it works with RGBeast, but not with E605 */ else if ( OpcodeFamily == i_MVMLE ) { /* In the case of movem, CurrentInstrCycles is dynamic (depends on the number */ /* of registers to transfer). The 4*n for .W or 8*n for .L is not counted in CurrentInstrCycles */ /* The last 4 cycles of a movem are for prefetch, so number of cycles is : */ /* x + 4*n + 4 (movem.w) or x + 8*n + 4 (movem.l) with x + 4 = CurrentInstrCycles */ if (nIoMemAccessSize == SIZE_LONG) /* long access from a movem.l */ { //AddCycles += -4 + IoAccessInstrCount * 8 - 4; AddCycles -= 0; /* NOTE : this is used only if cycle exact and prefetch are */ /* disabled in the CPU, but this certainly does not happen */ /* on real HW because IO regs can't be accessesed with a long */ } else /* word access with movem.w or movem.l doing 2 words accesses per long */ { AddCycles += -4 + IoAccessInstrCount * 4; } } else { /* Default case : write first, then prefetch (mostly for 'move' since this is the most */ /* common instr used when requiring cycle precise writes) */ if (nIoMemAccessSize == SIZE_LONG) /* long access */ AddCycles -= 8; else /* word/byte access */ { if ( IoAccessInstrCount == 0 ) /* instruction does only 1 access */ AddCycles -= 4; else /* instruction does multiple accesses (eg: move.l gives 2 word accesses) */ AddCycles += -12 + IoAccessInstrCount * 4; /* gives -8 or -4 */ } } } return AddCycles; } /*-----------------------------------------------------------------------*/ /** * Read a counter on CPU memory read access by taking care of the instruction * type (add the needed amount of additional cycles). */ int Cycles_GetCounterOnReadAccess(int nId) { int AddCycles; AddCycles = Cycles_GetInternalCycleOnReadAccess(); return Cycles_GetCounter(nId) + AddCycles; } /*-----------------------------------------------------------------------*/ /** * Read a counter on CPU memory write access by taking care of the instruction * type (add the needed amount of additional cycles). */ int Cycles_GetCounterOnWriteAccess(int nId) { int AddCycles; AddCycles = Cycles_GetInternalCycleOnWriteAccess(); return Cycles_GetCounter(nId) + AddCycles; } /*-----------------------------------------------------------------------*/ /** * Read the main clock counter on CPU memory read access by taking care of the instruction * type (add the needed amount of additional cycles). */ uint64_t Cycles_GetClockCounterOnReadAccess(void) { int AddCycles; AddCycles = Cycles_GetInternalCycleOnReadAccess(); return CyclesGlobalClockCounter + AddCycles; } /*-----------------------------------------------------------------------*/ /** * Read the main clock counter on CPU memory write access by taking care of the instruction * type (add the needed amount of additional cycles). */ uint64_t Cycles_GetClockCounterOnWriteAccess(void) { int AddCycles; AddCycles = Cycles_GetInternalCycleOnWriteAccess(); return CyclesGlobalClockCounter + AddCycles; } /*-----------------------------------------------------------------------*/ /** * Read the main clock counter * This function is mainly used in CycInt_xxx functions, either after processing * the current instruction (then currcycle=0) or during the processing of the current * instruction. */ uint64_t Cycles_GetClockCounterImmediate(void) { if ( CpuRunCycleExact ) return CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT; else return CyclesGlobalClockCounter; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/000077500000000000000000000000001504763705000224165ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/68kDisass.c000066400000000000000000000353541504763705000243530ustar00rootroot00000000000000/** * Disassemble M68k code * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #include "main.h" #include #include "sysdeps.h" #include "configuration.h" #include "m68000.h" #include "newcpu.h" #include "debug.h" #include "paths.h" #include "profile.h" #include "str.h" #include "68kDisass.h" #include "disasm.h" #if HAVE_CAPSTONE_M68K #include #include "stMemory.h" #include "tos.h" #endif typedef enum { doptNoSpace = (1 << 0), // ext: no space after a comma in the operands list doptOpcodesSmall = (1 << 1), // opcodes in lower case doptRegisterSmall = (1 << 2), // register names in lower case doptStackSP = (1 << 3), // ext: stack pointer is named "SP" instead of "A7" (except for MOVEM) doptNoWords = (1 << 4), // do no show 16-bit words at this address doptShowValues = (1 << 5), // uae: show EA & CC value in disassembly doptHexSmall = (1 << 6), // uae: hex addresses in lower case } Diss68kOptions; // Note: doptNoBrackets is not implemented anymore static Diss68kOptions options = doptOpcodesSmall | doptRegisterSmall | doptNoSpace | doptShowValues | doptHexSmall; /* all options for 'ext' and 'uae' disassemblers */ #define COMMON_OPTS (doptOpcodesSmall | doptRegisterSmall | doptNoWords) static const Diss68kOptions extOptMask = COMMON_OPTS | doptStackSP | doptNoSpace; static const Diss68kOptions uaeOptMask = COMMON_OPTS | doptShowValues | doptHexSmall; static const int defaultPositions[DISASM_COLUMNS] = { 0, // address: current address 10, // hexdump: 16-bit words at this address 33, // label: if defined 45, // opcode 55, // operands: for the opcode 80 // comment: if defined }; // DISASM_COLUMN_DISABLE value will skip given column data static int positions[DISASM_COLUMNS]; static int optionCPUTypeMask; #if HAVE_CAPSTONE_M68K static void Disass68k_AddLineAComment(uint16_t opcode, char *sCommentBuffer) { static const char *sLineAName[16] = { "Init", "Put pixel", "Get pixel", "Arbitrary line", "Horizontal line", "Filled rectangle", "Filled polygon", "BitBlt", "TextBlt", "Show mouse", "Hide mouse", "Transform mouse", "Undraw sprite", "Draw sprite", "Copy raster form", "Seedfill" }; opcode &= 0x0fff; if (opcode < ARRAY_SIZE(sLineAName)) sprintf(sCommentBuffer, "Line-A $%03x (\"%s\")", opcode, sLineAName[opcode]); else sprintf(sCommentBuffer, "Line-A $%03x", opcode); } static void Disass68k_ConvertA7ToSP(char *sOpBuf) { char *ptr; int cnt; /* Do this twice, once for source and once for destination */ for (cnt = 0; cnt < 2 ; cnt++) { ptr = strstr(sOpBuf, "(a7"); if (ptr) memcpy(ptr + 1, "sp", 2); } if (strncmp(sOpBuf, "a7,", 3) == 0) memcpy(sOpBuf, "sp", 2); cnt = strlen(sOpBuf); if (cnt > 4 && strncmp(&sOpBuf[cnt - 4], ", a7", 4) == 0) memcpy(&sOpBuf[cnt - 2], "sp", 2); } static int Disass68k(csh cshandle, long addr, char *labelBuffer, char *opcodeBuffer, char *operandBuffer, char *commentBuffer) { const int maxsize = MAX_68000_INSTRUCTION_SIZE; uint16_t opcode; cs_insn *insn; void *mem; char *ch; int size; labelBuffer[0] = 0; opcodeBuffer[0] = 0; operandBuffer[0] = 0; commentBuffer[0] = 0; if (!STMemory_CheckAreaType(addr, maxsize, ABFLAG_RAM | ABFLAG_ROM)) { strcpy(commentBuffer, "address out of bounds"); strcpy(opcodeBuffer, "???"); return 2; } mem = STMemory_STAddrToPointer(addr); if (cs_disasm(cshandle, mem, maxsize, addr, 1, &insn) <= 0) { strcpy(commentBuffer, "unknown opcode"); strcpy(opcodeBuffer, (options & doptOpcodesSmall) ? "dc.w" : "DC.W"); sprintf(operandBuffer, (options & doptRegisterSmall) ? "$%4.4x" : "$%4.4X", STMemory_ReadWord(addr)); return 2; } strcpy(opcodeBuffer, insn->mnemonic); /* Instruction mnemonic in uppercase letter? */ if (!(options & doptOpcodesSmall)) Str_ToUpper(opcodeBuffer); strcpy(operandBuffer, insn->op_str); /* Replace "a7" with "sp"? */ if ((options & doptStackSP) != 0) Disass68k_ConvertA7ToSP(operandBuffer); /* Operands in uppercase letters? */ if (!(options & doptRegisterSmall)) Str_ToUpper(operandBuffer); /* Remove spaces after comma? */ if ((options & doptNoSpace) != 0) { for (ch = operandBuffer; *ch != 0; ch++) { if (ch[0] == ',' && ch[1] == ' ') { ++ch; memmove(ch, ch + 1, strlen(ch + 1) + 1); } } } opcode = do_get_mem_word(mem); if (opcode >= 0xa000 && opcode <= 0xafff) Disass68k_AddLineAComment(opcode, commentBuffer); size = insn->size; cs_free(insn, 1); return size; } static void Disass68kComposeStr(char *dbuf, const char *str, int position, int maxPos) { int i; int len = strlen(dbuf); while(len < position) { dbuf[len++] = ' '; /* Will give harmless warning from GCC */ } for(i=0; str[i] && (!maxPos || len+i 0) { const int addrWidth = 8; // 6 on an ST (24 bit addressing), 8 on a TT (32 bit addressing) char lineBuffer[1024]; char addressBuffer[32]; char hexdumpBuffer[256]; char labelBuffer[258]; char opcodeBuffer[64]; char operandBuffer[256]; char commentBuffer[258]; int plen, len, j; len = Disass68k(cshandle, addr, labelBuffer, opcodeBuffer, operandBuffer, commentBuffer); if (!len) break; sprintf(addressBuffer, "$%*.*x", addrWidth,addrWidth, addr); hexdumpBuffer[0] = 0; plen = len; if(plen > 80 && (!strncmp(opcodeBuffer, "DC.", 3) || !strncmp(opcodeBuffer, "dc.", 3))) plen = ((positions[DISASM_COLUMN_LABEL] - positions[DISASM_COLUMN_HEXDUMP]) / 5) * 2; for(j=0; j 0) strcat(hexdumpBuffer, " "); if(j + 2 > plen) { sprintf(hexdumpBuffer+strlen(hexdumpBuffer), "%2.2x", STMemory_ReadWord(addr+j) >> 8); } else { sprintf(hexdumpBuffer+strlen(hexdumpBuffer), "%4.4x", STMemory_ReadWord(addr+j)); } } lineBuffer[0] = 0; if(positions[DISASM_COLUMN_ADDRESS] >= 0) Disass68kComposeStr(lineBuffer, addressBuffer, positions[DISASM_COLUMN_ADDRESS], 0); if(positions[DISASM_COLUMN_HEXDUMP] >= 0) Disass68kComposeStr(lineBuffer, hexdumpBuffer, positions[DISASM_COLUMN_HEXDUMP], positions[DISASM_COLUMN_LABEL]); if(positions[DISASM_COLUMN_LABEL] >= 0) Disass68kComposeStr(lineBuffer, labelBuffer, positions[DISASM_COLUMN_LABEL], 0); if(positions[DISASM_COLUMN_OPCODE] >= 0) Disass68kComposeStr(lineBuffer, opcodeBuffer, positions[DISASM_COLUMN_OPCODE], 0); if(positions[DISASM_COLUMN_OPERAND] >= 0) { size_t l = strlen(lineBuffer); if(lineBuffer[l-1] != ' ') // force at least one space between opcode and operand { lineBuffer[l++] = ' '; lineBuffer[l] = 0; } Disass68kComposeStr(lineBuffer, operandBuffer, positions[DISASM_COLUMN_OPERAND], 0); } if (positions[DISASM_COLUMN_COMMENT] >= 0) { if (Profile_CpuAddr_DataStr(commentBuffer, sizeof(commentBuffer), addr)) { Disass68kComposeStr(lineBuffer, commentBuffer, positions[DISASM_COLUMN_COMMENT]+1, 0); } /* show comments only if profile data is missing */ else if (commentBuffer[0]) { Disass68kComposeStr(lineBuffer, " ;", positions[DISASM_COLUMN_COMMENT], 0); Disass68kComposeStr(lineBuffer, commentBuffer, positions[DISASM_COLUMN_COMMENT]+3, 0); } } addr += len; if (f) fprintf(f, "%s\n", lineBuffer); } cs_close(&cshandle); if (nextpc) *nextpc = addr; } #endif /* HAVE_CAPSTONE_M68K */ /** * Calculate next PC address from given one, without output * @return next PC address */ uint32_t Disasm_GetNextPC(uint32_t pc) { char buf[256]; uaecptr nextpc; sm68k_disasm(buf, NULL, pc, &nextpc, -1); return nextpc; } /** * Call disassembly using the selected disassembly method, * either internal UAE one, or the stand alone disassembler above, * whichever is selected in Hatari configuration */ void Disasm (FILE *f, uaecptr addr, uaecptr *nextpc, int cnt) { #if HAVE_CAPSTONE_M68K if (!ConfigureParams.Debugger.bDisasmUAE) { Disass68k_loop (f, addr, nextpc, cnt); return; } #endif m68k_disasm_file_wrapper (f, addr, nextpc, addr, cnt); } /** * warn if flags for the other engine have been specified */ static void Disasm_CheckOptionEngine(Diss68kOptions opts) { const char *name; Diss68kOptions mask; if (ConfigureParams.Debugger.bDisasmUAE) { mask = uaeOptMask; name = "uae"; } else { mask = extOptMask; name = "ext"; } if (opts & ~mask) fprintf(stderr, "WARNING: '--disasm %s' does not support disassembly option(s) 0x%x!\n", name, opts & ~mask); } /** * query disassembly output column positions. */ void Disasm_GetColumns(int *pos) { pos[DISASM_COLUMN_ADDRESS] = positions[DISASM_COLUMN_ADDRESS]; pos[DISASM_COLUMN_HEXDUMP] = positions[DISASM_COLUMN_HEXDUMP]; pos[DISASM_COLUMN_LABEL] = positions[DISASM_COLUMN_LABEL]; pos[DISASM_COLUMN_OPCODE] = positions[DISASM_COLUMN_OPCODE]; pos[DISASM_COLUMN_OPERAND] = positions[DISASM_COLUMN_OPERAND]; pos[DISASM_COLUMN_COMMENT] = positions[DISASM_COLUMN_COMMENT]; } /** * set disassembly output column positions. */ void Disasm_SetColumns(int *pos) { positions[DISASM_COLUMN_ADDRESS] = pos[DISASM_COLUMN_ADDRESS]; positions[DISASM_COLUMN_HEXDUMP] = pos[DISASM_COLUMN_HEXDUMP]; positions[DISASM_COLUMN_LABEL] = pos[DISASM_COLUMN_LABEL]; positions[DISASM_COLUMN_OPCODE] = pos[DISASM_COLUMN_OPCODE]; positions[DISASM_COLUMN_OPERAND] = pos[DISASM_COLUMN_OPERAND]; positions[DISASM_COLUMN_COMMENT] = pos[DISASM_COLUMN_COMMENT]; } /** * function to disable given disassembly output 'column'. * input is current column positions in 'oldcols' array and * output is new column positions/values in 'newcols' array. * It's safe to use same array for both. */ void Disasm_DisableColumn(int column, const int *oldcols, int *newcols) { int i, diff = 0; assert(column >= 0 && column < DISASM_COLUMNS); if (column+1 < DISASM_COLUMNS) diff = oldcols[column+1] - oldcols[column]; for (i = 0; i < DISASM_COLUMNS; i++) { if (i && oldcols[i-1] > oldcols[i]) { printf("WARNING: disassembly columns aren't in the expected order!\n"); return; } if (i < column) newcols[i] = oldcols[i]; else if (i > column) newcols[i] = oldcols[i] - diff; else newcols[column] = DISASM_COLUMN_DISABLE; } } /** * Get current disassembly output option flags * @return current output flags */ int Disasm_GetOptions(void) { return options; } /** * Initialize disassembly options from config */ void Disasm_Init(void) { options = ConfigureParams.Debugger.nDisasmOptions; if (ConfigureParams.Debugger.bDisasmUAE) { if (options & doptOpcodesSmall) disasm_flags |= (DISASM_FLAG_LC_MNEMO | DISASM_FLAG_LC_SIZE); else disasm_flags &= ~(DISASM_FLAG_LC_MNEMO | DISASM_FLAG_LC_SIZE); if (options & doptRegisterSmall) disasm_flags |= DISASM_FLAG_LC_REG; else disasm_flags &= ~DISASM_FLAG_LC_REG; if (options & doptNoWords) disasm_flags &= ~DISASM_FLAG_WORDS; else disasm_flags |= DISASM_FLAG_WORDS; if (options & doptShowValues) disasm_flags |= (DISASM_FLAG_CC | DISASM_FLAG_EA | DISASM_FLAG_VAL | DISASM_FLAG_VAL_FORCE); else disasm_flags &= ~(DISASM_FLAG_CC | DISASM_FLAG_EA | DISASM_FLAG_VAL | DISASM_FLAG_VAL_FORCE); if (options & doptHexSmall) disasm_flags |= DISASM_FLAG_LC_HEX; else disasm_flags &= ~DISASM_FLAG_LC_HEX; disasm_init(); return; } /* ext disassembler */ if (options & doptNoWords) Disasm_DisableColumn(DISASM_COLUMN_HEXDUMP, defaultPositions, positions); else memcpy(positions, defaultPositions, sizeof(positions)); switch (ConfigureParams.System.nCpuLevel) { #ifdef HAVE_CAPSTONE_M68K case 0: optionCPUTypeMask = CS_MODE_M68K_000; break; case 1: optionCPUTypeMask = CS_MODE_M68K_010; break; case 2: optionCPUTypeMask = CS_MODE_M68K_020; break; case 3: optionCPUTypeMask = CS_MODE_M68K_030; break; case 4: optionCPUTypeMask = CS_MODE_M68K_040; break; case 5: optionCPUTypeMask = CS_MODE_M68K_060; break; /* special case: 5 => 060 */ #endif default: optionCPUTypeMask = 0; break; } } /** * Parse disasm command line option argument * @return error string (""=silent 'error') or NULL for success. */ const char *Disasm_ParseOption(const char *arg) { if (strcasecmp(arg, "help") == 0) { const struct { int flag; const char *desc; } option[] = { { doptNoSpace, "ext: no space after comma in the operands list" }, { doptOpcodesSmall, "opcodes in lower case" }, { doptRegisterSmall, "register names in lower case" }, { doptStackSP, "ext: stack pointer as 'SP', not 'A7'" }, { doptNoWords, "do not show hexa representation of instructions" }, { doptShowValues, "uae: show EA + CC values after instruction" }, { doptHexSmall, "uae: hex numbers in lower case" }, { 0, NULL } }; int i; fputs("Disassembly settings:\n" "\tuae - use CPU core internal disassembler\n" "\t (better instruction support)\n" "\text - use external disassembler\n" "\t (nicer output)\n" "\t - disassembly output option flags\n" "Flag values:\n", stderr); for (i = 0; option[i].desc; i++) { assert(option[i].flag == (1 << i)); fprintf(stderr, "\t0x%02x: %s\n", option[i].flag, option[i].desc); } fprintf(stderr, "Current settings are:\n\t--disasm %s --disasm 0x%x\n", ConfigureParams.Debugger.bDisasmUAE ? "uae" : "ext", ConfigureParams.Debugger.nDisasmOptions); return ""; } if (strcasecmp(arg, "uae") == 0) { fputs("Selected UAE CPU core internal disassembler.\n", stderr); fprintf(stderr, "Disassembly output flags are 0x%x.\n", options); ConfigureParams.Debugger.bDisasmUAE = true; Disasm_Init(); return NULL; } if (strcasecmp(arg, "ext") == 0) { #if HAVE_CAPSTONE_M68K fputs("Selected external disassembler.\n", stderr); fprintf(stderr, "Disassembly output flags are 0x%x.\n", options); ConfigureParams.Debugger.bDisasmUAE = false; Disasm_Init(); return NULL; #else return "external disassembler (capstone) not compiled into this binary"; #endif } if (isdigit((unsigned char)*arg)) { char *end; unsigned int newopt = strtol(arg, &end, 0); if (*end) { return "not a number"; } if ((newopt|extOptMask|uaeOptMask) != (extOptMask|uaeOptMask)) { return "unknown flags in the bitmask"; } if (newopt != options) { fprintf(stderr, "Changed CPU disassembly output flags from 0x%x to 0x%x.\n", options, newopt); ConfigureParams.Debugger.nDisasmOptions = options = newopt; Disasm_CheckOptionEngine(options); Disasm_Init(); } else fprintf(stderr, "No CPU disassembly options changed.\n"); return NULL; } return "invalid disasm option"; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/68kDisass.h000066400000000000000000000016371504763705000243550ustar00rootroot00000000000000/* Hatari - 68kDisass.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_68KDISASS_H #define HATARI_68KDISASS_H extern uint32_t Disasm_GetNextPC(uint32_t pc); extern void Disasm (FILE *f, uaecptr addr, uaecptr *nextpc, int cnt); enum { DISASM_COLUMN_ADDRESS = 0, DISASM_COLUMN_HEXDUMP, DISASM_COLUMN_LABEL, DISASM_COLUMN_OPCODE, DISASM_COLUMN_OPERAND, DISASM_COLUMN_COMMENT, DISASM_COLUMNS /* number of columns in disassembly output */ }; #define DISASM_COLUMN_DISABLE -1 extern void Disasm_GetColumns(int *columns); extern void Disasm_SetColumns(int *columns); extern void Disasm_DisableColumn(int column, const int *oldcols, int *newcols); extern const char* Disasm_ParseOption(const char *arg); extern int Disasm_GetOptions(void); void Disasm_Init(void); #endif /* HATARI_68KDISASS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/CMakeLists.txt000066400000000000000000000006461504763705000251640ustar00rootroot00000000000000 if(Readline_FOUND) include_directories(${READLINE_INCLUDE_DIR}) endif(Readline_FOUND) if(ENABLE_DSP_EMU) set(DSPDBG_C debugdsp.c) endif(ENABLE_DSP_EMU) add_library(Debug log.c debugui.c breakcond.c debugcpu.c debugInfo.c ${DSPDBG_C} evaluate.c history.c symbols.c vars.c profile.c profilecpu.c profiledsp.c natfeats.c console.c 68kDisass.c) target_link_libraries(Debug PRIVATE ${SDL2_LIBRARIES}) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/a.out.h000066400000000000000000000063621504763705000236240ustar00rootroot00000000000000/* a.out.h - Definitions and declarations for GNU-style a.out binaries. Written by Guido Flohr (gufl0000@stud.uni-sb.de). This file is in the public domain. */ #ifndef __A_OUT_GNU_H__ #define __A_OUT_GNU_H__ 1 struct nlist { union { const char *n_name; /* in memory address */ struct nlist *n_next; size_t n_strx; /* string table offset */ } n_un; unsigned char n_type; char n_other; short n_desc; uint32_t n_value; }; /* sizeof(struct nlist) on disk */ #define SIZEOF_STRUCT_NLIST 12 #define N_UNDF 0x00 /* undefined */ #define N_ABS 0x02 /* absolute */ #define N_TEXT 0x04 /* text */ #define N_DATA 0x06 /* data */ #define N_BSS 0x08 /* bss */ #define N_SIZE 0x0c /* pseudo type, defines a symbol's size */ #define N_FN 0x1f /* File name of a .o file */ #define N_COMM 0x12 /* common (internal to ld) */ #define N_EXT 0x01 /* external bit, or'ed in */ #define N_TYPE 0x1e /* mask for all the type bits */ #define N_STAB 0xe0 /* if any of these bits set, don't discard */ /* The following type indicates the definition of a symbol as being an indirect reference to another symbol. The other symbol appears as an undefined reference, immediately following this symbol. Indirection is asymmetrical. The other symbol's value will be used to satisfy requests for the indirect symbol, but not vice versa. If the other symbol does not have a definition, libraries will be searched to find a definition. */ #define N_INDR 0x0a /* The following symbols refer to set elements. All the N_SET[ATDB] symbols with the same name form one set. Space is allocated for the set in the text section, and each set element's value is stored into one word of the space. The first word of the space is the length of the set (number of elements). The address of the set is made into an N_SETV symbol whose name is the same as the name of the set. This symbol acts like a N_DATA global symbol in that it can satisfy undefined external references. */ /* These appear as input to LD, in a .o file. */ #define N_SETA 0x14 /* Absolute set element symbol */ #define N_SETT 0x16 /* Text set element symbol */ #define N_SETD 0x18 /* Data set element symbol */ #define N_SETB 0x1A /* Bss set element symbol */ /* This is output from LD. */ #define N_SETV 0x1C /* Pointer to set vector in data area. */ /* Warning symbol. The text gives a warning message, the next symbol in the table will be undefined. When the symbol is referenced, the message is printed. */ #define N_WARNING 0x1e /* Weak symbols. These are a GNU extension to the a.out format. The semantics are those of ELF weak symbols. Weak symbols are always externally visible. The N_WEAK? values are squeezed into the available slots. The value of a N_WEAKU symbol is 0. The values of the other types are the definitions. */ #define N_WEAKU 0x0d /* Weak undefined symbol. */ #define N_WEAKA 0x0e /* Weak absolute symbol. */ #define N_WEAKT 0x0f /* Weak text symbol. */ #define N_WEAKD 0x10 /* Weak data symbol. */ #define N_WEAKB 0x11 /* Weak bss symbol. */ #endif /* __A_OUT_GNU_H__ */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/breakcond.c000066400000000000000000001273501504763705000245220ustar00rootroot00000000000000/* Hatari - breakcond.c Copyright (c) 2009-2024 by Eero Tamminen This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. breakcond.c - code for breakpoint conditions that can check variable and memory values against each other, mask them etc. before deciding whether the breakpoint should be triggered. See BreakCond_Help() for the syntax. */ const char BreakCond_fileid[] = "Hatari breakcond.c"; #include #include #include "config.h" #include "main.h" #include "file.h" #include "m68000.h" #include "memorySnapShot.h" #include "dsp.h" #include "stMemory.h" #include "str.h" #include "vars.h" #include "debug_priv.h" #include "breakcond.h" #include "debugcpu.h" #include "debugdsp.h" #include "debugInfo.h" #include "debugui.h" #include "evaluate.h" #include "history.h" #include "symbols.h" #include "68kDisass.h" /* set to 1 to enable parsing function tracing / debug output */ #define DEBUG 0 /* needs to go through long long to handle x=32 */ #define BITMASK(x) ((uint32_t)(((unsigned long long)1<<(x))-1)) #define BC_DEFAULT_DSP_SPACE 'P' typedef struct { bool is_indirect; char dsp_space; /* DSP has P, X, Y address spaces, zero if not DSP */ value_t valuetype; /* Hatari value variable type */ union { uint32_t number; uint16_t (*func16)(void); uint32_t (*func32)(void); uint16_t *reg16; uint32_t *reg32; } value; uint32_t bits; /* CPU has 8/16/32 bit address widths */ uint32_t mask; /* && */ } bc_value_t; typedef struct { bc_value_t lvalue; bc_value_t rvalue; char comparison; bool track; /* track value changes */ } bc_condition_t; typedef struct { info_func_t info; /* pointer to specified ":info" function */ char *filename; /* file where to read commands to do on hit */ int skip; /* how many times to hit before breaking */ bool once; /* remove after hit&break */ bool quiet; /* set / hit breakpoint quietly */ bool trace; /* trace mode, don't break */ bool noinit; /* prevent debugger inits on break */ bool lock; /* tracing + show locked info */ bool deleted; /* delayed delete flag */ } bc_options_t; typedef struct { char *expression; bc_options_t options; bc_condition_t *conditions; int ccount; /* condition count */ int hits; /* how many times breakpoint hit */ } bc_breakpoint_t; typedef struct { bc_breakpoint_t *breakpoint; bc_breakpoint_t *breakpoint2delete; /* delayed delete of old alloc */ const char *name; int count; int allocated; bool delayed_change; const debug_reason_t reason; } bc_breakpoints_t; static bc_breakpoints_t CpuBreakPoints = { .name = "CPU", .reason = REASON_CPU_BREAKPOINT }; static bc_breakpoints_t DspBreakPoints = { .name = "DSP", .reason = REASON_DSP_BREAKPOINT }; /* forward declarations */ static void BreakCond_DoDelayedActions(bc_breakpoints_t *bps); static bool BreakCond_Remove(bc_breakpoints_t *bps, int position); static void BreakCond_Print(bc_breakpoint_t *bp); /** * Save breakpoints as debugger input file * return true for success, false for failure */ bool BreakCond_Save(const char *filename) { FILE *fp; int i; if (!(CpuBreakPoints.count || DspBreakPoints.count)) { if (File_Exists(filename)) { if (remove(filename)) { perror("ERROR"); return false; } } return true; } fprintf(stderr, "Saving breakpoints to '%s'...\n", filename); fp = fopen(filename, "w"); if (!fp) { perror("ERROR"); return false; } /* save conditional breakpoints as debugger input file */ for (i = 0; i < CpuBreakPoints.count; i++) { fprintf(fp, "b %s\n", CpuBreakPoints.breakpoint[i].expression); } for (i = 0; i < DspBreakPoints.count; i++) { fprintf(fp, "db %s\n", DspBreakPoints.breakpoint[i].expression); } fclose(fp); return true; } /* --------------------- debugging code ------------------- */ #if DEBUG /* see parsing code for usage examples */ static int _traceIndent; static void _spaces(void) { int spaces = _traceIndent; while(spaces-- > 0) { putchar(' '); /* fputc(' ',stdout); */ } } #define ENTERFUNC(args) { _traceIndent += 2; _spaces(); printf args ; fflush(stdout); } #define EXITFUNC(args) { _spaces(); printf args ; fflush(stdout); _traceIndent -= 2; } #else #define ENTERFUNC(args) #define EXITFUNC(args) #endif /* ------------- breakpoint condition checking, internals ------------- */ /** * Return value from given DSP memory space/address */ static uint32_t BreakCond_ReadDspMemory(uint32_t addr, const bc_value_t *bc_value) { const char *dummy; return DSP_ReadMemory(addr, bc_value->dsp_space, &dummy) & BITMASK(24); } /** * Return value of given size read from given ST memory address */ static uint32_t BreakCond_ReadSTMemory(uint32_t addr, const bc_value_t *bc_value) { switch (bc_value->bits) { case 32: return STMemory_ReadLong(addr); case 16: return STMemory_ReadWord(addr); case 8: return STMemory_ReadByte(addr); default: fprintf(stderr, "ERROR: unknown ST address size %d!\n", bc_value->bits); abort(); } } /** * Return uint32_t value according to given bc_value_t specification */ static uint32_t BreakCond_GetValue(const bc_value_t *bc_value) { uint32_t value; switch (bc_value->valuetype) { case VALUE_TYPE_NUMBER: value = bc_value->value.number; break; case VALUE_TYPE_FUNCTION32: value = bc_value->value.func32(); break; case VALUE_TYPE_REG16: value = *(bc_value->value.reg16); break; case VALUE_TYPE_VAR32: case VALUE_TYPE_REG32: value = *(bc_value->value.reg32); break; default: fprintf(stderr, "ERROR: unknown condition value size/type %d!\n", bc_value->valuetype); abort(); } if (bc_value->is_indirect) { if (bc_value->dsp_space) { value = BreakCond_ReadDspMemory(value, bc_value); } else { value = BreakCond_ReadSTMemory(value, bc_value); } } return (value & bc_value->mask); } /** * Show & update rvalue for a tracked breakpoint condition to lvalue */ static void BreakCond_UpdateTracked(bc_condition_t *condition, uint32_t value) { uint32_t addr; /* next monitor changes to this new value */ condition->rvalue.value.number = value; if (condition->lvalue.is_indirect && condition->lvalue.valuetype == VALUE_TYPE_NUMBER) { /* simple memory address */ addr = condition->lvalue.value.number; fprintf(stderr, " $%x = $%x\n", addr, value); } else { /* register etc. */ fprintf(stderr, " $%x\n", value); } } /** * Return true if all of the given breakpoint's conditions match */ static bool BreakCond_MatchConditions(bc_condition_t *condition, int count) { uint32_t lvalue, rvalue; bool hit = false; int i; for (i = 0; i < count; condition++, i++) { lvalue = BreakCond_GetValue(&(condition->lvalue)); rvalue = BreakCond_GetValue(&(condition->rvalue)); switch (condition->comparison) { case '<': hit = (lvalue < rvalue); break; case '>': hit = (lvalue > rvalue); break; case '=': hit = (lvalue == rvalue); break; case '!': hit = (lvalue != rvalue); break; default: fprintf(stderr, "ERROR: Unknown breakpoint value comparison operator '%c'!\n", condition->comparison); abort(); } if (likely(!hit)) { return false; } if (condition->track) { BreakCond_UpdateTracked(condition, lvalue); } } /* all conditions matched */ return true; } /** * Check and show which breakpoints' conditions matched * @return true if (non-tracing) breakpoint was hit, * or false if none matched */ static bool BreakCond_MatchBreakPoints(bc_breakpoints_t *bps) { bc_breakpoint_t *bp; bool changes = false; bool hit = false; int i; /* array should not be changed while it's being traversed */ assert(likely(!bps->delayed_change)); bps->delayed_change = true; bp = bps->breakpoint; for (i = 0; i < bps->count; bp++, i++) { if (BreakCond_MatchConditions(bp->conditions, bp->ccount)) { bp->hits++; if (bp->options.skip) { if (bp->hits % bp->options.skip) { /* check next */ continue; } } if (!bp->options.quiet) { fprintf(stderr, "%d. %s breakpoint condition(s) matched %d times.\n", i+1, bps->name, bp->hits); BreakCond_Print(bp); } History_Mark(bps->reason); if (bp->options.info || bp->options.lock || bp->options.filename) { bool reinit = !bp->options.noinit; if (reinit) { DebugCpu_InitSession(); DebugDsp_InitSession(); } if (bp->options.info) { bp->options.info(stderr, 0); } if (bp->options.lock) { DebugInfo_ShowSessionInfo(); } if (bp->options.filename) { bool verbose = !bp->options.quiet; DebugUI_ParseFile(bp->options.filename, reinit, verbose); changes = true; } } if (bp->options.once) { BreakCond_Remove(bps, i+1); changes = true; } if (!bp->options.trace) { /* index for current hit, they start from 1 */ hit = true; } /* continue checking breakpoints to make sure all relevant actions get performed */ } } bps->delayed_change = false; if (unlikely(changes)) { BreakCond_DoDelayedActions(bps); } return hit; } /* ------------- breakpoint condition checking, public API ------------- */ /** * Return true if there were CPU breakpoint hits, false otherwise. */ bool BreakCond_MatchCpu(void) { return BreakCond_MatchBreakPoints(&CpuBreakPoints); } /** * Return true if there were DSP breakpoint hits, false otherwise. */ bool BreakCond_MatchDsp(void) { return BreakCond_MatchBreakPoints(&DspBreakPoints); } /** * Return number of CPU condition breakpoints */ int BreakCond_CpuBreakPointCount(void) { return CpuBreakPoints.count; } /** * Return number of DSP condition breakpoints */ int BreakCond_DspBreakPointCount(void) { return DspBreakPoints.count; } /* -------------- breakpoint condition parsing, internals ------------- */ /* struct for passing around breakpoint conditions parsing state */ typedef struct { int arg; /* current arg */ int argc; /* arg count */ const char **argv; /* arg pointer array (+ strings) */ const char *error; /* error from parsing args */ } parser_state_t; /** * If given string is a Hatari variable name, set bc_value * fields accordingly and return true, otherwise return false. */ static bool BreakCond_ParseVariable(const char *name, bc_value_t *bc_value) { const var_addr_t *hvar; ENTERFUNC(("BreakCond_ParseVariable('%s')\n", name)); hvar = Vars_ParseVariable(name); if (hvar) { bc_value->value.reg32 = hvar->addr; bc_value->valuetype = hvar->vtype; bc_value->bits = hvar->bits; assert(bc_value->bits == 32 || bc_value->valuetype != VALUE_TYPE_VAR32); EXITFUNC(("-> true\n")); return true; } EXITFUNC(("-> false\n")); return false; } /** * If given string matches a suitable symbol, set bc_value * fields accordingly and return true, otherwise return false. */ static bool BreakCond_ParseSymbol(const char *name, bc_value_t *bc_value) { symtype_t symtype; uint32_t addr; ENTERFUNC(("BreakCond_ParseSymbol('%s')\n", name)); if (bc_value->is_indirect) { /* indirect use of address makes sense only for data */ symtype = SYMTYPE_DATA|SYMTYPE_BSS; } else { /* direct value can be compared for anything */ symtype = SYMTYPE_ALL; } if (bc_value->dsp_space) { if (!Symbols_GetDspAddress(symtype, name, &addr)) { EXITFUNC(("-> false (DSP)\n")); return false; } /* all DSP memory values are 24-bits */ bc_value->bits = 24; bc_value->value.number = addr; bc_value->valuetype = VALUE_TYPE_NUMBER; EXITFUNC(("-> true (DSP)\n")); return true; } if (!Symbols_GetCpuAddress(symtype, name, &addr)) { EXITFUNC(("-> false (CPU)\n")); return false; } if (addr & 1) { /* only bytes can be at odd addresses */ bc_value->bits = 8; } else { bc_value->bits = 32; } bc_value->value.number = addr; bc_value->valuetype = VALUE_TYPE_NUMBER; EXITFUNC(("-> true (CPU)\n")); return true; } /** * Helper function to get CPU PC register value with static inline as uint32_t */ static uint32_t GetCpuPC(void) { return M68000_GetPC(); } /** * Helper function to get CPU SR register value with static inline as uint32_t */ static uint32_t GetCpuSR(void) { return M68000_GetSR(); } /** * If given string is register name (for DSP or CPU), set bc_value * fields accordingly and return true, otherwise return false. */ static bool BreakCond_ParseRegister(const char *regname, bc_value_t *bc_value) { int regsize; ENTERFUNC(("BreakCond_ParseRegister('%s')\n", regname)); if (bc_value->dsp_space) { regsize = DSP_GetRegisterAddress(regname, &(bc_value->value.reg32), &(bc_value->mask)); if (regsize) { if (bc_value->is_indirect && toupper((unsigned char)regname[0]) != 'R') { fprintf(stderr, "ERROR: only R0-R7 DSP registers can be used for indirect addressing!\n"); EXITFUNC(("-> false (DSP)\n")); return false; } /* all DSP memory values are 24-bits */ bc_value->bits = 24; bc_value->valuetype = regsize; EXITFUNC(("-> true (DSP)\n")); return true; } EXITFUNC(("-> false (DSP)\n")); return false; } regsize = DebugCpu_GetRegisterAddress(regname, &(bc_value->value.reg32)); if (regsize) { bc_value->bits = regsize; /* valuetypes for registers are 16 & 32 */ bc_value->valuetype = regsize; EXITFUNC(("-> true (CPU)\n")); return true; } /* Exact UAE core 32-bit PC & 16-bit SR register values * can be gotten only through UAE accessors, not directly */ if (strcasecmp(regname, "PC") == 0) { bc_value->bits = 32; bc_value->value.func32 = GetCpuPC; bc_value->valuetype = VALUE_TYPE_FUNCTION32; EXITFUNC(("-> true (CPU)\n")); return true; } if (strcasecmp(regname, "SR") == 0) { bc_value->bits = 16; bc_value->value.func32 = GetCpuSR; bc_value->valuetype = VALUE_TYPE_FUNCTION32; EXITFUNC(("-> true (CPU)\n")); return true; } EXITFUNC(("-> false (CPU)\n")); return false; } /** * If given address is valid (for DSP or CPU), return true. */ static bool BreakCond_CheckAddress(bc_value_t *bc_value) { uint32_t addr = bc_value->value.number; int size = bc_value->bits >> 8; ENTERFUNC(("BreakCond_CheckAddress(%x)\n", addr)); if (bc_value->dsp_space) { if (addr+size > 0xFFFF) { EXITFUNC(("-> false (DSP)\n")); return false; } EXITFUNC(("-> true (DSP)\n")); return true; } if (!STMemory_CheckAreaType(addr, size, ABFLAG_RAM | ABFLAG_ROM | ABFLAG_IO)) { EXITFUNC(("-> false (CPU)\n")); return false; } EXITFUNC(("-> true (CPU)\n")); return true; } /** * Check for and parse a condition value address space/width modifier. * Modify pstate according to parsing (arg index and error string). * Return false for error and true for no or successfully parsed modifier. */ static bool BreakCond_ParseAddressModifier(parser_state_t *pstate, bc_value_t *bc_value) { char mode; ENTERFUNC(("BreakCond_ParseAddressModifier()\n")); if (pstate->arg+2 > pstate->argc || strcmp(pstate->argv[pstate->arg], ".") != 0) { if (bc_value->dsp_space && bc_value->is_indirect) { pstate->error = "DSP memory addresses need to specify address space"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } EXITFUNC(("arg:%d -> true (missing)\n", pstate->arg)); return true; } if (!bc_value->is_indirect) { pstate->error = "space/width modifier can be used only with an (address) expression\n" "(note that you can use a mask instead of width, for example: 'd0 & 0xff')"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } pstate->arg++; if (bc_value->dsp_space) { switch (pstate->argv[pstate->arg][0]) { case 'p': case 'x': case 'y': mode = toupper((unsigned char)pstate->argv[pstate->arg][0]); break; default: pstate->error = "invalid address space modifier"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } else { switch (pstate->argv[pstate->arg][0]) { case 'l': mode = 32; break; case 'w': mode = 16; break; case 'b': mode = 8; break; default: pstate->error = "invalid address width modifier"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } if (pstate->argv[pstate->arg][1]) { pstate->error = "invalid address space/width modifier"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } if (bc_value->dsp_space) { bc_value->dsp_space = mode; EXITFUNC(("arg:%d -> space:%c, true\n", pstate->arg, mode)); } else { bc_value->bits = mode; EXITFUNC(("arg:%d -> width:%d, true\n", pstate->arg, mode)); } pstate->arg++; return true; } /** * Check for and parse a condition value mask. * Modify pstate according to parsing (arg index and error string). * Return false for error and true for no or successfully parsed modifier. */ static bool BreakCond_ParseMaskModifier(parser_state_t *pstate, bc_value_t *bc_value) { ENTERFUNC(("BreakCond_ParseMaskModifier()\n")); if (pstate->arg+2 > pstate->argc || strcmp(pstate->argv[pstate->arg], "&") != 0) { EXITFUNC(("arg:%d -> true (missing)\n", pstate->arg)); return true; } if (bc_value->valuetype == VALUE_TYPE_NUMBER && !bc_value->is_indirect) { fprintf(stderr, "WARNING: plain numbers shouldn't need masks.\n"); } pstate->arg++; if (!Eval_Number(pstate->argv[pstate->arg], &(bc_value->mask), NUM_TYPE_NORMAL)) { pstate->error = "invalid dec/hex/bin value"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } if (bc_value->mask == 0 || (bc_value->valuetype == VALUE_TYPE_NUMBER && !bc_value->is_indirect && bc_value->value.number && !(bc_value->value.number & bc_value->mask))) { pstate->error = "mask zeroes value"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } EXITFUNC(("arg:%d -> true (%x)\n", pstate->arg, bc_value->mask)); pstate->arg++; return true; } /** * Parse a breakpoint condition value. * Modify pstate according to parsing (arg index and error string). * Return true for success and false for error. */ static bool BreakCond_ParseValue(parser_state_t *pstate, bc_value_t *bc_value) { const char *str; int skip = 1; ENTERFUNC(("BreakCond_Value()\n")); if (pstate->arg >= pstate->argc) { pstate->error = "value missing"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } /* parse indirection */ if (pstate->arg+3 <= pstate->argc) { if (strcmp(pstate->argv[pstate->arg+0], "(") == 0 && strcmp(pstate->argv[pstate->arg+2], ")") == 0) { bc_value->is_indirect = true; pstate->arg++; skip = 2; } } str = pstate->argv[pstate->arg]; if (isalpha((unsigned char)*str) || *str == '_') { /* parse direct or indirect variable/register/symbol name */ if (bc_value->is_indirect) { /* a valid register or data symbol name? */ if (!BreakCond_ParseRegister(str, bc_value) && !BreakCond_ParseSymbol(str, bc_value)) { pstate->error = "invalid register/symbol name for indirection"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } else { /* a valid Hatari variable or register name? * variables cannot be used for ST memory indirection. */ if (!BreakCond_ParseVariable(str, bc_value) && !BreakCond_ParseRegister(str, bc_value) && !BreakCond_ParseSymbol(str, bc_value)) { pstate->error = "invalid variable/register/symbol name"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } } else { /* a number */ if (!Eval_Number(str, &(bc_value->value.number), NUM_TYPE_NORMAL)) { pstate->error = "invalid dec/hex/bin value"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } /* memory address (indirect value) -> OK as address? */ if (bc_value->is_indirect && bc_value->valuetype == VALUE_TYPE_NUMBER && !BreakCond_CheckAddress(bc_value)) { pstate->error = "invalid address"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } pstate->arg += skip; /* parse modifiers */ if (!BreakCond_ParseAddressModifier(pstate, bc_value)) { EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } if (!BreakCond_ParseMaskModifier(pstate, bc_value)) { EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } EXITFUNC(("arg:%d -> true (%s value)\n", pstate->arg, (bc_value->is_indirect ? "indirect" : "direct"))); return true; } /** * Parse a breakpoint comparison character. * Modify pstate according to parsing (arg index and error string). * Return the character or nil for an error. */ static char BreakCond_ParseComparison(parser_state_t *pstate) { const char *comparison; ENTERFUNC(("BreakCond_ParseComparison(), arg:%d\n", pstate->arg)); if (pstate->arg >= pstate->argc) { pstate->error = "breakpoint comparison missing"; EXITFUNC(("-> false\n")); return false; } comparison = pstate->argv[pstate->arg]; switch (comparison[0]) { case '<': case '>': case '=': case '!': break; default: pstate->error = "invalid comparison character"; EXITFUNC(("-> false\n")); return false; } if (comparison[1]) { pstate->error = "trailing comparison character(s)"; EXITFUNC(("-> false\n")); return false; } pstate->arg++; if (pstate->arg >= pstate->argc) { pstate->error = "right side missing"; EXITFUNC(("-> false\n")); return false; } EXITFUNC(("-> '%c'\n", *comparison)); return *comparison; } /** * If no value, use the other value, if that also missing, use default */ static void BreakCond_InheritDefault(uint32_t *value1, uint32_t value2, uint32_t defvalue) { if (!*value1) { if (value2) { *value1 = value2; } else { *value1 = defvalue; } } } /** * Check & ensure that the masks and address sizes are sane * and allow comparison with the other side. * If yes, return true, otherwise false. */ static bool BreakCond_CrossCheckValues(parser_state_t *pstate, bc_value_t *bc_value1, bc_value_t *bc_value2) { uint32_t mask1, mask2, defbits; ENTERFUNC(("BreakCond_CrossCheckValues()\n")); /* make sure there're valid bit widths and that masks have some value */ if (bc_value1->dsp_space) { defbits = 24; } else { defbits = 32; } BreakCond_InheritDefault(&(bc_value1->bits), bc_value2->bits, defbits); BreakCond_InheritDefault(&(bc_value2->bits), bc_value1->bits, defbits); BreakCond_InheritDefault(&(bc_value1->mask), bc_value2->mask, BITMASK(bc_value1->bits)); BreakCond_InheritDefault(&(bc_value2->mask), bc_value1->mask, BITMASK(bc_value2->bits)); /* check first value mask & bit width */ mask1 = BITMASK(bc_value1->bits) & bc_value1->mask; if (mask1 != bc_value1->mask) { fprintf(stderr, "WARNING: mask 0x%x doesn't fit into %d address/register bits.\n", bc_value1->mask, bc_value1->bits); } if (!bc_value1->dsp_space && bc_value1->is_indirect && (bc_value1->value.number & 1) && bc_value1->bits > 8) { fprintf(stderr, "WARNING: odd CPU address 0x%x given without using byte (.b) width.\n", bc_value1->value.number); } /* cross-check both values masks */ mask2 = BITMASK(bc_value2->bits) & bc_value2->mask; if ((mask1 & mask2) == 0) { pstate->error = "values masks cancel each other"; EXITFUNC(("-> false\n")); return false; } if (bc_value2->is_indirect || bc_value2->value.number == 0 || bc_value2->valuetype != VALUE_TYPE_NUMBER) { EXITFUNC(("-> true (no problematic direct types)\n")); return true; } if ((bc_value2->value.number & mask1) != bc_value2->value.number) { pstate->error = "number doesn't fit the other side address width&mask"; EXITFUNC(("-> false\n")); return false; } EXITFUNC(("-> true\n")); return true; } /** * Parse given breakpoint conditions and append them to breakpoints. * Modify pstate according to parsing (arg index and error string). * Return number of added conditions or zero for failure. */ static int BreakCond_ParseCondition(parser_state_t *pstate, bool bForDsp, bc_breakpoint_t *bp, int ccount) { bc_condition_t condition; ENTERFUNC(("BreakCond_ParseCondition(...)\n")); /* setup condition */ memset(&condition, 0, sizeof(bc_condition_t)); if (bForDsp) { /* used also for checking whether value is for DSP */ condition.lvalue.dsp_space = BC_DEFAULT_DSP_SPACE; condition.rvalue.dsp_space = BC_DEFAULT_DSP_SPACE; } /* parse condition */ if (!BreakCond_ParseValue(pstate, &(condition.lvalue))) { EXITFUNC(("-> 0\n")); return 0; } condition.comparison = BreakCond_ParseComparison(pstate); if (!condition.comparison) { EXITFUNC(("-> 0\n")); return 0; } if (!BreakCond_ParseValue(pstate, &(condition.rvalue))) { EXITFUNC(("-> 0\n")); return 0; } if (!(BreakCond_CrossCheckValues(pstate, &(condition.lvalue), &(condition.rvalue)) && BreakCond_CrossCheckValues(pstate, &(condition.rvalue), &(condition.lvalue)))) { EXITFUNC(("-> 0\n")); return 0; } /* copy new condition */ ccount += 1; bp->conditions = realloc(bp->conditions, sizeof(bc_condition_t)*(ccount)); if (!bp->conditions) { pstate->error = "failed to allocate space for breakpoint condition"; EXITFUNC(("-> 0\n")); return 0; } bp->conditions[ccount-1] = condition; /* continue with next condition? */ if (pstate->arg == pstate->argc) { EXITFUNC(("-> %d conditions (all args parsed)\n", ccount)); return ccount; } if (strcmp(pstate->argv[pstate->arg], "&&") != 0) { pstate->error = "trailing content for breakpoint condition"; EXITFUNC(("-> 0\n")); return 0; } pstate->arg++; /* recurse conditions parsing */ ccount = BreakCond_ParseCondition(pstate, bForDsp, bp, ccount); if (!ccount) { EXITFUNC(("-> 0\n")); return 0; } EXITFUNC(("-> %d conditions (recursed)\n", ccount)); return ccount; } /** * Tokenize given breakpoint expression to given parser struct. * Return normalized expression string that corresponds to tokenization * or NULL on error. On error, pstate->error contains the error message * and pstate->arg index to invalid character (instead of to token like * after parsing). */ static char *BreakCond_TokenizeExpression(const char *expression, parser_state_t *pstate) { char separator[] = { '=', '!', '<', '>', /* comparison operators */ '(', ')', '.', '&', /* other separators */ '\0' /* terminator */ }; bool is_separated, has_comparison; char sep, *dst, *normalized; const char *src; int i, tokens; memset(pstate, 0, sizeof(parser_state_t)); /* _minimum_ safe size for normalized expression is 2x+1 */ normalized = malloc(2*strlen(expression)+1); if (!normalized) { pstate->error = "alloc failed"; return NULL; } /* check characters & normalize string */ dst = normalized; is_separated = false; has_comparison = false; for (src = expression; *src; src++) { /* discard white space in source */ if (isspace((unsigned char)*src)) { continue; } /* separate tokens with single space in destination */ for (i = 0; (sep = separator[i]); i++) { if (*src == sep) { if (dst > normalized) { /* don't separate boolean AND '&&' */ if (*src == '&' && *(src-1) == '&') { dst--; } else { if (!is_separated) { *dst++ = ' '; } } } *dst++ = *src; *dst++ = ' '; is_separated = true; if (i < 4) { has_comparison = true; } break; } } /* validate & copy other characters */ if (!sep) { /* variable/register/symbol or number prefix? */ if (!(isalnum((unsigned char)*src) || *src == '_' || *src == '$' || *src == '#' || *src == '%')) { pstate->error = "invalid character"; pstate->arg = src-expression; free(normalized); return NULL; } *dst++ = *src; is_separated = false; } } if (is_separated) { dst--; /* no trailing space */ } *dst = '\0'; if (!has_comparison) { pstate->error = "condition comparison missing"; pstate->arg = strlen(expression)/2; free(normalized); return NULL; } /* allocate exact space for tokenized string array + strings */ tokens = 1; for (dst = normalized; *dst; dst++) { if (*dst == ' ') { tokens++; } } pstate->argv = malloc(tokens*sizeof(char*)+strlen(normalized)+1); if (!pstate->argv) { pstate->error = "alloc failed"; free(normalized); return NULL; } /* and copy/tokenize... */ dst = (char*)(pstate->argv) + tokens*sizeof(char*); strcpy(dst, normalized); pstate->argv[0] = strtok(dst, " "); for (i = 1; (dst = strtok(NULL, " ")); i++) { pstate->argv[i] = dst; } assert(i == tokens); pstate->argc = tokens; #if DEBUG fprintf(stderr, "args->"); for (i = 0; i < tokens; i++) { fprintf(stderr, " %d: %s,", i, pstate->argv[i]); } fprintf(stderr, "\n"); #endif return normalized; } /** * Select current breakpoints struct and provide name for it. * Make sure there's always space for at least one additional breakpoint. * Return pointer to the breakpoints struct */ static bc_breakpoints_t* BreakCond_GetListInfo(bool bForDsp) { bc_breakpoints_t *bps; if (bForDsp) { bps = &DspBreakPoints; } else { bps = &CpuBreakPoints; } /* allocate (more) space for breakpoints when needed */ if (bps->count + 1 >= bps->allocated) { if (!bps->allocated) { /* initial count of available breakpoints */ bps->allocated = 16; } else { bps->allocated *= 2; } if (bps->delayed_change) { if(bps->breakpoint2delete) { /* getting second re-alloc within same breakpoint handler is really * unlikely, this would require adding dozens of new breakpoints. */ fprintf(stderr, "ERROR: too many new breakpoints added within single breakpoint hit!\n"); abort(); } bps->breakpoint2delete = bps->breakpoint; bps->breakpoint = malloc(bps->allocated * sizeof(bc_breakpoint_t)); } else { bps->breakpoint = realloc(bps->breakpoint, bps->allocated * sizeof(bc_breakpoint_t)); } assert(bps->breakpoint); } return bps; } /** * Check whether any of the breakpoint conditions is such that it's * intended for tracking given value changes (inequality comparison * on identical values) or for retrieving the current value to break * on next value change (other comparisons on identical values). * * On former case, mark it for tracking, on other cases, just * retrieve the value. */ static void BreakCond_CheckTracking(bc_breakpoint_t *bp) { bc_condition_t *condition; bool track = false; uint32_t value; int i; condition = bp->conditions; for (i = 0; i < bp->ccount; condition++, i++) { if (memcmp(&(condition->lvalue), &(condition->rvalue), sizeof(bc_value_t)) == 0) { /* set current value to right side */ value = BreakCond_GetValue(&(condition->rvalue)); condition->rvalue.value.number = value; condition->rvalue.valuetype = VALUE_TYPE_NUMBER; condition->rvalue.is_indirect = false; /* track those changes */ if (condition->comparison != '=') { condition->track = true; track = true; } else { fprintf(stderr, "\t%d. condition: %c $%x\n", i+1, condition->comparison, value); } } } if (track) { fprintf(stderr, "-> Track value changes, show value(s) when matched.\n"); } } /** * Parse given breakpoint expression and store it. * Return true for success and false for failure. */ static bool BreakCond_Parse(const char *expression, bc_options_t *options, bool bForDsp) { parser_state_t pstate; bc_breakpoints_t *bps; bc_breakpoint_t *bp; char *normalized; int ccount; bps = BreakCond_GetListInfo(bForDsp); bp = bps->breakpoint + bps->count; memset(bp, 0, sizeof(bc_breakpoint_t)); normalized = BreakCond_TokenizeExpression(expression, &pstate); if (normalized) { bp->expression = normalized; ccount = BreakCond_ParseCondition(&pstate, bForDsp, bp, 0); /* fail? */ if (!ccount) { bp->expression = NULL; if (bp->conditions) { /* free what was allocated by ParseCondition */ free(bp->conditions); bp->conditions = NULL; } } bp->ccount = ccount; } else { ccount = 0; } if (pstate.argv) { free(pstate.argv); } if (ccount > 0) { bps->count++; if (!options->quiet) { fprintf(stderr, "%s condition breakpoint %d with %d condition(s) added:\n\t%s\n", bps->name, bps->count, ccount, bp->expression); if (options->skip) { fprintf(stderr, "-> Break only on every %d hit.\n", options->skip); } if (options->once) { fprintf(stderr, "-> Break only once, and delete breakpoint afterwards.\n"); } if (options->trace) { fprintf(stderr, "-> Trace (just show breakpoint info, instead of dropping to debugger).\n"); /* all of these options enable also trace option */ if (options->info) { fprintf(stderr, "-> Call selected info command.\n"); } if (options->lock) { fprintf(stderr, "-> Call locked info command.\n"); } if (options->noinit) { fprintf(stderr, "-> Skip debugger initialization on hit.\n"); } } if (options->filename) { fprintf(stderr, "-> Execute debugger commands from '%s' file on hit.\n", options->filename); } } BreakCond_CheckTracking(bp); bp->options.quiet = options->quiet; bp->options.skip = options->skip; bp->options.once = options->once; bp->options.trace = options->trace; bp->options.info = options->info; bp->options.lock = options->lock; bp->options.noinit = options->noinit; if (options->filename) { bp->options.filename = strdup(options->filename); } } else { if (normalized) { int offset, i = 0; char *s = normalized; while (*s && i < pstate.arg) { if (*s++ == ' ') { i++; } } offset = s - normalized; /* show tokenized string and point out * the token where the error was encountered */ fprintf(stderr, "ERROR in tokenized string:\n'%s'\n%*c-%s\n", normalized, offset+2, '^', pstate.error); free(normalized); } else { /* show original string and point out the character * where the error was encountered */ fprintf(stderr, "ERROR in parsed string:\n'%s'\n%*c-%s\n", expression, pstate.arg+2, '^', pstate.error); } } return (ccount > 0); } /** * print single breakpoint */ static void BreakCond_Print(bc_breakpoint_t *bp) { fprintf(stderr, "\t%s", bp->expression); if (bp->options.skip) { fprintf(stderr, " :%d", bp->options.skip); } if (bp->options.once) { fprintf(stderr, " :once"); } if (bp->options.quiet) { fprintf(stderr, " :quiet"); } if (bp->options.trace) { fprintf(stderr, " :trace"); if (bp->options.info) { fprintf(stderr, " :info"); } if (bp->options.lock) { fprintf(stderr, " :lock"); } if (bp->options.noinit) { fprintf(stderr, " :noinit"); } } if (bp->options.filename) { fprintf(stderr, " :file %s", bp->options.filename); } if (bp->options.deleted) { fprintf(stderr, " (deleted)"); } fprintf(stderr, "\n"); } /** * List condition breakpoints */ static void BreakCond_List(bc_breakpoints_t *bps) { bc_breakpoint_t *bp; int i; if (!bps->count) { fprintf(stderr, "No conditional %s breakpoints.\n", bps->name); return; } fprintf(stderr, "%d conditional %s breakpoints:\n", bps->count, bps->name); bp = bps->breakpoint; for (i = 1; i <= bps->count; bp++, i++) { fprintf(stderr, "%4d:", i); BreakCond_Print(bp); } } /** * Remove condition breakpoint at given position */ static bool BreakCond_Remove(bc_breakpoints_t *bps, int position) { bc_breakpoint_t *bp; if (!bps->count) { fprintf(stderr, "No (more) %s breakpoints to remove.\n", bps->name); return false; } if (position < 1 || position > bps->count) { fprintf(stderr, "ERROR: No such %s breakpoint.\n", bps->name); return false; } bp = bps->breakpoint + (position - 1); if (bps->delayed_change) { bp->options.deleted = true; return true; } if (!bp->options.quiet) { fprintf(stderr, "Removed %s breakpoint %d:\n", bps->name, position); BreakCond_Print(bp); } free(bp->expression); free(bp->conditions); bp->expression = NULL; bp->conditions = NULL; if (bp->options.filename) { free(bp->options.filename); } if (position < bps->count) { memmove(bp, bp + 1, (bps->count - position) * sizeof(bc_breakpoint_t)); } bps->count--; return true; } /** * Remove all conditional breakpoints */ static void BreakCond_RemoveAll(bc_breakpoints_t *bps) { bool removed; int i; for (i = bps->count; i > 0; i--) { removed = BreakCond_Remove(bps, i); ASSERT_VARIABLE(removed); } fprintf(stderr, "%s breakpoints: %d\n", bps->name, bps->count); } /** * Do delayed breakpoint actions, remove breakpoints and old array alloc */ static void BreakCond_DoDelayedActions(bc_breakpoints_t *bps) { bc_options_t *options; bool removed; int i; assert(!bps->delayed_change); if (bps->breakpoint2delete) { free(bps->breakpoint2delete); bps->breakpoint2delete = NULL; } for (i = bps->count; i > 0; i--) { options = &(bps->breakpoint[i-1].options); if (options->deleted) { options->deleted = false; removed = BreakCond_Remove(bps, i); ASSERT_VARIABLE(removed); } } } /** * Return true if given CPU breakpoint has given CPU expression. * Used by the test code. */ int BreakCond_MatchCpuExpression(int position, const char *expression) { if (position < 1 || position > CpuBreakPoints.count) { return false; } if (strcmp(expression, CpuBreakPoints.breakpoint[position-1].expression)) { return false; } return true; } /* ------------- breakpoint condition parsing, public API ------------ */ static const char BreakCond_Help[] = " condition = [.mode] [& ] [.mode]\n" "\n" " where:\n" " value = [(] [)]\n" " number/mask = [#|$|%]\n" " comparison = '<' | '>' | '=' | '!'\n" " addressing mode (width) = 'b' | 'w' | 'l'\n" " addressing mode (space) = 'p' | 'x' | 'y'\n" "\n" " If the value is in parenthesis like in '($ff820)' or '(a0)', then\n" " the used value will be read from the memory address pointed by it.\n" "\n" " If the parsed value expressions on both sides of it are exactly\n" " the same, right side is replaced with its current value. For\n" " inequality ('!') comparison, the breakpoint will additionally track\n" " all further changes for the given address/register expression value.\n" " (This is useful for tracking register and memory value changes.)\n" "\n" " M68k addresses can have byte (b), word (w) or long (l, default) width.\n" " DSP addresses belong to different address spaces: P, X or Y. Note that\n" " on DSP only R0-R7 registers can be used for memory addressing.\n" "\n" " Examples:\n" " pc = $64543 && ($ff820).w & 3 = (a0) && d0 = %1100\n" " ($ffff9202).w ! ($ffff9202).w :trace\n" " (r0).x = 1 && (r0).y = 2\n" "\n" " For breakpoint options, see 'help b'.\n"; const char BreakCond_Description[] = " [&& ...] [:
[:
.\n" "\n" "\tBreakpoint action option alternatives:\n" "\t- 'trace', print the breakpoint match without stopping\n" "\t- 'lock', print the debugger entry info without stopping\n" "\t- 'once', delete the breakpoint after it's hit\n" "\t- 'quiet', set / hit breakpoint quietly\n" "\t- '', break only on every hit\n" "\n" "\tUse conditional breakpoint commands to manage the created\n" "\tbreakpoints."; /** * Set CPU & DSP program counter address breakpoints by converting * them to conditional breakpoints. * Return true for success and false for failure. */ bool BreakAddr_Command(char *args, bool bForDsp) { const char *errstr, *expression = (const char *)args; char *cut, command[32]; uint32_t addr; int offset; if (!args) { if (bForDsp) { DebugUI_PrintCmdHelp("dspaddress"); } else { DebugUI_PrintCmdHelp("address"); } return true; } /* split options */ if ((cut = strchr(args, ':'))) { *cut = '\0'; cut = Str_Trim(cut+1); if (strlen(cut) > 5) { cut[5] = '\0'; } } /* evaluate address expression */ errstr = Eval_Expression(expression, &addr, &offset, bForDsp); if (errstr) { fprintf(stderr, "ERROR in the address expression:\n'%s'\n%*c-%s\n", expression, offset+2, '^', errstr); return false; } /* add the address breakpoint with optional option */ sprintf(command, "pc=$%x %c%s", addr, cut?':':' ', cut?cut:""); if (!BreakCond_Command(command, bForDsp)) { return false; } /* on success, show on what instruction it was added */ if (bForDsp) { DSP_DisasmAddress(stderr, addr, addr); } else { uaecptr dummy; Disasm(stderr, (uaecptr)addr, &dummy, 1); } return true; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/breakcond.h000066400000000000000000000015421504763705000245210ustar00rootroot00000000000000/* Hatari - breakcond.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_BREAKCOND_H #define HATARI_BREAKCOND_H /* for debugui.c */ extern bool BreakCond_Save(const char *filename); /* for debugcpu.c & debugdsp.c */ extern const char BreakCond_Description[]; extern const char BreakAddr_Description[]; extern bool BreakCond_MatchCpu(void); extern bool BreakCond_MatchDsp(void); extern int BreakCond_CpuBreakPointCount(void); extern int BreakCond_DspBreakPointCount(void); extern bool BreakCond_Command(const char *expression, bool bForDsp); extern bool BreakAddr_Command(char *expression, bool bforDsp); /* extra functions exported for the test code */ extern int BreakCond_MatchCpuExpression(int position, const char *expression); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/console.c000066400000000000000000000170321504763705000242270ustar00rootroot00000000000000/* * Hatari - console.c * * Copyright (C) 2012-2015 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * console.c - catching of emulated console output with minimal VT52 emulation. */ const char Console_fileid[] = "Hatari console.c"; #include #include #include "main.h" #include "m68000.h" #include "stMemory.h" #include "hatari-glue.h" #include "console.h" #include "options.h" /* number of xconout devices to track */ int ConOutDevices; #define CONOUT_DEVICE_NONE 127 /* valid ones are 0-7 */ /* device number for xconout devices to track */ static int con_dev = CONOUT_DEVICE_NONE; static bool con_trace; /** * Set which Atari xconout device output goes to host console. * Returns true for valid device values (0-7), false otherwise */ bool Console_SetDevice(int dev) { if (dev < 0 || dev > 7) { return false; } if (con_dev == CONOUT_DEVICE_NONE) { ConOutDevices++; } con_dev = dev; return true; } /** * Enable / disable xconout 2 host output for tracing. * Overrides Console_SetDevice() device while enabled */ void Console_SetTrace(bool enable) { if (enable && !con_trace) { ConOutDevices++; } if (con_trace && !enable) { ConOutDevices--; } con_trace = enable; } /** * Maps Atari characters to their closest ASCII equivalents. */ static void map_character(uint8_t value) { static const uint8_t map_0_31[32] = { '.', '.', '.', '.', '.', '.', '.', '.', /* 0x00 */ /* white space */ '\b','\t','\n','.','.','\r', '.', '.', /* 0x08 */ /* LED numbers */ '0', '1', '2', '3', '4', '5', '6', '7', /* 0x10 */ '8', '9', '.', '.', '.', '.', '.', '.' /* 0x18 */ }; static const uint8_t map_128_255[128] = { /* accented characters */ 'C', 'U', 'e', 'a', 'a', 'a', 'a', 'c', /* 0x80 */ 'e', 'e', 'e', 'i', 'i', 'i', 'A', 'A', /* 0x88 */ 'E', 'a', 'A', 'o', 'o', 'o', 'u', 'u', /* 0x90 */ 'y', 'o', 'u', 'c', '.', 'Y', 'B', 'f', /* 0x98 */ 'a', 'i', 'o', 'u', 'n', 'N', 'a', 'o', /* 0xA0 */ '?', '.', '.', '.', '.', 'i', '<', '>', /* 0xA8 */ 'a', 'o', 'O', 'o', 'o', 'O', 'A', 'A', /* 0xB0 */ 'O', '"','\'', '.', '.', 'C', 'R', '.', /* 0xB8 */ 'j', 'J', '.', '.', '.', '.', '.', '.', /* 0xC0 */ '.', '.', '.', '.', '.', '.', '.', '.', /* 0xC8 */ '.', '.', '.', '.', '.', '.', '.', '.', /* 0xD0 */ '.', '.', '.', '.', '.', '.', '^', '.', /* 0xD8 */ '.', '.', '.', '.', '.', '.', '.', '.', /* 0xE0 */ '.', '.', '.', '.', '.', '.', '.', '.', /* 0xE8 */ '.', '.', '.', '.', '.', '.', '.', '.', /* 0xF0 */ '.', '.', '.', '.', '.', '.', '.', '.' /* 0xF8 */ }; /* map normal characters to host console */ if (value < 32) { fputc(map_0_31[value], stdout); } else if (value > 127) { fputc(map_128_255[value-128], stdout); } else { fputc(value, stdout); } } /** * Convert given console character output to ASCII. * Accepts one character at the time, parses VT52 escape codes * and outputs them on console. * * On host, TOS cursor forwards movement is done with spaces, * backwards movement is delayed until next non-white character * at which point output switches to next line. Other VT52 * escape sequences than cursor movement are ignored. */ static void vt52_emu(uint8_t value) { /* state machine to handle/ignore VT52 escape sequence */ static int escape_index; static int escape_target; static int hpos_host, hpos_tos; static bool need_nl; static enum { ESCAPE_NONE, ESCAPE_POSITION } escape_type; if (escape_target) { if (++escape_index == 1) { /* VT52 escape sequences */ switch(value) { case 'E': /* clear screen+home -> newline */ fputs("\n", stdout); hpos_host = 0; break; /* sequences with arguments */ case 'b': /* foreground color */ case 'c': /* background color */ escape_target = 2; return; case 'Y': /* cursor position */ escape_type = ESCAPE_POSITION; escape_target = 3; return; } } else if (escape_index < escape_target) { return; } if (escape_type == ESCAPE_POSITION) { /* last item gives horizontal position */ hpos_tos = value - ' '; if (hpos_tos > 79) { hpos_tos = 79; } else if (hpos_tos < 0) { hpos_tos = 0; } if (hpos_tos > hpos_host) { fprintf(stdout, "%*s", hpos_tos - hpos_host, ""); hpos_host = hpos_tos; } else if (hpos_tos < hpos_host) { need_nl = true; } } /* escape sequence end */ escape_target = 0; return; } if (value == 27) { /* escape sequence start */ escape_type = ESCAPE_NONE; escape_target = 1; escape_index = 0; return; } /* do newline & indent for backwards movement only when necessary */ if (need_nl) { /* TOS cursor horizontal movement until host output */ switch (value) { case ' ': hpos_tos++; return; case '\b': hpos_tos--; return; case '\t': hpos_tos = (hpos_tos + 8) & 0xfff0; return; case '\r': case '\n': hpos_tos = 0; break; } fputs("\n", stdout); if (hpos_tos > 0 && hpos_tos < 80) { fprintf(stdout, "%*s", hpos_tos, ""); hpos_host = hpos_tos; } else { hpos_host = 0; } need_nl = false; } /* host cursor horizontal movement */ switch (value) { case '\b': hpos_host--; break; case '\t': hpos_host = (hpos_host + 8) & 0xfff0; break; case '\r': case '\n': hpos_host = 0; break; default: hpos_host++; break; } map_character(value); } /** * Catch requested xconout vector calls and show their output on console */ void Console_Check(void) { uint32_t pc, xconout, stack, stackbeg, stackend; int increment, dev; uint16_t chr; if (con_trace) { dev = 2; } else { dev = con_dev; } /* xconout vector for requested device? */ xconout = STMemory_ReadLong(0x57e + dev * SIZE_LONG); pc = M68000_GetPC(); if (pc != xconout) { return; } /* assumptions about xconout function: * - c declaration: leftmost item on top of stackframe * - args: WORD device, WORD character to output * - can find the correct stackframe arguments by skipping * wrong looking stack content from intermediate functions * (bsr/jsr return addresses are > 0xff, local stack args * could be an issue but hopefully don't match device number * in any of the TOSes nor in MiNT or its conout devices) */ stackbeg = stack = Regs[REG_A7]; stackend = stack + 16; increment = SIZE_LONG; while (STMemory_ReadWord(stack) != dev) { stack += increment; if (stack > stackend) { if (increment == SIZE_LONG) { /* skipping return addresses not enough, * try skipping potential local args too */ Log_Printf(LOG_WARN, "xconout stack args not found by skipping return addresses, trying short skipping.\n"); increment = SIZE_WORD; stack = stackbeg; continue; } /* failed */ Log_Printf(LOG_WARN, "xconout args not found from stack.\n"); return; } } chr = STMemory_ReadWord(stack + SIZE_WORD); if (chr & 0xff00) { /* allow 0xff high byte (sign extension?) */ if ((chr & 0xff00) != 0xff00) { Log_Printf(LOG_WARN, "xconout character '%c' has unknown high byte bits: 0x%x.\n", chr&0xff, chr&0xff00); /* higher bits, assume not correct arg */ return; } chr &= 0xff; } switch(dev) { case 2: /* EmuTOS/TOS/MiNT/etc console, VT-52 terminal */ vt52_emu(chr); break; case 0: /* Printer/Parallel port */ case 1: /* Aux device, the RS-232 port */ case 3: /* MIDI port */ case 4: /* Keyboard port */ case 5: /* Raw screen device (no escape sequence / control char processing) */ case 6: /* ST compatible RS-232 port (Modem 1) */ case 7: /* SCC channel B (Modem 2) */ map_character(chr); break; } fflush(stdout); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/console.h000066400000000000000000000006031504763705000242300ustar00rootroot00000000000000/* * Hatari - profile.h * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CONSOLE_H #define HATARI_CONSOLE_H extern int ConOutDevices; extern bool Console_SetDevice(int dev); extern void Console_SetTrace(bool enable); extern void Console_Check(void); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugInfo.c000066400000000000000000000555441504763705000245010ustar00rootroot00000000000000/* Hatari - debuginfo.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. debuginfo.c - functions needed to show info about the atari HW & OS components and "lock" that info to be shown on entering the debugger. */ const char DebugInfo_fileid[] = "Hatari debuginfo.c"; #include #include #include #include "main.h" #include "acia.h" #include "bios.h" #include "blitter.h" #include "configuration.h" #include "crossbar.h" #include "debugInfo.h" #include "debugcpu.h" #include "debugdsp.h" #include "debugui.h" #include "debug_priv.h" #include "dmaSnd.h" #include "dsp.h" #include "evaluate.h" #include "file.h" #include "gemdos.h" #include "history.h" #include "ioMem.h" #include "ikbd.h" #include "m68000.h" #include "mfp.h" #include "nvram.h" #include "psg.h" #include "rtc.h" #include "stMemory.h" #include "scu_vme.h" #include "tos.h" #include "scc.h" #include "vdi.h" #include "video.h" #include "videl.h" #include "xbios.h" #include "newcpu.h" #include "68kDisass.h" /* ------------------------------------------------------------------ * TOS information */ #define OS_SYSBASE 0x4F2 #define OS_HEADER_SIZE 0x30 #define OS_PHYSTOP 0x42E #define COOKIE_JAR 0x5A0 #define BASEPAGE_SIZE 0x100 #define GEM_MAGIC 0x87654321 #define GEM_MUPB_SIZE 0xC #define RESET_MAGIC 0x31415926 #define RESET_VALID 0x426 #define RESET_VECTOR 0x42A #define COUNTRY_SPAIN 4 /** * DebugInfo_GetSysbase: get and validate system base * If warnings is set, output warnings if no valid system base * return on success sysbase address (+ set rombase), on failure return zero */ static uint32_t DebugInfo_GetSysbase(uint32_t *rombase, bool warnings) { uint32_t sysbase = STMemory_ReadLong(OS_SYSBASE); if ( !STMemory_CheckAreaType (sysbase, OS_HEADER_SIZE, ABFLAG_RAM | ABFLAG_ROM ) ) { if (warnings) { fprintf(stderr, "Invalid TOS sysbase RAM address (0x%x)!\n", sysbase); } return 0; } /* under TOS, sysbase = os_beg = TosAddress, but not under MiNT -> use os_beg */ *rombase = STMemory_ReadLong(sysbase+0x08); if ( !STMemory_CheckAreaType (*rombase, OS_HEADER_SIZE, ABFLAG_RAM | ABFLAG_ROM ) ) { if (warnings) { fprintf(stderr, "Invalid TOS sysbase ROM address (0x%x)!\n", *rombase); } *rombase = 0; } if (*rombase != TosAddress) { if (warnings) { fprintf(stderr, "os_beg (0x%x) != TOS address (0x%x), header in RAM not set up yet?\n", *rombase, TosAddress); } } return sysbase; } /** * DebugInfo_CurrentBasepage: get and validate currently running program basepage. * if given sysbase is zero, use system sysbase. * If warnings is set, output warnings if no valid basepage * return on success basepage address, on failure return zero */ static uint32_t DebugInfo_CurrentBasepage(uint32_t sysbase, bool warnings) { uint32_t basepage; uint16_t osversion, osconf; if (!sysbase) { uint32_t rombase; sysbase = DebugInfo_GetSysbase(&rombase, warnings); if (!sysbase) { return 0; } } osversion = STMemory_ReadWord(sysbase+0x02); if (osversion >= 0x0102) { basepage = STMemory_ReadLong(sysbase+0x28); } else { osconf = STMemory_ReadWord(sysbase+0x1C); if((osconf>>1) == COUNTRY_SPAIN) { basepage = 0x873C; } else { basepage = 0x602C; } } if ( STMemory_CheckAreaType ( basepage, 4, ABFLAG_RAM ) ) { return STMemory_ReadLong(basepage); } if (warnings) { fprintf(stderr, "Pointer 0x%06x to basepage address is invalid!\n", basepage); } return 0; } /** * GetBasepageValue: return basepage value at given offset in * TOS process basepage or zero if that is missing/invalid. */ static uint32_t GetBasepageValue(unsigned offset) { uint32_t basepage = DebugInfo_CurrentBasepage(0, false); if (!basepage) { return 0; } if ( !STMemory_CheckAreaType ( basepage, BASEPAGE_SIZE, ABFLAG_RAM ) || STMemory_ReadLong(basepage) != basepage) { return 0; } return STMemory_ReadLong(basepage+offset); } /** * DebugInfo_DTA: if no DTA address given, get one from current * basepage and ask GEMDOS to show its info. */ static void DebugInfo_DTA(FILE *fp, uint32_t dta_addr) { if (!dta_addr) { dta_addr = GetBasepageValue(0x20); if (!dta_addr) { fprintf(fp, "ERROR: no valid basepage!\n"); return; } } GemDOS_InfoDTA(fp, dta_addr); } /** * DebugInfo_GetTEXT: return current program TEXT segment address * or zero if basepage missing/invalid. For virtual debugger variable. */ uint32_t DebugInfo_GetTEXT(void) { return GetBasepageValue(0x08); } /** * DebugInfo_GetTEXTEnd: return address following current program TEXT segment * or zero if basepage missing/invalid. For virtual debugger variable. */ uint32_t DebugInfo_GetTEXTEnd(void) { uint32_t addr = GetBasepageValue(0x08); if (addr) { return addr + GetBasepageValue(0x0C); } return 0; } /** * DebugInfo_GetDATA: return current program DATA segment address * or zero if basepage missing/invalid. For virtual debugger variable. */ uint32_t DebugInfo_GetDATA(void) { return GetBasepageValue(0x010); } /** * DebugInfo_GetBSS: return current program BSS segment address * or zero if basepage missing/invalid. For virtual debugger variable. */ uint32_t DebugInfo_GetBSS(void) { return GetBasepageValue(0x18); } /** * DebugInfo_GetBASEPAGE: return current basepage address. */ uint32_t DebugInfo_GetBASEPAGE(void) { return DebugInfo_CurrentBasepage(0, false); } /** * output nil-terminated string from any Atari memory type */ static uint32_t print_mem_str(uint32_t addr, uint32_t end) { uint8_t chr; while (addr < end && (chr = STMemory_ReadByte(addr++))) { fputc(chr, stderr); } return addr; } /** * DebugInfo_Basepage: show TOS process basepage information * at given address. */ static void DebugInfo_Basepage(FILE *fp, uint32_t basepage) { uint8_t cmdlen; uint32_t addr; if (!basepage) { /* default to current process basepage */ basepage = DebugInfo_CurrentBasepage(0, true); if (!basepage) { return; } } fprintf(fp, "Process basepage (0x%x) information:\n", basepage); if ( !STMemory_CheckAreaType ( basepage, BASEPAGE_SIZE, ABFLAG_RAM ) || STMemory_ReadLong(basepage) != basepage) { fprintf(fp, "- address 0x%06x is invalid!\n", basepage); return; } fprintf(fp, "- TPA start : 0x%06x\n", STMemory_ReadLong(basepage)); fprintf(fp, "- TPA end +1 : 0x%06x\n", STMemory_ReadLong(basepage+0x04)); fprintf(fp, "- Text segment : 0x%06x\n", STMemory_ReadLong(basepage+0x08)); fprintf(fp, "- Text size : 0x%x\n", STMemory_ReadLong(basepage+0x0C)); fprintf(fp, "- Data segment : 0x%06x\n", STMemory_ReadLong(basepage+0x10)); fprintf(fp, "- Data size : 0x%x\n", STMemory_ReadLong(basepage+0x14)); fprintf(fp, "- BSS segment : 0x%06x\n", STMemory_ReadLong(basepage+0x18)); fprintf(fp, "- BSS size : 0x%x\n", STMemory_ReadLong(basepage+0x1C)); fprintf(fp, "- Process DTA : 0x%06x\n", STMemory_ReadLong(basepage+0x20)); fprintf(fp, "- Parent basepage: 0x%06x\n", STMemory_ReadLong(basepage+0x24)); addr = STMemory_ReadLong(basepage+0x2C); fprintf(fp, "- Environment : 0x%06x\n", addr); if ( STMemory_CheckAreaType ( addr, 4096, ABFLAG_RAM ) ) { uint32_t end = addr + 4096; while (addr < end && STMemory_ReadByte(addr)) { fprintf(fp, " '"); addr = print_mem_str(addr, end); addr = print_mem_str(addr, end); fprintf(fp, "'\n"); } } addr = basepage+0x80; cmdlen = STMemory_ReadByte(addr++); fprintf(fp, "- Command argslen: %d (at 0x%06x)\n", cmdlen, addr); if (cmdlen) { uint32_t end = addr + cmdlen; fprintf(fp, " '"); for (;;) { addr = print_mem_str(addr, end); if (addr >= end) { break; } fputc(' ', fp); } fprintf(fp, "'\n"); } } /** * DebugInfo_PrintOSHeader: output OS Header information */ static void DebugInfo_PrintOSHeader(FILE *fp, uint32_t sysbase) { uint32_t gemblock, basepage; uint16_t osversion, datespec, osconf, langbits; const char *lang; /* first more technical info */ osversion = STMemory_ReadWord(sysbase+0x02); fprintf(fp, "OS base addr : 0x%06x\n", sysbase); fprintf(fp, "OS RAM end+1 : 0x%06x\n", STMemory_ReadLong(sysbase+0x0C)); fprintf(fp, "Reset handler: 0x%06x\n", STMemory_ReadLong(sysbase+0x04)); fprintf(fp, "Reset vector : 0x%06x\n", STMemory_ReadLong(RESET_VECTOR)); fprintf(fp, "Reset valid : 0x%x (valid=0x%x)\n", STMemory_ReadLong(RESET_VALID), RESET_MAGIC); gemblock = STMemory_ReadLong(sysbase+0x14); fprintf(fp, "GEM Memory Usage Parameter Block:\n"); if ( STMemory_CheckAreaType ( gemblock, GEM_MUPB_SIZE, ABFLAG_RAM | ABFLAG_ROM ) ) { fprintf(fp, "- Block addr : 0x%06x\n", gemblock); fprintf(fp, "- GEM magic : 0x%x (valid=0x%x)\n", STMemory_ReadLong(gemblock), GEM_MAGIC); fprintf(fp, "- GEM entry : 0x%06x\n", STMemory_ReadLong(gemblock+4)); fprintf(fp, "- GEM end : 0x%06x\n", STMemory_ReadLong(gemblock+8)); } else { fprintf(fp, "- is at INVALID 0x%06x address.\n", gemblock); } if (osversion >= 0x0102) { /* last 3 OS header fields are only available as of TOS 1.02 */ fprintf(fp, "Memory pool : 0x%06x\n", STMemory_ReadLong(sysbase+0x20)); fprintf(fp, "Kbshift addr : 0x%06x\n", STMemory_ReadLong(sysbase+0x24)); } else { /* TOS 1.0 */ fprintf(fp, "Memory pool : 0x0056FA\n"); fprintf(fp, "Kbshift addr : 0x000E1B\n"); } basepage = DebugInfo_CurrentBasepage(sysbase, true); if (basepage) { fprintf(fp, "Basepage : 0x%06x\n", basepage); } /* and then basic TOS information */ fputs("\n", fp); fprintf(fp, "TOS version : 0x%x%s\n", osversion, bIsEmuTOS ? " (EmuTOS)" : ""); /* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */ datespec = STMemory_ReadWord(sysbase+0x1E); fprintf(fp, "Build date : %04d-%02d-%02d\n", (datespec >> 9) + 1980, (datespec & 0x1E0) >> 5, datespec & 0x1f); osconf = STMemory_ReadWord(sysbase+0x1C); langbits = osconf >> 1; lang = TOS_LanguageName(langbits); fprintf(fp, "OS config : %s (0x%x), %s (%d)\n", osconf&1 ? "PAL":"NTSC", osconf, lang, langbits); fprintf(fp, "Phystop : %d KB\n", (STMemory_ReadLong(OS_PHYSTOP) + 511) / 1024); } /** * DebugInfo_OSHeader: display TOS OS Header and RAM one * if their addresses differ */ static void DebugInfo_OSHeader(FILE *fp, uint32_t dummy) { uint32_t sysbase, rombase; sysbase = DebugInfo_GetSysbase(&rombase, true); if (!sysbase) { return; } fprintf(fp, "OS header information:\n"); DebugInfo_PrintOSHeader(fp, sysbase); if (sysbase != rombase && rombase) { fprintf(fp, "\nROM TOS OS header information:\n"); DebugInfo_PrintOSHeader(fp, rombase); return; } } /** * DebugInfo_Cookiejar: display TOS Cookiejar content */ static void DebugInfo_Cookiejar(FILE *fp, uint32_t dummy) { int items; uint32_t jar = STMemory_ReadLong(COOKIE_JAR); if (!jar) { fprintf(fp, "Cookiejar is empty.\n"); return; } fprintf(fp, "Cookiejar contents:\n"); items = 0; while ( STMemory_CheckAreaType (jar, 8, ABFLAG_RAM ) && STMemory_ReadLong(jar)) { fprintf(fp, "%c%c%c%c = 0x%08x\n", STMemory_ReadByte(jar+0), STMemory_ReadByte(jar+1), STMemory_ReadByte(jar+2), STMemory_ReadByte(jar+3), STMemory_ReadLong(jar+4)); jar += 8; items++; } fprintf(fp, "%d items at 0x%06x.\n", items, STMemory_ReadLong(COOKIE_JAR)); } /* ------------------------------------------------------------------ * CPU and DSP information wrappers */ /** * Helper to call debugcpu.c and debugdsp.c debugger commands */ static void DebugInfo_CallCommand(int (*func)(int, char* []), const char *command, uint32_t arg) { char cmdbuffer[16], argbuffer[12]; char *argv[] = { cmdbuffer, NULL }; int argc = 1; assert(strlen(command) < sizeof(cmdbuffer)); strcpy(cmdbuffer, command); if (arg) { sprintf(argbuffer, "$%x", arg); argv[argc++] = argbuffer; } func(argc, argv); } static void DebugInfo_CpuRegister(FILE *fp, uint32_t arg) { DebugInfo_CallCommand(DebugCpu_Register, "register", arg); } static void DebugInfo_CpuDisAsm(FILE *fp, uint32_t arg) { DebugInfo_CallCommand(DebugCpu_DisAsm, "disasm", arg); } static void DebugInfo_CpuMemDump(FILE *fp, uint32_t arg) { DebugInfo_CallCommand(DebugCpu_MemDump, "memdump", arg); } #if ENABLE_DSP_EMU static void DebugInfo_DspRegister(FILE *fp, uint32_t arg) { DebugInfo_CallCommand(DebugDsp_Register, "dspreg", arg); } static void DebugInfo_DspDisAsm(FILE *fp, uint32_t arg) { DebugInfo_CallCommand(DebugDsp_DisAsm, "dspdisasm", arg); } static void DebugInfo_DspMemDump(FILE *fp, uint32_t arg) { char cmdbuf[] = "dspmemdump"; char addrbuf[6], spacebuf[2] = "X"; char *argv[] = { cmdbuf, spacebuf, addrbuf }; spacebuf[0] = (arg>>16)&0xff; sprintf(addrbuf, "$%x", (uint16_t)(arg&0xffff)); DebugDsp_MemDump(3, argv); } /** * Convert arguments to uint32_t arg suitable for DSP memdump callback */ static uint32_t DebugInfo_DspMemArgs(int argc, char *argv[]) { uint32_t value; char space; if (argc != 2) { return 0; } space = toupper((unsigned char)argv[0][0]); if ((space != 'X' && space != 'Y' && space != 'P') || argv[0][1]) { fprintf(stderr, "ERROR: invalid DSP address space '%s'!\n", argv[0]); return 0; } if (!Eval_Number(argv[1], &value, NUM_TYPE_DSP) || value > 0xffff) { fprintf(stderr, "ERROR: invalid DSP address '%s'!\n", argv[1]); return 0; } return ((uint32_t)space<<16) | value; } #endif /* ENABLE_DSP_EMU */ static void DebugInfo_RegAddr(FILE *fp, uint32_t arg) { bool forDsp; char regname[3]; uint32_t *reg32, regvalue, mask; char cmdbuf[6], addrbuf[12]; char *argv[] = { cmdbuf, addrbuf }; regname[0] = (arg>>24)&0xff; regname[1] = (arg>>16)&0xff; regname[2] = '\0'; if (DebugCpu_GetRegisterAddress(regname, ®32)) { regvalue = *reg32; mask = 0xffffffff; forDsp = false; } else { int regsize = DSP_GetRegisterAddress(regname, ®32, &mask); switch (regsize) { /* currently regaddr supports only 32-bit Rx regs, but maybe later... */ case 16: regvalue = *((uint16_t*)reg32); break; case 32: regvalue = *reg32; break; default: fprintf(stderr, "ERROR: invalid address/data register '%s'!\n", regname); return; } forDsp = true; } sprintf(addrbuf, "$%x", regvalue & mask); if ((arg & 0xff) == 'D') { if (forDsp) { #if ENABLE_DSP_EMU strcpy(cmdbuf, "dd"); DebugDsp_DisAsm(2, argv); #endif } else { strcpy(cmdbuf, "d"); DebugCpu_DisAsm(2, argv); } } else { if (forDsp) { #if ENABLE_DSP_EMU /* use "Y" address space */ char cmd[] = "dm"; char space[] = "y"; char *dargv[] = { cmd, space, addrbuf }; DebugDsp_MemDump(3, dargv); #endif } else { strcpy(cmdbuf, "m"); DebugCpu_MemDump(2, argv); } } } /** * Convert arguments to uint32_t arg suitable for RegAddr callback */ static uint32_t DebugInfo_RegAddrArgs(int argc, char *argv[]) { uint32_t value, *regaddr; if (argc != 2) { return 0; } if (strcmp(argv[0], "disasm") == 0) { value = 'D'; } else if (strcmp(argv[0], "memdump") == 0) { value = 'M'; } else { fprintf(stderr, "ERROR: regaddr operation can be only 'disasm' or 'memdump', not '%s'!\n", argv[0]); return 0; } if (strlen(argv[1]) != 2 || (!DebugCpu_GetRegisterAddress(argv[1], ®addr) && (toupper((unsigned char)argv[1][0]) != 'R' || !isdigit((unsigned char)argv[1][1]) || argv[1][2]))) { /* not CPU register or Rx DSP register */ fprintf(stderr, "ERROR: invalid address/data register '%s'!\n", argv[1]); return 0; } value |= argv[1][0] << 24; value |= argv[1][1] << 16; value &= 0xffff00ff; return value; } /* ------------------------------------------------------------------ * wrappers for command to parse debugger input file */ /* file name to be given before calling the Parse function, * needs to be set separately as it's a host pointer which * can be 64-bit i.e. may not fit into uint32_t. */ static char *parse_filename; /** * Parse and exec commands in the previously given debugger input file */ static void DebugInfo_FileParse(FILE *fp, uint32_t dummy) { if (parse_filename) { DebugUI_ParseFile(parse_filename, true, true); } else { fputs("ERROR: debugger input file name to parse isn't set!\n", stderr); } } /** * Set which input file to parse. * Return true if file exists, false on error */ static uint32_t DebugInfo_FileArgs(int argc, char *argv[]) { if (argc != 1) { return false; } if (!File_Exists(argv[0])) { fprintf(stderr, "ERROR: given file '%s' doesn't exist!\n", argv[0]); return false; } if (parse_filename) { free(parse_filename); } parse_filename = strdup(argv[0]); return true; } /* ------------------------------------------------------------------ * Debugger & readline TAB completion integration */ /** * Default information on entering the debugger */ static void DebugInfo_Default(FILE *fp, uint32_t dummy) { int hbl, fcycles, lcycles; uaecptr nextpc, pc = M68000_GetPC(); Video_GetPosition(&fcycles, &hbl, &lcycles); fprintf(fp, "\nCPU=$%x, VBL=%d, FrameCycles=%d, HBL=%d, LineCycles=%d, DSP=", pc, nVBLs, fcycles, hbl, lcycles); if (bDspEnabled) fprintf(fp, "$%x\n", DSP_GetPC()); else fprintf(fp, "N/A\n"); Disasm(fp, pc, &nextpc, 1); } static const struct { /* if overlaps with other functionality, list only for lock command */ bool lock; const char *name; info_func_t func; /* convert args in argv into single uint32_t for func */ uint32_t (*args)(int argc, char *argv[]); const char *info; } infotable[] = { { false,"acia", ACIA_Info, NULL, "Show ACIA register contents" }, { false,"aes", AES_Info, NULL, "Show AES vector contents (with , show opcodes)" }, { false,"basepage", DebugInfo_Basepage, NULL, "Show program basepage contents at given
" }, { false,"bios", Bios_Info, NULL, "Show BIOS opcodes" }, { false,"blitter", Blitter_Info, NULL, "Show Blitter register contents" }, { false,"cookiejar", DebugInfo_Cookiejar, NULL, "Show TOS Cookiejar contents" }, { false,"crossbar", Crossbar_Info, NULL, "Show Falcon Crossbar register contents" }, { true, "default", DebugInfo_Default, NULL, "Show default debugger entry information" }, { true, "disasm", DebugInfo_CpuDisAsm, NULL, "Disasm CPU from PC or given
" }, { false,"dmasnd", DmaSnd_Info, NULL, "Show Sound DMA / LMC register contents" }, #if ENABLE_DSP_EMU { false, "dsp", DSP_Info, NULL, "Show misc. DSP core info (stack etc)" }, { true, "dspdisasm", DebugInfo_DspDisAsm, NULL, "Disasm DSP from given
" }, { true, "dspmemdump",DebugInfo_DspMemDump, DebugInfo_DspMemArgs, "Dump DSP memory from given
" }, { true, "dspregs", DebugInfo_DspRegister,NULL, "Show DSP register contents" }, #endif { false, "dta", DebugInfo_DTA, NULL, "Show current [or given] DTA information" }, { true, "file", DebugInfo_FileParse, DebugInfo_FileArgs, "Parse commands from given debugger input " }, { false,"gemdos", GemDOS_Info, NULL, "Show GEMDOS HDD emu information (with , show opcodes)" }, { true, "history", History_Show, NULL, "Show history of last instructions" }, { false,"ikbd", IKBD_Info, NULL, "Show IKBD (SCI) register contents" }, { true, "memdump", DebugInfo_CpuMemDump, NULL, "Dump CPU memory from given
" }, { false,"mfp", MFP_Info, NULL, "Show MFP register contents" }, { false,"mmu", M68000_MMU_Info, NULL, "Show MMU register contents" }, { false,"nvram", NvRam_Info, NULL, "Show (TT/Falcon) NVRAM contents" }, { false,"osheader", DebugInfo_OSHeader, NULL, "Show TOS OS header contents" }, { true, "regaddr", DebugInfo_RegAddr, DebugInfo_RegAddrArgs, "Show from CPU/DSP address pointed by " }, { true, "registers", DebugInfo_CpuRegister,NULL, "Show CPU register contents" }, { false,"rtc", Rtc_Info, NULL, "Show (Mega ST/STE) RTC register contents" }, { false,"scc", SCC_Info, NULL, "Show SCC register contents" }, { false,"scu", SCU_Info, NULL, "Show SCU/VME register information" }, { false,"vdi", VDI_Info, NULL, "Show VDI vector contents (with , show opcodes)" }, { false,"videl", Videl_Info, NULL, "Show Falcon Videl register contents" }, { false,"video", Video_Info, NULL, "Show Video information" }, { false,"xbios", XBios_Info, NULL, "Show XBIOS opcodes" }, { false,"ym", PSG_Info, NULL, "Show YM-2149 register contents" }, }; static int LockedFunction = 7; /* index for the "default" function */ static uint32_t LockedArgument; /** * Show selected debugger session information * (when debugger is (again) entered) */ void DebugInfo_ShowSessionInfo(void) { infotable[LockedFunction].func(stderr, LockedArgument); } /** * Return info function matching the given name, or NULL for no match */ info_func_t DebugInfo_GetInfoFunc(const char *name) { int i; for (i = 0; i < ARRAY_SIZE(infotable); i++) { if (strcmp(name, infotable[i].name) == 0) { return infotable[i].func; } } return NULL; } /** * Readline match callback for info subcommand name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ static char *DebugInfo_Match(const char *text, int state, bool lock) { static int i, len; const char *name; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i++ < ARRAY_SIZE(infotable)) { if (!lock && infotable[i-1].lock) { continue; } name = infotable[i-1].name; if (strncmp(name, text, len) == 0) return (strdup(name)); } return NULL; } char *DebugInfo_MatchLock(const char *text, int state) { return DebugInfo_Match(text, state, true); } char *DebugInfo_MatchInfo(const char *text, int state) { return DebugInfo_Match(text, state, false); } /** * Show requested command information. */ int DebugInfo_Command(int nArgc, char *psArgs[]) { uint32_t value; const char *cmd; bool ok, lock; int i, sub; sub = -1; if (nArgc > 1) { cmd = psArgs[1]; /* which subcommand? */ for (i = 0; i < ARRAY_SIZE(infotable); i++) { if (strcmp(cmd, infotable[i].name) == 0) { sub = i; break; } } } if (sub >= 0 && infotable[sub].args) { /* value needs callback specific conversion */ value = infotable[sub].args(nArgc-2, psArgs+2); ok = !!value; } else { if (nArgc > 2) { /* value is normal number */ ok = Eval_Number(psArgs[2], &value, NUM_TYPE_NORMAL); } else { value = 0; ok = true; } } lock = (strcmp(psArgs[0], "lock") == 0); if (sub < 0 || !ok) { /* no subcommand or something wrong with value, show info */ fprintf(stderr, "%s subcommands are:\n", psArgs[0]); for (i = 0; i < ARRAY_SIZE(infotable); i++) { if (!lock && infotable[i].lock) { continue; } fprintf(stderr, "- %s: %s\n", infotable[i].name, infotable[i].info); } return DEBUGGER_CMDDONE; } if (lock) { /* lock given subcommand and value */ LockedFunction = sub; LockedArgument = value; fprintf(stderr, "Locked %s output.\n", psArgs[1]); } else { /* do actual work */ infotable[sub].func(stderr, value); } return DEBUGGER_CMDDONE; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugInfo.h000066400000000000000000000017061504763705000244750ustar00rootroot00000000000000/* Hatari - debugInfo.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Public atari components debugging infos header. */ #ifndef HATARI_DEBUGINFO_H #define HATARI_DEBUGINFO_H /* for breakcond.c & profile.c */ extern uint32_t DebugInfo_GetTEXT(void); extern uint32_t DebugInfo_GetTEXTEnd(void); extern uint32_t DebugInfo_GetDATA(void); extern uint32_t DebugInfo_GetBSS(void); extern uint32_t DebugInfo_GetBASEPAGE(void); /* for debugui.c */ extern void DebugInfo_ShowSessionInfo(void); extern char *DebugInfo_MatchInfo(const char *text, int state); extern char *DebugInfo_MatchLock(const char *text, int state); extern int DebugInfo_Command(int nArgc, char *psArgs[]); /* for breakpoint ":info" callbacks */ typedef void (*info_func_t)(FILE *fp, uint32_t arg); extern info_func_t DebugInfo_GetInfoFunc(const char *name); #endif /* HATARI_DEBUGINFO_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debug_priv.h000066400000000000000000000030011504763705000247070ustar00rootroot00000000000000/* Hatari - debug.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Internal header used by debugger files. */ #ifndef HATARI_DEBUG_PRIV_H #define HATARI_DEBUG_PRIV_H /* internal defines for checks */ #define TTRAM_START 0x01000000 #define CART_START 0xFA0000 #define CART_END 0xFC0000 /* DebugUI command structure */ typedef struct { int (*pFunction)(int argc, char *argv[]); char* (*pMatch)(const char *, int); const char *sLongName; const char *sShortName; const char *sShortDesc; const char *sUsage; bool bNoParsing; } dbgcommand_t; /* Output file debugger output */ extern FILE *debugOutput; extern int DebugUI_PrintCmdHelp(const char *psCmd); extern int DebugUI_GetPageLines(int config, int defvalue); extern void DebugUI_PrintBinary(FILE *fp, int minwidth, uint32_t value); extern char *DebugUI_MatchHelper(const char **strings, int items, const char *text, int state); extern bool DebugUI_ParseFile(const char *path, bool reinit, bool verbose); extern bool DebugUI_DoQuitQuery(const char *info); extern int DebugCpu_Init(const dbgcommand_t **table); extern void DebugCpu_InitSession(void); #ifdef ENABLE_DSP_EMU extern int DebugDsp_Init(const dbgcommand_t **table); extern void DebugDsp_InitSession(void); #else /* !ENABLE_DSP_EMU */ static inline int DebugDsp_Init(const dbgcommand_t **t) { *t = NULL; return 0; } #define DebugDsp_InitSession() #endif /* !ENABLE_DSP_EMU */ #endif /* HATARI_DEBUG_PRIV_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugcpu.c000066400000000000000000001061161504763705000243650ustar00rootroot00000000000000/* Hatari - debugcpu.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. debugcpu.c - function needed for the CPU debugging tasks like memory and register dumps. */ const char DebugCpu_fileid[] = "Hatari debugcpu.c"; #include #include #include #include "config.h" #include "main.h" #include "breakcond.h" #include "configuration.h" #include "debugui.h" #include "debug_priv.h" #include "debugcpu.h" #include "evaluate.h" #include "hatari-glue.h" #include "history.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "profile.h" #include "stMemory.h" #include "str.h" #include "symbols.h" #include "68kDisass.h" #include "console.h" #include "options.h" #include "tos.h" #include "str.h" #include "vars.h" #define MEMDUMP_COLS 16 /* memdump, number of bytes per row */ #define NON_PRINT_CHAR '.' /* character to display for non-printables */ static uint32_t disasm_addr; /* disasm address */ static uint32_t memdump_addr; /* memdump address */ static uint32_t fake_regs[8]; /* virtual debugger "registers" */ static bool bFakeRegsUsed; /* whether to show virtual regs */ static bool bCpuProfiling; /* Whether CPU profiling is activated */ static int nCpuActiveCBs = 0; /* Amount of active conditional breakpoints */ static int nCpuSteps = 0; /* Amount of steps for CPU single-stepping */ /** * Load a binary file to a memory address. */ static int DebugCpu_LoadBin(int nArgc, char *psArgs[]) { FILE *fp; unsigned char c; uint32_t address; int i=0; if (nArgc < 3) { return DebugUI_PrintCmdHelp(psArgs[0]); } if (!Eval_Number(psArgs[2], &address, NUM_TYPE_CPU)) { fprintf(stderr, "Invalid address!\n"); return DEBUGGER_CMDDONE; } if ((fp = fopen(psArgs[1], "rb")) == NULL) { fprintf(stderr, "Cannot open file '%s'!\n", psArgs[1]); return DEBUGGER_CMDDONE; } /* TODO: more efficient would be to: * - check file size * - verify that it fits into valid memory area * - flush emulated CPU data cache * - read file contents directly into memory */ c = fgetc(fp); while (!feof(fp)) { i++; STMemory_WriteByte(address++, c); c = fgetc(fp); } fprintf(stderr," Read 0x%x bytes.\n", i); fclose(fp); return DEBUGGER_CMDDONE; } /** * Dump memory from an address to a binary file. */ static int DebugCpu_SaveBin(int nArgc, char *psArgs[]) { FILE *fp; unsigned char c; uint32_t address; uint32_t bytes, i = 0; if (nArgc < 4) { return DebugUI_PrintCmdHelp(psArgs[0]); } if (!Eval_Number(psArgs[2], &address, NUM_TYPE_CPU)) { fprintf(stderr, " Invalid address!\n"); return DEBUGGER_CMDDONE; } if (!Eval_Number(psArgs[3], &bytes, NUM_TYPE_NORMAL)) { fprintf(stderr, " Invalid length!\n"); return DEBUGGER_CMDDONE; } if ((fp = fopen(psArgs[1], "wb")) == NULL) { fprintf(stderr," Cannot open file '%s'!\n", psArgs[1]); return DEBUGGER_CMDDONE; } while (i < bytes) { c = STMemory_ReadByte(address++); fputc(c, fp); i++; } fclose(fp); fprintf(stderr, " Wrote 0x%x bytes.\n", bytes); return DEBUGGER_CMDDONE; } /** * Disassemble - arg = starting address, or PC. */ int DebugCpu_DisAsm(int nArgc, char *psArgs[]) { uint32_t prev_addr, disasm_upper = 0, pc = M68000_GetPC(); int shown, lines = INT_MAX; uaecptr nextpc; if (nArgc > 1) { switch (Eval_Range(psArgs[1], &disasm_addr, &disasm_upper, false)) { case -1: /* invalid value(s) */ return DEBUGGER_CMDDONE; case 0: /* single value */ break; case 1: /* range */ break; } } else { /* continue */ if(!disasm_addr) disasm_addr = pc; } /* limit is topmost address or instruction count */ if (!disasm_upper) { disasm_upper = 0xFFFFFFFF; lines = DebugUI_GetPageLines(ConfigureParams.Debugger.nDisasmLines, 8); } /* output a range */ prev_addr = disasm_addr; for (shown = 0; shown < lines && disasm_addr < disasm_upper; shown++) { const char *symbol; if (prev_addr < pc && disasm_addr > pc) { fputs("ERROR, disassembly misaligned with PC address, correcting\n", debugOutput); disasm_addr = pc; shown++; } if (disasm_addr == pc) { fputs("(PC)\n", debugOutput); shown++; } prev_addr = disasm_addr; symbol = Symbols_GetByCpuAddress(disasm_addr, SYMTYPE_ALL); if (symbol) { fprintf(debugOutput, "%s:\n", symbol); shown++; } Disasm(debugOutput, (uaecptr)disasm_addr, &nextpc, 1); disasm_addr = nextpc; } fflush(debugOutput); return DEBUGGER_CMDCONT; } /** * Readline match callback to list register names usable within debugger. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ static char *DebugCpu_MatchRegister(const char *text, int state) { static const char* regs_000[] = { "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "isp", "usp", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7" }; static const char* regs_020[] = { "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "caar", "cacr", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "dfc", "isp", "msp", "pc", "sfc", "sr", "usp", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "vbr" }; if (ConfigureParams.System.nCpuLevel < 2) return DebugUI_MatchHelper(regs_000, ARRAY_SIZE(regs_000), text, state); else return DebugUI_MatchHelper(regs_020, ARRAY_SIZE(regs_020), text, state); } /** * Set address of the named 32-bit register to given argument. * Handles V0-7 fake registers, D0-7 data, A0-7 address and several * special registers except for PC & SR registers because they need * to be accessed using UAE accessors. * * Return register size in bits or zero for unknown register name. */ int DebugCpu_GetRegisterAddress(const char *reg, uint32_t **addr) { char r0; int r1; if (!reg[0] || !reg[1]) return 0; /* 3-4 letter reg? */ if (reg[2]) { if (strcasecmp(reg, "ISP") == 0) { *addr = ®s.isp; return 32; } if (strcasecmp(reg, "USP") == 0) { *addr = ®s.usp; return 32; } if (ConfigureParams.System.nCpuLevel >= 2) { static const struct { const char name[5]; uint32_t *addr; } reg_020[] = { { "CAAR", ®s.caar }, { "CACR", ®s.cacr }, { "DFC", ®s.dfc }, { "MSP", ®s.msp }, { "SFC", ®s.sfc }, { "VBR", ®s.vbr } }; for (int i = 0; i < ARRAY_SIZE(reg_020); i++) { if (strcasecmp(reg, reg_020[i].name) == 0) { *addr = reg_020[i].addr; return 32; } } } return 0; } /* 2-letter reg */ r0 = toupper((unsigned char)reg[0]); r1 = toupper((unsigned char)reg[1]) - '0'; if (r0 == 'D') /* Data regs? */ { if (r1 >= 0 && r1 <= 7) { *addr = &(regs.regs[REG_D0 + r1]); return 32; } fprintf(stderr,"\tBad data register, valid values are 0-7\n"); return 0; } if(r0 == 'A') /* Address regs? */ { if (r1 >= 0 && r1 <= 7) { *addr = &(regs.regs[REG_A0 + r1]); return 32; } fprintf(stderr,"\tBad address register, valid values are 0-7\n"); return 0; } if(r0 == 'V') /* Virtual regs? */ { if (r1 >= 0 && r1 < ARRAY_SIZE(fake_regs)) { *addr = &fake_regs[r1]; bFakeRegsUsed = true; return 32; } fprintf(stderr,"\tBad virtual register, valid values are 0-7\n"); return 0; } return 0; } /** * Dump or set CPU registers */ int DebugCpu_Register(int nArgc, char *psArgs[]) { char *arg, *assign; uint32_t value; /* If no parameter has been given, simply dump all registers */ if (nArgc == 1) { uaecptr nextpc; int idx; /* use the UAE function instead */ m68k_dumpstate_file(debugOutput, &nextpc, 0xffffffff); fflush(debugOutput); if (!bFakeRegsUsed) return DEBUGGER_CMDDONE; fputs("Virtual registers:\n", debugOutput); for (idx = 0; idx < ARRAY_SIZE(fake_regs); idx++) { if (idx && idx % 4 == 0) fputs("\n", debugOutput); fprintf(debugOutput, " V%c %08x", '0' + idx, fake_regs[idx]); } fputs("\n", debugOutput); fflush(debugOutput); return DEBUGGER_CMDDONE; } arg = psArgs[1]; assign = strchr(arg, '='); if (!assign) { goto error_msg; } *assign++ = '\0'; if (!Eval_Number(Str_Trim(assign), &value, NUM_TYPE_CPU)) { goto error_msg; } arg = Str_Trim(arg); if (strlen(arg) < 2) { goto error_msg; } /* set SR and update conditional flags for the UAE CPU core. */ if (strcasecmp("SR", arg) == 0) { M68000_SetSR(value); } else if (strcasecmp("PC", arg) == 0) /* set PC */ { M68000_SetPC(value); } else { uint32_t *regaddr; /* check&set data and address registers */ if (DebugCpu_GetRegisterAddress(arg, ®addr)) { *regaddr = value; } else { goto error_msg; } } return DEBUGGER_CMDDONE; error_msg: fprintf(stderr,"\tError, usage: r or r xx=yyyy\n" "\tWhere: xx=A0-A7, D0-D7, PC, SR, ISP, USP\n" "\t020+: CAAR, CACR, DFC, SFC, MSP, VBR\n" "\tor V0-V7 (virtual).\n"); return DEBUGGER_CMDDONE; } /** * CPU wrapper for BreakAddr_Command(). */ static int DebugCpu_BreakAddr(int nArgc, char *psArgs[]) { BreakAddr_Command(psArgs[1], false); return DEBUGGER_CMDDONE; } /** * CPU wrapper for BreakCond_Command(). */ static int DebugCpu_BreakCond(int nArgc, char *psArgs[]) { BreakCond_Command(psArgs[1], false); return DEBUGGER_CMDDONE; } /** * CPU wrapper for Profile_Command(). */ static int DebugCpu_Profile(int nArgc, char *psArgs[]) { return Profile_Command(nArgc, psArgs, false); } /** * helper: return type width (b/c=1, w=2, l=4) */ static unsigned get_type_width(char mode) { switch(mode) { case 'b': /* byte */ case 'c': /* character */ return 1; case 'w': /* word */ return 2; case 'l': /* long */ return 4; default: return 0; } } /** * This is a helper function that prints `count` `size` sized memory items * from `addr` in `base`. * @param addr the start address * @param count the amount of items that should be printed * @param size the size of one item * @param base the number base */ static void print_mem_values(uint32_t addr, int count, int size, int base) { const char *separator = ""; for (int i = 0; i < count; i++) { uint32_t value; switch (size) { case 4: value = STMemory_ReadLong(addr); break; case 2: value = STMemory_ReadWord(addr); break; case 1: default: value = STMemory_ReadByte(addr); break; } switch (base) { case 1: fputs(separator, debugOutput); DebugUI_PrintBinary(debugOutput, 8*size, value); break; case 8: fprintf(debugOutput, "%s%0*o", separator, 3*size, value); break; case 10: fprintf(debugOutput, "%s%d", separator, value); break; case 16: default: fprintf(debugOutput, "%s%0*x", separator, 2*size, value); break; } addr += size; separator = " "; } } /** * This is a helper function that prints `count` bytes from Atari `addr` * as host encoded chars. * @param addr The ST RAM start address * @param count the amount of bytes that should get printed */ static void print_mem_chars(uint32_t addr, uint8_t count) { char host[4], st[2]; st[1] = '\0'; for (int i = 0; i < count; i++) { st[0] = STMemory_ReadByte(addr + i); if ((unsigned)st[0] >= 32 && st[0] != 127 && Str_AtariToHost(st, host, sizeof(host), NON_PRINT_CHAR)) fprintf(debugOutput,"%s", host); else fputc(NON_PRINT_CHAR, debugOutput); } } /** * Do a memory dump, args = starting address. */ int DebugCpu_MemDump(int nArgc, char *psArgs[]) { int arg = 1; unsigned size; char mode = 0; uint32_t memdump_upper = 0; if (nArgc > 1) mode = tolower(psArgs[arg][0]); if (!mode || isdigit((unsigned char)psArgs[arg][0]) || psArgs[arg][1]) { /* no args, single digit or multiple chars -> default mode */ mode = 'b'; size = 1; } else if ((size = get_type_width(mode))) { arg += 1; } else { fprintf(stderr, "Invalid width mode (not b|w|l)!\n"); return DEBUGGER_CMDDONE; } if (nArgc > arg) { switch (Eval_Range(psArgs[arg], &memdump_addr, &memdump_upper, false)) { case -1: /* invalid value(s) */ return DEBUGGER_CMDDONE; case 0: /* single value */ break; case 1: /* range */ break; } arg++; if (nArgc > arg) { int count = atoi(psArgs[arg]); if (count < 1) { fprintf(stderr, "Invalid count %d!\n", count); return DEBUGGER_CMDDONE; } memdump_upper = memdump_addr + count * size; } } if (!memdump_upper) { int lines = DebugUI_GetPageLines(ConfigureParams.Debugger.nMemdumpLines, 8); memdump_upper = memdump_addr + MEMDUMP_COLS * lines; } while (memdump_addr < memdump_upper) { unsigned all, cols, align; uint32_t memdump_line = memdump_addr; /* how many colums to print */ all = MEMDUMP_COLS/size; if (all*size > memdump_upper-memdump_addr) cols = (memdump_upper - memdump_addr)/size; else cols = all; /* print addr: HEX */ fprintf(debugOutput, "%08X: ", memdump_addr); print_mem_values(memdump_addr, cols, size, 16); /* print character data */ align = (all-cols)*(2*size+1); fprintf(debugOutput, "%*c", align + 2, ' '); print_mem_chars(memdump_line, cols*size); fprintf(debugOutput, "\n"); memdump_addr += cols*size; } fflush(debugOutput); return DEBUGGER_CMDCONT; } /** * helper: return type base (b=1, o=8, d=10, h=16) */ static unsigned get_type_base(char mode) { switch(mode) { case 'b': /* binary */ return 1; case 'o': /* oct */ return 8; case 'd': /* dec */ return 10; case 'h': /* hex */ return 16; default: return 0; } } /** * Sructured memory output */ static int DebugCpu_Struct(int nArgc, char *psArgs[]) { uint32_t start, addr, count, split; int maxlen, offlen; if (nArgc < 4) { fprintf(stderr, "Not enough arguments!\n"); return DEBUGGER_CMDDONE; } if (!Eval_Number(psArgs[2], &start, NUM_TYPE_CPU)) { fprintf(stderr, "Invalid structure address!\n"); return DEBUGGER_CMDDONE; } /* determine max header len for aligning and validate * argument content before it's split up. */ maxlen = 0; addr = start; for (int i = 3; i < nArgc; i++) { /* [name]:[:count[/split]] */ const char *arg, *str, *str2; int size, type; char buf[5]; arg = psArgs[i]; str = strchr(arg, ':'); if (!str) { fprintf(stderr, "':' missing from arg: '%s'!\n", arg); return DEBUGGER_CMDDONE; } /* lenght of longest title */ if (maxlen < str - arg) maxlen = str - arg; type = tolower(*++str); size = get_type_width(type); if (!size && type == 's') size = 1; if (!size) { fprintf(stderr, "invalid type for arg: '%s'!\n", arg); return DEBUGGER_CMDDONE; } /* type followed by base? */ if (get_type_base(tolower(*++str))) str++; /* done? */ if (!*str) { addr += size; continue; } if (*str++ != ':') { fprintf(stderr, "invalid base for arg: '%s'!\n", arg); return DEBUGGER_CMDDONE; } /* check split value that comes after count */ split = 0; str2 = strchr(str, '/'); if (str2) { if (!Eval_Number(str2+1, &split, NUM_TYPE_NORMAL) || split > 127) { fprintf(stderr, "Invalid or too large split value for arg: '%s'!\n", arg); return DEBUGGER_CMDDONE; } memcpy(buf, str, 3); /* terminate count */ buf[str2-str] = '\0'; str = buf; } if (!Eval_Number(str, &count, NUM_TYPE_NORMAL) || count > 255) { fprintf(stderr, "Invalid or too large count for arg: '%s'!\n", arg); return DEBUGGER_CMDDONE; } if (split >= count) { fprintf(stderr, "Invalid count/split, count<=split: '%s'!\n", arg); return DEBUGGER_CMDDONE; } addr += count*size; } /* count of hex digits for last printed address */ offlen = 1; count = addr - start; while (count >>= 4) offlen++; if (offlen >= maxlen) maxlen = offlen + 1; /* '$' prefix for numbers */ fprintf(debugOutput, "%s: $%x\n", psArgs[1], start); addr = start; for (int i = 3; i < nArgc; i++) { char *name, *str; int size, type, base; name = psArgs[i]; str = strchr(name, ':'); *str = '\0'; type = tolower(*++str); size = get_type_width(type); base = get_type_base(tolower(*++str)); if (!base) base = 16; else str++; count = 1; split = 0; if (*str++) /* ':' count separator? */ { char *str2 = strchr(str, '/'); if (str2) { *str2++ = '\0'; Eval_Number(str2, &split, NUM_TYPE_NORMAL); } Eval_Number(str, &count, NUM_TYPE_NORMAL); } if (type == 's') /* skip */ { addr += count; continue; } if (*name) fprintf(debugOutput, "+ %-*s: ", maxlen+1, name); else fprintf(debugOutput, "+ $%0*x%*c: ", offlen, addr-start, maxlen-offlen, ' '); if (split) fprintf(debugOutput, "\n"); while (count > 0) { unsigned int cols = split; if (!split || cols > count) cols = count; if (split) fprintf(debugOutput, " "); if (type == 'c') print_mem_chars(addr, cols); else print_mem_values(addr, cols, size, base); fprintf(debugOutput, "\n"); addr += cols * size; count -= cols; } } return DEBUGGER_CMDCONT; } /* helper to convert argument from host to Atari encoding. * returns false if argument could not be mapped completely, * or it maps to more than one Atari character. * * dst size 3 should be enough. */ static bool hostCharToAtari(const char *src, char *dst, size_t size) { /* atari string is smaller or same size as host enconded string */ if (strlen(src) > size-1) { fprintf(stderr, "'%s' is not a single char!\n", src); return false; } if (!Str_HostToAtari(src, dst, '.') || strlen(dst) != 1) { fprintf(stderr, "Unable to map '%s' to a single Atari char => use 'b' type instead\n", src); return false; } return true; } /** * Command: Write to memory, optional arg for value lengths, * followed by starting address and the values. */ static int DebugCpu_MemWrite(int nArgc, char *psArgs[]) { int i, arg, values, max_values; uint32_t write_addr, d; union { uint8_t bytes[256]; uint16_t words[128]; uint32_t longs[64]; } store; char mode; if (nArgc < 3) { return DebugUI_PrintCmdHelp(psArgs[0]); } arg = 1; mode = tolower(psArgs[arg][0]); max_values = ARRAY_SIZE(store.bytes); if (!mode || isdigit((unsigned char)psArgs[arg][0]) || psArgs[arg][1]) { /* no args, single digit or multiple chars -> default mode */ mode = 'b'; } else if (mode == 'b' || mode == 'c') { arg += 1; } else if (mode == 'w') { max_values = ARRAY_SIZE(store.words); arg += 1; } else if (mode == 'l') { max_values = ARRAY_SIZE(store.longs); arg += 1; } else { fprintf(stderr, "Invalid width mode (not b|w|l)!\n"); return DEBUGGER_CMDDONE; } /* Read address */ if (!Eval_Number(psArgs[arg++], &write_addr, NUM_TYPE_CPU)) { fprintf(stderr, "Bad address!\n"); return DEBUGGER_CMDDONE; } if (nArgc - arg > max_values) { fprintf(stderr, "Too many values (%d) given for mode '%c' (max %d)!\n", nArgc - arg, mode, max_values); return DEBUGGER_CMDDONE; } /* get the data */ values = 0; for (i = arg; i < nArgc; i++) { if (mode == 'c') { char str[3]; if (!hostCharToAtari(psArgs[i], str, sizeof(str))) return DEBUGGER_CMDDONE; store.bytes[values] = str[0]; values++; continue; } if (!Eval_Number(psArgs[i], &d, NUM_TYPE_NORMAL)) { fprintf(stderr, "Bad value '%s'!\n", psArgs[i]); return DEBUGGER_CMDDONE; } switch(mode) { case 'b': if (d > 0xff) { fprintf(stderr, "Illegal byte argument: 0x%x!\n", d); return DEBUGGER_CMDDONE; } store.bytes[values] = (uint8_t)d; break; case 'w': if (d > 0xffff) { fprintf(stderr, "Illegal word argument: 0x%x!\n", d); return DEBUGGER_CMDDONE; } store.words[values] = (uint16_t)d; break; case 'l': store.longs[values] = d; break; } values++; } /* write the data */ for (i = 0; i < values; i++) { switch(mode) { case 'b': case 'c': STMemory_WriteByte(write_addr + i, store.bytes[i]); break; case 'w': STMemory_WriteWord(write_addr + i*2, store.words[i]); break; case 'l': STMemory_WriteLong(write_addr + i*4, store.longs[i]); break; } } if (values > 1) { fprintf(stderr, "Wrote %d '%c' values starting from 0x%x.\n", values, mode, write_addr); } return DEBUGGER_CMDDONE; } /* return end of memory area where given address is, * or zero if address is invalid */ static uint32_t mem_end_for(uint32_t addr) { if (addr < STRamEnd) return STRamEnd; uint32_t TosEnd = TosAddress + TosSize; if (addr >= TosAddress && addr < TosEnd) return TosEnd; if (addr >= CART_START && addr < CART_END) return CART_END; uint32_t TTmemEnd = TTRAM_START + 1024*ConfigureParams.Memory.TTRamSize_KB; if (TTmemory && addr >= TTRAM_START && addr < TTmemEnd) return TTmemEnd; return 0; } /** * Do a memory find, args = mode, start-end, values * (most of code is identical to MemWrite) */ static int DebugCpu_MemFind(int nArgc, char *psArgs[]) { int arg, max_values; union { uint8_t bytes[256]; uint16_t words[128]; uint32_t longs[64]; } store; char mode; if (nArgc < 3) { return DebugUI_PrintCmdHelp(psArgs[0]); } arg = 1; mode = tolower(psArgs[arg][0]); max_values = ARRAY_SIZE(store.bytes); if (!mode || isdigit((unsigned char)psArgs[arg][0]) || psArgs[arg][1]) { /* no args, single digit or multiple chars -> default mode */ mode = 'b'; } else if (mode == 'b' || mode == 'c') { arg += 1; } else if (mode == 'w') { max_values = ARRAY_SIZE(store.words); arg += 1; } else if (mode == 'l') { max_values = ARRAY_SIZE(store.longs); arg += 1; } else { fprintf(stderr, "Invalid width mode (not a|b|w|l)!\n"); return DEBUGGER_CMDDONE; } /* parse address range */ uint32_t find_addr, find_upper = 0; switch (Eval_Range(psArgs[arg++], &find_addr, &find_upper, false)) { case -1: /* invalid value(s) */ return DEBUGGER_CMDDONE; case 0: /* single value */ break; case 1: /* range */ break; } if ((find_upper && find_upper <= find_addr) || !(mem_end_for(find_addr) && mem_end_for(find_upper))) { fprintf(stderr, "Invalid address range: 0x%x[-0x%x]\n", find_addr, find_upper); return DEBUGGER_CMDDONE; } if (!find_upper) find_upper = mem_end_for(find_addr); const int size = get_type_width(mode); if (find_addr & (size-1)) { fprintf(stderr, "Start address 0x%x not '%c' type aligned\n", find_addr, mode); return DEBUGGER_CMDDONE; } /* parse values */ if (nArgc - arg > max_values) { fprintf(stderr, "Too many values (%d) given for mode '%c' (max %d)!\n", nArgc - arg, mode, max_values); return DEBUGGER_CMDDONE; } uint32_t d; int i, values; for (values = 0, i = arg; i < nArgc; i++, values++) { if (mode == 'c') { char str[3]; if (!hostCharToAtari(psArgs[i], str, sizeof(str))) return DEBUGGER_CMDDONE; store.bytes[values] = str[0]; continue; } if (!Eval_Number(psArgs[i], &d, NUM_TYPE_NORMAL)) { fprintf(stderr, "Bad value '%s'!\n", psArgs[i]); return DEBUGGER_CMDDONE; } switch(mode) { case 'b': if (d > 0xff) { fprintf(stderr, "Illegal byte argument: 0x%x!\n", d); return DEBUGGER_CMDDONE; } store.bytes[values] = (uint8_t)d; break; case 'w': if (d > 0xffff) { fprintf(stderr, "Illegal word argument: 0x%x!\n", d); return DEBUGGER_CMDDONE; } store.words[values] = be_swap16(d); break; case 'l': store.longs[values] = be_swap32(d); break; } } /* search given range for specified values & show them */ const int rows = DebugUI_GetPageLines(ConfigureParams.Debugger.nFindLines, 20); const int count = nArgc - arg; const int bytes = count*size; int row = 0, matches = 0; while (find_addr < find_upper - bytes) { for (i = 0; i < bytes; i++) { if (STMemory_ReadByte(find_addr+i) != store.bytes[i]) break; } if (i < bytes) { find_addr += size; continue; } /* print : */ fprintf(debugOutput, "%08X: ", find_addr); print_mem_values(find_addr, count, size, 16); fprintf(debugOutput, " "); print_mem_chars(find_addr, count*size); fprintf(debugOutput, "\n"); matches++; if (++row >= rows) { row = 0; if (DebugUI_DoQuitQuery("find results")) break; } find_addr += bytes; } fprintf(debugOutput, "%d matches.\n", matches); fflush(debugOutput); return DEBUGGER_CMDCONT; } /** * Command: Continue CPU emulation / single-stepping */ static int DebugCpu_Continue(int nArgc, char *psArgv[]) { int steps = 0; if (nArgc > 1) { steps = atoi(psArgv[1]); } if (steps <= 0) { nCpuSteps = 0; fprintf(stderr,"Returning to emulation...\n"); return DEBUGGER_END; } nCpuSteps = steps; fprintf(stderr,"Returning to emulation for %i CPU instructions...\n", steps); return DEBUGGER_END; } /** * Command: Single-step CPU */ static int DebugCpu_Step(int nArgc, char *psArgv[]) { nCpuSteps = 1; return DEBUGGER_ENDCONT; } /** * Readline match callback to list next command opcode types. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ static char *DebugCpu_MatchNext(const char *text, int state) { static const char* ntypes[] = { "branch", "exception", "exreturn", "return", "subcall", "subreturn" }; return DebugUI_MatchHelper(ntypes, ARRAY_SIZE(ntypes), text, state); } /** * Variable + debugger variable function for tracking * subroutine call depth for "next" breakpoint */ static int CpuCallDepth; uint32_t DebugCpu_CallDepth(void) { return CpuCallDepth; } /* Depth tracking can start anywhere i.e. it can go below initial * value. Start from large enough value that it should never goes * negative, as then DebugCpu_CallDepth() return value would wrap */ #define CALL_START_DEPTH 10000 /** * Command: Step CPU, but proceed through subroutines * Does this by temporary conditional breakpoint */ static int DebugCpu_Next(int nArgc, char *psArgv[]) { char command[80]; if (nArgc > 1) { int optype; bool depthcheck = false; if(strcmp(psArgv[1], "branch") == 0) optype = CALL_BRANCH; else if(strcmp(psArgv[1], "exception") == 0) optype = CALL_EXCEPTION; else if(strcmp(psArgv[1], "exreturn") == 0) optype = CALL_EXCRETURN; else if(strcmp(psArgv[1], "subcall") == 0) optype = CALL_SUBROUTINE; else if (strcmp(psArgv[1], "subreturn") == 0) { optype = CALL_SUBRETURN; depthcheck = true; } else if (strcmp(psArgv[1], "return") == 0) optype = CALL_SUBRETURN | CALL_EXCRETURN; else { fprintf(stderr, "Unrecognized opcode type given!\n"); return DEBUGGER_CMDDONE; } /* CpuOpCodeType increases call depth on subroutine calls, * and decreases depth on return from them, so it must be * first check to get called on every relevant instruction. */ if (depthcheck) { CpuCallDepth = CALL_START_DEPTH; sprintf(command, "CpuOpcodeType & $%x > 0 && CpuCallDepth < $%x :once :quiet\n", optype, CALL_START_DEPTH); } else sprintf(command, "CpuOpcodeType & $%x > 0 :once :quiet\n", optype); } else { uint32_t optype, nextpc; optype = DebugCpu_OpcodeType(); /* should this instruction be stepped normally, or is it * - subroutine call * - exception * - loop branch backwards */ if (optype == CALL_SUBROUTINE || optype == CALL_EXCEPTION || (optype == CALL_BRANCH && (STMemory_ReadWord(M68000_GetPC()) & 0xf0f8) == 0x50c8 && (int16_t)STMemory_ReadWord(M68000_GetPC() + SIZE_WORD) < 0)) { nextpc = Disasm_GetNextPC(M68000_GetPC()); sprintf(command, "pc=$%x :once :quiet\n", nextpc); } else { nCpuSteps = 1; return DEBUGGER_ENDCONT; } } /* use breakpoint, not steps */ if (BreakCond_Command(command, false)) { nCpuSteps = 0; return DEBUGGER_ENDCONT; } return DEBUGGER_CMDDONE; } /* helper to get instruction type */ uint32_t DebugCpu_OpcodeType(void) { /* cannot use OpcodeFamily like profiler does, * as that's for previous instructions */ uint16_t opcode = STMemory_ReadWord(M68000_GetPC()); if (opcode == 0x4e74 || /* RTD */ opcode == 0x4e75 || /* RTS */ opcode == 0x4e77) /* RTR */ { CpuCallDepth--; return CALL_SUBRETURN; } if (opcode == 0x4e73) /* RTE */ { return CALL_EXCRETURN; } /* NOTE: BSR needs to be matched before BRA/BCC! */ if ((opcode & 0xff00) == 0x6100 || /* BSR */ (opcode & 0xffc0) == 0x4e80) /* JSR */ { CpuCallDepth++; return CALL_SUBROUTINE; } /* TODO: ftrapcc, chk2? */ if (opcode == 0x4e72 || /* STOP */ opcode == 0x4afc || /* ILLEGAL */ opcode == 0x4e76 || /* TRAPV */ (opcode & 0xfff0) == 0x4e40 || /* TRAP */ (opcode & 0xf1c0) == 0x4180 || /* CHK */ (opcode & 0xfff8) == 0x4848) /* BKPT */ { return CALL_EXCEPTION; } /* TODO: fbcc, fdbcc */ if ((opcode & 0xf000) == 0x6000 || /* BRA / BCC */ (opcode & 0xffc0) == 0x4ec0 || /* JMP */ (opcode & 0xf0f8) == 0x50c8) /* DBCC */ return CALL_BRANCH; return CALL_UNKNOWN; } /** * CPU instructions since continuing emulation */ static uint32_t nCpuInstructions; uint32_t DebugCpu_InstrCount(void) { return nCpuInstructions; } /** * This function is called after each CPU instruction when debugging is enabled. */ void DebugCpu_Check(void) { nCpuInstructions++; if (bCpuProfiling) { Profile_CpuUpdate(); } if (LOG_TRACE_LEVEL((TRACE_CPU_DISASM|TRACE_CPU_SYMBOLS))) { const char *symbol; symbol = Symbols_GetByCpuAddress(M68000_GetPC(), SYMTYPE_ALL); if (symbol) LOG_TRACE_PRINT("%s\n", symbol); } if (LOG_TRACE_LEVEL(TRACE_CPU_REGS)) { uaecptr nextpc; LOG_TRACE_DIRECT_INIT (); m68k_dumpstate_file(TraceFile, &nextpc, 0xffffffff); } if (nCpuActiveCBs) { if (BreakCond_MatchCpu()) { DebugUI(REASON_CPU_BREAKPOINT); /* make sure we don't decrease step count * below, before even even getting out of here */ if (nCpuSteps) nCpuSteps++; } } if (nCpuSteps) { nCpuSteps--; if (nCpuSteps == 0) DebugUI(REASON_CPU_STEPS); } if (History_TrackCpu()) { History_AddCpu(); } if (ConOutDevices) { Console_Check(); } } /** * Should be called before returning back emulation to tell the CPU core * to call us after each instruction if "real-time" debugging like * breakpoints has been set. */ void DebugCpu_SetDebugging(void) { bCpuProfiling = Profile_CpuStart(); nCpuActiveCBs = BreakCond_CpuBreakPointCount(); if (nCpuActiveCBs || nCpuSteps || bCpuProfiling || History_TrackCpu() || LOG_TRACE_LEVEL((TRACE_CPU_DISASM|TRACE_CPU_SYMBOLS|TRACE_CPU_REGS)) || ConOutDevices) { M68000_SetDebugger(true); nCpuInstructions = 0; } else M68000_SetDebugger(false); } static const dbgcommand_t cpucommands[] = { { NULL, NULL, "CPU commands", NULL, NULL, NULL, false }, /* NULL as match function will complete file names */ { DebugCpu_BreakAddr, Symbols_MatchCpuCodeAddress, "address", "a", "set CPU PC address breakpoints", BreakAddr_Description, true }, { DebugCpu_BreakCond, Vars_MatchCpuVariable, "breakpoint", "b", "set/remove/list conditional CPU breakpoints", BreakCond_Description, true }, { DebugCpu_DisAsm, Symbols_MatchCpuCodeAddress, "disasm", "d", "disassemble from PC, or given address", "[[-]]\n" "\tWhen no address is given, disassemble from the last disasm\n" "\taddress, or from current PC when debugger is (re-)entered.", false }, { DebugCpu_MemFind, Symbols_MatchCpuAddress, "find", "", "find given value sequence from memory", "[b|c|w|l] [-] \n" "\tBy default values are interpreted as bytes, with 'c', 'w'\n" "\tor 'l', they're interpreted as chars/words/longs instead,\n" "\tand find is done for correspondingly aligned addresses.", false }, { DebugCpu_Profile, Profile_Match, "profile", "", "profile CPU code", Profile_Description, false }, { DebugCpu_Register, DebugCpu_MatchRegister, "cpureg", "r", "dump register values or set register to value", "[REG=value]\n" "\tSet CPU register to given value, or dump all registers\n" "\twhen no parameter is given.", true }, { DebugCpu_MemDump, Symbols_MatchCpuDataAddress, "memdump", "m", "dump memory", "[b|w|l] [[-| ]]\n" "\tdump memory at address or continue dump from previous address.\n" "\tBy default memory output is done as bytes, with 'w' or 'l'\n" "\toption, it will be done as words/longs instead. Output amount\n" "\tcan be given either as a count or an address range.", false }, { DebugCpu_Struct, Symbols_MatchCpuDataAddress, "struct", "", "structured memory output, e.g. for breakpoints", "
[name]:[base][:[/] ...]\n\n" "\tShow d structure content at given
, with each\n" "\t[name]:[base][:] arg output on its own line, prefixed\n" "\twith offset from struct start address, if [name] is not given.\n" "\tOutput uses multiple lines when type count is given.\n" "\tSupported s are 'b|c|w|l|s' (byte|char|word|long|skip).\n" "\tOptional [base] can be 'b|o|d|h' (bin|oct|dec|hex).\n" "\tDefaults are hex [base], and [count] of 1.\n", false }, { DebugCpu_MemWrite, Symbols_MatchCpuAddress, "memwrite", "w", "write bytes/words/longs to memory", "[b|c|w|l]
\n" "\tWrite space separate values (in current number base) to given\n" "\tmemory address. By default they are written as bytes, with\n" "\t'w' or 'l' they will be done as words/longs instead.\n" "\t'c' can be used to provide byte values as chars.", false }, { DebugCpu_LoadBin, Symbols_MatchCpuAddrFile, "loadbin", "l", "load a file into memory", "
\n" "\tLoad the file into memory starting at
.", false }, { DebugCpu_SaveBin, Symbols_MatchCpuAddrFile, "savebin", "", "save memory to a file", "
\n" "\tSave the memory block at
with given to\n" "\tthe file .", false }, { Symbols_Command, Symbols_MatchCpuCommand, "symbols", "", "load CPU symbols & their addresses", Symbols_Description, false }, { DebugCpu_Step, NULL, "step", "s", "single-step CPU", "\n" "\tExecute next CPU instruction (like 'c 1', but repeats on Enter).", false }, { DebugCpu_Next, DebugCpu_MatchNext, "next", "n", "step CPU through subroutine calls / to given instruction type", "[instruction type]\n" "\tSame as 'step' command if there are no subroutine calls.\n" "\tWhen there are, those calls are treated as one instruction.\n" "\tIf argument is given, continues until instruction of given\n" "\ttype is encountered. Repeats on Enter.", false }, { DebugCpu_Continue, NULL, "cont", "c", "continue emulation / CPU single-stepping", "[steps]\n" "\tLeave debugger and continue emulation for CPU instructions\n" "\tor forever if no steps have been specified.", false } }; /** * Should be called when debugger is first entered to initialize * CPU debugging variables. * * if you want disassembly or memdumping to start/continue from * specific address, you can set them here. If disassembly * address is zero, disassembling starts from PC. * * returns number of CPU commands and pointer to array of them. */ int DebugCpu_Init(const dbgcommand_t **table) { memdump_addr = 0; disasm_addr = 0; *table = cpucommands; return ARRAY_SIZE(cpucommands); } /** * Should be called when debugger is re-entered to reset * relevant CPU debugging variables. */ void DebugCpu_InitSession(void) { #define MAX_CPU_DISASM_OFFSET 16 disasm_addr = History_DisasmAddr(M68000_GetPC(), MAX_CPU_DISASM_OFFSET, false); Profile_CpuStop(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugcpu.h000066400000000000000000000013471504763705000243720ustar00rootroot00000000000000/* Hatari - debugcpu.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Public CPU debugging header. */ #ifndef HATARI_DEBUGCPU_H #define HATARI_DEBUGCPU_H extern void DebugCpu_Check(void); extern void DebugCpu_SetDebugging(void); extern uint32_t DebugCpu_CallDepth(void); extern uint32_t DebugCpu_InstrCount(void); extern uint32_t DebugCpu_OpcodeType(void); extern int DebugCpu_DisAsm(int nArgc, char *psArgs[]); extern int DebugCpu_MemDump(int nArgc, char *psArgs[]); extern int DebugCpu_Register(int nArgc, char *psArgs[]); extern int DebugCpu_GetRegisterAddress(const char *reg, uint32_t **addr); #endif /* HATARI_DEBUGCPU_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugdsp.c000066400000000000000000000425251504763705000243670ustar00rootroot00000000000000/* Hatari - debugdsp.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. debugdsp.c - function needed for the DSP debugging tasks like memory and register dumps. */ const char DebugDsp_fileid[] = "Hatari debugdsp.c"; #include #include #include #include "config.h" #include "main.h" #include "breakcond.h" #include "configuration.h" #include "debugui.h" #include "debug_priv.h" #include "debugdsp.h" #include "dsp.h" #include "evaluate.h" #include "history.h" #include "log.h" #include "memorySnapShot.h" #include "profile.h" #include "str.h" #include "symbols.h" static uint16_t dsp_disasm_addr; /* DSP disasm address */ static uint16_t dsp_memdump_addr; /* DSP memdump address */ static char dsp_mem_space = 'P'; /* X, Y, P */ static bool bDspProfiling; /* Whether profiling is enabled */ static int nDspActiveCBs = 0; /* Amount of active conditional breakpoints */ static int nDspSteps = 0; /* Amount of steps for DSP single-stepping */ /** * Readline match callback to list register names usable within debugger. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ static char *DebugDsp_MatchRegister(const char *text, int state) { static const char* regs[] = { "a0", "a1", "a2", "b0", "b1", "b2", "la", "lc", "m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "omr", "pc", "sp", "sr", "ssh", "ssl", "x0", "x1", "y0", "y1", }; return DebugUI_MatchHelper(regs, ARRAY_SIZE(regs), text, state); } /** * Command: Dump or set a DSP register */ int DebugDsp_Register(int nArgc, char *psArgs[]) { char *assign; uint32_t value; char *arg; if (!bDspEnabled) { fprintf(stderr, "DSP isn't present or initialized.\n"); return DEBUGGER_CMDDONE; } if (nArgc == 1) { /* No parameter - dump all registers */ DSP_DisasmRegisters(debugOutput); fflush(debugOutput); return DEBUGGER_CMDDONE; } arg = psArgs[1]; assign = strchr(arg, '='); if (!assign) goto error_msg; *assign++ = '\0'; if (!Eval_Number(Str_Trim(assign), &value, NUM_TYPE_DSP)) goto error_msg; if (DSP_Disasm_SetRegister(Str_Trim(arg), value)) return DEBUGGER_CMDDONE; error_msg: fprintf(stderr,"\tError, usage: dr or dr xx=yyyy\n" "\tWhere: xx=A0-A2, B0-B2, X0, X1, Y0, Y1, R0-R7,\n" "\t N0-N7, M0-M7, LA, LC, PC, SR, SP, OMR, SSH, SSL\n"); return DEBUGGER_CMDDONE; } /** * DSP disassemble - arg = starting address/range, or PC. */ int DebugDsp_DisAsm(int nArgc, char *psArgs[]) { uint32_t lower, upper; uint16_t prev_addr, dsp_disasm_upper = 0, pc = DSP_GetPC(); int shown, lines = INT_MAX; if (!bDspEnabled) { fprintf(stderr, "DSP isn't present or initialized.\n"); return DEBUGGER_CMDDONE; } if (nArgc > 1) { switch (Eval_Range(psArgs[1], &lower, &upper, true)) { case -1: /* invalid value(s) */ return DEBUGGER_CMDDONE; case 0: /* single value */ break; case 1: /* range */ if (upper > 0xFFFF) { fprintf(stderr,"Invalid address 0x%x!\n", upper); return DEBUGGER_CMDDONE; } dsp_disasm_upper = upper; break; } if (lower > 0xFFFF) { fprintf(stderr,"Invalid address 0x%x!\n", lower); return DEBUGGER_CMDDONE; } dsp_disasm_addr = lower; } else { /* continue */ if(!dsp_disasm_addr) dsp_disasm_addr = pc; } if (!dsp_disasm_upper) { lines = DebugUI_GetPageLines(ConfigureParams.Debugger.nDisasmLines, 8); dsp_disasm_upper = 0xFFFF; } prev_addr = dsp_disasm_addr; fprintf(debugOutput, "DSP disasm 0x%hx-0x%hx:\n", dsp_disasm_addr, dsp_disasm_upper); for (shown = 1; shown < lines && dsp_disasm_addr < dsp_disasm_upper; shown++) { const char *symbol; if (prev_addr < pc && dsp_disasm_addr > pc) { fputs("ERROR, disassembly misaligned with PC address, correcting\n", debugOutput); dsp_disasm_addr = pc; shown++; } if (dsp_disasm_addr == pc) { fputs("(PC)\n", debugOutput); shown++; } prev_addr = dsp_disasm_addr; symbol = Symbols_GetByDspAddress(dsp_disasm_addr, SYMTYPE_ALL); if (symbol) { fprintf(debugOutput, "%s:\n", symbol); shown++; } dsp_disasm_addr = DSP_DisasmAddress(debugOutput, dsp_disasm_addr, dsp_disasm_addr); } fflush(debugOutput); return DEBUGGER_CMDCONT; } /** * Do a DSP memory dump, args = starting address or range. * `x|y|p` `address`: dump from X, Y or P, starting from given address, * e.g. "x 200" or "p 200-300" */ int DebugDsp_MemDump(int nArgc, char *psArgs[]) { uint32_t lower, upper; uint16_t dsp_memdump_upper = 0; char *range, space; if (!bDspEnabled) { fprintf(stderr, "DSP isn't present or initialized.\n"); return DEBUGGER_CMDDONE; } switch (nArgc) { case 1: break; case 3: /* "x $200" */ space = psArgs[1][0]; range = psArgs[2]; break; case 2: /* "x:$200" */ if (psArgs[1][1] == ':') { space = psArgs[1][0]; range = psArgs[1] + 2; break; } /* fall through */ default: return DebugUI_PrintCmdHelp(psArgs[0]); } if (nArgc > 1) { space = toupper((unsigned char)space); switch (space) { case 'X': case 'Y': case 'P': break; default: fprintf(stderr,"Invalid DSP address space '%c'!\n", space); return DEBUGGER_CMDDONE; } switch (Eval_Range(range, &lower, &upper, true)) { case -1: /* invalid value(s) */ return DEBUGGER_CMDDONE; case 0: /* single value */ break; case 1: /* range */ if (upper > 0xFFFF) { fprintf(stderr,"Invalid address 0x%x!\n", upper); return DEBUGGER_CMDDONE; } dsp_memdump_upper = upper; break; } if (lower > 0xFFFF) { fprintf(stderr,"Invalid address 0x%x!\n", lower); return DEBUGGER_CMDDONE; } dsp_memdump_addr = lower; dsp_mem_space = space; } if (!dsp_memdump_upper) { int lines = DebugUI_GetPageLines(ConfigureParams.Debugger.nMemdumpLines, 8); if ( dsp_memdump_addr < (0xFFFF - lines)) dsp_memdump_upper = dsp_memdump_addr + lines; else dsp_memdump_upper = 0xFFFF; } fprintf(debugOutput, "DSP memdump from 0x%hx in '%c' address space:\n", dsp_memdump_addr, dsp_mem_space); dsp_memdump_addr = DSP_DisasmMemory(debugOutput, dsp_memdump_addr, dsp_memdump_upper, dsp_mem_space); fflush(debugOutput); return DEBUGGER_CMDCONT; } /** * Command: Continue DSP emulation / single-stepping */ static int DebugDsp_Continue(int nArgc, char *psArgv[]) { int steps = 0; if (nArgc > 1) { steps = atoi(psArgv[1]); } if (steps <= 0) { nDspSteps = 0; fprintf(stderr,"Returning to emulation...\n"); return DEBUGGER_END; } nDspSteps = steps; fprintf(stderr,"Returning to emulation for %i DSP instructions...\n", steps); return DEBUGGER_END; } /** * Command: Single-step DSP */ static int DebugDsp_Step(int nArgc, char *psArgv[]) { nDspSteps = 1; return DEBUGGER_ENDCONT; } /** * Readline match callback to list next command opcode types. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ static char *DebugDsp_MatchNext(const char *text, int state) { static const char* ntypes[] = { "branch", "exreturn", "return", "subcall", "subreturn" }; return DebugUI_MatchHelper(ntypes, ARRAY_SIZE(ntypes), text, state); } /** * Variable + debugger variable function for tracking * subroutine call depth for "dspnext" breakpoint */ static int DspCallDepth; uint32_t DebugDsp_CallDepth(void) { return DspCallDepth; } /* Depth tracking can start anywhere i.e. it can go below initial * value. Start from large enough value that it should never goes * negative, as then DebugCpu_CallDepth() return value would wrap */ #define CALL_START_DEPTH 10000 /** * Command: Step DSP, but proceed through subroutines * Does this by temporary conditional breakpoint */ static int DebugDsp_Next(int nArgc, char *psArgv[]) { char command[80]; if (nArgc > 1) { int optype; bool depthcheck = false; if(strcmp(psArgv[1], "branch") == 0) optype = CALL_BRANCH; else if(strcmp(psArgv[1], "exreturn") == 0) optype = CALL_EXCRETURN; else if(strcmp(psArgv[1], "subcall") == 0) optype = CALL_SUBROUTINE; else if (strcmp(psArgv[1], "subreturn") == 0) { optype = CALL_SUBRETURN; depthcheck = true; } else if (strcmp(psArgv[1], "return") == 0) optype = CALL_SUBRETURN | CALL_EXCRETURN; else { fprintf(stderr, "Unrecognized opcode type given!\n"); return DEBUGGER_CMDDONE; } /* DspOpCodeType increases call depth on subroutine calls, and * decreases depth on return from them, so it must be first check * to get called on every relevant instruction. */ DspCallDepth = CALL_START_DEPTH; if (depthcheck) sprintf(command, "DspOpcodeType & $%x > 0 && DspCallDepth < $%x :once :quiet\n", optype, CALL_START_DEPTH); else sprintf(command, "DspOpcodeType & $%x > 0 :once :quiet\n", optype); } else { uint32_t optype; uint16_t nextpc; optype = DebugDsp_OpcodeType(); /* can this instruction be stepped normally? */ if (optype != CALL_SUBROUTINE && optype != CALL_EXCEPTION) { nDspSteps = 1; return DEBUGGER_ENDCONT; } nextpc = DSP_GetNextPC(DSP_GetPC()); sprintf(command, "pc=$%x :once :quiet\n", nextpc); } /* use breakpoint, not steps */ if (BreakCond_Command(command, true)) { nDspSteps = 0; return DEBUGGER_ENDCONT; } return DEBUGGER_CMDDONE; } /* helper to get instruction type, slightly simpler * version from one in profiledsp.c */ uint32_t DebugDsp_OpcodeType(void) { const char *dummy; uint32_t opcode; /* 24-bit instruction opcode */ opcode = DSP_ReadMemory(DSP_GetPC(), 'P', &dummy) & 0xFFFFFF; /* subroutine returns */ if (opcode == 0xC) { /* (just) RTS */ DspCallDepth--; return CALL_SUBRETURN; } if ( /* unconditional subroutine calls */ (opcode & 0xFFF000) == 0xD0000 || /* JSR 00001101 0000aaaa aaaaaaaa */ (opcode & 0xFFC0FF) == 0xBC080 || /* JSR 00001011 11MMMRRR 10000000 */ /* conditional subroutine calls */ (opcode & 0xFF0000) == 0xF0000 || /* JSCC 00001111 CCCCaaaa aaaaaaaa */ (opcode & 0xFFC0F0) == 0xBC0A0 || /* JSCC 00001011 11MMMRRR 1010CCCC */ (opcode & 0xFFC0A0) == 0xB4080 || /* JSCLR 00001011 01MMMRRR 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xB0080 || /* JSCLR 00001011 00aaaaaa 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xB8080 || /* JSCLR 00001011 10pppppp 1S0bbbbb */ (opcode & 0xFFC0E0) == 0xBC000 || /* JSCLR 00001011 11DDDDDD 000bbbbb */ (opcode & 0xFFC0A0) == 0xB40A0 || /* JSSET 00001011 01MMMRRR 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xB00A0 || /* JSSET 00001011 00aaaaaa 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xB80A0 || /* JSSET 00001011 10pppppp 1S1bbbbb */ (opcode & 0xFFC0E0) == 0xBC020) { /* JSSET 00001011 11DDDDDD 001bbbbb */ DspCallDepth++; return CALL_SUBROUTINE; } /* exception handler returns */ if (opcode == 0x4) { /* (just) RTI */ return CALL_EXCRETURN; } /* branches */ if ((opcode & 0xFFF000) == 0xC0000 || /* JMP 00001100 0000aaaa aaaaaaaa */ (opcode & 0xFFC0FF) == 0xAC080 || /* JMP 00001010 11MMMRRR 10000000 */ (opcode & 0xFF0000) == 0xE0000 || /* JCC 00001110 CCCCaaaa aaaaaaaa */ (opcode & 0xFFC0F0) == 0xAC0A0 || /* JCC 00001010 11MMMRRR 1010CCCC */ (opcode & 0xFFC0A0) == 0xA8080 || /* JCLR 00001010 10pppppp 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xA4080 || /* JCLR 00001010 01MMMRRR 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xA0080 || /* JCLR 00001010 00aaaaaa 1S0bbbbb */ (opcode & 0xFFC0E0) == 0xAC000 || /* JCLR 00001010 11dddddd 000bbbbb */ (opcode & 0xFFC0A0) == 0xA80A0 || /* JSET 00001010 10pppppp 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xA40A0 || /* JSET 00001010 01MMMRRR 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xA00A0 || /* JSET 00001010 00aaaaaa 1S1bbbbb */ (opcode & 0xFFC0E0) == 0xAC020 || /* JSET 00001010 11dddddd 001bbbbb */ (opcode & 0xFF00F0) == 0x600A0 || /* REP 00000110 iiiiiiii 1010hhhh */ (opcode & 0xFFC0FF) == 0x6C020 || /* REP 00000110 11dddddd 00100000 */ (opcode & 0xFFC0BF) == 0x64020 || /* REP 00000110 01MMMRRR 0s100000 */ (opcode & 0xFFC0BF) == 0x60020 || /* REP 00000110 00aaaaaa 0s100000 */ (opcode & 0xFF00F0) == 0x60080 || /* DO/ENDO 00000110 iiiiiiii 1000hhhh */ (opcode & 0xFFC0FF) == 0x6C000 || /* DO/ENDO 00000110 11DDDDDD 00000000 */ (opcode & 0xFFC0BF) == 0x64000 || /* DO/ENDO 00000110 01MMMRRR 0S000000 */ (opcode & 0xFFC0BF) == 0x60000) { /* DO/ENDO 00000110 00aaaaaa 0S000000 */ return CALL_BRANCH; } return CALL_UNKNOWN; } /** * DSP wrapper for BreakAddr_Command(). */ static int DebugDsp_BreakAddr(int nArgc, char *psArgs[]) { BreakAddr_Command(psArgs[1], true); return DEBUGGER_CMDDONE; } /** * DSP wrapper for BreakCond_Command(). */ static int DebugDsp_BreakCond(int nArgc, char *psArgs[]) { BreakCond_Command(psArgs[1], true); return DEBUGGER_CMDDONE; } /** * DSP wrapper for Profile_Command(). */ static int DebugDsp_Profile(int nArgc, char *psArgs[]) { return Profile_Command(nArgc, psArgs, true); } /** * DSP instructions since continuing emulation */ static uint32_t nDspInstructions; uint32_t DebugDsp_InstrCount(void) { return nDspInstructions; } /** * This function is called after each DSP instruction when debugging is enabled. */ void DebugDsp_Check(void) { nDspInstructions++; if (bDspProfiling) { Profile_DspUpdate(); } if (LOG_TRACE_LEVEL((TRACE_DSP_DISASM|TRACE_DSP_SYMBOLS))) { const char *symbol; symbol = Symbols_GetByDspAddress(DSP_GetPC(), SYMTYPE_ALL); if (symbol) LOG_TRACE_PRINT("%s\n", symbol); } if (nDspActiveCBs) { if (BreakCond_MatchDsp()) { DebugUI(REASON_DSP_BREAKPOINT); /* make sure we don't decrease step count * below, before even getting out of here */ if (nDspSteps) nDspSteps++; } } if (nDspSteps) { nDspSteps--; if (nDspSteps == 0) DebugUI(REASON_DSP_STEPS); } if (History_TrackDsp()) { History_AddDsp(); } } /** * Should be called before returning back emulation to tell the DSP core * to call us after each instruction if "real-time" debugging like * breakpoints has been set. */ void DebugDsp_SetDebugging(void) { bDspProfiling = Profile_DspStart(); nDspActiveCBs = BreakCond_DspBreakPointCount(); if (nDspActiveCBs || nDspSteps || bDspProfiling || History_TrackDsp() || LOG_TRACE_LEVEL((TRACE_DSP_DISASM|TRACE_DSP_SYMBOLS))) { DSP_SetDebugging(true); nDspInstructions = 0; } else DSP_SetDebugging(false); } static const dbgcommand_t dspcommands[] = { { NULL, NULL, "DSP commands", NULL, NULL, NULL, false }, { DebugDsp_BreakAddr, Symbols_MatchDspCodeAddress, "dspaddress", "da", "set DSP PC address breakpoints", BreakAddr_Description, true }, /* currently no DSP variables, so checks that DSP symbol addresses */ { DebugDsp_BreakCond, Symbols_MatchDspAddress, "dspbreak", "db", "set/remove/list conditional DSP breakpoints", BreakCond_Description, true }, { DebugDsp_DisAsm, Symbols_MatchDspCodeAddress, "dspdisasm", "dd", "disassemble DSP code", "[[-]]\n" "\tDisassemble from DSP PC address, otherwise from given address.", false }, { DebugDsp_MemDump, Symbols_MatchDspDataAddress, "dspmemdump", "dm", "dump DSP memory", "[ [-]]\n" "\tdump DSP memory from given memory space and address, or\n" "\tcontinue from previous address if none specified.", false }, { Symbols_Command, Symbols_MatchDspCommand, "dspsymbols", "", "load DSP symbols & their addresses", Symbols_Description, false }, { DebugDsp_Profile, Profile_Match, "dspprofile", "dp", "profile DSP code", Profile_Description, false }, { DebugDsp_Register, DebugDsp_MatchRegister, "dspreg", "dr", "read/write DSP registers", "[REG=value]\n" "\tSet DSP register to given value, or dump all registers\n" "\twhen no parameter is given.", true }, { DebugDsp_Step, NULL, "dspstep", "ds", "single-step DSP", "\n" "\tExecute next DSP instruction (like 'dc 1', but repeats on Enter).", false }, { DebugDsp_Next, DebugDsp_MatchNext, "dspnext", "dn", "step DSP through subroutine calls / to given instruction type", "[instruction type]\n" "\tSame as 'dspstep' command if there are no subroutine calls.\n" "\tWhen there are, those calls are treated as one instruction.\n" "\tIf argument is given, continues until instruction of given\n" "\ttype is encountered. Repeats on Enter.", false }, { DebugDsp_Continue, NULL, "dspcont", "dc", "continue emulation / DSP single-stepping", "[steps]\n" "\tLeave debugger and continue emulation for DSP instructions\n" "\tor forever if no steps have been specified.", false } }; /** * Should be called when debugger is first entered to initialize * DSP debugging variables. * * if you want disassembly or memdumping to start/continue from * specific address, you can set them here. If disassembly * address is zero, disassembling starts from PC. * * returns number of DSP commands and pointer to array of them. */ int DebugDsp_Init(const dbgcommand_t **table) { dsp_disasm_addr = 0; dsp_memdump_addr = 0; dsp_mem_space = 'P'; *table = dspcommands; return ARRAY_SIZE(dspcommands); } /** * Should be called when debugger is re-entered to reset * relevant DSP debugging variables. */ void DebugDsp_InitSession(void) { #define MAX_DSP_DISASM_OFFSET 8 dsp_disasm_addr = (uint16_t)History_DisasmAddr(DSP_GetPC(), MAX_DSP_DISASM_OFFSET, true); Profile_DspStop(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugdsp.h000066400000000000000000000014171504763705000243670ustar00rootroot00000000000000/* Hatari - debugdsp.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Public DSP debugging header file. */ #ifndef HATARI_DEBUGDSP_H #define HATARI_DEBUGDSP_H #ifdef ENABLE_DSP_EMU extern void DebugDsp_SetDebugging(void); #else /* !ENABLE_DSP_EMU */ #define DebugDsp_SetDebugging() #endif /* !ENABLE_DSP_EMU */ extern void DebugDsp_Check(void); extern uint32_t DebugDsp_CallDepth(void); extern uint32_t DebugDsp_InstrCount(void); extern uint32_t DebugDsp_OpcodeType(void); extern int DebugDsp_DisAsm(int nArgc, char *psArgs[]); extern int DebugDsp_MemDump(int nArgc, char *psArgs[]); extern int DebugDsp_Register(int nArgc, char *psArgs[]); #endif /* HATARI_DEBUGDSP_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugui.c000066400000000000000000001024521504763705000242120ustar00rootroot00000000000000/* Hatari - debugui.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. debugui.c - this is the code for the mini-debugger. When the pause button is pressed, the emulator is (hopefully) halted and this little CLI can be used (in the terminal box) for debugging tasks like memory and register dumps. */ const char DebugUI_fileid[] = "Hatari debugui.c"; #include #include #include #include #include "config.h" #if HAVE_LIBREADLINE #include #include #endif #include "main.h" #include "change.h" #include "configuration.h" #include "file.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "screenSnapShot.h" #include "options.h" #include "reset.h" #include "screen.h" #include "statusbar.h" #include "str.h" #include "debug_priv.h" #include "breakcond.h" #include "debugcpu.h" #include "debugdsp.h" #include "68kDisass.h" #include "debugInfo.h" #include "debugui.h" #include "evaluate.h" #include "history.h" #include "profile.h" #include "symbols.h" #include "vars.h" FILE *debugOutput; static dbgcommand_t *debugCommand; static int debugCommands; /* stores last 'e' command result as hex, used for TAB-completion */ static char lastResult[10]; /* array of files from which to read debugger commands after debugger is initialized */ static char **parseFileNames; static int parseFiles; /* to which directory to change after (potentially recursed) scripts parsing finishes */ static char *finalDir; /** * Save/Restore snapshot of debugging session variables */ void DebugUI_MemorySnapShot_Capture(const char *path, bool bSave) { char *filename; filename = Str_Alloc(strlen(path) + strlen(".debug")); strcpy(filename, path); strcat(filename, ".debug"); if (bSave) { /* save breakpoints as debugger input file */ BreakCond_Save(filename); } else { /* remove current CPU and DSP breakpoints */ BreakCond_Command("all", false); BreakCond_Command("all", true); if (File_Exists(filename)) { /* and parse back the saved breakpoints */ DebugUI_ParseFile(filename, true, true); } } free(filename); } /** * Close a log file if open, and set it to default stream. */ static void DebugUI_SetLogDefault(void) { if (debugOutput != stderr) { if (debugOutput) { File_Close(debugOutput); fprintf(stderr, "Debug log closed.\n"); } debugOutput = stderr; } } /** * Open (or close) given log file. */ static int DebugUI_SetLogFile(int nArgc, char *psArgs[]) { if (debugOutput != stderr) { fprintf(stderr, "Debug log closed.\n"); File_Close(debugOutput); } debugOutput = stderr; if (nArgc > 1) { if ((debugOutput = File_Open(psArgs[1], "w"))) { fprintf(stderr, "Debug log '%s' opened.\n", psArgs[1]); } else { fprintf(stderr, "Debug log '%s' opening FAILED.\n", psArgs[1]); debugOutput = stderr; } } return DEBUGGER_CMDDONE; } /** * Helper to output given value as binary number */ void DebugUI_PrintBinary(FILE *fp, int minwidth, uint32_t value) { bool one, ones; int bit; ones = false; for (bit = 31; bit >= 0; bit--) { one = value & (1U << bit); if (ones || bit < minwidth || one) { fputc(one ? '1':'0', fp); ones = true; } } if (!ones) fputc('0', fp); } /** * Helper to print given value in all supported number bases */ static void DebugUI_PrintValue(uint32_t value) { fputs("= %", stderr); DebugUI_PrintBinary(stderr, 0, value); if (value & 0x80000000) fprintf(stderr, " (bin), #%u/%d (dec), $%x (hex)\n", value, (int)value, value); else fprintf(stderr, " (bin), #%u (dec), $%x (hex)\n", value, value); sprintf(lastResult, "%x", value); } /** * Command: Evaluate an expression with CPU reg and symbol parsing. */ static int DebugUI_Evaluate(int nArgc, char *psArgs[]) { const char *errstr, *expression = (const char *)psArgs[1]; uint32_t result; int offset; if (nArgc < 2) { return DebugUI_PrintCmdHelp(psArgs[0]); } errstr = Eval_Expression(expression, &result, &offset, false); if (errstr) fprintf(stderr, "ERROR in the expression:\n'%s'\n%*c-%s\n", expression, offset+3, '^', errstr); else DebugUI_PrintValue(result); return DEBUGGER_CMDDONE; } /** * Check whether given string is a two letter command starting with 'd' * or a long command starting with "dsp". String should be trimmed. * Return true if given string is command for DSP, false otherwise. */ static bool DebugUI_IsForDsp(const char *cmd) { return ((cmd[0] == 'd' && isalpha((unsigned char)cmd[1]) && !isalpha((unsigned char)cmd[2])) || strncmp(cmd, "dsp", 3) == 0); } /** * Evaluate everything include within single or double quotes ("" or '') * and replace them with the result. * Caller needs to free the returned string separately. * * Return new string with expressions (potentially) expanded, or * NULL when there's an error in the expression. */ static char *DebugUI_EvaluateExpressions(const char *initial) { int offset, count, diff, inputlen; char *end, *start, *input; const char *errstr; char valuestr[12]; uint32_t value; bool fordsp; /* input is split later on, need to save len here */ input = strdup(initial); if (!input) { perror("ERROR: Input string alloc failed\n"); return NULL; } fordsp = DebugUI_IsForDsp(input); inputlen = strlen(input); start = input; while ((count = strcspn(start, "\"'")) && *(start+count)) { start += count; end = strchr(start+1, *start); if (!end) { fprintf(stderr, "ERROR: matching '%c' missing from '%s'!\n", *start, start); free(input); return NULL; } if (end == start+1) { /* empty expression */ memmove(start, start+2, strlen(start+2)+1); continue; } *end = '\0'; errstr = Eval_Expression(start+1, &value, &offset, fordsp); if (errstr) { *end = *start; /* restore expression mark */ fprintf(stderr, "Expression ERROR:\n'%s'\n%*c-%s\n", input, (int)(start-input)+offset+3, '^', errstr); free(input); return NULL; } end++; count = sprintf(valuestr, "$%x", value); fprintf(stderr, "- '%s' -> %s\n", start+1, valuestr); diff = end-start; if (count < diff) { memcpy(start, valuestr, count); start += count; memmove(start, end, strlen(end) + 1); } else { /* value won't fit to expression, expand string */ char *tmp; inputlen += count-diff+1; tmp = malloc(inputlen+1); if (!tmp) { perror("ERROR: Input string alloc failed\n"); free(input); return NULL; } memcpy(tmp, input, start-input); start = tmp+(start-input); memcpy(start, valuestr, count); start += count; memcpy(start, end, strlen(end) + 1); free(input); input = tmp; } } /* no (more) expressions to evaluate */ return input; } /** * Command: Store and restore emulation state */ static int DebugUI_DoMemorySnap(int argc, char *argv[]) { const char *file; if (argc > 1) file = argv[1]; else file = ConfigureParams.Memory.szMemoryCaptureFileName; /* [NP] TODO : we need to restart emulation to complete restore, */ /* it can't be done immediately. Try to call m68k_go() and go back automatically to debugger ? */ if (strcmp(argv[0], "stateload") == 0) MemorySnapShot_Restore(file, true); else MemorySnapShot_Capture_Immediate(file, true); return DEBUGGER_CMDDONE; } /** * Command: Set command line and debugger options */ static int DebugUI_SetOptions(int argc, char *argv[]) { CNF_PARAMS current; static const struct { const char name[4]; int base; } bases[] = { { "bin", 2 }, { "dec", 10 }, { "hex", 16 } }; const char *arg; int i; if (argc < 2) { return DebugUI_PrintCmdHelp(argv[0]); } arg = argv[1]; for (i = 0; i < ARRAY_SIZE(bases); i++) { if (strcasecmp(bases[i].name, arg) == 0) { if (ConfigureParams.Debugger.nNumberBase != bases[i].base) { fprintf(stderr, "Switched default number base from %d to %d-based (%s) values.\n", ConfigureParams.Debugger.nNumberBase, bases[i].base, bases[i].name); ConfigureParams.Debugger.nNumberBase = bases[i].base; } else { fprintf(stderr, "Already in '%s' mode.\n", bases[i].name); } return DEBUGGER_CMDDONE; } } /* get configuration changes */ current = ConfigureParams; /* Parse and apply options */ if (Opt_ParseParameters(argc, (const char * const *)argv)) { ConfigureParams.Screen.bFullScreen = false; Change_CopyChangedParamsToConfiguration(¤t, &ConfigureParams, false); } else { ConfigureParams = current; } return DEBUGGER_CMDDONE; } /** * Command: Screenshot */ static int DebugUI_Screenshot(int argc, char *argv[]) { if (argc == 2) ScreenSnapShot_SaveToFile(argv[1]); else return DebugUI_PrintCmdHelp(argv[0]); return DEBUGGER_CMDDONE; } /** * Command: Set tracing */ static int DebugUI_SetTracing(int argc, char *argv[]) { const char *errstr; if (argc != 2) { return DebugUI_PrintCmdHelp(argv[0]); } errstr = Log_SetTraceOptions(argv[1]); if (errstr && errstr[0]) fprintf(stderr, "ERROR: %s\n", errstr); return DEBUGGER_CMDDONE; } /** * Command: Change Hatari work directory */ static int DebugUI_ChangeDir(int argc, char *argv[]) { if (argc == 3 && strcmp("-f", argv[2]) == 0) { if (finalDir) free(finalDir); finalDir = strdup(argv[1]); fprintf(stderr, "Will switch to '%s' dir after all scripts have finished.\n", argv[1]); return DEBUGGER_CMDDONE; } if (argc == 2) { if (chdir(argv[1]) == 0) return DEBUGGER_CMDDONE; perror("ERROR"); } return DebugUI_PrintCmdHelp(argv[0]); } /** * Command: Print strings to debug log output, with escape handling. */ static int DebugUI_Echo(int argc, char *argv[]) { if (argc < 2) return DebugUI_PrintCmdHelp(argv[0]); for (int i = 1; i < argc; i++) { Str_UnEscape(argv[i]); fputs(argv[i], debugOutput); } return DEBUGGER_CMDDONE; } /** * Command: Rename file */ static int DebugUI_Rename(int argc, char *argv[]) { if (argc == 3) { if (rename(argv[1], argv[2]) == 0) return DEBUGGER_CMDDONE; perror("ERROR"); } return DebugUI_PrintCmdHelp(argv[0]); } /** * Command: Reset emulation */ static char *DebugUI_MatchReset(const char *text, int state) { static const char* types[] = { "cold", "hard", "soft", "warm" }; return DebugUI_MatchHelper(types, ARRAY_SIZE(types), text, state); } static int DebugUI_Reset(int argc, char *argv[]) { if (argc != 2) return DebugUI_PrintCmdHelp(argv[0]); if (strcmp(argv[1], "soft") == 0 || strcmp(argv[1], "warm") == 0) Reset_Warm(); else if (strcmp(argv[1], "cold") == 0 || strcmp(argv[1], "hard") == 0) Reset_Cold(); else return DebugUI_PrintCmdHelp(argv[0]); return DEBUGGER_END; } /** * Command: Read debugger commands from a file */ static int DebugUI_CommandsFromFile(int argc, char *argv[]) { if (argc == 2) DebugUI_ParseFile(argv[1], true, true); else DebugUI_PrintCmdHelp(argv[0]); return DEBUGGER_CMDDONE; } /** * Command: Quit emulator */ static int DebugUI_QuitEmu(int nArgc, char *psArgv[]) { int exitval; if (nArgc > 2) return DebugUI_PrintCmdHelp(psArgv[0]); if (nArgc == 2) exitval = atoi(psArgv[1]); else exitval = 0; ConfigureParams.Log.bConfirmQuit = false; Main_RequestQuit(exitval); return DEBUGGER_END; } /** * Print help text for one command */ int DebugUI_PrintCmdHelp(const char *psCmd) { dbgcommand_t *cmd; int i; /* Search the command ... */ for (cmd = debugCommand, i = 0; i < debugCommands; i++, cmd++) { if (!debugCommand[i].pFunction) continue; if ((*(cmd->sShortName) && !strcmp(psCmd, cmd->sShortName)) || !strcmp(psCmd, cmd->sLongName)) { bool bShort = *(cmd->sShortName); /* ... and print help text */ if (bShort) { fprintf(stderr, "'%s' or '%s' - %s\n", cmd->sLongName, cmd->sShortName, cmd->sShortDesc); } else { fprintf(stderr, "'%s' - %s\n", cmd->sLongName, cmd->sShortDesc); } fprintf(stderr, "Usage: %s %s\n", bShort ? cmd->sShortName : cmd->sLongName, cmd->sUsage); return DEBUGGER_CMDDONE; } } fprintf(stderr, "Unknown command '%s'\n", psCmd); return DEBUGGER_CMDDONE; } /** * Command: Print debugger help screen. */ static int DebugUI_Help(int nArgc, char *psArgs[]) { int i; if (nArgc > 1) { return DebugUI_PrintCmdHelp(psArgs[1]); } for (i = 0; i < debugCommands; i++) { if (!debugCommand[i].pFunction) { fprintf(stderr, "\n%s:\n", debugCommand[i].sLongName); continue; } fprintf(stderr, " %12s (%2s) : %s\n", debugCommand[i].sLongName, debugCommand[i].sShortName, debugCommand[i].sShortDesc); } fprintf(stderr, "\n" "If value is prefixed with '$', it's a hexadecimal, if with '#', it's\n" "a normal decimal, if with '%%', it's a binary decimal. Prefix can\n" "be skipped for numbers in the default number base (currently %d).\n" "\n" "Any expression given in quotes (within \"\"), will be evaluated\n" "before given to the debugger command. Any register and symbol\n" "names in the expression are replaced by their values.\n" "\n" "Note that address ranges like '$fc0000-$fc0100' should have no\n" "spaces between the range numbers.\n" "\n" "'help ' gives more help.\n", ConfigureParams.Debugger.nNumberBase); return DEBUGGER_CMDDONE; } /** * Parse debug command and execute it. */ static int DebugUI_ParseCommand(const char *input_orig) { char *psArgs[64], *input; const char *delim; static char sLastCmd[80] = { '\0' }; int nArgc, cmd = -1; int i, retval; input = strdup(input_orig); psArgs[0] = strtok(input, " \t"); if (psArgs[0] == NULL) { if (strlen(sLastCmd) > 0) psArgs[0] = sLastCmd; else { free(input); return DEBUGGER_CMDDONE; } } /* Search the command ... */ for (i = 0; i < debugCommands; i++) { if (!debugCommand[i].pFunction) continue; if (!strcmp(psArgs[0], debugCommand[i].sShortName) || !strcmp(psArgs[0], debugCommand[i].sLongName)) { cmd = i; break; } } if (cmd == -1) { fprintf(stderr, "Command '%s' not found.\n" "Use 'help' to view a list of available debugger commands.\n", psArgs[0]); free(input); return DEBUGGER_CMDDONE; } if (debugCommand[cmd].bNoParsing) delim = ""; else delim = " \t"; /* Separate arguments and put the pointers into psArgs */ for (nArgc = 1; nArgc < ARRAY_SIZE(psArgs); nArgc++) { psArgs[nArgc] = strtok(NULL, delim); if (psArgs[nArgc] == NULL) break; } if (nArgc >= ARRAY_SIZE(psArgs)) { fprintf(stderr, "Error: too many arguments (currently up to %d supported)\n", ARRAY_SIZE(psArgs)); retval = DEBUGGER_CMDCONT; } else { /* ... and execute the function */ retval = debugCommand[i].pFunction(nArgc, psArgs); } /* Save commando string if it can be repeated */ if (retval == DEBUGGER_CMDCONT || retval == DEBUGGER_ENDCONT) { if (psArgs[0] != sLastCmd) Str_Copy(sLastCmd, psArgs[0], sizeof(sLastCmd)); if (retval == DEBUGGER_ENDCONT) retval = DEBUGGER_END; } else sLastCmd[0] = '\0'; free(input); return retval; } /* See "info:readline" e.g. in Konqueror for readline usage. */ /** * Generic readline match callback helper. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *DebugUI_MatchHelper(const char **strings, int items, const char *text, int state) { static int i, len; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i < items) { if (strncasecmp(strings[i++], text, len) == 0) return (strdup(strings[i-1])); } return NULL; } /** * Readline match callback for long command name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ static char *DebugUI_MatchCommand(const char *text, int state) { static int i, len; const char *name; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i < debugCommands) { name = debugCommand[i].sLongName; if (debugCommand[i++].pFunction && strncmp(name, text, len) == 0) return (strdup(name)); } return NULL; } #if HAVE_LIBREADLINE /** * Readline match callback returning last result. */ static char *DebugUI_MatchLast(const char *text, int state) { if (state) return NULL; return strdup(lastResult); } /** * Readline completion callback. Returns matches. */ static char **DebugUI_Completion(const char *text, int a, int b) { int i, cmd, quotes, end, start = 0; char *str, buf[32]; size_t len; /* check where's the first word (ignore white space) */ while (start < rl_point && isspace((unsigned char)rl_line_buffer[start])) start++; end = start; while (end < rl_point && !isspace((unsigned char)rl_line_buffer[end])) end++; if (end >= rl_point) /* first word on line */ return rl_completion_matches(text, DebugUI_MatchCommand); /* complete '$' with last result? */ if (lastResult[0] && rl_line_buffer[rl_point-1] == '$') return rl_completion_matches(text, DebugUI_MatchLast); /* check which command args are to be completed */ len = end - start; if (len >= sizeof(buf)) len = sizeof(buf)-1; memcpy(buf, &(rl_line_buffer[start]), len); buf[len] = '\0'; /* expression completion needed (= open quote)? */ str = strchr(&(rl_line_buffer[end]), '"'); quotes = 0; while (str) { quotes++; str = strchr(str+1, '"'); } if (quotes & 1) { if (DebugUI_IsForDsp(buf)) return rl_completion_matches(text, Symbols_MatchDspAddress); return rl_completion_matches(text, Symbols_MatchCpuAddress); } /* do command argument completion */ cmd = -1; for (i = 0; i < debugCommands; i++) { if (!debugCommand[i].pFunction) continue; if (!strcmp(buf, debugCommand[i].sShortName) || !strcmp(buf, debugCommand[i].sLongName)) { cmd = i; break; } } if (cmd < 0) { rl_attempted_completion_over = true; return NULL; } if (debugCommand[cmd].pMatch) return rl_completion_matches(text, debugCommand[cmd].pMatch); else return rl_completion_matches(text, rl_filename_completion_function); } /** * Add non-repeated command to readline history * and free the given string */ static void DebugUI_FreeCommand(char *input) { if (input && *input) { HIST_ENTRY *hist = history_get(history_length); /* don't store duplicate successive entries */ if (!hist || !hist->line || strcmp(hist->line, input) != 0) { add_history(input); } free(input); } } /** * Read a command line from the keyboard and return a pointer to the string. * Only string returned by this function can be given for it as argument! * The string will be stored into command history buffer. * @return Pointer to the string which should be given back to this * function or DebugUI_FreeCommand() for re-use/history. * Returns NULL when error occurred. */ static char *DebugUI_GetCommand(char *input) { /* We need this indirection for libedit's rl_readline_name which is * not declared as "const char *" (i.e. this is necessary for macOS) */ static char hatari_readline_name[] = "Hatari"; /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = hatari_readline_name; /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = DebugUI_Completion; DebugUI_FreeCommand(input); return Str_Trim(readline("> ")); } /** * Get readlines idea of the terminal size */ static void DebugUI_GetScreenSize(int *rows, int *cols) { rl_get_screen_size(rows, cols); } #else /* !HAVE_LIBREADLINE */ /** * Free Command input string */ static void DebugUI_FreeCommand(char *input) { free(input); } /** * Get number of lines/columns for terminal output */ static void DebugUI_GetScreenSize(int *rows, int *cols) { const char *p; *rows = 24; *cols = 80; if ((p = getenv("LINES")) != NULL) *rows = (int)strtol(p, NULL, 0); if ((p = getenv("COLUMNS")) != NULL) *cols = (int)strtol(p, NULL, 0); } /** * Read a command line from the keyboard and return a pointer to the string. * Only string returned by this function can be given for it as argument! * @return Pointer to the string which should be given back to this * function or DebugUI_FreeCommand() for re-use/freeing. * Returns NULL when error occurred. */ static char *DebugUI_GetCommand(char *input) { fprintf(stderr, "> "); if (!input) { input = malloc(256); if (!input) return NULL; } input[0] = '\0'; if (fgets(input, 256, stdin) == NULL) { free(input); return NULL; } return Str_Trim(input); } #endif /* !HAVE_LIBREADLINE */ /** * How many lines to "page" when user invokes calling command. * * If config value is >=0, use that. If it's negative, get number of lines * from screensize. If even that's not defined, fall back to default value. * * @return Number of lines to output at the time. */ int DebugUI_GetPageLines(int config, int defvalue) { int rows, cols; if (config >= 0) { return config; } DebugUI_GetScreenSize(&rows, &cols); /* leave 1 line for pager prompt */ if (--rows > 0) { return rows; } return defvalue; } static const dbgcommand_t uicommand[] = { { NULL, NULL, "Generic commands", NULL, NULL, NULL, false }, /* NULL as match function will complete file names */ { DebugUI_ChangeDir, NULL, "cd", "", "change directory", " [-f]\n" "\tChange Hatari work directory. With '-f', directory is\n" "\tchanged only after all script files have been parsed.", false }, { DebugUI_Echo, NULL, "echo", "", "output given string(s)", "\n" "\tUse e.g. 'echo \\ec' to clear screen in a breakpoint.", false }, { DebugUI_Evaluate, Vars_MatchCpuVariable, "evaluate", "e", "evaluate an expression", "\n" "\tEvaluate an expression and show the result. Expression can\n" "\tinclude CPU register & symbol and Hatari variable names.\n" "\tThose are replaced by their values. Supported operators in\n" "\texpressions are, in the descending order of precedence:\n" "\t\t(), +, -, ~, *, /, +, -, >>, <<, ^, &, |\n" "\tParenthesis fetch long value from the given address,\n" "\tunless . suffix is given. Prefixes can be\n" "\tused only in start of line or parenthesis.\n" "\tFor example:\n" "\t\t~%101 & $f0f0f ^ (d0 + 0x21).w\n" "\tResult value is shown as binary, decimal and hexadecimal.\n" "\tAfter this, '$' will TAB-complete to last result value.", true }, { DebugUI_Help, DebugUI_MatchCommand, "help", "h", "print help", "[command]\n" "\tPrint help text for available commands.", false }, { History_Parse, History_Match, "history", "hi", "show last CPU and/or DSP PC values + instructions", "cpu|dsp|on|off| [limit]|save \n" "\t'cpu' and 'dsp' enable program counter history tracking for given\n" "\tprocessor, 'on' tracks them both, 'off' will disable history.\n" "\tOptional 'limit' will set how many past addresses are tracked.\n" "\tGiving just count will show (at max) given number of last saved PC\n" "\tvalues and instructions currently at corresponding RAM addresses.", false }, { DebugInfo_Command, DebugInfo_MatchInfo, "info", "i", "show machine/OS information", "[subject [arg]]\n" "\tPrint information on requested subject or list them if\n" "\tno subject given.", false }, { DebugInfo_Command, DebugInfo_MatchLock, "lock", "", "specify information to show on entering the debugger", "[subject [args]]\n" "\tLock what information should be shown every time debugger\n" "\tis entered, or list available options if no subject's given.", false }, { DebugUI_SetLogFile, NULL, "logfile", "f", "set (memdump/disasm/registers) log file", "[filename]\n" "\tOpen log file, no argument closes the log file. Output of\n" "\tregister & memory dumps and disassembly will be written to it.", false }, { DebugUI_CommandsFromFile, NULL, "parse", "p", "get debugger commands from file", "[filename]\n" "\tRead debugger commands from given file and do them.\n" "\tCurrent directory is script directory during this.\n" "\tTo specify directory to be used also for breakpoint\n" "\tscripts execution, use '-f' option for 'cd' command.", false }, { DebugUI_Rename, NULL, "rename", "", "rename given file", " \n" "\tRename file with name to .", false }, { DebugUI_Reset, DebugUI_MatchReset, "reset", "", "reset emulation", "\n", false }, { DebugUI_Screenshot, NULL, "screenshot", "", "save screenshot to given file", "\n", false }, { DebugUI_SetOptions, Opt_MatchOption, "setopt", "o", "set Hatari command line and debugger options", "[bin|dec|hex|]\n" "\tSpecial 'bin', 'dec' and 'hex' arguments change the default\n" "\tnumber base used in debugger. lists available command\n" "\tline options, 'setopt --help' their descriptions.", false }, { DebugUI_DoMemorySnap, NULL, "stateload", "", "restore emulation state", "[filename]\n" "\tRestore emulation snapshot from default or given file", false }, { DebugUI_DoMemorySnap, NULL, "statesave", "", "save emulation state", "[filename]\n" "\tSave emulation snapshot to default or given file", false }, { DebugUI_SetTracing, Log_MatchTrace, "trace", "t", "select Hatari tracing settings", "[set1,set2...]\n" "\tSelect Hatari tracing settings. 'help' shows all the available\n" "\tsettings. For example, to enable CPU disassembly and VBL\n" "\ttracing, use:\n\t\ttrace cpu_disasm,video_hbl", false }, { Vars_List, NULL, "variables", "v", "List builtin symbols / variables", "\n" "\tList Hatari debugger builtin symbols / variables and their values.\n" "\tThey're accepted by breakpoints and evaluate command.", false }, { DebugUI_QuitEmu, NULL, "quit", "q", "quit emulator", "[exit value]\n" "\tLeave debugger and quit emulator with given exit value.", false } }; /** * Debugger user interface initialization. */ void DebugUI_Init(void) { const dbgcommand_t *cpucmd, *dspcmd; int cpucmds, dspcmds; Log_ResetMsgRepeat(); /* already initialized? */ if (debugCommands) return; if (!debugOutput) DebugUI_SetLogDefault(); /* if you want disassembly or memdumping to start/continue from * specific address, you can set them in these functions. */ dspcmds = DebugDsp_Init(&dspcmd); cpucmds = DebugCpu_Init(&cpucmd); /* on first time copy the command structures to a single table */ debugCommands = ARRAY_SIZE(uicommand); debugCommand = malloc(sizeof(dbgcommand_t) * (dspcmds + cpucmds + debugCommands)); assert(debugCommand); memcpy(debugCommand, uicommand, sizeof(dbgcommand_t) * debugCommands); memcpy(&debugCommand[debugCommands], cpucmd, sizeof(dbgcommand_t) * cpucmds); debugCommands += cpucmds; memcpy(&debugCommand[debugCommands], dspcmd, sizeof(dbgcommand_t) * dspcmds); debugCommands += dspcmds; if (parseFiles) { int i; for (i = 0; i < parseFiles; i++) { DebugUI_ParseFile(parseFileNames[i], true, true); free(parseFileNames[i]); } free(parseFileNames); parseFileNames = NULL; parseFiles = 0; } } /** * Debugger user interface de-initialization / free. */ void DebugUI_UnInit(void) { Profile_CpuFree(); Profile_DspFree(); Symbols_FreeAll(); free(debugCommand); debugCommands = 0; } /** * Return true if user wants to quit current command */ bool DebugUI_DoQuitQuery(const char *info) { char input[8]; fprintf(stderr, "--- q to exit %s, enter to continue --- ", info); if (fgets(input, sizeof(input), stdin) == NULL || toupper(input[0]) == 'Q') { return true; } return false; } /** * Add debugger command files during Hatari startup before things * needed by the debugger are initialized so that it can be parsed * when debugger itself gets initialized. * Return true if file exists and it could be added, false otherwise. */ bool DebugUI_AddParseFile(const char *path) { if (!File_Exists(path)) { fprintf(stderr, "ERROR: debugger input file '%s' missing.\n", path); return false; } parseFileNames = realloc(parseFileNames, (parseFiles+1)*sizeof(char*)); if (!parseFileNames) { perror("DebugUI_AddParseFile"); return false; } parseFileNames[parseFiles++] = strdup(path); return true; } /** * Debugger user interface main function. */ void DebugUI(debug_reason_t reason) { int cmdret, alertLevel; char *expCmd, *psCmd = NULL; static const char *welcome = "\n----------------------------------------------------------------------" "\nYou have entered debug mode. Type c to continue emulation, h for help.\n"; static bool recursing; if (recursing) { fprintf(stderr, "WARNING: recursive call to DebugUI (through profiler debug option?)!\n"); recursing = false; return; } recursing = true; History_Mark(reason); if (bInFullScreen) Screen_ReturnFromFullScreen(); /* Make sure mouse isn't grabbed regardless of where * this is invoked from. E.g. returning from fullscreen * enables grab if that was enabled on windowed mode. */ SDL_SetRelativeMouseMode(false); DebugUI_Init(); if (welcome) { fputs(welcome, stderr); welcome = NULL; } DebugCpu_InitSession(); DebugDsp_InitSession(); Symbols_LoadCurrentProgram(); DebugInfo_ShowSessionInfo(); /* override paused message so that user knows to look into console * on how to continue in case he invoked the debugger by accident. */ Statusbar_AddMessage("Console Debugger", 100); Statusbar_Update(sdlscrn, true); /* disable normal GUI alerts while on console */ alertLevel = Log_SetAlertLevel(LOG_FATAL); cmdret = DEBUGGER_CMDDONE; do { /* Read command from the keyboard and give previous * command for freeing / adding to history */ psCmd = DebugUI_GetCommand(psCmd); if (!psCmd) break; /* returns new expression expanded string */ if (!(expCmd = DebugUI_EvaluateExpressions(psCmd))) continue; /* Parse and execute the command string */ cmdret = DebugUI_ParseCommand(expCmd); free(expCmd); } while (cmdret != DEBUGGER_END); /* free exit command */ DebugUI_FreeCommand(psCmd); Log_SetAlertLevel(alertLevel); DebugCpu_SetDebugging(); DebugDsp_SetDebugging(); recursing = false; } /** * Read debugger commands from a file. If 'reinit' is set (as it * normally should), reinitialize breakpoints etc. afterwards. * Processed command lines are printed if 'verbose' is set. * return false for error, true for success. */ bool DebugUI_ParseFile(const char *path, bool reinit, bool verbose) { int recurse; static int recursing; char *olddir, *dir, *cmd, *expanded, *slash; char input[256]; FILE *fp; if (verbose) fprintf(stderr, "Reading debugger commands from '%s'...\n", path); if (!(fp = fopen(path, "r"))) { perror("ERROR"); return false; } /* change to directory where the debugger file resides */ olddir = NULL; dir = strdup(path); slash = strrchr(dir, PATHSEP); if (slash) { olddir = malloc(FILENAME_MAX); if (olddir) { if (!getcwd(olddir, FILENAME_MAX)) strcpy(olddir, "."); } *slash = '\0'; if (chdir(dir) != 0) { perror("ERROR"); free(olddir); free(dir); fclose(fp); return false; } if (verbose) fprintf(stderr, "Changed to input file dir '%s'.\n", dir); } free(dir); recurse = recursing; recursing = true; while (fgets(input, sizeof(input), fp) != NULL) { /* ignore empty and comment lines */ cmd = Str_Trim(input); if (!*cmd || *cmd == '#') continue; /* returns new string if input needed expanding! */ expanded = DebugUI_EvaluateExpressions(input); if (!expanded) continue; cmd = Str_Trim(expanded); if (verbose) fprintf(stderr, "> %s\n", cmd); DebugUI_ParseCommand(cmd); free(expanded); } recursing = false; fclose(fp); if (olddir) { if (chdir(olddir) != 0) perror("ERROR"); else if (verbose) fprintf(stderr, "Changed back to '%s' dir.\n", olddir); free(olddir); } if (!recurse) { /* current script (or something called by it) specified final dir */ if (finalDir) { if (chdir(finalDir) != 0) perror("ERROR"); else if(verbose) fprintf(stderr, "Delayed change to '%s' dir.\n", finalDir); free(finalDir); finalDir = NULL; } /* only top-level (non-recursed) call has valid re-init info, * as that's the only one that can get directly called from * breakpoints */ if (reinit) { DebugCpu_SetDebugging(); DebugDsp_SetDebugging(); } } return true; } /** * Remote/parallel debugger line usage API. * Return false for failed command, true for success. */ bool DebugUI_ParseLine(const char *input) { char *expanded; int ret = 0; DebugUI_Init(); /* returns new string if input needed expanding! */ expanded = DebugUI_EvaluateExpressions(input); if (expanded) { fprintf(stderr, "> %s\n", expanded); ret = DebugUI_ParseCommand(expanded); free(expanded); DebugCpu_SetDebugging(); DebugDsp_SetDebugging(); } return (ret == DEBUGGER_CMDDONE); } /** * Debugger invocation based on exception */ void DebugUI_Exceptions(int nr, long pc) { static struct { int flag; const char *name; } ex[] = { { EXCEPT_BUS, "Bus error" }, /* 2 */ { EXCEPT_ADDRESS, "Address error" }, /* 3 */ { EXCEPT_ILLEGAL, "Illegal instruction" }, /* 4 */ { EXCEPT_ZERODIV, "Div by zero" }, /* 5 */ { EXCEPT_CHK, "CHK" }, /* 6 */ { EXCEPT_TRAPV, "TRAPCc/TRAPV" }, /* 7 */ { EXCEPT_PRIVILEGE, "Privilege violation" }, /* 8 */ { EXCEPT_TRACE, "Trace" }, /* 9 */ { EXCEPT_LINEA, "Line-A" }, /* 10 */ { EXCEPT_LINEF, "Line-F" } /* 11 */ }; nr -= 2; if (nr < 0 || nr >= ARRAY_SIZE(ex)) return; if (!(ExceptionDebugMask & ex[nr].flag)) return; fprintf(stderr,"%s exception at 0x%lx!\n", ex[nr].name, pc); DebugUI(REASON_CPU_EXCEPTION); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/debugui.h000066400000000000000000000021611504763705000242130ustar00rootroot00000000000000/* Hatari - debugui.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Public debugger UI header file. */ #ifndef HATARI_DEBUGUI_H #define HATARI_DEBUGUI_H /* DebugUI_ParseCommand() return values */ enum { DEBUGGER_END, // Leave debugger DEBUGGER_ENDCONT, // Command can be repeated + leaves debugger DEBUGGER_CMDCONT, // Command can be repeated DEBUGGER_CMDDONE // Command done }; typedef enum { REASON_NONE, // uninitialized REASON_CPU_EXCEPTION, REASON_DSP_EXCEPTION, REASON_CPU_BREAKPOINT, REASON_DSP_BREAKPOINT, REASON_CPU_STEPS, REASON_DSP_STEPS, REASON_PROGRAM, REASON_USER // e.g. keyboard shortcut } debug_reason_t; extern void DebugUI_Init(void); extern void DebugUI(debug_reason_t reason); extern void DebugUI_Exceptions(int nr, long pc); extern bool DebugUI_ParseLine(const char *input); extern bool DebugUI_AddParseFile(const char *input); extern void DebugUI_MemorySnapShot_Capture(const char *path, bool bSave); extern void DebugUI_UnInit(void); #endif /* HATARI_DEBUGUI_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/evaluate.c000066400000000000000000000433201504763705000243720ustar00rootroot00000000000000/* Hatari - evaluate.c Copyright (C) 1994, 2009-2014, 2022-2023 by Eero Tamminen This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. calculate.c - parse numbers, number ranges and expressions. Supports most unary and binary operations. Parenthesis are used for indirect ST RAM value addressing. Originally based on code from my Clac calculator MiNT filter version. */ const char Eval_fileid[] = "Hatari calculate.c"; #include #include #include #include #include #include #include #include "configuration.h" #include "dsp.h" #include "debugcpu.h" #include "evaluate.h" #include "main.h" #include "m68000.h" #include "stMemory.h" #include "symbols.h" #include "vars.h" /* define which character indicates which type of number on expression */ #define PREFIX_BIN '%' /* binary decimal */ #define PREFIX_DEC '#' /* normal decimal */ #define PREFIX_HEX '$' /* hexadecimal */ /* define error codes */ #define CLAC_EXP_ERR "No expression given" #define CLAC_GEN_ERR "Syntax error" #define CLAC_PAR_ERR "Mismatched parenthesis" #define CLAC_DEF_ERR "Undefined result (1/0)" #define CLAC_STK_ERR "Operation/value stack full" #define CLAC_OVF_ERR "Overflow" #define CLAC_OVR_ERR "Mode overflow" #define CLAC_PRG_ERR "Internal program error" #define CLAC_WDT_ERR "Invalid address width" /* define internal allocation sizes (should be enough ;-) */ #define PARDEPTH_MAX 16 /* max. parenth. nesting depth */ #define OSTACK_MAX 64 /* size of the operator stack */ #define VSTACK_MAX 64 /* size of the value stack */ /* operation with lowest precedence, used to finish calculations */ #define LOWEST_PREDECENCE '|' /* globals + function identifier stack(s) */ static struct { const char *error; /* global error code */ bool valid; /* value validation */ } id = { NULL, 0 }; /* parenthesis and function stacks */ static struct { int idx; /* parenthesis level */ int max; /* maximum idx */ int opx[PARDEPTH_MAX + 1]; /* current op index for par */ int vax[PARDEPTH_MAX + 1]; /* current val index for par */ } par = {0, PARDEPTH_MAX, {0}, {0}}; static struct { /* operator stack */ int idx; int max; char buf[OSTACK_MAX + 1]; } op = {0, OSTACK_MAX, ""}; static struct value_stk { /* value stack */ int idx; int max; long long buf[VSTACK_MAX + 1]; } val = {0, VSTACK_MAX, {0}}; /* -------------------------------------------------------------------- */ /* macros */ /* increment stack index and put value on stack (ie. PUSH) */ #define PUSH(stk,val) \ if((stk).idx < (stk).max) { \ (stk).idx += 1; \ (stk).buf[(stk).idx] = (val); \ } else { \ id.error = CLAC_STK_ERR; \ } /* -------------------------------------------------------------------- */ /* declare subfunctions */ /* parse in-between operations */ static void operation(long long value, char op); /* parse unary operators */ static void unary (char op); /* apply a prefix to a value */ static void apply_prefix(void); /* juggle stacks, if possible */ static void eval_stack(void); /* operator -> operator level */ static int get_level(int stk_offset); /* evaluate operation */ static long long apply_op(char op, long long x, long long y); /* increase parenthesis level */ static void open_bracket(void); /* decrease parenthesis level */ static long long close_bracket(long long x, char width); /* parse (addr).[bwl] width */ static int get_width (const char *str, char *width); /** * Parse & set an (unsigned) number, assuming it's in the configured * default number base unless it has a prefix: * - '$' / '0x' / '0h' => hexadecimal * - '#' / '0d' => normal decimal * - '%' / '0b' => binary decimal * - '0o' => octal decimal * Return how many characters were parsed or zero for error. */ static int getNumber(const char *str, uint32_t *number, int *nbase) { char *end; const char *start = str; int base = ConfigureParams.Debugger.nNumberBase; unsigned long int value; if (!str[0]) { fprintf(stderr, "Value missing!\n"); return 0; } /* determine correct number base */ if (str[0] == '0') { /* 0x & 0h = hex, 0d = dec, 0o = oct, 0b = bin ? */ switch(str[1]) { case 'b': base = 2; break; case 'o': base = 8; break; case 'd': base = 10; break; case 'h': case 'x': base = 16; break; default: str -= 2; } str += 2; } else if (!isxdigit((unsigned char)str[0])) { /* doesn't start with (hex) number -> is it prefix? */ switch (*str++) { case PREFIX_BIN: base = 2; break; case PREFIX_DEC: base = 10; break; case PREFIX_HEX: base = 16; break; default: fprintf(stderr, "Unrecognized number prefix in '%s'!\n", start); return 0; } } *nbase = base; /* parse number */ errno = 0; value = strtoul(str, &end, base); if (errno == ERANGE && value == LONG_MAX) { fprintf(stderr, "Overflow with value '%s'!\n", start); return 0; } if ((errno != 0 && value == 0) || end == str) { fprintf(stderr, "Invalid value '%s'!\n", start); return 0; } *number = value; return end - start; } /** * Parse unsigned register/symbol/number value and set it to "number" * and the number base used for parsing to "base". * Return how many characters were parsed or zero for error. */ static int getValue(const char *str, uint32_t *number, int *base, bool bForDsp) { char name[64]; const char *end; uint32_t mask, *addr; int len; for (end = str; *end == '_' || isalnum((unsigned char)*end); end++); len = end-str; if (len >= (int)sizeof(name)) { fprintf(stderr, "ERROR: symbol name at '%s' too long (%d chars)\n", str, len); return 0; } memcpy(name, str, len); name[len] = '\0'; *base = 0; /* no base (e.g. variable) */ /* internal Hatari variable? */ if (Vars_GetVariableValue(name, number)) { return len; } if (bForDsp) { int regsize = DSP_GetRegisterAddress(name, &addr, &mask); /* DSP register or symbol? */ switch (regsize) { case 16: *number = (*((uint16_t*)addr) & mask); return len; case 32: *number = (*addr & mask); return len; default: if (Symbols_GetDspAddress(SYMTYPE_ALL, name, number)) { return len; } } } else { /* a special case CPU register? */ if (strcasecmp(name, "PC") == 0) { *number = M68000_GetPC(); return len; } if (strcasecmp(name, "SR") == 0) { *number = M68000_GetSR(); return len; } /* a normal CPU register or symbol? */ if (DebugCpu_GetRegisterAddress(name, &addr)) { *number = *addr; return len; } if (Symbols_GetCpuAddress(SYMTYPE_ALL, name, number)) { return len; } } /* none of above, assume it's a number */ return getNumber(str, number, base); } /* Check that number string is OK and isn't followed by unrecognized * character (last char char is zero). If not, complain about it. * Return true for success and false for failure. */ static bool isNumberOK(const char *str, int offset, int base) { const char *basestr; if (!offset) { return false; } if (!str[offset]) { /* no extra chars after the parsed part */ return true; } switch (base) { case 0: fprintf(stderr, "Name '%s' contains non-alphanumeric characters!\n", str); return false; case 2: basestr = "binary"; break; case 8: basestr = "octal"; break; case 10: basestr = "decimal"; break; case 16: basestr = "hexadecimal"; break; default: basestr = "unknown"; } fprintf(stderr, "Extra characters in %s based number '%s'!\n", basestr, str); return false; } /** * Parse & set an (unsigned) number, assume it's in the configured * default number base unless it has a suitable prefix. * Return true for success and false for failure. */ bool Eval_Number(const char *str, uint32_t *number, num_type_t type) { int offset, base; if (type == NUM_TYPE_NORMAL) { offset = getNumber(str, number, &base); } else { offset = getValue(str, number, &base, type); } if (!offset) return false; else return isNumberOK(str, offset, base); } /** * Parse an address range, eg. "$fa0000[-$fa0100]" or "pc[-a0]" and * output appropriate warnings if range or values are invalid. * Address can also be a register/variable/symbol name. * returns: * -1 if invalid address or range, * 0 if single address, * +1 if a range. */ int Eval_Range(char *str1, uint32_t *lower, uint32_t *upper, bool bForDsp) { int offset, base, ret; bool fDash = false; char *str2 = str1; while (*str2) { if (*str2 == '-') { *str2++ = '\0'; fDash = true; break; } str2++; } offset = getValue(str1, lower, &base, bForDsp); if (offset == 0 || !isNumberOK(str1, offset, base)) { /* first number not OK */ fprintf(stderr,"Invalid address value '%s'!\n", str1); ret = -1; } else { /* first number OK */ ret = 0; } if (fDash) { offset = getValue(str2, upper, &base, bForDsp); if (offset == 0 || !isNumberOK(str2, offset, base)) { /* second number not OK */ fprintf(stderr, "Invalid address value '%s'!\n", str2); ret = -1; } else { if (*lower > *upper) { fprintf(stderr,"Invalid range ($%x > $%x)!\n", *lower, *upper); /* not a range */ ret = -1; } else { /* second number & range OK */ ret = 1; } } *--str2 = '-'; } return ret; } /** * Evaluate expression. bForDsp determines which registers and symbols * are interpreted. Sets given value and parsing offset. * Return error string or NULL for success. */ const char* Eval_Expression(const char *in, uint32_t *out, int *erroff, bool bForDsp) { /* in : expression to evaluate */ /* out : final parsed value */ /* value : current parsed value */ /* mark : current character in expression */ /* valid : expression validation flag, set when number parsed */ /* end : 'expression end' flag */ /* offset: character offset in expression */ long long value; int dummy, offset = 0; char mark, width; /* Uses global variables: */ par.idx = 0; /* parenthesis stack pointer */ par.opx[0] = par.vax[0] = 0; /* additional stack pointers */ op.idx = val.idx = -1; id.error = NULL; id.valid = false; /* value validation */ value = 0; /* parsing loop, repeated until expression ends */ do { mark = in[offset]; switch(mark) { case '\0': break; case ' ': case '\t': offset ++; /* jump over white space */ break; case '~': /* prefixes */ unary(mark); offset ++; break; case '>': /* operators */ case '<': offset ++; /* check that it's '>>' or '<<' */ if (in[offset] != mark) { id.error = CLAC_GEN_ERR; break; } operation (value, mark); offset ++; break; case '|': case '&': case '^': case '+': case '-': case '*': case '/': operation (value, mark); offset ++; break; case '(': open_bracket (); offset ++; break; case ')': offset ++; /* check for .b/.w/.l width spec */ offset += get_width (in+offset, &width); value = close_bracket (value, width); break; default: /* register/symbol/number value needed? */ if (id.valid == false) { uint32_t tmp; int consumed; consumed = getValue(&(in[offset]), &tmp, &dummy, bForDsp); /* number parsed? */ if (consumed) { offset += consumed; id.valid = true; value = tmp; break; } } id.error = CLAC_GEN_ERR; } /* until exit or error message */ } while(mark && !id.error); /* result of evaluation */ if (val.idx >= 0) *out = val.buf[val.idx]; /* something to return? */ if (!id.error) { if (id.valid) { /* evaluate rest of the expression */ operation (value, LOWEST_PREDECENCE); if (par.idx) /* mismatched */ id.error = CLAC_PAR_ERR; else if (val.idx < 0) id.error = CLAC_PRG_ERR; else /* result out */ *out = val.buf[val.idx]; } else { if ((val.idx < 0) && (op.idx < 0)) { id.error = CLAC_EXP_ERR; } else /* trailing operators */ id.error = CLAC_GEN_ERR; } } *erroff = offset; if (id.error) { *out = 0; return id.error; } return NULL; } /* ==================================================================== */ /* expression evaluation */ /* ==================================================================== */ static void operation (long long value, char oper) { /* uses globals par[], id.error[], op[], val[] * operation executed if the next one is on same or lower level */ /* something to calc? */ if (id.valid == true) { /* add new items to stack */ PUSH(op, oper); PUSH(val, value); /* more than 1 operator */ if(op.idx > par.opx[par.idx]) { /* but only one value */ if(val.idx == par.vax[par.idx]) { apply_prefix(); } else { /* evaluate all possible operations */ eval_stack(); } } /* next a number needed */ id.valid = false; } else { /* pre- or post-operators instead of in-betweens? */ unary(oper); } } /** * handle unary operators */ static void unary (char oper) { /* check pre-value operators * have to be parenthesised */ if(id.valid == false && op.idx < par.opx[par.idx]) { switch(oper) { case '+': /* not needed */ break; case '-': case '~': PUSH(op, oper); break; default: id.error = CLAC_GEN_ERR; } } else id.error = CLAC_GEN_ERR; } /** * apply a prefix to the current value */ static void apply_prefix(void) { long long value = val.buf[val.idx]; op.idx--; switch(op.buf[op.idx]) { case '-': value = (-value); break; case '~': value = (~value); break; default: id.error = CLAC_PRG_ERR; } val.buf[val.idx] = value; op.buf[op.idx] = op.buf[op.idx + 1]; } /* -------------------------------------------------------------------- */ /** * evaluate operators if precedence allows it */ /* evaluate all possible (according to order of precedence) operators */ static void eval_stack (void) { /* uses globals par[], op[], val[] */ /* # of operators >= 2 and prev. op-level >= current op-level ? */ while ((op.idx > par.opx[par.idx]) && get_level (-1) >= get_level (0)) { /* shorten value stacks by one */ /* + calculate resulting value */ op.idx -= 1; val.idx -= 1; val.buf[val.idx] = apply_op(op.buf[op.idx], val.buf[val.idx], val.buf[val.idx + 1]); /* pull the just used operator out of the stack */ op.buf[op.idx] = op.buf[op.idx + 1]; } } /* -------------------------------------------------------------------- */ /** * return the precedence level of a given operator */ static int get_level (int offset) { /* used globals par[], op[] * returns operator level of: operator[stack idx + offset] */ switch(op.buf[op.idx + offset]) { case '|': /* binary operations */ case '&': case '^': return 0; case '>': /* bit shifting */ case '<': return 1; case '+': case '-': return 2; case '*': case '/': return 3; default: id.error = CLAC_PRG_ERR; } return 6; } /* -------------------------------------------------------------------- */ /** * apply operator to given values, return the result */ static long long apply_op (char opcode, long long value1, long long value2) { /* uses global id.error[] */ /* returns the result of operation */ switch (opcode) { case '|': value1 |= value2; break; case '&': value1 &= value2; break; case '^': value1 ^= value2; break; case '>': value1 >>= value2; break; case '<': value1 <<= value2; break; case '+': value1 += value2; break; case '-': value1 -= value2; break; case '*': value1 *= value2; break; case '/': /* don't divide by zero */ if (value2) value1 /= value2; else id.error = CLAC_DEF_ERR; break; default: id.error = CLAC_PRG_ERR; } return value1; /* return result */ } /* ==================================================================== */ /* parenthesis and help */ /* ==================================================================== */ /** * open parenthesis, push values & operators to stack */ static void open_bracket (void) { if (id.valid == false) { /* preceded by operator */ if (par.idx < PARDEPTH_MAX) { /* not nested too deep */ par.idx ++; par.opx[par.idx] = op.idx + 1; par.vax[par.idx] = val.idx + 1; } else id.error = CLAC_STK_ERR; } else id.error = CLAC_GEN_ERR; } /* -------------------------------------------------------------------- */ /** * close parenthesis, and evaluate / pop stacks */ /* last parsed value, last param. flag, trigonometric mode */ static long long close_bracket (long long value, char width) { /* returns the value of the parenthesised expression */ if (id.valid) { /* preceded by an operator */ if (par.idx > 0) { /* parenthesis has a pair */ uint32_t addr; /* calculate the value of parenthesised exp. */ operation (value, LOWEST_PREDECENCE); /* fetch the indirect ST RAM value */ addr = val.buf[val.idx]; switch (width) { case 'b': value = STMemory_ReadByte(addr); break; case 'w': value = STMemory_ReadWord(addr); break; case 'l': value = STMemory_ReadLong(addr); break; default: id.error = CLAC_PRG_ERR; } fprintf(stderr, " value at ($%x).%c = $%"PRIx64"\n", addr, width, (uint64_t)value); /* restore state before parenthesis */ op.idx = par.opx[par.idx] - 1; val.idx = par.vax[par.idx] - 1; par.idx --; /* next operator */ id.valid = true; } else id.error = CLAC_PAR_ERR; } else id.error = CLAC_GEN_ERR; return value; } /* parse 'str' for '.[bwl]' pattern, set lower-case width char * to 'width', or if there was no match, use 'l'. Return number * of parsed characters. */ static int get_width(const char *str, char *width) { *width = 'l'; if (str[0] != '.') return 0; char lower = tolower(str[1]); if (lower != 'b' && lower != 'w' && lower != 'l') { id.error = CLAC_WDT_ERR; return 1; } *width = lower; return 2; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/evaluate.h000066400000000000000000000011721504763705000243760ustar00rootroot00000000000000/* Hatari - evaluate.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_EVALUATE_H #define HATARI_EVALUATE_H typedef enum { NUM_TYPE_CPU, NUM_TYPE_DSP, NUM_TYPE_NORMAL // neither CPU nor DSP memory address, just number } num_type_t; extern bool Eval_Number(const char *value, uint32_t *number, num_type_t type); extern int Eval_Range(char *str, uint32_t *lower, uint32_t *upper, bool bForDsp); extern const char* Eval_Expression(const char *expression, uint32_t *result, int *offset, bool bForDsp); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/history.c000066400000000000000000000176071504763705000242760ustar00rootroot00000000000000/* * Hatari - history.c * * Copyright (C) 2011-2016,2020-2023 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * history.c - functions for debugger entry & breakpoint history */ const char History_fileid[] = "Hatari history.c"; #include #include #include "main.h" #include "configuration.h" #include "debugui.h" #include "debug_priv.h" #include "dsp.h" #include "dsp_core.h" #include "evaluate.h" #include "file.h" #include "history.h" #include "m68000.h" #include "68kDisass.h" #define HISTORY_ITEMS_MIN 64 history_type_t HistoryTracking; typedef struct { bool shown:1; bool valid:1; bool for_dsp:1; /* reason for debugger entry/breakpoint hit */ debug_reason_t reason:8; union { uint16_t dsp; uint32_t cpu; } pc; } hist_item_t; static struct { unsigned idx; /* index to current history item */ unsigned count; /* how many items of history are collected */ unsigned limit; /* ring-buffer size */ unsigned repeats; /* repeats for the last instruction */ hist_item_t *item; /* ring-buffer */ } History; /** * Convert debugger entry/breakpoint entry reason to a string */ static const char* History_ReasonStr(debug_reason_t reason) { switch(reason) { case REASON_CPU_EXCEPTION: return "CPU exception"; case REASON_CPU_BREAKPOINT: return "CPU breakpoint"; case REASON_DSP_BREAKPOINT: return "DSP breakpoint"; case REASON_CPU_STEPS: return "CPU steps"; case REASON_DSP_STEPS: return "DSP steps"; case REASON_PROGRAM: return "Program break"; case REASON_USER: return "User break"; default: return "Unknown reason"; } } /** * Set what kind of history is collected. * Clear history if tracking type changes as rest of * data wouldn't then be anymore valid. */ static void History_Enable(history_type_t track, unsigned limit) { const char *msg; if (track != HistoryTracking || limit != History.limit) { fprintf(stderr, "Re-allocating & zeroing history due to type/limit change.\n"); if (History.item) { free(History.item); } memset(&History, 0, sizeof(History)); History.item = calloc(limit, sizeof(History.item[0])); History.limit = limit; } switch (track) { case HISTORY_TRACK_NONE: msg = "disabled"; break; case HISTORY_TRACK_CPU: msg = "enabled for CPU"; break; case HISTORY_TRACK_DSP: msg = "enabled for DSP"; break; case HISTORY_TRACK_ALL: msg = "enabled for CPU & DSP"; break; default: msg = "error"; } HistoryTracking = track; fprintf(stderr, "History tracking %s (max. %d instructions).\n", msg, limit); } /** * Advance & initialize next history item in ring buffer */ static void History_Advance(void) { History.idx++; History.idx %= History.limit; History.item[History.idx].valid = true; History.item[History.idx].shown = false; History.item[History.idx].reason = REASON_NONE; History.count++; } /** * Add CPU PC to history */ void History_AddCpu(void) { uint32_t pc = M68000_GetPC(); if (pc == History.item[History.idx].pc.cpu) { History.repeats++; return; } else { History.repeats = 0; } History_Advance(); History.item[History.idx].for_dsp = false; History.item[History.idx].pc.cpu = pc; } /** * Add DSP PC to history */ void History_AddDsp(void) { uint16_t pc = DSP_GetPC(); if (pc == History.item[History.idx].pc.dsp) { History.repeats++; return; } else { History.repeats = 0; } History_Advance(); History.item[History.idx].for_dsp = true; History.item[History.idx].pc.dsp = pc; } /** * Flag last history entry as debugger entry point, with given reason */ void History_Mark(debug_reason_t reason) { if (History.item) { History.item[History.idx].reason = reason; } } /** * Find lowest address in history that is within range: * (pc-offset) - pc * where 'offset' is the history disasm offset limit. * * If history has no such address, return given pc value. */ uint32_t History_DisasmAddr(uint32_t pc, uint32_t offset, bool for_dsp) { unsigned int i, count; uint32_t limit, first; int track; if (!offset) { return pc; } track = for_dsp ? HISTORY_TRACK_DSP : HISTORY_TRACK_CPU; if (!(track & HistoryTracking)) { return pc; } count = History.count; if (count > History.limit) { count = History.limit; } if (count <= 0) { return pc; } first = pc; limit = pc - offset; i = History.idx + History.limit - count; while (count-- > 0) { i++; i %= History.limit; assert(History.item[i].valid); if (History.item[i].for_dsp != for_dsp) { continue; } if (for_dsp) { pc = History.item[i].pc.dsp; } else { pc = History.item[i].pc.cpu; } if (pc >= limit && pc < first) { first = pc; } } return first; } /** * Output collected CPU/DSP debugger/breakpoint history */ static uint32_t History_Output(uint32_t count, FILE *fp) { bool show_all; uint32_t retval; int i; if (History.count > History.limit) { History.count = History.limit; } if (count > History.count) { count = History.count; } else { if (!count) { /* default to all */ count = History.count; } } if (count <= 0) { fprintf(stderr, "No history items to show.\n"); return 0; } retval = count; show_all = false; if (History.item[History.idx].shown) { /* even last item already shown, show all again */ show_all = true; } i = History.idx + History.limit - count; while (count-- > 0) { i++; i %= History.limit; assert(History.item[i].valid); if (History.item[i].shown && !show_all) { continue; } History.item[i].shown = true; if (History.item[i].for_dsp) { uint16_t pc = History.item[i].pc.dsp; DSP_DisasmAddress(fp, pc, pc); } else { uint32_t dummy; Disasm(fp, History.item[i].pc.cpu, &dummy, 1); } if (History.item[i].reason != REASON_NONE) { fprintf(fp, "Debugger: *%s*\n", History_ReasonStr(History.item[i].reason)); } } if (History.repeats) { fprintf(fp, "Last item repeated %d times.\n", History.repeats); } return retval; } /* History_Output() helper for "info" & "lock" commands */ void History_Show(FILE *fp, uint32_t count) { History_Output(count, fp); } /* * save all history to given file */ static void History_Save(const char *name) { uint32_t count; FILE *fp; if (File_Exists(name)) { fprintf(stderr, "ERROR: file '%s' already exists!\n", name); } else if ((fp = fopen(name, "w"))) { count = History_Output(0, fp); fprintf(stderr, "%d history items saved to '%s'.\n", count, name); fclose(fp); } else { fprintf(stderr, "ERROR: opening '%s' failed (%d).\n", name, errno); } } /* * Readline callback */ char *History_Match(const char *text, int state) { static const char* cmds[] = { "cpu", "dsp", "off", "save" }; return DebugUI_MatchHelper(cmds, ARRAY_SIZE(cmds), text, state); } /** * Command: Show collected CPU/DSP debugger/breakpoint history */ int History_Parse(int nArgc, char *psArgs[]) { int count, limit = 0; if (nArgc < 2) { return DebugUI_PrintCmdHelp(psArgs[0]); } if (nArgc > 2) { limit = atoi(psArgs[2]); } /* make sure value is valid & positive */ if (!limit) { limit = History.limit; } if (limit < HISTORY_ITEMS_MIN) { limit = HISTORY_ITEMS_MIN; } count = atoi(psArgs[1]); if (count <= 0) { /* no count -> enable or disable? */ if (strcmp(psArgs[1], "on") == 0) { History_Enable(HISTORY_TRACK_ALL, limit); return DEBUGGER_CMDDONE; } if (strcmp(psArgs[1], "off") == 0) { History_Enable(HISTORY_TRACK_NONE, limit); return DEBUGGER_CMDDONE; } if (strcmp(psArgs[1], "cpu") == 0) { History_Enable(HISTORY_TRACK_CPU, limit); return DEBUGGER_CMDDONE; } if (strcmp(psArgs[1], "dsp") == 0) { History_Enable(HISTORY_TRACK_DSP, limit); return DEBUGGER_CMDDONE; } if (nArgc == 3 && strcmp(psArgs[1], "save") == 0) { History_Save(psArgs[2]); return DEBUGGER_CMDDONE; } fprintf(stderr, "History range is 1-\n"); return DebugUI_PrintCmdHelp(psArgs[0]); } History_Show(stderr, count); return DEBUGGER_CMDDONE; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/history.h000066400000000000000000000020761504763705000242750ustar00rootroot00000000000000/* Hatari - history.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_HISTORY_H #define HATARI_HISTORY_H /* what processors are tracked */ typedef enum { HISTORY_TRACK_NONE = 0, HISTORY_TRACK_CPU = 1, HISTORY_TRACK_DSP = 2, HISTORY_TRACK_ALL = (HISTORY_TRACK_CPU|HISTORY_TRACK_DSP) } history_type_t; extern history_type_t HistoryTracking; static inline bool History_TrackCpu(void) { return HistoryTracking & HISTORY_TRACK_CPU; } static inline bool History_TrackDsp(void) { return HistoryTracking & HISTORY_TRACK_DSP; } /* for debugcpu/dsp.c */ extern void History_AddCpu(void); extern void History_AddDsp(void); extern uint32_t History_DisasmAddr(uint32_t pc, uint32_t offset, bool for_dsp); /* for debugInfo.c */ extern void History_Show(FILE *fp, uint32_t count); /* for debugui */ extern void History_Mark(debug_reason_t reason); extern char *History_Match(const char *text, int state); extern int History_Parse(int nArgc, char *psArgv[]); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/log.c000066400000000000000000000370261504763705000233530ustar00rootroot00000000000000/* * Hatari - log.c * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Logger functions. * * When Hatari runs, it can output information, debug, warning and error texts * to the error log file and/or displays them in alert dialog boxes. * * It can also dynamically output trace messages, based on the content * of LogTraceFlags. Multiple trace levels can be set at once, by setting * the corresponding bits in LogTraceFlags. */ const char Log_fileid[] = "Hatari log.c"; #include #include #include #include #include #include #include "main.h" #include "configuration.h" #include "console.h" #include "dialog.h" #include "log.h" #include "screen.h" #include "file.h" #include "vdi.h" #include "options.h" #include "str.h" int ExceptionDebugMask; typedef struct { uint64_t flag; const char *name; } flagname_t; static flagname_t ExceptionFlags[] = { { EXCEPT_NONE, "none" }, { EXCEPT_NOHANDLER, "nohandler" }, { EXCEPT_BUS, "bus" }, { EXCEPT_ADDRESS, "address" }, { EXCEPT_ILLEGAL, "illegal" }, { EXCEPT_ZERODIV, "zerodiv" }, { EXCEPT_CHK, "chk" }, { EXCEPT_TRAPV, "trapv" }, { EXCEPT_PRIVILEGE, "privilege" }, { EXCEPT_TRACE, "trace" }, { EXCEPT_LINEA, "linea" }, { EXCEPT_LINEF, "linef" }, { EXCEPT_DSP, "dsp" }, { EXCEPT_AUTOSTART, "autostart" }, { EXCEPT_ALL, "all" } }; #if ENABLE_TRACING static flagname_t TraceFlags[] = { { TRACE_ALL , "all" }, { TRACE_NONE , "none" }, { TRACE_ACIA , "acia" }, { TRACE_OS_AES , "aes" }, { TRACE_OS_BIOS , "bios" }, { TRACE_BLITTER , "blitter" }, { TRACE_CPU_ALL , "cpu_all" }, { TRACE_CPU_DISASM , "cpu_disasm" }, { TRACE_CPU_EXCEPTION , "cpu_exception" }, { TRACE_CPU_PAIRING , "cpu_pairing" }, { TRACE_CPU_REGS , "cpu_regs" }, { TRACE_CPU_SYMBOLS , "cpu_symbols" }, { TRACE_CPU_VIDEO_CYCLES , "cpu_video_cycles" }, { TRACE_CROSSBAR , "crossbar" }, { TRACE_DMASND , "dmasound" }, { TRACE_DSP_ALL , "dsp_all" }, { TRACE_DSP_DISASM , "dsp_disasm" }, { TRACE_DSP_DISASM_REG , "dsp_disasm_reg" }, { TRACE_DSP_DISASM_MEM , "dsp_disasm_mem" }, { TRACE_DSP_HOST_COMMAND , "dsp_host_command" }, { TRACE_DSP_HOST_INTERFACE,"dsp_host_interface" }, { TRACE_DSP_HOST_SSI , "dsp_host_ssi" }, { TRACE_DSP_INTERRUPT , "dsp_interrupt" }, { TRACE_DSP_STATE , "dsp_state" }, { TRACE_DSP_SYMBOLS , "dsp_symbols" }, { TRACE_FDC , "fdc" }, { TRACE_OS_GEMDOS , "gemdos" }, { TRACE_IDE , "ide" }, { TRACE_IKBD_ALL , "ikbd_all" }, { TRACE_IKBD_ACIA , "ikbd_acia" }, { TRACE_IKBD_CMDS , "ikbd_cmds" }, { TRACE_IKBD_EXEC , "ikbd_exec" }, { TRACE_INT , "int" }, { TRACE_IOMEM_ALL , "io_all" }, { TRACE_IOMEM_RD , "io_read" }, { TRACE_IOMEM_WR , "io_write" }, { TRACE_KEYMAP , "keymap" }, { TRACE_MEM , "mem" }, { TRACE_MFP_ALL , "mfp_all" }, { TRACE_MFP_EXCEPTION , "mfp_exception" }, { TRACE_MFP_READ , "mfp_read" }, { TRACE_MFP_START , "mfp_start" }, { TRACE_MFP_WRITE , "mfp_write" }, { TRACE_MIDI , "midi" }, { TRACE_MIDI_RAW , "midi_raw" }, { TRACE_NATFEATS , "natfeats" }, { TRACE_NVRAM , "nvram" }, { TRACE_OS_ALL , "os_all" }, { TRACE_OS_BASE , "os_base" }, { TRACE_PSG_ALL , "psg_all" }, { TRACE_PSG_READ , "psg_read" }, { TRACE_PSG_WRITE , "psg_write" }, { TRACE_SCC , "scc" }, { TRACE_SCSI_CMD , "scsi_cmd" }, { TRACE_SCSIDRV , "scsidrv" }, { TRACE_SCU , "scu" }, { TRACE_OS_VDI , "vdi" }, { TRACE_VIDEL , "videl" }, { TRACE_VIDEO_ALL , "video_all" }, { TRACE_VIDEO_ADDR , "video_addr" }, { TRACE_VIDEO_COLOR , "video_color" }, { TRACE_VIDEO_BORDER_H , "video_border_h" }, { TRACE_VIDEO_BORDER_V , "video_border_v" }, { TRACE_VIDEO_HBL , "video_hbl" }, { TRACE_VIDEO_RES , "video_res" }, { TRACE_VIDEO_STE , "video_ste" }, { TRACE_VIDEO_SYNC , "video_sync" }, { TRACE_VIDEO_VBL , "video_vbl" }, { TRACE_OS_XBIOS , "xbios" }, }; #endif /* ENABLE_TRACING */ uint64_t LogTraceFlags = TRACE_NONE; FILE *TraceFile = NULL; /* SDL GUI Alerts can show 4*50 chars at max, and much longer * console messages are not very readable either, just slow */ #define MAX_MSG_LEN 256 #define REPEAT_LIMIT_INIT 8 /* FILE* for output stream, message line repeat suppression limit, * current repeat count, and previous line content for checking * repetition */ static struct { /* prev msg fp, in case same msg goes to multiple FILE*s */ FILE *fp; int limit; int count; char prev[MAX_MSG_LEN]; } MsgState; static FILE *hLogFile = NULL; /* local settings, to be able change them temporarily */ static LOGTYPE TextLogLevel; static LOGTYPE AlertDlgLogLevel; /*-----------------------------------------------------------------------*/ /** * Set default files to stderr (used at the very start, before parsing options) */ void Log_Default(void) { hLogFile = stderr; TraceFile = stderr; TextLogLevel = LOG_INFO; MsgState.limit = REPEAT_LIMIT_INIT; } /** * Set local log levels from configuration values */ void Log_SetLevels(void) { TextLogLevel = ConfigureParams.Log.nTextLogLevel; AlertDlgLogLevel = ConfigureParams.Log.nAlertDlgLogLevel; } /*-----------------------------------------------------------------------*/ /** * Initialize the logging and tracing functionality (open the log files etc.). * * Return zero if that fails. */ int Log_Init(void) { Log_SetLevels(); /* Flush pending msg & drop cached prev msg FILE pointer * before default log & trace FILE pointers change */ Log_ResetMsgRepeat(); hLogFile = File_Open(ConfigureParams.Log.sLogFileName, "w"); TraceFile = File_Open(ConfigureParams.Log.sTraceFileName, "w"); return (hLogFile && TraceFile); } /** * Set Alert log level temporarily without config change. * * Return old level for restoring the original level with this. */ int Log_SetAlertLevel(int level) { int old = AlertDlgLogLevel; AlertDlgLogLevel = level; return old; } /*-----------------------------------------------------------------------*/ /** * Un-Initialize - close log files etc. */ void Log_UnInit(void) { /* Flush pending msg & drop cached prev msg FILE pointer * before log & trace FILE pointers change */ Log_ResetMsgRepeat(); hLogFile = File_Close(hLogFile); TraceFile = File_Close(TraceFile); } /*----------------------------------------------------------------------- * log/trace message repeat suppression handling */ static void printMsgRepeat(FILE *fp) { /* strings already include trailing newline */ fprintf(fp, "%d repeats of: %s", MsgState.count, MsgState.prev); } /** * If there is a pending message that has not been output yet, * output it and return true, otherwise false. */ static bool printPendingMsgRepeat(FILE *fp) { if (likely(MsgState.count == 0)) return false; if (MsgState.count > 1) printMsgRepeat(fp); else fputs(MsgState.prev, fp); return true; } /** * Output pending and given messages when appropriate, * and cache given fp & message if it's not a repeat. */ static void addMsgRepeat(FILE *fp, const char *line) { /* repeated message? */ if (fp == MsgState.fp && unlikely(strcmp(line, MsgState.prev) == 0)) { MsgState.count++; /* limit crossed? -> print + increase repeat limit */ if (unlikely(MsgState.count >= MsgState.limit)) { printMsgRepeat(fp); MsgState.limit *= 2; MsgState.count = 0; fflush(fp); } return; } /* no repeat -> print previous message/repeat */ printPendingMsgRepeat(MsgState.fp); /* store + print new message */ Str_Copy(MsgState.prev, line, sizeof(MsgState.prev)); MsgState.limit = REPEAT_LIMIT_INIT; MsgState.count = 0; MsgState.fp = fp; fputs(line, fp); fflush(fp); } /** * Output pending messages repeat info and reset repeat info. */ void Log_ResetMsgRepeat(void) { if (!printPendingMsgRepeat(MsgState.fp)) { MsgState.fp = NULL; return; } MsgState.prev[0] = '\0'; if (MsgState.limit) MsgState.limit = REPEAT_LIMIT_INIT; MsgState.count = 0; MsgState.fp = NULL; } /** * Toggle whether message repeats are shown */ void Log_ToggleMsgRepeat(void) { if (MsgState.limit) { fprintf(stderr, "Message repeats will be shown as-is\n"); MsgState.limit = 0; } else { fprintf(stderr, "Message repeats will be suppressed\n"); MsgState.limit = REPEAT_LIMIT_INIT; } Log_ResetMsgRepeat(); } /*-----------------------------------------------------------------------*/ /** * Add log prefix to given string and return its lenght */ static int Log_AddPrefix(char *msg, int len, LOGTYPE idx) { static const char* prefix[] = LOG_NAMES; assert(idx >= 0 && idx < ARRAY_SIZE(prefix)); return snprintf(msg, len, "%s: ", prefix[idx]); } /** * Add a new-line if it's missing. 'msg' points to place * where it should be, and size is buffer size. */ static void addMissingNewline(char *msg, int size) { assert(size > 2); if (size > 2 && msg[0] != '\n') { msg[1] = '\n'; msg[2] = '\0'; } } /*-----------------------------------------------------------------------*/ /** * Output string to log file */ void Log_Printf(LOGTYPE nType, const char *psFormat, ...) { if (!(hLogFile && nType <= TextLogLevel)) return; char line[sizeof(MsgState.prev)]; int count, len = sizeof(line); char *msg = line; count = Log_AddPrefix(line, len, nType); msg += count; len -= count; va_list argptr; va_start(argptr, psFormat); count = vsnprintf(msg, len, psFormat, argptr); va_end(argptr); msg += count; len -= count; addMissingNewline(msg-1, len+1); if (MsgState.limit) addMsgRepeat(hLogFile, line); else fputs(line, hLogFile); } /*-----------------------------------------------------------------------*/ /** * Show logging alert dialog box and output string to log file */ void Log_AlertDlg(LOGTYPE nType, const char *psFormat, ...) { va_list argptr; /* Output to log file: */ if (hLogFile && nType <= TextLogLevel) { char line[sizeof(MsgState.prev)]; int count, len = sizeof(line); char *msg = line; count = Log_AddPrefix(line, len, nType); msg += count; len -= count; va_start(argptr, psFormat); count = vsnprintf(msg, len, psFormat, argptr); va_end(argptr); msg += count; len -= count; addMissingNewline(msg-1, len+1); if (MsgState.limit) addMsgRepeat(hLogFile, line); else fputs(line, hLogFile); } /* Show alert dialog box: */ if (sdlscrn && nType <= AlertDlgLogLevel) { char buf[MAX_MSG_LEN]; va_start(argptr, psFormat); vsnprintf(buf, sizeof(buf), psFormat, argptr); va_end(argptr); DlgAlert_Notice(buf); } } /*-----------------------------------------------------------------------*/ /** * parse what log level should be used and return it */ LOGTYPE Log_ParseOptions(const char *arg) { const char *levels[] = { "fatal", "error", "warn", "info", "todo", "debug", NULL }; LOGTYPE level = LOG_FATAL; const char **level_str; char *input, *str; input = strdup(arg); str = input; while (*str) { *str++ = tolower((unsigned char)*arg++); } for (level_str = levels; *level_str; level_str++, level++) { if (strcmp(input, *level_str) == 0) { free(input); return level; } } free(input); return level; } /*-----------------------------------------------------------------------*/ /** * Parse a list of comma separated strings: * - Unless whole string is prefixed with '+'/'-', mask is initially zeroed * - Optionally prefixing flag name with '+' turns given mask flag on * - Prefixing flag name with '-' turns given mask flag off * Return error string (""=silent 'error') or NULL for success. */ static const char* Log_ParseOptionFlags (const char *FlagsStr, flagname_t *Flags, int MaxFlags, uint64_t *Mask) { char *FlagsCopy; char *cur, *sep; int i; int Mode; /* 0=add, 1=del */ enum { FLAG_ADD, FLAG_DEL }; /* special case for "help" : display the list of possible settings */ if (strcmp (FlagsStr, "help") == 0) { fprintf(stderr, "\nList of available option flags :\n"); for (i = 0; i < MaxFlags; i++) fprintf(stderr, " %s\n", Flags[i].name); fprintf(stderr, "Multiple flags can be separated by ','.\n" "Giving just 'none' flag disables all of them.\n\n" "Unless first flag starts with -/+ character, flags from\n" "previous trace command are zeroed. Prefixing flag with\n" "'-' removes it from set, (optional) '+' adds it to set\n" "(which is useful at run-time in debugger).\n\n" ); return ""; } if (strcmp (FlagsStr, "none") == 0) { *Mask = 0; return NULL; } FlagsCopy = strdup(FlagsStr); if (!FlagsCopy) { return "strdup error in Log_OptionFlags"; } cur = FlagsCopy; /* starting anew, not modifiying old set? */ if (*cur != '+' && *cur != '-') *Mask = 0; while (cur) { sep = strchr(cur, ','); if (sep) /* end of next options */ *sep++ = '\0'; Mode = FLAG_ADD; if (*cur == '+') cur++; else if (*cur == '-') { Mode = FLAG_DEL; cur++; } for (i = 0; i < MaxFlags; i++) { if (strcmp(cur, Flags[i].name) == 0) break; } if (i < MaxFlags) /* option found */ { if (Mode == FLAG_ADD) *Mask |= Flags[i].flag; else *Mask &= (~Flags[i].flag); } else { fprintf(stderr, "Unknown flag type '%s'\n", cur); free(FlagsCopy); return "Unknown flag type."; } cur = sep; } //fprintf(stderr, "flags parse <%x>\n", Mask); free (FlagsCopy); return NULL; } /** * Parse exception flags and store results in ExceptionDebugMask. * Return error string or NULL for success. * * See Log_ParseOptionFlags() for details. */ const char* Log_SetExceptionDebugMask (const char *FlagsStr) { const char *errstr; uint64_t mask = ConfigureParams.Debugger.nExceptionDebugMask; errstr = Log_ParseOptionFlags(FlagsStr, ExceptionFlags, ARRAY_SIZE(ExceptionFlags), &mask); ConfigureParams.Debugger.nExceptionDebugMask = mask; return errstr; } #if ENABLE_TRACING /** * Parse trace flags and store results in LogTraceFlags. * Return error string or NULL for success. * * See Log_ParseOptionFlags() for details. */ const char* Log_SetTraceOptions (const char *FlagsStr) { const char *errstr; errstr = Log_ParseOptionFlags(FlagsStr, TraceFlags, ARRAY_SIZE(TraceFlags), &LogTraceFlags); /* Enable Hatari flags needed for tracing selected items */ if (LogTraceFlags & (TRACE_OS_AES|TRACE_OS_VDI)) bVdiAesIntercept = true; if ((LogTraceFlags & TRACE_OS_BASE)) Console_SetTrace(true); else if (!LogTraceFlags) Console_SetTrace(false); return errstr; } /** * Readline match callback for trace type name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Log_MatchTrace(const char *text, int state) { static int i, len; const char *name; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i < ARRAY_SIZE(TraceFlags)) { name = TraceFlags[i++].name; if (strncasecmp(name, text, len) == 0) return (strdup(name)); } return NULL; } /** * Do trace output with optional repeat suppression */ void Log_Trace(const char *format, ...) { va_list argptr; char line[sizeof(MsgState.prev)]; if (!TraceFile) return; va_start(argptr, format); if (MsgState.limit) { vsnprintf(line, sizeof(line), format, argptr); addMsgRepeat(TraceFile, line); } else { vfprintf(TraceFile, format, argptr); fflush(TraceFile); } va_end(argptr); } #else /* !ENABLE_TRACING */ /** dummy */ const char* Log_SetTraceOptions (const char *FlagsStr) { return "Hatari has been compiled without ENABLE_TRACING!"; } /** dummy */ char *Log_MatchTrace(const char *text, int state) { return NULL; } /** dummy */ void Log_Trace(const char *format, ...) {} #endif /* !ENABLE_TRACING */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/log.h000066400000000000000000000237161504763705000233610ustar00rootroot00000000000000/* Hatari - log.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_LOG_H #define HATARI_LOG_H #include #include /* Exception debugging * ------------------- */ /* CPU exception flags */ #define EXCEPT_NOHANDLER (1<<0) #define EXCEPT_BUS (1<<1) #define EXCEPT_ADDRESS (1<<2) #define EXCEPT_ILLEGAL (1<<3) #define EXCEPT_ZERODIV (1<<4) #define EXCEPT_CHK (1<<5) #define EXCEPT_TRAPV (1<<6) #define EXCEPT_PRIVILEGE (1<<7) #define EXCEPT_TRACE (1<<8) #define EXCEPT_LINEA (1<<9) #define EXCEPT_LINEF (1<<10) /* DSP exception flags */ #define EXCEPT_DSP (1<<30) /* whether to enable exception debugging on autostart */ #define EXCEPT_AUTOSTART (1<<31) /* general flags */ #define EXCEPT_NONE (0) #define EXCEPT_ALL (~EXCEPT_AUTOSTART) /* defaults are same as with earlier -D option */ #define DEFAULT_EXCEPTIONS (EXCEPT_BUS|EXCEPT_ADDRESS|EXCEPT_DSP) extern int ExceptionDebugMask; extern const char* Log_SetExceptionDebugMask(const char *OptionsStr); /* Logging * ------- * Is always enabled as it's information that can be useful * to the Hatari users */ typedef enum { /* these present user with a dialog and log the issue */ LOG_FATAL, /* Hatari can't continue unless user resolves issue */ LOG_ERROR, /* something user did directly failed (e.g. save) */ /* these just log the issue */ LOG_WARN, /* something failed, but it's less serious */ LOG_INFO, /* user action success (e.g. TOS file load) */ LOG_TODO, /* functionality not yet being emulated */ LOG_DEBUG, /* information about internal Hatari working */ LOG_NONE /* invalid LOG level */ } LOGTYPE; #define LOG_NAMES {"FATAL", "ERROR", "WARN ", "INFO ", "TODO ", "DEBUG"} #ifndef __GNUC__ /* assuming attributes work only for GCC */ #define __attribute__(foo) #endif extern void Log_Default(void); extern void Log_SetLevels(void); extern int Log_Init(void); extern int Log_SetAlertLevel(int level); extern void Log_UnInit(void); extern void Log_Printf(LOGTYPE nType, const char *psFormat, ...) __attribute__ ((format (printf, 2, 3))); extern void Log_AlertDlg(LOGTYPE nType, const char *psFormat, ...) __attribute__ ((format (printf, 2, 3))); extern LOGTYPE Log_ParseOptions(const char *OptionStr); extern const char* Log_SetTraceOptions(const char *OptionsStr); extern char *Log_MatchTrace(const char *text, int state); extern void Log_ToggleMsgRepeat(void); extern void Log_ResetMsgRepeat(void); extern void Log_Trace(const char *format, ...) __attribute__ ((format (printf, 1, 2))); #ifndef __GNUC__ #undef __attribute__ #endif /* Tracing * ------- * Tracing outputs information about what happens in the emulated * system and slows down the emulation. As it's intended mainly * just for the Hatari developers, tracing support is compiled in * by default. * * Tracing can be enabled by defining ENABLE_TRACING * in the top level config.h */ #include "config.h" /* Up to 64 levels when using uint64_t for HatariTraceFlags */ enum { TRACE_BIT_ACIA, TRACE_BIT_BLITTER, TRACE_BIT_CPU_DISASM, TRACE_BIT_CPU_EXCEPTION, TRACE_BIT_CPU_PAIRING, TRACE_BIT_CPU_REGS, TRACE_BIT_CPU_SYMBOLS, TRACE_BIT_CPU_VIDEO_CYCLES, TRACE_BIT_CROSSBAR, TRACE_BIT_DMASND, TRACE_BIT_DSP_DISASM, TRACE_BIT_DSP_DISASM_MEM, TRACE_BIT_DSP_DISASM_REG, TRACE_BIT_DSP_HOST_COMMAND, TRACE_BIT_DSP_HOST_INTERFACE, TRACE_BIT_DSP_HOST_SSI, TRACE_BIT_DSP_INTERRUPT, TRACE_BIT_DSP_STATE, TRACE_BIT_DSP_SYMBOLS, TRACE_BIT_FDC, TRACE_BIT_IDE, TRACE_BIT_IKBD_ACIA, TRACE_BIT_IKBD_CMDS, TRACE_BIT_IKBD_EXEC, TRACE_BIT_INT, TRACE_BIT_IOMEM_RD, TRACE_BIT_IOMEM_WR, TRACE_BIT_KEYMAP, TRACE_BIT_MEM, TRACE_BIT_MFP_EXCEPTION, TRACE_BIT_MFP_READ, TRACE_BIT_MFP_START, TRACE_BIT_MFP_WRITE, TRACE_BIT_MIDI, TRACE_BIT_MIDI_RAW, TRACE_BIT_NATFEATS, TRACE_BIT_NVRAM, TRACE_BIT_OS_AES, TRACE_BIT_OS_BASE, TRACE_BIT_OS_BIOS, TRACE_BIT_OS_GEMDOS, TRACE_BIT_OS_VDI, TRACE_BIT_OS_XBIOS, TRACE_BIT_PSG_READ, TRACE_BIT_PSG_WRITE, TRACE_BIT_SCC, TRACE_BIT_SCSI_CMD, TRACE_BIT_SCSIDRV, TRACE_BIT_SCU, TRACE_BIT_VIDEL, TRACE_BIT_VIDEO_ADDR, TRACE_BIT_VIDEO_BORDER_H, TRACE_BIT_VIDEO_BORDER_V, TRACE_BIT_VIDEO_COLOR, TRACE_BIT_VIDEO_HBL, TRACE_BIT_VIDEO_RES, TRACE_BIT_VIDEO_STE, TRACE_BIT_VIDEO_SYNC, TRACE_BIT_VIDEO_VBL }; #define TRACE_ACIA (1ll< #include "main.h" #include "version.h" #include "configuration.h" #include "stMemory.h" #include "m68000.h" #include "reset.h" #include "natfeats.h" #include "debugui.h" #include "statusbar.h" #include "nf_scsidrv.h" #include "log.h" /* maximum input string length */ #define NF_MAX_STRING 4096 /* whether to allow XBIOS(255) style * Hatari command line parsing with "command" NF */ #define NF_COMMAND 0 /* TODO: * - supervisor vs. user stack handling? * - clipboard and hostfs native features? */ /* ---------------------------------- * Native Features shared with Aranym */ /** * Check whether given string address is valid * and whether strings is of "reasonable" length. * * Returns string length or negative value for error. */ static int mem_string_ok(uint32_t addr) { const char *buf; int i; if (!STMemory_CheckAreaType(addr, 1, ABFLAG_RAM | ABFLAG_ROM)) { /* invalid starting address -> error */ M68000_BusError(addr, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return -1; } buf = (const char *)STMemory_STAddrToPointer(addr); if (STMemory_CheckAreaType(addr, NF_MAX_STRING, ABFLAG_RAM | ABFLAG_ROM)) { /* valid area -> return length */ for (i = 0; i < NF_MAX_STRING; i++) { if (!buf[i]) { return i; } } /* unterminated NF string -> error */ M68000_BusError(addr, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return -1; } for (i = 0; i < NF_MAX_STRING; i++) { if (!STMemory_CheckAreaType(addr + i, 1, ABFLAG_RAM | ABFLAG_ROM)) { /* ends in invalid area -> error */ M68000_BusError(addr, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return -1; } if (!buf[i]) { return i; } } assert(false); /* should never be reached */ return -1; } /** * NF_NAME - emulator name * Stack arguments are: * - pointer to buffer for emulator name, and * - uint32_t for its size * subid == 1 -> emulator name includes also version information. * Returns length of the name */ static bool nf_name(uint32_t stack, uint32_t subid, uint32_t *retval) { uint32_t ptr, len; const char *str; char *buf; ptr = STMemory_ReadLong(stack); len = STMemory_ReadLong(stack + SIZE_LONG); LOG_TRACE(TRACE_NATFEATS, "NF_NAME[%d](0x%x, %d)\n", subid, ptr, len); if ( !STMemory_CheckAreaType ( ptr, len, ABFLAG_RAM | ABFLAG_ROM ) ) { M68000_BusError(ptr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return false; } if (subid == 1) { str = PROG_NAME; } else { str = "Hatari"; } buf = (char *)STMemory_STAddrToPointer ( ptr ); *retval = snprintf(buf, len, "%s", str); return true; } /** * NF_VERSION - NativeFeatures version * returns version number */ static bool nf_version(uint32_t stack, uint32_t subid, uint32_t *retval) { LOG_TRACE(TRACE_NATFEATS, "NF_VERSION() -> 0x00010000\n"); *retval = 0x00010000; return true; } /** * NF_STDERR - print string to stderr * Stack arguments are: * - pointer to buffer containing the string * Returns printed string length */ static bool nf_stderr(uint32_t stack, uint32_t subid, uint32_t *retval) { const char *str; uint32_t ptr; ptr = STMemory_ReadLong(stack); LOG_TRACE(TRACE_NATFEATS, "NF_STDERR(0x%x)\n", ptr); *retval = 0; if (subid) { /* unrecognized subid, nothing printed */ return true; } if (mem_string_ok(ptr) < 0) { return false; } str = (const char *)STMemory_STAddrToPointer (ptr); *retval = fprintf(stderr, "%s", str); fflush(stderr); return true; } /** * NF_SHUTDOWN - reset or exit emulator * * Needs to be called from supervisor mode */ static bool nf_shutdown(uint32_t stack, uint32_t subid, uint32_t *retval) { const char *msg; LOG_TRACE(TRACE_NATFEATS, "NF_SHUTDOWN[%d]()\n", subid); switch (subid) { case 1: /* warm reset */ msg = "warm reset"; Reset_Warm(); /* Some infos can change after 'reset' */ Statusbar_UpdateInfo(); break; case 2: /* cold reset (clear all) */ msg = "cold reset"; Reset_Cold(); /* Some infos can change after 'reset' */ Statusbar_UpdateInfo(); break; case 0: /* shutdown */ case 3: /* poweroff */ msg = "poweroff"; ConfigureParams.Log.bConfirmQuit = false; Main_RequestQuit(0); break; default: /* unrecognized subid -> no-op */ return true; } fprintf(stderr, "NatFeats: %s\n", msg); return true; } /* ---------------------------------- * Native Features specific to Hatari */ /** * NF_EXIT - exit emulator with given exit code * Stack arguments are: * - emulator's int32_t exit value */ static bool nf_exit(uint32_t stack, uint32_t subid, uint32_t *retval) { int32_t exitval; ConfigureParams.Log.bConfirmQuit = false; exitval = STMemory_ReadLong(stack); LOG_TRACE(TRACE_NATFEATS, "NF_EXIT(%d)\n", exitval); Main_RequestQuit(exitval); fprintf(stderr, "NatFeats: exit(%d)\n", exitval); return true; } /** * NF_DEBUGGER - invoke debugger */ static bool nf_debugger(uint32_t stack, uint32_t subid, uint32_t *retval) { LOG_TRACE(TRACE_NATFEATS, "NF_DEBUGGER()\n"); DebugUI(REASON_PROGRAM); return true; } /** * NF_FASTFORWARD - set fast forward state * Stack arguments are: * - state 0: off, >=1: on * Returns previous fast-forward value */ static bool nf_fastforward(uint32_t stack, uint32_t subid, uint32_t *retval) { uint32_t val; *retval = ConfigureParams.System.bFastForward; if (subid) { /* unrecognized sub id -> no-op */ val = *retval; } else { val = STMemory_ReadLong(stack); } LOG_TRACE(TRACE_NATFEATS, "NF_FASTFORWARD(%d -> %d)\n", *retval, val); ConfigureParams.System.bFastForward = ( val ? true : false ); return true; } #if NF_COMMAND /** * NF_COMMAND - execute Hatari (cli / debugger) command * Stack arguments are: * - pointer to command string */ static bool nf_command(uint32_t stack, uint32_t subid, uint32_t *retval) { const char *buffer; uint32_t ptr; if (subid) { /* unrecognized sub id -> no-op */ return true; } ptr = STMemory_ReadLong(stack); if (mem_string_ok(ptr) < 0) { LOG_TRACE(TRACE_NATFEATS, "NF_COMMAND(0x%x) => BUS ERROR (invalid address)\n", ptr); return false; } buffer = (const char *)STMemory_STAddrToPointer(ptr); LOG_TRACE(TRACE_NATFEATS, "NF_COMMAND(0x%x \"%s\")\n", ptr, buffer); Control_ProcessBuffer(buffer); return true; } #endif /* ---------------------------- */ #define FEATNAME_MAX 16 static const struct { const char *name; /* feature name */ bool super; /* should be called only in supervisor mode */ bool (*cb)(uint32_t stack, uint32_t subid, uint32_t *retval); } features[] = { #if NF_COMMAND { "NF_COMMAND", false, nf_command }, #endif { "NF_NAME", false, nf_name }, { "NF_VERSION", false, nf_version }, { "NF_STDERR", false, nf_stderr }, { "NF_SHUTDOWN", true, nf_shutdown }, { "NF_EXIT", false, nf_exit }, { "NF_DEBUGGER", false, nf_debugger }, { "NF_FASTFORWARD", false, nf_fastforward } #if defined(__linux__) ,{ "NF_SCSIDRV", true, nf_scsidrv } #endif }; /* macros from Aranym */ #define ID_SHIFT 20 #define IDX2MASTERID(idx) (((idx)+1) << ID_SHIFT) #define MASTERID2IDX(id) (((id) >> ID_SHIFT)-1) #define MASKOUTMASTERID(id) ((id) & ((1L << ID_SHIFT)-1)) /** * Set retval to internal ID for requested Native Feature, * or zero if feature is unknown/unsupported. * * Return true if caller is to proceed normally, * false if there was an exception. */ bool NatFeat_ID(uint32_t stack, uint32_t *retval) { const char *name; uint32_t ptr; int i; ptr = STMemory_ReadLong(stack); if ( !STMemory_CheckAreaType ( ptr, FEATNAME_MAX, ABFLAG_RAM | ABFLAG_ROM ) ) { M68000_BusError(ptr, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); LOG_TRACE(TRACE_NATFEATS, "NF ID(0x%x) => BUS ERROR (invalid address)\n", ptr); return false; } name = (const char *)STMemory_STAddrToPointer ( ptr ); LOG_TRACE(TRACE_NATFEATS, "NF ID(0x%x \"%s\")\n", ptr, name); for (i = 0; i < ARRAY_SIZE(features); i++) { if (strcmp(features[i].name, name) == 0) { *retval = IDX2MASTERID(i); return true; } } /* unknown feature */ *retval = 0; return true; } /** * Do given Native Feature, if it is supported * and set 'retval' accordingly. * * Return true if caller is to proceed normally, * false if there was an exception. */ bool NatFeat_Call(uint32_t stack, bool super, uint32_t *retval) { uint32_t subid = STMemory_ReadLong(stack); unsigned int idx = MASTERID2IDX(subid); subid = MASKOUTMASTERID(subid); if (idx >= ARRAY_SIZE(features)) { LOG_TRACE(TRACE_NATFEATS, "ERROR: invalid NF ID %d requested\n", idx); return true; /* undefined */ } if (features[idx].super && !super) { LOG_TRACE(TRACE_NATFEATS, "ERROR: NF function %d called without supervisor mode\n", idx); M68000_Exception(8, M68000_EXC_SRC_CPU); return false; } stack += SIZE_LONG; return features[idx].cb(stack, subid, retval); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/natfeats.h000066400000000000000000000006001504763705000243700ustar00rootroot00000000000000/* Hatari - natfeats.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_NATFEATS_H #define HATARI_NATFEATS_H extern bool NatFeat_ID(uint32_t, uint32_t *retval); extern bool NatFeat_Call(uint32_t, bool isSuper, uint32_t *retval); #endif /* HATARI_NATFEATS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/profile.c000066400000000000000000000571511504763705000242330ustar00rootroot00000000000000/* * Hatari - profile.c * * Copyright (C) 2010-2023 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * profile.c - profile caller info handling and debugger parsing functions */ const char Profile_fileid[] = "Hatari profile.c"; #include #include #include #include "config.h" #if HAVE_LIBREADLINE # include #else # define rl_filename_completion_function(x,y) NULL #endif #include "main.h" #include "version.h" #include "debugui.h" #include "debug_priv.h" #include "configuration.h" #include "clocks_timings.h" #include "evaluate.h" #include "symbols.h" #include "profile.h" #include "profile_priv.h" #include "m68000.h" #include "dsp.h" profile_loop_t profile_loop; #define CALLER_SEPARATOR ',' /* ------------------ CPU/DSP caller information handling ----------------- */ static const struct { char chr; calltype_t bit; const char *info; } flaginfo[] = { { 'u', CALL_UNKNOWN, "unknown PC change" }, { 'n', CALL_NEXT, "PC moved to next instruction", }, { 'b', CALL_BRANCH, "branch/jump" }, { 's', CALL_SUBROUTINE, "subroutine call" }, { 'r', CALL_SUBRETURN, "return from subroutine" }, { 'e', CALL_EXCEPTION, "exception" }, { 'x', CALL_EXCRETURN, "return from exception" } }; /** * compare function for qsort() to sort caller data by calls */ static int cmp_callers(const void *c1, const void *c2) { uint32_t calls1 = ((const caller_t*)c1)->calls; uint32_t calls2 = ((const caller_t*)c2)->calls; if (calls1 > calls2) { return -1; } if (calls1 < calls2) { return 1; } return 0; } /** * output caller counter information */ static bool output_counter_info(FILE *fp, counters_t *counter) { if (!counter->count) { return false; } /* number of calls needs to be first and rest must be in the same order as * they're in the profile disassembly (count of instructions, etc...). */ fprintf(fp, " %"PRIu64"/%"PRIu64"/%"PRIu64"", counter->calls, counter->count, counter->cycles); if (counter->i_misses) { /* these are only with specific WinUAE CPU core */ fprintf(fp, "/%"PRIu64"/%"PRIu64"", counter->i_misses, counter->d_hits); } return true; } /** * output caller call counts, call type(s) and costs */ static void output_caller_info(FILE *fp, caller_t *info, uint32_t *typeaddr) { int k, typecount; fprintf(fp, "0x%x = %d", info->addr, info->calls); if (info->flags) { /* calltypes supported? */ fputc(' ', fp); typecount = 0; for (k = 0; k < ARRAY_SIZE(flaginfo); k++) { if (info->flags & flaginfo[k].bit) { fputc(flaginfo[k].chr, fp); typecount++; } } if (typecount > 1) { *typeaddr = info->addr; } } if (output_counter_info(fp, &(info->all))) { output_counter_info(fp, &(info->own)); if (info->calls != info->own.calls) { fprintf(stderr, "WARNING: mismatch between function 0x%x call count %d and own call cost %"PRIu64"!\n", info->addr, info->calls, info->own.calls); } } fprintf(fp, "%c ", CALLER_SEPARATOR); } /** * Show collected CPU/DSP callee/caller information */ void Profile_ShowCallers(FILE *fp, int sites, callee_t *callsite, const char * (*addr2name)(uint32_t, uint64_t *)) { int i, j, countissues, countdiff; const char *name; caller_t *info; uint64_t total; uint32_t addr, typeaddr; /* legend */ fputs("# : = [ [ ]], ..., ", fp); fputs("\n# types: ", fp); for (i = 0; i < ARRAY_SIZE(flaginfo); i++) { fprintf(fp, "%c = %s, ", flaginfo[i].chr, flaginfo[i].info); } fputs("\n# totals: calls/instructions/cycles/i-misses/d-hits\n", fp); countdiff = 0; countissues = 0; for (i = 0; i < sites; i++, callsite++) { addr = callsite->addr; if (!addr) { continue; } name = addr2name(addr, &total); fprintf(fp, "0x%x: ", callsite->addr); typeaddr = 0; info = callsite->callers; qsort(info, callsite->count, sizeof(*info), cmp_callers); for (j = 0; j < callsite->count; j++, info++) { if (!info->calls) { break; } total -= info->calls; output_caller_info(fp, info, &typeaddr); } /* Symbol name output here is used only for debugging * profiler functionality, post-processor does not * need it. * * Output them only if they do not include caller * separator (','), because that would mess up * post-processor profile data parsing. * * Skip also all mangled (C++) symbols as they could * include that separator after demangling. */ if (name && strchr(name, CALLER_SEPARATOR) == NULL && strncmp(name, "__Z", 3) != 0) { fprintf(fp, "%s", name); } fputs("\n", fp); if (total) { #if DEBUG fprintf(stderr, "WARNING: %llu differences in call and instruction counts for '%s'!\n", total, name); #endif countdiff += total; countissues++; } if (typeaddr) { fprintf(stderr, "WARNING: different types of calls (at least) from 0x%x (to 0x%x),\n\t has its code changed during profiling?\n", typeaddr, callsite->addr); } } if (countissues) { if (countdiff <= 2 && countissues == countdiff) { fprintf(stderr, "WARNING: callcount mismatches (%d calls) with address instruction\n\t counts in %d cases, most likely profile start & end.\n", countdiff, countissues); } else { /* profiler bug: some (address?) mismatch in recording instruction counts and call counts */ fprintf(stderr, "ERROR: callcount mismatches with address instruction counts\n\t(%d in total) detected in %d cases!\n", countdiff, countissues); } } } /** * add second counter values to first counters */ static void add_counter_costs(counters_t *dst, counters_t *src) { dst->calls += src->calls; dst->count += src->count; dst->cycles += src->cycles; dst->i_misses += src->i_misses; dst->d_hits += src->d_hits; } /** * set first counter values to their difference from a reference value */ static void set_counter_diff(counters_t *dst, counters_t *ref) { dst->calls = ref->calls - dst->calls; dst->count = ref->count - dst->count; dst->cycles = ref->cycles - dst->cycles; dst->i_misses = ref->i_misses - dst->i_misses; dst->d_hits = ref->d_hits - dst->d_hits; } /** * add called (callee) function costs to caller information */ static void add_callee_cost(callee_t *callsite, callstack_t *stack) { caller_t *info = callsite->callers; counters_t owncost; int i; for (i = 0; i < callsite->count; i++, info++) { if (info->addr == stack->caller_addr) { /* own cost for callee is its child (out) costs * deducted from full (all) costs */ owncost = stack->out; set_counter_diff(&owncost, &(stack->all)); add_counter_costs(&(info->own), &owncost); add_counter_costs(&(info->all), &(stack->all)); return; } } /* cost is only added for updated callers, * so they should always exist */ fprintf(stderr, "ERROR: trying to add costs to non-existing 0x%x caller of 0x%x!\n", stack->caller_addr, callsite->addr); assert(0); } /** * Add new caller or updated earlier caller stats for call site */ static void add_caller(callee_t *callsite, uint32_t pc, uint32_t prev_pc, calltype_t flag) { int i, count, oldcount; caller_t *info; /* need to store real call addresses as symbols can change * after profiling has been stopped */ info = callsite->callers; if (!info) { info = calloc(1, sizeof(*info)); if (!info) { fprintf(stderr, "ERROR: caller info alloc failed!\n"); return; } /* first call to this address, save address */ callsite->addr = pc; callsite->callers = info; callsite->count = 1; } /* how many caller slots are currently allocated? */ count = callsite->count; oldcount = 0; for (;;) { for (i = oldcount; i < count; i++, info++) { if (info->addr == prev_pc) { /* increment caller */ info->flags |= flag; info->calls++; return; } if (!info->addr) { /* add caller to empty slot */ info->addr = prev_pc; info->flags |= flag; info->calls = 1; return; } } oldcount = count; /* not enough, double caller slots */ count *= 2; info = realloc(callsite->callers, count * sizeof(*info)); if (!info) { fprintf(stderr, "ERROR: caller info alloc failed!\n"); return; } callsite->callers = info; callsite->count = count; info = info + oldcount; memset(info, 0, oldcount * sizeof(*info)); } } /** * Add information about the called symbol, and if it was a subroutine * call, add it to stack of functions which total costs are tracked. * callinfo.return_pc needs to be set before invoking this if the call * is of type CALL_SUBROUTINE. */ void Profile_CallStart(int idx, callinfo_t *callinfo, uint32_t prev_pc, calltype_t flag, uint32_t pc, counters_t *totalcost) { callstack_t *stack; int count; if (unlikely(idx >= callinfo->sites)) { fprintf(stderr, "ERROR: number of symbols increased during profiling (%d > %d)!\n", idx, callinfo->sites); return; } add_caller(callinfo->site + idx, pc, prev_pc, flag); /* subroutine/exception call i.e. one which will return? */ if (flag != CALL_SUBROUTINE && flag != CALL_EXCEPTION) { /* no, some other call type */ return; } /* yes, add it to call stack */ if (unlikely(!callinfo->count)) { /* initial stack alloc, can be a bit larger */ count = 8; stack = calloc(count, sizeof(*stack)); if (!stack) { fputs("ERROR: callstack alloc failed!\n", stderr); return; } callinfo->stack = stack; callinfo->count = count; } else if (unlikely(callinfo->depth+1 >= callinfo->count)) { /* need to alloc more stack space for new call? */ count = callinfo->count * 2; stack = realloc(callinfo->stack, count * sizeof(*stack)); if (!stack) { fputs("ERROR: callstack alloc failed!\n", stderr); return; } memset(stack + callinfo->count, 0, callinfo->count * sizeof(*stack)); callinfo->stack = stack; callinfo->count = count; } /* only first instruction can be undefined */ assert(callinfo->return_pc != PC_UNDEFINED || !callinfo->depth); /* called function */ stack = &(callinfo->stack[callinfo->depth++]); /* store current running totals & zero subcall costs */ stack->all = *totalcost; memset(&(stack->out), 0, sizeof(stack->out)); /* set subroutine call information */ stack->ret_addr = callinfo->return_pc; stack->callee_idx = idx; stack->caller_addr = prev_pc; stack->callee_addr = pc; /* record call to this into costs... */ totalcost->calls++; } /** * If it really was subcall (function) return, store returned function * costs and update callinfo->return_pc value. Return address of * the instruction which did the returned call. */ uint32_t Profile_CallEnd(callinfo_t *callinfo, counters_t *totalcost) { callstack_t *stack; assert(callinfo->depth); /* remove call info from stack */ callinfo->depth--; /* callinfo->depth points now to to-be removed item */ stack = &(callinfo->stack[callinfo->depth]); if (unlikely(stack->caller_addr == PC_UNDEFINED)) { /* return address can be undefined only for * first profiled instruction, i.e. only for * function at top of stack */ assert(!callinfo->depth); } else { /* full cost is original global cost (in ->all) * deducted from current global (total) cost */ set_counter_diff(&(stack->all), totalcost); add_callee_cost(callinfo->site + stack->callee_idx, stack); } /* if current function had a parent: * - start tracking that * - add full cost of current function to parent's outside costs */ if (callinfo->depth) { callstack_t *parent = stack - 1; callinfo->return_pc = parent->ret_addr; add_counter_costs(&(parent->out), &(stack->all)); } else { callinfo->return_pc = PC_UNDEFINED; } /* where the returned function was called from */ return stack->caller_addr; } /** * Add costs to all functions still in call stack and print their names * * Diagram of variables involved in 2 functions deep call stack: * *
 *   (caller_addr 1)  bsr symbol1  -1->  symbol1     (callee_addr 1)
 *      (ret_addr 1)       <-.    ...
 *                                  |    bsr symbol2 (caller_addr 2)  -2->  symbol2 (callee_addr 2)
 *                                  1         (ret_addr 2)    <-.    ...
 *                                  |    ...                           2    ... (PC)
 *                                  '-   rts                           '-   rts
 * 
* * When one wants to match callee_addr (= symbol) to caller_addr (= which * place in that function called further functions), it's best to traverse * stack from last item to top, as these items are in following call stack * items, not in same one. */ void Profile_FinalizeCalls(uint32_t pc, callinfo_t *callinfo, counters_t *totalcost, const char* (*get_symbol)(uint32_t, symtype_t), const char* (*get_caller)(uint32_t*)) { const char *sym, *caller; uint32_t sym_addr, caller_addr; int i, lines, offset; bool dots; char sign; if (!callinfo->depth) { return; } fprintf(stderr, "Finalizing costs for %d non-returned functions:\n", callinfo->depth); i = 0; dots = false; caller_addr = pc; lines = ConfigureParams.Debugger.nBacktraceLines; while (callinfo->depth > 0) { /* finalize & decrease callinfo->depth */ Profile_CallEnd(callinfo, totalcost); if (++i > lines && lines > 0) { continue; } /* Skip showing middle part of a long callstack as messed * callstacks could be thousands of frames deep... */ if (i >= 32 && callinfo->depth > 32) { if (!dots) { fprintf(stderr, "- ...\n"); dots = true; } } else { sym_addr = callinfo->stack[callinfo->depth].callee_addr; sym = get_symbol(sym_addr, SYMTYPE_CODE); if (sym) { offset = caller_addr - sym_addr; sign = offset >= 0 ? '+' : '-'; fprintf(stderr, "- %d. 0x%06x: %s %c0x%x", i, caller_addr, sym, sign, abs(offset)); } else { fprintf(stderr, "- %d. 0x%06x", i, caller_addr); } sym_addr = caller_addr; caller = get_caller(&sym_addr); if (caller && caller != sym) { offset = caller_addr - sym_addr; fprintf(stderr, " (%s +0x%x)\n", caller, abs(offset)); } else { fprintf(stderr, "\n"); } } caller_addr = callinfo->stack[callinfo->depth].caller_addr; } } /** * Show current profile call stack, up to configured max backtrace depth */ static void Profile_ShowStack(bool forDsp) { const char *sym, *caller; const char *(*get_caller)(uint32_t*); const char *(*get_symbol)(uint32_t, symtype_t); uint32_t sym_addr, caller_addr; int i, offset, depth, top; callinfo_t *callinfo; if (forDsp) { Profile_DspGetCallinfo(&callinfo, &get_caller, &get_symbol); caller_addr = DSP_GetPC(); } else { Profile_CpuGetCallinfo(&callinfo, &get_caller, &get_symbol); caller_addr = M68000_GetPC(); } if (!callinfo->depth) { fprintf(stderr, "Empty stack.\n"); return; } depth = callinfo->depth; top = ConfigureParams.Debugger.nBacktraceLines; if (top > 0 && top < depth) { top = depth - top; } else { top = 0; } i = 0; while (depth-- > top) { i++; sym_addr = callinfo->stack[depth].callee_addr; offset = caller_addr - sym_addr; sym = get_symbol(sym_addr, SYMTYPE_CODE); if (sym) { char sign = offset >= 0 ? '+' : '-'; fprintf(stderr, "- %d. 0x%06x: %s %c0x%x", i, caller_addr, sym, sign, abs(offset)); } else { fprintf(stderr, "- %d. 0x%06x", i, caller_addr); } sym_addr = caller_addr; caller = get_caller(&sym_addr); if (caller && caller != sym) { offset = caller_addr - sym_addr; fprintf(stderr, " (%s +0x%x)\n", caller, abs(offset)); } else { fprintf(stderr, "\n"); } caller_addr = callinfo->stack[depth].caller_addr; } } /** * Allocate & set initial callinfo structure information */ int Profile_AllocCallinfo(callinfo_t *callinfo, int count, const char *name) { callinfo->sites = count; if (count) { /* alloc & clear new data */ callinfo->site = calloc(count, sizeof(callee_t)); if (callinfo->site) { fprintf(stderr, "Allocated %s profile callsite buffer for %d symbols.\n", name, count); callinfo->prev_pc = callinfo->return_pc = PC_UNDEFINED; } else { fprintf(stderr, "ERROR: callsite buffer alloc failed!\n"); callinfo->sites = 0; } } return callinfo->sites; } /** * Free all callinfo structure information */ void Profile_FreeCallinfo(callinfo_t *callinfo) { int i; if (callinfo->sites) { callee_t *site = callinfo->site; for (i = 0; i < callinfo->sites; i++, site++) { if (site->callers) { free(site->callers); } } free(callinfo->site); if (callinfo->stack) { free(callinfo->stack); } memset(callinfo, 0, sizeof(*callinfo)); } } /* ------------------- command parsing ---------------------- */ /** * Readline match callback to list profile subcommand names. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Profile_Match(const char *text, int state) { static const char *subs[] = { "addresses", "callers", "caches", "counts", "cycles", "d-hits", "i-misses", "loops", "off", "on", "save", "stack", "stats", "symbols" }; char *ret = DebugUI_MatchHelper(subs, ARRAY_SIZE(subs), text, state); if (ret) { return ret; } return rl_filename_completion_function(text, state); } const char Profile_Description[] = " [parameter]\n" "\n" "\tSubcommands:\n" "\t- on\n" "\t- off\n" "\t- counts [count]\n" "\t- cycles [count]\n" "\t- i-misses [count]\n" "\t- d-hits [count]\n" "\t- symbols [count]\n" "\t- addresses [address]\n" "\t- callers\n" "\t- caches\n" "\t- stack\n" "\t- stats\n" "\t- save \n" "\t- loops [CPU limit] [DSP limit]\n" "\n" "\t'on' & 'off' enable and disable profiling. Data is collected\n" "\tuntil debugger is entered again at which point you get profiling\n" "\tstatistics ('stats') summary.\n" "\n" "\tThen you can ask for list of the PC addresses, sorted either by\n" "\texecution 'counts', used 'cycles', i-cache misses or d-cache hits.\n" "\tFirst can be limited just to named addresses with 'symbols'.\n" "\tOptional count will limit how many items will be shown.\n" "\n" "\t'caches' shows histogram of CPU cache usage.\n" "\n" "\t'addresses' lists the profiled addresses in order, with the\n" "\tinstructions (currently) residing at them. By default this\n" "\tstarts from the first executed instruction, or you can\n" "\tspecify the starting address.\n" "\n" "\t'callers' shows (raw) caller information for addresses which\n" "\thad symbol(s) associated with them. 'stack' shows the current\n" "\tprofile stack (this is useful only with :noinit breakpoints).\n" "\n" "\tProfile address and callers information can be saved with\n" "\t'save' command.\n" "\n" "\tDetailed (spin) looping information can be collected by\n" "\tspecifying to which file it should be saved, with optional\n" "\tlimit(s) on how many bytes first and last instruction\n" "\taddress of the loop can differ (0 = no limit)."; /** * Save profiling information for CPU or DSP. */ static bool Profile_Save(const char *fname, bool bForDsp) { FILE *out; uint32_t freq; const char *proc; if (!(out = fopen(fname, "w"))) { fprintf(stderr, "ERROR: opening '%s' for writing failed!\n", fname); perror(NULL); return false; } if (bForDsp) { freq = MachineClocks.DSP_Freq; proc = "DSP"; } else { freq = MachineClocks.CPU_Freq_Emul; proc = "CPU"; } fprintf(out, "Hatari %s profile (%s)\n", proc, PROG_NAME); fprintf(out, "Cycles/second:\t%u\n", freq); if (bForDsp) { Profile_DspSave(out); } else { Profile_CpuSave(out); } fclose(out); return true; } /** * function CPU & DSP profiling functionality can call to * reset loop information log by truncating it. Only portable * way to do that is re-opening it again. */ bool Profile_LoopReset(void) { if (!profile_loop.filename) { return false; } if (profile_loop.fp) { fclose(profile_loop.fp); } profile_loop.fp = fopen(profile_loop.filename, "w"); if (!profile_loop.fp) { return false; } fprintf(profile_loop.fp, "#
\n"); return true; } /** * Open file common to both CPU and DSP profiling. */ static bool Profile_Loops(int nArgc, char *psArgs[]) { if (nArgc > 2) { /* check that the given file can be opened for writing */ if (profile_loop.filename) { free(profile_loop.filename); } profile_loop.filename = strdup(psArgs[2]); if (Profile_LoopReset()) { if (nArgc > 3) { profile_loop.cpu_limit = atoi(psArgs[3]); if (nArgc > 4) { profile_loop.dsp_limit = atoi(psArgs[4]); } } fprintf(stderr, "Additional max %d (CPU) & %d (DSP) byte loop profiling enabled to:\n\t%s\n", profile_loop.cpu_limit, profile_loop.cpu_limit, psArgs[2]); } else { free(profile_loop.filename); profile_loop.filename = NULL; perror("ERROR: opening profile loop output file failed, disabling!"); return false; } } else { if (profile_loop.fp) { fprintf(stderr, "Disabling loop profiling.\n"); free(profile_loop.filename); profile_loop.filename = NULL; fclose(profile_loop.fp); profile_loop.fp = NULL; } else { fprintf(stderr, "ERROR: no file name for saving the loop profiling information.\n"); } } return true; } /** * Command: CPU/DSP profiling enabling, exec stats, cycle and call stats. * Returns DEBUGGER_CMDDONE or DEBUGGER_CMDCONT. */ int Profile_Command(int nArgc, char *psArgs[], bool bForDsp) { static int show = 16; uint32_t *disasm_addr; bool *enabled; if (nArgc > 2) { show = atoi(psArgs[2]); } if (bForDsp) { Profile_DspGetPointers(&enabled, &disasm_addr); } else { Profile_CpuGetPointers(&enabled, &disasm_addr); } /* continue or explicit addresses command? */ if (nArgc < 2 || strcmp(psArgs[1], "addresses") == 0) { uint32_t lower, upper = 0; if (nArgc > 2) { if (Eval_Range(psArgs[2], &lower, &upper, false) < 0) { return DEBUGGER_CMDDONE; } } else { lower = *disasm_addr; } if (bForDsp) { *disasm_addr = Profile_DspShowAddresses(lower, upper, stderr, PAGING_ENABLED); } else { *disasm_addr = Profile_CpuShowAddresses(lower, upper, stderr, PAGING_ENABLED); } return DEBUGGER_CMDCONT; } else if (strcmp(psArgs[1], "on") == 0) { *enabled = true; fprintf(stderr, "Profiling enabled.\n"); } else if (strcmp(psArgs[1], "off") == 0) { *enabled = false; fprintf(stderr, "Profiling disabled.\n"); } else if (strcmp(psArgs[1], "stats") == 0) { if (bForDsp) { Profile_DspShowStats(); } else { Profile_CpuShowStats(); } } else if (strcmp(psArgs[1], "i-misses") == 0) { if (bForDsp) { fprintf(stderr, "Cache information is recorded only for CPU, not DSP.\n"); } else { Profile_CpuShowInstrMisses(show); } } else if (strcmp(psArgs[1], "d-hits") == 0) { if (bForDsp) { fprintf(stderr, "Cache information is recorded only for CPU, not DSP.\n"); } else { Profile_CpuShowDataHits(show); } } else if (strcmp(psArgs[1], "caches") == 0) { if (bForDsp) { fprintf(stderr, "Cache information is recorded only for CPU, not DSP.\n"); } else { Profile_CpuShowCaches(); } } else if (strcmp(psArgs[1], "cycles") == 0) { if (bForDsp) { Profile_DspShowCycles(show); } else { Profile_CpuShowCycles(show); } } else if (strcmp(psArgs[1], "counts") == 0) { if (bForDsp) { Profile_DspShowCounts(show, false); } else { Profile_CpuShowCounts(show, false); } } else if (strcmp(psArgs[1], "symbols") == 0) { if (bForDsp) { Profile_DspShowCounts(show, true); } else { Profile_CpuShowCounts(show, true); } } else if (strcmp(psArgs[1], "callers") == 0) { if (bForDsp) { Profile_DspShowCallers(stderr); } else { Profile_CpuShowCallers(stderr); } } else if (strcmp(psArgs[1], "stack") == 0) { Profile_ShowStack(bForDsp); } else if (strcmp(psArgs[1], "save") == 0) { Profile_Save(psArgs[2], bForDsp); } else if (strcmp(psArgs[1], "loops") == 0) { Profile_Loops(nArgc, psArgs); } else { DebugUI_PrintCmdHelp(psArgs[0]); } return DEBUGGER_CMDDONE; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/profile.h000066400000000000000000000025771504763705000242420ustar00rootroot00000000000000/* * Hatari - profile.h * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_PROFILE_H #define HATARI_PROFILE_H /* caller types */ #define CALL_UNDEFINED 0 /* = call type information not supported */ typedef enum { CALL_UNKNOWN = 1, CALL_NEXT = 2, CALL_BRANCH = 4, CALL_SUBROUTINE = 8, CALL_SUBRETURN = 16, CALL_EXCEPTION = 32, CALL_EXCRETURN = 64, CALL_INTERRUPT = 128 } calltype_t; /* profile command parsing */ extern const char Profile_Description[]; extern char *Profile_Match(const char *text, int state); extern int Profile_Command(int nArgc, char *psArgs[], bool bForDsp); /* CPU profile control */ extern void Profile_CpuFree(void); extern bool Profile_CpuStart(void); extern void Profile_CpuUpdate(void); extern void Profile_CpuStop(void); /* CPU profile results */ extern bool Profile_CpuAddr_HasData(uint32_t addr); extern int Profile_CpuAddr_DataStr(char *buffer, int maxlen, uint32_t addr); /* DSP profile control */ extern void Profile_DspFree(void); extern bool Profile_DspStart(void); extern void Profile_DspUpdate(void); extern void Profile_DspStop(void); /* DSP profile results */ extern bool Profile_DspAddressData(uint16_t addr, float *percentage, uint64_t *count, uint64_t *cycles, uint16_t *cycle_diff); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/profile_priv.h000066400000000000000000000112301504763705000252640ustar00rootroot00000000000000/* * Hatari - profile_priv.h * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_PROFILE_PRIV_H #define HATARI_PROFILE_PRIV_H typedef struct { char *filename; /* where to write loop info */ FILE *fp; /* pointer modified by CPU & DSP code */ uint32_t cpu_limit; /* max limit for profiled CPU loop size */ uint32_t dsp_limit; /* max limit for profiled DSP loop size */ } profile_loop_t; extern profile_loop_t profile_loop; typedef struct { uint64_t calls, count, cycles; /* common counters between CPU & DSP */ uint64_t i_misses, d_hits; /* CPU specific counters */ uint64_t cycles_diffs; /* DSP specific counter, not updated at run-time */ } counters_t; typedef struct { int callee_idx; /* index of called function */ uint32_t ret_addr; /* address after returning from call */ uint32_t caller_addr; /* caller address for callstack printing */ uint32_t callee_addr; /* callee address for callstack printing */ counters_t all; /* totals including everything called code does */ counters_t out; /* totals for subcalls done from callee */ } callstack_t; /* callee/caller information */ typedef struct { calltype_t flags; /* what kind of call it was */ uint32_t addr; /* address for the caller */ uint32_t calls; /* number of calls, exclusive */ counters_t all; /* totals including everything called code does */ counters_t own; /* totals excluding called code (=sum(all-out)) */ } caller_t; typedef struct { uint32_t addr; /* called address */ int count; /* number of callers */ caller_t *callers; /* who called this address */ } callee_t; /* impossible PC value, for uninitialized PC values */ #define PC_UNDEFINED 0xFFFFFFFF typedef struct { int sites; /* number of symbol callsites */ int count; /* number of items allocated for stack */ int depth; /* how many callstack calls haven't yet returned */ uint32_t prev_pc; /* stored previous PC value */ uint32_t return_pc; /* address for last call return address (speedup) */ callee_t *site; /* symbol specific caller information */ callstack_t *stack; /* calls that will return */ } callinfo_t; /* CPU/DSP memory area statistics */ typedef struct { counters_t counters; /* counters for this area */ uint32_t lowest, highest; /* active address range within memory area */ int active; /* number of active addresses */ bool overflow; /* whether counters overflowed */ } profile_area_t; /* generic profile caller/callee info functions */ extern void Profile_ShowCallers(FILE *fp, int sites, callee_t *callsite, const char * (*addr2name)(uint32_t, uint64_t *)); extern void Profile_CallStart(int idx, callinfo_t *callinfo, uint32_t prev_pc, calltype_t flag, uint32_t pc, counters_t *totalcost); extern void Profile_FinalizeCalls(uint32_t pc, callinfo_t *callinfo, counters_t *totalcost, const char* (get_symbol)(uint32_t, symtype_t), const char* (get_caller)(uint32_t*)); extern uint32_t Profile_CallEnd(callinfo_t *callinfo, counters_t *totalcost); extern int Profile_AllocCallinfo(callinfo_t *callinfo, int count, const char *info); extern void Profile_FreeCallinfo(callinfo_t *callinfo); extern bool Profile_LoopReset(void); /* parser helpers */ extern void Profile_CpuGetPointers(bool **enabled, uint32_t **disasm_addr); extern void Profile_DspGetPointers(bool **enabled, uint32_t **disasm_addr); extern void Profile_CpuGetCallinfo(callinfo_t **callinfo, const char* (**get_caller)(uint32_t*), const char* (**get_symbol)(uint32_t, symtype_t)); extern void Profile_DspGetCallinfo(callinfo_t **callinfo, const char* (**get_caller)(uint32_t*), const char* (**get_symbol)(uint32_t, symtype_t)); typedef enum { PAGING_DISABLED, PAGING_ENABLED } paging_t; /* internal CPU profile results */ extern uint32_t Profile_CpuShowAddresses(uint32_t lower, uint32_t upper, FILE *out, paging_t use_paging); extern void Profile_CpuShowCounts(int show, bool only_symbols); extern void Profile_CpuShowCycles(int show); extern void Profile_CpuShowInstrMisses(int show); extern void Profile_CpuShowDataHits(int show); extern void Profile_CpuShowCaches(void); extern void Profile_CpuShowStats(void); extern void Profile_CpuShowCallers(FILE *fp); extern void Profile_CpuSave(FILE *out); /* internal DSP profile results */ extern uint16_t Profile_DspShowAddresses(uint32_t lower, uint32_t upper, FILE *out, paging_t use_paging); extern void Profile_DspShowCounts(int show, bool only_symbols); extern void Profile_DspShowCycles(int show); extern void Profile_DspShowStats(void); extern void Profile_DspShowCallers(FILE *fp); extern void Profile_DspSave(FILE *out); #endif /* HATARI_PROFILE_PRIV_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/profilecpu.c000066400000000000000000001226431504763705000247420ustar00rootroot00000000000000/* * Hatari - profilecpu.c * * Copyright (C) 2010-2019 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * profilecpu.c - functions for profiling CPU and showing the results. */ const char Profilecpu_fileid[] = "Hatari profilecpu.c"; #include #include #include #include "main.h" #include "configuration.h" #include "clocks_timings.h" #include "debugInfo.h" #include "dsp.h" #include "m68000.h" #include "68kDisass.h" #include "symbols.h" #include "profile.h" #include "profile_priv.h" #include "debug_priv.h" #include "stMemory.h" #include "tos.h" #include "video.h" /* cartridge area */ #define CART_SIZE (CART_END - CART_START) /* if non-zero, output (more) warnings on suspicious: * - cycle/instruction counts * - PC switches * And drop to debugger on invalid current & previous PC addresses. * * NOTE: DebugUI() calls that DEBUG define enables, can cause * instruction count mismatch assertions because debugger invocation * resets the counters AND happens in middle of data collection. * It's best to quit after debugging the issue ('q' command). */ #define DEBUG 0 #if DEBUG #include "debugui.h" static bool skip_assert; #endif /* whether to track & show all cache stats for all instructions */ #define DEBUG_CACHE 0 static callinfo_t cpu_callinfo; #define MAX_CPU_PROFILE_VALUE 0xFFFFFFFF typedef struct { uint32_t count; /* how many times this address instruction is executed */ uint32_t cycles; /* how many CPU cycles was taken at this address */ #if DEBUG_CACHE /* track also less relevant cache events */ uint32_t i_hits; /* how many CPU i-cache hits happened at this address */ uint32_t d_misses; /* how many CPU d-cache misses happened at this address */ #endif uint32_t i_misses; /* how many CPU i-cache misses happened at this address */ uint32_t d_hits; /* how many CPU d-cache hits happened at this address */ } cpu_profile_item_t; /* max count of hits/misses single instruction can trigger at once */ #define MAX_I_HITS 8 #define MAX_I_MISSES 8 #define MAX_D_HITS 32 #define MAX_D_MISSES 20 static struct { counters_t all; /* total counts for all areas */ cpu_profile_item_t *data; /* profile data items */ uint32_t size; /* number of allocated profile data items */ profile_area_t ttram; /* TT-RAM stats */ profile_area_t ram; /* normal RAM stats */ profile_area_t rom; /* cartridge ROM stats */ profile_area_t tos; /* ROM TOS stats */ int active; /* number of active data items in all areas */ uint32_t *sort_arr; /* data indexes used for sorting */ int prev_family; /* previous instruction opcode family */ uint64_t prev_cycles; /* previous instruction cycles counter */ uint32_t prev_pc; /* previous instruction address */ uint32_t loop_start; /* address of last loop start */ uint32_t loop_end; /* address of last loop end */ uint32_t loop_count; /* how many times it was looped */ uint32_t disasm_addr; /* 'addresses' command start address */ uint32_t i_prefetched; /* instructions that don't incur prefetch hit/miss */ uint32_t i_hit_counts[MAX_I_HITS]; /* I-cache hit counts */ uint32_t d_hit_counts[MAX_D_HITS]; /* D-cache hit counts */ uint32_t i_miss_counts[MAX_I_MISSES]; /* I-cache miss counts */ uint32_t d_miss_counts[MAX_D_MISSES]; /* D-cache miss counts */ bool processed; /* true when data is already processed */ bool enabled; /* true when profiling enabled */ } cpu_profile; /* full counts for warnings that are printed without rate-limiting */ typedef struct { int odd; int address; int returns; int multireturn; int prevpc; int largevalue; int opfamily; int zerocycles; } cpu_warnings_t; static cpu_warnings_t cpu_warnings; #define MAX_SHOW_COUNT 8 #define MAX_MULTI_RETURN 1 /* ------------------ CPU profile address mapping ----------------- */ /** * convert Atari memory address to sorting array profile data index. */ static inline uint32_t address2index(uint32_t pc) { if (unlikely(pc & 1)) { if (++cpu_warnings.odd <= MAX_SHOW_COUNT) { fprintf(stderr, "WARNING: odd CPU profile instruction address 0x%x!\n", pc); if (cpu_warnings.odd == MAX_SHOW_COUNT) { fprintf(stderr, "Further warnings won't be shown.\n"); } } #if DEBUG skip_assert = true; DebugUI(REASON_CPU_EXCEPTION); #endif } if (pc < STRamEnd) { /* most likely case, use RAM address as-is */ } else if (pc >= TosAddress && pc < TosAddress + TosSize) { /* TOS, put it after RAM data */ pc = pc - TosAddress + STRamEnd; if (TosAddress >= CART_END) { /* and after cartridge data as it's higher */ pc += CART_SIZE; } } else if (pc >= CART_START && pc < CART_END) { /* ROM, put it after RAM data */ pc = pc - CART_START + STRamEnd; if (TosAddress < CART_START) { /* and after TOS as it's higher */ pc += TosSize; } } else if (TTmemory && pc >= TTRAM_START && pc < TTRAM_START + 1024*(unsigned)ConfigureParams.Memory.TTRamSize_KB) { pc += STRamEnd + TosSize + CART_SIZE - TTRAM_START; } else { if (++cpu_warnings.address <= MAX_SHOW_COUNT) { fprintf(stderr, "WARNING: 'invalid' CPU PC profile instruction address 0x%x!\n", pc); if (cpu_warnings.address == MAX_SHOW_COUNT) { fprintf(stderr, "Further warnings won't be shown.\n"); } } /* extra entry at end is reserved for invalid PC values */ pc = STRamEnd + TosSize + CART_SIZE; #if DEBUG skip_assert = true; DebugUI(REASON_CPU_EXCEPTION); #endif } /* CPU instructions are at even addresses, save space by halving */ return (pc >> 1); } /** * convert sorting array profile data index to Atari memory address. */ static uint32_t index2address(uint32_t idx) { idx <<= 1; /* RAM */ if (idx < STRamEnd) { return idx; } idx -= STRamEnd; /* TOS before cartridge area? */ if (TosAddress < CART_START) { /* TOS */ if (idx < TosSize) { return idx + TosAddress; } idx -= TosSize; /* ROM */ if (idx < CART_SIZE) { return idx + CART_START; } idx -= CART_SIZE; } else { /* ROM */ if (idx < CART_SIZE) { return idx + CART_START; } idx -= CART_SIZE; /* TOS */ if (idx < TosSize) { return idx + TosAddress; } idx -= TosSize; } return idx + TTRAM_START; } /* ------------------ CPU profile results ----------------- */ /** * Return true if there's profile data for given address, false otherwise */ bool Profile_CpuAddr_HasData(uint32_t addr) { cpu_profile_item_t *item; uint32_t idx; if (!cpu_profile.data) { return false; } idx = address2index(addr); item = &(cpu_profile.data[idx]); if (!item->count) { return false; } return true; } /** * Write string containing CPU cache stats, cycles, count, count percentage * for given address to provided buffer. * * Return zero if there's no profiling data for given address, * otherwise return the number of bytes consumed from the given buffer. */ int Profile_CpuAddr_DataStr(char *buffer, int maxlen, uint32_t addr) { cpu_profile_item_t *item; float percentage; uint32_t idx; int count; assert(buffer && maxlen > 0); if (!cpu_profile.data) { return 0; } idx = address2index(addr); item = &(cpu_profile.data[idx]); if (!item->count) { return 0; } if (cpu_profile.all.count) { percentage = 100.0 * item->count / cpu_profile.all.count; } else { percentage = 0.0; } #if DEBUG_CACHE count = snprintf(buffer, maxlen, "%5.2f%% (%u, %u, %u, %u, %u, %u)", percentage, item->count, item->cycles, item->i_hits, item->i_misses, item->d_hits, item->d_misses); #else count = snprintf(buffer, maxlen, "%5.2f%% (%u, %u, %u, %u)", percentage, item->count, item->cycles, item->i_misses, item->d_hits); #endif if (count >= maxlen) { /* truncated by (count - maxlen) amount */ return maxlen; } return count; } /** * Show CPU profiling warning counts */ static void show_cpu_warnings(void) { cpu_warnings_t warnings; memset(&warnings, 0, sizeof(warnings)); if (memcmp(&cpu_warnings, &warnings, sizeof(warnings)) == 0) { return; } fprintf(stderr, "\nCPU profiling warning counts:\n"); if (cpu_warnings.odd) { fprintf(stderr, "- Odd PC addresses: %d\n", cpu_warnings.odd); } if (cpu_warnings.address) { fprintf(stderr, "- Unmapped PC addresses: %d\n", cpu_warnings.address); } if (cpu_warnings.opfamily) { fprintf(stderr, "- Unrecognized (zero) opcode families: %d\n", cpu_warnings.opfamily); } if (cpu_warnings.returns) { fprintf(stderr, "- Subroutine calls didn't return through RTS etc: %d\n", cpu_warnings.returns); } if (cpu_warnings.multireturn > MAX_MULTI_RETURN) { fprintf(stderr, "- Subroutine calls returned (at max) through %d stack frames\n", cpu_warnings.multireturn); } if (cpu_warnings.prevpc) { fprintf(stderr, "- Undefined PC value for tracked address callers: %d\n", cpu_warnings.prevpc); } if (cpu_warnings.largevalue) { fprintf(stderr, "- Unexpectedly large cycles count or cache hit/miss values: %d\n", cpu_warnings.largevalue); } if (cpu_warnings.zerocycles) { fprintf(stderr, "- Successive instructions with zero cycles: %d\n", cpu_warnings.zerocycles); } } /** * Helper to show statistics for specified CPU profile area. */ static void show_cpu_area_stats(profile_area_t *area) { if (!area->active) { fprintf(stderr, "- no activity\n"); return; } fprintf(stderr, "- active address range:\n 0x%06x-0x%06x\n", index2address(area->lowest), index2address(area->highest)); fprintf(stderr, "- active instruction addresses:\n %d (%.2f%% of all areas)\n", area->active, 100.0 * area->active / cpu_profile.active); fprintf(stderr, "- executed instructions:\n %"PRIu64" (%.2f%% of all areas)\n", area->counters.count, 100.0 * area->counters.count / cpu_profile.all.count); /* CPU cache in use? */ if (cpu_profile.all.i_misses) { fprintf(stderr, "- instruction cache misses:\n %"PRIu64" (%.2f%% of all areas)\n", area->counters.i_misses, 100.0 * area->counters.i_misses / cpu_profile.all.i_misses); } if (cpu_profile.all.d_hits) { fprintf(stderr, "- data cache hits:\n %"PRIu64" (%.2f%% of all areas)\n", area->counters.d_hits, 100.0 * area->counters.d_hits / cpu_profile.all.d_hits); } fprintf(stderr, "- used cycles:\n %"PRIu64" (%.2f%% of all areas)\n = %.5fs\n", area->counters.cycles, 100.0 * area->counters.cycles / cpu_profile.all.cycles, (double)area->counters.cycles / MachineClocks.CPU_Freq_Emul); if (area->overflow) { fprintf(stderr, " *** COUNTER OVERFLOW! ***\n"); } } /** * show CPU area (RAM, ROM, TOS) specific statistics and error counts. */ void Profile_CpuShowStats(void) { fprintf(stderr, "Normal RAM (0-0x%X):\n", STRamEnd); show_cpu_area_stats(&cpu_profile.ram); fprintf(stderr, "ROM TOS (0x%X-0x%X):\n", TosAddress, TosAddress + TosSize); show_cpu_area_stats(&cpu_profile.tos); fprintf(stderr, "Cartridge ROM (0x%X-%X):\n", CART_START, CART_END); show_cpu_area_stats(&cpu_profile.rom); if (TTmemory && ConfigureParams.Memory.TTRamSize_KB) { fprintf(stderr, "TT-RAM (0x%X-%X):\n", TTRAM_START, TTRAM_START + 1024*ConfigureParams.Memory.TTRamSize_KB); show_cpu_area_stats(&cpu_profile.ttram); } fprintf(stderr, "\n= %.5fs\n", (double)cpu_profile.all.cycles / MachineClocks.CPU_Freq_Emul); show_cpu_warnings(); } /** * show percentage histogram of given array items */ static void show_histogram(const char *title, int count, uint32_t *items) { const uint64_t maxval = cpu_profile.all.count; uint32_t value; int i; fprintf(stderr, "\n%s, number of occurrences:\n", title); for (i = 0; i < count; i++) { value = items[i]; if (value) { int w, width = 50 * value / maxval+1; fprintf(stderr, " %2d: ", i); for (w = 0; w < width; w++) { fputc('#', stderr); } fprintf(stderr, " %.3f%%\n", 100.0 * value / maxval); } } } /** * show CPU cache usage histograms */ void Profile_CpuShowCaches(void) { if (!(cpu_profile.all.i_misses || cpu_profile.all.d_hits)) { fprintf(stderr, "No instruction/data cache information.\n"); return; } fprintf(stderr, "\nNote:\n" "- these statistics include all profiled instructions, but\n" "- instruction cache events happen only on prefetch/branch\n" "- data cache events can happen only for instructions that do memory reads\n" "\nAlready prefetched instructions: %.3f%% (no hits/misses)\n", 100.0 * cpu_profile.i_prefetched / cpu_profile.all.count); show_histogram("Instruction cache hits per instruction", ARRAY_SIZE(cpu_profile.i_hit_counts), cpu_profile.i_hit_counts); show_histogram("Instruction cache misses per instruction", ARRAY_SIZE(cpu_profile.i_miss_counts), cpu_profile.i_miss_counts); show_histogram("Data cache hits per instruction", ARRAY_SIZE(cpu_profile.d_hit_counts), cpu_profile.d_hit_counts); show_histogram("Data cache misses per instruction", ARRAY_SIZE(cpu_profile.d_miss_counts), cpu_profile.d_miss_counts); } /** * Show CPU instructions which execution was profiled, in the address order, * starting from the given address. Return next disassembly address. */ uint32_t Profile_CpuShowAddresses(uint32_t lower, uint32_t upper, FILE *out, paging_t use_paging) { int oldcols[DISASM_COLUMNS], newcols[DISASM_COLUMNS]; int show, shown, addrs, active; const char *symbol; cpu_profile_item_t *data; uint32_t idx, end, size; uaecptr nextpc, addr; data = cpu_profile.data; if (!data) { fprintf(stderr, "ERROR: no CPU profiling data available!\n"); return 0; } size = cpu_profile.size; active = cpu_profile.active; if (upper) { end = address2index(upper); if (end > size) { end = size; } } else { end = size; } show = INT_MAX; if (use_paging == PAGING_ENABLED) { show = DebugUI_GetPageLines(ConfigureParams.Debugger.nDisasmLines, 0); if (!show) { show = INT_MAX; } } /* get/change columns */ Disasm_GetColumns(oldcols); Disasm_DisableColumn(DISASM_COLUMN_HEXDUMP, oldcols, newcols); Disasm_SetColumns(newcols); fputs("# disassembly with profile data: % (, , , )\n", out); shown = 2; /* first and last printf */ addrs = nextpc = 0; idx = address2index(lower); for (; shown < show && addrs < active && idx < end; idx++) { if (!data[idx].count) { continue; } addr = index2address(idx); if (addr != nextpc && nextpc) { fprintf(out, "[...]\n"); shown++; } symbol = Symbols_GetByCpuAddress(addr, SYMTYPE_CODE); if (symbol) { fprintf(out, "%s:\n", symbol); shown++; } /* NOTE: column setup works only with 68kDisass disasm engine! */ Disasm(out, addr, &nextpc, 1); shown++; addrs++; } if (idx < end) { fprintf(stderr, "Disassembled %d (of active %d) CPU addresses.\n", addrs, active); } else { fprintf(stderr, "Disassembled last %d (of active %d) CPU addresses, wrapping...\n", addrs, active); nextpc = 0; } /* restore disassembly columns */ Disasm_SetColumns(oldcols); return nextpc; } /** * remove all disassembly columns except instruction ones. * data needed to restore columns is stored to "oldcols" */ static void leave_instruction_column(int *oldcols) { int i, newcols[DISASM_COLUMNS]; Disasm_GetColumns(oldcols); for (i = 0; i < DISASM_COLUMNS; i++) { if (i == DISASM_COLUMN_OPCODE || i == DISASM_COLUMN_OPERAND) { continue; } Disasm_DisableColumn(i, oldcols, newcols); oldcols = newcols; } Disasm_SetColumns(newcols); } /** * compare function for qsort() to sort CPU profile data by instruction cache misses. */ static int cmp_cpu_i_misses(const void *p1, const void *p2) { uint32_t count1 = cpu_profile.data[*(const uint32_t*)p1].i_misses; uint32_t count2 = cpu_profile.data[*(const uint32_t*)p2].i_misses; if (count1 > count2) { return -1; } if (count1 < count2) { return 1; } return 0; } /** * Sort CPU profile data addresses by instruction cache misses and show the results. */ void Profile_CpuShowInstrMisses(int show) { int active; int oldcols[DISASM_COLUMNS]; uint32_t *sort_arr, *end, addr, nextpc; cpu_profile_item_t *data = cpu_profile.data; float percentage; uint32_t count; if (!cpu_profile.all.i_misses) { fprintf(stderr, "No CPU instruction cache miss information available.\n"); return; } active = cpu_profile.active; sort_arr = cpu_profile.sort_arr; qsort(sort_arr, active, sizeof(*sort_arr), cmp_cpu_i_misses); leave_instruction_column(oldcols); fprintf(stderr, "addr:\t\ti-cache misses:\n"); show = (show < active ? show : active); for (end = sort_arr + show; sort_arr < end; sort_arr++) { addr = index2address(*sort_arr); count = data[*sort_arr].i_misses; percentage = 100.0*count/cpu_profile.all.i_misses; fprintf(stderr, "0x%06x\t%5.2f%%\t%d%s\t", addr, percentage, count, count == MAX_CPU_PROFILE_VALUE ? " (OVERFLOW)" : ""); Disasm(stderr, addr, &nextpc, 1); } fprintf(stderr, "%d CPU addresses listed.\n", show); Disasm_SetColumns(oldcols); } /** * compare function for qsort() to sort CPU profile data by data cache hits. */ static int cmp_cpu_d_hits(const void *p1, const void *p2) { uint32_t count1 = cpu_profile.data[*(const uint32_t*)p1].d_hits; uint32_t count2 = cpu_profile.data[*(const uint32_t*)p2].d_hits; if (count1 > count2) { return -1; } if (count1 < count2) { return 1; } return 0; } /** * Sort CPU profile data addresses by data cache hits and show the results. */ void Profile_CpuShowDataHits(int show) { int active; int oldcols[DISASM_COLUMNS]; uint32_t *sort_arr, *end, addr, nextpc; cpu_profile_item_t *data = cpu_profile.data; float percentage; uint32_t count; if (!cpu_profile.all.d_hits) { fprintf(stderr, "No CPU data cache hit information available.\n"); return; } active = cpu_profile.active; sort_arr = cpu_profile.sort_arr; qsort(sort_arr, active, sizeof(*sort_arr), cmp_cpu_d_hits); leave_instruction_column(oldcols); fprintf(stderr, "addr:\t\td-cache hits:\n"); show = (show < active ? show : active); for (end = sort_arr + show; sort_arr < end; sort_arr++) { addr = index2address(*sort_arr); count = data[*sort_arr].d_hits; percentage = 100.0*count/cpu_profile.all.d_hits; fprintf(stderr, "0x%06x\t%5.2f%%\t%d%s\t", addr, percentage, count, count == MAX_CPU_PROFILE_VALUE ? " (OVERFLOW)" : ""); Disasm(stderr, addr, &nextpc, 1); } fprintf(stderr, "%d CPU addresses listed.\n", show); Disasm_SetColumns(oldcols); } /** * compare function for qsort() to sort CPU profile data by cycles counts. */ static int cmp_cpu_cycles(const void *p1, const void *p2) { uint32_t count1 = cpu_profile.data[*(const uint32_t*)p1].cycles; uint32_t count2 = cpu_profile.data[*(const uint32_t*)p2].cycles; if (count1 > count2) { return -1; } if (count1 < count2) { return 1; } return 0; } /** * Sort CPU profile data addresses by cycle counts and show the results. */ void Profile_CpuShowCycles(int show) { int active; int oldcols[DISASM_COLUMNS]; uint32_t *sort_arr, *end, addr, nextpc; cpu_profile_item_t *data = cpu_profile.data; float percentage; uint32_t count; if (!data) { fprintf(stderr, "ERROR: no CPU profiling data available!\n"); return; } active = cpu_profile.active; sort_arr = cpu_profile.sort_arr; qsort(sort_arr, active, sizeof(*sort_arr), cmp_cpu_cycles); leave_instruction_column(oldcols); fprintf(stderr, "addr:\t\tcycles:\n"); show = (show < active ? show : active); for (end = sort_arr + show; sort_arr < end; sort_arr++) { addr = index2address(*sort_arr); count = data[*sort_arr].cycles; percentage = 100.0*count/cpu_profile.all.cycles; fprintf(stderr, "0x%06x\t%5.2f%%\t%d%s\t", addr, percentage, count, count == MAX_CPU_PROFILE_VALUE ? " (OVERFLOW)" : ""); Disasm(stderr, addr, &nextpc, 1); } fprintf(stderr, "%d CPU addresses listed.\n", show); Disasm_SetColumns(oldcols); } /** * compare function for qsort() to sort CPU profile data by descending * address access counts. */ static int cmp_cpu_count(const void *p1, const void *p2) { uint32_t count1 = cpu_profile.data[*(const uint32_t*)p1].count; uint32_t count2 = cpu_profile.data[*(const uint32_t*)p2].count; if (count1 > count2) { return -1; } if (count1 < count2) { return 1; } return 0; } /** * Sort CPU profile data addresses by call counts and show the results. * If symbols are requested and symbols are loaded, show (only) addresses * matching a symbol. */ void Profile_CpuShowCounts(int show, bool only_symbols) { cpu_profile_item_t *data = cpu_profile.data; int symbols, matched, active; int oldcols[DISASM_COLUMNS]; uint32_t *sort_arr, *end, addr, nextpc; const char *name; float percentage; uint32_t count; if (!data) { fprintf(stderr, "ERROR: no CPU profiling data available!\n"); return; } active = cpu_profile.active; show = (show < active ? show : active); sort_arr = cpu_profile.sort_arr; qsort(sort_arr, active, sizeof(*sort_arr), cmp_cpu_count); if (!only_symbols) { leave_instruction_column(oldcols); fprintf(stderr, "addr:\t\tcount:\n"); for (end = sort_arr + show; sort_arr < end; sort_arr++) { addr = index2address(*sort_arr); count = data[*sort_arr].count; percentage = 100.0*count/cpu_profile.all.count; fprintf(stderr, "0x%06x\t%5.2f%%\t%d%s\t", addr, percentage, count, count == MAX_CPU_PROFILE_VALUE ? " (OVERFLOW)" : ""); Disasm(stderr, addr, &nextpc, 1); } fprintf(stderr, "%d CPU addresses listed.\n", show); Disasm_SetColumns(oldcols); return; } symbols = Symbols_CpuCodeCount(); if (!symbols) { fprintf(stderr, "ERROR: no CPU symbols loaded!\n"); return; } matched = 0; leave_instruction_column(oldcols); fprintf(stderr, "addr: %%: count: symbol: disassembly:\n"); for (end = sort_arr + active; sort_arr < end; sort_arr++) { addr = index2address(*sort_arr); name = Symbols_GetByCpuAddress(addr, SYMTYPE_CODE); if (!name) { continue; } count = data[*sort_arr].count; percentage = 100.0*count/cpu_profile.all.count; fprintf(stderr, "0x%06x %6.2f %8d %-26s %s", addr, percentage, count, name, count == MAX_CPU_PROFILE_VALUE ? "(OVERFLOW) " : ""); Disasm(stderr, addr, &nextpc, 1); matched++; if (matched >= show || matched >= symbols) { break; } } fprintf(stderr, "%d CPU symbols listed.\n", matched); Disasm_SetColumns(oldcols); } static const char * addr2name(uint32_t addr, uint64_t *total) { uint32_t idx = address2index(addr); *total = cpu_profile.data[idx].count; return Symbols_GetByCpuAddress(addr, SYMTYPE_CODE); } /** * Output CPU callers info to given file. */ void Profile_CpuShowCallers(FILE *fp) { Profile_ShowCallers(fp, cpu_callinfo.sites, cpu_callinfo.site, addr2name); } /** * Save CPU profile information to given file. */ void Profile_CpuSave(FILE *out) { uint32_t text, end; fputs("Field names:\tExecuted instructions, Used cycles, Instruction cache misses, Data cache hits\n", out); /* (Python) regexp matching disassembly address & profiling data field * (for the profile post-processor), both for the (default) WinUAE * CPU core disassembler output: * % (, , , ) * 00e00cfe 4e75 rts == $e66218 0.16% (48753, 780396, 0, 0) * And for the external disassembler output: * $ : % (, , , ) * $e5af38 : rts 0.00% (12, 0, 12, 0) * CPU core disassembly addresses can be lower or upper case. */ fputs("Field regexp:\t^\\$?([0-9A-Fa-f]+) .*% \\(([^)]*)\\)$\n", out); /* some information for interpreting the addresses */ fprintf(out, "ST_RAM:\t\t0x%06x-0x%06x\n", 0, STRamEnd); end = TosAddress + TosSize; fprintf(out, "ROM_TOS:\t0x%06x-0x%06x\n", TosAddress, end); fprintf(out, "CARTRIDGE:\t0x%06x-0x%06x\n", CART_START, CART_END); text = DebugInfo_GetTEXT(); if (text && (text < TosAddress || text >= TTRAM_START)) { fprintf(out, "PROGRAM_TEXT:\t0x%06x-0x%06x\n", text, DebugInfo_GetTEXTEnd()); } if (TTmemory && ConfigureParams.Memory.TTRamSize_KB) { end = TTRAM_START + 1024*ConfigureParams.Memory.TTRamSize_KB; fprintf(out, "TT_RAM:\t\t0x%08x-0x%08x\n", TTRAM_START, end); } else if (end < CART_END) { end = CART_END; } Profile_CpuShowAddresses(0, end-2, out, PAGING_DISABLED); Profile_CpuShowCallers(out); } /* ------------------ CPU profile control ----------------- */ /** * Free data from last profiling run, if any */ void Profile_CpuFree(void) { Profile_FreeCallinfo(&(cpu_callinfo)); if (cpu_profile.sort_arr) { free(cpu_profile.sort_arr); cpu_profile.sort_arr = NULL; } if (cpu_profile.data) { free(cpu_profile.data); cpu_profile.data = NULL; fprintf(stderr, "Freed previous CPU profile buffers.\n"); } } /** * Initialize CPU profiling when necessary. Return true if profiling. */ bool Profile_CpuStart(void) { int size; Profile_CpuFree(); if (!cpu_profile.enabled) { return false; } /* zero everything */ memset(&cpu_profile, 0, sizeof(cpu_profile)); memset(&cpu_warnings, 0, sizeof(cpu_warnings)); cpu_warnings.multireturn = MAX_MULTI_RETURN; /* Shouldn't change within same debug session */ size = (STRamEnd + CART_SIZE + TosSize) / 2; if (TTmemory && ConfigureParams.Memory.TTRamSize_KB) { size += ConfigureParams.Memory.TTRamSize_KB * 1024/2; } /* Add one entry for catching invalid PC values */ cpu_profile.data = calloc(size + 1, sizeof(*cpu_profile.data)); if (!cpu_profile.data) { perror("ERROR, new CPU profile buffer alloc failed"); return false; } fprintf(stderr, "Allocated CPU profile buffer (%d MB).\n", (int)sizeof(*cpu_profile.data)*size/(1024*1024)); cpu_profile.size = size; Profile_AllocCallinfo(&(cpu_callinfo), Symbols_CpuCodeCount(), "CPU"); /* reset cache stats (CPU emulation doesn't do that) */ CpuInstruction.D_Cache_hit = 0; CpuInstruction.I_Cache_hit = 0; CpuInstruction.I_Cache_miss = 0; CpuInstruction.D_Cache_miss = 0; cpu_profile.prev_cycles = CyclesGlobalClockCounter; cpu_profile.prev_family = OpcodeFamily; cpu_profile.prev_pc = M68000_GetPC(); if (ConfigureParams.System.bAddressSpace24) { cpu_profile.prev_pc &= 0xffffff; } cpu_profile.loop_start = PC_UNDEFINED; cpu_profile.loop_end = PC_UNDEFINED; cpu_profile.loop_count = 0; Profile_LoopReset(); cpu_profile.disasm_addr = 0; cpu_profile.processed = false; cpu_profile.enabled = true; return cpu_profile.enabled; } /** * return true if pc could be next instruction for previous pc */ static bool is_prev_instr(uint32_t prev_pc, uint32_t pc) { /* just moved to next instruction (1-2 words)? */ if (prev_pc < pc && (pc - prev_pc) <= 10) { return true; } return false; } /** * return caller instruction type classification */ static calltype_t cpu_opcode_type(int family, uint32_t prev_pc, uint32_t pc) { switch (family) { case i_JSR: case i_BSR: return CALL_SUBROUTINE; case i_RTS: case i_RTR: case i_RTD: return CALL_SUBRETURN; case i_JMP: /* often used also for "inlined" function calls... */ case i_Bcc: /* both BRA & BCC */ case i_FBcc: case i_DBcc: case i_FDBcc: return CALL_BRANCH; case i_TRAP: case i_TRAPV: case i_TRAPcc: case i_FTRAPcc: case i_STOP: case i_ILLG: case i_CHK: case i_CHK2: case i_BKPT: return CALL_EXCEPTION; case i_RTE: return CALL_EXCRETURN; } /* just moved to next instruction? */ if (is_prev_instr(prev_pc, pc)) { return CALL_NEXT; } return CALL_UNKNOWN; } /** * Check callstack to see if return was for an earlier subroutine * call higher in the stack. * * This can happen when e.g. OS task switchers manipulate stack to * convert subroutine return (RTS) into a jump to another function. * I.e. one of the returns in callstack will be skipped. * * Returns number of frames to finish/end. */ static int returned_frames(callinfo_t *callinfo, uint32_t pc) { int frames, depth; uint32_t return_pc; depth = callinfo->depth; for (frames = 1; --depth >= 0; frames++) { return_pc = callinfo->stack[depth].ret_addr; if (pc == return_pc) { return frames; } } return 0; } /** * If call tracking is enabled (there are symbols), collect * information about subroutine and other calls, and their costs. * * Like with profile data, caller info checks need to be for previous * instruction, that's why "pc" argument for this function actually * needs to be previous PC. */ static void collect_calls(uint32_t pc, counters_t *counters) { calltype_t flag; int frames, idx, family; uint32_t prev_pc, caller_pc; family = cpu_profile.prev_family; cpu_profile.prev_family = OpcodeFamily; prev_pc = cpu_callinfo.prev_pc; cpu_callinfo.prev_pc = pc; caller_pc = PC_UNDEFINED; /* check opcode first as return frame check can be slow with deep call stacks */ flag = cpu_opcode_type(family, prev_pc, pc); if (unlikely(flag == CALL_SUBRETURN || flag == CALL_EXCRETURN)) { /* is address a return address for *any* of the previous subroutine calls? */ frames = returned_frames(&cpu_callinfo, pc); if (frames) { if (unlikely(frames > cpu_warnings.multireturn)) { fprintf(stderr, "WARNING: subroutine call returned through %d stack frames: 0x%x -> 0x%x!\n", frames, prev_pc, pc); cpu_warnings.multireturn = frames; } /* unwind callstack & update costs */ while (frames-- > 0) { caller_pc = Profile_CallEnd(&cpu_callinfo, counters); } } } else if (unlikely(pc == cpu_callinfo.return_pc)) { /* return address, but not due to return, e.g. because * there was a jsr or jump to return address. Checked * only for last return */ if (++cpu_warnings.returns <= MAX_SHOW_COUNT) { uint32_t nextpc; fprintf(stderr, "WARNING: subroutine call returned 0x%x -> 0x%x, not through RTS etc!\n", prev_pc, pc); Disasm(stderr, prev_pc, &nextpc, 1); if (cpu_warnings.returns == MAX_SHOW_COUNT) { fprintf(stderr, "Further warnings won't be shown.\n"); } } } /* address is one which we're tracking? */ idx = Symbols_GetCpuCodeIndex(pc); if (unlikely(idx >= 0)) { /* normal subroutine / exception call? */ if (likely(flag == CALL_SUBROUTINE || flag == CALL_EXCEPTION)) { if (unlikely(prev_pc == PC_UNDEFINED)) { /* if first profiled instruction * is subroutine call, it doesn't have * valid prev_pc value stored */ cpu_callinfo.return_pc = PC_UNDEFINED; if (++cpu_warnings.prevpc <= MAX_SHOW_COUNT) { fprintf(stderr, "WARNING: previous PC for tracked address 0x%d is undefined!\n", pc); if (cpu_warnings.prevpc == MAX_SHOW_COUNT) { fprintf(stderr, "Further warnings won't be shown.\n"); } } #if DEBUG skip_assert = true; DebugUI(REASON_CPU_EXCEPTION); #endif } else { /* return is to next instruction (slow!) */ cpu_callinfo.return_pc = Disasm_GetNextPC(prev_pc); } } else if (caller_pc != PC_UNDEFINED) { /* returned from function to first instruction of another symbol: * 0xf384 jsr some_function * other_symbol: * 0f3x8a some_instruction * -> change return instruction address to * address of what did the returned call. */ prev_pc = caller_pc; assert(is_prev_instr(prev_pc, pc)); flag = CALL_NEXT; } Profile_CallStart(idx, &cpu_callinfo, prev_pc, flag, pc, counters); } } /** * log last loop info, if there's suitable data for one */ static void log_last_loop(void) { unsigned len = cpu_profile.loop_end - cpu_profile.loop_start; if (cpu_profile.loop_count > 1 && (len < profile_loop.cpu_limit || !profile_loop.cpu_limit)) { fprintf(profile_loop.fp, "CPU %d 0x%06x %d %d\n", nVBLs, cpu_profile.loop_start, len, cpu_profile.loop_count); } } /** * Warning for values going out of expected range */ static uint32_t warn_too_large(const char *name, const int value, const int limit, const uint32_t prev_pc, const uint32_t pc) { if (++cpu_warnings.largevalue <= MAX_SHOW_COUNT) { uint32_t nextpc; fprintf(stderr, "WARNING: unexpected (%d > %d) %s at 0x%x:\n", value, limit - 1, name, pc); Disasm(stderr, prev_pc, &nextpc, 1); Disasm(stderr, pc, &nextpc, 1); if (cpu_warnings.largevalue == MAX_SHOW_COUNT) { fprintf(stderr, "Further warnings will not be shown.\n"); } } #if DEBUG skip_assert = true; DebugUI(REASON_CPU_EXCEPTION); #endif return limit - 1; } /** * Update CPU cycle and count statistics for PC address. * * This gets called after instruction has executed and PC * has advanced to next instruction. */ void Profile_CpuUpdate(void) { counters_t *counters = &(cpu_profile.all); uint32_t pc, prev_pc, idx, cycles; cpu_profile_item_t *prev; uint32_t i_hits, d_hits, i_misses, d_misses; prev_pc = cpu_profile.prev_pc; /* PC may have extra bits when using 24 bit addressing, they need to be masked away as * emulation itself does that too when PC value is used */ cpu_profile.prev_pc = pc = M68000_GetPC(); if (ConfigureParams.System.bAddressSpace24) { cpu_profile.prev_pc &= 0xffffff; } if (unlikely(profile_loop.fp)) { if (pc < prev_pc) { if (pc == cpu_profile.loop_start && prev_pc == cpu_profile.loop_end) { cpu_profile.loop_count++; } else { cpu_profile.loop_start = pc; cpu_profile.loop_end = prev_pc; cpu_profile.loop_count = 1; } } else { if (pc > cpu_profile.loop_end) { log_last_loop(); cpu_profile.loop_end = 0xffffffff; cpu_profile.loop_count = 0; } } } idx = address2index(prev_pc); assert(idx <= cpu_profile.size); prev = cpu_profile.data + idx; if (likely(prev->count < MAX_CPU_PROFILE_VALUE)) { prev->count++; } cycles = CyclesGlobalClockCounter - cpu_profile.prev_cycles; cpu_profile.prev_cycles = CyclesGlobalClockCounter; if (likely(prev->cycles < MAX_CPU_PROFILE_VALUE - cycles)) { prev->cycles += cycles; } else { prev->cycles = MAX_CPU_PROFILE_VALUE; } /* Cache informations are available for CPU>=68020 or for MegaSTE with 68000 in CE mode */ i_hits = CpuInstruction.I_Cache_hit; d_hits = CpuInstruction.D_Cache_hit; i_misses = CpuInstruction.I_Cache_miss; d_misses = CpuInstruction.D_Cache_miss; /* reset cache stats after reading them (for the next instruction) */ CpuInstruction.I_Cache_hit = 0; CpuInstruction.D_Cache_hit = 0; CpuInstruction.I_Cache_miss = 0; CpuInstruction.D_Cache_miss = 0; /* tracked for every address */ # if DEBUG_CACHE if (likely(prev->i_hits < MAX_CPU_PROFILE_VALUE - i_hits)) { prev->i_hits += i_hits; } else { prev->i_hits = MAX_CPU_PROFILE_VALUE; } if (likely(prev->d_misses < MAX_CPU_PROFILE_VALUE - d_misses)) { prev->d_misses += d_misses; } else { prev->d_misses = MAX_CPU_PROFILE_VALUE; } # endif if (likely(prev->i_misses < MAX_CPU_PROFILE_VALUE - i_misses)) { prev->i_misses += i_misses; } else { prev->i_misses = MAX_CPU_PROFILE_VALUE; } if (likely(prev->d_hits < MAX_CPU_PROFILE_VALUE - d_hits)) { prev->d_hits += d_hits; } else { prev->d_hits = MAX_CPU_PROFILE_VALUE; } /* tracking for histogram, check for array overflows */ if (!(i_hits || i_misses)) { cpu_profile.i_prefetched++; } if (unlikely(i_hits >= MAX_I_HITS)) { i_hits = warn_too_large("number of CPU instruction cache hits", i_hits, MAX_I_HITS, prev_pc, pc); } cpu_profile.i_hit_counts[i_hits]++; if (unlikely(i_misses >= MAX_I_MISSES)) { i_misses = warn_too_large("number of CPU instruction cache misses", i_misses, MAX_I_MISSES, prev_pc, pc); } cpu_profile.i_miss_counts[i_misses]++; if (unlikely(d_hits >= MAX_D_HITS)) { d_hits = warn_too_large("number of CPU data cache hits", d_hits, MAX_D_HITS, prev_pc, pc); } cpu_profile.d_hit_counts[d_hits]++; if (unlikely(d_misses >= MAX_D_MISSES)) { d_misses = warn_too_large("number of CPU data cache misses", d_misses, MAX_D_MISSES, prev_pc, pc); } cpu_profile.d_miss_counts[d_misses]++; if (cpu_callinfo.sites) { collect_calls(prev_pc, counters); } /* total counters are increased after caller info is processed, * otherwise cost for the instruction calling the callee * doesn't get accounted to caller (but callee). */ counters->count++; counters->cycles += cycles; counters->i_misses += i_misses; counters->d_hits += d_hits; #if DEBUG if (unlikely(OpcodeFamily == 0)) { if (++cpu_warnings.opfamily <= MAX_SHOW_COUNT) { uint32_t nextpc; fputs("WARNING: instruction opcode family is zero (=i_ILLG) for instruction:\n", stderr); Disasm(stderr, prev_pc, &nextpc, 1); if (cpu_warnings.opfamily == MAX_SHOW_COUNT) { fprintf(stderr, "Further warnings will not be shown.\n"); } } } /* catch too large (and negative) cycles for other than STOP instruction */ if (unlikely(cycles > 512 && OpcodeFamily != i_STOP)) { warn_too_large("cycles", cycles, 512, prev_pc, pc); } #endif } /** * Helper for accounting CPU profile area item. */ static void update_area_item(profile_area_t *area, uint32_t addr, cpu_profile_item_t *item) { uint32_t cycles = item->cycles; uint32_t count = item->count; if (!count) { return; } area->counters.count += count; area->counters.cycles += cycles; area->counters.i_misses += item->i_misses; area->counters.d_hits += item->d_hits; if (cycles == MAX_CPU_PROFILE_VALUE) { area->overflow = true; } if (addr < area->lowest) { area->lowest = addr; } area->highest = addr; area->active++; } /** * Helper for collecting CPU profile area statistics. */ static uint32_t update_area(profile_area_t *area, uint32_t start, uint32_t end) { cpu_profile_item_t *item; uint32_t addr; memset(area, 0, sizeof(profile_area_t)); area->lowest = end; item = &(cpu_profile.data[start]); for (addr = start; addr < end; addr++, item++) { update_area_item(area, addr, item); } return addr; } /** * Helper for initializing CPU profile area sorting indexes. */ static uint32_t* index_area(profile_area_t *area, uint32_t *sort_arr) { cpu_profile_item_t *item; uint32_t addr; item = &(cpu_profile.data[area->lowest]); for (addr = area->lowest; addr <= area->highest; addr++, item++) { if (item->count) { *sort_arr++ = addr; } } return sort_arr; } /** * Stop and process the CPU profiling data; collect stats and * prepare for more optimal sorting. */ void Profile_CpuStop(void) { uint32_t *sort_arr, next; unsigned int size, stsize; int active; if (cpu_profile.processed || !cpu_profile.enabled) { return; } log_last_loop(); if (profile_loop.fp) { fflush(profile_loop.fp); } /* user didn't change RAM or TOS size in the meanwhile? */ size = stsize = (STRamEnd + CART_SIZE + TosSize) / 2; if (TTmemory && ConfigureParams.Memory.TTRamSize_KB) { size += ConfigureParams.Memory.TTRamSize_KB * 1024/2; } assert(cpu_profile.size == size); Profile_FinalizeCalls(M68000_GetPC(), &(cpu_callinfo), &(cpu_profile.all), Symbols_GetByCpuAddress, Symbols_GetBeforeCpuAddress); /* find lowest and highest addresses executed etc */ next = update_area(&cpu_profile.ram, 0, STRamEnd/2); if (TosAddress < CART_START) { next = update_area(&cpu_profile.tos, next, (STRamEnd + TosSize)/2); next = update_area(&cpu_profile.rom, next, stsize); } else { next = update_area(&cpu_profile.rom, next, (STRamEnd + CART_SIZE)/2); next = update_area(&cpu_profile.tos, next, stsize); } next = update_area(&cpu_profile.ttram, next, size); assert(next == size); #if DEBUG if (skip_assert) { skip_assert = false; } else #endif { #if DEBUG if (cpu_profile.all.count != cpu_profile.ttram.counters.count + cpu_profile.ram.counters.count + cpu_profile.tos.counters.count + cpu_profile.rom.counters.count) { fprintf(stderr, "ERROR, instruction count mismatch:\n\t%"PRIu64" != %"PRIu64" + %"PRIu64" + %"PRIu64" + %"PRIu64"?\n", cpu_profile.all.count, cpu_profile.ttram.counters.count, cpu_profile.ram.counters.count, cpu_profile.tos.counters.count, cpu_profile.rom.counters.count); fprintf(stderr, "If there was debugger invocation from profiling before this, try with profiler DEBUG define disabled!!!\n"); } #endif assert(cpu_profile.all.count == cpu_profile.ttram.counters.count + cpu_profile.ram.counters.count + cpu_profile.tos.counters.count + cpu_profile.rom.counters.count); assert(cpu_profile.all.cycles == cpu_profile.ttram.counters.cycles + cpu_profile.ram.counters.cycles + cpu_profile.tos.counters.cycles + cpu_profile.rom.counters.cycles); assert(cpu_profile.all.i_misses == cpu_profile.ttram.counters.i_misses + cpu_profile.ram.counters.i_misses + cpu_profile.tos.counters.i_misses + cpu_profile.rom.counters.i_misses); assert(cpu_profile.all.d_hits == cpu_profile.ttram.counters.d_hits + cpu_profile.ram.counters.d_hits + cpu_profile.tos.counters.d_hits + cpu_profile.rom.counters.d_hits); } /* allocate address array for sorting */ active = cpu_profile.ttram.active + cpu_profile.ram.active + cpu_profile.rom.active + cpu_profile.tos.active; sort_arr = calloc(active, sizeof(*sort_arr)); if (!sort_arr) { perror("ERROR: allocating CPU profile address data"); free(cpu_profile.data); cpu_profile.data = NULL; return; } fprintf(stderr, "Allocated CPU profile address buffer (%d KB).\n", (int)sizeof(*sort_arr)*(active+512)/1024); cpu_profile.sort_arr = sort_arr; cpu_profile.active = active; /* and fill addresses for used instructions... */ sort_arr = index_area(&cpu_profile.ram, sort_arr); sort_arr = index_area(&cpu_profile.tos, sort_arr); sort_arr = index_area(&cpu_profile.rom, sort_arr); sort_arr = index_area(&cpu_profile.ttram, sort_arr); assert(sort_arr == cpu_profile.sort_arr + cpu_profile.active); //printf("%d/%d/%d\n", area->active, sort_arr-cpu_profile.sort_arr, active); Profile_CpuShowStats(); cpu_profile.processed = true; } /** * Get pointers to CPU profile enabling and disasm address variables * for updating them (in parser). */ void Profile_CpuGetPointers(bool **enabled, uint32_t **disasm_addr) { *disasm_addr = &cpu_profile.disasm_addr; *enabled = &cpu_profile.enabled; } /** * Get callinfo & symbol search pointers for stack walking. */ void Profile_CpuGetCallinfo(callinfo_t **callinfo, const char* (**get_caller)(uint32_t*), const char* (**get_symbol)(uint32_t, symtype_t)) { *callinfo = &(cpu_callinfo); *get_caller = Symbols_GetBeforeCpuAddress; *get_symbol = Symbols_GetByCpuAddress; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/profiledsp.c000066400000000000000000000526561504763705000247470ustar00rootroot00000000000000/* * Hatari - profiledsp.c * * Copyright (C) 2010-2019 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * profiledsp.c - functions for profiling DSP and showing the results. */ const char Profiledsp_fileid[] = "Hatari profiledsp.c"; #include #include #include #include #include "main.h" #include "configuration.h" #include "clocks_timings.h" #include "dsp.h" #include "symbols.h" #include "profile.h" #include "profile_priv.h" #include "debug_priv.h" /* for VBL info */ #include "video.h" static callinfo_t dsp_callinfo; #define DSP_PROFILE_ARR_SIZE 0x10000 #define MAX_DSP_PROFILE_VALUE 0xFFFFFFFFFFFFFFFFLL typedef struct { uint64_t count; /* how many times this address is used */ uint64_t cycles; /* how many DSP cycles was taken at this address */ uint16_t min_cycle; uint16_t max_cycle; } dsp_profile_item_t; static struct { dsp_profile_item_t *data; /* profile data */ profile_area_t ram; /* statistics for whole memory */ uint16_t *sort_arr; /* data indexes used for sorting */ uint16_t prev_pc; /* previous PC for which the cycles are for */ uint16_t loop_start; /* address of last loop start */ uint16_t loop_end; /* address of last loop end */ uint32_t loop_count; /* how many times it was looped */ uint32_t disasm_addr; /* 'dspaddresses' command start address */ bool processed; /* true when data is already processed */ bool enabled; /* true when profiling enabled */ } dsp_profile; /* ------------------ DSP profile results ----------------- */ /** * Get DSP cycles, count and count percentage for given address. * Return true if data was available and non-zero, false otherwise. */ bool Profile_DspAddressData(uint16_t addr, float *percentage, uint64_t *count, uint64_t *cycles, uint16_t *cycle_diff) { dsp_profile_item_t *item; if (!dsp_profile.data) { return false; } item = dsp_profile.data + addr; *cycles = item->cycles; *count = item->count; if (item->max_cycle) { *cycle_diff = item->max_cycle - item->min_cycle; } else { *cycle_diff = 0; } if (dsp_profile.ram.counters.count) { *percentage = 100.0*(*count)/dsp_profile.ram.counters.count; } else { *percentage = 0.0; } return (*count > 0); } /** * show DSP specific profile statistics. */ void Profile_DspShowStats(void) { profile_area_t *area = &dsp_profile.ram; fprintf(stderr, "DSP profile statistics (0x0-0xFFFF):\n"); if (!area->active) { fprintf(stderr, "- no activity\n"); return; } fprintf(stderr, "- active address range:\n 0x%04x-0x%04x\n", area->lowest, area->highest); fprintf(stderr, "- active instruction addresses:\n %d\n", area->active); fprintf(stderr, "- executed instructions:\n %"PRIu64"\n", area->counters.count); /* indicates either instruction(s) that address different memory areas * (they can have different access costs), or more significantly, * DSP code that has changed during profiling. */ fprintf(stderr, "- sum of per instruction cycle changes\n" " (can indicate code change during profiling):\n %"PRIu64"\n", area->counters.cycles_diffs); fprintf(stderr, "- used cycles:\n %"PRIu64"\n", area->counters.cycles); if (area->overflow) { fprintf(stderr, " *** COUNTERS OVERFLOW! ***\n"); } fprintf(stderr, "\n= %.5fs\n", (double)(area->counters.cycles) / MachineClocks.DSP_Freq); } /** * Show DSP instructions which execution was profiled, in the address order, * starting from the given address. Return next disassembly address. */ uint16_t Profile_DspShowAddresses(uint32_t addr, uint32_t upper, FILE *out, paging_t use_paging) { int show, shown, addrs, active; dsp_profile_item_t *data; uint16_t nextpc; uint32_t end; const char *symbol; data = dsp_profile.data; if (!data) { fprintf(stderr, "ERROR: no DSP profiling data available!\n"); return 0; } end = DSP_PROFILE_ARR_SIZE; active = dsp_profile.ram.active; if (upper) { if (upper < end) { end = upper; } } show = INT_MAX; if (use_paging == PAGING_ENABLED) { show = DebugUI_GetPageLines(ConfigureParams.Debugger.nDisasmLines, 0); if (!show) { show = INT_MAX; } } fputs("# disassembly with profile data: % (, , )\n", out); shown = 2; /* first and last printf */ addrs = nextpc = 0; for (; shown < show && addrs < active && addr < end; addr++) { if (!data[addr].count) { continue; } if (addr != nextpc && nextpc) { fputs("[...]\n", out); shown++; } symbol = Symbols_GetByDspAddress(addr, SYMTYPE_CODE); if (symbol) { fprintf(out, "%s:\n", symbol); shown++; } nextpc = DSP_DisasmAddress(out, addr, addr); addrs++; shown++; } /* use stderr for debugger feedback like elsewhere */ if (addr < end) { fprintf(stderr, "Disassembled %d (of active %d) DSP addresses.\n", addrs, active); } else { fprintf(stderr, "Disassembled last %d (of active %d) DSP addresses, wrapping...\n", addrs, active); nextpc = 0; } return nextpc; } /** * compare function for qsort() to sort DSP profile data by descdending * address cycles counts. */ static int cmp_dsp_cycles(const void *p1, const void *p2) { uint64_t count1 = dsp_profile.data[*(const uint16_t*)p1].cycles; uint64_t count2 = dsp_profile.data[*(const uint16_t*)p2].cycles; if (count1 > count2) { return -1; } if (count1 < count2) { return 1; } return 0; } /** * Sort DSP profile data addresses by cycle counts and show the results. */ void Profile_DspShowCycles(int show) { int active; uint16_t *sort_arr, *end, addr; dsp_profile_item_t *data = dsp_profile.data; float percentage; uint64_t count; if (!data) { fprintf(stderr, "ERROR: no DSP profiling data available!\n"); return; } active = dsp_profile.ram.active; sort_arr = dsp_profile.sort_arr; qsort(sort_arr, active, sizeof(*sort_arr), cmp_dsp_cycles); fprintf(stderr, "addr:\tcycles:\n"); show = (show < active ? show : active); for (end = sort_arr + show; sort_arr < end; sort_arr++) { addr = *sort_arr; count = data[addr].cycles; percentage = 100.0*count/dsp_profile.ram.counters.cycles; fprintf(stderr, "0x%04x\t%5.2f%%\t%"PRIu64"%s\n", addr, percentage, count, count == MAX_DSP_PROFILE_VALUE ? " (OVERFLOW)" : ""); } fprintf(stderr, "%d DSP addresses listed.\n", show); } /** * compare function for qsort() to sort DSP profile data by descdending * address access counts. */ static int cmp_dsp_count(const void *p1, const void *p2) { uint64_t count1 = dsp_profile.data[*(const uint16_t*)p1].count; uint64_t count2 = dsp_profile.data[*(const uint16_t*)p2].count; if (count1 > count2) { return -1; } if (count1 < count2) { return 1; } return 0; } /** * Sort DSP profile data addresses by call counts and show the results. * If symbols are requested and symbols are loaded, show (only) addresses * matching a symbol. */ void Profile_DspShowCounts(int show, bool only_symbols) { dsp_profile_item_t *data = dsp_profile.data; int symbols, matched, active; uint16_t *sort_arr, *end, addr; const char *name; float percentage; uint64_t count; if (!data) { fprintf(stderr, "ERROR: no DSP profiling data available!\n"); return; } active = dsp_profile.ram.active; show = (show < active ? show : active); sort_arr = dsp_profile.sort_arr; qsort(sort_arr, active, sizeof(*sort_arr), cmp_dsp_count); if (!only_symbols) { fprintf(stderr, "addr:\tcount:\n"); for (end = sort_arr + show; sort_arr < end; sort_arr++) { addr = *sort_arr; count = data[addr].count; percentage = 100.0*count/dsp_profile.ram.counters.count; fprintf(stderr, "0x%04x\t%5.2f%%\t%"PRIu64"%s\n", addr, percentage, count, count == MAX_DSP_PROFILE_VALUE ? " (OVERFLOW)" : ""); } fprintf(stderr, "%d DSP addresses listed.\n", show); return; } symbols = Symbols_DspCodeCount(); if (!symbols) { fprintf(stderr, "ERROR: no DSP symbols loaded!\n"); return; } matched = 0; fprintf(stderr, "addr:\tcount:\t\tsymbol:\n"); for (end = sort_arr + active; sort_arr < end; sort_arr++) { addr = *sort_arr; name = Symbols_GetByDspAddress(addr, SYMTYPE_CODE); if (!name) { continue; } count = data[addr].count; percentage = 100.0*count/dsp_profile.ram.counters.count; fprintf(stderr, "0x%04x\t%.2f%%\t%"PRIu64"\t%s%s\n", addr, percentage, count, name, count == MAX_DSP_PROFILE_VALUE ? " (OVERFLOW)" : ""); matched++; if (matched >= show || matched >= symbols) { break; } } fprintf(stderr, "%d DSP symbols listed.\n", matched); } static const char * addr2name(uint32_t addr, uint64_t *total) { *total = dsp_profile.data[addr].count; return Symbols_GetByDspAddress(addr, SYMTYPE_CODE); } /** * Output DSP callers info to given file. */ void Profile_DspShowCallers(FILE *fp) { Profile_ShowCallers(fp, dsp_callinfo.sites, dsp_callinfo.site, addr2name); } /** * Save DSP profile information to given file. */ void Profile_DspSave(FILE *out) { /* Comma separated descriptions for the profile disassembly data fields. * Instructions and cycles need to be first two fields! */ fputs("Field names:\tExecuted instructions, Used cycles, Largest cycle differences (= code changes during profiling)\n", out); /* (Python) pegexp that matches address and all describled fields from disassembly: * :
() % (, ) * p:0202 0aa980 000200 (07 cyc) jclr #0,x:$ffe9,p:$0200 0.00% (6, 42) */ fputs("Field regexp:\t^p:([0-9a-f]+) .*% \\((.*)\\)$\n", out); Profile_DspShowAddresses(0, DSP_PROFILE_ARR_SIZE, out, PAGING_DISABLED); Profile_DspShowCallers(out); } /* ------------------ DSP profile control ----------------- */ /** * Free data from last profiling run, if any */ void Profile_DspFree(void) { Profile_FreeCallinfo(&(dsp_callinfo)); if (dsp_profile.sort_arr) { free(dsp_profile.sort_arr); dsp_profile.sort_arr = NULL; } if (dsp_profile.data) { free(dsp_profile.data); dsp_profile.data = NULL; fprintf(stderr, "Freed previous DSP profile buffers.\n"); } } /** * Initialize DSP profiling when necessary. Return true if profiling. */ bool Profile_DspStart(void) { dsp_profile_item_t *item; int i; Profile_DspFree(); if (!dsp_profile.enabled) { return false; } /* zero everything */ memset(&dsp_profile, 0, sizeof(dsp_profile)); dsp_profile.data = calloc(DSP_PROFILE_ARR_SIZE, sizeof(*dsp_profile.data)); if (!dsp_profile.data) { perror("ERROR, new DSP profile buffer alloc failed"); return false; } fprintf(stderr, "Allocated DSP profile buffer (%d KB).\n", (int)sizeof(*dsp_profile.data)*DSP_PROFILE_ARR_SIZE/1024); Profile_AllocCallinfo(&(dsp_callinfo), Symbols_DspCodeCount(), "DSP"); item = dsp_profile.data; for (i = 0; i < DSP_PROFILE_ARR_SIZE; i++, item++) { item->min_cycle = 0xFFFF; } dsp_profile.prev_pc = DSP_GetPC(); dsp_profile.loop_start = 0xFFFF; dsp_profile.loop_end = 0xFFFF; dsp_profile.loop_count = 0; Profile_LoopReset(); dsp_profile.disasm_addr = 0; dsp_profile.processed = false; dsp_profile.enabled = true; return dsp_profile.enabled; } /* return true if pc is next instruction for previous pc */ static bool is_prev_instr(uint16_t prev_pc, uint16_t pc) { /* just moved to next instruction (1-2 words)? */ if (prev_pc < pc && (pc - prev_pc) <= 4) { return true; } return false; } /* return branch type based on caller instruction type */ static calltype_t dsp_opcode_type(uint16_t prev_pc, uint16_t pc) { const char *dummy; uint32_t opcode; /* 24-bit instruction opcode */ opcode = DSP_ReadMemory(prev_pc, 'P', &dummy) & 0xFFFFFF; /* subroutine returns */ if (opcode == 0xC) { /* (just) RTS */ return CALL_SUBRETURN; } /* unconditional subroutine calls */ if ((opcode & 0xFFF000) == 0xD0000 || /* JSR 00001101 0000aaaa aaaaaaaa */ (opcode & 0xFFC0FF) == 0xBC080) { /* JSR 00001011 11MMMRRR 10000000 */ return CALL_SUBROUTINE; } /* conditional subroutine calls */ if ((opcode & 0xFF0000) == 0xF0000 || /* JSCC 00001111 CCCCaaaa aaaaaaaa */ (opcode & 0xFFC0F0) == 0xBC0A0 || /* JSCC 00001011 11MMMRRR 1010CCCC */ (opcode & 0xFFC0A0) == 0xB4080 || /* JSCLR 00001011 01MMMRRR 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xB0080 || /* JSCLR 00001011 00aaaaaa 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xB8080 || /* JSCLR 00001011 10pppppp 1S0bbbbb */ (opcode & 0xFFC0E0) == 0xBC000 || /* JSCLR 00001011 11DDDDDD 000bbbbb */ (opcode & 0xFFC0A0) == 0xB40A0 || /* JSSET 00001011 01MMMRRR 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xB00A0 || /* JSSET 00001011 00aaaaaa 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xB80A0 || /* JSSET 00001011 10pppppp 1S1bbbbb */ (opcode & 0xFFC0E0) == 0xBC020) { /* JSSET 00001011 11DDDDDD 001bbbbb */ /* hopefully fairly safe heuristic: * if previously executed instruction * was one before current one, no * subroutine call was made to next * instruction, the condition just * wasn't met. */ if (is_prev_instr(prev_pc, pc)) { return CALL_NEXT; } return CALL_SUBROUTINE; } /* exception handler returns */ if (opcode == 0x4) { /* (just) RTI */ return CALL_EXCRETURN; } /* Besides CALL_UNKNOWN, rest isn't used by subroutine call * cost collection. However, it's useful info when debugging * code or reading full callgraphs (because optimized code uses * also jumps/branches for subroutine calls). */ /* TODO: exception invocation. * Could be detected by PC going through low interrupt vector addresses, * but fast-calls using JSR/RTS would need separate handling. */ if (0) { /* TODO */ return CALL_EXCEPTION; } /* branches */ if ((opcode & 0xFFF000) == 0xC0000 || /* JMP 00001100 0000aaaa aaaaaaaa */ (opcode & 0xFFC0FF) == 0xAC080 || /* JMP 00001010 11MMMRRR 10000000 */ (opcode & 0xFF0000) == 0xE0000 || /* JCC 00001110 CCCCaaaa aaaaaaaa */ (opcode & 0xFFC0F0) == 0xAC0A0 || /* JCC 00001010 11MMMRRR 1010CCCC */ (opcode & 0xFFC0A0) == 0xA8080 || /* JCLR 00001010 10pppppp 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xA4080 || /* JCLR 00001010 01MMMRRR 1S0bbbbb */ (opcode & 0xFFC0A0) == 0xA0080 || /* JCLR 00001010 00aaaaaa 1S0bbbbb */ (opcode & 0xFFC0E0) == 0xAC000 || /* JCLR 00001010 11dddddd 000bbbbb */ (opcode & 0xFFC0A0) == 0xA80A0 || /* JSET 00001010 10pppppp 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xA40A0 || /* JSET 00001010 01MMMRRR 1S1bbbbb */ (opcode & 0xFFC0A0) == 0xA00A0 || /* JSET 00001010 00aaaaaa 1S1bbbbb */ (opcode & 0xFFC0E0) == 0xAC020 || /* JSET 00001010 11dddddd 001bbbbb */ (opcode & 0xFF00F0) == 0x600A0 || /* REP 00000110 iiiiiiii 1010hhhh */ (opcode & 0xFFC0FF) == 0x6C020 || /* REP 00000110 11dddddd 00100000 */ (opcode & 0xFFC0BF) == 0x64020 || /* REP 00000110 01MMMRRR 0s100000 */ (opcode & 0xFFC0BF) == 0x60020 || /* REP 00000110 00aaaaaa 0s100000 */ (opcode & 0xFF00F0) == 0x60080 || /* DO/ENDO 00000110 iiiiiiii 1000hhhh */ (opcode & 0xFFC0FF) == 0x6C000 || /* DO/ENDO 00000110 11DDDDDD 00000000 */ (opcode & 0xFFC0BF) == 0x64000 || /* DO/ENDO 00000110 01MMMRRR 0S000000 */ (opcode & 0xFFC0BF) == 0x60000) { /* DO/ENDO 00000110 00aaaaaa 0S000000 */ return CALL_BRANCH; } if (is_prev_instr(prev_pc, pc)) { return CALL_NEXT; } return CALL_UNKNOWN; } /** * If call tracking is enabled (there are symbols), collect * information about subroutine and other calls, and their costs. * * Like with profile data, caller info checks need to be for previous * instruction, that's why "pc" argument for this function actually * needs to be previous PC. */ static void collect_calls(uint16_t pc, counters_t *counters) { calltype_t flag; uint16_t prev_pc; uint32_t caller_pc; int idx; prev_pc = dsp_callinfo.prev_pc; dsp_callinfo.prev_pc = pc; caller_pc = PC_UNDEFINED; /* address is return address for last subroutine call? */ if (unlikely(pc == dsp_callinfo.return_pc) && likely(dsp_callinfo.depth)) { flag = dsp_opcode_type(prev_pc, pc); /* return address is entered either by subroutine return, * or by returning from exception that interrupted * the instruction at return address. */ if (likely(flag == CALL_SUBRETURN || flag == CALL_EXCRETURN)) { caller_pc = Profile_CallEnd(&dsp_callinfo, counters); } } /* address is one which we're tracking? */ idx = Symbols_GetDspCodeIndex(pc); if (unlikely(idx >= 0)) { flag = dsp_opcode_type(prev_pc, pc); if (likely(flag == CALL_SUBROUTINE || flag == CALL_EXCEPTION)) { dsp_callinfo.return_pc = DSP_GetNextPC(prev_pc); /* slow! */ } else if (caller_pc != PC_UNDEFINED) { /* returned from function, change return * instruction address to address of * what did the returned call. */ prev_pc = caller_pc; assert(is_prev_instr(prev_pc, pc)); flag = CALL_NEXT; } Profile_CallStart(idx, &dsp_callinfo, prev_pc, flag, pc, counters); } } /** * log last loop info, if there's suitable data for one */ static void log_last_loop(void) { unsigned len = dsp_profile.loop_end - dsp_profile.loop_start; if (dsp_profile.loop_count > 1 && (len < profile_loop.dsp_limit || !profile_loop.dsp_limit)) { fprintf(profile_loop.fp, "DSP %d 0x%04x %d %d\n", nVBLs, dsp_profile.loop_start, len, dsp_profile.loop_count); fflush(profile_loop.fp); } } /** * Update DSP cycle and count statistics for PC address. * * This is called after instruction is executed and PC points * to next instruction i.e. info is for previous PC address. */ void Profile_DspUpdate(void) { dsp_profile_item_t *prev; uint16_t pc, prev_pc, cycles; counters_t *counters; prev_pc = dsp_profile.prev_pc; dsp_profile.prev_pc = pc = DSP_GetPC(); if (unlikely(profile_loop.fp)) { if (pc < prev_pc) { if (pc == dsp_profile.loop_start && prev_pc == dsp_profile.loop_end) { dsp_profile.loop_count++; } else { dsp_profile.loop_start = pc; dsp_profile.loop_end = prev_pc; dsp_profile.loop_count = 1; } } else { if (pc > dsp_profile.loop_end) { log_last_loop(); dsp_profile.loop_end = 0xFFFF; dsp_profile.loop_count = 0; } } } prev = dsp_profile.data + prev_pc; if (likely(prev->count < MAX_DSP_PROFILE_VALUE)) { prev->count++; } cycles = DSP_GetInstrCycles(); if (likely(prev->cycles < MAX_DSP_PROFILE_VALUE - cycles)) { prev->cycles += cycles; } else { prev->cycles = MAX_DSP_PROFILE_VALUE; } if (unlikely(cycles < prev->min_cycle)) { prev->min_cycle = cycles; } if (unlikely(cycles > prev->max_cycle)) { prev->max_cycle = cycles; } counters = &(dsp_profile.ram.counters); if (dsp_callinfo.sites) { collect_calls(prev_pc, counters); } /* counters are increased after caller info is processed, * otherwise cost for the instruction calling the callee * doesn't get accounted to caller (but callee). */ counters->cycles += cycles; counters->count++; } /** * Helper for collecting DSP profile area statistics. */ static void update_area_item(profile_area_t *area, uint16_t addr, dsp_profile_item_t *item) { uint64_t cycles = item->cycles; uint64_t count = item->count; uint16_t diff; if (!count) { return; } if (cycles == MAX_DSP_PROFILE_VALUE) { area->overflow = true; } if (item->max_cycle) { diff = item->max_cycle - item->min_cycle; } else { diff = 0; } area->counters.count += count; area->counters.cycles += cycles; area->counters.cycles_diffs += diff; if (addr < area->lowest) { area->lowest = addr; } area->highest = addr; area->active++; } /** * Stop and process the DSP profiling data; collect stats and * prepare for more optimal sorting. */ void Profile_DspStop(void) { dsp_profile_item_t *item; profile_area_t *area; uint16_t *sort_arr; uint32_t addr; if (dsp_profile.processed || !dsp_profile.enabled) { return; } log_last_loop(); if (profile_loop.fp) { fflush(profile_loop.fp); } Profile_FinalizeCalls(DSP_GetPC(), &(dsp_callinfo), &(dsp_profile.ram.counters), Symbols_GetByDspAddress, Symbols_GetBeforeDspAddress); /* find lowest and highest addresses executed */ area = &dsp_profile.ram; memset(area, 0, sizeof(profile_area_t)); area->lowest = DSP_PROFILE_ARR_SIZE; item = dsp_profile.data; for (addr = 0; addr < DSP_PROFILE_ARR_SIZE; addr++, item++) { update_area_item(area, addr, item); } /* allocate address array for sorting */ sort_arr = calloc(dsp_profile.ram.active, sizeof(*sort_arr)); if (!sort_arr) { perror("ERROR: allocating DSP profile address data"); free(dsp_profile.data); dsp_profile.data = NULL; return; } fprintf(stderr, "Allocated DSP profile address buffer (%d KB).\n", (int)sizeof(*sort_arr)*(dsp_profile.ram.active+512)/1024); dsp_profile.sort_arr = sort_arr; /* ...and fill addresses for used instructions... */ area = &dsp_profile.ram; item = &(dsp_profile.data[area->lowest]); for (addr = area->lowest; addr <= area->highest; addr++, item++) { if (item->count) { *sort_arr++ = addr; } } //printf("%d/%d/%d\n", area->active, sort_arr-dsp_profile.sort_arr, active); Profile_DspShowStats(); dsp_profile.processed = true; } /** * Get pointers to DSP profile enabling and disasm address variables * for updating them (in parser). */ void Profile_DspGetPointers(bool **enabled, uint32_t **disasm_addr) { *disasm_addr = &dsp_profile.disasm_addr; *enabled = &dsp_profile.enabled; } /** * Get callinfo & symbol search pointers for stack walking. */ void Profile_DspGetCallinfo(callinfo_t **callinfo, const char* (**get_caller)(uint32_t*), const char* (**get_symbol)(uint32_t, symtype_t)) { *callinfo = &(dsp_callinfo); *get_caller = Symbols_GetBeforeDspAddress; *get_symbol = Symbols_GetByDspAddress; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/symbols-common.c000066400000000000000000001317761504763705000255570ustar00rootroot00000000000000/* * Hatari - symbols-common.c * * Copyright (C) 2010-2025 by Eero Tamminen * Copyright (C) 2017,2021,2023 by Thorsten Otto * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * symbols-common.c - Hatari debugger symbol/address handling; parsing, * sorting, matching, TAB completion support etc. * * This code is shared between the internal debug "symbols" command * and the standalone "gst2asciii" tool. */ #include "symbols.h" typedef struct { int symbols; /* initial symbol count */ int namecount; /* final symbol count */ int codecount; /* TEXT/WEAK symbols address count */ int datacount; /* DATA/BSS symbol address count */ symbol_t *addresses; /* all address items sorted by address */ symbol_t *names; /* all items sorted by symbol name */ char *strtab; /* from a.out only */ char *debug_strtab; /* from pure-c debug information only */ } symbol_list_t; typedef struct { uint32_t offset; uint32_t end; } prg_section_t; typedef struct { /* shared by debugger & gst2ascii */ symtype_t notypes; bool no_files; bool no_gccint; bool no_local; bool no_dups; /* gst2ascii specific options */ bool sort_name; } symbol_opts_t; typedef struct { int debug; /* debug symbols */ int files; /* object file names */ int gccint; /* GCC internal symbols */ int invalid; /* invalid symbol types for addresses */ int locals; /* unnamed / local symbols */ int notypes; /* explicitly disabled symbol types */ int undefined;/* undefined symbols */ } ignore_counts_t; /* Magic used to denote different symbol table formats */ #define SYMBOL_FORMAT_GNU 0x474E555f /* "GNU_" */ #define SYMBOL_FORMAT_MINT 0x4D694E54 /* "MiNT" */ #define SYMBOL_FORMAT_ELF 0x454c4600 /* "ELF" */ #define SYMBOL_FORMAT_DRI 0x0 /* Magic identifying Atari programs */ #define ATARI_PROGRAM_MAGIC 0x601A /* ------- heuristic helpers for name comparisons ------- */ /** * return true if given symbol name is (anonymous/numbered) local one */ static bool is_local_symbol(const char *name) { return (name[0] == '.' && name[1] == 'L'); } /** * return true if given symbol name is object/library/file name */ static bool is_file_name(const char *name) { int len = strlen(name); /* object (.a or .o) file name? */ if (len > 2 && ((name[len-2] == '.' && (name[len-1] == 'a' || name[len-1] == 'o')))) { return true; } /* some other file name? */ const char *slash = strchr(name, '/'); /* not just overloaded '/' operator? */ if (slash && slash[1] != '(') { return true; } return false; } /** * Return true if symbol name matches internal GCC symbol name */ static bool is_gcc_internal(const char *name) { static const char *gcc_sym[] = { "___gnu_compiled_c", "gcc2_compiled." }; int i; /* useless symbols GCC (v2) seems to add to every object? */ for (i = 0; i < ARRAY_SIZE(gcc_sym); i++) { if (strcmp(name, gcc_sym[i]) == 0) { return true; } } return false; } /** * Return true if symbol name seems to be C/C++ one, * i.e. is unlikely to be assembly one */ static bool is_cpp_symbol(const char *name) { /* normally C symbols start with underscore */ if (name[0] == '_') { return true; } /* C++ method signatures can include '::' or spaces */ if (strchr(name, ' ') || strchr(name, ':')) { return true; } return false; } /* ------------------ symbol comparisons ------------------ */ /** * compare function for qsort(), to sort symbols by their * type, address, and finally name. * * Code symbols are sorted first, so that later phase can * split symbol table to separate code and data symbol lists. * * For symbols with same address, heuristics are used to sort * most useful name first, so that later phase can filter * the following, less useful names, out for that address. */ static int symbols_by_address(const void *s1, const void *s2) { const symbol_t *sym1 = (const symbol_t*)s1; const symbol_t *sym2 = (const symbol_t*)s2; /* separate code type addresses from others */ if ((sym1->type & SYMTYPE_CODE) && !(sym2->type & SYMTYPE_CODE)) { return -1; } if (!(sym1->type & SYMTYPE_CODE) && (sym2->type & SYMTYPE_CODE)) { return 1; } /* then sort by address */ if (sym1->address < sym2->address) { return -1; } if (sym1->address > sym2->address) { return 1; } /* and by name when addresses are equal */ const char *name1 = sym1->name; const char *name2 = sym2->name; /* first check for less desirable symbol names, * from most useless, to somewhat useful */ bool (*sym_check[])(const char *) = { is_gcc_internal, is_local_symbol, is_file_name, }; int i; for (i = 0; i < ARRAY_SIZE(sym_check); i++) { bool unwanted1 = sym_check[i](name1); bool unwanted2 = sym_check[i](name2); if (!unwanted1 && unwanted2) { return -1; } if (unwanted1 && !unwanted2) { return 1; } } /* => both symbol names look useful */ bool is_cpp1 = is_cpp_symbol(name1); bool is_cpp2 = is_cpp_symbol(name2); int len1 = strlen(name1); int len2 = strlen(name2); /* prefer shorter names for C/C++ symbols, as * this often avoid '___' C-function prefixes, * and C++ symbols can be *very* long */ if (is_cpp1 || is_cpp2) { return len1 - len2; } /* otherwise prefer longer symbols (e.g. ASM) */ return len2 - len1; } /** * compare function for qsort() to sort according to * symbol name & address */ static int symbols_by_name(const void *s1, const void *s2) { const symbol_t *sym1 = (const symbol_t*)s1; const symbol_t *sym2 = (const symbol_t*)s2; int ret; /* first by name */ ret = strcmp(sym1->name, sym2->name); if (ret) { return ret; } /* then by address */ return (sym1->address - sym2->address); } /** * Remove duplicate addresses from name list symbols, and trim its * allocation to remaining symbols. * * NOTE: symbols list *must* be *address-sorted* when this is called, * with the preferred symbol name being first, so this needs just to * remove symbols with duplicate addresses that follow it! * * Return number of removed address duplicates. */ static int symbols_trim_names(symbol_list_t* list) { symbol_t *sym = list->names; int i, next, count, skip, dups = 0; count = list->namecount; for (i = 0; i < count - 1; i++) { if (sym[i].type == SYMTYPE_ABS) { /* value, not an address */ continue; } /* count duplicates */ for (next = i+1; next < count; next++) { if (sym[i].address != sym[next].address || sym[next].type == SYMTYPE_ABS) { break; } /* free this duplicate's name */ if (sym[next].name_allocated) { free(sym[next].name); } } if (next == i+1) { continue; } /* drop counted duplicates */ memmove(sym+i+1, sym+next, (count-next) * sizeof(symbol_t)); skip = next - i - 1; count -= skip; dups += skip; } if (dups || list->namecount < list->symbols) { list->names = realloc(list->names, count * sizeof(symbol_t)); assert(list->names); list->namecount = count; } return dups; } /** * Check for duplicate addresses in address-sorted symbol list * (called separately for code & data symbol parts) * Return number of duplicates */ static int symbols_check_addresses(const symbol_t *syms, int count) { int i, j, total = 0; for (i = 0; i < (count - 1); i++) { /* absolute symbols have values, not addresses */ if (syms[i].type == SYMTYPE_ABS) { continue; } bool has_dup = false; for (j = i + 1; j < count && syms[i].address == syms[j].address; j++) { if (syms[j].type == SYMTYPE_ABS) { continue; } if (!total) { fprintf(stderr, "WARNING, following symbols have same address:\n"); } if (!has_dup) { fprintf(stderr, "- 0x%x: '%s'", syms[i].address, syms[i].name); has_dup = true; } fprintf(stderr, ", '%s'", syms[j].name); total++; i = j; } if (has_dup) { fprintf(stderr, "\n"); } } return total; } /** * Check for duplicate names in name-sorted symbol list * Return number of duplicates */ static int symbols_check_names(const symbol_t *syms, int count) { bool has_title = false; int i, j, dtotal = 0; for (i = 0; i < (count - 1); i++) { int dcount = 1; for (j = i + 1; j < count && strcmp(syms[i].name, syms[j].name) == 0; j++) { dtotal++; dcount++; i = j; } if (dcount > 1) { if (!has_title) { fprintf(stderr, "WARNING, following symbols have multiple addresses:\n"); has_title = true; } fprintf(stderr, "- %s: %d\n", syms[i].name, dcount); } } return dtotal; } /* ----------------- symbol list alloc / free ------------------ */ /** * Allocate zeroed symbol list & names for given number of items. * Return allocated list or NULL on failure. */ static symbol_list_t* symbol_list_alloc(int symbols) { symbol_list_t *list; if (!symbols) { return NULL; } list = calloc(1, sizeof(symbol_list_t)); if (list) { list->names = calloc(symbols, sizeof(symbol_t)); if (!list->names) { free(list); list = NULL; } } return list; } /** * Free symbol list & names. */ static void symbol_list_free(symbol_list_t *list) { int i; if (!list) { return; } assert(list->namecount); for (i = 0; i < list->namecount; i++) { if (list->names[i].name_allocated) { free(list->names[i].name); } } free(list->strtab); list->strtab = NULL; free(list->debug_strtab); list->debug_strtab = NULL; free(list->addresses); free(list->names); /* catch use of freed list */ list->addresses = NULL; list->codecount = 0; list->datacount = 0; list->names = NULL; list->namecount = 0; free(list); } /** * Return symbol type identifier char */ static char symbol_char(int type) { switch (type) { case SYMTYPE_TEXT: return 'T'; case SYMTYPE_WEAK: return 'W'; case SYMTYPE_DATA: return 'D'; case SYMTYPE_BSS: return 'B'; case SYMTYPE_ABS: return 'A'; default: return '?'; } } /* -------- Pure-C debug information handling --------- */ static uint32_t get_be32(const uint8_t *p) { const uint32_t *p32 = (const uint32_t *)p; return be_swap32(*p32); } struct pdb_h { uint32_t magic; uint32_t size_fileinfos; uint32_t size_lineinfo; uint32_t size_varinfo; uint32_t size_unknown; uint32_t size_typeinfo; uint32_t size_structinfo; uint32_t size_stringtable; }; #define SIZEOF_PDB_HEADER (8 * sizeof(uint32_t)) #define PDB_STORAGE_NONE 0 /* no storage; absolute value */ #define PDB_STORAGE_TEXT 4 /* in text segment */ #define PDB_STORAGE_DATA 5 /* in data segment */ #define PDB_STORAGE_BSS 6 /* in bss segment */ struct pdb_varinfo { int8_t type; uint8_t storage; uint32_t name_offset; uint32_t typeinfo_offset; uint32_t value; }; #define SIZEOF_VARINFO ((size_t)14) static void read_pc_debug_header(const uint8_t *ptr, struct pdb_h *header) { header->magic = get_be32(ptr + 0); header->size_fileinfos = get_be32(ptr + 4); header->size_lineinfo = get_be32(ptr + 8); header->size_varinfo = get_be32(ptr + 12); header->size_unknown = get_be32(ptr + 16); header->size_typeinfo = get_be32(ptr + 20); header->size_structinfo = get_be32(ptr + 24); header->size_stringtable = get_be32(ptr + 28); } static void read_varinfo(const uint8_t *ptr, struct pdb_varinfo *info) { info->type = ptr[0]; info->storage = ptr[1]; info->name_offset = get_be32(ptr + 2); info->typeinfo_offset = get_be32(ptr + 6); info->value = get_be32(ptr + 10); } static int read_pc_debug_names(FILE *fp, symbol_list_t *list, uint32_t offset) { uint8_t *buf; size_t filesize; size_t nread; uint32_t reloc_offset; uint32_t debug_offset; uint32_t varinfo_offset; uint32_t strtable_offset; struct pdb_h pdb_h; if (fseek(fp, 0, SEEK_END) < 0) return 0; if ((long)(filesize = ftell(fp)) < 0) return 0; if (fseek(fp, 0, SEEK_SET) < 0) return 0; buf = malloc(filesize); if (buf == NULL) { perror(""); return 0; } nread = fread(buf, 1, filesize, fp); if (nread != filesize){ perror("ERROR: reading failed"); free(buf); return 0; } reloc_offset = offset; /* * skip the TPA relocation table */ { uint32_t first_reloc = get_be32(buf + reloc_offset); reloc_offset += 4; if (first_reloc != 0) { while (reloc_offset < filesize && buf[reloc_offset] != 0) reloc_offset++; reloc_offset++; } if (reloc_offset & 1) reloc_offset++; debug_offset = reloc_offset; } if (debug_offset + SIZEOF_PDB_HEADER >= filesize) { /* fprintf(stderr, "no debug information present\n"); */ /* this is not an error */ free(buf); return 1; } read_pc_debug_header(buf + debug_offset, &pdb_h); /* 'QDB1' (in executables) */ if (pdb_h.magic != 0x51444231UL) { fprintf(stderr, "ERROR: unknown debug format 0x%08lx\n", (unsigned long)pdb_h.magic); free(buf); return 0; } if (pdb_h.size_stringtable == 0) { free(buf); return 0; } fprintf(stderr, "Reading symbol names from Pure-C debug information.\n"); list->debug_strtab = (char *)malloc(pdb_h.size_stringtable); if (list->debug_strtab == NULL) { perror("mem alloc of debug string table"); free(buf); return 0; } varinfo_offset = SIZEOF_PDB_HEADER + debug_offset + pdb_h.size_fileinfos + pdb_h.size_lineinfo; strtable_offset = varinfo_offset + pdb_h.size_varinfo + pdb_h.size_unknown + pdb_h.size_typeinfo + pdb_h.size_structinfo; if (strtable_offset >= filesize || strtable_offset + pdb_h.size_stringtable > filesize) { free(list->debug_strtab); list->debug_strtab = NULL; free(buf); return 0; } memcpy(list->debug_strtab, buf + strtable_offset, pdb_h.size_stringtable); if (pdb_h.size_varinfo != 0) { int i; for (i = 0; i < list->namecount; i++) { uint8_t storage; switch (list->names[i].type) { case SYMTYPE_TEXT: storage = PDB_STORAGE_TEXT; break; case SYMTYPE_DATA: storage = PDB_STORAGE_DATA; break; case SYMTYPE_BSS: storage = PDB_STORAGE_BSS; break; default: storage = PDB_STORAGE_NONE; break; } if (storage != PDB_STORAGE_NONE) { uint8_t *p, *end; int len = (int)strlen(list->names[i].name); /* * only need to care about possibly truncated names */ if (len != 8 && len != 22) { continue; } /* * Fixme: slurp the infos all in, and sort them so we can do a binary search */ p = buf + varinfo_offset; end = p + pdb_h.size_varinfo; while (p < end) { struct pdb_varinfo info; read_varinfo(p, &info); if (info.storage == storage && info.value == list->names[i].address && ((storage == PDB_STORAGE_TEXT && (info.type == 7 || info.type == 8)) || ((storage == PDB_STORAGE_DATA || storage == PDB_STORAGE_BSS) && (info.type == 4 || info.type == 5 || info.type == 6)))) { char *name = (char *)buf + strtable_offset + info.name_offset; if (strcmp(list->names[i].name, name) != 0) { if (list->names[i].name_allocated) { free(list->names[i].name); list->names[i].name_allocated = false; } list->names[i].name = list->debug_strtab + info.name_offset; } break; } p += SIZEOF_VARINFO; } } } } free(buf); return 1; } /* ---------- symbol ignore count handling ------------- */ /** * Return true if symbol should be ignored based on its name & type * and given options, and increase appropriate ignore count */ static bool ignore_symbol(const char *name, symtype_t symtype, const symbol_opts_t *opts, ignore_counts_t *counts) { if (opts->notypes & symtype) { counts->notypes++; return true; } if (opts->no_local) { if (is_local_symbol(name)) { counts->locals++; return true; } } if (opts->no_gccint) { if (is_gcc_internal(name)) { counts->gccint++; return true; } } if (opts->no_files) { if (is_file_name(name)) { counts->files++; return true; } } return false; } /** * show counts for all ignored symbol categories */ static void show_ignored(const ignore_counts_t *counts) { if (counts->debug) { fprintf(stderr, "NOTE: ignored %d debugging symbols.\n", counts->debug); } if (counts->files) { /* object file path names most likely get truncated and * as result cause unnecessary symbol name conflicts * in addition to object file addresses conflicting * with first symbol in the object file. */ fprintf(stderr, "NOTE: ignored %d file symbols ('*.[ao]'|'*/*').\n", counts->files); } if (counts->gccint) { fprintf(stderr, "NOTE: ignored %d GCC internal symbols.\n", counts->gccint); } if (counts->invalid) { fprintf(stderr, "NOTE: ignored %d invalid symbols.\n", counts->invalid); } if (counts->locals) { fprintf(stderr, "NOTE: ignored %d unnamed / local symbols ('.L*').\n", counts->locals); } if (counts->notypes) { fprintf(stderr, "NOTE: ignored %d symbols with unwanted types.\n", counts->notypes); } if (counts->undefined) { fprintf(stderr, "NOTE: ignored %d undefined symbols.\n", counts->undefined); } } /* ---------- symbol table type specific loading ------------- */ /** * Load symbols of given type and the symbol address addresses from * DRI/GST format symbol table, and add given offsets to the addresses: * http://toshyp.atari.org/en/005005.html * Return symbols list or NULL for failure. */ static symbol_list_t* symbols_load_dri(FILE *fp, const prg_section_t *sections, uint32_t tablesize, const symbol_opts_t *opts) { int i, count, symbols; ignore_counts_t ignore; const prg_section_t *section; symbol_list_t *list; symtype_t symtype; #define DRI_ENTRY_SIZE 14 char name[23]; uint16_t symid; uint32_t address; bool use_bssdata_offset; uint32_t textlen = sections[0].end - sections[0].offset; if (tablesize % DRI_ENTRY_SIZE || !tablesize) { fprintf(stderr, "ERROR: invalid DRI/GST symbol table size %d!\n", tablesize); return NULL; } symbols = tablesize / DRI_ENTRY_SIZE; if (!(list = symbol_list_alloc(symbols))) { return NULL; } memset(&ignore, 0, sizeof(ignore)); use_bssdata_offset = false; count = 0; for (i = 1; i <= symbols; i++) { /* read DRI symbol table slot */ if (fread(name, 8, 1, fp) != 1 || fread(&symid, sizeof(symid), 1, fp) != 1 || fread(&address, sizeof(address), 1, fp) != 1) { break; } address = be_swap32(address); symid = be_swap16(symid); /* GST extended DRI symbol format? */ if ((symid & 0x0048)) { /* next slot is rest of name */ i += 1; if (fread(name+8, 14, 1, fp) != 1) { break; } name[22] = '\0'; } else { name[8] = '\0'; } /* check section */ switch (symid & 0xf00) { case 0x0200: symtype = SYMTYPE_TEXT; break; case 0x0400: symtype = SYMTYPE_DATA; if (address < textlen) use_bssdata_offset = true; break; case 0x0100: symtype = SYMTYPE_BSS; if (address < textlen) use_bssdata_offset = true; break; default: if ((symid & 0xe000) == 0xe000) { ignore.debug++; continue; } if ((symid & 0x4000) == 0x4000) { symtype = SYMTYPE_ABS; break; } fprintf(stderr, "WARNING: ignoring symbol '%s' in slot %d of unknown type 0x%x.\n", name, i, symid); ignore.invalid++; continue; } /* whether to ignore symbol based on options and its name & type */ if (ignore_symbol(name, symtype, opts, &ignore)) { continue; } list->names[count].address = address; list->names[count].type = symtype; list->names[count].name = strdup(name); list->names[count].name_allocated = true; assert(list->names[count].name); count++; } if (i <= symbols) { perror("ERROR: reading symbol failed"); symbol_list_free(list); return NULL; } list->symbols = symbols; list->namecount = count; /* * now try to read the real names from Pure-C debug info */ read_pc_debug_names(fp, list, 28 + (sections[2].offset - sections[0].offset) + tablesize); /* * now offset the addresses if needed, and check them */ fprintf(stderr, "Offsetting BSS/DATA symbols from %s.\n", use_bssdata_offset ? "their own sections" : "TEXT section"); count = 0; for (i = 0; i < list->namecount; i++) { /* offsets are by default based on TEXT section */ const prg_section_t *offset_section = &(sections[0]); symbol_t *item = &list->names[i]; switch (item->type) { case SYMTYPE_TEXT: section = &(sections[0]); break; case SYMTYPE_DATA: section = &(sections[1]); if (use_bssdata_offset) { offset_section = &(sections[1]); } break; case SYMTYPE_BSS: section = &(sections[2]); if (use_bssdata_offset) { offset_section = &(sections[2]); } break; default: section = NULL; break; } if (section && offset_section) { item->address += offset_section->offset; if (item->address > section->end) { fprintf(stderr, "WARNING: ignoring %c symbol '%s' in slot %d with invalid offset 0x%x (>= 0x%x).\n", symbol_char(item->type), item->name, i, item->address, section->end); if (item->name_allocated) { free(item->name); } ignore.invalid++; continue; } } list->names[count] = *item; count++; } /* * update new final count again */ list->namecount = count; show_ignored(&ignore); return list; } /** * Load symbols of given type and the symbol address addresses from * a.out format symbol table, and add given offsets to the addresses. * Return symbols list or NULL for failure. */ static symbol_list_t* symbols_load_gnu(FILE *fp, const prg_section_t *sections, uint32_t tablesize, uint32_t stroff, uint32_t strsize, const symbol_opts_t *opts) { size_t slots = tablesize / SIZEOF_STRUCT_NLIST; size_t i; size_t strx; unsigned char *p; char *name; symbol_t *sym; symtype_t symtype; uint32_t address; size_t nread; symbol_list_t *list; unsigned char n_type; unsigned char n_other; unsigned short n_desc; static char dummy[] = ""; int count; ignore_counts_t ignore; const prg_section_t *section; if (!(list = symbol_list_alloc(slots))) { return NULL; } list->strtab = (char *)malloc(tablesize + strsize + 1); if (list->strtab == NULL) { symbol_list_free(list); return NULL; } nread = fread(list->strtab, tablesize + strsize, 1, fp); if (nread != 1) { perror("ERROR: reading symbols failed"); symbol_list_free(list); return NULL; } list->strtab[tablesize + strsize] = 0; p = (unsigned char *)list->strtab; sym = list->names; memset(&ignore, 0, sizeof(ignore)); count = 0; for (i = 0; i < slots; i++) { strx = get_be32(p); p += 4; n_type = *p++; n_other = *p++; n_desc = be_swap16(*(uint16_t*)p); p += 2; address = get_be32(p); p += 4; name = dummy; if (!strx) { ignore.invalid++; continue; } if (strx >= strsize) { fprintf(stderr, "symbol name index %x out of range\n", (unsigned int)strx); ignore.invalid++; continue; } name = list->strtab + strx + stroff; if (n_type & N_STAB) { ignore.debug++; continue; } section = NULL; switch (n_type & (N_TYPE|N_EXT)) { case N_UNDF: case N_UNDF|N_EXT: case N_WEAKU: /* shouldn't happen here */ ignore.undefined++; continue; case N_ABS: case N_ABS|N_EXT: symtype = SYMTYPE_ABS; break; case N_FN: /* filename symbol */ case N_TEXT: case N_TEXT|N_EXT: symtype = SYMTYPE_TEXT; section = &(sections[0]); break; case N_WEAKT: symtype = SYMTYPE_WEAK; section = &(sections[0]); break; case N_DATA: case N_DATA|N_EXT: case N_WEAKD: symtype = SYMTYPE_DATA; section = &(sections[1]); break; case N_BSS: case N_BSS|N_EXT: case N_COMM: case N_COMM|N_EXT: case N_WEAKB: symtype = SYMTYPE_BSS; section = &(sections[2]); break; case N_SIZE: case N_WARNING: case N_SETA: case N_SETT: case N_SETD: case N_SETB: case N_SETV: ignore.debug++; continue; default: fprintf(stderr, "WARNING: ignoring symbol '%s' in slot %u of unknown type 0x%x.\n", name, (unsigned int)i, n_type); ignore.invalid++; continue; } /* * the value of a common symbol is its size, not its address: */ if (((n_type & N_TYPE) == N_COMM) || (((n_type & N_EXT) && (n_type & N_TYPE) == N_UNDF && address != 0))) { /* if we ever want to know a symbols size, get that here */ fprintf(stderr, "WARNING: ignoring common symbol '%s' in slot %u.\n", name, (unsigned int)i); ignore.debug++; continue; } /* whether to ignore symbol based on options and its name & type */ if (ignore_symbol(name, symtype, opts, &ignore)) { continue; } if (section) { address += sections[0].offset; /* all GNU symbol addresses are TEXT relative */ if (address > section->end) { fprintf(stderr, "WARNING: ignoring symbol '%s' of type %c in slot %u with invalid offset 0x%x (>= 0x%x).\n", name, symbol_char(symtype), (unsigned int)i, address, section->end); ignore.invalid++; continue; } } sym->address = address; sym->type = symtype; sym->name = name; sym++; count++; (void) n_desc; (void) n_other; } list->symbols = slots; list->namecount = count; show_ignored(&ignore); return list; } /** * Load symbols of given type and the symbol address addresses from * ELF format symbol table, and add given offsets to the addresses. * Return symbols list or NULL for failure. */ #define SIZEOF_ELF32_SYM 16 #define ELF_ST_BIND(val) (((unsigned int)(val)) >> 4) #define ELF_ST_TYPE(val) ((val) & 0xF) #define ELF_ST_INFO(bind,type) (((bind) << 4) + ((type) & 0xF)) /* sh_type */ #define SHT_NULL 0 /* Section header table entry unused */ #define SHT_PROGBITS 1 /* Program specific (private) data */ #define SHT_SYMTAB 2 /* Link editing symbol table */ #define SHT_STRTAB 3 /* A string table */ #define SHT_RELA 4 /* Relocation entries with addends */ #define SHT_HASH 5 /* A symbol hash table */ #define SHT_DYNAMIC 6 /* Information for dynamic linking */ #define SHT_NOTE 7 /* Information that marks file */ #define SHT_NOBITS 8 /* Section occupies no space in file */ #define SHT_REL 9 /* Relocation entries, no addends */ #define SHT_SHLIB 10 /* Reserved, unspecified semantics */ #define SHT_DYNSYM 11 /* Dynamic linking symbol table */ #define SHT_INIT_ARRAY 14 /* Array of constructors */ #define SHT_FINI_ARRAY 15 /* Array of destructors */ #define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ #define SHT_GROUP 17 /* Section group */ #define SHT_SYMTAB_SHNDX 18 /* Extended section indices */ /* ST_BIND */ #define STB_LOCAL 0 /* Symbol not visible outside obj */ #define STB_GLOBAL 1 /* Symbol visible outside obj */ #define STB_WEAK 2 /* Like globals, lower precedence */ #define STB_LOOS 10 /* Start of OS-specific */ #define STB_GNU_UNIQUE 10 /* Symbol is unique in namespace */ #define STB_HIOS 12 /* End of OS-specific */ #define STB_LOPROC 13 /* Application-specific semantics */ #define STB_HIPROC 15 /* Application-specific semantics */ /* ST_TYPE */ #define STT_NOTYPE 0 /* Symbol type is unspecified */ #define STT_OBJECT 1 /* Symbol is a data object */ #define STT_FUNC 2 /* Symbol is a code object */ #define STT_SECTION 3 /* Symbol associated with a section */ #define STT_FILE 4 /* Symbol gives a file name */ #define STT_COMMON 5 /* Symbol is a common data object */ #define STT_TLS 6 /* Symbol is thread-local data object*/ #define STT_LOOS 10 /* Start of OS-specific */ #define STT_GNU_IFUNC 10 /* Symbol is an indirect code object */ #define STT_HIOS 12 /* End of OS-specific */ #define STT_LOPROC 13 /* Application-specific semantics */ #define STT_HIPROC 15 /* Application-specific semantics */ /* special sections indexes */ #define SHN_UNDEF 0 #define SHN_LORESERVE 0xFF00 /* Begin range of reserved indices */ #define SHN_LOPROC 0xFF00 /* Begin range of appl-specific */ #define SHN_HIPROC 0xFF1F /* End range of appl-specific */ #define SHN_LOOS 0xFF20 /* OS specific semantics, lo */ #define SHN_HIOS 0xFF3F /* OS specific semantics, hi */ #define SHN_ABS 0xfff1 /* Associated symbol is absolute */ #define SHN_COMMON 0xfff2 /* Associated symbol is in common */ #define SHN_XINDEX 0xFFFF /* Section index is held elsewhere */ #define SHN_HIRESERVE 0xFFFF /* End range of reserved indices */ /* Values for section header, sh_flags field. */ #define SHF_WRITE ((uint32_t)1 << 0) /* Writable data during execution */ #define SHF_ALLOC ((uint32_t)1 << 1) /* Occupies memory during execution */ #define SHF_EXECINSTR ((uint32_t)1 << 2) /* Executable machine instructions */ #define SHF_MERGE ((uint32_t)1 << 4) /* Might be merged */ #define SHF_STRINGS ((uint32_t)1 << 5) /* Contains nul-terminated strings */ #define SHF_INFO_LINK ((uint32_t)1 << 6) /* `sh_info' contains SHT index */ #define SHF_LINK_ORDER ((uint32_t)1 << 7) /* Preserve order after combining */ #define SHF_OS_NONCONFORMING ((uint32_t)1 << 8) /* Non-standard OS specific handling required */ #define SHF_GROUP ((uint32_t)1 << 9) /* Section is member of a group. */ #define SHF_TLS ((uint32_t)1 << 10) /* Section hold thread-local data. */ #define SHF_COMPRESSED ((uint32_t)1 << 11) /* Section with compressed data */ #define SHF_MASKOS 0x0ff00000 /* OS-specific. */ #define SHF_MASKPROC 0xf0000000 /* Processor-specific */ #define SHF_ORDERED ((uint32_t)1 << 30) /* Special ordering requirement (Solaris). */ #define SHF_EXCLUDE ((uint32_t)1 << 31) /* Section is excluded unless referenced or allocated (Solaris).*/ struct elf_shdr { uint32_t sh_name; /* Section name */ uint32_t sh_type; /* Type of section */ uint32_t sh_flags; /* Miscellaneous section attributes */ uint32_t sh_addr; /* Section virtual addr at execution */ uint32_t sh_offset; /* Section file offset */ uint32_t sh_size; /* Size of section in bytes */ uint32_t sh_link; /* Index of another section */ uint32_t sh_info; /* Additional section information */ uint32_t sh_addralign; /* Section alignment */ uint32_t sh_entsize; /* Entry size if section holds table */ }; static symbol_list_t* symbols_load_elf(FILE *fp, const prg_section_t *sections, uint32_t tablesize, uint32_t stroff, uint32_t strsize, const symbol_opts_t *opts, struct elf_shdr *headers, unsigned short e_shnum) { size_t slots = tablesize / SIZEOF_ELF32_SYM; size_t i; size_t strx; unsigned char *p; char *name; symbol_t *sym; symtype_t symtype; uint32_t address; size_t nread; symbol_list_t *list; uint32_t st_size; unsigned char st_info; unsigned char st_other; unsigned short st_shndx; static char dummy[] = ""; int count; ignore_counts_t ignore; const prg_section_t *section; unsigned char *symtab; struct elf_shdr *shdr; if (!(list = symbol_list_alloc(slots))) { return NULL; } list->strtab = (char *)malloc(strsize + 1); symtab = (unsigned char *)malloc(tablesize); if (list->strtab == NULL || symtab == NULL) { perror(""); free(symtab); symbol_list_free(list); return NULL; } nread = fread(symtab, tablesize, 1, fp); if (nread != 1) { perror("ERROR: reading symbols failed"); free(symtab); symbol_list_free(list); return NULL; } if (fseek(fp, stroff, SEEK_SET) < 0) { perror("ERROR: seeking to string table failed"); free(symtab); symbol_list_free(list); return NULL; } nread = fread(list->strtab, strsize, 1, fp); if (nread != 1) { perror("ERROR: reading symbol names failed"); free(symtab); symbol_list_free(list); return NULL; } list->strtab[strsize] = 0; p = (unsigned char *)symtab; sym = list->names; memset(&ignore, 0, sizeof(ignore)); count = 0; for (i = 0; i < slots; i++) { strx = get_be32(p); p += 4; address = get_be32(p); p += 4; st_size = get_be32(p); p += 4; st_info = *p++; st_other = *p++; st_shndx = be_swap16(*(uint16_t*)p); p += 2; name = dummy; if (!strx) { switch (st_info) { /* silently ignore no-name symbols * related to section names */ case ELF_ST_INFO(STB_LOCAL, STT_NOTYPE): case ELF_ST_INFO(STB_LOCAL, STT_SECTION): break; default: ignore.invalid++; break; } continue; } if (strx >= strsize) { fprintf(stderr, "symbol name index %x out of range\n", (unsigned int)strx); ignore.invalid++; continue; } name = list->strtab + strx; section = NULL; switch (st_info) { case ELF_ST_INFO(STB_LOCAL, STT_OBJECT): case ELF_ST_INFO(STB_GLOBAL, STT_OBJECT): case ELF_ST_INFO(STB_WEAK, STT_OBJECT): case ELF_ST_INFO(STB_LOCAL, STT_FUNC): case ELF_ST_INFO(STB_GLOBAL, STT_FUNC): case ELF_ST_INFO(STB_WEAK, STT_FUNC): case ELF_ST_INFO(STB_LOCAL, STT_COMMON): case ELF_ST_INFO(STB_GLOBAL, STT_COMMON): case ELF_ST_INFO(STB_WEAK, STT_COMMON): case ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE): case ELF_ST_INFO(STB_LOCAL, STT_NOTYPE): case ELF_ST_INFO(STB_WEAK, STT_NOTYPE): switch (st_shndx) { case SHN_ABS: symtype = SYMTYPE_ABS; break; case SHN_UNDEF: /* shouldn't happen here */ ignore.undefined++; continue; case SHN_COMMON: fprintf(stderr, "WARNING: ignoring common symbol '%s' in slot %u.\n", name, (unsigned int)i); ignore.debug++; continue; default: if (st_shndx >= e_shnum) { ignore.invalid++; continue; } else { shdr = &headers[st_shndx]; if (shdr->sh_type == SHT_NOBITS) { symtype = SYMTYPE_BSS; section = &(sections[2]); } else if (shdr->sh_flags & SHF_EXECINSTR) { /* symbol post-processing requirement: * only code symbols differentiated weak aliasing */ symtype = ELF_ST_BIND(st_info) == STB_WEAK ? SYMTYPE_WEAK : SYMTYPE_TEXT; section = &(sections[0]); } else { symtype = SYMTYPE_DATA; section = &(sections[1]); } } } break; case ELF_ST_INFO(STB_LOCAL, STT_FILE): /* filename symbol */ ignore.debug++; continue; case ELF_ST_INFO(STB_LOCAL, STT_SECTION): /* section name */ continue; default: fprintf(stderr, "WARNING: ignoring symbol '%s' in slot %u of unknown type 0x%x.\n", name, (unsigned int)i, st_info); ignore.invalid++; continue; } /* whether to ignore symbol based on options and its name & type */ if (ignore_symbol(name, symtype, opts, &ignore)) { continue; } if (section) { address += sections[0].offset; /* all GNU symbol addresses are TEXT relative */ if (address > section->end) { fprintf(stderr, "WARNING: ignoring symbol '%s' of type %c in slot %u with invalid offset 0x%x (>= 0x%x).\n", name, symbol_char(symtype), (unsigned int)i, address, section->end); ignore.invalid++; continue; } } sym->address = address; sym->type = symtype; sym->name = name; sym++; count++; (void)st_other; (void)st_size; } list->symbols = slots; list->namecount = count; free(symtab); show_ignored(&ignore); return list; } /* ---------- program info + symbols loading ------------- */ /** * Print program header information. * Return false for unrecognized symbol table type. */ static bool symbols_print_prg_info(uint32_t tabletype, uint32_t prgflags, uint16_t relocflag) { static const struct { uint32_t flag; const char *name; } flags[] = { { 0x0001, "FASTLOAD" }, { 0x0002, "TTRAMLOAD" }, { 0x0004, "TTRAMMEM" }, { 0x0008, "MINIMUM" }, /* MagiC */ { 0x1000, "SHAREDTEXT" } }; const char *info; int i; switch (tabletype) { case SYMBOL_FORMAT_MINT: /* "MiNT" */ info = "GCC/MiNT executable, GST symbol table"; break; case SYMBOL_FORMAT_GNU: /* "GNU_" */ info = "GCC/MiNT executable, a.out symbol table"; break; case SYMBOL_FORMAT_ELF: info = "GCC/MiNT executable, elf symbol table"; break; case SYMBOL_FORMAT_DRI: info = "TOS executable, DRI / GST symbol table"; break; default: fprintf(stderr, "ERROR: unknown executable type 0x%x!\n", tabletype); return false; } fprintf(stderr, "%s, reloc=%d, program flags:", info, relocflag); /* bit flags */ for (i = 0; i < ARRAY_SIZE(flags); i++) { if (prgflags & flags[i].flag) { fprintf(stderr, " %s", flags[i].name); } } /* memory protection flags */ switch((prgflags >> 4) & 3) { case 0: info = "PRIVATE"; break; case 1: info = "GLOBAL"; break; case 2: info = "SUPER"; break; case 3: info = "READONLY"; break; } fprintf(stderr, " %s (0x%x)\n", info, prgflags); return true; } /** * Parse program header and use symbol table format specific * loader function to load the symbols. * * update_sections() callback is called with .end fields set * to sizes of corresponding sections. It should set suitable * start offsets and update end end positions accordingly. * If that succeeds, it should return true. * * Return symbols list or NULL for failure. */ static symbol_list_t* symbols_load_binary(FILE *fp, const symbol_opts_t *opts, bool (*update_sections)(prg_section_t*)) { uint32_t textlen, datalen, bsslen, tablesize, tabletype, prgflags; prg_section_t sections[3]; size_t reads = 0; uint16_t relocflag; symbol_list_t* symbols; uint32_t symoff = 0; uint32_t stroff = 0; uint32_t strsize = 0; struct elf_shdr *headers = 0; uint16_t e_shnum = 0; /* get TEXT, DATA & BSS section sizes */ fseek(fp, 2, SEEK_SET); reads += fread(&textlen, sizeof(textlen), 1, fp); textlen = be_swap32(textlen); reads += fread(&datalen, sizeof(datalen), 1, fp); datalen = be_swap32(datalen); reads += fread(&bsslen, sizeof(bsslen), 1, fp); bsslen = be_swap32(bsslen); /* get symbol table size & type and check that all reads succeeded */ reads += fread(&tablesize, sizeof(tablesize), 1, fp); tablesize = be_swap32(tablesize); reads += fread(&tabletype, sizeof(tabletype), 1, fp); tabletype = be_swap32(tabletype); /* get program header and whether there's reloc table */ reads += fread(&prgflags, sizeof(prgflags), 1, fp); prgflags = be_swap32(prgflags); reads += fread(&relocflag, sizeof(relocflag), 1, fp); relocflag = be_swap16(relocflag); if (reads != 7) { fprintf(stderr, "ERROR: program header reading failed!\n"); return NULL; } /* * check for GNU-style symbol table in aexec header */ if (tabletype == SYMBOL_FORMAT_MINT) { /* MiNT */ uint32_t magic1, magic2; uint32_t dummy; uint32_t a_text, a_data, a_bss, a_syms, a_entry, a_trsize, a_drsize; uint32_t g_tparel_pos, g_tparel_size, g_stkpos, g_symbol_format; reads = fread(&magic1, sizeof(magic1), 1, fp); magic1 = be_swap32(magic1); reads += fread(&magic2, sizeof(magic2), 1, fp); magic2 = be_swap32(magic2); if (reads == 2 && ((magic1 == 0x283a001a && magic2 == 0x4efb48fa) || /* Original binutils: move.l 28(pc),d4; jmp 0(pc,d4.l) */ (magic1 == 0x203a001a && magic2 == 0x4efb08fa))) { /* binutils >= 2.18-mint-20080209: move.l 28(pc),d0; jmp 0(pc,d0.l) */ reads += fread(&dummy, sizeof(dummy), 1, fp); /* skip a_info */ reads += fread(&a_text, sizeof(a_text), 1, fp); a_text = be_swap32(a_text); reads += fread(&a_data, sizeof(a_data), 1, fp); a_data = be_swap32(a_data); reads += fread(&a_bss, sizeof(a_bss), 1, fp); a_bss = be_swap32(a_bss); reads += fread(&a_syms, sizeof(a_syms), 1, fp); a_syms = be_swap32(a_syms); reads += fread(&a_entry, sizeof(a_entry), 1, fp); a_entry = be_swap32(a_entry); reads += fread(&a_trsize, sizeof(a_trsize), 1, fp); a_trsize = be_swap32(a_trsize); reads += fread(&a_drsize, sizeof(a_drsize), 1, fp); a_drsize = be_swap32(a_drsize); reads += fread(&g_tparel_pos, sizeof(g_tparel_pos), 1, fp); g_tparel_pos = be_swap32(g_tparel_pos); reads += fread(&g_tparel_size, sizeof(g_tparel_size), 1, fp); g_tparel_size = be_swap32(g_tparel_size); reads += fread(&g_stkpos, sizeof(g_stkpos), 1, fp); g_stkpos = be_swap32(g_stkpos); reads += fread(&g_symbol_format, sizeof(g_symbol_format), 1, fp); g_symbol_format = be_swap32(g_symbol_format); if (g_symbol_format == 0) { tabletype = SYMBOL_FORMAT_GNU; } if ((a_text + (256 - 28)) != textlen) { fprintf(stderr, "warning: inconsistent text segment size %08x != %08x\n", textlen, a_text + (256 - 28)); } if (a_data != datalen) { fprintf(stderr, "warning: inconsistent data segment size %08x != %08x\n", datalen, a_data); } if (a_bss != bsslen) { fprintf(stderr, "warning: inconsistent bss segment size %08x != %08x\n", bsslen, a_bss); } /* * the symbol table size in the GEMDOS header includes the string table, * the symbol table size in the exec header does not. */ if (tabletype == SYMBOL_FORMAT_GNU) { strsize = tablesize - a_syms; tablesize = a_syms; stroff = a_syms; } textlen = a_text + (256 - 28); datalen = a_data; bsslen = a_bss; symoff = 0x100 + /* sizeof(extended exec header) */ a_text + a_data + a_trsize + a_drsize; } } else if ((tabletype & 0xffffff00) == SYMBOL_FORMAT_ELF && (tabletype & 0xff) >= 40) { /* new MiNT+ELF */ uint32_t magic; uint8_t e_ident[12]; uint32_t dummy; uint32_t e_phoff, e_shoff; uint16_t e_type, e_machine, e_phnum, e_shentsize, e_shstrndx; uint16_t i; int strtabidx; /* skip to ELF program header */ fseek(fp, (tabletype & 0xff) - 28, SEEK_CUR); tabletype = SYMBOL_FORMAT_ELF; /* symbol table size in GEMDOS header includes space of ELF headers, ignore it */ tablesize = 0; reads = fread(&magic, sizeof(magic), 1, fp); magic = be_swap32(magic); /* read rest of e_ident */ reads += fread(e_ident, sizeof(e_ident), 1, fp); reads += fread(&e_type, sizeof(e_type), 1, fp); e_type = be_swap16(e_type); reads += fread(&e_machine, sizeof(e_machine), 1, fp); e_machine = be_swap16(e_machine); if (reads == 4 && magic == 0x7f454c46 && /* '\177ELF' */ e_ident[0] == 1 && /* ELFCLASS32 */ e_ident[1] == 2 && /* ELFDATA2MSB */ e_type == 2 && /* ET_EXEC */ e_machine == 4) { /* EM_68K */ reads = fread(&dummy, sizeof(dummy), 1, fp); reads += fread(&dummy, sizeof(dummy), 1, fp); reads += fread(&e_phoff, sizeof(e_phoff), 1, fp); e_phoff = be_swap32(e_phoff); reads += fread(&e_shoff, sizeof(e_shoff), 1, fp); e_shoff = be_swap32(e_shoff); reads += fread(&dummy, sizeof(dummy), 1, fp); reads += fread(&dummy, sizeof(dummy), 1, fp); reads += fread(&e_phnum, sizeof(e_phnum), 1, fp); e_phnum = be_swap16(e_phnum); reads += fread(&e_shentsize, sizeof(e_shentsize), 1, fp); reads += fread(&e_shnum, sizeof(e_shnum), 1, fp); e_shnum = be_swap16(e_shnum); reads += fread(&e_shstrndx, sizeof(e_shstrndx), 1, fp); e_shstrndx = be_swap16(e_shstrndx); fseek(fp, e_shoff, SEEK_SET); headers = (struct elf_shdr *)malloc(sizeof(*headers) * e_shnum); if (headers == NULL) { perror(""); return NULL; } strtabidx = -1; for (i = 0; i < e_shnum; i++) { struct elf_shdr *shdr = &headers[i]; reads = fread(&shdr->sh_name, 1, sizeof(shdr->sh_name), fp); shdr->sh_name = be_swap32(shdr->sh_name); reads += fread(&shdr->sh_type, 1, sizeof(shdr->sh_type), fp); shdr->sh_type = be_swap32(shdr->sh_type); reads += fread(&shdr->sh_flags, 1, sizeof(shdr->sh_flags), fp); shdr->sh_flags = be_swap32(shdr->sh_flags); reads += fread(&shdr->sh_addr, 1, sizeof(shdr->sh_addr), fp); shdr->sh_addr = be_swap32(shdr->sh_addr); reads += fread(&shdr->sh_offset, 1, sizeof(shdr->sh_offset), fp); shdr->sh_offset = be_swap32(shdr->sh_offset); reads += fread(&shdr->sh_size, 1, sizeof(shdr->sh_size), fp); shdr->sh_size = be_swap32(shdr->sh_size); reads += fread(&shdr->sh_link, 1, sizeof(shdr->sh_link), fp); shdr->sh_link = be_swap32(shdr->sh_link); reads += fread(&shdr->sh_info, 1, sizeof(shdr->sh_info), fp); shdr->sh_info = be_swap32(shdr->sh_info); reads += fread(&shdr->sh_addralign, 1, sizeof(shdr->sh_addralign), fp); shdr->sh_addralign = be_swap32(shdr->sh_addralign); reads += fread(&shdr->sh_entsize, 1, sizeof(shdr->sh_entsize), fp); shdr->sh_entsize = be_swap32(shdr->sh_entsize); if (shdr->sh_type == SHT_SYMTAB) { symoff = shdr->sh_offset; tablesize = shdr->sh_size; strtabidx = shdr->sh_link; } } if (strtabidx < 0 || strtabidx >= e_shnum || headers[strtabidx].sh_type != SHT_STRTAB) { tabletype = 0; } else { stroff = headers[strtabidx].sh_offset; strsize = headers[strtabidx].sh_size; } } else { tabletype = 0; } if (tabletype == 0) { fprintf(stderr, "ERROR: reading ELF header failed!\n"); free(headers); return NULL; } } else { /* DRI: symbol table offset */ symoff = 0x1C + textlen + datalen; } if (!symbols_print_prg_info(tabletype, prgflags, relocflag)) { free(headers); return NULL; } if (!tablesize) { fprintf(stderr, "ERROR: symbol table missing from the program!\n"); free(headers); return NULL; } fprintf(stderr, "Program section sizes:\n text: 0x%x, data: 0x%x, bss: 0x%x, symtab: 0x%x\n", textlen, datalen, bsslen, tablesize); sections[0].end = textlen; sections[1].end = datalen; sections[2].end = bsslen; /* add suitable offsets to section beginnings & ends, and validate them */ if (!update_sections(sections)) { free(headers); return NULL; } /* go to start of symbol table */ if (fseek(fp, symoff, SEEK_SET) < 0) { perror("ERROR: seeking to symbol table failed"); free(headers); return NULL; } if (tabletype == SYMBOL_FORMAT_GNU) { fprintf(stderr, "Trying to load a.out symbol table at offset 0x%x...\n", symoff); symbols = symbols_load_gnu(fp, sections, tablesize, stroff, strsize, opts); } else if (tabletype == SYMBOL_FORMAT_ELF) { fprintf(stderr, "Trying to load ELF symbol table at offset 0x%x...\n", symoff); symbols = symbols_load_elf(fp, sections, tablesize, stroff, strsize, opts, headers, e_shnum); free(headers); } else { fprintf(stderr, "Trying to load DRI symbol table at offset 0x%x...\n", symoff); symbols = symbols_load_dri(fp, sections, tablesize, opts); } return symbols; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/symbols.c000066400000000000000000000753111504763705000242610ustar00rootroot00000000000000/* * Hatari - symbols.c * * Copyright (C) 2010-2024 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * symbols.c - Hatari debugger symbol/address handling; parsing, sorting, * matching, TAB completion support etc. * * Symbol/address information is read either from: * - A program file's symbol table (in DRI/GST, a.out, or ELF format), or * - ASCII file which contents are subset of "nm" output i.e. composed of * a hexadecimal addresses followed by a space, letter indicating symbol * type (T = text/code, D = data, B = BSS), space and the symbol name. * Empty lines and lines starting with '#' are ignored. It's AHCC SYM * output compatible. */ const char Symbols_fileid[] = "Hatari symbols.c"; #include #include #include #include #include "config.h" #if HAVE_LIBREADLINE # include #else # define rl_filename_completion_function(x,y) NULL #endif #include "main.h" #include "file.h" #include "options.h" #include "symbols.h" #include "debugui.h" #include "debug_priv.h" #include "debugInfo.h" #include "evaluate.h" #include "configuration.h" #include "a.out.h" #include "maccess.h" #include "symbols-common.c" /* how many characters the symbol name can have. * * While DRI/GST symbols are at max only couple of dozen * chars long, C++/a.out symbols can be almost of any size. */ #define MAX_SYM_SIZE 1024 #define MAX_SYM_SIZE_S "1024" /* TODO: add symbol name/address file names to configuration? */ static symbol_list_t *CpuSymbolsList; static symbol_list_t *DspSymbolsList; /* path for last loaded program (through GEMDOS HD emulation) */ static char *CurrentProgramPath; /* prevent repeated failing on every debugger invocation */ static bool AutoLoadFailed; typedef enum { SYMBOLS_FOR_NONE, SYMBOLS_FOR_USER, /* autoload facilities */ SYMBOLS_FOR_TOS, SYMBOLS_FOR_PROGRAM, } symbols_for_t; /* what triggered current CPU symbols to be loaded */ static symbols_for_t CpuSymbolsAreFor = SYMBOLS_FOR_NONE; /** * Load symbols of given type and the symbol address addresses from * the given ASCII file and add given offsets to the addresses. * Return symbols list or NULL for failure. */ static symbol_list_t* symbols_load_ascii(FILE *fp, uint32_t *offsets, uint32_t maxaddr, symtype_t gettype, const symbol_opts_t *opts) { symbol_list_t *list; char symchar, name[MAX_SYM_SIZE+1]; char *buf, buffer[MAX_SYM_SIZE+64]; int count, line, symbols; uint32_t address, offset; ignore_counts_t ignore; symtype_t symtype; /* count content lines */ line = symbols = 0; while (fgets(buffer, sizeof(buffer), fp)) { line++; /* skip comments (AHCC SYM file comments start with '*') */ if (*buffer == '#' || *buffer == '*') { continue; } /* skip empty lines */ for (buf = buffer; isspace((unsigned char)*buf); buf++); if (!*buf) { continue; } if (!isxdigit((unsigned char)*buf)) { fprintf(stderr, "ERROR: line %d doesn't start with an address.\n", line); return NULL; } symbols++; } if (!symbols) { fprintf(stderr, "ERROR: no symbols.\n"); return NULL; } fseek(fp, 0, SEEK_SET); /* allocate space for symbol list & names */ if (!(list = symbol_list_alloc(symbols))) { return NULL; } count = 0; memset(&ignore, 0, sizeof(ignore)); /* read symbols */ for (line = 1; fgets(buffer, sizeof(buffer), fp); line++) { /* skip comments (AHCC SYM file comments start with '*') */ if (*buffer == '#' || *buffer == '*') { continue; } /* skip empty lines */ for (buf = buffer; isspace((unsigned char)*buf); buf++); if (!*buf) { continue; } /* file not modified in meanwhile? */ assert(count < symbols); /* C++ symbols can contain almost any characters */ if (sscanf(buffer, "%x %c %"MAX_SYM_SIZE_S"[^$?@;\n]\n", &address, &symchar, name) != 3) { fprintf(stderr, "WARNING: syntax error on line %d, skipping.\n", line); continue; } switch (toupper((unsigned char)symchar)) { case 'T': symtype = SYMTYPE_TEXT; offset = offsets[0]; break; case 'W': /* ELF 'nm' code symbol type, weak */ symtype = SYMTYPE_WEAK; offset = offsets[0]; break; case 'O': /* AHCC type for _StkSize etc */ case 'V': /* ELF 'nm' data symbol type, weak */ case 'R': /* ELF 'nm' data symbol type, read only */ case 'D': symtype = SYMTYPE_DATA; offset = offsets[1]; break; case 'B': symtype = SYMTYPE_BSS; offset = offsets[2]; break; case 'A': /* absolute address or arbitrary constant value */ symtype = SYMTYPE_ABS; offset = 0; break; default: fprintf(stderr, "WARNING: unrecognized symbol type '%c' on line %d, skipping.\n", symchar, line); ignore.invalid++; continue; } if (!(gettype & symtype)) { continue; } address += offset; if (address > maxaddr && symtype != SYMTYPE_ABS) { fprintf(stderr, "WARNING: invalid address 0x%x on line %d, skipping.\n", address, line); ignore.invalid++; continue; } /* whether to ignore symbol based on options and its name & type */ if (ignore_symbol(name, symtype, opts, &ignore)) { continue; } list->names[count].address = address; list->names[count].type = symtype; list->names[count].name = strdup(name); list->names[count].name_allocated = true; assert(list->names[count].name); count++; } show_ignored(&ignore); list->symbols = symbols; list->namecount = count; return list; } /** * Return true if symbol name has (C++) data symbol prefix */ static bool is_cpp_data_symbol(const char *name) { static const char *cpp_data[] = { "typeinfo ", "vtable ", "VTT " }; int i; for (i = 0; i < ARRAY_SIZE(cpp_data); i++) { size_t len = strlen(cpp_data[i]); if (strncmp(name, cpp_data[i], len) == 0) { return true; } } return false; } /** * (C++) compiler can put certain data members to text section, and * some of the weak (C++) symbols are for data. For C++, these can be * recognized by their name. This changes their type to data, to * speed up text symbol searches in profiler. */ static int fix_symbol_types(symbol_list_t* list) { symbol_t *sym = list->names; int i, count, changed = 0; count = list->namecount; for (i = 0; i < count; i++) { if (!(sym[i].type & SYMTYPE_CODE)) { continue; } if (is_cpp_data_symbol(sym[i].name)) { sym[i].type = SYMTYPE_DATA; changed++; } /* TODO: add check also for C++ data member * names, similar to profiler post-processor * (requires using regex)? */ } return changed; } /** * Separate code symbols from other symbols in address list. */ static void symbols_split_addresses(symbol_list_t* list) { symbol_t *sym = list->addresses; uint32_t prev = 0; int i; for (i = 0; i < list->namecount; i++) { if (sym[i].type & ~SYMTYPE_CODE) { break; } if (sym[i].address < prev) { char stype = symbol_char(sym[i].type); fprintf(stderr, "INTERNAL ERROR: %c symbol %d/%d ('%s') at %x < %x (prev addr)\n", stype, i, list->namecount, sym[i].name, sym[i].address, prev); exit(1); } prev = sym[i].address; } list->codecount = i; list->datacount = list->namecount - i; } /** * Set sections to match running process by adding TEXT/DATA/BSS * start addresses to section offsets and ends, and return true if * results match it. */ static bool update_sections(prg_section_t *sections) { /* offsets & max sizes for running program TEXT/DATA/BSS section symbols */ uint32_t start = DebugInfo_GetTEXT(); if (!start) { fprintf(stderr, "ERROR: no valid program basepage!\n"); return false; } sections[0].offset = start; sections[0].end += start; if (DebugInfo_GetTEXTEnd() != sections[0].end) { fprintf(stderr, "ERROR: given program TEXT section size differs from one in RAM!\n"); return false; } start = DebugInfo_GetDATA(); sections[1].offset = start; if (sections[1].offset != sections[0].end) { fprintf(stderr, "WARNING: DATA start doesn't match TEXT start + size!\n"); } sections[1].end += start; start = DebugInfo_GetBSS(); sections[2].offset = start; if (sections[2].offset != sections[1].end) { fprintf(stderr, "WARNING: BSS start doesn't match DATA start + size!\n"); } sections[2].end += start; return true; } /** * Load symbols of given type and the symbol address addresses from * the given file and add given offsets to the addresses. * Return symbols list or NULL for failure. */ static symbol_list_t* Symbols_Load(const char *filename, uint32_t *offsets, uint32_t maxaddr, symtype_t gettype) { symbol_list_t *list; symbol_opts_t opts; int changed, dups; FILE *fp; if (!File_Exists(filename)) { fprintf(stderr, "ERROR: file '%s' doesn't exist or isn't readable!\n", filename); return NULL; } memset(&opts, 0, sizeof(opts)); opts.no_gccint = true; opts.no_local = true; opts.no_dups = true; if (Opt_IsAtariProgram(filename)) { const char *last = CurrentProgramPath; if (!last) { /* "pc=text" breakpoint used as point for loading program symbols gives false hits during bootup */ fprintf(stderr, "WARNING: no program loaded yet (through GEMDOS HD emu)!\n"); } else if (strcmp(last, filename) != 0) { fprintf(stderr, "WARNING: given program doesn't match last program executed by GEMDOS HD emulation:\n\t%s\n", last); } fprintf(stderr, "Reading symbols from program '%s' symbol table...\n", filename); fp = fopen(filename, "rb"); list = symbols_load_binary(fp, &opts, update_sections); } else { fprintf(stderr, "Reading 'nm' style ASCII symbols from '%s'...\n", filename); fp = fopen(filename, "r"); list = symbols_load_ascii(fp, offsets, maxaddr, gettype, &opts); } fclose(fp); if (!list) { fprintf(stderr, "ERROR: reading symbols from '%s' failed!\n", filename); return NULL; } if (!list->namecount) { fprintf(stderr, "ERROR: no valid symbols in '%s', loading failed!\n", filename); symbol_list_free(list); return NULL; } if ((changed = fix_symbol_types(list))) { fprintf(stderr, "Corrected type for %d symbols (text->data).\n", changed); } /* first sort symbols by address, _with_ code symbols being first */ qsort(list->names, list->namecount, sizeof(symbol_t), symbols_by_address); /* remove symbols with duplicate addresses? */ if (opts.no_dups) { if ((dups = symbols_trim_names(list))) { fprintf(stderr, "Removed %d symbols in same addresses as other symbols.\n", dups); } } /* copy name list to address list */ list->addresses = malloc(list->namecount * sizeof(symbol_t)); assert(list->addresses); memcpy(list->addresses, list->names, list->namecount * sizeof(symbol_t)); /* "split" address list to code and other symbols */ symbols_split_addresses(list); /* finally, sort name list by names */ qsort(list->names, list->namecount, sizeof(symbol_t), symbols_by_name); /* skip more verbose output when symbols are auto-loaded */ if (ConfigureParams.Debugger.bSymbolsAutoLoad) { fprintf(stderr, "Skipping detailed duplicate symbols reporting when autoload is enabled.\n"); } else { /* check for duplicate addresses? */ if (!opts.no_dups) { if ((dups = symbols_check_addresses(list->addresses, list->namecount))) { fprintf(stderr, "%d symbols in same addresses as other symbols.\n", dups); } } /* report duplicate names */ if ((dups = symbols_check_names(list->names, list->namecount))) { fprintf(stderr, "%d symbols having multiple addresses for the same name.\n" "Symbol expansion will match only one of the addresses for them!\n", dups); } } fprintf(stderr, "Loaded %d symbols (%d for code) from '%s'.\n", list->namecount, list->codecount, filename); return list; } /** * Free read symbols. */ static void Symbols_Free(symbol_list_t* list) { symbol_list_free(list); } /** * Free all symbols (at exit). */ void Symbols_FreeAll(void) { symbol_list_free(CpuSymbolsList); symbol_list_free(DspSymbolsList); } /** * Change current CPU symbols to new ones with given type, * free old ones */ static void Symbols_UpdateCpu(symbol_list_t* list, symbols_for_t symfor) { if (CpuSymbolsList) { symbol_list_free(CpuSymbolsList); } CpuSymbolsList = list; CpuSymbolsAreFor = symfor; } /** * Change current DSP symbols to new ones, free old ones */ static void Symbols_UpdateDsp(symbol_list_t* list) { if (DspSymbolsList) { symbol_list_free(DspSymbolsList); } DspSymbolsList = list; } /* ---------------- symbol name completion support ------------------ */ /** * Helper for symbol name completion and finding their addresses. * STATE = 0 -> different text from previous one. * Return (copy of) next name or NULL if no matches. */ static char* Symbols_MatchByName(symbol_list_t* list, symtype_t symtype, const char *text, int state) { static int i, len; const symbol_t *entry; if (!list) { return NULL; } if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ entry = list->names; while (i < list->namecount) { if ((entry[i].type & symtype) && strncmp(entry[i].name, text, len) == 0) { return strdup(entry[i++].name); } else { i++; } } return NULL; } /** * Readline match callbacks for CPU symbol name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char* Symbols_MatchCpuAddress(const char *text, int state) { return Symbols_MatchByName(CpuSymbolsList, SYMTYPE_ALL, text, state); } char* Symbols_MatchCpuCodeAddress(const char *text, int state) { if (ConfigureParams.Debugger.bMatchAllSymbols) { return Symbols_MatchByName(CpuSymbolsList, SYMTYPE_ALL, text, state); } else { return Symbols_MatchByName(CpuSymbolsList, SYMTYPE_CODE, text, state); } } char* Symbols_MatchCpuDataAddress(const char *text, int state) { if (ConfigureParams.Debugger.bMatchAllSymbols) { return Symbols_MatchByName(CpuSymbolsList, SYMTYPE_ALL, text, state); } else { return Symbols_MatchByName(CpuSymbolsList, SYMTYPE_DATA|SYMTYPE_BSS, text, state); } } /** * Readline match callback to list matching CPU symbols & file names. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Symbols_MatchCpuAddrFile(const char *text, int state) { char *ret = Symbols_MatchCpuAddress(text, state); if (ret) { return ret; } return rl_filename_completion_function(text, state); } /** * Readline match callback for DSP symbol name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char* Symbols_MatchDspAddress(const char *text, int state) { return Symbols_MatchByName(DspSymbolsList, SYMTYPE_ALL, text, state); } char* Symbols_MatchDspCodeAddress(const char *text, int state) { return Symbols_MatchByName(DspSymbolsList, SYMTYPE_CODE, text, state); } char* Symbols_MatchDspDataAddress(const char *text, int state) { return Symbols_MatchByName(DspSymbolsList, SYMTYPE_DATA|SYMTYPE_BSS, text, state); } /* ---------------- symbol name -> address search ------------------ */ /** * Binary search symbol of given type by name. * Return symbol if name matches, zero otherwise. */ static const symbol_t* Symbols_SearchByName(symbol_t* entries, int count, symtype_t symtype, const char *name) { /* left, right, middle */ int l, r, m, dir; /* bisect */ l = 0; r = count - 1; do { m = (l+r) >> 1; dir = strcmp(entries[m].name, name); if (dir == 0 && (entries[m].type & symtype)) { return &(entries[m]); } if (dir > 0) { r = m-1; } else { l = m+1; } } while (l <= r); return NULL; } /** * Set given symbol's address to variable and return true if one * was found from given list. */ static bool Symbols_GetAddress(symbol_list_t* list, symtype_t symtype, const char *name, uint32_t *addr) { const symbol_t *entry; if (!(list && list->names)) { return false; } entry = Symbols_SearchByName(list->names, list->namecount, symtype, name); if (entry) { *addr = entry->address; return true; } return false; } bool Symbols_GetCpuAddress(symtype_t symtype, const char *name, uint32_t *addr) { return Symbols_GetAddress(CpuSymbolsList, symtype, name, addr); } bool Symbols_GetDspAddress(symtype_t symtype, const char *name, uint32_t *addr) { return Symbols_GetAddress(DspSymbolsList, symtype, name, addr); } /* ---------------- symbol address -> name search ------------------ */ /** * Binary search code symbol by address in given sorted list. * Return index for symbol which address matches or precedes * the given one. */ static int Symbols_SearchBeforeAddress(symbol_t* entries, int count, uint32_t addr) { /* left, right, middle */ int l, r, m; uint32_t curr; /* bisect */ l = 0; r = count - 1; do { m = (l+r) >> 1; curr = entries[m].address; if (curr == addr) { return m; } if (curr > addr) { r = m-1; } else { l = m+1; } } while (l <= r); return r; } static const char* Symbols_GetBeforeAddress(symbol_list_t *list, uint32_t *addr) { if (!(list && list->addresses)) { return NULL; } int i = Symbols_SearchBeforeAddress(list->addresses, list->codecount, *addr); if (i >= 0) { *addr = list->addresses[i].address; return list->addresses[i].name; } return NULL; } const char* Symbols_GetBeforeCpuAddress(uint32_t *addr) { return Symbols_GetBeforeAddress(CpuSymbolsList, addr); } const char* Symbols_GetBeforeDspAddress(uint32_t *addr) { return Symbols_GetBeforeAddress(DspSymbolsList, addr); } /** * Binary search symbol by address in given sorted list. * Return symbol index if address matches, -1 otherwise. * * Performance critical, called on every instruction * when profiling is enabled. */ static int Symbols_SearchByAddress(symbol_t* entries, int count, uint32_t addr) { /* left, right, middle */ int l, r, m; uint32_t curr; /* bisect */ l = 0; r = count - 1; do { m = (l+r) >> 1; curr = entries[m].address; if (curr == addr) { return m; } if (curr > addr) { r = m-1; } else { l = m+1; } } while (l <= r); return -1; } /** * Search symbol in given list by type & address. * Return symbol name if there's a match, NULL otherwise. * Code symbols will be matched before other symbol types. * Returned name is valid only until next Symbols_* function call. */ static const char* Symbols_GetByAddress(symbol_list_t* list, uint32_t addr, symtype_t type) { if (!(list && list->addresses)) { return NULL; } if (type & SYMTYPE_CODE) { int i = Symbols_SearchByAddress(list->addresses, list->codecount, addr); if (i >= 0) { return list->addresses[i].name; } } if (type & ~SYMTYPE_CODE) { int i = Symbols_SearchByAddress(list->addresses + list->codecount, list->datacount, addr); if (i >= 0) { return list->addresses[list->codecount + i].name; } } return NULL; } const char* Symbols_GetByCpuAddress(uint32_t addr, symtype_t type) { return Symbols_GetByAddress(CpuSymbolsList, addr, type); } const char* Symbols_GetByDspAddress(uint32_t addr, symtype_t type) { return Symbols_GetByAddress(DspSymbolsList, addr, type); } /** * Search given list for code symbol by address. * Return symbol index if address matches, -1 otherwise. */ static int Symbols_GetCodeIndex(symbol_list_t* list, uint32_t addr) { if (!list) { return -1; } return Symbols_SearchByAddress(list->addresses, list->codecount, addr); } int Symbols_GetCpuCodeIndex(uint32_t addr) { return Symbols_GetCodeIndex(CpuSymbolsList, addr); } int Symbols_GetDspCodeIndex(uint32_t addr) { return Symbols_GetCodeIndex(DspSymbolsList, addr); } /** * Return how many TEXT symbols are loaded/available */ int Symbols_CpuCodeCount(void) { return (CpuSymbolsList ? CpuSymbolsList->codecount : 0); } int Symbols_DspCodeCount(void) { return (DspSymbolsList ? DspSymbolsList->codecount : 0); } /* ---------------- symbol showing ------------------ */ /** * Show symbols matching (optional) 'find' string from given list, * using paging. */ static void Symbols_Show(symbol_list_t* list, const char *sortcmd, const char *find) { symbol_t *entry, *entries; const char *symtype, *sorttype; int i, row, rows, count, matches; char symchar; if (!list) { fprintf(stderr, "No symbols!\n"); return; } if (strcmp("code", sortcmd) == 0) { sorttype = "address"; entries = list->addresses; count = list->codecount; symtype = " TEXT/WEAK"; } else if (strcmp("data", sortcmd) == 0) { sorttype = "address"; entries = list->addresses + list->codecount; count = list->datacount; symtype = " DATA/BSS/ABS"; } else { sorttype = "name"; entries = list->names; count = list->namecount; symtype = ""; } rows = DebugUI_GetPageLines(ConfigureParams.Debugger.nSymbolLines, 20); row = matches = 0; for (entry = entries, i = 0; i < count; i++, entry++) { if (find && !strstr(entry->name, find)) { continue; } matches++; symchar = symbol_char(entry->type); fprintf(stderr, "0x%08x %c %s\n", entry->address, symchar, entry->name); row++; if (row >= rows) { row = 0; if (DebugUI_DoQuitQuery("symbol list")) break; } } fprintf(stderr, "%d %s%s symbols (of %d) sorted by %s.\n", matches, (list == CpuSymbolsList ? "CPU" : "DSP"), symtype, count, sorttype); } /* ---------------- binary load handling ------------------ */ /** * If autoloading is enabled and program symbols are present, * remove them along with program path. * * Called on GEMDOS reset and when program terminates * (unless terminated with Ptermres()). */ void Symbols_RemoveCurrentProgram(void) { if (CurrentProgramPath) { free(CurrentProgramPath); CurrentProgramPath = NULL; if (CpuSymbolsList && CpuSymbolsAreFor == SYMBOLS_FOR_PROGRAM && ConfigureParams.Debugger.bSymbolsAutoLoad) { Symbols_Free(CpuSymbolsList); fprintf(stderr, "Program exit, removing its symbols.\n"); CpuSymbolsAreFor = SYMBOLS_FOR_NONE; CpuSymbolsList = NULL; } } AutoLoadFailed = false; } /** * Call Symbols_RemoveCurrentProgram() and * set last opened program path. * * Called on first Fopen() after Pexec(). */ void Symbols_ChangeCurrentProgram(const char *path) { if (Opt_IsAtariProgram(path)) { Symbols_RemoveCurrentProgram(); CurrentProgramPath = strdup(path); } } /* * Show currently set program path */ void Symbols_ShowCurrentProgramPath(FILE *fp) { if (CurrentProgramPath) { fprintf(fp, "Current program path: %s\n", CurrentProgramPath); } else { fputs("No program has been loaded (through GEMDOS HD).\n", fp); } } /** * Autoload helper. Given the base file name with .XXX extension, * if there's another file with .sym extension, load symbols from it, * and return them. * * Assumes all (relevant) sections use the same load address. */ static symbol_list_t *loadSymFile(const char *path, symtype_t symtype, uint32_t loadaddr, uint32_t maxaddr) { char symfile[PATH_MAX]; size_t len = strlen(path); if (len <= 3 || path[len-4] != '.' || len >= sizeof(symfile)) { return NULL; } strcpy(symfile, path); strcpy(symfile + len - 3, "sym"); if (!File_Exists(symfile)) { return NULL; } fprintf(stderr, "Loading sym file: %s\n", symfile); uint32_t offsets[3] = { loadaddr, loadaddr, loadaddr }; return Symbols_Load(symfile, offsets, maxaddr, symtype); } /** * Load symbols for last opened program when symbol autoloading is enabled. * * If there's file with same name as the program, but with '.sym' * extension, that overrides / is loaded instead of the symbol table * in the program. * * Called when debugger is invoked. */ void Symbols_LoadCurrentProgram(void) { if (!ConfigureParams.Debugger.bSymbolsAutoLoad) { return; } /* program path missing or previous load failed? */ if (!CurrentProgramPath || AutoLoadFailed) { return; } /* do not override manually loaded symbols, or * load new symbols if previous program did not terminate. * Autoloaded TOS symbols could be overridden though */ if (CpuSymbolsList && CpuSymbolsAreFor != SYMBOLS_FOR_TOS) { return; } uint32_t loadaddr = DebugInfo_GetTEXT(); uint32_t maxaddr = DebugInfo_GetTEXTEnd(); symbol_list_t *symbols; symbols = loadSymFile(CurrentProgramPath, SYMTYPE_CODE, loadaddr, maxaddr); if (symbols) { fprintf(stderr, "Symbols override loaded for: %s\n", CurrentProgramPath); } else { symbols = Symbols_Load(CurrentProgramPath, NULL, 0, SYMTYPE_CODE); } if (!symbols) { AutoLoadFailed = true; return; } Symbols_UpdateCpu(symbols, SYMBOLS_FOR_PROGRAM); AutoLoadFailed = false; } /** * If autoloading enabled and no symbols are present, load symbols * for ".img" file from ".sym" file, if one exists. * * Called whenever TOS is loaded. */ void Symbols_LoadTOS(const char *path, uint32_t maxaddr) { if (!ConfigureParams.Debugger.bSymbolsAutoLoad) { return; } /* do not override manually loaded symbols */ if (CpuSymbolsList && CpuSymbolsAreFor == SYMBOLS_FOR_USER) { return; } symbol_list_t *symbols; symbols = loadSymFile(path, SYMTYPE_ALL, 0, maxaddr); if (symbols) { fprintf(stderr, "Loaded symbols for TOS: %s\n", path); Symbols_UpdateCpu(symbols, SYMBOLS_FOR_TOS); } } /* ---------------- command parsing ------------------ */ /** * Readline match callback for CPU symbols command. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Symbols_MatchCpuCommand(const char *text, int state) { static const char* subs[] = { "autoload", "code", "data", "free", "match", "name", "prg" }; char *ret = DebugUI_MatchHelper(subs, ARRAY_SIZE(subs), text, state); if (ret) { return ret; } if ((ret = Symbols_MatchCpuAddress(text, state))) { return ret; } return rl_filename_completion_function(text, state); } /** * Readline match callback to list DSP symbols command. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Symbols_MatchDspCommand(const char *text, int state) { static const char* subs[] = { "code", "data", "free", "match", "name" }; char *ret = DebugUI_MatchHelper(subs, ARRAY_SIZE(subs), text, state); if (ret) { return ret; } if ((ret = Symbols_MatchDspAddress(text, state))) { return ret; } return rl_filename_completion_function(text, state); } const char Symbols_Description[] = " [find] -- list symbols containing 'find'\n" "\tsymbols -- load/free symbols\n" "\t [ [ ]]\n" "\tsymbols -- toggle symbol options\n" "\n" "\t'name' command lists the currently loaded symbols, sorted by name.\n" "\t'code' and 'data' commands list them sorted by address; 'code' lists\n" "\tonly TEXT/WEAK symbols, 'data' lists DATA/BSS/ABS symbols. If 'find'\n" "\tis given, only symbols with that substring are listed.\n" "\n" "\tBy default, symbols are loaded from the currently executing program's\n" "\tbinary when entering the debugger, IF program is started through\n" "\tGEMDOS HD, and they're freed when that program terminates.\n" "\n" "\tThat corresponds to 'prg' command which loads (DRI/GST or a.out\n" "\tformat) symbol table from the last program executed through\n" "\tthe GEMDOS HD emulation.\n" "\n" "\t'free' command removes the loaded symbols.\n" "\n" "\tIf program lacks symbols, or it's not run through the GEMDOS HD\n" "\temulation, user can ask symbols to be loaded from a file that's\n" "\tan unstripped version of the binary. Or from an ASCII symbols file\n" "\tproduced by the 'nm' and (Hatari) 'gst2ascii' tools.\n" "\n" "\tWith ASCII symbols files, given non-zero offset(s) are added to\n" "\tthe text (T), data (D) and BSS (B) symbols. Typically one uses\n" "\tTEXT variable, sometimes also DATA & BSS, variables for this.\n" "\n" "\t'autoload [on|off]' command toggle/set whether debugger will load\n" "\tsymbols for currently executing (GEMDOS HD) program automatically\n" "\ton entering the debugger (i.e. replace earlier loaded symbols),\n" "\tand free them when program terminates. It needs to be disabled\n" "\tto debug memory-resident programs used by other programs.\n" "\n" "\t'match' command toggles whether TAB completion matches all symbols,\n" "\tor only symbol types that should be relevant for given command."; /** * Handle debugger 'symbols' command and its arguments */ int Symbols_Command(int nArgc, char *psArgs[]) { enum { TYPE_CPU, TYPE_DSP } listtype; uint32_t offsets[3], maxaddr; symbol_list_t *list; const char *file; int i; if (strcmp("dspsymbols", psArgs[0]) == 0) { listtype = TYPE_DSP; maxaddr = 0xFFFF; } else { listtype = TYPE_CPU; if ( ConfigureParams.System.bAddressSpace24 ) maxaddr = 0x00FFFFFF; else maxaddr = 0xFFFFFFFF; } if (nArgc < 2) { file = "name"; } else { file = psArgs[1]; } /* set whether to autoload symbols on program start and * discard them when program terminates with GEMDOS HD, * or whether they need to be loaded manually. */ if (listtype == TYPE_CPU && strcmp(file, "autoload") == 0) { bool value; if (nArgc < 3) { value = !ConfigureParams.Debugger.bSymbolsAutoLoad; } else if (strcmp(psArgs[2], "on") == 0) { value = true; } else if (strcmp(psArgs[2], "off") == 0) { value = false; } else { DebugUI_PrintCmdHelp(psArgs[0]); return DEBUGGER_CMDDONE; } fprintf(stderr, "Program symbols auto-loading AND freeing (with GEMDOS HD) is %s\n", value ? "ENABLED." : "DISABLED!"); ConfigureParams.Debugger.bSymbolsAutoLoad = value; return DEBUGGER_CMDDONE; } /* toggle whether all or only specific symbols types get TAB completed? */ if (strcmp(file, "match") == 0) { ConfigureParams.Debugger.bMatchAllSymbols = !ConfigureParams.Debugger.bMatchAllSymbols; if (ConfigureParams.Debugger.bMatchAllSymbols) { fprintf(stderr, "Matching all symbols types.\n"); } else { fprintf(stderr, "Matching only symbols (most) relevant for given command.\n"); } return DEBUGGER_CMDDONE; } /* show requested symbol types in requested order? */ if (strcmp(file, "name") == 0 || strcmp(file, "code") == 0 || strcmp(file, "data") == 0) { const char *find = nArgc > 2 ? psArgs[2] : NULL; list = (listtype == TYPE_DSP ? DspSymbolsList : CpuSymbolsList); Symbols_Show(list, file, find); return DEBUGGER_CMDDONE; } /* free symbols? */ if (strcmp(file, "free") == 0) { if (listtype == TYPE_DSP) { Symbols_Free(DspSymbolsList); DspSymbolsList = NULL; } else { Symbols_Free(CpuSymbolsList); CpuSymbolsList = NULL; } return DEBUGGER_CMDDONE; } /* get offsets */ offsets[0] = 0; for (i = 0; i < ARRAY_SIZE(offsets); i++) { if (i+2 < nArgc) { int dummy; Eval_Expression(psArgs[i+2], &(offsets[i]), &dummy, listtype==TYPE_DSP); } else { /* default to first (text) offset */ offsets[i] = offsets[0]; } } /* load symbols from GEMDOS HD program? */ if (listtype == TYPE_CPU && strcmp(file, "prg") == 0) { file = CurrentProgramPath; if (!file) { fprintf(stderr, "ERROR: no program loaded (through GEMDOS HD emu)!\n"); return DEBUGGER_CMDDONE; } } /* do actual loading */ list = Symbols_Load(file, offsets, maxaddr, SYMTYPE_ALL); if (list) { if (listtype == TYPE_CPU) { Symbols_UpdateCpu(list, SYMBOLS_FOR_USER); } else { Symbols_UpdateDsp(list); } } else { DebugUI_PrintCmdHelp(psArgs[0]); } return DEBUGGER_CMDDONE; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/symbols.h000066400000000000000000000051031504763705000242560ustar00rootroot00000000000000/* * Hatari - symbols.h * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_SYMBOLS_H #define HATARI_SYMBOLS_H typedef enum { SYMTYPE_TEXT = 1, SYMTYPE_WEAK = 2, /* weakly aliased _code_ symbols */ SYMTYPE_CODE = (SYMTYPE_TEXT|SYMTYPE_WEAK), /* other types get sorted after code types */ SYMTYPE_DATA = 4, SYMTYPE_BSS = 8, SYMTYPE_ABS = 16, SYMTYPE_ALL = 32-1 } symtype_t; typedef struct { char *name; uint32_t address; symtype_t type; bool name_allocated; } symbol_t; extern const char Symbols_Description[]; /* readline completion support functions for CPU */ extern char* Symbols_MatchCpuAddress(const char *text, int state); extern char* Symbols_MatchCpuCodeAddress(const char *text, int state); extern char* Symbols_MatchCpuDataAddress(const char *text, int state); extern char *Symbols_MatchCpuAddrFile(const char *text, int state); /* readline completion support functions for DSP */ extern char* Symbols_MatchDspAddress(const char *text, int state); extern char* Symbols_MatchDspCodeAddress(const char *text, int state); extern char* Symbols_MatchDspDataAddress(const char *text, int state); /* symbol name -> address search */ extern bool Symbols_GetCpuAddress(symtype_t symtype, const char *name, uint32_t *addr); extern bool Symbols_GetDspAddress(symtype_t symtype, const char *name, uint32_t *addr); /* symbol address -> name search */ extern const char* Symbols_GetByCpuAddress(uint32_t addr, symtype_t symtype); extern const char* Symbols_GetByDspAddress(uint32_t addr, symtype_t symtype); extern const char* Symbols_GetBeforeCpuAddress(uint32_t *addr); extern const char* Symbols_GetBeforeDspAddress(uint32_t *addr); /* TEXT symbol address -> index */ extern int Symbols_GetCpuCodeIndex(uint32_t addr); extern int Symbols_GetDspCodeIndex(uint32_t addr); /* how many TEXT symbols are loaded */ extern int Symbols_CpuCodeCount(void); extern int Symbols_DspCodeCount(void); /* handlers for automatic program symbol loading */ extern void Symbols_RemoveCurrentProgram(void); extern void Symbols_ChangeCurrentProgram(const char *path); extern void Symbols_ShowCurrentProgramPath(FILE *fp); extern void Symbols_LoadCurrentProgram(void); extern void Symbols_LoadTOS(const char *path, uint32_t maxaddr); extern void Symbols_FreeAll(void); /* symbols/dspsymbols command parsing */ extern char *Symbols_MatchCpuCommand(const char *text, int state); extern char *Symbols_MatchDspCommand(const char *text, int state); extern int Symbols_Command(int nArgc, char *psArgs[]); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/vars.c000066400000000000000000000217441504763705000235450ustar00rootroot00000000000000/* Hatari - vars.c Copyright (c) 2016 by Eero Tamminen This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. vars.c - Hatari internal variable value and OS call number accessors for conditional breakpoint and evaluate commands. */ const char Vars_fileid[] = "Hatari vars.c"; #include #include #include "main.h" #include "configuration.h" #include "stMemory.h" #include "m68000.h" #include "video.h" /* for Hatari video variable addresses */ #include "hatari-glue.h" /* for currprefs */ #include "debugInfo.h" #include "debugcpu.h" #include "debugdsp.h" #include "debugui.h" #include "symbols.h" #include "68kDisass.h" #include "vars.h" static uint32_t GetCycleCounter(void) { /* 64-bit, so only lower 32-bits are returned */ return CyclesGlobalClockCounter; } /* Accessor functions for calculated Hatari values */ static uint32_t GetLineCycles(void) { int dummy1, dummy2, lcycles; Video_GetPosition(&dummy1, &dummy2 , &lcycles); return lcycles; } static uint32_t GetFrameCycles(void) { int dummy1, dummy2, fcycles; Video_GetPosition(&fcycles, &dummy1, &dummy2); return fcycles; } /* helpers for TOS OS call opcode accessor functions */ static inline uint16_t getLineOpcode(uint8_t line) { uint32_t pc; uint16_t instr; pc = M68000_GetPC(); instr = STMemory_ReadWord(pc); /* for opcode X, Line-A = 0xA00X, Line-F = 0xF00X */ if ((instr >> 12) == line) { return instr & 0xFF; } return INVALID_OPCODE; } static inline bool isTrap(uint8_t trap) { uint32_t pc; uint16_t instr; pc = M68000_GetPC(); instr = STMemory_ReadWord(pc); return (instr == (uint16_t)0x4e40u + trap); } static inline uint16_t getControlOpcode(void) { /* Control[] address from D1, opcode in Control[0] */ return STMemory_ReadWord(STMemory_ReadLong(Regs[REG_D1])); } static inline uint16_t getStackOpcode(void) { return STMemory_ReadWord(Regs[REG_A7]); } /* Actual TOS OS call opcode accessor functions */ static uint32_t GetLineAOpcode(void) { return getLineOpcode(0xA); } static uint32_t GetLineFOpcode(void) { return getLineOpcode(0xF); } static uint32_t GetGemdosOpcode(void) { if (isTrap(1)) { return getStackOpcode(); } return INVALID_OPCODE; } static uint32_t GetBiosOpcode(void) { if (isTrap(13)) { return getStackOpcode(); } return INVALID_OPCODE; } static uint32_t GetXbiosOpcode(void) { if (isTrap(14)) { return getStackOpcode(); } return INVALID_OPCODE; } uint32_t Vars_GetAesOpcode(void) { if (isTrap(2)) { uint16_t d0 = Regs[REG_D0]; if (d0 == 0xC8) { return getControlOpcode(); } else if (d0 == 0xC9) { /* same as appl_yield() */ return 0x11; } } return INVALID_OPCODE; } uint32_t Vars_GetVdiOpcode(void) { if (isTrap(2)) { uint16_t d0 = Regs[REG_D0]; if (d0 == 0x73) { return getControlOpcode(); } else if (d0 == 0xFFFE) { /* -2 = vq_[v]gdos() */ return 0xFFFE; } } return INVALID_OPCODE; } /** return 1 if PC is on Symbol, 0 otherwise */ static uint32_t PConSymbol(void) { const char *sym; uint32_t pc = M68000_GetPC(); sym = Symbols_GetByCpuAddress(pc, SYMTYPE_CODE); if (sym) { return 1; } return 0; } /** return first word in OS call parameters */ static uint32_t GetOsCallParam(void) { /* skip OS call opcode */ return STMemory_ReadWord(Regs[REG_A7]+SIZE_WORD); } static uint32_t GetNextPC(void) { return Disasm_GetNextPC(M68000_GetPC()); } /* sorted by variable name so that this can be bisected */ static const var_addr_t hatari_vars[] = { { "AesOpcode", (uint32_t*)Vars_GetAesOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on AES trap" }, { "Basepage", (uint32_t*)DebugInfo_GetBASEPAGE, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "BiosOpcode", (uint32_t*)GetBiosOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on BIOS trap" }, { "BSS", (uint32_t*)DebugInfo_GetBSS, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "CpuCallDepth", (uint32_t*)DebugCpu_CallDepth, VALUE_TYPE_FUNCTION32, 0, NULL }, /* call depth for 'next subreturn' */ { "CpuInstr", (uint32_t*)DebugCpu_InstrCount, VALUE_TYPE_FUNCTION32, 0, "CPU instructions count" }, { "CpuOpcodeType", (uint32_t*)DebugCpu_OpcodeType, VALUE_TYPE_FUNCTION32, 0, NULL }, /* opcode type for 'next' */ { "CycleCounter", (uint32_t*)GetCycleCounter, VALUE_TYPE_FUNCTION32, 0, "global cycles counter (lower 32 bits)" }, { "DATA", (uint32_t*)DebugInfo_GetDATA, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, #if ENABLE_DSP_EMU { "DspCallDepth", (uint32_t*)DebugDsp_CallDepth, VALUE_TYPE_FUNCTION32, 0, NULL }, /* call depth for 'dspnext subreturn' */ { "DspInstr", (uint32_t*)DebugDsp_InstrCount, VALUE_TYPE_FUNCTION32, 0, "DSP instructions count" }, { "DspOpcodeType", (uint32_t*)DebugDsp_OpcodeType, VALUE_TYPE_FUNCTION32, 0, NULL }, /* opcode type for 'dspnext' */ #endif { "FrameCycles", (uint32_t*)GetFrameCycles, VALUE_TYPE_FUNCTION32, 0, "cycles since VBL" }, { "GemdosOpcode", (uint32_t*)GetGemdosOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on GEMDOS trap" }, { "HBL", (uint32_t*)&nHBL, VALUE_TYPE_VAR32, sizeof(nHBL)*8, "number of HBL interrupts" }, { "LineAOpcode", (uint32_t*)GetLineAOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on Line-A opcode" }, { "LineCycles", (uint32_t*)GetLineCycles, VALUE_TYPE_FUNCTION32, 0, "cycles since HBL (divisible by 4)" }, { "LineFOpcode", (uint32_t*)GetLineFOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on Line-F opcode" }, { "NextPC", (uint32_t*)GetNextPC, VALUE_TYPE_FUNCTION32, 0, "Next instruction address" }, { "OsCallParam", (uint32_t*)GetOsCallParam, VALUE_TYPE_FUNCTION32, 16, "valid only on OS call opcode breakpoint" }, { "PConSymbol", (uint32_t*)PConSymbol, VALUE_TYPE_FUNCTION32, 16, "1 if PC on symbol, 0 otherwise" }, { "TEXT", (uint32_t*)DebugInfo_GetTEXT, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "TEXTEnd", (uint32_t*)DebugInfo_GetTEXTEnd, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "VBL", (uint32_t*)&nVBLs, VALUE_TYPE_VAR32, sizeof(nVBLs)*8, "number of VBL interrupts" }, { "VdiOpcode", (uint32_t*)Vars_GetVdiOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on VDI trap" }, { "XbiosOpcode", (uint32_t*)GetXbiosOpcode, VALUE_TYPE_FUNCTION32, 16, "$FFFF when not on XBIOS trap" } }; /** * Readline match callback for Hatari variable and CPU variable/symbol name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Vars_MatchCpuVariable(const char *text, int state) { static int i, len; const char *name; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i < ARRAY_SIZE(hatari_vars)) { name = hatari_vars[i++].name; if (strncasecmp(name, text, len) == 0) return (strdup(name)); } /* no variable match, check all CPU symbols */ return Symbols_MatchCpuAddress(text, state); } /** * If given string matches Hatari variable name, return its struct pointer, * otherwise return NULL. */ const var_addr_t *Vars_ParseVariable(const char *name) { const var_addr_t *hvar; /* left, right, middle, direction */ int l, r, m, dir; /* bisect */ l = 0; r = ARRAY_SIZE(hatari_vars) - 1; do { m = (l+r) >> 1; hvar = hatari_vars + m; dir = strcasecmp(name, hvar->name); if (dir == 0) { return hvar; } if (dir < 0) { r = m-1; } else { l = m+1; } } while (l <= r); return NULL; } /** * Return uint32_t value from given Hatari variable struct* */ uint32_t Vars_GetValue(const var_addr_t *hvar) { switch (hvar->vtype) { case VALUE_TYPE_FUNCTION32: return ((uint32_t(*)(void))(hvar->addr))(); case VALUE_TYPE_VAR32: return *(hvar->addr); default: fprintf(stderr, "ERROR: variable '%s' has unsupported type '%d'\n", hvar->name, hvar->vtype); exit(-1); } } /** * If given string is a Hatari variable name, set value to given * variable's value and return true, otherwise return false. */ bool Vars_GetVariableValue(const char *name, uint32_t *value) { const var_addr_t *hvar; if (!(hvar = Vars_ParseVariable(name))) { return false; } *value = Vars_GetValue(hvar); return true; } /** * List Hatari variable names & current values */ int Vars_List(int nArgc, char *psArgv[]) { uint32_t value; char numstr[16]; int i, maxlen = 0; for (i = 0; i < ARRAY_SIZE(hatari_vars); i++) { int len = strlen(hatari_vars[i].name); if (len > maxlen) { maxlen = len; } } fputs("Hatari debugger builtin symbols and their values are:\n", stderr); for (i = 0; i < ARRAY_SIZE(hatari_vars); i++) { const var_addr_t *hvar = &hatari_vars[i]; if (!hvar->info) { /* debugger internal variables don't have descriptions */ continue; } value = Vars_GetValue(hvar); if (hvar->bits == 16) { fprintf(stderr, " %*s: $%04X", maxlen, hvar->name, value); } else { fprintf(stderr, " %*s: $%08X", maxlen, hvar->name, value); } sprintf(numstr, "(%d)", value); fprintf(stderr, " %-*s %s\n", 12, numstr, hvar->info); } fputs("Some of the variables are valid only in specific situations.\n", stderr); return DEBUGGER_CMDDONE; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/debug/vars.h000066400000000000000000000027761504763705000235560ustar00rootroot00000000000000/* Hatari - vars.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_VARS_H #define HATARI_VARS_H typedef enum { /* plain number */ VALUE_TYPE_NUMBER = 0, /* functions to call to get value */ VALUE_TYPE_FUNCTION32 = 2, /* internal Hatari value variables */ VALUE_TYPE_VAR32 = 4, /* size must match register size used in BreakCond_ParseRegister() */ VALUE_TYPE_REG16 = 16, VALUE_TYPE_REG32 = 32 } value_t; /* Hatari variable name & address array items */ typedef struct { const char *name; uint32_t *addr; value_t vtype; size_t bits; const char *info; /* NULL for debugger internal variables */ } var_addr_t; /* variables + CPU symbols readline callback for breakpoints & evaluate */ extern char *Vars_MatchCpuVariable(const char *text, int state); /* pointer to variable struct with given variable name */ extern const var_addr_t *Vars_ParseVariable(const char *name); /* get variable value for given variable name */ bool Vars_GetVariableValue(const char *name, uint32_t *value); /* get variable value for given variable struct */ extern uint32_t Vars_GetValue(const var_addr_t *hvar); /* list variables & their names */ extern int Vars_List(int nArgc, char *psArgv[]); /* opcode functions return for no opcode */ #define INVALID_OPCODE 0xFFFFu /* Whether on AES/VDI trap */ extern uint32_t Vars_GetAesOpcode(void); extern uint32_t Vars_GetVdiOpcode(void); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/dialog.c000066400000000000000000000043051504763705000227350ustar00rootroot00000000000000/* Hatari - dialog.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Code to handle our options dialog. */ const char Dialog_fileid[] = "Hatari dialog.c"; #include "main.h" #include "configuration.h" #include "change.h" #include "dialog.h" #include "log.h" #include "sdlgui.h" #include "screen.h" /*-----------------------------------------------------------------------*/ /** * Open Property sheet Options dialog. * * We keep all our configuration details in a structure called * 'ConfigureParams'. When we open our dialog we make a backup * of this structure. When the user finally clicks on 'OK', * we can compare and makes the necessary changes. * * Return true if user chooses OK, or false if cancel! */ bool Dialog_DoProperty(void) { bool bOKDialog; /* Did user 'OK' dialog? */ bool bForceReset; bool bLoadedSnapshot; CNF_PARAMS current; bool bOldMouseMode = SDL_GetRelativeMouseMode(); SDL_SetRelativeMouseMode(SDL_FALSE); Main_PauseEmulation(true); bForceReset = false; /* Copy details (this is so can restore if 'Cancel' dialog) */ current = ConfigureParams; ConfigureParams.Screen.bFullScreen = bInFullScreen; bOKDialog = Dialog_MainDlg(&bForceReset, &bLoadedSnapshot); SDL_SetRelativeMouseMode(bOldMouseMode); /* If a memory snapshot has been loaded, no further changes are required */ if (bLoadedSnapshot) { Main_UnPauseEmulation(); return true; } /* Check if reset is required and ask user if he really wants to continue then */ if (bOKDialog && !bForceReset && Change_DoNeedReset(¤t, &ConfigureParams) && ConfigureParams.Log.nAlertDlgLogLevel > LOG_FATAL) { bOKDialog = DlgAlert_Query("The emulated system must be " "reset to apply these changes. " "Apply changes now and reset " "the emulator?"); } /* Copy details to configuration */ if (bOKDialog) { Change_CopyChangedParamsToConfiguration(¤t, &ConfigureParams, bForceReset); } else { ConfigureParams = current; } Main_UnPauseEmulation(); if (bQuitProgram) Main_RequestQuit(0); return bOKDialog; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/dim.c000066400000000000000000000114151504763705000222470ustar00rootroot00000000000000/* Hatari - dim.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. DIM disk image support. */ const char DIM_fileid[] = "Hatari dim.c"; #include "main.h" #if HAVE_ZLIB_H #include #endif #include "file.h" #include "floppy.h" #include "dim.h" #undef SAVE_TO_DIM_IMAGES /* .DIM FILE FORMAT --===============------------------------------------------------------------- The file format of normal .DIM image files are quite the same as the .ST image files (see st.c) - the .DIM image files just have an additional header of 32 bytes. However, there are also "compressed" images which only contain the used sectors of the disk. It is necessary to parse the FAT to "uncompress" these images. The header contains following information: Offset Size Description ------ -------- ----------- 0x0000 Word ID Header (0x4242('BB')) 0x0002 Byte 1 = disk configuration has been detected automatically 0 = the user specified the disk configuration 0x0003 Byte Image contains all sectors (0) or only used sectors (1) 0x0006 Byte Sides (0 or 1; add 1 to this to get correct number of sides) 0x0008 Byte Sectors per track 0x000A Byte Starting Track (0 based) 0x000C Byte Ending Track (0 based) 0x000D Byte Double-Density(0) or High-Density (1) 0x000E 18 Bytes A copy of the Bios Parameter Block (BPB) of this disk. */ /*-----------------------------------------------------------------------*/ /** * Does filename end with a .DIM extension? If so, return TRUE */ bool DIM_FileNameIsDIM(const char *pszFileName, bool bAllowGZ) { return(File_DoesFileExtensionMatch(pszFileName,".dim") || (bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".dim.gz"))); } /*-----------------------------------------------------------------------*/ /** * Load .DIM file into memory, set number of bytes loaded and return a pointer * to the buffer. */ uint8_t *DIM_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType) { uint8_t *pDimFile; uint8_t *pDiskBuffer = NULL; /* Load file into buffer */ pDimFile = File_Read(pszFileName, pImageSize, NULL); if (pDimFile) { /* Check header for valid image: */ if (pDimFile[0x00] != 0x42 || pDimFile[0x01] != 0x42 || pDimFile[0x03] != 0 || pDimFile[0x0A] != 0) { fprintf(stderr, "This is not a valid DIM image!\n"); *pImageSize = 0; free(pDimFile); return NULL; } /* Simply use disk contents without the DIM header: */ *pImageSize -= 32; pDiskBuffer = malloc(*pImageSize); if (pDiskBuffer) memcpy(pDiskBuffer, pDimFile+32, *pImageSize); else perror("DIM_ReadDisk"); /* Free DIM file we loaded */ free(pDimFile); } if (pDiskBuffer == NULL) { *pImageSize = 0; return NULL; } *pImageType = FLOPPY_IMAGE_TYPE_DIM; return pDiskBuffer; } /*-----------------------------------------------------------------------*/ /** * Save .DIM file from memory buffer. Returns TRUE is all OK */ bool DIM_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize) { #ifdef SAVE_TO_DIM_IMAGES unsigned short int nSectorsPerTrack, nSides; uint8_t *pDimFile; int nTracks; bool bRet; #if HAVE_LIBZ gzFile hGzFile; #else FILE *fhdl; #endif /* Allocate memory for the whole DIM image: */ pDimFile = malloc(ImageSize + 32); if (!pDimFile) { perror("DIM_WriteDisk"); return false; } memset(pDimFile, 0, 32); /* Try to load the old header data to preserve the header fields that are unknown yet: */ #if HAVE_LIBZ hGzFile = gzopen(pszFileName, "rb"); if (hGzFile != NULL) { gzread(hGzFile, pDimFile, 32); gzclose(hGzFile); } #else fhdl = fopen(pszFileName, "rb"); if (fhndl != NULL) { fread(pDimFile, 32, 1, fhndl); fclose(fhndl); } #endif /* Now fill in the new header information: */ Floppy_FindDiskDetails(pBuffer, ImageSize, &nSectorsPerTrack, &nSides); nTracks = ((ImageSize / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides; pDimFile[0x00] = pDimFile[0x01] = 0x42; /* ID */ pDimFile[0x03] = 0; /* Image contains all sectors */ pDimFile[0x06] = nSides - 1; /* Sides */ pDimFile[0x08] = nSectorsPerTrack; /* Sectors per track */ pDimFile[0x0A] = 0; /* Starting track */ pDimFile[0x0C] = nTracks - 1; /* Ending track */ pDimFile[0x0D] = (ImageSize > 1024*1024); /* DD / HD flag */ /* Now copy the disk data: */ memcpy(pDimFile + 32, pBuffer, ImageSize); /* And finally save it: */ bRet = File_Save(pszFileName, pDimFile, ImageSize + 32, false); free(pDimFile); return bRet; #else /*SAVE_TO_ST_IMAGES*/ /* Oops, cannot save */ return false; #endif /*SAVE_TO_ST_IMAGES*/ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/dmaSnd.c000066400000000000000000001443671504763705000227210ustar00rootroot00000000000000/* Hatari - dmaSnd.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. STE DMA sound emulation. Does not seem to be very hard at first glance, but since the DMA sound has to be mixed together with the PSG sound and the output frequency of the host computer differs from the DMA sound frequency, the copy function is a little bit complicated. The update function also triggers the ST interrupts (Timer A and MFP-i7) which are often used in ST programs for setting a new sound frame after the old one has finished. To support programs that write into the frame buffer while it's played, we should update dma sound on each video HBL. This is also how it works on a real STE : bytes are read by the DMA at the end of each HBL and stored in a small FIFO (8 bytes) that is sent to the DAC depending on the chosen DMA output freq. Falcon sound emulation is all taken into account in crossbar.c Hardware I/O registers: $FF8900 (word) : DMA sound control register $FF8903 (byte) : Frame Start Hi $FF8905 (byte) : Frame Start Mi $FF8907 (byte) : Frame Start Lo $FF8909 (byte) : Frame Count Hi $FF890B (byte) : Frame Count Mi $FF890D (byte) : Frame Count Lo $FF890F (byte) : Frame End Hi $FF8911 (byte) : Frame End Mi $FF8913 (byte) : Frame End Lo $FF8920 (word) : Sound Mode Control (frequency, mono/stereo) $FF8922 (word) : Microwire Data Register $FF8924 (word) : Microwire Mask Register The Microwire and LMC 1992 commands : a command looks like: 10 CCC DDD DDD chipset address : 10 command : 000 XXX XDD Mixing 00 : DMA sound only 01 : DMA sound + input 1 (YM2149 + AUDIOI, full frequency range) 10 : DMA sound + input 2 (YM2149 + AUDIOI, Low Pass Filter) -> DMA sound only 11 : DMA sound + input 3 (not connected) -> DMA sound only 001 XXD DDD Bass 0 000 : -12 dB 0 110 : 0 dB 1 100 : +12 dB 002 XXD DDD Treble 0 000 : -12 dB 0 110 : 0 dB 1 100 : +12 dB 003 DDD DDD Master volume 000 000 : -80 dB 010 100 : -40 dB 101 XXX : 0 dB 004 XDD DDD Right channel volume 00 000 : -40 dB 01 010 : -20 dB 10 1XX : 0 dB 005 XDD DDD Left channel volume 00 000 : -40 dB 01 010 : -20 dB 10 1XX : 0 dB Other : undefined LMC1992 IIR code Copyright by David Savinkoff 2010 A first order bass filter is multiplied with a first order treble filter to make a single second order IIR shelf filter. Sound is stereo filtered by Boosting or Cutting the Bass and Treble by +/-12dB in 2dB steps. This filter sounds exactly as the Atari TT or STE. Sampling frequency = selectable Bass turnover = 118.276Hz (8.2nF on LM1992 bass) Treble turnover = 8438.756Hz (8.2nF on LM1992 treble) */ const char DmaSnd_fileid[] = "Hatari dmaSnd.c"; #include /* Required for M_PI */ #include "main.h" #include "audio.h" #include "configuration.h" #include "dmaSnd.h" #include "cycles.h" #include "cycInt.h" #include "ioMem.h" #include "log.h" #include "memorySnapShot.h" #include "mfp.h" #include "sound.h" #include "stMemory.h" #include "crossbar.h" #include "video.h" #include "m68000.h" #include "clocks_timings.h" #define TONE_STEPS 13 #define DMASND_FIFO_SIZE 8 /* 8 bytes : size of the DMA Audio's FIFO, filled on every HBL */ #define DMASND_FIFO_SIZE_MASK (DMASND_FIFO_SIZE-1) /* mask to keep FIFO_pos in 0-7 range */ /* Global variables that can be changed/read from other parts of Hatari */ static void DmaSnd_Apply_LMC(int nMixBufIdx, int nSamplesToGenerate); static void DmaSnd_Set_Tone_Level(int set_bass, int set_treb); static float DmaSnd_IIRfilterL(float xn); static float DmaSnd_IIRfilterR(float xn); static struct first_order_s *DmaSnd_Treble_Shelf(float g, float fc, float Fs); static struct first_order_s *DmaSnd_Bass_Shelf(float g, float fc, float Fs); static int16_t DmaSnd_LowPassFilterLeft(int16_t in); static int16_t DmaSnd_LowPassFilterRight(int16_t in); static bool DmaSnd_LowPass; uint16_t nDmaSoundControl; /* Sound control register */ struct first_order_s { float a1, b0, b1; }; struct second_order_s { float a1, a2, b0, b1, b2; }; struct dma_s { uint16_t soundMode; /* Sound mode register */ uint32_t frameStartAddr; /* Sound frame start */ uint32_t frameEndAddr; /* Sound frame end */ uint32_t frameCounterAddr; /* Sound frame current address counter */ /* Internal 8 byte FIFO */ int8_t FIFO[ DMASND_FIFO_SIZE ]; uint16_t FIFO_Pos; /* from 0 to DMASND_FIFO_SIZE-1 */ uint16_t FIFO_NbBytes; /* from 0 to DMASND_FIFO_SIZE */ int16_t FrameLeft; /* latest values read from the FIFO */ int16_t FrameRight; uint8_t XSINT_Signal; /* Value of the XSINT signal (connected to MFP) */ }; static int64_t frameCounter_float = 0; static bool DmaInitSample = false; struct microwire_s { uint16_t data; /* Microwire Data register */ uint16_t mask; /* Microwire Mask register */ uint16_t mwTransferSteps; /* Microwire shifting counter */ uint16_t pendingCyclesOver; /* Number of delayed cycles for the interrupt */ uint16_t mixing; /* Mixing command */ uint16_t bass; /* Bass command */ uint16_t treble; /* Treble command */ uint16_t masterVolume; /* Master volume command */ uint16_t leftVolume; /* Left channel volume command */ uint16_t rightVolume; /* Right channel volume command */ }; struct lmc1992_s { struct first_order_s bass_table[TONE_STEPS]; struct first_order_s treb_table[TONE_STEPS]; float coef[5]; /* IIR coefficients */ float left_gain; float right_gain; }; static struct dma_s dma; static struct microwire_s microwire; static struct lmc1992_s lmc1992; /* dB = 20log(gain) : gain = antilog(dB/20) */ /* Table gain values = (int)(powf(10.0, dB/20.0)*65536.0 + 0.5) 2dB steps */ /* Values for LMC1992 Master volume control (*65536) */ static const uint16_t LMC1992_Master_Volume_Table[64] = { 7, 8, 10, 13, 16, 21, 26, 33, 41, 52, /* -80dB */ 66, 83, 104, 131, 165, 207, 261, 328, 414, 521, /* -60dB */ 655, 825, 1039, 1308, 1646, 2072, 2609, 3285, 4135, 5206, /* -40dB */ 6554, 8250, 10387, 13076, 16462, 20724, 26090, 32846, 41350, 52057, /* -20dB */ 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, /* 0dB */ 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, /* 0dB */ 65535, 65535, 65535, 65535 /* 0dB */ }; /* Values for LMC1992 Left and right volume control (*65536) */ static const uint16_t LMC1992_LeftRight_Volume_Table[32] = { 655, 825, 1039, 1308, 1646, 2072, 2609, 3285, 4135, 5206, /* -40dB */ 6554, 8250, 10387, 13076, 16462, 20724, 26090, 32846, 41350, 52057, /* -20dB */ 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, /* 0dB */ 65535, 65535 /* 0dB */ }; /* Values for LMC1992 BASS and TREBLE */ static const int16_t LMC1992_Bass_Treble_Table[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 12, 12 }; static const int DmaSndSampleRates[4] = { 6258, 12517, 25033, 50066 }; /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static void DmaSnd_Update_XSINT_Line ( uint8_t Bit ); static void DmaSnd_FIFO_Refill(void); static int8_t DmaSnd_FIFO_PullByte(void); static void DmaSnd_FIFO_SetStereo(void); static int DmaSnd_DetectSampleRate(void); static void DmaSnd_StartNewFrame(void); static inline int DmaSnd_EndOfFrameReached(void); /** * Reset DMA sound variables. */ void DmaSnd_Reset(bool bCold) { nDmaSoundControl = 0; dma.soundMode = 0; /* [NP] Set start/end to 0 even on warm reset ? (fix 'Brace' by Diamond Design) */ IoMem[0xff8903] = 0; /* frame start addr = 0 */ IoMem[0xff8905] = 0; IoMem[0xff8907] = 0; IoMem[0xff890f] = 0; /* frame end addr = 0 */ IoMem[0xff8911] = 0; IoMem[0xff8913] = 0; dma.FIFO_Pos = 0; dma.FIFO_NbBytes = 0; dma.FrameLeft = 0; dma.FrameRight = 0; DmaSnd_Update_XSINT_Line ( MFP_GPIP_STATE_LOW ); /* O/LOW=dma sound idle */ if ( bCold ) { /* Microwire has no reset signal, it will keep its values on warm reset */ microwire.masterVolume = 7; /* -80 dB ; TOS 1.62 will put 0x28 (ie 65535) = 0 dB (max volume) */ microwire.leftVolume = 655; /* -40 dB ; TOS 1.62 will put 0x14 (ie 65535) = 0 dB (max volume) */ microwire.rightVolume = 655; /* -40 db ; TOS 1.62 will put 0x14 (ie 65535) = 0 dB (max volume) */ microwire.mixing = 0; microwire.bass = 6; /* 0 dB (flat) */ microwire.treble = 6; /* 0 dB (flat) */ } /* Initialise microwire LMC1992 IIR filter parameters */ DmaSnd_Init_Bass_and_Treble_Tables(); microwire.mwTransferSteps = 0; microwire.pendingCyclesOver = 8; } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void DmaSnd_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&nDmaSoundControl, sizeof(nDmaSoundControl)); MemorySnapShot_Store(&dma, sizeof(dma)); MemorySnapShot_Store(µwire, sizeof(microwire)); MemorySnapShot_Store(&lmc1992, sizeof(lmc1992)); } /*-----------------------------------------------------------------------*/ /** * Update the value of the XSINT line ; this line is connected to TAI and to GPIP7 * Depending on the transition, this can trigger MFP interrupt for Timer A or for GPIP7 * - Bit is set to 0/LOW when dma sound is idle * - Bit is set to 1/HIGH when dma sound is playing * * Timer A input is associated to GPIP4. Under default TOS behaviour AER bit for GPIP4 is set to 0 * (because it's also shared with ACIA's interrupt lines which are active low). * This means that each time XSINT goes to idle/0 (when reaching end of frame in single or repeat mode) * an interrupt will trigger on Timer A and Timer A event count mode can be used to count * the number of "end of frame" events */ static void DmaSnd_Update_XSINT_Line ( uint8_t Bit ) { dma.XSINT_Signal = Bit; MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE7 , Bit ); MFP_TimerA_Set_Line_Input ( pMFP_Main , Bit ); /* Update events count / interrupt for timer A if needed */ } /*-----------------------------------------------------------------------*/ /** * Return the value of the XSINT line * 0=dma sound is idle * 1=dma sound is playing */ uint8_t DmaSnd_Get_XSINT_Line ( void ) { return dma.XSINT_Signal; } /*-----------------------------------------------------------------------*/ /** * This function is called on every HBL to ensure the DMA Audio's FIFO * is kept full. * In Hatari, the FIFO is handled like a ring buffer (to avoid memcopying bytes * inside the FIFO when a byte is pushed/pulled). * Note that the DMA fetches words, not bytes, so we read new data only * when 2 bytes or more are missing. * When end of frame is reached, we continue with a new frame if loop mode * is on, else we stop DMA Audio. * * NOTE : as verified on real STE, if frameEndAddr == frameStartAddr and * repeat is ON, then frame counter is increased anyway and end of frame * interrupt is not generated. In that case, the FIFO is updated * and sound should be played (this will be the same as playing a 2^24 bytes * sample) (eg 'A Little Bit Insane' demo by Lazer) */ static void DmaSnd_FIFO_Refill(void) { /* If DMA sound is OFF, don't update the FIFO */ if ( ( nDmaSoundControl & DMASNDCTRL_PLAY ) == 0) return; /* Refill the whole FIFO */ while ( DMASND_FIFO_SIZE - dma.FIFO_NbBytes >= 2 ) { /* Add one word to the FIFO */ LOG_TRACE(TRACE_DMASND, "DMA snd fifo refill adr=%x pos %d nb %d %x %x\n", dma.frameCounterAddr , dma.FIFO_Pos , dma.FIFO_NbBytes , STMemory_DMA_ReadByte ( dma.frameCounterAddr ) , STMemory_DMA_ReadByte ( dma.frameCounterAddr+1 ) ); dma.FIFO[ ( dma.FIFO_Pos+dma.FIFO_NbBytes+0 ) & DMASND_FIFO_SIZE_MASK ] = (int8_t)STMemory_DMA_ReadByte ( dma.frameCounterAddr ); /* add upper byte of the word */ dma.FIFO[ ( dma.FIFO_Pos+dma.FIFO_NbBytes+1 ) & DMASND_FIFO_SIZE_MASK ] = (int8_t)STMemory_DMA_ReadByte ( dma.frameCounterAddr+1 ); /* add lower byte of the word */ dma.FIFO_NbBytes += 2; /* One word more in the FIFO */ /* Increase current frame address and check if we reached frame's end */ dma.frameCounterAddr += 2; if ( dma.frameCounterAddr == dma.frameEndAddr ) /* end of frame reached, should we loop or stop dma ? */ { if ( DmaSnd_EndOfFrameReached() ) break; /* Loop mode off, dma audio is now turned off */ } } } /*-----------------------------------------------------------------------*/ /** * Pull one sample/byte from the DMA Audio's FIFO and decrease the number of * remaining bytes. * If the FIFO is empty, return 0 (empty sample) * Note : on a real STE, the 8 bytes FIFO is refilled on each HBL, which gives * a total of 313*8*VBL_PER_SEC=125326 bytes per sec read by the DMA. As the max freq * is 50066 Hz, the STE can play 100132 bytes per sec in stereo ; so on a real STE * the FIFO can never be empty while DMA is ON. * But on Hatari, if the user chooses an audio's output frequency that is much * lower than the current DMA freq, audio will be updated less frequently than * on each HBL and it could require to process more than DMASND_FIFO_SIZE in one * call to DmaSnd_GenerateSamples(). This is why we allow DmaSnd_FIFO_Refill() * to be called if FIFO is empty but DMA sound is still ON. * This way, sound remains correct even if the user uses very low output freq. */ static int8_t DmaSnd_FIFO_PullByte(void) { int8_t sample; if ( dma.FIFO_NbBytes == 0 ) { DmaSnd_FIFO_Refill(); if ( dma.FIFO_NbBytes == 0 ) /* Refill didn't add any new bytes */ { LOG_TRACE(TRACE_DMASND, "DMA snd fifo empty for pull\n" ); return 0; } } LOG_TRACE(TRACE_DMASND, "DMA snd fifo pull pos %d nb %d %02x\n", dma.FIFO_Pos , dma.FIFO_NbBytes , (uint8_t)dma.FIFO[ dma.FIFO_Pos ] ); sample = dma.FIFO[ dma.FIFO_Pos ]; /* Get oldest byte from the FIFO */ dma.FIFO_Pos = (dma.FIFO_Pos+1) & DMASND_FIFO_SIZE_MASK;/* Pos to be pulled on next call */ dma.FIFO_NbBytes--; /* One byte less in the FIFO */ return sample; } /*-----------------------------------------------------------------------*/ /** * In case a program switches from mono to stereo, we must ensure that * FIFO_pos is on even boundary to keep Left/Right bytes in the correct * order (Left byte should be on even addresses and Right byte on odd ones). * If this is not the case, we skip one byte. */ static void DmaSnd_FIFO_SetStereo(void) { uint16_t NewPos; if ( dma.FIFO_Pos & 1 ) { NewPos = (dma.FIFO_Pos+1) & DMASND_FIFO_SIZE_MASK; /* skip the byte on odd address */ if ( nDmaSoundControl & DMASNDCTRL_PLAY ) /* print a log if we change while playing */ { LOG_TRACE(TRACE_DMASND, "DMA snd switching to stereo mode while playing mono FIFO_pos %d->%d\n", dma.FIFO_Pos , NewPos ); } else { LOG_TRACE(TRACE_DMASND, "DMA snd switching to stereo mode FIFO_pos %d->%d\n", dma.FIFO_Pos , NewPos ); } dma.FIFO_Pos = NewPos; if ( dma.FIFO_NbBytes > 0 ) dma.FIFO_NbBytes--; /* remove one byte if FIFO was not already empty */ } } /*-----------------------------------------------------------------------*/ /** * Returns the frequency corresponding to the 2 lower bits of dma.soundMode */ static int DmaSnd_DetectSampleRate(void) { return DmaSndSampleRates[dma.soundMode & 3]; } /*-----------------------------------------------------------------------*/ /** * This function is called when a new sound frame is started. * It copies the start and end address from the I/O registers and set * the frame counter addr to the start of this new frame. * * NOTE : as verified on real STE, if frameEndAddr == frameStartAddr and * repeat is OFF, then DMA sound is turned off immediately and end of frame * interrupt is not generated (eg 'Amberstar cracktro' by DNT Crew / Fuzion) */ static void DmaSnd_StartNewFrame(void) { dma.frameStartAddr = (IoMem[0xff8903] << 16) | (IoMem[0xff8905] << 8) | (IoMem[0xff8907] & ~1); dma.frameEndAddr = (IoMem[0xff890f] << 16) | (IoMem[0xff8911] << 8) | (IoMem[0xff8913] & ~1); dma.frameCounterAddr = dma.frameStartAddr; LOG_TRACE(TRACE_DMASND, "DMA snd new frame start=%x end=%x\n", dma.frameStartAddr, dma.frameEndAddr); if ( ( dma.frameStartAddr == dma.frameEndAddr ) && ( ( nDmaSoundControl & DMASNDCTRL_PLAYLOOP ) == 0 ) ) { nDmaSoundControl &= ~DMASNDCTRL_PLAY; LOG_TRACE(TRACE_DMASND, "DMA snd stopped because new frame start=end=%x and repeat=off\n", dma.frameStartAddr); return; } /* DMA sound play : update XSINT */ DmaSnd_Update_XSINT_Line ( MFP_GPIP_STATE_HIGH ); /* 1/HIGH=dma sound play */ } /*-----------------------------------------------------------------------*/ /** * End-of-frame has been reached. Raise interrupts if needed. * Returns true if DMA sound processing should be stopped now and false * if it continues (DMA PLAYLOOP mode). * * NOTE : on early STE models the XSINT signal was directly connected * to MFP GPIP7 and to Timer A input (for event count mode). * On later revisions, as well as on TT, the signal to Timer A input * is now delayed by 8 shifts using a 74LS164 running at 2 MHz, which * is equivalent to 32 CPU cycles when the CPU runs at 8 MHz. * At the emulation level, we don't take into account this delay of * 32 CPU cycles, as it would add complexity and no program are known * so far to require this delay. */ static inline int DmaSnd_EndOfFrameReached(void) { LOG_TRACE(TRACE_DMASND, "DMA snd end of frame\n"); /* DMA sound idle : update XSINT */ DmaSnd_Update_XSINT_Line ( MFP_GPIP_STATE_LOW ); /* O/LOW=dma sound idle */ if (nDmaSoundControl & DMASNDCTRL_PLAYLOOP) { DmaSnd_StartNewFrame(); /* update XSINT */ } else { nDmaSoundControl &= ~DMASNDCTRL_PLAY; return true; } return false; } /*-----------------------------------------------------------------------*/ /** * Mix DMA sound sample with the normal PSG sound samples. * Note: We adjust the volume level of the 8-bit DMA samples to factor * 0.75 compared to the PSG sound samples. * * The following formula: -((256*3/4)/4)/4 * * Multiply by 256 to convert 8 to 16 bits; * DMA sound is 3/4 level of YM sound; * Divide by 4 to account for the STe YM volume table level; * ( STe sound at 1/2 amplitude to avoid overflow. ) * ( lmc1992.right_gain and lmc1992.left_gain are ) * ( doubled to compensate. ) * Divide by 4 to account for DmaSnd_LowPassFilter; * Multiply DMA sound by -1 because the LMC1992 inverts the signal * ( YM sign is +1 :: -1(op-amp) * -1(Lmc1992) ). */ void DmaSnd_GenerateSamples(int nMixBufIdx, int nSamplesToGenerate) { int i; int nBufIdx; int8_t MonoByte , LeftByte , RightByte; unsigned n; int64_t FreqRatio; /* DMA Audio OFF and FIFO empty : process YM2149's output */ if ( !(nDmaSoundControl & DMASNDCTRL_PLAY) && ( dma.FIFO_NbBytes == 0 ) ) { for (i = 0; i < nSamplesToGenerate; i++) { nBufIdx = (nMixBufIdx + i) & AUDIOMIXBUFFER_SIZE_MASK; switch (microwire.mixing) { case 1: /* DMA and YM2149 mixing */ AudioMixBuffer[nBufIdx][0] = AudioMixBuffer[nBufIdx][0] + dma.FrameLeft * -((256*3/4)/4)/4; AudioMixBuffer[nBufIdx][1] = AudioMixBuffer[nBufIdx][1] + dma.FrameRight * -((256*3/4)/4)/4; break; default: /* mixing=0 DMA only */ /* mixing=2 DMA and input 2 (YM2149 LPF) -> DMA */ /* mixing=3 DMA and input 3 -> DMA */ AudioMixBuffer[nBufIdx][0] = dma.FrameLeft * -((256*3/4)/4)/4; AudioMixBuffer[nBufIdx][1] = dma.FrameRight * -((256*3/4)/4)/4; break; } } /* Apply LMC1992 sound modifications (Bass and Treble) */ DmaSnd_Apply_LMC ( nMixBufIdx , nSamplesToGenerate ); return; } /* DMA Anti-alias filter */ if (DmaSnd_DetectSampleRate() > nAudioFrequency) DmaSnd_LowPass = true; else DmaSnd_LowPass = false; /* DMA Audio ON or FIFO not empty yet */ /* Compute ratio between DMA's sound frequency and host computer's sound frequency, */ /* use << 32 to simulate floating point precision */ FreqRatio = ( ((int64_t)DmaSnd_DetectSampleRate()) << 32 ) / nAudioFrequency; if (dma.soundMode & DMASNDMODE_MONO) { /* Mono 8-bit */ for (i = 0; i < nSamplesToGenerate; i++) { if ( DmaInitSample ) { MonoByte = DmaSnd_FIFO_PullByte (); dma.FrameLeft = DmaSnd_LowPassFilterLeft( (int16_t)MonoByte ); dma.FrameRight = DmaSnd_LowPassFilterRight( (int16_t)MonoByte ); DmaInitSample = false; } nBufIdx = (nMixBufIdx + i) & AUDIOMIXBUFFER_SIZE_MASK; switch (microwire.mixing) { case 1: /* DMA and YM2149 mixing */ AudioMixBuffer[nBufIdx][0] = AudioMixBuffer[nBufIdx][0] + dma.FrameLeft * -((256*3/4)/4)/4; break; default: /* mixing=0 DMA only */ /* mixing=2 DMA and input 2 (YM2149 LPF) -> DMA */ /* mixing=3 DMA and input 3 -> DMA */ AudioMixBuffer[nBufIdx][0] = dma.FrameLeft * -((256*3/4)/4)/4; break; } AudioMixBuffer[nBufIdx][1] = AudioMixBuffer[nBufIdx][0]; /* right = left */ /* Increase freq counter */ frameCounter_float += FreqRatio; n = frameCounter_float >> 32; /* number of samples to skip */ while ( n > 0 ) /* pull as many bytes from the FIFO as needed */ { MonoByte = DmaSnd_FIFO_PullByte (); dma.FrameLeft = DmaSnd_LowPassFilterLeft( (int16_t)MonoByte ); dma.FrameRight = DmaSnd_LowPassFilterRight( (int16_t)MonoByte ); n--; } frameCounter_float &= 0xffffffff; /* only keep the fractional part */ } } else { /* Stereo 8-bit */ for (i = 0; i < nSamplesToGenerate; i++) { if ( DmaInitSample ) { LeftByte = DmaSnd_FIFO_PullByte (); RightByte = DmaSnd_FIFO_PullByte (); dma.FrameLeft = DmaSnd_LowPassFilterLeft( (int16_t)LeftByte ); dma.FrameRight = DmaSnd_LowPassFilterRight( (int16_t)RightByte ); DmaInitSample = false; } nBufIdx = (nMixBufIdx + i) & AUDIOMIXBUFFER_SIZE_MASK; switch (microwire.mixing) { case 1: /* DMA and YM2149 mixing */ AudioMixBuffer[nBufIdx][0] = AudioMixBuffer[nBufIdx][0] + dma.FrameLeft * -((256*3/4)/4)/4; AudioMixBuffer[nBufIdx][1] = AudioMixBuffer[nBufIdx][1] + dma.FrameRight * -((256*3/4)/4)/4; break; default: /* mixing=0 DMA only */ /* mixing=2 DMA and input 2 (YM2149 LPF) -> DMA */ /* mixing=3 DMA and input 3 -> DMA */ AudioMixBuffer[nBufIdx][0] = dma.FrameLeft * -((256*3/4)/4)/4; AudioMixBuffer[nBufIdx][1] = dma.FrameRight * -((256*3/4)/4)/4; break; } /* Increase freq counter */ frameCounter_float += FreqRatio; n = frameCounter_float >> 32; /* number of samples to skip */ while ( n > 0 ) /* pull as many bytes from the FIFO as needed */ { LeftByte = DmaSnd_FIFO_PullByte (); RightByte = DmaSnd_FIFO_PullByte (); dma.FrameLeft = DmaSnd_LowPassFilterLeft( (int16_t)LeftByte ); dma.FrameRight = DmaSnd_LowPassFilterRight( (int16_t)RightByte ); n--; } frameCounter_float &= 0xffffffff; /* only keep the fractional part */ } } /* Apply LMC1992 sound modifications (Bass and Treble) */ DmaSnd_Apply_LMC ( nMixBufIdx , nSamplesToGenerate ); } /*-----------------------------------------------------------------------*/ /** * Apply LMC1992 sound modifications (Bass and Treble) * The Bass and Treble get samples at nAudioFrequency rate. * The tone control's sampling frequency must be at least 22050 Hz to sound good. */ static void DmaSnd_Apply_LMC(int nMixBufIdx, int nSamplesToGenerate) { int nBufIdx; int i; int32_t sample; /* Apply LMC1992 sound modifications (Left, Right and Master Volume) */ for (i = 0; i < nSamplesToGenerate; i++) { nBufIdx = (nMixBufIdx + i) & AUDIOMIXBUFFER_SIZE_MASK; sample = DmaSnd_IIRfilterL( Subsonic_IIR_HPF_Left( AudioMixBuffer[nBufIdx][0])); if (sample<-32767) /* check for overflow to clip waveform */ sample = -32767; else if (sample>32767) sample = 32767; AudioMixBuffer[nBufIdx][0] = sample; sample = DmaSnd_IIRfilterR( Subsonic_IIR_HPF_Right(AudioMixBuffer[nBufIdx][1])); if (sample<-32767) /* check for overflow to clip waveform */ sample = -32767; else if (sample>32767) sample = 32767; AudioMixBuffer[nBufIdx][1] = sample; } } /*-----------------------------------------------------------------------*/ /** * STE DMA sound is using an 8 bytes FIFO that is checked and filled on each HBL * (at 50066 Hz 8 bit stereo, the DMA requires approx 6.5 new bytes per HBL) * Calling Sound_Update on each HBL allows to emulate some programs that modify * the data between FrameStart and FrameEnd while DMA sound is ON * (eg the demo 'Mental Hangover' or the game 'Power Up Plus') * We first check if the FIFO needs to be refilled, then we call Sound_Update. * This function should be called from the HBL's handler (in video.c) * We should call it also in the case of the TT which uses the same DMA sound */ void DmaSnd_STE_HBL_Update(void) { if ( !Config_IsMachineSTE() && !Config_IsMachineTT() ) return; /* The DMA starts refilling the FIFO when display is OFF (eg cycle 376 in STE low res 50 Hz) */ DmaSnd_FIFO_Refill (); /* If DMA sound is ON or FIFO is not empty, update sound */ if ( (nDmaSoundControl & DMASNDCTRL_PLAY) || ( dma.FIFO_NbBytes > 0 ) ) Sound_Update ( CyclesGlobalClockCounter ); /* As long as display is OFF, the DMA will refill the FIFO after playing some samples during the HBL */ DmaSnd_FIFO_Refill (); } /*-----------------------------------------------------------------------*/ /** * Return current frame counter address (value is always even) */ static uint32_t DmaSnd_GetFrameCount(void) { uint32_t nActCount; /* Update sound to get the current DMA frame address */ Sound_Update ( CyclesGlobalClockCounter ); if (nDmaSoundControl & DMASNDCTRL_PLAY) nActCount = dma.frameCounterAddr; else nActCount = (IoMem[0xff8903] << 16) | (IoMem[0xff8905] << 8) | (IoMem[0xff8907] & ~1); return nActCount; } /*-----------------------------------------------------------------------*/ /** * Read word from sound control register (0xff8900). */ void DmaSnd_SoundControl_ReadWord(void) { IoMem_WriteWord(0xff8900, nDmaSoundControl); if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd control read: 0x%04x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", nDmaSoundControl, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Write word to sound control register (0xff8900). */ void DmaSnd_SoundControl_WriteWord(void) { uint16_t DMASndCtrl_old; if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd control write: 0x%04x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadWord(0xff8900), FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* Before starting/stopping DMA sound, create samples up until this point with current values */ Sound_Update ( Cycles_GetClockCounterOnWriteAccess() ); DMASndCtrl_old = nDmaSoundControl; nDmaSoundControl = IoMem_ReadWord(0xff8900) & 3; if (!(DMASndCtrl_old & DMASNDCTRL_PLAY) && (nDmaSoundControl & DMASNDCTRL_PLAY)) { LOG_TRACE(TRACE_DMASND, "DMA snd control write: starting dma sound output\n"); DmaInitSample = true; frameCounter_float = 0; DmaSnd_StartNewFrame(); /* update XSINT + this can clear DMASNDCTRL_PLAY */ } else if ((DMASndCtrl_old & DMASNDCTRL_PLAY) && !(nDmaSoundControl & DMASNDCTRL_PLAY)) { LOG_TRACE(TRACE_DMASND, "DMA snd control write: stopping dma sound output\n"); DmaSnd_Update_XSINT_Line ( MFP_GPIP_STATE_LOW ); /* O/LOW=dma sound idle */ } } /*-----------------------------------------------------------------------*/ /** * Read word from sound frame count high register (0xff8909). */ void DmaSnd_FrameCountHigh_ReadByte(void) { IoMem_WriteByte(0xff8909, DmaSnd_GetFrameCount() >> 16); } /*-----------------------------------------------------------------------*/ /** * Read word from sound frame count medium register (0xff890b). */ void DmaSnd_FrameCountMed_ReadByte(void) { IoMem_WriteByte(0xff890b, DmaSnd_GetFrameCount() >> 8); } /*-----------------------------------------------------------------------*/ /** * Read word from sound frame count low register (0xff890d). */ void DmaSnd_FrameCountLow_ReadByte(void) { IoMem_WriteByte(0xff890d, DmaSnd_GetFrameCount()); } /*-----------------------------------------------------------------------*/ /** * Write bytes to various registers with no action. */ void DmaSnd_FrameStartHigh_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame start high: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff8903) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* On STF/STE machines with <= 4MB of RAM, DMA addresses are limited to $3fffff */ IoMem[ 0xff8903 ] &= DMA_MaskAddressHigh(); } void DmaSnd_FrameStartMed_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame start med: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff8905) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } void DmaSnd_FrameStartLow_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame start low: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff8907) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* DMA address must be word-aligned, bit 0 at $ff8907 is always 0 */ IoMem[ 0xff8907 ] &= 0xfe; } void DmaSnd_FrameCountHigh_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame count high: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff8909) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* On STF/STE machines with <= 4MB of RAM, DMA addresses are limited to $3fffff */ IoMem[ 0xff8909 ] &= DMA_MaskAddressHigh(); } void DmaSnd_FrameCountMed_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame count med: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff890b) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } void DmaSnd_FrameCountLow_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame count low: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff890d) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } void DmaSnd_FrameEndHigh_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame end high: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff890f) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* On STF/STE machines with <= 4MB of RAM, DMA addresses are limited to $3fffff */ IoMem[ 0xff890f ] &= DMA_MaskAddressHigh(); } void DmaSnd_FrameEndMed_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame end med: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff8911) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } void DmaSnd_FrameEndLow_WriteByte(void) { if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd frame end low: 0x%02x at pos %d/%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadByte(0xff8913) , dma.frameCounterAddr - dma.frameStartAddr , dma.frameEndAddr - dma.frameStartAddr , FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* DMA address must be word-aligned, bit 0 at $ff8613 is always 0 */ IoMem[ 0xff8913 ] &= 0xfe; } /*-----------------------------------------------------------------------*/ /** * Read word from sound mode register (0xff8921). */ void DmaSnd_SoundModeCtrl_ReadByte(void) { IoMem_WriteByte(0xff8921, dma.soundMode); if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd mode read: 0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", dma.soundMode, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Write word to sound mode register (0xff8921). */ void DmaSnd_SoundModeCtrl_WriteByte(void) { uint16_t SoundModeNew; SoundModeNew = IoMem_ReadByte(0xff8921); if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("DMA snd mode write: 0x%02x mode=%s freq=%d video_cyc=%d %d@%d pc=%x instr_cycle %d\n", SoundModeNew, SoundModeNew & DMASNDMODE_MONO ? "mono" : "stereo" , DmaSndSampleRates[ SoundModeNew & 3 ], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* We maskout to only bits that exist on a real STE */ SoundModeNew &= 0x8f; /* Are we switching from mono to stereo ? */ if ( ( dma.soundMode & DMASNDMODE_MONO ) && ( ( SoundModeNew & DMASNDMODE_MONO ) == 0 ) ) DmaSnd_FIFO_SetStereo (); dma.soundMode = SoundModeNew; /* We also write the masked value back into the emulated hw registers so we have a correct value there */ IoMem_WriteByte(0xff8921, dma.soundMode); } /* ---------------------- Microwire / LMC 1992 ---------------------- */ /** * Handle the shifting/rotating of the microwire registers * The microwire regs should be done after 16 usec = 32 NOPs = 128 cycles. * That means we have to shift 16 times with a delay of 8 cycles. * Microwire uses the MWK clock signal at 1 Mhz */ void DmaSnd_InterruptHandler_Microwire(void) { int i; uint16_t cmd; int cmd_len; /* If emulated computer is the Falcon, let's the crossbar Microwire code do the job. */ if (Config_IsMachineFalcon()) { Crossbar_InterruptHandler_Microwire(); return; } /* How many cycle was this sound interrupt delayed (>= 0) */ microwire.pendingCyclesOver += -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Shift the mask and data according to the number of cycles (8 cycles for a shift) */ do { --microwire.mwTransferSteps; /* Shift data register until it becomes zero. */ IoMem_WriteWord(0xff8922, microwire.data<<(16-microwire.mwTransferSteps)); /* Rotate mask register */ IoMem_WriteWord(0xff8924, (microwire.mask<<(16-microwire.mwTransferSteps)) |(microwire.mask>>microwire.mwTransferSteps)); /* 8 cycles for 1 shift */ microwire.pendingCyclesOver -= 8; } while ((microwire.mwTransferSteps != 0) && (microwire.pendingCyclesOver >= 8) ); /* Is the transfer finished ? */ if (microwire.mwTransferSteps > 0) { /* No ==> start a new internal interrupt to continue to transfer the data */ microwire.pendingCyclesOver = 8 - microwire.pendingCyclesOver; CycInt_AddRelativeInterrupt(microwire.pendingCyclesOver, INT_CPU8_CYCLE, INTERRUPT_DMASOUND_MICROWIRE); } else { /* Yes : decode the address + command word according to the binary mask */ /* According to LMC1992 doc, command starts with the first '1' bit in the mask */ /* and ends when a '0' bits is received in the mask */ /* If we get a bad command, we must scan the rest of the mask in case there's a valid */ /* command in the remaining bits */ /* TODO [NP] : to be really cycle accurate, we should decode the command at the same */ /* time as we rotate mask/data, instead of doing it when 16 rotations were made. */ /* But this would not be noticeable, so leave it like this for now */ cmd = 0; cmd_len = 0; for ( i=15 ; i>=0 ; i-- ) if ( microwire.mask & ( 1 << i ) ) { /* Start of command found, wait for next '0' bit or end of mask */ do { cmd <<= 1; cmd_len++; if ( microwire.data & ( 1 << i ) ) cmd |= 1; i--; } while ( ( i >= 0 ) && ( microwire.mask & ( 1 << i ) ) ); if ( ( cmd_len >= 11 ) && ( ( cmd >> ( cmd_len-2 ) ) & 0x03 ) == 0x02 ) break; /* We found a valid command */ LOG_TRACE ( TRACE_DMASND, "Microwire bad command=0x%x len=%d ignored mask=0x%x data=0x%x\n", cmd , cmd_len , microwire.mask , microwire.data ); if ( i < 0 ) return; /* All bits were tested, stop here */ /* Check remaining bits for a possible command */ cmd = 0; cmd_len = 0; } //fprintf ( stderr , "mwire cmd=%x len=%d mask=%x data=%x\n" , cmd , cmd_len , microwire.mask , microwire.data ); /* The LMC 1992 address (first 2 bits) should be "10", else we ignore the command */ /* The address should be followed by at least 9 bits ; if more bits were received, */ /* then only the latest 9 ones should be kept */ if ( ( cmd_len < 11 ) || ( ( cmd >> ( cmd_len-2 ) ) & 0x03 ) != 0x02 ) { LOG_TRACE ( TRACE_DMASND, "Microwire bad command=0x%x len=%d ignored mask=0x%x data=0x%x\n", cmd , cmd_len , microwire.mask , microwire.data ); return; } /* Update the LMC 1992 commands */ switch ( ( cmd >> 6 ) & 0x7 ) { case 0: /* Mixing command */ LOG_TRACE ( TRACE_DMASND, "Microwire new mixing=0x%x\n", cmd & 0x3 ); microwire.mixing = cmd & 0x3; break; case 1: /* Bass command */ LOG_TRACE ( TRACE_DMASND, "Microwire new bass=0x%x\n", cmd & 0xf ); microwire.bass = cmd & 0xf; DmaSnd_Set_Tone_Level(LMC1992_Bass_Treble_Table[microwire.bass], LMC1992_Bass_Treble_Table[microwire.treble]); break; case 2: /* Treble command */ LOG_TRACE ( TRACE_DMASND, "Microwire new trebble=0x%x\n", cmd & 0xf ); microwire.treble = cmd & 0xf; DmaSnd_Set_Tone_Level(LMC1992_Bass_Treble_Table[microwire.bass], LMC1992_Bass_Treble_Table[microwire.treble]); break; case 3: /* Master volume command */ LOG_TRACE ( TRACE_DMASND, "Microwire new master volume=0x%x\n", cmd & 0x3f ); microwire.masterVolume = LMC1992_Master_Volume_Table[ cmd & 0x3f ]; lmc1992.left_gain = (microwire.leftVolume * (uint32_t)microwire.masterVolume) * (2.0/(65536.0*65536.0)); lmc1992.right_gain = (microwire.rightVolume * (uint32_t)microwire.masterVolume) * (2.0/(65536.0*65536.0)); break; case 4: /* Right channel volume */ LOG_TRACE ( TRACE_DMASND, "Microwire new right volume=0x%x\n", cmd & 0x1f ); microwire.rightVolume = LMC1992_LeftRight_Volume_Table[ cmd & 0x1f ]; lmc1992.right_gain = (microwire.rightVolume * (uint32_t)microwire.masterVolume) * (2.0/(65536.0*65536.0)); break; case 5: /* Left channel volume */ LOG_TRACE ( TRACE_DMASND, "Microwire new left volume=0x%x\n", cmd & 0x1f ); microwire.leftVolume = LMC1992_LeftRight_Volume_Table[ cmd & 0x1f ]; lmc1992.left_gain = (microwire.leftVolume * (uint32_t)microwire.masterVolume) * (2.0/(65536.0*65536.0)); break; default: /* Do nothing */ LOG_TRACE ( TRACE_DMASND, "Microwire unknown command=0x%x len=%d ignored mask=0x%x data=0x%x\n", cmd , cmd_len , microwire.mask , microwire.data ); break; } } } /** * Read word from microwire data register (0xff8922). */ void DmaSnd_MicrowireData_ReadWord(void) { /* Shifting is done in DmaSnd_InterruptHandler_Microwire! */ if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("Microwire data read: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadWord(0xff8922), FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /** * Write word to microwire data register (0xff8922). */ void DmaSnd_MicrowireData_WriteWord(void) { /* Only update, if no shift is in progress */ if (!microwire.mwTransferSteps) { microwire.data = IoMem_ReadWord(0xff8922); /* Start shifting events to simulate a microwire transfer */ microwire.mwTransferSteps = 16; microwire.pendingCyclesOver = 8; CycInt_AddRelativeInterrupt(microwire.pendingCyclesOver, INT_CPU8_CYCLE, INTERRUPT_DMASOUND_MICROWIRE); } if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("Microwire data write: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadWord(0xff8922), FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /** * Read word from microwire mask register (0xff8924). */ void DmaSnd_MicrowireMask_ReadWord(void) { /* Same as with data register, but mask is rotated, not shifted. */ if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("Microwire mask read: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadWord(0xff8924), FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /** * Write word to microwire mask register (0xff8924). */ void DmaSnd_MicrowireMask_WriteWord(void) { /* Only update, if no shift is in progress */ if (!microwire.mwTransferSteps) { microwire.mask = IoMem_ReadWord(0xff8924); } if(LOG_TRACE_LEVEL(TRACE_DMASND)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("Microwire mask write: 0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoMem_ReadWord(0xff8924), FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-------------------Bass / Treble filter ---------------------------*/ /** * Left voice Filter for Bass/Treble. */ static float DmaSnd_IIRfilterL(float xn) { static float data[2] = { 0.0, 0.0 }; float a, yn; /* Input coefficients */ /* biquad1 Note: 'a' coefficients are subtracted */ a = lmc1992.left_gain * xn; /* a=g*xn; */ a -= lmc1992.coef[0] * data[0]; /* a1; wn-1 */ a -= lmc1992.coef[1] * data[1]; /* a2; wn-2 */ /* If coefficient scale */ /* factor = 0.5 then */ /* multiply by 2 */ /* Output coefficients */ yn = lmc1992.coef[2] * a; /* b0; */ yn += lmc1992.coef[3] * data[0]; /* b1; */ yn += lmc1992.coef[4] * data[1]; /* b2; */ data[1] = data[0]; /* wn-1 -> wn-2; */ data[0] = a; /* wn -> wn-1 */ return yn; } /** * Right voice Filter for Bass/Treble. */ static float DmaSnd_IIRfilterR(float xn) { static float data[2] = { 0.0, 0.0 }; float a, yn; /* Input coefficients */ /* biquad1 Note: 'a' coefficients are subtracted */ a = lmc1992.right_gain * xn; /* a=g*xn; */ a -= lmc1992.coef[0]*data[0]; /* a1; wn-1 */ a -= lmc1992.coef[1]*data[1]; /* a2; wn-2 */ /* If coefficient scale */ /* factor = 0.5 then */ /* multiply by 2 */ /* Output coefficients */ yn = lmc1992.coef[2]*a; /* b0; */ yn += lmc1992.coef[3]*data[0]; /* b1; */ yn += lmc1992.coef[4]*data[1]; /* b2; */ data[1] = data[0]; /* wn-1 -> wn-2; */ data[0] = a; /* wn -> wn-1 */ return yn; } /** * LowPass Filter Left */ static int16_t DmaSnd_LowPassFilterLeft(int16_t in) { static int16_t lowPassFilter[2] = { 0, 0 }; static int16_t out = 0; if (DmaSnd_LowPass) out = lowPassFilter[0] + (lowPassFilter[1]<<1) + in; else out = lowPassFilter[1] << 2; lowPassFilter[0] = lowPassFilter[1]; lowPassFilter[1] = in; return out; /* Filter Gain = 4 */ } /** * LowPass Filter Right */ static int16_t DmaSnd_LowPassFilterRight(int16_t in) { static int16_t lowPassFilter[2] = { 0, 0 }; static int16_t out = 0; if (DmaSnd_LowPass) out = lowPassFilter[0] + (lowPassFilter[1]<<1) + in; else out = lowPassFilter[1] << 2; lowPassFilter[0] = lowPassFilter[1]; lowPassFilter[1] = in; return out; /* Filter Gain = 4 */ } /** * Set Bass and Treble tone level */ static void DmaSnd_Set_Tone_Level(int set_bass, int set_treb) { /* 13 levels; 0 through 12 correspond with -12dB to 12dB in 2dB steps */ lmc1992.coef[0] = lmc1992.treb_table[set_treb].a1 + lmc1992.bass_table[set_bass].a1; lmc1992.coef[1] = lmc1992.treb_table[set_treb].a1 * lmc1992.bass_table[set_bass].a1; lmc1992.coef[2] = lmc1992.treb_table[set_treb].b0 * lmc1992.bass_table[set_bass].b0; lmc1992.coef[3] = lmc1992.treb_table[set_treb].b0 * lmc1992.bass_table[set_bass].b1 + lmc1992.treb_table[set_treb].b1 * lmc1992.bass_table[set_bass].b0; lmc1992.coef[4] = lmc1992.treb_table[set_treb].b1 * lmc1992.bass_table[set_bass].b1; } /** * Compute the first order bass shelf */ static struct first_order_s *DmaSnd_Bass_Shelf(float g, float fc, float Fs) { static struct first_order_s bass; float a1; /* g, fc, Fs must be positive real numbers > 0.0 */ if (g < 1.0) bass.a1 = a1 = (tanf(M_PI*fc/Fs) - g ) / (tanf(M_PI*fc/Fs) + g ); else bass.a1 = a1 = (tanf(M_PI*fc/Fs) - 1.0) / (tanf(M_PI*fc/Fs) + 1.0); bass.b0 = (1.0 + a1) * (g - 1.0) / 2.0 + 1.0; bass.b1 = (1.0 + a1) * (g - 1.0) / 2.0 + a1; return &bass; } /** * Compute the first order treble shelf */ static struct first_order_s *DmaSnd_Treble_Shelf(float g, float fc, float Fs) { static struct first_order_s treb; float a1; /* g, fc, Fs must be positive real numbers > 0.0 */ if (g < 1.0) treb.a1 = a1 = (g*tanf(M_PI*fc/Fs) - 1.0) / (g*tanf(M_PI*fc/Fs) + 1.0); else treb.a1 = a1 = (tanf(M_PI*fc/Fs) - 1.0) / (tanf(M_PI*fc/Fs) + 1.0); treb.b0 = 1.0 + (1.0 - a1) * (g - 1.0) / 2.0; treb.b1 = a1 + (a1 - 1.0) * (g - 1.0) / 2.0; return &treb; } /** * Compute the bass and treble tables (nAudioFrequency) */ void DmaSnd_Init_Bass_and_Treble_Tables(void) { struct first_order_s *bass; struct first_order_s *treb; float dB_adjusted, dB, g, fc_bt, fc_tt, Fs; int n; fc_bt = 118.2763f; fc_tt = 8438.756f; Fs = (float)nAudioFrequency; if ((Fs < 8000.0) || (Fs > 96000.0)) Fs = 44100.0; if (fc_tt > 0.5*0.8*Fs) { fc_tt = 0.5*0.8*Fs; dB_adjusted = 2.0 * 0.5*0.8*Fs/fc_tt; }else { dB_adjusted = 2.0; } for (dB = dB_adjusted*(TONE_STEPS-1)/2, n = TONE_STEPS; n--; dB -= dB_adjusted) { g = powf(10.0, dB/20.0); /* 12dB to -12dB */ treb = DmaSnd_Treble_Shelf(g, fc_tt, Fs); lmc1992.treb_table[n].a1 = treb->a1; lmc1992.treb_table[n].b0 = treb->b0; lmc1992.treb_table[n].b1 = treb->b1; } for (dB = 12.0, n = TONE_STEPS; n--; dB -= 2.0) { g = powf(10.0, dB/20.0); /* 12dB to -12dB */ bass = DmaSnd_Bass_Shelf(g, fc_bt, Fs); lmc1992.bass_table[n].a1 = bass->a1; lmc1992.bass_table[n].b0 = bass->b0; lmc1992.bass_table[n].b1 = bass->b1; } DmaSnd_Set_Tone_Level(LMC1992_Bass_Treble_Table[microwire.bass & 0xf], LMC1992_Bass_Treble_Table[microwire.treble & 0xf]); /* Initialize IIR Filter Gain and use as a Volume Control */ lmc1992.left_gain = (microwire.leftVolume * (uint32_t)microwire.masterVolume) * (2.0/(65536.0*65536.0)); lmc1992.right_gain = (microwire.rightVolume * (uint32_t)microwire.masterVolume) * (2.0/(65536.0*65536.0)); } void DmaSnd_Info(FILE *fp, uint32_t dummy) { if (Config_IsMachineST()) { fprintf(fp, "ST doesn't include DMA!\n"); return; } fprintf(fp, "$FF8900.b : Sound DMA control : %02x\n", IoMem_ReadByte(0xff8900)); fprintf(fp, "$FF8901.b : Sound DMA control : %02x\n", IoMem_ReadByte(0xff8901)); fprintf(fp, "$FF8903.b : Frame Start High : %02x\n", IoMem_ReadByte(0xff8903)); fprintf(fp, "$FF8905.b : Frame Start middle : %02x\n", IoMem_ReadByte(0xff8905)); fprintf(fp, "$FF8907.b : Frame Start low : %02x\n", IoMem_ReadByte(0xff8907)); fprintf(fp, "$FF8909.b : Frame Count High : %02x\n", IoMem_ReadByte(0xff8909)); fprintf(fp, "$FF890B.b : Frame Count middle : %02x\n", IoMem_ReadByte(0xff890b)); fprintf(fp, "$FF890D.b : Frame Count low : %02x\n", IoMem_ReadByte(0xff890d)); fprintf(fp, "$FF890F.b : Frame End High : %02x\n", IoMem_ReadByte(0xff890f)); fprintf(fp, "$FF8911.b : Frame End middle : %02x\n", IoMem_ReadByte(0xff8911)); fprintf(fp, "$FF8913.b : Frame End low : %02x\n", IoMem_ReadByte(0xff8913)); fprintf(fp, "\n"); fprintf(fp, "$FF8920.b : Sound Mode Control : %02x\n", IoMem_ReadByte(0xff8920)); fprintf(fp, "$FF8921.b : Sound Mode Control : %02x\n", IoMem_ReadByte(0xff8921)); if (Config_IsMachineFalcon()) { return; } fprintf(fp, "\n"); fprintf(fp, "$FF8922.w : Microwire Data : %04x\n", IoMem_ReadWord(0xff8922)); fprintf(fp, "$FF8924.w : Microwire Mask : %04x\n", IoMem_ReadWord(0xff8924)); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/emscripten.js000066400000000000000000000001371504763705000240400ustar00rootroot00000000000000Module['arguments']=[ '--desktop','false', '-d','/share/hatari/fs/', '--machine','ste']; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/emscripten_shell.html000066400000000000000000000130211504763705000255530ustar00rootroot00000000000000 Hatari online Hatari - the Atari ST/STE/TT/Falcon emulator
Downloading...
Resize canvas Lock/hide mouse pointer    
{{{ SCRIPT }}} hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/faketos.s000066400000000000000000000067341504763705000231620ustar00rootroot00000000000000; A very minimalistic TOS ROM replacement, used for testing without real TOS ; ; All exception pointers are set to "unhandled_error", all interrupt pointers are set to "rte", ; except VBL at $70 which will update a counter at $462 (similar to real TOS) ; SR will be set to $0300 to allow VBL interrupt and its counter ; ; In case an SCU is detected at $FF8E01 - $FF8E0D (on MegaSTE / TT), we enable ; interrupts for hsync, vsync, scc and mfp as normal TOS does (if not, "stop $2300" won't work anymore) ; ; Assemble faketos.s as a binary only using vasm, and include it in faketosData.c ; vasmm68k_mot -nosym -devpac -showopt -o fakeTos.bin -Fbin faketos.s org $e00000 TEST_PRG_BASEPAGE equ $1000 rom_header: bra.s start ; Branch to 0xe00030 dc.w $0001 ; TOS version dc.l start ; Reset PC value dc.l rom_header ; Pointer to ROM header dc.l TEST_PRG_BASEPAGE ; End of OS BSS dc.l start ; Reserved dc.l $0 ; Unused (GEM's MUPB) dc.l $03032018 ; Fake date dc.w $0001 ; PAL flag dc.w $4c63 ; Fake DOS date dc.l $00000880 ; Fake pointer 1 (mem pool) dc.l $00000870 ; Fake pointer 2 (key shift) dc.l $00000800 ; Addr of basepage var dc.l $0 ; Reserved start: move #$2700,sr reset move.b #5,$ffff8001.w ; Fake memory config lea $20000,sp ; Set up SSP ;-- Config SCU interrupts on MegaSTE / TT bsr config_scu ;-- Set all exception vectors to "unhandled_error" lea unhandled_error(pc),a1 movea.w #8,a0 ; Start with bus error handler movea.w #$1bc,a2 bsr.s range_set_pointer ;-- Set all possible interrupt vectors to "rte" lea rte_only(pc),a1 movea.w #$64,a0 ; level 1-7 interrupts (hbl, vbl,...) movea.w #$7c,a2 bsr.s range_set_pointer movea.w #$100,a0 ; mfp and scc interrupts movea.w #$1bc,a2 bsr.s range_set_pointer ;-- Set a minimal VBL interrupt to update a fake TOS VBL counter at $462 lea vbl_mini(pc),a1 move.l a1,$70.w ; Minimal VBL clr.l $462 ; clear fake TOS vbl counter lea $fffffa00.w,a0 move.b #$48,17(a0) ; Configure MFP vector base lea $fffffc00.w,a0 move.b #3,(a0) ; Reset ACIA move.b #$16,(a0) ; Configure ACIA lea $fa0000,a0 cmp.l #$abcdef42,(a0) ; Cartridge enabled? bne.s no_sys_init dc.w $a ; Call SYSINIT_OPCODE to init trap #1 no_sys_init: moveq #0,d0 movea.l d0,a0 movea.l d0,a1 move #$0300,sr ; Go to user mode, allow VBL interrupt lea $18000,sp ; Set up USP pea TEST_PRG_BASEPAGE.w pea rom_header(pc) jmp TEST_PRG_BASEPAGE+$100.w range_set_pointer: move.l a1,(a0)+ cmp.l a2,a0 ble.s range_set_pointer rts unhandled_err_txt: dc.b "ERROR: Unhandled exception!",13,10,0 even unhandled_error: pea unhandled_err_txt(pc) move.w #9,-(sp) trap #1 ; Cconws addq.l #6,sp move.w #1,-(sp) move.w #76,-(sp) trap #1 ; Pterm rte_only: rte vbl_mini: addq.l #1,$462 ; TOS vbl counter rte ;-- Check if the SCU is present (only on MegaSTE and TT) ;-- If writing to $FF8E01 and $FF8E0D doesn't cause a bus error, then we have an SCU ;-- If so, we enable the following interrupts in SCU's sys_mask and vme_mask : ;-- hsync (level 2), vsync (level 4), scc (level 5) and mfp (level 6) ;-- This is similar to what normal TOS does on boot config_scu: move.l $8.w,a0 ; save bus error handler move.l a7,a6 ; save A7/SSP in case of bus error changing the stack lea config_scu_error(pc),a1 move.l a1,$8.w move.b #$14,$ffff8e01.w ; enable hsync and vsync move.b #$60,$ffff8e0d.w ; enable scc and mfp config_scu_error: move.l a0,$8.w ; restore bus error handler move.l a6,a7 ; restore A7/SSP rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/faketosData.c000066400000000000000000000046701504763705000237310ustar00rootroot00000000000000/* * Hatari - fakeTosData.c * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * NOTE: This file is included by tos.c - do not compile and link this file * separately! */ const char FakeTosData_fileid[] = "Hatari fakeTosData.c"; /* * This is the assembled code from fakeTos.s, our ROM TOS replacement for * testing. NOTE: Remove first 0x1c (PRG_HEADER_SIZE) bytes from the assembled * program file or use an assembler like TurboAss or vasm that can generate * absolute binary images. * * Assemble fakeTos.s using vasm without header : * * vasmm68k_mot -nosym -devpac -showopt -o fakeTos.bin -Fbin faketos.s * * Convert the assembled binary with: * * hexdump -v -e ' 16/1 "0x%02x," "\n" ' fakeTos.bin > fakeTos.txt * * Then replace the data below in FakeTos_data[] with the contents of * fakeTos.txt (edit the last line to remove empty "0x,") */ const uint8_t FakeTos_data[] = { 0x60,0x2e,0x00,0x01,0x00,0xe0,0x00,0x30,0x00,0xe0,0x00,0x00,0x00,0x00,0x10,0x00, 0x00,0xe0,0x00,0x30,0x00,0x00,0x00,0x00,0x03,0x03,0x20,0x18,0x00,0x01,0x4c,0x63, 0x00,0x00,0x08,0x80,0x00,0x00,0x08,0x70,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00, 0x46,0xfc,0x27,0x00,0x4e,0x70,0x11,0xfc,0x00,0x05,0x80,0x01,0x4f,0xf9,0x00,0x02, 0x00,0x00,0x61,0x00,0x00,0xbe,0x43,0xfa,0x00,0x9a,0x30,0x7c,0x00,0x08,0x34,0x7c, 0x01,0xbc,0x61,0x68,0x43,0xfa,0x00,0xa2,0x30,0x7c,0x00,0x64,0x34,0x7c,0x00,0x7c, 0x61,0x5a,0x30,0x7c,0x01,0x00,0x34,0x7c,0x01,0xbc,0x61,0x50,0x43,0xfa,0x00,0x8c, 0x21,0xc9,0x00,0x70,0x42,0xb9,0x00,0x00,0x04,0x62,0x41,0xf8,0xfa,0x00,0x11,0x7c, 0x00,0x48,0x00,0x11,0x41,0xf8,0xfc,0x00,0x10,0xbc,0x00,0x03,0x10,0xbc,0x00,0x16, 0x41,0xf9,0x00,0xfa,0x00,0x00,0x0c,0x90,0xab,0xcd,0xef,0x42,0x66,0x02,0x00,0x0a, 0x70,0x00,0x20,0x40,0x22,0x40,0x46,0xfc,0x03,0x00,0x4f,0xf9,0x00,0x01,0x80,0x00, 0x48,0x78,0x10,0x00,0x48,0x7a,0xff,0x4a,0x4e,0xf8,0x11,0x00,0x20,0xc9,0xb1,0xca, 0x6f,0xfa,0x4e,0x75,0x45,0x52,0x52,0x4f,0x52,0x3a,0x20,0x55,0x6e,0x68,0x61,0x6e, 0x64,0x6c,0x65,0x64,0x20,0x65,0x78,0x63,0x65,0x70,0x74,0x69,0x6f,0x6e,0x21,0x0d, 0x0a,0x00,0x48,0x7a,0xff,0xe0,0x3f,0x3c,0x00,0x09,0x4e,0x41,0x5c,0x8f,0x3f,0x3c, 0x00,0x01,0x3f,0x3c,0x00,0x4c,0x4e,0x41,0x4e,0x73,0x52,0xb9,0x00,0x00,0x04,0x62, 0x4e,0x73,0x20,0x78,0x00,0x08,0x2c,0x4f,0x43,0xfa,0x00,0x12,0x21,0xc9,0x00,0x08, 0x11,0xfc,0x00,0x14,0x8e,0x01,0x11,0xfc,0x00,0x60,0x8e,0x0d,0x21,0xc8,0x00,0x08, 0x2e,0x4e,0x4e,0x75 }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/000077500000000000000000000000001504763705000225725ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/CMakeLists.txt000066400000000000000000000003561504763705000253360ustar00rootroot00000000000000 if(ENABLE_DSP_EMU) set(DSP_SOURCES dsp_core.c dsp_cpu.c dsp_disasm.c) endif(ENABLE_DSP_EMU) add_library(Falcon crossbar.c microphone.c nvram.c videl.c dsp.c ${DSP_SOURCES}) target_link_libraries(Falcon PRIVATE ${SDL2_LIBRARIES}) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/crossbar.c000066400000000000000000002222531504763705000245620ustar00rootroot00000000000000/* Hatari - Crossbar.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Falcon Crossbar (Matrice) emulation. input device: - DSP transmit (SSI) - external DSP connector - ADC (micro + PSG chip) - DMA playback output device: - external DSP connector - DSP receive (SSI) - DAC (headphone, loudspeaker and monitor sound) - DMA record There are 3 possible clocks : - internal clock 25,175 MHz (Ste compatible) - internal clock 32 MHz - external clock (DSP external port, up to 32 Mhz) Transfers between 2 devices can use handshaking or continuous mode Hardware I/O registers: $FF8900 (byte) : Sound DMA control $FF8901 (byte) : Sound DMA control $FF8903 (byte) : Frame Start Hi $FF8905 (byte) : Frame Start Mi $FF8907 (byte) : Frame Start Lo $FF8909 (byte) : Frame Count Hi $FF890B (byte) : Frame Count Mi $FF890D (byte) : Frame Count Lo $FF890F (byte) : Frame End Hi $FF8911 (byte) : Frame End Mi $FF8913 (byte) : Frame End Lo $FF8920 (byte) : Sound Mode Control $FF8921 (byte) : Sound Mode Control $FF8930 (word) : DMA Crossbar Input Select Controller $FF8932 (word) : DMA Crossbar Output Select Controller $FF8934 (byte) : External Sync Frequency Divider $FF8935 (byte) : Internal Sync Frequency Divider $FF8936 (byte) : Record Track select $FF8937 (byte) : Codec Input Source $FF8938 (byte) : Codec ADC Input $FF8939 (byte) : Gain Settings Per Channel $FF893A (word) : Attenuation Settings Per Channel $FF893C (word) : Codec Status $FF8940 (word) : GPIO Data Direction $FF8942 (word) : GPIO Data Crossbar schematics: - one receiving device can be connected to only one source device - one source device can be connected to multiple receiving device Source devices CROSSBAR EXT INPUT ---O------O------O-----O CHANNEL | | | | | | | | DSP ---O------O------O-----O TRANSMIT | | | | | | | | Mic L -----| DMA ---O------O------O-----O /---|XOR ----|\ PLAYBACK | | | | PSG --| | \ | | | | \---| | /-------X--------O------O------O-----O Mic R -----|XOR ----|/ | | | | | ADC | | DMA DSP EXT OUTPUT Receiving Devices | | RECEIVE CHANNEL | | ----------------- \ + / \-----------/ | | ----- \ / DAC \ / | | Output to: - header, - internal speaker, - monitor speaker Additional notes [NP] : Some registers are not fully described in Atari's documentation. The following results were measured on a real Falcon : - Once audio DMA is playing or recording, it is not possible to change the loop mode by writing at $FF8901. If play mode was started with loop mode, clearing bit 1 at $FF8901 will have no effect, loop mode will remain active. It's only when play or record are stopped then started again that the loop bit will be taken into account. - SOUNDINT/SNDINT interrupt description is not fully accurate in the "Falcon030 Service Guide, Oct 1992" : "- SINT/SNDINT : This output is low when sound DMA is active and high otherwise. It will make a high to low transition at the beginning of a frame of sound data and a low to high transition at the end of frame. This signal can be programmed to come from either the record or play channels - SCNT/SOUNDINT : This output is similar to SINT/SNDINT but wider." As measured on a real Falcon, value of SOUNDINT/SNDINT depends on the content of $FF8900 - If bit 0 is cleared at $FF8900 then SNDINT will always be 1, whether DMA is playing or not. This means that when reading GPIP bit 7 at $FFFA01 it will always be 1 and there's no way to tell if DMA play is active or not. - If bit 0 is set at $FF8900 the SNDINT will be 0 when DMA is playing and 1 when DMA is idle Similar behaviour applies for Timer A Input bit and for record mode. SNDINT is connected to MFP's GPIP7 and SOUNDINT is connected to MFP's TAI. By setting the corresponding bit in AER (bit 7 for GPIP7 and bit 4 for TAI) it is then possible to have an interrupt that triggers on start of frame (when AER bit=0) or on end of frame (when AER bit=1) In loop mode, DMA signal will briefly goes from active to idle then active again ; this transition to "idle" allows to have an interrupt at the end of each sample or at the start of the next sample, depending on AER value. See Crossbar_Update_DMA_Sound_Line() for more details */ const char crossbar_fileid[] = "Hatari Crossbar.c"; #include "main.h" #include "audio.h" #include "configuration.h" #include "cycles.h" #include "cycInt.h" #include "dmaSnd.h" #include "m68000.h" #include "ioMem.h" #include "log.h" #include "memorySnapShot.h" #include "mfp.h" #include "sound.h" #include "crossbar.h" #include "microphone.h" #include "stMemory.h" #include "dsp.h" #include "clocks_timings.h" #include "video.h" #define DACBUFFER_SIZE 2048 #define DECIMAL_PRECISION 65536 /* Values for SOUNDINT DMA signal : 0/LOW=DMA active 1/HIGH=DMA idle */ /* SNDINT use the same values as SOUNDINT, so we use the same define's */ #define CROSSBAR_SOUNDINT_STATE_LOW 0 #define CROSSBAR_SOUNDINT_STATE_HIGH 1 /* Crossbar internal functions */ static int Crossbar_DetectSampleRate(uint16_t clock); static void Crossbar_Start_InterruptHandler_25Mhz(void); static void Crossbar_Start_InterruptHandler_32Mhz(void); /* Dma_Play sound functions */ static void Crossbar_setDmaPlay_Settings(void); static void Crossbar_Process_DMAPlay_Transfer(void); /* Dma_Record sound functions */ static void Crossbar_setDmaRecord_Settings(void); void Crossbar_SendDataToDmaRecord(int16_t value); static void Crossbar_Process_DMARecord_HandshakeMode(void); /* Dsp Xmit functions */ static void Crossbar_SendDataToDspReceive(uint32_t value, uint16_t frame); static void Crossbar_Process_DSPXmit_Transfer(void); /* DAC functions */ static void Crossbar_SendDataToDAC(int16_t value, uint16_t sample_pos); /* ADC functions */ static void Crossbar_Process_ADCXmit_Transfer(void); /* external data used by the MFP */ uint16_t nCbar_DmaSoundControl; /* internal data */ /* dB = 20log(gain) : gain = antilog(dB/20) */ /* Table gain values = (int)(powf(10.0, dB/20.0)*65536.0 + 0.5) 1.5dB steps */ /* Values for Codec's ADC volume control (* DECIMAL_PRECISION) */ /* PSG must be amplified by 2.66.. before mixing with crossbar */ /* The ADC table values are multiplied by 2'2/3 and divided */ /* by 4 (later multiplied by 4) eg 43691 = 65536 * 2.66.. / 4.0 */ static const uint16_t Crossbar_ADC_volume_table[16] = { 3276, 3894, 4628, 5500, 6537, 7769, 9234, 10975, 13043, 15502, 18424, 21897, 26025, 30931, 36761, 43691 }; /* Values for Codec's DAC volume control (* DECIMAL_PRECISION) */ static const uint16_t Crossbar_DAC_volume_table[16] = { 65535, 55142, 46396, 39037, 32846, 27636, 23253, 19565, 16462, 13851, 11654, 9806, 8250, 6942, 5841, 4915 }; static const int Ste_SampleRates[4] = { 6258, 12517, 25033, 50066 }; static const int Falcon_SampleRates_25Mhz[15] = { 49170, 32780, 24585, 19668, 16390, 14049, 12292, 10927, 9834, 8940, 8195, 7565, 7024, 6556, 6146 }; static const int Falcon_SampleRates_32Mhz[15] = { 62500, 41666, 31250, 25000, 20833, 17857, 15624, 13889, 12500, 11363, 10416, 9615, 8928, 8333, 7812 }; struct dma_s { uint32_t frameStartAddr; /* Sound frame start */ uint32_t frameEndAddr; /* Sound frame end */ uint32_t frameCounter; /* Counter in current sound frame */ uint32_t frameLen; /* TODO: Remove when it's ok to break memory snapshots (was: Length of the frame) */ uint32_t isRunning; /* Is Playing / Recording ? */ uint32_t loopMode; /* Loop mode enabled ? */ uint32_t currentFrame; /* Current Frame Played / Recorded (in stereo, 2 frames = 1 track) */ uint32_t timerA_int; /* Timer A interrupt at end of Play / Record ? */ uint32_t mfp15_int; /* MFP-15 interrupt at end of Play / Record ? */ uint32_t isConnectedToCodec; uint32_t isConnectedToDsp; uint32_t isConnectedToDspInHandShakeMode; uint32_t isConnectedToDma; uint32_t handshakeMode_Frame; /* state of the frame in handshake mode */ uint32_t handshakeMode_masterClk; /* 0 = crossbar master clock ; 1 = DSP master clock */ }; struct crossbar_s { uint32_t dmaSelected; /* 1 = DMA Record; 0 = DMA Play */ uint32_t playTracks; /* number of tracks played */ uint32_t recordTracks; /* number of tracks recorded */ uint16_t track_monitored; /* track monitored by the DAC */ uint32_t is16Bits; /* 0 = 8 bits; 1 = 16 bits */ uint32_t isStereo; /* 0 = mono; 1 = stereo */ uint32_t steFreq; /* from 0 (6258 Hz) to 3 (50066 Hz) */ uint32_t isInSteFreqMode; /* 0 = Falcon frequency mode ; 1 = Ste frequency mode */ uint32_t int_freq_divider; /* internal frequency divider */ uint32_t isDacMuted; /* 0 = DAC is running; 1 = DAC is muted */ uint32_t dspXmit_freq; /* 0 = 25 Mhz ; 1 = external clock ; 2 = 32 Mhz */ uint32_t dmaPlay_freq; /* 0 = 25 Mhz ; 1 = external clock ; 2 = 32 Mhz */ uint16_t codecInputSource; /* codec input source */ uint16_t codecAdcInput; /* codec ADC input */ uint16_t gainSettingLeft; /* Left channel gain for ADC */ uint16_t gainSettingRight; /* Right channel gain for ADC */ uint16_t attenuationSettingLeft; /* Left channel attenuation for DAC */ uint16_t attenuationSettingRight; /* Right channel attenuation for DAC */ uint16_t microphone_ADC_is_started; uint32_t clock25_cycles; /* cycles for 25 Mzh interrupt */ uint32_t clock25_cycles_decimal; /* decimal part of cycles counter for 25 Mzh interrupt (*DECIMAL_PRECISION) */ uint32_t clock25_cycles_counter; /* Cycle counter for 25 Mhz interrupts */ uint32_t pendingCyclesOver25; /* Number of delayed cycles for the interrupt */ uint32_t clock32_cycles; /* cycles for 32 Mzh interrupt */ uint32_t clock32_cycles_decimal; /* decimal part of cycles counter for 32 Mzh interrupt (*DECIMAL_PRECISION) */ uint32_t clock32_cycles_counter; /* Cycle counter for 32 Mhz interrupts */ uint32_t pendingCyclesOver32; /* Number of delayed cycles for the interrupt */ int64_t frequence_ratio; /* Ratio between host computer's sound frequency and hatari's sound frequency */ int64_t frequence_ratio2; /* Ratio between hatari's sound frequency and host computer's sound frequency */ uint32_t dmaPlay_CurrentFrameStart; /* current DmaPlay Frame start ($ff8903 $ff8905 $ff8907) */ uint32_t dmaPlay_CurrentFrameCount; /* current DmaRecord Frame start ($ff8903 $ff8905 $ff8907) */ uint32_t dmaPlay_CurrentFrameEnd; /* current DmaRecord Frame start ($ff8903 $ff8905 $ff8907) */ uint32_t dmaRecord_CurrentFrameStart; /* current DmaRecord Frame end ($ff890f $ff8911 $ff8913) */ uint32_t dmaRecord_CurrentFrameCount; /* current DmaRecord Frame start ($ff8903 $ff8905 $ff8907) */ uint32_t dmaRecord_CurrentFrameEnd; /* current DmaRecord Frame end ($ff890f $ff8911 $ff8913) */ uint32_t adc2dac_readBufferPosition; /* read position for direct adc->dac transfer */ int64_t adc2dac_readBufferPosition_float; /* float value of read position for direct adc->dac transfer index */ uint32_t save_special_transfer; /* Used in a special undocumented transfer mode (dsp sent is not in handshake mode and dsp receive is in handshake mode) */ uint8_t SNDINT_Signal; /* Value of the SNDINT signal (connected to MFP's GPIP7) */ uint8_t SOUNDINT_Signal; /* Value of the SOUNDINT signal (connected to MFP's Timer A input) */ }; struct codec_s { int16_t buffer_left[DACBUFFER_SIZE]; int16_t buffer_right[DACBUFFER_SIZE]; int64_t readPosition_float; uint32_t readPosition; uint32_t writePosition; uint32_t isConnectedToCodec; uint32_t isConnectedToDsp; uint32_t isConnectedToDma; uint32_t wordCount; }; struct dsp_s { uint32_t isTristated; /* 0 = DSP is not tristated; 1 = DSP is tristated */ uint32_t isInHandshakeMode; /* 0 = not in handshake mode; 1 = in handshake mode */ uint32_t isConnectedToCodec; uint32_t isConnectedToDsp; uint32_t isConnectedToDma; uint32_t wordCount; /* count number of words received from DSP transmitter (for TX frame computing) */ }; static struct crossbar_s crossbar; static struct dma_s dmaPlay; static struct dma_s dmaRecord; static struct codec_s dac; static struct codec_s adc; static struct dsp_s dspXmit; static struct dsp_s dspReceive; /** * Reset Crossbar variables. */ void Crossbar_Reset(bool bCold) { nCbar_DmaSoundControl = 0; /* Stop DMA sound playing / record */ IoMem_WriteByte(0xff8901,0); dmaPlay.isRunning = 0; dmaPlay.loopMode = 0; dmaPlay.currentFrame = 0; dmaPlay.isConnectedToDspInHandShakeMode = 0; dmaPlay.handshakeMode_Frame = 0; dmaPlay.handshakeMode_masterClk = 0; dmaRecord.isRunning = 0; dmaRecord.loopMode = 0; dmaRecord.currentFrame = 0; dmaRecord.isConnectedToDspInHandShakeMode = 0; dmaRecord.handshakeMode_Frame = 0; dmaRecord.handshakeMode_masterClk = 0; /* DMA stopped, force SNDINT/SOUNDINT to 1/HIGH (idle) */ crossbar.SNDINT_Signal = MFP_GPIP_STATE_HIGH; crossbar.SOUNDINT_Signal = MFP_GPIP_STATE_HIGH; MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE7 , crossbar.SNDINT_Signal ); MFP_TimerA_Set_Line_Input ( pMFP_Main , crossbar.SOUNDINT_Signal ); /* DAC inits */ memset(dac.buffer_left, 0, sizeof(dac.buffer_left)); memset(dac.buffer_right, 0, sizeof(dac.buffer_right)); dac.readPosition_float = 0; dac.readPosition = 0; dac.writePosition = (dac.readPosition+DACBUFFER_SIZE/2)%DACBUFFER_SIZE; /* ADC inits */ memset(adc.buffer_left, 0, sizeof(adc.buffer_left)); memset(adc.buffer_right, 0, sizeof(adc.buffer_right)); adc.readPosition_float = 0; adc.readPosition = 0; adc.writePosition = (adc.readPosition+DACBUFFER_SIZE/2)%DACBUFFER_SIZE; /* DSP inits */ dspXmit.wordCount = 0; /* Crossbar inits */ crossbar.clock25_cycles = 160; crossbar.clock25_cycles_decimal = 0; crossbar.clock25_cycles_counter = 0; crossbar.pendingCyclesOver25 = 0; crossbar.clock32_cycles = 160; crossbar.clock32_cycles_decimal = 0; crossbar.clock32_cycles_counter = 0; crossbar.pendingCyclesOver32 = 0; crossbar.frequence_ratio = 0; crossbar.frequence_ratio2 = 0; crossbar.dmaSelected = 0; crossbar.track_monitored = 0; crossbar.isInSteFreqMode = 1; crossbar.int_freq_divider = 0; crossbar.steFreq = 3; crossbar.playTracks = 1; crossbar.is16Bits = 0; crossbar.isStereo = 1; crossbar.codecInputSource = 3; crossbar.codecAdcInput = 3; crossbar.gainSettingLeft = 3276; crossbar.gainSettingRight = 3276; crossbar.attenuationSettingLeft = 65535; crossbar.attenuationSettingRight = 65535; crossbar.adc2dac_readBufferPosition = 0; crossbar.adc2dac_readBufferPosition_float = 0; /* Start 25 Mhz and 32 Mhz Clocks */ Crossbar_Recalculate_Clocks_Cycles(); Crossbar_Start_InterruptHandler_25Mhz(); Crossbar_Start_InterruptHandler_32Mhz(); /* Start Microphone jack emulation */ if (crossbar.microphone_ADC_is_started == 0) { crossbar.microphone_ADC_is_started = Microphone_Start((int)nAudioFrequency); } /* Initialize special transfer mode */ crossbar.save_special_transfer = 0; /* Initialize Crossbar values after reboot */ IoMem_WriteByte(0xff8900,0x05); IoMem_WriteByte(0xff8903,0xff); IoMem_WriteByte(0xff8905,0xff); IoMem_WriteByte(0xff8907,0xfe); IoMem_WriteByte(0xff8909,0xff); IoMem_WriteByte(0xff890b,0xff); IoMem_WriteByte(0xff890d,0xfe); IoMem_WriteByte(0xff890f,0xff); IoMem_WriteByte(0xff8911,0xff); IoMem_WriteByte(0xff8913,0xfe); IoMem_WriteWord(0xff893c,0x2401); } /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void Crossbar_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&nCbar_DmaSoundControl, sizeof(nCbar_DmaSoundControl)); MemorySnapShot_Store(&dmaPlay, sizeof(dmaPlay)); MemorySnapShot_Store(&dmaRecord, sizeof(dmaRecord)); MemorySnapShot_Store(&crossbar, sizeof(crossbar)); MemorySnapShot_Store(&dac, sizeof(dac)); MemorySnapShot_Store(&adc, sizeof(adc)); MemorySnapShot_Store(&dspXmit, sizeof(dspXmit)); MemorySnapShot_Store(&dspReceive, sizeof(dspReceive)); /* After restoring, update the clock/freq counters */ if ( !bSave ) Crossbar_Recalculate_Clocks_Cycles(); } /*----------------------------------------------------------------------*/ /* Hardware I/O functions */ /*----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/ /** * Update the value of the SNDINT/SOUNDINT lines * - SNDINT is the same line as SINT on the DMA chip and is connected to MFP's GPIP7 * - SOUNDINT is the same line as SCNT on the DMA chip and is connected to MFP's TAI * * Description from the "Falcon030 Service Guide, Oct 1992" : * - SINT/SNDINT : This output is low when sound DMA is active and high otherwise. * It will make a high to low transition at the beginning of a frame of sound data * and a low to high transition at the end of frame. This signal can be programmed * to come from either the record or play channels * - SCNT/SOUNDINT : This output is similar to SINT/SNDINT but wider. * * Depending on the transition and MFP's AER, this can trigger MFP interrupt for Timer A or for GPIP7 * - Bit is set to 0/LOW when dma sound is playing / recording * - Bit is set to 1/HIGH when dma sound is idle * * As measured on a real Falcon, if corresponding bit is set in FF8900 then SNDINT/SOUNDINT * will be updated to HIGH or LOW when idle or active * If bit is clear in FF8900 then SNDINT/SOUNDINT will always remain HIGH, even when * DMA is playing or recording. * * Timer A input is using AER bit 4, GPIP7 is using AER bit 7 * * Under default TOS configuration, AER bit4=0, so Timer A input will trigger * an interrupt at the start of a frame. * * This is different (opposite) from the STE/TT, where bit is set to 1/HIGH when playing * and 0/LOW when idle. So, under default TOS configuration STE/TT will trigger * Timer A interrupt at the end of a frame. * */ static void Crossbar_Update_DMA_Sound_Line ( bool PlayMode , uint8_t Bit ) { bool SetGPIP7 , SetTAI; if ( PlayMode ) { SetGPIP7 = dmaPlay.mfp15_int; SetTAI = dmaPlay.timerA_int; } else { SetGPIP7 = dmaRecord.mfp15_int; SetTAI = dmaRecord.timerA_int; } /* If mfp15_int is set we use the value of Bit, else line is always high */ if ( SetGPIP7 ) crossbar.SNDINT_Signal = Bit; else crossbar.SNDINT_Signal = CROSSBAR_SOUNDINT_STATE_HIGH; /* If timerA_int is set we use the value of Bit, else line is always high */ if ( SetTAI ) crossbar.SOUNDINT_Signal = Bit; else crossbar.SOUNDINT_Signal = CROSSBAR_SOUNDINT_STATE_HIGH; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : MFP GPIP7 set bit=%d VBL=%d HBL=%d\n", crossbar.SNDINT_Signal , nVBLs , nHBL); MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE7 , crossbar.SNDINT_Signal ); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : MFP TAI set bit=%d VBL=%d HBL=%d\n", crossbar.SOUNDINT_Signal , nVBLs , nHBL); MFP_TimerA_Set_Line_Input ( pMFP_Main , crossbar.SOUNDINT_Signal ); /* Update events count / interrupt for timer A if needed */ } static void Crossbar_Play_Update_DMA_Sound_Line_Active ( void ) { Crossbar_Update_DMA_Sound_Line ( true , CROSSBAR_SOUNDINT_STATE_LOW ); } static void Crossbar_Play_Update_DMA_Sound_Line_Idle ( void ) { Crossbar_Update_DMA_Sound_Line ( true , CROSSBAR_SOUNDINT_STATE_HIGH ); } static void Crossbar_Record_Update_DMA_Sound_Line_Active ( void ) { Crossbar_Update_DMA_Sound_Line ( false , CROSSBAR_SOUNDINT_STATE_LOW ); } static void Crossbar_Record_Update_DMA_Sound_Line_Idle ( void ) { Crossbar_Update_DMA_Sound_Line ( false , CROSSBAR_SOUNDINT_STATE_HIGH ); } /*-----------------------------------------------------------------------*/ /** * Return the value of the SNDINT line, used to update MFP's GPIP7 */ uint8_t Crossbar_Get_SNDINT_Line (void) { return crossbar.SNDINT_Signal; } /** * Write byte to Microwire Mask register(0xff8924). * Note: On Falcon, the Microwire is not present. * But for compatibility with the STe, Atari implemented the Microwire * as follow (when one writes at the following address): * $ff8922: always reads 0 for any value written at this address * $ff8924: NOT the value, then 8 cycles later, NOT the value again to its initial value. */ void Crossbar_Microwire_WriteWord(void) { uint16_t microwire = IoMem_ReadWord(0xff8924); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8924 (MicroWire Mask) write: 0x%04x\n", microwire); /* NOT the value and store it */ microwire = ~microwire; IoMem_WriteWord(0xff8924, microwire); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8924 (MicroWire Mask) NOT value: 0x%04x\n", microwire); /* Start a new Microwire interrupt */ CycInt_AddRelativeInterrupt(8, INT_CPU_CYCLE, INTERRUPT_DMASOUND_MICROWIRE); } /** * Crossbar Microwire mask interrupt, called from dmaSnd.c */ void Crossbar_InterruptHandler_Microwire(void) { uint16_t microwire = IoMem_ReadWord(0xff8924); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* NOT the value again to it's original value and store it */ microwire = ~microwire; IoMem_WriteWord(0xff8924, microwire); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8924 (MicroWire Mask) NOT value to original: 0x%04x\n", microwire); } /** * Write byte to buffer interrupts (0xff8900). */ void Crossbar_BufferInter_WriteByte(void) { uint8_t dmaCtrl = IoMem_ReadByte(0xff8900); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8900 (Sound DMA control) write: 0x%02x\n", dmaCtrl); dmaPlay.timerA_int = (dmaCtrl & 0x4) >> 2; dmaPlay.mfp15_int = (dmaCtrl & 0x1); dmaRecord.timerA_int = (dmaCtrl & 0x8) >> 3; dmaRecord.mfp15_int = (dmaCtrl & 0x2) >> 1; } /** * Write byte from DMA control register (0xff8901). */ void Crossbar_DmaCtrlReg_WriteByte(void) { uint8_t sndCtrl = IoMem_ReadByte(0xff8901); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8901 (additional Sound DMA control) write: 0x%02x VBL=%d HBL=%d\n", sndCtrl, nVBLs , nHBL ); crossbar.dmaSelected = (sndCtrl & 0x80) >> 7; /* DMA Play mode */ if ((dmaPlay.isRunning == 0) && (sndCtrl & CROSSBAR_SNDCTRL_PLAY)) { /* Turning on DMA Play sound emulation */ dmaPlay.isRunning = 1; dmaPlay.loopMode = (sndCtrl & 0x2) >> 1; nCbar_DmaSoundControl = sndCtrl; Crossbar_setDmaPlay_Settings(); } else if (dmaPlay.isRunning && ((sndCtrl & CROSSBAR_SNDCTRL_PLAY) == 0)) { /* Create samples up until this point with current values */ Sound_Update ( Cycles_GetClockCounterOnWriteAccess() ); /* Turning off DMA play sound emulation */ dmaPlay.isRunning = 0; dmaPlay.loopMode = 0; nCbar_DmaSoundControl = sndCtrl; Crossbar_Play_Update_DMA_Sound_Line_Idle (); /* 1/HIGH=dma sound play idle */ } /* DMA Record mode */ if ((dmaRecord.isRunning == 0) && (sndCtrl & CROSSBAR_SNDCTRL_RECORD)) { /* Turning on DMA record sound emulation */ dmaRecord.isRunning = 1; dmaRecord.loopMode = (sndCtrl & 0x20) >> 5; nCbar_DmaSoundControl = sndCtrl; Crossbar_setDmaRecord_Settings(); } else if (dmaRecord.isRunning && ((sndCtrl & CROSSBAR_SNDCTRL_RECORD) == 0)) { /* Turning off DMA record sound emulation */ dmaRecord.isRunning = 0; dmaRecord.loopMode = 0; nCbar_DmaSoundControl = sndCtrl; Crossbar_Record_Update_DMA_Sound_Line_Idle (); /* O/LOW=dma sound record idle */ } } /** * Read byte from sound frame start high register (0xff8903). */ void Crossbar_FrameStartHigh_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff8903, crossbar.dmaPlay_CurrentFrameStart >> 16); } else { /* DMA Record selected */ IoMem_WriteByte(0xff8903, crossbar.dmaRecord_CurrentFrameStart >> 16); } } /** * Write byte to sound frame start high register (0xff8903). */ void Crossbar_FrameStartHigh_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8903 (Sound frame start high) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff8903) , nVBLs , nHBL); addr = (IoMem_ReadByte(0xff8903) << 16) + (IoMem_ReadByte(0xff8905) << 8) + IoMem_ReadByte(0xff8907); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameStart = addr & ~1; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameStart = addr & ~1; } } /** * Read byte from sound frame start medium register (0xff8905). */ void Crossbar_FrameStartMed_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff8905, crossbar.dmaPlay_CurrentFrameStart >> 8); } else { /* DMA Record selected */ IoMem_WriteByte(0xff8905, crossbar.dmaRecord_CurrentFrameStart >> 8); } } /** * Write byte to sound frame start medium register (0xff8905). */ void Crossbar_FrameStartMed_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8905 (Sound frame start med) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff8905) , nVBLs , nHBL); addr = (IoMem_ReadByte(0xff8903) << 16) + (IoMem_ReadByte(0xff8905) << 8) + IoMem_ReadByte(0xff8907); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameStart = addr & ~1; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameStart = addr & ~1; } } /** * Read byte from sound frame start low register (0xff8907). */ void Crossbar_FrameStartLow_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff8907, crossbar.dmaPlay_CurrentFrameStart); } else { /* DMA Record selected */ IoMem_WriteByte(0xff8907, crossbar.dmaRecord_CurrentFrameStart); } } /** * Write byte to sound frame start low register (0xff8907). */ void Crossbar_FrameStartLow_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8907 (Sound frame start low) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff8907) , nVBLs , nHBL); addr = (IoMem_ReadByte(0xff8903) << 16) + (IoMem_ReadByte(0xff8905) << 8) + IoMem_ReadByte(0xff8907); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameStart = addr & ~1; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameStart = addr & ~1; } } /*-----------------------------------------------------------------------*/ /** * Read byte from sound frame count high register (0xff8909). */ void Crossbar_FrameCountHigh_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff8909, (dmaPlay.frameStartAddr + dmaPlay.frameCounter) >> 16); } else { /* DMA Record selected */ IoMem_WriteByte(0xff8909, (dmaRecord.frameStartAddr + dmaRecord.frameCounter) >> 16); } } /** * Write byte to sound frame count high register (0xff8909). */ void Crossbar_FrameCountHigh_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8909 (Sound frame count high) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff8909) , nVBLs , nHBL); /* Compute frameCounter current address */ addr = (IoMem_ReadByte(0xff8909) << 16) + (IoMem_ReadByte(0xff890b) << 8) + IoMem_ReadByte(0xff890d); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameCount = addr; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameCount = addr; } } /** * Read byte from sound frame count medium register (0xff890b). */ void Crossbar_FrameCountMed_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff890b, (dmaPlay.frameStartAddr + dmaPlay.frameCounter) >> 8); } else { /* DMA Record selected */ IoMem_WriteByte(0xff890b, (dmaRecord.frameStartAddr + dmaRecord.frameCounter) >> 8); } } /** * Write byte to sound frame count medium register (0xff890b). */ void Crossbar_FrameCountMed_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff890b (Sound frame count med) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff890b) , nVBLs , nHBL); /* Compute frameCounter current address */ addr = (IoMem_ReadByte(0xff8909) << 16) + (IoMem_ReadByte(0xff890b) << 8) + IoMem_ReadByte(0xff890d); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameCount = addr; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameCount = addr; } } /** * Read byte from sound frame count low register (0xff890d). */ void Crossbar_FrameCountLow_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff890d, (dmaPlay.frameStartAddr + dmaPlay.frameCounter)); } else { /* DMA Record selected */ IoMem_WriteByte(0xff890d, (dmaRecord.frameStartAddr + dmaRecord.frameCounter)); } } /** * Write byte to sound frame count low register (0xff890d). */ void Crossbar_FrameCountLow_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff890d (Sound frame count low) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff890d) , nVBLs , nHBL); /* Compute frameCounter current address */ addr = (IoMem_ReadByte(0xff8909) << 16) + (IoMem_ReadByte(0xff890b) << 8) + IoMem_ReadByte(0xff890d); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameCount = addr; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameCount = addr; } } /*-----------------------------------------------------------------------*/ /** * Read byte from sound frame end high register (0xff890f). */ void Crossbar_FrameEndHigh_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff890f, crossbar.dmaPlay_CurrentFrameEnd >> 16); } else { /* DMA Record selected */ IoMem_WriteByte(0xff890f, crossbar.dmaRecord_CurrentFrameEnd >> 16); } } /** * Write byte to sound frame end high register (0xff890f). */ void Crossbar_FrameEndHigh_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff890f (Sound frame end high) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff890f) , nVBLs , nHBL); addr = (IoMem_ReadByte(0xff890f) << 16) + (IoMem_ReadByte(0xff8911) << 8) + IoMem_ReadByte(0xff8913); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameEnd = addr & ~1; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameEnd = addr & ~1; } } /** * Read byte from sound frame end medium register (0xff8911). */ void Crossbar_FrameEndMed_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff8911, crossbar.dmaPlay_CurrentFrameEnd >> 8); } else { /* DMA Record selected */ IoMem_WriteByte(0xff8911, crossbar.dmaRecord_CurrentFrameEnd >> 8); } } /** * Write byte to sound frame end medium register (0xff8911). */ void Crossbar_FrameEndMed_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8911 (Sound frame end med) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff8911) , nVBLs , nHBL); addr = (IoMem_ReadByte(0xff890f) << 16) + (IoMem_ReadByte(0xff8911) << 8) + IoMem_ReadByte(0xff8913); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameEnd = addr & ~1; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameEnd = addr & ~1; } } /** * Read byte from sound frame end low register (0xff8913). */ void Crossbar_FrameEndLow_ReadByte(void) { if (crossbar.dmaSelected == 0) { /* DMA Play selected */ IoMem_WriteByte(0xff8913, crossbar.dmaPlay_CurrentFrameEnd); } else { /* DMA Record selected */ IoMem_WriteByte(0xff8913, crossbar.dmaRecord_CurrentFrameEnd); } } /** * Write byte to sound frame end low register (0xff8913). */ void Crossbar_FrameEndLow_WriteByte(void) { uint32_t addr; LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8913 (Sound frame end low) write: 0x%02x VBL=%d HBL=%d\n", IoMem_ReadByte(0xff8913) , nVBLs , nHBL); addr = (IoMem_ReadByte(0xff890f) << 16) + (IoMem_ReadByte(0xff8911) << 8) + IoMem_ReadByte(0xff8913); if (crossbar.dmaSelected == 0) { /* DMA Play selected */ crossbar.dmaPlay_CurrentFrameEnd = addr & ~1; } else { /* DMA Record selected */ crossbar.dmaRecord_CurrentFrameEnd = addr & ~1; } } /*-----------------------------------------------------------------------*/ /** * Write byte to DMA track control (0xff8920). */ void Crossbar_DmaTrckCtrl_WriteByte(void) { uint8_t sndCtrl = IoMem_ReadByte(0xff8920); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8920 (sound mode control) write: 0x%02x\n", sndCtrl); crossbar.playTracks = (sndCtrl & 3) + 1; crossbar.track_monitored = (sndCtrl & 30) >> 4; } /** * Write word to sound mode register (0xff8921). */ void Crossbar_SoundModeCtrl_WriteByte(void) { uint8_t sndCtrl = IoMem_ReadByte(0xff8921); LOG_TRACE(TRACE_CROSSBAR, "crossbar : $ff8921 (additional sound mode control) write: 0x%02x\n", sndCtrl); crossbar.is16Bits = (sndCtrl & 0x40) >> 6; crossbar.isStereo = 1 - ((sndCtrl & 0x80) >> 7); crossbar.steFreq = sndCtrl & 0x3; Crossbar_Recalculate_Clocks_Cycles(); } /** * Write word to Falcon Crossbar source controller (0xff8930). Source: A/D Converter BIT 15 14 13 12 00 - 25.175Mhz clock -------------------------+--+ 01 - External clock --------------------------+--+ 10 - 32Mhz clock (Don't use) -----------------+--' Source: External Input BIT 11 10 9 8 0 - DSP IN, 1 - All others ----------------' | | | 00 - 25.175Mhz clock -------------------------+--+ | 01 - External clock --------------------------+--+ | 10 - 32Mhz clock -----------------------------+--' | 0 - Handshake on, 1 - Handshake off ----------------' Source: DSP-XMIT BIT 7 6 5 4 0 - Tristate and disconnect DSP -----------+ | | | (Only for external SSI use) | | | | 1 - Connect DSP to multiplexer ------------' | | | 00 - 25.175Mhz clock -------------------------+--+ | 01 - External clock --------------------------+--+ | 10 - 32Mhz clock -----------------------------+--' | 0 - Handshake on, 1 - Handshake off ----------------' Source: DMA-PLAYBACK BIT 3 2 1 0 0 - Handshaking on, dest DSP-REC ----------+ | | | 1 - Destination is not DSP-REC ------------' | | | 00 - 25.175Mhz clock -------------------------+--+ | 01 - External clock --------------------------+--+ | 10 - 32Mhz clock -----------------------------+--' | 0 - Handshake on, 1 - Handshake off ----------------' */ void Crossbar_SrcControler_WriteWord(void) { uint16_t nCbSrc = IoMem_ReadWord(0xff8930); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8930 (source device) write: 0x%04x\n", nCbSrc); dspXmit.isTristated = 1 - ((nCbSrc >> 7) & 0x1); dspXmit.isInHandshakeMode = 1 - ((nCbSrc >> 4) & 0x1); crossbar.dspXmit_freq = (nCbSrc >> 5) & 0x3; crossbar.dmaPlay_freq = (nCbSrc >> 1) & 0x3; } /** * Write word to Falcon Crossbar destination controller (0xff8932). Source: D/A Converter BIT 15 14 13 12 00 - DMA output ------------------------------+--+ 01 - DSP output ------------------------------+--+ 10 - External input --------------------------+--+ 11 - ADC input -------------------------------+--' Source: External OutPut BIT 11 10 9 8 0 - DSP OUT, 1 - All others ---------------' | | | 00 - DMA output ------------------------------+--+ | 01 - DSP output ------------------------------+--+ | 10 - External input --------------------------+--+ | 11 - ADC input -------------------------------+--' | 0 - Handshake on, 1 - Handshake off ----------------' Source: DSP-RECEIVE BIT 7 6 5 4 0 - Tristate and disconnect DSP -----------+ | | | (Only for external SSI use) | | | | 1 - Connect DSP to multiplexer ------------' | | | 00 - DMA output ------------------------------+--+ | 01 - DSP output ------------------------------+--+ | 10 - External input --------------------------+--+ | 11 - ADC input -------------------------------+--' | 0 - Handshake on, 1 - Handshake off ----------------' Source: DMA-RECORD BIT 3 2 1 0 0 - Handshaking on, dest DSP-XMIT ---------+ | | | 1 - All -----------------------------------' | | | 00 - DMA output ------------------------------+--+ | 01 - DSP output ------------------------------+--+ | 10 - External input --------------------------+--+ | 11 - ADC input -------------------------------+--' | 0 - Handshake on, 1 - Handshake off ----------------' */ void Crossbar_DstControler_WriteWord(void) { uint16_t destCtrl = IoMem_ReadWord(0xff8932); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8932 (destination device) write: 0x%04x\n", destCtrl); dspReceive.isTristated = 1 - ((destCtrl & 0x80) >> 7); dspReceive.isInHandshakeMode = 1 - ((destCtrl & 0x10) >> 4); /* destinations devices connexions */ dspReceive.isConnectedToCodec = (destCtrl & 0x60) == 0x60 ? 1 : 0; dspReceive.isConnectedToDsp = (destCtrl & 0x60) == 0x20 ? 1 : 0; dspReceive.isConnectedToDma = (destCtrl & 0x60) == 0x00 ? 1 : 0; dmaRecord.isConnectedToCodec = (destCtrl & 0x6) == 0x6 ? 1 : 0; dmaRecord.isConnectedToDsp = (destCtrl & 0x6) == 0x2 ? 1 : 0; dmaRecord.isConnectedToDma = (destCtrl & 0x6) == 0x0 ? 1 : 0; dac.isConnectedToCodec = (destCtrl & 0x6000) == 0x6000 ? 1 : 0; dac.isConnectedToDsp = (destCtrl & 0x6000) == 0x2000 ? 1 : 0; dac.isConnectedToDma = (destCtrl & 0x6000) == 0x0000 ? 1 : 0; /* sources devices connexions */ dspXmit.isConnectedToCodec = (destCtrl & 0x6000) == 0x2000 ? 1 : 0; dspXmit.isConnectedToDsp = (destCtrl & 0x60) == 0x20 ? 1 : 0; dspXmit.isConnectedToDma = (destCtrl & 0x6) == 0x2 ? 1 : 0; dmaPlay.isConnectedToCodec = (destCtrl & 0x6000) == 0x0000 ? 1 : 0; dmaPlay.isConnectedToDsp = (destCtrl & 0x60) == 0x00 ? 1 : 0; dmaPlay.isConnectedToDma = (destCtrl & 0x6) == 0x0 ? 1 : 0; adc.isConnectedToCodec = (destCtrl & 0x6000) == 0x6000 ? 1 : 0; adc.isConnectedToDsp = (destCtrl & 0x60) == 0x60 ? 1 : 0; adc.isConnectedToDma = (destCtrl & 0x6) == 0x6 ? 1 : 0; dmaPlay.isConnectedToDspInHandShakeMode = (((destCtrl >> 4) & 7) == 0 ? 1 : 0); dmaPlay.handshakeMode_Frame = dmaPlay.isConnectedToDspInHandShakeMode; dmaPlay.handshakeMode_masterClk = 0; dmaRecord.isConnectedToDspInHandShakeMode = ((destCtrl & 0xf) == 2 ? 1 : 0); } /** * Write byte to external clock divider register (0xff8934). */ void Crossbar_FreqDivExt_WriteByte(void) { LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8934 (ext. clock divider) write: 0x%02x\n", IoMem_ReadByte(0xff8934)); } /** * Write byte to internal clock divider register (0xff8935). */ void Crossbar_FreqDivInt_WriteByte(void) { uint8_t clkDiv = IoMem_ReadByte(0xff8935); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8935 (int. clock divider) write: 0x%02x\n", clkDiv); crossbar.int_freq_divider = clkDiv & 0xf; Crossbar_Recalculate_Clocks_Cycles(); } /** * Write byte to record track select register (0xff8936). * 0 = Record 1 track * 1 = Record 2 tracks * 2 = Record 3 tracks * 3 = Record 4 tracks */ void Crossbar_TrackRecSelect_WriteByte(void) { uint8_t recTrack = IoMem_ReadByte(0xff8936); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8936 (record track select) write: 0x%02x\n", recTrack); crossbar.recordTracks = recTrack & 3; } /** * Write byte to CODEC input source from 16 bit adder (0xff8937). * Bit 1 : source = multiplexer * Bit 0 : source = A/D converter */ void Crossbar_CodecInput_WriteByte(void) { uint8_t inputSource = IoMem_ReadByte(0xff8937); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8937 (CODEC input) write: 0x%02x\n", IoMem_ReadByte(0xff8937)); crossbar.codecInputSource = inputSource & 3; } /** * Write byte to A/D converter input for L+R channel (0xff8938). * Bit 1 : Left (0 = Microphone ; 1 = PSG soundchip) * Bit 0 : Right (0 = Microphone ; 1 = PSG soundchip) */ void Crossbar_AdcInput_WriteByte(void) { uint8_t input = IoMem_ReadByte(0xff8938); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8938 (ADC input) write: 0x%02x\n", IoMem_ReadByte(0xff8938)); crossbar.codecAdcInput = input & 3; } /** * Write byte to input amplifier register (amplification for ADC device) (0xff8939). * Bits LLLLRRRR * Amplification is in +1.5 dB steps */ void Crossbar_InputAmp_WriteByte(void) { uint8_t amplification = IoMem_ReadByte(0xff8939); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff8939 (CODEC channel amplification) write: 0x%02x\n", IoMem_ReadByte(0xff8939)); crossbar.gainSettingLeft = Crossbar_ADC_volume_table[amplification >> 4]; crossbar.gainSettingRight = Crossbar_ADC_volume_table[amplification & 0xf]; } /** * Write byte to channel reduction register (attenuation for DAC device) (0xff893a). * Bits XXXXLLLL RRRRXXXX * Reduction is in -1.5 dB steps */ void Crossbar_OutputReduct_WriteWord(void) { uint16_t reduction = IoMem_ReadWord(0xff893a); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff893a (CODEC channel attenuation) write: 0x%04x\n", reduction); crossbar.attenuationSettingLeft = Crossbar_DAC_volume_table[(reduction >> 8) & 0x0f]; crossbar.attenuationSettingRight = Crossbar_DAC_volume_table[(reduction >> 4) & 0x0f]; } /** * Write word to CODEC status register (0xff893c). * Bit 1 : Left Channel Overflow (0/1) * Bit 0 : Right Channel Overflow (0/1) */ void Crossbar_CodecStatus_WriteWord(void) { LOG_TRACE(TRACE_CROSSBAR, "Crossbar : $ff893c (CODEC status) write: 0x%04x\n", IoMem_ReadWord(0xff893c)); } /*----------------------------------------------------------------------*/ /*------------------------- Crossbar functions -------------------------*/ /*----------------------------------------------------------------------*/ /** * Recalculates internal clocks 25 Mhz and 32 Mhz cycles */ void Crossbar_Recalculate_Clocks_Cycles(void) { double cyclesClk; crossbar.clock25_cycles_counter = 0; crossbar.clock32_cycles_counter = 0; /* Calculate 25 Mhz clock cycles */ /* Take nCpuFreqShift into account to keep a constant sound rate at all cpu freq */ cyclesClk = ((double)( MachineClocks.CPU_Freq_Emul ) / Crossbar_DetectSampleRate(25)) / (double)(crossbar.playTracks) / 2.0; crossbar.clock25_cycles = (int)(cyclesClk); crossbar.clock25_cycles_decimal = (int)((cyclesClk - (double)(crossbar.clock25_cycles)) * (double)DECIMAL_PRECISION); //fprintf ( stderr , "freq_25=%d cyclesclk=%f cyc_int=%d cyc_float=%d\n",Crossbar_DetectSampleRate(25), cyclesClk, crossbar.clock25_cycles, crossbar.clock25_cycles_decimal); /* Calculate 32 Mhz clock cycles */ /* Take nCpuFreqShift into account to keep a constant sound rate at all cpu freq */ cyclesClk = ((double)( MachineClocks.CPU_Freq_Emul ) / Crossbar_DetectSampleRate(32)) / (double)(crossbar.playTracks) / 2.0; crossbar.clock32_cycles = (int)(cyclesClk); crossbar.clock32_cycles_decimal = (int)((cyclesClk - (double)(crossbar.clock32_cycles)) * (double)DECIMAL_PRECISION); //fprintf ( stderr , "freq_32=%d cyclesclk=%f cyc_int=%d cyc_float=%d\n",Crossbar_DetectSampleRate(32), cyclesClk, crossbar.clock25_cycles, crossbar.clock25_cycles_decimal); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : Recalculate_clock_Cycles\n"); LOG_TRACE(TRACE_CROSSBAR, " clock25 : %d\n", crossbar.clock25_cycles); LOG_TRACE(TRACE_CROSSBAR, " clock32 : %d\n", crossbar.clock32_cycles); /* Verify if the new frequency doesn't mute the DAC */ crossbar.isDacMuted = 0; if ((crossbar.int_freq_divider == 0) && (crossbar.steFreq == 0)) crossbar.isDacMuted = 1; if ((crossbar.int_freq_divider == 6) || (crossbar.int_freq_divider == 8) || (crossbar.int_freq_divider == 10) || (crossbar.int_freq_divider >= 12)) { crossbar.isDacMuted = 1; LOG_TRACE(TRACE_CROSSBAR, " DAC is muted\n"); } // Compute Ratio between host computer sound frequency and Hatari's sound frequency. Crossbar_Compute_Ratio(); // Ensure dac.writePosition is correctly set based on current dac.readPosition // -> force dac.wordCount=0 to update dac.writePosition on next call to Crossbar_GenerateSamples() dac.wordCount = 0; } /** * Compute Ratio between host computer sound frequency and Hatari's DAC sound frequency and * ratio between hatari's DAC sound frequency and host's sound frequency. * Both values use << 32 to simulate floating point precision * Can be called by audio.c if a sound frequency value is changed in the parameter GUI. */ void Crossbar_Compute_Ratio(void) { crossbar.frequence_ratio = ( ((int64_t)Crossbar_DetectSampleRate(25)) << 32) / nAudioFrequency; crossbar.frequence_ratio2 = ( ((int64_t)nAudioFrequency) << 32) / Crossbar_DetectSampleRate(25); } /** * Detect sample rate frequency * clock : value of the internal clock (25 or 32). */ static int Crossbar_DetectSampleRate(uint16_t clock) { /* Ste compatible sound */ if (crossbar.int_freq_divider == 0) { crossbar.isInSteFreqMode = 1; return Ste_SampleRates[crossbar.steFreq]; } crossbar.isInSteFreqMode = 0; /* 25 Mhz internal clock */ if (clock == 25) return Falcon_SampleRates_25Mhz[crossbar.int_freq_divider - 1]; /* 32 Mhz internal clock */ return Falcon_SampleRates_32Mhz[crossbar.int_freq_divider - 1]; } /** * Start internal 25 Mhz clock interrupt. */ static void Crossbar_Start_InterruptHandler_25Mhz(void) { uint32_t cycles_25; //fprintf ( stderr , "start int25 %x %x %x %x\n" , crossbar.clock25_cycles, crossbar.clock25_cycles_counter, crossbar.clock25_cycles_decimal, crossbar.pendingCyclesOver25 ); cycles_25 = crossbar.clock25_cycles; crossbar.clock25_cycles_counter += crossbar.clock25_cycles_decimal; if (crossbar.clock25_cycles_counter >= DECIMAL_PRECISION) { crossbar.clock25_cycles_counter -= DECIMAL_PRECISION; cycles_25 ++; } if (crossbar.pendingCyclesOver25 >= cycles_25) { crossbar.pendingCyclesOver25 -= cycles_25; cycles_25 = 0; } else { cycles_25 -= crossbar.pendingCyclesOver25; crossbar.pendingCyclesOver25 = 0; } CycInt_AddRelativeInterrupt(cycles_25, INT_CPU_CYCLE, INTERRUPT_CROSSBAR_25MHZ); } /** * Start internal 32 Mhz clock interrupt. */ static void Crossbar_Start_InterruptHandler_32Mhz(void) { uint32_t cycles_32; //fprintf ( stderr , "start int32 %x %x %x %x\n" , crossbar.clock32_cycles, crossbar.clock32_cycles_counter, crossbar.clock32_cycles_decimal, crossbar.pendingCyclesOver32 ); cycles_32 = crossbar.clock32_cycles; crossbar.clock32_cycles_counter += crossbar.clock32_cycles_decimal; if (crossbar.clock32_cycles_counter >= DECIMAL_PRECISION) { crossbar.clock32_cycles_counter -= DECIMAL_PRECISION; cycles_32 ++; } if (crossbar.pendingCyclesOver32 >= cycles_32){ crossbar.pendingCyclesOver32 -= cycles_32; cycles_32 = 0; } else { cycles_32 -= crossbar.pendingCyclesOver32; crossbar.pendingCyclesOver32 = 0; } CycInt_AddRelativeInterrupt(cycles_32, INT_CPU_CYCLE, INTERRUPT_CROSSBAR_32MHZ); } /** * Execute transfers for internal 25 Mhz clock. */ void Crossbar_InterruptHandler_25Mhz(void) { //fprintf ( stderr , "int25 %x\n" , crossbar.pendingCyclesOver25 ); /* How many cycle was this sound interrupt delayed (>= 0) */ crossbar.pendingCyclesOver25 += -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* If transfer mode is in Ste mode, use only this clock for all the transfers */ if (crossbar.isInSteFreqMode) { Crossbar_Process_DSPXmit_Transfer(); Crossbar_Process_DMAPlay_Transfer(); Crossbar_Process_ADCXmit_Transfer(); /* Restart the 25 Mhz clock interrupt */ Crossbar_Start_InterruptHandler_25Mhz(); return; } Crossbar_Process_ADCXmit_Transfer(); /* DSP Play transfer ? */ if (crossbar.dspXmit_freq == CROSSBAR_FREQ_25MHZ) { Crossbar_Process_DSPXmit_Transfer(); } /* DMA Play transfer ? */ if (crossbar.dmaPlay_freq == CROSSBAR_FREQ_25MHZ) { Crossbar_Process_DMAPlay_Transfer(); } /* Restart the 25 Mhz clock interrupt */ Crossbar_Start_InterruptHandler_25Mhz(); } /** * Execute transfers for internal 32 Mhz clock. */ void Crossbar_InterruptHandler_32Mhz(void) { //fprintf ( stderr , "int32 %x\n" , crossbar.pendingCyclesOver32 ); /* How many cycle was this sound interrupt delayed (>= 0) */ crossbar.pendingCyclesOver32 += -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* If transfer mode is in Ste mode, don't use this clock for all the transfers */ if (crossbar.isInSteFreqMode) { /* Restart the 32 Mhz clock interrupt */ Crossbar_Start_InterruptHandler_32Mhz(); return; } /* DSP Play transfer ? */ if (crossbar.dspXmit_freq == CROSSBAR_FREQ_32MHZ) { Crossbar_Process_DSPXmit_Transfer(); } /* DMA Play transfer ? */ if (crossbar.dmaPlay_freq == CROSSBAR_FREQ_32MHZ) { Crossbar_Process_DMAPlay_Transfer(); } /* Restart the 32 Mhz clock interrupt */ Crossbar_Start_InterruptHandler_32Mhz(); } /*----------------------------------------------------------------------*/ /*--------------------- DSP Xmit processing ----------------------------*/ /*----------------------------------------------------------------------*/ /** * Process DSP xmit to crossbar transfer */ static void Crossbar_Process_DSPXmit_Transfer(void) { uint16_t frame=0; int32_t data; /* If DSP Xmit is tristated, do nothing */ if (dspXmit.isTristated) return; /* Is DSP Xmit connected to DMA Record in handshake mode ? */ if (dmaRecord.isConnectedToDspInHandShakeMode) { Crossbar_Process_DMARecord_HandshakeMode(); return; } /* Is DSP Xmit connected to something ? */ if (!dspXmit.isConnectedToCodec && !dspXmit.isConnectedToDma && !dspXmit.isConnectedToDsp) return; if (dspXmit.wordCount == 0) { frame = 1; } /* Send the frame status to the DSP SSI Xmit */ DSP_SsiReceive_SC2(frame); /* Send the clock to the DSP SSI Xmit */ DSP_SsiReceive_SCK(); /* read data from DSP Xmit */ data = DSP_SsiReadTxValue(); LOG_TRACE(TRACE_CROSSBAR, "Crossbar : DSP --> Crossbar transfer\t0x%06x\n", data); /* Send DSP data to the DAC ? */ if (dspXmit.isConnectedToCodec) { Crossbar_SendDataToDAC(data, dspXmit.wordCount); } /* Send DSP data to the DMA record ? */ if (dspXmit.isConnectedToDma) { Crossbar_SendDataToDmaRecord(data); } /* Send DSP data to the DSP in ? */ if (dspXmit.isConnectedToDsp) { Crossbar_SendDataToDspReceive(data, frame); } /* increase dspXmit.wordCount for next sample */ dspXmit.wordCount++; if (dspXmit.wordCount >= (crossbar.playTracks * 2)) { dspXmit.wordCount = 0; } } /*----------------------------------------------------------------------*/ /*--------------------- DSP Receive processing -------------------------*/ /*----------------------------------------------------------------------*/ /** * Transmit data from crossbar to DSP receive. */ static void Crossbar_SendDataToDspReceive(uint32_t value, uint16_t frame) { /* Verify that DSP IN is not tristated */ if (dspReceive.isTristated) { return; } /* Send sample to DSP receive */ DSP_SsiWriteRxValue(value); /* Send the frame status to the DSP SSI receive */ /* only in non handshake mode */ if (dmaPlay.handshakeMode_Frame == 0) { DSP_SsiReceive_SC1(frame); } dmaPlay.handshakeMode_Frame = 0; /* Send the clock to the DSP SSI receive */ DSP_SsiReceive_SC0(); } /*----------------------------------------------------------------------*/ /*--------------------- DMA PLAY sound processing ----------------------*/ /*----------------------------------------------------------------------*/ /** * Set DMA Play sound start frame buffer, stop frame buffer, frame length */ static void Crossbar_setDmaPlay_Settings(void) { /* DMA settings */ dmaPlay.frameStartAddr = crossbar.dmaPlay_CurrentFrameStart; dmaPlay.frameEndAddr = crossbar.dmaPlay_CurrentFrameEnd; dmaPlay.frameLen = dmaPlay.frameEndAddr - dmaPlay.frameStartAddr; /* TODO: Remove later */ // dmaPlay.frameCounter = crossbar.dmaPlay_CurrentFrameCount - crossbar.dmaPlay_CurrentFrameStart; dmaPlay.frameCounter = 0; if (dmaPlay.frameEndAddr <= dmaPlay.frameStartAddr) { Log_Printf(LOG_WARN, "crossbar DMA Play: Illegal buffer size (from 0x%06x to 0x%06x)\n", dmaPlay.frameStartAddr, dmaPlay.frameEndAddr); } /* DMA sound play : update SNDINT */ Crossbar_Play_Update_DMA_Sound_Line_Active (); /* 0/LOW=dma sound play ON */ } /** * Process DMA Play transfer to crossbar */ static void Crossbar_Process_DMAPlay_Transfer(void) { uint16_t temp, increment_frame; int16_t value, eightBits; uint32_t nFramePos; uint8_t dmaCtrlReg; /* if DMA play is not running, return */ if (dmaPlay.isRunning == 0) return; nFramePos = (dmaPlay.frameStartAddr + dmaPlay.frameCounter) & (DMA_MaskAddressHigh() << 16 | 0xffff); increment_frame = 0; /* 16 bits stereo mode ? */ if (crossbar.is16Bits) { eightBits = 1; value = (int16_t)STMemory_DMA_ReadWord(nFramePos); increment_frame = 2; } /* 8 bits stereo ? */ else if (crossbar.isStereo) { eightBits = 64; value = (int8_t)STMemory_DMA_ReadByte(nFramePos); increment_frame = 1; } /* 8 bits mono */ else { eightBits = 64; value = (int8_t)STMemory_DMA_ReadByte(nFramePos); if ((dmaPlay.currentFrame & 1) == 0) { increment_frame = 1; } } //fprintf ( stderr , "cbar %x %x %x\n" , dmaPlay.frameCounter , value , increment_frame ); // if (dmaPlay.isConnectedToDspInHandShakeMode && dmaPlay.handshakeMode_Frame != 0) { if (dmaPlay.isConnectedToDspInHandShakeMode == 1 && dmaPlay.handshakeMode_masterClk == 1) { /* Handshake mode */ if (dmaPlay.handshakeMode_Frame == 0) return; dmaPlay.frameCounter += increment_frame; /* Special undocumented transfer mode : When DMA Play --> DSP Receive is in HandShake mode at 32 Mhz, data are shifted 2 bits on the left after the transfer. This occurs with all demos using the Mpeg2 player from nocrew (amanita, LostBlubb, Wait, ...) */ if (crossbar.dmaPlay_freq == CROSSBAR_FREQ_32MHZ) { temp = (crossbar.save_special_transfer<<2) + ((value & 0xc000)>>14); crossbar.save_special_transfer = value; value = temp; } } else { /* Non Handshake mode */ dmaPlay.frameCounter += increment_frame; } /* Send sample to the DMA record ? */ if (dmaPlay.isConnectedToDma) { LOG_TRACE(TRACE_CROSSBAR, "Crossbar : DMA Play --> DMA record\n"); Crossbar_SendDataToDmaRecord(value); } /* Send sample to the DAC ? */ if (dmaPlay.isConnectedToCodec) { LOG_TRACE(TRACE_CROSSBAR, "Crossbar : DMA Play --> DAC\n"); Crossbar_SendDataToDAC(value * eightBits, dmaPlay.currentFrame); } /* Send sample to the DSP in ? */ if (dmaPlay.isConnectedToDsp) { LOG_TRACE(TRACE_CROSSBAR, "Crossbar : DMA Play --> DSP record\n"); /* New frame ? */ if (dmaPlay.currentFrame == 0) { Crossbar_SendDataToDspReceive(value, 1); } else { Crossbar_SendDataToDspReceive(value, 0); } } /* increase dmaPlay.currentFrame for next sample */ dmaPlay.currentFrame ++; if (dmaPlay.currentFrame >= (crossbar.playTracks * 2)) { dmaPlay.currentFrame = 0; } /* Check if end-of-frame has been reached and raise interrupts if needed. */ if (dmaPlay.frameStartAddr + dmaPlay.frameCounter >= dmaPlay.frameEndAddr) { /* DMA sound idle : update SNDINT */ Crossbar_Play_Update_DMA_Sound_Line_Idle (); /* 1/HIGH=dma sound play idle */ if (dmaPlay.loopMode) { Crossbar_setDmaPlay_Settings(); /* start a new frame */ } else { //fprintf ( stderr , "cbar %x %x %x end\n" , dmaPlay.frameStartAddr , dmaPlay.frameCounter , dmaPlay.frameEndAddr ); /* Create samples up until this point with current values */ Sound_Update ( CyclesGlobalClockCounter ); dmaCtrlReg = IoMem_ReadByte(0xff8901) & 0xfe; IoMem_WriteByte(0xff8901, dmaCtrlReg); /* Turning off DMA play sound emulation */ dmaPlay.isRunning = 0; dmaPlay.loopMode = 0; nCbar_DmaSoundControl = dmaCtrlReg; } } } /** * Function called when DmaPlay is in handshake mode */ void Crossbar_DmaPlayInHandShakeMode(void) { dmaPlay.handshakeMode_masterClk = 1; dmaPlay.handshakeMode_Frame = 1; } /*----------------------------------------------------------------------*/ /*--------------------- DMA Record processing --------------------------*/ /*----------------------------------------------------------------------*/ /** * Set DMA Record sound start frame buffer, stop frame buffer, frame length */ static void Crossbar_setDmaRecord_Settings(void) { /* DMA settings */ dmaRecord.frameStartAddr = crossbar.dmaRecord_CurrentFrameStart; dmaRecord.frameEndAddr = crossbar.dmaRecord_CurrentFrameEnd; dmaRecord.frameLen = dmaRecord.frameEndAddr - dmaRecord.frameStartAddr; /* TODO: Remove later */ // dmaRecord.frameCounter = crossbar.dmaRecord_CurrentFrameCount - crossbar.dmaRecord_CurrentFrameStart; dmaRecord.frameCounter = 0; if (dmaRecord.frameEndAddr <= dmaRecord.frameStartAddr) { Log_Printf(LOG_WARN, "crossbar DMA Record: Illegal buffer size (from 0x%06x to 0x%06x)\n", dmaRecord.frameStartAddr, dmaRecord.frameEndAddr); } /* DMA sound record : update SNDINT */ Crossbar_Record_Update_DMA_Sound_Line_Active (); /* 0/LOW=dma sound record ON */ } /** * DMA Record processing */ void Crossbar_SendDataToDmaRecord(int16_t value) { uint32_t nFramePos; uint8_t dmaCtrlReg; if (dmaRecord.isRunning == 0) { return; } nFramePos = (dmaRecord.frameStartAddr + dmaRecord.frameCounter) & (DMA_MaskAddressHigh() << 16 | 0xffff); /* 16 bits stereo mode ? */ if (crossbar.is16Bits) { STMemory_DMA_WriteWord(nFramePos, value); dmaRecord.frameCounter += 2; } /* 8 bits stereo ? */ else if (crossbar.isStereo) { STMemory_DMA_WriteWord(nFramePos, value); dmaRecord.frameCounter += 2; // pFrameStart[dmaRecord.frameCounter] = (uint8_t)value; // dmaRecord.frameCounter ++; } /* 8 bits mono */ else { STMemory_DMA_WriteByte(nFramePos, value); dmaRecord.frameCounter ++; } /* Check if end-of-frame has been reached and raise interrupts if needed. */ if (dmaRecord.frameStartAddr + dmaRecord.frameCounter >= dmaRecord.frameEndAddr) { /* DMA sound idle : update SNDINT */ Crossbar_Record_Update_DMA_Sound_Line_Idle (); /* 1/HIGH=dma sound record idle */ if (dmaRecord.loopMode) { Crossbar_setDmaRecord_Settings(); /* start a new frame */ } else { dmaCtrlReg = IoMem_ReadByte(0xff8901) & 0xef; IoMem_WriteByte(0xff8901, dmaCtrlReg); /* Turning off DMA record sound emulation */ dmaRecord.isRunning = 0; dmaRecord.loopMode = 0; nCbar_DmaSoundControl = dmaCtrlReg; } } } /** * Process DMA Record connected to DSP Xmit in HandShake mode. * In this special case, DMA Record is the "master" and Dsp Xmit is the "slave". */ static void Crossbar_Process_DMARecord_HandshakeMode(void) { int16_t data; /* If DMA record is activated and is running */ if (dmaRecord.isRunning == 0) { return; } /* If DSP frame is activated (SC2 pin of the SSI port) */ if (dmaRecord.handshakeMode_Frame == 0) { return; } /* Send the clock to the DSP SSI Xmit */ DSP_SsiReceive_SCK(); /* read data from DSP Xmit */ data = DSP_SsiReadTxValue(); dmaRecord.handshakeMode_Frame = 0; Crossbar_SendDataToDmaRecord(data); } /** * Get the frame value from DSP SSI (handshake mode only) */ void Crossbar_DmaRecordInHandShakeMode_Frame(uint32_t frame) { dmaRecord.handshakeMode_Frame = frame; } /*----------------------------------------------------------------------*/ /*-------------------------- ADC processing ----------------------------*/ /*----------------------------------------------------------------------*/ /** * Get data recorded by the microphone and convert them into falcon internal frequency * - micro_bufferL : left track recorded by the microphone * - micro_bufferR : right track recorded by the microphone * - microBuffer_size : buffers size */ void Crossbar_GetMicrophoneDatas(int16_t *micro_bufferL, int16_t *micro_bufferR, uint32_t microBuffer_size) { uint32_t i, size, bufferIndex; int64_t idxPos; size = (microBuffer_size * crossbar.frequence_ratio>>32); bufferIndex = 0; idxPos = 0; for (i = 0; i < size; i++) { adc.writePosition = (adc.writePosition + 1) % DACBUFFER_SIZE; adc.buffer_left[adc.writePosition] = micro_bufferL[bufferIndex]; adc.buffer_right[adc.writePosition] = micro_bufferR[bufferIndex]; idxPos += crossbar.frequence_ratio2; bufferIndex += idxPos>>32; idxPos &= 0xffffffff; /* only keep the fractional part */ } } /** * Process ADC transfer to crossbar */ static void Crossbar_Process_ADCXmit_Transfer(void) { int16_t sample; uint16_t frame; /* swap from left to right channel or right to left channel */ adc.wordCount = 1 - adc.wordCount; /* Left Channel */ if (adc.wordCount == 0) { sample = adc.buffer_left[adc.readPosition]; frame = 1; } else { sample = adc.buffer_right[adc.readPosition]; adc.readPosition = (adc.readPosition + 1) % DACBUFFER_SIZE; frame = 0; } /* Send sample to DSP receive ? */ if (adc.isConnectedToDsp) { Crossbar_SendDataToDspReceive(sample, frame); } /* Send sample to DMA record ? */ if (adc.isConnectedToDma) { Crossbar_SendDataToDmaRecord(sample); } /* Send sample to DAC ? */ if (adc.isConnectedToCodec) { Crossbar_SendDataToDAC(sample, adc.wordCount); } } /*----------------------------------------------------------------------*/ /*-------------------------- DAC processing ----------------------------*/ /*----------------------------------------------------------------------*/ /** * Put sample from crossbar into the DAC buffer. * - value : sample value to play * - sample_pos : position of the sample in the track (used to play the monitored track) */ static void Crossbar_SendDataToDAC(int16_t value, uint16_t sample_pos) { uint16_t track = crossbar.track_monitored * 2; //fprintf ( stderr , "datadac %x %x\n" , value , dac.writePosition ); /* Increase counter for each sample received by the DAC */ dac.wordCount++; if (sample_pos == track) { /* Left channel */ dac.buffer_left[dac.writePosition] = value; } else if (sample_pos == track + 1) { /* Right channel */ dac.buffer_right[dac.writePosition] = value; dac.writePosition = (dac.writePosition + 1) % (DACBUFFER_SIZE); } } /** * Mix PSG sound with microphone sound in ADC. * Also mix ADC sound sample with the crossbar DAC samples. * (Called by sound.c) */ void Crossbar_GenerateSamples(int nMixBufIdx, int nSamplesToGenerate) { int i, nBufIdx; int n; int16_t adc_leftData, adc_rightData, dac_LeftData, dac_RightData; int16_t dac_read_left, dac_read_right; //fprintf ( stderr , "gen %03x %03x %03x %03x\n" , dac.writePosition , dac.readPosition , (dac.writePosition-dac.readPosition)%DACBUFFER_SIZE , nSamplesToGenerate ); //fprintf ( stderr, "codecAdcInput %d wordCount %d codecInputSource %d\n" , crossbar.codecAdcInput, dac.wordCount, crossbar.codecInputSource); //uint32_t read_pos_in = dac.readPosition; //uint64_t read_pos_float_in = dac.readPosition_float; //fprintf ( stderr , "gen_in read_pos=%d read_pos_f=%lx ratio=%lx\n" , read_pos_in,read_pos_float_in,crossbar.frequence_ratio ); if (crossbar.isDacMuted) { /* Output sound = 0 */ for (i = 0; i < nSamplesToGenerate; i++) { nBufIdx = (nMixBufIdx + i) & AUDIOMIXBUFFER_SIZE_MASK; AudioMixBuffer[nBufIdx][0] = 0; AudioMixBuffer[nBufIdx][1] = 0; } /* Counters are refreshed for when DAC becomes unmuted */ dac.readPosition = (dac.writePosition-DACBUFFER_SIZE/2)%DACBUFFER_SIZE; crossbar.adc2dac_readBufferPosition = adc.writePosition; return; } for (i = 0; i < nSamplesToGenerate; i++) { nBufIdx = (nMixBufIdx + i) & AUDIOMIXBUFFER_SIZE_MASK; /* ADC mixing (PSG sound or microphone sound for left and right channels) */ switch (crossbar.codecAdcInput) { case 0: default: /* Just here to remove compiler's warnings */ /* Microphone sound for left and right channels */ adc_leftData = adc.buffer_left[crossbar.adc2dac_readBufferPosition]; adc_rightData = adc.buffer_right[crossbar.adc2dac_readBufferPosition]; break; case 1: /* Microphone sound for left channel, PSG sound for right channel */ adc_leftData = adc.buffer_left[crossbar.adc2dac_readBufferPosition]; adc_rightData = AudioMixBuffer[nBufIdx][1]; break; case 2: /* PSG sound for left channel, microphone sound for right channel */ adc_leftData = AudioMixBuffer[nBufIdx][0]; adc_rightData = adc.buffer_right[crossbar.adc2dac_readBufferPosition]; break; case 3: /* PSG sound for left and right channels */ adc_leftData = AudioMixBuffer[nBufIdx][0]; adc_rightData = AudioMixBuffer[nBufIdx][1]; break; } /* DAC mixing (direct ADC + crossbar) */ /* If DAC didn't receive any data, we force left/right value to 0 */ if ( dac.wordCount == 0 ) /* Nothing received */ { dac_read_left = 0; dac_read_right = 0; } else { dac_read_left = dac.buffer_left[dac.readPosition]; dac_read_right = dac.buffer_right[dac.readPosition]; } switch (crossbar.codecInputSource) { case 0: default: /* Just here to remove compiler's warnings */ /* No sound */ dac_LeftData = 0; dac_RightData = 0; break; case 1: /* direct ADC->DAC sound only ADC*4/65536 */ dac_LeftData = (adc_leftData * crossbar.gainSettingLeft) >> 14; dac_RightData = (adc_rightData * crossbar.gainSettingRight) >> 14; break; case 2: /* Crossbar->DAC sound only */ dac_LeftData = dac_read_left; dac_RightData = dac_read_right; break; case 3: /* Mixing Direct ADC sound with Crossbar->DMA sound */ dac_LeftData = ((adc_leftData * crossbar.gainSettingLeft) >> 14) + dac_read_left; dac_RightData = ((adc_rightData * crossbar.gainSettingRight) >> 14) + dac_read_right; break; } AudioMixBuffer[nBufIdx][0] = (dac_LeftData * crossbar.attenuationSettingLeft) >> 16; AudioMixBuffer[nBufIdx][1] = (dac_RightData * crossbar.attenuationSettingRight) >> 16; /* Upgrade dac's buffer read pointer */ dac.readPosition_float += crossbar.frequence_ratio; n = dac.readPosition_float >> 32; /* number of samples to skip */ #if 0 if (n) { // It becomes safe to zero old data if tail has moved for (j=0; jdac's buffer read pointer */ crossbar.adc2dac_readBufferPosition_float += crossbar.frequence_ratio; n = crossbar.adc2dac_readBufferPosition_float >> 32; /* number of samples to skip */ crossbar.adc2dac_readBufferPosition = (crossbar.adc2dac_readBufferPosition + n) % DACBUFFER_SIZE; crossbar.adc2dac_readBufferPosition_float &= 0xffffffff; /* only keep the fractional part */ } //fprintf ( stderr , "gen_out read_pos_delta=%x\n" , dac.readPosition-read_pos_in ); /* If the DAC didn't receive any data since last call to Crossbar_GenerateSamples() */ /* then we need to adjust dac.writePosition to be always ahead of dac.readPosition */ if ( dac.wordCount == 0 ) { // fprintf ( stderr , "fix writepos %x (readpos %x)\n" , (dac.readPosition+DACBUFFER_SIZE/2)%DACBUFFER_SIZE , dac.readPosition ); dac.writePosition = (dac.readPosition+DACBUFFER_SIZE/2)%DACBUFFER_SIZE; } dac.wordCount = 0; } /** * display the Crossbar registers values (for debugger info command) */ void Crossbar_Info(FILE *fp, uint32_t dummy) { const char *matrixDMA, *matrixDSP, *matrixEXT, *matrixDAC; char frqDMA[11], frqDAC[11], frqDSP[11], frqEXT[11]; char frqSTE[30], frq25Mhz[30], frq32Mhz[30]; char dataSize[15]; static const char *matrix_tab[8] = { "OOHO", "OOXO", "OHOO", "OXOO", "HOOO", "XOOO", "OOOH", "OOOX" }; if (!Config_IsMachineFalcon()) { fprintf(fp, "Not Falcon - no Crossbar!\n"); return; } DmaSnd_Info(fp, 0); fprintf(fp, "\n"); fprintf(fp, "$FF8930.w : DMA Crossbar Input Select Controller : %04x\n", IoMem_ReadWord(0xff8930)); fprintf(fp, "$FF8932.w : DMA Crossbar Output Select Controller : %04x\n", IoMem_ReadWord(0xff8932)); fprintf(fp, "\n"); fprintf(fp, "$FF8934.b : External Sync Frequency Divider : %02x\n", IoMem_ReadByte(0xff8934)); fprintf(fp, "$FF8935.b : Internal Sync Frequency Divider : %02x\n", IoMem_ReadByte(0xff8935)); fprintf(fp, "$FF8936.b : Record Track select : %02x\n", IoMem_ReadByte(0xff8936)); fprintf(fp, "$FF8937.b : Codec Input Source : %02x\n", IoMem_ReadByte(0xff8937)); fprintf(fp, "$FF8938.b : Codec ADC Input : %02x\n", IoMem_ReadByte(0xff8938)); fprintf(fp, "$FF8939.b : Gain Settings Per Channel : %02x\n", IoMem_ReadByte(0xff8939)); fprintf(fp, "$FF893A.b : Attenuation Settings Per Channel : %02x\n", IoMem_ReadByte(0xff893a)); fprintf(fp, "$FF893C.w : Codec Status : %04x\n", IoMem_ReadWord(0xff893c)); fprintf(fp, "$FF8940.w : GPIO Data Direction : %04x\n", IoMem_ReadWord(0xff8940)); fprintf(fp, "$FF8942.w : GPIO Data : %04x\n", IoMem_ReadWord(0xff8942)); fprintf(fp, "\n"); /* DAC connection */ switch ((IoMem_ReadWord(0xff8932) >> 13) & 0x3) { case 0 : /* DAC connection with DMA Playback */ if ((IoMem_ReadWord(0xff8930) & 0x1) == 1) matrixDAC = "OOXO"; else matrixDAC = "OOHO"; break; case 1 : /* DAC connection with DSP Transmit */ if ((IoMem_ReadWord(0xff8930) & 0x10) == 0x10) matrixDAC = "OXOO"; else matrixDAC = "OHOO"; break; case 2 : /* DAC connection with External Input */ if ((IoMem_ReadWord(0xff8930) & 0x100) == 0x100) matrixDAC = "XOOO"; else matrixDAC = "HOOO"; break; default: /* case 3 */ /* DAC connection with ADC */ matrixDAC = "OOOX"; break; } /* DMA connection */ matrixDMA = matrix_tab[IoMem_ReadWord(0xff8932) & 0x7]; /* DSP connection */ matrixDSP = matrix_tab[(IoMem_ReadWord(0xff8932) >> 4) & 0x7]; /* External input connection */ matrixEXT = matrix_tab[(IoMem_ReadWord(0xff8932) >> 8) & 0x7]; if ((IoMem_ReadByte(0xff8935) & 0xf) == 0) { strcpy(frqDSP, "(STe Freq)"); strcpy(frqDMA, "(STe Freq)"); strcpy(frqEXT, "(STe Freq)"); strcpy(frqDAC, "(STe Freq)"); } else { /* DSP Clock */ switch ((IoMem_ReadWord(0xff8930) >> 5) & 0x3) { case 0: strcpy(frqDSP, " (25 Mhz) "); break; case 1: strcpy(frqDSP, "(External)"); break; case 2: strcpy(frqDSP, " (32 Mhz) "); break; default: strcpy(frqDSP, "undefined "); break; } /* DMA Clock */ switch ((IoMem_ReadWord(0xff8930) >> 1) & 0x3) { case 0: strcpy(frqDMA, " (25 Mhz) "); break; case 1: strcpy(frqDMA, "(External)"); break; case 2: strcpy(frqDMA, " (32 Mhz) "); break; default: strcpy(frqDMA, "undefined "); break; } /* External Clock */ switch ((IoMem_ReadWord(0xff8930) >> 9) & 0x3) { case 0: strcpy(frqEXT, " (25 Mhz) "); break; case 1: strcpy(frqEXT, "(External)"); break; case 2: strcpy(frqEXT, " (32 Mhz) "); break; default: strcpy(frqEXT, "undefined "); break; } /* DAC Clock */ strcpy(frqDAC, " (25 Mhz) "); } /* data size */ switch ((IoMem_ReadByte(0xff8921) >> 6) & 0x3) { case 0: strcpy (dataSize, "8 bits stereo"); break; case 1: strcpy (dataSize, "16 bits stereo"); break; case 2: strcpy (dataSize, "8 bits mono"); break; default: strcpy (dataSize, "undefined"); break; } /* STE, 25Mhz and 32 Mhz sound frequencies */ if ((IoMem_ReadByte(0xff8935) & 0xf) == 0) { sprintf(frqSTE, "Ste Freq : %d Khz", Ste_SampleRates[IoMem_ReadByte(0xff8921) & 0x3]); strcpy (frq25Mhz, "25 Mhz Freq : - Khz"); strcpy (frq32Mhz, "32 Mzh Freq : - Khz"); } else { strcpy (frqSTE, "Ste Freq : - Khz"); sprintf(frq25Mhz, "25 Mhz Freq : %d Khz", Falcon_SampleRates_25Mhz[(IoMem_ReadByte(0xff8935) & 0xf) - 1]); sprintf(frq32Mhz, "32 Mzh Freq : %d Khz", Falcon_SampleRates_32Mhz[(IoMem_ReadByte(0xff8935) & 0xf) - 1]); } /* Display the crossbar Matrix */ fprintf(fp, " INPUT\n"); fprintf(fp, "External Imp ---%c------%c------%c------%c\n", matrixDAC[0], matrixDMA[0], matrixDSP[0], matrixEXT[0]); fprintf(fp, "%s | | | | O = no connection\n", frqEXT); fprintf(fp, " | | | | X = connection\n"); fprintf(fp, "Dsp Transmit ---%c------%c------%c------%c H = Handshake connection\n", matrixDAC[1], matrixDMA[1], matrixDSP[1], matrixEXT[1]); fprintf(fp, "%s | | | |\n", frqDSP); fprintf(fp, " | | | | %s\n", dataSize); fprintf(fp, "DMA PlayBack ---%c------%c------%c------%c\n", matrixDAC[2], matrixDMA[2], matrixDSP[2], matrixEXT[2]); fprintf(fp, "%s | | | | Sound Freq :\n", frqDMA); fprintf(fp, " | | | | %s\n", frqSTE); fprintf(fp, "ADC ---%c------%c------%c------%c %s\n", matrixDAC[3], matrixDMA[3], matrixDSP[3], matrixEXT[3], frq25Mhz); fprintf(fp, "%s | | | | %s\n", frqDAC, frq32Mhz); fprintf(fp, " | | | |\n"); fprintf(fp, " DAC DMA DSP External OUTPUT\n"); fprintf(fp, " Record Record Out\n"); fprintf(fp, "\n"); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/crossbar.h000066400000000000000000000064111504763705000245630ustar00rootroot00000000000000/* Hatari - crossbar.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CROSSBAR_H #define HATARI_CROSSBAR_H #define CROSSBAR_SNDCTRL_PLAY 0x01 #define CROSSBAR_SNDCTRL_PLAYLOOP 0x02 #define CROSSBAR_SNDCTRL_RECORD 0x10 #define CROSSBAR_SNDCTRL_RECORDLOOP 0x20 #define CROSSBAR_FREQ_25MHZ 0x0 #define CROSSBAR_FREQ_EXTERN 0x1 #define CROSSBAR_FREQ_32MHZ 0x2 #define CROSSBAR_SNDMODE_16BITSTEREO 0x40 #define CROSSBAR_SNDMODE_MONO 0x80 extern uint16_t nCbar_DmaSoundControl; /* Called by audio.c */ void Crossbar_Compute_Ratio(void); /* Called by mfp.c */ extern void Crossbar_GenerateSamples(int nMixBufIdx, int nSamplesToGenerate); /* Called by m68000.c */ extern void Crossbar_Recalculate_Clocks_Cycles(void); extern void Crossbar_Reset(bool bCold); extern void Crossbar_MemorySnapShot_Capture(bool bSave); /* Called by ioMemTabFalcon.c */ extern void Crossbar_BufferInter_WriteByte(void); extern void Crossbar_DmaCtrlReg_WriteByte(void); extern void Crossbar_FrameStartHigh_ReadByte(void); extern void Crossbar_FrameStartHigh_WriteByte(void); extern void Crossbar_FrameStartMed_ReadByte(void); extern void Crossbar_FrameStartMed_WriteByte(void); extern void Crossbar_FrameStartLow_ReadByte(void); extern void Crossbar_FrameStartLow_WriteByte(void); extern void Crossbar_FrameCountHigh_ReadByte(void); extern void Crossbar_FrameCountHigh_WriteByte(void); extern void Crossbar_FrameCountMed_ReadByte(void); extern void Crossbar_FrameCountMed_WriteByte(void); extern void Crossbar_FrameCountLow_ReadByte(void); extern void Crossbar_FrameEndHigh_ReadByte(void); extern void Crossbar_FrameCountLow_WriteByte(void); extern void Crossbar_FrameEndHigh_WriteByte(void); extern void Crossbar_FrameEndMed_ReadByte(void); extern void Crossbar_FrameEndMed_WriteByte(void); extern void Crossbar_FrameEndLow_ReadByte(void); extern void Crossbar_FrameEndLow_WriteByte(void); extern void Crossbar_DmaTrckCtrl_WriteByte(void); extern void Crossbar_SoundModeCtrl_WriteByte(void); extern void Crossbar_SrcControler_WriteWord(void); extern void Crossbar_DstControler_WriteWord(void); extern void Crossbar_FreqDivExt_WriteByte(void); extern void Crossbar_FreqDivInt_WriteByte(void); extern void Crossbar_TrackRecSelect_WriteByte(void); extern void Crossbar_CodecInput_WriteByte(void); extern void Crossbar_AdcInput_WriteByte(void); extern void Crossbar_InputAmp_WriteByte(void); extern void Crossbar_OutputReduct_WriteWord(void); extern void Crossbar_CodecStatus_WriteWord(void); extern uint8_t Crossbar_Get_SNDINT_Line (void); extern void Crossbar_Microwire_WriteWord(void); /* Called by cycint.c */ extern void Crossbar_InterruptHandler_25Mhz(void); extern void Crossbar_InterruptHandler_32Mhz(void); /* Called by dmaSnd.c */ extern void Crossbar_InterruptHandler_Microwire(void); /* Called by dsp.c */ void Crossbar_DmaPlayInHandShakeMode(void); void Crossbar_DmaRecordInHandShakeMode_Frame(uint32_t frame); /* Called by microphone.c */ void Crossbar_GetMicrophoneDatas(int16_t *micro_bufferL, int16_t *micro_bufferR, uint32_t microBuffer_size); /* called by debugInfo.c */ extern void Crossbar_Info(FILE *fp, uint32_t dummy); #endif /* HATARI_CROSSBAR_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp.c000066400000000000000000000532171504763705000235340ustar00rootroot00000000000000/* DSP M56001 emulation Dummy emulation, Hatari glue (C) 2001-2008 ARAnyM developer team Adaption to Hatari (C) 2008 by Thomas Huth This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include "main.h" #include "sysdeps.h" #include "memorySnapShot.h" #include "ioMem.h" #include "dsp.h" #include "crossbar.h" #include "configuration.h" #include "cycles.h" #include "cycInt.h" #include "m68000.h" #if ENABLE_DSP_EMU #include "debugdsp.h" #include "dsp_cpu.h" #include "dsp_disasm.h" #endif #define DEBUG 0 #if DEBUG #define Dprintf(a) printf a #else #define Dprintf(a) #endif #define DSP_HW_OFFSET 0xFFA200 #if ENABLE_DSP_EMU static const char* x_ext_memory_addr_name[] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "PBC", "PCC", "PBDDR", "PCDDR", "PBD", "PCD", "", "", "HCR", "HSR", "", "HRX/HTX", "CRA", "CRB", "SSISR/TSR", "RX/TX", "SCR", "SSR", "SCCR", "STXA", "SRX/STX", "SRX/STX", "SRX/STX", "", "", "", "", "", "", "", "BCR", "IPR" }; static int32_t save_cycles; #endif static bool bDspDebugging; bool bDspEnabled = false; bool bDspHostInterruptPending = false; uint64_t DSP_CyclesGlobalClockCounter = 0; /* Value of CyclesGlobalClockCounter when DSP_Run was last called */ /** * Trigger HREQ interrupt at the host CPU. */ #if ENABLE_DSP_EMU static void DSP_TriggerHostInterrupt(int hreq) { //fprintf ( stderr, "DSP_TriggerHostInterrupt %d %x %x\n" , hreq , regs.sr , regs.intmask ); // TODO [NP] : we should change GPIP bit 3 in MFP instead of using additional SPCFLAG_DSP and DSP_GetHREQ if ( hreq ) { M68000_SetSpecial(SPCFLAG_DSP); // TODO for old cpu core, remove, use level 6 instead and M68000_Update_intlev() bDspHostInterruptPending = true; M68000_Update_intlev (); } else { M68000_UnsetSpecial(SPCFLAG_DSP); // TODO for old cpu core, remove, use level 6 instead and M68000_Update_intlev() bDspHostInterruptPending = false; M68000_Update_intlev (); } } #endif /** * Return the state of HREQ */ uint8_t DSP_GetHREQ ( void ) { if ( bDspHostInterruptPending ) return 1; else return 0; } /** * Return the vector number associated to the HREQ interrupt. * If this function is called when HREQ=0, then we return -1 to indicate * a spurious interrupt. */ int DSP_ProcessIACK ( void ) { int VecNr; if ( bDspHostInterruptPending ) VecNr = IoMem_ReadByte ( 0xffa203 ); else VecNr = -1; return VecNr; } /** * This function is called from the CPU emulation part when SPCFLAG_DSP is set. * If the DSP's IRQ signal is set, we check that SR allows a level 6 interrupt, * and if so, we call M68000_Exception. */ #if ENABLE_DSP_EMU bool DSP_ProcessIRQ(void) { if (bDspHostInterruptPending && regs.intmask < 6) { M68000_Exception(IoMem_ReadByte(0xffa203), M68000_EXC_SRC_INT_DSP); bDspHostInterruptPending = false; // [NP] TODO : remove this line, should be cleared by DSP_TriggerHostInterrupt ? M68000_UnsetSpecial(SPCFLAG_DSP); // [NP] TODO : remove this line, should be cleared by DSP_TriggerHostInterrupt ? return true; } return false; } #endif /** * Initialize the DSP emulation (should be called only once at start) */ void DSP_Init(void) { #if ENABLE_DSP_EMU dsp_core_init(DSP_TriggerHostInterrupt); dsp56k_init_cpu(); save_cycles = 0; #endif } /** * Shut down the DSP emulation (should be called only once at exit) */ void DSP_UnInit(void) { #if ENABLE_DSP_EMU dsp_core_shutdown(); bDspEnabled = false; #endif } /** * Reset the DSP emulation */ void DSP_Reset(void) { #if ENABLE_DSP_EMU dsp_core_reset(); DSP_TriggerHostInterrupt ( 0 ); /* Clear HREQ */ save_cycles = 0; #endif } /** * Enable the DSP emulation */ void DSP_Enable(void) { #if ENABLE_DSP_EMU if ( Config_IsMachineFalcon() ) { bDspEnabled = true; DSP_CyclesGlobalClockCounter = CyclesGlobalClockCounter; } #endif } /** * Disable the DSP emulation */ void DSP_Disable(void) { #if ENABLE_DSP_EMU bDspEnabled = false; #endif } /** * Save/Restore snapshot of CPU variables ('MemorySnapShot_Store' handles type) */ void DSP_MemorySnapShot_Capture(bool bSave) { #if ENABLE_DSP_EMU MemorySnapShot_Store(&bDspEnabled, sizeof(bDspEnabled)); MemorySnapShot_Store(&dsp_core, sizeof(dsp_core)); MemorySnapShot_Store(&save_cycles, sizeof(save_cycles)); if ( bDspEnabled ) DSP_Enable(); else DSP_Disable(); #endif } /** * Run DSP for certain cycles */ void DSP_Run(int nHostCycles) { #if ENABLE_DSP_EMU if ( nHostCycles == 0 ) return; DSP_CyclesGlobalClockCounter = CyclesGlobalClockCounter; save_cycles += nHostCycles * 2; if (dsp_core.running == 0) return; if (save_cycles <= 0) return; if (unlikely(bDspDebugging)) { while (save_cycles > 0) { dsp56k_execute_instruction(); save_cycles -= dsp_core.instr_cycle; DebugDsp_Check(); } } else { // fprintf(stderr, "--> %d\n", save_cycles); while (save_cycles > 0) { dsp56k_execute_instruction(); save_cycles -= dsp_core.instr_cycle; } } #endif } /** * Enable/disable DSP debugging mode */ void DSP_SetDebugging(bool enabled) { bDspDebugging = enabled; } /** * Get DSP program counter (for debugging) */ uint16_t DSP_GetPC(void) { #if ENABLE_DSP_EMU if (bDspEnabled) return dsp_core.pc; else #endif return 0; } /** * Get next DSP PC without output (for debugging) */ uint16_t DSP_GetNextPC(uint16_t pc) { #if ENABLE_DSP_EMU /* code is reduced copy from dsp56k_execute_one_disasm_instruction() */ dsp_core_t dsp_core_save; uint16_t instruction_length; if (!bDspEnabled) return 0; /* Save DSP context */ memcpy(&dsp_core_save, &dsp_core, sizeof(dsp_core)); /* Disasm instruction */ dsp_core.pc = pc; /* why dsp56k_execute_one_disasm_instruction() does "-1" * for this value, that doesn't seem right??? */ instruction_length = dsp56k_disasm(DSP_DISASM_MODE, stderr); /* Restore DSP context */ memcpy(&dsp_core, &dsp_core_save, sizeof(dsp_core)); return pc + instruction_length; #else return 0; #endif } /** * Get current DSP instruction cycles (for profiling) */ uint16_t DSP_GetInstrCycles(void) { #if ENABLE_DSP_EMU if (bDspEnabled) return dsp_core.instr_cycle; else #endif return 0; } /** * Disassemble DSP code between given addresses, return next PC address */ uint16_t DSP_DisasmAddress(FILE *out, uint16_t lowerAdr, uint16_t UpperAdr) { #if ENABLE_DSP_EMU uint16_t dsp_pc; for (dsp_pc=lowerAdr; dsp_pc<=UpperAdr; dsp_pc++) { dsp_pc += dsp56k_execute_one_disasm_instruction(out, dsp_pc); } return dsp_pc; #else return 0; #endif } /** * Get the value from the given (16-bit) DSP memory address / space * exactly the same way as in dsp_cpu.c::read_memory() (except for * the host/transmit peripheral register values which access has * side-effects). Set the mem_str to suitable string for that * address / space. * Return the value at given address. For valid values AND the return * value with BITMASK(24). */ uint32_t DSP_ReadMemory(uint16_t address, char space_id, const char **mem_str) { #if ENABLE_DSP_EMU static const char *spaces[3][4] = { { "X ram", "X rom", "X", "X periph" }, { "Y ram", "Y rom", "Y", "Y periph" }, { "P ram", "P rom", "P ext memory", "P ext memory" } }; int idx, space; switch (space_id) { case 'X': space = DSP_SPACE_X; idx = 0; break; case 'Y': space = DSP_SPACE_Y; idx = 1; break; case 'P': space = DSP_SPACE_P; idx = 2; break; default: space = DSP_SPACE_X; idx = 0; } address &= 0xFFFF; if (space == DSP_SPACE_P) { /* Internal RAM ? */ if (address < 0x200) { *mem_str = spaces[idx][0]; return dsp_core.ramint[DSP_SPACE_P][address]; } /* External RAM, mask address to available ram size */ *mem_str = spaces[idx][2]; return dsp_core.ramext[address & (DSP_RAMSIZE-1)]; } /* Internal RAM ? */ if (address < 0x100) { *mem_str = spaces[idx][0]; return dsp_core.ramint[space][address]; } /* Internal ROM ? */ if (address < 0x200) { if (dsp_core.registers[DSP_REG_OMR] & (1<= 0xffc0) { *mem_str = spaces[idx][3]; /* reading host/transmit regs has side-effects, * so just give the memory value. */ return dsp_core.periph[space][address-0xffc0]; } /* Falcon: External RAM, map X to upper 16K of matching space in Y,P */ address &= (DSP_RAMSIZE>>1) - 1; if (space == DSP_SPACE_X) { address += DSP_RAMSIZE>>1; } /* Falcon: External RAM, finally map X,Y to P */ *mem_str = spaces[idx][2]; return dsp_core.ramext[address & (DSP_RAMSIZE-1)]; #endif return 0; } /** * Output memory values between given addresses in given DSP address space. * Return next DSP address value. */ uint16_t DSP_DisasmMemory(FILE *fp, uint16_t dsp_memdump_addr, uint16_t dsp_memdump_upper, char space) { #if ENABLE_DSP_EMU uint32_t mem, mem2, value; const char *mem_str; for (mem = dsp_memdump_addr; mem <= dsp_memdump_upper; mem++) { /* special printing of host communication/transmit registers */ if (space == 'X' && mem >= 0xffc0) { if (mem == 0xffeb) { fprintf(fp, "X periph:%04x HTX : %06x RTX:%06x\n", mem, dsp_core.dsp_host_htx, dsp_core.dsp_host_rtx); } else if (mem == 0xffef) { fprintf(fp, "X periph:%04x SSI TX : %06x SSI RX:%06x\n", mem, dsp_core.ssi.transmit_value, dsp_core.ssi.received_value); } else { value = DSP_ReadMemory(mem, space, &mem_str); fprintf(fp, "%s:%04x %06x\t%s\n", mem_str, mem, value, x_ext_memory_addr_name[mem-0xffc0]); } continue; } /* special printing of X & Y external RAM values */ if ((space == 'X' || space == 'Y') && mem >= 0x200 && mem < 0xffc0) { mem2 = mem & ((DSP_RAMSIZE>>1)-1); if (space == 'X') { mem2 += (DSP_RAMSIZE>>1); } fprintf(fp, "%c:%04x (P:%04x): %06x\n", space, mem, mem2, dsp_core.ramext[mem2 & (DSP_RAMSIZE-1)]); continue; } value = DSP_ReadMemory(mem, space, &mem_str); fprintf(fp, "%s:%04x %06x\n", mem_str, mem, value); } #endif return dsp_memdump_upper+1; } /** * Show information on DSP core state which isn't * shown by any of the other commands (dd, dm, dr). */ void DSP_Info(FILE *fp, uint32_t dummy) { #if ENABLE_DSP_EMU int i, j; const char *stackname[] = { "SSH", "SSL" }; fputs("\nDSP core information:\n", fp); for (i = 0; i < ARRAY_SIZE(stackname); i++) { fprintf(fp, " %s stack:", stackname[i]); for (j = 0; j < ARRAY_SIZE(dsp_core.stack[0]); j++) { fprintf(fp, " %04hx", dsp_core.stack[i][j]); } fputs("\n", fp); } fprintf(stderr, "\nInterrupts:\n"); for (i = 0; i < 32; i++) { fprintf(stderr, " %s: ", dsp_interrupt_name[i]); if ((1U << i) & dsp_core.interrupt_status & (dsp_core.interrupt_mask|DSP_INTER_NMI_MASK)) { fprintf(stderr, "Pending "); } if ((1U << i) & DSP_INTER_NMI_MASK) { fprintf(stderr, "at level 3"); } else { for (j = 2; j>=0; j--) { if ((1<%02d: %04x %04x\n", i, dsp_core.stack[0][i], dsp_core.stack[1][i]); else sprintf(stack_disasm[i], " %02d: %04x %04x\n", i, dsp_core.stack[0][i], dsp_core.stack[1][i]); } /* Display the DSP registers and stack state */ fprintf(fp, "\t\t\t\t| SP SSH SSL\n"); fprintf(fp, "A2: %02x A1: %06x A0: %06x\t|%s", dsp_core.registers[DSP_REG_A2], dsp_core.registers[DSP_REG_A1], dsp_core.registers[DSP_REG_A0], stack_disasm[0]); fprintf(fp, "B2: %02x B1: %06x B0: %06x\t|%s", dsp_core.registers[DSP_REG_B2], dsp_core.registers[DSP_REG_B1], dsp_core.registers[DSP_REG_B0], stack_disasm[1]); fprintf(fp, " X1: %06x X0: %06x\t|%s", dsp_core.registers[DSP_REG_X1], dsp_core.registers[DSP_REG_X0], stack_disasm[2]); fprintf(fp, " Y1: %06x Y0: %06x\t|%s", dsp_core.registers[DSP_REG_Y1], dsp_core.registers[DSP_REG_Y0], stack_disasm[3]); fprintf(fp, "\t\t\t\t|%s", stack_disasm[4]); for (i=0; i<8; i++) { fprintf(fp, "R%01x: %04x N%01x: %04x M%01x: %04x\t|%s", i, dsp_core.registers[DSP_REG_R0+i], i, dsp_core.registers[DSP_REG_N0+i], i, dsp_core.registers[DSP_REG_M0+i], stack_disasm[i+5]); } fprintf(fp, "\t\t\t\t|%s", stack_disasm[13]); fprintf(fp, "LA: %04x LC: %04x PC: %04x\t|%s", dsp_core.registers[DSP_REG_LA], dsp_core.registers[DSP_REG_LC], dsp_core.pc, stack_disasm[14]); fprintf(fp, "SR: %04x OMR: %02x SP: %02x\t|%s", dsp_core.registers[DSP_REG_SR], dsp_core.registers[DSP_REG_OMR], dsp_core.registers[DSP_REG_SP], stack_disasm[15]); fprintf(fp, "\n"); #endif } /** * Get given DSP register address and required bit mask. * Works for A0-2, B0-2, LA, LC, M0-7, N0-7, R0-7, X0-1, Y0-1, PC, SR, SP, * OMR, SSH & SSL registers, but note that the SP, SSH & SSL registers * need special handling (in DSP*SetRegister()) when they are set. * Return the register width in bits or zero for an error. */ int DSP_GetRegisterAddress(const char *regname, uint32_t **addr, uint32_t *mask) { #if ENABLE_DSP_EMU #define MAX_REGNAME_LEN 4 typedef struct { const char name[MAX_REGNAME_LEN]; uint32_t *addr; int bits; uint32_t mask; } reg_addr_t; /* sorted by name so that this can be bisected */ static const reg_addr_t registers[] = { /* 56-bit A register */ { "A0", &dsp_core.registers[DSP_REG_A0], 32, BITMASK(24) }, { "A1", &dsp_core.registers[DSP_REG_A1], 32, BITMASK(24) }, { "A2", &dsp_core.registers[DSP_REG_A2], 32, BITMASK(8) }, /* 56-bit B register */ { "B0", &dsp_core.registers[DSP_REG_B0], 32, BITMASK(24) }, { "B1", &dsp_core.registers[DSP_REG_B1], 32, BITMASK(24) }, { "B2", &dsp_core.registers[DSP_REG_B2], 32, BITMASK(8) }, /* 16-bit LA & LC registers */ { "LA", &dsp_core.registers[DSP_REG_LA], 32, BITMASK(16) }, { "LC", &dsp_core.registers[DSP_REG_LC], 32, BITMASK(16) }, /* 16-bit M registers */ { "M0", &dsp_core.registers[DSP_REG_M0], 32, BITMASK(16) }, { "M1", &dsp_core.registers[DSP_REG_M1], 32, BITMASK(16) }, { "M2", &dsp_core.registers[DSP_REG_M2], 32, BITMASK(16) }, { "M3", &dsp_core.registers[DSP_REG_M3], 32, BITMASK(16) }, { "M4", &dsp_core.registers[DSP_REG_M4], 32, BITMASK(16) }, { "M5", &dsp_core.registers[DSP_REG_M5], 32, BITMASK(16) }, { "M6", &dsp_core.registers[DSP_REG_M6], 32, BITMASK(16) }, { "M7", &dsp_core.registers[DSP_REG_M7], 32, BITMASK(16) }, /* 16-bit N registers */ { "N0", &dsp_core.registers[DSP_REG_N0], 32, BITMASK(16) }, { "N1", &dsp_core.registers[DSP_REG_N1], 32, BITMASK(16) }, { "N2", &dsp_core.registers[DSP_REG_N2], 32, BITMASK(16) }, { "N3", &dsp_core.registers[DSP_REG_N3], 32, BITMASK(16) }, { "N4", &dsp_core.registers[DSP_REG_N4], 32, BITMASK(16) }, { "N5", &dsp_core.registers[DSP_REG_N5], 32, BITMASK(16) }, { "N6", &dsp_core.registers[DSP_REG_N6], 32, BITMASK(16) }, { "N7", &dsp_core.registers[DSP_REG_N7], 32, BITMASK(16) }, { "OMR", &dsp_core.registers[DSP_REG_OMR], 32, 0x5f }, /* 16-bit program counter */ { "PC", (uint32_t*)(&dsp_core.pc), 16, BITMASK(16) }, /* 16-bit DSP R (address) registers */ { "R0", &dsp_core.registers[DSP_REG_R0], 32, BITMASK(16) }, { "R1", &dsp_core.registers[DSP_REG_R1], 32, BITMASK(16) }, { "R2", &dsp_core.registers[DSP_REG_R2], 32, BITMASK(16) }, { "R3", &dsp_core.registers[DSP_REG_R3], 32, BITMASK(16) }, { "R4", &dsp_core.registers[DSP_REG_R4], 32, BITMASK(16) }, { "R5", &dsp_core.registers[DSP_REG_R5], 32, BITMASK(16) }, { "R6", &dsp_core.registers[DSP_REG_R6], 32, BITMASK(16) }, { "R7", &dsp_core.registers[DSP_REG_R7], 32, BITMASK(16) }, { "SP", &dsp_core.registers[DSP_REG_SP], 32, BITMASK(6) }, /* 16-bit status register */ { "SR", &dsp_core.registers[DSP_REG_SR], 32, 0xefff }, { "SSH", &dsp_core.registers[DSP_REG_SSH], 32, BITMASK(16) }, { "SSL", &dsp_core.registers[DSP_REG_SSL], 32, BITMASK(16) }, /* 48-bit X register */ { "X0", &dsp_core.registers[DSP_REG_X0], 32, BITMASK(24) }, { "X1", &dsp_core.registers[DSP_REG_X1], 32, BITMASK(24) }, /* 48-bit Y register */ { "Y0", &dsp_core.registers[DSP_REG_Y0], 32, BITMASK(24) }, { "Y1", &dsp_core.registers[DSP_REG_Y1], 32, BITMASK(24) } }; /* left, right, middle, direction */ int l, r, m, dir = 0; unsigned int i, len; char reg[MAX_REGNAME_LEN]; if (!bDspEnabled) { return 0; } for (i = 0; i < sizeof(reg) && regname[i]; i++) { reg[i] = toupper((unsigned char)regname[i]); } if (i < 2 || regname[i]) { /* too short or longer than any of the names */ return 0; } len = i; /* bisect */ l = 0; r = ARRAY_SIZE(registers) - 1; do { m = (l+r) >> 1; for (i = 0; i < len; i++) { dir = (int)reg[i] - registers[m].name[i]; if (dir) { break; } } if (dir == 0) { *addr = registers[m].addr; *mask = registers[m].mask; return registers[m].bits; } if (dir < 0) { r = m-1; } else { l = m+1; } } while (l <= r); #undef MAX_REGNAME_LEN #endif return 0; } /** * Set given DSP register value, return false if unknown register given */ bool DSP_Disasm_SetRegister(const char *arg, uint32_t value) { #if ENABLE_DSP_EMU uint32_t *addr, mask, sp_value; int bits; /* first check registers needing special handling... */ if (arg[0]=='S' || arg[0]=='s') { if (arg[1]=='P' || arg[1]=='p') { dsp_core.registers[DSP_REG_SP] = value & BITMASK(6); value &= BITMASK(4); dsp_core.registers[DSP_REG_SSH] = dsp_core.stack[0][value]; dsp_core.registers[DSP_REG_SSL] = dsp_core.stack[1][value]; return true; } if (arg[1]=='S' || arg[1]=='s') { sp_value = dsp_core.registers[DSP_REG_SP] & BITMASK(4); if (arg[2]=='H' || arg[2]=='h') { if (sp_value == 0) { dsp_core.registers[DSP_REG_SSH] = 0; dsp_core.stack[0][sp_value] = 0; } else { dsp_core.registers[DSP_REG_SSH] = value & BITMASK(16); dsp_core.stack[0][sp_value] = value & BITMASK(16); } return true; } if (arg[2]=='L' || arg[2]=='l') { if (sp_value == 0) { dsp_core.registers[DSP_REG_SSL] = 0; dsp_core.stack[1][sp_value] = 0; } else { dsp_core.registers[DSP_REG_SSL] = value & BITMASK(16); dsp_core.stack[1][sp_value] = value & BITMASK(16); } return true; } } } /* ...then registers where address & mask are enough */ bits = DSP_GetRegisterAddress(arg, &addr, &mask); switch (bits) { case 32: *addr = value & mask; return true; case 16: *(uint16_t*)addr = value & mask; return true; } #endif return false; } /** * Read SSI transmit value */ uint32_t DSP_SsiReadTxValue(void) { #if ENABLE_DSP_EMU return dsp_core.ssi.transmit_value; #else return 0; #endif } /** * Write SSI receive value */ void DSP_SsiWriteRxValue(uint32_t value) { #if ENABLE_DSP_EMU dsp_core.ssi.received_value = value & 0xffffff; #endif } /** * Signal SSI clock tick to DSP */ void DSP_SsiReceive_SC0(void) { #if ENABLE_DSP_EMU dsp_core_ssi_Receive_SC0(); #endif } void DSP_SsiTransmit_SC0(void) { #if ENABLE_DSP_EMU #endif } void DSP_SsiReceive_SC1(uint32_t FrameCounter) { #if ENABLE_DSP_EMU dsp_core_ssi_Receive_SC1(FrameCounter); #endif } void DSP_SsiTransmit_SC1(void) { #if ENABLE_DSP_EMU Crossbar_DmaPlayInHandShakeMode(); #endif } void DSP_SsiReceive_SC2(uint32_t FrameCounter) { #if ENABLE_DSP_EMU dsp_core_ssi_Receive_SC2(FrameCounter); #endif } void DSP_SsiTransmit_SC2(uint32_t frame) { #if ENABLE_DSP_EMU Crossbar_DmaRecordInHandShakeMode_Frame(frame); #endif } void DSP_SsiReceive_SCK(void) { #if ENABLE_DSP_EMU dsp_core_ssi_Receive_SCK(); #endif } void DSP_SsiTransmit_SCK(void) { #if ENABLE_DSP_EMU #endif } /** * Read access wrapper for ioMemTabFalcon (DSP Host port) * DSP Host interface port is accessed by the 68030 in Byte mode. * A move.w value,$ffA206 results in 2 bus access for the 68030. */ void DSP_HandleReadAccess(void) { uint32_t addr; uint8_t value; bool multi_access = false; for (addr = IoAccessBaseAddress; addr < IoAccessBaseAddress+nIoMemAccessSize; addr++) { #if ENABLE_DSP_EMU value = dsp_core_read_host(addr-DSP_HW_OFFSET); #else /* this value prevents TOS from hanging in the DSP init code */ value = 0xff; #endif if (multi_access == true) M68000_WaitState(4); multi_access = true; Dprintf(("HWget_b(0x%08x)=0x%02x at 0x%08x\n", addr, value, m68k_getpc())); IoMem_WriteByte(addr, value); } } /** * Write access wrapper for ioMemTabFalcon (DSP Host port) * DSP Host interface port is accessed by the 68030 in Byte mode. * A move.w value,$ffA206 results in 2 bus access for the 68030. */ void DSP_HandleWriteAccess(void) { uint32_t addr; bool multi_access = false; for (addr = IoAccessBaseAddress; addr < IoAccessBaseAddress+nIoMemAccessSize; addr++) { #if ENABLE_DSP_EMU uint8_t value = IoMem_ReadByte(addr); Dprintf(("HWput_b(0x%08x,0x%02x) at 0x%08x\n", addr, value, m68k_getpc())); dsp_core_write_host(addr-DSP_HW_OFFSET, value); #endif if (multi_access == true) M68000_WaitState(4); multi_access = true; } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp.h000066400000000000000000000052671504763705000235430ustar00rootroot00000000000000/* DSP M56001 emulation Dummy emulation, Hatari glue (C) 2001-2008 ARAnyM developer team Adaption to Hatari (C) 2008 by Thomas Huth This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifndef DSP_H #define DSP_H #if ENABLE_DSP_EMU # include "dsp_core.h" #endif #define DSP_CPU_FREQ_RATIO 2 /* Dsp Freq = 2 * Cpu Freq (32 MHz vs 16 MHz on Falcon) */ extern bool bDspEnabled; extern bool bDspHostInterruptPending; extern uint64_t DSP_CyclesGlobalClockCounter; /* Dsp commands */ extern bool DSP_ProcessIRQ(void); extern void DSP_Init(void); extern void DSP_UnInit(void); extern void DSP_Reset(void); extern void DSP_Enable(void); extern void DSP_Disable(void); extern void DSP_Run(int nHostCycles); /* Save Dsp state to snapshot */ extern void DSP_MemorySnapShot_Capture(bool bSave); /* Dsp Debugger commands */ extern void DSP_SetDebugging(bool enabled); extern uint16_t DSP_GetPC(void); extern uint16_t DSP_GetNextPC(uint16_t pc); extern uint16_t DSP_GetInstrCycles(void); extern uint32_t DSP_ReadMemory(uint16_t addr, char space, const char **mem_str); extern uint16_t DSP_DisasmMemory(FILE *fp, uint16_t dsp_memdump_addr, uint16_t dsp_memdump_upper, char space); extern uint16_t DSP_DisasmAddress(FILE *out, uint16_t lowerAdr, uint16_t UpperAdr); extern void DSP_Info(FILE *fp, uint32_t dummy); extern void DSP_DisasmRegisters(FILE *fp); extern int DSP_GetRegisterAddress(const char *arg, uint32_t **addr, uint32_t *mask); extern bool DSP_Disasm_SetRegister(const char *arg, uint32_t value); /* Dsp SSI commands */ extern uint32_t DSP_SsiReadTxValue(void); extern void DSP_SsiWriteRxValue(uint32_t value); extern void DSP_SsiReceive_SC0(void); extern void DSP_SsiReceive_SC1(uint32_t value); extern void DSP_SsiReceive_SC2(uint32_t value); extern void DSP_SsiReceive_SCK(void); extern void DSP_SsiTransmit_SC0(void); extern void DSP_SsiTransmit_SC1(void); extern void DSP_SsiTransmit_SC2(uint32_t frame); extern void DSP_SsiTransmit_SCK(void); /* Dsp Host interface commands */ extern void DSP_HandleReadAccess(void); extern void DSP_HandleWriteAccess(void); extern uint8_t DSP_GetHREQ(void); extern int DSP_ProcessIACK(void); #endif /* DSP_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp_core.c000066400000000000000000001136711504763705000245450ustar00rootroot00000000000000/* DSP M56001 emulation Host/Emulator <-> DSP glue (C) 2003-2008 ARAnyM developer team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "dsp_core.h" #include "dsp_cpu.h" #include "ioMem.h" #include "dsp.h" #include "log.h" /*--- the DSP core itself ---*/ dsp_core_t dsp_core; /*--- Functions prototypes ---*/ static void dsp_core_dsp2host(void); static void dsp_core_host2dsp(void); static void (*dsp_host_interrupt)(int set); /* Function to trigger host interrupt */ static uint32_t const x_rom[0x100] = { /* mulaw table */ /* M_00 */ 0x7D7C00, /* 8031 */ /* M_01 */ 0x797C00, /* 7775 */ /* M_02 */ 0x757C00, /* 7519 */ /* M_03 */ 0x717C00, /* 7263 */ /* M_04 */ 0x6D7C00, /* 7007 */ /* M_05 */ 0x697C00, /* 6751 */ /* M_06 */ 0x657C00, /* 6495 */ /* M_07 */ 0x617C00, /* 6239 */ /* M_08 */ 0x5D7C00, /* 5983 */ /* M_09 */ 0x597C00, /* 5727 */ /* M_0A */ 0x557C00, /* 5471 */ /* M_0B */ 0x517C00, /* 5215 */ /* M_0C */ 0x4D7C00, /* 4959 */ /* M_0D */ 0x497C00, /* 4703 */ /* M_0E */ 0x457C00, /* 4447 */ /* M_0F */ 0x417C00, /* 4191 */ /* M_10 */ 0x3E7C00, /* 3999 */ /* M_11 */ 0x3C7C00, /* 3871 */ /* M_12 */ 0x3A7C00, /* 3743 */ /* M_13 */ 0x387C00, /* 3615 */ /* M_14 */ 0x367C00, /* 3487 */ /* M_15 */ 0x347C00, /* 3359 */ /* M_16 */ 0x327C00, /* 3231 */ /* M_17 */ 0x307C00, /* 3103 */ /* M_18 */ 0x2E7C00, /* 2975 */ /* M_19 */ 0x2C7C00, /* 2847 */ /* M_1A */ 0x2A7C00, /* 2719 */ /* M_1B */ 0x287C00, /* 2591 */ /* M_1C */ 0x267C00, /* 2463 */ /* M_1D */ 0x247C00, /* 2335 */ /* M_1E */ 0x227C00, /* 2207 */ /* M_1F */ 0x207C00, /* 2079 */ /* M_20 */ 0x1EFC00, /* 1983 */ /* M_21 */ 0x1DFC00, /* 1919 */ /* M_22 */ 0x1CFC00, /* 1855 */ /* M_23 */ 0x1BFC00, /* 1791 */ /* M_24 */ 0x1AFC00, /* 1727 */ /* M_25 */ 0x19FC00, /* 1663 */ /* M_26 */ 0x18FC00, /* 1599 */ /* M_27 */ 0x17FC00, /* 1535 */ /* M_28 */ 0x16FC00, /* 1471 */ /* M_29 */ 0x15FC00, /* 1407 */ /* M_2A */ 0x14FC00, /* 1343 */ /* M_2B */ 0x13FC00, /* 1279 */ /* M_2C */ 0x12FC00, /* 1215 */ /* M_2D */ 0x11FC00, /* 1151 */ /* M_2E */ 0x10FC00, /* 1087 */ /* M_2F */ 0x0FFC00, /* 1023 */ /* M_30 */ 0x0F3C00, /* 975 */ /* M_31 */ 0x0EBC00, /* 943 */ /* M_32 */ 0x0E3C00, /* 911 */ /* M_33 */ 0x0DBC00, /* 879 */ /* M_34 */ 0x0D3C00, /* 847 */ /* M_35 */ 0x0CBC00, /* 815 */ /* M_36 */ 0x0C3C00, /* 783 */ /* M_37 */ 0x0BBC00, /* 751 */ /* M_38 */ 0x0B3C00, /* 719 */ /* M_39 */ 0x0ABC00, /* 687 */ /* M_3A */ 0x0A3C00, /* 655 */ /* M_3B */ 0x09BC00, /* 623 */ /* M_3C */ 0x093C00, /* 591 */ /* M_3D */ 0x08BC00, /* 559 */ /* M_3E */ 0x083C00, /* 527 */ /* M_3F */ 0x07BC00, /* 495 */ /* M_40 */ 0x075C00, /* 471 */ /* M_41 */ 0x071C00, /* 455 */ /* M_42 */ 0x06DC00, /* 439 */ /* M_43 */ 0x069C00, /* 423 */ /* M_44 */ 0x065C00, /* 407 */ /* M_45 */ 0x061C00, /* 391 */ /* M_46 */ 0x05DC00, /* 375 */ /* M_47 */ 0x059C00, /* 359 */ /* M_48 */ 0x055C00, /* 343 */ /* M_49 */ 0x051C00, /* 327 */ /* M_4A */ 0x04DC00, /* 311 */ /* M_4B */ 0x049C00, /* 295 */ /* M_4C */ 0x045C00, /* 279 */ /* M_4D */ 0x041C00, /* 263 */ /* M_4E */ 0x03DC00, /* 247 */ /* M_4F */ 0x039C00, /* 231 */ /* M_50 */ 0x036C00, /* 219 */ /* M_51 */ 0x034C00, /* 211 */ /* M_52 */ 0x032C00, /* 203 */ /* M_53 */ 0x030C00, /* 195 */ /* M_54 */ 0x02EC00, /* 187 */ /* M_55 */ 0x02CC00, /* 179 */ /* M_56 */ 0x02AC00, /* 171 */ /* M_57 */ 0x028C00, /* 163 */ /* M_58 */ 0x026C00, /* 155 */ /* M_59 */ 0x024C00, /* 147 */ /* M_5A */ 0x022C00, /* 139 */ /* M_5B */ 0x020C00, /* 131 */ /* M_5C */ 0x01EC00, /* 123 */ /* M_5D */ 0x01CC00, /* 115 */ /* M_5E */ 0x01AC00, /* 107 */ /* M_5F */ 0x018C00, /* 99 */ /* M_60 */ 0x017400, /* 93 */ /* M_61 */ 0x016400, /* 89 */ /* M_62 */ 0x015400, /* 85 */ /* M_63 */ 0x014400, /* 81 */ /* M_64 */ 0x013400, /* 77 */ /* M_65 */ 0x012400, /* 73 */ /* M_66 */ 0x011400, /* 69 */ /* M_67 */ 0x010400, /* 65 */ /* M_68 */ 0x00F400, /* 61 */ /* M_69 */ 0x00E400, /* 57 */ /* M_6A */ 0x00D400, /* 53 */ /* M_6B */ 0x00C400, /* 49 */ /* M_6C */ 0x00B400, /* 45 */ /* M_6D */ 0x00A400, /* 41 */ /* M_6E */ 0x009400, /* 37 */ /* M_6F */ 0x008400, /* 33 */ /* M_70 */ 0x007800, /* 30 */ /* M_71 */ 0x007000, /* 28 */ /* M_72 */ 0x006800, /* 26 */ /* M_73 */ 0x006000, /* 24 */ /* M_74 */ 0x005800, /* 22 */ /* M_75 */ 0x005000, /* 20 */ /* M_76 */ 0x004800, /* 18 */ /* M_77 */ 0x004000, /* 16 */ /* M_78 */ 0x003800, /* 14 */ /* M_79 */ 0x003000, /* 12 */ /* M_7A */ 0x002800, /* 10 */ /* M_7B */ 0x002000, /* 8 */ /* M_7C */ 0x001800, /* 6 */ /* M_7D */ 0x001000, /* 4 */ /* M_7E */ 0x000800, /* 2 */ /* M_7F */ 0x000000, /* 0 */ /* a-law table */ /* A_80 */ 0x158000, /* 688 */ /* A_81 */ 0x148000, /* 656 */ /* A_82 */ 0x178000, /* 752 */ /* A_83 */ 0x168000, /* 720 */ /* A_84 */ 0x118000, /* 560 */ /* A_85 */ 0x108000, /* 528 */ /* A_86 */ 0x138000, /* 624 */ /* A_87 */ 0x128000, /* 592 */ /* A_88 */ 0x1D8000, /* 944 */ /* A_89 */ 0x1C8000, /* 912 */ /* A_8A */ 0x1F8000, /* 1008 */ /* A_8B */ 0x1E8000, /* 976 */ /* A_8C */ 0x198000, /* 816 */ /* A_8D */ 0x188000, /* 784 */ /* A_8E */ 0x1B8000, /* 880 */ /* A_8F */ 0x1A8000, /* 848 */ /* A_90 */ 0x0AC000, /* 344 */ /* A_91 */ 0x0A4000, /* 328 */ /* A_92 */ 0x0BC000, /* 376 */ /* A_93 */ 0x0B4000, /* 360 */ /* A_94 */ 0x08C000, /* 280 */ /* A_95 */ 0x084000, /* 264 */ /* A_96 */ 0x09C000, /* 312 */ /* A_97 */ 0x094000, /* 296 */ /* A_98 */ 0x0EC000, /* 472 */ /* A_99 */ 0x0E4000, /* 456 */ /* A_9A */ 0x0FC000, /* 504 */ /* A_9B */ 0x0F4000, /* 488 */ /* A_9C */ 0x0CC000, /* 408 */ /* A_9D */ 0x0C4000, /* 392 */ /* A_9E */ 0x0DC000, /* 440 */ /* A_9F */ 0x0D4000, /* 424 */ /* A_A0 */ 0x560000, /* 2752 */ /* A_A1 */ 0x520000, /* 2624 */ /* A_A2 */ 0x5E0000, /* 3008 */ /* A_A3 */ 0x5A0000, /* 2880 */ /* A_A4 */ 0x460000, /* 2240 */ /* A_A5 */ 0x420000, /* 2112 */ /* A_A6 */ 0x4E0000, /* 2496 */ /* A_A7 */ 0x4A0000, /* 2368 */ /* A_A8 */ 0x760000, /* 3776 */ /* A_A9 */ 0x720000, /* 3648 */ /* A_AA */ 0x7E0000, /* 4032 */ /* A_AB */ 0x7A0000, /* 3904 */ /* A_AC */ 0x660000, /* 3264 */ /* A_AD */ 0x620000, /* 3136 */ /* A_AE */ 0x6E0000, /* 3520 */ /* A_AF */ 0x6A0000, /* 3392 */ /* A_B0 */ 0x2B0000, /* 1376 */ /* A_B1 */ 0x290000, /* 1312 */ /* A_B2 */ 0x2F0000, /* 1504 */ /* A_B3 */ 0x2D0000, /* 1440 */ /* A_B4 */ 0x230000, /* 1120 */ /* A_B5 */ 0x210000, /* 1056 */ /* A_B6 */ 0x270000, /* 1248 */ /* A_B7 */ 0x250000, /* 1184 */ /* A_B8 */ 0x3B0000, /* 1888 */ /* A_B9 */ 0x390000, /* 1824 */ /* A_BA */ 0x3F0000, /* 2016 */ /* A_BB */ 0x3D0000, /* 1952 */ /* A_BC */ 0x330000, /* 1632 */ /* A_BD */ 0x310000, /* 1568 */ /* A_BE */ 0x370000, /* 1760 */ /* A_BF */ 0x350000, /* 1696 */ /* A_C0 */ 0x015800, /* 43 */ /* A_C1 */ 0x014800, /* 41 */ /* A_C2 */ 0x017800, /* 47 */ /* A_C3 */ 0x016800, /* 45 */ /* A_C4 */ 0x011800, /* 35 */ /* A_C5 */ 0x010800, /* 33 */ /* A_C6 */ 0x013800, /* 39 */ /* A_C7 */ 0x012800, /* 37 */ /* A_C8 */ 0x01D800, /* 59 */ /* A_C9 */ 0x01C800, /* 57 */ /* A_CA */ 0x01F800, /* 63 */ /* A_CB */ 0x01E800, /* 61 */ /* A_CC */ 0x019800, /* 51 */ /* A_CD */ 0x018800, /* 49 */ /* A_CE */ 0x01B800, /* 55 */ /* A_CF */ 0x01A800, /* 53 */ /* A_D0 */ 0x005800, /* 11 */ /* A_D1 */ 0x004800, /* 9 */ /* A_D2 */ 0x007800, /* 15 */ /* A_D3 */ 0x006800, /* 13 */ /* A_D4 */ 0x001800, /* 3 */ /* A_D5 */ 0x000800, /* 1 */ /* A_D6 */ 0x003800, /* 7 */ /* A_D7 */ 0x002800, /* 5 */ /* A_D8 */ 0x00D800, /* 27 */ /* A_D9 */ 0x00C800, /* 25 */ /* A_DA */ 0x00F800, /* 31 */ /* A_DB */ 0x00E800, /* 29 */ /* A_DC */ 0x009800, /* 19 */ /* A_DD */ 0x008800, /* 17 */ /* A_DE */ 0x00B800, /* 23 */ /* A_DF */ 0x00A800, /* 21 */ /* A_E0 */ 0x056000, /* 172 */ /* A_E1 */ 0x052000, /* 164 */ /* A_E2 */ 0x05E000, /* 188 */ /* A_E3 */ 0x05A000, /* 180 */ /* A_E4 */ 0x046000, /* 140 */ /* A_E5 */ 0x042000, /* 132 */ /* A_E6 */ 0x04E000, /* 156 */ /* A_E7 */ 0x04A000, /* 148 */ /* A_E8 */ 0x076000, /* 236 */ /* A_E9 */ 0x072000, /* 228 */ /* A_EA */ 0x07E000, /* 252 */ /* A_EB */ 0x07A000, /* 244 */ /* A_EC */ 0x066000, /* 204 */ /* A_ED */ 0x062000, /* 196 */ /* A_EE */ 0x06E000, /* 220 */ /* A_EF */ 0x06A000, /* 212 */ /* A_F0 */ 0x02B000, /* 86 */ /* A_F1 */ 0x029000, /* 82 */ /* A_F2 */ 0x02F000, /* 94 */ /* A_F3 */ 0x02D000, /* 90 */ /* A_F4 */ 0x023000, /* 70 */ /* A_F5 */ 0x021000, /* 66 */ /* A_F6 */ 0x027000, /* 78 */ /* A_F7 */ 0x025000, /* 74 */ /* A_F8 */ 0x03B000, /* 118 */ /* A_F9 */ 0x039000, /* 114 */ /* A_FA */ 0x03F000, /* 126 */ /* A_FB */ 0x03D000, /* 122 */ /* A_FC */ 0x033000, /* 102 */ /* A_FD */ 0x031000, /* 98 */ /* A_FE */ 0x037000, /* 110 */ /* A_FF */ 0x035000 /* 106 */ }; /* sin table */ static uint32_t const y_rom[0x100] = { /* S_00 */ 0x000000, /* +0.0000000000 */ /* S_01 */ 0x03242b, /* +0.0245412588 */ /* S_02 */ 0x0647d9, /* +0.0490676165 */ /* S_03 */ 0x096a90, /* +0.0735645294 */ /* S_04 */ 0x0c8bd3, /* +0.0980170965 */ /* S_05 */ 0x0fab27, /* +0.1224106550 */ /* S_06 */ 0x12c810, /* +0.1467304230 */ /* S_07 */ 0x15e214, /* +0.1709618568 */ /* S_08 */ 0x18f8b8, /* +0.1950902939 */ /* S_09 */ 0x1c0b82, /* +0.2191011906 */ /* S_0A */ 0x1f19f9, /* +0.2429801226 */ /* S_0B */ 0x2223a5, /* +0.2667127848 */ /* S_0C */ 0x25280c, /* +0.2902846336 */ /* S_0D */ 0x2826b9, /* +0.3136817217 */ /* S_0E */ 0x2b1f35, /* +0.3368898630 */ /* S_0F */ 0x2e110a, /* +0.3598949909 */ /* S_10 */ 0x30fbc5, /* +0.3826833963 */ /* S_11 */ 0x33def3, /* +0.4052413702 */ /* S_12 */ 0x36ba20, /* +0.4275550842 */ /* S_13 */ 0x398cdd, /* +0.4496113062 */ /* S_14 */ 0x3c56ba, /* +0.4713966846 */ /* S_15 */ 0x3f174a, /* +0.4928982258 */ /* S_16 */ 0x41ce1e, /* +0.5141026974 */ /* S_17 */ 0x447acd, /* +0.5349975824 */ /* S_18 */ 0x471ced, /* +0.5555702448 */ /* S_19 */ 0x49b415, /* +0.5758081675 */ /* S_1A */ 0x4c3fe0, /* +0.5956993103 */ /* S_1B */ 0x4ebfe9, /* +0.6152316332 */ /* S_1C */ 0x5133cd, /* +0.6343933344 */ /* S_1D */ 0x539b2b, /* +0.6531728506 */ /* S_1E */ 0x55f5a5, /* +0.6715589762 */ /* S_1F */ 0x5842dd, /* +0.6895405054 */ /* S_20 */ 0x5a827a, /* +0.7071068287 */ /* S_21 */ 0x5cb421, /* +0.7242470980 */ /* S_22 */ 0x5ed77d, /* +0.7409511805 */ /* S_23 */ 0x60ec38, /* +0.7572088242 */ /* S_24 */ 0x62f202, /* +0.7730104923 */ /* S_25 */ 0x64e889, /* +0.7883464098 */ /* S_26 */ 0x66cf81, /* +0.8032075167 */ /* S_27 */ 0x68a69f, /* +0.8175848722 */ /* S_28 */ 0x6a6d99, /* +0.8314696550 */ /* S_29 */ 0x6c2429, /* +0.8448535204 */ /* S_2A */ 0x6dca0d, /* +0.8577286005 */ /* S_2B */ 0x6f5f03, /* +0.8700870275 */ /* S_2C */ 0x70e2cc, /* +0.8819212914 */ /* S_2D */ 0x72552d, /* +0.8932243586 */ /* S_2E */ 0x73b5ec, /* +0.9039893150 */ /* S_2F */ 0x7504d3, /* +0.9142097235 */ /* S_30 */ 0x7641af, /* +0.9238795042 */ /* S_31 */ 0x776c4f, /* +0.9329928160 */ /* S_32 */ 0x788484, /* +0.9415440559 */ /* S_33 */ 0x798a24, /* +0.9495282173 */ /* S_34 */ 0x7a7d05, /* +0.9569402933 */ /* S_35 */ 0x7b5d04, /* +0.9637761116 */ /* S_36 */ 0x7c29fc, /* +0.9700312614 */ /* S_37 */ 0x7ce3cf, /* +0.9757021666 */ /* S_38 */ 0x7d8a5f, /* +0.9807852507 */ /* S_39 */ 0x7e1d94, /* +0.9852776527 */ /* S_3A */ 0x7e9d56, /* +0.9891765118 */ /* S_3B */ 0x7f0992, /* +0.9924795628 */ /* S_3C */ 0x7f6237, /* +0.9951847792 */ /* S_3D */ 0x7fa737, /* +0.9972904921 */ /* S_3E */ 0x7fd888, /* +0.9987955093 */ /* S_3F */ 0x7ff622, /* +0.9996988773 */ /* S_40 */ 0x7fffff, /* +1.0000000000 */ /* S_41 */ 0x7ff622, /* +0.9996988773 */ /* S_42 */ 0x7fd888, /* +0.9987955093 */ /* S_43 */ 0x7fa737, /* +0.9972904921 */ /* S_44 */ 0x7f6237, /* +0.9951847792 */ /* S_45 */ 0x7f0992, /* +0.9924795628 */ /* S_46 */ 0x7e9d56, /* +0.9891765118 */ /* S_47 */ 0x7e1d94, /* +0.9852776527 */ /* S_48 */ 0x7d8a5f, /* +0.9807852507 */ /* S_49 */ 0x7ce3cf, /* +0.9757021666 */ /* S_4A */ 0x7c29fc, /* +0.9700312614 */ /* S_4B */ 0x7b5d04, /* +0.9637761116 */ /* S_4C */ 0x7a7d05, /* +0.9569402933 */ /* S_4D */ 0x798a24, /* +0.9495282173 */ /* S_4E */ 0x788484, /* +0.9415440559 */ /* S_4F */ 0x776c4f, /* +0.9329928160 */ /* S_50 */ 0x7641af, /* +0.9238795042 */ /* S_51 */ 0x7504d3, /* +0.9142097235 */ /* S_52 */ 0x73b5ec, /* +0.9039893150 */ /* S_53 */ 0x72552d, /* +0.8932243586 */ /* S_54 */ 0x70e2cc, /* +0.8819212914 */ /* S_55 */ 0x6f5f03, /* +0.8700870275 */ /* S_56 */ 0x6dca0d, /* +0.8577286005 */ /* S_57 */ 0x6c2429, /* +0.8448535204 */ /* S_58 */ 0x6a6d99, /* +0.8314696550 */ /* S_59 */ 0x68a69f, /* +0.8175848722 */ /* S_5A */ 0x66cf81, /* +0.8032075167 */ /* S_5B */ 0x64e889, /* +0.7883464098 */ /* S_5C */ 0x62f202, /* +0.7730104923 */ /* S_5D */ 0x60ec38, /* +0.7572088242 */ /* S_5E */ 0x5ed77d, /* +0.7409511805 */ /* S_5F */ 0x5cb421, /* +0.7242470980 */ /* S_60 */ 0x5a827a, /* +0.7071068287 */ /* S_61 */ 0x5842dd, /* +0.6895405054 */ /* S_62 */ 0x55f5a5, /* +0.6715589762 */ /* S_63 */ 0x539b2b, /* +0.6531728506 */ /* S_64 */ 0x5133cd, /* +0.6343933344 */ /* S_65 */ 0x4ebfe9, /* +0.6152316332 */ /* S_66 */ 0x4c3fe0, /* +0.5956993103 */ /* S_67 */ 0x49b415, /* +0.5758081675 */ /* S_68 */ 0x471ced, /* +0.5555702448 */ /* S_69 */ 0x447acd, /* +0.5349975824 */ /* S_6A */ 0x41ce1e, /* +0.5141026974 */ /* S_6B */ 0x3f174a, /* +0.4928982258 */ /* S_6C */ 0x3c56ba, /* +0.4713966846 */ /* S_6D */ 0x398cdd, /* +0.4496113062 */ /* S_6E */ 0x36ba20, /* +0.4275550842 */ /* S_6F */ 0x33def3, /* +0.4052413702 */ /* S_70 */ 0x30fbc5, /* +0.3826833963 */ /* S_71 */ 0x2e110a, /* +0.3598949909 */ /* S_72 */ 0x2b1f35, /* +0.3368898630 */ /* S_73 */ 0x2826b9, /* +0.3136817217 */ /* S_74 */ 0x25280c, /* +0.2902846336 */ /* S_75 */ 0x2223a5, /* +0.2667127848 */ /* S_76 */ 0x1f19f9, /* +0.2429801226 */ /* S_77 */ 0x1c0b82, /* +0.2191011906 */ /* S_78 */ 0x18f8b8, /* +0.1950902939 */ /* S_79 */ 0x15e214, /* +0.1709618568 */ /* S_7A */ 0x12c810, /* +0.1467304230 */ /* S_7B */ 0x0fab27, /* +0.1224106550 */ /* S_7C */ 0x0c8bd3, /* +0.0980170965 */ /* S_7D */ 0x096a90, /* +0.0735645294 */ /* S_7E */ 0x0647d9, /* +0.0490676165 */ /* S_7F */ 0x03242b, /* +0.0245412588 */ /* S_80 */ 0x000000, /* +0.0000000000 */ /* S_81 */ 0xfcdbd5, /* -0.0245412588 */ /* S_82 */ 0xf9b827, /* -0.0490676165 */ /* S_83 */ 0xf69570, /* -0.0735645294 */ /* S_84 */ 0xf3742d, /* -0.0980170965 */ /* S_85 */ 0xf054d9, /* -0.1224106550 */ /* S_86 */ 0xed37f0, /* -0.1467304230 */ /* S_87 */ 0xea1dec, /* -0.1709618568 */ /* S_88 */ 0xe70748, /* -0.1950902939 */ /* S_89 */ 0xe3f47e, /* -0.2191011906 */ /* S_8A */ 0xe0e607, /* -0.2429801226 */ /* S_8B */ 0xdddc5b, /* -0.2667127848 */ /* S_8C */ 0xdad7f4, /* -0.2902846336 */ /* S_8D */ 0xd7d947, /* -0.3136817217 */ /* S_8E */ 0xd4e0cb, /* -0.3368898630 */ /* S_8F */ 0xd1eef6, /* -0.3598949909 */ /* S_90 */ 0xcf043b, /* -0.3826833963 */ /* S_91 */ 0xcc210d, /* -0.4052413702 */ /* S_92 */ 0xc945e0, /* -0.4275550842 */ /* S_93 */ 0xc67323, /* -0.4496113062 */ /* S_94 */ 0xc3a946, /* -0.4713966846 */ /* S_95 */ 0xc0e8b6, /* -0.4928982258 */ /* S_96 */ 0xbe31e2, /* -0.5141026974 */ /* S_97 */ 0xbb8533, /* -0.5349975824 */ /* S_98 */ 0xb8e313, /* -0.5555702448 */ /* S_99 */ 0xb64beb, /* -0.5758081675 */ /* S_9A */ 0xb3c020, /* -0.5956993103 */ /* S_9B */ 0xb14017, /* -0.6152316332 */ /* S_9C */ 0xaecc33, /* -0.6343933344 */ /* S_9D */ 0xac64d5, /* -0.6531728506 */ /* S_9E */ 0xaa0a5b, /* -0.6715589762 */ /* S_9F */ 0xa7bd23, /* -0.6895405054 */ /* S_A0 */ 0xa57d86, /* -0.7071068287 */ /* S_A1 */ 0xa34bdf, /* -0.7242470980 */ /* S_A2 */ 0xa12883, /* -0.7409511805 */ /* S_A3 */ 0x9f13c8, /* -0.7572088242 */ /* S_A4 */ 0x9d0dfe, /* -0.7730104923 */ /* S_A5 */ 0x9b1777, /* -0.7883464098 */ /* S_A6 */ 0x99307f, /* -0.8032075167 */ /* S_A7 */ 0x975961, /* -0.8175848722 */ /* S_A8 */ 0x959267, /* -0.8314696550 */ /* S_A9 */ 0x93dbd7, /* -0.8448535204 */ /* S_AA */ 0x9235f3, /* -0.8577286005 */ /* S_AB */ 0x90a0fd, /* -0.8700870275 */ /* S_AC */ 0x8f1d34, /* -0.8819212914 */ /* S_AD */ 0x8daad3, /* -0.8932243586 */ /* S_AE */ 0x8c4a14, /* -0.9039893150 */ /* S_AF */ 0x8afb2d, /* -0.9142097235 */ /* S_B0 */ 0x89be51, /* -0.9238795042 */ /* S_B1 */ 0x8893b1, /* -0.9329928160 */ /* S_B2 */ 0x877b7c, /* -0.9415440559 */ /* S_B3 */ 0x8675dc, /* -0.9495282173 */ /* S_B4 */ 0x8582fb, /* -0.9569402933 */ /* S_B5 */ 0x84a2fc, /* -0.9637761116 */ /* S_B6 */ 0x83d604, /* -0.9700312614 */ /* S_B7 */ 0x831c31, /* -0.9757021666 */ /* S_B8 */ 0x8275a1, /* -0.9807852507 */ /* S_B9 */ 0x81e26c, /* -0.9852776527 */ /* S_BA */ 0x8162aa, /* -0.9891765118 */ /* S_BB */ 0x80f66e, /* -0.9924795628 */ /* S_BC */ 0x809dc9, /* -0.9951847792 */ /* S_BD */ 0x8058c9, /* -0.9972904921 */ /* S_BE */ 0x802778, /* -0.9987955093 */ /* S_BF */ 0x8009de, /* -0.9996988773 */ /* S_C0 */ 0x800000, /* -1.0000000000 */ /* S_C1 */ 0x8009de, /* -0.9996988773 */ /* S_C2 */ 0x802778, /* -0.9987955093 */ /* S_C3 */ 0x8058c9, /* -0.9972904921 */ /* S_C4 */ 0x809dc9, /* -0.9951847792 */ /* S_C5 */ 0x80f66e, /* -0.9924795628 */ /* S_C6 */ 0x8162aa, /* -0.9891765118 */ /* S_C7 */ 0x81e26c, /* -0.9852776527 */ /* S_C8 */ 0x8275a1, /* -0.9807852507 */ /* S_C9 */ 0x831c31, /* -0.9757021666 */ /* S_CA */ 0x83d604, /* -0.9700312614 */ /* S_CB */ 0x84a2fc, /* -0.9637761116 */ /* S_CC */ 0x8582fb, /* -0.9569402933 */ /* S_CD */ 0x8675dc, /* -0.9495282173 */ /* S_CE */ 0x877b7c, /* -0.9415440559 */ /* S_CF */ 0x8893b1, /* -0.9329928160 */ /* S_D0 */ 0x89be51, /* -0.9238795042 */ /* S_D1 */ 0x8afb2d, /* -0.9142097235 */ /* S_D2 */ 0x8c4a14, /* -0.9039893150 */ /* S_D3 */ 0x8daad3, /* -0.8932243586 */ /* S_D4 */ 0x8f1d34, /* -0.8819212914 */ /* S_D5 */ 0x90a0fd, /* -0.8700870275 */ /* S_D6 */ 0x9235f3, /* -0.8577286005 */ /* S_D7 */ 0x93dbd7, /* -0.8448535204 */ /* S_D8 */ 0x959267, /* -0.8314696550 */ /* S_D9 */ 0x975961, /* -0.8175848722 */ /* S_DA */ 0x99307f, /* -0.8032075167 */ /* S_DB */ 0x9b1777, /* -0.7883464098 */ /* S_DC */ 0x9d0dfe, /* -0.7730104923 */ /* S_DD */ 0x9f13c8, /* -0.7572088242 */ /* S_DE */ 0xa12883, /* -0.7409511805 */ /* S_DF */ 0xa34bdf, /* -0.7242470980 */ /* S_E0 */ 0xa57d86, /* -0.7071068287 */ /* S_E1 */ 0xa7bd23, /* -0.6895405054 */ /* S_E2 */ 0xaa0a5b, /* -0.6715589762 */ /* S_E3 */ 0xac64d5, /* -0.6531728506 */ /* S_E4 */ 0xaecc33, /* -0.6343933344 */ /* S_E5 */ 0xb14017, /* -0.6152316332 */ /* S_E6 */ 0xb3c020, /* -0.5956993103 */ /* S_E7 */ 0xb64beb, /* -0.5758081675 */ /* S_E8 */ 0xb8e313, /* -0.5555702448 */ /* S_E9 */ 0xbb8533, /* -0.5349975824 */ /* S_EA */ 0xbe31e2, /* -0.5141026974 */ /* S_EB */ 0xc0e8b6, /* -0.4928982258 */ /* S_EC */ 0xc3a946, /* -0.4713966846 */ /* S_ED */ 0xc67323, /* -0.4496113062 */ /* S_EE */ 0xc945e0, /* -0.4275550842 */ /* S_EF */ 0xcc210d, /* -0.4052413702 */ /* S_F0 */ 0xcf043b, /* -0.3826833963 */ /* S_F1 */ 0xd1eef6, /* -0.3598949909 */ /* S_F2 */ 0xd4e0cb, /* -0.3368898630 */ /* S_F3 */ 0xd7d947, /* -0.3136817217 */ /* S_F4 */ 0xdad7f4, /* -0.2902846336 */ /* S_F5 */ 0xdddc5b, /* -0.2667127848 */ /* S_F6 */ 0xe0e607, /* -0.2429801226 */ /* S_F7 */ 0xe3f47e, /* -0.2191011906 */ /* S_F8 */ 0xe70748, /* -0.1950902939 */ /* S_F9 */ 0xea1dec, /* -0.1709618568 */ /* S_FA */ 0xed37f0, /* -0.1467304230 */ /* S_FB */ 0xf054d9, /* -0.1224106550 */ /* S_FC */ 0xf3742d, /* -0.0980170965 */ /* S_FD */ 0xf69570, /* -0.0735645294 */ /* S_FE */ 0xf9b827, /* -0.0490676165 */ /* S_FF */ 0xfcdbd5 /* -0.0245412588 */ }; /* Init DSP emulation */ void dsp_core_init(void (*host_interrupt)(int)) { LOG_TRACE(TRACE_DSP_STATE, "Dsp: core init\n"); dsp_host_interrupt = host_interrupt; memset(&dsp_core, 0, sizeof(dsp_core_t)); memcpy(&dsp_core.rom[DSP_SPACE_X][0x100], x_rom, sizeof(x_rom)); memcpy(&dsp_core.rom[DSP_SPACE_Y][0x100], y_rom, sizeof(y_rom)); } /* Shutdown DSP emulation */ void dsp_core_shutdown(void) { dsp_core.running = 0; LOG_TRACE(TRACE_DSP_STATE, "Dsp: core shutdown\n"); } /* Reset */ void dsp_core_reset(void) { int i; LOG_TRACE(TRACE_DSP_STATE, "Dsp: core reset\n"); dsp_core_shutdown(); /* Memory */ memset((void*)dsp_core.periph, 0, sizeof(dsp_core.periph)); memset(dsp_core.stack, 0, sizeof(dsp_core.stack)); memset(dsp_core.registers, 0, sizeof(dsp_core.registers)); dsp_core.dsp_host_rtx = 0; dsp_core.dsp_host_htx = 0; dsp_core.bootstrap_pos = 0; /* Registers */ dsp_core.pc = 0x0000; dsp_core.registers[DSP_REG_OMR]=0x02; for (i=0;i<8;i++) { dsp_core.registers[DSP_REG_M0+i]=0x00ffff; } /* Interruptions */ dsp_core.interrupt_state = DSP_INTERRUPT_NONE; dsp_core.interrupt_instr_fetch = -1; dsp_core.interrupt_save_pc = -1; dsp_core.interrupt_pipeline_count = 0; /* New Interruptions */ memset(dsp_core.interrupt_mask_level, 0, sizeof(dsp_core.interrupt_mask_level)); dsp_core.interrupt_status = 0; dsp_core.interrupt_mask = (DSP_INTER_IRQA_MASK|DSP_INTER_IRQB_MASK); dsp_core.interrupt_enable = 0; dsp_core.interrupt_edgetriggered_mask = DSP_INTER_EDGE_MASK; /* host port init, dsp side */ dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR]=(1<>= 1; } value = temp; } LOG_TRACE(TRACE_DSP_HOST_SSI, "Dsp SSI received value from crossbar: 0x%06x\n", value); if (dsp_core.ssi.crb_re && dsp_core.ssi.waitFrameRX == 0) { /* Send value to DSP receive */ dsp_core.ssi.RX = value; /* generate interrupt (hack: DATA_E is replaced by DATA for now, else there's no sound */ if (dsp_core.periph[DSP_SPACE_X][DSP_SSI_SR] & (1<>= (24 - dsp_core.ssi.cra_word_length); value &= dsp_core.ssi.cra_word_mask; /* if bit SHFD in CRB is set, swap data to transmit */ if (dsp_core.ssi.crb_shifter) { for (i=0; i>= 1; } value = temp; } LOG_TRACE(TRACE_DSP_HOST_SSI, "Dsp SSI transmit value to crossbar: 0x%06x\n", value); /* Transmit the data */ if (dsp_core.ssi.crb_te && dsp_core.ssi.waitFrameTX == 0) { /* Send value to crossbar */ dsp_core.ssi.transmit_value = value; /* generate interrupt */ if (dsp_core.periph[DSP_SPACE_X][DSP_SSI_SR] & (1<>DSP_SSI_CRA_WL0) & 3) { case 0: dsp_core.ssi.cra_word_length = 8; dsp_core.ssi.cra_word_mask = 0xff; break; case 1: dsp_core.ssi.cra_word_length = 12; dsp_core.ssi.cra_word_mask = 0xfff; break; case 2: dsp_core.ssi.cra_word_length = 16; dsp_core.ssi.cra_word_mask = 0xffff; break; case 3: dsp_core.ssi.cra_word_length = 24; dsp_core.ssi.cra_word_mask = 0xffffff; break; } LOG_TRACE(TRACE_DSP_HOST_SSI, "Dsp SSI CRA write: 0x%06x\n", value); /* Get the Frame rate divider ( 2 < value <32) */ dsp_core.ssi.cra_frame_rate_divider = ((value >> DSP_SSI_CRA_DC0) & 0x1f)+1; break; case DSP_SSI_CRB: crb_te = dsp_core.periph[DSP_SPACE_X][DSP_SSI_CRB] & (1<>DSP_SSI_CRB_SCKD) & 1; dsp_core.ssi.crb_shifter = (value>>DSP_SSI_CRB_SHFD) & 1; dsp_core.ssi.crb_synchro = (value>>DSP_SSI_CRB_SYN) & 1; dsp_core.ssi.crb_mode = (value>>DSP_SSI_CRB_MOD) & 1; dsp_core.ssi.crb_te = (value>>DSP_SSI_CRB_TE) & 1; dsp_core.ssi.crb_re = (value>>DSP_SSI_CRB_RE) & 1; dsp_core.ssi.crb_tie = (value>>DSP_SSI_CRB_TIE) & 1; dsp_core.ssi.crb_rie = (value>>DSP_SSI_CRB_RIE) & 1; if (crb_te == 0 && dsp_core.ssi.crb_te) { dsp_core.ssi.waitFrameTX = 1; } if (crb_re == 0 && dsp_core.ssi.crb_re) { dsp_core.ssi.waitFrameRX = 1; } LOG_TRACE(TRACE_DSP_HOST_SSI, "Dsp SSI CRB write: 0x%06x\n", value); break; } } /* HOST INTERFACE processing */ static void dsp_core_hostport_update_trdy(void) { int trdy; /* Clear/set TRDY bit */ dsp_core.hostport[CPU_HOST_ISR] &= 0xff-(1<>CPU_HOST_ISR_TXDE) & ~(dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR]>>DSP_HOST_HSR_HRDF); dsp_core.hostport[CPU_HOST_ISR] |= (trdy & 1)<< CPU_HOST_ISR_TRDY; } static void dsp_core_hostport_update_hreq(void) { /* Set HREQ bit in hostport and trigger host interrupt? */ if ((dsp_core.hostport[CPU_HOST_ICR] & dsp_core.hostport[CPU_HOST_ISR]) & 0x3) { dsp_core.hostport[CPU_HOST_ISR] |= 1<host) */ static void dsp_core_dsp2host(void) { /* RXDF = 1 ==> host hasn't read the last value yet */ if (dsp_core.hostport[CPU_HOST_ISR] & (1< nothing to tranfert from DSP port */ if (dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] & (1<>8; dsp_core.hostport[CPU_HOST_RXH] = dsp_core.dsp_host_htx>>16; /* Set HTDE bit to say that DSP can write */ dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] |= 1<Host): Transfer 0x%06x, Dsp HTDE=1, Host RXDF=1\n", dsp_core.dsp_host_htx); } /* Host port transfer ? (host->dsp) */ static void dsp_core_host2dsp(void) { /* TXDE = 1 ==> nothing to tranfert from host port */ if (dsp_core.hostport[CPU_HOST_ISR] & (1< DSP hasn't read the last value yet */ if (dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] & (1<DSP): Transfer 0x%06x, Dsp HRDF=1, Host TXDE=1\n", dsp_core.dsp_host_rtx); dsp_core_hostport_update_trdy(); } void dsp_core_hostport_dspread(void) { /* Clear HRDF bit to say that DSP has read */ dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] &= 0xff-(1<DSP): Dsp HRDF cleared\n"); dsp_core_hostport_update_trdy(); dsp_core_host2dsp(); } void dsp_core_hostport_dspwrite(void) { /* Clear HTDE bit to say that DSP has written */ dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] &= 0xff-(1<Host): Dsp HTDE cleared\n"); dsp_core_dsp2host(); } /* Read/writes on host port */ uint8_t dsp_core_read_host(int addr) { uint8_t value; value = dsp_core.hostport[addr]; if (addr == CPU_HOST_TRXL) { /* Clear RXDF bit to say that CPU has read */ dsp_core.hostport[CPU_HOST_ISR] &= 0xff-(1<Host): Host RXDF=0\n"); } return value; } void dsp_core_write_host(int addr, uint8_t value) { switch(addr) { case CPU_HOST_ICR: dsp_core.hostport[CPU_HOST_ICR]=value & 0xfb; /* Set HF1 and HF0 accordingly on the host side */ dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] &= 0xff-((1<DSP): Host command = %06x\n", value & 0x9f); break; case CPU_HOST_ISR: case CPU_HOST_TRX0: /* Read only */ break; case CPU_HOST_IVR: dsp_core.hostport[CPU_HOST_IVR]=value; break; case CPU_HOST_TRXH: dsp_core.hostport[CPU_HOST_TXH]=value; break; case CPU_HOST_TRXM: dsp_core.hostport[CPU_HOST_TXM]=value; break; case CPU_HOST_TRXL: dsp_core.hostport[CPU_HOST_TXL]=value; if (!dsp_core.running) { dsp_core.ramint[DSP_SPACE_P][dsp_core.bootstrap_pos] = (dsp_core.hostport[CPU_HOST_TXH]<<16) | (dsp_core.hostport[CPU_HOST_TXM]<<8) | dsp_core.hostport[CPU_HOST_TXL]; LOG_TRACE(TRACE_DSP_STATE, "Dsp: bootstrap p:0x%04x = 0x%06x\n", dsp_core.bootstrap_pos, dsp_core.ramint[DSP_SPACE_P][dsp_core.bootstrap_pos]); if (++dsp_core.bootstrap_pos == 0x200) { LOG_TRACE(TRACE_DSP_STATE, "Dsp: wait bootstrap done\n"); dsp_core.registers[DSP_REG_R0] = dsp_core.bootstrap_pos; dsp_core.registers[DSP_REG_OMR] = 0x02; dsp_core.running = 1; } } else { /* If TRDY is set, the tranfert is direct to DSP (Burst mode) */ if (dsp_core.hostport[CPU_HOST_ISR] & (1<DSP): Direct Transfer 0x%06x\n", dsp_core.dsp_host_rtx); /* Set HRDF bit to say that DSP can read */ dsp_core.periph[DSP_SPACE_X][DSP_HOST_HSR] |= 1<DSP): Dsp HRDF set\n"); } else{ /* Clear TXDE to say that CPU has written */ dsp_core.hostport[CPU_HOST_ISR] &= 0xff-(1<DSP): Host TXDE cleared\n"); } dsp_core_hostport_update_trdy(); dsp_core_host2dsp(); } break; } } /* vim:ts=4:sw=4: */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp_core.h000066400000000000000000000225571504763705000245540ustar00rootroot00000000000000/* DSP M56001 emulation Core of DSP emulation (C) 2003-2008 ARAnyM developer team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifndef DSP_CORE_H #define DSP_CORE_H #include #ifdef __cplusplus extern "C" { #endif #define DSP_RAMSIZE 32768 /* Host port, CPU side */ #define CPU_HOST_ICR 0x00 #define CPU_HOST_CVR 0x01 #define CPU_HOST_ISR 0x02 #define CPU_HOST_IVR 0x03 #define CPU_HOST_TRX0 0x04 #define CPU_HOST_TRXH 0x05 #define CPU_HOST_TRXM 0x06 #define CPU_HOST_TRXL 0x07 #define CPU_HOST_RX0 0x04 #define CPU_HOST_RXH 0x05 #define CPU_HOST_RXM 0x06 #define CPU_HOST_RXL 0x07 #define CPU_HOST_TXH 0x09 #define CPU_HOST_TXM 0x0a #define CPU_HOST_TXL 0x0b #define CPU_HOST_ICR_RREQ 0x00 #define CPU_HOST_ICR_TREQ 0x01 #define CPU_HOST_ICR_HF0 0x03 #define CPU_HOST_ICR_HF1 0x04 #define CPU_HOST_ICR_HM0 0x05 #define CPU_HOST_ICR_HM1 0x06 #define CPU_HOST_ICR_INIT 0x07 #define CPU_HOST_CVR_HC 0x07 #define CPU_HOST_ISR_RXDF 0x00 #define CPU_HOST_ISR_TXDE 0x01 #define CPU_HOST_ISR_TRDY 0x02 #define CPU_HOST_ISR_HF2 0x03 #define CPU_HOST_ISR_HF3 0x04 #define CPU_HOST_ISR_DMA 0x06 #define CPU_HOST_ISR_HREQ 0x07 /* Host port, DSP side, DSP addresses are 0xffc0+value */ #define DSP_PBC 0x20 /* Port B control register */ #define DSP_PCC 0x21 /* Port C control register */ #define DSP_PBDDR 0x22 /* Port B data direction register */ #define DSP_PCDDR 0x23 /* Port C data direction register */ #define DSP_PBD 0x24 /* Port B data register */ #define DSP_PCD 0x25 /* Port C data register */ #define DSP_HOST_HCR 0x28 /* Host control register */ #define DSP_HOST_HSR 0x29 /* Host status register */ #define DSP_HOST_HRX 0x2b /* Host receive register */ #define DSP_HOST_HTX 0x2b /* Host transmit register */ #define DSP_SSI_CRA 0x2c /* SSI control register A */ #define DSP_SSI_CRB 0x2d /* SSI control register B */ #define DSP_SSI_SR 0x2e /* SSI status register */ #define DSP_SSI_TSR 0x2e /* SSI time slot register */ #define DSP_SSI_RX 0x2f /* SSI receive register */ #define DSP_SSI_TX 0x2f /* SSI transmit register */ #define DSP_SCI_SCR 0x30 /* SCI control register */ #define DSP_SCI_SSR 0x31 /* SCI status register */ #define DSP_SCI_SCCR 0x32 /* SCI clock control register */ #define DSP_BCR 0x3e /* Port A bus control register */ #define DSP_IPR 0x3f /* Interrupt priority register */ #define DSP_HOST_HCR_HRIE 0x00 #define DSP_HOST_HCR_HTIE 0x01 #define DSP_HOST_HCR_HCIE 0x02 #define DSP_HOST_HCR_HF2 0x03 #define DSP_HOST_HCR_HF3 0x04 #define DSP_HOST_HSR_HRDF 0x00 #define DSP_HOST_HSR_HTDE 0x01 #define DSP_HOST_HSR_HCP 0x02 #define DSP_HOST_HSR_HF0 0x03 #define DSP_HOST_HSR_HF1 0x04 #define DSP_HOST_HSR_DMA 0x07 #define DSP_SCI_SCR_TMIE 0xd /* Timer interrupt enabled */ #define DSP_SCI_SCCR_COD 0xc /* Clock output divider */ #define DSP_SCI_SCCR_SCP 0xd /* Clock prescaler */ #define DSP_SCI_SCCR_RCM 0xe /* Receive clock source bit */ #define DSP_SCI_SCCR_TCM 0xf /* Transmit clock source bit */ #define DSP_SSI_CRA_DC0 0x8 #define DSP_SSI_CRA_DC1 0x9 #define DSP_SSI_CRA_DC2 0xa #define DSP_SSI_CRA_DC3 0xb #define DSP_SSI_CRA_DC4 0xc #define DSP_SSI_CRA_WL0 0xd #define DSP_SSI_CRA_WL1 0xe #define DSP_SSI_CRB_OF0 0x0 #define DSP_SSI_CRB_OF1 0x1 #define DSP_SSI_CRB_SCD0 0x2 #define DSP_SSI_CRB_SCD1 0x3 #define DSP_SSI_CRB_SCD2 0x4 #define DSP_SSI_CRB_SCKD 0x5 #define DSP_SSI_CRB_SHFD 0x6 #define DSP_SSI_CRB_FSL0 0x7 #define DSP_SSI_CRB_FSL1 0x8 #define DSP_SSI_CRB_SYN 0x9 #define DSP_SSI_CRB_GCK 0xa #define DSP_SSI_CRB_MOD 0xb #define DSP_SSI_CRB_TE 0xc #define DSP_SSI_CRB_RE 0xd #define DSP_SSI_CRB_TIE 0xe #define DSP_SSI_CRB_RIE 0xf #define DSP_SSI_SR_IF0 0x0 #define DSP_SSI_SR_IF1 0x1 #define DSP_SSI_SR_TFS 0x2 #define DSP_SSI_SR_RFS 0x3 #define DSP_SSI_SR_TUE 0x4 #define DSP_SSI_SR_ROE 0x5 #define DSP_SSI_SR_TDE 0x6 #define DSP_SSI_SR_RDF 0x7 #define DSP_INTERRUPT_NONE 0x0 #define DSP_INTERRUPT_DISABLED 0x1 #define DSP_INTERRUPT_LONG 0x2 #define DSP_INTER_RESET 0 #define DSP_INTER_STACK_ERROR 1 #define DSP_INTER_TRACE 2 #define DSP_INTER_SWI 3 #define DSP_INTER_IRQA 4 #define DSP_INTER_IRQB 5 #define DSP_INTER_SSI_RCV_DATA 6 #define DSP_INTER_SSI_RCV_DATA_E 7 #define DSP_INTER_SSI_TRX_DATA 8 #define DSP_INTER_SSI_TRX_DATA_E 9 #define DSP_INTER_SCI_RCV_DATA 10 #define DSP_INTER_SCI_RCV_DATA_E 11 #define DSP_INTER_SCI_TRX_DATA 12 #define DSP_INTER_SCI_IDLE_LINE 13 #define DSP_INTER_SCI_TIMER 14 #define DSP_INTER_NMI 15 #define DSP_INTER_HOST_RCV_DATA 16 #define DSP_INTER_HOST_TRX_DATA 17 #define DSP_INTER_HOST_COMMAND 18 #define DSP_INTER_ILLEGAL 31 #define DSP_INTER_NMI_MASK 0x8000800F #define DSP_INTER_IRQA_MASK 0x00000010 #define DSP_INTER_IRQB_MASK 0x00000020 #define DSP_INTER_SSI_MASK 0x000003C0 #define DSP_INTER_SCI_MASK 0x00007C00 #define DSP_INTER_HOST_MASK 0x00070000 #define DSP_INTER_EDGE_MASK 0x8004C00E #define DSP_PRIORITY_LIST_EXIT 32 extern const char dsp_inter_priority_list[32]; extern const char *dsp_interrupt_name[32]; typedef struct dsp_core_ssi_s dsp_core_ssi_t; typedef struct dsp_core_s dsp_core_t; typedef struct dsp_interrupt_s dsp_interrupt_t; struct dsp_core_ssi_s { uint16_t cra_word_length; uint32_t cra_word_mask; uint16_t cra_frame_rate_divider; uint16_t crb_src_clock; uint16_t crb_shifter; uint16_t crb_synchro; uint16_t crb_mode; uint16_t crb_te; uint16_t crb_re; uint16_t crb_tie; uint16_t crb_rie; uint32_t TX; uint32_t RX; uint32_t transmit_value; /* DSP Transmit --> SSI */ uint32_t received_value; /* DSP Receive --> SSI */ uint16_t waitFrameTX; uint16_t waitFrameRX; uint32_t dspPlay_handshakeMode_frame; }; struct dsp_interrupt_s { const uint16_t inter; const uint16_t vectorAddr; const uint16_t periph; const char *name; }; struct dsp_core_s { /* DSP executing instructions ? */ int running; /* DSP instruction Cycle counter */ uint16_t instr_cycle; /* Registers */ uint16_t pc; uint32_t registers[64]; /* stack[0=ssh], stack[1=ssl] */ uint16_t stack[2][16]; /* External ram[] (mapped to p:) */ uint32_t ramext[DSP_RAMSIZE]; /* rom[0] is x:, rom[1] is y:, rom[2] is p: */ uint32_t rom[3][512]; /* Internal ram[0] is x:, ram[1] is y:, ram[2] is p: */ uint32_t ramint[3][512]; /* peripheral space, [x|y]:0xffc0-0xffff */ uint32_t periph[2][64]; uint32_t dsp_host_htx; uint32_t dsp_host_rtx; uint16_t dsp_host_isr_HREQ; /* host port, CPU side */ uint8_t hostport[12]; /* SSI */ dsp_core_ssi_t ssi; /* Misc */ uint32_t loop_rep; /* executing rep ? */ uint32_t pc_on_rep; /* True if PC is on REP instruction */ /* For bootstrap routine */ uint16_t bootstrap_pos; /* Interruptions */ uint16_t interrupt_state; /* NONE, FAST or LONG interrupt */ uint16_t interrupt_instr_fetch; /* vector of the current interrupt */ uint16_t interrupt_save_pc; /* save next pc value before interrupt */ uint16_t interrupt_IplToRaise; /* save the IPL level to save in the SR register */ uint16_t interrupt_pipeline_count; /* used to prefetch correctly the 2 inter instructions */ /* Interruptions new */ uint32_t interrupt_status; uint32_t interrupt_enable; uint32_t interrupt_mask; uint32_t interrupt_mask_level[3]; uint32_t interrupt_edgetriggered_mask; /* AGU pipeline for indirect move ea instructions Indirect move instructions are : LUA, MOVEC, MOVEP, Tcc, parallel MOVE Registers concerned are Rx, Nx, Mx [LS] The motorola documentation includes MOVEM as an indirect move, but all my tests seem to conclude that MOVEM is not affected by the 1 instruction delay of the AGU */ uint16_t agu_move_indirect_instr; /* is the current instruction an indirect move ? (0=no ; 1 = yes)*/ uint16_t agu_pipeline_reg[2]; /* pipeline index description : 0 = current delayed register ; 1 = new register to delay */ uint16_t agu_pipeline_val[2]; /* register value */ }; /* DSP */ extern dsp_core_t dsp_core; /* Emulator call these to init/stop/reset DSP emulation */ extern void dsp_core_init(void (*host_interrupt)(int)); extern void dsp_core_shutdown(void); extern void dsp_core_reset(void); /* host port read/write by emulator, addr is 0-7, not 0xffa200-0xffa207 */ extern uint8_t dsp_core_read_host(int addr); extern void dsp_core_write_host(int addr, uint8_t value); /* dsp_cpu call these to read/write host port */ extern void dsp_core_hostport_dspread(void); extern void dsp_core_hostport_dspwrite(void); /* dsp_cpu call these to read/write/configure SSI port */ extern void dsp_core_ssi_configure(uint32_t address, uint32_t value); extern void dsp_core_ssi_writeTX(uint32_t value); extern void dsp_core_ssi_writeTSR(void); extern uint32_t dsp_core_ssi_readRX(void); extern void dsp_core_ssi_Receive_SC0(void); extern void dsp_core_ssi_Receive_SC1(uint32_t value); extern void dsp_core_ssi_Receive_SC2(uint32_t value); extern void dsp_core_ssi_Receive_SCK(void); extern void dsp_core_setPortCDataRegister(uint32_t value); #ifdef __cplusplus } #endif #endif /* DSP_CORE_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp_cpu.c000066400000000000000000007641701504763705000244120ustar00rootroot00000000000000/* DSP M56001 emulation Instructions interpreter (C) 2003-2008 ARAnyM developer team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ /* DSP memory mapping ------------------ The memory map is configured as follows : Program space P is one contiguous block of 32K dsp Words X and Y data space are each separate 16K dsp Word blocks. Both X and Y can be accessed as blocks starting at 0 or 16K. Program space physically overlaps both X and Y data spaces. Y: memory is mapped at address $0 in P memory X: memory is mapped at address $4000 in P memory The DSP external RAM is zero waitstate, but there is a penalty for accessing it twice or more in a single instruction, because there is only one external data bus. The extra access costs 2 cycles penalty. The internal buses are all separate (0 waitstate) X: Y: P: $ffff |--------------+--------------+--------------| | Int. I/O | Ext. I/O | | $ffc0 |--------------+--------------+ | | | | | | Reserved | Reserved | Reserved | | | | | | | | | | | | | $8000 |--------------+--------------+--------------| | | | | | 16k Shadow | 16k Shadow | | | | | 32K | $4000 |--------------+--------------| Program | | 16K | 16K | RAM | | External | External | | | RAM | RAM | | $0200 |--------------+--------------+--------------| | Log table or | Sin table or | | | external mem | external mem | Internal | $0100 |--------------+--------------+ program | | Internal X | Internal Y | memory | | memory | memory | | $0000 |--------------+--------------+--------------| Special Note : As the Falcon DSP is a 0 waitstate access memory, I've simplified a little the cycle counting. If this DSP emulator code is used in another project, one should take into account the bus control register (BCR) waitstates. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "main.h" #include "dsp_core.h" #include "dsp_cpu.h" #include "dsp_disasm.h" #include "log.h" #include "debugui.h" #define DSP_COUNT_IPS 0 /* Count instruction per seconds */ #if DSP_COUNT_IPS /* For counting instructions per second */ #include static uint32_t start_time; static uint32_t num_inst; #endif /********************************** * Defines **********************************/ #define SIGN_PLUS 0 #define SIGN_MINUS 1 /********************************** * Variables **********************************/ /* Length of current instruction */ static uint32_t cur_inst_len; /* =0:jump, >0:increment */ /* Current instruction */ static uint32_t cur_inst; /* Counts the number of access to the external memory for one instruction */ static uint16_t access_to_ext_memory; /* DSP is in disasm mode ? */ /* If yes, stack overflow, underflow and illegal instructions messages are not displayed */ static bool isDsp_in_disasm_mode; /* Disasm mode : buffer for memory change */ /* 12 slots are needed when tracing RESET instruction (it can change up to 12 addresses at once) */ static char str_disasm_memory[12][50]; static uint16_t disasm_memory_ptr; /* Pointer for memory change in disasm mode */ /********************************** * Functions **********************************/ typedef void (*dsp_emul_t)(void); static void dsp_postexecute_update_pc(void); static void dsp_postexecute_interrupts(void); static void dsp_setInterruptIPL(uint32_t value); static void dsp_ccr_update_e_u_n_z(uint32_t reg0, uint32_t reg1, uint32_t reg2); static uint32_t read_memory(int space, uint16_t address); static inline uint32_t read_memory_p(uint16_t address); static uint32_t read_memory_disasm(int space, uint16_t address); static inline void write_memory(int space, uint16_t address, uint32_t value); static void write_memory_raw(int space, uint16_t address, uint32_t value); static void write_memory_disasm(int space, uint16_t address, uint32_t value); static void dsp_write_reg(uint32_t numreg, uint32_t value); static void dsp_stack_push(uint32_t curpc, uint32_t cursr, uint16_t sshOnly); static void dsp_stack_pop(uint32_t *curpc, uint32_t *cursr); static void dsp_compute_ssh_ssl(void); static void opcode8h_0(void); static void dsp_update_rn(uint32_t numreg, uint32_t reg_value, int16_t reg_mofifier, uint16_t reg_modulo); static void dsp_update_rn_bitreverse(uint32_t numreg, uint32_t reg_value, int16_t reg_mofifier); static void dsp_update_rn_modulo(uint32_t numreg, uint32_t reg_value, int16_t reg_mofifier, uint16_t reg_modulo); static int dsp_calc_ea(uint32_t ea_mode, uint32_t *dst_addr); static int dsp_calc_cc(uint32_t cc_code); static void dsp_undefined(void); /* Instructions without parallel moves */ static void dsp_andi(void); static void dsp_bchg_aa(void); static void dsp_bchg_ea(void); static void dsp_bchg_pp(void); static void dsp_bchg_reg(void); static void dsp_bclr_aa(void); static void dsp_bclr_ea(void); static void dsp_bclr_pp(void); static void dsp_bclr_reg(void); static void dsp_bset_aa(void); static void dsp_bset_ea(void); static void dsp_bset_pp(void); static void dsp_bset_reg(void); static void dsp_btst_aa(void); static void dsp_btst_ea(void); static void dsp_btst_pp(void); static void dsp_btst_reg(void); static void dsp_div(void); static void dsp_enddo(void); static void dsp_illegal(void); static void dsp_jcc_imm(void); static void dsp_jcc_ea(void); static void dsp_jclr_aa(void); static void dsp_jclr_ea(void); static void dsp_jclr_pp(void); static void dsp_jclr_reg(void); static void dsp_jmp_ea(void); static void dsp_jmp_imm(void); static void dsp_jscc_ea(void); static void dsp_jscc_imm(void); static void dsp_jsclr_aa(void); static void dsp_jsclr_ea(void); static void dsp_jsclr_pp(void); static void dsp_jsclr_reg(void); static void dsp_jset_aa(void); static void dsp_jset_ea(void); static void dsp_jset_pp(void); static void dsp_jset_reg(void); static void dsp_jsr_ea(void); static void dsp_jsr_imm(void); static void dsp_jsset_aa(void); static void dsp_jsset_ea(void); static void dsp_jsset_pp(void); static void dsp_jsset_reg(void); static void dsp_lua(void); static void dsp_movem_ea(void); static void dsp_movem_aa(void); static void dsp_nop(void); static void dsp_norm(void); static void dsp_ori(void); static void dsp_reset(void); static void dsp_rti(void); static void dsp_rts(void); static void dsp_stop(void); static void dsp_swi(void); static void dsp_tcc(void); static void dsp_wait(void); static void dsp_do_ea(void); static void dsp_do_aa(void); static void dsp_do_imm(void); static void dsp_do_reg(void); static void dsp_rep_aa(void); static void dsp_rep_ea(void); static void dsp_rep_imm(void); static void dsp_rep_reg(void); static void dsp_movec_aa(void); static void dsp_movec_ea(void); static void dsp_movec_imm(void); static void dsp_movec_reg(void); static void dsp_movep_0(void); static void dsp_movep_1(void); static void dsp_movep_23(void); /* Parallel move analyzer */ static int dsp_pm_read_accu24(int numreg, uint32_t *dest); static void dsp_pm_0(void); static void dsp_pm_1(void); static void dsp_pm_2(void); static void dsp_pm_2_2(void); static void dsp_pm_3(void); static void dsp_pm_4(void); static void dsp_pm_4x(void); static void dsp_pm_5(void); static void dsp_pm_8(void); /* 56bits arithmetic */ static uint16_t dsp_abs56(uint32_t *dest); static uint16_t dsp_asl56(uint32_t *dest); static uint16_t dsp_asr56(uint32_t *dest); static uint16_t dsp_add56(uint32_t *source, uint32_t *dest); static uint16_t dsp_sub56(uint32_t *source, uint32_t *dest); static void dsp_mul56(uint32_t source1, uint32_t source2, uint32_t *dest, uint8_t signe); static void dsp_rnd56(uint32_t *dest); /* Instructions with parallel moves */ static void dsp_abs_a(void); static void dsp_abs_b(void); static void dsp_adc_x_a(void); static void dsp_adc_x_b(void); static void dsp_adc_y_a(void); static void dsp_adc_y_b(void); static void dsp_add_b_a(void); static void dsp_add_a_b(void); static void dsp_add_x_a(void); static void dsp_add_x_b(void); static void dsp_add_y_a(void); static void dsp_add_y_b(void); static void dsp_add_x0_a(void); static void dsp_add_x0_b(void); static void dsp_add_y0_a(void); static void dsp_add_y0_b(void); static void dsp_add_x1_a(void); static void dsp_add_x1_b(void); static void dsp_add_y1_a(void); static void dsp_add_y1_b(void); static void dsp_addl_b_a(void); static void dsp_addl_a_b(void); static void dsp_addr_b_a(void); static void dsp_addr_a_b(void); static void dsp_and_x0_a(void); static void dsp_and_x0_b(void); static void dsp_and_y0_a(void); static void dsp_and_y0_b(void); static void dsp_and_x1_a(void); static void dsp_and_x1_b(void); static void dsp_and_y1_a(void); static void dsp_and_y1_b(void); static void dsp_asl_a(void); static void dsp_asl_b(void); static void dsp_asr_a(void); static void dsp_asr_b(void); static void dsp_clr_a(void); static void dsp_clr_b(void); static void dsp_cmp_b_a(void); static void dsp_cmp_a_b(void); static void dsp_cmp_x0_a(void); static void dsp_cmp_x0_b(void); static void dsp_cmp_y0_a(void); static void dsp_cmp_y0_b(void); static void dsp_cmp_x1_a(void); static void dsp_cmp_x1_b(void); static void dsp_cmp_y1_a(void); static void dsp_cmp_y1_b(void); static void dsp_cmpm_b_a(void); static void dsp_cmpm_a_b(void); static void dsp_cmpm_x0_a(void); static void dsp_cmpm_x0_b(void); static void dsp_cmpm_y0_a(void); static void dsp_cmpm_y0_b(void); static void dsp_cmpm_x1_a(void); static void dsp_cmpm_x1_b(void); static void dsp_cmpm_y1_a(void); static void dsp_cmpm_y1_b(void); static void dsp_eor_x0_a(void); static void dsp_eor_x0_b(void); static void dsp_eor_y0_a(void); static void dsp_eor_y0_b(void); static void dsp_eor_x1_a(void); static void dsp_eor_x1_b(void); static void dsp_eor_y1_a(void); static void dsp_eor_y1_b(void); static void dsp_lsl_a(void); static void dsp_lsl_b(void); static void dsp_lsr_a(void); static void dsp_lsr_b(void); static void dsp_mac_p_x0_x0_a(void); static void dsp_mac_m_x0_x0_a(void); static void dsp_mac_p_x0_x0_b(void); static void dsp_mac_m_x0_x0_b(void); static void dsp_mac_p_y0_y0_a(void); static void dsp_mac_m_y0_y0_a(void); static void dsp_mac_p_y0_y0_b(void); static void dsp_mac_m_y0_y0_b(void); static void dsp_mac_p_x1_x0_a(void); static void dsp_mac_m_x1_x0_a(void); static void dsp_mac_p_x1_x0_b(void); static void dsp_mac_m_x1_x0_b(void); static void dsp_mac_p_y1_y0_a(void); static void dsp_mac_m_y1_y0_a(void); static void dsp_mac_p_y1_y0_b(void); static void dsp_mac_m_y1_y0_b(void); static void dsp_mac_p_x0_y1_a(void); static void dsp_mac_m_x0_y1_a(void); static void dsp_mac_p_x0_y1_b(void); static void dsp_mac_m_x0_y1_b(void); static void dsp_mac_p_y0_x0_a(void); static void dsp_mac_m_y0_x0_a(void); static void dsp_mac_p_y0_x0_b(void); static void dsp_mac_m_y0_x0_b(void); static void dsp_mac_p_x1_y0_a(void); static void dsp_mac_m_x1_y0_a(void); static void dsp_mac_p_x1_y0_b(void); static void dsp_mac_m_x1_y0_b(void); static void dsp_mac_p_y1_x1_a(void); static void dsp_mac_m_y1_x1_a(void); static void dsp_mac_p_y1_x1_b(void); static void dsp_mac_m_y1_x1_b(void); static void dsp_macr_p_x0_x0_a(void); static void dsp_macr_m_x0_x0_a(void); static void dsp_macr_p_x0_x0_b(void); static void dsp_macr_m_x0_x0_b(void); static void dsp_macr_p_y0_y0_a(void); static void dsp_macr_m_y0_y0_a(void); static void dsp_macr_p_y0_y0_b(void); static void dsp_macr_m_y0_y0_b(void); static void dsp_macr_p_x1_x0_a(void); static void dsp_macr_m_x1_x0_a(void); static void dsp_macr_p_x1_x0_b(void); static void dsp_macr_m_x1_x0_b(void); static void dsp_macr_p_y1_y0_a(void); static void dsp_macr_m_y1_y0_a(void); static void dsp_macr_p_y1_y0_b(void); static void dsp_macr_m_y1_y0_b(void); static void dsp_macr_p_x0_y1_a(void); static void dsp_macr_m_x0_y1_a(void); static void dsp_macr_p_x0_y1_b(void); static void dsp_macr_m_x0_y1_b(void); static void dsp_macr_p_y0_x0_a(void); static void dsp_macr_m_y0_x0_a(void); static void dsp_macr_p_y0_x0_b(void); static void dsp_macr_m_y0_x0_b(void); static void dsp_macr_p_x1_y0_a(void); static void dsp_macr_m_x1_y0_a(void); static void dsp_macr_p_x1_y0_b(void); static void dsp_macr_m_x1_y0_b(void); static void dsp_macr_p_y1_x1_a(void); static void dsp_macr_m_y1_x1_a(void); static void dsp_macr_p_y1_x1_b(void); static void dsp_macr_m_y1_x1_b(void); static void dsp_move(void); static void dsp_mpy_p_x0_x0_a(void); static void dsp_mpy_m_x0_x0_a(void); static void dsp_mpy_p_x0_x0_b(void); static void dsp_mpy_m_x0_x0_b(void); static void dsp_mpy_p_y0_y0_a(void); static void dsp_mpy_m_y0_y0_a(void); static void dsp_mpy_p_y0_y0_b(void); static void dsp_mpy_m_y0_y0_b(void); static void dsp_mpy_p_x1_x0_a(void); static void dsp_mpy_m_x1_x0_a(void); static void dsp_mpy_p_x1_x0_b(void); static void dsp_mpy_m_x1_x0_b(void); static void dsp_mpy_p_y1_y0_a(void); static void dsp_mpy_m_y1_y0_a(void); static void dsp_mpy_p_y1_y0_b(void); static void dsp_mpy_m_y1_y0_b(void); static void dsp_mpy_p_x0_y1_a(void); static void dsp_mpy_m_x0_y1_a(void); static void dsp_mpy_p_x0_y1_b(void); static void dsp_mpy_m_x0_y1_b(void); static void dsp_mpy_p_y0_x0_a(void); static void dsp_mpy_m_y0_x0_a(void); static void dsp_mpy_p_y0_x0_b(void); static void dsp_mpy_m_y0_x0_b(void); static void dsp_mpy_p_x1_y0_a(void); static void dsp_mpy_m_x1_y0_a(void); static void dsp_mpy_p_x1_y0_b(void); static void dsp_mpy_m_x1_y0_b(void); static void dsp_mpy_p_y1_x1_a(void); static void dsp_mpy_m_y1_x1_a(void); static void dsp_mpy_p_y1_x1_b(void); static void dsp_mpy_m_y1_x1_b(void); static void dsp_mpyr_p_x0_x0_a(void); static void dsp_mpyr_m_x0_x0_a(void); static void dsp_mpyr_p_x0_x0_b(void); static void dsp_mpyr_m_x0_x0_b(void); static void dsp_mpyr_p_y0_y0_a(void); static void dsp_mpyr_m_y0_y0_a(void); static void dsp_mpyr_p_y0_y0_b(void); static void dsp_mpyr_m_y0_y0_b(void); static void dsp_mpyr_p_x1_x0_a(void); static void dsp_mpyr_m_x1_x0_a(void); static void dsp_mpyr_p_x1_x0_b(void); static void dsp_mpyr_m_x1_x0_b(void); static void dsp_mpyr_p_y1_y0_a(void); static void dsp_mpyr_m_y1_y0_a(void); static void dsp_mpyr_p_y1_y0_b(void); static void dsp_mpyr_m_y1_y0_b(void); static void dsp_mpyr_p_x0_y1_a(void); static void dsp_mpyr_m_x0_y1_a(void); static void dsp_mpyr_p_x0_y1_b(void); static void dsp_mpyr_m_x0_y1_b(void); static void dsp_mpyr_p_y0_x0_a(void); static void dsp_mpyr_m_y0_x0_a(void); static void dsp_mpyr_p_y0_x0_b(void); static void dsp_mpyr_m_y0_x0_b(void); static void dsp_mpyr_p_x1_y0_a(void); static void dsp_mpyr_m_x1_y0_a(void); static void dsp_mpyr_p_x1_y0_b(void); static void dsp_mpyr_m_x1_y0_b(void); static void dsp_mpyr_p_y1_x1_a(void); static void dsp_mpyr_m_y1_x1_a(void); static void dsp_mpyr_p_y1_x1_b(void); static void dsp_mpyr_m_y1_x1_b(void); static void dsp_neg_a(void); static void dsp_neg_b(void); static void dsp_not_a(void); static void dsp_not_b(void); static void dsp_or_x0_a(void); static void dsp_or_x0_b(void); static void dsp_or_y0_a(void); static void dsp_or_y0_b(void); static void dsp_or_x1_a(void); static void dsp_or_x1_b(void); static void dsp_or_y1_a(void); static void dsp_or_y1_b(void); static void dsp_rnd_a(void); static void dsp_rnd_b(void); static void dsp_rol_a(void); static void dsp_rol_b(void); static void dsp_ror_a(void); static void dsp_ror_b(void); static void dsp_sbc_x_a(void); static void dsp_sbc_x_b(void); static void dsp_sbc_y_a(void); static void dsp_sbc_y_b(void); static void dsp_sub_b_a(void); static void dsp_sub_a_b(void); static void dsp_sub_x_a(void); static void dsp_sub_x_b(void); static void dsp_sub_y_a(void); static void dsp_sub_y_b(void); static void dsp_sub_x0_a(void); static void dsp_sub_x0_b(void); static void dsp_sub_y0_a(void); static void dsp_sub_y0_b(void); static void dsp_sub_x1_a(void); static void dsp_sub_x1_b(void); static void dsp_sub_y1_a(void); static void dsp_sub_y1_b(void); static void dsp_subl_a(void); static void dsp_subl_b(void); static void dsp_subr_a(void); static void dsp_subr_b(void); static void dsp_tfr_b_a(void); static void dsp_tfr_a_b(void); static void dsp_tfr_x0_a(void); static void dsp_tfr_x0_b(void); static void dsp_tfr_y0_a(void); static void dsp_tfr_y0_b(void); static void dsp_tfr_x1_a(void); static void dsp_tfr_x1_b(void); static void dsp_tfr_y1_a(void); static void dsp_tfr_y1_b(void); static void dsp_tst_a(void); static void dsp_tst_b(void); static const dsp_emul_t opcodes8h[512] = { /* 0x00 - 0x3f */ opcode8h_0, dsp_undefined, dsp_undefined, dsp_undefined, opcode8h_0, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_div, dsp_div, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_norm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, /* 0x40 - 0x7f */ dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, /* 0x80 - 0xbf */ dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_lua, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_reg, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_reg, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, /* 0xc0 - 0xff */ dsp_do_aa, dsp_rep_aa, dsp_do_aa, dsp_rep_aa, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_do_ea, dsp_rep_ea, dsp_do_ea, dsp_rep_ea, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_do_reg, dsp_rep_reg, dsp_undefined, dsp_undefined, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_movem_aa, dsp_movem_aa, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movem_ea, dsp_movem_ea, dsp_undefined, dsp_undefined, dsp_movem_aa, dsp_movem_aa, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movem_ea, dsp_movem_ea, dsp_undefined, dsp_undefined, /* 0x100 - 0x13f */ dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_pm_0, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, /* 0x140 - 0x17f */ dsp_bclr_aa, dsp_bset_aa, dsp_bclr_aa, dsp_bset_aa, dsp_jclr_aa, dsp_jset_aa, dsp_jclr_aa, dsp_jset_aa, dsp_bclr_ea, dsp_bset_ea, dsp_bclr_ea, dsp_bset_ea, dsp_jclr_ea, dsp_jset_ea, dsp_jclr_ea, dsp_jset_ea, dsp_bclr_pp, dsp_bset_pp, dsp_bclr_pp, dsp_bset_pp, dsp_jclr_pp, dsp_jset_pp, dsp_jclr_pp, dsp_jset_pp, dsp_jclr_reg, dsp_jset_reg, dsp_bclr_reg, dsp_bset_reg, dsp_jmp_ea, dsp_jcc_ea, dsp_undefined, dsp_undefined, dsp_bchg_aa, dsp_btst_aa, dsp_bchg_aa, dsp_btst_aa, dsp_jsclr_aa, dsp_jsset_aa, dsp_jsclr_aa, dsp_jsset_aa, dsp_bchg_ea, dsp_btst_ea, dsp_bchg_ea, dsp_btst_ea, dsp_jsclr_ea, dsp_jsset_ea, dsp_jsclr_ea, dsp_jsset_ea, dsp_bchg_pp, dsp_btst_pp, dsp_bchg_pp, dsp_btst_pp, dsp_jsclr_pp, dsp_jsset_pp, dsp_jsclr_pp, dsp_jsset_pp, dsp_jsclr_reg, dsp_jsset_reg, dsp_bchg_reg, dsp_btst_reg, dsp_jsr_ea, dsp_jscc_ea, dsp_undefined, dsp_undefined, /* 0x180 - 0x1bf */ dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, /* 0x1c0 - 0x1ff */ dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, }; static const dsp_emul_t opcodes_parmove[16] = { dsp_pm_0, dsp_pm_1, dsp_pm_2, dsp_pm_3, dsp_pm_4, dsp_pm_5, dsp_pm_5, dsp_pm_5, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8 }; static const dsp_emul_t opcodes_alu[256] = { /* 0x00 - 0x3f */ dsp_move , dsp_tfr_b_a, dsp_addr_b_a, dsp_tst_a, dsp_undefined, dsp_cmp_b_a, dsp_subr_a, dsp_cmpm_b_a, dsp_undefined, dsp_tfr_a_b, dsp_addr_a_b, dsp_tst_b, dsp_undefined, dsp_cmp_a_b, dsp_subr_b, dsp_cmpm_a_b, dsp_add_b_a, dsp_rnd_a, dsp_addl_b_a, dsp_clr_a, dsp_sub_b_a, dsp_undefined, dsp_subl_a, dsp_not_a, dsp_add_a_b, dsp_rnd_b, dsp_addl_a_b, dsp_clr_b, dsp_sub_a_b, dsp_undefined, dsp_subl_b, dsp_not_b, dsp_add_x_a, dsp_adc_x_a, dsp_asr_a, dsp_lsr_a, dsp_sub_x_a, dsp_sbc_x_a, dsp_abs_a, dsp_ror_a, dsp_add_x_b, dsp_adc_x_b, dsp_asr_b, dsp_lsr_b, dsp_sub_x_b, dsp_sbc_x_b, dsp_abs_b, dsp_ror_b, dsp_add_y_a, dsp_adc_y_a, dsp_asl_a, dsp_lsl_a, dsp_sub_y_a, dsp_sbc_y_a, dsp_neg_a, dsp_rol_a, dsp_add_y_b, dsp_adc_y_b, dsp_asl_b, dsp_lsl_b, dsp_sub_y_b, dsp_sbc_y_b, dsp_neg_b, dsp_rol_b, /* 0x40 - 0x7f */ dsp_add_x0_a, dsp_tfr_x0_a, dsp_or_x0_a, dsp_eor_x0_a, dsp_sub_x0_a, dsp_cmp_x0_a, dsp_and_x0_a, dsp_cmpm_x0_a, dsp_add_x0_b, dsp_tfr_x0_b, dsp_or_x0_b, dsp_eor_x0_b, dsp_sub_x0_b, dsp_cmp_x0_b, dsp_and_x0_b, dsp_cmpm_x0_b, dsp_add_y0_a, dsp_tfr_y0_a, dsp_or_y0_a, dsp_eor_y0_a, dsp_sub_y0_a, dsp_cmp_y0_a, dsp_and_y0_a, dsp_cmpm_y0_a, dsp_add_y0_b, dsp_tfr_y0_b, dsp_or_y0_b, dsp_eor_y0_b, dsp_sub_y0_b, dsp_cmp_y0_b, dsp_and_y0_b, dsp_cmpm_y0_b, dsp_add_x1_a, dsp_tfr_x1_a, dsp_or_x1_a, dsp_eor_x1_a, dsp_sub_x1_a, dsp_cmp_x1_a, dsp_and_x1_a, dsp_cmpm_x1_a, dsp_add_x1_b, dsp_tfr_x1_b, dsp_or_x1_b, dsp_eor_x1_b, dsp_sub_x1_b, dsp_cmp_x1_b, dsp_and_x1_b, dsp_cmpm_x1_b, dsp_add_y1_a, dsp_tfr_y1_a, dsp_or_y1_a, dsp_eor_y1_a, dsp_sub_y1_a, dsp_cmp_y1_a, dsp_and_y1_a, dsp_cmpm_y1_a, dsp_add_y1_b, dsp_tfr_y1_b, dsp_or_y1_b, dsp_eor_y1_b, dsp_sub_y1_b, dsp_cmp_y1_b, dsp_and_y1_b, dsp_cmpm_y1_b, /* 0x80 - 0xbf */ dsp_mpy_p_x0_x0_a, dsp_mpyr_p_x0_x0_a, dsp_mac_p_x0_x0_a, dsp_macr_p_x0_x0_a, dsp_mpy_m_x0_x0_a, dsp_mpyr_m_x0_x0_a, dsp_mac_m_x0_x0_a, dsp_macr_m_x0_x0_a, dsp_mpy_p_x0_x0_b, dsp_mpyr_p_x0_x0_b, dsp_mac_p_x0_x0_b, dsp_macr_p_x0_x0_b, dsp_mpy_m_x0_x0_b, dsp_mpyr_m_x0_x0_b, dsp_mac_m_x0_x0_b, dsp_macr_m_x0_x0_b, dsp_mpy_p_y0_y0_a, dsp_mpyr_p_y0_y0_a, dsp_mac_p_y0_y0_a, dsp_macr_p_y0_y0_a, dsp_mpy_m_y0_y0_a, dsp_mpyr_m_y0_y0_a, dsp_mac_m_y0_y0_a, dsp_macr_m_y0_y0_a, dsp_mpy_p_y0_y0_b, dsp_mpyr_p_y0_y0_b, dsp_mac_p_y0_y0_b, dsp_macr_p_y0_y0_b, dsp_mpy_m_y0_y0_b, dsp_mpyr_m_y0_y0_b, dsp_mac_m_y0_y0_b, dsp_macr_m_y0_y0_b, dsp_mpy_p_x1_x0_a, dsp_mpyr_p_x1_x0_a, dsp_mac_p_x1_x0_a, dsp_macr_p_x1_x0_a, dsp_mpy_m_x1_x0_a, dsp_mpyr_m_x1_x0_a, dsp_mac_m_x1_x0_a, dsp_macr_m_x1_x0_a, dsp_mpy_p_x1_x0_b, dsp_mpyr_p_x1_x0_b, dsp_mac_p_x1_x0_b, dsp_macr_p_x1_x0_b, dsp_mpy_m_x1_x0_b, dsp_mpyr_m_x1_x0_b, dsp_mac_m_x1_x0_b, dsp_macr_m_x1_x0_b, dsp_mpy_p_y1_y0_a, dsp_mpyr_p_y1_y0_a, dsp_mac_p_y1_y0_a, dsp_macr_p_y1_y0_a, dsp_mpy_m_y1_y0_a, dsp_mpyr_m_y1_y0_a, dsp_mac_m_y1_y0_a, dsp_macr_m_y1_y0_a, dsp_mpy_p_y1_y0_b, dsp_mpyr_p_y1_y0_b, dsp_mac_p_y1_y0_b, dsp_macr_p_y1_y0_b, dsp_mpy_m_y1_y0_b, dsp_mpyr_m_y1_y0_b, dsp_mac_m_y1_y0_b, dsp_macr_m_y1_y0_b, /* 0xc0_m_ 0xff */ dsp_mpy_p_x0_y1_a, dsp_mpyr_p_x0_y1_a, dsp_mac_p_x0_y1_a, dsp_macr_p_x0_y1_a, dsp_mpy_m_x0_y1_a, dsp_mpyr_m_x0_y1_a, dsp_mac_m_x0_y1_a, dsp_macr_m_x0_y1_a, dsp_mpy_p_x0_y1_b, dsp_mpyr_p_x0_y1_b, dsp_mac_p_x0_y1_b, dsp_macr_p_x0_y1_b, dsp_mpy_m_x0_y1_b, dsp_mpyr_m_x0_y1_b, dsp_mac_m_x0_y1_b, dsp_macr_m_x0_y1_b, dsp_mpy_p_y0_x0_a, dsp_mpyr_p_y0_x0_a, dsp_mac_p_y0_x0_a, dsp_macr_p_y0_x0_a, dsp_mpy_m_y0_x0_a, dsp_mpyr_m_y0_x0_a, dsp_mac_m_y0_x0_a, dsp_macr_m_y0_x0_a, dsp_mpy_p_y0_x0_b, dsp_mpyr_p_y0_x0_b, dsp_mac_p_y0_x0_b, dsp_macr_p_y0_x0_b, dsp_mpy_m_y0_x0_b, dsp_mpyr_m_y0_x0_b, dsp_mac_m_y0_x0_b, dsp_macr_m_y0_x0_b, dsp_mpy_p_x1_y0_a, dsp_mpyr_p_x1_y0_a, dsp_mac_p_x1_y0_a, dsp_macr_p_x1_y0_a, dsp_mpy_m_x1_y0_a, dsp_mpyr_m_x1_y0_a, dsp_mac_m_x1_y0_a, dsp_macr_m_x1_y0_a, dsp_mpy_p_x1_y0_b, dsp_mpyr_p_x1_y0_b, dsp_mac_p_x1_y0_b, dsp_macr_p_x1_y0_b, dsp_mpy_m_x1_y0_b, dsp_mpyr_m_x1_y0_b, dsp_mac_m_x1_y0_b, dsp_macr_m_x1_y0_b, dsp_mpy_p_y1_x1_a, dsp_mpyr_p_y1_x1_a, dsp_mac_p_y1_x1_a, dsp_macr_p_y1_x1_a, dsp_mpy_m_y1_x1_a, dsp_mpyr_m_y1_x1_a, dsp_mac_m_y1_x1_a, dsp_macr_m_y1_x1_a, dsp_mpy_p_y1_x1_b, dsp_mpyr_p_y1_x1_b, dsp_mac_p_y1_x1_b, dsp_macr_p_y1_x1_b, dsp_mpy_m_y1_x1_b, dsp_mpyr_m_y1_x1_b, dsp_mac_m_y1_x1_b, dsp_macr_m_y1_x1_b }; static const int registers_tcc[16][2] = { {DSP_REG_B,DSP_REG_A}, {DSP_REG_A,DSP_REG_B}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_X0,DSP_REG_A}, {DSP_REG_X0,DSP_REG_B}, {DSP_REG_Y0,DSP_REG_A}, {DSP_REG_Y0,DSP_REG_B}, {DSP_REG_X1,DSP_REG_A}, {DSP_REG_X1,DSP_REG_B}, {DSP_REG_Y1,DSP_REG_A}, {DSP_REG_Y1,DSP_REG_B} }; static const int registers_mask[64] = { 0, 0, 0, 0, 24, 24, 24, 24, 24, 24, 8, 8, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 6, 16, 16, 16, 16 }; const char dsp_inter_priority_list[32] = { DSP_INTER_ILLEGAL, DSP_INTER_TRACE, DSP_INTER_SWI, DSP_INTER_IRQA, /* 0 */ DSP_INTER_IRQB, DSP_INTER_HOST_COMMAND, DSP_INTER_SSI_TRX_DATA_E, DSP_INTER_SSI_RCV_DATA, /* 4 */ DSP_INTER_SCI_RCV_DATA_E, DSP_INTER_SSI_TRX_DATA, DSP_INTER_SCI_TRX_DATA, DSP_INTER_SCI_RCV_DATA, /* 8 */ DSP_INTER_SCI_IDLE_LINE, DSP_INTER_SCI_TIMER, DSP_PRIORITY_LIST_EXIT, DSP_INTER_STACK_ERROR, /* 12 */ DSP_INTER_HOST_TRX_DATA, DSP_INTER_SSI_RCV_DATA_E, DSP_INTER_HOST_RCV_DATA, 0, /* 16 */ 0, 0, 0, 0, /* 20 */ 0, 0, 0, 0, /* 24 */ 0, 0, 0, DSP_INTER_NMI, /* 28 */ }; const char *dsp_interrupt_name[32] = { "Reset", "Stack Error", "Trace", "SWI", "IRQA", "IRQB", "SSI Receive Data", "SSI Receive Data with Exception", "SSI Transmit Data", "SSI Transmit Data with Exception", "SCI Receive Data", "SCI Receive Data with Exception", "SCI Transmit Data", "SCI Idle Line", "SCI Timer", "NMI", "Host Receive Data", "Host Transmit Data", "Host Command", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Illegal" }; /********************************** * Emulator kernel **********************************/ void dsp56k_init_cpu(void) { dsp56k_disasm_init(); isDsp_in_disasm_mode = false; #if DSP_COUNT_IPS start_time = SDL_GetTicks(); num_inst = 0; #endif } /** * Execute one instruction in trace mode at a given PC address. * */ uint16_t dsp56k_execute_one_disasm_instruction(FILE *out, uint16_t pc) { dsp_core_t *ptr1, *ptr2; static dsp_core_t dsp_core_save; uint16_t instruction_length; ptr1 = &dsp_core; ptr2 = &dsp_core_save; /* Set DSP in disasm mode */ isDsp_in_disasm_mode = true; /* Save DSP context before executing instruction */ memcpy(ptr2, ptr1, sizeof(dsp_core)); /* execute and disasm instruction */ dsp_core.pc = pc; /* Disasm instruction */ instruction_length = dsp56k_disasm(DSP_DISASM_MODE, out) - 1; /* Execute instruction at address given in parameter to get the number of cycles it takes */ dsp56k_execute_instruction(); fprintf(out, "%s", dsp56k_getInstructionText()); /* Restore DSP context after executing instruction */ memcpy(ptr1, ptr2, sizeof(dsp_core)); /* Unset DSP in disasm mode */ isDsp_in_disasm_mode = false; return instruction_length; } void dsp56k_execute_instruction(void) { uint32_t value; uint32_t disasm_return = 0; disasm_memory_ptr = 0; /* Initialise the number of access to the external memory for this instruction */ /* for cycles counting */ access_to_ext_memory = 0; /* Init the indirect AGU move instruction flag */ dsp_core.agu_move_indirect_instr = 0; /* Trace Interrupt at end of instruction? */ if (dsp_core.registers[DSP_REG_SR] & (1<> 11) & (BITMASK(6) << 3); value += (cur_inst >> 5) & BITMASK(3); opcodes8h[value](); } else { /* Do parallel move read */ opcodes_parmove[(cur_inst>>20) & BITMASK(4)](); } /* Add the waitstate due to external memory access */ /* (2 extra cycles per extra access to the external memory after the first one */ if (access_to_ext_memory != 0) { value = (access_to_ext_memory >> DSP_SPACE_X) & 1; value += (access_to_ext_memory >> DSP_SPACE_Y) & 1; value += (access_to_ext_memory >> DSP_SPACE_P) & 1; if (value > 1) dsp_core.instr_cycle += (value - 1) * 2; } /* Process the AGU pipeline */ dsp_core.agu_pipeline_reg[0] = 0; if (dsp_core.agu_pipeline_reg[1] != 0 ) { dsp_core.agu_pipeline_reg[0] = dsp_core.agu_pipeline_reg[1]; dsp_core.agu_pipeline_val[0] = dsp_core.agu_pipeline_val[1]; dsp_core.agu_pipeline_reg[1] = 0; } /* Process the PC */ dsp_postexecute_update_pc(); /* Process Interrupts */ dsp_postexecute_interrupts(); /* Disasm current instruction ? (trace mode only) */ if (LOG_TRACE_LEVEL(TRACE_DSP_DISASM)) { /* Display only when DSP is called in trace mode */ if (isDsp_in_disasm_mode == false) { if (disasm_return != 0) { LOG_TRACE_DIRECT_INIT(); fprintf(TraceFile, "%s", dsp56k_getInstructionText()); /* DSP regs trace enabled only if DSP DISASM is enabled */ if (LOG_TRACE_LEVEL(TRACE_DSP_DISASM_REG)) dsp56k_disasm_reg_compare(TraceFile); if (LOG_TRACE_LEVEL(TRACE_DSP_DISASM_MEM)) { /* 1 memory change to display ? */ if (disasm_memory_ptr == 1) fprintf(TraceFile, "\t%s\n", str_disasm_memory[0]); /* 2 memory changes to display ? */ else if (disasm_memory_ptr == 2) { fprintf(TraceFile, "\t%s\n", str_disasm_memory[0]); fprintf(TraceFile, "\t%s\n", str_disasm_memory[1]); } } } } } #if DSP_COUNT_IPS ++num_inst; if ((num_inst & 63) == 0) { /* Evaluate time after instructions have been executed to avoid asking too frequently */ uint32_t cur_time = SDL_GetTicks(); if (cur_time-start_time>1000) { fprintf(stderr, "Dsp: %d i/s\n", (num_inst*1000)/(cur_time-start_time)); start_time=cur_time; num_inst=0; } } #endif } /********************************** * Update the PC **********************************/ static void dsp_postexecute_update_pc(void) { /* When running a REP, PC must stay on the current instruction */ if (dsp_core.loop_rep) { /* Is PC on the instruction to repeat ? */ if (dsp_core.pc_on_rep==0) { --dsp_core.registers[DSP_REG_LC]; dsp_core.registers[DSP_REG_LC] &= BITMASK(16); if (dsp_core.registers[DSP_REG_LC] > 0) { cur_inst_len = 0; /* Stay on this instruction */ } else { dsp_core.loop_rep = 0; dsp_core.registers[DSP_REG_LC] = dsp_core.registers[DSP_REG_LCSAVE]; } } else { /* Init LC at right value */ if (dsp_core.registers[DSP_REG_LC] == 0) { dsp_core.registers[DSP_REG_LC] = 0x010000; } dsp_core.pc_on_rep = 0; } } /* Normal execution, go to next instruction */ dsp_core.pc += cur_inst_len; /* When running a DO loop, we test the end of loop with the */ /* updated PC, pointing to last instruction of the loop */ if (dsp_core.registers[DSP_REG_SR] & (1<> 3) & 3); ipl_hi = ((value >> 10) & 3); ipl_ssi = ((value >> 12) & 3); ipl_sci = ((value >> 14) & 3); /* Reset all masks */ dsp_core.interrupt_enable = 0; dsp_core.interrupt_mask_level[0] = 0; dsp_core.interrupt_mask_level[1] = 0; dsp_core.interrupt_mask_level[2] = 0; dsp_core.interrupt_edgetriggered_mask = DSP_INTER_EDGE_MASK; /* Set masks to programmed values */ if (ipl_irqa) { dsp_core.interrupt_enable |= DSP_INTER_IRQA_MASK; dsp_core.interrupt_mask_level[ipl_irqa-1] |= DSP_INTER_IRQA_MASK; } if (ipl_irqb) { dsp_core.interrupt_enable |= DSP_INTER_IRQB_MASK; dsp_core.interrupt_mask_level[ipl_irqb-1] |= DSP_INTER_IRQB_MASK; } if (ipl_hi) { dsp_core.interrupt_enable |= DSP_INTER_HOST_MASK; dsp_core.interrupt_mask_level[ipl_hi-1] |= DSP_INTER_HOST_MASK; } if (ipl_ssi) { dsp_core.interrupt_enable |= DSP_INTER_SSI_MASK; dsp_core.interrupt_mask_level[ipl_ssi-1] |= DSP_INTER_SSI_MASK; } if (ipl_sci) { dsp_core.interrupt_enable |= DSP_INTER_SCI_MASK; dsp_core.interrupt_mask_level[ipl_sci-1] |= DSP_INTER_SCI_MASK; } /* Add IRQA, IRQB as edge triggered, if configured */ if (value & 0x04) { dsp_core.interrupt_edgetriggered_mask |= DSP_INTER_IRQA_MASK; } if (value & 0x20) { dsp_core.interrupt_edgetriggered_mask |= DSP_INTER_IRQB_MASK; } } static void dsp_postexecute_interrupts(void) { int i; uint32_t interrupt, inter; uint32_t priority_list_start; uint32_t instr; int32_t ipl_sr; /* REP is not interruptible */ if (dsp_core.loop_rep) { return; } /* A fast interrupt can not be interrupted. */ if (dsp_core.interrupt_state == DSP_INTERRUPT_DISABLED) { switch (dsp_core.interrupt_pipeline_count) { case 5: dsp_core.interrupt_pipeline_count --; return; case 4: /* Prefetch interrupt instruction 1 */ dsp_core.interrupt_save_pc = dsp_core.pc; dsp_core.pc = dsp_core.interrupt_instr_fetch; /* is it a LONG interrupt ? */ /* Extract from Motorola doc A long interrupt is formed if one of the interrupt instructions fetched is a JSR instruction. The PC is immediately released, the SR and the PC are saved in the stack, and the jump instruction controls where the next instruction is fetched from. While either an unconditional jump or conditional jump can be used to form a long interrupt, they do not store the PC on the stack; therefore, there is no return path. */ instr = read_memory_p(dsp_core.interrupt_instr_fetch); // JSR or JMP ? if ( ((instr & 0xfff000) == 0x0d0000) || ((instr & 0xffc0ff) == 0x0bc080) || /* JSR pattern */ ((instr & 0xfff000) == 0x0c0000) || ((instr & 0xffc0ff) == 0x0ac080) ) { /* JMP pattern */ dsp_core.interrupt_state = DSP_INTERRUPT_LONG; dsp_stack_push(dsp_core.interrupt_save_pc, dsp_core.registers[DSP_REG_SR], 0); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>DSP_SR_I0) & BITMASK(2); for (i = 2; i >= ipl_sr; i--) { if (inter & dsp_core.interrupt_mask_level[i]) { dsp_core.interrupt_IplToRaise = i+1; interrupt = inter & dsp_core.interrupt_mask_level[i]; break; } } priority_list_start = DSP_INTER_IRQA; } /* If there is no unmasked interrupt, stop here */ if (!interrupt) { return; } /* Find out which interrupt is pending, using priorities */ for (i = priority_list_start; i != DSP_PRIORITY_LIST_EXIT; i = dsp_inter_priority_list[i]) { if (interrupt & (1<>DSP_SR_S0) & BITMASK(2); switch(scaling) { case 0: /* Extension Bit (E) */ value_e = (reg0<<1) + (reg1>>23); if ((value_e != 0) && (value_e != BITMASK(9))) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_E; /* Unnormalized bit (U) */ if ((reg1 & 0xc00000) == 0 || (reg1 & 0xc00000) == 0xc00000) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_U; break; case 1: /* Extension Bit (E) */ if ((reg0 != 0) && (reg0 != BITMASK(8))) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_E; /* Unnormalized bit (U) */ value_u = ((reg0<<1) + (reg1>>23)) & 3; if (value_u == 0 || value_u == 3) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_U; break; case 2: /* Extension Bit (E) */ value_e = (reg0<<2) + (reg1>>22); if ((value_e != 0) && (value_e != BITMASK(10))) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_E; /* Unnormalized bit (U) */ if ((reg1 & 0x600000) == 0 || (reg1 & 0x600000) == 0x600000) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_U; break; default: return; break; } /* Zero Flag (Z) */ if ((reg1 == 0) && (reg2 == 0) && (reg0 == 0)) dsp_core.registers[DSP_REG_SR] |= 1 << DSP_SR_Z; /* Negative Flag (N) */ dsp_core.registers[DSP_REG_SR] |= (reg0>>4) & 0x8; } /********************************** * Read/Write memory functions **********************************/ static uint32_t read_memory_disasm(int space, uint16_t address) { /* Program memory space ? */ if (space==DSP_SPACE_P) { return read_memory_p(address); } /* Internal RAM ? */ if (address<0x100) { return dsp_core.ramint[space][address] & BITMASK(24); } /* Internal ROM? */ if ((dsp_core.registers[DSP_REG_OMR] & (1<= 0xffc0) { if ((space==DSP_SPACE_X) && (address==0xffc0+DSP_HOST_HTX)) { return dsp_core.dsp_host_htx; } if ((space==DSP_SPACE_X) && (address==0xffc0+DSP_SSI_TX)) { return dsp_core.ssi.transmit_value; } return dsp_core.periph[space][address-0xffc0] & BITMASK(24); } /* Falcon: External RAM, map X to upper 16K of matching space in Y,P */ address &= (DSP_RAMSIZE>>1) - 1; if (space == DSP_SPACE_X) { address |= DSP_RAMSIZE>>1; } /* Falcon: External RAM, finally map X,Y to P */ return dsp_core.ramext[address & (DSP_RAMSIZE-1)] & BITMASK(24); } static inline uint32_t read_memory_p(uint16_t address) { /* Internal RAM ? */ if (address < 0x200) { return dsp_core.ramint[DSP_SPACE_P][address] & BITMASK(24); } /* Access to the external P memory */ access_to_ext_memory |= 1 << DSP_SPACE_P; /* External RAM, mask address to available ram size */ return dsp_core.ramext[address & (DSP_RAMSIZE-1)] & BITMASK(24); } static uint32_t read_memory(int space, uint16_t address) { uint32_t value; /* Program memory space ? */ if (space == DSP_SPACE_P) { return read_memory_p(address); } /* Internal RAM ? */ if (address < 0x100) { return dsp_core.ramint[space][address] & BITMASK(24); } /* Internal ROM ? */ if (address < 0x200) { if (dsp_core.registers[DSP_REG_OMR] & (1<= 0xffc0) { value = dsp_core.periph[space][address-0xffc0] & BITMASK(24); if (space == DSP_SPACE_X) { if (address == 0xffc0+DSP_HOST_HRX) { value = dsp_core.dsp_host_rtx; dsp_core_hostport_dspread(); } else if (address == 0xffc0+DSP_SSI_RX) { value = dsp_core_ssi_readRX(); } } return value; } /* Access to external memory */ access_to_ext_memory |= 1 << space; /* Falcon: External X or Y RAM access */ address &= (DSP_RAMSIZE>>1) - 1; if (space == DSP_SPACE_X) { /* Map X to upper 16K of matching space in Y,P */ address |= DSP_RAMSIZE>>1; } /* Falcon: External RAM, finally map X,Y to P */ return dsp_core.ramext[address & (DSP_RAMSIZE-1)] & BITMASK(24); } static inline void write_memory(int space, uint16_t address, uint32_t value) { if (LOG_TRACE_LEVEL(TRACE_DSP_DISASM_MEM)) write_memory_disasm(space, address, value); else write_memory_raw(space, address, value); } static void write_memory_raw(int space, uint16_t address, uint32_t value) { value &= BITMASK(24); /* Peripheral address ? */ if (address >= 0xffc0) { if (space == DSP_SPACE_X) { switch(address-0xffc0) { case DSP_HOST_HTX: dsp_core.dsp_host_htx = value; dsp_core_hostport_dspwrite(); break; case DSP_HOST_HCR: dsp_core.periph[DSP_SPACE_X][DSP_HOST_HCR] = value & 0x1f; /* Set HF3 and HF2 accordingly on the host side */ dsp_core.hostport[CPU_HOST_ISR] &= BITMASK(8)-((1<>1) - 1; if (space == DSP_SPACE_X) { /* Access to the X external RAM */ /* map X to upper 16K of matching space in Y,P */ address |= DSP_RAMSIZE>>1; } } /* Falcon: External RAM, map X,Y to P */ dsp_core.ramext[address & (DSP_RAMSIZE-1)] = value; } static void write_memory_disasm(int space, uint16_t address, uint32_t value) { uint32_t oldvalue, curvalue; uint8_t space_c = 'p'; value &= BITMASK(24); oldvalue = read_memory_disasm(space, address); write_memory_raw(space,address,value); switch(space) { case DSP_SPACE_X: space_c = 'x'; break; case DSP_SPACE_Y: space_c = 'y'; break; default: break; } curvalue = read_memory_disasm(space, address); snprintf(str_disasm_memory[disasm_memory_ptr], sizeof(str_disasm_memory[0]), "Mem: %c:0x%04x 0x%06x -> 0x%06x", space_c, address, oldvalue, curvalue); disasm_memory_ptr ++; } static void dsp_write_reg(uint32_t numreg, uint32_t value) { uint32_t stack_error; switch (numreg) { case DSP_REG_A: dsp_core.registers[DSP_REG_A0] = 0; dsp_core.registers[DSP_REG_A1] = value & BITMASK(24); dsp_core.registers[DSP_REG_A2] = value & (1<<23) ? 0xff : 0x0; break; case DSP_REG_B: dsp_core.registers[DSP_REG_B0] = 0; dsp_core.registers[DSP_REG_B1] = value & BITMASK(24); dsp_core.registers[DSP_REG_B2] = value & (1<<23) ? 0xff : 0x0; break; case DSP_REG_R0: case DSP_REG_R1: case DSP_REG_R2: case DSP_REG_R3: case DSP_REG_R4: case DSP_REG_R5: case DSP_REG_R6: case DSP_REG_R7: case DSP_REG_N0: case DSP_REG_N1: case DSP_REG_N2: case DSP_REG_N3: case DSP_REG_N4: case DSP_REG_N5: case DSP_REG_N6: case DSP_REG_N7: case DSP_REG_M0: case DSP_REG_M1: case DSP_REG_M2: case DSP_REG_M3: case DSP_REG_M4: case DSP_REG_M5: case DSP_REG_M6: case DSP_REG_M7: /* Indirect move instructions (LUA, MOVEC, MOVEP, Tcc, parallel moves) to registers Rn, Nn, or Mn are delayed by one instruction */ if (dsp_core.agu_move_indirect_instr == 1) { dsp_core.agu_pipeline_reg[1] = numreg; dsp_core.agu_pipeline_val[1] = dsp_core.registers[numreg]; } dsp_core.registers[numreg] = value & BITMASK(16); break; case DSP_REG_OMR: dsp_core.registers[DSP_REG_OMR] = value & 0xc7; break; case DSP_REG_SR: dsp_core.registers[DSP_REG_SR] = value & 0xaf7f; break; case DSP_REG_SP: stack_error = dsp_core.registers[DSP_REG_SP] & (3<modulo) { Log_Printf(LOG_WARN, "Dsp: Modulo addressing result unpredictable\n"); } else { r_reg += reg_mofifier; if (r_reg>hibound) { r_reg -= modulo; } else if (r_reg> 3) & BITMASK(3); numreg = ea_mode & BITMASK(3); /* Get AGU values for registers R, N and M. If previous instruction was a parallel move, there's a 1 instruction delay before using the new value of R, N or M as an address pointer */ /* Registers R0 -> R7 */ if (dsp_core.agu_pipeline_reg[0] == DSP_REG_R0+numreg) reg_r = dsp_core.agu_pipeline_val[0]; else reg_r = dsp_core.registers[DSP_REG_R0+numreg]; /* Registers N0 -> N7 */ if (dsp_core.agu_pipeline_reg[0] == DSP_REG_N0+numreg) reg_n = dsp_core.agu_pipeline_val[0]; else reg_n = dsp_core.registers[DSP_REG_N0+numreg]; /* Registers M0 -> M7 */ if (dsp_core.agu_pipeline_reg[0] == DSP_REG_M0+numreg) reg_m = dsp_core.agu_pipeline_val[0]; else reg_m = dsp_core.registers[DSP_REG_M0+numreg]; switch (value) { case 0: /* (Rx)-Nx */ *dst_addr = reg_r; dsp_update_rn(numreg, reg_r, -reg_n, reg_m); break; case 1: /* (Rx)+Nx */ *dst_addr = reg_r; dsp_update_rn(numreg, reg_r, +reg_n, reg_m); break; case 2: /* (Rx)- */ *dst_addr = reg_r; dsp_update_rn(numreg, reg_r, -1, reg_m); break; case 3: /* (Rx)+ */ *dst_addr = reg_r; dsp_update_rn(numreg, reg_r, +1, reg_m); break; case 4: /* (Rx) */ *dst_addr = reg_r; break; case 5: /* (Rx+Nx) */ dsp_core.instr_cycle += 2; curreg = dsp_core.registers[DSP_REG_R0+numreg]; dsp_update_rn(numreg, reg_r, reg_n, reg_m); *dst_addr = dsp_core.registers[DSP_REG_R0+numreg]; dsp_core.registers[DSP_REG_R0+numreg] = curreg; break; case 6: /* aa */ dsp_core.instr_cycle += 2; *dst_addr = read_memory_p(dsp_core.pc+1); cur_inst_len++; if (numreg != 0) { return 1; /* immediate value */ } break; case 7: /* -(Rx) */ dsp_core.instr_cycle += 2; dsp_update_rn(numreg, reg_r, -1, reg_m); *dst_addr = dsp_core.registers[DSP_REG_R0+numreg]; break; } /* address */ return 0; } /********************************** * Condition code test **********************************/ static int dsp_calc_cc(uint32_t cc_code) { uint16_t value1, value2, value3; switch (cc_code) { case 0: /* CC (HS) */ value1 = dsp_core.registers[DSP_REG_SR] & (1<> DSP_SR_N) & 1; value2 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_V) & 1; return ((value1 ^ value2) == 0); case 2: /* NE */ value1 = dsp_core.registers[DSP_REG_SR] & (1<> DSP_SR_Z) & 1; value2 = (~(dsp_core.registers[DSP_REG_SR] >> DSP_SR_U)) & 1; value3 = (~(dsp_core.registers[DSP_REG_SR] >> DSP_SR_E)) & 1; return ((value1 | (value2 & value3)) == 0); case 5: /* EC */ value1 = dsp_core.registers[DSP_REG_SR] & (1<> DSP_SR_N) & 1; value2 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_V) & 1; value3 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_Z) & 1; return ((value3 | (value1 ^ value2)) == 0); case 8: /* CS (LO) */ value1 = dsp_core.registers[DSP_REG_SR] & (1<> DSP_SR_N) & 1; value2 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_V) & 1; return ((value1 ^ value2) == 1); case 10: /* EQ */ value1 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_Z) & 1; return (value1==1); case 11: /* MI */ value1 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_N) & 1; return (value1==1); case 12: /* NR */ value1 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_Z) & 1; value2 = (~(dsp_core.registers[DSP_REG_SR] >> DSP_SR_U)) & 1; value3 = (~(dsp_core.registers[DSP_REG_SR] >> DSP_SR_E)) & 1; return ((value1 | (value2 & value3)) == 1); case 13: /* ES */ value1 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_E) & 1; return (value1==1); case 14: /* LS */ value1 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_L) & 1; return (value1==1); case 15: /* LE */ value1 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_N) & 1; value2 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_V) & 1; value3 = (dsp_core.registers[DSP_REG_SR] >> DSP_SR_Z) & 1; return ((value3 | (value1 ^ value2)) == 1); } return 0; } /********************************** * Highbyte opcodes dispatchers **********************************/ static void opcode8h_0(void) { switch(cur_inst) { case 0x000000: dsp_nop(); break; case 0x000004: dsp_rti(); break; case 0x000005: dsp_illegal(); break; case 0x000006: dsp_swi(); break; case 0x00000c: dsp_rts(); break; case 0x000084: dsp_reset(); break; case 0x000086: dsp_wait(); break; case 0x000087: dsp_stop(); break; case 0x00008c: dsp_enddo(); break; default: dsp_undefined(); break; } } /********************************** * Non-parallel moves instructions **********************************/ static void dsp_undefined(void) { if (isDsp_in_disasm_mode == false) { cur_inst_len = 0; Log_Printf(LOG_WARN, "Dsp: 0x%04x: 0x%06x Illegal instruction\n",dsp_core.pc, cur_inst); /* Add some artificial CPU cycles to avoid being stuck in an infinite loop */ dsp_core.instr_cycle += 100; } else { cur_inst_len = 1; dsp_core.instr_cycle = 0; } if (ExceptionDebugMask & EXCEPT_DSP) { DebugUI(REASON_DSP_EXCEPTION); } } static void dsp_andi(void) { uint32_t regnum, value; value = (cur_inst >> 8) & BITMASK(8); regnum = cur_inst & BITMASK(2); switch(regnum) { case 0: /* mr */ dsp_core.registers[DSP_REG_SR] &= (value<<8)|BITMASK(8); break; case 1: /* ccr */ dsp_core.registers[DSP_REG_SR] &= (BITMASK(8)<<8)|value; break; case 2: /* omr */ dsp_core.registers[DSP_REG_OMR] &= value; break; } } static void dsp_bchg_aa(void) { uint32_t memspace, addr, value, newcarry, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; if (newcarry) { value -= (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; if (newcarry) { value -= (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; if (newcarry) { value -= (1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } newcarry = (value>>numbit) & 1; if (newcarry) { value -= (1<>6) & 1; addr = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; value &= 0xffffffff-(1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; value &= 0xffffffff-(1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; value &= 0xffffffff-(1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } newcarry = (value>>numbit) & 1; value &= 0xffffffff-(1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; value |= (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; value |= (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; value |= (1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } newcarry = (value>>numbit) & 1; value |= (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; /* Set carry */ dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-(1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; /* Set carry */ dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-(1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newcarry = (value>>numbit) & 1; /* Set carry */ dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-(1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } newcarry = (value>>numbit) & 1; /* Set carry */ dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-(1<>4) & BITMASK(2)) { case 0: srcreg = DSP_REG_X0; break; case 1: srcreg = DSP_REG_Y0; break; case 2: srcreg = DSP_REG_X1; break; case 3: srcreg = DSP_REG_Y1; break; } source[2] = 0; source[1] = dsp_core.registers[srcreg]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; destreg = DSP_REG_A + ((cur_inst>>3) & 1); if (destreg == DSP_REG_A) { dest[0] = dsp_core.registers[DSP_REG_A2]; dest[1] = dsp_core.registers[DSP_REG_A1]; dest[2] = dsp_core.registers[DSP_REG_A0]; } else { dest[0] = dsp_core.registers[DSP_REG_B2]; dest[1] = dsp_core.registers[DSP_REG_B1]; dest[2] = dsp_core.registers[DSP_REG_B0]; } if (((dest[0]>>7) & 1) ^ ((source[1]>>23) & 1)) { /* D += S */ newsr = dsp_asl56(dest); dsp_add56(source, dest); } else { /* D -= S */ newsr = dsp_asl56(dest); dsp_sub56(source, dest); } dest[2] |= (dsp_core.registers[DSP_REG_SR]>>DSP_SR_C) & 1; if (destreg == DSP_REG_A) { dsp_core.registers[DSP_REG_A2] = dest[0]; dsp_core.registers[DSP_REG_A1] = dest[1]; dsp_core.registers[DSP_REG_A0] = dest[2]; } else { dsp_core.registers[DSP_REG_B2] = dest[0]; dsp_core.registers[DSP_REG_B1] = dest[1]; dsp_core.registers[DSP_REG_B0] = dest[2]; } dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>7) & 1))<>6) & 1; addr = (cur_inst>>8) & BITMASK(6); dsp_core.registers[DSP_REG_LC] = read_memory(memspace, addr) & BITMASK(16); dsp_core.instr_cycle += 4; } static void dsp_do_imm(void) { /* #xx */ dsp_stack_push(dsp_core.registers[DSP_REG_LA], dsp_core.registers[DSP_REG_LC], 0); dsp_core.registers[DSP_REG_LA] = read_memory_p(dsp_core.pc+1) & BITMASK(16); cur_inst_len++; dsp_stack_push(dsp_core.pc+cur_inst_len, dsp_core.registers[DSP_REG_SR], 0); dsp_core.registers[DSP_REG_SR] |= (1<>8) & BITMASK(8)) + ((cur_inst & BITMASK(4))<<8); dsp_core.instr_cycle += 4; } static void dsp_do_ea(void) { uint32_t memspace, ea_mode, addr; /* x:ea */ /* y:ea */ dsp_stack_push(dsp_core.registers[DSP_REG_LA], dsp_core.registers[DSP_REG_LC], 0); dsp_core.registers[DSP_REG_LA] = read_memory_p(dsp_core.pc+1) & BITMASK(16); cur_inst_len++; dsp_stack_push(dsp_core.pc+cur_inst_len, dsp_core.registers[DSP_REG_SR], 0); dsp_core.registers[DSP_REG_SR] |= (1<>6) & 1; ea_mode = (cur_inst>>8) & BITMASK(6); dsp_calc_ea(ea_mode, &addr); dsp_core.registers[DSP_REG_LC] = read_memory(memspace, addr) & BITMASK(16); dsp_core.instr_cycle += 4; } static void dsp_do_reg(void) { uint32_t numreg; /* S */ dsp_stack_push(dsp_core.registers[DSP_REG_LA], dsp_core.registers[DSP_REG_LC], 0); dsp_core.registers[DSP_REG_LA] = read_memory_p(dsp_core.pc+1) & BITMASK(16); cur_inst_len++; numreg = (cur_inst>>8) & BITMASK(6); if ((numreg == DSP_REG_A) || (numreg == DSP_REG_B)) { dsp_pm_read_accu24(numreg, &dsp_core.registers[DSP_REG_LC]); } else { dsp_core.registers[DSP_REG_LC] = dsp_core.registers[numreg]; } dsp_core.registers[DSP_REG_LC] &= BITMASK(16); dsp_stack_push(dsp_core.pc+cur_inst_len, dsp_core.registers[DSP_REG_SR], 0); dsp_core.registers[DSP_REG_SR] |= (1<>12) & BITMASK(4); if (dsp_calc_cc(cc_code)) { dsp_core.pc = newpc; cur_inst_len = 0; } dsp_core.instr_cycle += 2; } static void dsp_jcc_ea(void) { uint32_t newpc, cc_code; dsp_calc_ea((cur_inst >>8) & BITMASK(6), &newpc); cc_code=cur_inst & BITMASK(4); if (dsp_calc_cc(cc_code)) { dsp_core.pc = newpc; cur_inst_len = 0; } dsp_core.instr_cycle += 2; } static void dsp_jclr_aa(void) { uint32_t memspace, addr, value, numbit, newaddr; memspace = (cur_inst>>6) & 1; addr = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if ((value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); newaddr = read_memory_p(dsp_core.pc+1); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); dsp_core.instr_cycle += 4; if ((value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if ((value & (1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); newaddr = read_memory_p(dsp_core.pc+1); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } dsp_core.instr_cycle += 4; if ((value & (1<>8) & BITMASK(6), &newpc); cur_inst_len = 0; dsp_core.pc = newpc; dsp_core.instr_cycle += 2; } static void dsp_jmp_imm(void) { uint32_t newpc; newpc = cur_inst & BITMASK(12); cur_inst_len = 0; dsp_core.pc = newpc; dsp_core.instr_cycle += 2; } static void dsp_jscc_ea(void) { uint32_t newpc, cc_code; dsp_calc_ea((cur_inst >>8) & BITMASK(6), &newpc); cc_code=cur_inst & BITMASK(4); if (dsp_calc_cc(cc_code)) { dsp_stack_push(dsp_core.pc+cur_inst_len, dsp_core.registers[DSP_REG_SR], 0); dsp_core.pc = newpc; cur_inst_len = 0; } dsp_core.instr_cycle += 2; } static void dsp_jscc_imm(void) { uint32_t cc_code, newpc; newpc = cur_inst & BITMASK(12); cc_code=(cur_inst>>12) & BITMASK(4); if (dsp_calc_cc(cc_code)) { dsp_stack_push(dsp_core.pc+cur_inst_len, dsp_core.registers[DSP_REG_SR], 0); dsp_core.pc = newpc; cur_inst_len = 0; } dsp_core.instr_cycle += 2; } static void dsp_jsclr_aa(void) { uint32_t memspace, addr, value, newpc, numbit, newaddr; memspace = (cur_inst>>6) & 1; addr = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if ((value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if ((value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if ((value & (1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); newaddr = read_memory_p(dsp_core.pc+1); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } dsp_core.instr_cycle += 4; if ((value & (1<>6) & 1; addr = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if (value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if (value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if (value & (1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); newaddr = read_memory_p(dsp_core.pc+1); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } dsp_core.instr_cycle += 4; if (value & (1<>8) & BITMASK(6),&newpc); if (dsp_core.interrupt_state != DSP_INTERRUPT_LONG){ dsp_stack_push(dsp_core.pc+cur_inst_len, dsp_core.registers[DSP_REG_SR], 0); } else { dsp_core.interrupt_state = DSP_INTERRUPT_DISABLED; } dsp_core.pc = newpc; cur_inst_len = 0; dsp_core.instr_cycle += 2; } static void dsp_jsset_aa(void) { uint32_t memspace, addr, value, newpc, numbit, newaddr; memspace = (cur_inst>>6) & 1; addr = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if (value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, &addr); value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if (value & (1<>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); addr = 0xffc0 + value; value = read_memory(memspace, addr); newaddr = read_memory_p(dsp_core.pc+1); dsp_core.instr_cycle += 4; if (value & (1<>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); newaddr = read_memory_p(dsp_core.pc+1); if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } dsp_core.instr_cycle += 4; if (value & (1<>8) & BITMASK(3); srcsave = dsp_core.registers[DSP_REG_R0+srcreg]; dsp_calc_ea((cur_inst>>8) & BITMASK(5), &value); srcnew = dsp_core.registers[DSP_REG_R0+srcreg]; dsp_core.registers[DSP_REG_R0+srcreg] = srcsave; if (cur_inst & (1<<3)) dstreg = DSP_REG_N0 + (cur_inst & BITMASK(3)); else dstreg = DSP_REG_R0 + (cur_inst & BITMASK(3)); dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(dstreg, srcnew); dsp_core.instr_cycle += 2; } static void dsp_movec_reg(void) { uint32_t numreg1, numreg2, value, dummy; /* S1,D2 */ /* S2,D1 */ numreg2 = (cur_inst>>8) & BITMASK(6); numreg1 = cur_inst & BITMASK(6); dsp_core.agu_move_indirect_instr = 1; if (cur_inst & (1<<15)) { /* Write D1 */ if ((numreg2 == DSP_REG_A) || (numreg2 == DSP_REG_B)) { dsp_pm_read_accu24(numreg2, &value); } else { value = dsp_core.registers[numreg2]; } dsp_write_reg(numreg1, value); } else { /* Read S1 */ if (numreg1 == DSP_REG_SSH) { dsp_stack_pop(&value, &dummy); } else { value = dsp_core.registers[numreg1]; } dsp_write_reg(numreg2, value); } } static void dsp_movec_aa(void) { uint32_t numreg, addr, memspace, value, dummy; /* x:aa,D1 */ /* S1,x:aa */ /* y:aa,D1 */ /* S1,y:aa */ numreg = cur_inst & BITMASK(6); addr = (cur_inst>>8) & BITMASK(6); memspace = (cur_inst>>6) & 1; if (cur_inst & (1<<15)) { /* Write D1 */ value = read_memory(memspace, addr); dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } else { /* Read S1 */ if (numreg == DSP_REG_SSH) { dsp_stack_pop(&value, &dummy); } else { value = dsp_core.registers[numreg]; } write_memory(memspace, addr, value); } } static void dsp_movec_imm(void) { uint32_t numreg, value; /* #xx,D1 */ numreg = cur_inst & BITMASK(6); value = (cur_inst>>8) & BITMASK(8); dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } static void dsp_movec_ea(void) { uint32_t numreg, addr, memspace, ea_mode, value, dummy; int retour; /* x:ea,D1 */ /* S1,x:ea */ /* y:ea,D1 */ /* S1,y:ea */ /* #xxxx,D1 */ numreg = cur_inst & BITMASK(6); ea_mode = (cur_inst>>8) & BITMASK(6); memspace = (cur_inst>>6) & 1; if (cur_inst & (1<<15)) { /* Write D1 */ retour = dsp_calc_ea(ea_mode, &addr); if (retour) { value = addr; } else { value = read_memory(memspace, addr); } dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } else { /* Read S1 */ dsp_calc_ea(ea_mode, &addr); if (numreg == DSP_REG_SSH) { dsp_stack_pop(&value, &dummy); } else { value = dsp_core.registers[numreg]; } write_memory(memspace, addr, value); } } static void dsp_movem_aa(void) { uint32_t numreg, addr, value, dummy; numreg = cur_inst & BITMASK(6); addr = (cur_inst>>8) & BITMASK(6); if (cur_inst & (1<<15)) { /* Write D */ value = read_memory_p(addr); /* [LS] According to Motorola doc, MOVEM is said to be a parallel move instruction. But tests seems to contradict this. Next line should stay commented for now. */ //dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } else { /* Read S */ if (numreg == DSP_REG_SSH) { dsp_stack_pop(&value, &dummy); } else if ((numreg == DSP_REG_A) || (numreg == DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } write_memory(DSP_SPACE_P, addr, value); } dsp_core.instr_cycle += 4; } static void dsp_movem_ea(void) { uint32_t numreg, addr, ea_mode, value, dummy; numreg = cur_inst & BITMASK(6); ea_mode = (cur_inst>>8) & BITMASK(6); dsp_calc_ea(ea_mode, &addr); if (cur_inst & (1<<15)) { /* Write D */ value = read_memory_p(addr); /* [LS] According to Motorola doc, MOVEM is said to be a parallel move instruction. But tests seems to contradict this. Next line should stay commented for now. */ //dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } else { /* Read S */ if (numreg == DSP_REG_SSH) { dsp_stack_pop(&value, &dummy); } else if ((numreg == DSP_REG_A) || (numreg == DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else { value = dsp_core.registers[numreg]; } write_memory(DSP_SPACE_P, addr, value); } dsp_core.instr_cycle += 4; } static void dsp_movep_0(void) { /* S,x:pp */ /* x:pp,D */ /* S,y:pp */ /* y:pp,D */ uint32_t addr, memspace, numreg, value, dummy; addr = 0xffc0 + (cur_inst & BITMASK(6)); memspace = (cur_inst>>16) & 1; numreg = (cur_inst>>8) & BITMASK(6); if (cur_inst & (1<<15)) { /* Write pp */ if ((numreg == DSP_REG_A) || (numreg == DSP_REG_B)) { dsp_pm_read_accu24(numreg, &value); } else if (numreg == DSP_REG_SSH) { dsp_stack_pop(&value, &dummy); } else { value = dsp_core.registers[numreg]; } write_memory(memspace, addr, value); } else { /* Read pp */ value = read_memory(memspace, addr); dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } dsp_core.instr_cycle += 2; } static void dsp_movep_1(void) { /* p:ea,x:pp */ /* x:pp,p:ea */ /* p:ea,y:pp */ /* y:pp,p:ea */ uint32_t xyaddr, memspace, paddr; xyaddr = 0xffc0 + (cur_inst & BITMASK(6)); dsp_calc_ea((cur_inst>>8) & BITMASK(6), &paddr); memspace = (cur_inst>>16) & 1; if (cur_inst & (1<<15)) { /* Write pp */ write_memory(memspace, xyaddr, read_memory_p(paddr)); } else { /* Read pp */ write_memory(DSP_SPACE_P, paddr, read_memory(memspace, xyaddr)); } /* Movep is 4 cycles, but according to the motorola doc, */ /* movep from p memory to x or y peripheral memory takes */ /* 2 more cycles, so +4 cycles at total */ dsp_core.instr_cycle += 4; } static void dsp_movep_23(void) { /* x:ea,x:pp */ /* y:ea,x:pp */ /* #xxxxxx,x:pp */ /* x:pp,x:ea */ /* x:pp,y:pp */ /* x:ea,y:pp */ /* y:ea,y:pp */ /* #xxxxxx,y:pp */ /* y:pp,y:ea */ /* y:pp,x:ea */ uint32_t addr, peraddr, easpace, perspace, ea_mode; int retour; peraddr = 0xffc0 + (cur_inst & BITMASK(6)); perspace = (cur_inst>>16) & 1; ea_mode = (cur_inst>>8) & BITMASK(6); easpace = (cur_inst>>6) & 1; retour = dsp_calc_ea(ea_mode, &addr); if (cur_inst & (1<<15)) { /* Write pp */ if (retour) { write_memory(perspace, peraddr, addr); } else { write_memory(perspace, peraddr, read_memory(easpace, addr)); } } else { /* Read pp */ write_memory(easpace, addr, read_memory(perspace, peraddr)); } dsp_core.instr_cycle += 2; } static void dsp_norm(void) { uint32_t cursr,cur_e, cur_euz, dest[3], numreg, rreg; uint16_t newsr; cursr = dsp_core.registers[DSP_REG_SR]; cur_e = (cursr>>DSP_SR_E) & 1; /* E */ cur_euz = ~cur_e; /* (not E) and U and (not Z) */ cur_euz &= (cursr>>DSP_SR_U) & 1; cur_euz &= ~((cursr>>DSP_SR_Z) & 1); cur_euz &= 1; numreg = (cur_inst>>3) & 1; dest[0] = dsp_core.registers[DSP_REG_A2+numreg]; dest[1] = dsp_core.registers[DSP_REG_A1+numreg]; dest[2] = dsp_core.registers[DSP_REG_A0+numreg]; rreg = DSP_REG_R0+((cur_inst>>8) & BITMASK(3)); if (cur_euz) { newsr = dsp_asl56(dest); --dsp_core.registers[rreg]; dsp_core.registers[rreg] &= BITMASK(16); } else if (cur_e) { newsr = dsp_asr56(dest); ++dsp_core.registers[rreg]; dsp_core.registers[rreg] &= BITMASK(16); } else { newsr = 0; } dsp_core.registers[DSP_REG_A2+numreg] = dest[0]; dsp_core.registers[DSP_REG_A1+numreg] = dest[1]; dsp_core.registers[DSP_REG_A0+numreg] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<> 8) & BITMASK(8); regnum = cur_inst & BITMASK(2); switch(regnum) { case 0: /* mr */ dsp_core.registers[DSP_REG_SR] |= value<<8; break; case 1: /* ccr */ dsp_core.registers[DSP_REG_SR] |= value; break; case 2: /* omr */ dsp_core.registers[DSP_REG_OMR] |= value; break; } } /* REP instruction parameter encoding xxxxxxxx 00xxxxxx 0xxxxxxx aa xxxxxxxx 01xxxxxx 0xxxxxxx ea xxxxxxxx YYxxxxxx 1xxxxxxx imm xxxxxxxx 11xxxxxx 0xxxxxxx reg */ static void dsp_rep_aa(void) { /* x:aa */ /* y:aa */ dsp_core.registers[DSP_REG_LCSAVE] = dsp_core.registers[DSP_REG_LC]; dsp_core.pc_on_rep = 1; /* Not decrement LC at first time */ dsp_core.loop_rep = 1; /* We are now running rep */ dsp_core.registers[DSP_REG_LC]=read_memory((cur_inst>>6) & 1,(cur_inst>>8) & BITMASK(6)); dsp_core.instr_cycle += 2; } static void dsp_rep_imm(void) { /* #xxx */ dsp_core.registers[DSP_REG_LCSAVE] = dsp_core.registers[DSP_REG_LC]; dsp_core.pc_on_rep = 1; /* Not decrement LC at first time */ dsp_core.loop_rep = 1; /* We are now running rep */ dsp_core.registers[DSP_REG_LC] = ((cur_inst>>8) & BITMASK(8)) + ((cur_inst & BITMASK(4))<<8); dsp_core.instr_cycle += 2; } static void dsp_rep_ea(void) { uint32_t value; /* x:ea */ /* y:ea */ dsp_core.registers[DSP_REG_LCSAVE] = dsp_core.registers[DSP_REG_LC]; dsp_core.pc_on_rep = 1; /* Not decrement LC at first time */ dsp_core.loop_rep = 1; /* We are now running rep */ dsp_calc_ea((cur_inst>>8) & BITMASK(6),&value); dsp_core.registers[DSP_REG_LC]= read_memory((cur_inst>>6) & 1, value); dsp_core.instr_cycle += 2; } static void dsp_rep_reg(void) { uint32_t numreg; /* R */ dsp_core.registers[DSP_REG_LCSAVE] = dsp_core.registers[DSP_REG_LC]; dsp_core.pc_on_rep = 1; /* Not decrement LC at first time */ dsp_core.loop_rep = 1; /* We are now running rep */ numreg = (cur_inst>>8) & BITMASK(6); if ((numreg == DSP_REG_A) || (numreg == DSP_REG_B)) { dsp_pm_read_accu24(numreg, &dsp_core.registers[DSP_REG_LC]); } else { dsp_core.registers[DSP_REG_LC] = dsp_core.registers[numreg]; } dsp_core.registers[DSP_REG_LC] &= BITMASK(16); dsp_core.instr_cycle += 2; } static void dsp_reset(void) { /* Clear the IPR register */ write_memory(DSP_SPACE_X, 0xffc0 + DSP_IPR, 0); /* Software reset all on-chip peripherals */ /* HOST_HCR x:$FFE8 : clear the full register */ write_memory(DSP_SPACE_X, 0xffc0 + DSP_HOST_HCR, 0); /* HOST_ICR $0 : clear the full register */ dsp_core_write_host(CPU_HOST_ICR, 0); /* HOST_CVR $1 : set the register to $12 */ dsp_core_write_host(CPU_HOST_CVR, 0x12); /* HOST_ISR $2 : set the bits TRDY and TXDE 1, other bits to 0 */ dsp_core.hostport[CPU_HOST_ISR] = (1<>12) & BITMASK(4); if (dsp_calc_cc(cc_code)) { regsrc1 = registers_tcc[(cur_inst>>3) & BITMASK(4)][0]; regdest1 = registers_tcc[(cur_inst>>3) & BITMASK(4)][1]; /* Read S1 */ if (regsrc1 == DSP_REG_A) { val0 = dsp_core.registers[DSP_REG_A0]; val1 = dsp_core.registers[DSP_REG_A1]; val2 = dsp_core.registers[DSP_REG_A2]; } else if (regsrc1 == DSP_REG_B) { val0 = dsp_core.registers[DSP_REG_B0]; val1 = dsp_core.registers[DSP_REG_B1]; val2 = dsp_core.registers[DSP_REG_B2]; } else { val0 = 0; val1 = dsp_core.registers[regsrc1]; val2 = val1 & (1<<23) ? 0xff : 0x0; } /* Write D1 */ if (regdest1 == DSP_REG_A) { dsp_core.registers[DSP_REG_A2] = val2; dsp_core.registers[DSP_REG_A1] = val1; dsp_core.registers[DSP_REG_A0] = val0; } else { dsp_core.registers[DSP_REG_B2] = val2; dsp_core.registers[DSP_REG_B1] = val1; dsp_core.registers[DSP_REG_B0] = val0; } /* S2,D2 transfer */ if (cur_inst & (1<<16)) { regsrc2 = DSP_REG_R0+((cur_inst>>8) & BITMASK(3)); regdest2 = DSP_REG_R0+(cur_inst & BITMASK(3)); dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(regdest2, dsp_core.registers[regsrc2]); } } } static void dsp_wait(void) { LOG_TRACE(TRACE_DSP_STATE, "Dsp: WAIT instruction\n"); } static int dsp_pm_read_accu24(int numreg, uint32_t *dest) { uint32_t scaling, value, reg; int got_limited = 0; /* Read an accumulator, stores it limited */ scaling = (dsp_core.registers[DSP_REG_SR]>>DSP_SR_S0) & BITMASK(2); reg = numreg & 1; value = (dsp_core.registers[DSP_REG_A2+reg]) << 24; value += dsp_core.registers[DSP_REG_A1+reg]; switch(scaling) { case 0: /* No scaling */ break; case 1: /* scaling down */ value >>= 1; break; case 2: /* scaling up */ value <<= 1; value |= (dsp_core.registers[DSP_REG_A0+reg]>>23) & 1; break; /* indeterminate */ case 3: break; } /* limiting ? */ value &= BITMASK(24); if (dsp_core.registers[DSP_REG_A2+reg] == 0) { if (value <= 0x007fffff) { /* No limiting */ *dest=value; return 0; } } if (dsp_core.registers[DSP_REG_A2+reg] == 0xff) { if (value >= 0x00800000) { /* No limiting */ *dest=value; return 0; } } if (dsp_core.registers[DSP_REG_A2+reg] & (1<<7)) { /* Limited to maximum negative value */ *dest=0x00800000; dsp_core.registers[DSP_REG_SR] |= (1<>15) & 1; numreg = (cur_inst>>16) & 1; dsp_calc_ea((cur_inst>>8) & BITMASK(6), &addr); /* Save A or B */ dsp_pm_read_accu24(numreg, &save_accu); /* Save X0 or Y0 */ save_xy0 = dsp_core.registers[DSP_REG_X0+(memspace<<1)]; /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); /* Move [A|B] to [x|y]:ea */ write_memory(memspace, addr, save_accu); /* Move [x|y]0 to [A|B] */ dsp_core.registers[DSP_REG_A0+numreg] = 0; dsp_core.registers[DSP_REG_A1+numreg] = save_xy0; dsp_core.registers[DSP_REG_A2+numreg] = save_xy0 & (1<<23) ? 0xff : 0x0; } static void dsp_pm_1(void) { uint32_t memspace, numreg1, numreg2, value, xy_addr, retour, save_1, save_2; /* 0001 ffdf w0mm mrrr x:ea,D1 S2,D2 S1,x:ea S2,D2 #xxxxxx,D1 S2,D2 0001 deff w1mm mrrr S1,D1 y:ea,D2 S1,D1 S2,y:ea S1,D1 #xxxxxx,D2 */ value = (cur_inst>>8) & BITMASK(6); retour = dsp_calc_ea(value, &xy_addr); memspace = (cur_inst>>14) & 1; numreg1 = numreg2 = DSP_REG_NULL; if (memspace) { /* Y: */ switch((cur_inst>>16) & BITMASK(2)) { case 0: numreg1 = DSP_REG_Y0; break; case 1: numreg1 = DSP_REG_Y1; break; case 2: numreg1 = DSP_REG_A; break; case 3: numreg1 = DSP_REG_B; break; } } else { /* X: */ switch((cur_inst>>18) & BITMASK(2)) { case 0: numreg1 = DSP_REG_X0; break; case 1: numreg1 = DSP_REG_X1; break; case 2: numreg1 = DSP_REG_A; break; case 3: numreg1 = DSP_REG_B; break; } } if (cur_inst & (1<<15)) { /* Write D1 */ if (retour) save_1 = xy_addr; else save_1 = read_memory(memspace, xy_addr); } else { /* Read S1 */ if ((numreg1==DSP_REG_A) || (numreg1==DSP_REG_B)) dsp_pm_read_accu24(numreg1, &save_1); else save_1 = dsp_core.registers[numreg1]; } /* S2 */ if (memspace) { /* Y: */ numreg2 = DSP_REG_A + ((cur_inst>>19) & 1); } else { /* X: */ numreg2 = DSP_REG_A + ((cur_inst>>17) & 1); } dsp_pm_read_accu24(numreg2, &save_2); /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); /* Write parallel move values */ if (cur_inst & (1<<15)) { /* Write D1 */ dsp_write_reg(numreg1, save_1); } else { /* Read S1 */ write_memory(memspace, xy_addr, save_1); } /* S2 -> D2 */ if (memspace) { /* Y: */ numreg2 = DSP_REG_X0 + ((cur_inst>>18) & 1); } else { /* X: */ numreg2 = DSP_REG_Y0 + ((cur_inst>>16) & 1); } dsp_core.registers[numreg2] = save_2; } static void dsp_pm_2(void) { uint32_t dummy; /* 0010 0000 0000 0000 nop 0010 0000 010m mrrr R update 0010 00ee eeed dddd S,D 001d dddd iiii iiii #xx,D */ if ((cur_inst & 0xffff00) == 0x200000) { /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); return; } if ((cur_inst & 0xffe000) == 0x204000) { dsp_calc_ea((cur_inst>>8) & BITMASK(5), &dummy); /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); return; } if ((cur_inst & 0xfc0000) == 0x200000) { dsp_pm_2_2(); return; } dsp_pm_3(); } static void dsp_pm_2_2(void) { /* 0010 00ee eeed dddd S,D */ uint32_t srcreg, dstreg, save_reg; srcreg = (cur_inst >> 13) & BITMASK(5); dstreg = (cur_inst >> 8) & BITMASK(5); if ((srcreg == DSP_REG_A) || (srcreg == DSP_REG_B)) /* Accu to register: limited 24 bits */ dsp_pm_read_accu24(srcreg, &save_reg); else save_reg = dsp_core.registers[srcreg]; /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); /* Write reg */ dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(dstreg, save_reg); } static void dsp_pm_3(void) { uint32_t dstreg, srcvalue; /* 001d dddd iiii iiii #xx,R */ /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); /* Write reg */ dstreg = (cur_inst >> 16) & BITMASK(5); srcvalue = (cur_inst >> 8) & BITMASK(8); switch(dstreg) { case DSP_REG_X0: case DSP_REG_X1: case DSP_REG_Y0: case DSP_REG_Y1: case DSP_REG_A: case DSP_REG_B: srcvalue <<= 16; break; } dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(dstreg, srcvalue); } static void dsp_pm_4(void) { /* 0100 l0ll w0aa aaaa l:aa,D S,l:aa 0100 l0ll w1mm mrrr l:ea,D S,l:ea 01dd 0ddd w0aa aaaa x:aa,D S,x:aa 01dd 0ddd w1mm mrrr x:ea,D S,x:ea #xxxxxx,D 01dd 1ddd w0aa aaaa y:aa,D S,y:aa 01dd 1ddd w1mm mrrr y:ea,D S,y:ea #xxxxxx,D */ if ((cur_inst & 0xf40000)==0x400000) { dsp_pm_4x(); return; } dsp_pm_5(); } static void dsp_pm_4x(void) { uint32_t value, numreg, l_addr, save_lx, save_ly; /* 0100 l0ll w0aa aaaa l:aa,D S,l:aa 0100 l0ll w1mm mrrr l:ea,D S,l:ea */ value = (cur_inst>>8) & BITMASK(6); if (cur_inst & (1<<14)) { dsp_calc_ea(value, &l_addr); } else { l_addr = value; } numreg = (cur_inst>>16) & BITMASK(2); numreg |= (cur_inst>>17) & (1<<2); if (cur_inst & (1<<15)) { /* Write D */ save_lx = read_memory(DSP_SPACE_X,l_addr); save_ly = read_memory(DSP_SPACE_Y,l_addr); } else { /* Read S */ switch(numreg) { case 0: /* A10 */ save_lx = dsp_core.registers[DSP_REG_A1]; save_ly = dsp_core.registers[DSP_REG_A0]; break; case 1: /* B10 */ save_lx = dsp_core.registers[DSP_REG_B1]; save_ly = dsp_core.registers[DSP_REG_B0]; break; case 2: /* X */ save_lx = dsp_core.registers[DSP_REG_X1]; save_ly = dsp_core.registers[DSP_REG_X0]; break; case 3: /* Y */ save_lx = dsp_core.registers[DSP_REG_Y1]; save_ly = dsp_core.registers[DSP_REG_Y0]; break; case 4: /* A */ if (dsp_pm_read_accu24(DSP_REG_A, &save_lx)) { /* Was limited, set lower part */ save_ly = (save_lx & (1<<23) ? 0 : 0xffffff); } else { /* Not limited */ save_ly = dsp_core.registers[DSP_REG_A0]; } break; case 5: /* B */ if (dsp_pm_read_accu24(DSP_REG_B, &save_lx)) { /* Was limited, set lower part */ save_ly = (save_lx & (1<<23) ? 0 : 0xffffff); } else { /* Not limited */ save_ly = dsp_core.registers[DSP_REG_B0]; } break; case 6: /* AB */ dsp_pm_read_accu24(DSP_REG_A, &save_lx); dsp_pm_read_accu24(DSP_REG_B, &save_ly); break; case 7: /* BA */ dsp_pm_read_accu24(DSP_REG_B, &save_lx); dsp_pm_read_accu24(DSP_REG_A, &save_ly); break; } } /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); if (cur_inst & (1<<15)) { /* Write D */ switch(numreg) { case 0: /* A10 */ dsp_core.registers[DSP_REG_A1] = save_lx; dsp_core.registers[DSP_REG_A0] = save_ly; break; case 1: /* B10 */ dsp_core.registers[DSP_REG_B1] = save_lx; dsp_core.registers[DSP_REG_B0] = save_ly; break; case 2: /* X */ dsp_core.registers[DSP_REG_X1] = save_lx; dsp_core.registers[DSP_REG_X0] = save_ly; break; case 3: /* Y */ dsp_core.registers[DSP_REG_Y1] = save_lx; dsp_core.registers[DSP_REG_Y0] = save_ly; break; case 4: /* A */ dsp_core.registers[DSP_REG_A0] = save_ly; dsp_core.registers[DSP_REG_A1] = save_lx; dsp_core.registers[DSP_REG_A2] = save_lx & (1<<23) ? 0xff : 0; break; case 5: /* B */ dsp_core.registers[DSP_REG_B0] = save_ly; dsp_core.registers[DSP_REG_B1] = save_lx; dsp_core.registers[DSP_REG_B2] = save_lx & (1<<23) ? 0xff : 0; break; case 6: /* AB */ dsp_core.registers[DSP_REG_A0] = 0; dsp_core.registers[DSP_REG_A1] = save_lx; dsp_core.registers[DSP_REG_A2] = save_lx & (1<<23) ? 0xff : 0; dsp_core.registers[DSP_REG_B0] = 0; dsp_core.registers[DSP_REG_B1] = save_ly; dsp_core.registers[DSP_REG_B2] = save_ly & (1<<23) ? 0xff : 0; break; case 7: /* BA */ dsp_core.registers[DSP_REG_B0] = 0; dsp_core.registers[DSP_REG_B1] = save_lx; dsp_core.registers[DSP_REG_B2] = save_lx & (1<<23) ? 0xff : 0; dsp_core.registers[DSP_REG_A0] = 0; dsp_core.registers[DSP_REG_A1] = save_ly; dsp_core.registers[DSP_REG_A2] = save_ly & (1<<23) ? 0xff : 0; break; } } else { /* Read S */ write_memory(DSP_SPACE_X, l_addr, save_lx); write_memory(DSP_SPACE_Y, l_addr, save_ly); } } static void dsp_pm_5(void) { uint32_t memspace, numreg, value, xy_addr, retour; /* 01dd 0ddd w0aa aaaa x:aa,D S,x:aa 01dd 0ddd w1mm mrrr x:ea,D S,x:ea #xxxxxx,D 01dd 1ddd w0aa aaaa y:aa,D S,y:aa 01dd 1ddd w1mm mrrr y:ea,D S,y:ea #xxxxxx,D */ value = (cur_inst>>8) & BITMASK(6); if (cur_inst & (1<<14)) { retour = dsp_calc_ea(value, &xy_addr); } else { xy_addr = value; retour = 0; } memspace = (cur_inst>>19) & 1; numreg = (cur_inst>>16) & BITMASK(3); numreg |= (cur_inst>>17) & (BITMASK(2)<<3); if (cur_inst & (1<<15)) { /* Write D */ if (retour) value = xy_addr; else value = read_memory(memspace, xy_addr); } else { /* Read S */ if ((numreg==DSP_REG_A) || (numreg==DSP_REG_B)) dsp_pm_read_accu24(numreg, &value); else value = dsp_core.registers[numreg]; } /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); if (cur_inst & (1<<15)) { /* Write D */ dsp_core.agu_move_indirect_instr = 1; dsp_write_reg(numreg, value); } else { /* Read S */ write_memory(memspace, xy_addr, value); } } static void dsp_pm_8(void) { uint32_t ea1, ea2; uint32_t numreg1, numreg2; uint32_t save_reg1, save_reg2, x_addr, y_addr; /* 1wmm eeff WrrM MRRR x:ea,D1 y:ea,D2 x:ea,D1 S2,y:ea S1,x:ea y:ea,D2 S1,x:ea S2,y:ea */ numreg1 = numreg2 = DSP_REG_NULL; ea1 = (cur_inst>>8) & BITMASK(5); if ((ea1>>3) == 0) { ea1 |= (1<<5); } ea2 = (cur_inst>>13) & BITMASK(2); ea2 |= (cur_inst>>17) & (BITMASK(2)<<3); if ((ea1 & (1<<2))==0) { ea2 |= 1<<2; } if ((ea2>>3) == 0) { ea2 |= (1<<5); } dsp_calc_ea(ea1, &x_addr); dsp_calc_ea(ea2, &y_addr); switch((cur_inst>>18) & BITMASK(2)) { case 0: numreg1=DSP_REG_X0; break; case 1: numreg1=DSP_REG_X1; break; case 2: numreg1=DSP_REG_A; break; case 3: numreg1=DSP_REG_B; break; } switch((cur_inst>>16) & BITMASK(2)) { case 0: numreg2=DSP_REG_Y0; break; case 1: numreg2=DSP_REG_Y1; break; case 2: numreg2=DSP_REG_A; break; case 3: numreg2=DSP_REG_B; break; } if (cur_inst & (1<<15)) { /* Write D1 */ save_reg1 = read_memory(DSP_SPACE_X, x_addr); } else { /* Read S1 */ if ((numreg1==DSP_REG_A) || (numreg1==DSP_REG_B)) dsp_pm_read_accu24(numreg1, &save_reg1); else save_reg1 = dsp_core.registers[numreg1]; } if (cur_inst & (1<<22)) { /* Write D2 */ save_reg2 = read_memory(DSP_SPACE_Y, y_addr); } else { /* Read S2 */ if ((numreg2==DSP_REG_A) || (numreg2==DSP_REG_B)) dsp_pm_read_accu24(numreg2, &save_reg2); else save_reg2 = dsp_core.registers[numreg2]; } /* Execute parallel instruction */ opcodes_alu[cur_inst & BITMASK(8)](); /* Write first parallel move */ if (cur_inst & (1<<15)) { /* Write D1 */ if (numreg1 == DSP_REG_A) { dsp_core.registers[DSP_REG_A0] = 0x0; dsp_core.registers[DSP_REG_A1] = save_reg1; dsp_core.registers[DSP_REG_A2] = save_reg1 & (1<<23) ? 0xff : 0x0; } else if (numreg1 == DSP_REG_B) { dsp_core.registers[DSP_REG_B0] = 0x0; dsp_core.registers[DSP_REG_B1] = save_reg1; dsp_core.registers[DSP_REG_B2] = save_reg1 & (1<<23) ? 0xff : 0x0; } else { dsp_core.registers[numreg1] = save_reg1; } } else { /* Read S1 */ write_memory(DSP_SPACE_X, x_addr, save_reg1); } /* Write second parallel move */ if (cur_inst & (1<<22)) { /* Write D2 */ if (numreg2 == DSP_REG_A) { dsp_core.registers[DSP_REG_A0] = 0x0; dsp_core.registers[DSP_REG_A1] = save_reg2; dsp_core.registers[DSP_REG_A2] = save_reg2 & (1<<23) ? 0xff : 0x0; } else if (numreg2 == DSP_REG_B) { dsp_core.registers[DSP_REG_B0] = 0x0; dsp_core.registers[DSP_REG_B1] = save_reg2; dsp_core.registers[DSP_REG_B2] = save_reg2 & (1<<23) ? 0xff : 0x0; } else { dsp_core.registers[numreg2] = save_reg2; } } else { /* Read S2 */ write_memory(DSP_SPACE_Y, y_addr, save_reg2); } } /********************************** * 56bit arithmetic **********************************/ /* source,dest[0] is 55:48 */ /* source,dest[1] is 47:24 */ /* source,dest[2] is 23:00 */ static uint16_t dsp_abs56(uint32_t *dest) { uint32_t zerodest[3]; uint16_t newsr; /* D=|D| */ if (dest[0] & (1<<7)) { zerodest[0] = zerodest[1] = zerodest[2] = 0; newsr = dsp_sub56(dest, zerodest); dest[0] = zerodest[0]; dest[1] = zerodest[1]; dest[2] = zerodest[2]; } else { newsr = 0; } return newsr; } static uint16_t dsp_asl56(uint32_t *dest) { uint16_t overflow, carry; /* Shift left dest 1 bit: D<<=1 */ carry = (dest[0]>>7) & 1; dest[0] <<= 1; dest[0] |= (dest[1]>>23) & 1; dest[0] &= BITMASK(8); dest[1] <<= 1; dest[1] |= (dest[2]>>23) & 1; dest[1] &= BITMASK(24); dest[2] <<= 1; dest[2] &= BITMASK(24); overflow = (carry != ((dest[0]>>7) & 1)); return (overflow<>=1 */ carry = dest[2] & 1; dest[2] >>= 1; dest[2] |= (dest[1] & 1)<<23; dest[1] >>= 1; dest[1] |= (dest[0] & 1)<<23; dest[0] >>= 1; dest[0] |= (dest[0] & (1<<6))<<1; return (carry<>7) & 1; flg_d = (dest[0]>>7) & 1; /* Add source to dest: D = D+S */ dest[2] += source[2]; dest[1] += source[1]+((dest[2]>>24) & 1); dest[0] += source[0]+((dest[1]>>24) & 1); carry = (dest[0]>>8) & 1; dest[2] &= BITMASK(24); dest[1] &= BITMASK(24); dest[0] &= BITMASK(8); flg_r = (dest[0]>>7) & 1; /*set overflow*/ overflow = (flg_s ^ flg_r) & (flg_d ^ flg_r); return (overflow<>24) & 1); dest[0] -= source[0]+((dest[1]>>24) & 1); carry = (dest[0]>>8) & 1; dest[2] &= BITMASK(24); dest[1] &= BITMASK(24); dest[0] &= BITMASK(8); flg_s = (source[0]>>7) & 1; flg_d = (dest_save>>7) & 1; flg_r = (dest[0]>>7) & 1; /* set overflow */ overflow = (flg_s ^ flg_d) & (flg_r ^ flg_d); return (overflow<>12) & BITMASK(12))*(source2 & BITMASK(12)); /* bits 0-11 * bits 12-23 */ part[2]=(source1 & BITMASK(12))*((source2>>12) & BITMASK(12)); /* bits 12-23 * bits 12-23 */ part[3]=((source1>>12) & BITMASK(12))*((source2>>12) & BITMASK(12)); /* Calc dest 2 */ dest[2] = part[0]; dest[2] += (part[1] & BITMASK(12)) << 12; dest[2] += (part[2] & BITMASK(12)) << 12; /* Calc dest 1 */ dest[1] = (part[1]>>12) & BITMASK(12); dest[1] += (part[2]>>12) & BITMASK(12); dest[1] += part[3]; /* Calc dest 0 */ dest[0] = 0; /* Add carries */ value = (dest[2]>>24) & BITMASK(8); if (value) { dest[1] += value; dest[2] &= BITMASK(24); } value = (dest[1]>>24) & BITMASK(8); if (value) { dest[0] += value; dest[1] &= BITMASK(24); } /* Get rid of extra sign bit */ dsp_asl56(dest); if (signe) { zerodest[0] = zerodest[1] = zerodest[2] = 0; dsp_sub56(dest, zerodest); dest[0] = zerodest[0]; dest[1] = zerodest[1]; dest[2] = zerodest[2]; } } static void dsp_rnd56(uint32_t *dest) { uint32_t rnd_const[3]; rnd_const[0] = 0; /* Scaling mode S0 */ if (dsp_core.registers[DSP_REG_SR] & (1<>DSP_SR_C) & 1; dest[0] = dsp_core.registers[DSP_REG_A2]; dest[1] = dsp_core.registers[DSP_REG_A1]; dest[2] = dsp_core.registers[DSP_REG_A0]; source[2] = dsp_core.registers[DSP_REG_X0]; source[1] = dsp_core.registers[DSP_REG_X1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_add56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_add56(source, dest); } dsp_core.registers[DSP_REG_A2] = dest[0]; dsp_core.registers[DSP_REG_A1] = dest[1]; dsp_core.registers[DSP_REG_A0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>DSP_SR_C) & 1; dest[0] = dsp_core.registers[DSP_REG_B2]; dest[1] = dsp_core.registers[DSP_REG_B1]; dest[2] = dsp_core.registers[DSP_REG_B0]; source[2] = dsp_core.registers[DSP_REG_X0]; source[1] = dsp_core.registers[DSP_REG_X1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_add56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_add56(source, dest); } dsp_core.registers[DSP_REG_B2] = dest[0]; dsp_core.registers[DSP_REG_B1] = dest[1]; dsp_core.registers[DSP_REG_B0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>DSP_SR_C) & 1; dest[0] = dsp_core.registers[DSP_REG_A2]; dest[1] = dsp_core.registers[DSP_REG_A1]; dest[2] = dsp_core.registers[DSP_REG_A0]; source[2] = dsp_core.registers[DSP_REG_Y0]; source[1] = dsp_core.registers[DSP_REG_Y1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_add56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_add56(source, dest); } dsp_core.registers[DSP_REG_A2] = dest[0]; dsp_core.registers[DSP_REG_A1] = dest[1]; dsp_core.registers[DSP_REG_A0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>DSP_SR_C) & 1; dest[0] = dsp_core.registers[DSP_REG_B2]; dest[1] = dsp_core.registers[DSP_REG_B1]; dest[2] = dsp_core.registers[DSP_REG_B0]; source[2] = dsp_core.registers[DSP_REG_Y0]; source[1] = dsp_core.registers[DSP_REG_Y1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_add56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_add56(source, dest); } dsp_core.registers[DSP_REG_B2] = dest[0]; dsp_core.registers[DSP_REG_B1] = dest[1]; dsp_core.registers[DSP_REG_B0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1; dsp_core.registers[DSP_REG_A1] <<= 1; dsp_core.registers[DSP_REG_A1] &= BITMASK(24); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>23) & 1)<>23) & 1; dsp_core.registers[DSP_REG_B1] <<= 1; dsp_core.registers[DSP_REG_B1] &= BITMASK(24); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>23) & 1)<>= 1; dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>= 1; dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1)<>23) & 1; dsp_core.registers[DSP_REG_A1] <<= 1; dsp_core.registers[DSP_REG_A1] |= dsp_core.registers[DSP_REG_SR] & 1; dsp_core.registers[DSP_REG_A1] &= BITMASK(24); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>23) & 1)<>23) & 1; dsp_core.registers[DSP_REG_B1] <<= 1; dsp_core.registers[DSP_REG_B1] |= dsp_core.registers[DSP_REG_SR] & 1; dsp_core.registers[DSP_REG_B1] &= BITMASK(24); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>23) & 1)<>= 1; dsp_core.registers[DSP_REG_A1] |= (dsp_core.registers[DSP_REG_SR] & 1)<<23; dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>= 1; dsp_core.registers[DSP_REG_B1] |= (dsp_core.registers[DSP_REG_SR] & 1)<<23; dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>(DSP_SR_C)) & 1; dest[2] = dsp_core.registers[DSP_REG_A0]; dest[1] = dsp_core.registers[DSP_REG_A1]; dest[0] = dsp_core.registers[DSP_REG_A2]; source[2] = dsp_core.registers[DSP_REG_X0]; source[1] = dsp_core.registers[DSP_REG_X1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_sub56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_sub56(source, dest); } dsp_core.registers[DSP_REG_A2] = dest[0]; dsp_core.registers[DSP_REG_A1] = dest[1]; dsp_core.registers[DSP_REG_A0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>(DSP_SR_C)) & 1; dest[2] = dsp_core.registers[DSP_REG_B0]; dest[1] = dsp_core.registers[DSP_REG_B1]; dest[0] = dsp_core.registers[DSP_REG_B2]; source[2] = dsp_core.registers[DSP_REG_X0]; source[1] = dsp_core.registers[DSP_REG_X1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_sub56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_sub56(source, dest); } dsp_core.registers[DSP_REG_B2] = dest[0]; dsp_core.registers[DSP_REG_B1] = dest[1]; dsp_core.registers[DSP_REG_B0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>(DSP_SR_C)) & 1; dest[2] = dsp_core.registers[DSP_REG_A0]; dest[1] = dsp_core.registers[DSP_REG_A1]; dest[0] = dsp_core.registers[DSP_REG_A2]; source[2] = dsp_core.registers[DSP_REG_Y0]; source[1] = dsp_core.registers[DSP_REG_Y1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_sub56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_sub56(source, dest); } dsp_core.registers[DSP_REG_A2] = dest[0]; dsp_core.registers[DSP_REG_A1] = dest[1]; dsp_core.registers[DSP_REG_A0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<>(DSP_SR_C)) & 1; dest[2] = dsp_core.registers[DSP_REG_B0]; dest[1] = dsp_core.registers[DSP_REG_B1]; dest[0] = dsp_core.registers[DSP_REG_B2]; source[2] = dsp_core.registers[DSP_REG_Y0]; source[1] = dsp_core.registers[DSP_REG_Y1]; source[0] = source[1] & (1<<23) ? 0xff : 0x0; newsr = dsp_sub56(source, dest); if (curcarry) { source[0]=0; source[1]=0; source[2]=1; newsr |= dsp_sub56(source, dest); } dsp_core.registers[DSP_REG_B2] = dest[0]; dsp_core.registers[DSP_REG_B1] = dest[1]; dsp_core.registers[DSP_REG_B0] = dest[2]; dsp_ccr_update_e_u_n_z(dest[0], dest[1], dest[2]); dsp_core.registers[DSP_REG_SR] &= BITMASK(16)-((1<. */ #ifndef DSP_CPU_H #define DSP_CPU_H #ifdef __cplusplus extern "C" { #endif /* Defines */ #define BITMASK(x) ((1<<(x))-1) #define DSP_OMR_MA 0x00 #define DSP_OMR_MB 0x01 #define DSP_OMR_DE 0x02 #define DSP_OMR_SD 0x06 #define DSP_OMR_EA 0x07 #define DSP_SR_C 0x00 #define DSP_SR_V 0x01 #define DSP_SR_Z 0x02 #define DSP_SR_N 0x03 #define DSP_SR_U 0x04 #define DSP_SR_E 0x05 #define DSP_SR_L 0x06 #define DSP_SR_I0 0x08 #define DSP_SR_I1 0x09 #define DSP_SR_S0 0x0a #define DSP_SR_S1 0x0b #define DSP_SR_T 0x0d #define DSP_SR_LF 0x0f #define DSP_SP_SE 0x04 #define DSP_SP_UF 0x05 /* Registers numbers in dsp.registers[] */ #define DSP_REG_X0 0x04 #define DSP_REG_X1 0x05 #define DSP_REG_Y0 0x06 #define DSP_REG_Y1 0x07 #define DSP_REG_A0 0x08 #define DSP_REG_B0 0x09 #define DSP_REG_A2 0x0a #define DSP_REG_B2 0x0b #define DSP_REG_A1 0x0c #define DSP_REG_B1 0x0d #define DSP_REG_A 0x0e #define DSP_REG_B 0x0f #define DSP_REG_R0 0x10 #define DSP_REG_R1 0x11 #define DSP_REG_R2 0x12 #define DSP_REG_R3 0x13 #define DSP_REG_R4 0x14 #define DSP_REG_R5 0x15 #define DSP_REG_R6 0x16 #define DSP_REG_R7 0x17 #define DSP_REG_N0 0x18 #define DSP_REG_N1 0x19 #define DSP_REG_N2 0x1a #define DSP_REG_N3 0x1b #define DSP_REG_N4 0x1c #define DSP_REG_N5 0x1d #define DSP_REG_N6 0x1e #define DSP_REG_N7 0x1f #define DSP_REG_M0 0x20 #define DSP_REG_M1 0x21 #define DSP_REG_M2 0x22 #define DSP_REG_M3 0x23 #define DSP_REG_M4 0x24 #define DSP_REG_M5 0x25 #define DSP_REG_M6 0x26 #define DSP_REG_M7 0x27 #define DSP_REG_SR 0x39 #define DSP_REG_OMR 0x3a #define DSP_REG_SP 0x3b #define DSP_REG_SSH 0x3c #define DSP_REG_SSL 0x3d #define DSP_REG_LA 0x3e #define DSP_REG_LC 0x3f #define DSP_REG_NULL 0x00 #define DSP_REG_LCSAVE 0x30 /* Memory spaces for dsp.ram[], dsp.rom[] */ #define DSP_SPACE_X 0x00 #define DSP_SPACE_Y 0x01 #define DSP_SPACE_P 0x02 /* Functions */ extern void dsp56k_init_cpu(void); /* Set dsp_core to use */ extern void dsp56k_execute_instruction(void); /* Execute 1 instruction */ extern uint16_t dsp56k_execute_one_disasm_instruction(FILE *out, uint16_t pc); /* Execute 1 instruction in disasm mode */ /* Interrupt relative functions */ void dsp_set_interrupt(uint32_t intr, uint32_t set); void dsp_set_interrupt_mask(uint32_t intr, uint32_t set); #ifdef __cplusplus } #endif #endif /* DSP_CPU_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp_disasm.c000066400000000000000000001707431504763705000251000ustar00rootroot00000000000000/* DSP M56001 emulation Disassembler (C) 2003-2008 ARAnyM developer team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "dsp_core.h" #include "dsp_cpu.h" #include "dsp_disasm.h" #include "profile.h" /* More disasm infos, if wanted */ #define DSP_DISASM_REG_PC 0 /********************************** * Defines **********************************/ /********************************** * Variables **********************************/ /* Current instruction */ static uint32_t cur_inst; static uint16_t disasm_cur_inst_len; static char str_instr[90]; static char str_instr2[120]; static char parallelmove_name[64]; /* Previous instruction */ static uint32_t prev_inst_pc; static bool isLooping; /* Used to display dc instead of unknown instruction for illegal opcodes */ static bool isInDisasmMode; void dsp56k_disasm_init(void) { prev_inst_pc = 0x10000; /* Init to an invalid value */ isLooping = false; isInDisasmMode = false; } /********************************** * Register change **********************************/ static uint32_t registers_save[64]; #if DSP_DISASM_REG_PC static uint32_t pc_save; #endif static const char *registers_name[64]={ "","","","", "x0","x1","y0","y1", "a0","b0","a2","b2", "a1","b1","a","b", "r0","r1","r2","r3", "r4","r5","r6","r7", "n0","n1","n2","n3", "n4","n5","n6","n7", "m0","m1","m2","m3", "m4","m5","m6","m7", "","","","", "","","","", "","","","", "","","","", "","sr","omr","sp", "ssh","ssl","la","lc" }; /********************************** * Opcode disassembler **********************************/ static uint32_t read_memory(uint32_t currPc); typedef void (*dsp_emul_t)(void); static void opcode8h_0(void); static int dsp_calc_ea(uint32_t ea_mode, char *dest, unsigned dest_size); static void dsp_calc_cc(uint32_t cc_mode, char *dest); static void dsp_undefined(void); /* Instructions without parallel moves */ static void dsp_andi(void); static void dsp_bchg_aa(void); static void dsp_bchg_ea(void); static void dsp_bchg_pp(void); static void dsp_bchg_reg(void); static void dsp_bclr_aa(void); static void dsp_bclr_ea(void); static void dsp_bclr_pp(void); static void dsp_bclr_reg(void); static void dsp_bset_aa(void); static void dsp_bset_ea(void); static void dsp_bset_pp(void); static void dsp_bset_reg(void); static void dsp_btst_aa(void); static void dsp_btst_ea(void); static void dsp_btst_pp(void); static void dsp_btst_reg(void); static void dsp_div(void); static void dsp_enddo(void); static void dsp_illegal(void); static void dsp_jcc_imm(void); static void dsp_jcc_ea(void); static void dsp_jclr_aa(void); static void dsp_jclr_ea(void); static void dsp_jclr_pp(void); static void dsp_jclr_reg(void); static void dsp_jmp_ea(void); static void dsp_jmp_imm(void); static void dsp_jscc_ea(void); static void dsp_jscc_imm(void); static void dsp_jsclr_aa(void); static void dsp_jsclr_ea(void); static void dsp_jsclr_pp(void); static void dsp_jsclr_reg(void); static void dsp_jset_aa(void); static void dsp_jset_ea(void); static void dsp_jset_pp(void); static void dsp_jset_reg(void); static void dsp_jsr_ea(void); static void dsp_jsr_imm(void); static void dsp_jsset_aa(void); static void dsp_jsset_ea(void); static void dsp_jsset_pp(void); static void dsp_jsset_reg(void); static void dsp_lua(void); static void dsp_movem_ea(void); static void dsp_movem_aa(void); static void dsp_nop(void); static void dsp_norm(void); static void dsp_ori(void); static void dsp_reset(void); static void dsp_rti(void); static void dsp_rts(void); static void dsp_stop(void); static void dsp_swi(void); static void dsp_tcc(void); static void dsp_wait(void); static void dsp_do_ea(void); static void dsp_do_aa(void); static void dsp_do_imm(void); static void dsp_do_reg(void); static void dsp_rep_aa(void); static void dsp_rep_ea(void); static void dsp_rep_imm(void); static void dsp_rep_reg(void); static void dsp_movec_aa(void); static void dsp_movec_ea(void); static void dsp_movec_imm(void); static void dsp_movec_reg(void); static void dsp_movep_0(void); static void dsp_movep_1(void); static void dsp_movep_23(void); /* Parallel moves */ static void dsp_pm_class2(void); static void dsp_pm(void); static void dsp_pm_0(void); static void dsp_pm_1(void); static void dsp_pm_2(void); static void dsp_pm_4(void); static void dsp_pm_8(void); static const dsp_emul_t opcodes8h[512] = { /* 0x00 - 0x3f */ opcode8h_0, dsp_undefined, dsp_undefined, dsp_undefined, opcode8h_0, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_andi, dsp_undefined, dsp_ori, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_div, dsp_div, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_norm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, /* 0x40 - 0x7f */ dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_tcc, dsp_tcc, dsp_tcc, dsp_tcc, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, /* 0x80 - 0xbf */ dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_lua, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_reg, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_reg, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_aa, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_ea, dsp_undefined, dsp_movec_imm, dsp_undefined, dsp_undefined, /* 0xc0 - 0xff */ dsp_do_aa, dsp_rep_aa, dsp_do_aa, dsp_rep_aa, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_do_ea, dsp_rep_ea, dsp_do_ea, dsp_rep_ea, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_do_reg, dsp_rep_reg, dsp_undefined, dsp_undefined, dsp_do_imm, dsp_rep_imm, dsp_undefined, dsp_undefined, dsp_movem_aa, dsp_movem_aa, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movem_ea, dsp_movem_ea, dsp_undefined, dsp_undefined, dsp_movem_aa, dsp_movem_aa, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_movem_ea, dsp_movem_ea, dsp_undefined, dsp_undefined, /* 0x100 - 0x13f */ dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_pm_class2, dsp_movep_0, dsp_movep_0, dsp_movep_1, dsp_movep_1, dsp_movep_23, dsp_movep_23, dsp_movep_23, dsp_movep_23, /* 0x140 - 0x17f */ dsp_bclr_aa, dsp_bset_aa, dsp_bclr_aa, dsp_bset_aa, dsp_jclr_aa, dsp_jset_aa, dsp_jclr_aa, dsp_jset_aa, dsp_bclr_ea, dsp_bset_ea, dsp_bclr_ea, dsp_bset_ea, dsp_jclr_ea, dsp_jset_ea, dsp_jclr_ea, dsp_jset_ea, dsp_bclr_pp, dsp_bset_pp, dsp_bclr_pp, dsp_bset_pp, dsp_jclr_pp, dsp_jset_pp, dsp_jclr_pp, dsp_jset_pp, dsp_jclr_reg, dsp_jset_reg, dsp_bclr_reg, dsp_bset_reg, dsp_jmp_ea, dsp_jcc_ea, dsp_undefined, dsp_undefined, dsp_bchg_aa, dsp_btst_aa, dsp_bchg_aa, dsp_btst_aa, dsp_jsclr_aa, dsp_jsset_aa, dsp_jsclr_aa, dsp_jsset_aa, dsp_bchg_ea, dsp_btst_ea, dsp_bchg_ea, dsp_btst_ea, dsp_jsclr_ea, dsp_jsset_ea, dsp_jsclr_ea, dsp_jsset_ea, dsp_bchg_pp, dsp_btst_pp, dsp_bchg_pp, dsp_btst_pp, dsp_jsclr_pp, dsp_jsset_pp, dsp_jsclr_pp, dsp_jsset_pp, dsp_jsclr_reg, dsp_jsset_reg, dsp_bchg_reg, dsp_btst_reg, dsp_jsr_ea, dsp_jscc_ea, dsp_undefined, dsp_undefined, /* 0x180 - 0x1bf */ dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_jmp_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_jsr_imm, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, dsp_undefined, /* 0x1c0 - 0x1ff */ dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jcc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, dsp_jscc_imm, }; static const char* opcodes_alu[256] = { /* 0x00 - 0x3f */ "move" , "tfr b,a", "addr b,a", "tst a", "undefined", "cmp b,a" , "subr b,a", "cmpm b,a", "undefined", "tfr a,b", "addr a,b", "tst b", "undefined", "cmp a,b" , "subr a,b", "cmpm a,b", "add b,a" , "rnd a" , "addl b,a", "clr a", "sub b,a" , "undefined", "subl b,a", "not a", "add a,b" , "rnd b" , "addl a,b", "clr b", "sub a,b" , "undefined", "subl a,b", "not b", "add x,a" , "adc x,a", "asr a" , "lsr a", "sub x,a" , "sbc x,a" , "abs a" , "ror a", "add x,b" , "adc x,b", "asr b" , "lsr b", "sub x,b" , "sbc x,b" , "abs b" , "ror b", "add y,a" , "adc y,a", "asl a" , "lsl a", "sub y,a" , "sbc y,a" , "neg a" , "rol a", "add y,b" , "adc y,b", "asl b" , "lsl b", "sub y,b" , "sbc y,b" , "neg b" , "rol b", /* 0x40 - 0x7f */ "add x0,a", "tfr x0,a", "or x0,a", "eor x0,a", "sub x0,a", "cmp x0,a", "and x0,a", "cmpm x0,a", "add x0,b", "tfr x0,b", "or x0,b", "eor x0,b", "sub x0,b", "cmp x0,b", "and x0,b", "cmpm x0,b", "add y0,a", "tfr y0,a", "or y0,a", "eor y0,a", "sub y0,a", "cmp y0,a", "and y0,a", "cmpm y0,a", "add y0,b", "tfr y0,b", "or y0,b", "eor y0,b", "sub y0,b", "cmp y0,b", "and y0,b", "cmpm y0,b", "add x1,a", "tfr x1,a", "or x1,a", "eor x1,a", "sub x1,a", "cmp x1,a", "and x1,a", "cmpm x1,a", "add x1,b", "tfr x1,b", "or x1,b", "eor x1,b", "sub x1,b", "cmp x1,b", "and x1,b", "cmpm x1,b", "add y1,a", "tfr y1,a", "or y1,a", "eor y1,a", "sub y1,a", "cmp y1,a", "and y1,a", "cmpm y1,a", "add y1,b", "tfr y1,b", "or y1,b", "eor y1,b", "sub y1,b", "cmp y1,b", "and y1,b", "cmpm y1,b", /* 0x80 - 0xbf */ "mpy +x0,x0,a", "mpyr +x0,x0,a", "mac +x0,x0,a", "macr +x0,x0,a", "mpy -x0,x0,a", "mpyr -x0,x0,a", "mac -x0,x0,a", "macr -x0,x0,a", "mpy +x0,x0,b", "mpyr +x0,x0,b", "mac +x0,x0,b", "macr +x0,x0,b", "mpy -x0,x0,b", "mpyr -x0,x0,b", "mac -x0,x0,b", "macr -x0,x0,b", "mpy +y0,y0,a", "mpyr +y0,y0,a", "mac +y0,y0,a", "macr +y0,y0,a", "mpy -y0,y0,a", "mpyr -y0,y0,a", "mac -y0,y0,a", "macr -y0,y0,a", "mpy +y0,y0,b", "mpyr +y0,y0,b", "mac +y0,y0,b", "macr +y0,y0,b", "mpy -y0,y0,b", "mpyr -y0,y0,b", "mac -y0,y0,b", "macr -y0,y0,b", "mpy +x1,x0,a", "mpyr +x1,x0,a", "mac +x1,x0,a", "macr +x1,x0,a", "mpy -x1,x0,a", "mpyr -x1,x0,a", "mac -x1,x0,a", "macr -x1,x0,a", "mpy +x1,x0,b", "mpyr +x1,x0,b", "mac +x1,x0,b", "macr +x1,x0,b", "mpy -x1,x0,b", "mpyr -x1,x0,b", "mac -x1,x0,b", "macr -x1,x0,b", "mpy +y1,y0,a", "mpyr +y1,y0,a", "mac +y1,y0,a", "macr +y1,y0,a", "mpy -y1,y0,a", "mpyr -y1,y0,a", "mac -y1,y0,a", "macr -y1,y0,a", "mpy +y1,y0,b", "mpyr +y1,y0,b", "mac +y1,y0,b", "macr +y1,y0,b", "mpy -y1,y0,b", "mpyr -y1,y0,b", "mac -y1,y0,b", "macr -y1,y0,b", /* 0xc0 - 0xff */ "mpy +x0,y1,a", "mpyr +x0,y1,a", "mac +x0,y1,a", "macr +x0,y1,a", "mpy -x0,y1,a", "mpyr -x0,y1,a", "mac -x0,y1,a", "macr -x0,y1,a", "mpy +x0,y1,b", "mpyr +x0,y1,b", "mac +x0,y1,b", "macr +x0,y1,b", "mpy -x0,y1,b", "mpyr -x0,y1,b", "mac -x0,y1,b", "macr -x0,y1,b", "mpy +y0,x0,a", "mpyr +y0,x0,a", "mac +y0,x0,a", "macr +y0,x0,a", "mpy -y0,x0,a", "mpyr -y0,x0,a", "mac -y0,x0,a", "macr -y0,x0,a", "mpy +y0,x0,b", "mpyr +y0,x0,b", "mac +y0,x0,b", "macr +y0,x0,b", "mpy -y0,x0,b", "mpyr -y0,x0,b", "mac -y0,x0,b", "macr -y0,x0,b", "mpy +x1,y0,a", "mpyr +x1,y0,a", "mac +x1,y0,a", "macr +x1,y0,a", "mpy -x1,y0,a", "mpyr -x1,y0,a", "mac -x1,y0,a", "macr -x1,y0,a", "mpy +x1,y0,b", "mpyr +x1,y0,b", "mac +x1,y0,b", "macr +x1,y0,b", "mpy -x1,y0,b", "mpyr -x1,y0,b", "mac -x1,y0,b", "macr -x1,y0,b", "mpy +y1,x1,a", "mpyr +y1,x1,a", "mac +y1,x1,a", "macr +y1,x1,a", "mpy -y1,x1,a", "mpyr -y1,x1,a", "mac -y1,x1,a", "macr -y1,x1,a", "mpy +y1,x1,b", "mpyr +y1,x1,b", "mac +y1,x1,b", "macr +y1,x1,b", "mpy -y1,x1,b", "mpyr -y1,x1,b", "mac -y1,x1,b", "macr -y1,x1,b" }; static const dsp_emul_t opcodes_parmove[16] = { dsp_pm_0, dsp_pm_1, dsp_pm_2, dsp_pm_2, dsp_pm_4, dsp_pm_4, dsp_pm_4, dsp_pm_4, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8, dsp_pm_8 }; static const int registers_tcc[16][2] = { {DSP_REG_B,DSP_REG_A}, {DSP_REG_A,DSP_REG_B}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_NULL,DSP_REG_NULL}, {DSP_REG_X0,DSP_REG_A}, {DSP_REG_X0,DSP_REG_B}, {DSP_REG_Y0,DSP_REG_A}, {DSP_REG_Y0,DSP_REG_B}, {DSP_REG_X1,DSP_REG_A}, {DSP_REG_X1,DSP_REG_B}, {DSP_REG_Y1,DSP_REG_A}, {DSP_REG_Y1,DSP_REG_B} }; static const char *registers_lmove[8] = { "a10", "b10", "x", "y", "a", "b", "ab", "ba" }; static const char *ea_names[9] = { "(r%d)-n%d", /* 000xxx */ "(r%d)+n%d", /* 001xxx */ "(r%d)-", /* 010xxx */ "(r%d)+", /* 011xxx */ "(r%d)", /* 100xxx */ "(r%d+n%d)", /* 101xxx */ "$%04x", /* 110000 */ "-(r%d)", /* 111xxx */ "$%06x" /* 110100 */ }; static const char *cc_name[16] = { "cc", "ge", "ne", "pl", "nn", "ec", "lc", "gt", "cs", "lt", "eq", "mi", "nr", "es", "ls", "le" }; void dsp56k_disasm_reg_save(void) { memcpy(registers_save, dsp_core.registers , sizeof(registers_save)); #if DSP_DISASM_REG_PC pc_save = dsp_core.pc; #endif } void dsp56k_disasm_reg_compare(FILE *fp) { int i; bool bRegA = false; bool bRegB = false; for (i=4; i<64; i++) { if (registers_save[i] == dsp_core.registers[i]) { continue; } switch(i) { case DSP_REG_X0: case DSP_REG_X1: case DSP_REG_Y0: case DSP_REG_Y1: fprintf(fp, "\tReg: %s $%06x -> $%06x\n", registers_name[i], registers_save[i], dsp_core.registers[i]); break; case DSP_REG_R0: case DSP_REG_R1: case DSP_REG_R2: case DSP_REG_R3: case DSP_REG_R4: case DSP_REG_R5: case DSP_REG_R6: case DSP_REG_R7: case DSP_REG_M0: case DSP_REG_M1: case DSP_REG_M2: case DSP_REG_M3: case DSP_REG_M4: case DSP_REG_M5: case DSP_REG_M6: case DSP_REG_M7: case DSP_REG_N0: case DSP_REG_N1: case DSP_REG_N2: case DSP_REG_N3: case DSP_REG_N4: case DSP_REG_N5: case DSP_REG_N6: case DSP_REG_N7: case DSP_REG_SR: case DSP_REG_LA: case DSP_REG_LC: fprintf(fp, "\tReg: %s $%04x -> $%04x\n", registers_name[i], registers_save[i], dsp_core.registers[i]); break; case DSP_REG_OMR: case DSP_REG_SP: case DSP_REG_SSH: case DSP_REG_SSL: fprintf(fp, "\tReg: %s $%02x -> $%02x\n", registers_name[i], registers_save[i], dsp_core.registers[i]); break; case DSP_REG_A0: case DSP_REG_A1: case DSP_REG_A2: if (bRegA == false) { fprintf(fp, "\tReg: a $%02x:%06x:%06x -> $%02x:%06x:%06x\n", registers_save[DSP_REG_A2], registers_save[DSP_REG_A1], registers_save[DSP_REG_A0], dsp_core.registers[DSP_REG_A2], dsp_core.registers[DSP_REG_A1], dsp_core.registers[DSP_REG_A0] ); bRegA = true; } break; case DSP_REG_B0: case DSP_REG_B1: case DSP_REG_B2: if (bRegB == false) { fprintf(fp, "\tReg: b $%02x:%06x:%06x -> $%02x:%06x:%06x\n", registers_save[DSP_REG_B2], registers_save[DSP_REG_B1], registers_save[DSP_REG_B0], dsp_core.registers[DSP_REG_B2], dsp_core.registers[DSP_REG_B1], dsp_core.registers[DSP_REG_B0] ); bRegB = true; } break; } } #if DSP_DISASM_REG_PC if (pc_save != dsp_core.pc) { fprintf(fp, "\tReg: pc $%04x -> $%04x\n", pc_save, dsp_core.pc); } #endif } uint16_t dsp56k_disasm(dsp_trace_disasm_t mode, FILE *fp) { uint32_t value; if (mode == DSP_TRACE_MODE) { isInDisasmMode = false; if (prev_inst_pc == dsp_core.pc) { if (!isLooping) { fprintf(fp, "Looping on DSP instruction at PC = $%04x\n", prev_inst_pc); isLooping = true; } return 0; } } else { isInDisasmMode = true; } prev_inst_pc = dsp_core.pc; isLooping = false; cur_inst = read_memory(dsp_core.pc); disasm_cur_inst_len = 1; strcpy(parallelmove_name, ""); if (cur_inst < 0x100000) { value = (cur_inst >> 11) & (BITMASK(6) << 3); value += (cur_inst >> 5) & BITMASK(3); opcodes8h[value](); } else { dsp_pm(); snprintf(str_instr, sizeof(str_instr), "%-16s %s", opcodes_alu[cur_inst & BITMASK(8)], parallelmove_name); } return disasm_cur_inst_len; } /** * dsp56k_getInstrText : return the disasembled instructions */ const char* dsp56k_getInstructionText(void) { const int len = 50; uint64_t count, cycles; uint16_t cycle_diff; float percentage; int offset; if (isLooping) { *str_instr2 = 0; } if (disasm_cur_inst_len == 1) { offset = snprintf(str_instr2, sizeof(str_instr2), "p:%04x %06x (%02d cyc) %-*s\n", prev_inst_pc, cur_inst, dsp_core.instr_cycle, len, str_instr); } else { offset = snprintf(str_instr2, sizeof(str_instr2), "p:%04x %06x %06x (%02d cyc) %-*s\n", prev_inst_pc, cur_inst, read_memory(prev_inst_pc + 1), dsp_core.instr_cycle, len, str_instr); } if (offset > 2 && Profile_DspAddressData(prev_inst_pc, &percentage, &count, &cycles, &cycle_diff)) { offset -= 2; snprintf(str_instr2+offset, sizeof(str_instr2) - offset, "%5.2f%% (%"PRId64", %"PRId64", %d)\n", percentage, count, cycles, cycle_diff); } return str_instr2; } static void dsp_pm_class2(void) { dsp_pm(); snprintf(str_instr, sizeof(str_instr), "%-16s %s", opcodes_alu[cur_inst & BITMASK(8)], parallelmove_name); } static uint32_t read_memory(uint32_t currPc) { uint32_t value; if (currPc<0x200) { value = dsp_core.ramint[DSP_SPACE_P][currPc]; } else { value = dsp_core.ramext[currPc & (DSP_RAMSIZE-1)]; } return value & BITMASK(24); } /********************************** * Conditions code calculation **********************************/ static void dsp_calc_cc(uint32_t cc_mode, char *dest) { strcpy(dest, cc_name[cc_mode & BITMASK(4)]); } /********************************** * Effective address calculation **********************************/ static int dsp_calc_ea(uint32_t ea_mode, char *dest, unsigned dest_size) { int value, retour, numreg; value = (ea_mode >> 3) & BITMASK(3); numreg = ea_mode & BITMASK(3); retour = 0; switch (value) { case 0: /* (Rx)-Nx */ snprintf(dest, dest_size, ea_names[value], numreg, numreg); break; case 1: /* (Rx)+Nx */ snprintf(dest, dest_size, ea_names[value], numreg, numreg); break; case 5: /* (Rx+Nx) */ snprintf(dest, dest_size, ea_names[value], numreg, numreg); break; case 2: /* (Rx)- */ snprintf(dest, dest_size, ea_names[value], numreg); break; case 3: /* (Rx)+ */ snprintf(dest, dest_size, ea_names[value], numreg); break; case 4: /* (Rx) */ snprintf(dest, dest_size, ea_names[value], numreg); break; case 7: /* -(Rx) */ snprintf(dest, dest_size, ea_names[value], numreg); break; case 6: disasm_cur_inst_len++; switch ((ea_mode >> 2) & 1) { case 0: /* Absolute address */ snprintf(dest, dest_size, ea_names[value], read_memory(dsp_core.pc+1)); break; case 1: /* Immediate value */ snprintf(dest, dest_size, ea_names[8], read_memory(dsp_core.pc+1)); retour = 1; break; } break; } return retour; } static void opcode8h_0(void) { switch(cur_inst) { case 0x000000: dsp_nop(); break; case 0x000004: dsp_rti(); break; case 0x000005: dsp_illegal(); break; case 0x000006: dsp_swi(); break; case 0x00000c: dsp_rts(); break; case 0x000084: dsp_reset(); break; case 0x000086: dsp_wait(); break; case 0x000087: dsp_stop(); break; case 0x00008c: dsp_enddo(); break; default: dsp_undefined(); break; } } /********************************** * Non-parallel moves instructions **********************************/ static void dsp_undefined(void) { /* In Disasm mode, display dc instruction_opcode */ if (isInDisasmMode) snprintf(str_instr, sizeof(str_instr), "dc $%06x", cur_inst); /* In trace mode, display unknown instruction */ else snprintf(str_instr, sizeof(str_instr), "$%06x unknown instruction", cur_inst); } static void dsp_andi(void) { switch(cur_inst & BITMASK(2)) { case 0: snprintf(str_instr, sizeof(str_instr), "andi #$%02x,mr", (cur_inst>>8) & BITMASK(8)); break; case 1: snprintf(str_instr, sizeof(str_instr), "andi #$%02x,ccr", (cur_inst>>8) & BITMASK(8)); break; case 2: snprintf(str_instr, sizeof(str_instr), "andi #$%02x,omr", (cur_inst>>8) & BITMASK(8)); break; default: break; } } static void dsp_bchg_aa(void) { /* bchg #n,x:aa */ /* bchg #n,y:aa */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value); } else { snprintf(name, sizeof(name), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "bchg #%d,%s", numbit, name); } static void dsp_bchg_ea(void) { /* bchg #n,x:ea */ /* bchg #n,y:ea */ char name[18], addr_name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(name, sizeof(name), "y:%.12s", addr_name); } else { snprintf(name, sizeof(name), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "bchg #%d,%s", numbit, name); } static void dsp_bchg_pp(void) { /* bchg #n,x:pp */ /* bchg #n,y:pp */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value+0xffc0); } else { snprintf(name, sizeof(name), "x:$%04x", value+0xffc0); } snprintf(str_instr, sizeof(str_instr), "bchg #%d,%s", numbit, name); } static void dsp_bchg_reg(void) { /* bchg #n,R */ uint32_t value, numbit; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "bchg #%d,%s", numbit, registers_name[value]); } static void dsp_bclr_aa(void) { /* bclr #n,x:aa */ /* bclr #n,y:aa */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value); } else { snprintf(name, sizeof(name), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "bclr #%d,%s", numbit, name); } static void dsp_bclr_ea(void) { /* bclr #n,x:ea */ /* bclr #n,y:ea */ char name[18], addr_name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(name, sizeof(name), "y:%.12s", addr_name); } else { snprintf(name, sizeof(name), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "bclr #%d,%s", numbit, name); } static void dsp_bclr_pp(void) { /* bclr #n,x:pp */ /* bclr #n,y:pp */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value+0xffc0); } else { snprintf(name, sizeof(name), "x:$%04x", value+0xffc0); } snprintf(str_instr, sizeof(str_instr), "bclr #%d,%s", numbit, name); } static void dsp_bclr_reg(void) { /* bclr #n,R */ uint32_t value, numbit; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "bclr #%d,%s", numbit, registers_name[value]); } static void dsp_bset_aa(void) { /* bset #n,x:aa */ /* bset #n,y:aa */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value); } else { snprintf(name, sizeof(name), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "bset #%d,%s", numbit, name); } static void dsp_bset_ea(void) { /* bset #n,x:ea */ /* bset #n,y:ea */ char name[18], addr_name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(name, sizeof(name), "y:%.12s", addr_name); } else { snprintf(name, sizeof(name), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "bset #%d,%s", numbit, name); } static void dsp_bset_pp(void) { /* bset #n,x:pp */ /* bset #n,y:pp */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value+0xffc0); } else { snprintf(name, sizeof(name), "x:$%04x", value+0xffc0); } snprintf(str_instr, sizeof(str_instr), "bset #%d,%s", numbit, name); } static void dsp_bset_reg(void) { /* bset #n,R */ uint32_t value, numbit; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "bset #%d,%s", numbit, registers_name[value]); } static void dsp_btst_aa(void) { /* btst #n,x:aa */ /* btst #n,y:aa */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value); } else { snprintf(name, sizeof(name), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "btst #%d,%s", numbit, name); } static void dsp_btst_ea(void) { /* btst #n,x:ea */ /* btst #n,y:ea */ char name[18], addr_name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(name, sizeof(name), "y:%.12s", addr_name); } else { snprintf(name, sizeof(name), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "btst #%d,%s", numbit, name); } static void dsp_btst_pp(void) { /* btst #n,x:pp */ /* btst #n,y:pp */ char name[16]; uint32_t memspace, value, numbit; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(name, sizeof(name), "y:$%04x", value+0xffc0); } else { snprintf(name, sizeof(name), "x:$%04x", value+0xffc0); } snprintf(str_instr, sizeof(str_instr), "btst #%d,%s", numbit, name); } static void dsp_btst_reg(void) { /* btst #n,R */ uint32_t value, numbit; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "btst #%d,%s", numbit, registers_name[value]); } static void dsp_div(void) { uint32_t srcreg=DSP_REG_NULL, destreg; switch((cur_inst>>4) & BITMASK(2)) { case 0: srcreg = DSP_REG_X0; break; case 1: srcreg = DSP_REG_Y0; break; case 2: srcreg = DSP_REG_X1; break; case 3: srcreg = DSP_REG_Y1; break; } destreg = DSP_REG_A+((cur_inst>>3) & 1); snprintf(str_instr, sizeof(str_instr), "div %s,%s", registers_name[srcreg], registers_name[destreg]); } static void dsp_do_aa(void) { char name[16]; disasm_cur_inst_len++; if (cur_inst & (1<<6)) { snprintf(name, sizeof(name), "y:$%04x", (cur_inst>>8) & BITMASK(6)); } else { snprintf(name, sizeof(name), "x:$%04x", (cur_inst>>8) & BITMASK(6)); } snprintf(str_instr, sizeof(str_instr), "do %s,p:$%04x", name, read_memory(dsp_core.pc+1) ); } static void dsp_do_imm(void) { disasm_cur_inst_len++; snprintf(str_instr, sizeof(str_instr), "do #$%04x,p:$%04x", ((cur_inst>>8) & BITMASK(8))|((cur_inst & BITMASK(4))<<8), read_memory(dsp_core.pc+1) ); } static void dsp_do_ea(void) { char addr_name[16], name[18]; uint32_t ea_mode; disasm_cur_inst_len++; ea_mode = (cur_inst>>8) & BITMASK(6); dsp_calc_ea(ea_mode, addr_name, sizeof(addr_name)); if (cur_inst & (1<<6)) { snprintf(name, sizeof(name), "y:%.12s", addr_name); } else { snprintf(name, sizeof(name), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "do %s,p:$%04x", name, read_memory(dsp_core.pc+1) ); } static void dsp_do_reg(void) { disasm_cur_inst_len++; snprintf(str_instr, sizeof(str_instr), "do %s,p:$%04x", registers_name[(cur_inst>>8) & BITMASK(6)], read_memory(dsp_core.pc+1) ); } static void dsp_enddo(void) { snprintf(str_instr, sizeof(str_instr), "enddo"); } static void dsp_illegal(void) { snprintf(str_instr, sizeof(str_instr), "illegal"); } static void dsp_jcc_ea(void) { char cond_name[16], addr_name[16]; uint32_t cc_code=0; dsp_calc_ea((cur_inst >>8) & BITMASK(6), addr_name, sizeof(addr_name)); cc_code=cur_inst & BITMASK(4); dsp_calc_cc(cc_code, cond_name); snprintf(str_instr, sizeof(str_instr), "j%s p:%s", cond_name, addr_name); } static void dsp_jcc_imm(void) { char cond_name[16], addr_name[16]; uint32_t cc_code=0; snprintf(addr_name, sizeof(addr_name), "$%04x", cur_inst & BITMASK(12)); cc_code=(cur_inst>>12) & BITMASK(4); dsp_calc_cc(cc_code, cond_name); snprintf(str_instr, sizeof(str_instr), "j%s p:%s", cond_name, addr_name); } static void dsp_jclr_aa(void) { /* jclr #n,x:aa,p:xx */ /* jclr #n,y:aa,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jclr #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jclr_ea(void) { /* jclr #n,x:ea,p:xx */ /* jclr #n,y:ea,p:xx */ char srcname[18], addr_name[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(srcname, sizeof(srcname), "y:%.12s", addr_name); } else { snprintf(srcname, sizeof(srcname), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "jclr #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jclr_pp(void) { /* jclr #n,x:pp,p:xx */ /* jclr #n,y:pp,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value += 0xffc0; if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jclr #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jclr_reg(void) { /* jclr #n,R,p:xx */ uint32_t value, numbit; disasm_cur_inst_len++; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "jclr #%d,%s,p:$%04x", numbit, registers_name[value], read_memory(dsp_core.pc+1) ); } static void dsp_jmp_imm(void) { snprintf(str_instr, sizeof(str_instr), "jmp p:$%04x", cur_inst & BITMASK(12)); } static void dsp_jmp_ea(void) { char dstname[16]; dsp_calc_ea((cur_inst >>8) & BITMASK(6), dstname, sizeof(dstname)); snprintf(str_instr, sizeof(str_instr), "jmp p:%s", dstname); } static void dsp_jscc_ea(void) { char cond_name[16], addr_name[16]; uint32_t cc_code=0; dsp_calc_ea((cur_inst>>8) & BITMASK(6), addr_name, sizeof(addr_name)); cc_code=cur_inst & BITMASK(4); dsp_calc_cc(cc_code, cond_name); snprintf(str_instr, sizeof(str_instr), "js%s p:%s", cond_name, addr_name); } static void dsp_jscc_imm(void) { char cond_name[16], addr_name[16]; uint32_t cc_code=0; snprintf(addr_name, sizeof(addr_name), "$%04x", cur_inst & BITMASK(12)); cc_code=(cur_inst>>12) & BITMASK(4); dsp_calc_cc(cc_code, cond_name); snprintf(str_instr, sizeof(str_instr), "js%s p:%s", cond_name, addr_name); } static void dsp_jsclr_aa(void) { /* jsclr #n,x:aa,p:xx */ /* jsclr #n,y:aa,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jsclr #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jsclr_ea(void) { /* jsclr #n,x:ea,p:xx */ /* jsclr #n,y:ea,p:xx */ char srcname[18], addr_name[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(srcname, sizeof(srcname), "y:%.12s", addr_name); } else { snprintf(srcname, sizeof(srcname), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "jsclr #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jsclr_pp(void) { /* jsclr #n,x:pp,p:xx */ /* jsclr #n,y:pp,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value += 0xffc0; if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jsclr #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jsclr_reg(void) { /* jsclr #n,R,p:xx */ uint32_t value, numbit; disasm_cur_inst_len++; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "jsclr #%d,%s,p:$%04x", numbit, registers_name[value], read_memory(dsp_core.pc+1) ); } static void dsp_jset_aa(void) { /* jset #n,x:aa,p:xx */ /* jset #n,y:aa,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jset #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jset_ea(void) { /* jset #n,x:ea,p:xx */ /* jset #n,y:ea,p:xx */ char srcname[18], addr_name[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(srcname, sizeof(srcname), "y:%.12s", addr_name); } else { snprintf(srcname, sizeof(srcname), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "jset #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jset_pp(void) { /* jset #n,x:pp,p:xx */ /* jset #n,y:pp,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value += 0xffc0; if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jset #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jset_reg(void) { /* jset #n,R,p:xx */ uint32_t value, numbit; disasm_cur_inst_len++; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "jset #%d,%s,p:$%04x", numbit, registers_name[value], read_memory(dsp_core.pc+1) ); } static void dsp_jsr_imm(void) { snprintf(str_instr, sizeof(str_instr), "jsr p:$%04x", cur_inst & BITMASK(12)); } static void dsp_jsr_ea(void) { char dstname[16]; dsp_calc_ea((cur_inst>>8) & BITMASK(6), dstname, sizeof(dstname)); snprintf(str_instr, sizeof(str_instr), "jsr p:%s", dstname); } static void dsp_jsset_aa(void) { /* jsset #n,x:aa,p:xx */ /* jsset #n,y:aa,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jsset #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jsset_ea(void) { /* jsset #n,x:ea,p:xx */ /* jsset #n,y:ea,p:xx */ char srcname[18], addr_name[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); dsp_calc_ea(value, addr_name, sizeof(addr_name)); if (memspace) { snprintf(srcname, sizeof(srcname), "y:%.12s", addr_name); } else { snprintf(srcname, sizeof(srcname), "x:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "jsset #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jsset_pp(void) { /* jsset #n,x:pp,p:xx */ /* jsset #n,y:pp,p:xx */ char srcname[16]; uint32_t memspace, value, numbit; disasm_cur_inst_len++; memspace = (cur_inst>>6) & 1; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); value += 0xffc0; if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", value); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", value); } snprintf(str_instr, sizeof(str_instr), "jsset #%d,%s,p:$%04x", numbit, srcname, read_memory(dsp_core.pc+1) ); } static void dsp_jsset_reg(void) { /* jsset #n,r,p:xx */ uint32_t value, numbit; disasm_cur_inst_len++; value = (cur_inst>>8) & BITMASK(6); numbit = cur_inst & BITMASK(5); snprintf(str_instr, sizeof(str_instr), "jsset #%d,%s,p:$%04x", numbit, registers_name[value], read_memory(dsp_core.pc+1) ); } static void dsp_lua(void) { char addr_name[16], numreg; dsp_calc_ea((cur_inst>>8) & BITMASK(5), addr_name, sizeof(addr_name)); numreg = cur_inst & BITMASK(3); if (cur_inst & (1<<3)) snprintf(str_instr, sizeof(str_instr), "lua %s,n%d", addr_name, numreg); else snprintf(str_instr, sizeof(str_instr), "lua %s,r%d", addr_name, numreg); } static void dsp_movec_reg(void) { uint32_t numreg1, numreg2; /* S1,D2 */ /* S2,D1 */ numreg2 = (cur_inst>>8) & BITMASK(6); numreg1 = cur_inst & BITMASK(6); if (cur_inst & (1<<15)) { /* Write D1 */ snprintf(str_instr, sizeof(str_instr), "movec %s,%s", registers_name[numreg2], registers_name[numreg1]); } else { /* Read S1 */ snprintf(str_instr, sizeof(str_instr), "movec %s,%s", registers_name[numreg1], registers_name[numreg2]); } } static void dsp_movec_aa(void) { const char *spacename; char srcname[16],dstname[16]; uint32_t numreg, addr; /* x:aa,D1 */ /* S1,x:aa */ /* y:aa,D1 */ /* S1,y:aa */ numreg = cur_inst & BITMASK(6); addr = (cur_inst>>8) & BITMASK(6); if (cur_inst & (1<<6)) { spacename="y"; } else { spacename="x"; } if (cur_inst & (1<<15)) { /* Write D1 */ snprintf(srcname, sizeof(srcname), "%s:$%04x", spacename, addr); strcpy(dstname, registers_name[numreg]); } else { /* Read S1 */ strcpy(srcname, registers_name[numreg]); snprintf(dstname, sizeof(dstname), "%s:$%04x", spacename, addr); } snprintf(str_instr, sizeof(str_instr), "movec %s,%s", srcname, dstname); } static void dsp_movec_imm(void) { uint32_t numreg; /* #xx,D1 */ numreg = cur_inst & BITMASK(6); snprintf(str_instr, sizeof(str_instr), "movec #$%02x,%s", (cur_inst>>8) & BITMASK(8), registers_name[numreg]); } static void dsp_movec_ea(void) { const char *spacename; char srcname[18], dstname[18], addr_name[16]; uint32_t numreg, ea_mode; int retour; /* x:ea,D1 */ /* S1,x:ea */ /* y:ea,D1 */ /* S1,y:ea */ /* #xxxx,D1 */ numreg = cur_inst & BITMASK(6); ea_mode = (cur_inst>>8) & BITMASK(6); retour = dsp_calc_ea(ea_mode, addr_name, sizeof(addr_name)); if (cur_inst & (1<<6)) { spacename="y"; } else { spacename="x"; } if (cur_inst & (1<<15)) { /* Write D1 */ if (retour) { snprintf(srcname, sizeof(srcname), "#%.12s", addr_name); } else { snprintf(srcname, sizeof(srcname), "%s:%.12s", spacename, addr_name); } strcpy(dstname, registers_name[numreg]); } else { /* Read S1 */ strcpy(srcname, registers_name[numreg]); snprintf(dstname, sizeof(dstname), "%s:%.12s", spacename, addr_name); } snprintf(str_instr, sizeof(str_instr), "movec %s,%s", srcname, dstname); } static void dsp_movem_aa(void) { /* S,p:aa */ /* p:aa,D */ char addr_name[16], srcname[18], dstname[18]; uint32_t numreg; snprintf(addr_name, sizeof(addr_name), "$%04x",(cur_inst>>8) & BITMASK(6)); numreg = cur_inst & BITMASK(6); if (cur_inst & (1<<15)) { /* Write D */ snprintf(srcname, sizeof(srcname), "p:%.12s", addr_name); strcpy(dstname, registers_name[numreg]); } else { /* Read S */ strcpy(srcname, registers_name[numreg]); snprintf(dstname, sizeof(dstname), "p:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "movem %s,%s", srcname, dstname); } static void dsp_movem_ea(void) { /* S,p:ea */ /* p:ea,D */ char addr_name[16], srcname[18], dstname[18]; uint32_t ea_mode, numreg; ea_mode = (cur_inst>>8) & BITMASK(6); dsp_calc_ea(ea_mode, addr_name, sizeof(addr_name)); numreg = cur_inst & BITMASK(6); if (cur_inst & (1<<15)) { /* Write D */ snprintf(srcname, sizeof(srcname), "p:%.12s", addr_name); strcpy(dstname, registers_name[numreg]); } else { /* Read S */ strcpy(srcname, registers_name[numreg]); snprintf(dstname, sizeof(dstname), "p:%.12s", addr_name); } snprintf(str_instr, sizeof(str_instr), "movem %s,%s", srcname, dstname); } static void dsp_movep_0(void) { char srcname[16]="",dstname[16]=""; uint32_t addr, memspace, numreg; /* S,x:pp */ /* x:pp,D */ /* S,y:pp */ /* y:pp,D */ addr = 0xffc0 + (cur_inst & BITMASK(6)); memspace = (cur_inst>>16) & 1; numreg = (cur_inst>>8) & BITMASK(6); if (cur_inst & (1<<15)) { /* Write pp */ strcpy(srcname, registers_name[numreg]); if (memspace) { snprintf(dstname, sizeof(dstname), "y:$%04x", addr); } else { snprintf(dstname, sizeof(dstname), "x:$%04x", addr); } } else { /* Read pp */ if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", addr); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", addr); } strcpy(dstname, registers_name[numreg]); } snprintf(str_instr, sizeof(str_instr), "movep %s,%s", srcname, dstname); } static void dsp_movep_1(void) { char srcname[18] = "", dstname[18] = "", name[16] = ""; uint32_t addr, memspace; /* p:ea,x:pp */ /* x:pp,p:ea */ /* p:ea,y:pp */ /* y:pp,p:ea */ addr = 0xffc0 + (cur_inst & BITMASK(6)); dsp_calc_ea((cur_inst>>8) & BITMASK(6), name, sizeof(name)); memspace = (cur_inst>>16) & 1; if (cur_inst & (1<<15)) { /* Write pp */ snprintf(srcname, sizeof(srcname), "p:%s", name); if (memspace) { snprintf(dstname, sizeof(dstname), "y:$%04x", addr); } else { snprintf(dstname, sizeof(dstname), "x:$%04x", addr); } } else { /* Read pp */ if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", addr); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", addr); } snprintf(dstname, sizeof(dstname), "p:%s", name); } snprintf(str_instr, sizeof(str_instr), "movep %s,%s", srcname, dstname); } static void dsp_movep_23(void) { char srcname[18] = "", dstname[18] = "", name[16] = ""; uint32_t addr, memspace, easpace, retour; /* x:ea,x:pp */ /* y:ea,x:pp */ /* #xxxxxx,x:pp */ /* x:pp,x:ea */ /* x:pp,y:ea */ /* x:ea,y:pp */ /* y:ea,y:pp */ /* #xxxxxx,y:pp */ /* y:pp,y:ea */ /* y:pp,x:ea */ addr = 0xffc0 + (cur_inst & BITMASK(6)); retour = dsp_calc_ea((cur_inst>>8) & BITMASK(6), name, sizeof(name)); memspace = (cur_inst>>16) & 1; easpace = (cur_inst>>6) & 1; if (cur_inst & (1<<15)) { /* Write pp */ if (retour) { snprintf(srcname, sizeof(srcname), "#%s", name); } else { if (easpace) { snprintf(srcname, sizeof(srcname), "y:%s", name); } else { snprintf(srcname, sizeof(srcname), "x:%s", name); } } if (memspace) { snprintf(dstname, sizeof(dstname), "y:$%04x", addr); } else { snprintf(dstname, sizeof(dstname), "x:$%04x", addr); } } else { /* Read pp */ if (memspace) { snprintf(srcname, sizeof(srcname), "y:$%04x", addr); } else { snprintf(srcname, sizeof(srcname), "x:$%04x", addr); } if (easpace) { snprintf(dstname, sizeof(dstname), "y:%s", name); } else { snprintf(dstname, sizeof(dstname), "x:%s", name); } } snprintf(str_instr, sizeof(str_instr), "movep %s,%s", srcname, dstname); } static void dsp_nop(void) { snprintf(str_instr, sizeof(str_instr), "nop"); } static void dsp_norm(void) { uint32_t srcreg, destreg; srcreg = DSP_REG_R0+((cur_inst>>8) & BITMASK(3)); destreg = DSP_REG_A+((cur_inst>>3) & 1); snprintf(str_instr, sizeof(str_instr), "norm %s,%s", registers_name[srcreg], registers_name[destreg]); } static void dsp_ori(void) { switch(cur_inst & BITMASK(2)) { case 0: snprintf(str_instr, sizeof(str_instr), "ori #$%02x,mr", (cur_inst>>8) & BITMASK(8)); break; case 1: snprintf(str_instr, sizeof(str_instr), "ori #$%02x,ccr", (cur_inst>>8) & BITMASK(8)); break; case 2: snprintf(str_instr, sizeof(str_instr), "ori #$%02x,omr", (cur_inst>>8) & BITMASK(8)); break; default: break; } } static void dsp_rep_aa(void) { char name[16]; /* x:aa */ /* y:aa */ if (cur_inst & (1<<6)) { snprintf(name, sizeof(name), "y:$%04x",(cur_inst>>8) & BITMASK(6)); } else { snprintf(name, sizeof(name), "x:$%04x",(cur_inst>>8) & BITMASK(6)); } snprintf(str_instr, sizeof(str_instr), "rep %s", name); } static void dsp_rep_imm(void) { /* #xxx */ snprintf(str_instr, sizeof(str_instr), "rep #$%02x", ((cur_inst>>8) & BITMASK(8)) + ((cur_inst & BITMASK(4))<<8)); } static void dsp_rep_ea(void) { char name[18], addr_name[16]; /* x:ea */ /* y:ea */ dsp_calc_ea((cur_inst>>8) & BITMASK(6), addr_name, sizeof(addr_name)); if (cur_inst & (1<<6)) { snprintf(name, sizeof(name), "y:%s",addr_name); } else { snprintf(name, sizeof(name), "x:%s",addr_name); } snprintf(str_instr, sizeof(str_instr), "rep %s", name); } static void dsp_rep_reg(void) { /* R */ snprintf(str_instr, sizeof(str_instr), "rep %s", registers_name[(cur_inst>>8) & BITMASK(6)]); } static void dsp_reset(void) { snprintf(str_instr, sizeof(str_instr), "reset"); } static void dsp_rti(void) { snprintf(str_instr, sizeof(str_instr), "rti"); } static void dsp_rts(void) { snprintf(str_instr, sizeof(str_instr), "rts"); } static void dsp_stop(void) { snprintf(str_instr, sizeof(str_instr), "stop"); } static void dsp_swi(void) { snprintf(str_instr, sizeof(str_instr), "swi"); } static void dsp_tcc(void) { char ccname[16]; uint32_t src1reg, dst1reg, src2reg, dst2reg; dsp_calc_cc((cur_inst>>12) & BITMASK(4), ccname); src1reg = registers_tcc[(cur_inst>>3) & BITMASK(4)][0]; dst1reg = registers_tcc[(cur_inst>>3) & BITMASK(4)][1]; if (cur_inst & (1<<16)) { src2reg = DSP_REG_R0+((cur_inst>>8) & BITMASK(3)); dst2reg = DSP_REG_R0+(cur_inst & BITMASK(3)); snprintf(str_instr, sizeof(str_instr), "t%s %s,%s %s,%s", ccname, registers_name[src1reg], registers_name[dst1reg], registers_name[src2reg], registers_name[dst2reg] ); } else { snprintf(str_instr, sizeof(str_instr), "t%s %s,%s", ccname, registers_name[src1reg], registers_name[dst1reg] ); } } static void dsp_wait(void) { snprintf(str_instr, sizeof(str_instr), "wait"); } /********************************** * Parallel moves **********************************/ static void dsp_pm(void) { uint32_t value; value = (cur_inst >> 20) & BITMASK(4); opcodes_parmove[value](); } static void dsp_pm_0(void) { char addr_name[16]; static char parallel_instr1[32], parallel_instr2[32]; uint32_t memspace, numreg; /* 0000 100d 00mm mrrr S,x:ea x0,D 0000 100d 10mm mrrr y0,D S,y:ea */ memspace = (cur_inst>>15) & 1; numreg = DSP_REG_A+((cur_inst>>16) & 1); dsp_calc_ea((cur_inst>>8) & BITMASK(6), addr_name, sizeof(addr_name)); if (memspace) { snprintf(parallel_instr1, sizeof(parallel_instr1), "y0,%s", registers_name[numreg]); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,y:%s", registers_name[numreg], addr_name); } else { snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,x:%s", registers_name[numreg], addr_name); snprintf(parallel_instr2, sizeof(parallel_instr2), "x0,%s", registers_name[numreg]); } snprintf(parallelmove_name, sizeof(parallelmove_name), "%-16s %s", parallel_instr1, parallel_instr2); } static void dsp_pm_1(void) { /* 0001 ffdf w0mm mrrr x:ea,D1 S2,D2 S1,x:ea S2,D2 #xxxxxx,D1 S2,D2 0001 deff w1mm mrrr S1,D1 y:ea,D2 S1,D1 S2,y:ea S1,D1 #xxxxxx,D2 */ char addr_name[16]; static char parallel_instr1[32], parallel_instr2[32]; uint32_t memspace, write_flag, retour, s1reg, s2reg, d1reg, d2reg; memspace = (cur_inst>>14) & 1; write_flag = (cur_inst>>15) & 1; retour = dsp_calc_ea((cur_inst>>8) & BITMASK(6), addr_name, sizeof(addr_name)); if (memspace==DSP_SPACE_Y) { s2reg = d2reg = DSP_REG_Y0; switch((cur_inst>>16) & BITMASK(2)) { case 0: s2reg = d2reg = DSP_REG_Y0; break; case 1: s2reg = d2reg = DSP_REG_Y1; break; case 2: s2reg = d2reg = DSP_REG_A; break; case 3: s2reg = d2reg = DSP_REG_B; break; } s1reg = DSP_REG_A+((cur_inst>>19) & 1); d1reg = DSP_REG_X0+((cur_inst>>18) & 1); if (write_flag) { /* Write D2 */ if (retour) { snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,%s", registers_name[s1reg], registers_name[d1reg]); snprintf(parallel_instr2, sizeof(parallel_instr2), "#%s,%s", addr_name, registers_name[d2reg]); } else { snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,%s", registers_name[s1reg], registers_name[d1reg]); snprintf(parallel_instr2, sizeof(parallel_instr2), "y:%s,%s", addr_name, registers_name[d2reg]); } } else { /* Read S2 */ snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,%s", registers_name[s1reg], registers_name[d1reg]); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,y:%s", registers_name[s2reg], addr_name); } } else { s1reg = d1reg = DSP_REG_X0; switch((cur_inst>>18) & BITMASK(2)) { case 0: s1reg = d1reg = DSP_REG_X0; break; case 1: s1reg = d1reg = DSP_REG_X1; break; case 2: s1reg = d1reg = DSP_REG_A; break; case 3: s1reg = d1reg = DSP_REG_B; break; } s2reg = DSP_REG_A+((cur_inst>>17) & 1); d2reg = DSP_REG_Y0+((cur_inst>>16) & 1); if (write_flag) { /* Write D1 */ if (retour) { snprintf(parallel_instr1, sizeof(parallel_instr1), "#%s,%s", addr_name, registers_name[d1reg]); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,%s", registers_name[s2reg], registers_name[d2reg]); } else { snprintf(parallel_instr1, sizeof(parallel_instr1), "x:%s,%s", addr_name, registers_name[d1reg]); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,%s", registers_name[s2reg], registers_name[d2reg]); } } else { /* Read S1 */ snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,x:%s", registers_name[s1reg], addr_name); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,%s", registers_name[s2reg], registers_name[d2reg]); } } snprintf(parallelmove_name, sizeof(parallelmove_name), "%-16s %s", parallel_instr1, parallel_instr2); } static void dsp_pm_2(void) { char addr_name[16]; uint32_t numreg1, numreg2; /* 0010 0000 0000 0000 nop 0010 0000 010m mrrr R update 0010 00ee eeed dddd S,D 001d dddd iiii iiii #xx,D */ if (((cur_inst >> 8) & 0xffff) == 0x2000) { return; } if (((cur_inst >> 8) & 0xffe0) == 0x2040) { dsp_calc_ea((cur_inst>>8) & BITMASK(5), addr_name, sizeof(addr_name)); snprintf(parallelmove_name, sizeof(parallelmove_name), "%s,r%d", addr_name, (cur_inst>>8) & BITMASK(3)); return; } if (((cur_inst >> 8) & 0xfc00) == 0x2000) { numreg1 = (cur_inst>>13) & BITMASK(5); numreg2 = (cur_inst>>8) & BITMASK(5); snprintf(parallelmove_name, sizeof(parallelmove_name), "%s,%s", registers_name[numreg1], registers_name[numreg2]); return; } numreg1 = (cur_inst>>16) & BITMASK(5); snprintf(parallelmove_name, sizeof(parallelmove_name), "#$%02x,%s", (cur_inst >> 8) & BITMASK(8), registers_name[numreg1]); } static void dsp_pm_4(void) { char addr_name[16]; uint32_t value, retour, ea_mode, memspace; /* 0100 l0ll w0aa aaaa l:aa,D S,l:aa 0100 l0ll w1mm mrrr l:ea,D S,l:ea 01dd 0ddd w0aa aaaa x:aa,D S,x:aa 01dd 0ddd w1mm mrrr x:ea,D S,x:ea #xxxxxx,D 01dd 1ddd w0aa aaaa y:aa,D S,y:aa 01dd 1ddd w1mm mrrr y:ea,D S,y:ea #xxxxxx,D */ value = (cur_inst>>16) & BITMASK(3); value |= (cur_inst>>17) & (BITMASK(2)<<3); ea_mode = (cur_inst>>8) & BITMASK(6); if ((value>>2)==0) { /* L: memory move */ if (cur_inst & (1<<14)) { retour = dsp_calc_ea(ea_mode, addr_name, sizeof(addr_name)); } else { snprintf(addr_name, sizeof(addr_name), "$%04x", ea_mode); retour = 0; } value = (cur_inst>>16) & BITMASK(2); value |= (cur_inst>>17) & (1<<2); if (cur_inst & (1<<15)) { /* Write D */ if (retour) { snprintf(parallelmove_name, sizeof(parallelmove_name), "#%s,%s", addr_name, registers_lmove[value]); } else { snprintf(parallelmove_name, sizeof(parallelmove_name), "l:%s,%s", addr_name, registers_lmove[value]); } } else { /* Read S */ snprintf(parallelmove_name, sizeof(parallelmove_name), "%s,l:%s", registers_lmove[value], addr_name); } return; } memspace = (cur_inst>>19) & 1; if (cur_inst & (1<<14)) { retour = dsp_calc_ea(ea_mode, addr_name, sizeof(addr_name)); } else { snprintf(addr_name, sizeof(addr_name), "$%04x", ea_mode); retour = 0; } if (memspace) { /* Y: */ if (cur_inst & (1<<15)) { /* Write D */ if (retour) { snprintf(parallelmove_name, sizeof(parallelmove_name), "#%s,%s", addr_name, registers_name[value]); } else { snprintf(parallelmove_name, sizeof(parallelmove_name), " y:%s,%s", addr_name, registers_name[value]); } } else { /* Read S */ snprintf(parallelmove_name, sizeof(parallelmove_name), " %s,y:%s", registers_name[value], addr_name); } } else { /* X: */ if (cur_inst & (1<<15)) { /* Write D */ if (retour) { snprintf(parallelmove_name, sizeof(parallelmove_name), "#%s,%s", addr_name, registers_name[value]); } else { snprintf(parallelmove_name, sizeof(parallelmove_name), "x:%s,%s", addr_name, registers_name[value]); } } else { /* Read S */ snprintf(parallelmove_name, sizeof(parallelmove_name), "%s,x:%s", registers_name[value], addr_name); } } } static void dsp_pm_8(void) { static char addr1_name[16], addr2_name[16]; static char parallel_instr1[32], parallel_instr2[32]; uint32_t ea_mode1, ea_mode2, numreg1, numreg2; /* 1wmm eeff WrrM MRRR x:ea,D1 y:ea,D2 x:ea,D1 S2,y:ea S1,x:ea y:ea,D2 S1,x:ea S2,y:ea */ numreg1 = DSP_REG_X0; switch((cur_inst>>18) & BITMASK(2)) { case 0: numreg1 = DSP_REG_X0; break; case 1: numreg1 = DSP_REG_X1; break; case 2: numreg1 = DSP_REG_A; break; case 3: numreg1 = DSP_REG_B; break; } numreg2 = DSP_REG_Y0; switch((cur_inst>>16) & BITMASK(2)) { case 0: numreg2 = DSP_REG_Y0; break; case 1: numreg2 = DSP_REG_Y1; break; case 2: numreg2 = DSP_REG_A; break; case 3: numreg2 = DSP_REG_B; break; } ea_mode1 = (cur_inst>>8) & BITMASK(5); if ((ea_mode1>>3) == 0) { ea_mode1 |= (1<<5); } ea_mode2 = (cur_inst>>13) & BITMASK(2); ea_mode2 |= ((cur_inst>>20) & BITMASK(2))<<3; if ((ea_mode1 & (1<<2))==0) { ea_mode2 |= 1<<2; } if ((ea_mode2>>3) == 0) { ea_mode2 |= (1<<5); } dsp_calc_ea(ea_mode1, addr1_name, sizeof(addr1_name)); dsp_calc_ea(ea_mode2, addr2_name, sizeof(addr2_name)); if (cur_inst & (1<<15)) { if (cur_inst & (1<<22)) { snprintf(parallel_instr1, sizeof(parallel_instr1), "x:%s,%s", addr1_name, registers_name[numreg1]); snprintf(parallel_instr2, sizeof(parallel_instr2), "y:%s,%s", addr2_name, registers_name[numreg2]); } else { snprintf(parallel_instr1, sizeof(parallel_instr1), "x:%s,%s", addr1_name, registers_name[numreg1]); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,y:%s", registers_name[numreg2], addr2_name); } } else { if (cur_inst & (1<<22)) { snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,x:%s", registers_name[numreg1], addr1_name); snprintf(parallel_instr2, sizeof(parallel_instr2), "y:%s,%s", addr2_name, registers_name[numreg2]); } else { snprintf(parallel_instr1, sizeof(parallel_instr1), "%s,x:%s", registers_name[numreg1], addr1_name); snprintf(parallel_instr2, sizeof(parallel_instr2), "%s,y:%s", registers_name[numreg2], addr2_name); } } snprintf(parallelmove_name, sizeof(parallelmove_name), "%-16s %s", parallel_instr1, parallel_instr2); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/dsp_disasm.h000066400000000000000000000022631504763705000250740ustar00rootroot00000000000000/* DSP M56001 emulation Disassembler (C) 2003-2008 ARAnyM developer team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifndef DSP_DISASM_H #define DSP_DISASM_H #ifdef __cplusplus extern "C" { #endif typedef enum { DSP_TRACE_MODE, DSP_DISASM_MODE } dsp_trace_disasm_t; /* Functions */ extern void dsp56k_disasm_init(void); extern uint16_t dsp56k_disasm(dsp_trace_disasm_t value, FILE *fp); extern const char* dsp56k_getInstructionText(void); /* Registers change */ extern void dsp56k_disasm_reg_save(void); extern void dsp56k_disasm_reg_compare(FILE *fp); #ifdef __cplusplus } #endif #endif /* DSP_DISASM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/microphone.c000066400000000000000000000045161504763705000251070ustar00rootroot00000000000000/* Hatari - microphone.c microphone (jack connector) emulation (Falcon mode only) This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #include #include "main.h" #include "microphone.h" #include "configuration.h" #include "crossbar.h" #include "log.h" #define FRAMES_PER_BUFFER 512 static int nMicDevId; static Sint16 micro_buffer_L[FRAMES_PER_BUFFER]; /* left buffer */ static Sint16 micro_buffer_R[FRAMES_PER_BUFFER]; /* right buffer */ /* * This routine will be called by the SDL2 library when audio is available. * It may be called at interrupt level on some machines so don't do anything * that could mess up the system like calling malloc() or free(). */ static void Microphone_Callback(void *pUserData, Uint8 *inputBuffer, int nLen) { unsigned int i; const Sint16 *in = (Sint16 *)inputBuffer; unsigned int framesPerBuffer = nLen / 4; Sint16 *out_L = (Sint16*)micro_buffer_L; Sint16 *out_R = (Sint16*)micro_buffer_R; for (i=0; i #include "main.h" #include "configuration.h" #include "ioMem.h" #include "log.h" #include "nvram.h" #include "paths.h" #include "tos.h" #include "vdi.h" #include "m68000.h" // Defs for NVRAM control register A (10) bits #define REG_BIT_UIP 0x80 /* update-in-progress */ // and 3 clock divider & 3 rate-control bits // Defs for NVRAM control register B (11) bits #define REG_BIT_DSE 0x01 /* daylight saving enable (ignored) */ #define REG_BIT_24H 0x02 /* 24/12h clock, 1=24h */ #define REG_BIT_DM 0x04 /* data mode: 1=BIN, 0=BCD */ #define REG_BIT_SQWE 0x08 /* square wave enable, signal to SQW pin */ #define REG_BIT_UIE 0x10 /* update-ended interrupt enable */ #define REG_BIT_AIE 0x20 /* alarm interrupt enable */ #define REG_BIT_PIE 0x40 /* periodic interrupt enable */ #define REG_BIT_SET 0x80 /* suspend RTC updates to set clock values */ // Defs for NVRAM status register C (12) bits #define REG_BIT_UF 0x10 /* update-ended interrupt flag */ #define REG_BIT_AF 0x20 /* alarm interrupt flag */ #define REG_BIT_PF 0x40 /* periodic interrupt flag */ #define REG_BIT_IRQF 0x80 /* interrupt request flag */ // Defs for NVRAM status register D (13) bits #define REG_BIT_VRM 0x80 /* valid RAM and time */ // Defs for checksum #define CKS_RANGE_START 14 #define CKS_RANGE_END (14+47) #define CKS_RANGE_LEN (CKS_RANGE_END-CKS_RANGE_START+1) #define CKS_LOC (14+48) #define NVRAM_START 14 #define NVRAM_LEN 50 static uint8_t nvram[64] = { 48, 255, 21, 255, 23, 255, 1, 25, 3, 33, /* clock/alarm registers */ 42, REG_BIT_DM|REG_BIT_24H, 0, REG_BIT_VRM, /* regs A-D */ 0,0,0,0,0,0,0,0,17,46,32,1,255,0,1,10,135,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; static uint8_t nvram_index; static char nvram_filename[FILENAME_MAX]; static int year_offset; /*-----------------------------------------------------------------------*/ /** * Load NVRAM data from file. */ static bool NvRam_Load(void) { bool ret = false; FILE *f = fopen(nvram_filename, "rb"); if (f != NULL) { uint8_t fnvram[NVRAM_LEN]; if (fread(fnvram, 1, NVRAM_LEN, f) == NVRAM_LEN) { memcpy(nvram+NVRAM_START, fnvram, NVRAM_LEN); LOG_TRACE(TRACE_NVRAM, "NVRAM: loaded from '%s'\n", nvram_filename); ret = true; } else { Log_Printf(LOG_WARN, "NVRAM loading from '%s' failed\n", nvram_filename); } fclose(f); } else { Log_Printf(LOG_INFO, "NVRAM not found at '%s'\n", nvram_filename); } return ret; } /*-----------------------------------------------------------------------*/ /** * Save NVRAM data to file */ static bool NvRam_Save(void) { bool ret = false; FILE *f = fopen(nvram_filename, "wb"); if (f != NULL) { if (fwrite(nvram+NVRAM_START, 1, NVRAM_LEN, f) == NVRAM_LEN) { LOG_TRACE(TRACE_NVRAM, "NVRAM: saved to '%s'\n", nvram_filename); ret = true; } else { Log_Printf(LOG_WARN, "Writing NVRAM to '%s' failed\n", nvram_filename); } fclose(f); } else { Log_Printf(LOG_WARN, "Storing NVRAM to '%s' failed\n", nvram_filename); } return ret; } /*-----------------------------------------------------------------------*/ /** * Create NVRAM checksum. The checksum is over all bytes except the * checksum bytes themselves; these are at the very end. */ static void NvRam_SetChecksum(void) { int i; unsigned char sum = 0; for(i = CKS_RANGE_START; i <= CKS_RANGE_END; ++i) sum += nvram[i]; nvram[NVRAM_CHKSUM1] = ~sum; nvram[NVRAM_CHKSUM2] = sum; } /*-----------------------------------------------------------------------*/ /** * Register C interrupt status flags clearing. * * Flags are cleared both on resets and register C reads. * Because rest of bits are always zero, whole register goes to zero. * * However, because interrupt handling isn't emulated yet * (there isn't even a known Atari use-case for them), * and most of them would occur quickly: * - update-ended interrupt flag is set at 1Hz clock update cycle * - periodic interrupt is set based on divider & rate-control clock rate * - alarm interrupt flag is set when time matches alarm time * i.e. only once a day */ static void clear_reg_c(void) { /* => set flags for fastest 2 interrupts right away */ nvram[12] = REG_BIT_UF|REG_BIT_PF; /* are these interrupts also enable in reg B? */ if (nvram[11] & nvram[12]) { /* -> set also interrupt request flag */ nvram[12] |= REG_BIT_IRQF; /* TODO: generate interrupt */ } } /*-----------------------------------------------------------------------*/ /** * NvRam_Reset: Called during init and reset, used for resetting the * emulated chip. */ void NvRam_Reset(void) { if (bUseVDIRes) { /* The objective is to start the TOS with a video mode similar * to the requested one. This is important for the TOS to initialize * the right font height and palette. */ if (VDIHeight < 400) { /* This will select the 8x8 system font */ switch(VDIPlanes) { /* The case 1 is not handled, because that would result in 0x0000 * which is an invalid video mode. This does not matter, * since any color palette is good for monochrome, anyway. */ case 2: /* set 320x200x4 colors */ nvram[NVRAM_VMODE1] = 0x00; nvram[NVRAM_VMODE2] = 0x01; break; case 4: /* set 320x200x16 colors */ default: nvram[NVRAM_VMODE1] = 0x00; nvram[NVRAM_VMODE2] = 0x02; } } else { /* This will select the 8x16 system font */ switch(VDIPlanes) { case 4: /* set 640x400x16 colors */ nvram[NVRAM_VMODE1] = 0x01; nvram[NVRAM_VMODE2] = 0x0a; break; case 2: /* set 640x400x4 colors */ nvram[NVRAM_VMODE1] = 0x01; nvram[NVRAM_VMODE2] = 0x09; break; case 1: /* set 640x400x2 colors */ default: nvram[NVRAM_VMODE1] = 0x01; nvram[NVRAM_VMODE2] = 0x08; } } NvRam_SetChecksum(); } /* reset clears SWQE + interrupt enable bits */ nvram[11] &= ~(REG_BIT_SQWE|REG_BIT_UIE|REG_BIT_AIE|REG_BIT_PIE); /* and interrupt flag bits */ clear_reg_c(); nvram_index = 0; } /*-----------------------------------------------------------------------*/ /** * Initialization */ void NvRam_Init(void) { const char sBaseName[] = "hatari.nvram"; const char *psHomeDir; // set up the nvram filename psHomeDir = Paths_GetHatariHome(); if (strlen(psHomeDir)+sizeof(sBaseName)+1 < sizeof(nvram_filename)) sprintf(nvram_filename, "%s%c%s", psHomeDir, PATHSEP, sBaseName); else strcpy(nvram_filename, sBaseName); if (!NvRam_Load()) // load NVRAM file automatically { if (ConfigureParams.Screen.nMonitorType == MONITOR_TYPE_VGA) // VGA ? { nvram[NVRAM_VMODE1] &= ~0x01; // No doublescan nvram[NVRAM_VMODE2] |= 0x10; // VGA mode nvram[NVRAM_VMODE2] &= ~0x20; // 60 Hz } else { nvram[NVRAM_VMODE1] |= 0x01; // Interlaced nvram[NVRAM_VMODE2] &= ~0x10; // TV/RGB mode nvram[NVRAM_VMODE2] |= 0x20; // 50 Hz } } if (ConfigureParams.Keyboard.nLanguage != TOS_LANG_UNKNOWN) nvram[NVRAM_LANGUAGE] = ConfigureParams.Keyboard.nLanguage; if (ConfigureParams.Keyboard.nKbdLayout != TOS_LANG_UNKNOWN) nvram[NVRAM_KEYBOARDLAYOUT] = ConfigureParams.Keyboard.nKbdLayout; NvRam_SetChecksum(); NvRam_Reset(); /* Set suitable tm->tm_year offset * (tm->tm_year starts from 1900, NVRAM year from 1968) */ year_offset = 68; if (!ConfigureParams.System.nRtcYear) return; time_t ticks = time(NULL); int year = 1900 + localtime(&ticks)->tm_year; year_offset += year - ConfigureParams.System.nRtcYear; } /*-----------------------------------------------------------------------*/ /** * De-Initialization */ void NvRam_UnInit(void) { NvRam_Save(); // save NVRAM file upon exit automatically (should be conditionalized) } /*-----------------------------------------------------------------------*/ /** * Read from RTC/NVRAM offset selection register ($ff8961) */ void NvRam_Select_ReadByte(void) { IoMem_WriteByte(0xff8961, nvram_index); } /*-----------------------------------------------------------------------*/ /** * Write to RTC/NVRAM offset selection register ($ff8961) */ void NvRam_Select_WriteByte(void) { uint8_t value = IoMem_ReadByte(0xff8961); if (value < sizeof(nvram)) { nvram_index = value; } else { Log_Printf(LOG_WARN, "NVRAM: trying to set out-of-bound position (%d)\n", value); } } /*-----------------------------------------------------------------------*/ static struct tm* refreshFrozenTime(bool refresh) { static struct tm frozen_time; if (refresh) { /* update frozen time */ time_t tim = time(NULL); frozen_time = *localtime(&tim); } return &frozen_time; } /** * Returns pointer to "frozen time". Unless NVRAM SET time bit is set, * that's first refreshed from host clock (= doing "RTC update cycle"). * Correct applications have SET bit enabled while they write clock registers. */ static struct tm* getFrozenTime(void) { if (nvram[11] & REG_BIT_SET) return refreshFrozenTime(false); else return refreshFrozenTime(true); } /** * If NVRAM data mode bit is set, returns given value, * otherwise returns it as BCD. */ static uint8_t bin2BCD(uint8_t value) { if ((nvram[11] & REG_BIT_DM)) return value; return ((value / 10) << 4) | (value % 10); } /*-----------------------------------------------------------------------*/ /** * Read from RTC/NVRAM data register ($ff8963) */ void NvRam_Data_ReadByte(void) { uint8_t value = 0; switch(nvram_index) { case 1: /* alarm seconds */ case 3: /* alarm minutes */ case 5: /* alarm hour */ value = bin2BCD(nvram[nvram_index]); break; case 0: value = bin2BCD(getFrozenTime()->tm_sec); break; case 2: value = bin2BCD(getFrozenTime()->tm_min); break; case 4: value = getFrozenTime()->tm_hour; if (!(nvram[11] & REG_BIT_24H)) { uint8_t pmflag = (value == 0 || value >= 13) ? 0x80 : 0; value = value % 12; if (value == 0) value = 12; value = bin2BCD(value) | pmflag; } else value = bin2BCD(value); break; case 6: value = bin2BCD(getFrozenTime()->tm_wday + 1); break; case 7: value = bin2BCD(getFrozenTime()->tm_mday); break; case 8: value = bin2BCD(getFrozenTime()->tm_mon + 1); break; case 9: value = bin2BCD(getFrozenTime()->tm_year - year_offset); break; case 10: /* control reg A * read-only UIP bit + clock dividers & rate selectors * * UIP is suspended during SET, otherwise * dummy toggling it is enough to fool programs */ if (nvram[11] & REG_BIT_SET) nvram[nvram_index] &= ~REG_BIT_UIP; else nvram[nvram_index] ^= REG_BIT_UIP; value = nvram[nvram_index]; break; case 12: /* status reg C, read-only * 0xf0 interrupt status bits, 0x0f unused/zero * register is cleared after read */ value = nvram[nvram_index]; clear_reg_c(); break; case 11: /* control reg B * set, interrupt enable, sqw enable, clock mode, daylight savings bits * writing SET bit aborts/suspends UIP and clears UIP bit */ /* fall-through */ case 13: /* status reg D, read-only * Valid RAM and Time bit, rest of bits are zero/unused */ /* fall-through */ default: value = nvram[nvram_index]; break; } LOG_TRACE(TRACE_NVRAM, "NVRAM: read data at %d = %d ($%02x) pc=%x\n", nvram_index, value, value, M68000_GetPC()); IoMem_WriteByte(0xff8963, value); } /*-----------------------------------------------------------------------*/ /** * Write to RTC/NVRAM data register ($ff8963) */ void NvRam_Data_WriteByte(void) { /* enable & flag bits in B & C regs match each other -> use same mask for both */ const uint8_t int_mask = REG_BIT_UF|REG_BIT_AF|REG_BIT_PF; uint8_t value = IoMem_ReadByte(0xff8963); switch (nvram_index) { case 0: /* high-order bit read-only: don't care as we always read from host */ break; case 10: /* UIP bit is read-only */ value = (value & ~REG_BIT_UIP) | (nvram[10] & REG_BIT_UIP); break; case 11: if (value & int_mask) { Log_Printf(LOG_WARN, "Write to unimplemented RTC/NVRAM interrupt enable bits 0x%x\n", value & int_mask); if (nvram[12] & int_mask) { /* reg B enabling bits matched reg C flag bits */ nvram[12] |= REG_BIT_IRQF; /* TODO: generate interrupt */ } /* TODO: start updating reg C flag bits & generate interrupts when appropriate */ } if (value & REG_BIT_SET) { /* refresh clock as its updating is suspended while SET is enabled */ refreshFrozenTime(true); } break; case 12: case 13: IoMem_WriteByte(0xff8963, nvram[nvram_index]); Log_Printf(LOG_WARN, "Ignored write %d ($%02x) to read-only RTC/NVRAM status register %d!\n", value, value, nvram_index); return; } LOG_TRACE(TRACE_NVRAM, "NVRAM: write data at %d = %d ($%02x) pc=%x\n", nvram_index, value, value, M68000_GetPC()); nvram[nvram_index] = value; } void NvRam_Info(FILE *fp, uint32_t dummy) { fprintf(fp, "- File: '%s'\n", nvram_filename); fprintf(fp, "- Time: from host (regs: 0, 2, 4, 6-9)\n"); fprintf(fp, "- Alarm: %02d:%02d:%02d (1, 3, 5)\n", bin2BCD(nvram[5]), bin2BCD(nvram[3]), bin2BCD(nvram[1])); fprintf(fp, "- Control reg A: 0x%02x (10)\n", nvram[10]); fprintf(fp, "- Control reg B: 0x%02x (11)\n", nvram[11]); fprintf(fp, "- Status reg A: 0x%02x (12)\n", nvram[12]); fprintf(fp, "- Status reg B: 0x%02x (13)\n", nvram[13]); fprintf(fp, "- Preferred OS: 0x%02x 0x%02x (14, 15)\n", nvram[14], nvram[15]); fprintf(fp, "- Language: 0x%02x (20)\n", nvram[20]); fprintf(fp, "- Keyboard layout: 0x%02x (21)\n", nvram[21]); fprintf(fp, "- Date/time format: 0x%02x (22)\n", nvram[22]); fprintf(fp, "- Date separator: 0x%02x (23)\n", nvram[23]); fprintf(fp, "- Video mode: 0x%02x 0x%02x (28, 19)\n", nvram[28], nvram[29]); fprintf(fp, "- SCSI ID: %d, bus arbitration: %s (30)\n", nvram[30] & 0x7, nvram[30] & 128 ? "off" : "on"); } int NvRam_GetKbdLayoutCode(void) { return nvram[NVRAM_KEYBOARDLAYOUT]; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/nvram.h000066400000000000000000000030151504763705000240650ustar00rootroot00000000000000/* Hatari - nvram.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Atari TT and Falcon NVRAM and RTC emulation code - declarations */ #ifndef HATARI_NVRAM_H #define HATARI_NVRAM_H /* some constants to give NVRAM locations symbolic names */ #define NVRAM_SECONDS 0 #define NVRAM_MINUTES 2 #define NVRAM_HOURS 4 #define NVRAM_WDAY 6 #define NVRAM_MDAY 7 #define NVRAM_MONTH 8 #define NVRAM_YEAR 9 /* FIXME: give better names to the OS selector cells */ #define NVRAM_OS1 14 #define NVRAM_OS2 15 #define NVRAM_LANGUAGE 20 #define NVRAM_KEYBOARDLAYOUT 21 #define NVRAM_TIMEFORMAT 22 #define NVRAM_DATESEPERATOR 23 #define NVRAM_BOOTDELAY 24 #define NVRAM_VMODE1 28 #define NVRAM_VMODE2 29 #define NVRAM_SCSI 30 /* FIXME: give better names to these (maybe byte order if there is any?) * keep track on NvRam_SetChecksum()! */ #define NVRAM_CHKSUM1 62 #define NVRAM_CHKSUM2 63 extern void NvRam_Reset(void); extern void NvRam_Init(void); extern void NvRam_UnInit(void); extern void NvRam_Select_ReadByte(void); extern void NvRam_Select_WriteByte(void); extern void NvRam_Data_ReadByte(void); extern void NvRam_Data_WriteByte(void); extern void NvRam_Info(FILE *fp, uint32_t dummy); extern int NvRam_GetKbdLayoutCode(void); /* for tos.c */ static inline bool NvRam_Present(void) { return ConfigureParams.System.nMachineType == MACHINE_TT || ConfigureParams.System.nMachineType == MACHINE_FALCON; } #endif /* HATARI_NVRAM_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/videl.c000066400000000000000000001103111504763705000240360ustar00rootroot00000000000000/* Hatari - videl.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Falcon Videl emulation. The Videl is the graphics shifter chip of the Falcon. It supports free programmable resolutions with 1, 2, 4, 8 or 16 bits per pixel. This file originally came from the Aranym project and has been heavily modified to work for Hatari (but the kudos for the great Videl emulation code goes to the people from the Aranym project of course). Videl can run at 2 frequencies : 25.175 Mhz or 32 MHz Hardware I/O registers: $FFFF8006 (byte) : monitor type $FFFF8201 (byte) : VDL_VBH - Video Base Hi $FFFF8203 (byte) : VDL_VBM - Video Base Mi $FFFF8205 (byte) : VDL_VCH - Video Count Hi $FFFF8207 (byte) : VDL_VCM - Video Count Mi $FFFF8209 (byte) : VDL_VCL - Video Count Lo $FFFF820A (byte) : VDL_SYM - Sync mode $FFFF820D (byte) : VDL_VBL - Video Base Lo $FFFF820E (word) : VDL_LOF - Offset to next line $FFFF8210 (word) : VDL_LWD - Line Wide in Words $FFFF8240 (word) : VDL_STC - ST Palette Register 00 ......... $FFFF825E (word) : VDL_STC - ST Palette Register 15 $FFFF8260 (byte) : ST shift mode $FFFF8264 (byte) : Horizontal scroll register shadow register $FFFF8265 (byte) : Horizontal scroll register $FFFF8266 (word) : Falcon shift mode $FFFF8280 (word) : HHC - Horizontal Hold Counter $FFFF8282 (word) : HHT - Horizontal Hold Timer $FFFF8284 (word) : HBB - Horizontal Border Begin $FFFF8286 (word) : HBE - Horizontal Border End $FFFF8288 (word) : HDB - Horizontal Display Begin $FFFF828A (word) : HDE - Horizontal Display End $FFFF828C (word) : HSS - Horizontal SS $FFFF828E (word) : HFS - Horizontal FS $FFFF8290 (word) : HEE - Horizontal EE $FFFF82A0 (word) : VFC - Vertical Frequency Counter $FFFF82A2 (word) : VFT - Vertical Frequency Timer $FFFF82A4 (word) : VBB - Vertical Border Begin $FFFF82A6 (word) : VBE - Vertical Border End $FFFF82A8 (word) : VDB - Vertical Display Begin $FFFF82AA (word) : VDE - Vertical Display End $FFFF82AC (word) : VSS - Vertical SS $FFFF82C0 (word) : VCO - Video control $FFFF82C2 (word) : VMD - Video mode $FFFF9800 (long) : VDL_PAL - Videl palette Register 000 ........... $FFFF98FC (long) : VDL_PAL - Videl palette Register 255 */ const char VIDEL_fileid[] = "Hatari videl.c"; #include "main.h" #include "configuration.h" #include "memorySnapShot.h" #include "ioMem.h" #include "log.h" #include "screen.h" #include "screenConvert.h" #include "avi_record.h" #include "statusbar.h" #include "stMemory.h" #include "tos.h" #include "videl.h" #include "video.h" /* for bUseHighRes variable */ #include "vdi.h" /* for bUseVDIRes variable */ #define VIDEL_COLOR_REGS_BEGIN 0xff9800 struct videl_s { Uint8 reg_ffff8006_save; /* save reg_ffff8006 as it's a read only register */ Uint8 monitor_type; /* 00 Monochrome (SM124) / 01 Color (SC1224) / 10 VGA Color / 11 Television ($FFFF8006) */ Uint16 vertFreqCounter; /* Counter for VFC register $ff82a0, restarted on each VBL */ Uint32 videoRaster; /* Video raster offset, restarted on each VBL */ Sint16 leftBorderSize; /* Size of the left border */ Sint16 rightBorderSize; /* Size of the right border */ Sint16 upperBorderSize; /* Size of the upper border */ Sint16 lowerBorderSize; /* Size of the lower border */ Uint16 XSize; /* X size of the graphical area */ Uint16 YSize; /* Y size of the graphical area */ Uint16 save_scrWidth; /* save screen width to detect a change of X resolution */ Uint16 save_scrHeight; /* save screen height to detect a change of Y resolution */ Uint16 save_scrBpp; /* save screen Bpp to detect a change of bitplan mode */ bool hostColorsSync; /* Sync palette with host's */ bool bUseSTShifter; /* whether to use ST or Falcon palette */ }; static struct videl_s videl; static void Videl_SetDefaultSavedRes(void) { /* Default resolution to boot with */ videl.save_scrWidth = 640; videl.save_scrHeight = 480; videl.save_scrBpp = 4; } /** * Called upon startup (and via VIDEL_reset()) */ void Videl_Init(void) { videl.hostColorsSync = false; Videl_SetDefaultSavedRes(); } /** * Called when CPU encounters a RESET instruction. */ void VIDEL_reset(void) { Videl_Init(); Screen_SetGenConvSize(videl.save_scrWidth, videl.save_scrHeight, false); videl.bUseSTShifter = false; /* Use Falcon color palette by default */ videl.reg_ffff8006_save = IoMem_ReadByte(0xff8006); videl.monitor_type = videl.reg_ffff8006_save & 0xc0; VIDEL_RestartVideoCounter(); /* Reset IO register (some are not initialized by TOS) */ IoMem_WriteWord(0xff820e, 0); /* Line offset */ IoMem_WriteWord(0xff8264, 0); /* Horizontal scroll */ } /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void VIDEL_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&videl, sizeof(videl)); /* Make sure that the save_scr* variables match the ones during reset, * so that resolution changes get evaluated properly (e.g. to set the * right zooming variables */ if (!bSave) Videl_SetDefaultSavedRes(); } static bool Videl_GetPixelCyclesAndDivider(int *cyc_per_pixel, int *divider) { Uint16 vdm = IoMem_ReadWord(0xff82c2) & 0xc; /* Compute cycles per pixel */ if (vdm == 0) *cyc_per_pixel = 4; else if (vdm == 4) *cyc_per_pixel = 2; else *cyc_per_pixel = 1; /* Compute the divider */ if (videl.monitor_type == FALCON_MONITOR_VGA) { if (vdm == 0) *divider = 4; else *divider = 2; } else if (videl.bUseSTShifter == true) { *divider = 16; } else { *divider = *cyc_per_pixel; } return vdm != 0xc; /* Return whether it is a valid setting */ } /** * Return the vertical refresh rate for the current video mode * We use the following formula : * VFreq = ( HFreq / (VFT+1) ) * 2 * HFreq is 15625 Hz in RGB/TV mode or 31250 Hz in VGA mode (in VGA mode HFreq can take other values in the same range) * * Some VFT values set by TOS : * - 320x200 16 colors, RGB : VFT = 625 -> 50 Hz * - 320x200 16 colors, VGA : VFT = 1049 -> 60 Hz */ int VIDEL_Get_VFreq(void) { static int prev_hfreq, prev_vfreq; int baseclock, hfreq, vfreq; int cyc_per_pixel, divider; int hht, vft; Videl_GetPixelCyclesAndDivider(&cyc_per_pixel, ÷r); if ( IoMem_ReadWord(0xff82c0) & 4 ) /* VC0 : bit2=0 32 MHz bit2=1 25 MHz */ { baseclock = 25175000; } else { /* 32.084988 MHz for PAL and 32.215905 for NTSC systems? */ baseclock = 32084988; } vft = IoMem_ReadWord(0xff82a2); if (vft < 2) /* Avoid bad value */ return 60; hht = IoMem_ReadWord(0xff8282); hfreq = baseclock / (2 * divider * (hht + 2)); vfreq = round(2.0 * hfreq / (vft - !(IoMem_ReadWord(0xff82C2) & 2))); if (prev_hfreq != hfreq || prev_vfreq != vfreq) { Log_Printf(LOG_DEBUG, "Videl refresh rate changed: " "baseclock=%i hfreq=%i vfreq=%i\n", baseclock, hfreq, vfreq); prev_hfreq = hfreq; prev_vfreq = vfreq; } return vfreq; } /** * Return the content of videl.bUseSTShifter. * This tells if the current video mode is compatible with ST/STE * video mode or not */ bool VIDEL_Use_STShifter(void) { return videl.bUseSTShifter; } /** * Monitor write access to Falcon color palette registers */ void VIDEL_FalconColorRegsWrite(void) { uint32_t color = IoMem_ReadLong(IoAccessBaseAddress & ~3); color &= 0xfcfc00fc; /* Unused bits have to be set to 0 */ IoMem_WriteLong(IoAccessBaseAddress & ~3, color); videl.hostColorsSync = false; } /** * VIDEL_Monitor_WriteByte : Contains memory and monitor configuration. * This register is read only. */ void VIDEL_Monitor_WriteByte(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8006 Monitor and memory conf write (Read only)\n"); /* Restore hardware value */ IoMem_WriteByte(0xff8006, videl.reg_ffff8006_save); } /** * VIDEL_SyncMode_WriteByte: * Videl synchronization mode. Bit 1 is used by TOS 4.04 to set either 50 Hz * (bit set) or 60 Hz (bit cleared). * Note: There are documentation files out there that claim that bit 1 is * used to distinguish between monochrome or color monitor, but these are * definitely wrong. */ void VIDEL_SyncMode_WriteByte(void) { Uint8 syncMode = IoMem_ReadByte(0xff820a); LOG_TRACE(TRACE_VIDEL, "Videl : $ff820a Sync Mode write: 0x%02x\n", syncMode); syncMode &= 0x03; /* Upper bits are hard-wired to 0 */ IoMem_WriteByte(0xff820a, syncMode); } /** * Read video address counter and update ff8205/07/09 */ void VIDEL_ScreenCounter_ReadByte(void) { Uint32 addr = videl.videoRaster; IoMem[0xff8205] = ( addr >> 16 ) & 0xff; IoMem[0xff8207] = ( addr >> 8 ) & 0xff; IoMem[0xff8209] = addr & 0xff; LOG_TRACE(TRACE_VIDEL, "Videl : $ff8205/07/09 Sync Mode read: 0x%08x\n", addr); } /** * Write video address counter */ void VIDEL_ScreenCounter_WriteByte(void) { Uint32 addr_new = videl.videoRaster; Uint8 AddrByte = IoMem[ IoAccessCurrentAddress ]; /* Compute the new video address with one modified byte */ if ( IoAccessCurrentAddress == 0xff8205 ) addr_new = ( addr_new & 0x00ffff ) | ( AddrByte << 16 ); else if ( IoAccessCurrentAddress == 0xff8207 ) addr_new = ( addr_new & 0xff00ff ) | ( AddrByte << 8 ); else if ( IoAccessCurrentAddress == 0xff8209 ) addr_new = ( addr_new & 0xffff00 ) | ( AddrByte ); videl.videoRaster = addr_new; LOG_TRACE(TRACE_VIDEL, "Videl : $ff8205/07/09 Sync Mode write: 0x%08x\n", addr_new); } /** * VIDEL_LineOffset_ReadWord: $FFFF820E [R/W] W _______876543210 Line Offset * How many words are added to the end of display line, i.e. how many words are * 'behind' the display. */ void VIDEL_LineOffset_ReadWord(void) { /* Unused bits in the first byte are read as zero, so mask them */ IoMem[0xff820e] &= 0x01; } /** * VIDEL_LineOffset_WriteWord: $FFFF820E [R/W] W _______876543210 Line Offset * How many words are added to the end of display line, i.e. how many words are * 'behind' the display. */ void VIDEL_LineOffset_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff820e Line Offset write: 0x%04x\n", IoMem_ReadWord(0xff820e)); } /** * VIDEL_Line_Width_WriteWord: $FFFF8210 [R/W] W ______9876543210 Line Width (VWRAP) * Length of display line in words.Or, how many words should be added to * vram counter after every display line. */ void VIDEL_Line_Width_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8210 Line Width write: 0x%04x\n", IoMem_ReadWord(0xff8210)); } /** * Write to video address base high, med and low register (0xff8201/03/0d). * On Falcon, when a program writes to high or med registers, base low register * is reset to zero. */ void VIDEL_ScreenBase_WriteByte(void) { if ((IoAccessCurrentAddress == 0xff8201) || (IoAccessCurrentAddress == 0xff8203)) { /* Reset screen base low register */ IoMem[0xff820d] = 0; } LOG_TRACE(TRACE_VIDEL, "Videl : $%04x Screen base write: 0x%02x\t (screen: 0x%04x)\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], (IoMem[0xff8201]<<16) + (IoMem[0xff8203]<<8) + IoMem[0xff820d]); } /** VIDEL_ST_ShiftModeWriteByte : $FFFF8260 [R/W] B ______10 ST Shift Mode || || others vga || $FF8210 $FF82C2 $FF82C2 00--4BP/320 Pixels=> $0050 $0000 $0005 01--2BP/640 Pixels=> $0050 $0004 $0009 10--1BP/640 Pixels=> $0028 $0006 $0008 11--???/320 Pixels=> $0050 $0000 $0000 Writing to this register does the following things: - activate STE palette - sets line width ($ffff8210) - sets video mode in $ffff82c2 (double lines/interlace & cycles/pixel) */ void VIDEL_ST_ShiftModeWriteByte(void) { Uint16 line_width, video_mode; Uint8 st_shiftMode; st_shiftMode = IoMem_ReadByte(0xff8260); LOG_TRACE(TRACE_VIDEL, "Videl : $ff8260 ST Shift Mode (STSHIFT) write: 0x%02x\n", st_shiftMode); /* Bits 2-7 are set to 0 */ IoMem_WriteByte(0xff8260, st_shiftMode & 3); /* Activate STE palette */ videl.bUseSTShifter = true; /* Compute line width and video mode */ switch (st_shiftMode & 0x3) { case 0: /* 4BP/320 Pixels */ line_width = 0x50; /* half pixels + double lines vs. no scaling */ video_mode = videl.monitor_type == FALCON_MONITOR_VGA ? 0x5 : 0x0; break; case 1: /* 2BP/640 Pixels */ line_width = 0x50; /* quarter pixels + double lines vs. half pixels */ video_mode = videl.monitor_type == FALCON_MONITOR_VGA ? 0x9 : 0x4; break; case 2: /* 1BP/640 Pixels */ line_width = 0x28; if (videl.monitor_type == FALCON_MONITOR_MONO) { video_mode = 0x0; break; } /* quarter pixels vs. half pixels + interlace */ video_mode = videl.monitor_type == FALCON_MONITOR_VGA ? 0x8 : 0x6; break; case 3: /* ???/320 Pixels */ default: line_width = 0x50; video_mode = 0x0; break; } /* Set line width ($FFFF8210) */ IoMem_WriteWord(0xff8210, line_width); /* Set video mode ($FFFF82C2) */ IoMem_WriteWord(0xff82c2, video_mode); /* Hack for Sparrow-TOS (which does not know about Videl registers): */ if (TosVersion == 0x207) { if (st_shiftMode == 2) /* Mono? */ { IoMem_WriteWord(0xff82a4, 0); IoMem_WriteWord(0xff82a6, 0); IoMem_WriteWord(0xff82a8, 0x43); IoMem_WriteWord(0xff82aa, 0x363); } else if (ConfigureParams.Screen.nMonitorType == MONITOR_TYPE_VGA) { IoMem_WriteWord(0xff82a4, 0x3af); IoMem_WriteWord(0xff82a6, 0x8f); IoMem_WriteWord(0xff82a8, 0x8f); IoMem_WriteWord(0xff82aa, 0x3af); } else { IoMem_WriteWord(0xff82a4, 0x20e); IoMem_WriteWord(0xff82a6, 0x7e); IoMem_WriteWord(0xff82a8, 0x7e); IoMem_WriteWord(0xff82aa, 0x20e); } } } /** VIDEL_HorScroll64_WriteByte : Horizontal scroll register (0-15) $FFFF8264 [R/W] ________ ................................ H-SCROLL HI |||| [ Shadow register for $FFFF8265 ] ++++--Pixel shift [ 0:normal / 1..15:Left shift ] [ Change in line-width NOT required ] */ void VIDEL_HorScroll64_WriteByte(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8264 Horizontal scroll 64 write: 0x%02x\n", IoMem_ReadByte(0xff8264)); } /** VIDEL_HorScroll65_WriteByte : Horizontal scroll register (0-15) $FFFF8265 [R/W] ____3210 .................................H-SCROLL LO |||| ++++--Pixel [ 0:normal / 1..15:Left shift ] [ Change in line-width NOT required ] */ void VIDEL_HorScroll65_WriteByte(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8265 Horizontal scroll 65 write: 0x%02x\n", IoMem_ReadByte(0xff8265)); } /** VIDEL_Falcon_ShiftMode_WriteWord : $FFFF8266 [R/W] W _____A98_6543210 Falcon Shift Mode (SPSHIFT) ||| ||||||| ||| |||++++- 0..15: Colourbank choice from 256-colour table in 16 colour multiples ||| ||+----- 8 Bitplanes mode (256 Colors) [0:off / 1:on] ||| |+------ Vertical Sync [0: internal / 1: external] ||| +------- Horizontal Sync [0: internal / 1: external] ||+--------- True-Color-Mode [0:off / 1:on] |+---------- Overlay-Mode [0:off / 1:on] +----------- 0: 2-Color-Mode [0:off / 1:on] Writing to this register does the following things: - activate Falcon palette - if you set Bits A/8/4 == 0, it selects 16-Color-Falcon-Mode (NOT the same as ST LOW since Falcon palette is used!) - $8260 register is ignored, you don't need to write here anything Note: 4-Color-Mode isn't realisable with Falcon palette. */ void VIDEL_Falcon_ShiftMode_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8266 Falcon Shift Mode (SPSHIFT) write: 0x%04x\n", IoMem_ReadWord(0xff8266)); videl.bUseSTShifter = false; } /** * Write Horizontal Hold Counter (HHC) */ void VIDEL_HHC_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8280 Horizontal Hold Counter (HHC) write: 0x%04x\n", IoMem_ReadWord(0xff8280)); } /** * Write Horizontal Hold Timer (HHT) */ void VIDEL_HHT_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8282 Horizontal Hold Timer (HHT) write: 0x%04x\n", IoMem_ReadWord(0xff8282)); } /** * Write Horizontal Border Begin (HBB) */ void VIDEL_HBB_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8284 Horizontal Border Begin (HBB) write: 0x%04x\n", IoMem_ReadWord(0xff8284)); } /** * Write Horizontal Border End (HBE) */ void VIDEL_HBE_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8286 Horizontal Border End (HBE) write: 0x%04x\n", IoMem_ReadWord(0xff8286)); } /** * Write Horizontal Display Begin (HDB) $FFFF8288 [R/W] W ______9876543210 Horizontal Display Begin (HDB) | +---------- Display will start in [0: 1st halfline / 1: 2nd halfline] */ void VIDEL_HDB_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8288 Horizontal Display Begin (HDB) write: 0x%04x\n", IoMem_ReadWord(0xff8288)); } /** * Write Horizontal Display End (HDE) */ void VIDEL_HDE_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff828a Horizontal Display End (HDE) write: 0x%04x\n", IoMem_ReadWord(0xff828a)); } /** * Write Horizontal SS (HSS) */ void VIDEL_HSS_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff828c Horizontal SS (HSS) write: 0x%04x\n", IoMem_ReadWord(0xff828c)); } /** * Write Horizontal FS (HFS) */ void VIDEL_HFS_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff828e Horizontal FS (HFS) write: 0x%04x\n", IoMem_ReadWord(0xff828e)); } /** * Write Horizontal EE (HEE) */ void VIDEL_HEE_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff8290 Horizontal EE (HEE) write: 0x%04x\n", IoMem_ReadWord(0xff8290)); } /** * Write Vertical Frequency Counter (VFC) */ void VIDEL_VFC_ReadWord(void) { IoMem_WriteWord(0xff82a0, videl.vertFreqCounter); videl.vertFreqCounter++; LOG_TRACE(TRACE_VIDEL, "Videl : $ff82a0 Vertical Frequency Counter (VFC) read: 0x%04x\n", videl.vertFreqCounter); } /** * Write Vertical Frequency Timer (VFT) */ void VIDEL_VFT_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82a2 Vertical Frequency Timer (VFT) write: 0x%04x\n", IoMem_ReadWord(0xff82a2)); } /** * Write Vertical Border Begin (VBB) */ void VIDEL_VBB_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82a4 Vertical Border Begin (VBB) write: 0x%04x\n", IoMem_ReadWord(0xff82a4)); } /** * Write Vertical Border End (VBE) */ void VIDEL_VBE_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82a6 Vertical Border End (VBE) write: 0x%04x\n", IoMem_ReadWord(0xff82a6)); } /** * Write Vertical Display Begin (VDB) */ void VIDEL_VDB_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82a8 Vertical Display Begin (VDB) write: 0x%04x\n", IoMem_ReadWord(0xff82a8)); } /** * Write Vertical Display End (VDE) */ void VIDEL_VDE_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82aa Vertical Display End (VDE) write: 0x%04x\n", IoMem_ReadWord(0xff82aa)); } /** * Write Vertical SS (VSS) */ void VIDEL_VSS_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82ac Vertical SS (VSS) write: 0x%04x\n", IoMem_ReadWord(0xff82ac)); } /** * Write Video Control (VCO) */ void VIDEL_VCO_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82c0 Video control (VCO) write: 0x%04x\n", IoMem_ReadWord(0xff82c0)); } /** * Write Video Mode (VDM) */ void VIDEL_VMD_WriteWord(void) { LOG_TRACE(TRACE_VIDEL, "Videl : $ff82c2 Video Mode (VDM) write: 0x%04x\n", IoMem_ReadWord(0xff82c2)); } /** * Reset appropriate registers on VBL etc */ void VIDEL_RestartVideoCounter(void) { videl.videoRaster = Video_GetScreenBaseAddr(); /* counter for VFC register $ff82a0 */ videl.vertFreqCounter = 0; } /** * Increment appropriate registers on HBL */ void VIDEL_VideoRasterHBL(void) { int lineoffset = IoMem_ReadWord(0xff820e) & 0x01ff; /* 9 bits */ int linewidth = IoMem_ReadWord(0xff8210) & 0x03ff; /* 10 bits */ videl.videoRaster += linewidth + lineoffset; /* TODO: VFC is incremented every half line, here, we increment it every line */ videl.vertFreqCounter++; } static Uint16 VIDEL_getScreenBpp(void) { Uint16 f_shift = IoMem_ReadWord(0xff8266); Uint16 bits_per_pixel; Uint8 st_shift = IoMem_ReadByte(0xff8260); /* to get bpp, we must examine f_shift and st_shift. * f_shift is valid if any of bits no. 10, 8 or 4 is set. * Priority in f_shift is: 10 ">" 8 ">" 4, i.e. * if bit 10 set then bit 8 and bit 4 don't care... * If all these bits are 0 and ST shifter is written * after Falcon one, get display depth from st_shift * (as for ST and STE) */ if (f_shift & 0x400) /* Falcon: 2 colors */ bits_per_pixel = 1; else if (f_shift & 0x100) /* Falcon: hicolor */ bits_per_pixel = 16; else if (f_shift & 0x010) /* Falcon: 8 bitplanes */ bits_per_pixel = 8; else if (!videl.bUseSTShifter) /* Falcon: 4 bitplanes */ bits_per_pixel = 4; else if (st_shift == 0) bits_per_pixel = 4; else if (st_shift == 0x01) bits_per_pixel = 2; else /* if (st_shift == 0x02) */ bits_per_pixel = 1; /* LOG_TRACE(TRACE_VIDEL, "Videl works in %d bpp, f_shift=%04x, st_shift=%d", bits_per_pixel, f_shift, st_shift); */ return bits_per_pixel; } /** * VIDEL_getScreenWidth : returns the visible X resolution * left border + graphic area + right border * left border : hdb - hbe-offset * right border : hbb - hde-offset * Graphics display : starts at cycle HDB and ends at cycle HDE. */ static int VIDEL_getScreenWidth(void) { Uint16 hbb, hbe, hdb, hde, hht; Sint16 hdb_offset, hde_offset; Sint16 leftBorder, rightBorder; Uint16 bpp = VIDEL_getScreenBpp(); int cycPerPixel, divider; /* X Size of the Display area */ videl.XSize = (IoMem_ReadWord(0xff8210) & 0x03ff) * 16 / bpp; /* Sanity check - don't use unsupported texture sizes for SDL2: * http://answers.unity3d.com/questions/563094/mobile-max-texture-size.html * (largest currently known real Videl width is ~1600) */ while (videl.XSize > 2048) videl.XSize /= 2; /* If the user disabled the borders display from the gui, we suppress them */ if (ConfigureParams.Screen.bAllowOverscan == 0) { videl.leftBorderSize = 0; videl.rightBorderSize = 0; return videl.XSize; } /* According to Aura and Animal Mine doc about Videl, if a monochrome monitor is connected, * HDB and HDE have no significance and no border is displayed. */ if (videl.monitor_type == FALCON_MONITOR_MONO) { videl.leftBorderSize = 0; videl.rightBorderSize = 0; return videl.XSize; } hbb = IoMem_ReadWord(0xff8284) & 0x01ff; hbe = IoMem_ReadWord(0xff8286) & 0x01ff; hdb = IoMem_ReadWord(0xff8288) & 0x01ff; hde = IoMem_ReadWord(0xff828a) & 0x01ff; hht = IoMem_ReadWord(0xff8282) & 0x1ff; Videl_GetPixelCyclesAndDivider(&cycPerPixel, ÷r); /* Compute hdb_offset and hde_offset */ if (videl.bUseSTShifter == false) { if (bpp < 16) { /* falcon mode bpp */ hdb_offset = ((64+(128/bpp + 16 + 2) * cycPerPixel) / divider ) + 1; hde_offset = ((128/bpp + 2) * cycPerPixel) / divider; } else { /* falcon mode true color */ hdb_offset = ((64 + 16 * cycPerPixel) / divider ) + 1; hde_offset = 0; } } else { /* ST bitplan mode */ hdb_offset = ((128+(128/bpp + 2) * cycPerPixel) / divider ) + 1; hde_offset = ((128/bpp + 2) * cycPerPixel) / divider; } LOG_TRACE(TRACE_VIDEL, "hdb_offset=%04x, hde_offset=%04x\n", hdb_offset, hde_offset); /* Compute left border size in cycles */ if (IoMem_ReadWord(0xff8288) & 0x0200) leftBorder = hdb - hbe + hdb_offset - hht - 2; else leftBorder = hdb - hbe + hdb_offset; /* Compute right border size in cycles */ rightBorder = hbb - hde_offset - hde; videl.leftBorderSize = leftBorder / cycPerPixel; videl.rightBorderSize = rightBorder / cycPerPixel; LOG_TRACE(TRACE_VIDEL, "left border size=%04x, right border size=%04x\n", videl.leftBorderSize, videl.rightBorderSize); if (videl.leftBorderSize < 0) { // fprintf(stderr, "BORDER LEFT < 0 %d\n", videl.leftBorderSize); videl.leftBorderSize = 0; } if (videl.rightBorderSize < 0) { // fprintf(stderr, "BORDER RIGHT < 0 %d\n", videl.rightBorderSize); videl.rightBorderSize = 0; } return videl.leftBorderSize + videl.XSize + videl.rightBorderSize; } /** * VIDEL_getScreenHeight : returns the visible Y resolution * upper border + graphic area + lower border * upper border : vdb - vbe * lower border : vbb - vde * Graphics display : starts at line VDB and ends at line VDE. * If interlace mode off unit of VC-registers is half lines, else lines. */ static int VIDEL_getScreenHeight(void) { Uint16 vbb = IoMem_ReadWord(0xff82a4) & 0x07ff; Uint16 vbe = IoMem_ReadWord(0xff82a6) & 0x07ff; Uint16 vdb = IoMem_ReadWord(0xff82a8) & 0x07ff; Uint16 vde = IoMem_ReadWord(0xff82aa) & 0x07ff; Uint16 vmode = IoMem_ReadWord(0xff82c2); /* According to Aura and Animal Mine doc about Videl, if a monochrome monitor is connected, * VDB and VDE have no significance and no border is displayed. */ if (videl.monitor_type == FALCON_MONITOR_MONO) { videl.upperBorderSize = 0; videl.lowerBorderSize = 0; } else { /* We must take the positive value only, as a program like AceTracker starts the */ /* graphical area 1 line before the end of the upper border */ videl.upperBorderSize = vdb - vbe > 0 ? vdb - vbe : 0; videl.lowerBorderSize = vbb - vde > 0 ? vbb - vde : 0; } /* Y Size of the Display area */ if (vde >= vdb) { videl.YSize = vde - vdb; } else { LOG_TRACE(TRACE_VIDEL, "WARNING: vde=0x%x is less than vdb=0x%x\n", vde, vdb); } /* If the user disabled the borders display from the gui, we suppress them */ if (ConfigureParams.Screen.bAllowOverscan == 0) { videl.upperBorderSize = 0; videl.lowerBorderSize = 0; } if (!(vmode & 0x02)){ /* interlace */ videl.YSize >>= 1; videl.upperBorderSize >>= 1; videl.lowerBorderSize >>= 1; } if (vmode & 0x01) { /* double */ videl.YSize >>= 1; videl.upperBorderSize >>= 1; videl.lowerBorderSize >>= 1; } return videl.upperBorderSize + videl.YSize + videl.lowerBorderSize; } /** * Map the correct colortable into the correct pixel format */ void VIDEL_UpdateColors(void) { int i, r, g, b, colors = 1 << videl.save_scrBpp; if (videl.hostColorsSync) return; #define F_COLORS(i) IoMem_ReadByte(VIDEL_COLOR_REGS_BEGIN + (i)) #define STE_COLORS(i) IoMem_ReadByte(0xff8240 + (i)) /* True color mode ? */ if (videl.save_scrBpp > 8) { /* Videl color 0 ($ffff9800) must be taken into account as it is the border color in true color mode */ r = F_COLORS(0) & 0xfc; r |= r>>6; g = F_COLORS(0 + 1) & 0xfc; g |= g>>6; b = F_COLORS(0 + 3) & 0xfc; b |= b>>6; Screen_SetPaletteColor(0, r,g,b); return; } if (!videl.bUseSTShifter) { for (i = 0; i < colors; i++) { int offset = i << 2; r = F_COLORS(offset) & 0xfc; r |= r>>6; g = F_COLORS(offset + 1) & 0xfc; g |= g>>6; b = F_COLORS(offset + 3) & 0xfc; b |= b>>6; Screen_SetPaletteColor(i, r,g,b); } } else { for (i = 0; i < colors; i++) { int offset = i << 1; r = STE_COLORS(offset) & 0x0f; r = ((r & 7)<<1)|(r>>3); r |= r<<4; g = (STE_COLORS(offset + 1)>>4) & 0x0f; g = ((g & 7)<<1)|(g>>3); g |= g<<4; b = STE_COLORS(offset + 1) & 0x0f; b = ((b & 7)<<1)|(b>>3); b |= b<<4; Screen_SetPaletteColor(i, r,g,b); } } videl.hostColorsSync = true; } void Videl_ScreenModeChanged(bool bForceChange) { LOG_TRACE(TRACE_VIDEL, "Videl : video mode change to %dx%d@%d\n", videl.save_scrWidth, videl.save_scrHeight, videl.save_scrBpp); Screen_SetGenConvSize(videl.save_scrWidth, videl.save_scrHeight, bForceChange); } bool VIDEL_renderScreen(void) { /* Atari screen infos */ int vw = VIDEL_getScreenWidth(); int vh = VIDEL_getScreenHeight(); int vbpp = VIDEL_getScreenBpp(); int lineoffset = IoMem_ReadWord(0xff820e) & 0x01ff; /* 9 bits */ int linewidth = IoMem_ReadWord(0xff8210) & 0x03ff; /* 10 bits */ int hscrolloffset = IoMem_ReadByte(0xff8265) & 0x0f; int nextline; bool change = false; Uint32 videoBase = Video_GetScreenBaseAddr(); if (vw > 0 && vw != videl.save_scrWidth) { LOG_TRACE(TRACE_VIDEL, "Videl : width change from %d to %d\n", videl.save_scrWidth, vw); videl.save_scrWidth = vw; change = true; } if (vh > 0 && vh != videl.save_scrHeight) { LOG_TRACE(TRACE_VIDEL, "Videl : height change from %d to %d\n", videl.save_scrHeight, vh); videl.save_scrHeight = vh; change = true; } if (vbpp != videl.save_scrBpp) { LOG_TRACE(TRACE_VIDEL, "Videl : bpp change from %d to %d\n", videl.save_scrBpp, vbpp); videl.save_scrBpp = vbpp; change = true; } if (change) { Videl_ScreenModeChanged(false); } if (vw < 32 || vh < 32) { LOG_TRACE(TRACE_VIDEL, "Videl : %dx%d screen size, not drawing\n", vw, vh); return false; } if (!Screen_Lock()) return false; /* I think this implementation is naive: indeed, I suspect that we should instead skip lineoffset words each time we have read "more" than linewidth words (possibly "more" because of the number of bit planes). Moreover, the 1 bit plane mode is particular; while doing some experiments on my Falcon, it seems to behave like the 4 bit planes mode. At last, we have also to take into account the 4 bits register located at the word $ffff8264 (bit offset). This register makes the semantics of the lineoffset register change a little. int bitoffset = IoMem_ReadWord(0xff8264) & 0x000f; The meaning of this register in True Color mode is not clear for me at the moment (and my experiments on the Falcon don't help me). */ nextline = linewidth + lineoffset; VIDEL_UpdateColors(); Screen_GenConvert(videoBase, &STRam[videoBase], videl.XSize, videl.YSize, videl.save_scrBpp, nextline, hscrolloffset, videl.leftBorderSize, videl.rightBorderSize, videl.upperBorderSize, videl.lowerBorderSize); Screen_UnLock(); Screen_GenConvUpdate(Statusbar_Update(sdlscrn, false), false); return true; } /** * Write to videl ST palette registers (0xff8240-0xff825e) * * Note that there's a special "strange" case when writing only to the upper byte * of the color reg (instead of writing 16 bits at once with .W/.L). * In that case, the byte written to address x is automatically written * to address x+1 too (but we shouldn't copy x in x+1 after masking x ; we apply the mask at the end) * Similarly, when writing a byte to address x+1, it's also written to address x * So : move.w #0,$ff8240 -> color 0 is now $000 * move.b #7,$ff8240 -> color 0 is now $707 ! * move.b #$55,$ff8241 -> color 0 is now $555 ! * move.b #$71,$ff8240 -> color 0 is now $171 (bytes are first copied, then masked) */ static void Videl_ColorReg_WriteWord(void) { Uint16 col; Uint32 addr = IoAccessCurrentAddress; videl.hostColorsSync = false; if (bUseHighRes || bUseVDIRes) /* Don't store if hi-res or VDI resolution */ return; /* Handle special case when writing only to the lower or * upper byte of the color reg: copy written byte also * to the other byte before masking the color value. */ if (nIoMemAccessSize == SIZE_BYTE) col = (IoMem_ReadByte(addr) << 8) + IoMem_ReadByte(addr); /* Usual case, writing a word or a long (2 words) */ else col = IoMem_ReadWord(addr); col &= 0xfff; /* Mask off to 4096 palette */ addr &= 0xfffffffe; /* Ensure addr is even to store the 16 bit color */ IoMem_WriteWord(addr, col); } /* * [NP] TODO : due to how .L accesses are handled in ioMem.c, we can't call directly * Video_ColorReg_WriteWord from ioMemTabFalcon.c, we must use an intermediate * function, else .L accesses will not change 2 .W color regs, but only one. * This should be changed in ioMem.c to do 2 separate .W accesses, as would do a real 68000 */ void Videl_Color0_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color1_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color2_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color3_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color4_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color5_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color6_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color7_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color8_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color9_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color10_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color11_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color12_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color13_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color14_WriteWord(void) { Videl_ColorReg_WriteWord(); } void Videl_Color15_WriteWord(void) { Videl_ColorReg_WriteWord(); } /** * display Videl registers values (for debugger info command) */ void Videl_Info(FILE *fp, uint32_t dummy) { if (ConfigureParams.System.nMachineType != MACHINE_FALCON) { fprintf(fp, "Not Falcon - no Videl!\n"); return; } fprintf(fp, "$FF8006.b : monitor type : %02x\n", IoMem_ReadByte(0xff8006)); fprintf(fp, "$FF8201.b : Video Base Hi : %02x\n", IoMem_ReadByte(0xff8201)); fprintf(fp, "$FF8203.b : Video Base Mi : %02x\n", IoMem_ReadByte(0xff8203)); fprintf(fp, "$FF8205.b : Video Count Hi : %02x\n", IoMem_ReadByte(0xff8205)); fprintf(fp, "$FF8207.b : Video Count Mi : %02x\n", IoMem_ReadByte(0xff8207)); fprintf(fp, "$FF8209.b : Video Count Lo : %02x\n", IoMem_ReadByte(0xff8209)); fprintf(fp, "$FF820A.b : Sync mode : %02x\n", IoMem_ReadByte(0xff820a)); fprintf(fp, "$FF820D.b : Video Base Lo : %02x\n", IoMem_ReadByte(0xff820d)); fprintf(fp, "$FF820E.w : offset to next line : %04x\n", IoMem_ReadWord(0xff820e)); fprintf(fp, "$FF8210.w : VWRAP - line width : %04x\n", IoMem_ReadWord(0xff8210)); fprintf(fp, "$FF8260.b : ST shift mode : %02x\n", IoMem_ReadByte(0xff8260)); fprintf(fp, "$FF8264.w : Horizontal scroll register : %04x\n", IoMem_ReadWord(0xff8264)); fprintf(fp, "$FF8266.w : Falcon shift mode : %04x\n", IoMem_ReadWord(0xff8266)); fprintf(fp, "\n"); fprintf(fp, "$FF8280.w : HHC - Horizontal Hold Counter : %04x\n", IoMem_ReadWord(0xff8280)); fprintf(fp, "$FF8282.w : HHT - Horizontal Hold Timer : %04x\n", IoMem_ReadWord(0xff8282)); fprintf(fp, "$FF8284.w : HBB - Horizontal Border Begin : %04x\n", IoMem_ReadWord(0xff8284)); fprintf(fp, "$FF8286.w : HBE - Horizontal Border End : %04x\n", IoMem_ReadWord(0xff8286)); fprintf(fp, "$FF8288.w : HDB - Horizontal Display Begin : %04x\n", IoMem_ReadWord(0xff8288)); fprintf(fp, "$FF828A.w : HDE - Horizontal Display End : %04x\n", IoMem_ReadWord(0xff828a)); fprintf(fp, "$FF828C.w : HSS - Horizontal SS : %04x\n", IoMem_ReadWord(0xff828c)); fprintf(fp, "$FF828E.w : HFS - Horizontal FS : %04x\n", IoMem_ReadWord(0xff828e)); fprintf(fp, "$FF8290.w : HEE - Horizontal EE : %04x\n", IoMem_ReadWord(0xff8290)); fprintf(fp, "\n"); fprintf(fp, "$FF82A0.w : VFC - Vertical Frequency Counter : %04x\n", IoMem_ReadWord(0xff82a0)); fprintf(fp, "$FF82A2.w : VFT - Vertical Frequency Timer : %04x\n", IoMem_ReadWord(0xff82a2)); fprintf(fp, "$FF82A4.w : VBB - Vertical Border Begin : %04x\n", IoMem_ReadWord(0xff82a4)); fprintf(fp, "$FF82A6.w : VBE - Vertical Border End : %04x\n", IoMem_ReadWord(0xff82a6)); fprintf(fp, "$FF82A8.w : VDB - Vertical Display Begin : %04x\n", IoMem_ReadWord(0xff82a8)); fprintf(fp, "$FF82AA.w : VDE - Vertical Display End : %04x\n", IoMem_ReadWord(0xff82aa)); fprintf(fp, "$FF82AC.w : VSS - Vertical SS : %04x\n", IoMem_ReadWord(0xff82ac)); fprintf(fp, "\n"); fprintf(fp, "$FF82C0.w : VCO - Video control : %04x\n", IoMem_ReadWord(0xff82c0)); fprintf(fp, "$FF82C2.w : VMD - Video mode : %04x\n", IoMem_ReadWord(0xff82c2)); fprintf(fp, "\n-------------------------\n"); fprintf(fp, "Video base : %08x\n", (IoMem_ReadByte(0xff8201)<<16) + (IoMem_ReadByte(0xff8203)<<8) + IoMem_ReadByte(0xff820d)); fprintf(fp, "Video count : %08x\n", (IoMem_ReadByte(0xff8205)<<16) + (IoMem_ReadByte(0xff8207)<<8) + IoMem_ReadByte(0xff8209)); fprintf(fp, "Palette type: %s\n", videl.bUseSTShifter ? "ST/STE compat ($FF8240)" : "Falcon ($FF9800)"); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/falcon/videl.h000066400000000000000000000055061504763705000240540ustar00rootroot00000000000000/* Hatari - videl.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_VIDEL_H #define HATARI_VIDEL_H extern bool VIDEL_renderScreen(void); extern void Videl_Init(void); extern void VIDEL_reset(void); extern void Videl_ScreenModeChanged(bool bForceChange); extern void VIDEL_UpdateColors(void); extern void VIDEL_RestartVideoCounter(void); extern void VIDEL_VideoRasterHBL(void); extern int VIDEL_Get_VFreq(void); extern bool VIDEL_Use_STShifter(void); /* Called from ioMemTabFalcon.c */ extern void VIDEL_Monitor_WriteByte(void); extern void VIDEL_SyncMode_WriteByte(void); extern void VIDEL_ScreenBase_WriteByte(void); extern void VIDEL_ScreenCounter_ReadByte(void); extern void VIDEL_ScreenCounter_WriteByte(void); extern void VIDEL_StColorRegsWrite(void); extern void VIDEL_FalconColorRegsWrite(void); extern void VIDEL_LineOffset_WriteWord(void); extern void VIDEL_LineOffset_ReadWord(void); extern void VIDEL_Line_Width_WriteWord(void); extern void VIDEL_HorScroll64_WriteByte(void); extern void VIDEL_HorScroll65_WriteByte(void); extern void VIDEL_ST_ShiftModeWriteByte(void); extern void VIDEL_Falcon_ShiftMode_WriteWord(void); extern void VIDEL_HHC_WriteWord(void); extern void VIDEL_HHT_WriteWord(void); extern void VIDEL_HBB_WriteWord(void); extern void VIDEL_HBE_WriteWord(void); extern void VIDEL_HDB_WriteWord(void); extern void VIDEL_HDE_WriteWord(void); extern void VIDEL_HSS_WriteWord(void); extern void VIDEL_HFS_WriteWord(void); extern void VIDEL_HEE_WriteWord(void); extern void VIDEL_VFC_ReadWord(void); extern void VIDEL_VFT_WriteWord(void); extern void VIDEL_VBB_WriteWord(void); extern void VIDEL_VBE_WriteWord(void); extern void VIDEL_VDB_WriteWord(void); extern void VIDEL_VDE_WriteWord(void); extern void VIDEL_VSS_WriteWord(void); extern void VIDEL_VCO_WriteWord(void); extern void VIDEL_VMD_WriteWord(void); extern void Videl_Color0_WriteWord(void); extern void Videl_Color1_WriteWord(void); extern void Videl_Color2_WriteWord(void); extern void Videl_Color3_WriteWord(void); extern void Videl_Color4_WriteWord(void); extern void Videl_Color5_WriteWord(void); extern void Videl_Color6_WriteWord(void); extern void Videl_Color7_WriteWord(void); extern void Videl_Color8_WriteWord(void); extern void Videl_Color9_WriteWord(void); extern void Videl_Color10_WriteWord(void); extern void Videl_Color11_WriteWord(void); extern void Videl_Color12_WriteWord(void); extern void Videl_Color13_WriteWord(void); extern void Videl_Color14_WriteWord(void); extern void Videl_Color15_WriteWord(void); /* Called from cycint.c */ extern void VIDEL_InterruptHandler_HalfLine(void); /* Called from memorySnapShot.c */ extern void VIDEL_MemorySnapShot_Capture(bool bSave); extern void Videl_Info(FILE *fp, uint32_t dummy); #endif /* _VIDEL_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/fdc.c000066400000000000000000005574201504763705000222450ustar00rootroot00000000000000/* Hatari - fdc.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Floppy Disk Controller(FDC) emulation. All commands are emulated with good timings estimation, as many programs (demo or cracked games) rely on accurate FDC timings and DMA transfer by blocks of 16 bytes. The behaviour of all FDC's registers matches the official docs and should not cause programs to fail when accessing the FDC (especially for Status Register). As Hatari only handles ST/MSA disk images that only support 512 bytes sectors as well as a fixed number of sectors per track, a few parts of the FDC emulation are simplified and would need to be changed to handle more complex disk images (Pasti). */ const char FDC_fileid[] = "Hatari fdc.c"; #include #include "main.h" #include "configuration.h" #include "fdc.h" #include "hdc.h" #include "floppy.h" #include "floppy_ipf.h" #include "floppy_stx.h" #include "ioMem.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "mfp.h" #include "psg.h" #include "stMemory.h" #include "video.h" #include "clocks_timings.h" #include "utils.h" #include "statusbar.h" /* Floppy Disk Controller Programmable Sound Generator (YM-2149) 0xff8800(even byte) - PSG Register Data (Read, used for parallel port) - PSG Register Select (Write) Write to bits 0-3 to select PSG register to use(then write data to 0xfff8802) Value Register 0000 Channel A Fine Tune 0001 Channel A Coarse Tune 0010 Channel B Fine Tune 0011 Channel B Coarse Tune 0100 Channel C Fine Tune 0101 Channel C Coarse Tune 0110 Noise Generator Control 0111 Mixer Control - I/O enable 1000 Channel A Amplitude 1001 Channel B Amplitude 1010 Channel C Amplitude 1011 Envelope Period Fine Tune 1100 Envelope Period Coarse Tune 1101 Envelope Shape 1110 I/O Port A Select (Write only) 1111 I/O Port B Select 0xfff8802(even byte) - Bits according to 0xff8800 Register select 1110(Register 14) - I/O Port A Bit 0 - Floppy side 0/1 Bit 1 - Floppy drive 0 select Bit 2 - Floppy drive 1 select Bit 3 - RS232 Ready to send (RTS) Bit 4 - RS232 Data Terminal Ready (DTR) Bit 5 - Centronics Strobe Bit 6 - General Purpose Output Bit 7 - Reserved ACSI DMA and Floppy Disk Controller(FDC) 0xff8604 - information from file '1772.info.txt, by David Gahris' (register r0) Word access only, but only lower byte (ff8605) is used (write) - Disk controller Set DMA sector count if ff8606 bit 4 == 1 Set FDC's internal registers depending on bit 1/2 of ff8606 if bit 4 == 0 (read) - Disk controller status Bit 0 - Busy. This bit is 1 when the 177x is busy. This bit is 0 when the 177x is free for CPU commands. Bit 1 - Index / Data Request. On Type I commands, this bit is high during the index pulse that occurs once per disk rotation. This bit is low at all times other than the index pulse. For Type II and III commands, Bit 1 high signals the CPU to handle the data register in order to maintain a continuous flow of data. Bit 1 is high when the data register is full during a read or when the data register is empty during a write. "Worst case service time" for Data Request is 23.5 cycles. Bit 2 - Track Zero / Lost Data. After Type I commands, this bit is 0 if the mechanism is at track zero. This bit is 1 if the head is not at track zero. After Type II or III commands, this bit is 1 if the CPU did not respond to Data Request (Status bit 1) in time for the 177x to maintain a continuous data flow. This bit is 0 if the CPU responded promptly to Data Request. NOTE : on ST, Lost Data is never set because the DMA always handles the data request signal. Bit 3 - CRC Error. This bit is high if a sector CRC on disk does not match the CRC which the 177x computed from the data. The CRC polynomial is x^16+x^12+x^5+1. If the stored CRC matches the newly calculated CRC, the CRC Error bit is low. If this bit and the Record Not Found bit are set, the error was in an ID field. If this bit is set but Record Not Found is clear, the error was in a data field. Bit 4 - Record Not Found. This bit is set if the 177x cannot find the track, sector, or side which the CPU requested. Otherwise, this bit is clear. Bit 5 - Spin-up / Record Type. For Type I commands, this bit is low during the 6-revolution motor spin-up time. This bit is high after spin-up. For Type II and Type III commands, Bit 5 low indicates a normal data mark. Bit 5 high indicates a deleted data mark. Bit 6 - Write Protect. This bit is not used during reads. During writes, this bit is high when the disk is write protected. After a type I command, this bit is constantly updated an give the current value of the WPT signal. Bit 7 - Motor On. This bit is high when the drive motor is on, and low when the motor is off. 0xff8606 - DMA Status(read), DMA Mode Control(write) - NOTE bits 0,9-15 are not used Bit 1 - FDC Pin A0 (See below) Bit 2 - FDC Pin A1 Bit 3 - FDC/HDC Register Select Bit 4 - FDC/Sector count select Bit 5 - Reserved Bit 6 - Enable/Disable DMA Bit 7 - HDC/FDC Bit 8 - Read/Write A1 A0 Read Write(bit 8==1) 0 0 Status Command 0 1 Track Register Track Register 1 0 Sector Register Sector Register 1 1 Data Register Data Register 0xff860e - DD/HD mode selection - NOTE bits 2-15 are not used This register is only available on MegaSTE, TT and Falcon Bit 0 - FDC Frequency 0=8 MHz 1=16 MHz Bit 1 - Density 0=DD 1=HD 0xff9200 - DIP switches setting - NOTE bits 0-7 are used by joypads on STE and Falcon Bit 8-15 of this register are only used on MegaSTE, TT and Falcon and reflect the state of the 8 DIP switches (or soldering) on the motherboard TT and Falcon were produced with HD drives. But MegaSTE were first produced with DD drives then later with HD drives Bit 8-13 - Not used or machine dependent Bit 14 - Floppy Drive model 0=HD drive 1=DD drive Bit 15 - Disable DMA sound 0=disable 1=don't disable NOTE [NP] : The DMA is connected to the FDC and its Data Register, each time a DRQ is made by the FDC, it's handled by the DMA through its internal 16 bytes buffer. This means that in the case of the Atari ST the LOST_DATA bit will never be set in the Status Register (but data can be lost if FDC_DMA.SectorCount=0 as there will be no transfer between DMA and RAM). So, "read sector" and "write sector" type II command will never set LOST_DATA, but strangely on a real STF the "read track" type III command will set LOST_DATA when it succeeds (maybe it depends on the size of the track ?) (eg Super Monaco GP on Superior 65 : loader fails if LOST_DATA is set when there're not enough DMA sectors to transfer bytes with read sectors) NOTE [NP] : All commands that require to read data from the floppy (verify sequence, searching next sector id, ...) will not fail if the drive is OFF or empty. They will wait forever until the drive is enabled again or a floppy is inserted ; at this point the command will complete as usual, even after several seconds/minutes of delay. NOTE [NP] : Although the doc says a new type I/II/III command can't be started while the busy bit is set, it's in fact possible to do it under certain conditions. As seen in the loader of 'The Overdrive Demos' by Phalanx, the 'restore' command should be replaced by a 'seek' command when it occurs in less than 900 cycles. A possible explanation from this could be seen in the WD1772's documentation, where the specific type I command is in fact checked after the 'prepare + spinup' sequence in the state machine diagram. Similarly, we can guess that a type II command can be replaced by another type II as long as the 'prepare + spinup + head settle' sequence is not over (this was not tested on real HW) NOTE [NP] : As verified on a real STF, when reading DMA status at $ff8606 or DMA sector count at $ff8604, the unused bits are not set to 0, but they contain the value from the latest read/write made at $ff8604 when accessing FDC or HDC registers. Moreover, it's not possible to read DMA sector count, so we return the lowest 8 bits from the latest access at $ff8604. Cycles and wait states : ------------------------ As verified on a real STF, reading or writing to $ff8604 or $ff8606 can add some 4 cycles wait state. lea $ffff8604,a3 lea $ffff8606,a4 move.w (a4),d0 ; dma status motorola=8 stf=8 move.w #$90,(a4) ; dma ctrl sector count motorola=12 stf=12+4 move.w (a3),d0 ; read sector count / fdc reg motorola=8 stf=8 move.w #1,(a3) ; write sector count motorola=12 stf=12+4 move.w #$80,(a4) ; dma ctrl status/cmd reg motorola=12 stf=12+4 move.w (a3),d0 ; read fdc reg motorola=8 stf=8+4 move.w #$d0,(a3) ; write fdc reg motorola=12 stf=12+4 -> All accesses take 4 extra cycles, except reading DMA status and reading DMA sector count (which can't really be read, see note above) Detecting disk changes : ------------------------ 3'1/2 floppy drives include a 'DSKCHG' signal on pin 34 to detect when a disk was changed. Unfortunately on ST, this signal is not connected. Nevertheless, it's possible to detect a disk was inserted or ejected by looking at the 'WPT' signal which tells if a disk is write protected or not (but this method has some limitations and doesn't work in all cases). The following values of the WPT signal were measured with a custom program when ejecting/inserting a floppy (tested on a 520 STF with a single sided drive and with a double sided drive) : - floppy with write protection OFF (write possible), WPT=0 : eject start=0 -> end=1 insert start=1 -> end=0 - floppy with write protection ON (write not possible), WPT=1 : eject start=1 -> end=1 insert start=1 -> end=1 As can be seen, when a disk is write protected (WPT=1), it is not possible to detect the transition between inserting and ejecting, WPT will always be 1. The TOS monitors the changes on the WPT signal to determine if a floppy was ejected or inserted. On TOS 1.02fr, the code is located between $fc1bc4 and $fc1ebc. Every 8 VBL, one floppy drive is checked to see if the WPT signal changed. When 1 drive is connected, this means a floppy change should keep the WPT signal during at least 8 VBLs. When 2 drive are connected, each drive is checked every 16 VBLs, so the WPT signal should be kept for at least 16 VBLs. During these transition phases between "ejected" and "inserted", we force the WPT signal to 1, depending on which transition we're emulating (see Floppy_DriveTransitionUpdateState()) : - Ejecting : WPT will be X, then 1 - Inserting : WPT will be 1, then X NOTE : the TT machines have support for "real" disk change by the mean of the Disk Change (DC) signal as output on the pin 34 of the internal floppy drive. The DC signal is then inverted and connected to GPIP4 on the TT MFP. See FDC_Drive_Get_DC_signal for more details. The DC signal is only available for the internal floppy drive, not the external one. Note also that TOS 3 (for TT) doesn't use this signal, it only uses the above method monitoring the write protect signal. */ /*-----------------------------------------------------------------------*/ /* Status register */ #define FDC_STR_BIT_BUSY 0x01 #define FDC_STR_BIT_INDEX 0x02 /* type I */ #define FDC_STR_BIT_DRQ 0x02 /* type II and III */ #define FDC_STR_BIT_TR00 0x04 /* type I */ #define FDC_STR_BIT_LOST_DATA 0x04 /* type II and III */ #define FDC_STR_BIT_CRC_ERROR 0x08 #define FDC_STR_BIT_RNF 0x10 #define FDC_STR_BIT_SPIN_UP 0x20 /* type I */ #define FDC_STR_BIT_RECORD_TYPE 0x20 /* type II and III */ #define FDC_STR_BIT_WPRT 0x40 #define FDC_STR_BIT_MOTOR_ON 0x80 /* FDC Emulation commands used in FDC.Command */ enum { FDCEMU_CMD_NULL = 0, /* Type I */ FDCEMU_CMD_RESTORE, FDCEMU_CMD_SEEK, FDCEMU_CMD_STEP, /* Also used for STEP IN and STEP OUT */ /* Type II */ FDCEMU_CMD_READSECTORS, FDCEMU_CMD_WRITESECTORS, /* Type III */ FDCEMU_CMD_READADDRESS, FDCEMU_CMD_READTRACK, FDCEMU_CMD_WRITETRACK, /* Other fake commands used internally */ FDCEMU_CMD_MOTOR_STOP }; /* FDC Emulation commands' sub-states used in FDC.CommandState */ enum { FDCEMU_RUN_NULL = 0, /* Restore */ FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO, FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_SPIN_UP, FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_MOTOR_ON, FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_LOOP, FDCEMU_RUN_RESTORE_VERIFY, FDCEMU_RUN_RESTORE_VERIFY_HEAD_OK, FDCEMU_RUN_RESTORE_VERIFY_NEXT_SECTOR_HEADER, FDCEMU_RUN_RESTORE_VERIFY_CHECK_SECTOR_HEADER, FDCEMU_RUN_RESTORE_COMPLETE, /* Seek */ FDCEMU_RUN_SEEK_TOTRACK, FDCEMU_RUN_SEEK_TOTRACK_SPIN_UP, FDCEMU_RUN_SEEK_TOTRACK_MOTOR_ON, FDCEMU_RUN_SEEK_VERIFY, FDCEMU_RUN_SEEK_VERIFY_HEAD_OK, FDCEMU_RUN_SEEK_VERIFY_NEXT_SECTOR_HEADER, FDCEMU_RUN_SEEK_VERIFY_CHECK_SECTOR_HEADER, FDCEMU_RUN_SEEK_COMPLETE, /* Step / Step In / Step Out */ FDCEMU_RUN_STEP_ONCE, FDCEMU_RUN_STEP_ONCE_SPIN_UP, FDCEMU_RUN_STEP_ONCE_MOTOR_ON, FDCEMU_RUN_STEP_VERIFY, FDCEMU_RUN_STEP_VERIFY_HEAD_OK, FDCEMU_RUN_STEP_VERIFY_NEXT_SECTOR_HEADER, FDCEMU_RUN_STEP_VERIFY_CHECK_SECTOR_HEADER, FDCEMU_RUN_STEP_COMPLETE, /* Read Sector */ FDCEMU_RUN_READSECTORS_READDATA, FDCEMU_RUN_READSECTORS_READDATA_SPIN_UP, FDCEMU_RUN_READSECTORS_READDATA_HEAD_LOAD, FDCEMU_RUN_READSECTORS_READDATA_MOTOR_ON, FDCEMU_RUN_READSECTORS_READDATA_NEXT_SECTOR_HEADER, FDCEMU_RUN_READSECTORS_READDATA_CHECK_SECTOR_HEADER, FDCEMU_RUN_READSECTORS_READDATA_TRANSFER_START, FDCEMU_RUN_READSECTORS_READDATA_TRANSFER_LOOP, FDCEMU_RUN_READSECTORS_CRC, FDCEMU_RUN_READSECTORS_MULTI, FDCEMU_RUN_READSECTORS_RNF, FDCEMU_RUN_READSECTORS_COMPLETE, /* Write Sector */ FDCEMU_RUN_WRITESECTORS_WRITEDATA, FDCEMU_RUN_WRITESECTORS_WRITEDATA_SPIN_UP, FDCEMU_RUN_WRITESECTORS_WRITEDATA_HEAD_LOAD, FDCEMU_RUN_WRITESECTORS_WRITEDATA_MOTOR_ON, FDCEMU_RUN_WRITESECTORS_WRITEDATA_NEXT_SECTOR_HEADER, FDCEMU_RUN_WRITESECTORS_WRITEDATA_CHECK_SECTOR_HEADER, FDCEMU_RUN_WRITESECTORS_WRITEDATA_TRANSFER_START, FDCEMU_RUN_WRITESECTORS_WRITEDATA_TRANSFER_LOOP, FDCEMU_RUN_WRITESECTORS_CRC, FDCEMU_RUN_WRITESECTORS_MULTI, FDCEMU_RUN_WRITESECTORS_RNF, FDCEMU_RUN_WRITESECTORS_COMPLETE, /* Read Address */ FDCEMU_RUN_READADDRESS, FDCEMU_RUN_READADDRESS_SPIN_UP, FDCEMU_RUN_READADDRESS_HEAD_LOAD, FDCEMU_RUN_READADDRESS_MOTOR_ON, FDCEMU_RUN_READADDRESS_NEXT_SECTOR_HEADER, FDCEMU_RUN_READADDRESS_TRANSFER_START, FDCEMU_RUN_READADDRESS_TRANSFER_LOOP, FDCEMU_RUN_READADDRESS_RNF, FDCEMU_RUN_READADDRESS_COMPLETE, /* Read Track */ FDCEMU_RUN_READTRACK, FDCEMU_RUN_READTRACK_SPIN_UP, FDCEMU_RUN_READTRACK_HEAD_LOAD, FDCEMU_RUN_READTRACK_MOTOR_ON, FDCEMU_RUN_READTRACK_INDEX, FDCEMU_RUN_READTRACK_TRANSFER_LOOP, FDCEMU_RUN_READTRACK_COMPLETE, /* Write Track */ FDCEMU_RUN_WRITETRACK, FDCEMU_RUN_WRITETRACK_SPIN_UP, FDCEMU_RUN_WRITETRACK_HEAD_LOAD, FDCEMU_RUN_WRITETRACK_MOTOR_ON, FDCEMU_RUN_WRITETRACK_INDEX, FDCEMU_RUN_WRITETRACK_TRANSFER_LOOP, FDCEMU_RUN_WRITETRACK_COMPLETE, /* Motor Stop */ FDCEMU_RUN_MOTOR_STOP, FDCEMU_RUN_MOTOR_STOP_WAIT, FDCEMU_RUN_MOTOR_STOP_COMPLETE }; /* * Standard hardware values for the FDC. This should allow to get very good timings' emulation * when dealing with non protected disks that still require a correct speed (MSA or ST images) * * - WD1772's datasheet is based on a reference clock of 8 MHz, so delays expressed in milli-seconds * will be slightly different for the Atari ST, whose FDC's clock is around 8.021247 MHz (but this is * not really noticeable in practice, less than 0.3 %) * - DD MFM encoding defines a standard signal of 4 micro sec per bit (a possible variation of +/- 10 % * should still be possible). This means the WD1772 will read/write at 250 kbits/sec. * Taking 4 us per bit means 32 us for a full byte, and with a 8 MHz clock, 256 cycles per byte. * - The floppy drives used in the Atari ST are spinning at 300 RPM. Variations are possible, as long * as it keeps the duration of an MFM bit in the required 4 us +/- 10 % (in practice, ST drives are often * at 299-301 RPM) * - When FDC runs at 8 MHz, the 250 kbits/s and 300 RPM give 6250 bytes for a standard track * - When FDC runs at 8.021247 MHz (Atari ST), the 250.664 kbit/s and 300 RPM give 6267 bytes per track * * Notes on timings required for precise emulation : * For a standard floppy recorded with a constant speed, the FDC will take 32 microsec * to read/write 1 byte on the floppy. On STF with a 8 MHz CPU clock, this means one byte can be * transferred every 256 cpu cycles. So, to get some correct timings as required by some games' protection * we must update the emulated FDC's state every 256 cycles (it could be less frequently and still work, * due to the 16 bytes DMA FIFO that will transfer data only 16 bytes at a time, every 256*16=4096 cycles) */ #define FDC_CLOCK_STANDARD (8000000.L) /* In the WD1772's datasheet, all timings are related to a reference clock of 8 MHz */ #define FDC_DELAY_CYCLE_MFM_BYTE ( 4 * 8 * 8 ) /* 4 us per bit, 8 bits per byte, 8 MHz clock -> 256 cycles */ #define FDC_BITRATE_STANDARD 250000 /* read/write speed of the WD1772 in bits per sec */ #define FDC_RPM_STANDARD 300 /* 300 RPM or 5 spins per sec */ //#define FDC_TRACK_BYTES_STANDARD ( ( FDC_BITRATE_STANDARD / 8 ) / ( FDC_RPM_STANDARD / 60 ) ) /* 6250 bytes */ #define FDC_TRACK_BYTES_STANDARD 6268 #define FDC_TRANSFER_BYTES_US( n ) ( ( n ) * 8 * 1000000.L / FDC_BITRATE_STANDARD ) /* micro sec to read/write 'n' bytes in the WD1772 */ #define FDC_DELAY_IP_SPIN_UP 6 /* 6 index pulses to reach correct speed during spin up */ #define FDC_DELAY_IP_MOTOR_OFF 9 /* Turn off motor 9 index pulses after the last command */ #define FDC_DELAY_IP_ADDRESS_ID 5 /* 5 index pulses max when looking for next sector's address id field */ /* Delays are in micro sec */ #define FDC_DELAY_US_HEAD_LOAD ( 15 * 1000 ) /* Additional 15 ms delay to load the head in type II/III */ /* Index pulse signal remains high during 3.71 ms on each rotation ([NP] tested on my STF, can vary between 1.5 and 4 ms depending on the drive) */ #define FDC_DELAY_US_INDEX_PULSE_LENGTH ( 3.71 * 1000 ) /* Internal delays to process commands are in fdc cycles for a 8 MHz clock */ #define FDC_DELAY_CYCLE_TYPE_I_PREPARE (90*8) /* Types I commands take at least 0.09 ms to execute */ /* (~740 cpu cycles @ 8 Mhz). [NP] : this was measured on a 520 STF */ /* and avoid returning immediately when command has no effect */ #define FDC_DELAY_CYCLE_TYPE_II_PREPARE (1*8) // 65 /* Start Type II commands immediately */ #define FDC_DELAY_CYCLE_TYPE_III_PREPARE (1*8) /* Start Type III commands immediately */ #define FDC_DELAY_CYCLE_TYPE_IV_PREPARE (100*8) /* FIXME [NP] : this was not measured */ #define FDC_DELAY_CYCLE_COMMAND_COMPLETE (1*8) /* Number of cycles before going to the _COMPLETE state (~8 cpu cycles) */ #define FDC_DELAY_CYCLE_COMMAND_IMMEDIATE (0) /* Number of cycles to go immediately to another state */ /* When the drive is switched off or if there's no floppy, some commands will wait forever */ /* as they can't find the next index pulse. Instead of continuously testing if a valid drive */ /* or floppy becomes available (which would slow down emulation), we only test every 50000 FDC cycles, */ /* which shouldn't give any noticeable emulation error */ #define FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY 50000 /* Update the floppy's angular position on a regular basis to detect the index pulses */ #define FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE 500 #define FDC_DMA_SECTOR_SIZE 512 /* Sector count at $ff8606 is for 512 bytes blocks */ #define FDC_DMA_FIFO_SIZE 16 /* DMA transfers blocks of 16 bytes at a time */ #define FDC_PHYSICAL_MAX_TRACK 90 /* Head can't go beyond 90 tracks */ #define FDC_STEP_RATE ( FDC.CR & 0x03 ) /* Bits 0 and 1 of the current type I command */ int FDC_StepRate_ms[] = { 6 , 12 , 2 , 3 }; /* Controlled by bits 1 and 0 (r1/r0) in type I commands */ #define FDC_FAST_FDC_FACTOR 10 /* Divide all delays by this value when --fastfdc is used */ /* Standard ST floppies are double density ; to simulate HD or ED floppies, we use */ /* a density factor to have x2 or x4 more bytes during 1 FDC cycle */ #define FDC_DENSITY_FACTOR_DD 1 #define FDC_DENSITY_FACTOR_HD 2 /* For a HD disk, we get x2 bytes than DD */ #define FDC_DENSITY_FACTOR_ED 4 /* For a ED disk, we get x4 bytes than DD */ #define FDC_EMULATION_MODE_INTERNAL 1 /* Use fdc.c to handle emulation (ST, MSA, DIM and STX images) */ #define FDC_EMULATION_MODE_IPF 2 /* Use floppy_ipf.c to handle emulation (IPF, CTR images) */ typedef struct { /* WD1772 internal registers */ uint8_t DR; /* Data Register */ uint8_t TR; /* Track Register */ uint8_t SR; /* Sector Register */ uint8_t CR; /* Command Register */ uint8_t STR; /* Status Register */ int StepDirection; /* +1 (Step In) or -1 (Step Out) */ uint8_t SideSignal; /* Side 0 or 1 */ int DriveSelSignal; /* 0 or 1 for drive A or B ; or -1 if no drive selected */ uint8_t IRQ_Signal; /* 0 if IRQ is not set, else value depends on the source of the IRQ */ uint16_t DensityMode; /* bits 0 and 1 of $ff860e */ /* 0x00 : FDC operates in DD mode */ /* 0x03 : FDC operates in HD mode */ /* Other variables */ int Command; /* FDC emulation command currently being executed */ int CommandState; /* Current state for the running command */ uint8_t CommandType; /* Type of latest FDC command (1,2,3 or 4) */ bool ReplaceCommandPossible; /* true if the current command can be replaced by another one (see notes) */ uint8_t Status_Temp; /* Temporary content of the status register */ bool StatusTypeI; /* When true, STR will report the status of a type I command */ int IndexPulse_Counter; /* To count the number of rotations when motor is ON */ uint8_t NextSector_ID_Field_TR; /* Track value in the next ID Field after a call to FDC_NextSectorID_FdcCycles_ST() */ uint8_t NextSector_ID_Field_SR; /* Sector value in the next ID Field after a call to FDC_NextSectorID_FdcCycles_ST() */ uint8_t NextSector_ID_Field_LEN; /* Sector's length in the next ID Field after a call to FDC_NextSectorID_FdcCycles_ST() */ uint8_t NextSector_ID_Field_CRC_OK; /* CRC OK or not in the next ID Field after a call to FDC_NextSectorID_FdcCycles_ST() */ uint8_t InterruptCond; /* For a type IV force interrupt, contains the condition on the lower 4 bits */ int EmulationMode; /* FDC_EMULATION_MODE_INTERNAL or FDC_EMULATION_MODE_IPF */ } FDC_STRUCT; typedef struct { /* DMA internal registers */ uint16_t Status; uint16_t Mode; uint16_t SectorCount; int16_t BytesInSector; uint8_t FIFO[ FDC_DMA_FIFO_SIZE ]; int FIFO_Size; /* Between 0 and FDC_DMA_FIFO_SIZE */ uint32_t Address; uint16_t ff8604_recent_val; /* Most recent value read/written at $ff8604 in fdc/hdc mode (bit4=0 in $ff8606) */ /* Variables to handle our DMA buffer */ int PosInBuffer; int PosInBufferTransfer; /* FIXME REMOVE */ int BytesToTransfer; } FDC_DMA_STRUCT; typedef struct { bool Enabled; bool DiskInserted; int RPM; /* Rotation Per Minutes * 1000 */ int FloppyDensity; /* Density of the inserted floppy for current track/side */ /* 1 for DD (720 kB), 2 for HD (1.4 MB), 4 for ED (2.8 MB) */ uint8_t HeadTrack; /* Current position of the head */ // uint8_t Motor; /* State of the drive's motor : 0=OFF 1=ON */ uint8_t NumberOfHeads; /* 1 for single sided drive, 2 for double sided */ uint8_t DiskChange_signal; /* 0=disk ejected 1=disk inserted and step pulse received */ /* This signal is available on pin 34 of compatible drives */ /* and connected to the 2nd MFP of the TT */ uint64_t IndexPulse_Time; /* CyclesGlobalClockCounter value last time we had an index pulse with motor ON */ } FDC_DRIVE_STRUCT; /** * Bytes to transfer with type II/III commands are stored in this buffer * which associates a specific delay to each byte. This allows to * have a common method to transfer data from ST/MSA disk images (with fixed * timing), as well as data from STX disk images (with possible timing variations) */ typedef struct { int Size; int PosRead; struct { uint8_t Byte; uint16_t Timing; } Data [ FDC_TRACK_BYTES_STANDARD*4+1000 ]; } FDC_BUFFER_STRUCT; static FDC_STRUCT FDC; /* All variables related to the WD1772 emulation */ static FDC_DMA_STRUCT FDC_DMA; /* All variables related to the DMA transfer */ static FDC_DRIVE_STRUCT FDC_DRIVES[ MAX_FLOPPYDRIVES ]; /* A: and B: */ static FDC_BUFFER_STRUCT FDC_BUFFER; /* Buffer of Timing/Byte to transfer with the FDC */ static uint8_t DMADiskWorkSpace[ FDC_TRACK_BYTES_STANDARD*4+1000 ];/* Workspace used to transfer bytes between floppy and DMA */ /* It should be large enough to contain a whole track */ /* We use a x4 factor when we need to simulate HD and ED too */ /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static uint32_t FDC_DelayToFdcCycles ( uint32_t Delay_micro ); static uint32_t FDC_FdcCyclesToCpuCycles ( uint32_t FdcCycles ); static uint32_t FDC_CpuCyclesToFdcCycles ( uint32_t CpuCycles ); static void FDC_StartTimer_FdcCycles ( int FdcCycles , int InternalCycleOffset ); static int FDC_TransferByte_FdcCycles ( int NbBytes ); static void FDC_CRC16 ( uint8_t *buf , int nb , uint16_t *pCRC ); static void FDC_ResetDMA ( void ); static int FDC_GetEmulationMode ( void ); static void FDC_Drive_Connect_DC_signal_GPIP ( int Drive ); static void FDC_Drive_Set_DC_signal ( int Drive , uint8_t val ); static int FDC_GetSectorsPerTrack ( int Drive , int Track , int Side ); static int FDC_GetSidesPerDisk ( int Drive , int Track ); static int FDC_GetTracksPerDisk ( int Drive ); static int FDC_ComputeFloppyDensity ( uint8_t Drive , uint8_t Track , uint8_t Side ); static void FDC_UpdateFloppyDensity ( uint8_t Drive , uint8_t Track , uint8_t Side ); static uint32_t FDC_GetCyclesPerRev_FdcCycles ( int Drive ); static void FDC_IndexPulse_Update ( void ); static void FDC_IndexPulse_Init ( int Drive ); static void FDC_Update_STR ( uint8_t DisableBits , uint8_t EnableBits ); static int FDC_CmdCompleteCommon ( bool DoInt ); static bool FDC_VerifyTrack ( void ); static int FDC_UpdateMotorStop ( void ); static int FDC_UpdateRestoreCmd ( void ); static int FDC_UpdateSeekCmd ( void ); static int FDC_UpdateStepCmd ( void ); static int FDC_UpdateReadSectorsCmd ( void ); static int FDC_UpdateWriteSectorsCmd ( void ); static int FDC_UpdateReadAddressCmd ( void ); static int FDC_UpdateReadTrackCmd ( void ); static int FDC_UpdateWriteTrackCmd ( void ); static bool FDC_Set_MotorON ( uint8_t FDC_CR ); static int FDC_TypeI_Restore ( void ); static int FDC_TypeI_Seek ( void ); static int FDC_TypeI_Step ( void ); static int FDC_TypeI_StepIn ( void ); static int FDC_TypeI_StepOut ( void ); static int FDC_TypeII_ReadSector ( void ); static int FDC_TypeII_WriteSector(void); static int FDC_TypeIII_ReadAddress ( void ); static int FDC_TypeIII_ReadTrack ( void ); static int FDC_TypeIII_WriteTrack ( void ); static int FDC_TypeIV_ForceInterrupt ( void ); static int FDC_ExecuteTypeICommands ( void ); static int FDC_ExecuteTypeIICommands ( void ); static int FDC_ExecuteTypeIIICommands ( void ); static int FDC_ExecuteTypeIVCommands ( void ); static void FDC_ExecuteCommand ( void ); static void FDC_WriteSectorCountRegister ( void ); static void FDC_WriteCommandRegister ( void ); static void FDC_WriteTrackRegister ( void ); static void FDC_WriteSectorRegister ( void ); static void FDC_WriteDataRegister ( void ); static int FDC_NextSectorID_FdcCycles_ST ( uint8_t Drive , uint8_t NumberOfHeads , uint8_t Track , uint8_t Side ); static uint8_t FDC_NextSectorID_TR_ST ( void ); static uint8_t FDC_NextSectorID_SR_ST ( void ); static uint8_t FDC_NextSectorID_LEN_ST ( void ); static uint8_t FDC_NextSectorID_CRC_OK_ST ( void ); static uint8_t FDC_ReadSector_ST ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side , int *pSectorSize ); static uint8_t FDC_WriteSector_ST ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side , int SectorSize ); static uint8_t FDC_ReadAddress_ST ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side ); static uint8_t FDC_ReadTrack_ST ( uint8_t Drive , uint8_t Track , uint8_t Side ); static uint8_t FDC_WriteTrack_ST ( uint8_t Drive , uint8_t Track , uint8_t Side , int TrackSize ); /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void FDC_MemorySnapShot_Capture(bool bSave) { MemorySnapShot_Store(&FDC, sizeof(FDC)); MemorySnapShot_Store(&FDC_DMA, sizeof(FDC_DMA)); MemorySnapShot_Store(&FDC_DRIVES, sizeof(FDC_DRIVES)); MemorySnapShot_Store(&FDC_BUFFER, sizeof(FDC_BUFFER_STRUCT)); MemorySnapShot_Store(DMADiskWorkSpace, sizeof(DMADiskWorkSpace)); } /*-----------------------------------------------------------------------*/ /** * Change the color of the drive's led color in the statusbar, depending * on the state of the busy bit in STR */ void FDC_Drive_Set_BusyLed ( uint8_t STR ) { //fprintf ( stderr ,"fdc led %d %x\n" , FDC.DriveSelSignal , STR ); if ( FDC.DriveSelSignal < 0 ) return; /* no drive selected */ if ( STR & FDC_STR_BIT_BUSY ) Statusbar_SetFloppyLed ( FDC.DriveSelSignal , LED_STATE_ON_BUSY ); else Statusbar_SetFloppyLed ( FDC.DriveSelSignal , LED_STATE_ON ); } /*-----------------------------------------------------------------------*/ /** * Return a small text + length with the current values of the FDC's registers * This text is displayed in the statusbar and it looks like : * CC:xx HH:TT:SS:s * CC=command in 2 letters * xx=command in hexa * HH=physical head's position * TT=track register * SS=sector register * s=side */ int FDC_Get_Statusbar_Text ( char *text, size_t maxlen ) { uint8_t Command , Head , Track , Sector , Side; char CommandText[ 3 ]; size_t written; int Drive; Drive = FDC.DriveSelSignal; if ( Drive < 0 ) /* If no drive enabled, use drive O for Head */ Drive = 0; if ( FDC_GetEmulationMode() == FDC_EMULATION_MODE_INTERNAL ) { Command = FDC.CR; Head = FDC_DRIVES[ Drive ].HeadTrack; Track = FDC.TR; Sector = FDC.SR; Side = FDC.SideSignal; } else /* FDC_EMULATION_MODE_IPF */ { IPF_FDC_StatusBar ( &Command , &Head , &Track , &Sector , &Side ); } if ( ( Command & 0xf0 ) == 0x00 ) strcpy ( CommandText , "RE" ); /* Restore */ else if ( ( Command & 0xf0 ) == 0x10 ) strcpy ( CommandText , "SE" ); /* Seek */ else if ( ( Command & 0xe0 ) == 0x20 ) strcpy ( CommandText , "ST" ); /* Step */ else if ( ( Command & 0xe0 ) == 0x40 ) strcpy ( CommandText , "SI" ); /* Step In */ else if ( ( Command & 0xe0 ) == 0x60 ) strcpy ( CommandText , "SO" ); /* Step Out */ else if ( ( Command & 0xe0 ) == 0x80 ) strcpy ( CommandText , "RS" ); /* Read Sector */ else if ( ( Command & 0xe0 ) == 0xa0 ) strcpy ( CommandText , "WS" ); /* Write Sector */ else if ( ( Command & 0xf0 ) == 0xc0 ) strcpy ( CommandText , "RA" ); /* Read Address */ else if ( ( Command & 0xf0 ) == 0xe0 ) strcpy ( CommandText , "RT" ); /* Read Track */ else if ( ( Command & 0xf0 ) == 0xf0 ) strcpy ( CommandText , "WT" ); /* Write Track */ else strcpy ( CommandText , "FI" ); /* Force Int */ written = snprintf ( text, maxlen, "%s:%02X %02X:%02X:%02X:%d" , CommandText , Command , Head , Track , Sector , Side ); assert(written < maxlen); /* more space needs to be allocated */ return written; } /*-----------------------------------------------------------------------*/ /** * Convert a delay in micro seconds to its equivalent of fdc cycles * (delays in the WD1772 specs are relative to a 8 MHz reference clock) */ static uint32_t FDC_DelayToFdcCycles ( uint32_t Delay_micro ) { uint32_t FdcCycles; FdcCycles = (uint32_t) ( ( (uint64_t) FDC_CLOCK_STANDARD * Delay_micro ) / 1000000 ); //fprintf ( stderr , "fdc state %d delay %d us %d fdc cycles\n" , FDC.Command , Delay_micro , FdcCycles ); return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Convert a number of fdc cycles at freq MachineClocks.FDC_Freq to a number * of cpu cycles at freq MachineClocks.CPU_Freq * TODO : we use a fixed 8 MHz clock to convert cycles for our internal timers * in cycInt.c. This should be replaced some days by using MachineClocks.CPU_Freq. * (for Falcon, we multiply cycles by 2 to simulate a freq in the 8 MHz range) */ static uint32_t FDC_FdcCyclesToCpuCycles ( uint32_t FdcCycles ) { uint32_t CpuCycles; /* Our conversion expects FDC_Freq to be nearly the same as CPU_Freq (8 Mhz) */ /* but the Falcon uses a 16 MHz clock for the Ajax FDC */ /* FIXME : as stated above, this should be handled better, without involving 8 MHz CPU_Freq */ if (Config_IsMachineFalcon()) FdcCycles *= 2; /* correct delays for a 8 MHz FDC_Freq clock instead of 16 */ // CpuCycles = rint ( ( (uint64_t)FdcCycles * MachineClocks.CPU_Freq ) / MachineClocks.FDC_Freq ); CpuCycles = rint ( ( (uint64_t)FdcCycles * 8021247.L ) / MachineClocks.FDC_Freq ); CpuCycles <<= nCpuFreqShift; /* Convert to x1 or x2 or x4 cpu speed */ //fprintf ( stderr , "fdc command %d machine %d fdc cycles %d cpu cycles %d\n" , FDC.Command , ConfigureParams.System.nMachineType , FdcCycles , CpuCycles ); //if ( Delay==4104) Delay=4166; // 4166 : decade demo return CpuCycles; } /*-----------------------------------------------------------------------*/ /** * Convert a number of cpu cycles at freq MachineClocks.CPU_Freq to a number * of fdc cycles at freq MachineClocks.FDC_Freq (this is the opposite * of FDC_FdcCyclesToCpuCycles) * TODO : we use a fixed 8 MHz clock to convert cycles for our internal timers * in cycInt.c. This should be replaced some days by using MachineClocks.CPU_Freq. */ static uint32_t FDC_CpuCyclesToFdcCycles ( uint32_t CpuCycles ) { int FdcCycles; CpuCycles >>= nCpuFreqShift; /* Compensate for x2 or x4 cpu speed */ // FdcCycles = rint ( ( (uint64_t)CpuCycles * MachineClocks.FDC_Freq ) / MachineClocks.CPU_Freq ); FdcCycles = rint ( ( (uint64_t)CpuCycles * MachineClocks.FDC_Freq ) / 8021247.L ); /* Our conversion expects FDC_Freq to be nearly the same as CPU_Freq (8 Mhz) */ /* but the Falcon uses a 16 MHz clock for the Ajax FDC */ /* FIXME : as stated above, this should be handled better, without involving 8 MHz CPU_Freq */ if (Config_IsMachineFalcon()) FdcCycles /= 2; /* correct delays for a 8 MHz FDC_Freq clock instead of 16 */ //fprintf ( stderr , "fdc state %d delay %d cpu cycles %d fdc cycles\n" , FDC.Command , CpuCycles , FdcCycles ); return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Start an internal timer to handle the FDC's events. * If "fast floppy" mode is used, we speed up the timer by dividing * the number of cycles by a fixed number. */ static void FDC_StartTimer_FdcCycles ( int FdcCycles , int InternalCycleOffset ) { //fprintf ( stderr , "fdc start timer %d cycles\n" , FdcCycles ); if ( ( ConfigureParams.DiskImage.FastFloppy ) && ( FdcCycles > FDC_FAST_FDC_FACTOR ) ) FdcCycles /= FDC_FAST_FDC_FACTOR; CycInt_AddRelativeInterruptWithOffset ( FDC_FdcCyclesToCpuCycles ( FdcCycles ) , INT_CPU_CYCLE , INTERRUPT_FDC , InternalCycleOffset ); } /*-----------------------------------------------------------------------*/ /** * Return the number of FDC cycles required to read/write 'nb' bytes * This function will always be called when FDC.DriveSelSignal >= 0, as * there's no case where we transfer bytes if no drive is enabled. * We take "FloppyDensity" into account to simulate faster transfer for HD/ED floppies. * * 2015/10/23 [NP] As seen in the 'Bird Mad Girl Show' demo, it's possible to get * FDC.DriveSelSignal < 0 once a transfer was started (for example, read sector * will complete successfully). So we use DD by default in that case. */ static int FDC_TransferByte_FdcCycles ( int NbBytes ) { //fprintf ( stderr , "fdc state %d transfer %d bytes\n" , FDC.Command , NbBytes ); if ( FDC.DriveSelSignal < 0 ) { /* Drive was unselected during the transfer : assume DD for the rest of the bytes */ return ( NbBytes * FDC_DELAY_CYCLE_MFM_BYTE ) / FDC_DENSITY_FACTOR_DD; } return ( NbBytes * FDC_DELAY_CYCLE_MFM_BYTE ) / FDC_GetFloppyDensity ( FDC.DriveSelSignal ); } /*-----------------------------------------------------------------------*/ /** * Compute the CRC16 of 'nb' bytes stored in 'buf'. */ static void FDC_CRC16 ( uint8_t *buf , int nb , uint16_t *pCRC ) { int i; crc16_reset ( pCRC ); for ( i=0 ; i>8 , *pCRC & 0xff ); } /*-----------------------------------------------------------------------*/ /** * Init variables used in FDC and DMA emulation */ void FDC_Init ( void ) { int i; LOG_TRACE ( TRACE_FDC , "fdc init\n" ); for ( i=0 ; i0, so high byte of ff8604_recent_val will be * updated only in that case. */ void FDC_DMA_FIFO_Push ( uint8_t Byte ) { uint32_t Address; //fprintf ( stderr , "dma push pos=%d byte=%x %lld\n" , FDC_DMA.FIFO_Size , Byte , CyclesGlobalClockCounter ); /* Store the byte that was just read from FDC's Data Register */ FDC_DMA.ff8604_recent_val = ( FDC_DMA.ff8604_recent_val & 0xff00 ) | Byte; if ( FDC_DMA.SectorCount == 0 ) { //FDC_Update_STR ( 0 , FDC_STR_BIT_LOST_DATA ); /* If DMA is OFF, data are lost -> Not on the ST */ FDC_SetDMAStatus ( true ); /* Set DMA error (bit 0) */ return; } FDC_SetDMAStatus ( false ); /* No DMA error (bit 0) */ FDC_DMA.FIFO [ FDC_DMA.FIFO_Size++ ] = Byte; if ( FDC_DMA.FIFO_Size < FDC_DMA_FIFO_SIZE ) /* FIFO is not full yet */ return; /* FIFO full : transfer data from FIFO to RAM and update DMA address */ Address = FDC_GetDMAAddress(); STMemory_SafeCopy ( Address , FDC_DMA.FIFO , FDC_DMA_FIFO_SIZE , "FDC DMA push to fifo" ); FDC_WriteDMAAddress ( Address + FDC_DMA_FIFO_SIZE ); FDC_DMA.FIFO_Size = 0; /* FIFO is now empty again */ /* When the FIFO transfers data to RAM it takes 4 cycles per word and the CPU is stalled during this time */ M68000_AddCycles_CE ( 4 * FDC_DMA_FIFO_SIZE / 2 ); /* 32 cycles */ /* For the MegaSTE, using the FDC DMA will flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); /* Store the last word that was just transferred by the DMA */ FDC_DMA.ff8604_recent_val = ( FDC_DMA.FIFO [ FDC_DMA_FIFO_SIZE-2 ] << 8 ) | FDC_DMA.FIFO [ FDC_DMA_FIFO_SIZE-1 ]; /* Update Sector Count */ FDC_DMA.BytesInSector -= FDC_DMA_FIFO_SIZE; if ( FDC_DMA.BytesInSector <= 0 ) { FDC_DMA.SectorCount--; FDC_DMA.BytesInSector = FDC_DMA_SECTOR_SIZE; } } /*-----------------------------------------------------------------------*/ /** * Get a byte from the DMA's FIFO buffer (write to disk). * If the buffer is empty and DMA is ON, load 16 bytes in the FIFO from DMA's address. * * NOTE [NP] : on a real ST, there're two 16 byte DMA FIFO, this allows to refill one FIFO * while the other FIFO is used to transfer data to the FDC. We don't emulate this at the * moment, as it doesn't cause any problem (when a DMA is set to write mode, we would need * to prefill 32 bytes instead of 16 bytes as we do now) * * NOTE [NP] : as with FDC_DMA_FIFO_Push, this also changes the unused bits at $ff8606 */ uint8_t FDC_DMA_FIFO_Pull ( void ) { uint32_t Address; uint8_t Byte; //fprintf ( stderr , "fifo pull %d %d %d\n" , FDC_DMA.BytesToTransfer , FDC_DMA.BytesInSector , FDC_DMA.SectorCount ); if ( FDC_DMA.SectorCount == 0 ) { //FDC_Update_STR ( 0 , FDC_STR_BIT_LOST_DATA ); /* If DMA is OFF, data are lost -> Not on the ST */ FDC_SetDMAStatus ( true ); /* Set DMA error (bit 0) */ return 0; /* Write a '0' byte when dma is off */ } FDC_SetDMAStatus ( false ); /* No DMA error (bit 0) */ if ( FDC_DMA.FIFO_Size > 0 ) /* FIFO is not empty yet */ Byte = FDC_DMA.FIFO [ FDC_DMA_FIFO_SIZE - ( FDC_DMA.FIFO_Size-- ) ]; /* return byte at pos 0, 1, .., 15 */ else { /* FIFO empty : transfer data from RAM to FIFO and update DMA address */ Address = FDC_GetDMAAddress(); memcpy ( FDC_DMA.FIFO , &STRam[ Address ] , FDC_DMA_FIFO_SIZE );/* TODO : check we read from a valid RAM location ? */ FDC_WriteDMAAddress ( Address + FDC_DMA_FIFO_SIZE ); FDC_DMA.FIFO_Size = FDC_DMA_FIFO_SIZE - 1; /* FIFO is now full again (minus the byte we will return below) */ /* When the FIFO reads data from RAM it takes 4 cycles per word and the CPU is stalled during this time */ M68000_AddCycles_CE ( 4 * FDC_DMA_FIFO_SIZE / 2 ); /* 32 cycles */ /* For the MegaSTE, using the FDC DMA will flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); /* Store the last word that was just transferred by the DMA */ FDC_DMA.ff8604_recent_val = ( FDC_DMA.FIFO [ FDC_DMA_FIFO_SIZE-2 ] << 8 ) | FDC_DMA.FIFO [ FDC_DMA_FIFO_SIZE-1 ]; /* Update Sector Count */ FDC_DMA.BytesInSector -= FDC_DMA_FIFO_SIZE; if ( FDC_DMA.BytesInSector < 0 ) { FDC_DMA.SectorCount--; FDC_DMA.BytesInSector = FDC_DMA_SECTOR_SIZE; } Byte = FDC_DMA.FIFO [ 0 ]; /* return the 1st byte we just transferred in the FIFO */ } /* Store the byte that will be written to FDC's Data Register */ FDC_DMA.ff8604_recent_val = ( FDC_DMA.ff8604_recent_val & 0xff00 ) | Byte; return Byte; } /*-----------------------------------------------------------------------*/ /** * Return the value of FDC_DMA.SectorCount (used in floppy_ipf.c) */ int FDC_DMA_GetSectorCount ( void ) { return FDC_DMA.SectorCount; } /*-----------------------------------------------------------------------*/ /** * Reset the buffer used to transfer data between the FDC and the DMA */ void FDC_Buffer_Reset ( void ) { FDC_BUFFER.Size = 0; FDC_BUFFER.PosRead = 0; } /*-----------------------------------------------------------------------*/ /** * Add a byte to the FDC transfer buffer, using a specific timing */ void FDC_Buffer_Add_Timing ( uint8_t Byte , uint16_t Timing ) { FDC_BUFFER.Data[ FDC_BUFFER.Size ].Byte = Byte; FDC_BUFFER.Data[ FDC_BUFFER.Size ].Timing = Timing; FDC_BUFFER.Size++; } /*-----------------------------------------------------------------------*/ /** * Add a byte to the FDC transfer buffer, using a default timing */ void FDC_Buffer_Add ( uint8_t Byte ) { FDC_Buffer_Add_Timing ( Byte , FDC_TransferByte_FdcCycles ( 1 ) ); } /*-----------------------------------------------------------------------*/ /** * Return the timing needed to transfer the Byte at the current position */ uint16_t FDC_Buffer_Read_Timing ( void ) { //fprintf ( stderr , "read timing %d %x\n" , FDC_BUFFER.PosRead , FDC_BUFFER.Data[ FDC_BUFFER.PosRead ].Timing ); return FDC_BUFFER.Data[ FDC_BUFFER.PosRead ].Timing; } /*-----------------------------------------------------------------------*/ /** * Return the Byte at the current position and increment position */ uint8_t FDC_Buffer_Read_Byte ( void ) { //fprintf ( stderr , "read byte %d %x\n" , FDC_BUFFER.PosRead , FDC_BUFFER.Data[ FDC_BUFFER.PosRead ].Byte ); return FDC_BUFFER.Data[ FDC_BUFFER.PosRead++ ].Byte; } /*-----------------------------------------------------------------------*/ /** * Return the Byte at a given position */ uint8_t FDC_Buffer_Read_Byte_pos ( int pos ) { //fprintf ( stderr , "read byte pos %d %x\n" , pos , FDC_BUFFER.Data[ pos ].Byte ); return FDC_BUFFER.Data[ pos ].Byte; } /*-----------------------------------------------------------------------*/ /** * Return the number of bytes stored in FDC_BUFFER */ int FDC_Buffer_Get_Size ( void ) { return FDC_BUFFER.Size; } /*-----------------------------------------------------------------------*/ /** * Return the mode to handle a read/write in $ff86xx * Depending on the images inserted in each floppy drive and on the * selected drive, we must choose which fdc emulation should be used. * Possible emulation methods are 'internal' or 'ipf'. * To avoid mixing emulation methods on both drives when possible (which * could lead to inconstancies when precise timings are required), we also * use the IPF mode for an empty drive if the other drive contains an IPF * image. * If no drive is selected, we must use the previous mode (before drives were * unselected), not the internal one : in case some commands are sent when * drives are deselected and the drive was in IPF mode, we must send * the command to IPF to ensure no command are lost if the drive is selected again * (eg : D0 command in "Saint Dragon" IPF) */ static int FDC_GetEmulationMode ( void ) { int Mode; Mode = FDC.EmulationMode; /* Default to previous mode if no drive is selected */ /* Check drive 1 first */ if ( ( PSGRegisters[PSG_REG_IO_PORTA] & 0x04 ) == 0 ) { if ( EmulationDrives[ 1 ].ImageType == FLOPPY_IMAGE_TYPE_IPF ) Mode = FDC_EMULATION_MODE_IPF; else if ( ( EmulationDrives[ 1 ].ImageType == FLOPPY_IMAGE_TYPE_NONE ) /* Drive 1 is empty */ && ( EmulationDrives[ 0 ].ImageType == FLOPPY_IMAGE_TYPE_IPF ) ) /* Drive 0 is an IPF image */ Mode = FDC_EMULATION_MODE_IPF; /* Use IPF for drive 1 too */ else Mode = FDC_EMULATION_MODE_INTERNAL; } /* If both drive 0 and drive 1 are enabled, we keep only drive 0 to choose emulation's mode */ if ( ( PSGRegisters[PSG_REG_IO_PORTA] & 0x02 ) == 0 ) { if ( EmulationDrives[ 0 ].ImageType == FLOPPY_IMAGE_TYPE_IPF ) Mode = FDC_EMULATION_MODE_IPF; else if ( ( EmulationDrives[ 0 ].ImageType == FLOPPY_IMAGE_TYPE_NONE ) /* Drive 0 is empty */ && ( EmulationDrives[ 1 ].ImageType == FLOPPY_IMAGE_TYPE_IPF ) ) /* Drive 1 is an IPF image */ Mode = FDC_EMULATION_MODE_IPF; /* Use IPF for drive 0 too */ else Mode = FDC_EMULATION_MODE_INTERNAL; } FDC.EmulationMode = Mode; //fprintf ( stderr , "emul mode %x %d\n" , PSGRegisters[PSG_REG_IO_PORTA] & 0x06 , Mode ); // return FDC_EMULATION_MODE_INTERNAL; // return FDC_EMULATION_MODE_IPF; return Mode; } /*-----------------------------------------------------------------------*/ /** * Update the FDC's internal variables on a regular basis. * To get correct accuracy, this should be called every 200-500 FDC cycles * So far, we only need to update the index position for the valid * drive/floppy ; updating every 500 cycles is enough for this case. */ static void FDC_UpdateAll(void) { FDC_IndexPulse_Update (); } /*-----------------------------------------------------------------------*/ /** * This function is used to enable/disable a drive when * using the UI or command line parameters */ void FDC_Drive_Set_Enable ( int Drive , bool value ) { LOG_TRACE ( TRACE_FDC , "fdc enable drive=%d %s\n" , Drive , value?"on":"off" ); if ( ( Drive >= 0 ) && ( Drive < MAX_FLOPPYDRIVES ) ) FDC_DRIVES[ Drive ].Enabled = value; /* Also forward change to IPF emulation */ IPF_Drive_Set_Enable ( Drive , value ); } /*-----------------------------------------------------------------------*/ /** * This function is used to choose single sided or double sided for a drive * when using the UI or command line parameters */ void FDC_Drive_Set_NumberOfHeads ( int Drive , int NbrHeads ) { LOG_TRACE ( TRACE_FDC , "fdc set nbr heads drive=%d %d\n" , Drive , NbrHeads ); if ( ( Drive >= 0 ) && ( Drive < MAX_FLOPPYDRIVES ) ) FDC_DRIVES[ Drive ].NumberOfHeads = NbrHeads; /* Also forward change to IPF emulation */ IPF_Drive_Set_DoubleSided ( Drive , NbrHeads==2 ? true : false ); } /*-----------------------------------------------------------------------*/ /** * Get the value of the Disk Change (DC) signal available on pin 34 of some * floppy drives (used in TT emulation) and update the TT GPIP register. * * This signal is active low unless a disk is inserted and a STEP pulse is received * and the drive is selected : * 0 = drive is selected and a disk was ejected (ie drive is empty) * 1 = drive is not selected or a disk was inserted and a step pulse received * * DC signal was only available on the TT machines and in that case it's connected * to the TT MFP on GPIP4 (the signal is inverted before going to GPIP4) */ static void FDC_Drive_Connect_DC_signal_GPIP ( int Drive ) { uint8_t state; if ( FDC.DriveSelSignal != Drive ) state = 1; /* drive is not selected */ else state = FDC_DRIVES[ Drive ].DiskChange_signal; /* DC signal is inverted before going into GPIP4 */ if ( state == 1 ) state = MFP_GPIP_STATE_LOW; else state = MFP_GPIP_STATE_HIGH; MFP_GPIP_Set_Line_Input ( pMFP_TT , MFP_TT_GPIP_LINE_DC , state ); } /*-----------------------------------------------------------------------*/ /** * Update the DC signal for a drive and update GPIP for TT machines * - set DC=0 when disk is ejected * - set DC=1 when a step pulse (Type I command) is received and a floppy * is inserted and the drive is selected */ static void FDC_Drive_Set_DC_signal ( int Drive , uint8_t val ) { FDC_DRIVES[ Drive ].DiskChange_signal = val; if ( Config_IsMachineTT() && ( Drive == 0 ) ) FDC_Drive_Connect_DC_signal_GPIP ( Drive ); } /*-----------------------------------------------------------------------*/ /** * This function is called when a floppy is inserted in a drive * using the UI or command line parameters */ void FDC_InsertFloppy ( int Drive ) { LOG_TRACE ( TRACE_FDC , "fdc insert drive=%d\n" , Drive ); if ( ( Drive >= 0 ) && ( Drive < MAX_FLOPPYDRIVES ) ) { FDC_DRIVES[ Drive ].DiskInserted = true; if ( ( FDC.STR & FDC_STR_BIT_MOTOR_ON ) != 0 ) /* If we insert a floppy while motor is already on, we must */ FDC_IndexPulse_Init ( Drive ); /* init the index pulse's position */ else FDC_DRIVES[ Drive ].IndexPulse_Time = 0; /* Index pulse's position not known yet */ /* Update floppy's density for this drive */ FDC_UpdateFloppyDensity ( Drive , FDC_DRIVES[ Drive ].HeadTrack , FDC.SideSignal ); } } /*-----------------------------------------------------------------------*/ /** * This function is called when a floppy is ejected from a drive * using the UI or command line parameters */ void FDC_EjectFloppy ( int Drive ) { LOG_TRACE ( TRACE_FDC , "fdc eject drive=%d\n" , Drive ); if ( ( Drive >= 0 ) && ( Drive < MAX_FLOPPYDRIVES ) ) { FDC_DRIVES[ Drive ].DiskInserted = false; FDC_DRIVES[ Drive ].IndexPulse_Time = 0; /* Stop counting index pulses on an empty drive */ /* Set the Disk Change signal to "ejected" */ FDC_Drive_Set_DC_signal ( Drive , FDC_DC_SIGNAL_EJECTED ); } } /*-----------------------------------------------------------------------*/ /** * Handle a write in the IO_PORTA register $E through $ff8802. Only bits * 0-2 are available here, others are masked to 0. * bit 0 : side select * bit 1-2 : drive select * * - For internal FDC emulation, we init index pulse if the active drive * changed * - We also forward the change to IPF emulation, as it doesn't have direct access * to this IO_PORTA register. * * If both drives are selected, we keep only drive 0 */ void FDC_SetDriveSide ( uint8_t io_porta_old , uint8_t io_porta_new ) { int Side; int Drive; if ( io_porta_old == io_porta_new ) return; /* No change */ Side = ( (~io_porta_new) & 0x01 ); /* Side 0 or 1 */ Drive = -1; /* By default, don't select any drive */ /* Check drive 1 first */ if ( ( io_porta_new & 0x04 ) == 0 ) Drive = 1; /* Select drive 1 */ /* If both drive 0 and drive 1 are enabled, we keep only drive 0 as newdrive */ if ( ( io_porta_new & 0x02 ) == 0 ) Drive = 0; /* Select drive 0 (and un-select drive 1 if set above) */ LOG_TRACE(TRACE_FDC, "fdc change drive/side io_porta_old=0x%x io_porta_new=0x%x side %d->%d drive %d->%d VBL=%d HBL=%d\n" , io_porta_old , io_porta_new , FDC.SideSignal , Side , FDC.DriveSelSignal , Drive , nVBLs , nHBL ); if ( FDC.DriveSelSignal != Drive ) { if ( FDC.DriveSelSignal >= 0 ) /* A drive was previously enabled */ FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time = 0; /* Stop counting index pulses on the previous drive */ if ( Drive >= 0 ) /* A new drive is enabled */ { if ( ( FDC_DRIVES[ Drive ].DiskInserted ) /* If there's a disk in the new drive and motor is already */ && ( ( FDC.STR & FDC_STR_BIT_MOTOR_ON ) != 0 ) ) /* on, we must init the index pulse's position */ FDC_IndexPulse_Init ( Drive ); else FDC_DRIVES[ Drive ].IndexPulse_Time = 0; /* Index pulse's position not known yet */ } } FDC.SideSignal = Side; FDC.DriveSelSignal = Drive; /* Update floppy's density for this drive */ if ( Drive >= 0 ) FDC_UpdateFloppyDensity ( Drive , FDC_DRIVES[ Drive ].HeadTrack , FDC.SideSignal ); /* Also forward change to IPF emulation */ IPF_SetDriveSide ( io_porta_old , io_porta_new ); } /*-----------------------------------------------------------------------*/ /** * Return the number of sectors for track/side for the current floppy in a drive * TODO [NP] : this function calls Floppy_FindDiskDetails which handles only ST/MSA * disk images so far, so this implies all tracks have in fact the same number * of sectors (we don't use Track and Side for now) * Drive should be a valid drive (0 or 1) */ static int FDC_GetSectorsPerTrack ( int Drive , int Track , int Side ) { uint16_t SectorsPerTrack; if (EmulationDrives[ Drive ].bDiskInserted) { Floppy_FindDiskDetails ( EmulationDrives[ Drive ].pBuffer , EmulationDrives[ Drive ].nImageBytes , &SectorsPerTrack , NULL ); return SectorsPerTrack; } else return 0; } /*-----------------------------------------------------------------------*/ /** * Return the number of sides for a track for the current floppy in a drive * Drive should be a valid drive (0 or 1) */ static int FDC_GetSidesPerDisk ( int Drive , int Track ) { uint16_t SidesPerDisk; if (EmulationDrives[ Drive ].bDiskInserted) { Floppy_FindDiskDetails ( EmulationDrives[ Drive ].pBuffer , EmulationDrives[ Drive ].nImageBytes , NULL , &SidesPerDisk ); return SidesPerDisk; /* 1 or 2 */ } else return 0; } /*-----------------------------------------------------------------------*/ /** * Return the number of tracks for the current floppy in a drive * For ST/MSA, this assumes both sides have the same number of tracks * Drive should be a valid drive (0 or 1) */ static int FDC_GetTracksPerDisk ( int Drive ) { uint16_t SectorsPerTrack; uint16_t SidesPerDisk; if (EmulationDrives[ Drive ].bDiskInserted) { Floppy_FindDiskDetails ( EmulationDrives[ Drive ].pBuffer , EmulationDrives[ Drive ].nImageBytes , &SectorsPerTrack , &SidesPerDisk ); return ( ( EmulationDrives[Drive].nImageBytes / NUMBYTESPERSECTOR ) / SectorsPerTrack ) / SidesPerDisk; } else return 0; } /*-----------------------------------------------------------------------*/ /** * Return the number of bytes in a raw track * For ST/MSA disk images, we consider all the tracks have the same size. * To simulate HD/ED floppies, we multiply the size by a density factor (x2 or x4). * Drive should be a valid drive (0 or 1) */ int FDC_GetBytesPerTrack ( uint8_t Drive , uint8_t Track , uint8_t Side ) { int SectorsPerTrack; int TrackSize; if ( EmulationDrives[ Drive ].bDiskInserted ) { /* If the inserted disk is an STX, we use the supplied track length */ if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_STX ) return FDC_GetBytesPerTrack_STX ( Drive , Track , Side ); SectorsPerTrack = FDC_GetSectorsPerTrack ( Drive , FDC_DRIVES[ Drive ].HeadTrack , FDC.SideSignal ); if ( SectorsPerTrack >= 36 ) TrackSize = FDC_TRACK_BYTES_STANDARD * 4; /* Simulate a ED disk, 36 sectors or more */ else if ( SectorsPerTrack >= 18 ) TrackSize = FDC_TRACK_BYTES_STANDARD * 2; /* Simulate a HD disk, between 18 and 36 sectors */ else TrackSize = FDC_TRACK_BYTES_STANDARD; /* Normal DD disk */ } else TrackSize = FDC_TRACK_BYTES_STANDARD; /* No disk, default to DD disk */ return TrackSize; } /*-----------------------------------------------------------------------*/ /** * Return a density factor for the current track/side of the floppy in a drive * A DD track is usually FDC_TRACK_BYTES_STANDARD bytes, a HD track is * twice that number and an ED track is 4 times that number. * As number of bytes can vary slightly depending on mastering process and RPM * speed, we don't use x1, x2 and x4 directly but we use a margin such as x1.5 * to choose between DD and HD and x3 to choose between HD and ED * * We return a factor of x1, x2 or x4 for DD, HD or ED * Drive should be a valid drive (0 or 1) */ static int FDC_ComputeFloppyDensity ( uint8_t Drive , uint8_t Track , uint8_t Side ) { int TrackSize; TrackSize = FDC_GetBytesPerTrack ( Drive , Track , Side ); if ( TrackSize > 3 * FDC_TRACK_BYTES_STANDARD ) return FDC_DENSITY_FACTOR_ED; /* Simulate a ED disk */ else if ( TrackSize > 1.5 * FDC_TRACK_BYTES_STANDARD ) return FDC_DENSITY_FACTOR_HD; /* Simulate a HD disk */ else return FDC_DENSITY_FACTOR_DD; /* Normal DD disk */ } /*-----------------------------------------------------------------------*/ /** * Update the density value for the inserted floppy depending on the density * of the current track/side * This function should be called each time track/side are changed and before * reading/writing bytes. */ static void FDC_UpdateFloppyDensity ( uint8_t Drive , uint8_t Track , uint8_t Side ) { FDC_DRIVES[ Drive ].FloppyDensity = FDC_ComputeFloppyDensity ( Drive , Track , Side ); //fprintf ( stderr , "fdc update density drive=%d track=0x%x side=%d density=%d\n" , Drive , Track, Side , FDC_DRIVES[ Drive ].FloppyDensity ); } /*-----------------------------------------------------------------------*/ /** * Return the latest Density value set for a drive */ int FDC_GetFloppyDensity ( uint8_t Drive ) { return FDC_DRIVES[ Drive ].FloppyDensity; } /*-----------------------------------------------------------------------*/ /** * Check if the emulated machine can access the floppy in Drive depending * on its density (DD/HD/ED) * - For MegaSTE, TT and Falcon, we use the content of $ff860e to compare the * floppy's density with the config of the FDC. If density doesn't match, * the FDC command will abort with RNF error * - For other machines (STF, STE) we consider as a convenience that any * DD/HD/ED floppy can be accessed, even if this is not the case on real HW * * Return true if the floppy in Drive can be accessed, else false */ int FDC_MachineHandleDensity ( uint8_t Drive ) { bool res; if ( Config_IsMachineMegaSTE() || Config_IsMachineTT() || Config_IsMachineFalcon() ) { if ( FDC_DRIVES[ Drive ].FloppyDensity == FDC_DENSITY_FACTOR_DD ) { if ( ( FDC.DensityMode & 0x03 ) == 0x00 ) /* FDC is in DD mode ? */ res = true; else res = false; } else /* HD and ED */ { if ( ( FDC.DensityMode & 0x03 ) == 0x03 ) /* FDC is in HD mode ? */ res = true; else res = false; } } else /* STF, STE */ res = true; if ( !res ) LOG_TRACE(TRACE_FDC, "fdc handle density failed, drive=%d drive_floppy_density=%d, fdc_mode=%d VBL=%d HBL=%d\n" , Drive , FDC_DRIVES[ Drive ].FloppyDensity , FDC.DensityMode , nVBLs , nHBL ); return res; } /*-----------------------------------------------------------------------*/ /** * Get the number of FDC cycles for one revolution of the floppy * RPM is already multiplied by 1000 to simulate non-integer values * (for Falcon, we divide cycles by 2 to simulate a FDC freq in the 8 MHz range) * For STX image, the number of cycles depends on drive/track/side. * Drive should be a valid drive (0 or 1) */ static uint32_t FDC_GetCyclesPerRev_FdcCycles ( int Drive ) { uint32_t FdcCyclesPerRev; assert(Drive == 0 || Drive == 1); /* If the inserted disk is an STX, we use the supplied track length to compute cycles per rev */ if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_STX ) return FDC_GetCyclesPerRev_FdcCycles_STX ( Drive , FDC_DRIVES[ Drive ].HeadTrack , FDC.SideSignal ); /* Assume a standard length for all tracks for ST/MSA images */ FdcCyclesPerRev = (uint64_t)(MachineClocks.FDC_Freq * 1000.L) / ( FDC_DRIVES[ Drive ].RPM / 60 ); /* Our conversion expects FDC_Freq to be nearly the same as CPU_Freq (8 Mhz) */ /* but the Falcon uses a 16 MHz clock for the Ajax FDC */ /* FIXME : this should be handled better, without involving 8 MHz CPU_Freq */ if (Config_IsMachineFalcon()) FdcCyclesPerRev /= 2; /* correct delays for a 8 MHz FDC_Freq clock instead of 16 */ return FdcCyclesPerRev; } /*-----------------------------------------------------------------------*/ /** * If some valid drive/floppy are available and the motor signal is on, * update the current angular position for the drive and check if * a new index pulse was reached. Increase Index Pulse counter in that case. * * This function should be called at least every 500 FDC cycles when motor * is ON to get good accuracy. * * [NP] TODO : should we have 2 different Index Pulses for each side or do they * happen at the same time ? */ static void FDC_IndexPulse_Update(void) { uint32_t FdcCyclesPerRev; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); //fprintf ( stderr , "update index drive=%d side=%d counter=%d VBL=%d HBL=%d\n" , FDC.DriveSelSignal , FDC.SideSignal , FDC.IndexPulse_Counter , nVBLs , nHBL ); if ( ( FDC.STR & FDC_STR_BIT_MOTOR_ON ) == 0 ) return; /* Motor is OFF, nothing to update */ if ( ( FDC.DriveSelSignal < 0 ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].DiskInserted ) ) return; /* No valid drive/floppy, nothing to update */ if ( FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time == 0 ) /* No reference Index Pulse for this drive */ FDC_IndexPulse_Init ( FDC.DriveSelSignal ); /* (could be the case after a 'reset') */ FdcCyclesPerRev = FDC_GetCyclesPerRev_FdcCycles ( FDC.DriveSelSignal ); if ( CyclesGlobalClockCounter - FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time >= FDC_FdcCyclesToCpuCycles ( FdcCyclesPerRev ) ) { /* Store new position of the most recent Index Pulse */ FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time += FDC_FdcCyclesToCpuCycles ( FdcCyclesPerRev ); FDC.IndexPulse_Counter++; LOG_TRACE(TRACE_FDC, "fdc update index drive=%d side=%d counter=%d ip_time=%"PRIu64" VBL=%d HBL=%d\n" , FDC.DriveSelSignal , FDC.SideSignal , FDC.IndexPulse_Counter , FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time , nVBLs , nHBL ); if ( FDC.InterruptCond & FDC_INTERRUPT_COND_IP ) /* Do we have a "force int on index pulse" command running ? */ { LOG_TRACE(TRACE_FDC, "fdc type IV force int on index, set irq VBL=%d video_cyc=%d %d@%d pc=%x\n" , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_SetIRQ ( FDC_IRQ_SOURCE_INDEX ); } } } /*-----------------------------------------------------------------------*/ /** * When motor is started, the position of the next index pulse will be random, * as we don't know how much the floppy rotated when the motor was stopped or * the floppy was inserted. * We compute a random position in the "past" (less than one revolution) * and use it as a reference to detect the next index pulse. * */ static void FDC_IndexPulse_Init ( int Drive ) { uint32_t FdcCyclesPerRev; uint64_t IndexPulse_Time; FdcCyclesPerRev = FDC_GetCyclesPerRev_FdcCycles ( Drive ); IndexPulse_Time = CyclesGlobalClockCounter - Hatari_rand() % FDC_FdcCyclesToCpuCycles ( FdcCyclesPerRev ); if ( IndexPulse_Time <= 0 ) /* Should not happen (only if FDC_IndexPulse_Init is */ IndexPulse_Time = 1; /* called just after emulation starts) */ FDC_DRIVES[ Drive ].IndexPulse_Time = IndexPulse_Time; LOG_TRACE(TRACE_FDC, "fdc init index drive=%d side=%d counter=%d ip_time=%"PRIu64" VBL=%d HBL=%d\n" , Drive , FDC.SideSignal , FDC.IndexPulse_Counter , FDC_DRIVES[ Drive ].IndexPulse_Time , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Return the number of FDC cycles since the previous index pulse signal * for the current drive. * If there's no available drive/floppy (i.e. no index), we return -1 */ int FDC_IndexPulse_GetCurrentPos_FdcCycles ( uint32_t *pFdcCyclesPerRev ) { uint32_t FdcCyclesPerRev; uint32_t CpuCyclesSinceIndex; if ( ( FDC.DriveSelSignal < 0 ) || ( FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time == 0 ) ) return -1; FdcCyclesPerRev = FDC_GetCyclesPerRev_FdcCycles ( FDC.DriveSelSignal ); CpuCyclesSinceIndex = CyclesGlobalClockCounter - FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time; if ( pFdcCyclesPerRev ) *pFdcCyclesPerRev = FdcCyclesPerRev; //fprintf ( stderr , "current pos %d %lld %d %lld\n" , FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time , // FdcCyclesPerRev , CyclesGlobalClockCounter - FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time ); return FDC_CpuCyclesToFdcCycles ( CpuCyclesSinceIndex ); } /*-----------------------------------------------------------------------*/ /** * Return the current position in the track relative to the index pulse. * For standard floppy, this is a number of bytes in the range [0,6250[ * If there's no available drive/floppy and no index, we return -1 * To simulate HD/ED floppies, we multiply the number of bytes by a density factor. */ int FDC_IndexPulse_GetCurrentPos_NbBytes ( void ) { int FdcCyclesSinceIndex; FdcCyclesSinceIndex = FDC_IndexPulse_GetCurrentPos_FdcCycles ( NULL ); if ( FdcCyclesSinceIndex < 0 ) /* No drive/floppy available at the moment */ return -1; //fprintf ( stderr , "fdc index current pos new=%d\n" , FdcCyclesSinceIndex / FDC_DELAY_CYCLE_MFM_BYTE ); return FdcCyclesSinceIndex * FDC_GetFloppyDensity ( FDC.DriveSelSignal ) / FDC_DELAY_CYCLE_MFM_BYTE; } /*-----------------------------------------------------------------------*/ /** * Return the current state of the index pulse signal. * The signal goes to 1 when reaching the index pulse location and remain * to 1 during 1.5 ms (approx 46 bytes). * During the rest of the track, the signal will be 0 (it will also be 0 * if the drive if OFF or empty) */ int FDC_IndexPulse_GetState ( void ) { int state; int FdcCyclesSinceIndex; FdcCyclesSinceIndex = FDC_IndexPulse_GetCurrentPos_FdcCycles ( NULL ); state = 0; if ( ( FdcCyclesSinceIndex >= 0 ) /* We have a valid drive/floppy */ && ( (uint32_t)FdcCyclesSinceIndex < FDC_DelayToFdcCycles ( FDC_DELAY_US_INDEX_PULSE_LENGTH ) ) ) state = 1; //fprintf ( stderr , "fdc index state 2 pos pos=%d state=%d\n" , FdcCyclesSinceIndex , state ); return state; } /*-----------------------------------------------------------------------*/ /** * Return the number of FDC cycles before reaching the next index pulse signal. * If there's no available drive/floppy and no index, we return -1 */ int FDC_NextIndexPulse_FdcCycles ( void ) { uint32_t FdcCyclesPerRev; int FdcCyclesSinceIndex; int res; FdcCyclesSinceIndex = FDC_IndexPulse_GetCurrentPos_FdcCycles ( &FdcCyclesPerRev ); if ( FdcCyclesSinceIndex < 0 ) /* No drive/floppy available at the moment */ return -1; res = FdcCyclesPerRev - FdcCyclesSinceIndex; /* If the next IP is in 0 or 1 cycle, we consider this is a rounding error */ /* and we wait for one full revolution (this can happen in Force Int on Index Pulse */ /* when we call FDC_NextIndexPulse_FdcCycles in a loop) */ if ( res <= 1 ) res = FdcCyclesPerRev; // TODO : 0 should be allowed //fprintf ( stderr , "fdc next index current pos new=%d\n" , res ); return res; } /*-----------------------------------------------------------------------*/ /** * Set the IRQ signal * This is called either on command completion, or when an index pulse * is received or when the "force interrupt immediate" command is used. * This function can also be called from the HDC emulation or from another * FDC emulation module (IPF for example) * * NOTE : although high/1 on the IRQ pin of the FDC means an interrupt is * requested, this signal is inverted before going into MFP's GPIP5. * So, we must set the line to low/0 to request an interrupt. */ void FDC_SetIRQ ( uint8_t IRQ_Source ) { if ( FDC.IRQ_Signal != 0 ) /* Don't set MFP's IRQ again if already set */ { LOG_TRACE(TRACE_FDC, "fdc set irq, irq 0x%x already set VBL=%d HBL=%d\n" , FDC.IRQ_Signal , nVBLs , nHBL ); } else { /* Acknowledge in MFP circuit, pass bit, enable, pending */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_FDC_HDC , MFP_GPIP_STATE_LOW ); LOG_TRACE(TRACE_FDC, "fdc set irq 0x%x source 0x%x VBL=%d HBL=%d\n" , FDC.IRQ_Signal , IRQ_Source , nVBLs , nHBL ); } /* If IRQ comes from HDC or other sources (IPF), we don't need */ /* to handle the forced IRQ case used in FDC */ if ( IRQ_Source == FDC_IRQ_SOURCE_HDC ) FDC.IRQ_Signal = FDC_IRQ_SOURCE_HDC; else if ( IRQ_Source == FDC_IRQ_SOURCE_OTHER ) FDC.IRQ_Signal = FDC_IRQ_SOURCE_OTHER; else /* IRQ comes from FDC */ { FDC.IRQ_Signal &= ~( FDC_IRQ_SOURCE_HDC | FDC_IRQ_SOURCE_OTHER ); FDC.IRQ_Signal |= IRQ_Source; } } /*-----------------------------------------------------------------------*/ /** * Reset the IRQ signal ; in case the source of the interrupt is also * a "force interrupt immediate" command, the IRQ signal should not be cleared * (only command 0xD0 or any new command followed by a read of status reg * can clear the forced IRQ) * * NOTE : although low/0 on the IRQ pin of the FDC means interrupt is * cleared, this signal is inverted before going into MFP's GPIP5. * So, we must set the line to high/1 to clear interrupt request. */ void FDC_ClearIRQ ( void ) { if ( ( FDC.IRQ_Signal & FDC_IRQ_SOURCE_FORCED ) == 0 ) { FDC.IRQ_Signal = 0; MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_FDC_HDC , MFP_GPIP_STATE_HIGH ); LOG_TRACE(TRACE_FDC, "fdc clear irq VBL=%d HBL=%d\n" , nVBLs , nHBL ); } else { FDC.IRQ_Signal &= FDC_IRQ_SOURCE_FORCED; /* Clear all sources except 'forced irq' and keep IRQ set in MFP */ LOG_TRACE(TRACE_FDC, "fdc clear irq not done, irq forced VBL=%d HBL=%d\n" , nVBLs , nHBL ); } } void FDC_ClearHdcIRQ(void) { FDC.IRQ_Signal &= ~FDC_IRQ_SOURCE_HDC; if (FDC.IRQ_Signal == 0) { MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_FDC_HDC , MFP_GPIP_STATE_HIGH ); } } /*-----------------------------------------------------------------------*/ /** * Handle the current FDC command. * We use a timer to go from one state to another to emulate the different * phases of an FDC command. * When the command completes (success or failure), FDC.Command will be * set to FDCEMU_CMD_NULL. Until then, this function will be called to * handle each state of the command and the corresponding delay in micro * seconds. * This handler is called after a first delay corresponding to the prepare * delay and the eventual motor on delay. * Once we reach this point, the current command can not be replaced by * another command (except 'Force Interrupt') */ void FDC_InterruptHandler_Update ( void ) { int FdcCycles = 0; int PendingCyclesOver; /* Number of internal cycles we went over for this timer ( <= 0 ) */ /* Used to restart the next timer and keep a constant rate (important for DMA transfers) */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ //fprintf ( stderr , "fdc int handler %lld delay %d\n" , CyclesGlobalClockCounter, PendingCyclesOver ); CycInt_AcknowledgeInterrupt(); do /* We loop as long as FdcCycles == 0 (immediate change of state) */ { /* Update FDC's internal variables */ FDC_UpdateAll (); /* Is FDC active? */ if (FDC.Command!=FDCEMU_CMD_NULL) { /* Which command are we running ? */ switch(FDC.Command) { case FDCEMU_CMD_RESTORE: FdcCycles = FDC_UpdateRestoreCmd(); break; case FDCEMU_CMD_SEEK: FdcCycles = FDC_UpdateSeekCmd(); break; case FDCEMU_CMD_STEP: FdcCycles = FDC_UpdateStepCmd(); break; case FDCEMU_CMD_READSECTORS: FdcCycles = FDC_UpdateReadSectorsCmd(); break; case FDCEMU_CMD_WRITESECTORS: FdcCycles = FDC_UpdateWriteSectorsCmd(); break; case FDCEMU_CMD_READADDRESS: FdcCycles = FDC_UpdateReadAddressCmd(); break; case FDCEMU_CMD_READTRACK: FdcCycles = FDC_UpdateReadTrackCmd(); break; case FDCEMU_CMD_WRITETRACK: FdcCycles = FDC_UpdateWriteTrackCmd(); break; case FDCEMU_CMD_MOTOR_STOP: FdcCycles = FDC_UpdateMotorStop(); break; } } } while ( ( FDC.Command != FDCEMU_CMD_NULL ) && ( FdcCycles == 0 ) ); if ( FDC.Command != FDCEMU_CMD_NULL ) { FDC_StartTimer_FdcCycles ( FdcCycles , -PendingCyclesOver ); } } /*-----------------------------------------------------------------------*/ /** * Return the type of a command, based on the upper bits of CR */ uint8_t FDC_GetCmdType ( uint8_t CR ) { if ( ( CR & 0x80 ) == 0 ) /* Type I - Restore, Seek, Step, Step-In, Step-Out */ return 1; else if ( ( CR & 0x40 ) == 0 ) /* Type II - Read Sector, Write Sector */ return 2; else if ( ( CR & 0xf0 ) != 0xd0 ) /* Type III - Read Address, Read Track, Write Track */ return 3; else /* Type IV - Force Interrupt */ return 4; } /*-----------------------------------------------------------------------*/ /** * Update the FDC's Status Register. * All bits in DisableBits are cleared in STR, then all bits in EnableBits * are set in STR. */ static void FDC_Update_STR ( uint8_t DisableBits , uint8_t EnableBits ) { FDC.STR &= (~DisableBits); /* Clear bits in DisableBits */ FDC.STR |= EnableBits; /* Set bits in EnableBits */ FDC_Drive_Set_BusyLed ( FDC.STR ); //fprintf ( stderr , "fdc str 0x%x\n" , FDC.STR ); } /*-----------------------------------------------------------------------*/ /** * Common to all commands once they're completed : * - remove busy bit * - set interrupt if necessary * - stop motor after 2 sec */ static int FDC_CmdCompleteCommon ( bool DoInt ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc complete command VBL=%d video_cyc=%d %d@%d pc=%x\n", nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( FDC_STR_BIT_BUSY , 0 ); /* Remove busy bit */ if ( DoInt ) FDC_SetIRQ ( FDC_IRQ_SOURCE_COMPLETE ); FDC.Command = FDCEMU_CMD_MOTOR_STOP; /* Fake command to stop the motor */ FDC.CommandState = FDCEMU_RUN_MOTOR_STOP; return FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } /*-----------------------------------------------------------------------*/ /** * Verify track after a type I command. * The FDC will read the first ID field of the current track and will * compare the track number in this ID field with the current Track Register. * If they don't match, we try again with the next ID field until we * reach 5 revolutions, in which case we set RNF. * * NOTE [NP] : in the case of Hatari when using ST/MSA images, the track is always the correct one, * so the verify will always be good (except if no disk is inserted or the physical head is * not on the same track as FDC.TR) * For STX images, verify track might fail on purpose with some protection */ static bool FDC_VerifyTrack ( void ) { int FrameCycles, HblCounterVideo, LineCycles; uint8_t Next_TR; uint8_t Next_CRC_OK; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Return false if no drive selected, or drive not enabled, or no disk in drive */ if ( ( FDC.DriveSelSignal < 0 ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].DiskInserted ) ) { LOG_TRACE(TRACE_FDC, "fdc type I verify track failed disabled/empty drive=%d VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); return false; } /* Check if the current ID Field is the one we're looking for (same track and correct CRC) */ if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) { Next_TR = FDC_NextSectorID_TR_STX (); Next_CRC_OK = FDC_NextSectorID_CRC_OK_STX (); } else { Next_TR = FDC_NextSectorID_TR_ST (); Next_CRC_OK = FDC_NextSectorID_CRC_OK_ST (); } /* ST/MSA image will always be correct, only STX can fail depending on some protections */ if ( ( Next_TR != FDC.TR ) || ( Next_CRC_OK == 0 ) ) { LOG_TRACE(TRACE_FDC, "fdc type I verify track failed ID_TR=0x%x TR=0x%x crc_ok=%d head=0x%x drive=%d VBL=%d video_cyc=%d %d@%d pc=%x\n", Next_TR , FDC.TR , Next_CRC_OK , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); return false; } /* If disk image has only one side or drive is single sided and we're trying to verify on 2nd side, then return false */ if ( ( FDC.SideSignal == 1 ) && ( ( FDC_GetSidesPerDisk ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack ) != 2 ) || ( FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads == 1 ) ) ) { LOG_TRACE(TRACE_FDC, "fdc type I verify track failed TR=0x%x head=0x%x side=1 doesn't exist drive=%d VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.TR , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); return false; } /* The track is the correct one */ return true; } /*-----------------------------------------------------------------------*/ /** * Run the 'motor stop' sequence : wait for 9 revolutions (1.8 sec) * and stop the motor. * We clear motor bit, but spinup bit remains to 1 (verified on a real STF) */ static int FDC_UpdateMotorStop ( void ) { int FdcCycles = 0; int FrameCycles, HblCounterVideo, LineCycles; /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_MOTOR_STOP: FDC.IndexPulse_Counter = 0; FDC.CommandState = FDCEMU_RUN_MOTOR_STOP_WAIT; /* Fall through to next state */ case FDCEMU_RUN_MOTOR_STOP_WAIT: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_MOTOR_OFF ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _COMPLETE state */ case FDCEMU_RUN_MOTOR_STOP_COMPLETE: Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc motor stopped VBL=%d video_cyc=%d %d@%d pc=%x\n", nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC.IndexPulse_Counter = 0; if ( FDC.DriveSelSignal >= 0 ) /* A drive was previously enabled */ FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time = 0; /* Stop counting index pulses on the drive */ FDC_Update_STR ( FDC_STR_BIT_MOTOR_ON , 0 ); /* Unset motor bit and keep spin up bit */ FDC.Command = FDCEMU_CMD_NULL; /* Motor stopped, this is the last state */ FdcCycles = 0; break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'RESTORE' command */ static int FDC_UpdateRestoreCmd ( void ) { int FdcCycles = 0; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_MOTOR_ON; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_MOTOR_ON: FDC_Update_STR ( 0 , FDC_STR_BIT_SPIN_UP ); /* At this point, spin up sequence is ok */ FDC.ReplaceCommandPossible = false; /* The FDC will try 255 times to reach track 0 using step out signals */ /* If track 0 signal is not detected after 255 attempts, the command is interrupted */ /* and FDC_STR_BIT_RNF is set in the Status Register. */ /* This can happen if no drive is selected or if the selected drive is disabled */ /* TR should be set to 255 once the spin-up sequence is made and the command */ /* can't be interrupted anymore by another command (else TR value will be wrong */ /* for other type I commands) */ FDC.TR = 0xff; FDC.CommandState = FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_LOOP; /* Fall through to the _LOOP state */ case FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO_LOOP: if ( FDC.TR == 0 ) /* Track 0 not reached after 255 attempts ? */ { /* (this can happen if the drive is disabled) */ FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); FDC_Update_STR ( FDC_STR_BIT_TR00 , 0 ); /* Unset bit TR00 */ FdcCycles = FDC_CmdCompleteCommon( true ); break; } if ( ( FDC.DriveSelSignal < 0 ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) || ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack != 0 ) ) /* Are we at track zero ? */ { FDC_Update_STR ( FDC_STR_BIT_TR00 , 0 ); /* Unset bit TR00 */ FDC.TR--; /* One less attempt */ if ( ( FDC.DriveSelSignal >= 0 ) && ( FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) ) { FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack--; /* Move physical head only if an enabled drive is selected */ FDC_UpdateFloppyDensity ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); } FdcCycles = FDC_DelayToFdcCycles ( FDC_StepRate_ms[ FDC_STEP_RATE ] * 1000 ); } else /* Drive is enabled and head is at track 0 */ { FDC_Update_STR ( 0 , FDC_STR_BIT_TR00 ); /* Set bit TR00 */ FDC.TR = 0; /* Update Track Register to 0 */ FDC.CommandState = FDCEMU_RUN_RESTORE_VERIFY; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_RESTORE_VERIFY: if ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) { FDC.CommandState = FDCEMU_RUN_RESTORE_VERIFY_HEAD_OK; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ } else { FDC.CommandState = FDCEMU_RUN_RESTORE_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } break; case FDCEMU_RUN_RESTORE_VERIFY_HEAD_OK: FDC.IndexPulse_Counter = 0; /* Head OK, fall through and look for sector header */ case FDCEMU_RUN_RESTORE_VERIFY_NEXT_SECTOR_HEADER: /* If 'verify' doesn't succeed after 5 revolutions, we abort with RNF */ if ( FDC.IndexPulse_Counter >= FDC_DELAY_IP_ADDRESS_ID ) { LOG_TRACE(TRACE_FDC, "fdc type I restore track=%d drive=%d verify RNF VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); /* Set RNF bit */ FDC.CommandState = FDCEMU_RUN_RESTORE_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; break; } if ( FDC.DriveSelSignal < 0 ) /* No drive selected */ FdcCycles = -1; else if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FdcCycles = FDC_NextSectorID_FdcCycles_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); else FdcCycles = FDC_NextSectorID_FdcCycles_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); if ( FdcCycles < 0 ) { FDC.CommandState = FDCEMU_RUN_RESTORE_VERIFY_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { /* Read bytes to reach the next sector's ID field and skip 10 more bytes to read the whole ID field */ FdcCycles += FDC_TransferByte_FdcCycles ( 10 ); /* Add delay to read 3xA1, FE, ID field */ FDC.CommandState = FDCEMU_RUN_RESTORE_VERIFY_CHECK_SECTOR_HEADER; } break; case FDCEMU_RUN_RESTORE_VERIFY_CHECK_SECTOR_HEADER: /* Check if the current ID Field matches the track number */ if ( FDC_VerifyTrack () ) { FDC_Update_STR ( FDC_STR_BIT_RNF , 0 ); /* Track is correct, remove RNF bit */ FDC.CommandState = FDCEMU_RUN_RESTORE_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } else { /* Verify failed with this ID field ; check the next one */ FDC.CommandState = FDCEMU_RUN_RESTORE_VERIFY_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_RESTORE_COMPLETE: FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'SEEK' command */ static int FDC_UpdateSeekCmd ( void ) { int FdcCycles = 0; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_SEEK_TOTRACK: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_SEEK_TOTRACK_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_SEEK_TOTRACK_MOTOR_ON; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_SEEK_TOTRACK_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_SEEK_TOTRACK_MOTOR_ON: FDC_Update_STR ( 0 , FDC_STR_BIT_SPIN_UP ); /* At this point, spin up sequence is ok */ FDC.ReplaceCommandPossible = false; if ( FDC.TR == FDC.DR ) /* Are we at the selected track ? */ { FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } else { if ( FDC.DR < FDC.TR ) /* Set StepDirection to the correct value */ FDC.StepDirection = -1; else FDC.StepDirection = 1; /* Move head by one track depending on FDC.StepDirection and update Track Register */ FDC.TR += FDC.StepDirection; FdcCycles = FDC_DelayToFdcCycles ( FDC_StepRate_ms[ FDC_STEP_RATE ] * 1000 ); FDC_Update_STR ( FDC_STR_BIT_TR00 , 0 ); /* By default, unset bit TR00 */ /* Check / move physical head only if an enabled drive is selected */ if ( ( FDC.DriveSelSignal >= 0 ) && ( FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) ) { if ( ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == FDC_PHYSICAL_MAX_TRACK ) && ( FDC.StepDirection == 1 ) ) { FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No delay if trying to go after max track */ } else if ( ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == 0 ) && ( FDC.StepDirection == -1 ) ) { FDC.TR = 0; /* If we reach track 0, we stop there */ FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } else { FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack += FDC.StepDirection; /* Move physical head */ FDC_UpdateFloppyDensity ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); } if ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == 0 ) FDC_Update_STR ( 0 , FDC_STR_BIT_TR00 ); /* Set bit TR00 */ } } break; case FDCEMU_RUN_SEEK_VERIFY: if ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) { FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY_HEAD_OK; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ } else { FDC.CommandState = FDCEMU_RUN_SEEK_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } break; case FDCEMU_RUN_SEEK_VERIFY_HEAD_OK: FDC.IndexPulse_Counter = 0; /* Head OK, fall through and look for sector header */ case FDCEMU_RUN_SEEK_VERIFY_NEXT_SECTOR_HEADER: /* If 'verify' doesn't succeed after 5 revolutions, we abort with RNF */ if ( FDC.IndexPulse_Counter >= FDC_DELAY_IP_ADDRESS_ID ) { LOG_TRACE(TRACE_FDC, "fdc type I seek track=%d drive=%d verify RNF VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); /* Set RNF bit */ FDC.CommandState = FDCEMU_RUN_SEEK_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; break; } if ( FDC.DriveSelSignal < 0 ) /* No drive selected */ FdcCycles = -1; else if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FdcCycles = FDC_NextSectorID_FdcCycles_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); else FdcCycles = FDC_NextSectorID_FdcCycles_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); if ( FdcCycles < 0 ) { FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { /* Read bytes to reach the next sector's ID field and skip 10 more bytes to read the whole ID field */ FdcCycles += FDC_TransferByte_FdcCycles ( 10 ); /* Add delay to read 3xA1, FE, ID field */ FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY_CHECK_SECTOR_HEADER; } break; case FDCEMU_RUN_SEEK_VERIFY_CHECK_SECTOR_HEADER: /* Check if the current ID Field matches the track number */ if ( FDC_VerifyTrack () ) { FDC_Update_STR ( FDC_STR_BIT_RNF , 0 ); /* Track is correct, remove RNF bit */ FDC.CommandState = FDCEMU_RUN_SEEK_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } else { /* Verify failed with this ID field ; check the next one */ FDC.CommandState = FDCEMU_RUN_SEEK_VERIFY_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_SEEK_COMPLETE: FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'STEP' command */ static int FDC_UpdateStepCmd ( void ) { int FdcCycles = 0; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_STEP_ONCE: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_STEP_ONCE_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_STEP_ONCE_MOTOR_ON; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_STEP_ONCE_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_STEP_ONCE_MOTOR_ON: FDC_Update_STR ( 0 , FDC_STR_BIT_SPIN_UP ); /* At this point, spin up sequence is ok */ FDC.ReplaceCommandPossible = false; /* Move head by one track depending on FDC.StepDirection */ if ( FDC.CR & FDC_COMMAND_BIT_UPDATE_TRACK ) FDC.TR += FDC.StepDirection; /* Update Track Register */ FdcCycles = FDC_DelayToFdcCycles ( FDC_StepRate_ms[ FDC_STEP_RATE ] * 1000 ); FDC_Update_STR ( FDC_STR_BIT_TR00 , 0 ); /* By default, unset bit TR00 */ /* Check / move physical head only if an enabled drive is selected */ if ( ( FDC.DriveSelSignal >= 0 ) && ( FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) ) { if ( ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == FDC_PHYSICAL_MAX_TRACK ) && ( FDC.StepDirection == 1 ) ) FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No delay if trying to go after max track */ else if ( ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == 0 ) && ( FDC.StepDirection == -1 ) ) FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No delay if trying to go before track 0 */ else { FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack += FDC.StepDirection; /* Move physical head */ FDC_UpdateFloppyDensity ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); } if ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == 0 ) FDC_Update_STR ( 0 , FDC_STR_BIT_TR00 ); /* Set bit TR00 */ } FDC.CommandState = FDCEMU_RUN_STEP_VERIFY; break; case FDCEMU_RUN_STEP_VERIFY: if ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) { FDC.CommandState = FDCEMU_RUN_STEP_VERIFY_HEAD_OK; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ } else { FDC.CommandState = FDCEMU_RUN_STEP_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } break; case FDCEMU_RUN_STEP_VERIFY_HEAD_OK: FDC.IndexPulse_Counter = 0; /* Head OK, fall through and look for sector header */ case FDCEMU_RUN_STEP_VERIFY_NEXT_SECTOR_HEADER: /* If 'verify' doesn't succeed after 5 revolutions, we abort with RNF */ if ( FDC.IndexPulse_Counter >= FDC_DELAY_IP_ADDRESS_ID ) { LOG_TRACE(TRACE_FDC, "fdc type I step track=%d drive=%d verify RNF VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); /* Set RNF bit */ FDC.CommandState = FDCEMU_RUN_STEP_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; break; } if ( FDC.DriveSelSignal < 0 ) /* No drive selected */ FdcCycles = -1; else if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FdcCycles = FDC_NextSectorID_FdcCycles_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); else FdcCycles = FDC_NextSectorID_FdcCycles_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); if ( FdcCycles < 0 ) { FDC.CommandState = FDCEMU_RUN_STEP_VERIFY_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { /* Read bytes to reach the next sector's ID field and skip 10 more bytes to read the whole ID field */ FdcCycles += FDC_TransferByte_FdcCycles ( 10 ); /* Add delay to read 3xA1, FE, ID field */ FDC.CommandState = FDCEMU_RUN_STEP_VERIFY_CHECK_SECTOR_HEADER; } break; case FDCEMU_RUN_STEP_VERIFY_CHECK_SECTOR_HEADER: /* Check if the current ID Field matches the track number */ if ( FDC_VerifyTrack () ) { FDC_Update_STR ( FDC_STR_BIT_RNF , 0 ); /* Track is correct, remove RNF bit */ FDC.CommandState = FDCEMU_RUN_STEP_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } else { /* Verify failed with this ID field ; check the next one */ FDC.CommandState = FDCEMU_RUN_STEP_VERIFY_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_STEP_COMPLETE: FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'READ SECTOR/S' command */ static int FDC_UpdateReadSectorsCmd ( void ) { int FdcCycles = 0; int SectorSize; int FrameCycles, HblCounterVideo, LineCycles; uint8_t Next_TR; uint8_t Next_SR; uint8_t Next_CRC_OK; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_READSECTORS_READDATA: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_HEAD_LOAD; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_READSECTORS_READDATA_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _HEAD_LOAD state */ case FDCEMU_RUN_READSECTORS_READDATA_HEAD_LOAD: if ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) { FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_MOTOR_ON; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ break; } /* If there's no head settle, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_READSECTORS_READDATA_MOTOR_ON: FDC.ReplaceCommandPossible = false; FDC.IndexPulse_Counter = 0; FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; case FDCEMU_RUN_READSECTORS_READDATA_NEXT_SECTOR_HEADER: /* If we're looking for sector FDC.SR for more than 5 revolutions, we abort with RNF */ if ( FDC.IndexPulse_Counter >= FDC_DELAY_IP_ADDRESS_ID ) { FDC.CommandState = FDCEMU_RUN_READSECTORS_RNF; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; } if ( FDC.DriveSelSignal < 0 ) /* No drive selected */ FdcCycles = -1; else if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FdcCycles = FDC_NextSectorID_FdcCycles_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); else FdcCycles = FDC_NextSectorID_FdcCycles_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); if ( FdcCycles < 0 ) { FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { /* Read bytes to reach the next sector's ID field and skip 10 more bytes to read the whole ID field */ FdcCycles += FDC_TransferByte_FdcCycles ( 10 ); /* Add delay to read 3xA1, FE, TR, SIDE, SR, LEN, CRC1, CRC2 */ FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_CHECK_SECTOR_HEADER; } break; case FDCEMU_RUN_READSECTORS_READDATA_CHECK_SECTOR_HEADER: /* Check if the current ID Field is the one we're looking for (same track/sector and correct CRC) */ if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) { Next_TR = FDC_NextSectorID_TR_STX (); Next_SR = FDC_NextSectorID_SR_STX (); Next_CRC_OK = FDC_NextSectorID_CRC_OK_STX (); } else { Next_TR = FDC_NextSectorID_TR_ST (); Next_SR = FDC_NextSectorID_SR_ST (); Next_CRC_OK = FDC_NextSectorID_CRC_OK_ST (); } if ( ( Next_TR == FDC.TR ) && ( Next_SR == FDC.SR ) && ( Next_CRC_OK ) ) { FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_TRANSFER_START; /* Read bytes to reach the sector's data : GAP3a + GAP3b + 3xA1 + FB */ FdcCycles = FDC_TransferByte_FdcCycles ( FDC_TRACK_LAYOUT_STANDARD_GAP3a + FDC_TRACK_LAYOUT_STANDARD_GAP3b + 3 + 1 ); } else { /* This is not the ID field we're looking for ; check the next one */ FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_READSECTORS_READDATA_TRANSFER_START: /* Read a single sector into temporary buffer (512 bytes for ST/MSA) */ FDC_Buffer_Reset(); if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FDC.Status_Temp = FDC_ReadSector_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SR , FDC.SideSignal , &SectorSize ); else FDC.Status_Temp = FDC_ReadSector_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SR , FDC.SideSignal , &SectorSize ); if ( FDC.Status_Temp & FDC_STR_BIT_RNF ) /* Sector FDC.SR was not found */ { FDC.CommandState = FDCEMU_RUN_READSECTORS_RNF; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } else { if ( FDC.Status_Temp & FDC_STR_BIT_RECORD_TYPE ) FDC_Update_STR ( 0 , FDC_STR_BIT_RECORD_TYPE ); else FDC_Update_STR ( FDC_STR_BIT_RECORD_TYPE , 0 ); FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_TRANSFER_LOOP; FdcCycles = FDC_Buffer_Read_Timing (); /* Delay to transfer the first byte */ } break; case FDCEMU_RUN_READSECTORS_READDATA_TRANSFER_LOOP: /* Transfer the sector 1 byte at a time using DMA */ FDC_DMA_FIFO_Push ( FDC_Buffer_Read_Byte () ); /* Add 1 byte to the DMA FIFO */ if ( FDC_BUFFER.PosRead < FDC_Buffer_Get_Size () ) { FdcCycles = FDC_Buffer_Read_Timing (); /* Delay to transfer the next byte */ } else /* Sector transferred, check the CRC */ { FDC.CommandState = FDCEMU_RUN_READSECTORS_CRC; FdcCycles = FDC_TransferByte_FdcCycles ( 2 ); /* Read 2 bytes for CRC */ } break; case FDCEMU_RUN_READSECTORS_CRC: /* Sector completely transferred, CRC is always good for ST/MSA, but not always for STX */ if ( FDC.Status_Temp & FDC_STR_BIT_CRC_ERROR ) { LOG_TRACE(TRACE_FDC, "fdc type II read sector=%d track=0x%x side=%d drive=%d CRC VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_CRC_ERROR ); FdcCycles = FDC_CmdCompleteCommon( true ); } else { FDC.CommandState = FDCEMU_RUN_READSECTORS_MULTI; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_READSECTORS_MULTI: /* Check for multi bit */ if ( FDC.CR & FDC_COMMAND_BIT_MULTIPLE_SECTOR ) { FDC.SR++; /* Try to read next sector and set RNF if not possible */ FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA_MOTOR_ON; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; LOG_TRACE(TRACE_FDC, "fdc type II read sector with multi sector=0x%x track=0x%x side=%d drive=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR, FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } else /* Multi=0, stop here with no error */ { FDC.CommandState = FDCEMU_RUN_READSECTORS_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } break; case FDCEMU_RUN_READSECTORS_RNF: LOG_TRACE(TRACE_FDC, "fdc type II read sector=%d track=0x%x side=%d drive=%d RNF VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); FdcCycles = FDC_CmdCompleteCommon( true ); break; case FDCEMU_RUN_READSECTORS_COMPLETE: FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'WRITE SECTOR/S' command */ static int FDC_UpdateWriteSectorsCmd ( void ) { int FdcCycles = 0; int FrameCycles, HblCounterVideo, LineCycles; uint8_t Next_TR; uint8_t Next_SR; uint8_t Next_LEN; uint8_t Next_CRC_OK; uint8_t Byte; uint8_t Status; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Stop now if disk is write protected */ if ( ( FDC.DriveSelSignal >= 0 ) && ( FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) && ( FDC_DRIVES[ FDC.DriveSelSignal ].DiskInserted ) && ( Floppy_IsWriteProtected ( FDC.DriveSelSignal ) ) ) { LOG_TRACE(TRACE_FDC, "fdc type II write sector=%d track=0x%x side=%d drive=%d WPRT VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_WPRT ); /* Set WPRT bit */ FdcCycles = FDC_CmdCompleteCommon( true ); } else FDC_Update_STR ( FDC_STR_BIT_WPRT , 0 ); /* Unset WPRT bit */ /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_WRITESECTORS_WRITEDATA: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_HEAD_LOAD; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_WRITESECTORS_WRITEDATA_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _HEAD_LOAD state */ case FDCEMU_RUN_WRITESECTORS_WRITEDATA_HEAD_LOAD: if ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_MOTOR_ON; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ break; } /* If there's no head settle, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_WRITESECTORS_WRITEDATA_MOTOR_ON: FDC.ReplaceCommandPossible = false; FDC.IndexPulse_Counter = 0; FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; case FDCEMU_RUN_WRITESECTORS_WRITEDATA_NEXT_SECTOR_HEADER: /* If we're looking for sector FDC.SR for more than 5 revolutions, we abort with RNF */ if ( FDC.IndexPulse_Counter >= FDC_DELAY_IP_ADDRESS_ID ) { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_RNF; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; } if ( FDC.DriveSelSignal < 0 ) /* No drive selected */ FdcCycles = -1; else if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FdcCycles = FDC_NextSectorID_FdcCycles_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); else FdcCycles = FDC_NextSectorID_FdcCycles_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); if ( FdcCycles < 0 ) { FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { /* Read bytes to reach the next sector's ID field and skip 10 more bytes to read the whole ID field */ FdcCycles += FDC_TransferByte_FdcCycles ( 10 ); /* Add delay to read 3xA1, FE, TR, SIDE, SR, LEN, CRC1, CRC2 */ FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_CHECK_SECTOR_HEADER; } break; case FDCEMU_RUN_WRITESECTORS_WRITEDATA_CHECK_SECTOR_HEADER: /* Check if the current ID Field is the one we're looking for (same track/sector and correct CRC) */ if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) { Next_TR = FDC_NextSectorID_TR_STX (); Next_SR = FDC_NextSectorID_SR_STX (); Next_CRC_OK = FDC_NextSectorID_CRC_OK_STX (); } else { Next_TR = FDC_NextSectorID_TR_ST (); Next_SR = FDC_NextSectorID_SR_ST (); Next_CRC_OK = FDC_NextSectorID_CRC_OK_ST (); } if ( ( Next_TR == FDC.TR ) && ( Next_SR == FDC.SR ) && ( Next_CRC_OK ) ) { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_TRANSFER_START; /* Read bytes to reach the sector's data : GAP3a + GAP3b + 3xA1 + FB */ FdcCycles = FDC_TransferByte_FdcCycles ( FDC_TRACK_LAYOUT_STANDARD_GAP3a + FDC_TRACK_LAYOUT_STANDARD_GAP3b + 3 + 1 ); } else { /* This is not the ID field we're looking for ; check the next one */ FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_WRITESECTORS_WRITEDATA_TRANSFER_START: /* Write a single sector from RAM (512 bytes for ST/MSA) */ if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) Next_LEN = FDC_NextSectorID_LEN_STX (); else Next_LEN = FDC_NextSectorID_LEN_ST (); FDC_Buffer_Reset(); FDC_DMA.BytesToTransfer = 128 << ( Next_LEN & FDC_SECTOR_SIZE_MASK ); FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_TRANSFER_LOOP; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; case FDCEMU_RUN_WRITESECTORS_WRITEDATA_TRANSFER_LOOP: /* Transfer the sector 1 byte at a time using DMA */ if ( FDC_DMA.BytesToTransfer-- > 0 ) { Byte = FDC_DMA_FIFO_Pull (); /* Get 1 byte from the DMA FIFO */ //fprintf ( stderr , "byte %d %x\n" , FDC_DMA.BytesToTransfer , Byte ); FDC_Buffer_Add ( Byte ); FdcCycles = FDC_TransferByte_FdcCycles ( 1 ); } else /* Sector transferred, add the CRC */ { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_CRC; FdcCycles = FDC_TransferByte_FdcCycles ( 2 ); /* Write 2 bytes for CRC */ } break; case FDCEMU_RUN_WRITESECTORS_CRC: /* Sector completely transferred, CRC is always good for ST/MSA */ /* This is where we save the buffer to the disk image */ if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) Status = FDC_WriteSector_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SR , FDC.SideSignal , FDC_Buffer_Get_Size () ); else Status = FDC_WriteSector_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SR , FDC.SideSignal , FDC_Buffer_Get_Size () ); if ( Status & FDC_STR_BIT_RNF ) /* Sector FDC.SR was not correctly written */ { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_RNF; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } else { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_MULTI; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_WRITESECTORS_MULTI: /* Check for multi bit */ if ( FDC.CR & FDC_COMMAND_BIT_MULTIPLE_SECTOR ) { FDC.SR++; /* Try to write next sector and set RNF if not possible */ FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA_MOTOR_ON; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; LOG_TRACE(TRACE_FDC, "fdc type II write sector with multi sector=0x%x track=0x%x side=%d drive=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR, FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } else /* Multi=0, stop here with no error */ { FDC.CommandState = FDCEMU_RUN_WRITESECTORS_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } break; case FDCEMU_RUN_WRITESECTORS_RNF: LOG_TRACE(TRACE_FDC, "fdc type II write sector=%d track=0x%x side=%d drive=%d RNF VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); FdcCycles = FDC_CmdCompleteCommon( true ); break; case FDCEMU_RUN_WRITESECTORS_COMPLETE: FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'READ ADDRESS' command */ static int FDC_UpdateReadAddressCmd ( void ) { int FdcCycles = 0; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_READADDRESS: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_READADDRESS_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_READADDRESS_HEAD_LOAD; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_READADDRESS_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _HEAD_LOAD state */ case FDCEMU_RUN_READADDRESS_HEAD_LOAD: FDC.ReplaceCommandPossible = false; if ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) { FDC.CommandState = FDCEMU_RUN_READADDRESS_MOTOR_ON; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ break; } /* If there's no head settle, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_READADDRESS_MOTOR_ON: FDC.ReplaceCommandPossible = false; FDC.IndexPulse_Counter = 0; FDC.CommandState = FDCEMU_RUN_READADDRESS_NEXT_SECTOR_HEADER; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; case FDCEMU_RUN_READADDRESS_NEXT_SECTOR_HEADER: /* If we don't find a sector header after more than 5 revolutions, we abort with RNF */ if ( FDC.IndexPulse_Counter >= FDC_DELAY_IP_ADDRESS_ID ) { FDC.CommandState = FDCEMU_RUN_READADDRESS_RNF; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; break; } if ( FDC.DriveSelSignal < 0 ) /* No drive selected */ FdcCycles = -1; else if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FdcCycles = FDC_NextSectorID_FdcCycles_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); else FdcCycles = FDC_NextSectorID_FdcCycles_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal ); if ( FdcCycles < 0 ) { FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { /* Read bytes to reach the next sector's ID field */ FdcCycles += FDC_TransferByte_FdcCycles ( 4 ); /* Add delay to read 3xA1, FE */ FDC.CommandState = FDCEMU_RUN_READADDRESS_TRANSFER_START; } break; case FDCEMU_RUN_READADDRESS_TRANSFER_START: /* Read the ID field into buffer */ FDC_Buffer_Reset(); if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) FDC.Status_Temp = FDC_ReadAddress_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC_NextSectorID_SR_STX () , FDC.SideSignal ); else FDC.Status_Temp = FDC_ReadAddress_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC_NextSectorID_SR_ST () , FDC.SideSignal ); FDC.SR = FDC_Buffer_Read_Byte_pos ( 0 ); /* The 1st byte of the ID field is also copied into Sector Register */ FDC.CommandState = FDCEMU_RUN_READADDRESS_TRANSFER_LOOP; FdcCycles = FDC_Buffer_Read_Timing (); /* Delay to transfer the first byte */ break; case FDCEMU_RUN_READADDRESS_TRANSFER_LOOP: /* Transfer the ID field 1 byte at a time using DMA */ FDC_DMA_FIFO_Push ( FDC_Buffer_Read_Byte () ); /* Add 1 byte to the DMA FIFO */ if ( FDC_BUFFER.PosRead < FDC_Buffer_Get_Size () ) { FdcCycles = FDC_Buffer_Read_Timing (); /* Delay to transfer the next byte */ } else { FDC.CommandState = FDCEMU_RUN_READADDRESS_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_COMPLETE; } break; case FDCEMU_RUN_READADDRESS_RNF: LOG_TRACE(TRACE_FDC, "fdc type III read address track=0x%x side=%d drive=%d RNF VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC.DriveSelSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( 0 , FDC_STR_BIT_RNF ); FdcCycles = FDC_CmdCompleteCommon( true ); break; case FDCEMU_RUN_READADDRESS_COMPLETE: FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Run 'READ TRACK' command */ static int FDC_UpdateReadTrackCmd ( void ) { int FdcCycles = 0; int i; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Which command is running? */ switch (FDC.CommandState) { case FDCEMU_RUN_READTRACK: if ( FDC_Set_MotorON ( FDC.CR ) ) { FDC.CommandState = FDCEMU_RUN_READTRACK_SPIN_UP; FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Spin up needed */ } else { FDC.CommandState = FDCEMU_RUN_READTRACK_HEAD_LOAD; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; /* No spin up needed */ } break; case FDCEMU_RUN_READTRACK_SPIN_UP: if ( FDC.IndexPulse_Counter < FDC_DELAY_IP_SPIN_UP ) { FdcCycles = FDC_DELAY_CYCLE_REFRESH_INDEX_PULSE; /* Wait for the correct number of IP */ break; } /* If IndexPulse_Counter reached, we fall through directly to the _HEAD_LOAD state */ case FDCEMU_RUN_READTRACK_HEAD_LOAD: FDC.ReplaceCommandPossible = false; if ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) { FDC.CommandState = FDCEMU_RUN_READTRACK_MOTOR_ON; FdcCycles = FDC_DelayToFdcCycles ( FDC_DELAY_US_HEAD_LOAD ); /* Head settle delay */ break; } /* If there's no head settle, we fall through directly to the _MOTOR_ON state */ case FDCEMU_RUN_READTRACK_MOTOR_ON: FdcCycles = FDC_NextIndexPulse_FdcCycles (); /* Wait for the next index pulse */ //fprintf ( stderr , "read tr idx=%d %d\n" , FDC_IndexPulse_GetState() , FdcCycles ); if ( FdcCycles < 0 ) { FdcCycles = FDC_DELAY_CYCLE_WAIT_NO_DRIVE_FLOPPY; /* Wait for a valid drive/floppy */ } else { FDC.CommandState = FDCEMU_RUN_READTRACK_INDEX; } break; case FDCEMU_RUN_READTRACK_INDEX: /* At this point, we have a valid drive/floppy, build the track data */ FDC_Buffer_Reset(); if ( ( ( FDC.SideSignal == 1 ) /* Try to read side 1 on a disk that doesn't have 2 sides or drive is single sided */ && ( ( FDC_GetSidesPerDisk ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack ) != 2 ) || ( FDC_DRIVES[ FDC.DriveSelSignal ].NumberOfHeads == 1 ) ) ) || ( FDC_MachineHandleDensity ( FDC.DriveSelSignal ) == false ) ) /* Can't handle the floppy's density */ { LOG_TRACE(TRACE_FDC, "fdc type III read track drive=%d track=%d side=%d, side not found or wrong density VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); for ( i=0 ; i 0 ) { Byte = FDC_DMA_FIFO_Pull (); /* Get 1 byte from the DMA FIFO */ //fprintf ( stderr , "byte %d %x\n" , FDC_DMA.BytesToTransfer , Byte ); FDC_Buffer_Add ( Byte ); FdcCycles = FDC_TransferByte_FdcCycles ( 1 ); } else /* Track written */ { FDC.CommandState = FDCEMU_RUN_WRITETRACK_COMPLETE; FdcCycles = FDC_DELAY_CYCLE_COMMAND_IMMEDIATE; } break; case FDCEMU_RUN_WRITETRACK_COMPLETE: /* Track completely transferred */ /* This is where we save the buffer to the disk image */ if ( EmulationDrives[ FDC.DriveSelSignal ].ImageType == FLOPPY_IMAGE_TYPE_STX ) Status = FDC_WriteTrack_STX ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC_Buffer_Get_Size () ); else Status = FDC_WriteTrack_ST ( FDC.DriveSelSignal , FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack , FDC.SideSignal , FDC_Buffer_Get_Size () ); if ( Status & FDC_STR_BIT_LOST_DATA ) /* Error while writing */ FDC_Update_STR ( 0 , FDC_STR_BIT_LOST_DATA ); /* Set LOST_DATA bit */ FdcCycles = FDC_CmdCompleteCommon( true ); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Common to types I, II and III * * Start motor / spin up sequence if needed * Return true if spin up sequence is needed, else false */ static bool FDC_Set_MotorON ( uint8_t FDC_CR ) { int FrameCycles, HblCounterVideo, LineCycles; bool SpinUp; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); if ( ( ( FDC_CR & FDC_COMMAND_BIT_SPIN_UP ) == 0 ) /* Command wants motor's spin up */ && ( ( FDC.STR & FDC_STR_BIT_MOTOR_ON ) == 0 ) ) /* Motor on not enabled yet */ { LOG_TRACE(TRACE_FDC, "fdc start motor with spinup VBL=%d video_cyc=%d %d@%d pc=%x\n", nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); FDC_Update_STR ( FDC_STR_BIT_SPIN_UP , 0 ); /* Unset spin up bit */ FDC.IndexPulse_Counter = 0; /* Reset counter to measure the spin up sequence */ SpinUp = true; } else /* No spin up : don't add delay to start the motor */ { LOG_TRACE(TRACE_FDC, "fdc start motor without spinup VBL=%d video_cyc=%d %d@%d pc=%x\n", nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); SpinUp = false; } FDC_Update_STR ( 0 , FDC_STR_BIT_MOTOR_ON ); /* Start motor */ if ( ( FDC.DriveSelSignal < 0 ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].DiskInserted ) ) { LOG_TRACE(TRACE_FDC, "fdc start motor : no disk/drive VBL=%d video_cyc=%d %d@%d pc=%x\n", nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } else if ( FDC_DRIVES[ FDC.DriveSelSignal ].IndexPulse_Time == 0 ) FDC_IndexPulse_Init ( FDC.DriveSelSignal ); /* Index Pulse's position is random when motor starts */ return SpinUp; } /*-----------------------------------------------------------------------*/ /** * Type I Commands * * Restore, Seek, Step, Step-In and Step-Out */ /*-----------------------------------------------------------------------*/ static int FDC_TypeI_Restore(void) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type I restore spinup=%s verify=%s steprate_ms=%d drive=%d tr=0x%x head_track=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ FDC_STEP_RATE ] , FDC.DriveSelSignal , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal].HeadTrack : -1 , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to seek to track zero */ FDC.Command = FDCEMU_CMD_RESTORE; FDC.CommandState = FDCEMU_RUN_RESTORE_SEEKTOTRACKZERO; FDC_Update_STR ( FDC_STR_BIT_INDEX | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_I_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeI_Seek ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type I seek dest_track=0x%x spinup=%s verify=%s steprate_ms=%d drive=%d tr=0x%x head_track=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.DR, ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ FDC_STEP_RATE ] , FDC.DriveSelSignal , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to seek to chosen track */ FDC.Command = FDCEMU_CMD_SEEK; FDC.CommandState = FDCEMU_RUN_SEEK_TOTRACK; FDC_Update_STR ( FDC_STR_BIT_INDEX | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_I_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeI_Step ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type I step %d spinup=%s verify=%s steprate_ms=%d drive=%d tr=0x%x head_track=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.StepDirection, ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ FDC_STEP_RATE ] , FDC.DriveSelSignal , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to step (using same direction as latest seek executed, ie 'FDC.StepDirection') */ FDC.Command = FDCEMU_CMD_STEP; FDC.CommandState = FDCEMU_RUN_STEP_ONCE; FDC_Update_STR ( FDC_STR_BIT_INDEX | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_I_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeI_StepIn(void) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type I step in spinup=%s verify=%s steprate_ms=%d drive=%d tr=0x%x head_track=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ FDC_STEP_RATE ] , FDC.DriveSelSignal , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to step in (direction = +1) */ FDC.Command = FDCEMU_CMD_STEP; FDC.CommandState = FDCEMU_RUN_STEP_ONCE; FDC.StepDirection = 1; /* Increment track*/ FDC_Update_STR ( FDC_STR_BIT_INDEX | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_I_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeI_StepOut ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type I step out spinup=%s verify=%s steprate_ms=%d drive=%d tr=0x%x head_track=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ FDC_STEP_RATE ] , FDC.DriveSelSignal , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to step out (direction = -1) */ FDC.Command = FDCEMU_CMD_STEP; FDC.CommandState = FDCEMU_RUN_STEP_ONCE; FDC.StepDirection = -1; /* Decrement track */ FDC_Update_STR ( FDC_STR_BIT_INDEX | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_I_PREPARE; } /*-----------------------------------------------------------------------*/ /** * Type II Commands * * Read Sector, Write Sector */ /*-----------------------------------------------------------------------*/ static int FDC_TypeII_ReadSector ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type II read sector sector=0x%x multi=%s spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d dmasector=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR, ( FDC.CR & FDC_COMMAND_BIT_MULTIPLE_SECTOR ) ? "on" : "off" , ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , FDC.SideSignal , FDC.DriveSelSignal , FDC_DMA.SectorCount , FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to read sector(s) */ FDC.Command = FDCEMU_CMD_READSECTORS; FDC.CommandState = FDCEMU_RUN_READSECTORS_READDATA; FDC_Update_STR ( FDC_STR_BIT_DRQ | FDC_STR_BIT_LOST_DATA | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF | FDC_STR_BIT_RECORD_TYPE | FDC_STR_BIT_WPRT , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_II_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeII_WriteSector ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type II write sector sector=0x%x multi=%s spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d dmasector=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.SR, ( FDC.CR & FDC_COMMAND_BIT_MULTIPLE_SECTOR ) ? "on" : "off" , ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , FDC.SideSignal , FDC.DriveSelSignal , FDC_DMA.SectorCount, FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to write a sector(s) */ FDC.Command = FDCEMU_CMD_WRITESECTORS; FDC.CommandState = FDCEMU_RUN_WRITESECTORS_WRITEDATA; FDC_Update_STR ( FDC_STR_BIT_DRQ | FDC_STR_BIT_LOST_DATA | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF | FDC_STR_BIT_RECORD_TYPE , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_II_PREPARE; } /*-----------------------------------------------------------------------*/ /** * Type III Commands * * Read Address, Read Track, Write Track */ /*-----------------------------------------------------------------------*/ static int FDC_TypeIII_ReadAddress ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type III read address spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , FDC.SideSignal , FDC.DriveSelSignal , FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to seek to track zero */ FDC.Command = FDCEMU_CMD_READADDRESS; FDC.CommandState = FDCEMU_RUN_READADDRESS; FDC_Update_STR ( FDC_STR_BIT_DRQ | FDC_STR_BIT_LOST_DATA | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF | FDC_STR_BIT_RECORD_TYPE | FDC_STR_BIT_WPRT , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_III_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeIII_ReadTrack ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type III read track spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , FDC.SideSignal , FDC.DriveSelSignal , FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to read a single track */ FDC.Command = FDCEMU_CMD_READTRACK; FDC.CommandState = FDCEMU_RUN_READTRACK; FDC_Update_STR ( FDC_STR_BIT_DRQ | FDC_STR_BIT_LOST_DATA | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF | FDC_STR_BIT_RECORD_TYPE | FDC_STR_BIT_WPRT , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_III_PREPARE; } /*-----------------------------------------------------------------------*/ static int FDC_TypeIII_WriteTrack ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type III write track spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d addr=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", ( FDC.CR & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( FDC.CR & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , FDC.TR , FDC.DriveSelSignal >= 0 ? FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack : -1 , FDC.SideSignal , FDC.DriveSelSignal , FDC_GetDMAAddress(), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* Set emulation to write a single track */ FDC.Command = FDCEMU_CMD_WRITETRACK; FDC.CommandState = FDCEMU_RUN_WRITETRACK; FDC_Update_STR ( FDC_STR_BIT_DRQ | FDC_STR_BIT_LOST_DATA | FDC_STR_BIT_CRC_ERROR | FDC_STR_BIT_RNF | FDC_STR_BIT_RECORD_TYPE | FDC_STR_BIT_WPRT , FDC_STR_BIT_BUSY ); return FDC_DELAY_CYCLE_TYPE_III_PREPARE; } /*-----------------------------------------------------------------------*/ /** * Type IV Commands * * Force Interrupt */ /*-----------------------------------------------------------------------*/ static int FDC_TypeIV_ForceInterrupt ( void ) { int FdcCycles; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type IV force int 0x%x irq=%d index=%d VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.CR , ( FDC.CR & 0x8 ) >> 3 , ( FDC.CR & 0x4 ) >> 2 , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* If a command was running, just remove busy bit and keep the current content of Status reg */ /* If FDC was idle, the content of Status reg is forced to type I */ if ( ( FDC.STR & FDC_STR_BIT_BUSY ) == 0 ) { FDC.StatusTypeI = true; /* Starting a Force Int command when idle should set the motor bit and clear the spinup bit (verified on STF) */ FDC_Update_STR ( FDC_STR_BIT_SPIN_UP , FDC_STR_BIT_MOTOR_ON ); /* Clear spinup bit and set motor bit */ } /* Get the interrupt's condition and set IRQ accordingly */ /* Most of the time a 0xD8 command is followed by a 0xD0 command and a read STR to clear the IRQ signal */ FDC.InterruptCond = FDC.CR & 0x0f; /* Keep the 4 lowest bits */ if ( FDC.InterruptCond & FDC_INTERRUPT_COND_IMMEDIATE ) FDC_SetIRQ ( FDC_IRQ_SOURCE_FORCED ); else FDC_ClearIRQ (); /* Remove busy bit, don't change IRQ's state and stop the motor */ FdcCycles = FDC_CmdCompleteCommon( false ); return FDC_DELAY_CYCLE_TYPE_IV_PREPARE + FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Execute Type I commands */ static int FDC_ExecuteTypeICommands ( void ) { int FdcCycles = 0; FDC.CommandType = 1; FDC.StatusTypeI = true; /* Check Type I Command */ switch ( FDC.CR & 0xf0 ) { case 0x00: /* Restore */ FdcCycles = FDC_TypeI_Restore(); break; case 0x10: /* Seek */ FdcCycles = FDC_TypeI_Seek(); break; case 0x20: /* Step */ case 0x30: FdcCycles = FDC_TypeI_Step(); break; case 0x40: /* Step-In */ case 0x50: FdcCycles = FDC_TypeI_StepIn(); break; case 0x60: /* Step-Out */ case 0x70: FdcCycles = FDC_TypeI_StepOut(); break; } /* After a "STEP" command we set the Disk Change signal to "inserted" */ /* if the drive is selected and a floppy is inserted */ if ( ( FDC.DriveSelSignal >= 0 ) && ( FDC_DRIVES[ FDC.DriveSelSignal ].DiskInserted == true ) ) FDC_Drive_Set_DC_signal ( FDC.DriveSelSignal , FDC_DC_SIGNAL_INSERTED ); return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Execute Type II commands */ static int FDC_ExecuteTypeIICommands ( void ) { int FdcCycles = 0; FDC.CommandType = 2; FDC.StatusTypeI = false; /* Check Type II Command */ switch ( FDC.CR & 0xf0 ) { case 0x80: /* Read Sector multi=0*/ case 0x90: /* Read Sectors multi=1 */ FdcCycles = FDC_TypeII_ReadSector(); break; case 0xa0: /* Write Sector multi=0 */ case 0xb0: /* Write Sectors multi=1 */ FdcCycles = FDC_TypeII_WriteSector(); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Execute Type III commands */ static int FDC_ExecuteTypeIIICommands ( void ) { int FdcCycles = 0; FDC.CommandType = 3; FDC.StatusTypeI = false; /* Check Type III Command */ switch ( FDC.CR & 0xf0 ) { case 0xc0: /* Read Address */ FdcCycles = FDC_TypeIII_ReadAddress(); break; case 0xe0: /* Read Track */ FdcCycles = FDC_TypeIII_ReadTrack(); break; case 0xf0: /* Write Track */ FdcCycles = FDC_TypeIII_WriteTrack(); break; } return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Execute Type IV commands */ static int FDC_ExecuteTypeIVCommands ( void ) { int FdcCycles; FDC.CommandType = 4; FdcCycles = FDC_TypeIV_ForceInterrupt(); return FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Find FDC command type and execute * * NOTE [NP] : as verified on a real STF and contrary to what is written * in the WD1772 doc, any new command will reset the InterruptCond set by * a previous Dx command, not just a D0. * This means that a D8 command (force int) can be cancelled by a D0 command * or by any other command ; but in any case, IRQ will remain set until * status register is read or another new command is started. * -> 1st command clears force IRQ condition, 2nd command clears IRQ */ static void FDC_ExecuteCommand ( void ) { int FdcCycles; uint8_t Type; Type = FDC_GetCmdType ( FDC.CR ); /* When a new command is started, FDC's IRQ is reset (except if "force interrupt immediate" is set) */ /* If IRQ is forced but FDC_INTERRUPT_COND_IMMEDIATE is not set anymore, this means */ /* the D8 command was stopped and we can clear the forced IRQ when starting a new command */ if ( ( FDC.IRQ_Signal & FDC_IRQ_SOURCE_FORCED ) && ( ( FDC.InterruptCond & FDC_INTERRUPT_COND_IMMEDIATE ) == 0 ) ) FDC.IRQ_Signal &= ~FDC_IRQ_SOURCE_FORCED; /* Really stop the forced IRQ */ /* Starting a new type I/II/III should clear the IRQ (except when IRQ is forced) */ /* For type IV, this is handled in FDC_TypeIV_ForceInterrupt() */ if ( Type != 4 ) FDC_ClearIRQ (); /* When a new command is executed, we clear InterruptCond */ /* (not just when the new command is D0) */ /* InterruptCond is cleared here, but it might be set again just after */ /* when we call FDC_ExecuteTypeIVCommands() */ FDC.InterruptCond = 0; /* Check type of command and execute */ if ( Type == 1 ) /* Type I - Restore, Seek, Step, Step-In, Step-Out */ FdcCycles = FDC_ExecuteTypeICommands(); else if ( Type == 2 ) /* Type II - Read Sector, Write Sector */ FdcCycles = FDC_ExecuteTypeIICommands(); else if ( Type == 3 ) /* Type III - Read Address, Read Track, Write Track */ FdcCycles = FDC_ExecuteTypeIIICommands(); else /* Type IV - Force Interrupt */ FdcCycles = FDC_ExecuteTypeIVCommands(); FDC.ReplaceCommandPossible = true; /* This new command can be replaced during the prepare+spinup phase */ FDC_StartTimer_FdcCycles ( FdcCycles , 0 ); } /*-----------------------------------------------------------------------*/ /** * Write to SectorCount register $ff8604 */ static void FDC_WriteSectorCountRegister ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); FDC_DMA.SectorCount = IoMem_ReadWord(0xff8604); if (!Config_IsMachineFalcon()) FDC_DMA.SectorCount &= 0xff; LOG_TRACE(TRACE_FDC, "fdc write 8604 dma sector count=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC_DMA.SectorCount, nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } /*-----------------------------------------------------------------------*/ /** * Write to Command register $ff8604 */ static void FDC_WriteCommandRegister ( void ) { int FrameCycles, HblCounterVideo, LineCycles; uint8_t Type_new; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write 8604 command=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); /* If fdc is busy, only 'Force Interrupt' is possible */ /* [NP] : it's also possible to start a new command just after another command */ /* was started and spinup phase was not completed yet (eg Overdrive Demos by Phalanx) (see notes at the top of the file)*/ if ( FDC.STR & FDC_STR_BIT_BUSY ) { Type_new = FDC_GetCmdType ( IoMem_ReadByte(0xff8605) ); if ( Type_new == 4 ) /* 'Force Interrupt' command */ { LOG_TRACE(TRACE_FDC, "fdc write 8604 while fdc busy, current command=0x%x interrupted by command=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.CR , IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } else if ( FDC.ReplaceCommandPossible && ( ( ( Type_new == 1 ) && ( FDC.CommandType == Type_new ) ) /* Replace a type I command with a type I */ || ( ( Type_new == 2 ) && ( FDC.CommandType == Type_new ) ) ) ) /* Replace a type II command with a type II */ { LOG_TRACE(TRACE_FDC, "fdc write 8604 while fdc busy in prepare+spinup, current command=0x%x replaced by command=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC.CR , IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } else /* Other cases : new command is ignored */ { LOG_TRACE(TRACE_FDC, "fdc write 8604 fdc busy, command=0x%x ignored VBL=%d video_cyc=%d %d@%d pc=%x\n", IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); return; } } FDC.CR = IoMem_ReadByte(0xff8605); FDC_ExecuteCommand(); } /*-----------------------------------------------------------------------*/ /** * Write to Track register $ff8604 */ static void FDC_WriteTrackRegister ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write 8604 track=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoMem_ReadByte(0xff8605) , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* [NP] Contrary to what is written in the WD1772 doc, Track Register can be changed */ /* while the fdc is busy (the change will be ignored or not, depending on the current sub-state */ /* in the state machine) */ if ( FDC.STR & FDC_STR_BIT_BUSY ) { LOG_TRACE(TRACE_FDC, "fdc write 8604 fdc busy, track=0x%x may be ignored VBL=%d video_cyc=%d %d@%d pc=%x\n", IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); //return; } FDC.TR = IoMem_ReadByte(0xff8605); } /*-----------------------------------------------------------------------*/ /** * Write to Sector register $ff8604 */ static void FDC_WriteSectorRegister ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write 8604 sector=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoMem_ReadByte(0xff8605) , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* [NP] Contrary to what is written in the WD1772 doc, Sector Register can be changed */ /* while the fdc is busy (but it will have no effect once the sector's header is found) */ /* (fix Delirious Demo IV's loader, which is bugged and set SR after starting the Read Sector command) */ if ( FDC.STR & FDC_STR_BIT_BUSY ) { LOG_TRACE(TRACE_FDC, "fdc write 8604 fdc busy, sector=0x%x may be ignored VBL=%d video_cyc=%d %d@%d pc=%x\n", IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); } FDC.SR = IoMem_ReadByte(0xff8605); } /*-----------------------------------------------------------------------*/ /** * Write to Data register $ff8604 */ static void FDC_WriteDataRegister ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write 8604 data=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoMem_ReadByte(0xff8605), nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); FDC.DR = IoMem_ReadByte(0xff8605); } /*-----------------------------------------------------------------------*/ /** * Store byte in FDC/HDC registers or DMA sector count, when writing to $ff8604 * When accessing FDC/HDC registers, a copy of $ff8604 should be kept in ff8604_recent_val * to be used later when reading unused bits at $ff8604/$ff8606 * * NOTE [NP] : add 4 cycles wait state in all cases (sector count / FDC / HDC) */ void FDC_DiskController_WriteWord ( void ) { int FrameCycles, HblCounterVideo, LineCycles; int EmulationMode; int FDC_reg; if ( nIoMemAccessSize == SIZE_BYTE ) { /* This register does not like to be accessed in byte mode on a normal ST */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } M68000_WaitState(4); Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write 8604 data=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoMem_ReadWord(0xff8604), nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* Are we trying to set the DMA SectorCount ? */ if ( FDC_DMA.Mode & 0x10 ) /* Bit 4 */ { FDC_WriteSectorCountRegister(); return; } /* Store the byte that was just accessed by this write */ FDC_DMA.ff8604_recent_val = ( FDC_DMA.ff8604_recent_val & 0xff00 ) | IoMem_ReadByte(0xff8605); if ( ( FDC_DMA.Mode & 0x0008 ) == 0x0008 ) /* Is it an ACSI (or Falcon SCSI) HDC command access ? */ { /* Handle HDC access */ LOG_TRACE(TRACE_FDC, "fdc write 8604 hdc command addr=%x command=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n", FDC_DMA.Mode & 0x7 , IoMem_ReadByte(0xff8605), nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); HDC_WriteCommandByte(FDC_DMA.Mode & 0x7, IoMem_ReadByte(0xff8605)); return; } else /* It's a FDC register access */ { FDC_reg = ( FDC_DMA.Mode & 0x6 ) >> 1; /* Bits 1,2 (A0,A1) */ EmulationMode = FDC_GetEmulationMode(); if ( EmulationMode == FDC_EMULATION_MODE_INTERNAL ) { /* Update FDC's internal variables */ FDC_UpdateAll (); /* Write to FDC registers */ switch ( FDC_reg ) { case 0x0: /* 0 0 - Command register */ FDC_WriteCommandRegister(); break; case 0x1: /* 0 1 - Track register */ FDC_WriteTrackRegister(); break; case 0x2: /* 1 0 - Sector register */ FDC_WriteSectorRegister(); break; case 0x3: /* 1 1 - Data register */ FDC_WriteDataRegister(); break; } } else if ( EmulationMode == FDC_EMULATION_MODE_IPF ) { IPF_FDC_WriteReg ( FDC_reg , IoMem_ReadByte(0xff8605) ); } } } /*-----------------------------------------------------------------------*/ /** * Return FDC/HDC registers or DMA sector count when reading from $ff8604 * - When accessing FDC/HDC registers, a copy of $ff8604 should be kept in ff8604_recent_val * to be used later when reading unused bits at $ff8604/$ff8606 * - DMA sector count can't be read, this will return ff8604_recent_val (verified on a real STF) * * NOTE [NP] : add 4 cycles wait state in that case, except when reading DMA sector count */ void FDC_DiskControllerStatus_ReadWord ( void ) { uint16_t DiskControllerByte = 0; /* Used to pass back the parameter */ int FrameCycles, HblCounterVideo, LineCycles; int ForceWPRT; int EmulationMode; int FDC_reg; if (nIoMemAccessSize == SIZE_BYTE && !Config_IsMachineFalcon()) { /* This register does not like to be accessed in byte mode on a normal ST */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } /* Are we trying to read the DMA SectorCount ? */ if ( FDC_DMA.Mode & 0x10 ) /* Bit 4 */ { DiskControllerByte = FDC_DMA.ff8604_recent_val; /* As verified on real STF, DMA sector count can't be read back */ } else if ( ( FDC_DMA.Mode & 0x0008) == 0x0008) /* HDC status reg selected ? */ { M68000_WaitState(4); /* [NP] : possible, but not tested on real HW */ /* Return the HDC status byte */ DiskControllerByte = HDC_ReadCommandByte(FDC_DMA.Mode & 0x7); } else /* It's a FDC register access */ { M68000_WaitState(4); FDC_reg = ( FDC_DMA.Mode & 0x6 ) >> 1; /* Bits 1,2 (A0,A1) */ EmulationMode = FDC_GetEmulationMode(); if ( EmulationMode == FDC_EMULATION_MODE_INTERNAL ) { /* Update FDC's internal variables */ FDC_UpdateAll (); /* Read FDC registers */ switch ( FDC_reg ) { case 0x0: /* 0 0 - Status register */ /* If we report a type I status, some bits are updated in real time */ /* depending on the corresponding signals. If this is not a type I, we return STR unmodified */ /* [NP] Contrary to what is written in the WD1772 doc, the WPRT bit */ /* is updated after a Type I command */ /* (eg : Procopy or Terminators Copy 1.68 do a Restore/Seek to test WPRT) */ if ( FDC.StatusTypeI ) { /* If no drive available, FDC's input signals TR00, INDEX and WPRT are off */ if ( ( FDC.DriveSelSignal < 0 ) || ( !FDC_DRIVES[ FDC.DriveSelSignal ].Enabled ) ) FDC_Update_STR ( FDC_STR_BIT_TR00 | FDC_STR_BIT_INDEX | FDC_STR_BIT_WPRT , 0 ); else { if ( FDC_DRIVES[ FDC.DriveSelSignal ].HeadTrack == 0 ) FDC_Update_STR ( 0 , FDC_STR_BIT_TR00 ); /* Set TR00 bit2 */ else FDC_Update_STR ( FDC_STR_BIT_TR00 , 0 ); /* Unset TR00 bit2 */ if ( FDC_IndexPulse_GetState () ) FDC_Update_STR ( 0 , FDC_STR_BIT_INDEX ); /* Set INDEX bit1 */ else FDC_Update_STR ( FDC_STR_BIT_INDEX , 0 ); /* Unset INDEX bit1 */ /* For Type I, always unset CRC ERROR bit3 */ FDC_Update_STR ( FDC_STR_BIT_CRC_ERROR , 0 ); /* When there's no disk in drive, the floppy drive hardware can't see */ /* the difference with an inserted disk that would be write protected */ if ( ! FDC_DRIVES[ FDC.DriveSelSignal ].DiskInserted ) FDC_Update_STR ( 0 , FDC_STR_BIT_WPRT ); /* Set WPRT bit6 */ else if ( Floppy_IsWriteProtected ( FDC.DriveSelSignal ) ) FDC_Update_STR ( 0 , FDC_STR_BIT_WPRT ); /* Set WPRT bit6 */ else FDC_Update_STR ( FDC_STR_BIT_WPRT , 0 ); /* Unset WPRT bit6 */ /* Temporarily change the WPRT bit if we're in a transition phase */ /* regarding the disk in the drive (inserting or ejecting) */ ForceWPRT = Floppy_DriveTransitionUpdateState ( FDC.DriveSelSignal ); if ( ForceWPRT == 1 ) FDC_Update_STR ( 0 , FDC_STR_BIT_WPRT ); /* Force setting WPRT */ else if ( ForceWPRT == -1 ) FDC_Update_STR ( FDC_STR_BIT_WPRT , 0 ); /* Force clearing WPRT */ if ( ForceWPRT != 0 ) LOG_TRACE(TRACE_FDC, "force wprt=%d VBL=%d drive=%d str=%x\n", ForceWPRT==1?1:0, nVBLs, FDC.DriveSelSignal, FDC.STR ); } } DiskControllerByte = FDC.STR; /* When Status Register is read, FDC's IRQ is reset (except if "force interrupt immediate" is set) */ /* If IRQ is forced but FDC_INTERRUPT_COND_IMMEDIATE is not set anymore, this means */ /* the D8 command was stopped and we can clear the forced IRQ while reading status register */ if ( ( FDC.IRQ_Signal & FDC_IRQ_SOURCE_FORCED ) && ( ( FDC.InterruptCond & FDC_INTERRUPT_COND_IMMEDIATE ) == 0 ) ) FDC.IRQ_Signal &= ~FDC_IRQ_SOURCE_FORCED; /* Really stop the forced IRQ */ FDC_ClearIRQ (); break; case 0x1: /* 0 1 - Track register */ DiskControllerByte = FDC.TR; break; case 0x2: /* 1 0 - Sector register */ DiskControllerByte = FDC.SR; break; case 0x3: /* 1 1 - Data register */ DiskControllerByte = FDC.DR; break; } } else if ( EmulationMode == FDC_EMULATION_MODE_IPF ) { DiskControllerByte = IPF_FDC_ReadReg ( FDC_reg ); if ( ( FDC_reg == 0x0 ) && ( FDC.DriveSelSignal >= 0 ) ) /* 0 0 - Status register */ { /* Temporarily change the WPRT bit if we're in a transition phase */ /* regarding the disk in the drive (inserting or ejecting) */ ForceWPRT = Floppy_DriveTransitionUpdateState ( FDC.DriveSelSignal ); if ( ForceWPRT == 1 ) DiskControllerByte |= FDC_STR_BIT_WPRT; /* Force setting WPRT */ if ( ForceWPRT == -1 ) DiskControllerByte &= ~FDC_STR_BIT_WPRT; /* Force clearing WPRT */ if ( ForceWPRT != 0 ) LOG_TRACE(TRACE_FDC, "force wprt=%d VBL=%d drive=%d str=%x\n", ForceWPRT==1?1:0, nVBLs, FDC.DriveSelSignal, DiskControllerByte ); } } } /* Store the byte that was just returned by this read if we accessed fdc/hdc regs */ if ( ( FDC_DMA.Mode & 0x10 ) == 0 ) /* Bit 4 */ FDC_DMA.ff8604_recent_val = ( FDC_DMA.ff8604_recent_val & 0xff00 ) | ( DiskControllerByte & 0xff ); IoMem_WriteWord(0xff8604, DiskControllerByte); Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc read 8604 ctrl status=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , DiskControllerByte , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } /*-----------------------------------------------------------------------*/ /** * Write word to $ff8606 (DMA Mode Control) * * Eg. * $80 - Selects command/status register * $82 - Selects track register * $84 - Selects sector register * $86 - Selects data register * NOTE - OR above values with $100 is transfer from memory to floppy * Also if bit 4 is set, write to DMA sector count register * * NOTE [NP] : add 4 cycles wait state in that case */ void FDC_DmaModeControl_WriteWord ( void ) { uint16_t Mode_prev; /* Store previous write to 0xff8606 for 'toggle' checks */ int FrameCycles, HblCounterVideo, LineCycles; if (nIoMemAccessSize == SIZE_BYTE) { /* This register does not like to be accessed in byte mode on a normal ST */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } M68000_WaitState(4); Mode_prev = FDC_DMA.Mode; /* Store previous to check for _read/_write toggle (DMA reset) */ FDC_DMA.Mode = IoMem_ReadWord(0xff8606); /* Store to DMA Mode control */ Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write 8606 ctrl=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , FDC_DMA.Mode , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* When write to 0xff8606, check bit '8' toggle. This causes DMA status reset */ if ((Mode_prev ^ FDC_DMA.Mode) & 0x0100) FDC_ResetDMA(); if ((Mode_prev & 0xc0) != 0 && (FDC_DMA.Mode & 0xc0) == 0) HDC_DmaTransfer(); } /*-----------------------------------------------------------------------*/ /** * Read DMA Status at $ff8606 * * Only bits 0-2 are used : * Bit 0 - Error Status (0=Error) * Bit 1 - Sector Count Zero Status (0=Sector Count Zero) * Bit 2 - Data Request signal from the FDC * * NOTE [NP] : as verified on STF, bit 0 will be cleared (=error) if DMA sector count is 0 * when we get some DRQ to process. * * NOTE [NP] : on the ST, the Data Register will always be read by the DMA when the FDC's DRQ * signal is set. This means bit 2 of DMA status will be '0' nearly all the time * (as verified on STF by constantly reading DMA Status, bit 2 can be '1' during * a few cycles, before the DMA read the Data Register, but for the emulation we * consider it's always '0') * * NOTE [NP] : unused bits 3-15 are the ones from the latest $ff8604 access (verified on real STF) * * NOTE [NP] : no 4 cycles wait state in that case */ void FDC_DmaStatus_ReadWord ( void ) { if (nIoMemAccessSize == SIZE_BYTE && !Config_IsMachineFalcon()) { /* This register does not like to be accessed in byte mode on a normal ST */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } /* Update Bit1 for zero sector count */ if ( FDC_DMA.SectorCount != 0 ) FDC_DMA.Status |= 0x02; else FDC_DMA.Status &= ~0x02; /* In the case of the ST, Bit2 / DRQ is always 0 because it's handled by the DMA and its 16 bytes buffer */ /* Return Status with unused bits replaced by latest bits from $ff8604 */ IoMem_WriteWord( 0xff8606 , FDC_DMA.Status | ( FDC_DMA.ff8604_recent_val & 0xfff8 ) ); } /*-----------------------------------------------------------------------*/ /** * Read hi/med/low DMA address byte at $ff8609/0b/0d */ void FDC_DmaAddress_ReadByte ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc read dma address %x val=0x%02x address=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoAccessCurrentAddress , IoMem[ IoAccessCurrentAddress ] , FDC_GetDMAAddress() , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } /*-----------------------------------------------------------------------*/ /** * Write hi/med/low DMA address byte at $ff8609/0b/0d * * NOTE [NP] : as described by Ijor in http://atari-forum.com/viewtopic.php?f=16&t=30289 * the STF DMA address counter uses a ripple carry adder that will increment middle byte * when bit 7 of lower byte goes from 1 to 0 (same for middle/high bytes) * To avoid possible error with this carry, DMA address bytes should be written in the order * low, middle, high (as specified by Atari documentations) and not high/middle/low */ void FDC_DmaAddress_WriteByte ( void ) { uint32_t Address; uint32_t Address_old; int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write dma address %x val=0x%02x VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoAccessCurrentAddress , IoMem[ IoAccessCurrentAddress ] , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* Build up 24-bit address from hardware registers */ Address = ((uint32_t)STMemory_ReadByte(0xff8609)<<16) | ((uint32_t)STMemory_ReadByte(0xff860b)<<8) | (uint32_t)STMemory_ReadByte(0xff860d); /* On STF, DMA address uses a "ripple carry adder" which can trigger when writing to $ff860b/0d */ if ( Config_IsMachineST() ) { Address_old = FDC_GetDMAAddress(); if ( ( Address_old & 0x80 ) && !( Address & 0x80 ) ) /* Bit 7 goes from 1 to 0 */ { Address += 0x100; /* Increase middle byte (and high byte if needed) */ //fprintf ( stderr , "fdc write dma address detect ripple carry at $ff860d old=0x%x new=0x%x\n" , Address_old , Address ); LOG_TRACE(TRACE_FDC, "fdc write dma address detect ripple carry at $ff860d old=0x%x new=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , Address_old , Address , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } else if ( ( Address_old & 0x8000 ) && !( Address & 0x8000 ) ) /* Bit 15 goes from 1 to 0 */ { Address += 0x10000; /* Increase high byte */ //fprintf ( stderr , "fdc write dma address detect ripple carry at $ff860b old=0x%x new=0x%x\n" , Address_old , Address ); LOG_TRACE(TRACE_FDC, "fdc write dma address detect ripple carry at $ff860b old=0x%x new=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , Address_old , Address , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } } /* Store new address as DMA address and update $ff8609/0b/0d */ FDC_WriteDMAAddress ( Address ); } /*-----------------------------------------------------------------------*/ /** * Get DMA address used to transfer data between FDC/HDC and RAM */ uint32_t FDC_GetDMAAddress(void) { return FDC_DMA.Address; } /*-----------------------------------------------------------------------*/ /** * Write a new address to the FDC/HDC DMA address registers at $ff8909/0b/0d * As verified on real STF, DMA address high byte written at $ff8609 is masked * with 0x3f : * move.b #$ff,$ff8609 * move.b $ff8609,d0 -> d0=$3f * DMA address must also be word-aligned, low byte at $ff860d is masked with 0xfe * move.b #$ff,$ff860d * move.b $ff860d,d0 -> d0=$fe */ void FDC_WriteDMAAddress ( uint32_t Address ) { int FrameCycles, HblCounterVideo, LineCycles; uint32_t dma_mask; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc write dma address new=0x%x VBL=%d video_cyc=%d %d@%d pc=%x\n" , Address , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* Mask DMA address : */ /* - DMA address must be word-aligned, bit 0 at $ff860d is always 0 */ /* - On STF/STE machines limited to 4MB of RAM, DMA address is also limited to $3fffff */ dma_mask = 0xff00fffe | ( DMA_MaskAddressHigh() << 16 ); /* Force bit 0 to 0 */ FDC_DMA.Address = Address & dma_mask; /* Store as 24-bit address */ STMemory_WriteByte(0xff8609, FDC_DMA.Address>>16); STMemory_WriteByte(0xff860b, FDC_DMA.Address>>8); STMemory_WriteByte(0xff860d, FDC_DMA.Address); } /*-----------------------------------------------------------------------*/ /** * Return the number of FDC cycles to wait before reaching the next * sector's ID Field in the track ($A1 $A1 $A1 $FE TR SIDE SR LEN CRC1 CRC2) * If no ID Field is found before the end of the track, we use the 1st * ID Field of the track (which simulates a full spin of the floppy). * We also store the next sector's number into NextSector_ID_Field_SR, * the next track's number into NextSector_ID_Field_TR, the next sector's length * into NextSector_ID_Field_LEN and if the CRC is correct or not into * NextSector_ID_Field_CRC_OK. * This function assumes some 512 byte sectors stored in ascending * order (for ST/MSA) * If there's no available drive/floppy, we return -1 */ static int FDC_NextSectorID_FdcCycles_ST ( uint8_t Drive , uint8_t NumberOfHeads , uint8_t Track , uint8_t Side ) { int CurrentPos; int MaxSector; int TrackPos; int i; int NextSector; int NbBytes; CurrentPos = FDC_IndexPulse_GetCurrentPos_NbBytes (); if ( CurrentPos < 0 ) /* No drive/floppy available at the moment */ return -1; if ( ( Side == 1 ) && ( NumberOfHeads == 1 ) ) /* Can't read side 1 on a single sided drive */ return -1; if ( Track >= FDC_GetTracksPerDisk ( Drive ) ) /* Try to access a non existing track */ return -1; if ( FDC_MachineHandleDensity ( Drive ) == false ) /* Can't handle the floppy's density */ return -1; MaxSector = FDC_GetSectorsPerTrack ( Drive , Track , Side ); TrackPos = FDC_TRACK_LAYOUT_STANDARD_GAP1; /* Position of 1st raw sector */ TrackPos += FDC_TRACK_LAYOUT_STANDARD_GAP2; /* Position of ID Field in 1st raw sector */ /* Compare CurrentPos with each sector's position in ascending order */ for ( i=0 ; i= FDC_GetTracksPerDisk ( Drive ) ) { fprintf ( stderr , "fdc : read address drive=%d track=%d side=%d, but maxtrack=%d, return RNF\n" , Drive , Track , Side , FDC_GetTracksPerDisk ( Drive ) ); return STX_SECTOR_FLAG_RNF; /* Should not happen if FDC_NextSectorID_FdcCycles_ST succeeded before */ } p = buf_id; *p++ = 0xa1; /* SYNC bytes and IAM byte are included in the CRC */ *p++ = 0xa1; *p++ = 0xa1; *p++ = 0xfe; *p++ = Track; *p++ = Side; *p++ = Sector; *p++ = FDC_SECTOR_SIZE_512; /* ST/MSA images are 512 bytes per sector */ FDC_CRC16 ( buf_id , 8 , &CRC ); *p++ = CRC >> 8; *p++ = CRC & 0xff; /* 6 bytes per ID field, don't return the 3 x $A1 and $FE */ for ( i=4 ; i<10 ; i++ ) FDC_Buffer_Add ( buf_id[ i ] ); LOG_TRACE(TRACE_FDC, "fdc read address 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x VBL=%d video_cyc=%d %d@%d pc=%x\n", buf_id[4] , buf_id[5] , buf_id[6] , buf_id[7] , buf_id[8] , buf_id[9] , nVBLs, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC()); return 0; /* No error */ } /*-----------------------------------------------------------------------*/ /** * Read a track from a floppy image in ST format (used in type III command) * As ST images don't have gaps,sync,..., we compute a standard track based * on the current track/side. * Each byte of the track is added to the FDC buffer with a default timing * (32 microsec) * Always return 0 = OK (we fill the track buffer in all cases) */ static uint8_t FDC_ReadTrack_ST ( uint8_t Drive , uint8_t Track , uint8_t Side ) { int FrameCycles, HblCounterVideo, LineCycles; uint8_t buf_id[ 10 ]; /* 3 SYNC + IAM + TR + SIDE + SECTOR + SIZE + CRC1 + CRC2 */ uint8_t *p; uint16_t CRC; int Sector; uint8_t *pSectorData; int SectorSize; int i; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_FDC, "fdc type III read track drive=%d track=%d side=%d VBL=%d video_cyc=%d %d@%d pc=%x\n" , Drive, Track, Side, nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); /* If trying to access a non existing track, then return an empty / not formatted track */ if ( Track >= FDC_GetTracksPerDisk ( Drive ) ) { fprintf ( stderr , "fdc : read track drive=%d track=%d side=%d, but maxtrack=%d, building an unformatted track\n" , Drive , Track , Side , FDC_GetTracksPerDisk ( Drive ) ); for ( i=0 ; i> 8; /* CRC1 (write $F7) */ *p++ = CRC & 0xff; /* CRC2 */ for ( i=0 ; i<10 ; i++ ) /* Add the ID field to the track data */ FDC_Buffer_Add ( buf_id[ i ] ); for ( i=0 ; i> 8 ); /* CRC1 (write $F7) */ FDC_Buffer_Add ( CRC & 0xff ); /* CRC2 */ for ( i=0 ; i #include #if HAVE_SYS_TIME_H #include #endif #include #include #include #include #if HAVE_ZLIB_H #include #endif #if defined(WIN32) && !defined(_VCWIN_) #include #endif #include "dialog.h" #include "file.h" #include "str.h" #include "zip.h" #ifdef HAVE_FLOCK # include #endif #if defined(__APPLE__) #include #endif /*-----------------------------------------------------------------------*/ /** * Remove any '/'s from end of filenames, but keeps / intact */ void File_CleanFileName(char *pszFileName) { int len; len = strlen(pszFileName); /* Remove end slashes from filename! But / remains! Doh! */ while (len > 2 && pszFileName[--len] == PATHSEP) pszFileName[len] = '\0'; } /*-----------------------------------------------------------------------*/ /** * Add '/' to end of filename */ void File_AddSlashToEndFileName(char *pszFileName) { int len; len = strlen(pszFileName); /* Check dir/filenames */ if (len != 0) { if (pszFileName[len-1] != PATHSEP) { pszFileName[len] = PATHSEP; /* Must use end slash */ pszFileName[len+1] = '\0'; } } } /*-----------------------------------------------------------------------*/ /** * Does filename's extension match? If so, return TRUE */ bool File_DoesFileExtensionMatch(const char *pszFileName, const char *pszExtension) { if (strlen(pszFileName) < strlen(pszExtension)) return false; /* Is matching extension? */ if (!strcasecmp(&pszFileName[strlen(pszFileName)-strlen(pszExtension)], pszExtension)) return true; /* No */ return false; } /*-----------------------------------------------------------------------*/ /** * If filename's extension matches, replace it with a new extension and * copy the result in the new filename. * Return TRUE if OK */ bool File_ChangeFileExtension(const char *Filename_old, const char *Extension_old , char *Filename_new , const char *Extension_new) { if ( strlen ( Filename_old ) >= FILENAME_MAX - strlen ( Extension_new ) ) return false; /* file name is already too long */ if ( strlen ( Filename_old ) < strlen ( Extension_old ) ) return false; if ( !strcasecmp ( Filename_old + strlen(Filename_old) - strlen(Extension_old) , Extension_old ) ) { strcpy ( Filename_new , Filename_old ); strcpy ( Filename_new + strlen ( Filename_new ) - strlen ( Extension_old ) , Extension_new ); return true; } return false; } /*-----------------------------------------------------------------------*/ /** * Check if filename is from root * * Return TRUE if filename is '/', else give FALSE */ static bool File_IsRootFileName(const char *pszFileName) { if (pszFileName[0] == '\0') /* If NULL string return! */ return false; if (pszFileName[0] == PATHSEP) return true; #ifdef WIN32 if (pszFileName[1] == ':') return true; #endif #ifdef GEKKO if (strlen(pszFileName) > 2 && pszFileName[2] == ':') /* sd: */ return true; if (strlen(pszFileName) > 3 && pszFileName[3] == ':') /* fat: */ return true; if (strlen(pszFileName) > 4 && pszFileName[4] == ':') /* fat3: */ return true; #endif return false; } /*-----------------------------------------------------------------------*/ /** * Return string, to remove 'C:' part of filename */ const char *File_RemoveFileNameDrive(const char *pszFileName) { if ( (pszFileName[0] != '\0') && (pszFileName[1] == ':') ) return &pszFileName[2]; else return pszFileName; } /*-----------------------------------------------------------------------*/ /** * Check if filename end with a '/' * * Return TRUE if filename ends with '/' */ bool File_DoesFileNameEndWithSlash(char *pszFileName) { if (pszFileName[0] == '\0') /* If NULL string return! */ return false; /* Does string end in a '/'? */ if (pszFileName[strlen(pszFileName)-1] == PATHSEP) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Read file via zlib into a newly allocated buffer and return the buffer * or NULL for error. If pFileSize is non-NULL, read file size is set to that. */ #if HAVE_LIBZ uint8_t *File_ZlibRead(const char *pszFileName, long *pFileSize) { uint8_t *pFile = NULL; gzFile hGzFile; long nFileSize = 0; hGzFile = gzopen(pszFileName, "rb"); if (hGzFile != NULL) { /* Find size of file: */ do { /* Seek through the file until we hit the end... */ char tmp[1024]; if (gzread(hGzFile, tmp, sizeof(tmp)) < 0) { gzclose(hGzFile); fprintf(stderr, "Failed to read gzip file!\n"); return NULL; } } while (!gzeof(hGzFile)); nFileSize = gztell(hGzFile); gzrewind(hGzFile); /* Read in... */ pFile = malloc(nFileSize); if (pFile) nFileSize = gzread(hGzFile, pFile, nFileSize); gzclose(hGzFile); } if (pFileSize) *pFileSize = nFileSize; return pFile; } #endif /** * Read file from disk into allocated buffer and return the buffer * unmodified, or NULL for error. If pFileSize is non-NULL, read * file size is set to that. */ uint8_t *File_ReadAsIs(const char *pszFileName, long *pFileSize) { uint8_t *pFile = NULL; long FileSize = 0; FILE *hDiskFile; /* Open and read normal file */ hDiskFile = fopen(pszFileName, "rb"); if (hDiskFile != NULL) { /* Find size of file: */ if (fseek(hDiskFile, 0, SEEK_END) == 0) { FileSize = ftell(hDiskFile); if (FileSize > 0 && fseek(hDiskFile, 0, SEEK_SET) == 0) { /* Read in... */ pFile = malloc(FileSize); if (pFile) FileSize = fread(pFile, 1, FileSize, hDiskFile); } } fclose(hDiskFile); } /* Store size of file we read in (or 0 if failed) */ if (pFileSize) *pFileSize = FileSize; return pFile; /* Return to where read in/allocated */ } /** * Read file from disk into allocated buffer and return the buffer or * NULL for error. If file is missing, given additional compression * extension are checked too. If file has one of the supported * compression extension, its contents are uncompressed (in case of * ZIP, first file in it is read). If pFileSize is non-NULL, read * file size is set to that. */ uint8_t *File_Read(const char *pszFileName, long *pFileSize, const char * const ppszExts[]) { char *filepath = NULL; uint8_t *pFile = NULL; long FileSize = 0; /* Does the file exist? If not, see if can scan for other extensions and try these */ if (!File_Exists(pszFileName) && ppszExts) { /* Try other extensions, if succeeds, returns correct one */ filepath = File_FindPossibleExtFileName(pszFileName, ppszExts); } if (!filepath) filepath = strdup(pszFileName); #if HAVE_LIBZ /* Is it a gzipped file? */ if (File_DoesFileExtensionMatch(filepath, ".gz")) { pFile = File_ZlibRead(filepath, &FileSize); } else if (File_DoesFileExtensionMatch(filepath, ".zip")) { /* It is a .ZIP file! -> Try to load the first file in the archive */ pFile = ZIP_ReadFirstFile(filepath, &FileSize, ppszExts); } else #endif /* HAVE_LIBZ */ { /* It is a normal file */ pFile = File_ReadAsIs(filepath, &FileSize); } free(filepath); /* Store size of file we read in (or 0 if failed) */ if (pFileSize) *pFileSize = FileSize; return pFile; /* Return to where read in/allocated */ } /*-----------------------------------------------------------------------*/ /** * Save file to disk, return FALSE if errors * If built with ZLib support + file name ends with *.gz, compress it first */ bool File_Save(const char *pszFileName, const uint8_t *pAddress, size_t Size, bool bQueryOverwrite) { bool bRet = false; /* Check if need to ask user if to overwrite */ if (bQueryOverwrite) { /* If file exists, ask if OK to overwrite */ if (!File_QueryOverwrite(pszFileName)) return false; } #if HAVE_LIBZ /* Normal file or gzipped file? */ if (File_DoesFileExtensionMatch(pszFileName, ".gz")) { gzFile hGzFile; /* Create a gzipped file: */ hGzFile = gzopen(pszFileName, "wb"); if (hGzFile != NULL) { /* Write data, set success flag */ if (gzwrite(hGzFile, pAddress, Size) == (int)Size) bRet = true; gzclose(hGzFile); } } else #endif /* HAVE_LIBZ */ { FILE *hDiskFile; /* Create a normal file: */ hDiskFile = fopen(pszFileName, "wb"); if (hDiskFile != NULL) { /* Write data, set success flag */ if (fwrite(pAddress, 1, Size, hDiskFile) == Size) bRet = true; fclose(hDiskFile); } } return bRet; } /*-----------------------------------------------------------------------*/ /** * Return size of file, -1 if error */ off_t File_Length(const char *pszFileName) { FILE *hDiskFile; off_t FileSize; hDiskFile = fopen(pszFileName, "rb"); if (hDiskFile!=NULL) { #if defined(__APPLE__) /* special handling for character/block devices on macOS, where the seeking method doesn't determine the size */ struct stat buf; unsigned long blocksize; unsigned long numblocks; int fd = fileno(hDiskFile); if ((fstat(fd, &buf) == 0) && (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode))) /* both S_ISBLK() and S_ISCHR() is needed, because /dev/rdisk? devices in macOS identify themselves as character devices, not block devices, meanwhile /dev/disk?, which are a lot slower and buffered, say they are block devices... Thanks, Apple! This hack allows either of them to be used by Hatari. */ { if ((ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize) == 0) && (ioctl(fd, DKIOCGETBLOCKCOUNT, &numblocks) == 0)) { FileSize = blocksize * numblocks; return FileSize; } } #endif fseeko(hDiskFile, 0, SEEK_END); FileSize = ftello(hDiskFile); fclose(hDiskFile); return FileSize; } return -1; } /*-----------------------------------------------------------------------*/ /** * Return TRUE if file exists, is readable or writable at least and is not * a directory. */ bool File_Exists(const char *filename) { struct stat buf; if (stat(filename, &buf) == 0 && (buf.st_mode & (S_IRUSR|S_IWUSR)) && !S_ISDIR(buf.st_mode)) { /* file points to user readable regular file */ return true; } return false; } /*-----------------------------------------------------------------------*/ /** * Return TRUE if directory exists. */ bool File_DirExists(const char *path) { struct stat buf; return (stat(path, &buf) == 0 && S_ISDIR(buf.st_mode)); } /*-----------------------------------------------------------------------*/ /** * Find if file exists, and if so ask user if OK to overwrite */ bool File_QueryOverwrite(const char *pszFileName) { const char *fmt; char *szString; bool ret = true; /* Try and find if file exists */ if (File_Exists(pszFileName)) { fmt = "File '%s' exists, overwrite?"; /* File does exist, are we OK to overwrite? */ szString = malloc(strlen(pszFileName) + strlen(fmt) + 1); if ( !szString ) return false; sprintf(szString, fmt, pszFileName); fprintf(stderr, "%s\n", szString); ret = DlgAlert_Query(szString); free(szString); } return ret; } /*-----------------------------------------------------------------------*/ /** * Try filename with various extensions and check if file exists * - if so, return allocated string which caller should free, * otherwise return NULL */ char * File_FindPossibleExtFileName(const char *pszFileName, const char * const ppszExts[]) { char *szSrcDir, *szSrcName, *szSrcExt; int i; /* Allocate temporary memory for strings: */ szSrcDir = malloc(3 * FILENAME_MAX); if (!szSrcDir) { perror("File_FindPossibleExtFileName"); return NULL; } szSrcName = szSrcDir + FILENAME_MAX; szSrcExt = szSrcName + FILENAME_MAX; /* Split filename into parts */ File_SplitPath(pszFileName, szSrcDir, szSrcName, szSrcExt); /* Scan possible extensions */ for (i = 0; ppszExts[i]; i++) { char *szTempFileName; /* Re-build with new file extension */ szTempFileName = File_MakePath(szSrcDir, szSrcName, ppszExts[i]); if (szTempFileName) { /* Does this file exist? */ if (File_Exists(szTempFileName)) { free(szSrcDir); /* return filename without extra strings */ return szTempFileName; } free(szTempFileName); } } free(szSrcDir); return NULL; } /*-----------------------------------------------------------------------*/ /** * Return basename of given path (remove directory names) */ const char *File_Basename(const char *path) { const char *basename; if ((basename = strrchr(path, PATHSEP))) return basename + 1; return path; } /*-----------------------------------------------------------------------*/ /** * Split a complete filename into path, filename and extension. * If pExt is NULL, don't split the extension from the file name! * It's safe for pSrcFileName and pDir to be the same string. */ void File_SplitPath(const char *pSrcFileName, char *pDir, char *pName, char *pExt) { char *ptr1, *ptr2; /* Build pathname: */ ptr1 = strrchr(pSrcFileName, PATHSEP); if (ptr1) { strcpy(pName, ptr1+1); memmove(pDir, pSrcFileName, ptr1-pSrcFileName); pDir[ptr1-pSrcFileName] = 0; } else { strcpy(pName, pSrcFileName); sprintf(pDir, ".%c", PATHSEP); } /* Build the raw filename: */ if (pExt != NULL) { ptr2 = strrchr(pName+1, '.'); if (ptr2) { pName[ptr2-pName] = 0; /* Copy the file extension: */ strcpy(pExt, ptr2+1); } else pExt[0] = 0; } } /*-----------------------------------------------------------------------*/ /** * Construct a complete filename from path, filename and extension. * Return the constructed filename. * pExt can also be NULL. */ char * File_MakePath(const char *pDir, const char *pName, const char *pExt) { char *filepath; int len; /* dir or "." + "/" + name + "." + ext + \0 */ len = strlen(pDir) + 2 + strlen(pName) + 1 + (pExt ? strlen(pExt) : 0) + 1; filepath = malloc(len); if (!filepath) { perror("File_MakePath"); return NULL; } if (!pDir[0]) { filepath[0] = '.'; filepath[1] = '\0'; } else { strcpy(filepath, pDir); } len = strlen(filepath); if (filepath[len-1] != PATHSEP) { filepath[len++] = PATHSEP; } strcpy(&filepath[len], pName); if (pExt != NULL && pExt[0]) { len += strlen(pName); if (pExt[0] != '.') strcat(&filepath[len++], "."); strcat(&filepath[len], pExt); } return filepath; } /** * Like File_MakePath(), but puts the string into a pre-allocated buffer * instead of returning a freshly allocated memory buffer. */ int File_MakePathBuf(char *buf, size_t buflen, const char *pDir, const char *pName, const char *pExt) { char *tmp; if (!buflen) return -EINVAL; tmp = File_MakePath(pDir, pName, pExt); if (!tmp) { buf[0] = '\0'; return -ENOMEM; } strncpy(buf, tmp, buflen); free(tmp); if (buf[buflen - 1]) { buf[buflen - 1] = '\0'; return -E2BIG; } return 0; } /*-----------------------------------------------------------------------*/ /** * Shrink a file name to a certain length and insert some dots if we cut * something away (useful for showing file names in a dialog). * Note: maxlen is the maximum length of the destination string, _not_ * including the final '\0' byte! So the destination buffer has to be * at least one byte bigger than maxlen. */ void File_ShrinkName(char *pDestFileName, const char *pSrcFileName, int maxlen) { int srclen = strlen(pSrcFileName); if (srclen < maxlen) strcpy(pDestFileName, pSrcFileName); /* It fits! */ else { assert(maxlen > 6); strncpy(pDestFileName, pSrcFileName, maxlen/2); if (maxlen&1) /* even or uneven? */ pDestFileName[maxlen/2-1] = 0; else pDestFileName[maxlen/2-2] = 0; strcat(pDestFileName, "..."); strcat(pDestFileName, &pSrcFileName[strlen(pSrcFileName)-maxlen/2+1]); } } /*-----------------------------------------------------------------------*/ /** * Open given filename in given mode and handle "stdout" & "stderr" * filenames specially. Return FILE* to the opened file or NULL on error. */ FILE *File_Open(const char *path, const char *mode) { int wr = 0, rd = 0; FILE *fp; /* empty name signifies file that shouldn't be opened/enabled */ if (!path || !*path) return NULL; /* special "stdout" and "stderr" files can be used * for files which are written or appended */ if (strchr(mode, 'w') || strchr(mode, 'a')) wr = 1; if (strchr(mode, 'r')) rd = 1; if (strcmp(path, "stdin") == 0) { assert(rd && !wr); return stdin; } if (strcmp(path, "stdout") == 0) { assert(wr && !rd); return stdout; } if (strcmp(path, "stderr") == 0) { assert(wr && !rd); return stderr; } /* Open a normal log file */ fp = fopen(path, mode); if (!fp) fprintf(stderr, "Can't open file '%s' (wr=%i, rd=%i):\n %s\n", path, wr, rd, strerror(errno)); /* printf("'%s' opened in mode '%s'\n", path, mode, fp); */ return fp; } /*-----------------------------------------------------------------------*/ /** * Close given FILE pointer and return the closed pointer * as NULL for the idiom "fp = File_Close(fp);" */ FILE *File_Close(FILE *fp) { if (fp && fp != stdin && fp != stdout && fp != stderr) { fclose(fp); } return NULL; } /*-----------------------------------------------------------------------*/ /** * Internal lock function for File_Lock() / File_UnLock(). * Returns true on success, otherwise false. */ static bool lock_operation(FILE *fp, int cmd) { #ifndef HAVE_FLOCK # define DO_LOCK 0 # define DO_UNLOCK 0 return true; #else # define DO_LOCK (LOCK_EX|LOCK_NB) # define DO_UNLOCK (LOCK_UN) /* Advantage of locking is only small bit of extra safety if * one runs (e.g. accidentally) multiple Hatari instances at * same time, so replacing it with no-op is no big deal. * * NOTE: this uses BSD file locking as it's a bit more usable than POSIX one: * http://0pointer.de/blog/projects/locking.html */ int ret, fd = fileno(fp); if (fd < 0) return false; ret = flock(fd, cmd); return (ret >= 0); #endif } /*-----------------------------------------------------------------------*/ /** * Takes advisory, exclusive lock on given file (FILE *). * Returns false if locking fails (e.g. another Hatari * instance has already file open for writing). */ bool File_Lock(FILE *fp) { return lock_operation(fp, DO_LOCK); } /*-----------------------------------------------------------------------*/ /** * Releases advisory, exclusive lock on given file (FILE *). */ void File_UnLock(FILE *fp) { lock_operation(fp, DO_UNLOCK); } /*-----------------------------------------------------------------------*/ /** * Check if input is available at the specified file descriptor. */ bool File_InputAvailable(FILE *fp) { #if HAVE_SELECT fd_set rfds; struct timeval tv; int fh; int ret; if (!fp || (fh = fileno(fp)) == -1) return false; /* Add the file handle to the file descriptor set */ FD_ZERO(&rfds); FD_SET(fh, &rfds); /* Return immediately */ tv.tv_sec = 0; tv.tv_usec = 0; /* Check if file descriptor is ready for a read */ ret = select(fh+1, &rfds, NULL, NULL, &tv); if (ret > 0) return true; /* Data available */ #endif return false; } /*-----------------------------------------------------------------------*/ /** * Wrapper for File_MakeAbsoluteName() which special-cases stdin/out/err * named files and empty file name. The given buffer should be opened * with File_Open() and closed with File_Close() if this function is used! * (On Linux one can use /dev/stdout etc, this is intended for other OSes) */ void File_MakeAbsoluteSpecialName(char *path) { if (path[0] && strcmp(path, "stdin") != 0 && strcmp(path, "stdout") != 0 && strcmp(path, "stderr") != 0) File_MakeAbsoluteName(path); } /*-----------------------------------------------------------------------*/ /** * Create a clean absolute file name from a (possibly) relative file name. * I.e. filter out all occurancies of "./" and "../". * pFileName needs to point to a buffer of at least FILENAME_MAX bytes. */ void File_MakeAbsoluteName(char *pFileName) { char *pTempName; int inpos, outpos; #if defined (__AMIGAOS4__) /* This function does not work on Amiga OS */ return; #endif inpos = 0; pTempName = malloc(FILENAME_MAX); if (!pTempName) { perror("File_MakeAbsoluteName - malloc"); return; } /* Is it already an absolute name? */ if (File_IsRootFileName(pFileName)) { outpos = 0; } else { if (!getcwd(pTempName, FILENAME_MAX)) { perror("File_MakeAbsoluteName - getcwd"); free(pTempName); return; } File_AddSlashToEndFileName(pTempName); outpos = strlen(pTempName); } /* Now filter out the relative paths "./" and "../" */ while (pFileName[inpos] != 0 && outpos < FILENAME_MAX) { if (pFileName[inpos] == '.' && pFileName[inpos+1] == PATHSEP) { /* Ignore "./" */ inpos += 2; } else if (pFileName[inpos] == '.' && pFileName[inpos+1] == 0) { inpos += 1; /* Ignore "." at the end of the path string */ if (outpos > 1) pTempName[outpos - 1] = 0; /* Remove the last slash, too */ } else if (pFileName[inpos] == '.' && pFileName[inpos+1] == '.' && (pFileName[inpos+2] == PATHSEP || pFileName[inpos+2] == 0)) { /* Handle "../" */ char *pSlashPos; inpos += 2; pTempName[outpos - 1] = 0; pSlashPos = strrchr(pTempName, PATHSEP); if (pSlashPos) { *(pSlashPos + 1) = 0; outpos = strlen(pTempName); } else { pTempName[0] = PATHSEP; outpos = 1; } /* Were we already at the end of the string or is there more to come? */ if (pFileName[inpos] == PATHSEP) { /* There was a slash after the '..', so skip slash and * simply proceed with next part */ inpos += 1; } else { /* We were at the end of the string, so let's remove the slash * from the new string, too */ if (outpos > 1) pTempName[outpos - 1] = 0; } } else { /* Copy until next slash or end of input string */ while (pFileName[inpos] != 0 && outpos < FILENAME_MAX) { pTempName[outpos++] = pFileName[inpos++]; if (pFileName[inpos - 1] == PATHSEP) break; } } } pTempName[outpos] = 0; strcpy(pFileName, pTempName); /* Copy back */ free(pTempName); } /*-----------------------------------------------------------------------*/ /** * Create a valid path name from a possibly invalid name by erasing invalid * path parts at the end of the string. If string doesn't contain any path * component, it will be pointed to the root directory. Empty string will * be left as-is to prevent overwriting past allocated area. */ void File_MakeValidPathName(char *pPathName) { struct stat dirstat; char *pLastSlash; if (!pPathName[0]) return; /* Avoid writing to zero-size buffers */ do { /* Check for a valid path */ if (stat(pPathName, &dirstat) == 0 && S_ISDIR(dirstat.st_mode)) { break; } pLastSlash = strrchr(pPathName, PATHSEP); if (pLastSlash) { /* Erase the (probably invalid) part after the last slash */ *pLastSlash = 0; } else { /* point to root */ pPathName[0] = PATHSEP; pPathName[1] = 0; return; } } while (pLastSlash); /* Make sure that path name ends with a slash */ File_AddSlashToEndFileName(pPathName); } /*-----------------------------------------------------------------------*/ /** * Remove given number of path elements from the end of the given path. * Leaves '/' at the end if path still has directories. Given path * may not be empty. */ void File_PathShorten(char *path, int dirs) { int n = 0; /* ignore last char, it may or may not be '/' */ int i = strlen(path) - 1; #ifdef WIN32 int len = i; #endif assert(i >= 0); while(i > 0 && n < dirs) { if (path[--i] == PATHSEP) n++; } #ifdef WIN32 /* if we have only drive with root, don't shorten it more*/ if (len == 2 && n == 0 && path[1] == ':' && path[2] == PATHSEP) return; #endif if (path[i] == PATHSEP) { path[i+1] = '\0'; } else { path[0] = PATHSEP; path[1] = '\0'; } } /*-----------------------------------------------------------------------*/ /* If "/." or "/.." at end, remove that and in case of ".." remove also preceding dir (go one dir up). Leave '/' at the end of the path. */ void File_HandleDotDirs(char *path) { int len = strlen(path); if (len >= 2 && path[len-2] == PATHSEP && path[len-1] == '.') { /* keep in same dir */ path[len-1] = '\0'; } else if (len >= 3 && path[len-3] == PATHSEP && path[len-2] == '.' && path[len-1] == '.') { /* go one dir up */ if (len == 3) { path[1] = 0; /* already root */ } else { char *ptr; path[len-3] = 0; ptr = strrchr(path, PATHSEP); if (ptr) *(ptr+1) = 0; } } } #if defined(WIN32) static TCHAR szTempFileName[MAX_PATH]; /*-----------------------------------------------------------------------*/ /** * Get temporary filename for Windows */ static char* WinTmpFile(void) { DWORD dwRetVal = 0; UINT uRetVal = 0; TCHAR lpTempPathBuffer[MAX_PATH]; /* Gets the temp path env string (no guarantee it's a valid path) */ dwRetVal = GetTempPath(MAX_PATH, /* length of the buffer */ lpTempPathBuffer); /* buffer for path */ if (dwRetVal > MAX_PATH || (dwRetVal == 0)) { fprintf(stderr, "GetTempPath failed.\n"); return NULL; } /* Generates a temporary file name */ uRetVal = GetTempFileName(lpTempPathBuffer, /* directory for tmp files */ TEXT("HATARI"), /* temp file name prefix */ 0, /* create unique name */ szTempFileName); /* buffer for name */ if (uRetVal == 0) { fprintf(stderr, "GetTempFileName failed.\n"); return NULL; } return (char*)szTempFileName; } #endif /** * Open a temporary file. This is a wrapper for tmpfile() that can be used * on Windows, too. If *psFileName gets set by this function, the caller * should delete the file manually when it is not needed anymore. */ FILE *File_OpenTempFile(char **psFileName) { FILE *fh; char *psTmpName = NULL; fh = tmpfile(); /* Open temporary file */ #if defined(WIN32) if (!fh) { /* Unfortunately tmpfile() needs administrative privileges on * Windows, so if it failed, let's work around this issue. */ psTmpName = WinTmpFile(); fh = fopen(psTmpName, "w+b"); } #endif if (psFileName) { *psFileName = psTmpName; } return fh; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/floppy.c000066400000000000000000001015751504763705000230160ustar00rootroot00000000000000/* Hatari - floppy.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This is where we read/write sectors to/from the disk image buffers. NOTE: these buffers are in memory so we only need to write routines for the .ST format. When the buffer is to be saved (ie eject disk) we save it back to the original file in the correct format (.ST or .MSA). There are some important notes about image accessing - as we use TOS and the FDC to access the disk the boot-sector MUST be valid. Sometimes this is NOT the case! In these situations we must guess at the disk format. Eg, some disk images have a a boot sector which states single-sided, but the images have been created as double-sided. As sides are interleaved we need to read the image as if it was double-sided. Also note that 'NumBytesPerSector' is ALWAYS 512 bytes, even if the boot-sector states otherwise. Also note that old versions of the MAKEDISK utility do not set the correct boot sector structure for a real ST (and also Hatari) to read it correctly. (PaCifiST will, however, read/write to these images as it does not perform FDC access as on a real ST) */ const char Floppy_fileid[] = "Hatari floppy.c"; #include #include #include "main.h" #include "configuration.h" #include "file.h" #include "floppy.h" #include "gemdos.h" #include "hdc.h" #include "ncr5380.h" #include "log.h" #include "memorySnapShot.h" #include "st.h" #include "msa.h" #include "dim.h" #include "floppy_ipf.h" #include "floppy_stx.h" #include "zip.h" #include "str.h" #include "video.h" #include "fdc.h" /* Emulation drive details, eg FileName, Inserted, Changed etc... */ EMULATION_DRIVE EmulationDrives[MAX_FLOPPYDRIVES]; /* Drive A is the default */ int nBootDrive = 0; /* Possible disk image file extensions to scan for */ static const char * const pszDiskImageNameExts[] = { ".msa", ".st", ".dim", ".ipf", ".raw", ".ctr", ".stx", NULL }; /* local functions */ static bool Floppy_EjectBothDrives(void); static void Floppy_DriveTransitionSetState ( int Drive , int State ); /*-----------------------------------------------------------------------*/ /** * Initialize emulation floppy drives */ void Floppy_Init(void) { int i; /* Clear drive structures */ for (i = 0; i < MAX_FLOPPYDRIVES; i++) { /* Clear structs and if floppies available, insert them */ memset(&EmulationDrives[i], 0, sizeof(EMULATION_DRIVE)); if (strlen(ConfigureParams.DiskImage.szDiskFileName[i]) > 0) Floppy_InsertDiskIntoDrive(i); } } /*-----------------------------------------------------------------------*/ /** * UnInitialize drives */ void Floppy_UnInit(void) { Floppy_EjectBothDrives(); } /*-----------------------------------------------------------------------*/ /** * Called on Warm/Cold Reset */ void Floppy_Reset(void) { int i; /* Cancel any pending disk change transitions */ for (i = 0; i < MAX_FLOPPYDRIVES; i++) { EmulationDrives[i].TransitionState1 = 0; EmulationDrives[i].TransitionState2 = 0; } } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void Floppy_MemorySnapShot_Capture(bool bSave) { int i; /* If restoring then eject old drives first! */ if (!bSave) Floppy_EjectBothDrives(); /* Save/Restore details */ for (i = 0; i < MAX_FLOPPYDRIVES; i++) { MemorySnapShot_Store(&EmulationDrives[i].ImageType, sizeof(EmulationDrives[i].ImageType)); MemorySnapShot_Store(&EmulationDrives[i].bDiskInserted, sizeof(EmulationDrives[i].bDiskInserted)); MemorySnapShot_Store(&EmulationDrives[i].nImageBytes, sizeof(EmulationDrives[i].nImageBytes)); if (!bSave && EmulationDrives[i].bDiskInserted) { EmulationDrives[i].pBuffer = malloc(EmulationDrives[i].nImageBytes); if (!EmulationDrives[i].pBuffer) perror("Floppy_MemorySnapShot_Capture"); } if (EmulationDrives[i].pBuffer) MemorySnapShot_Store(EmulationDrives[i].pBuffer, EmulationDrives[i].nImageBytes); MemorySnapShot_Store(EmulationDrives[i].sFileName, sizeof(EmulationDrives[i].sFileName)); MemorySnapShot_Store(&EmulationDrives[i].bContentsChanged,sizeof(EmulationDrives[i].bContentsChanged)); MemorySnapShot_Store(&EmulationDrives[i].bOKToSave,sizeof(EmulationDrives[i].bOKToSave)); MemorySnapShot_Store(&EmulationDrives[i].TransitionState1,sizeof(EmulationDrives[i].TransitionState1)); MemorySnapShot_Store(&EmulationDrives[i].TransitionState1_VBL,sizeof(EmulationDrives[i].TransitionState1_VBL)); MemorySnapShot_Store(&EmulationDrives[i].TransitionState2,sizeof(EmulationDrives[i].TransitionState2)); MemorySnapShot_Store(&EmulationDrives[i].TransitionState2_VBL,sizeof(EmulationDrives[i].TransitionState2_VBL)); /* Because Floppy_EjectBothDrives() was called above before restoring (which cleared */ /* FDC_DRIVES[].DiskInserted that was restored just before), we must call FDC_InsertFloppy */ /* for each restored drive with an inserted disk to set FDC_DRIVES[].DiskInserted=true */ if ( !bSave && ( EmulationDrives[i].bDiskInserted ) ) FDC_InsertFloppy ( i ); } } /*-----------------------------------------------------------------------*/ /** * Find which device to boot from (hard drive or floppy). */ void Floppy_GetBootDrive(void) { /* Default to drive A: */ nBootDrive = 0; /* Boot only from hard drive if user wants this */ if (!ConfigureParams.HardDisk.bBootFromHardDisk) return; if (bAcsiEmuOn || bScsiEmuOn || ConfigureParams.Ide[0].bUseDevice) { nBootDrive = 2; /* Drive C */ } else if (GEMDOS_EMU_ON) { int i; for (i = 0; i < MAX_HARDDRIVES; i++) { if (emudrives[i]) { nBootDrive = emudrives[i]->drive_number; break; } } } } /*-----------------------------------------------------------------------*/ /** * Test if disk image is write protected. Write protection can be configured * in the GUI. When set to "automatic", we check the file permissions of the * floppy disk image to decide. */ bool Floppy_IsWriteProtected(int Drive) { if (ConfigureParams.DiskImage.nWriteProtection == WRITEPROT_OFF) { return false; } else if (ConfigureParams.DiskImage.nWriteProtection == WRITEPROT_ON) { return true; } else { struct stat FloppyStat; /* Check whether disk is writable */ if (stat(EmulationDrives[Drive].sFileName, &FloppyStat) == 0 && (FloppyStat.st_mode & S_IWUSR)) return false; else return true; } } /*-----------------------------------------------------------------------*/ /** * Test disk image for executable boot sector. * The boot sector is executable if the 16 bit sum of its 256 words * gives the value 0x1234. */ static bool Floppy_IsBootSectorExecutable(int Drive) { uint8_t *pDiskBuffer; int sum , i; if (EmulationDrives[Drive].bDiskInserted) { pDiskBuffer = EmulationDrives[Drive].pBuffer; sum = 0; for ( i=0 ; i<256 ; i++ ) { sum += ( ( *pDiskBuffer << 8 ) + *(pDiskBuffer+1) ); pDiskBuffer += 2; } if ( ( sum & 0xffff ) == FLOPPY_BOOT_SECTOR_EXE_SUM ) /* 0x1234 */ return true; } return false; /* Not executable */ } /*-----------------------------------------------------------------------*/ /** * Test disk image for valid boot-sector. * It has been noticed that some disks, eg blank images made by the MakeDisk * utility or PaCifiST emulator fill in the boot-sector with incorrect information. * Such images cannot be read correctly using a real ST, and also Hatari. * To try and prevent data loss, we check for this error and flag the drive so * the image will not be saved back to the file. */ static bool Floppy_IsBootSectorOK(int Drive) { uint8_t *pDiskBuffer; /* Does our drive have a disk in? */ if (EmulationDrives[Drive].bDiskInserted) { pDiskBuffer = EmulationDrives[Drive].pBuffer; /* Check SPC (byte 13) for !=0 value. If is '0', invalid image and Hatari * won't be-able to read (nor will a real ST)! */ if ( (pDiskBuffer[13] != 0) || ( Floppy_IsBootSectorExecutable ( Drive ) == true ) ) { return true; /* Disk sector is OK! */ } else { Log_AlertDlg(LOG_WARN, "Disk in drive %c: maybe suffers from the Pacifist/Makedisk bug.\n" "If it does not work, please repair the disk first!\n", 'A' + Drive); } } return false; /* Bad sector */ } /*-----------------------------------------------------------------------*/ /** * Try to create disk B filename, eg 'auto_100a' becomes 'auto_100b' * Return new filename if think we should try, otherwise NULL * * TODO: doesn't work with images in ZIP archives */ static char* Floppy_CreateDiskBFileName(const char *pSrcFileName) { char *szDir, *szName, *szExt; /* Allocate temporary memory for strings: */ szDir = malloc(3 * FILENAME_MAX); if (!szDir) { perror("Floppy_CreateDiskBFileName"); return NULL; } szName = szDir + FILENAME_MAX; szExt = szName + FILENAME_MAX; /* So, first split name into parts */ File_SplitPath(pSrcFileName, szDir, szName, szExt); /* All OK? */ if (strlen(szName) > 0) { char *last = &(szName[strlen(szName)-1]); /* Now, did filename end with an 'A' or 'a' */ if (*last == 'A' || *last == 'a') { char *szFull; /* Change 'A' to a 'B' */ *last += 1; /* And re-build name into destination */ szFull = File_MakePath(szDir, szName, szExt); if (szFull) { /* Does file exist? */ if (File_Exists(szFull)) { free(szDir); return szFull; } free(szFull); } } } free(szDir); return NULL; } /*-----------------------------------------------------------------------*/ /** * Set floppy image to be ejected */ const char* Floppy_SetDiskFileNameNone(int Drive) { assert(Drive >= 0 && Drive < MAX_FLOPPYDRIVES); ConfigureParams.DiskImage.szDiskFileName[Drive][0] = '\0'; return ConfigureParams.DiskImage.szDiskFileName[Drive]; } /*-----------------------------------------------------------------------*/ /** * Set given floppy drive image file name and handle * different image extensions. * Return corrected file name on success and NULL on failure. */ const char* Floppy_SetDiskFileName(int Drive, const char *pszFileName, const char *pszZipPath) { char *filename; int i; /* setting to empty or "none" ejects */ if (!*pszFileName || strcasecmp(pszFileName, "none") == 0) { return Floppy_SetDiskFileNameNone(Drive); } /* See if file exists, and if not, get/add correct extension */ if (!File_Exists(pszFileName)) filename = File_FindPossibleExtFileName(pszFileName, pszDiskImageNameExts); else filename = strdup(pszFileName); if (!filename) { Log_AlertDlg(LOG_INFO, "Image '%s' not found", pszFileName); return NULL; } /* If we insert a disk into Drive A, should we try to put disk 2 into drive B? */ if (Drive == 0 && ConfigureParams.DiskImage.bAutoInsertDiskB) { /* Attempt to make up second filename, eg was 'auto_100a' to 'auto_100b' */ char *szDiskBFileName = Floppy_CreateDiskBFileName(filename); if (szDiskBFileName) { /* recurse with Drive B */ Floppy_SetDiskFileName(1, szDiskBFileName, pszZipPath); free(szDiskBFileName); } } /* validity checks */ assert(Drive >= 0 && Drive < MAX_FLOPPYDRIVES); for (i = 0; i < MAX_FLOPPYDRIVES; i++) { if (i == Drive) continue; /* prevent inserting same image to multiple drives */ if (strcmp(filename, ConfigureParams.DiskImage.szDiskFileName[i]) == 0) { Log_AlertDlg(LOG_ERROR, "ERROR: Cannot insert same floppy to multiple drives!"); free(filename); return NULL; } } /* do the changes */ if (pszZipPath) strcpy(ConfigureParams.DiskImage.szDiskZipPath[Drive], pszZipPath); else ConfigureParams.DiskImage.szDiskZipPath[Drive][0] = '\0'; Str_Copy(ConfigureParams.DiskImage.szDiskFileName[Drive], filename, sizeof(ConfigureParams.DiskImage.szDiskFileName[Drive])); free(filename); //File_MakeAbsoluteName(ConfigureParams.DiskImage.szDiskFileName[Drive]); return ConfigureParams.DiskImage.szDiskFileName[Drive]; } /*-----------------------------------------------------------------------*/ /** * Update the drive when a disk is inserted or ejected. Depending on the state, * we change the Write Protect bit for the drive (the TOS and other programs * monitor this bit to detect that a disk was changed in the drive ; see fdc.c) * The floppy drive transition can be a single action ("eject" or "insert"), or * two actions ("eject then insert" or "insert then eject"). * First action is stored in State1 ; State2 store the second (or last) action. * In case the user eject/insert several disks before returning to emulation, * State1 will contain the first action, and State2 the latest action (intermediate * actions are ignored, as they wouldn't be seen while the emulation is paused). * Each action will take FLOPPY_DRIVE_TRANSITION_DELAY_VBL VBLs to execute, * see fdc.c for details. */ static void Floppy_DriveTransitionSetState ( int Drive , int State ) { /* First, update State1 and State2 depending on the current VBL number */ /* (we discard the return value as we don't want to update FDC.STR now) */ Floppy_DriveTransitionUpdateState ( Drive ); /* If State1 is not defined yet, we set it */ if ( EmulationDrives[Drive].TransitionState1 == 0 ) { EmulationDrives[Drive].TransitionState1 = State; EmulationDrives[Drive].TransitionState1_VBL = nVBLs; /* Cancel State2 in case we start a new transition before State2 was over */ EmulationDrives[Drive].TransitionState2 = 0; } /* State1 is already set, so we set State2 */ else { /* If State2 == State1, ignore it (eg : two inserts in a row) */ if ( EmulationDrives[Drive].TransitionState1 == State ) EmulationDrives[Drive].TransitionState2 = 0; else { /* Set State2 just after State1 ends */ EmulationDrives[Drive].TransitionState2 = State; EmulationDrives[Drive].TransitionState2_VBL = EmulationDrives[Drive].TransitionState1_VBL + FLOPPY_DRIVE_TRANSITION_DELAY_VBL; } } //fprintf ( stderr , "drive transition state1 %d %d state2 %d %d\n" , // EmulationDrives[Drive].TransitionState1 , EmulationDrives[Drive].TransitionState1_VBL, // EmulationDrives[Drive].TransitionState2 , EmulationDrives[Drive].TransitionState2_VBL ); } /*-----------------------------------------------------------------------*/ /** * When a disk is inserted or ejected, each transition has 2 phases that * lasts FLOPPY_DRIVE_TRANSITION_DELAY_VBL VBLs. This function checks if * we're during one of these transition phases and tells if the Write * Protect signal should be overwritten. * Returns 0 if there's no change, 1 if WPRT should be forced to 1 and * -1 if WPRT should be forced to 0 (see fdc.c for details). */ int Floppy_DriveTransitionUpdateState ( int Drive ) { int Force = 0; if ( EmulationDrives[Drive].TransitionState1 != 0 ) { if ( nVBLs >= EmulationDrives[Drive].TransitionState1_VBL + FLOPPY_DRIVE_TRANSITION_DELAY_VBL ) EmulationDrives[Drive].TransitionState1 = 0; /* State1's delay elapsed */ else { if ( EmulationDrives[Drive].TransitionState1 == FLOPPY_DRIVE_TRANSITION_STATE_INSERT ) Force = 0; /* Insert : keep WPRT */ else Force = 1; /* Eject : set WPRT */ } } if ( ( EmulationDrives[Drive].TransitionState2 != 0 ) && ( nVBLs >= EmulationDrives[Drive].TransitionState2_VBL ) ) { if ( nVBLs >= EmulationDrives[Drive].TransitionState2_VBL + FLOPPY_DRIVE_TRANSITION_DELAY_VBL ) EmulationDrives[Drive].TransitionState2 = 0; /* State2's delay elapsed */ else { if ( EmulationDrives[Drive].TransitionState2 == FLOPPY_DRIVE_TRANSITION_STATE_INSERT ) Force = 0; /* Insert : keep WPRT */ else Force = 1; /* Eject : set WPRT */ } } return Force; } /*-----------------------------------------------------------------------*/ /** * Insert previously set disk file image into floppy drive. * The WHOLE image is copied into Hatari drive buffers, and * uncompressed if necessary. * Return TRUE on success, false otherwise. */ bool Floppy_InsertDiskIntoDrive(int Drive) { long nImageBytes = 0; char *filename; int ImageType = FLOPPY_IMAGE_TYPE_NONE; /* Eject disk, if one is inserted (doesn't inform user) */ assert(Drive >= 0 && Drive < MAX_FLOPPYDRIVES); Floppy_EjectDiskFromDrive(Drive); filename = ConfigureParams.DiskImage.szDiskFileName[Drive]; if (!filename[0]) { return true; /* only do eject */ } if (!File_Exists(filename)) { Log_AlertDlg(LOG_INFO, "Image '%s' not found", filename); return false; } /* Check disk image type and read the file: */ if (MSA_FileNameIsMSA(filename, true)) EmulationDrives[Drive].pBuffer = MSA_ReadDisk(Drive, filename, &nImageBytes, &ImageType); else if (ST_FileNameIsST(filename, true)) EmulationDrives[Drive].pBuffer = ST_ReadDisk(Drive, filename, &nImageBytes, &ImageType); else if (DIM_FileNameIsDIM(filename, true)) EmulationDrives[Drive].pBuffer = DIM_ReadDisk(Drive, filename, &nImageBytes, &ImageType); else if (IPF_FileNameIsIPF(filename, true)) EmulationDrives[Drive].pBuffer = IPF_ReadDisk(Drive, filename, &nImageBytes, &ImageType); else if (STX_FileNameIsSTX(filename, true)) EmulationDrives[Drive].pBuffer = STX_ReadDisk(Drive, filename, &nImageBytes, &ImageType); else if (ZIP_FileNameIsZIP(filename)) { const char *zippath = ConfigureParams.DiskImage.szDiskZipPath[Drive]; EmulationDrives[Drive].pBuffer = ZIP_ReadDisk(Drive, filename, zippath, &nImageBytes, &ImageType); } if ( (EmulationDrives[Drive].pBuffer == NULL) || ( ImageType == FLOPPY_IMAGE_TYPE_NONE ) ) { Log_AlertDlg(LOG_INFO, "Image '%s' filename extension, or content unrecognized", filename); return false; } /* For IPF, call specific function to handle the inserted image */ if ( ImageType == FLOPPY_IMAGE_TYPE_IPF ) { if ( IPF_Insert ( Drive , EmulationDrives[Drive].pBuffer , nImageBytes ) == false ) { free ( EmulationDrives[Drive].pBuffer ); EmulationDrives[Drive].pBuffer = NULL; Log_AlertDlg(LOG_INFO, "IPF image '%s' loading failed", filename); return false; } } /* For STX, call specific function to handle the inserted image */ else if ( ImageType == FLOPPY_IMAGE_TYPE_STX ) { if ( STX_Insert ( Drive , filename , EmulationDrives[Drive].pBuffer , nImageBytes ) == false ) { free ( EmulationDrives[Drive].pBuffer ); EmulationDrives[Drive].pBuffer = NULL; Log_AlertDlg(LOG_INFO, "STX image '%s' loading failed", filename); return false; } } /* Store image filename (required for ejecting the disk later!) */ strcpy(EmulationDrives[Drive].sFileName, filename); /* Store size and set drive states */ EmulationDrives[Drive].ImageType = ImageType; EmulationDrives[Drive].nImageBytes = nImageBytes; EmulationDrives[Drive].bDiskInserted = true; EmulationDrives[Drive].bContentsChanged = false; if ( ( ImageType == FLOPPY_IMAGE_TYPE_ST ) || ( ImageType == FLOPPY_IMAGE_TYPE_MSA ) || ( ImageType == FLOPPY_IMAGE_TYPE_DIM ) ) EmulationDrives[Drive].bOKToSave = Floppy_IsBootSectorOK(Drive); else if ( ImageType == FLOPPY_IMAGE_TYPE_STX ) EmulationDrives[Drive].bOKToSave = true; else if ( ImageType == FLOPPY_IMAGE_TYPE_IPF ) EmulationDrives[Drive].bOKToSave = false; else EmulationDrives[Drive].bOKToSave = false; Floppy_DriveTransitionSetState ( Drive , FLOPPY_DRIVE_TRANSITION_STATE_INSERT ); FDC_InsertFloppy ( Drive ); Log_Printf(LOG_INFO, "Inserted disk '%s' to drive %c:.", filename, 'A'+Drive); return true; } /*-----------------------------------------------------------------------*/ /** * Eject disk from floppy drive, save contents back to PCs hard-drive if * they have been changed. * Return true if there was something to eject. */ bool Floppy_EjectDiskFromDrive(int Drive) { bool bEjected = false; /* Does our drive have a disk in? */ if (EmulationDrives[Drive].bDiskInserted) { bool bSaved = false; char *psFileName = EmulationDrives[Drive].sFileName; /* OK, has contents changed? If so, need to save */ if (EmulationDrives[Drive].bContentsChanged) { /* Is OK to save image (if boot-sector is bad, don't allow a save) */ if (EmulationDrives[Drive].bOKToSave) { /* Save as .MSA, .ST, .DIM, .IPF or .STX image? */ if (MSA_FileNameIsMSA(psFileName, true)) bSaved = MSA_WriteDisk(Drive, psFileName, EmulationDrives[Drive].pBuffer, EmulationDrives[Drive].nImageBytes); else if (ST_FileNameIsST(psFileName, true)) bSaved = ST_WriteDisk(Drive, psFileName, EmulationDrives[Drive].pBuffer, EmulationDrives[Drive].nImageBytes); else if (DIM_FileNameIsDIM(psFileName, true)) bSaved = DIM_WriteDisk(Drive, psFileName, EmulationDrives[Drive].pBuffer, EmulationDrives[Drive].nImageBytes); else if (IPF_FileNameIsIPF(psFileName, true)) bSaved = IPF_WriteDisk(Drive, psFileName, EmulationDrives[Drive].pBuffer, EmulationDrives[Drive].nImageBytes); else if (STX_FileNameIsSTX(psFileName, true)) bSaved = STX_WriteDisk(Drive, psFileName, EmulationDrives[Drive].pBuffer, EmulationDrives[Drive].nImageBytes); else if (ZIP_FileNameIsZIP(psFileName)) bSaved = ZIP_WriteDisk(Drive, psFileName, EmulationDrives[Drive].pBuffer, EmulationDrives[Drive].nImageBytes); if (bSaved) Log_Printf(LOG_INFO, "Updated the contents of floppy image '%s'.", psFileName); else Log_Printf(LOG_INFO, "Writing of this format failed or not supported, discarded the contents\n of floppy image '%s'.", psFileName); } else Log_Printf(LOG_INFO, "Writing not possible, discarded the contents of floppy image\n '%s'.", psFileName); } /* Inform user that disk has been ejected! */ Log_Printf(LOG_INFO, "Floppy %c: has been removed from drive.", 'A'+Drive); Floppy_DriveTransitionSetState ( Drive , FLOPPY_DRIVE_TRANSITION_STATE_EJECT ); FDC_EjectFloppy ( Drive ); bEjected = true; } /* Free data used by this IPF image */ if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_IPF ) IPF_Eject ( Drive ); /* Free data used by this STX image */ else if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_STX ) STX_Eject ( Drive ); /* Drive is now empty */ if (EmulationDrives[Drive].pBuffer != NULL) { free(EmulationDrives[Drive].pBuffer); EmulationDrives[Drive].pBuffer = NULL; } EmulationDrives[Drive].sFileName[0] = '\0'; EmulationDrives[Drive].ImageType = FLOPPY_IMAGE_TYPE_NONE; EmulationDrives[Drive].nImageBytes = 0; EmulationDrives[Drive].bDiskInserted = false; EmulationDrives[Drive].bContentsChanged = false; EmulationDrives[Drive].bOKToSave = false; return bEjected; } /*-----------------------------------------------------------------------*/ /** * Eject all disk image from floppy drives - call when quit. * Return true if there was something to eject. */ static bool Floppy_EjectBothDrives(void) { bool bEjectedA, bEjectedB; /* Eject disk images from drives 'A' and 'B' */ bEjectedA = Floppy_EjectDiskFromDrive(0); bEjectedB = Floppy_EjectDiskFromDrive(1); return bEjectedA || bEjectedB; } /*-----------------------------------------------------------------------*/ /** * Double-check information read from boot-sector as this is sometimes found to * be incorrect. The .ST image file should be divisible by the sector size, * the sectors per track. the number of tracks and the number of sides. * NOTE - Pass information from boot-sector to this function (if we can't * decide we leave it alone). */ static void Floppy_DoubleCheckFormat(long nDiskSize, long nSectorsPerDisk, uint16_t *pnSides, uint16_t *pnSectorsPerTrack) { long TotalSectors; int Sides_fixed; int SectorsPerTrack_fixed; /* Now guess at number of sides */ if ( nDiskSize < (500*1024) ) /* If size >500k assume 2 sides */ Sides_fixed = 1; else Sides_fixed = 2; /* Number of 512 bytes sectors for this disk image */ TotalSectors = nDiskSize / 512; /* Check some common values */ if ( TotalSectors == 80*9*Sides_fixed ) { SectorsPerTrack_fixed = 9; } else if ( TotalSectors == 81*9*Sides_fixed ) { SectorsPerTrack_fixed = 9; } else if ( TotalSectors == 82*9*Sides_fixed ) { SectorsPerTrack_fixed = 9; } else if ( TotalSectors == 83*9*Sides_fixed ) { SectorsPerTrack_fixed = 9; } else if ( TotalSectors == 84*9*Sides_fixed ) { SectorsPerTrack_fixed = 9; } else if ( TotalSectors == 80*10*Sides_fixed ) { SectorsPerTrack_fixed = 10; } else if ( TotalSectors == 81*10*Sides_fixed ) { SectorsPerTrack_fixed = 10; } else if ( TotalSectors == 82*10*Sides_fixed ) { SectorsPerTrack_fixed = 10; } else if ( TotalSectors == 83*10*Sides_fixed ) { SectorsPerTrack_fixed = 10; } else if ( TotalSectors == 84*10*Sides_fixed ) { SectorsPerTrack_fixed = 10; } else if ( TotalSectors == 80*11*Sides_fixed ) { SectorsPerTrack_fixed = 11; } else if ( TotalSectors == 81*11*Sides_fixed ) { SectorsPerTrack_fixed = 11; } else if ( TotalSectors == 82*11*Sides_fixed ) { SectorsPerTrack_fixed = 11; } else if ( TotalSectors == 83*11*Sides_fixed ) { SectorsPerTrack_fixed = 11; } else if ( TotalSectors == 84*11*Sides_fixed ) { SectorsPerTrack_fixed = 11; } else if ( TotalSectors == 80*12*Sides_fixed ) { SectorsPerTrack_fixed = 12; } else if ( TotalSectors == 81*12*Sides_fixed ) { SectorsPerTrack_fixed = 12; } else if ( TotalSectors == 82*12*Sides_fixed ) { SectorsPerTrack_fixed = 12; } else if ( TotalSectors == 83*12*Sides_fixed ) { SectorsPerTrack_fixed = 12; } else if ( TotalSectors == 84*12*Sides_fixed ) { SectorsPerTrack_fixed = 12; } else /* unknown combination */ { if (*pnSectorsPerTrack >= 5 && *pnSectorsPerTrack <= 48) { /* ED floppies could have up to 48 sectors per track, * so assume boot sector is correct for such values */ SectorsPerTrack_fixed = *pnSectorsPerTrack; } else { /* Looks like the boot sector contains completely bad * values, so try to calculate it instead, assuming * the disk has 80 tracks */ SectorsPerTrack_fixed = TotalSectors / 80 / Sides_fixed; } } /* Valid new values if necessary */ if ( ( *pnSides != Sides_fixed ) || ( *pnSectorsPerTrack != SectorsPerTrack_fixed ) ) { #if 0 int TracksPerDisk_fixed = TotalSectors / ( SectorsPerTrack_fixed * Sides_fixed ); Log_Printf(LOG_WARN, "Floppy_DoubleCheckFormat: boot sector doesn't match disk image's size :" " total sectors %ld->%ld sides %d->%d sectors %d->%d tracks %d\n", nSectorsPerDisk , TotalSectors , *pnSides , Sides_fixed , *pnSectorsPerTrack , SectorsPerTrack_fixed , TracksPerDisk_fixed ); #endif *pnSides = Sides_fixed; *pnSectorsPerTrack = SectorsPerTrack_fixed; } } /*-----------------------------------------------------------------------*/ /** * Find details of disk image. We need to do this via a function as sometimes the boot-block * is not actually correct with the image - some demos/game disks have incorrect bytes in the * boot sector and this attempts to find the correct values. */ void Floppy_FindDiskDetails(const uint8_t *pBuffer, int nImageBytes, uint16_t *pnSectorsPerTrack, uint16_t *pnSides) { uint16_t nSectorsPerTrack, nSides, nSectorsPerDisk; /* First do check to find number of sectors and bytes per sector */ nSectorsPerTrack = pBuffer[24] | (pBuffer[25] << 8); /* SPT */ nSides = pBuffer[26] | (pBuffer[27] << 8); /* SIDE */ nSectorsPerDisk = pBuffer[19] | (pBuffer[20] << 8); /* total sectors */ /* If the boot sector information looks suspicious, it may contain * incorrect information, eg the 'Eat.st' demo, or wrongly imaged * single/double sided floppies... */ if (nSectorsPerDisk != nImageBytes/512 || nSides == 0 || nSides > 2 || nSectorsPerTrack == 0 || nSectorsPerTrack > 48) Floppy_DoubleCheckFormat(nImageBytes, nSectorsPerDisk, &nSides, &nSectorsPerTrack); /* And set values */ if (pnSectorsPerTrack) *pnSectorsPerTrack = nSectorsPerTrack; if (pnSides) *pnSides = nSides; } /*-----------------------------------------------------------------------*/ /** * Read sectors from floppy disk image, return TRUE if all OK * NOTE Pass -ve as Count to read whole track */ bool Floppy_ReadSectors(int Drive, uint8_t **pBuffer, uint16_t Sector, uint16_t Track, uint16_t Side, short Count, int *pnSectorsPerTrack, int *pSectorSize) { uint8_t *pDiskBuffer; uint16_t nSectorsPerTrack, nSides, nBytesPerTrack; long Offset; int nImageTracks; /* Do we have a disk in our drive? */ if (EmulationDrives[Drive].bDiskInserted) { /* Looks good */ pDiskBuffer = EmulationDrives[Drive].pBuffer; /* Find #sides and #sectors per track */ Floppy_FindDiskDetails(EmulationDrives[Drive].pBuffer,EmulationDrives[Drive].nImageBytes,&nSectorsPerTrack,&nSides); nImageTracks = ((EmulationDrives[Drive].nImageBytes / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides; /* Need to read whole track? */ if (Count<0) Count = nSectorsPerTrack; /* Write back number of sector per track */ if (pnSectorsPerTrack) *pnSectorsPerTrack = nSectorsPerTrack; if (pSectorSize) *pSectorSize = NUMBYTESPERSECTOR; /* Size is 512 bytes for ST/MSA */ /* Debug check as if we read over the end of a track we read into side 2! */ if (Count > nSectorsPerTrack) { Log_Printf(LOG_DEBUG, "Floppy_ReadSectors: reading over single track\n"); } /* Check that the side number (0 or 1) does not exceed the amount of sides (1 or 2). * (E.g. some games like Drakkhen or Bolo can load additional data from the * second disk side, but they also work with single side floppy drives) */ if (Side >= nSides) { Log_Printf(LOG_DEBUG, "Floppy_ReadSectors: Program tries to read from side %i " "of a disk image with %i sides!\n", Side+1, nSides); return false; } /* Check if track number is in range */ if (Track >= nImageTracks) { Log_Printf(LOG_DEBUG, "Floppy_ReadSectors: Program tries to read from track %i " "of a disk image with only %i tracks!\n", Track, nImageTracks); return false; } /* Check if sector number is in range */ if (Sector <= 0 || Sector > nSectorsPerTrack) { Log_Printf(LOG_DEBUG, "Floppy_ReadSectors: Program tries to read from sector %i " "of a disk image with %i sectors per track!\n", Sector, nSectorsPerTrack); return false; } /* Seek to sector */ nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack; Offset = nBytesPerTrack*Side; /* First seek to side */ Offset += (nBytesPerTrack*nSides)*Track; /* Then seek to track */ Offset += (NUMBYTESPERSECTOR*(Sector-1)); /* And finally to sector */ /* Return a pointer to the sectors data (usually 512 bytes per sector) */ *pBuffer = pDiskBuffer+Offset; return true; } return false; } /*-----------------------------------------------------------------------*/ /** * Write sectors from floppy disk image, return TRUE if all OK * NOTE Pass -ve as Count to write whole track */ bool Floppy_WriteSectors(int Drive, uint8_t *pBuffer, uint16_t Sector, uint16_t Track, uint16_t Side, short Count, int *pnSectorsPerTrack, int *pSectorSize) { uint8_t *pDiskBuffer; uint16_t nSectorsPerTrack, nSides, nBytesPerTrack; long Offset; int nImageTracks; /* Do we have a writable disk in our drive? */ if (EmulationDrives[Drive].bDiskInserted && !Floppy_IsWriteProtected(Drive)) { /* Looks good */ pDiskBuffer = EmulationDrives[Drive].pBuffer; /* Find #sides and #sectors per track */ Floppy_FindDiskDetails(EmulationDrives[Drive].pBuffer,EmulationDrives[Drive].nImageBytes,&nSectorsPerTrack,&nSides); nImageTracks = ((EmulationDrives[Drive].nImageBytes / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides; /* Need to write whole track? */ if (Count<0) Count = nSectorsPerTrack; /* Write back number of sector per track */ if (pnSectorsPerTrack) *pnSectorsPerTrack = nSectorsPerTrack; if (pSectorSize) *pSectorSize = NUMBYTESPERSECTOR; /* Size is 512 bytes for ST/MSA */ /* Debug check as if we write over the end of a track we write into side 2! */ if (Count > nSectorsPerTrack) { Log_Printf(LOG_DEBUG, "Floppy_WriteSectors: writing over single track\n"); } /* Check that the side number (0 or 1) does not exceed the amount of sides (1 or 2). */ if (Side >= nSides) { Log_Printf(LOG_DEBUG, "Floppy_WriteSectors: Program tries to write to side %i " "of a disk image with %i sides!\n", Side+1, nSides); return false; } /* Check if track number is in range */ if (Track >= nImageTracks) { Log_Printf(LOG_DEBUG, "Floppy_WriteSectors: Program tries to write to track %i " "of a disk image with only %i tracks!\n", Track, nImageTracks); return false; } /* Check if sector number is in range */ if (Sector <= 0 || Sector > nSectorsPerTrack) { Log_Printf(LOG_DEBUG, "Floppy_WriteSectors: Program tries to write to sector %i " "of a disk image with %i sectors per track!\n", Sector, nSectorsPerTrack); return false; } /* Seek to sector */ nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack; Offset = nBytesPerTrack*Side; /* First seek to side */ Offset += (nBytesPerTrack*nSides)*Track; /* Then seek to track */ Offset += (NUMBYTESPERSECTOR*(Sector-1)); /* And finally to sector */ /* Write sectors (usually 512 bytes per sector) */ memcpy(pDiskBuffer+Offset, pBuffer, (int)Count*NUMBYTESPERSECTOR); /* And set 'changed' flag */ EmulationDrives[Drive].bContentsChanged = true; return true; } return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/floppy_ipf.c000066400000000000000000001051171504763705000236500ustar00rootroot00000000000000/* Hatari - floppy_ipf.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. IPF disk image support. IPF files are handled using the capsimage library, which emulates the FDC at low level and allows to read complex protections. RAW stream files made with KryoFlux board or CT RAW dumped with an Amiga are also handled by the capsimage library. */ const char floppy_ipf_fileid[] = "Hatari floppy_ipf.c"; #include "main.h" #include "file.h" #include "floppy.h" #include "floppy_ipf.h" #include "fdc.h" #include "log.h" #include "memorySnapShot.h" #include "video.h" #include "m68000.h" #include "cycles.h" #include #ifdef HAVE_CAPSIMAGE #ifndef __cdecl #define __cdecl /* CAPS headers need this, but do not define it on their own */ #endif #include #define CapsLong SDWORD #define CapsULong UDWORD /* Macro to check release and revision */ #define CAPS_LIB_REL_REV ( CAPS_LIB_RELEASE * 100 + CAPS_LIB_REVISION ) #endif /* To handle RAW stream images with one file per track/side */ #define IPF_MAX_TRACK_RAW_STREAM_IMAGE 84 /* track number can be 0 .. 83 */ #define IPF_MAX_SIDE_RAW_STREAM_IMAGE 2 /* side number can be 0 or 1 */ struct { int TrackSize; uint8_t *TrackData; } IPF_RawStreamImage[ MAX_FLOPPYDRIVES ][ IPF_MAX_TRACK_RAW_STREAM_IMAGE ][ IPF_MAX_SIDE_RAW_STREAM_IMAGE ]; typedef struct { #ifdef HAVE_CAPSIMAGE uint32_t CapsLibRelease; uint32_t CapsLibRevision; struct CapsFdc Fdc; /* Fdc state */ struct CapsDrive Drive[ MAX_FLOPPYDRIVES ]; /* Physical drives */ CapsLong CapsImage[ MAX_FLOPPYDRIVES ]; /* Image Id or -1 if drive empty */ CapsLong CapsImageType[ MAX_FLOPPYDRIVES ]; /* ImageType or -1 if not known */ int Rev_Track[ MAX_FLOPPYDRIVES ]; /* Needed to handle CAPSSetRevolution for type II/III commands */ int Rev_Side[ MAX_FLOPPYDRIVES ]; bool DriveEnabled[ MAX_FLOPPYDRIVES ];/* Is drive ON or OFF */ bool DoubleSided[ MAX_FLOPPYDRIVES ];/* Is drive double sided or not */ #endif int64_t FdcClock; /* Current value of CyclesGlobalClockCounter */ } IPF_STRUCT; static IPF_STRUCT IPF_State; /* All variables related to the IPF support */ static bool IPF_Eject_RawStreamImage ( int Drive ); #ifdef HAVE_CAPSIMAGE static char *IPF_FilenameFindTrackSide (char *FileName); static bool IPF_Insert_RawStreamImage ( int Drive ); static void IPF_CallBack_Trk ( struct CapsFdc *pc , CapsULong State ); static void IPF_CallBack_Irq ( struct CapsFdc *pc , CapsULong State ); static void IPF_CallBack_Drq ( struct CapsFdc *pc , CapsULong State ); static void IPF_Drive_Update_Enable_Side ( void ); static void IPF_FDC_LogCommand ( uint8_t Command ); #endif /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) * We must take care of whether Hatari was compiled with IPF support of not * when saving/restoring snapshots to avoid incompatibilities. */ void IPF_MemorySnapShot_Capture(bool bSave) { int StructSize; int Drive; int Track , Side; int TrackSize; uint8_t *p; if ( bSave ) /* Saving snapshot */ { StructSize = sizeof ( IPF_State ); /* 0 if HAVE_CAPSIMAGE is not defined */ MemorySnapShot_Store(&StructSize, sizeof(StructSize)); if ( StructSize > 0 ) { MemorySnapShot_Store(&IPF_State, sizeof(IPF_State)); /* Save the content of IPF_RawStreamImage[] */ for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ ) for ( Track=0 ; Track 0 ) MemorySnapShot_Store(IPF_RawStreamImage[ Drive ][ Track ][Side].TrackData, TrackSize); } } } else /* Restoring snapshot */ { MemorySnapShot_Store(&StructSize, sizeof(StructSize)); if ( ( StructSize == 0 ) && ( sizeof ( IPF_State ) > 0 ) ) { Log_AlertDlg(LOG_ERROR, "Hatari built with IPF floppy support, but no IPF data in memory snapshot -> skip"); return; /* Continue restoring the rest of the memory snapshot */ } else if ( ( StructSize > 0 ) && ( sizeof ( IPF_State ) == 0 ) ) { Log_AlertDlg(LOG_ERROR, "Memory snapshot with IPF floppy data, but Hatari built without IPF support -> skip"); MemorySnapShot_Skip( StructSize ); /* Ignore the IPF data */ return; /* Continue restoring the rest of the memory snapshot */ } else if ( ( StructSize > 0 ) && ( StructSize != sizeof ( IPF_State ) ) ) { Log_AlertDlg(LOG_ERROR, "Memory snapshot IPF floppy data incompatible with this Hatari version -> skip"); MemorySnapShot_Skip( StructSize ); /* Ignore the IPF data */ return; /* Continue restoring the rest of the memory snapshot */ } if ( StructSize > 0 ) { MemorySnapShot_Store(&IPF_State, sizeof(IPF_State)); #ifdef HAVE_CAPSIMAGE /* For IPF structures, we need to update some pointers in Fdc/Drive/CapsImage */ /* drive : PUBYTE trackbuf, PUDWORD timebuf */ /* fdc : PCAPSDRIVE driveprc, PCAPSDRIVE drive, CAPSFDCHOOK callback functions */ IPF_State.Fdc.drive = IPF_State.Drive; /* Connect drives array to the FDC */ if ( IPF_State.Fdc.driveprc != NULL ) /* Recompute active drive's pointer */ IPF_State.Fdc.driveprc = IPF_State.Fdc.drive + IPF_State.Fdc.driveact; CAPSFdcInvalidateTrack ( &IPF_State.Fdc , 0 ); /* Invalidate buffered track data for drive 0 */ CAPSFdcInvalidateTrack ( &IPF_State.Fdc , 1 ); /* Invalidate buffered track data for drive 1 */ /* Set callback functions */ IPF_State.Fdc.cbirq = IPF_CallBack_Irq; IPF_State.Fdc.cbdrq = IPF_CallBack_Drq; IPF_State.Fdc.cbtrk = IPF_CallBack_Trk; #endif /* Call IPF_Insert to recompute IPF_State.CapsImage[ Drive ] */ for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ ) if ( EmulationDrives[Drive].ImageType == FLOPPY_IMAGE_TYPE_IPF ) if ( IPF_Insert ( Drive , EmulationDrives[Drive].pBuffer , EmulationDrives[Drive].nImageBytes ) == false ) { Log_AlertDlg(LOG_ERROR, "Error restoring IPF image %s in drive %d" , EmulationDrives[Drive].sFileName , Drive ); return; } /* Restore the content of IPF_RawStreamImage[] */ /* NOTE : IPF_Insert above might already have read the raw tracks from disk, */ /* so we free all those tracks and read them again from the snapshot instead */ /* (not very efficient, but it's a rare case anyway) */ for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ ) { IPF_Eject_RawStreamImage ( Drive ); for ( Track=0 ; Track 0 ) { p = malloc ( TrackSize ); if ( p == NULL ) { Log_AlertDlg(LOG_ERROR, "Error restoring IPF raw track drive %d track %d side %d size %d" , Drive, Track, Side , TrackSize ); return; } MemorySnapShot_Store(p, TrackSize); IPF_RawStreamImage[ Drive ][ Track ][Side].TrackData = p; } } } Log_Printf ( LOG_DEBUG , "ipf load ok\n" ); } } } /*-----------------------------------------------------------------------*/ /** * Does filename end with a .IPF or .RAW or .CTR extension ? If so, return true. * .RAW and .CTR support requires caps lib >= 5.1 */ bool IPF_FileNameIsIPF(const char *pszFileName, bool bAllowGZ) { return ( File_DoesFileExtensionMatch(pszFileName,".ipf" ) || ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".ipf.gz") ) || File_DoesFileExtensionMatch(pszFileName,".raw" ) || ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".raw.gz") ) || File_DoesFileExtensionMatch(pszFileName,".ctr" ) || ( bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".ctr.gz") ) ); } /* * Return a pointer to the part "tt.s.raw" at the end of the filename * (there can be an extra suffix to ignore if the file is compressed). * If we found a string where "tt" and "s" are digits, then we return * a pointer to this string. * If not found, we return NULL */ #ifdef HAVE_CAPSIMAGE static char *IPF_FilenameFindTrackSide (char *FileName) { char ext[] = ".raw"; int len; char *p; len = strlen ( FileName ); len -= strlen ( ext ); while ( len >= 4 ) /* need at least 4 chars for "tt.s" */ { if ( strncasecmp ( ext , FileName + len , strlen ( ext ) ) == 0 ) { p = FileName + len - 4; if ( isdigit( p[0] ) && isdigit( p[1] ) && ( p[2] == '.' ) && isdigit( p[3] ) ) return p; } len--; } return NULL; } #endif /*-----------------------------------------------------------------------*/ /** * Load .IPF file into memory, set number of bytes loaded and return a pointer * to the buffer. */ uint8_t *IPF_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType) { #ifndef HAVE_CAPSIMAGE Log_AlertDlg(LOG_ERROR, "Hatari built without IPF support -> can't handle floppy image"); return NULL; #else uint8_t *pIPFFile; *pImageSize = 0; /* Just load directly a buffer, and set ImageSize accordingly */ pIPFFile = File_Read(pszFileName, pImageSize, NULL); if (!pIPFFile) { *pImageSize = 0; return NULL; } *pImageType = FLOPPY_IMAGE_TYPE_IPF; return pIPFFile; #endif } /*-----------------------------------------------------------------------*/ /** * Save .IPF file from memory buffer. Returns true is all OK. */ bool IPF_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize) { /* saving is not supported for IPF files */ return false; } /* * Init the FDC and the drives used to handle IPF images */ bool IPF_Init ( void ) { #ifndef HAVE_CAPSIMAGE return true; #else int i; struct CapsVersionInfo caps_vi; Log_Printf ( LOG_DEBUG , "IPF : IPF_Init\n" ); if ( CAPSInit() != imgeOk ) { Log_Printf ( LOG_ERROR , "IPF : Could not initialize the capsimage library\n" ); return false; } if ( CAPSGetVersionInfo ( &caps_vi , 0 ) != imgeOk ) { Log_Printf ( LOG_ERROR , "IPF : CAPSVersionInfo failed\n" ); return false; } Log_Printf ( LOG_INFO , "IPF : capsimage library version release=%d revision=%d\n" , (int)caps_vi.release , (int)caps_vi.revision ); IPF_State.CapsLibRelease = caps_vi.release; IPF_State.CapsLibRevision = caps_vi.revision; /* Default values for each physical drive */ memset ( IPF_State.Drive , 0 , sizeof ( IPF_State.Drive ) ); for ( i=0 ; i < MAX_FLOPPYDRIVES ; i++ ) { IPF_State.Drive[ i ].type = sizeof ( struct CapsDrive ); IPF_State.Drive[ i ].rpm = CAPSDRIVE_35DD_RPM; IPF_State.Drive[ i ].maxtrack = CAPSDRIVE_35DD_HST; IPF_State.Rev_Track[ i ] = -1; IPF_State.Rev_Side[ i ] = -1; IPF_State.DriveEnabled[ i ] = true; IPF_State.DoubleSided[ i ] = true; IPF_State.CapsImageType[ i ] = -1; } /* Init FDC with 2 physical drives */ memset ( &IPF_State.Fdc , 0 , sizeof ( IPF_State.Fdc ) ); IPF_State.Fdc.type = sizeof( struct CapsFdc ); IPF_State.Fdc.model = cfdcmWD1772; IPF_State.Fdc.drive = IPF_State.Drive; IPF_State.Fdc.drivecnt = MAX_FLOPPYDRIVES; if ( CAPSFdcInit ( &IPF_State.Fdc ) != imgeOk) { Log_Printf ( LOG_ERROR , "IPF : CAPSFdcInit failed\n" ); return false; } /* 2 drives by default */ IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES; /* Update drives' state in case we have some drives ON or OFF in config or parameters */ IPF_Drive_Update_Enable_Side (); /* FDC clock */ IPF_State.Fdc.clockfrq = 8000000; /* Set callback functions */ IPF_State.Fdc.cbirq = IPF_CallBack_Irq; IPF_State.Fdc.cbdrq = IPF_CallBack_Drq; IPF_State.Fdc.cbtrk = IPF_CallBack_Trk; CAPSFdcReset ( &IPF_State.Fdc ); return true; #endif } /* * Exit */ void IPF_Exit ( void ) { #ifndef HAVE_CAPSIMAGE #else CAPSExit(); #endif } /* * Init the resources to handle the IPF image inserted into a drive (0=A: 1=B:) */ bool IPF_Insert ( int Drive , uint8_t *pImageBuffer , long ImageSize ) { #ifndef HAVE_CAPSIMAGE return false; #else CapsLong ImageId; CapsLong ImageType; const char *ImageTypeStr; bool Type_OK; ImageId = CAPSAddImage(); if ( ImageId < 0 ) { Log_Printf ( LOG_ERROR , "IPF : error CAPSAddImage\n" ); return false; } ImageType = CAPSGetImageTypeMemory ( pImageBuffer , ImageSize ); if ( ImageType == citError ) { Log_Printf ( LOG_ERROR , "IPF : error CAPSGetImageTypeMemory\n" ); CAPSRemImage ( ImageId ) ; return false; } else if ( ImageType == citUnknown ) { Log_Printf ( LOG_ERROR , "IPF : unknown image type\n" ); CAPSRemImage ( ImageId ) ; return false; } Type_OK = true; switch ( ImageType ) { case citIPF: ImageTypeStr = "IPF"; break; case citCTRaw: ImageTypeStr = "CT RAW"; break; case citKFStream: ImageTypeStr = "KF STREAM" ; break; case citDraft: ImageTypeStr = "DRAFT" ; break; default : ImageTypeStr = "NOT SUPPORTED\n"; Type_OK = false; } Log_Printf ( LOG_INFO , "IPF : IPF_Insert drive=%d buf=%p size=%ld imageid=%d type=%s\n" , Drive , pImageBuffer , ImageSize , ImageId , ImageTypeStr ); if ( !Type_OK ) { CAPSRemImage ( ImageId ) ; return false; } /* Special case for RAW stream image, we load all the tracks now */ if ( ImageType == citKFStream ) { if ( IPF_Insert_RawStreamImage ( Drive ) == false ) { Log_Printf ( LOG_ERROR , "IPF : can't load raw stream files\n" ); CAPSRemImage ( ImageId ) ; return false; } } if ( CAPSLockImageMemory ( ImageId , pImageBuffer , (CapsULong)ImageSize , DI_LOCK_MEMREF ) == imgeOk ) { struct CapsImageInfo cii; int i; /* Print some debug infos */ if ( CAPSGetImageInfo ( &cii , ImageId ) == imgeOk ) { Log_Printf ( LOG_INFO , "\tType: %d\n", (int)cii.type); Log_Printf ( LOG_INFO , "\tRelease: %d\n", (int)cii.release); Log_Printf ( LOG_INFO , "\tRevision: %d\n", (int)cii.revision); Log_Printf ( LOG_INFO , "\tMin Cylinder: %d\n", (int)cii.mincylinder); Log_Printf ( LOG_INFO , "\tMax Cylinder: %d\n", (int)cii.maxcylinder); Log_Printf ( LOG_INFO , "\tMin Head: %d\n", (int)cii.minhead); Log_Printf ( LOG_INFO , "\tMax Head: %d\n", (int)cii.maxhead); Log_Printf ( LOG_INFO , "\tCreation Date: %04d/%02d/%02d %02d:%02d:%02d.%03d\n", (int)cii.crdt.year, (int)cii.crdt.month, (int)cii.crdt.day, (int)cii.crdt.hour, (int)cii.crdt.min, (int)cii.crdt.sec, (int)cii.crdt.tick); Log_Printf ( LOG_INFO , "\tPlatforms:"); for (i = 0; i < CAPS_MAXPLATFORM; i++) if (cii.platform[i] != ciipNA) Log_Printf ( LOG_INFO , " %s" , CAPSGetPlatformName(cii.platform[i]) ); Log_Printf ( LOG_INFO , "\n"); /* Some IPF disks are not correctly supported yet : display a warning */ if ( (int)cii.release == 3222 ) /* Sundog */ Log_AlertDlg ( LOG_INFO , "'Sundog' is not correctly supported yet, it requires write access." ); else if ( (int)cii.release == 3058 ) /* Lethal Xcess */ Log_AlertDlg ( LOG_INFO , "'Lethal Xcess' is not correctly supported yet, protection will fail" ); } } else { CAPSRemImage ( ImageId ) ; return false; } if ( CAPSLoadImage ( ImageId , DI_LOCK_DENALT | DI_LOCK_DENVAR | DI_LOCK_UPDATEFD ) != imgeOk ) { Log_Printf ( LOG_ERROR , "IPF : error CAPSLoadImage\n" ); CAPSUnlockImage ( ImageId ); CAPSRemImage ( ImageId ) ; return false; } IPF_State.CapsImage[ Drive ] = ImageId; IPF_State.CapsImageType[ Drive ] = ImageType; IPF_State.Drive[ Drive ].diskattr |= CAPSDRIVE_DA_IN; /* Disk inserted, keep the value for "write protect" */ CAPSFdcInvalidateTrack ( &IPF_State.Fdc , Drive ); /* Invalidate previous buffered track data for drive, if any */ IPF_State.Rev_Track[ Drive ] = -1; /* Invalidate previous track/side to handle revolution's count */ IPF_State.Rev_Side[ Drive ] = -1; return true; #endif } /* * Load all the raw stream files for all tracks/sides of a dump. * We use the filename of the raw file in drive 'Drive' as a template * where we replace track and side will all the possible values. */ #ifdef HAVE_CAPSIMAGE static bool IPF_Insert_RawStreamImage ( int Drive ) { int Track , Side; char TrackFileName[ FILENAME_MAX ]; char *TrackSide_pointer; char TrackSide_buf[ 4 + 1 ]; /* "tt.s" + \0 */ int TrackCount; int TrackCount_0 , TrackCount_1; uint8_t *p; long Size; return true; /* This function is not used for now, always return true */ /* Ensure the previous tracks are removed from memory */ IPF_Eject_RawStreamImage ( Drive ); /* Get the path+filename of the raw file that was inserted in 'Drive' */ /* then parse it to find the part with track/side */ strcpy ( TrackFileName , ConfigureParams.DiskImage.szDiskFileName[Drive] ); TrackSide_pointer = IPF_FilenameFindTrackSide ( TrackFileName ); if ( TrackSide_pointer == NULL ) { Log_Printf ( LOG_ERROR , "IPF : error parsing track/side in raw filename\n" ); return false; } /* We try to load all the tracks for all the sides */ /* We ignore errors, as some tracks/side can really be missing from the image dump */ TrackCount = 0; TrackCount_0 = 0; TrackCount_1 = 0; for ( Track=0 ; Track not found\n" , Drive , Track , Side , TrackFileName ); /* File not loaded : either this track really doesn't exist or there was a system error */ IPF_RawStreamImage[ Drive ][ Track ][Side].TrackData = NULL; IPF_RawStreamImage[ Drive ][ Track ][Side].TrackSize = 0; } } } /* If we didn't load any track, there's a problem, we stop here */ if ( TrackCount == 0 ) { Log_Printf ( LOG_WARN , "IPF : error, no raw track file could be loaded for %s\n" , ConfigureParams.DiskImage.szDiskFileName[Drive] ); /* Free all the tracks that were loaded so far */ IPF_Eject_RawStreamImage ( Drive ); return false; } Log_Printf ( LOG_INFO , "IPF : insert raw stream drive=%d, loaded %d tracks for side 0 and %d tracks for side 1\n", Drive, TrackCount_0, TrackCount_1 ); return true; } #endif /* * When ejecting a disk, free the resources associated with an IPF image */ bool IPF_Eject ( int Drive ) { #ifndef HAVE_CAPSIMAGE return false; #else Log_Printf ( LOG_DEBUG , "IPF : IPF_Eject drive=%d imageid=%d\n" , Drive , IPF_State.CapsImage[ Drive ] ); CAPSFdcInvalidateTrack ( &IPF_State.Fdc , Drive ); /* Invalidate previous buffered track data for drive, if any */ if ( CAPSUnlockImage ( IPF_State.CapsImage[ Drive ] ) < 0 ) { Log_Printf ( LOG_ERROR , "IPF : error CAPSUnlockImage\n" ); return false; } if ( CAPSRemImage ( IPF_State.CapsImage[ Drive ] ) < 0 ) { Log_Printf ( LOG_ERROR , "IPF : error CAPSRemImage\n" ); return false; } /* Special case for RAW stream image, we must free all the tracks */ if ( IPF_State.CapsImageType[ Drive ] == citKFStream ) IPF_Eject_RawStreamImage ( Drive ); IPF_State.CapsImage[ Drive ] = -1; IPF_State.CapsImageType[ Drive ] = -1; IPF_State.Drive[ Drive ].diskattr &= ~CAPSDRIVE_DA_IN; return true; #endif } /* * When ejecting a RAW stream image we must free all the individual tracks */ static bool IPF_Eject_RawStreamImage ( int Drive ) { #ifndef HAVE_CAPSIMAGE return true; #else int Track , Side; return true; /* This function is not used for now, always return true */ for ( Track=0 ; Trackdrive+Drive; /* Current drive where the track change occurred */ struct CapsTrackInfoT1 cti; cti.type=1; if ( CAPSLockTrack ( &cti , IPF_State.CapsImage[ Drive ] , pd->buftrack , pd->bufside , DI_LOCK_DENALT|DI_LOCK_DENVAR|DI_LOCK_UPDATEFD|DI_LOCK_TYPE ) != imgeOk ) return; LOG_TRACE(TRACE_FDC, "fdc ipf callback trk drive=%d buftrack=%d bufside=%d VBL=%d HBL=%d\n" , Drive , (int)pd->buftrack , (int)pd->bufside , nVBLs , nHBL ); pd->ttype = cti.type; pd->trackbuf = cti.trackbuf; pd->timebuf = cti.timebuf; pd->tracklen = cti.tracklen; pd->overlap = cti.overlap; } #endif /* * Callback function used when the FDC change the IRQ signal */ #ifdef HAVE_CAPSIMAGE static void IPF_CallBack_Irq ( struct CapsFdc *pc , CapsULong State ) { LOG_TRACE(TRACE_FDC, "fdc ipf callback irq state=0x%x VBL=%d HBL=%d\n" , (int)State , nVBLs , nHBL ); if ( State ) FDC_SetIRQ ( FDC_IRQ_SOURCE_OTHER ); /* IRQ bit was set */ else FDC_ClearIRQ (); /* IRQ bit was reset */ } #endif /* * Callback function used when the FDC change the DRQ signal * -> copy the byte to/from the DMA's FIFO if it's a read or a write to the disk */ #ifdef HAVE_CAPSIMAGE static void IPF_CallBack_Drq ( struct CapsFdc *pc , CapsULong State ) { uint8_t Byte; if ( State == 0 ) return; /* DRQ bit was reset, do nothing */ if ( FDC_DMA_GetModeControl_R_WR () != 0 ) /* DMA write mode */ { Byte = FDC_DMA_FIFO_Pull (); /* Get a byte from the DMA FIFO */ CAPSFdcWrite ( &IPF_State.Fdc , 3 , Byte ); /* Write to FDC's reg 3 */ LOG_TRACE(TRACE_FDC, "fdc ipf callback drq state=0x%x write byte 0x%02x VBL=%d HBL=%d\n" , (int)State , Byte , nVBLs , nHBL ); } else /* DMA read mode */ { Byte = CAPSFdcRead ( &IPF_State.Fdc , 3 ); /* Read from FDC's reg 3 */ FDC_DMA_FIFO_Push ( Byte ); /* Add byte to the DMA FIFO */ LOG_TRACE(TRACE_FDC, "fdc ipf callback drq state=0x%x read byte 0x%02x VBL=%d HBL=%d\n" , (int)State , Byte , nVBLs , nHBL ); } } #endif /* * This function is used to enable/disable a drive when * using the UI or command line parameters * * NOTE : for now, IPF only supports changing drive 1, drive 0 * is always ON. */ void IPF_Drive_Set_Enable ( int Drive , bool value ) { #ifndef HAVE_CAPSIMAGE return; #else IPF_State.DriveEnabled[ Drive ] = value; /* Store the new state */ IPF_Drive_Update_Enable_Side (); /* Update IPF's internal state */ #endif } /* * This function is used to configure a drive as single sided * or double sided when using the UI or command line parameters */ void IPF_Drive_Set_DoubleSided ( int Drive , bool value ) { #ifndef HAVE_CAPSIMAGE return; #else IPF_State.DoubleSided[ Drive ] = value; /* Store the new state */ IPF_Drive_Update_Enable_Side (); /* Update IPF's internal state */ #endif } /* * Update IPF's internal state depending on which drives are ON or OFF * and if the drive is single or double sided (for capslib >= 5.1) */ #ifdef HAVE_CAPSIMAGE static void IPF_Drive_Update_Enable_Side ( void ) { int i; if ( IPF_State.DriveEnabled[ 1 ] ) IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES; /* Should be 2 */ else IPF_State.Fdc.drivemax = MAX_FLOPPYDRIVES - 1; /* Should be 1 */ for ( i=0 ; i < MAX_FLOPPYDRIVES ; i++ ) { if ( IPF_State.DoubleSided[ i ] ) IPF_State.Drive[ i ].diskattr &= ~CAPSDRIVE_DA_SS; /* Double sided */ else IPF_State.Drive[ i ].diskattr |= CAPSDRIVE_DA_SS; /* Single sided */ } } #endif /* * Set the drive and the side to be used for the next FDC commands * io_porta_old is the previous value, io_porta_new is the new value * to take into account. * We report a side change only when a drive is selected. */ void IPF_SetDriveSide ( uint8_t io_porta_old , uint8_t io_porta_new ) { #ifndef HAVE_CAPSIMAGE return; #else int Side; LOG_TRACE(TRACE_FDC, "fdc ipf change drive/side io_porta_old=0x%x io_porta_new=0x%x VBL=%d HBL=%d\n" , io_porta_old , io_porta_new , nVBLs , nHBL ); Side = ( (~io_porta_new) & 0x01 ); /* Side 0 or 1 */ IPF_State.Fdc.drivenew = -1; /* By default, don't select any drive */ /* Check drive 1 first */ if ( ( io_porta_new & 0x04 ) == 0 ) { IPF_State.Drive[ 1 ].newside = Side; IPF_State.Fdc.drivenew = 1; /* Select drive 1 */ } /* If both drive 0 and drive 1 are enabled, we keep only drive 0 as newdrive */ if ( ( io_porta_new & 0x02 ) == 0 ) { IPF_State.Drive[ 0 ].newside = Side; IPF_State.Fdc.drivenew = 0; /* Select drive 0 (and un-select drive 1 if set above) */ } IPF_Emulate(); /* Update emulation's state up to this point, then set new drive/side */ #endif } /* * Write a byte into one of the FDC registers * 0=command 1=track 2=sector 3=data */ void IPF_FDC_WriteReg ( uint8_t Reg , uint8_t Byte ) { #ifndef HAVE_CAPSIMAGE return; /* This should not be reached (an IPF image can't be inserted without capsimage) */ #else if ( Reg == 0 ) /* more detailed logs for command register */ IPF_FDC_LogCommand ( Byte ); else LOG_TRACE(TRACE_FDC, "fdc ipf write reg=%d data=0x%x VBL=%d HBL=%d\n" , Reg , Byte , nVBLs , nHBL ); /* In the case of CTR images, we must reset the revolution counter */ /* when a command access data on disk and track/side changed since last access */ if ( Reg == 0 ) { int Type; int Drive; Type = FDC_GetCmdType ( Byte ); if ( ( Type == 2 ) || ( Type == 3 ) ) { Drive = IPF_State.Fdc.driveact; if ( ( Drive >= 0 ) && ( ( IPF_State.Drive[ Drive ].side != IPF_State.Rev_Side[ Drive ] ) || ( IPF_State.Drive[ Drive ].track != IPF_State.Rev_Track[ Drive ] ) ) ) { IPF_State.Rev_Side[ Drive ] = IPF_State.Drive[ Drive ].side; IPF_State.Rev_Track[ Drive ] = IPF_State.Drive[ Drive ].track; CAPSSetRevolution ( IPF_State.CapsImage[ Drive ] , 0 ); } } } IPF_Emulate(); /* Update emulation's state up to this point */ CAPSFdcWrite ( &IPF_State.Fdc , Reg , Byte ); #endif } /* * Read the content of one of the FDC registers * 0=status 1=track 2=sector 3=data */ uint8_t IPF_FDC_ReadReg ( uint8_t Reg ) { #ifndef HAVE_CAPSIMAGE return 0; /* This should not be reached (an IPF image can't be inserted without capsimage) */ #else uint8_t Byte; IPF_Emulate(); /* Update emulation's state up to this point */ Byte = CAPSFdcRead ( &IPF_State.Fdc , Reg ); LOG_TRACE(TRACE_FDC, "fdc ipf read reg=%d data=0x%x VBL=%d HBL=%d\n" , Reg , Byte , nVBLs , nHBL ); return Byte; #endif } /* * Return the content of some registers to display them in the statusbar * We should not call IPF_Emulate() or similar, reading should not change emulation's state */ void IPF_FDC_StatusBar ( uint8_t *pCommand , uint8_t *pHead , uint8_t *pTrack , uint8_t *pSector , uint8_t *pSide ) { #ifndef HAVE_CAPSIMAGE abort(); /* This should not be reached (an IPF image can't be inserted without capsimage) */ #else int Drive; Drive = IPF_State.Fdc.driveact; if ( Drive < 0 ) /* If no drive enabled, use drive O for Head/Side */ Drive = 0; /* We read directly in the structures, to be sure we don't change emulation's state */ *pCommand = IPF_State.Fdc.r_command; *pHead = IPF_State.Drive[ Drive ].track; *pTrack = IPF_State.Fdc.r_track; *pSector = IPF_State.Fdc.r_sector; *pSide = IPF_State.Drive[ Drive ].side; #endif } #ifdef HAVE_CAPSIMAGE static void IPF_FDC_LogCommand ( uint8_t Command ) { uint8_t Head , Track , Sector , Side , DataReg; int Drive; int FrameCycles, HblCounterVideo, LineCycles; char buf[ 200 ]; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); Drive = IPF_State.Fdc.driveact; if ( Drive < 0 ) /* If no drive enabled, use drive O for Head/Side */ Drive = 0; /* We read directly in the structures, to be sure we don't change emulation's state */ Head = IPF_State.Drive[ Drive ].track; Track = IPF_State.Fdc.r_track; Sector = IPF_State.Fdc.r_sector; DataReg = IPF_State.Fdc.r_data; Side = IPF_State.Drive[ Drive ].side; if ( ( Command & 0xf0 ) == 0x00 ) /* Restore */ sprintf ( buf , "type I restore spinup=%s verify=%s steprate=%d drive=%d tr=0x%x head_track=0x%x" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ Command & 0x03 ] , Drive , Track , Head ); else if ( ( Command & 0xf0 ) == 0x10 ) /* Seek */ sprintf ( buf , "type I seek dest_track=0x%x spinup=%s verify=%s steprate=%d drive=%d tr=0x%x head_track=0x%x" , DataReg , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ Command & 0x03 ] , Drive , Track , Head ); else if ( ( Command & 0xe0 ) == 0x20 ) /* Step */ sprintf ( buf , "type I step %d spinup=%s verify=%s steprate_ms=%d drive=%d tr=0x%x head_track=0x%x", ( IPF_State.Fdc.lineout & CAPSFDC_LO_DIRC ) ? 1 : -1 , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ Command & 0x03 ] , Drive , Track , Head ); else if ( ( Command & 0xe0 ) == 0x40 ) /* Step In */ sprintf ( buf , "type I step in spinup=%s verify=%s steprate=%d drive=%d tr=0x%x head_track=0x%x" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ Command & 0x03 ] , Drive , Track , Head ); else if ( ( Command & 0xe0 ) == 0x60 ) /* Step Out */ sprintf ( buf , "type I step out spinup=%s verify=%s steprate=%d drive=%d tr=0x%x head_track=0x%x" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_VERIFY ) ? "on" : "off" , FDC_StepRate_ms[ Command & 0x03 ] , Drive , Track , Head ); else if ( ( Command & 0xe0 ) == 0x80 ) /* Read Sector */ sprintf ( buf , "type II read sector sector=0x%x multi=%s spinup=%s settle=%s tr=0x%x head_track=0x%x" " side=%d drive=%d dmasector=%d addr=0x%x", Sector, ( Command & FDC_COMMAND_BIT_MULTIPLE_SECTOR ) ? "on" : "off" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , Track , Head , Side , Drive , FDC_DMA_GetSectorCount() , FDC_GetDMAAddress() ); else if ( ( Command & 0xe0 ) == 0xa0 ) /* Write Sector */ sprintf ( buf , "type II write sector sector=0x%x multi=%s spinup=%s settle=%s tr=0x%x head_track=0x%x" " side=%d drive=%d dmasector=%d addr=0x%x", Sector, ( Command & FDC_COMMAND_BIT_MULTIPLE_SECTOR ) ? "on" : "off" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , Track , Head , Side , Drive , FDC_DMA_GetSectorCount() , FDC_GetDMAAddress() ); else if ( ( Command & 0xf0 ) == 0xc0 ) /* Read Address */ sprintf ( buf , "type III read address spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d addr=0x%x" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , Track , Head , Side , Drive , FDC_GetDMAAddress() ); else if ( ( Command & 0xf0 ) == 0xe0 ) /* Read Track */ sprintf ( buf , "type III read track spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d addr=0x%x" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , Track , Head , Side , Drive , FDC_GetDMAAddress() ); else if ( ( Command & 0xf0 ) == 0xf0 ) /* Write Track */ sprintf ( buf , "type III write track spinup=%s settle=%s tr=0x%x head_track=0x%x side=%d drive=%d addr=0x%x" , ( Command & FDC_COMMAND_BIT_SPIN_UP ) ? "off" : "on" , ( Command & FDC_COMMAND_BIT_HEAD_LOAD ) ? "on" : "off" , Track , Head , Side , Drive , FDC_GetDMAAddress() ); else /* Force Int */ sprintf ( buf , "type IV force int 0x%x irq=%d index=%d" , Command , ( Command & 0x8 ) >> 3 , ( Command & 0x4 ) >> 2 ); LOG_TRACE(TRACE_FDC, "fdc ipf %s VBL=%d video_cyc=%d %d@%d pc=%x\n" , buf , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } #endif /* * Run the FDC emulation during NbCycles cycles (relative to the 8MHz FDC's clock) */ void IPF_Emulate ( void ) { #ifndef HAVE_CAPSIMAGE return; #else int NbCycles; int Drive; NbCycles = CyclesGlobalClockCounter - IPF_State.FdcClock; /* Number of cycles since last emulation */ if ( NbCycles < 0 ) NbCycles = 0; /* We should call CAPSFdcEmulate even when NbCycles=0 */ // LOG_TRACE(TRACE_FDC, "fdc ipf emulate cycles=%d VBL=%d HBL=%d clock=%lld\n" , NbCycles , nVBLs , nHBL , CyclesGlobalClockCounter ); /* Update Write Protect status for each drive */ for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ ) if ( Floppy_IsWriteProtected ( Drive ) ) IPF_State.Drive[ Drive ].diskattr |= CAPSDRIVE_DA_WP; /* Disk write protected */ else IPF_State.Drive[ Drive ].diskattr &= ~CAPSDRIVE_DA_WP; /* Disk is not write protected */ CAPSFdcEmulate ( &IPF_State.Fdc , NbCycles ); /* Process at max NbCycles */ IPF_State.FdcClock += IPF_State.Fdc.clockact; /* clockact can be < NbCycle in some cases */ /* Update UI's LEDs depending on Status Register */ FDC_Drive_Set_BusyLed ( (IPF_State.Fdc.r_st0 & ~IPF_State.Fdc.r_stm) | (IPF_State.Fdc.r_st1 & IPF_State.Fdc.r_stm) ); #endif } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/floppy_stx.c000066400000000000000000002353161504763705000237150ustar00rootroot00000000000000/* Hatari - floppy_stx.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. STX disk image support. STX files are created using the program 'Pasti' made by Jorge Cwik (Ijor). As no official documentation exists, this file is based on the reverse engineering and docs made by the following people, mainly using Pasti 0.4b : - Markus Fritze (Sarnau) - P. Putnik - Jean Louis Guerin (Dr CoolZic) - Nicolas Pomarede */ const char floppy_stx_fileid[] = "Hatari floppy_stx.c"; #include "main.h" #include "file.h" #include "floppy.h" #include "floppy_stx.h" #include "fdc.h" #include "log.h" #include "memorySnapShot.h" #include "video.h" #include "cycles.h" #include "str.h" #include "utils.h" #define STX_DEBUG_FLAG_STRUCTURE 1 #define STX_DEBUG_FLAG_DATA 2 #define STX_DEBUG_FLAG 0 // #define STX_DEBUG_FLAG ( STX_DEBUG_FLAG_STRUCTURE ) // #define STX_DEBUG_FLAG ( STX_DEBUG_FLAG_STRUCTURE | STX_DEBUG_FLAG_DATA ) #define FDC_DELAY_CYCLE_MFM_BIT ( 4 * 8 ) /* 4 us per bit, 8 MHz clock -> 32 cycles */ #define FDC_DELAY_CYCLE_MFM_BYTE ( 4 * 8 * 8 ) /* 4 us per bit, 8 bits per byte, 8 MHz clock -> 256 cycles */ #define FDC_TRACK_BYTES_STANDARD 6250 #define WD1772_SAVE_FILE_EXT ".wd1772" #define WD1772_SAVE_FILE_ID "WD1772" /* 6 bytes */ #define WD1772_SAVE_VERSION 1 #define WD1772_SAVE_REVISION 0 #define WD1772_SAVE_SECTOR_ID "SECT" /* 4 bytes */ #define WD1772_SAVE_TRACK_ID "TRCK" /* 4 bytes */ typedef struct { STX_MAIN_STRUCT *ImageBuffer[ MAX_FLOPPYDRIVES ]; /* For the STX disk images */ uint32_t NextSectorStruct_Nbr; /* Sector Number in pSectorsStruct after a call to FDC_NextSectorID_FdcCycles_STX() */ uint8_t NextSector_ID_Field_TR; /* Track value in the next ID Field after a call to FDC_NextSectorID_FdcCycles_STX() */ uint8_t NextSector_ID_Field_SR; /* Sector value in the next ID Field after a call to FDC_NextSectorID_FdcCycles_STX() */ uint8_t NextSector_ID_Field_LEN; /* Sector's length in the next ID Field after a call to FDC_NextSectorID_FdcCycles_STX() */ uint8_t NextSector_ID_Field_CRC_OK; /* CRC OK or not in the next ID Field after a call to FDC_NextSectorID_FdcCycles_STX() */ } STX_STRUCT; static STX_STRUCT STX_State; /* All variables related to the STX support */ static STX_SAVE_STRUCT STX_SaveStruct[ MAX_FLOPPYDRIVES ]; /* To save 'write sector' data */ /* Default timing table for Macrodos when revision=0 */ /* 1 unit of timing means 32 FDC cycles ; + 28 cycles every 16 bytes, so a standard block of 16 bytes */ /* should have a value of 0x7f or 0x80, which gives 4092-4124 cycles */ static uint8_t TimingDataDefault[] = { 0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f, 0x00,0x85,0x00,0x85,0x00,0x85,0x00,0x85,0x00,0x85,0x00,0x85,0x00,0x85,0x00,0x85, 0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79, 0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f,0x00,0x7f }; /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static bool STX_LoadSaveFile ( int Drive , const char *FilenameSave ); static bool STX_LoadSaveFile_SECT ( int Drive, STX_SAVE_SECTOR_STRUCT *pStxSaveSector , uint8_t *p ); static bool STX_LoadSaveFile_TRCK ( int Drive , STX_SAVE_TRACK_STRUCT *pStxSaveTrack , uint8_t *p ); static bool STX_Insert_internal ( int Drive , const char *FilenameSTX , uint8_t *pImageBuffer , long ImageSize ); static uint16_t STX_ReadU16_LE ( uint8_t *p ); static uint32_t STX_ReadU32_LE ( uint8_t *p ); static uint16_t STX_ReadU16_BE ( uint8_t *p ); static uint32_t STX_ReadU32_BE ( uint8_t *p ); static void STX_WriteU16_BE ( uint8_t *p , uint16_t val ); static void STX_WriteU32_BE ( uint8_t *p , uint32_t val ); static void STX_FreeStruct ( STX_MAIN_STRUCT *pStxMain ); static void STX_FreeSaveStruct ( int Drive ); static void STX_FreeSaveSectorsStructAll ( STX_SAVE_SECTOR_STRUCT *pSaveSectorsStruct , uint32_t SaveSectorsCount ); static void STX_FreeSaveSectorsStruct ( STX_SAVE_SECTOR_STRUCT *pSaveSectorsStruct , int Nb ); static void STX_FreeSaveTracksStructAll ( STX_SAVE_TRACK_STRUCT *pSaveTracksStruct , uint32_t SaveTracksCount ); static void STX_FreeSaveTracksStruct ( STX_SAVE_TRACK_STRUCT *pSaveTracksStruct , int Nb ); static void STX_BuildSectorsSimple ( STX_TRACK_STRUCT *pStxTrack , uint8_t *p ); static uint16_t STX_BuildSectorID_CRC ( STX_SECTOR_STRUCT *pStxSector ); static STX_TRACK_STRUCT *STX_FindTrack ( uint8_t Drive , uint8_t Track , uint8_t Side ); static STX_SECTOR_STRUCT *STX_FindSector ( uint8_t Drive , uint8_t Track , uint8_t Side , uint8_t SectorStruct_Nb ); static STX_SECTOR_STRUCT *STX_FindSector_By_Position ( uint8_t Drive , uint8_t Track , uint8_t Side , uint16_t BitPosition ); /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void STX_MemorySnapShot_Capture(bool bSave) { int Drive; uint32_t i; STX_SECTOR_STRUCT *pStxSector; STX_TRACK_STRUCT *pStxTrack; if ( bSave ) /* Saving snapshot */ { MemorySnapShot_Store( &STX_State , sizeof (STX_State) ); /* Also save the 'write sector' and 'write track' buffers */ for ( Drive=0 ; Drive < MAX_FLOPPYDRIVES ; Drive++ ) { /* Save the sectors' buffer */ MemorySnapShot_Store ( &STX_SaveStruct[ Drive ].SaveSectorsCount , sizeof ( STX_SaveStruct[ Drive ].SaveSectorsCount ) ); if ( STX_SaveStruct[ Drive ].SaveSectorsCount > 0 ) { /* Save all sectors in the memory state */ /* For each sector, we save the structure, then the data */ for ( i=0 ; i 0 ) { /* Save all tracks in the memory state */ /* For each track, we save the structure, then the data */ for ( i=0 ; i 0 ) { /* Alloc a buffer for all the sectors */ STX_SaveStruct[ Drive ].pSaveSectorsStruct = malloc ( STX_SaveStruct[ Drive ].SaveSectorsCount * sizeof ( STX_SAVE_SECTOR_STRUCT ) ); if ( !STX_SaveStruct[ Drive ].pSaveSectorsStruct ) { Log_AlertDlg(LOG_ERROR, "Error restoring STX sectors save buffer malloc size=%d in drive %d" , STX_SaveStruct[ Drive ].SaveSectorsCount , Drive ); return; } /* Load all the sectors from the memory state */ /* For each sector, we load the structure, then the data */ for ( i=0 ; iSaveSectorIndex = i; } } else STX_SaveStruct[ Drive ].pSaveSectorsStruct = NULL; //fprintf ( stderr , "stx load write buffer drive=%d count=%d buf=%p\n" , Drive , STX_SaveStruct[ Drive ].SaveSectorsCount , STX_SaveStruct[ Drive ].pSaveSectorsStruct ); /* Restore the tracks' buffer */ MemorySnapShot_Store ( &STX_SaveStruct[ Drive ].SaveTracksCount , sizeof ( STX_SaveStruct[ Drive ].SaveTracksCount ) ); if ( STX_SaveStruct[ Drive ].SaveTracksCount > 0 ) { /* Alloc a buffer for all the tracks */ STX_SaveStruct[ Drive ].pSaveTracksStruct = malloc ( STX_SaveStruct[ Drive ].SaveTracksCount * sizeof ( STX_SAVE_TRACK_STRUCT ) ); if ( !STX_SaveStruct[ Drive ].pSaveTracksStruct ) { Log_AlertDlg(LOG_ERROR, "Error restoring STX tracks save buffer malloc size=%d in drive %d" , STX_SaveStruct[ Drive ].SaveTracksCount , Drive ); return; } /* Load all the tracks from the memory state */ /* For each track, we load the structure, then the data */ for ( i=0 ; iSaveTrackIndex = i; } } else STX_SaveStruct[ Drive ].pSaveTracksStruct = NULL; } Log_Printf ( LOG_DEBUG , "stx load ok\n" ); } } /*-----------------------------------------------------------------------*/ /** * Does filename end with a .STX extension? If so, return true. */ bool STX_FileNameIsSTX(const char *pszFileName, bool bAllowGZ) { return(File_DoesFileExtensionMatch(pszFileName,".stx") || (bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".stx.gz"))); } /*-----------------------------------------------------------------------*/ /** * Create a filename to save modifications made to an STX file * We replace the ".stx" or ".stx.gz" extension with ".wd1772" * Return true if OK */ bool STX_FileNameToSave ( const char *FilenameSTX , char *FilenameSave ) { if ( File_ChangeFileExtension ( FilenameSTX , ".stx.gz" , FilenameSave , WD1772_SAVE_FILE_EXT ) ) return true; else if ( File_ChangeFileExtension ( FilenameSTX , ".stx" , FilenameSave , WD1772_SAVE_FILE_EXT ) ) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Load .STX file into memory, set number of bytes loaded and return a pointer * to the buffer. */ uint8_t *STX_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType) { uint8_t *pSTXFile; *pImageSize = 0; /* Just load directly a buffer, and set ImageSize accordingly */ pSTXFile = File_Read(pszFileName, pImageSize, NULL); if (!pSTXFile) { *pImageSize = 0; return NULL; } /* Check the file's header is "RSY\0" */ if ( ( *pImageSize <= STX_HEADER_ID_LEN ) || ( memcmp ( STX_HEADER_ID , pSTXFile , STX_HEADER_ID_LEN ) ) ) { Log_Printf ( LOG_ERROR , "%s is not a valid STX image\n" , pszFileName ); free ( pSTXFile ); *pImageSize = 0; return NULL; } *pImageType = FLOPPY_IMAGE_TYPE_STX; return pSTXFile; } /*-----------------------------------------------------------------------*/ /** * Save .STX file from memory buffer. Returns true if all OK. * We create a file based on the initial filename by replacing the ".stx" extension * with ".wd1772". * We save all sectors, then all tracks. * If there're no sector and no track to save, return true and don't create * the save file */ bool STX_WriteDisk ( int Drive , const char *pszFileName , uint8_t *pBuffer , int ImageSize ) { FILE *FileOut; char FilenameSave[ FILENAME_MAX ]; uint8_t buf[ 100 ]; uint8_t *p; uint32_t Sector; uint32_t Track; uint32_t BlockLen; uint32_t SaveSectorsCount_real; STX_SAVE_SECTOR_STRUCT *pStxSaveSector; STX_SAVE_TRACK_STRUCT *pStxSaveTrack; uint32_t i; Log_Printf ( LOG_DEBUG , "stx write <%s>\n" , pszFileName ); /* We can only save if the filename ends with ".stx" (or ".stx.gz"), not if it's a ".zip" file */ if ( STX_FileNameIsSTX ( pszFileName , true ) == false ) { Log_AlertDlg ( LOG_INFO , "WARNING : can't save changes made to this STX disk, bad file extension" ); return false; } /* Count the saved sectors that are really used */ SaveSectorsCount_real = 0; i = 0; while ( i < STX_SaveStruct[ Drive ].SaveSectorsCount ) if ( STX_SaveStruct[ Drive ].pSaveSectorsStruct[ i++ ].StructIsUsed != 0 ) SaveSectorsCount_real++; /* Do we have data to save ? */ if ( ( SaveSectorsCount_real == 0 ) && ( STX_SaveStruct[ Drive ].SaveTracksCount == 0 ) ) return true; if ( STX_FileNameToSave ( pszFileName , FilenameSave ) == false ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error STX_FileNameToSave\n" , Drive , pszFileName ); return false; } Log_Printf ( LOG_DEBUG , "stx write <%s>\n" , FilenameSave ); FileOut = fopen ( FilenameSave , "wb+" ); if ( !FileOut ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error fopen\n" , Drive , pszFileName ); return false; } /* Write the file's header : 6 + 1 + 1 + 4 + 4 = 16 bytes */ p = buf; strcpy ( (char *) p , WD1772_SAVE_FILE_ID ); /* +0 .. +5 */ p += strlen ( WD1772_SAVE_FILE_ID ); *p++ = WD1772_SAVE_VERSION; /* +6 */ *p++ = WD1772_SAVE_REVISION; /* +7 */ STX_WriteU32_BE ( p , SaveSectorsCount_real ); /* +8 ... +11 */ p += 4; STX_WriteU32_BE ( p , STX_SaveStruct[ Drive ].SaveTracksCount ); /* +12 ... +15 */ p += 4; if ( fwrite ( buf , p-buf , 1 , FileOut ) != 1 ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error fwrite header\n" , Drive , pszFileName ); fclose(FileOut); return false; } /* Write the sectors' buffer */ Sector = 0; while ( Sector < STX_SaveStruct[ Drive ].SaveSectorsCount ) { pStxSaveSector = &STX_SaveStruct[ Drive ].pSaveSectorsStruct[ Sector ]; if ( pStxSaveSector->StructIsUsed == 0 ) { Sector++; continue; /* This structure is not used anymore, ignore it */ } /* Build the sector's header : 20 bytes */ p = buf; strcpy ( (char *) p , WD1772_SAVE_SECTOR_ID ); /* +0 .. +3 */ p += strlen ( WD1772_SAVE_SECTOR_ID ); BlockLen = 20-4 + pStxSaveSector->SectorSize; STX_WriteU32_BE ( p , BlockLen ); /* +4 ... +7 */ p += 4; *p++ = pStxSaveSector->Track; /* +8 */ *p++ = pStxSaveSector->Side; /* +9 */ STX_WriteU16_BE ( p , pStxSaveSector->BitPosition ); /* +10 ... +11 */ p += 2; *p++ = pStxSaveSector->ID_Track; /* +12 */ *p++ = pStxSaveSector->ID_Head; /* +13 */ *p++ = pStxSaveSector->ID_Sector; /* +14 */ *p++ = pStxSaveSector->ID_Size; /* +15 */ STX_WriteU16_BE ( p , pStxSaveSector->ID_CRC ); /* +16 ... +17 */ p += 2; STX_WriteU16_BE ( p , pStxSaveSector->SectorSize ); /* +18 ... +19 */ p += 2; /* Write the header */ //Str_Dump_Hex_Ascii ( (char *) buf , p-buf, 16, "" , stderr ); if ( fwrite ( buf , p-buf , 1 , FileOut ) != 1 ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error fwrite sector header\n" , Drive , pszFileName ); fclose(FileOut); return false; } /* Write the data */ //Str_Dump_Hex_Ascii ( (char *) pStxSaveSector->pData , pStxSaveSector->SectorSize, 16, "" , stderr ); if ( fwrite ( pStxSaveSector->pData , pStxSaveSector->SectorSize , 1 , FileOut ) != 1 ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error fwrite sector data\n" , Drive , pszFileName ); fclose(FileOut); return false; } Sector++; } /* Write the tracks' buffer */ Track = 0; while ( Track < STX_SaveStruct[ Drive ].SaveTracksCount ) { pStxSaveTrack = &STX_SaveStruct[ Drive ].pSaveTracksStruct[ Track ]; /* Build the track's header : 12 bytes */ p = buf; strcpy ( (char *) p , WD1772_SAVE_TRACK_ID ); /* +0 ... +3 */ p += strlen ( WD1772_SAVE_TRACK_ID ); BlockLen = 12-4 + pStxSaveTrack->TrackSizeWrite; STX_WriteU32_BE ( p , BlockLen ); /* +4 ... +7 */ p += 4; *p++ = pStxSaveTrack->Track; /* +8 */ *p++ = pStxSaveTrack->Side; /* +9 */ STX_WriteU16_BE ( p , pStxSaveTrack->TrackSizeWrite ); /* +10 ... +11 */ p += 2; /* Write the header */ //Str_Dump_Hex_Ascii ( (char *) buf , p-buf, 16, "" , stderr ); if ( fwrite ( buf , p-buf , 1 , FileOut ) != 1 ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error fwrite track header\n" , Drive , pszFileName ); fclose(FileOut); return false; } /* Write the data at +12 */ //Str_Dump_Hex_Ascii ( (char *) pStxSaveTrack->pDataWrite , pStxSaveTrack->TrackSizeWrite, 16, "" , stderr ); if ( fwrite ( pStxSaveTrack->pDataWrite , pStxSaveTrack->TrackSizeWrite , 1 , FileOut ) != 1 ) { Log_Printf ( LOG_ERROR , "STX_WriteDisk drive=%d file=%s, error fwrite track data\n" , Drive , pszFileName ); fclose(FileOut); return false; } Track++; } fclose ( FileOut ); return true; } /*-----------------------------------------------------------------------*/ /* * Load a ".wd1772" save file and add it to the STX structures * Return true if OK. */ static bool STX_LoadSaveFile ( int Drive , const char *FilenameSave ) { uint8_t *SaveFileBuffer; long SaveFileSize; uint8_t *p; uint8_t *p_save; uint8_t version , revision; uint32_t SectorNb; uint32_t TrackNb; STX_SECTOR_STRUCT *pStxSector; STX_TRACK_STRUCT *pStxTrack; SaveFileBuffer = File_Read ( FilenameSave, &SaveFileSize, NULL ); if (!SaveFileBuffer) { Log_Printf ( LOG_ERROR , "STX_LoadSaveFile drive=%d file=%s error\n" , Drive , FilenameSave ); return false; } p = SaveFileBuffer; if ( strncmp ( (char *) p , WD1772_SAVE_FILE_ID , strlen ( WD1772_SAVE_FILE_ID ) ) ) /* +0 ... +5 */ { Log_Printf ( LOG_ERROR , "STX_LoadSaveFile drive=%d file=%s bad header\n" , Drive , FilenameSave ); free ( SaveFileBuffer ); return false; } p += strlen ( WD1772_SAVE_FILE_ID ); version = *p++; /* +6 */ revision = *p++; /* +7 */ if ( ( version != WD1772_SAVE_VERSION ) || ( revision != WD1772_SAVE_REVISION ) ) { Log_Printf ( LOG_ERROR , "STX_LoadSaveFile drive=%d file=%s bad version 0x%x revision 0x%x\n" , Drive , FilenameSave , version , revision ); free ( SaveFileBuffer ); return false; } STX_SaveStruct[ Drive ].SaveSectorsCount = STX_ReadU32_BE ( p ); /* +8 ... +11 */ p += 4; STX_SaveStruct[ Drive ].SaveTracksCount = STX_ReadU32_BE ( p ); /* +12 ... +15 */ p += 4; /* Alloc a buffer for all the sectors */ if ( STX_SaveStruct[ Drive ].SaveSectorsCount > 0 ) { STX_SaveStruct[ Drive ].pSaveSectorsStruct = malloc ( STX_SaveStruct[ Drive ].SaveSectorsCount * sizeof ( STX_SAVE_SECTOR_STRUCT ) ); if ( !STX_SaveStruct[ Drive ].pSaveSectorsStruct ) { Log_AlertDlg(LOG_ERROR, "Error loading STX sectors save file malloc size=%d in drive %d" , STX_SaveStruct[ Drive ].SaveSectorsCount , Drive ); STX_FreeSaveStruct ( Drive ); free ( SaveFileBuffer ); return false; } } /* Alloc a buffer for all the tracks */ if ( STX_SaveStruct[ Drive ].SaveTracksCount > 0 ) { STX_SaveStruct[ Drive ].pSaveTracksStruct = malloc ( STX_SaveStruct[ Drive ].SaveTracksCount * sizeof ( STX_SAVE_TRACK_STRUCT ) ); if ( !STX_SaveStruct[ Drive ].pSaveTracksStruct ) { Log_AlertDlg(LOG_ERROR, "Error loading STX tracks save file malloc size=%d in drive %d" , STX_SaveStruct[ Drive ].SaveTracksCount , Drive ); STX_FreeSaveStruct ( Drive ); free ( SaveFileBuffer ); return false; } } SectorNb = 0; TrackNb = 0; while ( p < SaveFileBuffer + SaveFileSize ) { /* Start of a block */ p_save = p; //Str_Dump_Hex_Ascii ( (char *) p , 32, 16, "" , stderr ); /* Check the name of this block */ if ( strncmp ( (char *) p , WD1772_SAVE_SECTOR_ID , 4 ) == 0 ) { //fprintf ( stderr , "STX_LoadSaveFile drive=%d SECT block %d\n" , Drive , SectorNb ); if ( STX_LoadSaveFile_SECT ( Drive , &STX_SaveStruct[ Drive ].pSaveSectorsStruct[ SectorNb ] , p+4+4 ) == false ) { Log_AlertDlg(LOG_ERROR, "Error loading STX save file SECT block %d in drive %d" , SectorNb , Drive ); STX_FreeSaveStruct ( Drive ); free ( SaveFileBuffer ); return false; } //Str_Dump_Hex_Ascii ( (char *) &STX_SaveStruct[ Drive ].pSaveSectorsStruct[ SectorNb ] , sizeof(STX_SAVE_SECTOR_STRUCT) , 16, "" , stderr ); //Str_Dump_Hex_Ascii ( (char *) STX_SaveStruct[ Drive ].pSaveSectorsStruct[ SectorNb ].pData , 32, 16, "" , stderr ); /* Find the original sector to associate it with this saved sector */ pStxSector = STX_FindSector_By_Position ( Drive , STX_SaveStruct[ Drive ].pSaveSectorsStruct[ SectorNb ].Track , STX_SaveStruct[ Drive ].pSaveSectorsStruct[ SectorNb ].Side , STX_SaveStruct[ Drive ].pSaveSectorsStruct[ SectorNb ].BitPosition ); if ( !pStxSector ) { Log_AlertDlg(LOG_ERROR, "Error restoring STX save buffer for sector=%d in drive %d" , SectorNb , Drive ); STX_FreeSaveStruct ( Drive ); free ( SaveFileBuffer ); return false; } pStxSector->SaveSectorIndex = SectorNb; SectorNb++; } else if ( strncmp ( (char *) p , WD1772_SAVE_TRACK_ID , 4 ) == 0 ) { //fprintf ( stderr , "STX_LoadSaveFile drive=%d TRCK block %d\n" , Drive , TrackNb ); if ( STX_LoadSaveFile_TRCK ( Drive , &STX_SaveStruct[ Drive ].pSaveTracksStruct[ TrackNb ] , p+4+4 ) == false ) { Log_AlertDlg(LOG_ERROR, "Error loading STX save file TRCK block %d in drive %d" , TrackNb , Drive ); STX_FreeSaveStruct ( Drive ); free ( SaveFileBuffer ); return false; } /* Find the original track to associate it with this saved track */ pStxTrack = STX_FindTrack ( Drive , STX_SaveStruct[ Drive ].pSaveTracksStruct[ TrackNb ].Track , STX_SaveStruct[ Drive ].pSaveTracksStruct[ TrackNb ].Side ); if ( !pStxTrack ) { Log_AlertDlg(LOG_ERROR, "Error loading STX save file TRCK block %d in drive %d" , TrackNb , Drive ); STX_FreeSaveStruct ( Drive ); free ( SaveFileBuffer ); return false; } pStxTrack->SaveTrackIndex = TrackNb; TrackNb++; } else { Log_Printf ( LOG_WARN , "STX_LoadSaveFile drive=%d file=%s, unknown block %4.4s, skipping\n" , Drive , FilenameSave , p ); } /* Next block */ p = p_save + 4; p += STX_ReadU32_BE ( p ); } free ( SaveFileBuffer ); return true; } /*-----------------------------------------------------------------------*/ /* * Parse the "SECT" block from a ".wd1772" save file * Return true if OK. */ static bool STX_LoadSaveFile_SECT ( int Drive, STX_SAVE_SECTOR_STRUCT *pStxSaveSector , uint8_t *p ) { pStxSaveSector->Track = *p++; pStxSaveSector->Side = *p++; pStxSaveSector->BitPosition = STX_ReadU16_BE ( p ); p += 2; pStxSaveSector->ID_Track = *p++; pStxSaveSector->ID_Head = *p++; pStxSaveSector->ID_Sector = *p++; pStxSaveSector->ID_Size = *p++; pStxSaveSector->ID_CRC = STX_ReadU16_BE ( p ); p += 2; pStxSaveSector->SectorSize = STX_ReadU16_BE ( p ); p += 2; /* Copy the sector's data */ pStxSaveSector->pData = malloc ( pStxSaveSector->SectorSize ); if ( !pStxSaveSector->pData ) { Log_AlertDlg(LOG_ERROR, "Error loading STX save buffer for track=%d side=%d bitposition=%d in drive %d" , pStxSaveSector->Track , pStxSaveSector->Side , pStxSaveSector->BitPosition , Drive ); return false; } memcpy ( pStxSaveSector->pData , p , pStxSaveSector->SectorSize ); pStxSaveSector->StructIsUsed = 1; return true; } /*-----------------------------------------------------------------------*/ /* * Parse the "TRCK" block from a ".wd1772" save file * Return true if OK. */ static bool STX_LoadSaveFile_TRCK ( int Drive , STX_SAVE_TRACK_STRUCT *pStxSaveTrack , uint8_t *p ) { pStxSaveTrack->Track = *p++; pStxSaveTrack->Side = *p++; pStxSaveTrack->TrackSizeWrite = STX_ReadU16_BE ( p ); p += 2; /* Copy the track's data */ pStxSaveTrack->pDataWrite = malloc ( pStxSaveTrack->TrackSizeWrite ); if ( !pStxSaveTrack->pDataWrite ) { Log_AlertDlg(LOG_ERROR, "Error loading STX save buffer for track=%d side=%d in drive %d" , pStxSaveTrack->Track , pStxSaveTrack->Side , Drive ); return false; } memcpy ( pStxSaveTrack->pDataWrite , p , pStxSaveTrack->TrackSizeWrite ); pStxSaveTrack->pDataRead = NULL; /* TODO : compute interpreted track */ pStxSaveTrack->TrackSizeRead = 0; /* TODO : compute interpreted track */ return true; } /*-----------------------------------------------------------------------*/ /* * Init variables used to handle STX images */ bool STX_Init ( void ) { int i; for ( i=0 ; i>= 8; p[ 0 ] = val & 0xff; } static void STX_WriteU32_BE ( uint8_t *p , uint32_t val ) { p[ 3 ] = val & 0xff; val >>= 8; p[ 2 ] = val & 0xff; val >>= 8; p[ 1 ] = val & 0xff; val >>= 8; p[ 0 ] = val & 0xff; } /*-----------------------------------------------------------------------*/ /** * Free all the memory allocated to store an STX file */ static void STX_FreeStruct ( STX_MAIN_STRUCT *pStxMain ) { int Track; if ( !pStxMain ) return; for ( Track = 0 ; Track < pStxMain->TracksCount ; Track++ ) { free ( (pStxMain->pTracksStruct[ Track ]).pSectorsStruct ); } free ( pStxMain->pTracksStruct ); free ( pStxMain ); } /*-----------------------------------------------------------------------*/ /** * Free all the memory allocated to store saved sectors / tracks */ static void STX_FreeSaveStruct ( int Drive ) { if ( STX_SaveStruct[ Drive ].pSaveSectorsStruct ) { STX_FreeSaveSectorsStructAll ( STX_SaveStruct[ Drive ].pSaveSectorsStruct , STX_SaveStruct[ Drive ].SaveSectorsCount ); STX_SaveStruct[ Drive ].SaveSectorsCount = 0; STX_SaveStruct[ Drive ].pSaveSectorsStruct = NULL; } if ( STX_SaveStruct[ Drive ].pSaveTracksStruct ) { STX_FreeSaveTracksStructAll ( STX_SaveStruct[ Drive ].pSaveTracksStruct , STX_SaveStruct[ Drive ].SaveTracksCount ); STX_SaveStruct[ Drive ].SaveTracksCount = 0; STX_SaveStruct[ Drive ].pSaveTracksStruct = NULL; } } /*-----------------------------------------------------------------------*/ /** * Free the memory allocated to store all the STX_SAVE_SECTOR_STRUCT */ static void STX_FreeSaveSectorsStructAll ( STX_SAVE_SECTOR_STRUCT *pSaveSectorsStruct , uint32_t SaveSectorsCount ) { uint32_t i; if ( !pSaveSectorsStruct ) return; for ( i = 0 ; i < SaveSectorsCount ; i++ ) { STX_FreeSaveSectorsStruct ( pSaveSectorsStruct , i ); } free ( pSaveSectorsStruct ); } /*-----------------------------------------------------------------------*/ /** * Free the memory allocated to store one STX_SAVE_SECTOR_STRUCT */ static void STX_FreeSaveSectorsStruct ( STX_SAVE_SECTOR_STRUCT *pSaveSectorsStruct , int Nb ) { if ( pSaveSectorsStruct[ Nb ].StructIsUsed == 0 ) return; /* This structure is already free */ free(pSaveSectorsStruct[Nb].pData); pSaveSectorsStruct[ Nb ].StructIsUsed = 0; } /*-----------------------------------------------------------------------*/ /** * Free the memory allocated to store all the STX_SAVE_TRACK_STRUCT */ static void STX_FreeSaveTracksStructAll ( STX_SAVE_TRACK_STRUCT *pSaveTracksStruct , uint32_t SaveTracksCount ) { uint32_t i; if ( !pSaveTracksStruct ) return; for ( i = 0 ; i < SaveTracksCount ; i++ ) { STX_FreeSaveTracksStruct ( pSaveTracksStruct , i ); } free ( pSaveTracksStruct ); } /*-----------------------------------------------------------------------*/ /** * Free the memory allocated to store one STX_SAVE_TRACK_STRUCT */ static void STX_FreeSaveTracksStruct ( STX_SAVE_TRACK_STRUCT *pSaveTracksStruct , int Nb ) { free(pSaveTracksStruct[Nb].pDataWrite); free(pSaveTracksStruct[Nb].pDataRead); } /*-----------------------------------------------------------------------*/ /** * Parse an STX file. * The file is in pFileBuffer and we dynamically allocate memory to store * the components (main header, tracks, sectors). * Some internal variables/pointers are also computed, to speed up * data access when the FDC emulates an STX file. */ STX_MAIN_STRUCT *STX_BuildStruct ( uint8_t *pFileBuffer , int Debug ) { STX_MAIN_STRUCT *pStxMain; STX_TRACK_STRUCT *pStxTrack; STX_SECTOR_STRUCT *pStxSector; uint8_t *p; uint8_t *p_cur; int Track; int Sector; uint8_t *pFuzzyData; uint8_t *pTimingData; uint32_t MaxOffsetSectorEnd; int VariableTimings; pStxMain = malloc ( sizeof ( STX_MAIN_STRUCT ) ); if ( !pStxMain ) return NULL; memset ( pStxMain , 0 , sizeof ( STX_MAIN_STRUCT ) ); p = pFileBuffer; /* Read file's header */ memcpy ( pStxMain->FileID , p , 4 ); p += 4; pStxMain->Version = STX_ReadU16_LE ( p ); p += 2; pStxMain->ImagingTool = STX_ReadU16_LE ( p ); p += 2; pStxMain->Reserved_1 = STX_ReadU16_LE ( p ); p += 2; pStxMain->TracksCount = *p++;; pStxMain->Revision = *p++; pStxMain->Reserved_2 = STX_ReadU32_LE ( p ); p += 4; if ( Debug & STX_DEBUG_FLAG_STRUCTURE ) fprintf ( stderr , "STX header ID='%.4s' Version=%4.4x ImagingTool=%4.4x Reserved1=%4.4x" " TrackCount=%d Revision=%2.2x Reserved2=%x\n" , pStxMain->FileID , pStxMain->Version , pStxMain->ImagingTool , pStxMain->Reserved_1 , pStxMain->TracksCount , pStxMain->Revision , pStxMain->Reserved_2 ); pStxMain->WarnedWriteSector = false; pStxMain->WarnedWriteTrack = false; pStxTrack = malloc ( sizeof ( STX_TRACK_STRUCT ) * pStxMain->TracksCount ); if ( !pStxTrack ) { STX_FreeStruct ( pStxMain ); return NULL; } memset ( pStxTrack , 0 , sizeof ( STX_TRACK_STRUCT ) * pStxMain->TracksCount ); pStxMain->pTracksStruct = pStxTrack; /* Parse all the track blocks */ for ( Track = 0 ; Track < pStxMain->TracksCount ; Track++ ) { p_cur = p; pStxTrack->BlockSize = STX_ReadU32_LE ( p ); p += 4; pStxTrack->FuzzySize = STX_ReadU32_LE ( p ); p += 4; pStxTrack->SectorsCount = STX_ReadU16_LE ( p ); p += 2; pStxTrack->Flags = STX_ReadU16_LE ( p ); p += 2; pStxTrack->MFMSize = STX_ReadU16_LE ( p ); p += 2; pStxTrack->TrackNumber = *p++; pStxTrack->RecordType = *p++; pStxTrack->SaveTrackIndex = -1; if ( pStxTrack->SectorsCount == 0 ) /* No sector (track image only, or empty / non formatted track) */ { pStxTrack->pSectorsStruct = NULL; } else { /* Track contains some sectors */ pStxSector = malloc ( sizeof ( STX_SECTOR_STRUCT ) * pStxTrack->SectorsCount ); if ( !pStxSector ) { STX_FreeStruct ( pStxMain ); return NULL; } memset ( pStxSector , 0 , sizeof ( STX_SECTOR_STRUCT ) * pStxTrack->SectorsCount ); pStxTrack->pSectorsStruct = pStxSector; /* Do we have some sector infos after the track header or only sector data ? */ if ( ( pStxTrack->Flags & STX_TRACK_FLAG_SECTOR_BLOCK ) == 0 ) { /* The track only contains SectorsCount sectors of 512 bytes */ /* NOTE |NP] : in that case, pStxTrack->MFMSize seems to be in bits instead of bytes */ STX_BuildSectorsSimple ( pStxTrack , p ); goto next_track; } } /* Start of the optional fuzzy bits data */ pStxTrack->pFuzzyData = p + pStxTrack->SectorsCount * STX_SECTOR_BLOCK_SIZE; /* Start of the optional track data */ pStxTrack->pTrackData = pStxTrack->pFuzzyData + pStxTrack->FuzzySize; if ( ( pStxTrack->Flags & STX_TRACK_FLAG_TRACK_IMAGE ) == 0 ) { pStxTrack->TrackImageSyncPosition = 0; pStxTrack->TrackImageSize = 0; pStxTrack->pTrackImageData = NULL; pStxTrack->pSectorsImageData = pStxTrack->pTrackData; } else if ( ( pStxTrack->Flags & STX_TRACK_FLAG_TRACK_IMAGE_SYNC ) == 0 ) /* Track with size+data */ { pStxTrack->TrackImageSyncPosition = 0; pStxTrack->TrackImageSize = STX_ReadU16_LE ( pStxTrack->pTrackData ); pStxTrack->pTrackImageData = pStxTrack->pTrackData + 2; pStxTrack->pSectorsImageData = pStxTrack->pTrackImageData + pStxTrack->TrackImageSize; } else /* Track with sync offset + size + data */ { pStxTrack->TrackImageSyncPosition = STX_ReadU16_LE ( pStxTrack->pTrackData ); pStxTrack->TrackImageSize = STX_ReadU16_LE ( pStxTrack->pTrackData + 2 ); pStxTrack->pTrackImageData = pStxTrack->pTrackData + 4; pStxTrack->pSectorsImageData = pStxTrack->pTrackImageData + pStxTrack->TrackImageSize; } if ( pStxTrack->SectorsCount == 0 ) /* No sector (track image only, or empty / non formatted track) */ goto next_track; /* Parse all the sectors in this track */ pFuzzyData = pStxTrack->pFuzzyData; VariableTimings = 0; MaxOffsetSectorEnd = 0; for ( Sector = 0 ; Sector < pStxTrack->SectorsCount ; Sector++ ) { pStxSector = &(pStxTrack->pSectorsStruct[ Sector ]); pStxSector->DataOffset = STX_ReadU32_LE ( p ); p += 4; pStxSector->BitPosition = STX_ReadU16_LE ( p ); p += 2; pStxSector->ReadTime = STX_ReadU16_LE ( p ); p += 2; pStxSector->ID_Track = *p++; pStxSector->ID_Head = *p++; pStxSector->ID_Sector = *p++; pStxSector->ID_Size = *p++; pStxSector->ID_CRC = ( p[0] << 8 ) | p[1] ; p +=2; pStxSector->FDC_Status = *p++; pStxSector->Reserved = *p++; /* Check if sector has data */ if ( ( pStxSector->FDC_Status & STX_SECTOR_FLAG_RNF ) == 0 ) { /* Check if SectorSize is valid (this is just a warning, we keep only bits 0-1 anyway) */ if ( pStxSector->ID_Size & ~FDC_SECTOR_SIZE_MASK ) { // fprintf ( stderr , "STX : invalid ID_Size=%d on track %d sector %d\n" , // pStxSector->ID_Size , Track , Sector ); } pStxSector->SectorSize = 128 << ( pStxSector->ID_Size & FDC_SECTOR_SIZE_MASK ); pStxSector->pData = pStxTrack->pTrackData + pStxSector->DataOffset; if ( pStxSector->FDC_Status & STX_SECTOR_FLAG_FUZZY ) { pStxSector->pFuzzyData = pFuzzyData; pFuzzyData += pStxSector->SectorSize; } /* Max offset of the end of all sectors image in the track */ if ( MaxOffsetSectorEnd < pStxSector->DataOffset + pStxSector->SectorSize ) MaxOffsetSectorEnd = pStxSector->DataOffset + pStxSector->SectorSize; if ( pStxSector->FDC_Status & STX_SECTOR_FLAG_VARIABLE_TIME ) VariableTimings = 1; } pStxSector->SaveSectorIndex = -1; } /* Start of the optional timings data, after the optional sectors image data */ pStxTrack->pTiming = pStxTrack->pTrackData + MaxOffsetSectorEnd; if ( pStxTrack->pTiming < pStxTrack->pSectorsImageData ) /* If all sectors image were inside the track image */ pStxTrack->pTiming = pStxTrack->pSectorsImageData; /* then timings data are just after the track image */ if ( VariableTimings == 1 ) /* Track has at least one variable sector */ { if ( pStxMain->Revision == 2 ) /* Specific timing table */ { pStxTrack->TimingFlags = STX_ReadU16_LE ( pStxTrack->pTiming ); /* always '5' ? */ pStxTrack->TimingSize = STX_ReadU16_LE ( pStxTrack->pTiming + 2 ); pStxTrack->pTimingData = pStxTrack->pTiming + 4; /* 2 bytes of timing for each block of 16 bytes */ } /* Compute the address of the timings data for each sector with variable timings */ pTimingData = pStxTrack->pTimingData; for ( Sector = 0 ; Sector < pStxTrack->SectorsCount ; Sector++ ) { pStxSector = &(pStxTrack->pSectorsStruct[ Sector ]); pStxSector->pTimingData = NULL; /* No timings by default */ /* Check if sector has data + variable timings */ if ( ( ( pStxSector->FDC_Status & STX_SECTOR_FLAG_RNF ) == 0 ) && ( pStxSector->FDC_Status & STX_SECTOR_FLAG_VARIABLE_TIME ) ) { if ( pStxMain->Revision == 2 ) /* Specific table for revision 2 */ { pStxSector->pTimingData = pTimingData; pTimingData += ( pStxSector->SectorSize / 16 ) * 2; } else pStxSector->pTimingData = TimingDataDefault; /* Fixed table for revision 0 */ } } } next_track: if ( Debug & STX_DEBUG_FLAG_STRUCTURE ) { fprintf ( stderr , " track %3d BlockSize=%d FuzzySize=%d Sectors=%4.4x Flags=%4.4x" " MFMSize=%d TrackNb=%2.2x Side=%d RecordType=%x" " TrackImage=%s (%d bytes, sync=%4.4x) Timings=%d,%d\n" , Track , pStxTrack->BlockSize , pStxTrack->FuzzySize , pStxTrack->SectorsCount , pStxTrack->Flags , pStxTrack->MFMSize , pStxTrack->TrackNumber & 0x7f , ( pStxTrack->TrackNumber >> 7 ) & 0x01 , pStxTrack->RecordType , pStxTrack->pTrackImageData ? "yes" : "no" , pStxTrack->TrackImageSize , pStxTrack->TrackImageSyncPosition , pStxTrack->TimingFlags , pStxTrack->TimingSize ); if ( ( Debug & STX_DEBUG_FLAG_DATA ) && pStxTrack->pTrackImageData ) { fprintf ( stderr , " track image data :\n" ); Str_Dump_Hex_Ascii ( (char *)pStxTrack->pTrackImageData , pStxTrack->TrackImageSize , 16 , " " , stderr ); } if ( pStxTrack->SectorsCount == 0 ) fprintf ( stderr , " no sector in this track, %s\n" , pStxTrack->pTrackImageData ? "only track image" : "track empty / not formatted" ); else for ( Sector = 0 ; Sector < pStxTrack->SectorsCount ; Sector++ ) { /* If the sector use the internal timing table, we print TimingsOffset=-1 */ pStxSector = &(pStxTrack->pSectorsStruct[ Sector ]); fprintf ( stderr , " sector %2d DataOffset=%d BitPosition=%d ReadTime=%d" " [track=%2.2x head=%2.2x sector=%2.2x size=%2.2x crc=%4.4x]" " FdcStatus=%2.2x Reserved=%2.2x TimingsOffset=%d\n" , Sector , pStxSector->DataOffset , pStxSector->BitPosition , pStxSector->ReadTime , pStxSector->ID_Track , pStxSector->ID_Head , pStxSector->ID_Sector , pStxSector->ID_Size , pStxSector->ID_CRC , pStxSector->FDC_Status , pStxSector->Reserved , pStxSector->pTimingData ? ( pStxTrack->TimingSize > 0 ? (int)(pStxSector->pTimingData - pStxTrack->pTrackData) : -1 ) : 0 ); if ( ( Debug & STX_DEBUG_FLAG_DATA ) && pStxSector->pData ) { fprintf ( stderr , " sector data :\n" ); Str_Dump_Hex_Ascii ( (char *)pStxSector->pData , pStxSector->SectorSize , 16 , " " , stderr ); } if ( ( Debug & STX_DEBUG_FLAG_DATA ) && pStxSector->pFuzzyData ) { fprintf ( stderr , " fuzzy data :\n" ); Str_Dump_Hex_Ascii ( (char *)pStxSector->pFuzzyData , pStxSector->SectorSize , 16 , " " , stderr ); } if ( ( Debug & STX_DEBUG_FLAG_DATA ) && pStxSector->pTimingData ) { fprintf ( stderr , " timing data :\n" ); Str_Dump_Hex_Ascii ( (char *)pStxSector->pTimingData , ( pStxSector->SectorSize / 16 ) * 2 , 16 , " " , stderr ); } } } p = p_cur + pStxTrack->BlockSize; /* Next Track block */ pStxTrack++; } return pStxMain; } /*-----------------------------------------------------------------------*/ /** * When a track only consists of the content of each 512 bytes sector and * no timings information, we must compute some default values for each * sector, as well as the position of the corresponding 512 bytes of data. * This is only used when storing unprotected tracks. */ static void STX_BuildSectorsSimple ( STX_TRACK_STRUCT *pStxTrack , uint8_t *p ) { int Sector; int BytePosition; uint16_t CRC; BytePosition = FDC_TRACK_LAYOUT_STANDARD_GAP1 + FDC_TRACK_LAYOUT_STANDARD_GAP2; /* Points to the 3x$A1 before the 1st IDAM $FE */ BytePosition += 4; /* Pasti seems to point after the 3x$A1 and the IDAM $FE */ for ( Sector = 0 ; Sector < pStxTrack->SectorsCount ; Sector++ ) { pStxTrack->pSectorsStruct[ Sector ].SaveSectorIndex = -1; pStxTrack->pSectorsStruct[ Sector ].DataOffset = 0; pStxTrack->pSectorsStruct[ Sector ].BitPosition = BytePosition * 8; pStxTrack->pSectorsStruct[ Sector ].ReadTime = 0; /* Build the ID Field */ pStxTrack->pSectorsStruct[ Sector ].ID_Track = pStxTrack->TrackNumber & 0x7f; pStxTrack->pSectorsStruct[ Sector ].ID_Head = ( pStxTrack->TrackNumber >> 7 ) & 0x01; pStxTrack->pSectorsStruct[ Sector ].ID_Sector = Sector + 1; pStxTrack->pSectorsStruct[ Sector ].ID_Size = FDC_SECTOR_SIZE_512; CRC = STX_BuildSectorID_CRC ( &(pStxTrack->pSectorsStruct[ Sector ]) ); pStxTrack->pSectorsStruct[ Sector ].ID_CRC = CRC; pStxTrack->pSectorsStruct[ Sector ].FDC_Status = 0; pStxTrack->pSectorsStruct[ Sector ].Reserved = 0; pStxTrack->pSectorsStruct[ Sector ].pData = p + Sector * 512; pStxTrack->pSectorsStruct[ Sector ].SectorSize = 128 << pStxTrack->pSectorsStruct[ Sector ].ID_Size; BytePosition += FDC_TRACK_LAYOUT_STANDARD_RAW_SECTOR_512; } } /*-----------------------------------------------------------------------*/ /** * Compute the CRC of the Address Field for a given sector. */ static uint16_t STX_BuildSectorID_CRC ( STX_SECTOR_STRUCT *pStxSector ) { uint16_t CRC; crc16_reset ( &CRC ); crc16_add_byte ( &CRC , 0xa1 ); crc16_add_byte ( &CRC , 0xa1 ); crc16_add_byte ( &CRC , 0xa1 ); crc16_add_byte ( &CRC , 0xfe ); crc16_add_byte ( &CRC , pStxSector->ID_Track ); crc16_add_byte ( &CRC , pStxSector->ID_Head ); crc16_add_byte ( &CRC , pStxSector->ID_Sector ); crc16_add_byte ( &CRC , pStxSector->ID_Size ); return CRC; } /*-----------------------------------------------------------------------*/ /** * Find a track in the floppy image inserted into a drive. */ static STX_TRACK_STRUCT *STX_FindTrack ( uint8_t Drive , uint8_t Track , uint8_t Side ) { int i; if ( STX_State.ImageBuffer[ Drive ] == NULL ) return NULL; for ( i=0 ; iTracksCount ; i++ ) if ( STX_State.ImageBuffer[ Drive ]->pTracksStruct[ i ].TrackNumber == ( ( Track & 0x7f ) | ( Side << 7 ) ) ) return &(STX_State.ImageBuffer[ Drive ]->pTracksStruct[ i ]); return NULL; } /*-----------------------------------------------------------------------*/ /** * Find a sector in the floppy image inserted into a drive. * SectorStruct_Nb is a value set by a previous call to FDC_NextSectorID_FdcCycles_STX() */ static STX_SECTOR_STRUCT *STX_FindSector ( uint8_t Drive , uint8_t Track , uint8_t Side , uint8_t SectorStruct_Nb ) { STX_TRACK_STRUCT *pStxTrack; if ( STX_State.ImageBuffer[ Drive ] == NULL ) return NULL; pStxTrack = STX_FindTrack ( Drive , Track , Side ); if ( pStxTrack == NULL ) return NULL; if ( pStxTrack->pSectorsStruct == NULL ) return NULL; return &(pStxTrack->pSectorsStruct[ SectorStruct_Nb ]); } /*-----------------------------------------------------------------------*/ /** * Find a sector in the floppy image inserted into a drive. * The sector is identified by its BitPosition which is unique per track/side */ static STX_SECTOR_STRUCT *STX_FindSector_By_Position ( uint8_t Drive , uint8_t Track , uint8_t Side , uint16_t BitPosition ) { STX_TRACK_STRUCT *pStxTrack; int Sector; if ( STX_State.ImageBuffer[ Drive ] == NULL ) return NULL; pStxTrack = STX_FindTrack ( Drive , Track , Side ); if ( pStxTrack == NULL ) return NULL; if ( pStxTrack->pSectorsStruct == NULL ) return NULL; for ( Sector=0 ; SectorSectorsCount ; Sector++ ) if ( pStxTrack->pSectorsStruct[ Sector ].BitPosition == BitPosition ) return &(pStxTrack->pSectorsStruct[ Sector ]); return NULL; } /*-----------------------------------------------------------------------*/ /** * Return the number of bytes in a raw track * For a DD floppy, tracks will usually have a size of more or less * FDC_TRACK_BYTES_STANDARD bytes (depending on the mastering process used * for different protections) * NOTE : Although STX format was supposed to handle only DD floppies, some tools like HxC * allow to convert a HD floppy image to an STX equivalent. In that case * TrackSize will be approximately 2 x FDC_TRACK_BYTES_STANDARD */ int FDC_GetBytesPerTrack_STX ( uint8_t Drive , uint8_t Track , uint8_t Side ) { STX_TRACK_STRUCT *pStxTrack; int TrackSize; pStxTrack = STX_FindTrack ( Drive , Track , Side ); if ( pStxTrack == NULL ) TrackSize = FDC_TRACK_BYTES_STANDARD; /* Use a standard track length is track is not available */ else if ( pStxTrack->pTrackImageData ) TrackSize = pStxTrack->TrackImageSize; else if ( ( pStxTrack->Flags & STX_TRACK_FLAG_SECTOR_BLOCK ) == 0 ) TrackSize = pStxTrack->MFMSize / 8; /* When the track contains only sector data, MFMSize is in bits */ else TrackSize = pStxTrack->MFMSize; //fprintf ( stderr , "fdc stx drive=%d track=0x%x side=%d size=%d\n" , Drive , Track, Side , TrackSize ); return TrackSize; } /*-----------------------------------------------------------------------*/ /** * Return the number of FDC cycles to go from one index pulse to the next * one on a given drive/track/side. * We take the TrackSize into account to return this delay. * NOTE : in the case of HD/ED floppies (instead of DD), we must take the density * factor into account (it should take the same time to read a DD track and a HD track * as the drive spins at 300 RPM in both cases) */ uint32_t FDC_GetCyclesPerRev_FdcCycles_STX ( uint8_t Drive , uint8_t Track , uint8_t Side ) { int TrackSize; TrackSize = FDC_GetBytesPerTrack_STX ( Drive , Track , Side ); return TrackSize * FDC_DELAY_CYCLE_MFM_BYTE / FDC_GetFloppyDensity ( Drive ); /* Take density into account for HD/ED floppies */; } /*-----------------------------------------------------------------------*/ /** * Return the number of FDC cycles to wait before reaching the next * sector's ID Field in the track ($A1 $A1 $A1 $FE TR SIDE SR LEN CRC1 CRC2) * If no ID Field is found before the end of the track, we use the 1st * ID Field of the track (which simulates a full spin of the floppy). * We also store the next sector's number into NextSectorStruct_Nbr, * the next sector's number into NextSector_ID_Field_SR, the next track's number * into NextSector_ID_Field_TR, the next sector's length into * NextSector_ID_Field_LEN and if the CRC is correct or not into NextSector_ID_Field_CRC_OK. * This function assumes the sectors of each track are sorted in ascending order * using BitPosition. * If there's no available drive/floppy or no ID field in the track, we return -1 */ int FDC_NextSectorID_FdcCycles_STX ( uint8_t Drive , uint8_t NumberOfHeads , uint8_t Track , uint8_t Side ) { STX_TRACK_STRUCT *pStxTrack; int CurrentPos_FdcCycles; int i; int Delay_FdcCycles; int TrackSize; CurrentPos_FdcCycles = FDC_IndexPulse_GetCurrentPos_FdcCycles ( NULL ); if ( CurrentPos_FdcCycles < 0 ) /* No drive/floppy available at the moment */ return -1; if ( ( Side == 1 ) && ( NumberOfHeads == 1 ) ) /* Can't read side 1 on a single sided drive */ return -1; pStxTrack = STX_FindTrack ( Drive , Track , Side ); if ( pStxTrack == NULL ) /* Track/Side don't exist in this STX image */ return -1; if ( pStxTrack->SectorsCount == 0 ) /* No sector (track image only, or empty / non formatted track) */ return -1; if ( FDC_MachineHandleDensity ( Drive ) == false ) /* Can't handle the floppy's density */ return -1; /* Compare CurrentPos_FdcCycles with each sector's position in ascending order */ /* (minus 4 bytes, see below) */ for ( i=0 ; iSectorsCount ; i++ ) { if ( CurrentPos_FdcCycles < (int)pStxTrack->pSectorsStruct[ i ].BitPosition*FDC_DELAY_CYCLE_MFM_BIT /* 1 bit = 32 cycles at 8 MHz */ - 4 * FDC_DELAY_CYCLE_MFM_BYTE ) break; /* We found the next sector */ } if ( i == pStxTrack->SectorsCount ) /* CurrentPos_FdcCycles is after the last ID Field of this track */ { /* Reach end of track (new index pulse), then go to 1st sector from current position */ if ( pStxTrack->pTrackImageData ) TrackSize = pStxTrack->TrackImageSize; else if ( ( pStxTrack->Flags & STX_TRACK_FLAG_SECTOR_BLOCK ) == 0 ) TrackSize = pStxTrack->MFMSize / 8; /* When the track contains only sector data, MFMSize is in bits */ else TrackSize = pStxTrack->MFMSize; Delay_FdcCycles = TrackSize * FDC_DELAY_CYCLE_MFM_BYTE - CurrentPos_FdcCycles + pStxTrack->pSectorsStruct[ 0 ].BitPosition*FDC_DELAY_CYCLE_MFM_BIT; STX_State.NextSectorStruct_Nbr = 0; //fprintf ( stderr , "size=%d pos=%d pos0=%d delay=%d\n" , TrackSize, CurrentPos_FdcCycles, pStxTrack->pSectorsStruct[ 0 ].BitPosition , Delay_FdcCycles ); } else /* There's an ID Field before end of track */ { Delay_FdcCycles = (int)pStxTrack->pSectorsStruct[ i ].BitPosition*FDC_DELAY_CYCLE_MFM_BIT - CurrentPos_FdcCycles; STX_State.NextSectorStruct_Nbr = i; //fprintf ( stderr , "i=%d pos=%d posi=%d delay=%d\n" , i, CurrentPos_FdcCycles, pStxTrack->pSectorsStruct[ i ].BitPosition*FDC_DELAY_CYCLE_MFM_BIT , Delay_FdcCycles ); } /* Store the value of the track/sector numbers in the next ID field */ STX_State.NextSector_ID_Field_TR = pStxTrack->pSectorsStruct[ STX_State.NextSectorStruct_Nbr ].ID_Track; STX_State.NextSector_ID_Field_SR = pStxTrack->pSectorsStruct[ STX_State.NextSectorStruct_Nbr ].ID_Sector; STX_State.NextSector_ID_Field_LEN = pStxTrack->pSectorsStruct[ STX_State.NextSectorStruct_Nbr ].ID_Size; /* If RNF is set and CRC error is set, then this ID field has a CRC error */ if ( ( pStxTrack->pSectorsStruct[ STX_State.NextSectorStruct_Nbr ].FDC_Status & STX_SECTOR_FLAG_RNF ) && ( pStxTrack->pSectorsStruct[ STX_State.NextSectorStruct_Nbr ].FDC_Status & STX_SECTOR_FLAG_CRC ) ) STX_State.NextSector_ID_Field_CRC_OK = 0; /* CRC bad */ else STX_State.NextSector_ID_Field_CRC_OK = 1; /* CRC correct */ /* BitPosition in STX seems to point just after the IDAM $FE ; we need to point 4 bytes earlier at the 1st $A1 */ Delay_FdcCycles -= 4 * FDC_DELAY_CYCLE_MFM_BYTE; /* Correct delay to point to $A1 $A1 $A1 $FE */ //fprintf ( stderr , "fdc bytes next sector pos=%d delay=%d maxsr=%d nextsr=%d\n" , CurrentPos_FdcCycles, Delay_FdcCycles, pStxTrack->SectorsCount, STX_State.NextSectorStruct_Nbr ); return Delay_FdcCycles; } /*-----------------------------------------------------------------------*/ /** * Return the value of the track number in the next ID field set by * FDC_NextSectorID_FdcCycles_STX. */ uint8_t FDC_NextSectorID_TR_STX ( void ) { return STX_State.NextSector_ID_Field_TR; } /*-----------------------------------------------------------------------*/ /** * Return the value of the sector number in the next ID field set by * FDC_NextSectorID_FdcCycles_STX. */ uint8_t FDC_NextSectorID_SR_STX ( void ) { return STX_State.NextSector_ID_Field_SR; } /*-----------------------------------------------------------------------*/ /** * Return the value of the sector's length in the next ID field set by * FDC_NextSectorID_FdcCycles_STX. */ uint8_t FDC_NextSectorID_LEN_STX ( void ) { return STX_State.NextSector_ID_Field_LEN; } /*-----------------------------------------------------------------------*/ /** * Return the status of the CRC in the next ID field set by * FDC_NextSectorID_FdcCycles_STX. * If '0', CRC is bad, else CRC is OK */ uint8_t FDC_NextSectorID_CRC_OK_STX ( void ) { return STX_State.NextSector_ID_Field_CRC_OK; } /*-----------------------------------------------------------------------*/ /** * Read a sector from a floppy image in STX format (used in type II command) * We return the sector NextSectorStruct_Nbr, whose value was set * by the latest call to FDC_NextSectorID_FdcCycles_STX * Each byte of the sector is added to the FDC buffer with a default timing * (32 microsec) or a variable timing, depending on the sector's flags. * Some sectors can also contains "fuzzy" bits. * Special care must be taken to compute the timing of each byte, which can * be a decimal value and must be rounded to the best possible integer. * * If the sector's data were changed by a 'write sector' command, then we assume * a sector with no fuzzy byte and standard timings. * * Return RNF if sector was not found, else return CRC and RECORD_TYPE values * for the status register. */ uint8_t FDC_ReadSector_STX ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side , int *pSectorSize ) { STX_SECTOR_STRUCT *pStxSector; int i; uint8_t Byte; uint16_t Timing; uint32_t Sector_ReadTime; double Total_cur; /* To compute closest integer timings for each byte */ double Total_prev; uint8_t *pSector_WriteData; pStxSector = STX_FindSector ( Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); if ( pStxSector == NULL ) { Log_Printf ( LOG_WARN , "FDC_ReadSector_STX drive=%d track=%d side=%d sector=%d returns null !\n" , Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); return STX_SECTOR_FLAG_RNF; /* Should not happen if FDC_NextSectorID_FdcCycles_STX succeeded before */ } /* If RNF is set, return FDC_STR_BIT_RNF */ if ( pStxSector->FDC_Status & STX_SECTOR_FLAG_RNF ) return STX_SECTOR_FLAG_RNF; /* RNF in FDC's status register */ *pSectorSize = pStxSector->SectorSize; Sector_ReadTime = pStxSector->ReadTime; /* Check if this sector was changed by a 'write sector' command */ /* If so, we use this recent buffer instead of the original STX content */ if (STX_SaveStruct[Drive].SaveSectorsCount > 0 && pStxSector->SaveSectorIndex >= 0) { pSector_WriteData = STX_SaveStruct[ Drive ].pSaveSectorsStruct[ pStxSector->SaveSectorIndex ].pData; Sector_ReadTime = 0; /* Standard timings */ LOG_TRACE(TRACE_FDC, "fdc stx read sector drive=%d track=%d sect=%d side=%d using saved sector=%d\n" , Drive, Track, Sector, Side , pStxSector->SaveSectorIndex ); } else pSector_WriteData = NULL; if ( Sector_ReadTime == 0 ) /* Sector has a standard delay (32 us per byte) */ Sector_ReadTime = 32 * pStxSector->SectorSize; /* Use the real standard value instead of 0 */ Sector_ReadTime *= 8; /* Convert delay in us to a number of FDC cycles at 8 MHz */ Total_prev = 0; for ( i=0 ; iSectorSize ; i++ ) { /* Get the value of each byte, with possible fuzzy bits */ if ( pSector_WriteData == NULL ) /* Use original STX content */ { Byte = pStxSector->pData[ i ]; if ( pStxSector->pFuzzyData ) Byte = ( Byte & pStxSector->pFuzzyData[ i ] ) | ( Hatari_rand() & ~pStxSector->pFuzzyData[ i ] ); } else /* Use data from 'write sector' */ Byte = pSector_WriteData[ i ]; /* Compute the timing in FDC cycles to transfer this byte */ if ( ( pStxSector->pTimingData ) /* Specific timing for each block of 16 bytes */ && ( pSector_WriteData == NULL ) ) { Timing = ( pStxSector->pTimingData[ ( i>>4 ) * 2 ] << 8 ) + pStxSector->pTimingData[ ( i>>4 ) * 2 + 1 ]; /* Get big endian timing for this block of 16 bytes */ /* [NP] Formula to convert timing data comes from Pasti.prg 0.4b : */ /* 1 unit of timing = 32 FDC cycles at 8 MHz + 28 cycles to complete each block of 16 bytes */ Timing = Timing * 32 + 28; if ( i % 16 == 0 ) Total_prev = 0; /* New block of 16 bytes */ Total_cur = ( (double)Timing * ( ( i % 16 ) + 1 ) ) / 16; Timing = rint ( Total_cur - Total_prev ); Total_prev += Timing; } else /* Specific timing in us for the whole sector */ { Total_cur = ( (double)Sector_ReadTime * ( i+1 ) ) / pStxSector->SectorSize; Timing = rint ( Total_cur - Total_prev ); Total_prev += Timing; } /* Add the Byte to the buffer, Timing should be a number of FDC cycles at 8 MHz */ FDC_Buffer_Add_Timing ( Byte , Timing ); } /* Return only bits 3 and 5 of the FDC_Status */ return pStxSector->FDC_Status & ( STX_SECTOR_FLAG_CRC | STX_SECTOR_FLAG_RECORD_TYPE ); } /*-----------------------------------------------------------------------*/ /** * Write a sector to a floppy image in STX format (used in type II command) * * STX format doesn't support write command. For each 'write sector' we * store the sector data in a dedicated buffer STX_SaveStruct[].pSaveSectorsStruct. * When the sector is read later, we return the data from STX_SaveStruct[].pSaveSectorsStruct * instead of returning the data from the original STX file. * * We only allow writing for sectors whose ID field has a correct CRC and * where RNF is not set. * Any valid size can be written : 128, 256, 512 or 1024 bytes * * NOTE : data will saved in memory snapshot, as well as in an additional * file with the extension .wd1772. * * Return RNF if sector was not found or CRC if ID field has a CRC error. * Return 0 if OK. */ uint8_t FDC_WriteSector_STX ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side , int SectorSize ) { STX_SECTOR_STRUCT *pStxSector; int i; uint8_t *pSector_WriteData; void *pNewBuf; STX_SAVE_SECTOR_STRUCT *pStxSaveSector; pStxSector = STX_FindSector ( Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); if ( pStxSector == NULL ) { Log_Printf ( LOG_WARN , "FDC_WriteSector_STX drive=%d track=%d side=%d sector=%d returns null !\n" , Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); return STX_SECTOR_FLAG_RNF; /* Should not happen if FDC_NextSectorID_FdcCycles_STX succeeded before */ } /* If RNF is set, return FDC_STR_BIT_RNF */ if ( pStxSector->FDC_Status & STX_SECTOR_FLAG_RNF ) return STX_SECTOR_FLAG_RNF; /* RNF in FDC's status register */ /* If CRC is set, return FDC_STR_BIT_RNF */ if ( pStxSector->FDC_Status & STX_SECTOR_FLAG_CRC ) return STX_SECTOR_FLAG_CRC; /* CRC in FDC's status register */ /* Check if this sector was already changed by a 'write sector' command */ /* If so, we use the same buffer. Else we alloc a new buffer for this sector */ if ( pStxSector->SaveSectorIndex < 0 ) { //fprintf ( stderr , "realloc\n" ); /* Increase save buffer by 1 */ pNewBuf = realloc ( STX_SaveStruct[ Drive ].pSaveSectorsStruct , ( STX_SaveStruct[ Drive ].SaveSectorsCount + 1 ) * sizeof ( STX_SAVE_SECTOR_STRUCT ) ); if ( pNewBuf == NULL ) { Log_Printf ( LOG_ERROR , "FDC_WriteSector_STX drive=%d track=%d side=%d sector=%d realloc error !\n" , Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); return STX_SECTOR_FLAG_RNF; } /* Save the new buffer values */ STX_SaveStruct[ Drive ].pSaveSectorsStruct = (STX_SAVE_SECTOR_STRUCT *) pNewBuf;; STX_SaveStruct[ Drive ].SaveSectorsCount++; /* Create the new entry in pSaveSectorsStruct */ pNewBuf = malloc ( SectorSize ); if ( pNewBuf == NULL ) { Log_Printf ( LOG_ERROR , "FDC_WriteSector_STX drive=%d track=%d side=%d sector=%d malloc error !\n" , Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); return STX_SECTOR_FLAG_RNF; } pStxSector->SaveSectorIndex = STX_SaveStruct[ Drive ].SaveSectorsCount - 1; /* Fill the new SaveSectorStruct. We copy some of the original sector's values */ /* in the saved sector */ pStxSaveSector = &STX_SaveStruct[ Drive ].pSaveSectorsStruct[ pStxSector->SaveSectorIndex ]; pStxSaveSector->Track = Track; pStxSaveSector->Side = Side; pStxSaveSector->BitPosition = pStxSector->BitPosition; pStxSaveSector->ID_Track = pStxSector->ID_Track; pStxSaveSector->ID_Head = pStxSector->ID_Head; pStxSaveSector->ID_Sector = pStxSector->ID_Sector; pStxSaveSector->ID_Size = pStxSector->ID_Size; pStxSaveSector->ID_CRC = pStxSector->ID_CRC; pStxSaveSector->SectorSize = SectorSize; pStxSaveSector->pData = (uint8_t *) pNewBuf; pStxSaveSector->StructIsUsed = 1; } pSector_WriteData = STX_SaveStruct[ Drive ].pSaveSectorsStruct[ pStxSector->SaveSectorIndex ].pData; /* Get the sector's data (ignore timings) */ for ( i=0 ; iSaveSectorIndex ); //Str_Dump_Hex_Ascii ( (char *) pSector_WriteData, SectorSize, 16, "" , stderr ); /* Warn that 'write sector' data will be lost or saved (if zipped or not) */ if ( STX_State.ImageBuffer[ Drive ]->WarnedWriteSector == false ) { if ( File_DoesFileExtensionMatch ( EmulationDrives[ Drive ].sFileName , ".zip" ) ) Log_AlertDlg ( LOG_INFO , "WARNING : can't save changes made with 'write sector' to an STX disk inside a zip file" ); else Log_AlertDlg ( LOG_INFO , "Changes made with 'write sector' to an STX disk will be saved into an additional .wd1772 file" ); STX_State.ImageBuffer[ Drive ]->WarnedWriteSector = true; } /* No error */ EmulationDrives[Drive].bContentsChanged = true; return 0; } /*-----------------------------------------------------------------------*/ /** * Read an address field from a floppy image in STX format (used in type III command) * We return the address field NextSectorStruct_Nbr, whose value was set * by the latest call to FDC_NextSectorID_FdcCycles_STX * Each byte of the ID field is added to the FDC buffer with a default timing * (32 microsec) * Return 0 if OK, or a CRC error */ uint8_t FDC_ReadAddress_STX ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side ) { STX_SECTOR_STRUCT *pStxSector; pStxSector = STX_FindSector ( Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); if ( pStxSector == NULL ) { Log_Printf ( LOG_ERROR , "FDC_ReadAddress_STX drive=%d track=%d side=%d sector=%d returns null !\n" , Drive , Track , Side , STX_State.NextSectorStruct_Nbr ); return STX_SECTOR_FLAG_RNF; /* Should not happen if FDC_NextSectorID_FdcCycles_STX succeeded before */ } FDC_Buffer_Add ( pStxSector->ID_Track ); FDC_Buffer_Add ( pStxSector->ID_Head ); FDC_Buffer_Add ( pStxSector->ID_Sector ); FDC_Buffer_Add ( pStxSector->ID_Size ); FDC_Buffer_Add ( pStxSector->ID_CRC >> 8 ); FDC_Buffer_Add ( pStxSector->ID_CRC & 0xff ); /* If RNF is set and CRC error is set, then this ID field has a CRC error */ if ( ( pStxSector->FDC_Status & STX_SECTOR_FLAG_RNF ) && ( pStxSector->FDC_Status & STX_SECTOR_FLAG_CRC ) ) return STX_SECTOR_FLAG_CRC; return 0; /* No error */ } /*-----------------------------------------------------------------------*/ /** * Read a track from a floppy image in STX format (used in type III command) * This function is called after an index pulse was encountered, and it will * always succeeds and fill the track buffer. * If the Track/Side infos exist in the STX image, then the corresponding * bytes from the track's image are returned. * If these Track/Side infos don't exist, we return some random bytes * (empty / not formatted track). * If the Track/Side infos exist but there's no track's image, then we build * a standard track by using the available sectors and standard GAP values. * Return 0 if OK */ uint8_t FDC_ReadTrack_STX ( uint8_t Drive , uint8_t Track , uint8_t Side ) { STX_TRACK_STRUCT *pStxTrack; STX_SECTOR_STRUCT *pStxSector; int i; uint16_t Timing; uint32_t Track_ReadTime; double Total_cur; /* To compute closest integer timings for each byte */ double Total_prev; int TrackSize; int Sector; int SectorSize; uint16_t CRC; uint8_t *pData; uint8_t Byte; if ( STX_State.ImageBuffer[ Drive ] == NULL ) { Log_Printf ( LOG_ERROR , "FDC_ReadTrack_STX drive=%d track=%d side=%d, no image buffer !\n" , Drive , Track , Side ); return STX_SECTOR_FLAG_RNF; /* Should not happen, just in case of a bug */ } pStxTrack = STX_FindTrack ( Drive , Track , Side ); if ( pStxTrack == NULL ) /* Track/Side don't exist in this STX image */ { Log_Printf ( LOG_WARN , "fdc stx : track info not found for read track drive=%d track=%d side=%d, returning random bytes\n" , Drive , Track , Side ); for ( i=0 ; ipTrackImageData ) { Track_ReadTime = 8000000 / 5; /* 300 RPM, gives 5 RPS and 1600000 cycles per revolution at 8 MHz */ Total_prev = 0; for ( i=0 ; iTrackImageSize ; i++ ) { Total_cur = ( (double)Track_ReadTime * ( i+1 ) ) / pStxTrack->TrackImageSize; Timing = rint ( Total_cur - Total_prev ); Total_prev += Timing; /* Add each byte to the buffer, Timing should be a number of FDC cycles at 8 MHz */ FDC_Buffer_Add_Timing ( pStxTrack->pTrackImageData[ i ] , Timing ); } } /* If the track block doesn't contain a dump of the track image, we must build a track */ /* using the sector blocks and some standard GAP values */ /* [NP] NOTE : we build a track of pStxTrack->MFMSize bytes, as this seems to always be != 0 */ /* even for empty / not formatted track */ /* [NP] NOTE : instead of using standard GAP values, we could compute GAP based on pStxSector->BitPosition */ /* but this seems unnecessary, as a track image would certainly be present if precise GAP values */ /* were required */ else { TrackSize = pStxTrack->MFMSize; if ( ( pStxTrack->Flags & STX_TRACK_FLAG_SECTOR_BLOCK ) == 0 ) TrackSize /= 8; /* When the track contains only sector data, MFMSize is in bits */ /* If there's no image for this track, and no sector as well, then track is empty / not formatted */ if ( pStxTrack->SectorsCount == 0 ) { Log_Printf ( LOG_WARN , "fdc stx : no track image and no sector for read track drive=%d track=%d side=%d, building an unformatted track\n" , Drive , Track , Side ); for ( i=0 ; iSectorsCount ; Sector++ ) { pStxSector = &(pStxTrack->pSectorsStruct[ Sector ]); SectorSize = pStxSector->SectorSize; /* Check that the data+GAPs for this sector will not be above track's length */ /* (in case we build a track with a high / non standard number of sectors) */ if ( FDC_Buffer_Get_Size () + SectorSize + FDC_TRACK_LAYOUT_STANDARD_GAP2 + 10 + FDC_TRACK_LAYOUT_STANDARD_GAP3a + FDC_TRACK_LAYOUT_STANDARD_GAP3b + 4 + 2 + FDC_TRACK_LAYOUT_STANDARD_GAP4 >= TrackSize ) { Log_Printf ( LOG_WARN , "fdc stx : no track image for read track drive=%d track=%d side=%d, too many data sector=%d\n" , Drive , Track , Side , Sector ); break; /* Exit the loop and fill the rest of the track */ } for ( i=0 ; iID_Track ); FDC_Buffer_Add ( pStxSector->ID_Head ); FDC_Buffer_Add ( pStxSector->ID_Sector ); FDC_Buffer_Add ( pStxSector->ID_Size ); FDC_Buffer_Add ( pStxSector->ID_CRC >> 8 ); FDC_Buffer_Add ( pStxSector->ID_CRC & 0xff ); for ( i=0 ; iSaveSectorIndex < 0 ) /* Use original data from the STX */ pData = pStxSector->pData; else /* Use data from the 'write sector' */ pData = STX_SaveStruct[ Drive ].pSaveSectorsStruct[ pStxSector->SaveSectorIndex ].pData; for ( i=0 ; i> 8 ); /* CRC1 (write $F7) */ FDC_Buffer_Add ( CRC & 0xff ); /* CRC2 */ for ( i=0 ; iSaveTrackIndex < 0 ) { //fprintf ( stderr , "realloc\n" ); /* Increase save buffer by 1 */ pNewBuf = realloc ( STX_SaveStruct[ Drive ].pSaveTracksStruct , ( STX_SaveStruct[ Drive ].SaveTracksCount + 1 ) * sizeof ( STX_SAVE_TRACK_STRUCT ) ); if ( pNewBuf == NULL ) { Log_Printf ( LOG_WARN , "FDC_WriteTrack_STX drive=%d track=%d side=%d realloc error !\n" , Drive , Track , Side ); return STX_SECTOR_FLAG_LOST_DATA; } /* Save the new buffer values */ STX_SaveStruct[ Drive ].pSaveTracksStruct = (STX_SAVE_TRACK_STRUCT *) pNewBuf;; STX_SaveStruct[ Drive ].SaveTracksCount++; pStxTrack->SaveTrackIndex = STX_SaveStruct[ Drive ].SaveTracksCount - 1; } /* Use the same structure : free previous DataWrite buffer */ else { free ( STX_SaveStruct[ Drive ].pSaveTracksStruct[ pStxTrack->SaveTrackIndex ].pDataWrite ); STX_SaveStruct[ Drive ].pSaveTracksStruct[ pStxTrack->SaveTrackIndex ].pDataWrite = NULL; /* TODO : also free pDataRead */ } /* Create the new DataWrite buffer in pSaveTracksStruct */ pNewBuf = malloc ( TrackSize ); if ( pNewBuf == NULL ) { Log_Printf ( LOG_WARN , "FDC_WriteTrack_STX drive=%d track=%d side=%d malloc error !\n" , Drive , Track , Side ); return STX_SECTOR_FLAG_LOST_DATA; } /* Fill the new SaveTrackStruct */ pStxSaveTrack = &STX_SaveStruct[ Drive ].pSaveTracksStruct[ pStxTrack->SaveTrackIndex ]; pStxSaveTrack->Track = Track; pStxSaveTrack->Side = Side; pStxSaveTrack->TrackSizeWrite = TrackSize; pStxSaveTrack->pDataWrite = (uint8_t *) pNewBuf; /* Get the track's data (ignore timings) */ pTrack_DataWrite = STX_SaveStruct[ Drive ].pSaveTracksStruct[ pStxTrack->SaveTrackIndex ].pDataWrite; for ( i=0 ; iTrackSizeWrite ; i++ ) pTrack_DataWrite[ i ] = FDC_Buffer_Read_Byte_pos ( i ); //fprintf ( stderr , "write drive=%d track=%d side=%d size=%d index=%d\n", Drive, Track, Side, pStxSaveTrack->TrackSizeWrite , pStxTrack->SaveTrackIndex ); //Str_Dump_Hex_Ascii ( (char *) pTrack_DataWrite, pStxSaveTrack->TrackSizeWrite, 16, "" , stderr ); // TODO : convert pDataWrite into pDataRead pStxSaveTrack->TrackSizeRead = 0; /* TODO : compute interpreted track */ pStxSaveTrack->pDataRead = NULL; /* TODO : compute interpreted track */ /* If some sectors were already saved for that track, we must remove them */ /* as the 'write track' takes precedence over the previous 'write sector' */ for ( Sector=0 ; Sector < pStxTrack->SectorsCount ; Sector++ ) { if ( pStxTrack->pSectorsStruct[ Sector ].SaveSectorIndex >= 0 ) { STX_FreeSaveSectorsStruct ( STX_SaveStruct[ Drive ].pSaveSectorsStruct , pStxTrack->pSectorsStruct[ Sector ].SaveSectorIndex ); pStxTrack->pSectorsStruct[ Sector ].SaveSectorIndex = -1; } } /* Warn that 'write track' data will be lost or saved (if zipped or not) */ if ( STX_State.ImageBuffer[ Drive ]->WarnedWriteTrack == false ) { if ( File_DoesFileExtensionMatch ( EmulationDrives[ Drive ].sFileName , ".zip" ) ) Log_AlertDlg ( LOG_INFO , "WARNING : can't save changes made with 'write track' to an STX disk inside a zip file" ); else Log_AlertDlg ( LOG_INFO , "Changes made with 'write track' to an STX disk will be saved into an additional .wd1772 file" ); STX_State.ImageBuffer[ Drive ]->WarnedWriteTrack = true; } /* No error */ EmulationDrives[Drive].bContentsChanged = true; return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gemdos.c000066400000000000000000003420021504763705000227530ustar00rootroot00000000000000/* Hatari - gemdos.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. GEMDOS intercept routines. These are used mainly for hard drive redirection of high level file routines. Host file names are handled case insensitively, so files on GEMDOS drive emulation directories may be either in lower or upper case. Too long file and directory names and names with invalid characters are converted to TOS compatible 8+3 names, but matching them back to host names is slower and may match several such filenames (of which first one will be returned), so using them should be avoided. Known bugs / things to fix: * Host file names are in many places limited to 255 chars (same as on TOS). They should be dynamically allocated instead * GEMDOS Ddelete() implementation uses rmdir() which cannot remove a dir with files in it (a TOS/unix difference) */ const char Gemdos_fileid[] = "Hatari gemdos.c"; #include #include #if HAVE_STATVFS #include #endif #include #if HAVE_UTIME_H #include #elif HAVE_SYS_UTIME_H #include #endif #include #include #include #include #include #include "main.h" #include "cart.h" #include "configuration.h" #include "file.h" #include "floppy.h" #include "ide.h" #include "inffile.h" #include "hdc.h" #include "ncr5380.h" #include "gemdos.h" #include "gemdos_defines.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "printer.h" #include "statusbar.h" #include "scandir.h" #include "stMemory.h" #include "str.h" #include "tos.h" #include "hatari-glue.h" #include "maccess.h" #include "symbols.h" /* Maximum supported length of a GEMDOS path: */ #define MAX_GEMDOS_PATH 256 #define BASEPAGE_SIZE (0x80+0x80) /* info + command line */ #define BASEPAGE_OFFSET_DTA 0x20 #define BASEPAGE_OFFSET_PARENT 0x24 /* Have we re-directed GemDOS vector to our own routines yet? */ bool bInitGemDOS; /* structure with all the drive-specific data for our emulated drives, * used by GEMDOS_EMU_ON macro */ EMULATEDDRIVE **emudrives = NULL; #define ISHARDDRIVE(Drive) (Drive!=-1) /* Disk Transfer Address (DTA) */ #define TOS_NAMELEN 14 typedef struct { /* GEMDOS internals */ uint8_t index[2]; uint8_t magic[4]; char dta_pat[TOS_NAMELEN]; /* unused */ char dta_sattrib; /* unused */ /* TOS API */ char dta_attrib; uint8_t dta_time[2]; uint8_t dta_date[2]; uint8_t dta_size[4]; char dta_name[TOS_NAMELEN]; } DTA; /* PopulateDTA() return values */ typedef enum { DTA_ERR = -1, DTA_OK = 0, DTA_SKIP = 1 } dta_ret_t; #define DTA_MAGIC_NUMBER 0x12983476 #define DTA_CACHE_INC 256 /* DTA cache initial and increment size (grows on demand) */ #define DTA_CACHE_MAX 4096 /* max DTA cache size (multiple of DTA_CACHE_INC) */ #define BASE_FILEHANDLE 64 /* Our emulation handles - MUST not be valid TOS ones, but MUST be <256 */ #define MAX_FILE_HANDLES 64 /* We can allow 64 files open at once */ /* DateTime structure used by TOS call $57 f_dtatime Changed to fix potential problem with alignment. */ typedef struct { uint16_t timeword; uint16_t dateword; } DATETIME; #define UNFORCED_HANDLE -1 static struct { int Handle; uint32_t Basepage; } ForcedHandles[5]; /* (standard) handles aliased to emulated handles */ typedef struct { bool bUsed; bool bReadOnly; char szMode[4]; /* enough for all used fopen() modes: rb/rb+/wb+ */ uint32_t Basepage; FILE *FileHandle; /* TODO: host path might not fit into this */ char szActualName[MAX_GEMDOS_PATH]; /* used by F_DATIME (0x57) */ } FILE_HANDLE; /* stored FsFirst() information */ typedef struct { bool bUsed; uint32_t addr; /* ST-RAM DTA address for matching reused entries */ int nentries; /* number of entries in fs directory */ int centry; /* current entry # */ struct dirent **found; /* legal files */ char path[MAX_GEMDOS_PATH]; char dta_attrib; } INTERNAL_DTA; /* DTA population ignores archive & read-only attributes */ #define IGNORED_FILE_ATTRIBS (GEMDOS_FILE_ATTRIB_WRITECLOSE|GEMDOS_FILE_ATTRIB_READONLY) #define IS_VOLUME_LABEL(x) (((x) & ~(IGNORED_FILE_ATTRIBS)) == GEMDOS_FILE_ATTRIB_VOLUME_LABEL) static FILE_HANDLE FileHandles[MAX_FILE_HANDLES]; static INTERNAL_DTA *InternalDTAs; static int DTACount; /* Current DTA cache size */ static uint16_t DTAIndex; /* Circular index into above */ static uint16_t CurrentDrive; /* Current drive (0=A,1=B,2=C etc...) */ static uint32_t act_pd; /* Used to get a pointer to the current basepage */ static uint32_t CallingPC; /* Program counter from caller */ static uint32_t nSavedPexecParams; #if defined(WIN32) && !defined(mkdir) #define mkdir(name,mode) mkdir(name) #endif /* WIN32 */ #ifndef S_IRGRP #define S_IRGRP 0 #define S_IROTH 0 #endif /* set to 1 if you want to see debug output from pattern matching */ #define DEBUG_PATTERN_MATCH 0 /*-------------------------------------------------------*/ /** * Routine to convert time and date to GEMDOS format. * Originally from the STonX emulator. (cheers!) */ static void GemDOS_DateTime2Tos(time_t t, DATETIME *DateTime, const char *fname) { struct tm *x; /* localtime takes DST into account */ x = localtime(&t); if (x == NULL) { Log_Printf(LOG_WARN, "'%s' timestamp is invalid for (Windows?) localtime(), defaulting to TOS epoch!", fname); DateTime->dateword = 1|(1<<5); /* 1980-01-01 */ DateTime->timeword = 0; return; } /* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */ DateTime->timeword = (x->tm_sec>>1)|(x->tm_min<<5)|(x->tm_hour<<11); /* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */ DateTime->dateword = x->tm_mday | ((x->tm_mon+1)<<5) | (((x->tm_year-80 > 0) ? x->tm_year-80 : 0) << 9); } /*-----------------------------------------------------------------------*/ /** * Populate a DATETIME structure with file info. Handle needs to be * validated before calling. Return true on success. */ static bool GemDOS_GetFileInformation(int Handle, DATETIME *DateTime) { const char *fname = FileHandles[Handle].szActualName; struct stat fstat; if (stat(fname, &fstat) == 0) { GemDOS_DateTime2Tos(fstat.st_mtime, DateTime, fname); return true; } return false; } /*-----------------------------------------------------------------------*/ /** * Set given file date/time from given DATETIME. Handle needs to be * validated before calling. Return true on success. */ static bool GemDOS_SetFileInformation(int Handle, DATETIME *DateTime) { const char *filename; struct utimbuf timebuf; struct stat filestat; struct tm timespec; /* make sure Hatari itself doesn't need to write/modify * the file after it's modification time is changed. */ fflush(FileHandles[Handle].FileHandle); /* use host modification times instead of Atari ones? */ if (ConfigureParams.HardDisk.bGemdosHostTime) return true; filename = FileHandles[Handle].szActualName; /* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */ timespec.tm_sec = (DateTime->timeword & 0x1F) << 1; timespec.tm_min = (DateTime->timeword & 0x7E0) >> 5; timespec.tm_hour = (DateTime->timeword & 0xF800) >> 11; /* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */ timespec.tm_mday = (DateTime->dateword & 0x1F); timespec.tm_mon = ((DateTime->dateword & 0x1E0) >> 5) - 1; timespec.tm_year = ((DateTime->dateword & 0xFE00) >> 9) + 80; /* check whether DST should be taken into account */ timespec.tm_isdst = -1; /* set new modification time */ timebuf.modtime = mktime(×pec); /* but keep previous access time */ if (stat(filename, &filestat) != 0) return false; timebuf.actime = filestat.st_atime; if (utime(filename, &timebuf) != 0) return false; // fprintf(stderr, "set date '%s' for %s\n", asctime(×pec), name); return true; } /*-----------------------------------------------------------------------*/ /** * Convert from FindFirstFile/FindNextFile attribute to GemDOS format */ static uint8_t GemDOS_ConvertAttribute(mode_t mode, const char *path) { uint8_t Attrib = 0; /* Directory attribute */ if (S_ISDIR(mode)) Attrib |= GEMDOS_FILE_ATTRIB_SUBDIRECTORY; /* Read-only attribute */ if (!(mode & S_IWUSR) || access(path, W_OK) != 0) Attrib |= GEMDOS_FILE_ATTRIB_READONLY; /* TODO, Other attributes: * - GEMDOS_FILE_ATTRIB_HIDDEN (file not visible on desktop/fsel) * - GEMDOS_FILE_ATTRIB_WRITECLOSE (archive bit, file written after being backed up) * ? */ return Attrib; } /*-----------------------------------------------------------------------*/ /** * Populate the DTA buffer with file info. * @return DTA_OK if entry is ok, DTA_SKIP if it should be skipped, DTA_ERR on errors */ static dta_ret_t PopulateDTA(INTERNAL_DTA *iDTA, struct dirent *file, DTA *pDTA, uint32_t DTA_Gemdos) { /* TODO: host file path can be longer than MAX_GEMDOS_PATH */ char tempstr[MAX_GEMDOS_PATH]; struct stat filestat; DATETIME DateTime; int nFileAttr, nAttrMask; if (snprintf(tempstr, sizeof(tempstr), "%s%c%s", iDTA->path, PATHSEP, file->d_name) >= (int)sizeof(tempstr)) { Log_Printf(LOG_ERROR, "PopulateDTA: path is too long.\n"); return DTA_ERR; } if (stat(tempstr, &filestat) != 0) { /* skip file if it doesn't exist, otherwise return an error */ dta_ret_t ret = (errno == ENOENT ? DTA_SKIP : DTA_ERR); perror(tempstr); return ret; } if (!pDTA) return DTA_ERR; /* no DTA pointer set */ /* Check file attributes (check is done according to the Profibuch) */ nFileAttr = GemDOS_ConvertAttribute(filestat.st_mode, tempstr); nAttrMask = iDTA->dta_attrib | IGNORED_FILE_ATTRIBS; if (nFileAttr != 0 && !(nAttrMask & nFileAttr)) return DTA_SKIP; GemDOS_DateTime2Tos(filestat.st_mtime, &DateTime, tempstr); /* Atari memory modified directly through pDTA members -> flush the data cache */ M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA)); /* convert to atari-style uppercase */ Str_Filename_Host2Atari(file->d_name, pDTA->dta_name); #if DEBUG_PATTERN_MATCH fprintf(stderr, "DEBUG: GEMDOS: host: %s -> GEMDOS: %s\n", file->d_name, pDTA->dta_name); #endif do_put_mem_long(pDTA->dta_size, filestat.st_size); do_put_mem_word(pDTA->dta_time, DateTime.timeword); do_put_mem_word(pDTA->dta_date, DateTime.dateword); pDTA->dta_attrib = nFileAttr; return DTA_OK; } /*-----------------------------------------------------------------------*/ /** * Clear given DTA cache structure. */ static void ClearInternalDTA(int idx) { int i; /* clear the old DTA structure */ if (InternalDTAs[idx].found != NULL) { for (i = 0; i < InternalDTAs[idx].nentries; i++) free(InternalDTAs[idx].found[i]); free(InternalDTAs[idx].found); InternalDTAs[idx].found = NULL; } InternalDTAs[idx].nentries = 0; InternalDTAs[idx].bUsed = false; } /*-----------------------------------------------------------------------*/ /** * Clear all DTA cache structures. * * If there are no DTA structures yet, allocate default amount */ static void GemDOS_ClearAllInternalDTAs(void) { int i; if (!InternalDTAs) { DTACount = DTA_CACHE_INC; InternalDTAs = calloc(DTACount, sizeof(*InternalDTAs)); assert(InternalDTAs); } for(i = 0; i < DTACount; i++) { ClearInternalDTA(i); } DTAIndex = 0; } /** * Free all DTA cache structures */ static void GemDOS_FreeAllInternalDTAs(void) { int i; for(i = 0; i < DTACount; i++) { ClearInternalDTA(i); } if (InternalDTAs) { free(InternalDTAs); InternalDTAs = NULL; DTACount = 0; } DTAIndex = 0; } /*-----------------------------------------------------------------------*/ /** * Match a TOS file name to a dir mask. */ static bool fsfirst_match(const char *pat, const char *name) { const char *dot, *p=pat, *n=name; if (name[0] == '.') return false; /* skip .* files */ dot = strrchr(name, '.'); /* '*' matches everything except last dot in name */ if (dot && p[0] == '*' && p[1] == 0) return false; /* plain '*' must not match anything with extension */ while (*n) { if (*p=='*') { while (*n && n != dot) n++; p++; } else if (*p=='?' && *n) { n++; p++; } else if (toupper((unsigned char)*p++) != toupper((unsigned char)*n++)) return false; } /* printf("'%s': '%s' -> '%s' : '%s' -> %d\n", name, pat, n, p); */ /* whole name consumed, what about pattern? */ /* '*' match any number of chars, so skip them all */ while (p[0] == '*') p++; /* '*' for extension matches also filenames without extension */ if (p[0] == '.' && p[1] == '*') p += 2; /* skip rest of extension '*' chars */ while (p[0] == '*') p++; /* ends here = match? */ return (p[0] == 0); } /*-----------------------------------------------------------------------*/ /** * Parse directory from sfirst mask * - e.g.: input: "hdemudir/auto/mask*.*" outputs: "hdemudir/auto" */ static void fsfirst_dirname(const char *string, char *newstr) { int i=0; strcpy(newstr, string); /* convert to front slashes and go to end of string. */ while (newstr[i] != '\0') { if (newstr[i] == '\\') newstr[i] = PATHSEP; i++; } /* find last slash and terminate string */ while (i && newstr[i] != PATHSEP) i--; newstr[i] = '\0'; } /*-----------------------------------------------------------------------*/ /** * Close given internal file handle if it's still in use * and (always) reset handle variables */ static void GemDOS_CloseFileHandle(int i) { if (FileHandles[i].bUsed) fclose(FileHandles[i].FileHandle); FileHandles[i].FileHandle = NULL; FileHandles[i].Basepage = 0; FileHandles[i].bUsed = false; } /** * Un-force given file handle */ static void GemDOS_UnforceFileHandle(int i) { ForcedHandles[i].Handle = UNFORCED_HANDLE; ForcedHandles[i].Basepage = 0; } /** * Clear & un-force all file handles */ static void GemDOS_ClearAllFileHandles(void) { int i; for(i = 0; i < ARRAY_SIZE(FileHandles); i++) { GemDOS_CloseFileHandle(i); } for(i = 0; i < ARRAY_SIZE(ForcedHandles); i++) { GemDOS_UnforceFileHandle(i); } } /*-----------------------------------------------------------------------*/ /** * Initialize GemDOS/PC file system */ void GemDOS_Init(void) { bInitGemDOS = false; } /*-----------------------------------------------------------------------*/ /** * Initialize GemDOS drives current paths (to drive root) */ static void GemDOS_InitCurPaths(void) { int i; if (emudrives) { for (i = 0; i < MAX_HARDDRIVES; i++) { if (emudrives[i]) { /* Initialize current directory to the root of the drive */ strcpy(emudrives[i]->fs_currpath, emudrives[i]->hd_emulation_dir); File_AddSlashToEndFileName(emudrives[i]->fs_currpath); } } } } /*-----------------------------------------------------------------------*/ /** * Reset GemDOS file system */ void GemDOS_Reset(void) { GemDOS_Init(); GemDOS_InitCurPaths(); /* Reset */ act_pd = 0; CurrentDrive = nBootDrive; Symbols_RemoveCurrentProgram(); INF_CreateOverride(); } /*-----------------------------------------------------------------------*/ /** * Routine to check the Host OS HDD path for a Drive letter sub folder */ static bool GEMDOS_DoesHostDriveFolderExist(char* lpstrPath, int iDrive) { bool bExist = false; Log_Printf(LOG_DEBUG, "Checking GEMDOS %c: HDD: %s\n", 'A'+iDrive, lpstrPath); if (access(lpstrPath, F_OK) != 0 ) { /* Try lower case drive letter instead */ int iIndex = strlen(lpstrPath)-1; lpstrPath[iIndex] = tolower((unsigned char)lpstrPath[iIndex]); } /* Check if it's a HDD identifier (or other emulated device) * and if the file/folder is accessible (security basis) */ if (iDrive > 1 && access(lpstrPath, F_OK) == 0 ) { struct stat status; if (stat(lpstrPath, &status) == 0 && (status.st_mode & S_IFDIR) != 0) { bExist = true; } else { Log_Printf(LOG_WARN, "Not suitable as GEMDOS HDD dir: %s\n", lpstrPath); } } return bExist; } /** * Determine upper limit of partitions that should be emulated. * * @return true if multiple GEMDOS partitions should be emulated, false otherwise */ static bool GemDOS_DetermineMaxPartitions(int *pnMaxDrives) { struct dirent **files; int count, i, last; char letter; bool bMultiPartitions; *pnMaxDrives = 0; /* Scan through the main directory to see whether there are just single * letter sub-folders there (then use multi-partition mode) or if * arbitrary sub-folders are there (then use single-partition mode) */ count = scandir(ConfigureParams.HardDisk.szHardDiskDirectories[0], &files, 0, alphasort); if (count < 0) { Log_Printf(LOG_ERROR, "GEMDOS hard disk emulation failed:\n " "Can not access '%s'.\n", ConfigureParams.HardDisk.szHardDiskDirectories[0]); return false; } else if (count <= 2) { /* Empty directory Only "." and ".."), assume single partition mode */ last = 1; bMultiPartitions = false; } else { bMultiPartitions = true; /* Check all files in the directory */ last = 0; for (i = 0; i < count; i++) { letter = toupper((unsigned char)files[i]->d_name[0]); if (!letter || letter == '.') { /* Ignore hidden files like "." and ".." */ continue; } if (letter < 'C' || letter > 'Z' || files[i]->d_name[1]) { /* folder with name other than C-Z... * (until Z under MultiTOS, to P otherwise) * ... so use single partition mode! */ last = 1; bMultiPartitions = false; break; } /* alphasort isn't case insensitive */ letter = letter - 'C' + 1; if (letter > last) last = letter; } } if (last > MAX_HARDDRIVES) *pnMaxDrives = MAX_HARDDRIVES; else *pnMaxDrives = last; /* Free file list */ for (i = 0; i < count; i++) free(files[i]); free(files); return bMultiPartitions; } /*-----------------------------------------------------------------------*/ /** * Initialize a GEMDOS drive. * Supports up to MAX_HARDDRIVES HDD units. */ void GemDOS_InitDrives(void) { int i; int nMaxDrives; int DriveNumber; int SkipPartitions; int ImagePartitions; bool bMultiPartitions; GemDOS_ClearAllFileHandles(); GemDOS_ClearAllInternalDTAs(); bMultiPartitions = GemDOS_DetermineMaxPartitions(&nMaxDrives); /* initialize data for harddrive emulation: */ if (nMaxDrives > 0 && !emudrives) { emudrives = calloc(MAX_HARDDRIVES, sizeof(EMULATEDDRIVE *)); if (!emudrives) { perror("GemDOS_InitDrives"); return; } } ImagePartitions = nAcsiPartitions + nScsiPartitions + nIDEPartitions; if (ConfigureParams.HardDisk.nGemdosDrive == DRIVE_SKIP) SkipPartitions = ImagePartitions; else SkipPartitions = ConfigureParams.HardDisk.nGemdosDrive; Log_Printf(LOG_DEBUG, "ACSI: %d, SCSI: %d, IDE: %d - GEMDOS skipping %d partitions.\n", nAcsiPartitions, nScsiPartitions, nIDEPartitions, SkipPartitions); /* Now initialize all available drives */ for(i = 0; i < nMaxDrives; i++) { /* If single partition mode, skip to specified / first free drive */ if (!bMultiPartitions) { i += SkipPartitions; } /* Allocate emudrives entry for this drive */ emudrives[i] = malloc(sizeof(EMULATEDDRIVE)); if (!emudrives[i]) { perror("GemDOS_InitDrives"); continue; } /* set emulation directory string */ strcpy(emudrives[i]->hd_emulation_dir, ConfigureParams.HardDisk.szHardDiskDirectories[0]); /* remove trailing slash, if any in the directory name */ File_CleanFileName(emudrives[i]->hd_emulation_dir); /* Add requisite folder ID */ if (bMultiPartitions) { char sDriveLetter[] = { PATHSEP, (char)('C' + i), '\0' }; strcat(emudrives[i]->hd_emulation_dir, sDriveLetter); } /* drive number (C: = 2, D: = 3, etc.) */ DriveNumber = 2 + i; // Check host file system to see if the drive folder for THIS // drive letter/number exists... if (GEMDOS_DoesHostDriveFolderExist(emudrives[i]->hd_emulation_dir, DriveNumber)) { /* map drive */ Log_Printf(LOG_INFO, "GEMDOS HDD emulation, %c: <-> %s.\n", 'A'+DriveNumber, emudrives[i]->hd_emulation_dir); emudrives[i]->drive_number = DriveNumber; nNumDrives = i + 3; /* This letter may already be allocated to the one supported physical disk images * (depends on how well Atari HD driver and Hatari interpretation of partition * table(s) match each other). */ if (i < ImagePartitions) Log_Printf(LOG_WARN, "GEMDOS HD drive %c: (may) override ACSI/SCSI/IDE image partitions!\n", 'A'+DriveNumber); } else { free(emudrives[i]); // Deallocate Memory (save space) emudrives[i] = NULL; } } /* Set current paths in case Atari -> host GEMDOS path mapping * is needed before TOS boots GEMDOS up (at which point they're * also initialized), like happens with autostart INF file * handling. */ GemDOS_InitCurPaths(); } /*-----------------------------------------------------------------------*/ /** * Un-init GEMDOS drives */ void GemDOS_UnInitDrives(void) { int i; GemDOS_FreeAllInternalDTAs(); GemDOS_Reset(); /* Close all open files on emulated drive */ if (GEMDOS_EMU_ON) { for(i = 0; i < MAX_HARDDRIVES; i++) { if (emudrives[i]) { free(emudrives[i]); /* Release memory */ emudrives[i] = NULL; nNumDrives -= 1; } } free(emudrives); emudrives = NULL; } } /*-----------------------------------------------------------------------*/ /** * Save file handle info. If handle is used, save valid file modification * timestamp and file position, otherwise dummies. */ static void save_file_handle_info(FILE_HANDLE *handle) { struct stat fstat; time_t mtime = 0; off_t offset = 0; MemorySnapShot_Store(&handle->bUsed, sizeof(handle->bUsed)); MemorySnapShot_Store(&handle->szMode, sizeof(handle->szMode)); MemorySnapShot_Store(&handle->Basepage, sizeof(handle->Basepage)); MemorySnapShot_Store(&handle->szActualName, sizeof(handle->szActualName)); if (handle->bUsed) { offset = ftello(handle->FileHandle); if (stat(handle->szActualName, &fstat) == 0) mtime = fstat.st_mtime; /* modification time */ } MemorySnapShot_Store(&mtime, sizeof(mtime)); MemorySnapShot_Store(&offset, sizeof(offset)); } /*-----------------------------------------------------------------------*/ /** * Restore saved file handle info. If handle is used, open file, validate * that file modification timestamp matches, then seek to saved position. * Restoring order must match one used in save_file_handle_info(). */ static void restore_file_handle_info(int i, FILE_HANDLE *handle) { struct stat fstat; time_t mtime; off_t offset; FILE *fp; if (handle->bUsed) fclose(handle->FileHandle); /* read all to proceed correctly in snapshot */ MemorySnapShot_Store(&handle->bUsed, sizeof(handle->bUsed)); MemorySnapShot_Store(&handle->szMode, sizeof(handle->szMode)); MemorySnapShot_Store(&handle->Basepage, sizeof(handle->Basepage)); MemorySnapShot_Store(&handle->szActualName, sizeof(handle->szActualName)); MemorySnapShot_Store(&mtime, sizeof(mtime)); MemorySnapShot_Store(&offset, sizeof(offset)); handle->FileHandle = NULL; if (!handle->bUsed) return; if (stat(handle->szActualName, &fstat) != 0) { handle->bUsed = false; Log_Printf(LOG_WARN, "GEMDOS handle %d cannot be restored, file missing: %s\n", i, handle->szActualName); return; } /* assumes time_t is primitive type (unsigned long on Linux) */ if (fstat.st_mtime != mtime) { Log_Printf(LOG_WARN, "restored GEMDOS handle %d points to a file that has been modified in meanwhile: %s\n", i, handle->szActualName); } fp = fopen(handle->szActualName, handle->szMode); if (fp == NULL || fseeko(fp, offset, SEEK_SET) != 0) { handle->bUsed = false; Log_Printf(LOG_WARN, "GEMDOS '%s' handle %d cannot be restored, seek to saved offset %"PRId64" failed for: %s\n", handle->szMode, i, (int64_t)offset, handle->szActualName); if (fp) fclose(fp); return; } /* used only for warnings, ignore those after restore */ handle->bReadOnly = false; handle->FileHandle = fp; } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void GemDOS_MemorySnapShot_Capture(bool bSave) { FILE_HANDLE *finfo; int i, handles = ARRAY_SIZE(FileHandles); bool bEmudrivesAvailable; /* Save/Restore the emudrives structure */ bEmudrivesAvailable = (emudrives != NULL); MemorySnapShot_Store(&bEmudrivesAvailable, sizeof(bEmudrivesAvailable)); if (bEmudrivesAvailable) { if (!emudrives) { /* As memory snapshot contained emulated drive(s), * but currently there are none allocated yet... * let's do it now! */ GemDOS_InitDrives(); } for(i = 0; i < MAX_HARDDRIVES; i++) { int bDummyDrive = false; if (!emudrives[i]) { /* Allocate a dummy drive */ emudrives[i] = malloc(sizeof(EMULATEDDRIVE)); if (!emudrives[i]) { perror("GemDOS_MemorySnapShot_Capture"); continue; } memset(emudrives[i], 0, sizeof(EMULATEDDRIVE)); bDummyDrive = true; } MemorySnapShot_Store(emudrives[i]->hd_emulation_dir, sizeof(emudrives[i]->hd_emulation_dir)); MemorySnapShot_Store(emudrives[i]->fs_currpath, sizeof(emudrives[i]->fs_currpath)); MemorySnapShot_Store(&emudrives[i]->drive_number, sizeof(emudrives[i]->drive_number)); if (bDummyDrive) { free(emudrives[i]); emudrives[i] = NULL; } } } /* misc information */ MemorySnapShot_Store(&bInitGemDOS,sizeof(bInitGemDOS)); MemorySnapShot_Store(&act_pd, sizeof(act_pd)); MemorySnapShot_Store(&CurrentDrive, sizeof(CurrentDrive)); MemorySnapShot_Store(&nSavedPexecParams, sizeof(nSavedPexecParams)); /* File handle related information */ MemorySnapShot_Store(&ForcedHandles, sizeof(ForcedHandles)); if (bSave) { MemorySnapShot_Store(&handles, sizeof(handles)); for (finfo = FileHandles, i = 0; i < handles; i++, finfo++) save_file_handle_info(finfo); } else { int saved_handles; MemorySnapShot_Store(&saved_handles, sizeof(saved_handles)); assert(saved_handles == handles); for (finfo = FileHandles, i = 0; i < handles; i++, finfo++) restore_file_handle_info(i, finfo); /* DTA file name cache isn't valid anymore */ GemDOS_ClearAllInternalDTAs(); } } /*-----------------------------------------------------------------------*/ /** * Return free PC file handle table index, or -1 if error */ static int GemDOS_FindFreeFileHandle(void) { int i; /* Scan our file list for free slot */ for(i = 0; i < ARRAY_SIZE(FileHandles); i++) { if (!FileHandles[i].bUsed) return i; } /* Cannot open any more files, return error */ return -1; } /*-----------------------------------------------------------------------*/ /** * Check whether given basepage matches current program basepage * or basepage for its parents. If yes, return true, otherwise false. */ static bool GemDOS_BasepageMatches(uint32_t checkbase) { int maxparents = 12; /* prevent basepage parent loops */ uint32_t basepage = STMemory_ReadLong(act_pd); while (maxparents-- > 0 && STMemory_CheckAreaType(basepage, BASEPAGE_SIZE, ABFLAG_RAM)) { if (basepage == checkbase) return true; basepage = STMemory_ReadLong(basepage + BASEPAGE_OFFSET_PARENT); } return false; } /** * Check whether TOS handle is within our table range, or aliased, * return (positive) internal Handle if yes, (negative) -1 for error. */ static int GemDOS_GetValidFileHandle(int Handle) { int Forced = -1; /* Has handle been aliased with Fforce()? */ if (Handle >= 0 && Handle < ARRAY_SIZE(ForcedHandles) && ForcedHandles[Handle].Handle != UNFORCED_HANDLE) { if (GemDOS_BasepageMatches(ForcedHandles[Handle].Basepage)) { Forced = Handle; Handle = ForcedHandles[Handle].Handle; } else { Log_Printf(LOG_WARN, "Removing (stale?) %d->%d file handle redirection.", Handle, ForcedHandles[Handle].Handle); GemDOS_UnforceFileHandle(Handle); return -1; } } else { Handle -= BASE_FILEHANDLE; } /* handle is valid for current program and in our handle table? */ if (Handle >= 0 && Handle < ARRAY_SIZE(FileHandles) && FileHandles[Handle].bUsed) { uint32_t current = STMemory_ReadLong(act_pd); if (FileHandles[Handle].Basepage == current || Forced >= 0) return Handle; /* A potential bug in Atari program or Hatari GEMDOS emulation */ Log_Printf(LOG_WARN, "program (basebase 0x%x) accessing another program (basepage 0x%x) file handle %d.", current, FileHandles[Handle].Basepage, Handle); return Handle; } /* invalid handle */ return -1; } /*-----------------------------------------------------------------------*/ /** * Find drive letter from a filename, eg C,D... and return as drive ID(C:2, D:3...) * returns the current drive number if no drive is specified. For special * devices (CON:, AUX:, PRN:), returns an invalid drive number. */ static int GemDOS_FindDriveNumber(char *pszFileName) { /* Does have 'A:' or 'C:' etc.. at start of string? */ if (pszFileName[0] != '\0' && pszFileName[1] == ':') { char letter = toupper((unsigned char)pszFileName[0]); if (letter >= 'A' && letter <= 'Z') return (letter-'A'); } else if (strlen(pszFileName) == 4 && pszFileName[3] == ':') { /* ':' can be used only as drive indicator, not otherwise, * so no need to check even special device name. */ return 0; } return CurrentDrive; } /** * Return true if drive ID (C:2, D:3 etc...) matches emulated hard-drive */ bool GemDOS_IsDriveEmulated(int drive) { drive -= 2; if (drive < 0 || drive >= MAX_HARDDRIVES) return false; if (!(emudrives && emudrives[drive])) return false; assert(emudrives[drive]->drive_number == drive+2); return true; } /*-----------------------------------------------------------------------*/ /** * Return drive ID(C:2, D:3 etc...) or -1 if not one of our emulation hard-drives */ static int GemDOS_FileName2HardDriveID(char *pszFileName) { /* Do we even have a hard-drive? */ if (GEMDOS_EMU_ON) { int DriveNumber; /* Find drive letter (as number) */ DriveNumber = GemDOS_FindDriveNumber(pszFileName); if (GemDOS_IsDriveEmulated(DriveNumber)) return DriveNumber; } /* Not a high-level redirected drive, let TOS handle it */ return -1; } /*-----------------------------------------------------------------------*/ /** * Check whether a file in given path matches given case-insensitive pattern. * Return first matched name which caller needs to free, or NULL for no match. */ static char* match_host_dir_entry(const char *path, const char *name, bool pattern) { #define MAX_UTF8_NAME_LEN (3*(8+1+3)+1) /* UTF-8 can have up to 3 bytes per character */ struct dirent *entry; char *match = NULL; DIR *dir; char nameHost[MAX_UTF8_NAME_LEN]; Str_Filename_Atari2Host(name, nameHost, MAX_UTF8_NAME_LEN, INVALID_CHAR); name = nameHost; dir = opendir(path); if (!dir) return NULL; #if DEBUG_PATTERN_MATCH fprintf(stderr, "DEBUG: GEMDOS match '%s'%s in '%s'", name, pattern?" (pattern)":"", path); #endif if (pattern) { while ((entry = readdir(dir))) { char *d_name = entry->d_name; Str_DecomposedToPrecomposedUtf8(d_name, d_name); /* for OSX */ if (fsfirst_match(name, d_name)) { match = strdup(d_name); break; } } } else { while ((entry = readdir(dir))) { char *d_name = entry->d_name; Str_DecomposedToPrecomposedUtf8(d_name, d_name); /* for OSX */ if (strcasecmp(name, d_name) == 0) { match = strdup(d_name); break; } } } closedir(dir); #if DEBUG_PATTERN_MATCH fprintf(stderr, "-> '%s'\n", match); #endif return match; } static int to_same(int ch) { return ch; } /** * Clip given file name to 8+3 length like TOS does, * return resulting name length. */ static int clip_to_83(char *name) { int diff, len; char *dot; dot = strchr(name, '.'); if (dot) { diff = strlen(dot) - 4; if (diff > 0) { Log_Printf(LOG_WARN, "have to clip %d chars from '%s' extension!\n", diff, name); dot[4] = '\0'; } diff = dot - name - 8; if (diff > 0) { Log_Printf(LOG_WARN, "have to clip %d chars from '%s' base!\n", diff, name); memmove(name + 8, dot, strlen(dot) + 1); } return strlen(name); } len = strlen(name); if (len > 8) { Log_Printf(LOG_WARN, "have to clip %d chars from '%s'!\n", len - 8, name); name[8] = '\0'; len = 8; } return len; } /*-----------------------------------------------------------------------*/ /** * Check whether given TOS file/dir exists in given host path. * If it does, add the matched host filename to the given path, * otherwise add the given filename as is to it. Guarantees * that the resulting string doesn't exceed maxlen+1. * * Return true if match found, false otherwise. */ static bool add_path_component(char *path, int maxlen, const char *origname, bool is_dir) { char *tmp, *match; int dot, namelen, pathlen; int (*chr_conv)(int); bool modified; char *name = alloca(strlen(origname) + 3); /* append separator */ pathlen = strlen(path); if (pathlen >= maxlen) return false; path[pathlen++] = PATHSEP; path[pathlen] = '\0'; /* TOS clips names to 8+3 length */ strcpy(name, origname); namelen = clip_to_83(name); /* first try exact (case insensitive) match */ match = match_host_dir_entry(path, name, false); if (match) { /* use strncat so that string is always nul terminated */ strncat(path+pathlen, match, maxlen-pathlen); free(match); return true; } /* Here comes a work-around for a bug in the file selector * of TOS 1.02: When a folder name has exactly 8 characters, * it appends a '.' at the end of the name... */ if (is_dir && namelen == 9 && name[8] == '.') { name[8] = '\0'; match = match_host_dir_entry(path, name, false); if (match) { strncat(path+pathlen, match, maxlen-pathlen); free(match); return true; } } /* Assume there were invalid characters or that the host file * was too long to fit into GEMDOS 8+3 filename limits. * If that's the case, modify the name to a pattern that * will match such host files and try again. */ modified = false; /* catch potentially invalid characters */ for (tmp = name; *tmp; tmp++) { if (*tmp == INVALID_CHAR) { *tmp = '?'; modified = true; } } /* catch potentially too long extension */ for (dot = 0; name[dot] && name[dot] != '.'; dot++); if (namelen - dot > 3) { dot++; /* "emulated.too" -> "emulated.too*" */ name[namelen++] = '*'; name[namelen] = '\0'; modified = true; } /* catch potentially too long part before extension */ if (namelen > 8 && name[8] == '.') { dot++; /* "emulated.too*" -> "emulated*.too*" */ memmove(name+9, name+8, namelen-7); namelen++; name[8] = '*'; modified = true; } /* catch potentially too long part without extension */ else if (namelen == 8 && !name[dot]) { /* "emulated" -> "emulated*" */ name[8] = '*'; name[9] = '\0'; namelen++; modified = true; } if (modified) { match = match_host_dir_entry(path, name, true); if (match) { strncat(path+pathlen, match, maxlen-pathlen); free(match); return true; } } /* not found, copy file/dirname as is */ switch (ConfigureParams.HardDisk.nGemdosCase) { case GEMDOS_UPPER: chr_conv = toupper; break; case GEMDOS_LOWER: chr_conv = tolower; break; default: chr_conv = to_same; } tmp = name; while (*origname) *tmp++ = chr_conv(*origname++); *tmp = '\0'; /* strncat(path+pathlen, name, maxlen-pathlen); */ Str_Filename_Atari2Host(name, path+pathlen, maxlen-pathlen, INVALID_CHAR); return false; } /** * Join remaining path without matching. This helper is used after host * file name matching fails, to append the failing part of the TOS path * to the host path, so that it won't be a valid host path. * * Specifically, the path separators need to be converted, otherwise things * like Fcreate() could create files that have TOS directory names as part * of file names on Unix (as \ is valid filename char on Unix). Fcreate() * needs to create them only when just the file name isn't found, but all * the directory components have. */ static void add_remaining_path(const char *src, char *dstpath, int dstlen) { char *dst; int i = strlen(dstpath); Str_Filename_Atari2Host(src, dstpath+i, dstlen-i, INVALID_CHAR); for (dst = dstpath + i; *dst; dst++) if (*dst == '\\') *dst = PATHSEP; } /*-----------------------------------------------------------------------*/ /** * Use hard-drive directory, current ST directory and filename * to create correct path to host file system. If given filename * isn't found on host file system, just append GEMDOS filename * to the path as is. * * TODO: currently there are many callers which give this dest buffer of * MAX_GEMDOS_PATH size i.e. don't take into account that host filenames * can be up to FILENAME_MAX long. Plain GEMDOS paths themselves may be * MAX_GEMDOS_PATH long even before host dir is prepended to it! * Way forward: allocate the host path here as FILENAME_MAX so that * it's always long enough and let callers free it. Assert if alloc * fails so that callers' don't need to. */ void GemDOS_CreateHardDriveFileName(int Drive, const char *pszFileName, char *pszDestName, int nDestNameLen) { const char *s, *filename = pszFileName; int minlen; /* make sure that more convenient strncat() can be used on the * destination string (it always null terminates unlike strncpy()) */ *pszDestName = 0; /* Is it a valid hard drive? */ assert(GemDOS_IsDriveEmulated(Drive)); /* Check for valid string */ if (filename[0] == '\0') return; /* strcat writes n+1 chars, so decrease len */ nDestNameLen--; /* full filename with drive "C:\foo\bar" */ if (filename[1] == ':') { strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen); filename += 2; } /* filename referenced from root: "\foo\bar" */ else if (filename[0] == '\\') { strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen); } /* filename relative to current directory */ else { strncat(pszDestName, emudrives[Drive-2]->fs_currpath, nDestNameLen); } minlen = strlen(emudrives[Drive-2]->hd_emulation_dir); /* this doesn't take into account possible long host filenames * that will make dest name longer than pszFileName 8.3 paths, * or GEMDOS paths using "../" which make it smaller. Both * should(?) be rare in paths, so this info to user should be * good enough. */ if (nDestNameLen < minlen + (int)strlen(pszFileName) + 2) { Log_AlertDlg(LOG_ERROR, "Appending GEMDOS path '%s' to HDD emu host root dir doesn't fit to %d chars (current Hatari limit)!", pszFileName, nDestNameLen); add_remaining_path(filename, pszDestName, nDestNameLen); return; } /* "../" handling breaks if there are extra slashes */ File_CleanFileName(pszDestName); /* go through path directory components, advancing 'filename' * pointer while parsing them. */ for (;;) { /* skip extra path separators */ while (*filename == '\\') filename++; // fprintf(stderr, "filename: '%s', path: '%s'\n", filename, pszDestName); /* skip "." references to current directory */ if (filename[0] == '.' && (filename[1] == '\\' || !filename[1])) { filename++; continue; } /* ".." path component -> strip last dir from dest path */ if (filename[0] == '.' && filename[1] == '.' && (filename[2] == '\\' || !filename[2])) { char *sep = strrchr(pszDestName, PATHSEP); if (sep) { if (sep - pszDestName < minlen) Log_Printf(LOG_WARN, "GEMDOS path '%s' tried to back out of GEMDOS drive!\n", pszFileName); else *sep = '\0'; } filename += 2; continue; } /* handle directory component */ if ((s = strchr(filename, '\\'))) { int dirlen = s - filename; char *dirname = alloca(dirlen + 1); /* copy dirname */ strncpy(dirname, filename, dirlen); dirname[dirlen] = '\0'; /* and advance filename */ filename = s; if (strchr(dirname, '?') || strchr(dirname, '*')) Log_Printf(LOG_WARN, "GEMDOS dir name '%s' with wildcards in %s!\n", dirname, pszFileName); /* convert and append dirname to host path */ if (!add_path_component(pszDestName, nDestNameLen, dirname, true)) { Log_Printf(LOG_WARN, "No GEMDOS dir '%s'\n", pszDestName); add_remaining_path(filename, pszDestName, nDestNameLen); return; } continue; } /* path directory components done */ break; } if (*filename) { /* a wildcard instead of a complete file name? */ if (strchr(filename,'?') || strchr(filename,'*')) { int len = strlen(pszDestName); if (len < nDestNameLen) { pszDestName[len++] = PATHSEP; pszDestName[len] = '\0'; } /* use strncat so that string is always nul terminated */ /* strncat(pszDestName+len, filename, nDestNameLen-len); */ Str_Filename_Atari2Host(filename, pszDestName+len, nDestNameLen-len, INVALID_CHAR); } else if (!add_path_component(pszDestName, nDestNameLen, filename, false)) { /* It's often normal, that GEM uses this to test for * existence of desktop.inf or newdesk.inf for example. */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS didn't find filename %s\n", pszDestName); return; } } LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS: %s -> host: %s\n", pszFileName, pszDestName); } /** * GEMDOS Cconws * Call 0x9 */ static bool GemDOS_Cconws(uint32_t Params) { uint32_t Addr; char *pBuffer; Addr = STMemory_ReadLong(Params); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x9 Cconws(0x%X) at PC 0x%X\n", Addr, CallingPC); /* We only intercept this call in non-TOS mode */ if (bUseTos) return false; /* Check that write is from valid memory area */ if ((CallingPC < TosAddress || CallingPC >= TosAddress + TosSize) && !STMemory_CheckAreaType(Addr, 80 * 25, ABFLAG_RAM)) { Log_Printf(LOG_WARN, "GEMDOS Cconws() failed due to invalid RAM range at 0x%x\n", Addr); Regs[REG_D0] = GEMDOS_ERANGE; return true; } pBuffer = (char *)STMemory_STAddrToPointer(Addr); if (fwrite(pBuffer, strnlen(pBuffer, 80 * 25), 1, stdout) < 1) Regs[REG_D0] = GEMDOS_ERROR; else Regs[REG_D0] = GEMDOS_EOK; return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Set drive (0=A,1=B,2=C etc...) * Call 0xE */ static bool GemDOS_SetDrv(uint32_t Params) { /* Read details from stack for our own use */ CurrentDrive = STMemory_ReadWord(Params); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x0E Dsetdrv(0x%x) at PC=0x%X\n", (int)CurrentDrive, CallingPC); /* Still re-direct to TOS */ return false; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Dfree Free disk space. * Call 0x36 */ static bool GemDOS_DFree(uint32_t Params) { #ifdef HAVE_STATVFS struct statvfs buf; #endif int Drive; uint64_t Total, Free; uint32_t Address; Address = STMemory_ReadLong(Params); Drive = STMemory_ReadWord(Params+SIZE_LONG); /* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x36 Dfree(0x%x, %i) at PC 0x%X\n", Address, Drive, CallingPC); if (Drive == 0) Drive = CurrentDrive; else Drive--; /* is it our drive? */ if (!GemDOS_IsDriveEmulated(Drive)) { /* no, redirect to TOS */ return false; } /* Check that write is requested to valid memory area */ if ( !STMemory_CheckAreaType ( Address, 16, ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "GEMDOS Dfree() failed due to invalid RAM range 0x%x+%i\n", Address, 16); Regs[REG_D0] = GEMDOS_ERANGE; return true; } #ifdef HAVE_STATVFS memset(&buf, 0, sizeof(buf)); if (statvfs(emudrives[Drive-2]->hd_emulation_dir, &buf) == 0) { unsigned tosMax; long bsize; /* According to Linux manpage, f_frsize should be used for * total, and according to NetBSD manpage, also for free. * * According to GNU coreutils code, f_frsize might not * be set (according to kernel code, it's set to * f_bsize when zero, so that might be old info). * * => use frsize if it's set */ bsize = buf.f_frsize > 0 ? buf.f_frsize : buf.f_bsize; Total = buf.f_blocks * bsize / 1024; /* use unprivileged user free, if available */ Free = buf.f_bavail > 0 ? buf.f_bavail : buf.f_bfree; Free = Free * bsize / 1024; /* TOS version limits based on: * http://hddriver.seimet.de/en/faq.html */ if (TosVersion >= 0x0400) { tosMax = 1024*1024; } else { if (TosVersion >= 0x0106) tosMax = 512*1024; else tosMax = 256*1024; } /* free cannot be larger than total, and * total should not be zero, but free can */ if (Total > tosMax) Total = tosMax; if (Free > Total) Free = Total; if (Total == 0) Total = tosMax; } else #endif { /* fake 32MB drive with 16MB free */ Total = 32*1024; Free = 16*1024; } M68000_Flush_Data_Cache(Address, 4*SIZE_LONG); STMemory_WriteLong(Address, Free); /* free clusters */ STMemory_WriteLong(Address+SIZE_LONG, Total); /* total clusters */ STMemory_WriteLong(Address+SIZE_LONG*2, 512); /* bytes per sector */ STMemory_WriteLong(Address+SIZE_LONG*3, 2); /* sectors per cluster (cluster = 1KB) */ Regs[REG_D0] = GEMDOS_EOK; return true; } /*-----------------------------------------------------------------------*/ typedef enum { ERROR_FILE, ERROR_PATH } etype_t; /** * Helper to log libc errno and map it to GEMDOS error value */ static uint32_t errno2gemdos(const int error, const etype_t etype) { LOG_TRACE(TRACE_OS_GEMDOS, "-> ERROR (errno = %d)\n", error); switch (error) { case ENOENT: if (etype == ERROR_FILE) return GEMDOS_EFILNF;/* File not found */ /* fallthrough */ case ENOTDIR: return GEMDOS_EPTHNF; /* Path not found */ case ENOTEMPTY: case EEXIST: case EPERM: case EACCES: case EROFS: return GEMDOS_EACCDN; /* Access denied */ default: return GEMDOS_ERROR; /* Misc error */ } } /*-----------------------------------------------------------------------*/ /** * GEMDOS MkDir * Call 0x39 */ static bool GemDOS_MkDir(uint32_t Params) { char *pDirName, *psDirPath; int Drive; uint32_t nStrAddr = STMemory_ReadLong(Params); pDirName = STMemory_GetStringPointer(nStrAddr); if (!pDirName || !pDirName[0]) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x39 bad Dcreate(0x%X) at PC 0x%X\n", nStrAddr, CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x39 Dcreate(\"%s\") at PC 0x%X\n", pDirName, CallingPC); Drive = GemDOS_FileName2HardDriveID(pDirName); if (!ISHARDDRIVE(Drive)) { /* redirect to TOS */ return false; } /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Dcreate(\"%s\")\n", pDirName); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } psDirPath = malloc(FILENAME_MAX); if (!psDirPath) { perror("GemDOS_MkDir"); Regs[REG_D0] = GEMDOS_ENSMEM; return true; } /* Copy old directory, as if calls fails keep this one */ GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX); /* Attempt to make directory */ if (mkdir(psDirPath, 0755) == 0) Regs[REG_D0] = GEMDOS_EOK; else Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH); free(psDirPath); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS RmDir * Call 0x3A */ static bool GemDOS_RmDir(uint32_t Params) { char *pDirName, *psDirPath; int Drive; uint32_t nStrAddr = STMemory_ReadLong(Params); pDirName = STMemory_GetStringPointer(nStrAddr); if (!pDirName || !pDirName[0]) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3A bad Ddelete(0x%X) at PC 0x%X\n", nStrAddr, CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3A Ddelete(\"%s\") at PC 0x%X\n", pDirName, CallingPC); Drive = GemDOS_FileName2HardDriveID(pDirName); if (!ISHARDDRIVE(Drive)) { /* redirect to TOS */ return false; } /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Ddelete(\"%s\")\n", pDirName); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } psDirPath = malloc(FILENAME_MAX); if (!psDirPath) { perror("GemDOS_RmDir"); Regs[REG_D0] = GEMDOS_ENSMEM; return true; } /* Copy old directory, as if calls fails keep this one */ GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX); /* Attempt to remove directory */ if (rmdir(psDirPath) == 0) Regs[REG_D0] = GEMDOS_EOK; else Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH); free(psDirPath); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS ChDir * Call 0x3B */ static bool GemDOS_ChDir(uint32_t Params) { char *pDirName, *psTempDirPath; int Drive; uint32_t nStrAddr = STMemory_ReadLong(Params); pDirName = STMemory_GetStringPointer(nStrAddr); if (!pDirName) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3B Dsetpath with illegal file name (0x%x) at PC 0x%X\n", nStrAddr, CallingPC); Regs[REG_D0] = GEMDOS_EPTHNF; return true; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3B Dsetpath(\"%s\") at PC 0x%X\n", pDirName, CallingPC); Drive = GemDOS_FileName2HardDriveID(pDirName); if (!ISHARDDRIVE(Drive)) { /* redirect to TOS */ return false; } /* Empty string does nothing */ if (*pDirName == '\0') { Regs[REG_D0] = GEMDOS_EOK; return true; } /* Allocate temporary memory for path name: */ psTempDirPath = malloc(FILENAME_MAX); if (!psTempDirPath) { perror("GemDOS_ChDir"); Regs[REG_D0] = GEMDOS_ENSMEM; return true; } GemDOS_CreateHardDriveFileName(Drive, pDirName, psTempDirPath, FILENAME_MAX); /* Remove trailing slashes (stat on Windows does not like that) */ File_CleanFileName(psTempDirPath); if (access(psTempDirPath, F_OK) != 0) { /* error */ free(psTempDirPath); Regs[REG_D0] = GEMDOS_EPTHNF; return true; } File_AddSlashToEndFileName(psTempDirPath); File_MakeAbsoluteName(psTempDirPath); /* Prevent '..' commands moving BELOW the root HDD folder */ /* by double checking if path is valid */ if (strncmp(psTempDirPath, emudrives[Drive-2]->hd_emulation_dir, strlen(emudrives[Drive-2]->hd_emulation_dir)) == 0) { Str_Copy(emudrives[Drive-2]->fs_currpath, psTempDirPath, sizeof(emudrives[Drive-2]->fs_currpath)); Regs[REG_D0] = GEMDOS_EOK; } else { Regs[REG_D0] = GEMDOS_EPTHNF; } free(psTempDirPath); return true; } /*-----------------------------------------------------------------------*/ /** * Helper to check whether given file's path is missing. * Returns true if missing, false if found. * Modifies the argument buffer. */ static bool GemDOS_FilePathMissing(char *szActualFileName) { char *ptr = strrchr(szActualFileName, PATHSEP); if (ptr) { *ptr = 0; /* Strip filename from string */ if (!File_DirExists(szActualFileName)) return true; } return false; } /*-----------------------------------------------------------------------*/ static inline bool redirect_to_TOS(void) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> to TOS\n"); return false; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Create file * Call 0x3C */ static bool GemDOS_Create(uint32_t Params) { /* TODO: host filenames might not fit into this */ char szActualFileName[MAX_GEMDOS_PATH]; char *pszFileName; int Drive, Index; uint32_t nStrAddr = STMemory_ReadLong(Params); int Mode = STMemory_ReadWord(Params + SIZE_LONG); pszFileName = STMemory_GetStringPointer(nStrAddr); if (!pszFileName || !pszFileName[0]) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3C bad Fcreate(0x%X, 0x%x) at PC 0x%X\n", nStrAddr, Mode, CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3C Fcreate(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, Mode, CallingPC); Drive = GemDOS_FileName2HardDriveID(pszFileName); if (!ISHARDDRIVE(Drive)) { /* redirect to TOS */ return redirect_to_TOS(); } if (IS_VOLUME_LABEL(Mode)) { Log_Printf(LOG_WARN, "volume label creation not supported on GEMDOS HD\n"); Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */ return true; } /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fcreate(\"%s\")\n", pszFileName); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } /* Now convert to hard drive filename */ GemDOS_CreateHardDriveFileName(Drive, pszFileName, szActualFileName, sizeof(szActualFileName)); /* Find slot to store file handle, as need to return WORD handle for ST */ Index = GemDOS_FindFreeFileHandle(); if (Index == -1) { /* No free handles, return error code */ Regs[REG_D0] = GEMDOS_ENHNDL; /* No more handles */ return true; } /* truncate and open for reading & writing */ FileHandles[Index].FileHandle = fopen(szActualFileName, "wb+"); if (FileHandles[Index].FileHandle != NULL) { /* FIXME: implement other Mode attributes * - GEMDOS_FILE_ATTRIB_HIDDEN (FA_HIDDEN) * - GEMDOS_FILE_ATTRIB_SYSTEM_FILE (FA_SYSTEM) * - GEMDOS_FILE_ATTRIB_SUBDIRECTORY (FA_DIR) * - GEMDOS_FILE_ATTRIB_WRITECLOSE (FA_ARCHIVE) * (set automatically by GemDOS >= 0.15) */ FileHandles[Index].bReadOnly = false; if (Mode & GEMDOS_FILE_ATTRIB_READONLY) { /* enable write warnings */ FileHandles[Index].bReadOnly = true; /* after closing, file should be read-only */ if (chmod(szActualFileName, S_IRUSR|S_IRGRP|S_IROTH)) { perror("Failed to set file to read-only"); } } /* Tag handle table entry as used in this process and return handle */ FileHandles[Index].bUsed = true; strcpy(FileHandles[Index].szMode, "wb+"); FileHandles[Index].Basepage = STMemory_ReadLong(act_pd); snprintf(FileHandles[Index].szActualName, sizeof(FileHandles[Index].szActualName), "%s", szActualFileName); /* Return valid ST file handle from our range (from BASE_FILEHANDLE upwards) */ Regs[REG_D0] = Index+BASE_FILEHANDLE; LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> FD %d (%s)\n", Regs[REG_D0], Mode & GEMDOS_FILE_ATTRIB_READONLY ? "read-only":"read/write"); return true; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR (errno = %d)\n", errno); /* We failed to create the file, did we have required access rights? */ if (errno == EACCES || errno == EROFS || errno == EPERM || errno == EISDIR) { Log_Printf(LOG_WARN, "GEMDOS failed to create/truncate '%s'\n", szActualFileName); Regs[REG_D0] = GEMDOS_EACCDN; return true; } /* Or was path to file missing? (ST-Zip 2.6 relies on getting * correct error about that during extraction of ZIP files.) */ if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName)) { Regs[REG_D0] = GEMDOS_EPTHNF; /* Path not found */ return true; } Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */ return true; } /** * GEMDOS Open file * Call 0x3D */ static bool GemDOS_Open(uint32_t Params) { /* TODO: host filenames might not fit into this */ char szActualFileName[MAX_GEMDOS_PATH]; char *pszFileName; const char *ModeStr; LOG_TRACE_VAR const char *RealMode; const char *Modes[] = { "read-only", "write-only", "read/write", "read/write" }; int Drive, Index; FILE *OverrideHandle; bool bToTos = false; uint32_t nStrAddr = STMemory_ReadLong(Params); int Mode = STMemory_ReadWord(Params+SIZE_LONG) & 3; pszFileName = STMemory_GetStringPointer(nStrAddr); if (!pszFileName || !pszFileName[0]) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3D bad Fopen(0x%X, %s) at PC=0x%X\n", nStrAddr, Modes[Mode], CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3D Fopen(\"%s\", %s) at PC=0x%X\n", pszFileName, Modes[Mode], CallingPC); Drive = GemDOS_FileName2HardDriveID(pszFileName); if (!ISHARDDRIVE(Drive)) { if (INF_Overriding(AUTOSTART_FOPEN)) bToTos = true; else return redirect_to_TOS(); } /* Find slot to store file handle, as need to return WORD handle for ST */ Index = GemDOS_FindFreeFileHandle(); if (Index == -1) { if (bToTos) return redirect_to_TOS(); /* No free handles, return error code */ Regs[REG_D0] = GEMDOS_ENHNDL; /* No more handles */ Log_Printf(LOG_WARN, "no free GEMDOS HD file handles for Fopen()"); LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR %d\n", Regs[REG_D0]); return true; } if ((OverrideHandle = INF_OpenOverride(pszFileName))) { strcpy(szActualFileName, pszFileName); FileHandles[Index].FileHandle = OverrideHandle; FileHandles[Index].bReadOnly = true; RealMode = "read-only"; ModeStr = "rb"; } else { if (bToTos) return redirect_to_TOS(); /* Convert to hard drive filename */ GemDOS_CreateHardDriveFileName(Drive, pszFileName, szActualFileName, sizeof(szActualFileName)); /* Fread/Fwrite calls succeed in all TOS versions * regardless of access rights specified in Fopen(). * Only time when things can fail is when file is * opened, if file mode doesn't allow given opening * mode. As there's no write-only file mode, access * failures happen only when trying to open read-only * file with (read+)write mode. * * Therefore only read-only & read+write modes need * to be supported (ANSI-C fopen() doesn't even * support write-only without truncating the file). * * Read-only status is used if: * - Hatari write protection is enabled * - File exists, but is not writable * Latter is done to help cases where application * needlessly requests write access, but file is * on read-only media (like CD/DVD). */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON || (access(szActualFileName, F_OK) == 0 && access(szActualFileName, W_OK) != 0)) { ModeStr = "rb"; RealMode = "read-only"; FileHandles[Index].bReadOnly = true; } else { ModeStr = "rb+"; RealMode = "read+write"; FileHandles[Index].bReadOnly = (Mode == 0); } FileHandles[Index].FileHandle = fopen(szActualFileName, ModeStr); } if (FileHandles[Index].FileHandle != NULL) { /* Tag handle table entry as used in this process and return handle */ FileHandles[Index].bUsed = true; strcpy(FileHandles[Index].szMode, ModeStr); FileHandles[Index].Basepage = STMemory_ReadLong(act_pd); snprintf(FileHandles[Index].szActualName, sizeof(FileHandles[Index].szActualName), "%s", szActualFileName); /* Return valid ST file handle from our range (BASE_FILEHANDLE upwards) */ Regs[REG_D0] = Index+BASE_FILEHANDLE; LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> FD %d (%s -> %s)\n", Regs[REG_D0], Modes[Mode], RealMode); return true; } if (errno == EACCES || errno == EROFS || errno == EPERM || errno == EISDIR) { Log_Printf(LOG_WARN, "GEMDOS missing %s permission to file '%s'\n", Modes[Mode], szActualFileName); Regs[REG_D0] = GEMDOS_EACCDN; } else if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName)) { /* Path not found */ Regs[REG_D0] = GEMDOS_EPTHNF; } else { /* File not found / error opening */ Regs[REG_D0] = GEMDOS_EFILNF; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR %d (errno = %d)\n", Regs[REG_D0], errno); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Close file * Call 0x3E */ static bool GemDOS_Close(uint32_t Params) { int i, Handle; /* Find our handle - may belong to TOS */ Handle = STMemory_ReadWord(Params); LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x3E Fclose(%i) at PC 0x%X\n", Handle, CallingPC); /* Get internal handle */ if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0) { /* no, assume it was TOS one -> redirect */ return false; } /* Close file and free up handle table */ if (INF_CloseOverride(FileHandles[Handle].FileHandle)) { FileHandles[Handle].bUsed = false; } GemDOS_CloseFileHandle(Handle); /* unalias handle */ for (i = 0; i < ARRAY_SIZE(ForcedHandles); i++) { if (ForcedHandles[i].Handle == Handle) GemDOS_UnforceFileHandle(i); } /* Return no error */ Regs[REG_D0] = GEMDOS_EOK; return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Read file * Call 0x3F */ static bool GemDOS_Read(uint32_t Params) { char *pBuffer; off_t CurrentPos, FileSize; long nBytesRead, nBytesLeft; uint32_t Addr; uint32_t Size; int Handle; /* Read details from stack */ Handle = STMemory_ReadWord(Params); Size = STMemory_ReadLong(Params+SIZE_WORD); Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3F Fread(%i, %i, 0x%x) at PC 0x%X\n", Handle, Size, Addr, CallingPC); /* Get internal handle */ if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0) { /* assume it was TOS one -> redirect */ return false; } /* Old TOS versions treat the Size parameter as signed */ if (TosVersion < 0x400 && (Size & 0x80000000)) { /* return -1 as original GEMDOS */ Regs[REG_D0] = -1; return true; } /* To quick check to see where our file pointer is and how large the file is */ CurrentPos = ftello(FileHandles[Handle].FileHandle); if (CurrentPos == -1L || fseeko(FileHandles[Handle].FileHandle, 0, SEEK_END) != 0) { Regs[REG_D0] = GEMDOS_E_SEEK; return true; } FileSize = ftello(FileHandles[Handle].FileHandle); if (FileSize == -1L || fseeko(FileHandles[Handle].FileHandle, CurrentPos, SEEK_SET) != 0) { Regs[REG_D0] = GEMDOS_E_SEEK; return true; } nBytesLeft = FileSize-CurrentPos; /* Check for bad size and End Of File */ if (Size <= 0 || nBytesLeft <= 0) { /* return zero (bytes read) as original GEMDOS/EmuTOS */ Regs[REG_D0] = 0; return true; } /* Limit to size of file to prevent errors */ if (Size > (uint32_t)nBytesLeft) Size = nBytesLeft; /* Check that read is to valid memory area */ if ( !STMemory_CheckAreaType ( Addr, Size, ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "GEMDOS Fread() failed due to invalid RAM range 0x%x+%i\n", Addr, Size); Regs[REG_D0] = GEMDOS_ERANGE; return true; } /* Atari memory modified directly with fread() -> flush the instr/data caches */ M68000_Flush_All_Caches(Addr, Size); /* And read data in */ pBuffer = (char *)STMemory_STAddrToPointer(Addr); nBytesRead = fread(pBuffer, 1, Size, FileHandles[Handle].FileHandle); if (ferror(FileHandles[Handle].FileHandle)) { int errnum = errno; Log_Printf(LOG_WARN, "GEMDOS failed to read from '%s': %s\n", FileHandles[Handle].szActualName, strerror(errno)); Regs[REG_D0] = errno2gemdos(errnum, ERROR_FILE); clearerr(FileHandles[Handle].FileHandle); } else /* Return number of bytes read */ Regs[REG_D0] = nBytesRead; return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Write file * Call 0x40 */ static bool GemDOS_Write(uint32_t Params) { char *pBuffer; long nBytesWritten; uint32_t Addr; int32_t Size; int Handle, fh_idx; FILE *fp; /* Read details from stack */ Handle = STMemory_ReadWord(Params); Size = STMemory_ReadLong(Params+SIZE_WORD); Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x40 Fwrite(%i, %i, 0x%x) at PC 0x%X\n", Handle, Size, Addr, CallingPC); /* Get internal handle */ fh_idx = GemDOS_GetValidFileHandle(Handle); if (fh_idx >= 0) { /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fwrite(%d,...)\n", Handle); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } fp = FileHandles[fh_idx].FileHandle; } else { if (!bUseTos && Handle == 1) fp = stdout; else if (!bUseTos && (Handle == 2 || Handle == -1)) fp = stderr; else return false; /* assume it was TOS one -> redirect */ } /* Check that write is from valid memory area */ if (!STMemory_CheckAreaType(Addr, Size, ABFLAG_RAM | ABFLAG_ROM)) { Log_Printf(LOG_WARN, "GEMDOS Fwrite() failed due to invalid RAM range 0x%x+%i\n", Addr, Size); Regs[REG_D0] = GEMDOS_ERANGE; return true; } pBuffer = (char *)STMemory_STAddrToPointer(Addr); fseek(fp, 0, SEEK_CUR); nBytesWritten = fwrite(pBuffer, 1, Size, fp); if (fh_idx >= 0 && ferror(fp)) { int errnum = errno; Log_Printf(LOG_WARN, "GEMDOS failed to write to '%s': %s\n", FileHandles[fh_idx].szActualName, strerror(errno)); Regs[REG_D0] = errno2gemdos(errnum, ERROR_FILE); clearerr(fp); } else { fflush(fp); Regs[REG_D0] = nBytesWritten; /* OK */ } if (fh_idx >= 0 && FileHandles[fh_idx].bReadOnly) { Log_Printf(LOG_WARN, "GEMDOS Fwrite() to a read-only file '%s'\n", File_Basename(FileHandles[fh_idx].szActualName)); } return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Delete file * Call 0x41 */ static bool GemDOS_FDelete(uint32_t Params) { char *pszFileName, *psActualFileName; int Drive; uint32_t nStrAddr = STMemory_ReadLong(Params); pszFileName = STMemory_GetStringPointer(nStrAddr); if (!pszFileName || !pszFileName[0]) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x41 bad Fdelete(0x%X) at PC 0x%X\n", nStrAddr, CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x41 Fdelete(\"%s\") at PC 0x%X\n", pszFileName, CallingPC); Drive = GemDOS_FileName2HardDriveID(pszFileName); if (!ISHARDDRIVE(Drive)) { /* redirect to TOS */ return false; } /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdelete(\"%s\")\n", pszFileName); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } psActualFileName = malloc(FILENAME_MAX); if (!psActualFileName) { perror("GemDOS_FDelete"); Regs[REG_D0] = GEMDOS_ENSMEM; return true; } /* And convert to hard drive filename */ GemDOS_CreateHardDriveFileName(Drive, pszFileName, psActualFileName, FILENAME_MAX); /* Now delete file?? */ if (unlink(psActualFileName) == 0) Regs[REG_D0] = GEMDOS_EOK; /* OK */ else Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE); free(psActualFileName); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS File seek * Call 0x42 */ static bool GemDOS_LSeek(uint32_t Params) { long Offset; int Handle, Mode; long nFileSize; long nOldPos, nDestPos; FILE *fhndl; /* Read details from stack */ Offset = (int32_t)STMemory_ReadLong(Params); Handle = STMemory_ReadWord(Params+SIZE_LONG); Mode = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x42 Fseek(%li, %i, %i) at PC 0x%X\n", Offset, Handle, Mode, CallingPC); /* get internal handle */ if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0) { /* assume it was TOS one -> redirect */ return false; } fhndl = FileHandles[Handle].FileHandle; /* Save old position in file */ nOldPos = ftell(fhndl); /* Determine the size of the file */ if (fseek(fhndl, 0L, SEEK_END) != 0 || nOldPos < 0) { Regs[REG_D0] = GEMDOS_E_SEEK; return true; } nFileSize = ftell(fhndl); switch (Mode) { case 0: nDestPos = Offset; break; /* positive offset */ case 1: nDestPos = nOldPos + Offset; break; case 2: nDestPos = nFileSize + Offset; break; /* negative offset */ default: nDestPos = -1; } if (nDestPos < 0 || nDestPos > nFileSize) { /* Restore old position and return error */ if (fseek(fhndl, nOldPos, SEEK_SET) != 0) perror("GemDOS_LSeek"); Regs[REG_D0] = GEMDOS_ERANGE; return true; } /* Seek to new position and return offset from start of file */ if (fseek(fhndl, nDestPos, SEEK_SET) != 0) perror("GemDOS_LSeek"); Regs[REG_D0] = ftell(fhndl); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Fattrib() - get or set file and directory attributes * Call 0x43 */ static bool GemDOS_Fattrib(uint32_t Params) { /* TODO: host filenames might not fit into this */ char sActualFileName[MAX_GEMDOS_PATH]; char *psFileName; int nDrive; struct stat FileStat; mode_t mode; uint32_t nStrAddr = STMemory_ReadLong(Params); int nRwFlag = STMemory_ReadWord(Params + SIZE_LONG); int nAttrib = STMemory_ReadWord(Params + SIZE_LONG + SIZE_WORD); psFileName = STMemory_GetStringPointer(nStrAddr); if (!psFileName || !psFileName[0]) { LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x43 bad Fattrib(0x%X, %d, 0x%x) at PC 0x%X\n", nStrAddr, nRwFlag, nAttrib, CallingPC); return false; } nDrive = GemDOS_FileName2HardDriveID(psFileName); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x43 Fattrib(\"%s\", %d, 0x%x) at PC 0x%X\n", psFileName, nRwFlag, nAttrib, CallingPC); if (!ISHARDDRIVE(nDrive)) { /* redirect to TOS */ return false; } /* Convert to hard drive filename */ GemDOS_CreateHardDriveFileName(nDrive, psFileName, sActualFileName, sizeof(sActualFileName)); if (IS_VOLUME_LABEL(nAttrib)) { Log_Printf(LOG_WARN, "volume label Fattrib() not supported on GEMDOS HD\n"); Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */ return true; } if (stat(sActualFileName, &FileStat) != 0) { Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */ return true; } mode = FileStat.st_mode; if (nRwFlag == 0) { /* Read attributes */ Regs[REG_D0] = GemDOS_ConvertAttribute(mode, sActualFileName); return true; } /* prevent modifying access rights both on write & auto-protected devices */ if (ConfigureParams.HardDisk.nWriteProtection != WRITEPROT_OFF) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fattrib(\"%s\",...)\n", psFileName); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } if (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY) { if (!S_ISDIR(mode)) { /* file, not dir -> path not found */ Regs[REG_D0] = GEMDOS_EPTHNF; return true; } } else { if (S_ISDIR(mode)) { /* dir, not file -> file not found */ Regs[REG_D0] = GEMDOS_EFILNF; return true; } } /* type checks done, mask other than mode info away */ mode &= S_IRWXU|S_IRWXG|S_IRWXO; /* set suitable mode */ if (nAttrib & GEMDOS_FILE_ATTRIB_READONLY) { /* mask write bits away */ mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH); } else { /* add user write bit */ mode |= S_IWUSR; } if (chmod(sActualFileName, mode) == 0) { Regs[REG_D0] = nAttrib; return true; } /* FIXME: support hidden/system/archive flags? * System flag is from DOS, not used by TOS. * Archive bit is cleared by backup programs * and set whenever file is written to. */ Regs[REG_D0] = errno2gemdos(errno, (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY) ? ERROR_PATH : ERROR_FILE); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Force (file handle aliasing) * Call 0x46 */ static bool GemDOS_Force(uint32_t Params) { int std, own; /* Read details from stack */ std = STMemory_ReadWord(Params); own = STMemory_ReadWord(Params+SIZE_WORD); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x46 Fforce(%d, %d) at PC 0x%X\n", std, own, CallingPC); /* Get internal handle */ if (std > own) { int tmp = std; std = own; own = tmp; } if ((own = GemDOS_GetValidFileHandle(own)) < 0) { /* assume it was TOS one -> let TOS handle it */ return false; } if (std < 0 || std >= ARRAY_SIZE(ForcedHandles)) { Log_Printf(LOG_WARN, "forcing of non-standard %d (> %d) handle ignored.\n", std, ARRAY_SIZE(ForcedHandles)); return false; } /* mark given standard handle redirected by this process */ ForcedHandles[std].Basepage = STMemory_ReadLong(act_pd); ForcedHandles[std].Handle = own; Regs[REG_D0] = GEMDOS_EOK; return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Get Directory * Call 0x47 */ static bool GemDOS_GetDir(uint32_t Params) { uint32_t Address; uint16_t Drive; Address = STMemory_ReadLong(Params); Drive = STMemory_ReadWord(Params+SIZE_LONG); /* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x47 Dgetpath(0x%x, %i) at PC 0x%X\n", Address, (int)Drive, CallingPC); if (Drive == 0) Drive = CurrentDrive; else Drive--; /* is it our drive? */ if (GemDOS_IsDriveEmulated(Drive)) { char path[MAX_GEMDOS_PATH]; int i,len,c; *path = '\0'; strncat(path,&emudrives[Drive-2]->fs_currpath[strlen(emudrives[Drive-2]->hd_emulation_dir)], sizeof(path)-1); // convert it to ST path (DOS) File_CleanFileName(path); if (path[0] == PATHSEP && path[1] == '\0') { /* Root directory is represented by empty string */ path[0] = '\0'; } len = strlen(path) + 1; /* Check that write is requested to valid memory area */ if ( !STMemory_CheckAreaType ( Address, len, ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "GEMDOS Dgetpath() failed due to invalid RAM range 0x%x+%i\n", Address, len); Regs[REG_D0] = GEMDOS_ERANGE; return true; } M68000_Flush_Data_Cache(Address, len); for (i = 0; i < len; i++) { c = path[i]; STMemory_WriteByte(Address+i, (c==PATHSEP ? '\\' : c) ); } LOG_TRACE(TRACE_OS_GEMDOS, "-> '%s'\n", (char *)STMemory_STAddrToPointer(Address)); Regs[REG_D0] = GEMDOS_EOK; /* OK */ return true; } /* redirect to TOS */ return false; } /*-----------------------------------------------------------------------*/ /** * GEMDOS PExec handler * Call 0x4B */ static int GemDOS_Pexec(uint32_t Params) { int Drive, len; char *pszFileName; FILE *fh; char sFileName[FILENAME_MAX]; uint8_t prgh[28]; /* Buffer for program header */ uint32_t prgname, cmdline, env_string; uint16_t mode; /* Get Pexec parameters */ mode = STMemory_ReadWord(Params); prgname = STMemory_ReadLong(Params + SIZE_WORD); cmdline = STMemory_ReadLong(Params + SIZE_WORD + SIZE_LONG); env_string = STMemory_ReadLong(Params + SIZE_WORD + SIZE_LONG + SIZE_LONG); if (LOG_TRACE_LEVEL(TRACE_OS_GEMDOS|TRACE_OS_BASE)) { if (mode == 0 || mode == 3) { int cmdlen; char *str; const char *name, *cmd; name = STMemory_GetStringPointer(prgname); if (!name) { LOG_TRACE_PRINT("GEMDOS 0x4B bad Pexec(%i, 0x%X, ...) at PC 0x%X\n", mode, prgname, CallingPC); return false; } cmd = (const char *)STMemory_STAddrToPointer(cmdline); cmdlen = *cmd++; str = Str_Alloc(cmdlen); memcpy(str, cmd, cmdlen); str[cmdlen] = '\0'; LOG_TRACE_PRINT("GEMDOS 0x4B Pexec(%i, \"%s\", [%d]\"%s\", 0x%x) at PC 0x%X\n", mode, name, cmdlen, str, env_string, CallingPC); free(str); } else { LOG_TRACE_PRINT("GEMDOS 0x4B Pexec(%i, 0x%x, 0x%x, 0x%x) at PC 0x%X\n", mode, prgname, cmdline, env_string, CallingPC); } } /* We only have to intercept the "load" modes */ if (mode != 0 && mode != 3) return false; pszFileName = STMemory_GetStringPointer(prgname); if (!pszFileName) return false; Drive = GemDOS_FileName2HardDriveID(pszFileName); /* Skip if it is not using our emulated drive */ if (!ISHARDDRIVE(Drive)) return false; GemDOS_CreateHardDriveFileName(Drive, pszFileName, sFileName, sizeof(sFileName)); fh = fopen(sFileName, "rb"); if (!fh) { Regs[REG_D0] = GEMDOS_EFILNF; return true; } len = fread(prgh, 1, sizeof(prgh), fh); fclose(fh); if (len != sizeof(prgh) || prgh[0] != 0x60 || prgh[1] != 0x1a || prgh[2] & 0x80 || prgh[6] & 0x80 || prgh[10] & 0x80) { Regs[REG_D0] = GEMDOS_EPLFMT; return true; } Symbols_ChangeCurrentProgram(sFileName); /* Prepare stack to run "create basepage": */ Regs[REG_A7] -= 16; M68000_Flush_Data_Cache(Regs[REG_A7], 2*SIZE_WORD+3*SIZE_LONG); STMemory_WriteWord(Regs[REG_A7], 0x4b); /* Pexec number */ STMemory_WriteWord(Regs[REG_A7] + 2, TosVersion >= 0x200 ? 7 : 5); STMemory_WriteLong(Regs[REG_A7] + 4, prgh[22] << 24 | prgh[23] << 16 | prgh[24] << 8 | prgh[25]); STMemory_WriteLong(Regs[REG_A7] + 8, cmdline); STMemory_WriteLong(Regs[REG_A7] + 12, env_string); nSavedPexecParams = Params; return -1; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Search Next * Call 0x4F */ static bool GemDOS_SNext(bool trace) { struct dirent **temp; int ret; DTA *pDTA; uint32_t DTA_Gemdos; uint16_t Index; /* real OS call, not part of SFirst()? */ if (trace) LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4F Fsnext() at PC 0x%X\n" , CallingPC); /* Refresh pDTA pointer (from the current basepage) */ DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd) + BASEPAGE_OFFSET_DTA); if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "GEMDOS Fsnext() failed due to invalid DTA address 0x%x\n", DTA_Gemdos); Regs[REG_D0] = GEMDOS_EINTRN; /* "internal error */ return true; } pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos); /* Was DTA ours or TOS? */ if (do_get_mem_long(pDTA->magic) != DTA_MAGIC_NUMBER) { /* redirect to TOS */ return false; } /* Find index into our list of structures */ Index = do_get_mem_word(pDTA->index); if (Index >= DTACount || !InternalDTAs[Index].bUsed) { /* Invalid handle, TOS returns ENMFIL * (if Fsetdta() has been used by any process) */ Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Invalid DTA\n"); Regs[REG_D0] = GEMDOS_ENMFIL; return true; } if (IS_VOLUME_LABEL(InternalDTAs[Index].dta_attrib)) { /* Volume label was given already in Sfirst() */ Regs[REG_D0] = GEMDOS_ENMFIL; return true; } temp = InternalDTAs[Index].found; do { if (InternalDTAs[Index].centry >= InternalDTAs[Index].nentries) { /* older TOS versions zero file name if there are no (further) matches */ if (TosVersion < 0x0400) { M68000_Flush_Data_Cache(DTA_Gemdos+offsetof(DTA,dta_name), 1); pDTA->dta_name[0] = 0; } Regs[REG_D0] = GEMDOS_ENMFIL; /* No more files */ return true; } ret = PopulateDTA(&InternalDTAs[Index], temp[InternalDTAs[Index].centry++], pDTA, DTA_Gemdos); } while (ret == DTA_SKIP); if (ret == DTA_ERR) { Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Error setting DTA\n"); Regs[REG_D0] = GEMDOS_EINTRN; return true; } Regs[REG_D0] = GEMDOS_EOK; return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Find first file * Call 0x4E */ static bool GemDOS_SFirst(uint32_t Params) { /* TODO: host filenames might not fit into this */ char szActualFileName[MAX_GEMDOS_PATH]; char *pszFileName; const char *dirmask; struct dirent **files; int Drive; DIR *fsdir; int i, j, count; DTA *pDTA; uint32_t DTA_Gemdos; uint16_t useidx; uint16_t attrib; attrib = STMemory_ReadWord(Params+SIZE_LONG); pszFileName = STMemory_GetStringPointer(STMemory_ReadLong(Params)); if (!pszFileName) { LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4E bad Fsfirst(0x%X, 0x%x) at PC 0x%X\n", STMemory_ReadLong(Params), attrib, CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4E Fsfirst(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, attrib, CallingPC); Drive = GemDOS_FileName2HardDriveID(pszFileName); if (!ISHARDDRIVE(Drive)) { /* redirect to TOS */ return false; } /* Convert to hard drive filename */ GemDOS_CreateHardDriveFileName(Drive, pszFileName, szActualFileName, sizeof(szActualFileName)); /* Refresh pDTA pointer (from the current basepage) */ DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd) + BASEPAGE_OFFSET_DTA); if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "GEMDOS Fsfirst() failed due to invalid DTA address 0x%x\n", DTA_Gemdos); Regs[REG_D0] = GEMDOS_EINTRN; /* "internal error */ return true; } /* Atari memory will be modified (below) directly with * do_mem_* + strcpy() -> flush the data cache */ M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA)); pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos); /* re-use earlier Hatari DTA? */ if (do_get_mem_long(pDTA->magic) == DTA_MAGIC_NUMBER) { useidx = do_get_mem_word(pDTA->index); if (useidx >= DTACount || InternalDTAs[useidx].addr != DTA_Gemdos) useidx = DTAIndex; } else { /* set our magic num on new Hatari DTA */ do_put_mem_long(pDTA->magic, DTA_MAGIC_NUMBER); useidx = DTAIndex; } /* Populate DTA, set index for our use */ do_put_mem_word(pDTA->index, useidx); if (InternalDTAs[useidx].bUsed == true) { ClearInternalDTA(useidx); } InternalDTAs[useidx].bUsed = true; InternalDTAs[useidx].addr = DTA_Gemdos; InternalDTAs[useidx].dta_attrib = attrib; /* volume labels are supported only when no other * file types are specified in same query */ if (IS_VOLUME_LABEL(attrib)) { /* Volume name */ strcpy(pDTA->dta_name,"EMULATED.001"); pDTA->dta_name[11] = '0' + Drive; pDTA->dta_attrib = GEMDOS_FILE_ATTRIB_VOLUME_LABEL; Regs[REG_D0] = GEMDOS_EOK; /* Got volume */ return true; } /* open directory * TODO: host path may not fit into InternalDTA */ fsfirst_dirname(szActualFileName, InternalDTAs[useidx].path); fsdir = opendir(InternalDTAs[useidx].path); if (fsdir == NULL) { Regs[REG_D0] = GEMDOS_EPTHNF; /* Path not found */ return true; } /* close directory */ closedir(fsdir); count = scandir(InternalDTAs[useidx].path, &files, 0, alphasort); /* File (directory actually) not found */ if (count < 0) { Regs[REG_D0] = GEMDOS_EFILNF; return true; } InternalDTAs[useidx].centry = 0; /* current entry is 0 */ dirmask = File_Basename(szActualFileName);/* directory mask part */ InternalDTAs[useidx].found = files; /* get files */ /* count & copy the entries that match our mask and discard the rest */ j = 0; for (i=0; i < count; i++) { char *d_name = files[i]->d_name; Str_DecomposedToPrecomposedUtf8(d_name, d_name); /* for OSX */ if (fsfirst_match(dirmask, d_name)) { InternalDTAs[useidx].found[j] = files[i]; j++; } else { free(files[i]); files[i] = NULL; } } InternalDTAs[useidx].nentries = j; /* set number of legal entries */ /* No files of that match, return error code */ if (j==0) { free(files); InternalDTAs[useidx].found = NULL; Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */ return true; } /* Scan for first file (SNext uses no parameters) */ GemDOS_SNext(false); /* increment DTA buffer index unless earlier one was reused */ if (useidx != DTAIndex) return true; if (++DTAIndex >= DTACount) { if (DTACount < DTA_CACHE_MAX) { INTERNAL_DTA *pNewIntDTAs; /* increase DTA cache size */ pNewIntDTAs = realloc(InternalDTAs, (DTACount + DTA_CACHE_INC) * sizeof(*InternalDTAs)); if (pNewIntDTAs) { InternalDTAs = pNewIntDTAs; memset(InternalDTAs + DTACount, 0, DTA_CACHE_INC * sizeof(*InternalDTAs)); DTACount += DTA_CACHE_INC; } else { Log_Printf(LOG_WARN, "Failed to alloc GEMDOS HD DTA entries, wrapping DTA index\n"); DTAIndex = 0; } } else { Log_Printf(LOG_WARN, "Too many (%d) active GEMDOS HD DTA entries, wrapping DTA index\n", DTAIndex); DTAIndex = 0; } } return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS Rename * Call 0x56 */ static bool GemDOS_Rename(uint32_t Params) { char *pszNewFileName,*pszOldFileName; /* TODO: host filenames might not fit into this */ char szNewActualFileName[MAX_GEMDOS_PATH]; char szOldActualFileName[MAX_GEMDOS_PATH]; int NewDrive, OldDrive; uint32_t nOldStrAddr = STMemory_ReadLong(Params + SIZE_WORD); uint32_t nNewStrAddr = STMemory_ReadLong(Params + SIZE_WORD + SIZE_LONG); /* Read details from stack, skip first (dummy) arg */ pszOldFileName = STMemory_GetStringPointer(nOldStrAddr); pszNewFileName = STMemory_GetStringPointer(nNewStrAddr); if (!pszOldFileName || !pszOldFileName[0] || !pszNewFileName || !pszNewFileName[0]) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x56 bad Frename(0x%X, 0x%X) at PC 0x%X\n", nOldStrAddr, nNewStrAddr, CallingPC); return false; } LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x56 Frename(\"%s\", \"%s\") at PC 0x%X\n", pszOldFileName, pszNewFileName, CallingPC); NewDrive = GemDOS_FileName2HardDriveID(pszNewFileName); OldDrive = GemDOS_FileName2HardDriveID(pszOldFileName); if (!(ISHARDDRIVE(NewDrive) && ISHARDDRIVE(OldDrive))) { /* redirect to TOS */ return false; } /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Frename(\"%s\", \"%s\")\n", pszOldFileName, pszNewFileName); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } /* And convert to hard drive filenames */ GemDOS_CreateHardDriveFileName(NewDrive, pszNewFileName, szNewActualFileName, sizeof(szNewActualFileName)); GemDOS_CreateHardDriveFileName(OldDrive, pszOldFileName, szOldActualFileName, sizeof(szOldActualFileName)); /* TOS allows renaming only when target does not exist */ if (access(szOldActualFileName, F_OK) == 0 && access(szNewActualFileName, F_OK) == 0) Regs[REG_D0] = GEMDOS_EACCDN; else if (rename(szOldActualFileName, szNewActualFileName) == 0) Regs[REG_D0] = GEMDOS_EOK; else Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE); return true; } /*-----------------------------------------------------------------------*/ /** * GEMDOS GSDToF * Call 0x57 */ static bool GemDOS_GSDToF(uint32_t Params) { DATETIME DateTime; uint32_t pBuffer; int Handle,Flag; /* Read details from stack */ pBuffer = STMemory_ReadLong(Params); Handle = STMemory_ReadWord(Params+SIZE_LONG); Flag = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD); LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x57 Fdatime(0x%x, %i, %i) at PC 0x%X\n", pBuffer, Handle, Flag, CallingPC); /* get internal handle */ if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0) { /* No, assume was TOS -> redirect */ return false; } if (Flag == 1) { /* write protected device? */ if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON) { Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdatime(,%d,)\n", Handle); Regs[REG_D0] = GEMDOS_EWRPRO; return true; } DateTime.timeword = STMemory_ReadWord(pBuffer); DateTime.dateword = STMemory_ReadWord(pBuffer+SIZE_WORD); if (GemDOS_SetFileInformation(Handle, &DateTime) == true) Regs[REG_D0] = GEMDOS_EOK; else Regs[REG_D0] = GEMDOS_EACCDN; /* Access denied */ return true; } if (GemDOS_GetFileInformation(Handle, &DateTime) == true) { /* Check that write is requested to valid memory area */ if ( STMemory_CheckAreaType ( pBuffer, 4, ABFLAG_RAM ) ) { M68000_Flush_Data_Cache(pBuffer, 2*SIZE_WORD); STMemory_WriteWord(pBuffer, DateTime.timeword); STMemory_WriteWord(pBuffer+SIZE_WORD, DateTime.dateword); Regs[REG_D0] = GEMDOS_EOK; } else { Log_Printf(LOG_WARN, "GEMDOS Fdatime() failed due to invalid RAM range 0x%x+%i\n", pBuffer, 4); Regs[REG_D0] = GEMDOS_ERANGE; } } else { Regs[REG_D0] = GEMDOS_ERROR; /* Generic error */ } return true; } /*-----------------------------------------------------------------------*/ /** * Do implicit file handle closing/unforcing on program termination */ static void GemDOS_TerminateClose(void) { int i, closed, unforced; uint32_t current = STMemory_ReadLong(act_pd); closed = 0; for (i = 0; i < ARRAY_SIZE(FileHandles); i++) { if (FileHandles[i].Basepage == current) { GemDOS_CloseFileHandle(i); closed++; } } unforced = 0; for (i = 0; i < ARRAY_SIZE(ForcedHandles); i++) { if (ForcedHandles[i].Basepage == current) { GemDOS_UnforceFileHandle(i); unforced++; } } if (!(closed || unforced)) return; Log_Printf(LOG_WARN, "Closing %d & unforcing %d file handle(s) remaining at program 0x%x exit.\n", closed, unforced, current); } /** * GEMDOS Pterm0 * Call 0x00 */ static bool GemDOS_Pterm0(uint32_t Params) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x00 Pterm0() at PC 0x%X\n", CallingPC); GemDOS_TerminateClose(); Symbols_RemoveCurrentProgram(); if (!bUseTos) { Main_SetQuitValue(0); return true; } return false; } /** * GEMDOS Ptermres * Call 0x31 */ static bool GemDOS_Ptermres(uint32_t Params) { LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x31 Ptermres(0x%X, %hd) at PC 0x%X\n", STMemory_ReadLong(Params), (int16_t)STMemory_ReadWord(Params+SIZE_WORD), CallingPC); GemDOS_TerminateClose(); return false; } /** * GEMDOS Pterm * Call 0x4c */ static bool GemDOS_Pterm(uint32_t Params) { uint16_t nExitVal = STMemory_ReadWord(Params); LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x4C Pterm(%hd) at PC 0x%X\n", nExitVal, CallingPC); GemDOS_TerminateClose(); Symbols_RemoveCurrentProgram(); if (!bUseTos) { Main_SetQuitValue(nExitVal); return true; } return false; } /** * GEMDOS Super * Call 0x20 */ static bool GemDOS_Super(uint32_t Params) { uint32_t nParam = STMemory_ReadLong(Params); uint32_t nExcFrameSize, nRetAddr; uint16_t nSR, nVec = 0; LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x20 Super(0x%X) at PC 0x%X\n", nParam, CallingPC); /* This call is normally fully handled by TOS - we only * need to emulate it for TOS-less testing mode */ if (bUseTos) return false; /* Get SR, return address and vector offset from stack frame */ nSR = STMemory_ReadWord(Regs[REG_A7]); nRetAddr = STMemory_ReadLong(Regs[REG_A7] + SIZE_WORD); if (currprefs.cpu_model > 68000) nVec = STMemory_ReadWord(Regs[REG_A7] + SIZE_WORD + SIZE_LONG); if (nParam == 1) /* Query mode? */ { Regs[REG_D0] = (nSR & SR_SUPERMODE) ? -1 : 0; return true; } if (nParam == 0) { nParam = regs.usp; } if (currprefs.cpu_model > 68000) nExcFrameSize = SIZE_WORD + SIZE_LONG + SIZE_WORD; else nExcFrameSize = SIZE_WORD + SIZE_LONG; Regs[REG_D0] = Regs[REG_A7] + nExcFrameSize; Regs[REG_A7] = nParam - nExcFrameSize; nSR ^= SR_SUPERMODE; M68000_Flush_Data_Cache(Regs[REG_A7], SIZE_LONG+2*SIZE_WORD); STMemory_WriteWord(Regs[REG_A7], nSR); STMemory_WriteLong(Regs[REG_A7] + SIZE_WORD, nRetAddr); STMemory_WriteWord(Regs[REG_A7] + SIZE_WORD + SIZE_LONG, nVec); return true; } /** * Map GEMDOS call opcodes to their names * * Mapping is based on TOSHYP information: * http://toshyp.atari.org/en/005013.html */ static const char* GemDOS_Opcode2Name(uint16_t opcode) { static const char* names[] = { "Pterm0", "Cconin", "Cconout", "Cauxin", "Cauxout", "Cprnout", "Crawio", "Crawcin", "Cnecin", "Cconws", "Cconrs", "Cconis", "-", /* 0C */ "-", /* 0D */ "Dsetdrv", "-", /* 0F */ "Cconos", "Cprnos", "Cauxis", "Cauxos", "Maddalt", "Srealloc", /* TOS4 */ "-", /* 16 */ "-", /* 17 */ "-", /* 18 */ "Dgetdrv", "Fsetdta", "-", /* 1B */ "-", /* 1C */ "-", /* 1D */ "-", /* 1E */ "-", /* 1F */ "Super", "-", /* 21 */ "-", /* 22 */ "-", /* 23 */ "-", /* 24 */ "-", /* 25 */ "-", /* 26 */ "-", /* 27 */ "-", /* 28 */ "-", /* 29 */ "Tgetdate", "Tsetdate", "Tgettime", "Tsettime", "-", /* 2E */ "Fgetdta", "Sversion", "Ptermres", "-", /* 32 */ "-", /* 33 */ "-", /* 34 */ "-", /* 35 */ "Dfree", "-", /* 37 */ "-", /* 38 */ "Dcreate", "Ddelete", "Dsetpath", "Fcreate", "Fopen", "Fclose", "Fread", "Fwrite", "Fdelete", "Fseek", "Fattrib", "Mxalloc", "Fdup", "Fforce", "Dgetpath", "Malloc", "Mfree", "Mshrink", "Pexec", "Pterm", "-", /* 4D */ "Fsfirst", "Fsnext", "-", /* 50 */ "-", /* 51 */ "-", /* 52 */ "-", /* 53 */ "-", /* 54 */ "-", /* 55 */ "Frename", "Fdatime", "-", /* 58 */ "-", /* 59 */ "-", /* 5A */ "-", /* 5B */ "Flock", /* 5C */ "-", /* 5D */ "-", /* 5E */ "-", /* 5F */ "Nversion", /* 60 */ "-", /* 61 */ "-", /* 62 */ "-", /* 63 */ "-", /* 64 */ "-", /* 65 */ "-", /* 66 */ "-", /* 67 */ "-", /* 68 */ "-", /* 69 */ "-", /* 6A */ "-", /* 6B */ "-", /* 6C */ "-", /* 6D */ "-", /* 6E */ "-", /* 6F */ "-", /* 70 */ "-", /* 71 */ "-", /* 72 */ "-", /* 73 */ "-", /* 74 */ "-", /* 75 */ "-", /* 76 */ "-", /* 77 */ "-", /* 78 */ "-", /* 79 */ "-", /* 7A */ "-", /* 7B */ "-", /* 7C */ "-", /* 7D */ "-", /* 7E */ "-", /* 7F */ "-", /* 80 */ "-", /* 81 */ "-", /* 82 */ "-", /* 83 */ "-", /* 84 */ "-", /* 85 */ "-", /* 86 */ "-", /* 87 */ "-", /* 88 */ "-", /* 89 */ "-", /* 8A */ "-", /* 8B */ "-", /* 8C */ "-", /* 8D */ "-", /* 8E */ "-", /* 8F */ "-", /* 90 */ "-", /* 91 */ "-", /* 92 */ "-", /* 93 */ "-", /* 94 */ "-", /* 95 */ "-", /* 96 */ "-", /* 97 */ "-", /* 98 */ "-", /* 99 */ "-", /* 9A */ "-", /* 9B */ "-", /* 9C */ "-", /* 9D */ "-", /* 9E */ "-", /* 9F */ "-", /* A0 */ "-", /* A1 */ "-", /* A2 */ "-", /* A3 */ "-", /* A4 */ "-", /* A5 */ "-", /* A6 */ "-", /* A7 */ "-", /* A8 */ "-", /* A9 */ "-", /* AA */ "-", /* AB */ "-", /* AC */ "-", /* AD */ "-", /* AE */ "-", /* AF */ "-", /* B0 */ "-", /* B1 */ "-", /* B2 */ "-", /* B3 */ "-", /* B4 */ "-", /* B5 */ "-", /* B6 */ "-", /* B7 */ "-", /* B8 */ "-", /* B9 */ "-", /* BA */ "-", /* BB */ "-", /* BC */ "-", /* BD */ "-", /* BE */ "-", /* BF */ "-", /* C0 */ "-", /* C1 */ "-", /* C2 */ "-", /* C3 */ "-", /* C4 */ "-", /* C5 */ "-", /* C6 */ "-", /* C7 */ "-", /* C8 */ "-", /* C9 */ "-", /* CA */ "-", /* CB */ "-", /* CC */ "-", /* CD */ "-", /* CE */ "-", /* CF */ "-", /* D0 */ "-", /* D1 */ "-", /* D2 */ "-", /* D3 */ "-", /* D4 */ "-", /* D5 */ "-", /* D6 */ "-", /* D7 */ "-", /* D8 */ "-", /* D9 */ "-", /* DA */ "-", /* DB */ "-", /* DC */ "-", /* DD */ "-", /* DE */ "-", /* DF */ "-", /* E0 */ "-", /* E1 */ "-", /* E2 */ "-", /* E3 */ "-", /* E4 */ "-", /* E5 */ "-", /* E6 */ "-", /* E7 */ "-", /* E8 */ "-", /* E9 */ "-", /* EA */ "-", /* EB */ "-", /* EC */ "-", /* ED */ "-", /* EE */ "-", /* EF */ "-", /* F0 */ "-", /* F1 */ "-", /* F2 */ "-", /* F3 */ "-", /* F4 */ "-", /* F5 */ "-", /* F6 */ "-", /* F7 */ "-", /* F8 */ "-", /* F9 */ "-", /* FA */ "-", /* FB */ "-", /* FC */ "-", /* FD */ "-", /* FE */ "Syield", /* FF */ "Fpipe", /* 100 */ "Ffchown", /* 101 */ "Ffchmod", /* 102 */ "Fsync", /* 103 */ "Fcntl", /* 104 */ "Finstat", /* 105 */ "Foutstat", /* 106 */ "Fgetchar", /* 107 */ "Fputchar", /* 108 */ "Pwait", /* 109 */ "Pnice", /* 10A */ "Pgetpid", /* 10B */ "Pgetppid", /* 10C */ "Pgetpgrp", /* 10D */ "Psetpgrp", /* 10E */ "Pgetuid", /* 10F */ "Psetuid", /* 110 */ "Pkill", /* 111 */ "Psignal", /* 112 */ "Pvfork", /* 113 */ "Pgetgid", /* 114 */ "Psetgid", /* 115 */ "Psigblock", /* 116 */ "Psigsetmask", /* 117 */ "Pusrval", /* 118 */ "Pdomain", /* 119 */ "Psigreturn", /* 11A */ "Pfork", /* 11B */ "Pwait3", /* 11C */ "Fselect", /* 11D */ "Prusage", /* 11E */ "Psetlimit", /* 11F */ "Talarm", /* 120 */ "Pause", /* 121 */ "Sysconf", /* 122 */ "Psigpending", /* 123 */ "Dpathconf", /* 124 */ "Pmsg", /* 125 */ "Fmidipipe", /* 126 */ "Prenice", /* 127 */ "Dopendir", /* 128 */ "Dreaddir", /* 129 */ "Drewinddir", /* 12A */ "Dclosedir", /* 12B */ "Fxattr", /* 12C */ "Flink", /* 12D */ "Fsymlink", /* 12E */ "Freadlink", /* 12F */ "Dcntl", /* 130 */ "Fchown", /* 131 */ "Fchmod", /* 132 */ "Pumask", /* 133 */ "Psemaphore", /* 134 */ "Dlock", /* 135 */ "Psigpause", /* 136 */ "Psigaction", /* 137 */ "Pgeteuid", /* 138 */ "Pgetegid", /* 139 */ "Pwaitpid", /* 13A */ "Dgetcwd", /* 13B */ "Salert", /* 13C */ "Tmalarm", /* 13D */ "Psigintr", /* 13E */ "Suptime", /* 13F */ "Ptrace", /* 140 */ "Mvalidate", /* 141 */ "Dxreaddir", /* 142 */ "Pseteuid", /* 143 */ "Psetegid", /* 144 */ "Pgetauid", /* 145 */ "Psetauid", /* 146 */ "Pgetgroups", /* 147 */ "Psetgroups", /* 148 */ "Tsetitimer", /* 149 */ "Dchroot", /* 14A; was Scookie */ "Fstat64", /* 14B */ "Fseek64", /* 14C */ "Dsetkey", /* 14D */ "Psetreuid", /* 14E */ "Psetregid", /* 14F */ "Sync", /* 150 */ "Shutdown", /* 151 */ "Dreadlabel", /* 152 */ "Dwritelabel", /* 153 */ "Ssystem", /* 154 */ "Tgettimeofday", /* 155 */ "Tsettimeofday", /* 156 */ "Tadjtime", /* 157 */ "Pgetpriority", /* 158 */ "Psetpriority", /* 159 */ "Fpoll", /* 15A */ "Fwritev", /* 15B */ "Freadv", /* 15C */ "Ffstat64", /* 15D */ "Psysctl", /* 15E */ "Semulation", /* 15F */ "Fsocket", /* 160 */ "Fsocketpair", /* 161 */ "Faccept", /* 162 */ "Fconnect", /* 163 */ "Fbind", /* 164 */ "Flisten", /* 165 */ "Frecvmsg", /* 166 */ "Fsendmsg", /* 167 */ "Frecvfrom", /* 168 */ "Fsendto", /* 169 */ "Fsetsockopt", /* 16A */ "Fgetsockopt", /* 16B */ "Fgetpeername", /* 16C */ "Fgetsockname", /* 16D */ "Fshutdown", /* 16E */ "-", /* 16F */ "Pshmget", /* 170 */ "Pshmctl", /* 171 */ "Pshmat", /* 172 */ "Pshmdt", /* 173 */ "Psemget", /* 174 */ "Psemctl", /* 175 */ "Psemop", /* 176 */ "Psemconfig", /* 177 */ "Pmsgget", /* 178 */ "Pmsgctl", /* 179 */ "Pmsgsnd", /* 17A */ "Pmsgrcv", /* 17B */ "-", /* 17C */ "Maccess", /* 17D */ "-", /* 17E */ "-", /* 17F */ "Fchown16", /* 180 */ "Fchdir", /* 181 */ "Ffdopendir", /* 182 */ "Fdirfd" /* 183 */ }; if (opcode < ARRAY_SIZE(names)) return names[opcode]; return "-"; } /** * If bShowOpcodes is true, show GEMDOS call opcode/function name table, * otherwise GEMDOS HDD emulation information. */ void GemDOS_Info(FILE *fp, uint32_t bShowOpcodes) { int i, used; if (bShowOpcodes) { uint16_t opcode; /* list just normal TOS GEMDOS calls * * MiNT ones would need separate table as their names * are much longer and 0x60 - 0xFE range is unused. */ for (opcode = 0; opcode < 0x5A; ) { fprintf(fp, "%02x %-9s", opcode, GemDOS_Opcode2Name(opcode)); if (++opcode % 6 == 0) fputs("\n", fp); } return; } if (!GEMDOS_EMU_ON) { fputs("GEMDOS HDD emulation isn't enabled!\n", fp); return; } /* GEMDOS vector (can be overwritten e.g. MiNT) */ fprintf(fp, "Current GEMDOS handler: (0x84) = 0x%x\n", STMemory_ReadLong(0x0084)); fprintf(fp, "Connected drives mask: 0x%x\n\n", ConnectedDriveMask); fputs("GEMDOS HDD emulation drives:\n", fp); for(i = 0; i < MAX_HARDDRIVES; i++) { if (!emudrives[i]) continue; fprintf(fp, "- %c: %s\n curpath: %s\n", 'A' + emudrives[i]->drive_number, emudrives[i]->hd_emulation_dir, emudrives[i]->fs_currpath); } fputs("\nInternal Fsfirst() DTAs:\n", fp); for(used = i = 0; i < DTACount; i++) { int j, centry, entries; if (!InternalDTAs[i].bUsed) continue; fprintf(fp, "+ %d: %s\n", i, InternalDTAs[i].path); centry = InternalDTAs[i].centry; entries = InternalDTAs[i].nentries; for (j = 0; j < entries; j++) { fprintf(fp, " - %d: %s%s\n", j, InternalDTAs[i].found[j]->d_name, j == centry ? " *" : ""); } fprintf(fp, " Fsnext entry = %d.\n", centry); used++; } if (!used) fputs("- None in use.\n", fp); fputs("\nOpen GEMDOS HDD file handles:\n", fp); for (used = i = 0; i < ARRAY_SIZE(FileHandles); i++) { if (!FileHandles[i].bUsed) continue; fprintf(fp, "- %d (0x%x): %s (%s)\n", i + BASE_FILEHANDLE, FileHandles[i].Basepage, FileHandles[i].szActualName, FileHandles[i].bReadOnly ? "ro" : "rw"); used++; } if (!used) fputs("- None.\n", fp); fputs("\nForced GEMDOS HDD file handles:\n", fp); for (used = i = 0; i < ARRAY_SIZE(ForcedHandles); i++) { if (ForcedHandles[i].Handle == UNFORCED_HANDLE) continue; fprintf(fp, "- %d -> %d (0x%x)\n", i, ForcedHandles[i].Handle + BASE_FILEHANDLE, ForcedHandles[i].Basepage); used++; } if (!used) fputs("- None.\n", fp); fputs("\n", fp); Symbols_ShowCurrentProgramPath(fp); } /** * Show given DTA info * (works also without GEMDOS HD emu) */ void GemDOS_InfoDTA(FILE *fp, uint32_t dta_addr) { DTA *dta; uint32_t magic; char name[TOS_NAMELEN+1]; fprintf(fp, "DTA (0x%x):\n", dta_addr); if (act_pd) { uint32_t basepage = STMemory_ReadLong(act_pd); uint32_t dta_curr = STMemory_ReadLong(basepage + BASEPAGE_OFFSET_DTA); if (dta_addr != dta_curr) { fprintf(fp, "- NOTE: given DTA (0x%x) is not current program one (0x%x)\n", dta_addr, dta_curr); } if (dta_addr >= basepage && dta_addr + sizeof(DTA) < basepage + BASEPAGE_SIZE) { const char *msg = (dta_addr == basepage + 0x80) ? ", replacing command line" : ""; fprintf(fp, "- NOTE: DTA (0x%x) is within current program basepage (0x%x)%s!\n", dta_addr, basepage, msg); } } if (!STMemory_CheckAreaType(dta_addr, sizeof(DTA), ABFLAG_RAM)) { fprintf(fp, "- ERROR: invalid memory address!\n"); return; } dta = (DTA *)STMemory_STAddrToPointer(dta_addr); memcpy(name, dta->dta_name, TOS_NAMELEN); name[TOS_NAMELEN] = '\0'; magic = do_get_mem_long(dta->magic); fprintf(fp, "- magic: 0x%08x (GEMDOS HD = 0x%08x)\n", magic, DTA_MAGIC_NUMBER); if (magic == DTA_MAGIC_NUMBER) fprintf(fp, "- index: 0x%04x\n", do_get_mem_word(dta->index)); fprintf(fp, "- attr: 0x%x\n", dta->dta_attrib); fprintf(fp, "- time: 0x%04x\n", do_get_mem_word(dta->dta_time)); fprintf(fp, "- date: 0x%04x\n", do_get_mem_word(dta->dta_date)); fprintf(fp, "- size: %d\n", do_get_mem_long(dta->dta_size)); fprintf(fp, "- name: '%s'\n", name); } /** * Run GEMDos call, and re-direct if need to. Used to handle hard disk emulation etc... * This sets the condition codes (in SR), which are used in the 'cart_asm.s' program to * decide if we need to run old GEM vector, or PExec or nothing. * * This method keeps the stack and other states consistent with the original ST * which is very important for the PExec call and maximum compatibility through-out */ int GemDOS_Trap(void) { uint16_t GemDOSCall, CallingSReg; uint32_t Params; int Finished = false; uint16_t sr = M68000_GetSR(); /* Read SReg from stack to see if parameters are on User or Super stack */ CallingSReg = STMemory_ReadWord(Regs[REG_A7]); CallingPC = STMemory_ReadLong(Regs[REG_A7] + SIZE_WORD); if (!(CallingSReg & SR_SUPERMODE)) /* Calling from user mode */ Params = regs.usp; else { Params = Regs[REG_A7] + SIZE_WORD + SIZE_LONG; /* skip SR & PC pushed to super stack */ if (currprefs.cpu_model > 68000) Params += SIZE_WORD; /* Skip extra word if CPU is >=68010 */ } /* Find pointer to call parameters */ GemDOSCall = STMemory_ReadWord(Params); Params += SIZE_WORD; sr &= ~SR_OVERFLOW; /* Intercept call */ switch(GemDOSCall) { case 0x00: Finished = GemDOS_Pterm0(Params); break; case 0x09: Finished = GemDOS_Cconws(Params); break; case 0x0e: Finished = GemDOS_SetDrv(Params); break; case 0x20: Finished = GemDOS_Super(Params); break; case 0x31: Finished = GemDOS_Ptermres(Params); break; case 0x36: Finished = GemDOS_DFree(Params); break; case 0x39: Finished = GemDOS_MkDir(Params); break; case 0x3a: Finished = GemDOS_RmDir(Params); break; case 0x3b: /* Dsetpath */ Finished = GemDOS_ChDir(Params); break; case 0x3c: Finished = GemDOS_Create(Params); break; case 0x3d: Finished = GemDOS_Open(Params); break; case 0x3e: Finished = GemDOS_Close(Params); break; case 0x3f: Finished = GemDOS_Read(Params); break; case 0x40: Finished = GemDOS_Write(Params); break; case 0x41: Finished = GemDOS_FDelete(Params); break; case 0x42: Finished = GemDOS_LSeek(Params); break; case 0x43: Finished = GemDOS_Fattrib(Params); break; case 0x46: Finished = GemDOS_Force(Params); break; case 0x47: /* Dgetpath */ Finished = GemDOS_GetDir(Params); break; case 0x4b: Finished = GemDOS_Pexec(Params); if (Finished == -1) { sr |= SR_OVERFLOW; Finished = true; } break; case 0x4c: Finished = GemDOS_Pterm(Params); break; case 0x4e: Finished = GemDOS_SFirst(Params); break; case 0x4f: Finished = GemDOS_SNext(true); break; case 0x56: Finished = GemDOS_Rename(Params); break; case 0x57: Finished = GemDOS_GSDToF(Params); break; /* print args for other calls */ case 0x01: /* Conin */ case 0x03: /* Cauxin */ case 0x12: /* Cauxis */ case 0x13: /* Cauxos */ case 0x0B: /* Conis */ case 0x10: /* Conos */ case 0x08: /* Cnecin */ case 0x11: /* Cprnos */ case 0x07: /* Crawcin */ case 0x19: /* Dgetdrv */ case 0x2F: /* Fgetdta */ case 0x30: /* Sversion */ case 0x2A: /* Tgetdate */ case 0x2C: /* Tgettime */ /* commands with no args */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s() at PC 0x%X\n", GemDOSCall, GemDOS_Opcode2Name(GemDOSCall), CallingPC); break; case 0x02: /* Cconout */ case 0x04: /* Cauxout */ case 0x05: /* Cprnout */ case 0x06: /* Crawio */ case 0x2b: /* Tsetdate */ case 0x2d: /* Tsettime */ case 0x45: /* Fdup */ /* commands taking single word */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s(0x%hX) at PC 0x%X\n", GemDOSCall, GemDOS_Opcode2Name(GemDOSCall), STMemory_ReadWord(Params), CallingPC); break; case 0x0A: /* Cconrs */ case 0x1A: /* Fsetdta */ case 0x48: /* Malloc */ case 0x49: /* Mfree */ /* commands taking long/pointer */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s(0x%X) at PC 0x%X\n", GemDOSCall, GemDOS_Opcode2Name(GemDOSCall), STMemory_ReadLong(Params), CallingPC); break; case 0x44: /* Mxalloc */ /* commands taking long/pointer + word */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x44 Mxalloc(0x%X, 0x%hX) at PC 0x%X\n", STMemory_ReadLong(Params), STMemory_ReadWord(Params+SIZE_LONG), CallingPC); break; case 0x14: /* Maddalt */ /* commands taking 2 longs/pointers */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x14 Maddalt(0x%X, 0x%X) at PC 0x%X\n", STMemory_ReadLong(Params), STMemory_ReadLong(Params+SIZE_LONG), CallingPC); break; case 0x4A: /* Mshrink */ /* Mshrink's two pointers are prefixed by reserved zero word: * http://toshyp.atari.org/en/00500c.html#Bindings_20for_20Mshrink */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4A Mshrink(0x%X, 0x%X) at PC 0x%X\n", STMemory_ReadLong(Params+SIZE_WORD), STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG), CallingPC); if (!bUseTos) Finished = true; break; default: /* rest of commands */ LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX (%s) at PC 0x%X\n", GemDOSCall, GemDOS_Opcode2Name(GemDOSCall), CallingPC); } if (Finished) { sr |= SR_ZERO; /* visualize GemDOS emu HD access? */ switch (GemDOSCall) { case 0x36: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: case 0x40: case 0x41: case 0x42: case 0x43: case 0x47: case 0x4e: case 0x4f: case 0x56: Statusbar_EnableHDLed( LED_STATE_ON ); } } else { if (!bUseTos) { if (GemDOSCall >= 0x58) /* Ignore optional calls */ { Regs[REG_D0] = GEMDOS_EINVFN; M68000_SetSR(sr | SR_ZERO); return true; } Log_Printf(LOG_FATAL, "GEMDOS 0x%02hX %s at PC 0x%X unsupported in test mode\n", GemDOSCall, GemDOS_Opcode2Name(GemDOSCall), CallingPC); Main_SetQuitValue(1); } sr &= ~SR_ZERO; } M68000_SetSR(sr); return Finished; } /** * GemDOS_Boot * Sets up stuff for our gemdos handler */ void GemDOS_Boot(void) { if (bInitGemDOS) GemDOS_Reset(); bInitGemDOS = true; LOG_TRACE(TRACE_OS_GEMDOS, "Gemdos_Boot(GEMDOS_EMU_ON=%d) at PC 0x%X\n", GEMDOS_EMU_ON, M68000_GetPC() ); /* install our gemdos handler, if user has enabled either * GEMDOS HD, autostarting or GEMDOS tracing */ if (!GEMDOS_EMU_ON && !INF_Overriding(AUTOSTART_INTERCEPT) && !LOG_TRACE_LEVEL(TRACE_OS_GEMDOS|TRACE_OS_BASE)) return; /* Get the address of the p_run variable that points to the actual basepage */ if (TosVersion == 0x100) { /* We have to use fix addresses on TOS 1.00 :-( */ if ((STMemory_ReadWord(TosAddress+28)>>1) == 4) act_pd = 0x873c; /* Spanish TOS is different from others! */ else act_pd = 0x602c; } else { uint32_t osAddress = STMemory_ReadLong(0x4f2); act_pd = STMemory_ReadLong(osAddress + 0x28); } /* Save old GEMDOS handler address */ M68000_Flush_Instr_Cache(CART_OLDGEMDOS, SIZE_LONG); STMemory_WriteLong(CART_OLDGEMDOS, STMemory_ReadLong(0x0084)); /* Setup new GEMDOS handler, see "cart_asm.s" */ M68000_Flush_Instr_Cache(0x0084, SIZE_LONG); STMemory_WriteLong(0x0084, CART_GEMDOS); } /** * Load and relocate a PRG file into the memory of the emulated machine. */ int GemDOS_LoadAndReloc(const char *psPrgName, uint32_t baseaddr, bool bFullBpSetup) { long nFileSize, nRelTabIdx; uint8_t *prg; uint32_t nTextLen, nDataLen, nBssLen, nSymLen; uint32_t nRelOff, nCurrAddr; uint32_t memtop; prg = File_ReadAsIs(psPrgName, &nFileSize); if (!prg) { Log_Printf(LOG_ERROR, "Failed to load '%s'.\n", psPrgName); return GEMDOS_EFILNF; } /* Check program header size and magic */ if (nFileSize < 30 || prg[0] != 0x60 || prg[1] != 0x1a) { free(prg); Log_Printf(LOG_ERROR, "The file '%s' is not a valid PRG.\n", psPrgName); return GEMDOS_EPLFMT; } nTextLen = (prg[2] << 24) | (prg[3] << 16) | (prg[4] << 8) | prg[5]; nDataLen = (prg[6] << 24) | (prg[7] << 16) | (prg[8] << 8) | prg[9]; nBssLen = (prg[10] << 24) | (prg[11] << 16) | (prg[12] << 8) | prg[13]; nSymLen = (prg[14] << 24) | (prg[15] << 16) | (prg[16] << 8) | prg[17]; if (baseaddr < 0x1000000) memtop = STMemory_ReadLong(0x436); else memtop = STMemory_ReadLong(0x5a4); if (baseaddr + 0x100 + nTextLen + nDataLen + nBssLen > memtop) { free(prg); Log_Printf(LOG_ERROR, "Program too large: '%s'.\n", psPrgName); return GEMDOS_ENSMEM; } /* relocation info is also written to this area */ M68000_Flush_All_Caches(baseaddr, 0x100 + nTextLen + nDataLen + nBssLen); if (!STMemory_SafeCopy(baseaddr + 0x100, prg + 28, nTextLen + nDataLen, psPrgName)) { free(prg); return GEMDOS_EIMBA; } /* Clear BSS */ if (!STMemory_SafeClear(baseaddr + 0x100 + nTextLen + nDataLen, nBssLen)) { free(prg); Log_Printf(LOG_ERROR, "Failed to clear BSS for '%s'.\n", psPrgName); return GEMDOS_EIMBA; } /* Set up basepage */ STMemory_WriteLong(baseaddr + 8, baseaddr + 0x100); /* p_tbase */ STMemory_WriteLong(baseaddr + 12, nTextLen); /* p_tlen */ STMemory_WriteLong(baseaddr + 16, baseaddr + 0x100 + nTextLen); /* p_dbase */ STMemory_WriteLong(baseaddr + 20, nDataLen); /* p_dlen */ STMemory_WriteLong(baseaddr + 24, baseaddr + 0x100 + nTextLen + nDataLen); /* p_bbase */ STMemory_WriteLong(baseaddr + 28, nBssLen); /* p_blen */ /* In case we run without TOS, set some of the other values as good as possible, too */ if (bFullBpSetup) { STMemory_WriteLong(baseaddr, baseaddr); /* p_lowtpa */ STMemory_WriteLong(baseaddr + 4, memtop); /* p_hitpa */ STMemory_WriteLong(baseaddr + 32, baseaddr + 0x80); /* p_dta */ STMemory_WriteLong(baseaddr + 36, baseaddr); /* p_parent */ STMemory_WriteLong(baseaddr + 40, 0); /* p_reserved */ /* The environment should point to an empty string - use p_reserved for that: */ STMemory_WriteLong(baseaddr + 44, baseaddr + 40); /* p_env */ } /* If FASTLOAD flag is not set, then also clear the heap */ if (!(prg[25] & 1)) { uint32_t nAddrLen; nCurrAddr = baseaddr + 0x100 + nTextLen + nDataLen + nBssLen; nAddrLen = STMemory_ReadLong(baseaddr + 4) - nCurrAddr; M68000_Flush_All_Caches(baseaddr, nAddrLen); if (!STMemory_SafeClear(nCurrAddr, nAddrLen)) { free(prg); Log_Printf(LOG_ERROR, "Failed to clear heap for '%s'.\n", psPrgName); return GEMDOS_EIMBA; } } if (*(uint16_t *)&prg[26] != 0) /* No reloc information available? */ { free(prg); return 0; } nRelTabIdx = 0x1c + nTextLen + nDataLen; if (nRelTabIdx > nFileSize - 3) { free(prg); Log_Printf(LOG_ERROR, "Can not parse relocation table of '%s'.\n", psPrgName); return GEMDOS_EPLFMT; } if (nRelTabIdx + nSymLen <= nFileSize - 3U) { nRelTabIdx += nSymLen; } else { /* Original TOS ignores the error if the symbol table length * is too, big, so just log a warning here instead of failing */ Log_Printf(LOG_WARN, "Symbol table length of '%s' is too big!\n", psPrgName); } nRelOff = (prg[nRelTabIdx] << 24) | (prg[nRelTabIdx + 1] << 16) | (prg[nRelTabIdx + 2] << 8) | prg[nRelTabIdx + 3]; if (nRelOff == 0) { free(prg); return 0; } nCurrAddr = baseaddr + 0x100 + nRelOff; STMemory_WriteLong(nCurrAddr, STMemory_ReadLong(nCurrAddr) + baseaddr + 0x100); nRelTabIdx += 4; while (nRelTabIdx < nFileSize && prg[nRelTabIdx]) { if (prg[nRelTabIdx] == 1) { nRelOff += 254; nRelTabIdx += 1; continue; } nRelOff += prg[nRelTabIdx]; nCurrAddr = baseaddr + 0x100 + nRelOff; STMemory_WriteLong(nCurrAddr, STMemory_ReadLong(nCurrAddr) + baseaddr + 0x100); nRelTabIdx += 1; } free(prg); if (nRelTabIdx >= nFileSize) { Log_Printf(LOG_WARN, "Relocation table of '%s' is not terminated!\n", psPrgName); } return 0; } /** * This function is run after we've told TOS to create a basepage. We * can now load and relocate a program from our emulated HD into the * new TPA. */ void GemDOS_PexecBpCreated(void) { char sFileName[FILENAME_MAX]; char *sStFileName; uint32_t errcode; uint16_t sr = M68000_GetSR(); uint16_t mode; uint32_t prgname; int drive; sr &= ~SR_OVERFLOW; mode = STMemory_ReadWord(nSavedPexecParams); prgname = STMemory_ReadLong(nSavedPexecParams + SIZE_WORD); sStFileName = STMemory_STAddrToPointer(prgname); LOG_TRACE(TRACE_OS_GEMDOS, "Basepage has been created - now loading '%s'\n", sStFileName); drive = GemDOS_FileName2HardDriveID(sStFileName); if (drive >= 2) { GemDOS_CreateHardDriveFileName(drive, sStFileName, sFileName, sizeof(sFileName)); errcode = GemDOS_LoadAndReloc(sFileName, Regs[REG_D0], false); } else { errcode = GEMDOS_EDRIVE; } if (errcode) { Regs[REG_A0] = Regs[REG_D0]; Regs[REG_D0] = errcode; sr &= ~SR_ZERO; } else if (mode == 0) { M68000_Flush_Data_Cache(nSavedPexecParams, 6+4); /* Run another "just-go" Pexec call to start the program */ STMemory_WriteWord(nSavedPexecParams, TosVersion >= 0x104 ? 6 : 4); STMemory_WriteLong(nSavedPexecParams + 6, Regs[REG_D0]); sr |= SR_OVERFLOW; } else { sr |= SR_ZERO; } M68000_SetSR(sr); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/000077500000000000000000000000001504763705000227235ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/AlertHooks.h000066400000000000000000000012241504763705000251460ustar00rootroot00000000000000/* Hatari - AlertHooks.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Hooked alerts, to be used instead of SDL alert windows */ #ifdef ALERT_HOOKS // Replacement for a regular alert (with just an OK button) // Returns TRUE if OK clicked, FALSE otherwise bool HookedAlertNotice(const char* szMessage); // Replacement for a query alert (OK and Cancel buttons) // Returns TRUE if OK clicked, FALSE otherwise bool HookedAlertQuery(const char* szMessage); // Runtime switch to activate/deactivate alert hooks extern bool useAlertHooks; #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/AlertHooks.m000066400000000000000000000040401504763705000251520ustar00rootroot00000000000000/* Hatari - AlertHooks.m This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Hooked alerts, to be used instead of SDL alert windows June 2006, Sébastien Molines - Created */ #import #import "AlertHooks.h" #import "Shared.h" #ifdef ALERT_HOOKS /*-----------------------------------------------------------------------*/ /* Displays a Cocoa alert */ bool HookedAlertNotice(const char* szMessage) { NSString *message ; NSRange cantTOS, firstPv, lastPv ; message = [NSString stringWithCString:szMessage encoding:NSASCIIStringEncoding] ; //NSLog(@"Notice: %@", message ) ; cantTOS = [message rangeOfString:@"Can not load TOS file:"] ; firstPv = [message rangeOfString:@"'"] ; lastPv = [message rangeOfString:@"'" options:NSBackwardsSearch] ; if ((cantTOS.location == NSNotFound) || (firstPv.location==lastPv.location)) // TOS can be found return ([NSApp myAlerte:NSAlertStyleInformational Txt:nil firstB:localize(@"Ok") alternateB:localize(@"Cancel") otherB:nil informativeTxt:message ] == NSAlertFirstButtonReturn //NSAlertDefaultReturn ); else // TOS can be found return ([NSApp myAlerte:NSAlertStyleCritical Txt:nil firstB:localize(@"Ok") alternateB:nil otherB:nil informativeTxt:localize(@"Can not load TOS file:") ] == NSAlertFirstButtonReturn) ; } /*----------------------------------------------------------------------*/ /* Displays a Cocoa alert with a choice (OK and Cancel buttons) */ /*----------------------------------------------------------------------*/ bool HookedAlertQuery(const char* szMessage) { NSString *message ; int ret; message = localize([NSString stringWithCString:szMessage encoding:NSASCIIStringEncoding]) ; ret= [NSApp myAlerte:NSAlertStyleInformational Txt:nil firstB:localize(@"Ok") alternateB:localize(@"Cancel") otherB:nil informativeTxt:message ] ; if(ret==NSAlertFirstButtonReturn) return true; //OK else return false; // otherwise false } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/CreateFloppyController.h000066400000000000000000000007631504763705000275430ustar00rootroot00000000000000/* Hatari - CreateFloppyController.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #import @interface CreateFloppyController : NSObject { IBOutlet NSWindow *window; IBOutlet NSMatrix *sectors; IBOutlet NSMatrix *sides; IBOutlet NSPopUpButton *tracks; } - (void)awakeFromNib; - (IBAction)runModal:(id)sender; - (IBAction)createFloppyImage:(id)sender; @end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/CreateFloppyController.m000066400000000000000000000042621504763705000275460ustar00rootroot00000000000000/* Hatari - CreateFloppyController.m This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Create floppy image window controller implementation file Feb-Mar 2006, Sébastien Molines - Created */ #import "CreateFloppyController.h" #import "Shared.h" #include "main.h" #include "configuration.h" #include "createBlankImage.h" #include "floppy.h" @implementation CreateFloppyController - (IBAction)createFloppyImage:(id)sender { BOOL cRet; int ret, cTracks, cSectors, cSides ; char szPath[FILENAME_MAX] ; NSString *defaultDir ; NSString *newCnf ; // Get the default images directory defaultDir = [NSString stringWithCString:ConfigureParams.DiskImage.szDiskImageDirectory encoding:NSASCIIStringEncoding]; // Run the SavePanel, then check if the user clicked OK newCnf = [NSApp hsavefile:YES defoDir:defaultDir defoFile:nil types:@[allF] ] ; if (newCnf.length != 0) { [newCnf getCString:szPath maxLength:FILENAME_MAX-1 encoding:NSASCIIStringEncoding] ; // Get the tracks, sectors and sides values cTracks = tracks.selectedCell.tag ; cSectors = sectors.selectedCell.tag ; cSides = sides.selectedCell.tag ; // Create the image cRet=CreateBlankImage_CreateFile(szPath, cTracks, cSectors, cSides, NULL); if(cRet==TRUE) { ret = [NSApp myAlerte:NSAlertStyleInformational Txt:nil firstB:localize(@"Ignore") alternateB:@" A: " otherB:@" B: " informativeTxt:@""] ; if (ret == NSAlertFirstButtonReturn) return ; ret = ret == NSAlertSecondButtonReturn ? 0 : 1 ; Floppy_SetDiskFileName(ret, szPath, NULL); Floppy_InsertDiskIntoDrive(ret); } ; } ; } - (void)awakeFromNib { // Fill the "Tracks" dropdown [tracks removeAllItems]; int i; for (i = 40; i <= 85; i++) { [tracks addItemWithTitle:[NSString stringWithFormat:@"%d", i]]; [tracks.lastItem setTag:i]; } // Select the default value of 80 tracks [tracks selectItemAtIndex:[tracks indexOfItemWithTag:80]]; // Equivalent to Tiger-only [tracks selectItemWithTag:80]; } - (IBAction)runModal:(id)sender { ModalWrapper *mw=[[ModalWrapper alloc] init]; [mw runModal:window]; [mw release]; } @end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/Info-Hatari.plist000066400000000000000000000043311504763705000261020ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleDocumentTypes CFBundleTypeExtensions st CFBundleTypeName ST disk image CFBundleTypeRole Editor LSItemContentTypes com.sourceforge.hatari.st CFBundleTypeExtensions msa CFBundleTypeName MSA disk image CFBundleTypeRole Editor LSItemContentTypes com.sourceforge.hatari.msa CFBundleTypeExtensions cfg CFBundleTypeName Config File CFBundleTypeRole Editor LSItemContentTypes com.sourceforge.hatari.cfg LSTypeIsPackage NSPersistentStoreTypeKey Binary CFBundleExecutable Hatari CFBundleIconFile Hatari.icns CFBundleIdentifier org.hatari-emu.Hatari CFBundleInfoDictionaryVersion 6.0 CFBundleName Hatari CFBundlePackageType APPL CFBundleShortVersionString 2.6.1 CFBundleSignature HATA CFBundleVersion 2.6.1 Hatari SDL Cocoa App LSMinimumSystemVersion 10.9.0 NSMainNibFile SDLMain NSPrincipalClass NSApplication NSMicrophoneUsageDescription Hatari needs access to your microphone for sound input. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/PrefsController.h000066400000000000000000000144671504763705000262330ustar00rootroot00000000000000/* Hatari - PrefsController.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #import @interface PrefsController : NSObject { // Preferences window IBOutlet NSWindow *window ; IBOutlet NSView *partage ; IBOutlet NSView *hartage ; // Disks IBOutlet NSTextField *floppyImageA; IBOutlet NSButton *enableDriveA; IBOutlet NSButton *driveA_NumberOfHeads; IBOutlet NSTextField *floppyImageB; IBOutlet NSButton *enableDriveB; IBOutlet NSButton *driveB_NumberOfHeads; IBOutlet NSButton *autoInsertB; IBOutlet NSButton *fastFDC; IBOutlet NSMatrix *floppyWriteProtection; IBOutlet NSTextField *defaultImagesLocation; IBOutlet NSTextField *hdImage; IBOutlet NSTextField *ideMasterHdImage; IBOutlet NSTextField *ideSlaveHdImage; IBOutlet NSTextField *gemdosImage; IBOutlet NSButton *bootFromHD ; IBOutlet NSMatrix *HDWriteProtection; IBOutlet NSButton *bFilenameConversion; IBOutlet NSButton *nGemdosDrive; // ROM IBOutlet NSTextField *tosImage; IBOutlet NSTextField *cartridgeImage; // Atari screen IBOutlet NSMatrix *monitor; IBOutlet NSButton *useBorders; IBOutlet NSButton *falconTTRatio; IBOutlet NSButton *zoomSTLowRes; IBOutlet NSButton *useVDIResolution; IBOutlet NSMatrix *resolution; IBOutlet NSMatrix *colorDepth; // Display IBOutlet NSButton *showStatusBar; IBOutlet NSButton *fullScreen; IBOutlet NSButton *ledDisks; IBOutlet NSPopUpButton *frameSkip; IBOutlet NSTextField *maxZoomedWidth; // N IBOutlet NSTextField *maxZoomedHeight; // N IBOutlet NSButton *keepDesktopResolution; IBOutlet NSButton *SDL2UseGpuScaling; IBOutlet NSButton *SDL2Resizable ; IBOutlet NSButton *SDL2UseVSync ; // Hidestatus, Capture only, Avi codec, Avi FPS // Sound IBOutlet NSButton *enableSound; IBOutlet NSMatrix *playbackQuality; IBOutlet NSMatrix *YMVoicesMixing; // System IBOutlet NSMatrix *cpuType; IBOutlet NSMatrix *cpuClock; IBOutlet NSMatrix *machineType; IBOutlet NSMatrix *ramSize; // ram size IBOutlet NSButton *compatibleCpu; // bCompatibleCpu IBOutlet NSButton *blitter; IBOutlet NSButton *realTime; IBOutlet NSButton *patchTimerD; IBOutlet NSButton *FastBootPatch; IBOutlet NSPopUpButton *videoTiming; // for WinUAE CPU core IBOutlet NSButton *cycleExactCPU; //bCycleExactCpu IBOutlet NSButton *MMU_Emulation; IBOutlet NSButton *adressSpace24; // bAddressSpace24 IBOutlet NSStepper *TTRAMSizeStepper; // MS 12-2016 IBOutlet NSTextField *TTRAMSizeValue; // MS 12-2016 //IBOutlet NSButton *CompatibleFPU; IBOutlet NSMatrix *FPUType; IBOutlet NSButtonCell *bCell68060; // load/save state IBOutlet NSPopUpButton *enableDSP; // Joysticks IBOutlet NSPopUpButton *currentJoystick; IBOutlet NSMatrix *joystickMode; IBOutlet NSPopUpButton *realJoystick; IBOutlet NSPopUpButton *joystickUp; IBOutlet NSPopUpButton *joystickRight; IBOutlet NSPopUpButton *joystickDown; IBOutlet NSPopUpButton *joystickLeft; IBOutlet NSPopUpButton *joystickFire; IBOutlet NSButton *enableAutoFire; // Keyboard IBOutlet NSMatrix *keyboardMapping; IBOutlet NSTextField *keyboardMappingFile; // T // Disable Key Repeat // Peripheral IBOutlet NSButton *enablePrinter; IBOutlet NSTextField *printToFile; // T IBOutlet NSButton *enableRS232; IBOutlet NSTextField *writeRS232ToFile; // T IBOutlet NSTextField *readRS232FromFile; // T IBOutlet NSButton *enableMidi; IBOutlet NSTextField *writeMidiToFile; // T __unsafe_unretained IBOutlet NSPopUpButton *midiInPort; __unsafe_unretained IBOutlet NSPopUpButton *midiOutPort; // Other __unsafe_unretained IBOutlet NSButtonCell *confirmQuit; IBOutlet NSButton *captureOnChange; IBOutlet NSButton *interleaved; IBOutlet NSSlider *nSpec512Treshold; IBOutlet NSStepper *widthStepper; IBOutlet NSStepper *heightStepper; IBOutlet NSTextField *configFile; // T ?? BOOL bInitialized; int cRealJoysticks; int nCurrentJoystick; BOOL applyChanges ; // moved from NSOpenPanel *opnPanel ; NSSavePanel *savPanel ; NSMutableString *cartridge ; NSMutableString *imgeDir ; NSMutableString *floppyA ; NSMutableString *floppyB ; NSMutableString *gemdos ; NSMutableString *hrdDisk ; NSMutableString *masterIDE ; NSMutableString *slaveIDE ; NSMutableString *keyboard ; NSMutableString *midiOut ; NSMutableString *printit ; NSMutableString *rs232In ; NSMutableString *rs232Out ; NSMutableString *TOS ; NSMutableString *configNm ; } - (IBAction)changeViewedJoystick:(id)sender; - (IBAction)chooseCartridgeImage:(id)sender; - (IBAction)chooseDefaultImagesLocation:(id)sender; - (IBAction)chooseFloppyImageA:(id)sender; - (IBAction)chooseFloppyImageB:(id)sender; - (IBAction)chooseGemdosImage:(id)sender; - (IBAction)chooseHdImage:(id)sender; - (IBAction)chooseIdeMasterHdImage:(id)sender; - (IBAction)chooseIdeSlaveHdImage:(id)sender; - (IBAction)chooseKeyboardMappingFile:(id)sender; - (IBAction)chooseMidiOutputFile:(id)sender; - (IBAction)choosePrintToFile:(id)sender; - (IBAction)chooseRS232InputFile:(id)sender; - (IBAction)chooseRS232OutputFile:(id)sender; - (IBAction)chooseTosImage:(id)sender; - (IBAction)commitAndClose:(id)sender; - (IBAction)ejectFloppyA:(id)sender; - (IBAction)ejectFloppyB:(id)sender; - (IBAction)ejectGemdosImage:(id)sender; - (IBAction)ejectHdImage:(id)sender; - (IBAction)ejectIdeMasterHdImage:(id)sender; - (IBAction)ejectIdeSlaveHdImage:(id)sender; - (IBAction)loadPrefs:(id)sender; - (IBAction)saveConfigAs:(id)sender; - (IBAction)loadConfigFrom:(id)sender; - (IBAction)aller:(id)sender ; // add - (IBAction)halle:(id)sender ; // add - (IBAction)finished:(id)sender; // add - (void)setAllControls; - (void)saveAllControls; - (void)insertFloppyImageIntoDrive:(int)drive forTextField:(NSTextField*)floppyTextField realPath:(NSMutableString *)realPath ; - (BOOL)choosePathForControl:(NSTextField*)textField chooseDirectories:(BOOL)chooseDirectories defaultInitialDir:(NSString*)defaultInitialDir mutString:(NSMutableString *)mutString ; - (void)initKeysDropDown:(NSPopUpButton*)dropDown; - (void)setJoystickControls; - (void)saveJoystickControls; - (IBAction)updateEnabledStates:(id)sender; - (IBAction)setWidth:(id)sender; - (IBAction)setHeight:(id)sender; //System RAM Stepper - (IBAction)setTTRAMSize:(id)sender; +(PrefsController*)prefs ; @end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/PrefsController.m000066400000000000000000001264541504763705000262400ustar00rootroot00000000000000/* Hatari - PrefsController.m This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Preferences window controller implementation file Feb-Mar 2006, Sébastien Molines - Created Jan 2006, Sébastien Molines - Updated for recent emulator updates 2013 : Miguel SARO, J. VERNET 2016 : J. VERNET - Updated for 1.9.0 2017 : Miguel SARO resizable */ // bOKDialog = Dialog_MainDlg(&bForceReset, &bLoadedSnapshot); // prise des préférences // TODO: Set the default paths to MacOS-friendly values // TODO: Move hardcoded string to localizable resources (e.g. string "Reset the emulator?") #import "PrefsController.h" #import "Shared.h" #include "main.h" #include "configuration.h" #include "change.h" #include "dialog.h" #include "file.h" #include "floppy.h" #include "screen.h" #include "sdlgui.h" #include "paths.h" #include "keymap.h" #include "joy.h" #include "midi.h" // Macros to transfer data between Cocoa controls and Hatari data structures // de l'affichage vers la structure (saveAllControls) #define EXPORT_TEXTFIELD(mutablStrng, target) [mutablStrng getCString:target maxLength:sizeof((target))-1 encoding:NSASCIIStringEncoding] #define EXPORT_NTEXTFIELD(nstextfield, target) target = [nstextfield intValue] #define EXPORT_SWITCH(nsbutton, target) target = ([(nsbutton) state] == NSOnState) #define EXPORT_RADIO(nsmatrix, target) target = [[(nsmatrix) selectedCell] tag] #define EXPORT_DROPDOWN(nspopupbutton, target) target = [[(nspopupbutton) selectedItem] tag] #define EXPORT_SLIDER(nsslider, target) target = [(nsslider) intValue] // la structure vers l'affichage (setAllControls) #define IMPORT_TEXTFIELD(nstextfield, mutablStrng, source) [mutablStrng setString:[NSString stringWithCString:(source) encoding:NSASCIIStringEncoding]] ; [nstextfield setStringValue:[NSApp pathUser:mutablStrng]] #define IMPORT_NTEXTFIELD(nstextfield, source) [(nstextfield) setIntValue:(source)] #define IMPORT_SWITCH(nsbutton, source) [(nsbutton) setState:((source))? NSOnState : NSOffState] #define IMPORT_RADIO(nsmatrix, source) [(nsmatrix) selectCellWithTag:(source)] #define IMPORT_DROPDOWN(nspopupbutton, source) [(nspopupbutton) selectItemAtIndex:[(nspopupbutton) indexOfItemWithTag:(source)]] #define IMPORT_SLIDER(nsslider,source) [(nsslider) setIntValue:source] #define INITIAL_DIR(dossier) [dossier length] < 2 ? @"~" : dossier // Back up of the current configuration parameters CNF_PARAMS CurrentParams; // Keys to be listed in the Joysticks dropdowns SDL_Keycode Preferences_KeysForJoysticks[] = { SDLK_BACKSPACE, SDLK_TAB, SDLK_CLEAR, SDLK_RETURN, SDLK_PAUSE, SDLK_ESCAPE, SDLK_SPACE, SDLK_EXCLAIM, SDLK_QUOTEDBL, SDLK_HASH, SDLK_DOLLAR, SDLK_AMPERSAND, SDLK_QUOTE, SDLK_LEFTPAREN, SDLK_RIGHTPAREN, SDLK_ASTERISK, SDLK_PLUS, SDLK_COMMA, SDLK_MINUS, SDLK_PERIOD, SDLK_SLASH, SDLK_0, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_COLON, SDLK_SEMICOLON, SDLK_LESS, SDLK_EQUALS, SDLK_GREATER, SDLK_QUESTION, SDLK_AT, SDLK_LEFTBRACKET, SDLK_BACKSLASH, SDLK_RIGHTBRACKET, SDLK_CARET, SDLK_UNDERSCORE, SDLK_BACKQUOTE, SDLK_a, SDLK_b, SDLK_c, SDLK_d, SDLK_e, SDLK_f, SDLK_g, SDLK_h, SDLK_i, SDLK_j, SDLK_k, SDLK_l, SDLK_m, SDLK_n, SDLK_o, SDLK_p, SDLK_q, SDLK_r, SDLK_s, SDLK_t, SDLK_u, SDLK_v, SDLK_w, SDLK_x, SDLK_y, SDLK_z, SDLK_DELETE, SDLK_KP_0, SDLK_KP_1, SDLK_KP_2, SDLK_KP_3, SDLK_KP_4, SDLK_KP_5, SDLK_KP_6, SDLK_KP_7, SDLK_KP_8, SDLK_KP_9, SDLK_KP_PERIOD, SDLK_KP_DIVIDE, SDLK_KP_MULTIPLY, SDLK_KP_MINUS, SDLK_KP_PLUS, SDLK_KP_ENTER, SDLK_KP_EQUALS, SDLK_UP, SDLK_DOWN, SDLK_RIGHT, SDLK_LEFT, SDLK_INSERT, SDLK_HOME, SDLK_END, SDLK_PAGEUP, SDLK_PAGEDOWN, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_F11, SDLK_F12, SDLK_F13, SDLK_F14, SDLK_F15, SDLK_NUMLOCKCLEAR, SDLK_CAPSLOCK, SDLK_SCROLLLOCK, SDLK_RSHIFT, SDLK_LSHIFT, SDLK_RCTRL, SDLK_LCTRL, SDLK_RALT, SDLK_LALT, SDLK_RGUI, SDLK_LGUI, SDLK_MODE, SDLK_HELP, SDLK_PRINTSCREEN, SDLK_SYSREQ, SDLK_MENU, SDLK_POWER, SDLK_UNDO }; size_t Preferences_cKeysForJoysticks = sizeof(Preferences_KeysForJoysticks) / sizeof(Preferences_KeysForJoysticks[0]); #define DLGSOUND_11KHZ 0 #define DLGSOUND_12KHZ 1 #define DLGSOUND_16KHZ 2 #define DLGSOUND_22KHZ 3 #define DLGSOUND_25KHZ 4 #define DLGSOUND_32KHZ 5 #define DLGSOUND_44KHZ 6 #define DLGSOUND_48KHZ 7 #define DLGSOUND_50KHZ 8 static const int nSoundFreqs[] = { 11025, 12517, 16000, 22050, 25033, 32000, 44100, 48000, 50066 }; @implementation PrefsController // not used - (IBAction)finished:(id)sender { //Main_RequestQuit(0) ; } /*-----------------------------------------------------------------------*/ /* Helper method for Choose buttons */ /* Returns: TRUE is the user selected a path, FALSE if he/she aborted */ /*-----------------------------------------------------------------------*/ - (BOOL)choosePathForControl:(NSTextField*)textField chooseDirectories:(BOOL)chooseDirectories defaultInitialDir:(NSString*)defaultInitialDir mutString:(NSMutableString *)mutString { NSString *directoryToOpen ; NSString *fileToPreselect ; NSString *newPath ; if ((mutString != nil) && (mutString.length > 2)) { directoryToOpen = mutString.stringByDeletingLastPathComponent ; // There is existing path: we use it. fileToPreselect = mutString.lastPathComponent ; } else { directoryToOpen = defaultInitialDir.stringByExpandingTildeInPath ; // no path: use user's directory fileToPreselect = nil; } ; newPath = [NSApp hopenfile:chooseDirectories defoDir:directoryToOpen defoFile:fileToPreselect]; if (newPath.length != 0) // user canceled if empty { [mutString setString:[NSString stringWithString:newPath]] ; // save this path [textField setStringValue:[NSApp pathUser:newPath]]; // show localized path return YES; } ; return NO; // Selection aborted } /*----------------------------------------------------------------------*/ /* Helper method to insert a floppy image */ /* TODO: Add code to restrict to known file types */ /*----------------------------------------------------------------------*/ - (void)insertFloppyImageIntoDrive:(int)drive forTextField:(NSTextField*)floppyTextField realPath:(NSMutableString *)realPath { if ([self choosePathForControl:floppyTextField chooseDirectories:NO defaultInitialDir:imgeDir mutString:realPath]) Floppy_SetDiskFileName(drive, [realPath cStringUsingEncoding:NSASCIIStringEncoding], NULL); // Insert the floppy image at this path ???? } //----------------------------------------------------------------------------- - (NSString *)initial:(NSString *)route { BOOL flag1, flag2; if ((route==nil) || (route.length == 0)) return @"~" ; flag1 = [[NSFileManager defaultManager] fileExistsAtPath:route isDirectory:&flag2] ; if (flag1 && !flag2) return route ; return route.stringByDeletingLastPathComponent ; } /*----------------------------------------------------------------------*/ // Methods for all the "Choose" buttons /*----------------------------------------------------------------------*/ - (IBAction)chooseCartridgeImage:(id)sender; { [self choosePathForControl: cartridgeImage chooseDirectories:NO defaultInitialDir:[self initial:cartridge] // cartridge mutString:cartridge]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseDefaultImagesLocation:(id)sender { [self choosePathForControl: defaultImagesLocation chooseDirectories:YES defaultInitialDir:[self initial:imgeDir] // images location mutString:imgeDir]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseFloppyImageA:(id)sender { [self insertFloppyImageIntoDrive:0 forTextField:floppyImageA realPath:floppyA]; // floppy A } /*----------------------------------------------------------------------*/ - (IBAction)chooseFloppyImageB:(id)sender { [self insertFloppyImageIntoDrive:1 forTextField:floppyImageB realPath:floppyB]; // floppy B } /*----------------------------------------------------------------------*/ - (IBAction)chooseGemdosImage:(id)sender // directory for Gemdos { [self choosePathForControl: gemdosImage chooseDirectories:YES defaultInitialDir:INITIAL_DIR(gemdos) // gemdos mutString:gemdos] ; if (gemdos.length >2 ) [gemdosImage setStringValue:[NSApp pathUser:gemdos]] ; } /*----------------------------------------------------------------------*/ - (IBAction)chooseHdImage:(id)sender { [self choosePathForControl: hdImage chooseDirectories:NO defaultInitialDir:[self initial:hrdDisk] // HD image ? mutString:hrdDisk] ; } /*----------------------------------------------------------------------*/ - (IBAction)chooseIdeMasterHdImage:(id)sender { [self choosePathForControl: ideMasterHdImage chooseDirectories:NO defaultInitialDir:[self initial:masterIDE] // IDE master mutString:masterIDE]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseIdeSlaveHdImage:(id)sender { [self choosePathForControl: ideSlaveHdImage chooseDirectories:NO defaultInitialDir:[self initial:slaveIDE] // IDE slave mutString:slaveIDE]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseKeyboardMappingFile:(id)sender { [self choosePathForControl: keyboardMappingFile chooseDirectories:NO defaultInitialDir:[self initial:keyboard] // keyboard mapping mutString:keyboard]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseMidiOutputFile:(id)sender { [self choosePathForControl: writeMidiToFile chooseDirectories:NO defaultInitialDir:[self initial:midiOut] // midi output mutString:midiOut]; } /*----------------------------------------------------------------------*/ - (IBAction)choosePrintToFile:(id)sender { [self choosePathForControl: printToFile chooseDirectories:NO defaultInitialDir:[self initial:printit] // print to file mutString:printit]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseRS232InputFile:(id)sender { [self choosePathForControl: readRS232FromFile chooseDirectories:NO defaultInitialDir:[self initial:rs232In] // RS232 input mutString:rs232In]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseRS232OutputFile:(id)sender { [self choosePathForControl: writeRS232ToFile chooseDirectories:NO defaultInitialDir:[self initial:rs232Out] // RS232 output mutString:rs232Out]; } /*----------------------------------------------------------------------*/ - (IBAction)chooseTosImage:(id)sender; { [self choosePathForControl: tosImage chooseDirectories:NO defaultInitialDir:[self initial:TOS] // TOS image mutString:TOS]; } /*----------------------------------------------------------------------*/ // Methods for the "Eject" buttons /*----------------------------------------------------------------------*/ - (IBAction)ejectFloppyA:(id)sender { Floppy_SetDiskFileNameNone(0); // Refresh control & mutablestring floppyImageA.stringValue = @"" ; floppyA.string = @"" ; } /*----------------------------------------------------------------------*/ - (IBAction)ejectFloppyB:(id)sender { Floppy_SetDiskFileNameNone(1); // Refresh control & mutablestring floppyImageB.stringValue = @"" ; floppyB.string = @"" ; } /*----------------------------------------------------------------------*/ - (IBAction)ejectGemdosImage:(id)sender { // Clear the control. Later. saveAllControls will set the ConfigureParams accordingly // to signal this is ejected gemdosImage.stringValue = @"" ; gemdos.string = @"" ; } /*----------------------------------------------------------------------*/ - (IBAction)ejectHdImage:(id)sender { // Clear the control. Later. saveAllControls will set the ConfigureParams accordingly // to signal this is ejected hdImage.stringValue = @"" ; hrdDisk.string = @"" ; } /*----------------------------------------------------------------------*/ - (IBAction)ejectIdeMasterHdImage:(id)sender { // Clear the control. Later. saveAllControls will set the ConfigureParams accordingly // to signal this is ejected ideMasterHdImage.stringValue = @"" ; masterIDE.string = @"" ; } /*----------------------------------------------------------------------*/ - (IBAction)ejectIdeSlaveHdImage:(id)sender { // Clear the control. Later. saveAllControls will set the ConfigureParams accordingly // to signal this is ejected ideSlaveHdImage.stringValue = @"" ; slaveIDE.string = @"" ; } /*----------------------------------------------------------------------*/ /* Methods for the "Load Config" button */ /*----------------------------------------------------------------------*/ - (IBAction)loadConfigFrom:(id)sender { NSArray *lesURLs ; NSString *ru ; BOOL btOk ; ru = [NSString stringWithCString:(Paths_GetHatariHome()) encoding:NSASCIIStringEncoding] ; opnPanel.allowedFileTypes = @[@"cfg"] ; opnPanel.canChooseDirectories = NO ; opnPanel.canChooseFiles = YES ; opnPanel.accessoryView = partage ; //10.5 ? // if ([opnPanel respondsToSelector:@selector(setDirectoryURL:)]) { opnPanel.directoryURL = [NSURL fileURLWithPath:ru isDirectory:YES] ; opnPanel.nameFieldStringValue = @"hatari" ; btOk = [opnPanel runModal] == NSModalResponseOK; // Ok ? } // 10.5 ? // else // btOk = [opnPanel runModalForDirectory:ru file:@"hatari"] == NSModalResponseOK; //NSOKButton ; if (!btOk) return ; // Cancel lesURLs = opnPanel.URLs ; if ((lesURLs == nil) || (lesURLs.count == 0)) return ; [configNm setString:[[lesURLs objectAtIndex:0] path]] ; // Make a non-const C string out of it [configNm getCString:sConfigFileName maxLength:FILENAME_MAX encoding:NSASCIIStringEncoding]; // Load the config into ConfigureParams Configuration_Load(sConfigFileName); // Refresh all the controls to match ConfigureParams [self setAllControls]; } /*----------------------------------------------------------------------*/ // Methods for the "Save Config" button (bottom preference window) /*----------------------------------------------------------------------*/ - (IBAction)saveConfigAs:(id)sender { NSString *ru ; BOOL btOk ; ru = [NSString stringWithCString:(Paths_GetHatariHome()) encoding:NSASCIIStringEncoding] ; savPanel.allowedFileTypes = @[@"cfg"] ; savPanel.accessoryView = hartage ; //10.5 // if ([savPanel respondsToSelector:@selector(setDirectoryURL:)]) { savPanel.directoryURL = [NSURL fileURLWithPath:ru isDirectory:YES] ; // Since OS X 10.6 savPanel.nameFieldStringValue = @"hatari" ; btOk = [savPanel runModal] == NSModalResponseOK ; // Ok ? } //10.5 // else // btOk = [savPanel runModalForDirectory:ru file:@"hatari"] == NSModalResponseOK; //NSOKButton ; // avant 10.6 if (!btOk) return ; // Cancel [configNm setString: savPanel.URL.path ]; // Make a non-const C string out of it [configNm getCString:sConfigFileName maxLength:FILENAME_MAX encoding:NSASCIIStringEncoding]; [self saveAllControls] ; // Save the config from ConfigureParams Configuration_Save(); // [self configSave:configNm] ; } /*----------------------------------------------------------------------*/ - (IBAction)aller:(id)sender { NSString *defaultDirectory ; defaultDirectory = [NSString stringWithCString:(Paths_GetHatariHome()) encoding:NSASCIIStringEncoding] ; // if ([opnPanel respondsToSelector:@selector(setDirectoryURL:)]) opnPanel.directoryURL = [NSURL fileURLWithPath:defaultDirectory isDirectory:YES] ; // else // [opnPanel setDirectory:defaultDirectory] ; } /*----------------------------------------------------------------------*/ - (IBAction)halle:(id)sender { NSString *defaultDirectory ; defaultDirectory = [NSString stringWithCString:(Paths_GetHatariHome()) encoding:NSASCIIStringEncoding] ; // if ([savPanel respondsToSelector:@selector(setDirectoryURL:)]) savPanel.directoryURL = [NSURL fileURLWithPath:defaultDirectory isDirectory:YES] ; // else // [savPanel setDirectory:defaultDirectory] ; } /*----------------------------------------------------------------------*/ //Commits and closes Ok button in preferences window /*----------------------------------------------------------------------*/ - (IBAction)commitAndClose:(id)sender { // The user clicked OK [self saveAllControls]; [window close] ; } /*----------------------------------------------------------------------*/ // Populate Joystick key dropdown - (void)initKeysDropDown:(NSPopUpButton*)dropDown { [dropDown removeAllItems]; unsigned int i; for (i = 0; i < Preferences_cKeysForJoysticks; i++) { SDL_Keycode key = Preferences_KeysForJoysticks[i]; const char* szKeyName = SDL_GetKeyName(key); [dropDown addItemWithTitle:[[NSString stringWithCString:szKeyName encoding:NSASCIIStringEncoding] capitalizedString]]; dropDown.lastItem.tag = key ; } } // ---------------------------------------------------------------------------- // populate MIDI dropdowns // - (void)initMidiDropdowns { [midiInPort removeAllItems]; [midiOutPort removeAllItems]; const char* szinPortName = "Off"; [midiInPort addItemWithTitle:[NSString stringWithCString:szinPortName encoding:NSASCIIStringEncoding]]; [midiOutPort addItemWithTitle:[NSString stringWithCString:szinPortName encoding:NSASCIIStringEncoding]]; #ifdef HAVE_PORTMIDI const char* portName = NULL; while ((portName = Midi_Host_GetPortName(portName, MIDI_NAME_NEXT, MIDI_FOR_INPUT))) [midiInPort addItemWithTitle:[NSString stringWithCString:portName encoding:NSASCIIStringEncoding]]; portName = NULL; while ((portName = Midi_Host_GetPortName(portName, MIDI_NAME_NEXT, MIDI_FOR_OUTPUT))) [midiOutPort addItemWithTitle:[NSString stringWithCString:portName encoding:NSASCIIStringEncoding]]; #endif // HAVE_PORTMIDI } // ---------------------------------------------------------------------------- // ConfigureParams -> GUI controls // - (void)setMidiDropdowns { [midiInPort selectItemWithTitle:[NSString stringWithCString:ConfigureParams.Midi.sMidiInPortName encoding:NSASCIIStringEncoding]]; [midiOutPort selectItemWithTitle:[NSString stringWithCString:ConfigureParams.Midi.sMidiOutPortName encoding:NSASCIIStringEncoding]]; } // ---------------------------------------------------------------------------- // GUI controls -> ConfigureParams // - (void)saveMidiDropdowns { const char *midiOutPortUtf8String = [[midiOutPort titleOfSelectedItem] UTF8String]; const char *midiInPortUtf8String = [[midiInPort titleOfSelectedItem] UTF8String]; strlcpy(ConfigureParams.Midi.sMidiOutPortName, midiOutPortUtf8String ? midiOutPortUtf8String : "Off", sizeof(ConfigureParams.Midi.sMidiOutPortName)); strlcpy(ConfigureParams.Midi.sMidiInPortName, midiInPortUtf8String ? midiInPortUtf8String : "Off", sizeof(ConfigureParams.Midi.sMidiInPortName)); } /*----------------------------------------------------------------------*/ //Displays the Preferences dialog Ouverture de la fenêtre des préférences /*----------------------------------------------------------------------*/ - (IBAction)loadPrefs:(id)sender { //GuiOsx_Pause(true); [configNm setString:[NSString stringWithCString:sConfigFileName encoding:NSASCIIStringEncoding]] ; if (!bInitialized) { // Note: These inits cannot be done in awakeFromNib as by this time SDL is not yet initialized. // Fill the keyboard dropdowns [self initKeysDropDown:joystickUp]; [self initKeysDropDown:joystickRight]; [self initKeysDropDown:joystickDown]; [self initKeysDropDown:joystickLeft]; [self initKeysDropDown:joystickFire]; // Get and store the number of real joysticks cRealJoysticks = SDL_NumJoysticks(); // Fill the real joysticks dropdown, if any are available if (cRealJoysticks > 0) { [realJoystick removeAllItems]; int i; for (i = 0; i < cRealJoysticks; i++) { const char* szJoystickName = Joy_GetName(i); [realJoystick addItemWithTitle:[[NSString stringWithCString:szJoystickName encoding:NSASCIIStringEncoding] capitalizedString]]; realJoystick.lastItem.tag = i ; } } else // No real joysticks: Disable the controls { [joystickMode cellWithTag:1].enabled = FALSE ; realJoystick.enabled = FALSE ; } // Fill MIDI dropdowns [self initMidiDropdowns]; bInitialized = true; } // Backup of configuration settings to CurrentParams (which we will only // commit back to the configuration settings if choosing OK) CurrentParams = ConfigureParams; applyChanges=false; [self setAllControls]; // Display the window ModalWrapper *mw = [[ModalWrapper alloc] init]; [mw runModal:window]; [mw release]; // */ // solve bug screen-reset: close and kill preference windows before // M. Saro, 2013 //if(Ok button in preferences Windows) //{ // Check if change need reset if (Change_DoNeedReset(&CurrentParams, &ConfigureParams)) { applyChanges = [NSApp myAlerte:NSAlertStyleInformational Txt:nil firstB:localize(@"Don't reset") alternateB:localize(@"Reset") otherB:nil informativeTxt:localize(@"Must be reset") ] == NSAlertSecondButtonReturn ; if (applyChanges) Change_CopyChangedParamsToConfiguration(&CurrentParams, &ConfigureParams, true) ; else ConfigureParams = CurrentParams; //Restore backup params } else Change_CopyChangedParamsToConfiguration(&CurrentParams, &ConfigureParams, false); //Apply config without reset //} // else // not OK button // { // ConfigureParams = CurrentParams; //Restore backup params // } } /*----------------------------------------------------------------------*/ //Updates the controls following a change in the joystick selection /*----------------------------------------------------------------------*/ - (IBAction)changeViewedJoystick:(id)sender { // Save the pre-joystick controls, as we are about to change them [self saveJoystickControls]; // Refresh the per-joystick controls [self setJoystickControls]; // Update the controls' enabled states [self updateEnabledStates:self]; } /*----------------------------------------------------------------------*/ //Initializes all controls, transfert des préférences dans la fenêtre /*----------------------------------------------------------------------*/ - (void)setAllControls { // Import the floppy paths into their controls. //Disk A IMPORT_TEXTFIELD(floppyImageA, floppyA, ConfigureParams.DiskImage.szDiskFileName[0]); IMPORT_SWITCH(enableDriveA, ConfigureParams.DiskImage.EnableDriveA); if(ConfigureParams.DiskImage.DriveA_NumberOfHeads==1) [driveA_NumberOfHeads setState:NSOffState]; else [driveA_NumberOfHeads setState:NSOnState]; //Disk B IMPORT_TEXTFIELD(floppyImageB, floppyB, ConfigureParams.DiskImage.szDiskFileName[1]); // le B IMPORT_SWITCH(enableDriveB,ConfigureParams.DiskImage.EnableDriveB); if(ConfigureParams.DiskImage.DriveB_NumberOfHeads==1) [driveB_NumberOfHeads setState:NSOffState]; else [driveB_NumberOfHeads setState:NSOnState]; // Import all the preferences into their controls IMPORT_TEXTFIELD(cartridgeImage, cartridge, ConfigureParams.Rom.szCartridgeImageFileName); IMPORT_TEXTFIELD(defaultImagesLocation, imgeDir, ConfigureParams.DiskImage.szDiskImageDirectory); IMPORT_TEXTFIELD(keyboardMappingFile, keyboard, ConfigureParams.Keyboard.szMappingFileName); IMPORT_TEXTFIELD(printToFile, printit, ConfigureParams.Printer.szPrintToFileName); IMPORT_TEXTFIELD(tosImage, TOS, ConfigureParams.Rom.szTosImageFileName); IMPORT_TEXTFIELD(configFile, configNm, sConfigFileName); IMPORT_TEXTFIELD(readRS232FromFile, rs232In, ConfigureParams.RS232.szInFileName); IMPORT_TEXTFIELD(writeRS232ToFile, rs232Out, ConfigureParams.RS232.szOutFileName); IMPORT_SWITCH(autoInsertB, ConfigureParams.DiskImage.bAutoInsertDiskB); IMPORT_SWITCH(blitter, ConfigureParams.System.bBlitter); IMPORT_SWITCH(bootFromHD, ConfigureParams.HardDisk.bBootFromHardDisk); //1.9.0 New Option IMPORT_SWITCH(bFilenameConversion, ConfigureParams.HardDisk.bFilenameConversion); if (ConfigureParams.HardDisk.nGemdosDrive == DRIVE_SKIP) [nGemdosDrive setState:NSOnState]; else [nGemdosDrive setState:NSOffState]; IMPORT_SWITCH(nGemdosDrive, ConfigureParams.HardDisk.nGemdosDrive); IMPORT_SWITCH(captureOnChange, ConfigureParams.Screen.bCrop); IMPORT_RADIO(colorDepth, ConfigureParams.Screen.nVdiColors); IMPORT_SWITCH(compatibleCpu, ConfigureParams.System.bCompatibleCpu); IMPORT_RADIO(cpuClock, ConfigureParams.System.nCpuFreq); IMPORT_RADIO(cpuType, ConfigureParams.System.nCpuLevel); IMPORT_SWITCH(enableMidi, ConfigureParams.Midi.bEnableMidi); IMPORT_SWITCH(enablePrinter, ConfigureParams.Printer.bEnablePrinting); IMPORT_SWITCH(enableRS232, ConfigureParams.RS232.bEnableRS232); IMPORT_SWITCH(enableSound, ConfigureParams.Sound.bEnableSound); IMPORT_DROPDOWN(frameSkip, ConfigureParams.Screen.nFrameSkips); IMPORT_RADIO(keyboardMapping, ConfigureParams.Keyboard.nKeymapType); IMPORT_RADIO(machineType, ConfigureParams.System.nMachineType); IMPORT_RADIO(monitor, ConfigureParams.Screen.nMonitorType); IMPORT_SWITCH(patchTimerD, ConfigureParams.System.bPatchTimerD); IMPORT_RADIO(ramSize, ConfigureParams.Memory.STRamSize_KB); // MS 12-2016 IMPORT_SWITCH(fastFDC, ConfigureParams.DiskImage.FastFloppy); IMPORT_SWITCH(useBorders, ConfigureParams.Screen.bAllowOverscan); IMPORT_SWITCH(useVDIResolution, ConfigureParams.Screen.bUseExtVdiResolutions); IMPORT_RADIO(floppyWriteProtection, ConfigureParams.DiskImage.nWriteProtection); IMPORT_RADIO(HDWriteProtection, ConfigureParams.HardDisk.nWriteProtection); // IMPORT_SWITCH(zoomSTLowRes, ConfigureParams.Screen.bZoomLowRes); IMPORT_SWITCH(showStatusBar, ConfigureParams.Screen.bShowStatusbar); IMPORT_DROPDOWN(enableDSP,ConfigureParams.System.nDSPType); // 12/04/2010 IMPORT_SWITCH(falconTTRatio, ConfigureParams.Screen.bAspectCorrect); IMPORT_SWITCH(fullScreen, ConfigureParams.Screen.bFullScreen); IMPORT_SWITCH(ledDisks, ConfigureParams.Screen.bShowDriveLed); IMPORT_SWITCH(keepDesktopResolution, ConfigureParams.Screen.bKeepResolution); //v1.6.1 IMPORT_SWITCH(FastBootPatch,ConfigureParams.System.bFastBoot); IMPORT_RADIO(YMVoicesMixing,ConfigureParams.Sound.YmVolumeMixing); IMPORT_SWITCH(SDL2UseGpuScaling, ConfigureParams.Screen.bUseSdlRenderer); IMPORT_SWITCH(SDL2Resizable, ConfigureParams.Screen.bResizable); IMPORT_SWITCH(SDL2UseVSync, ConfigureParams.Screen.bUseVsync); //deal with the Max Zoomed Stepper IMPORT_NTEXTFIELD(maxZoomedWidth, ConfigureParams.Screen.nMaxWidth); IMPORT_NTEXTFIELD(maxZoomedHeight, ConfigureParams.Screen.nMaxHeight); [widthStepper setDoubleValue:[maxZoomedWidth intValue]]; [heightStepper setDoubleValue:[maxZoomedHeight intValue]]; //1.9.1 Video Timing switch(ConfigureParams.System.VideoTimingMode) { case VIDEO_TIMING_MODE_RANDOM:[videoTiming selectItemWithTag:0];break; case VIDEO_TIMING_MODE_WS1:[videoTiming selectItemWithTag:1];break; case VIDEO_TIMING_MODE_WS2:[videoTiming selectItemWithTag:2];break; case VIDEO_TIMING_MODE_WS3:[videoTiming selectItemWithTag:3];break; case VIDEO_TIMING_MODE_WS4:[videoTiming selectItemWithTag:4];break; } //deal with TT RAM Size Stepper int ttramsize_MB=ConfigureParams.Memory.TTRamSize_KB/1024 ; //JV 12-2016 IMPORT_NTEXTFIELD(TTRAMSizeValue, ttramsize_MB); // MS 12-2016 [TTRAMSizeStepper setDoubleValue:[TTRAMSizeValue intValue]]; IMPORT_SWITCH(cycleExactCPU, ConfigureParams.System.bCycleExactCpu); IMPORT_SWITCH(MMU_Emulation, ConfigureParams.System.bMMU); IMPORT_SWITCH(adressSpace24, ConfigureParams.System.bAddressSpace24); if (ConfigureParams.System.n_FPUType == FPU_NONE) [FPUType selectCellWithTag:0]; else if (ConfigureParams.System.n_FPUType == FPU_68881) [FPUType selectCellWithTag:1]; else if (ConfigureParams.System.n_FPUType == FPU_68882) [FPUType selectCellWithTag:2]; else if (ConfigureParams.System.n_FPUType == FPU_CPU) [FPUType selectCellWithTag:3]; //not needed anymore //IMPORT_SWITCH(CompatibleFPU, ConfigureParams.System.bCompatibleFPU); int i; for (i = 0; i <= DLGSOUND_50KHZ-DLGSOUND_11KHZ; i++) { if (ConfigureParams.Sound.nPlaybackFreq > nSoundFreqs[i]-500 && ConfigureParams.Sound.nPlaybackFreq < nSoundFreqs[i]+500) { [playbackQuality selectCellWithTag:(i)]; break; } } if (ConfigureParams.Screen.nVdiWidth >= 1024) [resolution selectCellWithTag:(2)]; else if (ConfigureParams.Screen.nVdiWidth >= 768) [resolution selectCellWithTag:(1)]; else [resolution selectCellWithTag:(0)]; // If the HD flag is set, load the HD path, otherwise make it blank if (ConfigureParams.Acsi[0].bUseDevice) { IMPORT_TEXTFIELD(hdImage, hrdDisk, ConfigureParams.Acsi[0].sDeviceFile); } else { hdImage.stringValue = @""; hrdDisk.string = @"" ; } // If the IDE HD flag is set, load the IDE HD path, otherwise make it blank //Master if (ConfigureParams.Ide[0].bUseDevice) { IMPORT_TEXTFIELD(ideMasterHdImage, masterIDE, ConfigureParams.Ide[0].sDeviceFile); } else { ideMasterHdImage.stringValue = @"" ; [masterIDE setString:@""] ; } //Slave if (ConfigureParams.Ide[1].bUseDevice) { IMPORT_TEXTFIELD(ideSlaveHdImage, slaveIDE, ConfigureParams.Ide[1].sDeviceFile); } else { ideSlaveHdImage.stringValue = @"" ; [slaveIDE setString:@""] ; } // If the Gemdos flag is set, load the Gemdos path, otherwise make it blank if (ConfigureParams.HardDisk.bUseHardDiskDirectories) { [gemdos setString:[NSString stringWithCString:(ConfigureParams.HardDisk.szHardDiskDirectories[0]) encoding:NSASCIIStringEncoding]] ; // [gemdosImage setStringValue:[NSApp pathUser:[gemdos stringByDeletingLastPathComponent]]] ; [gemdosImage setStringValue:[NSApp pathUser:gemdos]] ; } else { gemdosImage.stringValue = @"" ; [gemdos setString:@""]; } // Set the per-joystick controls [self setJoystickControls]; // -- MIDI [self setMidiDropdowns]; // Update the controls' enabled states [self updateEnabledStates:self]; } /*----------------------------------------------------------------------*/ /* Updates the enabled states of controls who depend on other controls */ /*----------------------------------------------------------------------*/ - (IBAction)updateEnabledStates:(id)sender { // Joystick key controls are only enabled if "Use keyboard" is selected int nJoystickMode; EXPORT_RADIO(joystickMode, nJoystickMode); BOOL bUsingKeyboard = (nJoystickMode == JOYSTICK_KEYBOARD); joystickUp.enabled = bUsingKeyboard; joystickRight.enabled = bUsingKeyboard; joystickDown.enabled = bUsingKeyboard; joystickLeft.enabled = bUsingKeyboard; joystickFire.enabled = bUsingKeyboard; // Resolution and colour depth depend on Extended GEM VDI resolution BOOL bUsingVDI; EXPORT_SWITCH(useVDIResolution, bUsingVDI); resolution.enabled = bUsingVDI; colorDepth.enabled = bUsingVDI; // Playback quality depends on enable sound BOOL bSoundEnabled; EXPORT_SWITCH(enableSound, bSoundEnabled); playbackQuality.enabled = bSoundEnabled ; } /*----------------------------------------------------------------------*/ /* Updates the joystick controls to match the new joystick selection */ /*----------------------------------------------------------------------*/ - (void)setJoystickControls { // Get and persist the ID of the newly selected joystick EXPORT_DROPDOWN(currentJoystick, nCurrentJoystick); // Data validation: If the JoyID is out of bounds, correct it and, if set to use real joystick, change to disabled if ( (ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoyId < 0) || (ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoyId >= cRealJoysticks) ) { ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoyId = 0; if (ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoystickMode == JOYSTICK_REALSTICK) { ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoystickMode = JOYSTICK_DISABLED; } } // Don't change the realJoystick dropdown if none is available (to keep "(None available)" selected) if (cRealJoysticks > 0) { IMPORT_DROPDOWN(realJoystick, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoyId); } IMPORT_RADIO(joystickMode, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoystickMode); IMPORT_DROPDOWN(joystickUp, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeUp); IMPORT_DROPDOWN(joystickRight, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeRight); IMPORT_DROPDOWN(joystickDown, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeDown); IMPORT_DROPDOWN(joystickLeft, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeLeft); IMPORT_DROPDOWN(joystickFire, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeFire); IMPORT_SWITCH(enableAutoFire, ConfigureParams.Joysticks.Joy[nCurrentJoystick].bEnableAutoFire); } /*----------------------------------------------------------------------*/ // Saves the setting for the joystick currently being viewed /*----------------------------------------------------------------------*/ - (void)saveJoystickControls { EXPORT_RADIO(joystickMode, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoystickMode); EXPORT_DROPDOWN(realJoystick, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nJoyId); EXPORT_DROPDOWN(joystickUp, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeUp); EXPORT_DROPDOWN(joystickRight, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeRight); EXPORT_DROPDOWN(joystickDown, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeDown); EXPORT_DROPDOWN(joystickLeft, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeLeft); EXPORT_DROPDOWN(joystickFire, ConfigureParams.Joysticks.Joy[nCurrentJoystick].nKeyCodeFire); EXPORT_SWITCH(enableAutoFire, ConfigureParams.Joysticks.Joy[nCurrentJoystick].bEnableAutoFire); } /*----------------------------------------------------------------------*/ // Saves the settings for all controls /*----------------------------------------------------------------------*/ - (void)saveAllControls { // Export the preference controls into their vars EXPORT_TEXTFIELD(cartridge, ConfigureParams.Rom.szCartridgeImageFileName); EXPORT_TEXTFIELD(imgeDir, ConfigureParams.DiskImage.szDiskImageDirectory); EXPORT_TEXTFIELD(keyboard, ConfigureParams.Keyboard.szMappingFileName); EXPORT_TEXTFIELD(printit, ConfigureParams.Printer.szPrintToFileName); EXPORT_TEXTFIELD(rs232In, ConfigureParams.RS232.szInFileName); EXPORT_TEXTFIELD(TOS, ConfigureParams.Rom.szTosImageFileName); EXPORT_TEXTFIELD(midiOut, ConfigureParams.Midi.sMidiOutFileName); EXPORT_TEXTFIELD(rs232Out, ConfigureParams.RS232.szOutFileName); //Disk A EXPORT_SWITCH(enableDriveA, ConfigureParams.DiskImage.EnableDriveA); if([driveA_NumberOfHeads state]==NSOnState) ConfigureParams.DiskImage.DriveA_NumberOfHeads=2; else ConfigureParams.DiskImage.DriveA_NumberOfHeads=1; //Disk B EXPORT_SWITCH(enableDriveB,ConfigureParams.DiskImage.EnableDriveB); if([driveB_NumberOfHeads state]==NSOnState) ConfigureParams.DiskImage.DriveB_NumberOfHeads=2; else ConfigureParams.DiskImage.DriveB_NumberOfHeads=1; EXPORT_SWITCH(autoInsertB, ConfigureParams.DiskImage.bAutoInsertDiskB); EXPORT_SWITCH(blitter, ConfigureParams.System.bBlitter); EXPORT_SWITCH(bootFromHD, ConfigureParams.HardDisk.bBootFromHardDisk); // 1.9.0 new options in Disk EXPORT_SWITCH(bFilenameConversion, ConfigureParams.HardDisk.bFilenameConversion); if ([nGemdosDrive state]==NSOnState) ConfigureParams.HardDisk.nGemdosDrive = DRIVE_SKIP; else ConfigureParams.HardDisk.nGemdosDrive = DRIVE_C; EXPORT_SWITCH(captureOnChange, ConfigureParams.Screen.bCrop); EXPORT_RADIO(colorDepth, ConfigureParams.Screen.nVdiColors); EXPORT_SWITCH(compatibleCpu, ConfigureParams.System.bCompatibleCpu); EXPORT_RADIO(cpuClock, ConfigureParams.System.nCpuFreq); EXPORT_RADIO(cpuType, ConfigureParams.System.nCpuLevel); EXPORT_SWITCH(enableMidi, ConfigureParams.Midi.bEnableMidi); EXPORT_SWITCH(enablePrinter, ConfigureParams.Printer.bEnablePrinting); EXPORT_SWITCH(enableRS232, ConfigureParams.RS232.bEnableRS232); EXPORT_SWITCH(enableSound, ConfigureParams.Sound.bEnableSound); EXPORT_DROPDOWN(frameSkip, ConfigureParams.Screen.nFrameSkips); EXPORT_RADIO(keyboardMapping, ConfigureParams.Keyboard.nKeymapType); EXPORT_RADIO(machineType, ConfigureParams.System.nMachineType); EXPORT_RADIO(monitor, ConfigureParams.Screen.nMonitorType); EXPORT_SWITCH(patchTimerD, ConfigureParams.System.bPatchTimerD); EXPORT_RADIO(ramSize, ConfigureParams.Memory.STRamSize_KB); // MS 12-2016 EXPORT_SWITCH(fastFDC, ConfigureParams.DiskImage.FastFloppy); EXPORT_SWITCH(useBorders, ConfigureParams.Screen.bAllowOverscan); EXPORT_SWITCH(useVDIResolution, ConfigureParams.Screen.bUseExtVdiResolutions); EXPORT_RADIO(floppyWriteProtection, ConfigureParams.DiskImage.nWriteProtection); EXPORT_RADIO(HDWriteProtection, ConfigureParams.HardDisk.nWriteProtection); // EXPORT_SWITCH(zoomSTLowRes, ConfigureParams.Screen.bZoomLowRes); EXPORT_SWITCH(showStatusBar,ConfigureParams.Screen.bShowStatusbar); EXPORT_DROPDOWN(enableDSP,ConfigureParams.System.nDSPType); EXPORT_SWITCH(falconTTRatio, ConfigureParams.Screen.bAspectCorrect); EXPORT_SWITCH(fullScreen, ConfigureParams.Screen.bFullScreen); EXPORT_SWITCH(ledDisks, ConfigureParams.Screen.bShowDriveLed); EXPORT_SWITCH(keepDesktopResolution, ConfigureParams.Screen.bKeepResolution); //v1.6.1 EXPORT_SWITCH(FastBootPatch,ConfigureParams.System.bFastBoot); EXPORT_RADIO(YMVoicesMixing,ConfigureParams.Sound.YmVolumeMixing); EXPORT_SWITCH(SDL2UseGpuScaling, ConfigureParams.Screen.bUseSdlRenderer); EXPORT_SWITCH(SDL2Resizable, ConfigureParams.Screen.bResizable); EXPORT_SWITCH(SDL2UseVSync, ConfigureParams.Screen.bUseVsync); EXPORT_NTEXTFIELD(maxZoomedWidth, ConfigureParams.Screen.nMaxWidth); EXPORT_NTEXTFIELD(maxZoomedHeight, ConfigureParams.Screen.nMaxHeight); // VIDEO TIMING switch([videoTiming selectedTag]) { case 0: ConfigureParams.System.VideoTimingMode=VIDEO_TIMING_MODE_RANDOM; break; case 1: ConfigureParams.System.VideoTimingMode=VIDEO_TIMING_MODE_WS1; break; case 2: ConfigureParams.System.VideoTimingMode=VIDEO_TIMING_MODE_WS2; break; case 3: ConfigureParams.System.VideoTimingMode=VIDEO_TIMING_MODE_WS3; break; case 4: ConfigureParams.System.VideoTimingMode=VIDEO_TIMING_MODE_WS4; break; } int ttramsize_MB=[TTRAMSizeValue intValue]; //JV 12-2016 ConfigureParams.Memory.TTRamSize_KB=1024*ttramsize_MB; //EXPORT_NTEXTFIELD(TTRAMSizeValue, ConfigureParams.Memory.TTRamSize_KB); // MS 12-2016 EXPORT_SWITCH(cycleExactCPU, ConfigureParams.System.bCycleExactCpu); EXPORT_SWITCH(MMU_Emulation, ConfigureParams.System.bMMU); EXPORT_SWITCH(adressSpace24, ConfigureParams.System.bAddressSpace24); if(FPUType.selectedCell.tag == 0 ) ConfigureParams.System.n_FPUType = FPU_NONE; else if(FPUType.selectedCell.tag == 1 ) ConfigureParams.System.n_FPUType = FPU_68881; else if(FPUType.selectedCell.tag == 2 ) ConfigureParams.System.n_FPUType = FPU_68882; else if(FPUType.selectedCell.tag == 3 ) ConfigureParams.System.n_FPUType = FPU_CPU; //not needed anymore //EXPORT_SWITCH(CompatibleFPU, ConfigureParams.System.bCompatibleFPU); ConfigureParams.Sound.nPlaybackFreq = nSoundFreqs[[[playbackQuality selectedCell] tag]]; switch (resolution.selectedCell.tag ) { case 0: ConfigureParams.Screen.nVdiWidth = 640; ConfigureParams.Screen.nVdiHeight = 480; break; case 1: ConfigureParams.Screen.nVdiWidth = 800; ConfigureParams.Screen.nVdiHeight = 600; break; case 2: ConfigureParams.Screen.nVdiWidth = 1024; ConfigureParams.Screen.nVdiHeight = 768; break; } // Define the HD flag, and export the HD path if one is selected if (hrdDisk.length > 0) { EXPORT_TEXTFIELD(hrdDisk, ConfigureParams.Acsi[0].sDeviceFile); ConfigureParams.Acsi[0].bUseDevice = true; } else { ConfigureParams.Acsi[0].bUseDevice = false; } // Define the IDE HD flag, and export the IDE HD path if one is selected if (masterIDE.length > 0) { EXPORT_TEXTFIELD(masterIDE, ConfigureParams.Ide[0].sDeviceFile); ConfigureParams.Ide[0].bUseDevice = YES; } else { ConfigureParams.Ide[0].bUseDevice = NO; } // IDE Slave if (slaveIDE.length > 0) { EXPORT_TEXTFIELD(slaveIDE, ConfigureParams.Ide[1].sDeviceFile); ConfigureParams.Ide[1].bUseDevice = YES; } else { ConfigureParams.Ide[1].bUseDevice = NO; } // Define the Gemdos flag, and export the Gemdos path if one is selected if (gemdos.length > 0) { EXPORT_TEXTFIELD(gemdos, ConfigureParams.HardDisk.szHardDiskDirectories[0]); ConfigureParams.HardDisk.bUseHardDiskDirectories = YES; } else { ConfigureParams.HardDisk.bUseHardDiskDirectories = NO; } // Save the per-joystick controls [self saveJoystickControls]; // -- MIDI [self saveMidiDropdowns]; } /*----------------------------------------------------------------------*/ // Max Zoomed Adjust - (IBAction) setWidth:(id)sender; { NSLog(@"Change Max Zoom width: %d", [sender intValue] ); maxZoomedWidth.intValue = [sender intValue] ; } /*----------------------------------------------------------------------*/ - (IBAction) setHeight:(id)sender; { NSLog(@"Change Max Zoom height: %d", [sender intValue]); maxZoomedHeight.intValue = [sender intValue] ; } /*----------------------------------------------------------------------*/ - (IBAction)setTTRAMSize:(id)sender { NSLog(@"Change TTRAMSize: %d", [sender intValue]); TTRAMSizeValue.intValue = [sender intValue] ; } +(PrefsController *)prefs { static PrefsController *prefs = nil; if (!prefs) prefs = [[PrefsController alloc] init]; return prefs; } /*----------------------------------------------------------------------*/ - (void)awakeFromNib { cartridge = [NSMutableString stringWithCapacity:50] ; [cartridge setString:@""] ; [cartridge retain] ; imgeDir = [NSMutableString stringWithCapacity:50] ; [imgeDir setString:@""] ; [imgeDir retain] ; floppyA = [NSMutableString stringWithCapacity:50] ; [floppyA setString:@""] ; [floppyA retain] ; floppyB = [NSMutableString stringWithCapacity:50] ; [floppyB setString:@""] ; [floppyB retain] ; gemdos = [NSMutableString stringWithCapacity:50] ; [gemdos setString:@""] ; [gemdos retain] ; hrdDisk = [NSMutableString stringWithCapacity:50] ; [hrdDisk setString:@""] ; [hrdDisk retain] ; masterIDE = [NSMutableString stringWithCapacity:50] ; [masterIDE setString:@""] ; [masterIDE retain] ; slaveIDE = [NSMutableString stringWithCapacity:50] ; [slaveIDE setString:@""] ; [slaveIDE retain] ; keyboard = [NSMutableString stringWithCapacity:50] ; [keyboard setString:@""] ; [keyboard retain] ; midiOut = [NSMutableString stringWithCapacity:50] ; [midiOut setString:@""] ; [midiOut retain] ; printit = [NSMutableString stringWithCapacity:50] ; [printit setString:@""] ; [printit retain] ; rs232In = [NSMutableString stringWithCapacity:50] ; [rs232In setString:@""] ; [rs232In retain] ; rs232Out = [NSMutableString stringWithCapacity:50] ; [rs232Out setString:@""] ; [rs232Out retain] ; TOS = [NSMutableString stringWithCapacity:50] ; [TOS setString:@""] ; [TOS retain] ; configNm = [NSMutableString stringWithCapacity:50] ; [configNm setString:@""] ; [configNm retain] ; opnPanel = [NSOpenPanel openPanel]; [opnPanel retain] ; savPanel = [NSSavePanel savePanel]; [savPanel retain] ; cycleExactCPU.enabled = true; MMU_Emulation.enabled = true; adressSpace24.enabled = true; TTRAMSizeValue.enabled = true; //CompatibleFPU.enabled = true; FPUType.enabled = true; bCell68060.enabled = true; } @end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/SDLMain.h000066400000000000000000000037671504763705000243400ustar00rootroot00000000000000/* SDLMain.m - main entry point for our Cocoa-ized SDL app Initial Version: Darrell Walisser Non-NIB-Code & other changes: Max Horn Modifications for Hatari by Miguel Saro and Jerome Vernet Feel free to customize this file to suit your needs */ #ifndef _SDLMain_h_ #define _SDLMain_h_ #import #import "SDL.h" @interface HatariAppDelegate : NSObject // SDLApplication// NSObject // { IBOutlet NSMenuItem *beginCaptureAnim; IBOutlet NSMenuItem *endCaptureAnim; IBOutlet NSMenuItem *beginCaptureSound; IBOutlet NSMenuItem *endCaptureSound; IBOutlet NSMenuItem *pauseMenuItem; BOOL emulationPaused; } - (IBAction)PauseMenu:(id)sender; - (IBAction)openConfig:(id)sender; - (IBAction)saveConfig:(id)sender; - (IBAction)prefsMenu:(id)sender; //- (IBAction)openPreferences:(id)sender; - (IBAction)warmReset:(id)sender; - (IBAction)coldReset:(id)sender; - (IBAction)insertDiskA:(id)sender; - (IBAction)insertDiskB:(id)sender; - (IBAction)help:(id)sender; - (IBAction)compat:(id)sender; - (IBAction)captureScreen:(id)sender; - (IBAction)captureAnimation:(id)sender; - (IBAction)endCaptureAnimation:(id)sender; //- (IBAction)captureAnimation_AVI:(id)sender;; //- (IBAction)endCaptureAnimation_AVI:(id)sender; - (IBAction)captureSound:(id)sender; - (IBAction)endCaptureSound:(id)sender; - (IBAction)saveMemorySnap:(id)sender; - (IBAction)restoreMemorySnap:(id)sender; - (IBAction)doFullScreen:(id)sender; - (IBAction)debugUI:(id)sender; - (IBAction)quit:(id)sender; - (BOOL)validateMenuItem:(NSMenuItem*)item; - (void)setupWorkingDirectory:(BOOL)shouldChdir ; - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename ; - (void)insertDisk:(int)disque ; - (BOOL)validateMenuItem:(NSMenuItem*)item ; - (NSString*)displayFileSelection:(const char*)pathInParams preferredFileName:(NSString*)preferredFileName allowedExtensions:(NSArray*)allowedExtensions ; @end #endif /* _SDLMain_h_ */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/SDLMain.m000066400000000000000000000375331504763705000243430ustar00rootroot00000000000000/* SDLMain.m - main entry point for our Cocoa-ized SDL app Initial Version: Darrell Walisser Non-NIB-Code & other changes: Max Horn Modifications for Hatari by Miguel Saro and Jerome Vernet Feel free to customize this file to suit your needs */ /* Use this flag to determine whether we use SDLMain.nib or not */ #define SDL_USE_NIB_FILE 1 /* Use this flag to determine whether we use CPS (docking) or not */ #define SDL_USE_CPS 1 #import "SDL.h" #import "SDLMain.h" #import // for MAXPATHLEN #import // for Hatari #import "dialog.h" #import "floppy.h" #import "reset.h" #import "screenSnapShot.h" #import "memorySnapShot.h" #import "sound.h" #import "screen.h" #import "PrefsController.h" #import "Shared.h" #import "video.h" #import "avi_record.h" #import "debugui.h" #import "change.h" extern void Main_RequestQuit(int exitval) ; #ifdef SDL_USE_CPS // Portions of CPS.h typedef struct CPSProcessSerNum { UInt32 lo; UInt32 hi; } CPSProcessSerNum; extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); #endif // SDL_USE_CPS static int gArgc; static char **gArgv; static BOOL gFinderLaunch = NO ; static BOOL gCalledAppMainline = NO ; // The main class of the application, the application's delegate // @implementation HatariAppDelegate // Set the working directory to the .app's parent directory - (void) setupWorkingDirectory:(BOOL)shouldChdir { if (shouldChdir) chdir([[[NSBundle mainBundle].bundlePath stringByDeletingLastPathComponent] cStringUsingEncoding:NSASCIIStringEncoding]) ; } /* * Catch document open requests...this lets us notice files when the app * was launched by double-clicking a document, or when a document was * dragged/dropped on the app's icon. You need to have a * CFBundleDocumentsType section in your Info.plist to get this message, * apparently. * * Files are added to gArgv, so to the app, they'll look like command line * arguments. Previously, apps launched from the finder had nothing but * an argv[0]. * * This message may be received multiple times to open several docs on launch. * * This message is ignored once the app's mainline has been called. */ - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { const char *temparg; size_t arglen; char *arg; char **newargv; if (!gFinderLaunch) // MacOS is passing command line args. return FALSE; if (gCalledAppMainline) // app has started, ignore this document. return FALSE; temparg = filename.UTF8String ; arglen = SDL_strlen(temparg) + 1 ; arg = (char *) SDL_malloc(arglen) ; if (arg == NULL) return FALSE; newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)) ; if (newargv == NULL) { SDL_free(arg); return FALSE; } gArgv = newargv ; SDL_strlcpy(arg, temparg, arglen) ; gArgv[gArgc++] = arg ; gArgv[gArgc] = NULL ; return TRUE; } /*----------------------------------------------------------------------*/ // Called when the internal event loop has just started running /*----------------------------------------------------------------------*/ - (void) applicationDidFinishLaunching: (NSNotification *) note { int status; // Set the working directory to the .app's parent directory [self setupWorkingDirectory:gFinderLaunch]; //setenv ("SDL_ENABLEAPPEVENTS", "1", 1) ; // Hand off to main application code emulationPaused=NO; gCalledAppMainline = TRUE; status = SDL_main (gArgc, gArgv) ; // We're done, thank you for playing exit(status) ; } /*----------------------------------------------------------------------*/ // Hatari Stuff /*----------------------------------------------------------------------*/ - (IBAction)prefsMenu:(id)sender { static int in_propdialog = 0; if (in_propdialog) return ; ++in_propdialog ; Dialog_DoProperty() ; --in_propdialog; } /*- (IBAction) openPreferences:(id)sender { [[PrefsController prefs] loadPrefs:sender]; } // */ /*----------------------------------------------------------------------*/ - (IBAction)debugUI:(id)sender { DebugUI(REASON_USER); } /*----------------------------------------------------------------------*/ - (IBAction)warmReset:(id)sender { if ([NSApp myAlerte:NSAlertStyleInformational Txt:localize(@"Warm reset!") firstB:localize(@"OK") alternateB:localize(@"Cancel") otherB:nil informativeTxt:localize(@"Really reset the emulator?")] == NSAlertFirstButtonReturn ) Reset_Warm(); } /*----------------------------------------------------------------------*/ - (IBAction)coldReset:(id)sender { if ([NSApp myAlerte:NSAlertStyleInformational Txt:localize(@"Cold reset") firstB:localize(@"OK") alternateB:localize(@"Cancel") otherB:nil informativeTxt:localize(@"Really reset the emulator?")] == NSAlertFirstButtonReturn ) Reset_Cold(); } /*----------------------------------------------------------------------*/ - (IBAction)insertDiskA:(id)sender { [self insertDisk:0] ; } /*----------------------------------------------------------------------*/ - (IBAction)insertDiskB:(id)sender { [self insertDisk:1] ; } /*----------------------------------------------------------------------*/ - (void)insertDisk:(int)disque { char szPath[FILENAME_MAX] ; NSString *aDisk ; aDisk = [NSApp hopenfile:NO defoDir:nil defoFile:@""] ; if (aDisk.length == 0) return ; // user canceled [aDisk getCString:szPath maxLength:FILENAME_MAX-1 encoding:NSASCIIStringEncoding] ; Floppy_SetDiskFileName(disque, szPath, NULL) ; Floppy_InsertDiskIntoDrive(disque) ; } /*----------------------------------------------------------------------*/ - (IBAction)quit:(id)sender { Main_RequestQuit(0) ; } /*----------------------------------------------------------------------*/ /*Controls the enabled state of the menu items */ /*----------------------------------------------------------------------*/ - (BOOL)validateMenuItem:(NSMenuItem*)item { if (item == beginCaptureAnim) { return !Avi_AreWeRecording() ; } if (item == endCaptureAnim) { return Avi_AreWeRecording() ; } if (item == beginCaptureSound) { return !Sound_AreWeRecording() ; } if (item == endCaptureSound) { return Sound_AreWeRecording() ; } return YES; } /*----------------------------------------------------------------------*/ - (NSString*)displayFileSelection:(const char*)pathInParams preferredFileName:(NSString*)preferredFileName allowedExtensions:(NSArray*)allowedExtensions { // BOOL test ; NSString *directoryToOpen; NSString *fileToPreselect; NSString *preferredPath; NSString *extensionText; NSString *selectFile; // Get the path from the user settings preferredPath = [[NSString stringWithCString:pathInParams encoding:NSASCIIStringEncoding] stringByAbbreviatingWithTildeInPath]; if ((preferredPath != nil) && (preferredPath.length > 0)) // Determine the directory and filename { directoryToOpen = preferredPath.stringByDeletingLastPathComponent ; // Existing path: we use it fileToPreselect = preferredPath.lastPathComponent; } else { directoryToOpen = [@"~" stringByExpandingTildeInPath]; // No path: we use the user's directory fileToPreselect = preferredFileName; } ; if(bInFullScreen) Screen_ReturnFromFullScreen(); // SavePanel for choosing what file to write extensionText = [NSString stringWithFormat:localize(@"Please specify a .%@ file"), [allowedExtensions componentsJoinedByString:localize(@" or a .")] ]; selectFile = [NSApp hsavefile:YES defoDir:directoryToOpen defoFile:fileToPreselect types:allowedExtensions titre:extensionText ] ; if (selectFile.length != 0 ) return selectFile ; return nil; } /*----------------------------------------------------------------------*/ - (IBAction)captureScreen:(id)sender { GuiOsx_Pause(false); ScreenSnapShot_SaveScreen(); GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)captureAnimation:(id)sender { GuiOsx_Pause(false); if(!Avi_AreWeRecording()) { NSString* path = [self displayFileSelection:ConfigureParams.Video.AviRecordFile preferredFileName:@"hatari.avi" allowedExtensions:@[@"avi"]]; if(path) { GuiOsx_ExportPathString(path, ConfigureParams.Video.AviRecordFile, sizeof(ConfigureParams.Video.AviRecordFile)); Avi_StartRecording_WithConfig (); } } else { Avi_StopRecording(); } GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)endCaptureAnimation:(id)sender { GuiOsx_Pause(false); Avi_StopRecording(); GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)captureSound:(id)sender { GuiOsx_Pause(true); NSString* path = [self displayFileSelection:ConfigureParams.Sound.szYMCaptureFileName preferredFileName:@"hatari.wav" allowedExtensions:@[@"ym", @"wav"]]; if(path) { GuiOsx_ExportPathString(path, ConfigureParams.Sound.szYMCaptureFileName, sizeof(ConfigureParams.Sound.szYMCaptureFileName)); Sound_BeginRecording(ConfigureParams.Sound.szYMCaptureFileName); } GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)endCaptureSound:(id)sender { GuiOsx_Pause(false); Sound_EndRecording(); GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)saveMemorySnap:(id)sender { GuiOsx_Pause(true); NSString* path = [self displayFileSelection:ConfigureParams.Memory.szMemoryCaptureFileName preferredFileName:@"hatari.sav" allowedExtensions:@[@"sav"]]; if(path) { GuiOsx_ExportPathString(path, ConfigureParams.Memory.szMemoryCaptureFileName, sizeof(ConfigureParams.Memory.szMemoryCaptureFileName)); MemorySnapShot_Capture(ConfigureParams.Memory.szMemoryCaptureFileName, TRUE); } GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)restoreMemorySnap:(id)sender { NSString *directoryToOpen; NSString *fileToPreselect; NSString *oldPath ; NSString *newPath ; GuiOsx_Pause(true); // Get the path from the user settings oldPath = [NSString stringWithCString:(ConfigureParams.Memory.szMemoryCaptureFileName) encoding:NSASCIIStringEncoding]; if ((oldPath != nil) && (oldPath.length > 0)) // Determine directory and filename { directoryToOpen = oldPath.stringByDeletingLastPathComponent ; // existing path: we use it. fileToPreselect = oldPath.lastPathComponent ; } else { directoryToOpen = @"~".stringByExpandingTildeInPath ; // Currently no path: we use user's directory fileToPreselect = nil; } ; newPath = [NSApp hopenfile:NO defoDir:directoryToOpen defoFile:fileToPreselect] ; if (newPath.length != 0) // Perform the memory snapshot load MemorySnapShot_Restore([newPath cStringUsingEncoding:NSASCIIStringEncoding], TRUE); GuiOsx_Resume(); } /*----------------------------------------------------------------------*/ - (IBAction)doFullScreen:(id)sender { // A call to Screen_EnterFullScreen() would be required, but this causes a crash when using // SDL runtime 1.2.11, probably due to conflicts between Cocoa and SDL. // Therefore we simulate the fullscreen key press instead SDL_KeyboardEvent event; memset(&event, 0, sizeof(event)); event.type = SDL_KEYDOWN; event.state = SDL_PRESSED; event.keysym.sym = SDLK_F11; SDL_PushEvent((SDL_Event*)&event); // Send the F11 key press event.type = SDL_KEYUP; event.state = SDL_RELEASED; SDL_PushEvent((SDL_Event*)&event); // Send the F11 key release } /*----------------------------------------------------------------------*/ - (IBAction)help:(id)sender { NSString *the_help; the_help = [[NSBundle mainBundle] pathForResource:@"manual" ofType:@"html" inDirectory:@"HatariHelp"]; [[NSWorkspace sharedWorkspace] openFile:the_help]; } /*----------------------------------------------------------------------*/ - (IBAction)compat:(id)sender { NSString *the_help ; the_help = [[NSBundle mainBundle] pathForResource:@"compatibility" ofType:@"html" inDirectory:@"HatariHelp"] ; [[NSWorkspace sharedWorkspace] openFile:the_help]; } /*----------------------------------------------------------------------*/ - (IBAction)PauseMenu:(id)sender { if(!emulationPaused) { GuiOsx_Pause(true); emulationPaused=YES; [pauseMenuItem setState:NSOnState]; } else { GuiOsx_Resume(); emulationPaused=NO; [pauseMenuItem setState:NSOffState]; } } /*----------------------------------------------------------------------*/ - (IBAction)openConfig:(id)sender { BOOL applyChanges ; char szPath[FILENAME_MAX] ; NSString *ConfigFile, *newCfg ; CNF_PARAMS CurrentParams; applyChanges = true ; ConfigFile = [NSString stringWithCString:(sConfigFileName) encoding:NSASCIIStringEncoding]; // Backup of configuration settings to CurrentParams (which we will only // commit back to the configuration settings if choosing user confirm) CurrentParams = ConfigureParams; bool bWasRunning = GuiOsx_Pause(true); newCfg = [NSApp hopenfile:NO defoDir:nil defoFile:ConfigFile] ; if (newCfg.length != 0) { [newCfg getCString:szPath maxLength:FILENAME_MAX-1 encoding:NSASCIIStringEncoding] ; // get Cstring szPath Configuration_Load(szPath) ; // Load the config into ConfigureParams strcpy(sConfigFileName,szPath) ; // Refresh all the controls to match ConfigureParams if (Change_DoNeedReset(&CurrentParams, &ConfigureParams)) applyChanges = [NSApp myAlerte:NSAlertStyleInformational Txt:localize(@"Reset the emulator") firstB:localize(@"Don't reset") alternateB:localize(@"Reset") otherB:nil informativeTxt:@"" ] == NSAlertSecondButtonReturn ; if (applyChanges) Change_CopyChangedParamsToConfiguration(&CurrentParams, &ConfigureParams, true); // Ok with Reset else ConfigureParams = CurrentParams; //Restore previous Params. } ; if (bWasRunning) { GuiOsx_Resume(); } } /*----------------------------------------------------------------------*/ - (IBAction)saveConfig:(id)sender { } @end /*----------------------------------------------------------------------*/ static int IsRootCwd(void) { char buf[MAXPATHLEN]; char *cwd = getcwd(buf, sizeof (buf)); return (cwd && (strcmp(cwd, "/") == 0)); } /*----------------------------------------------------------------------*/ static int IsTenPointNineOrLater(void) { // OK for 10.9, but before ?? NSOperatingSystemVersion systemVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; return (systemVersion.majorVersion == 10 && systemVersion.minorVersion >= 9) || systemVersion.majorVersion > 10; } /*----------------------------------------------------------------------*/ static int IsFinderLaunch(const int argc, char **argv) { /* -psn_XXX is passed if we are launched from Finder in 10.8 and earlier */ if (argc >= 2 && strncmp(argv[1], "-psn", 4) == 0) { return 1; } if (IsTenPointNineOrLater() && argc == 1 && IsRootCwd()) { /* we might still be launched from the Finder; on 10.9+, you might not get the -psn command line anymore. Check version, if there's no command line, and if our current working directory is "/". */ return 1; } return 0; /* not a Finder launch. */ } #ifdef main # undef main #endif /*----------------------------------------------------------------------*/ // Main entry point to executable - should *not* be SDL_main! /*----------------------------------------------------------------------*/ int main (int argc, char **argv) { // Copy the arguments into a global variable if (IsFinderLaunch(argc, argv)) { gArgv = (char **) SDL_malloc(sizeof (char *) * 2); gArgv[0] = argv[0]; gArgv[1] = NULL; gArgc = 1; gFinderLaunch = YES; } else { int i; gArgc = argc; gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); for (i = 0; i <= argc; i++) gArgv[i] = argv[i]; gFinderLaunch = NO; } #if SDL_USE_NIB_FILE NSApplicationMain (argc, (const char**)argv); #else CustomApplicationMain (argc, argv); #endif return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/Shared.h000066400000000000000000000044031504763705000243030ustar00rootroot00000000000000/* Hatari - Shared.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. M. SARO 2013 */ #import // add some macro for easy writing #define localize(laklef) [[NSBundle mainBundle] localizedStringForKey:laklef value:(laklef != nil ? laklef : @"???") table:@"Localizable"] // disk extensions allowed in open box #define allF @"st",@"msa",@"dim",@"gz",@"zip",@"stx",@"ipf",@"raw",@"ctr" // Wrapper to run an NSWindow modally @protocol NSWindowDelegate; @interface ModalWrapper : NSWindowController { IBOutlet NSWindow *modalWindow; } - (void)runModal:(NSWindow*)window; - (void)windowWillClose:(NSNotification*)notification; @end // Helper function to write the contents of a path as an NSString to a string void GuiOsx_ExportPathString(NSString* path, char* szTarget, size_t cchTarget); // Pauses emulation and gets ready to use Cocoa UI bool GuiOsx_Pause(bool); // Switches back to emulation mode and resume emulation void GuiOsx_Resume(void); // Add method for general Usage // @interface NSApplication (service) // Some useful tools // choose file to open - (NSString *)hopenfile:(BOOL)chooseDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile ; - (NSString *)hopenfile:(BOOL)chooseDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile titre:(NSString *)titre ; // choose file to save - (NSString *)hsavefile:(BOOL)creatDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile types:(NSArray *)types ; - (NSString *)hsavefile:(BOOL)creatDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile types:(NSArray *)types titre:(NSString *)titre ; // Return localized path, Full path or partial path. - (NSString *)localpath:(NSString *)thepath ; // Full - (NSString *)pathUser:(NSString *)thepath ; // Partial if possible. // Alert available 10.4 to 10.9 (styles: NSAlertStyleWarning, NSAlertStyleInformational, NSAlertStyleCritical) // return: NSAlertDefaultReturn, NSAlertAlternateReturn, and NSAlertOtherReturn. - (NSInteger)myAlerte:(NSUInteger)style Txt:(NSString *)Txt firstB:(NSString *)firstB alternateB:(NSString *)alternateB otherB:(NSString *)otherB informativeTxt:(NSString *)informativeT ; @end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/Shared.m000066400000000000000000000170241504763705000243130ustar00rootroot00000000000000/* Hatari - Shared.m This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Helper code used by the other Cocoa code files June 2006, Sébastien Molines - Created 2013, 2016 M. SARO */ #import #import "Shared.h" #import "AlertHooks.h" #import "main.h" @implementation ModalWrapper // Runs an NSWindow modally - (void)runModal:(NSWindow*)window { // Grab the window modalWindow = window; // Set the window's delegate [window setDelegate:self]; // Change emulation and UI state bool bWasRunning = GuiOsx_Pause(true); // Run it as modal [NSApp runModalForWindow:window]; // Restore emulation and UI state if (bWasRunning) { GuiOsx_Resume(); } } // On closure of the NSWindow, end the modal session - (void) windowWillClose:(NSNotification *)notification { NSWindow *windowAboutToClose = notification.object ; // Is this our modal window? if (windowAboutToClose == modalWindow) { // Stop the modal loop [NSApp stopModal]; } } @end /*-----------------------------------------------------------------------*/ /*Helper function to write the contents of a path as an NSString to a string*/ /*----------------------------------------------------------------------*/ void GuiOsx_ExportPathString(NSString* path, char* szTarget, size_t cchTarget) { NSCAssert((szTarget), @"Target buffer must not be null."); NSCAssert((cchTarget > 0), @"Target buffer size must be greater than zero."); // Copy the string getCString:maxLength:encoding: [path getCString:szTarget maxLength:cchTarget-1 encoding:NSASCIIStringEncoding] ; } /*----------------------------------------------------------------------*/ /*Pauses emulation */ /* Added the visualize option for 2.0.0 */ /*----------------------------------------------------------------------*/ bool GuiOsx_Pause(bool visualize) { // Pause emulation return Main_PauseEmulation(visualize); } /*-----------------------------------------------------------------------*/ /*Switches back to emulation mode */ /*----------------------------------------------------------------------*/ void GuiOsx_Resume(void) { // Resume emulation Main_UnPauseEmulation(); } /*----------------------------------------------------------------------*/ // Add global services. 6 methods /*----------------------------------------------------------------------*/ @implementation NSApplication (service) // Open file or directory // - (NSString *)hopenfile:(BOOL)chooseDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile { return [self hopenfile:chooseDir defoDir:defoDir defoFile:defoFile titre:nil] ; } /*----------------------------------------------------------------------*/ - (NSString *)hopenfile:(BOOL)chooseDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile titre:(NSString *)titre { NSOpenPanel *openPanel ; NSArray *lesURLs = nil ; BOOL btOk ; openPanel = [NSOpenPanel openPanel]; openPanel.canChooseDirectories = chooseDir ; openPanel.canChooseFiles = !chooseDir; openPanel.allowsMultipleSelection = NO ; if (titre != nil) openPanel.title = titre ; // if ([openPanel respondsToSelector:@selector(setDirectoryURL:)]) { if (defoDir!=nil) openPanel.directoryURL = [NSURL fileURLWithPath:defoDir isDirectory:YES] ; // A partir de 10.6 if (defoFile!=nil) openPanel.nameFieldStringValue = defoFile ; btOk = [openPanel runModal] == NSModalResponseOK ; // Ok ? } // else // btOk = [openPanel runModalForDirectory:defoDir file:defoFile] == NSModalResponseOK; // avant 10.6 if (btOk) { lesURLs = openPanel.URLs ; if ((lesURLs != nil) && (lesURLs.count != 0)) return [[lesURLs objectAtIndex:0] path] ; } ; return @"" ; } /*----------------------------------------------------------------------*/ // Save file // - (NSString *)hsavefile:(BOOL)creatDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile types:(NSArray *)types { return [self hsavefile:creatDir defoDir:defoDir defoFile:defoFile types:types titre:nil] ; } /*----------------------------------------------------------------------*/ - (NSString *)hsavefile:(BOOL)creatDir defoDir:(NSString *)defoDir defoFile:(NSString *)defoFile types:(NSArray *)types titre:(NSString *)titre { NSSavePanel *savPanel ; NSURL *lURL ; BOOL btOk ; savPanel = [NSSavePanel savePanel]; savPanel.canCreateDirectories = creatDir ; if (types != nil) { savPanel.allowedFileTypes = types ; savPanel.allowsOtherFileTypes = YES ; } ; if (titre != nil) savPanel.title = titre ; // if ([savPanel respondsToSelector:@selector(setDirectoryURL:)]) { if (defoDir!=nil) savPanel.directoryURL = [NSURL fileURLWithPath:defoDir isDirectory:YES] ; // A partir de 10.6 if (defoFile!=nil) savPanel.nameFieldStringValue = defoFile ; btOk = [savPanel runModal] == NSModalResponseOK; // Ok? } // else // btOk = [savPanel runModalForDirectory:defoDir file:defoFile] == NSOKButton ; // Ok ? deprecated en 10.6 if (btOk) { lURL = savPanel.URL ; if (lURL != nil) return lURL.path ; } ; return @"" ; } /*----------------------------------------------------------------------*/ // Returne localized path // - (NSString *)localpath:(NSString *)thepath :(NSFileManager *)afilemanager { NSString *thend ; NSArray *thelist ; if (thepath == nil) return @"" ; if (thepath.length == 0) return @"" ; if (![afilemanager fileExistsAtPath:thepath]) { thend = thepath.lastPathComponent ; return [[self localpath:thepath.stringByDeletingLastPathComponent :afilemanager] stringByAppendingPathComponent:thend] ; } ; thelist = [afilemanager componentsToDisplayForPath:thepath] ; // convert in matrix if ( thelist.count != 0) return [NSString pathWithComponents:thelist] ; // return localized path else return thepath ; } - (NSString *)localpath:(NSString *)thepath // return a full localized path { NSFileManager *afilemanager = [NSFileManager defaultManager] ; // call "default manager" return [self localpath:thepath :afilemanager] ; } /*----------------------------------------------------------------------*/ // return a localized path related to user home directoryr ~/ // - (NSString *)pathUser:(NSString *)thepath { NSString *here ; NSString *apath ; apath = [self localpath:thepath] ; if (apath.length == 0) return @"" ; here = [self localpath: @"~/".stringByExpandingTildeInPath ] ; if (([apath rangeOfString:here].location) != NSNotFound) return [NSString stringWithFormat:@"~%@", [apath substringFromIndex:here.length ]] ; return apath; } /*----------------------------------------------------------------------*/ // NSAlert available 10.3 to 10.9 ..... 10.11 // - (NSInteger)myAlerte:(NSUInteger)style Txt:(NSString *)Txt firstB:(NSString *)firstB alternateB:(NSString *)alternateB otherB:(NSString *)otherB informativeTxt:(NSString *)informativeT { NSAlert *lalerte ; NSInteger ret ; lalerte = [[NSAlert alloc] init] ; lalerte.alertStyle = style ; lalerte.messageText = Txt == nil ? @"Hatari" : Txt ; [lalerte addButtonWithTitle:firstB] ; if (alternateB != nil) [lalerte addButtonWithTitle:alternateB] ; if (otherB != nil) [lalerte addButtonWithTitle:otherB] ; if (informativeT!= nil) [lalerte setInformativeText:informativeT] ; ret = [lalerte runModal] ; [lalerte release] ; switch (ret) { case NSAlertFirstButtonReturn : return NSAlertFirstButtonReturn; case NSAlertSecondButtonReturn: return NSAlertSecondButtonReturn; default: return NSAlertFirstButtonReturn; } ; } @end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/en.lproj/000077500000000000000000000000001504763705000244525ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/en.lproj/InfoPlist.strings000066400000000000000000000002031504763705000277670ustar00rootroot00000000000000/* Localized versions of Info.plist keys */ CFBundleName = "Hatari"; NSHumanReadableCopyright = "Copyright © 2022, T. Huth."; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/en.lproj/Localizable.strings000066400000000000000000000070731504763705000303150ustar00rootroot00000000000000/* English */ "Insert newly created disk in"="Insert newly created disk in drive"; "Ignore"="Ignore"; "Reset the emulator"="Reset the Emulator ?"; "Must be reset"="The emulator must be reset in order to apply your changes.\nAll current work will be lost."; "Don't reset"="Don't reset"; "Reset"="Reset"; "OK"="Ok"; "Cancel"="Cancel"; "Warm reset!"="Warn reset!"; "Really reset the emulator?"="Really reset the emulator? All current work will be lost. Click Cancel to continue without reset."; "Quit" = "Quit" ; "Cold reset!"="Cold reset!"; "Detected double bus error => CPU halted!\nEmulation needs to be reset.\n" = "Detected double bus error => CPU halted!\nEmulation needs to be reset.\n" ; "The emulated system must be reset to apply these changes. Apply changes now and reset the emulator?" = "The emulated system must be reset to apply these changes. Apply changes now and reset the emulator?" ; "All unsaved data will be lost.\nDo you really want to quit?" = "All unsaved data will be lost.\nDo you really want to quit?" ; "Insert newly created disk in" = "Insert newly created disk in" ; "Ignore" = "Ignore" ; "Please specify a .%@ file" = "Please specify a .%@ file" ; " or a ." = " or a ." ; "ERROR: Cannot insert same floppy to multiple drives!" = "ERROR: Cannot insert same floppy to multiple drives!" ; "Error in Config structure (Contact author).\n" = "Error in Config structure (Contact author).\n" ; "Error, Config file not saved" = "Error, Config file not saved" ; "Can not load TOS file:" = "Can not load TOS file!\n Choose TOS File." ; "Detected a RAM TOS - this will probably not work very well!\n" = "Detected a RAM TOS - this will probably not work very well!\n" ; "Detected RAM TOS image, skipping TOS patches.\n" = "Detected RAM TOS image, skipping TOS patches.\n" ; "Only TOS versions >= 1.04 support autostarting!\n" = "Only TOS versions >= 1.04 support autostarting!\n" ; "Autostart file removed.\n" = "Autostart file removed.\n" ; "TOS versions 1.06 and 1.62 are for Atari STE only.\n ==> Switching to STE mode now.\n" = "TOS versions 1.06 and 1.62 are for Atari STE only.\n ==> Switching to STE mode now.\n" ; "TOS versions 3.0x are for Atari TT only.\n ==> Switching to TT mode now.\n" = "TOS versions 3.0x are for Atari TT only.\n ==> Switching to TT mode now.\n" ; "TOS versions 4.x are for Atari Falcon only.\n ==> Switching to Falcon mode now.\n" = "TOS versions 4.x are for Atari Falcon only.\n ==> Switching to Falcon mode now.\n" ; "TOS versions <= 1.4 work only in\nST mode and with a 68000 CPU.\n ==> Switching to ST mode with 68000 now.\n" = "TOS versions <= 1.4 work only in\nST mode and with a 68000 CPU.\n ==> Switching to ST mode with 68000 now.\n" ; "This TOS version does not work in TT/Falcon mode.\n ==> Switching to STE mode now.\n" = "This TOS version does not work in TT/Falcon mode.\n ==> Switching to STE mode now.\n" ; "TOS versions 4.x require a CPU >= 68020.\n ==> Switching to 68020 mode now.\n" = "TOS versions 4.x require a CPU >= 68020.\n ==> Switching to 68020 mode now.\n" ; "TOS versions 3.0x require a CPU >= 68030.\n ==> Switching to 68030 mode now.\n" = "TOS versions 3.0x require a CPU >= 68030.\n ==> Switching to 68030 mode now.\n" ; "Please use at least TOS v1.04 for the HD directory emulation (all required GEMDOS functionality isn't completely emulated for this TOS version)." = "Please use at least TOS v1.04 for the HD directory emulation (all required GEMDOS functionality isn't completely emulated for this TOS version)." ; "To use extended VDI resolutions, you must select a TOS >= 1.02." = "To use extended VDI resolutions, you must select a TOS >= 1.02." ; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/en.lproj/SDLMain.xib000066400000000000000000010320261504763705000264110ustar00rootroot00000000000000 TOS image: Cartridge image: Up: Right: Down : Left: Fire: Debug Todo Info Warn Error Fatal Debug Todo Info Warn Error Fatal Tracks: hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/fr.lproj/000077500000000000000000000000001504763705000244575ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/fr.lproj/InfoPlist.strings000066400000000000000000000002031504763705000277740ustar00rootroot00000000000000/* Localized versions of Info.plist keys */ CFBundleName = "Hatari"; NSHumanReadableCopyright = "Copyright © 2022, T. Huth."; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/fr.lproj/Localizable.strings000066400000000000000000000074661504763705000303300ustar00rootroot00000000000000/* French */ "Insert newly created disk in"="Inserer la disquette créée dans le lecteur"; "Ignore"="Ignorer"; "Reset the emulator"="Reset de l'Emulateur ?"; "Must be reset"="Hatari doit être réinitialisé pour appliquer vos modifications.\nTout travail non enregistré sera perdu."; "Don't reset"="Ne pas réinitialiser"; "Reset"="Reset"; "OK"="Ok"; "Cancel"="Annuler"; "Warm reset!"="Reset à chaud"; "Really reset the emulator?"="Tout travail ne sera pas sauvegardé. Cliquez sur Annuler pour continuer sans Reset"; "Quit" = "Quitter" ; "Cold reset!"="Reset à froid"; "Detected double bus error => CPU halted!\nEmulation needs to be reset.\n" = "Double erreur de bus => Arrêt du CPU\n Reset de l'émulation obligatoire." ; "The emulated system must be reset to apply these changes. Apply changes now and reset the emulator?" = "Pour appliquer vos changements,l'émulateur doit être re-démarrer\nAppliquer les changements et re-démarrer?" ; "All unsaved data will be lost.\nDo you really want to quit?" = "Les données non sauvées seront perdues\n Quitter quand même ?" ; "Insert newly created disk in" = "Insérer le nouveau disque créé" ; "Ignore" = "Ignorer" ; "Please specify a .%@ file" = "SVP, spécifier un fichier .%@ " ; " or a ." = " ou ." ; "ERROR: Cannot insert same floppy to multiple drives!" = "ERREUR: Impossible d'insérer une disquette dans plusieurs lecteurs" ; "Error in Config structure (Contact author).\n" = "Erreur dans la structure de configuration (contacter les auteurs).\n" ; "Error, Config file not saved" = "Erreur, fichier de configuration non sauvé." ; "Can not load TOS file:" = "Impossible de charger le fichier TOS\nChoisir un fichier TOS" ; "Detected a RAM TOS - this will probably not work very well!\n" = "Le TOS RAM détecté ne fonctionnera probablement pas très bien!\n" ; "Detected RAM TOS image, skipping TOS patches.\n" = "Une image de TOS RAM détectée -> pas de correction du TOS.\n" ; "Only TOS versions >= 1.04 support autostarting!\n" = "seuls les versions de TOS >= 1.04 supportent l'auto-démarrage!\n" ; "Autostart file removed.\n" = "Suppression du fichier d'auto-démarrage.\n" ; "TOS versions 1.06 and 1.62 are for Atari STE only.\n ==> Switching to STE mode now.\n" = "Les TOS 1.06 et 1.62 sont pour l'Atari STE seulement.\n ==> Mettre le mode STE maintenant.\n" ; "TOS versions 3.0x are for Atari TT only.\n ==> Switching to TT mode now.\n" = "Les TOS 3.0x sont pour l'Atari Atari TT seulement.\n ==> Mettre le mode TT maintenant.\n" ; "TOS versions 4.x are for Atari Falcon only.\n ==> Switching to Falcon mode now.\n" = "Les TOS 4.x sont pour Atari Falcon seulement.\n ==> Mettre le mode Falcon maintenant.\n" ; "TOS versions <= 1.4 work only in\nST mode and with a 68000 CPU.\n ==> Switching to ST mode with 68000 now.\n" = "Les TOS <= 1.4 marchent seulement\nen mode ST avec un CPU 68000.\n ==> Mettre le mode ST et le CPU 68000 maintenant.\n" ; "This TOS version does not work in TT/Falcon mode.\n ==> Switching to STE mode now.\n" = "Cette version du TOS ne marche pas en mode TT/Falcon.\n ==> Mettre le mode STE maintenant.\n" ; "TOS versions 4.x require a CPU >= 68020.\n ==> Switching to 68020 mode now.\n" = "Les TOS 4.x exigent un CPU >= 68020.\n ==> Mettre un CPU 68020 maintenant.\n" ; "TOS versions 3.0x require a CPU >= 68030.\n ==> Switching to 68030 mode now.\n" = "Les TOS 3.0x exigent un CPU >= 68030.\n ==> Mettre un CPU 68030 maintenant.\n" ; "Please use at least TOS v1.04 for the HD directory emulation (all required GEMDOS functionality isn't completely emulated for this TOS version)." = "SVP, un TOS >=1.04 est nécessaire pour l'émulation HD (Toutes les fonctionalités GEMDOS ne sont pas émulées avec cette version du TOS version)" ; "To use extended VDI resolutions, you must select a TOS >= 1.02." = "Pour utiliser les résolutions VDI étendues, le TOS doit être >= 1.02." ; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/fr.lproj/SDLMain.xib000066400000000000000000010240201504763705000264110ustar00rootroot00000000000000 TOS image: Cartouche image: Haut: Droit: Bas : Gauche: Debug Todo Info Warn Error Fatal Debug Todo Info Warn Error Fatal Pistes: hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/hatari.app.xcent000066400000000000000000000003651504763705000260210ustar00rootroot00000000000000 com.apple.security.device.audio-input hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/paths.m000066400000000000000000000022571504763705000242260ustar00rootroot00000000000000/* Hatari - paths.m This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. macOS Objective-C helper function for accessing user configured screenshot path */ #import #include "main.h" #include "paths.h" #include "str.h" /** * Returns the user configured screenshot path property, or if none is found * uses the macOS default of ~/Desktop/. Caller is responsible for freeing * the returned pointer. */ char *Paths_GetMacScreenShotDir(void) { /* Allocate memory for storing the path string */ char *psPath = malloc(FILENAME_MAX); if (!psPath) { fprintf(stderr, "Out of memory (Paths_GetMacScreenShotDir)\n"); exit(-1); } NSString *keyValue = nil; NSUserDefaults *defaults = [[[NSUserDefaults alloc] init] autorelease]; if(defaults != nil) { [defaults addSuiteNamed:@"com.apple.screencapture.plist"]; keyValue = [defaults stringForKey:@"location"]; } if (keyValue!=nil) { Str_Copy(psPath, [keyValue UTF8String], FILENAME_MAX); } else { snprintf(psPath, FILENAME_MAX, "%s%c%s", Paths_GetUserHome(), PATHSEP, "Desktop"); } return psPath; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-osx/stdisk.png000066400000000000000000001126541504763705000247430ustar00rootroot00000000000000PNG  IHDR44_? pHYs+PLTE)pqq"Az%D 7<@/^=p<@ABGJ;^ ^=`# 7m-y -Q,GM2Xv-HCZl0Q,DO(BCBB~CA}BDDEABED~C}@CA}B|ED@|AAEA{B?~B~DDGD@FY]VG?G>|BZ_YJ]_Y`\YYXUF]YTTWR?zC}D_YYV[VX\Z_[^`a]WYTH\UR\[[\\V[_^YX[\ZWT\^]a\=z^^a;vGL^\PW^`TZZZ\^F~`VTWTWd\YYXOa\T_d_SWW\b`>a`X[X^\VX^_Uc^\RTOU_YScXWc_aE~Aw9rINPY`eUX]KXRPOSVFz_W]b`e?sg^]Xa^6g Dwg`W2cbXPdc^Gu>ncgb8mL}TT]cfX]P?iH}cX^Tt}I}`dXi$Qy+E^\cUhh44k^`ZZN'Ki0bAMW2PkJv Id IxFkIT^T$Z̻ͭ;IT@Sd8L` X+Z 'D'W'6CP_o&&Lm1ZCj9XDDbbttU\a|*+HRȁ@B4Menfb|KӞ=WeI}ӟO_gW_n+xg߭[o{|g_/~Oŷ??7Oяŧ?7/KGG}_|7?y2ۗoO?]zg _z~0yku6z]^_5!_?-zu)@+GDv] <u77j?GG.⅑p8|7<227|Djarx/*\Rk0<999Nu _~:`~x}r]XX__:]K i\齓:+?E0px$Mz@!jD"3tOW>ɏbŢj싥c>w_AXou0bf܊bqk2x|x&uU>~c `l<|xu~~vvk9=0lB> 0x^B>_ T*ImjVs\vrҬ4{,V/+fg?^.PLֹ^ЫW9-;ߡWjUE:h h `.~fHYq|[}1X M%[垮oMLwmfccmmcmVۘ/5 =*Ḿ](T*JZm4rcc.vaqq[wVWqU;w+o\}8{p[vwp'3*@X?ҭ Q%M B33MKd4)z1xC`/Mqhj,J {.~?"vۊ>]6pveFQ͗JJcL@j+U1{:_Xy4W f`cU;w f޽{k޸qpp Zso[[l4GFC ҆3t_TUugר"Xֿ}EIRF_H | W\x|0Bߴ,ZuoLR|)iy p|6Ѭ4B}ii 1U- cX|aqe> ãf+F*UɦRF5_j\>j٫Xnvsv7s9D:ʈOwD"q.jH#;˺3}푏~PeF^MR.I$0bW`$qQ5}x? i"j0G@ŋ_W Gux{ե|~xeڵ)5|3mo!SSv~rxמ|(f- ȍUjZ4檥o*I-Hj!g!!-2::ڥJWFrj7ſ?R-t]~8ªaƌ 1az10+t}6BJݞE:^^ύOY~ x75umxe%[ /MaGs&0BUl4 ك<@oe/?m7"_r'^p8`ns48J"s _y/Pܽ纔ϧ&ϹRP޹+rh! W>󑖴cuLSh_i. %Y8=8Th''$jLs`% `+IQU8&- `_ ;&";w0mby6[^+">7:KgE*{νat>]!H˲L Kd=PGWW$%We !I>#<[ʑֵC$\<\M7Ҧ}mEC6b}i& ӥY+klR)Rqgaz'M7,K55<E7ۋ_}w[7>y3pVJ mkN,402t%L\C.®]#ҕQ$TgH# =*,f,zK;{)*u!jHGvpJˎaH>V44g-HOlE/ݦ{'E;B*⳽Ja)XoMM [M_/|'嵇7Xy^vuT]R%h>`dd;Eh.IiQ5bܶ+xtEa Z ȉQ) rEăc nh0 >oaQ&0\ӄU5h_9B;fZ,,޽;(uBȮrQ_ /& r;sd){@D;~ dݼ^_={I#'X^ſOXx(_Heuj@ ##EF!?X!=J ?  əP3ZbLS\╛a#n"MÀ22oin[*$WyV w,Q Ơ=C!PmN|V5#=Z"=2r@AB\x[QcQHF LRj7Mt"I*ph+^4x[Pb:i_n?ՙ:m3K1S,ZxfN>SنHi\|p ]&e?9 {Ph]i5DnϞ "kG}F O tk { kQ'{ ~VШ֍C;.HY$Ǡ3:]вhiVY4օtGb4[^J"~❠&b`Ý텋jYh߾u:gj3;;G,^@x̔n|roȈQJ &bի7ʰeRaij_|3j?y#C!`~"0#Ay8KQkַ_}^y( 4ς{*GK#g AG&TmX0GK+R+^Hr W.X VDCv$Atol:4}Yg-c+c9??M55 *Fu|RIOar|(_VǫxFia}0,YbΘn #ma4^烈r' ˯_\+r\^̨ H`iL?pU2`HI+I:F vW>x/_x%Eݲ#W.EV(C1S 4un -@{N!%0Y"i&Vϭ"3QĨKÅPkp]| I5_j=/=aZn<#bFv#Yg &iw.*d{R:~q;*IRR xh#ɯ'uJ"rTHO:@ӝϡ 6tw{]/\r%`i٘M) [PGbpZWoꇇ%NNjFu*pM7AScpz ݈<1m6^za,7Yln?WB/T"{ *$PaM  @t.vۺrafNjf1gq _U'@ %@6"zHfxKD|›MdZЬbQpr@rAU~;}.OGsg2_^f9x"A/fظZLboi}}r)Rh\MT'*Q_B"1% D洌 6 MGn@VE3Fo˅]2ۙZpɰf/.,*Ht:"Exz7jLWtC|:[)/^<yG{$n(.t050^iSgt{;- .eG[좬1/E= qA5_C,vbvgi$ٝь;5[!Y'f3E O[5+6􊾊u1m&~=3u *Ԁ #t"/j 2EARejT(}hЯJ&9rBo?a4+i]22nAYHu E,q P@v?5 u1jݺu ݳs 7e[upv˻,>|o9Ar{T_`O*А%\_~y<ⓇkL=4ك r@+Jh&".^˺u8C%_QTx+મFSs(h{ǥ(?W(--r}V1DdUí&{?O/08dT6d9? zgVf6ृCU_ӭ'/י}qelv>xfK!EE6vt:يRٮCHѮtEXi# +tݣh! sIDQ2*P kr &q`hl /us%U--j$S6v2U'h~az~K⶷ \55> +=A?zXР:˞B̀Ml^O]4'=Id@+4:ۥI2$d`}H$4-qڥP $h}KIpj#\ؘpS`V:dY4HEhz-%X_!Z_g kҪ]P3l8]jCgw{ \)TjrA,<Y'qkY3>x\ l\X+atYa8H/E&q r+xILW9X0C {))+`ž- OFDA\FܷJǏ^f2(+AkNɽl_rPTߪThOuRI(?gࠓ烏H^2.SK˭έ~Qta h^W%rF$.2izԯDFG+#hB܉hpj^~pE{#*G\uOڰ  Ǯbuk^ -L %,-b.ƪi]֗8%zI#| mLӀ^jRԯ݁IQmol35*9=h{{TH ͭCαEdJ~kSUF# QK!8 rf&&Ҁ ZO_.ͥJU685PE/,--2IZG;žeуvYN#R  'ii1MӼ\8w.;Lê\|pB!+ iH)q{n ٱXH 9QC)!E\b&@Y*UF W˹3ٝ,-LÓ !qD酵w/YVw KHgs3X(,}uQŖ\l1C^++FI-$F IIgH)P. Y)hiw>3nI7tbLcYCܞ@ Y$ŐJӆ&]~~mH2C33bnm <MIY ^@sb k$@ ?8`Bjr |5c6`Ym^O>'.fjv]W>=ȡȍg!֤"T=pS e'B\KdY[R`٦.!?; hoeQ-h7tG@͝6E_ԯz}RZ;#r#AfO9ܒh j5QNP6Hmyvgj|@k3,i0G:8$u'36SSpO>gK # @B%/(W]Y*B&Hhւ tw(LJZ@,pv~Y@4vfMv-8@ڰCʍ |sRU[͚Hjl$#  A̝PW9{h"ӫ +=Y`IXύ&@NmV[_{`M-7^;(y%wzQbAz*% 4k׍"P+E}ra'~dY Q$6TaHlcFÅ1XY*v69k3"|q*LBrޱTgTKWl5L~rg?l'닋6{Q H"3D? LsosgST6Y.V@nU2`L [u Å@hZ5/kN PeY>Ġ ( x'[(V(kG k[t&g,U\ϟDf'5`P)6[80?_ ?#F2tf%tjqz ƴv󝹹G PA b9aХJE}hk!H֡ZQ<WϜ/\uDz~5h/vQYA1YZ mnÞP09L=lF6)po5kz%?>˲ZX ۨ7 tA#˧8Rj V>`Zb;e02G#s4!u+7Բ3H(4C*ʜ္T]`!#U5;z *e2GV9Q$%) bԪuw 3@bpty~+N ̬*,^n튶Zgqn,fgٗga|?[qv`qz"ܼh/8?*͉l5?  ,  UT@$iIU0*Hzv(H9/^;:!,)L/L|o?A&bFBP> -OW֪2b@;Op]wH66ʷwOGRN)[ED {w6%-i;OfWg+ğg׊l2"zd:.X4mZk#:}1h R{!In78Z/-<8j #f_q54>ޮ7 6_pw0:2pZ_`Y=QJiHAvbS,:N_PT ɽ:ϘOZsTŨ2#n`,ݸ-0\5Y*! 4yչGvӋG֊r.ŠK{NALV|H$жe "I/9\k /Ncۀtիr B,ޤ+&PgDcfJnZQ+ Yx\8Ovce2.M}BƘ}0lؓrpxLVB:>&Ǻ'3&/CpArV^+vQuV$4#@,%"+Kbzٗ/ǶTI=M^8vL&J&ɶpj,"}FQa2\5pD,& %V6FPWn cţvLl(@'8BQ$htF2jDčRl̃M8םhhJ{{'Yikk"ˁ1q}#׈0Y=(ei5|fnuo޺eիo к9ĉ/DX nrW&-::?qgAyǩ8PM 0_QAQ=CN@:m[Ջ=ҝ0{E{*vb feCl $u9tKKcPRBptg04B} =}v)h e/*[lx{[ib*>CQ[CV爛Ԟ᪑non:MxMDtGܪ92Wo^&@TwI8L(d&W* ƢEYRݞϟ? QL 1@NW 9tD055IUR}4+mXl[[[F00`͜3g΋)Biov4A*^]q'f}rig12.!tYC$Þ @#Ӟhr{^(tR4KS, ^%Ft*"t' Ɲ!] Esunw0F\)r9)/mZG8]eB|dpU^9v0n Eui HPv%:B>E~|iJ(Q0{Ξ)Jȳ#/.ElA-CYH/exJ>r| iKR$X{V Nmo// I-[/35q^cGN 4 XGVAܡ(V]9]WHU9@lj4hK&SP| t|76 W}u7h`Z 2kDR3\dD.>{֕侂W.O{Wbictv.MJz乤x.^,.T@h{H >5AUtt޷OH:>ĩ6$&=\xu,%>E>}Z7ÌYjOTp L'L\['|m+cZQreSةX Ĺ!vª${]g{+q%=@_4T{ 3L=t0r t7rgSCP fڰ8U >g7Vw8K@x@871^5ۋSbzU47+l-Υ/t?H a,s8g )1uqN]{W⩕A9|T |qp1'sI$:IEKy*B_+qց{Rԯd\jdϏ~7OU[CៜaSpgT|SV=DUb]ןѦʣفg8 `sp8A|C,a{YacRql܊g_Bo@Eœ)Lؠ&1/~(쨳W%ot!|\d4KC ꓻ=aqB*nZALvL[CCwdWV8*J[+\nmU8MuX{+#77 ĄkMZ˫Dm4Eqf.KTNB4=w`c1?%9ɤ#ic%sIW3pUs%@FNե O<.y, H{F7P* ZOyqO:O7 c 3\?2۠ٔ.p ؔ W}G$S[9"B9*.8v.WTI&9umW\CŒH0 sɶ1x;spl=uFĪd GE/`x}ZdE2,N+U%|d89?2T}h"AvbjvlZ-_<.1'.Kg{Aۛɨa08'"tQ$79d :FAV1;wE1 ):A8k*`6EbD!PZ\>\_Rs9 $H%չGSbPrK Ja|}aA{+<´!80QVȜ A(:rV `; E R=:`:o ;JWp!q.^.ƽ^ ȡPg>oa!a:sI{yڴ5xjkSt`|1R._˭eQ[Év=g'w6;ކ>چ8`m'WgɡKi 1iEbG=,!U}eb{)'!gm(VPJD Dy IAt9x|(:tzFo/8${{{,Lqhӧ8⊏- vQvk\n*ϐ(onOXgָ+7bXh97߸q;nؽ51Qaߓl QS3bgp7Cgb.W/&Y}iT)M7+Ljw!f;leFBe2KS@ṕZ{>̎b#`b,V B oKs ?P8Kݺ%[N뙈Cxv ΦjRyo$tc;YJg^Ƚ@%CZ2xDW$ [E=XC_ճ@m (*cc$hwDe8Ig6x9٬,DKy{x <,{2#~(Gh:̜Q8886v|((G6 O<:GuJʄB G=gρutuyDW% u/Eb@rx{]H2029&eJ(ˊ  lzx2T_: ף_ .|p vz)#nG}߄x t"fkG4 t et7I S9SO/Q%3944];-B5/$93Q ѣ! rS9Klo0uX4MY<'WF$sԚw\XM ۵ȗf689/Qn1X0dT8HE-K$&yxh,+~nygiqZ!H3z$$$Ăۯf2R 5"!UqHQ'{BE!/Z= WnNxf;I9L*hOLH,.JXCo 9ީږh핪-8Z,N[[zuVk cj͉\aDYEX591Pr A<honp&ā1SWH:C nz=j΄NwvT|X+izդA9n~uViօ߸ID`_ٛ1aw)>>_*ʦJ6%$;Ǻ2#ۓ E OƁ RW?+";șhA_P8<ZA"Mj^|G|s-G)[8f,sϊ[q1gCgsݣ$z3"U`_61zb@s$=LAh*/o u04QqϯĤhg:hL yz \>uIf{Xsw9jU`}g<GapU<:#@xA:sB_.vgΞ eI7zɌ&)tFCh?*[)7mS3]Z倐3i WP,RSn>+ОA"057X&l0M]{; I0TT(UWWwG8q>/ /&syU[Ti~7?~7ˑs1[1f؅hQN+tQYGL0DU4Yhv2  x8h P!D0ڨ؅A^I2b-gpkeb!x"Tطvg·(ER^ NYկx~?{w5s$f8fL Aɯ:hHwB:U9ѡ2ʙ/Ă~;AaB*xE|#Yh F4n`|do%'"O9S#}=C}ptBToql~Ϗ-rٍ52j\cC8bE#3*M+R$Rtig!DL)hc<ދ~Y W>b1 :]4pn=@7 Z~plh@-#ݝc/V͟aY?exC|𷁅 Dg-bPbg#C|zg2F N߫_|aSe]OtwPGVPu-D-0B/=JM貿ii~ N;LEY-1Mav1BqAQC2Zi7鉇?}OOqY62$Tʜh^+sUs˹E&Fc{xgޒہ~눆d[] OZצV&pC8~-`k; ~xsxI_4 o^<ԇ}*Uڶ4h#sF$فhIro_EP1%E0peځΛdMjՌ 99|ݘCE<2r"OTE׍bߋcC+‚w\9"2PzT۸4 _LN AhNCdh0O-HK8x&&ou-rA)vP(9Jtnb. <P#vua! /c/%Fm|J*N؃X6 dSQ:>K)֤lޥ6AQU2i1]];5"zl"@lWWK?xI@I-!iNY:8:Qq&|G7Gˑqyޡڧª;۱oú>*>tӆW~ߊ\~ #/_x7.X/+7oowW~ӻ>Yvd,Jy|d~a~|˙ T59-~mή2 ٙV;/>{|3bkA,Kb'wE2ri|_D.]fER=gmK`Ī$|gT6WO緍+KM[,^^(JEiVU@ :\ -  ?DAb$ A^:ɼn'pcw~n,`""Yd}uq9Q=Ǔ?Ğ)?SO!Ư8կ//28}/W'w8'^5߿=KNr,jw%2"}AcXe;\SCX {hhxTt7Њcvi$ 4 ҰOe{pwV7N<>wh?iIV~҇OMp×/K]z9#HVb1cK^0C \ڼJM#Yd>vx'eIlMcOmThj]チ/Tԝq{ӗ~19zqDzx-3z|KwoQ !=g?ZL|oz\xɯk8 bm-k˵2E/54֪FsФǐ4 7,¡UJ]a fp&m+PH֟K$%=0ݍri;s0:/|0g{I]w϶}d;Bc/ѓ!|]}D<ԃJJ9o\ϲ}~s>ԸE)q&lb:Ffyxeqfm. >˝ ɦ; YIVAj&-.R'Q0hxm >.h(6p, DAsZ5m覽z-Ɣ6P.3_rC`Nd{WZ5 Iu$j _F*[ݙ"y&h0+I 'QZPgҐi:QbA,{VEn聺56 cdܖjIRHci)]MMOиף8 lr\i8̣6p&5Z-1).\ yT76^upUhL fZLiXIсgˍ>b2FEXQ`ppZpn̵6| "c/nȠBR٪|/_]%DCaU2~턏 !SgY՛ 9dEైeUa׷I)",NHm;PAGP]sQ\03+B|#y]6IV7Ȱx<,[uDQvSL8h1{jFQ/Lvn-2e1i~|J-T-| .ɞ-UEl٧ ^o4Mcdvmz=#;Q+Z ⑻?ghqɷĬ0I4լU.zY*),8~nhOn A(.ߤܘdt|Fl]&iN1Rrd_QD*H+lYDɸ=ip/ piҝ-j$nkB)xw0kkYǾbism{1$r5T,wFs t7O05qZ4Ia)۪LIJ1zu$+*ٔzŌ(kI_3 cN0Gu@b #³_7.H/UA{q2=ϐMq/s)N2f(-VJc% NN3U DJ=vB΀VMB ;f6h^gqɓIܭu(+ya8 ,N ,sb-٦'LJE: Q1p7s|&}7 t9TaI IA.T.m5.5 ?Mfϗx LP n[BJ$OwJ݇,ZQXag293\ۍ&V(bVJHSN|-ozи$d᠚9w"veq8,ϥ6!,kt(b{⧑`N.XCH t{l0 3X~ ۧ;Jrk6 ؅IHO~,h!gYE{JQYY>"+:[6]f+d h\ׁYEQ,b*9-YӋNIC2 ZUiENNZHl)b4# gFy*+s>I[\4 JSC0' E3Cpt2\uDt?{|"pDꠓ@Q {-w9ұR^!}&gmx T&zUU'I;9rN-vٴۓ|*l--,tFKvds:qdFLv/v:D;ŌHgCh7B ҴEiVg+SUfj̍Mqv"8ԙT|%j/&w@)Wi$7- f;$#AbFzdߐR氖rHf j+i1@ PYje@Êť% t:xGjERZ"N1VFq'$/.vƄ0a C"@qAN3QD>;n;1i p<m#cnܸÍ3ymU߾D㷿?>xߘO^KS6.&EZ](nESQ餸٪ )j{>@`'Y/^y >?{yxqoNj/{߿ʝKKZ"K Pn/4ǝE*_"/pM^  /w-?c{̞:uo>SMSg'asvvȑS緶N;|j[wcoxK3I: \4w60 çS2v泍HU~܃砺4\ݾky333'y}˳3j}8?\Gw=Nkˏ87^va#,t%"Y \7cۗ^ޜ9?ާ>k<=_?Ncû^އ_6s_~޵b1)fm8]H9.!z}Zn"R?{{#x?wdGf+o/]b1v䎅ݏd0eK>"4^l hG?Lwf/~qau)(c'FHG w g^v/O:(׹'z~qhTwԞS+sI"tnr qavЎJG1YL(lPʢ|=q̴f#ݹnfM]y=d]XYA\JkV1s_l/L)ܱZ,Wj5VwQW#g2Fz%YYi(džL;%ʝ@VV+|\q8=Z}^8^0va8ɲbQ~hj]V%U< uM)3z=_ 7sAKӢX"bO$v&CiAٱf؂REyZ%W7d3(Uk ;* N}gpg%O%ùu=ē WGc|p'}K&wX!$ɂ8V{]Ӱ܌Z#Ub?A}WiHL[0 0q9Dr:4!jU\SyF^/4#"%Q{e$nl 3|=jbLq. }7ǖ`tY>2J%M;aӍbKm{riz~R{BnxXDPY{ӹ㧑_GS,1ӐZ狇JҾn3`hFY%qުqd4e"6=ϿƗ%iy,fI&> ؝1Y*mY=;dm :m |nD} aN]9?/Įju';AZ4-G)@Sy>+uCl]h8t,! ܔQd4LuZQ0iB@rivA;<:H>=7FE~"Zŵ&̙U`gZ-%8hLAdF9r2QtJ#Ii ;`92skuoNЪ.38b]3Mn&eK&*daԶa%| *cvY϶`iUvAYĺ)U.҄bbkVUu96f,)N*v}=._L -lwal$emdt;#e+;Vܐdi/xJUrPҢ hhJKɄ,($w֪n{qhWhL-s݇/7L6ҤH*lEJiLo hb#skME2fYvGjzM8rŞGv$BlzNmb^OT am{;Ț=-C6;iVd{4&IԂ4o_c%:ݎe-AZB Yе52lBrrw/ҫu "UC"v8l+9hW#gJș٢,6M^<j°^#hp9VGq,d i9& }Q,@%_%zﯷZ"2Mx^=0%ԁI[P'e~ I;|m fUa%uZ,.gR_YU֗#qt#帼ކVVc?)LydcpZ^i\7E ƅ@.jAu\'0f67dz[gw O?svxoٶc-$>%#$rD`kč4e\N=z8|O_}o[۷o_<}zv؝?ĉ?u|vه]8n9'v^YqtUjdx q3]YSc=s+~ Aݜ=~G髷ܽ6ݻw\>}㬋SO'GN hQ΋ל>DFtz&/|)KP 0C '~[wɭ[|g_#.Ƕ0!8Ҟ$mhUfHќ͈4'{9 ~lv@[es vFE|cΖ$5tYM w'%e ) MyR׽@鴣?B>Ü8?_:A' r?q2hؚ-ħi9Q6b˝AoMVL2젊8j8-ݟ(;'io\{uܑjX&iHNKv*Vd@G=WEۘg h_Dn§R)4MA3}Vɦ.T[MteI$7o>8a65aH-Yr;HJtJ[W`o\vgWWFä(+eH9vՆ掟&-Ľ^=rEp.?TiMCpbO{JZXEq4Hn~5ƮrD"N9t嬑ivWk/Ux*h8]\rmV@9پU:{%̨!iOҍ(INK,ӴmƝ6)N.T煽Ծowӧ~O?;@7v7hOWжi4^@nJII]]lוm؇YHvm! Giy5sk 40   h B 2 ⠊ 5ha x}菚9Y  GZv${KCʖi^Ģ</瞿{4&Ria@2l9M@=h]T-V {BP̮>^Z߸>`mscj?q}{0I{De!%bey l}qAf+3r@33=r_THi"::v g"3m>݁墆 t+Ր#<0WT:6Sң.oi]V[\=X#PfvMg7WnoTZ"ӼծntzOṓ~ݑeڞqM є%nmGʹ %Ot 47M'4\,i/n6v{)[/] [.,e[DNtHwG.v"F4jVg qcfWc U 뙀M4A+¨: ;rj1m5Vy'3=Id H $yK㵓'"|BxΞ Z/tZp~*=xLp8&@{ʭ],vamZLJm!oϓJV#7%0Ȟ UJ @#LIIױ=)tLNt:[٭QOmكBcnHB1DZ*J@Fzk8^D摃\,zYЦ<AT'k*(Q4"i\XAg!;#Hv=،bt]c!>"G08`ݛs΅JL}.,@?*R*#I TEә ⶮ%[ls::G-<\E"{=IZ˥i*hcˁm9T@ZZϠ8PusèTikv| i,G nYܤi$bIlkܠ7da! r -Nm \n'zwOv4Ƹn8Pr?f0/4SJ@nnj9-sXjz^ѱX9;Ӧ0n=3͕']Dϯ꽵CsGΗRU3P۠bi=K Wu9Zᔸ0E.~al/HK+kP!avp<2`2T-4M5[:$q)6۷"f`/<}4cm ȍ]_{0TՔHYUOnUGa3)>J̓fN WD ڍD20Z\L0[gfE:N5t6*Ө.˵^qPEʭ:vocQ(~ϫy )9lh27mЍ(fnU u|qYp!R.=C{=l/W>8 U"s5: CIHc*䞸h&=%8pn6T"h͙HB)zRD22R(tb&(*ut4| C@;\evhWu{hDyzZI9|lhF A#ۉA`e9zfucwmѣكC 9pf|G;@TSfd2bɻ {ڼ2228{fJPn.=j?{Z;TIq\K keU8Ut⦬[k޹9}OبYXwJr,/*h0S>fs7|( PYJ,ڼy#/پၩk_u{:c# 9v{!R{A25tx s%M`ʨo4 d&iĹ[Ӛ'N wG]t-o=ۏP>ZjT @MC@D<:5Dg~Q[/ GK!fO⮙|G#D͆g=hd|jh6120wϺ,{{>m=\kOQ/!5543&TfQHaPY$ }x<>0 ?CY` 33T˰׮Nj)ڞq@FDę[ܨ"Xdrhpp(oH+گ_hkzAsexOeG&c*bBSp~ZCfO7 &~-h֑ܶ;l.+2`d*d?m=,T>eIn:ɌD>7&&t+iUއϔ2$ai);@,zX1`&Q͓Q@F[4i ĻW n Adƣ}p-^[Z go\SP49J&#&Z#-#P9IK h}>0dQñӐ7OU ~XڷB>rLp t #M^ BeFn\ 30 \ZX_{:}p=PJ6SgG.^3H ;Z)!{  q K6@J'sTH 6POkWe 1PY$06 .ptw6R [- V촞"'Bs5kVԉ4 5 A84(\l p"mcn߻zX9XWwֹT\)ڗIz3 I6 lT` 6!r};?坭vfDŚU۷icVIi衤IF^aOm`r$NI.ۗ92=|~_V)ܞ9«䬙 lHDz{CZ,|S 2_|ެL(9%ah.tEk_ur<Ű݇5Jnrx81Dz|j8nt9i#r ϣ Inaf24L=O 2fhRSrFKČt޿vtڍ}S-4!j)r&̧EX2A`1@8O)ډ9 L϶ f)i-EEI7\[#FY;b.ִrTt$%U6@ι;:959w}8plcy\ !hƔ)iK)iCЮD zk=rd_a&Yl', 4`@t'eI$b)&rGdYI|wꯎˁеt5p4ŶQڞEMEjL$Mp1vAs%^*UP`#rjy|3ۡ%A %/k.+Zw/|ȧ]08(Z;hVLSurYJO\=v3r_J$YduZbQyEd+#˪dz*¨csqY,NCxYzZNr8H=ϱ=~rL\,v(`τֳ5=bcor`{\ɛڵHIn#Vgj1;.JbzyEܷ4LZ3'q !igʥ>)z:xYN@K WHk|2R)HRLn(;YFۇ;`J asmQ6F7W4v A9LeR=#õUIf!h?|>y>/l3ŸbO }v(Eg 4s@tA; ^$ '+ϗ3 lpqkV$߿ Do`@C&?ptmW0V 9Lyq:GYw!hʙsE{F9mp{@lWcF\8n (b& ^gZ-Д\O;ֿ4(ɩɁT1|2hY?0e;h9xW T͛~hgJ mXZ= "]051199;5-t8`\P|"SQ"ۖ쵬'J, jت+hGK8Ї Lt=Q;Z2>luGsSӰ]8xIK(qGDцDmZ Y˴@c`m@?}7p9>QQ0iJ)OIr;FS ١[kQ d8zVdKkL?~N9ӧrï2шZs>I@Ӵ~SIYِ:~#2:8d}}9s_2:s/g55})HVQ_4[CKݡ)qLBbm}B>ҥzyvOOC~{kOko ;/J_7I%E <^G.Pk~?;}'6;7=s}Y^?{wc?~m\OYoj'ӿw[ݭ'1FIENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/000077500000000000000000000000001504763705000226745ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/CMakeLists.txt000066400000000000000000000007651504763705000254440ustar00rootroot00000000000000 include_directories(. ../.. ../debug ../includes ${SDL2_INCLUDE_DIRS}) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-write-strings") endif() add_library(GuiSdl dlgAbout.c dlgAlert.c dlgCpu.c dlgDevice.c dlgFileSelect.c dlgFloppy.c dlgHalt.c dlgHardDisk.c dlgJoystick.c dlgKeyboard.c dlgMain.c dlgMemory.c dlgNewDisk.c dlgRom.c dlgScreen.c dlgSound.c dlgSystem.c sdlgui.c ) target_link_libraries(GuiSdl PRIVATE ${SDL2_LIBRARIES}) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgAbout.c000066400000000000000000000040411504763705000246000ustar00rootroot00000000000000/* Hatari - dlgAbout.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Show information about the program and its license. */ const char DlgAbout_fileid[] = "Hatari dlgAbout.c"; #include "main.h" #include "version.h" #include "dialog.h" #include "sdlgui.h" static char aboutstr[] = PROG_NAME; /* The "About"-dialog: */ static SGOBJ aboutdlg[] = { { SGBOX, 0, 0, 0,0, 40,25, NULL }, { SGTEXT, 0, 0, 13,1, 12,1, aboutstr }, { SGTEXT, 0, 0, 13,2, 12,1, "=============" }, { SGTEXT, 0, 0, 1,4, 38,1, "Written by Thomas Huth and many other" }, { SGTEXT, 0, 0, 1,5, 38,1, "people around the world." }, { SGTEXT, 0, 0, 2,7, 34,1, "Please see the docs for more info!" }, { SGTEXT, 0, 0, 1,9, 38,1, "This program is free software; you can" }, { SGTEXT, 0, 0, 1,10, 38,1, "redistribute it and/or modify it under" }, { SGTEXT, 0, 0, 1,11, 38,1, "the terms of the GNU General Public" }, { SGTEXT, 0, 0, 1,12, 38,1, "License as published by the Free Soft-" }, { SGTEXT, 0, 0, 1,13, 38,1, "ware Foundation; either version 2 of" }, { SGTEXT, 0, 0, 1,14, 38,1, "the License, or (at your option) any" }, { SGTEXT, 0, 0, 1,15, 38,1, "later version." }, { SGTEXT, 0, 0, 1,17, 38,1, "This program is distributed in the" }, { SGTEXT, 0, 0, 1,18, 38,1, "hope that it will be useful, but" }, { SGTEXT, 0, 0, 1,19, 38,1, "WITHOUT ANY WARRANTY. See the GNU Ge-" }, { SGTEXT, 0, 0, 1,20, 38,1, "neral Public License for more details." }, { SGBUTTON, SG_DEFAULT, 0, 16,23, 8,1, "OK" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Show the "about" dialog: */ void Dialog_AboutDlg(void) { if ((int)strlen(aboutstr) > aboutdlg[0].w) { /* Shorten the name if it is too long */ char *p = strrchr(aboutstr, '('); if (p) *(p-1) = 0; } /* Center the program name title string */ aboutdlg[1].x = (aboutdlg[0].w - strlen(aboutstr)) / 2; SDLGui_CenterDlg(aboutdlg); SDLGui_DoDialog(aboutdlg); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgAlert.c000066400000000000000000000143561504763705000246070ustar00rootroot00000000000000/* * Hatari - dlgAlert.c - AES-like AlertBox * * Based on dlgAlert.cpp from the emulator ARAnyM, * Copyright (c) 2004 Petr Stehlik of ARAnyM dev team * * Adaptation to Hatari by Thomas Huth. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License (gpl.txt) for more details. */ const char DlgAlert_fileid[] = "Hatari dlgAlert.c"; #include #include "main.h" #include "dialog.h" #include "screen.h" #include "sdlgui.h" #include "str.h" #define MAX_LINES 4 static char dlglines[MAX_LINES][50+1]; #ifdef ALERT_HOOKS // The alert hook functions extern bool HookedAlertNotice(const char* szMessage); // Must return true if OK clicked, false otherwise extern bool HookedAlertQuery(const char* szMessage); // Must return true if OK clicked, false otherwise #endif #define DLGALERT_OK 5 #define DLGALERT_CANCEL 6 /* The "Alert"-dialog: */ static SGOBJ alertdlg[] = { { SGBOX, 0, 0, 0,0, 52,7, NULL }, { SGTEXT, 0, 0, 1,1, 50,1, dlglines[0] }, { SGTEXT, 0, 0, 1,2, 50,1, dlglines[1] }, { SGTEXT, 0, 0, 1,3, 50,1, dlglines[2] }, { SGTEXT, 0, 0, 1,4, 50,1, dlglines[3] }, { SGBUTTON, SG_DEFAULT, 0, 5,5, 8,1, "OK" }, { SGBUTTON, SG_CANCEL, 0, 24,5, 8,1, NULL }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Breaks long string to several strings of max_width, divided by '\0', * sets text_width to the longest line width and returns the number of lines * you need to display the strings. */ static int DlgAlert_FormatTextToBox(char *text, int max_width, int *text_width) { int columns = 0; int lines = 1; int txtlen; char *p; /* pointer to begin of actual line */ char *q; /* pointer to start of next search */ char *llb; /* pointer to last place suitable for breaking the line */ char *txtend; /* pointer to end of the text */ txtlen = strlen(text); q = p = text; llb = text-1; /* pointer to last line break */ txtend = text + txtlen; if (txtlen <= max_width) { *text_width = txtlen; return lines; } while(q < txtend) /* q was last place suitable for breaking */ { char *r = strpbrk(q, " \t/\\\n"); /* find next suitable place for the break */ if (r == NULL) r = txtend; /* if there's no place then point to the end */ if ((r-p) <= max_width && *r != '\n') /* '\n' is always used for breaking */ { llb = r; /* remember new place suitable for breaking */ q++; if ((r-p) > columns) columns = r - p; continue; /* search again */ } if ((r-p) > max_width) /* too long line already? */ { if (p > llb) /* bad luck - no place for the delimiter. Let's do it the strong way */ llb = p + max_width; /* we loose one character */ } else llb = r; /* break from previous delimiter */ *llb = '\0'; /* BREAK */ if ((llb-p) > columns) columns = llb - p; /* longest line so far */ p = q = llb + 1; /* next line begins here */ lines++; /* increment line counter */ } *text_width = columns; return lines; /* return line counter */ } /*-----------------------------------------------------------------------*/ /** * Show the "alert" dialog. Return true if user pressed "OK". */ static bool DlgAlert_ShowDlg(const char *text) { static int maxlen = sizeof(dlglines[0])-1; char *t = Str_Alloc(strlen(text)); char *orig_t = t; int lines, i, len, offset; bool bOldMouseVisibility; int nOldMouseX, nOldMouseY; bool bWasEmuActive; bool bOldMouseMode = SDL_GetRelativeMouseMode(); SDL_SetRelativeMouseMode(SDL_FALSE); strcpy(t, text); lines = DlgAlert_FormatTextToBox(t, maxlen, &len); offset = (maxlen-len)/2; for(i=0; i=030)" }, { SGCHECKBOX, 0, 0, 3,17, 16,1, "_MMU emulation*" }, { SGCHECKBOX, 0, 0, 3,18, 20,1, "24-bit _addressing" }, { SGCHECKBOX, 0, 0, 3,19, 26,1, "Accurate _FPU emulation*" }, { SGTEXT, 0, 0, 3,21, 20,1, "* Uses more host CPU" }, { SGBUTTON, SG_DEFAULT, 0, 13,23, 19,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Show and process the "CPU" dialog */ void DlgCpu_Main(void) { int i; SDLGui_CenterDlg(cpudlg); /* CPU level: */ for (i = DLGCPU_68000; i <= DLGCPU_68060; i++) { cpudlg[i].state &= ~SG_SELECTED; } cpudlg[DLGCPU_68000+ConfigureParams.System.nCpuLevel].state |= SG_SELECTED; /* CPU frequency: */ for (i = DLGCPU_8MHZ; i <= DLGCPU_32MHZ; i++) { cpudlg[i].state &= ~SG_SELECTED; } if (ConfigureParams.System.nCpuFreq == 32) cpudlg[DLGCPU_32MHZ].state |= SG_SELECTED; else if (ConfigureParams.System.nCpuFreq == 16) cpudlg[DLGCPU_16MHZ].state |= SG_SELECTED; else cpudlg[DLGCPU_8MHZ].state |= SG_SELECTED; /* More compatible CPU, Prefetch mode */ if (ConfigureParams.System.bCompatibleCpu) cpudlg[DLGCPU_PREFETCH].state |= SG_SELECTED; else cpudlg[DLGCPU_PREFETCH].state &= ~SG_SELECTED; /* Address space 24 bits */ if (ConfigureParams.System.bAddressSpace24) cpudlg[DLGCPU_24BITS].state |= SG_SELECTED; else cpudlg[DLGCPU_24BITS].state &= ~SG_SELECTED; /* Cycle exact CPU */ if (ConfigureParams.System.bCycleExactCpu) cpudlg[DLGCPU_CYC_EXACT].state |= SG_SELECTED; else cpudlg[DLGCPU_CYC_EXACT].state &= ~SG_SELECTED; /* CPU data cache */ if (ConfigureParams.System.bCpuDataCache) cpudlg[DLGCPU_DATA_CACHE].state |= SG_SELECTED; else cpudlg[DLGCPU_DATA_CACHE].state &= ~SG_SELECTED; /* FPU emulation */ for (i = DLGCPU_FPU_NONE; i <= DLGCPU_FPU_CPU_IN; i++) { cpudlg[i].state &= ~SG_SELECTED; } if (ConfigureParams.System.n_FPUType == FPU_NONE) cpudlg[DLGCPU_FPU_NONE].state |= SG_SELECTED; else if (ConfigureParams.System.n_FPUType == FPU_68881) cpudlg[DLGCPU_FPU_68881].state |= SG_SELECTED; else if (ConfigureParams.System.n_FPUType == FPU_68882) cpudlg[DLGCPU_FPU_68882].state |= SG_SELECTED; else cpudlg[DLGCPU_FPU_CPU_IN].state |= SG_SELECTED; /* MMU emulation */ if (ConfigureParams.System.bMMU) cpudlg[DLGCPU_MMU_EMUL].state |= SG_SELECTED; else cpudlg[DLGCPU_MMU_EMUL].state &= ~SG_SELECTED; /* FPU emulation using softfloat */ if (ConfigureParams.System.bSoftFloatFPU) cpudlg[DLGCPU_SOFTFLOAT].state |= SG_SELECTED; else cpudlg[DLGCPU_SOFTFLOAT].state &= ~SG_SELECTED; /* Show the dialog: */ SDLGui_DoDialog(cpudlg); /* Read values from dialog: */ for (i = DLGCPU_68000; i <= DLGCPU_68060; i++) { if (cpudlg[i].state&SG_SELECTED) { ConfigureParams.System.nCpuLevel = i-DLGCPU_68000; break; } } if (cpudlg[DLGCPU_32MHZ].state & SG_SELECTED) Configuration_ChangeCpuFreq ( 32 ); else if (cpudlg[DLGCPU_16MHZ].state & SG_SELECTED) Configuration_ChangeCpuFreq ( 16 ); else Configuration_ChangeCpuFreq ( 8 ); ConfigureParams.System.bCompatibleCpu = (cpudlg[DLGCPU_PREFETCH].state & SG_SELECTED); ConfigureParams.System.bCycleExactCpu = (cpudlg[DLGCPU_CYC_EXACT].state & SG_SELECTED); ConfigureParams.System.bCpuDataCache = (cpudlg[DLGCPU_DATA_CACHE].state & SG_SELECTED); ConfigureParams.System.bMMU = (cpudlg[DLGCPU_MMU_EMUL].state & SG_SELECTED); ConfigureParams.System.bAddressSpace24 = (cpudlg[DLGCPU_24BITS].state & SG_SELECTED); ConfigureParams.System.bSoftFloatFPU = (cpudlg[DLGCPU_SOFTFLOAT].state & SG_SELECTED); /* FPU emulation */ if (cpudlg[DLGCPU_FPU_NONE].state & SG_SELECTED) ConfigureParams.System.n_FPUType = FPU_NONE; else if (cpudlg[DLGCPU_FPU_68881].state & SG_SELECTED) ConfigureParams.System.n_FPUType = FPU_68881; else if (cpudlg[DLGCPU_FPU_68882].state & SG_SELECTED) ConfigureParams.System.n_FPUType = FPU_68882; else ConfigureParams.System.n_FPUType = FPU_CPU; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgDevice.c000066400000000000000000000207271504763705000247360ustar00rootroot00000000000000/* Hatari - dlgDevice.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Device (Printer etc.) setup dialog */ const char DlgDevice_fileid[] = "Hatari dlgDevice.c"; #include #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #include "midi.h" /* needed only with PortMidi */ #include "str.h" #define DEVDLG_PRNENABLE 3 #define DEVDLG_PRNBROWSE 5 #define DEVDLG_PRNFILENAME 6 #define DEVDLG_RS232ENABLE 8 #define DEVDLG_RS232OUTBROWSE 10 #define DEVDLG_RS232OUTNAME 11 #define DEVDLG_RS232INBROWSE 13 #define DEVDLG_RS232INNAME 14 #define DEVDLG_MIDIENABLE 16 #ifndef HAVE_PORTMIDI #define DEVDLG_MIDIINBROWSE 18 #define DEVDLG_MIDIINNAME 19 #define DEVDLG_MIDIOUTBROWSE 21 #define DEVDLG_MIDIOUTNAME 22 #define DEVDLG_EXIT 23 #else #define DEVDLG_PREVIN 18 #define DEVDLG_NEXTIN 19 #define DEVDLG_MIDIINNAME 21 #define DEVDLG_PREVOUT 23 #define DEVDLG_NEXTOUT 24 #define DEVDLG_MIDIOUTNAME 26 #define DEVDLG_EXIT 27 #endif #define MAX_DLG_FILENAME 46+1 static char dlgPrinterName[MAX_DLG_FILENAME]; static char dlgRs232OutName[MAX_DLG_FILENAME]; static char dlgRs232InName[MAX_DLG_FILENAME]; static char dlgMidiInName[MAX_DLG_FILENAME]; static char dlgMidiOutName[MAX_DLG_FILENAME]; /* The devices dialog: */ static SGOBJ devicedlg[] = { { SGBOX, 0, 0, 0,0, 52,24, NULL }, { SGTEXT, 0, 0, 20,1, 13,1, "Devices setup" }, { SGBOX, 0, 0, 1,3, 50,4, NULL }, { SGCHECKBOX, 0, 0, 2,3, 26,1, "Enable _printer emulation" }, { SGTEXT, 0, 0, 2,5, 10,1, "Print to file:" }, { SGBUTTON, 0, 0, 42,5, 8,1, "_Browse" }, { SGTEXT, 0, 0, 3,6, 46,1, dlgPrinterName }, { SGBOX, 0, 0, 1,8, 50,6, NULL }, { SGCHECKBOX, 0, 0, 2,8, 24,1, "Enable _RS232 emulation" }, { SGTEXT, 0, 0, 2,10, 10,1, "Write RS232 output to file:" }, { SGBUTTON, 0, 0, 42,10, 8,1, "Br_owse" }, { SGTEXT, 0, 0, 3,11, 46,1, dlgRs232OutName }, { SGTEXT, 0, 0, 2,12, 10,1, "Read RS232 input from file:" }, { SGBUTTON, 0, 0, 42,12, 8,1, "Bro_wse" }, { SGTEXT, 0, 0, 3,13, 46,1, dlgRs232InName }, { SGBOX, 0, 0, 1,15, 50,6, NULL }, { SGCHECKBOX, 0, 0, 2,15, 23,1, "Enable _MIDI emulation" }, #ifndef HAVE_PORTMIDI { SGTEXT, 0, 0, 2,17, 26,1, "Read MIDI input from file:" }, { SGBUTTON, 0, 0, 42,17, 8,1, "Brow_se" }, { SGTEXT, 0, 0, 3,18, 46,1, dlgMidiInName }, { SGTEXT, 0, 0, 2,19, 26,1, "Write MIDI output to file:" }, { SGBUTTON, 0, 0, 42,19, 8,1, "Brows_e" }, { SGTEXT, 0, 0, 3,20, 46,1, dlgMidiOutName }, #else { SGTEXT, 0, 0, 5,17, 7,1, "input:" }, { SGBUTTON, 0, 0, 12,17, 3,1, "\x04", SG_SHORTCUT_LEFT }, { SGBUTTON, 0, 0, 15,17, 3,1, "\x03", SG_SHORTCUT_RIGHT }, { SGBOX, 0, 0, 18,17, 32,1, NULL }, { SGTEXT, 0, 0, 19,17, 30,1, dlgMidiInName }, { SGTEXT, 0, 0, 4,19, 7,1, "output:" }, { SGBUTTON, 0, 0, 12,19, 3,1, "\x04", SG_SHORTCUT_LEFT }, { SGBUTTON, 0, 0, 15,19, 3,1, "\x03", SG_SHORTCUT_RIGHT }, { SGBOX, 0, 0, 18,19, 32,1, NULL }, { SGTEXT, 0, 0, 19,19, 30,1, dlgMidiOutName }, #endif { SGBUTTON, SG_DEFAULT, 0, 16,22, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Show and process the "Device" dialog. */ void Dialog_DeviceDlg(void) { int but; #ifdef HAVE_PORTMIDI const char *name, *midiInName, *midiOutName; #endif SDLGui_CenterDlg(devicedlg); /* Set up dialog from actual values: */ if (ConfigureParams.Printer.bEnablePrinting) devicedlg[DEVDLG_PRNENABLE].state |= SG_SELECTED; else devicedlg[DEVDLG_PRNENABLE].state &= ~SG_SELECTED; File_ShrinkName(dlgPrinterName, ConfigureParams.Printer.szPrintToFileName, devicedlg[DEVDLG_PRNFILENAME].w); if (ConfigureParams.RS232.bEnableRS232) devicedlg[DEVDLG_RS232ENABLE].state |= SG_SELECTED; else devicedlg[DEVDLG_RS232ENABLE].state &= ~SG_SELECTED; File_ShrinkName(dlgRs232OutName, ConfigureParams.RS232.szOutFileName, devicedlg[DEVDLG_RS232OUTNAME].w); File_ShrinkName(dlgRs232InName, ConfigureParams.RS232.szInFileName, devicedlg[DEVDLG_RS232INNAME].w); if (ConfigureParams.Midi.bEnableMidi) devicedlg[DEVDLG_MIDIENABLE].state |= SG_SELECTED; else devicedlg[DEVDLG_MIDIENABLE].state &= ~SG_SELECTED; #ifndef HAVE_PORTMIDI File_ShrinkName(dlgMidiInName, ConfigureParams.Midi.sMidiInFileName, devicedlg[DEVDLG_MIDIINNAME].w); File_ShrinkName(dlgMidiOutName, ConfigureParams.Midi.sMidiOutFileName, devicedlg[DEVDLG_MIDIOUTNAME].w); #else midiInName = Midi_Host_GetPortName(ConfigureParams.Midi.sMidiInPortName, MIDI_NAME_FIND, MIDI_FOR_INPUT); File_ShrinkName(dlgMidiInName, midiInName ? midiInName : "Off", devicedlg[DEVDLG_MIDIINNAME].w); midiOutName = Midi_Host_GetPortName(ConfigureParams.Midi.sMidiOutPortName, MIDI_NAME_FIND, MIDI_FOR_OUTPUT); File_ShrinkName(dlgMidiOutName, midiOutName ? midiOutName : "Off", devicedlg[DEVDLG_MIDIOUTNAME].w); #endif /* The devices dialog main loop */ do { but = SDLGui_DoDialog(devicedlg); switch(but) { case DEVDLG_PRNBROWSE: /* Choose a new printer file */ SDLGui_FileConfSelect("Printer output:", dlgPrinterName, ConfigureParams.Printer.szPrintToFileName, devicedlg[DEVDLG_PRNFILENAME].w, true); break; case DEVDLG_RS232OUTBROWSE: /* Choose a new RS232 output file */ SDLGui_FileConfSelect("RS232 output:", dlgRs232OutName, ConfigureParams.RS232.szOutFileName, devicedlg[DEVDLG_RS232OUTNAME].w, true); break; case DEVDLG_RS232INBROWSE: /* Choose a new RS232 input file */ SDLGui_FileConfSelect("RS232 input:", dlgRs232InName, ConfigureParams.RS232.szInFileName, devicedlg[DEVDLG_RS232INNAME].w, true); break; #ifndef HAVE_PORTMIDI case DEVDLG_MIDIINBROWSE: /* Choose a new MIDI file */ SDLGui_FileConfSelect("MIDI input:", dlgMidiInName, ConfigureParams.Midi.sMidiInFileName, devicedlg[DEVDLG_MIDIINNAME].w, true); break; case DEVDLG_MIDIOUTBROWSE: /* Choose a new MIDI file */ SDLGui_FileConfSelect("MIDI output:", dlgMidiOutName, ConfigureParams.Midi.sMidiOutFileName, devicedlg[DEVDLG_MIDIOUTNAME].w, true); break; #else case DEVDLG_PREVIN: midiInName = Midi_Host_GetPortName(midiInName, MIDI_NAME_PREV, MIDI_FOR_INPUT); File_ShrinkName(dlgMidiInName, midiInName ? midiInName : "Off", devicedlg[DEVDLG_MIDIINNAME].w); break; case DEVDLG_NEXTIN: name = Midi_Host_GetPortName(midiInName, MIDI_NAME_NEXT, MIDI_FOR_INPUT); if (name) { midiInName = name; File_ShrinkName(dlgMidiInName, midiInName, devicedlg[DEVDLG_MIDIINNAME].w); } break; case DEVDLG_PREVOUT: midiOutName = Midi_Host_GetPortName(midiOutName, MIDI_NAME_PREV, MIDI_FOR_OUTPUT); File_ShrinkName(dlgMidiOutName, midiOutName ? midiOutName : "Off", devicedlg[DEVDLG_MIDIOUTNAME].w); break; case DEVDLG_NEXTOUT: name = Midi_Host_GetPortName(midiOutName, MIDI_NAME_NEXT, MIDI_FOR_OUTPUT); if (name) { midiOutName = name; File_ShrinkName(dlgMidiOutName, midiOutName, devicedlg[DEVDLG_MIDIOUTNAME].w); } break; #endif } } while (but != DEVDLG_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Read values from dialog */ ConfigureParams.Printer.bEnablePrinting = (devicedlg[DEVDLG_PRNENABLE].state & SG_SELECTED); ConfigureParams.RS232.bEnableRS232 = (devicedlg[DEVDLG_RS232ENABLE].state & SG_SELECTED); ConfigureParams.Midi.bEnableMidi = (devicedlg[DEVDLG_MIDIENABLE].state & SG_SELECTED); #ifdef HAVE_PORTMIDI Str_Copy(ConfigureParams.Midi.sMidiInPortName, midiInName ? midiInName : "Off", sizeof(ConfigureParams.Midi.sMidiInPortName)); Str_Copy(ConfigureParams.Midi.sMidiOutPortName, midiOutName ? midiOutName : "Off", sizeof(ConfigureParams.Midi.sMidiOutPortName)); #endif } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgFileSelect.c000066400000000000000000000727021504763705000255560ustar00rootroot00000000000000/* Hatari - dlgFileSelect.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. A file selection dialog for the graphical user interface for Hatari. */ const char DlgFileSelect_fileid[] = "Hatari dlgFileSelect.c"; #include #include #include #if WIN32 #define WIN32_LEAN_AND_MEAN #include #endif #include "main.h" #include "scandir.h" #include "sdlgui.h" #include "file.h" #include "paths.h" #include "str.h" #include "zip.h" #include "log.h" #define SGFS_NUMENTRIES 16 /* How many entries are displayed at once */ #define SGFSDLG_TITLE 1 #define SGFSDLG_FILENAME 5 #define SGFSDLG_UPDIR 6 #define SGFSDLG_CWD 7 #define SGFSDLG_HOMEDIR 8 #define SGFSDLG_ROOTDIR 9 #define SGFSDLG_ENTRYFIRST 12 #define SGFSDLG_ENTRYLAST 27 #define SGFSDLG_SCROLLBAR 28 #define SGFSDLG_UP 29 #define SGFSDLG_DOWN 30 #define SGFSDLG_SHOWHIDDEN 31 #define SGFSDLG_OKAY 32 #define SGFSDLG_CANCEL 33 #if WIN32 #define SGFSDLG_DRIVE_LESS 34 #define SGFSDLG_DRIVE_TEXT 35 #define SGFSDLG_DRIVE_MORE 36 static char sCurrDrive[3]; #endif #define SCROLLOUT_ABOVE 1 #define SCROLLOUT_UNDER 2 #define DLGPATH_SIZE 62 static char dlgpath[DLGPATH_SIZE+1]; /* Path name in the dialog */ #if WIN32 #define DLGFNAME_SIZE 49 /* make it a little bit shorter on Windows so we have place on drive change*/ #else #define DLGFNAME_SIZE 56 #endif static char dlgfname[DLGFNAME_SIZE+1]; /* Name of the selected file in the dialog */ #define DLGFILENAMES_SIZE 59 static char dlgfilenames[SGFS_NUMENTRIES][DLGFILENAMES_SIZE+1]; /* Visible file names in the dialog */ #define SCROLLBAR_MIN_HEIGHT 4 /* Min value for yScrollbar_size */ #define TITLE_OFFSET 1 #define TITLE_MAXLEN 40 /* The dialog data: */ static SGOBJ fsdlg[] = { { SGBOX, 0, 0, 0,0, 64,25, NULL }, { SGTEXT, 0, 0, 1,1, 13,1, NULL, }, { SGTEXT, 0, 0, 1,2, 7,1, "Folder:" }, { SGTEXT, 0, 0, 1,3, DLGPATH_SIZE,1, dlgpath }, { SGTEXT, 0, 0, 1,4, 6,1, "File:" }, { SGTEXT, 0, 0, 7,4, DLGFNAME_SIZE,1, dlgfname }, { SGBUTTON, 0, 0, 39,1, 4,1, "_Up" }, { SGBUTTON, 0, 0, 44,1, 5,1, "_CWD" }, { SGBUTTON, 0, 0, 50,1, 6,1, "_Home" }, { SGBUTTON, 0, 0, 57,1, 6,1, "_Root" }, { SGBOX, 0, 0, 1,6, 62,16, NULL }, { SGBOX, 0, 0, 62,7, 1,14, NULL }, { SGTEXT, SG_EXIT, 0, 2,6, DLGFILENAMES_SIZE,1, dlgfilenames[0] }, { SGTEXT, SG_EXIT, 0, 2,7, DLGFILENAMES_SIZE,1, dlgfilenames[1] }, { SGTEXT, SG_EXIT, 0, 2,8, DLGFILENAMES_SIZE,1, dlgfilenames[2] }, { SGTEXT, SG_EXIT, 0, 2,9, DLGFILENAMES_SIZE,1, dlgfilenames[3] }, { SGTEXT, SG_EXIT, 0, 2,10, DLGFILENAMES_SIZE,1, dlgfilenames[4] }, { SGTEXT, SG_EXIT, 0, 2,11, DLGFILENAMES_SIZE,1, dlgfilenames[5] }, { SGTEXT, SG_EXIT, 0, 2,12, DLGFILENAMES_SIZE,1, dlgfilenames[6] }, { SGTEXT, SG_EXIT, 0, 2,13, DLGFILENAMES_SIZE,1, dlgfilenames[7] }, { SGTEXT, SG_EXIT, 0, 2,14, DLGFILENAMES_SIZE,1, dlgfilenames[8] }, { SGTEXT, SG_EXIT, 0, 2,15, DLGFILENAMES_SIZE,1, dlgfilenames[9] }, { SGTEXT, SG_EXIT, 0, 2,16, DLGFILENAMES_SIZE,1, dlgfilenames[10] }, { SGTEXT, SG_EXIT, 0, 2,17, DLGFILENAMES_SIZE,1, dlgfilenames[11] }, { SGTEXT, SG_EXIT, 0, 2,18, DLGFILENAMES_SIZE,1, dlgfilenames[12] }, { SGTEXT, SG_EXIT, 0, 2,19, DLGFILENAMES_SIZE,1, dlgfilenames[13] }, { SGTEXT, SG_EXIT, 0, 2,20, DLGFILENAMES_SIZE,1, dlgfilenames[14] }, { SGTEXT, SG_EXIT, 0, 2,21, DLGFILENAMES_SIZE,1, dlgfilenames[15] }, { SGSCROLLBAR, SG_TOUCHEXIT|SG_REPEAT, 0, 62, 7, 0, 0, NULL }, { SGBUTTON, SG_TOUCHEXIT|SG_REPEAT, 0, 62, 6,1,1, "\x01", SG_SHORTCUT_UP }, { SGBUTTON, SG_TOUCHEXIT|SG_REPEAT, 0, 62,21,1,1, "\x02", SG_SHORTCUT_DOWN }, { SGCHECKBOX, SG_EXIT, 0, 2,23, 19,1, "_Show hidden files" }, { SGBUTTON, SG_DEFAULT, 0, 32,23, 8,1, "OK" }, { SGBUTTON, SG_CANCEL, 0, 50,23, 8,1, "Cancel" }, #if WIN32 /* Drive selection */ { SGBUTTON, 0, 0, 57,4, 1,1, "\x04", SG_SHORTCUT_LEFT }, { SGTEXT, 0, 0, 59,4, 2,1, sCurrDrive }, { SGBUTTON, 0, 0, 62,4, 1,1, "\x03", SG_SHORTCUT_RIGHT }, #endif { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; static int ypos = -1; /* First entry number to be displayed. If -1, file selector start on the 1st file */ /* else we continue from the previous position when SDLGui_FileSelect is called again */ static bool refreshentries; /* Do we have to update the file names in the dialog? */ static int entries; /* How many files are in the actual directory? */ static int oldMouseY = 0; /* Keep the latest Y mouse position for scrollbar move computing */ static int mouseClicked = 0; /* used to know if mouse if down for the first time or not */ static int mouseIsOut = 0; /* used to keep info that mouse if above or under the scrollbar when mousebutton is down */ static float scrollbar_Ypos = 0.0; /* scrollbar height */ static char *dirpath; /* for get_dtype() */ #if !defined(HAVE_DIRENT_D_TYPE) && !defined(DT_UNKNOWN) enum { DT_UNKNOWN, DT_LNK, DT_DIR, DT_REG }; #endif /* Convert file position (in file list) to scrollbar y position */ static void DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(void); /*-----------------------------------------------------------------------*/ /** * Update the file name strings in the dialog. * Returns false if it failed, true on success. */ static int DlgFileSelect_RefreshEntries(struct dirent **files, char *path, bool browsingzip) { int i; char *tempstr = malloc(FILENAME_MAX); if (!tempstr) { perror("DlgFileSelect_RefreshEntries"); return false; } /* Copy entries to dialog: */ for (i=0; id_name); File_ShrinkName(dlgfilenames[i], tempstr, DLGFILENAMES_SIZE); /* Mark folders: */ strcpy(tempstr, path); strcat(tempstr, files[i+ypos]->d_name); if (browsingzip) { if (File_DoesFileNameEndWithSlash(tempstr)) dlgfilenames[i][0] = SGFOLDER; /* Mark folders */ } else { if( stat(tempstr, &filestat)==0 && S_ISDIR(filestat.st_mode) ) dlgfilenames[i][0] = SGFOLDER; /* Mark folders */ if (ZIP_FileNameIsZIP(tempstr) && browsingzip == false) dlgfilenames[i][0] = SGFOLDER; /* Mark .ZIP archives as folders */ } fsdlg[SGFSDLG_ENTRYFIRST+i].flags |= SG_EXIT; } else { dlgfilenames[i][0] = 0; /* Clear entry */ fsdlg[SGFSDLG_ENTRYFIRST+i].flags &= ~SG_EXIT; } } free(tempstr); return true; } /*-----------------------------------------------------------------------*/ /** * Remove all hidden files (files with file names that begin with a dot) from * the list. */ static void DlgFileSelect_RemoveHiddenFiles(struct dirent **files) { int i; int nActPos = -1; int nOldEntries; nOldEntries = entries; /* Scan list for hidden files and remove them. */ for (i = 0; i < nOldEntries; i++) { /* Does file name start with a dot? -> hidden file! */ if (files[i]->d_name[0] == '.') { if (nActPos == -1) nActPos = i; /* Remove file from list: */ free(files[i]); files[i] = NULL; entries -= 1; } } /* Now close the gaps in the list: */ if (nActPos != -1) { for (i = nActPos; i < nOldEntries; i++) { if (files[i] != NULL) { /* Move entry to earlier position: */ files[nActPos] = files[i]; files[i] = NULL; nActPos += 1; } } } } /** * Reset focus to first entry if necessary. */ static void DlgFileSelect_ResetFocus(void) { int i; for (i = SGFSDLG_ENTRYFIRST+1; i <= SGFSDLG_ENTRYLAST; i++) { if (fsdlg[i].state & SG_FOCUSED) { fsdlg[i].state &= ~SG_FOCUSED; fsdlg[SGFSDLG_ENTRYFIRST].state |= SG_FOCUSED; break; } } } /*-----------------------------------------------------------------------*/ /** * Prepare to scroll up one entry. */ static void DlgFileSelect_ScrollUp(void) { if (ypos > 0) { --ypos; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); refreshentries = true; } } /*-----------------------------------------------------------------------*/ /** * Prepare to scroll down one entry. */ static void DlgFileSelect_ScrollDown(void) { if (ypos+SGFS_NUMENTRIES < entries) { ++ypos; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); refreshentries = true; } } /*-----------------------------------------------------------------------*/ /** * Manage the scrollbar up or down. */ static void DlgFileSelect_ManageScrollbar(void) { int b, x, y; int scrollY, scrollYmin, scrollYmax, scrollH_half; float scrollMove; SDL_GetMouseState(&x, &y); SDLGui_ScaleMouseStateCoordinates(&x, &y); /* If mouse is down on the scrollbar for the first time */ if (fsdlg[SGFSDLG_SCROLLBAR].state & SG_MOUSEDOWN) { if (mouseClicked == 0) { mouseClicked = 1; mouseIsOut = 0; oldMouseY = y; } } else { /* Mouse button is up on the scrollbar */ mouseClicked = 0; oldMouseY = y; mouseIsOut = 0; } /* If mouse Y position didn't change */ if (oldMouseY == y) return; /* Compute scrollbar ymin and ymax values */ scrollYmin = (fsdlg[SGFSDLG_SCROLLBAR].y + fsdlg[0].y) * sdlgui_fontheight; scrollYmax = (fsdlg[SGFSDLG_DOWN].y + fsdlg[0].y) * sdlgui_fontheight; scrollY = fsdlg[SGFSDLG_SCROLLBAR].y * sdlgui_fontheight + fsdlg[SGFSDLG_SCROLLBAR].h + fsdlg[0].y * sdlgui_fontheight; scrollH_half = scrollY + fsdlg[SGFSDLG_SCROLLBAR].w / 2; scrollMove = (float)(y-oldMouseY)/sdlgui_fontheight; /* Verify if mouse is not above the scrollbar area */ if (y < scrollYmin) { mouseIsOut = SCROLLOUT_ABOVE; oldMouseY = y; return; } if (mouseIsOut == SCROLLOUT_ABOVE && y < scrollH_half) { oldMouseY = y; return; } /* Verify if mouse is not under the scrollbar area */ if (y > scrollYmax) { mouseIsOut = SCROLLOUT_UNDER; oldMouseY = y; return; } if (mouseIsOut == SCROLLOUT_UNDER && y > scrollH_half) { oldMouseY = y; return; } mouseIsOut = 0; scrollbar_Ypos += scrollMove; oldMouseY = y; /* Verifiy if scrollbar is in correct inferior boundary */ if (scrollbar_Ypos < 0) scrollbar_Ypos = 0.0; /* Verifiy if scrollbar is in correct superior boundary */ b = (int) (scrollbar_Ypos * ((float)entries/(float)(SGFS_NUMENTRIES-2)) + 0.5); if (b+SGFS_NUMENTRIES >= entries) { ypos = entries - SGFS_NUMENTRIES; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); } refreshentries = true; } /*-----------------------------------------------------------------------*/ /** * Return true for handled SDL events, should match what's * handled in DlgFileSelect_HandleSdlEvents() */ static bool acceptEvents(SDL_EventType evtype) { if (evtype == SDL_MOUSEWHEEL || evtype == SDL_KEYDOWN) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Handle SDL events. */ static void DlgFileSelect_HandleSdlEvents(SDL_Event *pEvent) { int oldypos = ypos; switch (pEvent->type) { case SDL_MOUSEWHEEL: if (pEvent->wheel.y>0) DlgFileSelect_ScrollUp(); else if (pEvent->wheel.y<0) DlgFileSelect_ScrollDown(); break; case SDL_KEYDOWN: switch (pEvent->key.keysym.sym) { case SDLK_UP: DlgFileSelect_ScrollUp(); break; case SDLK_DOWN: DlgFileSelect_ScrollDown(); break; case SDLK_HOME: ypos = 0; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); break; case SDLK_END: ypos = entries-SGFS_NUMENTRIES; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); break; case SDLK_PAGEUP: ypos -= SGFS_NUMENTRIES; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); break; case SDLK_PAGEDOWN: if (ypos+2*SGFS_NUMENTRIES < entries) ypos += SGFS_NUMENTRIES; else ypos = entries-SGFS_NUMENTRIES; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); break; default: break; } break; default: break; } if (ypos < 0) { ypos = 0; scrollbar_Ypos = 0.0; } if (ypos != oldypos) refreshentries = true; } /*-----------------------------------------------------------------------*/ /** * Free file entries */ static struct dirent **files_free(struct dirent **files) { int i; if (files != NULL) { for(i=0; id_name; const char *name2 = (*d2)->d_name; #ifndef HAVE_DIRENT_D_TYPE int type1 = DT_UNKNOWN; int type2 = DT_UNKNOWN; #else int type1 = (*d1)->d_type; int type2 = (*d2)->d_type; #endif /* OS / file system that doesn't support d_type field, or symlink */ if (type1 == DT_UNKNOWN || type1 == DT_LNK) type1 = get_dtype(name1); if (type2 == DT_UNKNOWN || type2 == DT_LNK) type2 = get_dtype(name2); if (type1 == DT_DIR) { if (type2 != DT_DIR) return -1; } else if (type2 == DT_DIR) { if (type1 != DT_DIR) return 1; } return strcasecmp(name1, name2); } /*-----------------------------------------------------------------------*/ /** * Create and return suitable path into zip file */ static char* zip_get_path(const char *zipdir, const char *zipfilename, int browsingzip) { if (browsingzip) { char *zippath; zippath = Str_Alloc(strlen(zipdir) + strlen(zipfilename)); strcpy(zippath, zipdir); strcat(zippath, zipfilename); return zippath; } return strdup(""); } /** * string for zip root needs to be empty, check and correct if needed */ static void correct_zip_root(char *zippath) { if (zippath[0] == PATHSEP && !zippath[1]) { zippath[0] = '\0'; } } /** * Convert Ypos to Y scrollbar position */ static void DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(void) { if (entries <= SGFS_NUMENTRIES) scrollbar_Ypos = 0.0; else scrollbar_Ypos = (float)ypos / ((float)entries/(float)(SGFS_NUMENTRIES-2)); } #if WIN32 /** * Find next or previous drive relative to the current (sCurrDrive) * Is meaningful only on Windows (or another OSes with mounting by letters) * step can be 1 or -1 (next or previous) * path will be filled with root dir of the new drive if there will be some available * returns 1 if drive was changed, otherwise 0 */ static char findNextOrPreviousDrive(char step, char *path) { char chDrv; UINT driveType; char rootPath[3]; char endDrv = step > 0 ? 'Z' : 'A'; for (chDrv = sCurrDrive[0] + step; chDrv != endDrv + step; chDrv += step) { /* make root path */ sprintf_s(rootPath, 3, "%c:", chDrv); /* get drive type */ driveType = GetDriveTypeA(rootPath); if ((driveType == DRIVE_NO_ROOT_DIR) || (driveType == DRIVE_UNKNOWN)) continue; sCurrDrive[0] = rootPath[0]; path[0] = rootPath[0]; path[1] = rootPath[1]; path[2] = PATHSEP; path[3] = '\0'; return 1; } return 0; } /** * Refreshes drive according driveletter or if driveletter is root ('\\'), it extracts drive from getcwd */ static void refreshDrive(char driveletter) { /* if we don't have root path with letter get it from cwd */ if (driveletter == PATHSEP) { char* pTempName = Str_Alloc(FILENAME_MAX); if (!getcwd(pTempName, FILENAME_MAX)) { perror("WinInitializeDriveLetter - getcwd"); driveletter = 'C'; } driveletter = pTempName[0]; free(pTempName); } /* find drive of given path */ sCurrDrive[0] = driveletter; sCurrDrive[1] = ':'; sCurrDrive[2] = '\0'; } #endif /*-----------------------------------------------------------------------*/ /** * Show and process a file selection dialog. * Returns path/name user selected or NULL if user canceled * input: zip_path = pointer's pointer to buffer to contain file path * within a selected zip file, or NULL if browsing zip files is disallowed. * bAllowNew: true if the user is allowed to insert new file names. */ char* SDLGui_FileSelect(const char *title, const char *path_and_name, char **zip_path, bool bAllowNew) { struct dirent **files = NULL; char *pStringMem; char *retpath = NULL; const char *home; char *path, *fname; /* The actual file and path names */ bool reloaddir = true; /* Do we have to reload the directory file list? */ int retbut, len; bool bOldMouseVisibility; int selection; /* The selection index */ char *zipfilename; /* Filename in zip file */ char *zipdir; bool browsingzip = false; /* Are we browsing an archive? */ zip_dir *zipfiles = NULL; SDL_Event sdlEvent; int yScrollbar_size; /* Size of the vertical scrollbar */ union { char *mtxt; const char *ctxt; } dlgtitle; /* A hack to silent recent GCCs warnings */ dlgtitle.ctxt = title; /* If this is the first call to SDLGui_FileSelect, we reset scrollbar_Ypos and ypos */ /* Else, we keep the previous value of scrollbar_Ypos and update ypos below, to open */ /* the fileselector at the same position it was used */ if ( ypos < 0 ) { scrollbar_Ypos = 0.0; ypos = 0; } refreshentries = true; entries = 0; /* Allocate memory for the file and path name strings: */ pStringMem = malloc(4 * FILENAME_MAX); if ( !pStringMem ) return NULL; path = pStringMem; fname = pStringMem + FILENAME_MAX; zipdir = pStringMem + 2 * FILENAME_MAX; zipfilename = pStringMem + 3 * FILENAME_MAX; zipfilename[0] = 0; fname[0] = 0; path[0] = 0; len = strlen(title); fsdlg[SGFSDLG_TITLE].txt = dlgtitle.mtxt; fsdlg[SGFSDLG_TITLE].x = TITLE_OFFSET + (TITLE_MAXLEN-len)/2; fsdlg[SGFSDLG_TITLE].w = len; /* Save mouse state and enable cursor */ bOldMouseVisibility = Main_ShowCursor(true); SDLGui_CenterDlg(fsdlg); if (bAllowNew) { fsdlg[SGFSDLG_FILENAME].type = SGEDITFIELD; fsdlg[SGFSDLG_FILENAME].flags |= SG_EXIT; } else { fsdlg[SGFSDLG_FILENAME].type = SGTEXT; fsdlg[SGFSDLG_FILENAME].flags &= ~SG_EXIT; } /* Prepare the path and filename variables */ if (path_and_name && path_and_name[0]) { Str_Copy(path, path_and_name, FILENAME_MAX); } if (!File_DirExists(path)) { File_SplitPath(path, path, fname, NULL); if (!(File_DirExists(path) || getcwd(path, FILENAME_MAX))) { perror("SDLGui_FileSelect: non-existing path and CWD failed"); goto clean_exit; } } File_MakeAbsoluteName(path); File_MakeValidPathName(path); File_ShrinkName(dlgpath, path, DLGPATH_SIZE); File_ShrinkName(dlgfname, fname, DLGFNAME_SIZE); #if WIN32 refreshDrive(path[0]); #endif /* current object when entering the dialog */ retbut = SDLGUI_NOTFOUND; do { if (reloaddir) { files = files_free(files); if (browsingzip) { files = ZIP_GetFilesDir(zipfiles, zipdir, &entries); if(!files) { Log_Printf(LOG_WARN, "SDLGui_FileSelect: ZIP_GetFilesDir() error!\n"); goto clean_exit; } } else { /* for get_dtype() */ dirpath = path; /* Load directory entries: */ entries = scandir(path, &files, NULL, filesort); } /* Remove hidden files from the list if necessary: */ if (!(fsdlg[SGFSDLG_SHOWHIDDEN].state & SG_SELECTED)) { DlgFileSelect_RemoveHiddenFiles(files); } if (entries < 0) { Log_Printf(LOG_WARN, "SDLGui_FileSelect: Path not found.\n"); goto clean_exit; } /* reload always implies refresh */ reloaddir = false; refreshentries = true; /* Check if focus was in list - if yes then reset to first entry */ DlgFileSelect_ResetFocus(); }/* reloaddir */ /* Refresh scrollbar size */ if (entries <= SGFS_NUMENTRIES) yScrollbar_size = (SGFS_NUMENTRIES-2) * sdlgui_fontheight; else { yScrollbar_size = (int)((SGFS_NUMENTRIES-2) / ((float)entries/(float)SGFS_NUMENTRIES) * sdlgui_fontheight); if ( yScrollbar_size < SCROLLBAR_MIN_HEIGHT ) /* Value could be 0 for very large directory */ yScrollbar_size = SCROLLBAR_MIN_HEIGHT; } fsdlg[SGFSDLG_SCROLLBAR].w = yScrollbar_size; /* Refresh scrollbar pos */ ypos = (int) (scrollbar_Ypos * ((float)entries/(float)(SGFS_NUMENTRIES-2)) + 0.5); if (ypos+SGFS_NUMENTRIES >= entries) { /* Ensure Y pos is in the correct boundaries */ ypos = entries - SGFS_NUMENTRIES; if ( ypos < 0 ) ypos = 0; DlgFileSelect_Convert_ypos_to_scrollbar_Ypos(); } fsdlg[SGFSDLG_SCROLLBAR].h = (int) (scrollbar_Ypos * sdlgui_fontheight); /* Update the file name strings in the dialog? */ if (refreshentries) { if (!DlgFileSelect_RefreshEntries(files, path, browsingzip)) { goto clean_exit; } refreshentries = false; } /* Show dialog: */ retbut = SDLGui_DoDialogExt(fsdlg, acceptEvents, &sdlEvent, retbut); /* Has the user clicked on a file or folder? */ if (retbut>=SGFSDLG_ENTRYFIRST && retbut<=SGFSDLG_ENTRYLAST && retbut-SGFSDLG_ENTRYFIRST+yposd_name)) { Log_Printf(LOG_WARN, "SDLGui_FileSelect: Path name too long!\n"); free(tempstr); goto clean_exit; } /* directory? */ if (File_DoesFileNameEndWithSlash(tempstr)) { /* handle the ../ directory */ if (strcmp(files[retbut-SGFSDLG_ENTRYFIRST+ypos]->d_name, "../") == 0) { /* close the zip file */ if (strcmp(tempstr, "../") == 0) { /* free zip file entries */ ZIP_FreeZipDir(zipfiles); zipfiles = NULL; /* Copy the path name to the dialog */ File_ShrinkName(dlgpath, path, DLGPATH_SIZE); browsingzip = false; } else { /* remove "../" and previous dir from path */ File_PathShorten(tempstr, 2); correct_zip_root(tempstr); strcpy(zipdir, tempstr); File_ShrinkName(dlgpath, zipdir, DLGPATH_SIZE); } } else /* not the "../" directory */ { strcpy(zipdir, tempstr); File_ShrinkName(dlgpath, zipdir, DLGPATH_SIZE); } reloaddir = true; /* Copy the path name to the dialog */ zipfilename[0] = '\0'; dlgfname[0] = 0; ypos = 0; scrollbar_Ypos = 0.0; } else { /* not dir, select a file in the zip */ selection = retbut-SGFSDLG_ENTRYFIRST+ypos; strcpy(zipfilename, files[selection]->d_name); File_ShrinkName(dlgfname, zipfilename, DLGFNAME_SIZE); } } else /* not browsingzip */ { if (!strcat_maxlen(tempstr, FILENAME_MAX, path, files[retbut-SGFSDLG_ENTRYFIRST+ypos]->d_name)) { Log_Printf(LOG_WARN, "SDLGui_FileSelect: Path name too long!\n"); free(tempstr); goto clean_exit; } if (File_DirExists(tempstr)) { File_HandleDotDirs(tempstr); File_AddSlashToEndFileName(tempstr); /* Copy the path name to the dialog */ File_ShrinkName(dlgpath, tempstr, DLGPATH_SIZE); strcpy(path, tempstr); reloaddir = true; dlgfname[0] = 0; ypos = 0; scrollbar_Ypos = 0.0; } else if (ZIP_FileNameIsZIP(tempstr) && zip_path != NULL) { /* open a zip file */ zipfiles = ZIP_GetFiles(tempstr); if (zipfiles != NULL && browsingzip == false) { selection = retbut-SGFSDLG_ENTRYFIRST+ypos; strcpy(fname, files[selection]->d_name); File_ShrinkName(dlgfname, fname, DLGFNAME_SIZE); browsingzip = true; zipdir[0] = '\0'; /* zip root */ File_ShrinkName(dlgpath, zipdir, DLGPATH_SIZE); reloaddir = true; ypos = 0; scrollbar_Ypos = 0.0; } } else { /* Select a file */ selection = retbut-SGFSDLG_ENTRYFIRST+ypos; strcpy(fname, files[selection]->d_name); File_ShrinkName(dlgfname, fname, DLGFNAME_SIZE); } } /* not browsingzip */ free(tempstr); } else /* Has the user clicked on another button? */ { switch(retbut) { case SGFSDLG_UPDIR: /* Change path to parent directory */ if (browsingzip) { /* close the zip file? */ if (!zipdir[0]) { /* free zip file entries */ ZIP_FreeZipDir(zipfiles); browsingzip = false; zipfiles = NULL; File_ShrinkName(dlgpath, path, DLGPATH_SIZE); } else { /* remove last dir from zipdir path */ File_PathShorten(zipdir, 1); correct_zip_root(zipdir); File_ShrinkName(dlgpath, zipdir, DLGPATH_SIZE); zipfilename[0] = '\0'; } } /* not a zip file: */ else { File_PathShorten(path, 1); File_ShrinkName(dlgpath, path, DLGPATH_SIZE); } reloaddir = true; break; case SGFSDLG_HOMEDIR: /* Change to home directory */ case SGFSDLG_CWD: /* Change to current work directory */ if (retbut == SGFSDLG_CWD) home = Paths_GetWorkingDir(); else home = Paths_GetUserHome(); if (home == NULL || !*home) break; if (browsingzip) { /* free zip file entries */ ZIP_FreeZipDir(zipfiles); zipfiles = NULL; browsingzip = false; } strcpy(path, home); File_AddSlashToEndFileName(path); File_ShrinkName(dlgpath, path, DLGPATH_SIZE); #if WIN32 refreshDrive(path[0]); #endif reloaddir = true; break; case SGFSDLG_ROOTDIR: /* Change to root directory */ if (browsingzip) { /* free zip file entries */ ZIP_FreeZipDir(zipfiles); zipfiles = NULL; browsingzip = false; } #if WIN32 path[0] = sCurrDrive[0]; path[1] = ':'; path[2] = PATHSEP; path[3] = '\0'; #else path[0] = PATHSEP; path[1] = '\0'; #endif strcpy(dlgpath, path); reloaddir = true; break; case SGFSDLG_UP: /* Scroll up */ DlgFileSelect_ScrollUp(); SDL_Delay(10); break; case SGFSDLG_DOWN: /* Scroll down */ DlgFileSelect_ScrollDown(); SDL_Delay(10); break; case SGFSDLG_SCROLLBAR: /* Scrollbar selected */ DlgFileSelect_ManageScrollbar(); SDL_Delay(10); break; case SGFSDLG_FILENAME: /* User entered new filename */ strcpy(fname, dlgfname); break; case SGFSDLG_SHOWHIDDEN: /* Show/hide hidden files */ reloaddir = true; ypos = 0; scrollbar_Ypos = 0.0; break; case SDLGUI_UNKNOWNEVENT: DlgFileSelect_HandleSdlEvents(&sdlEvent); break; #if WIN32 case SGFSDLG_DRIVE_LESS: if (findNextOrPreviousDrive(-1, path)) { strcpy(dlgpath, path); reloaddir = true; } break; case SGFSDLG_DRIVE_MORE: if (findNextOrPreviousDrive(1, path)) { strcpy(dlgpath, path); reloaddir = true; } break; #endif } /* switch */ if (reloaddir) { /* Remove old selection */ fname[0] = 0; dlgfname[0] = 0; ypos = 0; scrollbar_Ypos = 0.0; } } /* other button code */ } /* do */ while (retbut!=SGFSDLG_OKAY && retbut!=SGFSDLG_CANCEL && retbut!=SDLGUI_QUIT && retbut != SDLGUI_ERROR && !bQuitProgram); if (retbut == SGFSDLG_OKAY) { if (zip_path) *zip_path = zip_get_path(zipdir, zipfilename, browsingzip); retpath = File_MakePath(path, fname, NULL); } else retpath = NULL; clean_exit: Main_ShowCursor(bOldMouseVisibility); if (browsingzip && zipfiles != NULL) { /* free zip file entries */ ZIP_FreeZipDir(zipfiles); zipfiles = NULL; } files_free(files); free(pStringMem); return retpath; } /*-----------------------------------------------------------------------*/ /** * Let user browse for a file, confname is used as default. * If bAllowNew is true, user can select new files also. * * If no file is selected, or there's some problem with the file, * return false and clear dlgname & confname. * Otherwise return true, set dlgname & confname to the new file name * (dlgname is shrunken & limited to maxlen and confname is assumed * to have FILENAME_MAX amount of space). */ bool SDLGui_FileConfSelect(const char *title, char *dlgname, char *confname, int maxlen, bool bAllowNew) { char *selname; selname = SDLGui_FileSelect(title, confname, NULL, bAllowNew); if (selname) { if (!File_DoesFileNameEndWithSlash(selname) && (bAllowNew || File_Exists(selname))) { Str_Copy(confname, selname, FILENAME_MAX); File_ShrinkName(dlgname, selname, maxlen); free(selname); return true; } dlgname[0] = confname[0] = 0; free(selname); } return false; } /*-----------------------------------------------------------------------*/ /** * Let user browse given directory. If one is selected, set directory * to confname & short name to dlgname, and return true, else false. * * (dlgname is limited to maxlen and confname is assumed to be * Hatari config field with FILENAME_MAX amount of space) */ bool SDLGui_DirConfSelect(const char *title, char *dlgname, char *confname, int maxlen) { char *selname; selname = SDLGui_FileSelect(title, confname, NULL, false); if (selname) { File_MakeValidPathName(selname); Str_Copy(confname, selname, FILENAME_MAX); File_ShrinkName(dlgname, selname, maxlen); free(selname); return true; } return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgFloppy.c000066400000000000000000000205451504763705000250060ustar00rootroot00000000000000/* Hatari - dlgFloppy.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgFloppy_fileid[] = "Hatari dlgFloppy.c"; #include #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #include "floppy.h" #define FLOPPYDLG_ENABLE_A 3 #define FLOPPYDLG_HEADS_DS_A 4 #define FLOPPYDLG_EJECTA 5 #define FLOPPYDLG_BROWSEA 6 #define FLOPPYDLG_DISKA 7 #define FLOPPYDLG_ENABLE_B 9 #define FLOPPYDLG_HEADS_DS_B 10 #define FLOPPYDLG_EJECTB 11 #define FLOPPYDLG_BROWSEB 12 #define FLOPPYDLG_DISKB 13 #define FLOPPYDLG_IMGDIR 15 #define FLOPPYDLG_BROWSEIMG 16 #define FLOPPYDLG_AUTOB 17 #define FLOPPYDLG_FASTFLOPPY 18 #define FLOPPYDLG_CREATEIMG 19 #define FLOPPYDLG_PROTOFF 21 #define FLOPPYDLG_PROTON 22 #define FLOPPYDLG_PROTAUTO 23 #define FLOPPYDLG_EXIT 24 /* The floppy disks dialog: */ static SGOBJ floppydlg[] = { { SGBOX, 0, 0, 0,0, 64,20, NULL }, { SGTEXT, 0, 0, 25,1, 12,1, "Floppy disks" }, { SGTEXT, 0, 0, 2,3, 8,1, "Drive A:" }, { SGCHECKBOX, 0, 0, 12,3, 9,1, "En_abled" }, { SGCHECKBOX, 0, 0, 23,3, 14,1, "_Double Sided" }, { SGBUTTON, 0, 0, 46,3, 7,1, "_Eject" }, { SGBUTTON, 0, 0, 54,3, 8,1, "B_rowse" }, { SGTEXT, 0, 0, 3,4, 58,1, NULL }, { SGTEXT, 0, 0, 2,6, 8,1, "Drive B:" }, { SGCHECKBOX, 0, 0, 12,6, 9,1, "Ena_bled" }, { SGCHECKBOX, 0, 0, 23,6, 14,1, "Doub_le Sided" }, { SGBUTTON, 0, 0, 46,6, 7,1, "E_ject" }, { SGBUTTON, 0, 0, 54,6, 8,1, "Bro_wse" }, { SGTEXT, 0, 0, 3,7, 58,1, NULL }, { SGTEXT, 0, 0, 2,9, 32,1, "Default floppy images directory:" }, { SGTEXT, 0, 0, 3,10, 58,1, NULL }, { SGBUTTON, 0, 0, 54, 9, 8,1, "Brow_se" }, { SGCHECKBOX, 0, 0, 2,12, 15,1, "Auto _insert B" }, { SGCHECKBOX, 0, 0, 2,14, 20,1, "_Fast floppy access" }, { SGBUTTON, 0, 0, 42,14, 20,1, "_Create blank image" }, { SGTEXT, 0, 0, 2,16, 17,1, "Write protection:" }, { SGRADIOBUT, 0, 0, 21,16, 5,1, "_Off" }, { SGRADIOBUT, 0, 0, 28,16, 4,1, "O_n" }, { SGRADIOBUT, 0, 0, 34,16, 6,1, "A_uto" }, { SGBUTTON, SG_DEFAULT, 0, 22,18, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; #define DLGMOUNT_A 2 #define DLGMOUNT_B 3 #define DLGMOUNT_CANCEL 4 /* The "Alert"-dialog: */ static SGOBJ alertdlg[] = { { SGBOX, 0, 0, 0,0, 40,6, NULL }, { SGTEXT, 0, 0, 3,1, 30,1, "Insert last created disk to?" }, { SGBUTTON, 0, 0, 3,4, 10,1, "Drive _A:" }, { SGBUTTON, 0, 0, 15,4, 10,1, "Drive _B:" }, { SGBUTTON, SG_CANCEL, 0, 27,4, 10,1, "_Cancel" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /** * Let user browse given disk, insert disk if one selected. */ static void DlgDisk_BrowseDisk(char *dlgname, int drive, int diskid) { char *selname, *zip_path; const char *tmpname, *realname; assert(drive >= 0 && drive < MAX_FLOPPYDRIVES); if (ConfigureParams.DiskImage.szDiskFileName[drive][0]) tmpname = ConfigureParams.DiskImage.szDiskFileName[drive]; else tmpname = ConfigureParams.DiskImage.szDiskImageDirectory; selname = SDLGui_FileSelect("Floppy image:", tmpname, &zip_path, false); if (!selname) return; if (File_Exists(selname)) { realname = Floppy_SetDiskFileName(drive, selname, zip_path); if (realname) File_ShrinkName(dlgname, realname, floppydlg[diskid].w); } else { Floppy_SetDiskFileNameNone(drive); dlgname[0] = '\0'; } free(zip_path); free(selname); } /** * Ask whether new disk should be inserted to A: or B: and if yes, insert. */ static void DlgFloppy_QueryInsert(char *namea, int ida, char *nameb, int idb, const char *path) { const char *realname; int diskid, dlgid; char *dlgname; SDLGui_CenterDlg(alertdlg); switch (SDLGui_DoDialog(alertdlg)) { case DLGMOUNT_A: dlgname = namea; dlgid = ida; diskid = 0; break; case DLGMOUNT_B: dlgname = nameb; dlgid = idb; diskid = 1; break; default: return; } realname = Floppy_SetDiskFileName(diskid, path, NULL); if (realname) File_ShrinkName(dlgname, realname, floppydlg[dlgid].w); } /** * Show and process the floppy disk image dialog. */ void DlgFloppy_Main(void) { int but, i; char *newdisk; char dlgname[MAX_FLOPPYDRIVES][64], dlgdiskdir[64]; SDLGui_CenterDlg(floppydlg); /* Set up dialog to actual values: */ /* Disk name A: */ if (EmulationDrives[0].bDiskInserted) File_ShrinkName(dlgname[0], ConfigureParams.DiskImage.szDiskFileName[0], floppydlg[FLOPPYDLG_DISKA].w); else dlgname[0][0] = '\0'; floppydlg[FLOPPYDLG_DISKA].txt = dlgname[0]; /* Disk name B: */ if (EmulationDrives[1].bDiskInserted) File_ShrinkName(dlgname[1], ConfigureParams.DiskImage.szDiskFileName[1], floppydlg[FLOPPYDLG_DISKB].w); else dlgname[1][0] = '\0'; floppydlg[FLOPPYDLG_DISKB].txt = dlgname[1]; /* Default image directory: */ File_ShrinkName(dlgdiskdir, ConfigureParams.DiskImage.szDiskImageDirectory, floppydlg[FLOPPYDLG_IMGDIR].w); floppydlg[FLOPPYDLG_IMGDIR].txt = dlgdiskdir; /* Auto insert disk B: */ if (ConfigureParams.DiskImage.bAutoInsertDiskB) floppydlg[FLOPPYDLG_AUTOB].state |= SG_SELECTED; else floppydlg[FLOPPYDLG_AUTOB].state &= ~SG_SELECTED; /* Write protection */ for (i = FLOPPYDLG_PROTOFF; i <= FLOPPYDLG_PROTAUTO; i++) { floppydlg[i].state &= ~SG_SELECTED; } floppydlg[FLOPPYDLG_PROTOFF+ConfigureParams.DiskImage.nWriteProtection].state |= SG_SELECTED; /* Fast floppy access */ if (ConfigureParams.DiskImage.FastFloppy) floppydlg[FLOPPYDLG_FASTFLOPPY].state |= SG_SELECTED; else floppydlg[FLOPPYDLG_FASTFLOPPY].state &= ~SG_SELECTED; /* Enable/disable drives A: and B: */ if (ConfigureParams.DiskImage.EnableDriveA) floppydlg[FLOPPYDLG_ENABLE_A].state |= SG_SELECTED; else floppydlg[FLOPPYDLG_ENABLE_A].state &= ~SG_SELECTED; if (ConfigureParams.DiskImage.EnableDriveB) floppydlg[FLOPPYDLG_ENABLE_B].state |= SG_SELECTED; else floppydlg[FLOPPYDLG_ENABLE_B].state &= ~SG_SELECTED; /* Set drives to single sided or double sided */ if (ConfigureParams.DiskImage.DriveA_NumberOfHeads == 2) floppydlg[FLOPPYDLG_HEADS_DS_A].state |= SG_SELECTED; else floppydlg[FLOPPYDLG_HEADS_DS_A].state &= ~SG_SELECTED; if (ConfigureParams.DiskImage.DriveB_NumberOfHeads == 2) floppydlg[FLOPPYDLG_HEADS_DS_B].state |= SG_SELECTED; else floppydlg[FLOPPYDLG_HEADS_DS_B].state &= ~SG_SELECTED; /* Draw and process the dialog */ do { but = SDLGui_DoDialog(floppydlg); switch (but) { case FLOPPYDLG_EJECTA: /* Eject disk in drive A: */ Floppy_SetDiskFileNameNone(0); dlgname[0][0] = '\0'; break; case FLOPPYDLG_BROWSEA: /* Choose a new disk A: */ DlgDisk_BrowseDisk(dlgname[0], 0, FLOPPYDLG_DISKA); break; case FLOPPYDLG_EJECTB: /* Eject disk in drive B: */ Floppy_SetDiskFileNameNone(1); dlgname[1][0] = '\0'; break; case FLOPPYDLG_BROWSEB: /* Choose a new disk B: */ DlgDisk_BrowseDisk(dlgname[1], 1, FLOPPYDLG_DISKB); break; case FLOPPYDLG_BROWSEIMG: SDLGui_DirConfSelect("Floppy image directory:", dlgdiskdir, ConfigureParams.DiskImage.szDiskImageDirectory, floppydlg[FLOPPYDLG_IMGDIR].w); break; case FLOPPYDLG_CREATEIMG: newdisk = DlgNewDisk_Main(); if (newdisk) { DlgFloppy_QueryInsert(dlgname[0], FLOPPYDLG_DISKA, dlgname[1], FLOPPYDLG_DISKB, newdisk); free(newdisk); } break; } } while (but != FLOPPYDLG_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Read values from dialog: */ for (i = FLOPPYDLG_PROTOFF; i <= FLOPPYDLG_PROTAUTO; i++) { if (floppydlg[i].state & SG_SELECTED) { ConfigureParams.DiskImage.nWriteProtection = i-FLOPPYDLG_PROTOFF; break; } } ConfigureParams.DiskImage.bAutoInsertDiskB = (floppydlg[FLOPPYDLG_AUTOB].state & SG_SELECTED); ConfigureParams.DiskImage.FastFloppy = (floppydlg[FLOPPYDLG_FASTFLOPPY].state & SG_SELECTED); ConfigureParams.DiskImage.EnableDriveA = (floppydlg[FLOPPYDLG_ENABLE_A].state & SG_SELECTED); ConfigureParams.DiskImage.EnableDriveB = (floppydlg[FLOPPYDLG_ENABLE_B].state & SG_SELECTED); ConfigureParams.DiskImage.DriveA_NumberOfHeads = ( (floppydlg[FLOPPYDLG_HEADS_DS_A].state & SG_SELECTED) ? 2 : 1 ); ConfigureParams.DiskImage.DriveB_NumberOfHeads = ( (floppydlg[FLOPPYDLG_HEADS_DS_B].state & SG_SELECTED) ? 2 : 1 ); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgHalt.c000066400000000000000000000047301504763705000244230ustar00rootroot00000000000000/* Hatari - dlgHalt.c - Emulation halt + reset handling alertbox Copyright (C) 2015 by Eero Tamminen This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgHalt_fileid[] = "Hatari dlgHalt.c"; #include #include "main.h" #include "reset.h" #include "debugui.h" #include "dialog.h" #include "screen.h" #include "sdlgui.h" #include "m68000.h" #define DLGHALT_WARM 2 #define DLGHALT_COLD 3 #define DLGHALT_DEBUG 4 #define DLGHALT_QUIT 5 #define DLGHALT_MSG "Detected double bus/address error => CPU halted!" /* The "Halt"-dialog: */ static SGOBJ haltdlg[] = { { SGBOX, 0, 0, 0,0, 52,7, NULL }, { SGTEXT, 0, 0, 2,1, 48,1, DLGHALT_MSG }, { SGBUTTON, SG_DEFAULT, 0, 6,3, 12,1, "_Warm reset" }, { SGBUTTON, 0, 0, 6,5, 12,1, "_Cold reset" }, { SGBUTTON, 0, 0, 28,3, 18,1, "Console _debugger" }, { SGBUTTON, SG_CANCEL, 0, 28,5, 18,1, "_Quit Hatari" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Make Hatari quit */ static void do_quit(int exitval) { if (bQuitProgram) { /* got here again, cold reset emulation to make sure we actually can exit */ fputs("Halt dialog invoked during Hatari shutdown, doing emulation cold reset...\n", stderr); Reset_Cold(); } Main_SetQuitValue(exitval); } /*-----------------------------------------------------------------------*/ /** * Show the "halt" dialog */ void Dialog_HaltDlg(void) { bool show = Main_ShowCursor(true); bool mode = SDL_GetRelativeMouseMode(); SDL_SetRelativeMouseMode(SDL_FALSE); /* if we get halt with --run-vbls, just quit right away */ if (Main_SetRunVBLs(0)) { Log_Printf(LOG_ERROR, DLGHALT_MSG); do_quit(1); return; } if (SDLGui_SetScreen(sdlscrn)) return; SDLGui_CenterDlg(haltdlg); switch (SDLGui_DoDialog(haltdlg)) { case DLGHALT_WARM: /* Reset to exit 'halt' state (resets CPU and regs.spcflags) */ Reset_Warm(); break; case DLGHALT_COLD: /* Warm reset isn't always enough to restore emulated system to working state */ Reset_Cold(); break; case DLGHALT_DEBUG: /* Call the debugger, restore screen so user sees what's on it */ Screen_UpdateRect(sdlscrn, 0,0, 0,0); DebugUI(REASON_CPU_EXCEPTION); break; case DLGHALT_QUIT: case SDLGUI_QUIT: do_quit(0); break; default: /* GUI errors */ do_quit(1); } Main_ShowCursor(show); SDL_SetRelativeMouseMode(mode); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgHardDisk.c000066400000000000000000000231031504763705000252170ustar00rootroot00000000000000/* Hatari - dlgHardDisk.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgHardDisk_fileid[] = "Hatari dlgHardDisk.c"; #include #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #define DISKDLG_ACSIPREVID 4 #define DISKDLG_ACSIID 6 #define DISKDLG_ACSINEXTID 7 #define DISKDLG_ACSIEJECT 8 #define DISKDLG_ACSIBROWSE 9 #define DISKDLG_ACSINAME 10 #define DISKDLG_SCSIPREVID 13 #define DISKDLG_SCSIID 15 #define DISKDLG_SCSINEXTID 16 #define DISKDLG_SCSIEJECT 17 #define DISKDLG_SCSIBROWSE 18 #define DISKDLG_SCSINAME 19 #define DISKDLG_IDEPREVID 22 #define DISKDLG_IDEID 24 #define DISKDLG_IDENEXTID 25 #define DISKDLG_IDESWAPOFF 27 #define DISKDLG_IDESWAPON 28 #define DISKDLG_IDESWAPAUTO 29 #define DISKDLG_IDEEJECT 30 #define DISKDLG_IDEBROWSE 31 #define DISKDLG_IDENAME 32 #define DISKDLG_GEMDOSEJECT 35 #define DISKDLG_GEMDOSBROWSE 36 #define DISKDLG_GEMDOSNAME 37 #define DISKDLG_GEMDOSCONV 38 #define DISKDLG_DRIVESKIP 39 #define DISKDLG_PROTOFF 41 #define DISKDLG_PROTON 42 #define DISKDLG_PROTAUTO 43 #define DISKDLG_BOOTHD 44 #define DISKDLG_EXIT 45 static char acsi_id_txt[2]; static char scsi_id_txt[2]; static char ide_id_txt[2]; static char dlgname_gdos[64], dlgname_acsi[64]; static char dlgname_scsi[64], dlgname_ide[64]; /* The disks dialog: */ static SGOBJ diskdlg[] = { { SGBOX, 0, 0, 0,0, 64,25, NULL }, { SGTEXT, 0, 0, 27,1, 10,1, "Hard disks" }, { SGBOX, 0, 0, 1,3, 62,2, NULL }, { SGBOX, 0, 0, 1,3, 62,1, NULL }, { SGBUTTON, 0, 0, 1,3, 3,1, "\x04" }, { SGTEXT, 0, 0, 5,3, 7,1, "ACSI HD" }, { SGTEXT, 0, 0, 13,3, 3,1, acsi_id_txt }, { SGBUTTON, 0, 0, 15,3, 3,1, "\x03" }, { SGBUTTON, 0, 0, 47,3, 7,1, "Ejec_t" }, { SGBUTTON, 0, 0, 55,3, 8,1, "Brow_se" }, { SGTEXT, 0, 0, 2,4, 60,1, dlgname_acsi }, { SGBOX, 0, 0, 1,6, 62,2, NULL }, { SGBOX, 0, 0, 1,6, 62,1, NULL }, { SGBUTTON, 0, 0, 1,6, 3,1, "\x04" }, { SGTEXT, 0, 0, 5,6, 9,1, "SCSI HD" }, { SGTEXT, 0, 0, 13,6, 1,1, scsi_id_txt }, { SGBUTTON, 0, 0, 15,6, 3,1, "\x03" }, { SGBUTTON, 0, 0, 47,6, 7,1, "Eje_ct" }, { SGBUTTON, 0, 0, 55,6, 8,1, "Bro_wse" }, { SGTEXT, 0, 0, 2,7, 60,1, dlgname_scsi }, { SGBOX, 0, 0, 1,9, 62,2, NULL }, { SGBOX, 0, 0, 1,9, 62,1, NULL }, { SGBUTTON, 0, 0, 1,9, 3,1, "\x04" }, { SGTEXT, 0, 0, 5,9, 19,1, "IDE HD" }, { SGTEXT, 0, 0, 12,9, 1,1, ide_id_txt }, { SGBUTTON, 0, 0, 15,9, 3,1, "\x03" }, { SGTEXT, 0, 0, 19,9, 9,1, "Byteswap:" }, { SGRADIOBUT, 0, 0, 29,9, 5,1, "Off" }, { SGRADIOBUT, 0, 0, 35,9, 4,1, "On" }, { SGRADIOBUT, 0, 0, 40,9, 6,1, "Auto" }, { SGBUTTON, 0, 0, 47,9, 7,1, "E_ject" }, { SGBUTTON, 0, 0, 55,9, 8,1, "Br_owse" }, { SGTEXT, 0, 0, 2,10, 60,1, dlgname_ide }, { SGBOX, 0, 0, 1,12, 62,8, NULL }, { SGTEXT, 0, 0, 2,12, 13,1, "GEMDOS drive:" }, { SGBUTTON, 0, 0, 47,12, 7,1, "_Eject" }, { SGBUTTON, 0, 0, 55,12, 8,1, "B_rowse" }, { SGTEXT, 0, 0, 3,13, 58,1, dlgname_gdos }, { SGCHECKBOX, 0, 0, 8,15, 43,1, "Atari <-> _host 8-bit file name conversion" }, { SGCHECKBOX, 0, 0, 8,16, 46,1, "Add GEMDOS HD after ACSI/SCSI/IDE _partitions" }, { SGTEXT, 0, 0, 8,18, 31,1, "Write protection:" }, { SGRADIOBUT, 0, 0, 26,18, 5,1, "O_ff" }, { SGRADIOBUT, 0, 0, 32,18, 4,1, "O_n" }, { SGRADIOBUT, 0, 0, 37,18, 6,1, "_Auto" }, { SGCHECKBOX, 0, 0, 2,21, 21,1, "_Boot from hard disk" }, { SGBUTTON, SG_DEFAULT, 0, 22,23, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; static void DlgHardDisk_PrepAcsi(int id) { if (ConfigureParams.Acsi[id].bUseDevice) { File_ShrinkName(dlgname_acsi, ConfigureParams.Acsi[id].sDeviceFile, diskdlg[DISKDLG_ACSINAME].w); } else { dlgname_acsi[0] = '\0'; } acsi_id_txt[0] = '0' + id; acsi_id_txt[1] = 0; } static void DlgHardDisk_PrepScsi(int id) { if (ConfigureParams.Scsi[id].bUseDevice) { File_ShrinkName(dlgname_scsi, ConfigureParams.Scsi[id].sDeviceFile, diskdlg[DISKDLG_SCSINAME].w); } else { dlgname_scsi[0] = '\0'; } scsi_id_txt[0] = '0' + id; scsi_id_txt[1] = 0; } static void DlgHardDisk_PrepIde(int id) { int idx; if (ConfigureParams.Ide[id].bUseDevice) { File_ShrinkName(dlgname_ide, ConfigureParams.Ide[id].sDeviceFile, diskdlg[DISKDLG_IDENAME].w); } else { dlgname_ide[0] = '\0'; } for (idx = DISKDLG_IDESWAPOFF; idx <= DISKDLG_IDESWAPAUTO; idx++) diskdlg[idx].state &= ~SG_SELECTED; diskdlg[DISKDLG_IDESWAPOFF + ConfigureParams.Ide[id].nByteSwap].state |= SG_SELECTED; ide_id_txt[0] = '0' + id; ide_id_txt[1] = '\0'; } static void DlgHardDisk_ReadBackIdeByteSwapSetting(int id) { int idx; for (idx = DISKDLG_IDESWAPOFF; idx <= DISKDLG_IDESWAPAUTO; idx++) { if (diskdlg[idx].state & SG_SELECTED) { ConfigureParams.Ide[id].nByteSwap = idx - DISKDLG_IDESWAPOFF; break; } } } /** * Show and process the hard disk dialog. */ void DlgHardDisk_Main(void) { int but, i; static int a_id, s_id, i_id; SDLGui_CenterDlg(diskdlg); /* Set up dialog to actual values: */ /* Boot from harddisk? */ if (ConfigureParams.HardDisk.bBootFromHardDisk) diskdlg[DISKDLG_BOOTHD].state |= SG_SELECTED; else diskdlg[DISKDLG_BOOTHD].state &= ~SG_SELECTED; /* Hard disk images: */ DlgHardDisk_PrepAcsi(a_id); DlgHardDisk_PrepScsi(s_id); DlgHardDisk_PrepIde(i_id); /* GEMDOS hard disk directory: */ if (ConfigureParams.HardDisk.bUseHardDiskDirectories) File_ShrinkName(dlgname_gdos, ConfigureParams.HardDisk.szHardDiskDirectories[0], diskdlg[DISKDLG_GEMDOSNAME].w); else dlgname_gdos[0] = '\0'; diskdlg[DISKDLG_GEMDOSNAME].txt = dlgname_gdos; if (ConfigureParams.HardDisk.bFilenameConversion) diskdlg[DISKDLG_GEMDOSCONV].state |= SG_SELECTED; else diskdlg[DISKDLG_GEMDOSCONV].state &= ~SG_SELECTED; if (ConfigureParams.HardDisk.nGemdosDrive == DRIVE_SKIP) diskdlg[DISKDLG_DRIVESKIP].state |= SG_SELECTED; else diskdlg[DISKDLG_DRIVESKIP].state &= ~SG_SELECTED; /* Write protection */ for (i = DISKDLG_PROTOFF; i <= DISKDLG_PROTAUTO; i++) { diskdlg[i].state &= ~SG_SELECTED; } diskdlg[DISKDLG_PROTOFF+ConfigureParams.HardDisk.nWriteProtection].state |= SG_SELECTED; /* Draw and process the dialog */ do { but = SDLGui_DoDialog(diskdlg); switch (but) { case DISKDLG_ACSIPREVID: if (a_id > 0) { --a_id; DlgHardDisk_PrepAcsi(a_id); } break; case DISKDLG_ACSINEXTID: if (a_id < 7) { ++a_id; DlgHardDisk_PrepAcsi(a_id); } break; case DISKDLG_ACSIEJECT: ConfigureParams.Acsi[a_id].bUseDevice = false; dlgname_acsi[0] = '\0'; break; case DISKDLG_ACSIBROWSE: if (SDLGui_FileConfSelect("ACSI HD image:", dlgname_acsi, ConfigureParams.Acsi[a_id].sDeviceFile, diskdlg[DISKDLG_ACSINAME].w, false)) ConfigureParams.Acsi[a_id].bUseDevice = true; break; case DISKDLG_SCSIPREVID: if (s_id > 0) { --s_id; DlgHardDisk_PrepScsi(s_id); } break; case DISKDLG_SCSINEXTID: if (s_id < 7) { ++s_id; DlgHardDisk_PrepScsi(s_id); } break; case DISKDLG_SCSIEJECT: ConfigureParams.Scsi[s_id].bUseDevice = false; dlgname_scsi[0] = '\0'; break; case DISKDLG_SCSIBROWSE: if (SDLGui_FileConfSelect("SCSI HD image:", dlgname_scsi, ConfigureParams.Scsi[s_id].sDeviceFile, diskdlg[DISKDLG_SCSINAME].w, false)) ConfigureParams.Scsi[s_id].bUseDevice = true; break; case DISKDLG_IDEPREVID: DlgHardDisk_ReadBackIdeByteSwapSetting(i_id); if (i_id > 0) { --i_id; DlgHardDisk_PrepIde(i_id); } break; case DISKDLG_IDENEXTID: DlgHardDisk_ReadBackIdeByteSwapSetting(i_id); if (i_id < 1) { ++i_id; DlgHardDisk_PrepIde(i_id); } break; case DISKDLG_IDEEJECT: ConfigureParams.Ide[i_id].bUseDevice = false; dlgname_ide[0] = '\0'; break; case DISKDLG_IDEBROWSE: if (SDLGui_FileConfSelect("IDE HD 0 image:", dlgname_ide, ConfigureParams.Ide[i_id].sDeviceFile, diskdlg[DISKDLG_IDENAME].w, false)) ConfigureParams.Ide[i_id].bUseDevice = true; break; case DISKDLG_GEMDOSEJECT: ConfigureParams.HardDisk.bUseHardDiskDirectories = false; dlgname_gdos[0] = '\0'; break; case DISKDLG_GEMDOSBROWSE: if (SDLGui_DirConfSelect("GEMDOS drive directory:", dlgname_gdos, ConfigureParams.HardDisk.szHardDiskDirectories[0], diskdlg[DISKDLG_GEMDOSNAME].w)) ConfigureParams.HardDisk.bUseHardDiskDirectories = true; break; } } while (but != DISKDLG_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Read values from dialog: */ DlgHardDisk_ReadBackIdeByteSwapSetting(i_id); for (i = DISKDLG_PROTOFF; i <= DISKDLG_PROTAUTO; i++) { if (diskdlg[i].state & SG_SELECTED) { ConfigureParams.HardDisk.nWriteProtection = i-DISKDLG_PROTOFF; break; } } ConfigureParams.HardDisk.bBootFromHardDisk = (diskdlg[DISKDLG_BOOTHD].state & SG_SELECTED); if (diskdlg[DISKDLG_DRIVESKIP].state & SG_SELECTED) ConfigureParams.HardDisk.nGemdosDrive = DRIVE_SKIP; else if (ConfigureParams.HardDisk.nGemdosDrive == DRIVE_SKIP) ConfigureParams.HardDisk.nGemdosDrive = DRIVE_C; if (diskdlg[DISKDLG_GEMDOSCONV].state & SG_SELECTED) ConfigureParams.HardDisk.bFilenameConversion = true; else ConfigureParams.HardDisk.bFilenameConversion = false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgJoystick.c000066400000000000000000000301271504763705000253310ustar00rootroot00000000000000/* Hatari - dlgJoystick.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgJoystick_fileid[] = "Hatari dlgJoystick.c"; #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "joy.h" #include "str.h" #define DLGJOY_STJOYNAME 3 #define DLGJOY_PREVSTJOY 4 #define DLGJOY_NEXTSTJOY 5 #define DLGJOY_DEFINEKEYS 7 #define DLGJOY_DISABLED 8 #define DLGJOY_USEKEYS 9 #define DLGJOY_USEREALJOY 10 #define DLGJOY_SDLJOYNAME 12 #define DLGJOY_PREVSDLJOY 13 #define DLGJOY_NEXTSDLJOY 14 #define DLGJOY_AUTOFIRE 15 #define DLGJOY_BUT2_SPACE 17 #define DLGJOY_BUT2_JUMP 18 #define DLGJOY_REMAPBUTTONS 19 #define DLGJOY_EXIT 20 /* The joysticks dialog: */ static char sSdlStickName[23]; static SGOBJ joydlg[] = { { SGBOX, 0, 0, 0,0, 32,23, NULL }, { SGTEXT, 0, 0, 8,1, 15,1, "Joysticks setup" }, { SGBOX, 0, 0, 4,3, 24,1, NULL }, { SGTEXT, 0, 0, 5,3, 22,1, NULL }, { SGBUTTON, 0, 0, 1,3, 3,1, "\x04", SG_SHORTCUT_LEFT }, { SGBUTTON, 0, 0, 28,3, 3,1, "\x03", SG_SHORTCUT_RIGHT }, { SGBOX, 0, 0, 1,4, 30,16, NULL }, { SGBUTTON, 0, 0, 17,7, 13,1, "D_efine keys" }, { SGRADIOBUT, 0, 0, 2,5, 10,1, "_disabled" }, { SGRADIOBUT, 0, 0, 2,7, 14,1, "use _keyboard" }, { SGRADIOBUT, 0, 0, 2,9, 20,1, "use real _joystick:" }, { SGBOX, 0, 0, 5,11, 24,1, NULL }, { SGTEXT, 0, 0, 6,11, 22,1, sSdlStickName }, { SGBUTTON, 0, 0, 4,11, 1,1, "\x04", SG_SHORTCUT_UP }, { SGBUTTON, 0, 0, 29,11, 1,1, "\x03", SG_SHORTCUT_DOWN }, { SGCHECKBOX, 0, 0, 2,13, 17,1, "Enable _autofire" }, { SGTEXT, 0, 0, 4,15, 9,1, "Button 2:" }, { SGRADIOBUT, 0, 0, 2,16, 10,1, "_space key" }, { SGRADIOBUT, 0, 0, 15,16, 10,1, "_up / jump" }, { SGBUTTON, 0, 0, 4,18, 24,1, "Re_map joystick buttons" }, { SGBUTTON, SG_DEFAULT, 0, 6,21, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /* The joystick keys setup dialog: */ static char sKeyInstruction[27]; static char sKeyName[27]; static SGOBJ joykeysdlg[] = { { SGBOX, 0, 0, 0,0, 30,5, NULL }, { SGTEXT, 0, 0, 2,1, 26,1, sKeyInstruction }, { SGTEXT, 0, 0, 2,3, 26,1, sKeyName }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /* The joystick button remapping setup dialog: */ static SGOBJ joybuttondlg[] = { { SGBOX, 0, 0, 0,0, 25,7, NULL }, { SGTEXT, 0, 0, 2,1, 21,1, "Press joystick button" }, { SGTEXT, 0, 0, 5,2, 15,1, sKeyInstruction }, { SGTEXT, 0, 0, 2,3, 18,1, "or ESC for none..." }, { SGTEXT, 0, 0, 2,5, 15,1, sKeyName }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; static char *sJoystickNames[JOYSTICK_COUNT] = { "ST Joystick 0", "ST Joystick 1", "STE Joypad A", "STE Joypad B", "Parallel port stick 1", "Parallel port stick 2" }; /*-----------------------------------------------------------------------*/ /** * Show dialogs for defining joystick keys and wait for a key press. */ static void DlgJoystick_DefineOneKey(char *pType, int *pKey) { SDL_Event sdlEvent; if (bQuitProgram) return; snprintf(sKeyInstruction, sizeof(sKeyInstruction), "Press key for '%s'...", pType); snprintf(sKeyName, sizeof(sKeyName), "(was: '%s')", SDL_GetKeyName(*pKey)); SDLGui_DrawDialog(joykeysdlg); /* drain buffered key events */ SDL_Delay(200); while (SDL_PollEvent(&sdlEvent)) { if (sdlEvent.type == SDL_KEYUP || sdlEvent.type == SDL_KEYDOWN) break; } /* get the real key */ do { SDL_WaitEvent(&sdlEvent); if (sdlEvent.type == SDL_KEYDOWN) { *pKey = sdlEvent.key.keysym.sym; snprintf(sKeyName, sizeof(sKeyName), "(now: '%s')", SDL_GetKeyName(*pKey)); SDLGui_DrawDialog(joykeysdlg); } else if (sdlEvent.type == SDL_QUIT) { bQuitProgram = true; return; } } while (sdlEvent.type != SDL_KEYUP); } /*-----------------------------------------------------------------------*/ /** * Let the user define joystick keys. */ static void DlgJoystick_DefineKeys(int nActJoy) { JOYSTICK *joycnf = &ConfigureParams.Joysticks.Joy[nActJoy]; SDLGui_CenterDlg(joykeysdlg); DlgJoystick_DefineOneKey("up", &joycnf->nKeyCodeUp); DlgJoystick_DefineOneKey("down", &joycnf->nKeyCodeDown); DlgJoystick_DefineOneKey("left", &joycnf->nKeyCodeLeft); DlgJoystick_DefineOneKey("right", &joycnf->nKeyCodeRight); if (nActJoy != JOYID_JOYPADA && nActJoy != JOYID_JOYPADB) { DlgJoystick_DefineOneKey("fire", &joycnf->nKeyCodeFire); } else { /* Handle the joypad buttons */ DlgJoystick_DefineOneKey("A", &joycnf->nKeyCodeFire); DlgJoystick_DefineOneKey("B", &joycnf->nKeyCodeB); DlgJoystick_DefineOneKey("C", &joycnf->nKeyCodeC); DlgJoystick_DefineOneKey("option", &joycnf->nKeyCodeOption); DlgJoystick_DefineOneKey("pause", &joycnf->nKeyCodePause); if (DlgAlert_Query("Do you also want to set up the number pad?")) { DlgJoystick_DefineOneKey("0", &joycnf->nKeyCodeNum[0]); DlgJoystick_DefineOneKey("1", &joycnf->nKeyCodeNum[1]); DlgJoystick_DefineOneKey("2", &joycnf->nKeyCodeNum[2]); DlgJoystick_DefineOneKey("3", &joycnf->nKeyCodeNum[3]); DlgJoystick_DefineOneKey("4", &joycnf->nKeyCodeNum[4]); DlgJoystick_DefineOneKey("5", &joycnf->nKeyCodeNum[5]); DlgJoystick_DefineOneKey("6", &joycnf->nKeyCodeNum[6]); DlgJoystick_DefineOneKey("7", &joycnf->nKeyCodeNum[7]); DlgJoystick_DefineOneKey("8", &joycnf->nKeyCodeNum[8]); DlgJoystick_DefineOneKey("9", &joycnf->nKeyCodeNum[9]); DlgJoystick_DefineOneKey("*", &joycnf->nKeyCodeStar); DlgJoystick_DefineOneKey("#", &joycnf->nKeyCodeHash); } } } /*-----------------------------------------------------------------------*/ /** * Show dialogs for remapping joystick buttons and wait for a button press. */ static void DlgJoystick_MapOneButton(const char *name, int *pButton) { SDL_Event sdlEvent; bool bDone = false; bool bSet = false; if (bQuitProgram) return; Str_Copy(sKeyInstruction, name, sizeof(sKeyInstruction)); if (*pButton >= 0) { snprintf(sKeyName, sizeof(sKeyName), "(was: id %d)", *pButton); } else { Str_Copy(sKeyName, "(was: none)", sizeof(sKeyName)); } SDLGui_DrawDialog(joybuttondlg); do { SDL_WaitEvent(&sdlEvent); switch (sdlEvent.type) { case SDL_JOYBUTTONDOWN: *pButton = sdlEvent.jbutton.button; bSet = true; snprintf(sKeyName, sizeof(sKeyName), "(now: id %d)", *pButton); SDLGui_DrawDialog(joybuttondlg); break; case SDL_JOYBUTTONUP: bDone = bSet; break; case SDL_KEYDOWN: if ((sdlEvent.key.keysym.sym == SDLK_ESCAPE) && (sdlEvent.key.repeat == 0)) { *pButton = -1; bSet = true; Str_Copy(sKeyName, "(now: none)", sizeof(sKeyName)); SDLGui_DrawDialog(joybuttondlg); } break; case SDL_KEYUP: if (sdlEvent.key.keysym.sym == SDLK_ESCAPE) { bDone = bSet; } break; case SDL_QUIT: bQuitProgram = true; bDone = true; break; } } while (!bDone); } /*-----------------------------------------------------------------------*/ /** * Let the user remap joystick buttons. */ static void DlgJoystick_RemapButtons(int nActJoy) { int *map = ConfigureParams.Joysticks.Joy[nActJoy].nJoyButMap; static const char *names[JOYSTICK_BUTTONS] = { "1: fire", "2: space / jump", "3: autofire" }; SDLGui_CenterDlg(joybuttondlg); for (int i = 0; i < JOYSTICK_BUTTONS; i++) DlgJoystick_MapOneButton(names[i], &map[i]); } /*-----------------------------------------------------------------------*/ /** * Adapt dialog using the values from the configuration structure. */ static void DlgJoystick_ReadValuesFromConf(int nActJoy) { int i; /* Check if joystick ID is available */ if (SDL_NumJoysticks() <= 0) { strcpy(sSdlStickName, "0: (none available)"); } else if (Joy_ValidateJoyId(nActJoy)) { snprintf(sSdlStickName, sizeof(sSdlStickName), "%i: %s", ConfigureParams.Joysticks.Joy[nActJoy].nJoyId, Joy_GetName(ConfigureParams.Joysticks.Joy[nActJoy].nJoyId)); } else { snprintf(sSdlStickName, sizeof(sSdlStickName), "0: %s", Joy_GetName(0)); } for (i = DLGJOY_DISABLED; i <= DLGJOY_USEREALJOY; i++) joydlg[i].state &= ~SG_SELECTED; switch (ConfigureParams.Joysticks.Joy[nActJoy].nJoystickMode) { case JOYSTICK_DISABLED: joydlg[DLGJOY_DISABLED].state |= SG_SELECTED; break; case JOYSTICK_KEYBOARD: joydlg[DLGJOY_USEKEYS].state |= SG_SELECTED; break; case JOYSTICK_REALSTICK: joydlg[DLGJOY_USEREALJOY].state |= SG_SELECTED; break; } if (ConfigureParams.Joysticks.Joy[nActJoy].bEnableAutoFire) joydlg[DLGJOY_AUTOFIRE].state |= SG_SELECTED; else joydlg[DLGJOY_AUTOFIRE].state &= ~SG_SELECTED; if (ConfigureParams.Joysticks.Joy[nActJoy].bEnableJumpOnFire2) { joydlg[DLGJOY_BUT2_JUMP].state |= SG_SELECTED; joydlg[DLGJOY_BUT2_SPACE].state &= ~SG_SELECTED; } else { joydlg[DLGJOY_BUT2_SPACE].state |= SG_SELECTED; joydlg[DLGJOY_BUT2_JUMP].state &= ~SG_SELECTED; } } /*-----------------------------------------------------------------------*/ /** * Read values from dialog and write them to the configuration structure. */ static void DlgJoystick_WriteValuesToConf(int nActJoy) { if (joydlg[DLGJOY_DISABLED].state & SG_SELECTED) ConfigureParams.Joysticks.Joy[nActJoy].nJoystickMode = JOYSTICK_DISABLED; else if (joydlg[DLGJOY_USEKEYS].state & SG_SELECTED) ConfigureParams.Joysticks.Joy[nActJoy].nJoystickMode = JOYSTICK_KEYBOARD; else ConfigureParams.Joysticks.Joy[nActJoy].nJoystickMode = JOYSTICK_REALSTICK; ConfigureParams.Joysticks.Joy[nActJoy].bEnableAutoFire = (joydlg[DLGJOY_AUTOFIRE].state & SG_SELECTED); ConfigureParams.Joysticks.Joy[nActJoy].bEnableJumpOnFire2 = (joydlg[DLGJOY_BUT2_JUMP].state & SG_SELECTED); ConfigureParams.Joysticks.Joy[nActJoy].nJoyId = joydlg[DLGJOY_SDLJOYNAME].txt[0] - '0'; } /*-----------------------------------------------------------------------*/ /** * Show and process the joystick dialog. */ void Dialog_JoyDlg(void) { int but; static int nActJoy = 1; int nMaxId; SDLGui_CenterDlg(joydlg); joydlg[DLGJOY_STJOYNAME].txt = sJoystickNames[nActJoy]; nMaxId = Joy_GetMaxId(); /* Set up dialog from actual values: */ DlgJoystick_ReadValuesFromConf(nActJoy); do { but = SDLGui_DoDialog(joydlg); switch (but) { case DLGJOY_PREVSDLJOY: // Select the previous SDL joystick if (ConfigureParams.Joysticks.Joy[nActJoy].nJoyId > 0) { ConfigureParams.Joysticks.Joy[nActJoy].nJoyId -= 1; snprintf(sSdlStickName, sizeof(sSdlStickName), "%i: %s", ConfigureParams.Joysticks.Joy[nActJoy].nJoyId, Joy_GetName(ConfigureParams.Joysticks.Joy[nActJoy].nJoyId)); } break; case DLGJOY_NEXTSDLJOY: // Select the next SDL joystick if (ConfigureParams.Joysticks.Joy[nActJoy].nJoyId < nMaxId) { ConfigureParams.Joysticks.Joy[nActJoy].nJoyId += 1; snprintf(sSdlStickName, sizeof(sSdlStickName), "%i: %s", ConfigureParams.Joysticks.Joy[nActJoy].nJoyId, Joy_GetName(ConfigureParams.Joysticks.Joy[nActJoy].nJoyId)); } break; case DLGJOY_DEFINEKEYS: // Define new keys for keyboard emulation DlgJoystick_DefineKeys(nActJoy); break; case DLGJOY_PREVSTJOY: // Switch to the previous ST joystick setup tab if (nActJoy > 0) { DlgJoystick_WriteValuesToConf(nActJoy); nActJoy -= 1; DlgJoystick_ReadValuesFromConf(nActJoy); joydlg[DLGJOY_STJOYNAME].txt = sJoystickNames[nActJoy]; } break; case DLGJOY_NEXTSTJOY: // Switch to the next ST joystick setup tab if (nActJoy < 5) { DlgJoystick_WriteValuesToConf(nActJoy); nActJoy += 1; DlgJoystick_ReadValuesFromConf(nActJoy); joydlg[DLGJOY_STJOYNAME].txt = sJoystickNames[nActJoy]; } break; case DLGJOY_REMAPBUTTONS: // Remap joystick buttons DlgJoystick_RemapButtons(nActJoy); break; } } while (but != DLGJOY_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Tell ikbd to release joystick button 2 emulated * space bar in the theoretical case it has gotten stuck * down, and to avoid that case, prevent it also going * down if user pressed the button when invoking GUI */ if (JoystickSpaceBar == JOYSTICK_SPACE_DOWNED) JoystickSpaceBar = JOYSTICK_SPACE_UP; else if (JoystickSpaceBar == JOYSTICK_SPACE_DOWN) JoystickSpaceBar = JOYSTICK_SPACE_NULL; DlgJoystick_WriteValuesToConf(nActJoy); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgKeyboard.c000066400000000000000000000160531504763705000252740ustar00rootroot00000000000000/* Hatari - dlgKeyboard.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgKeyboard_fileid[] = "Hatari dlgKeyboard.c"; #include #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #include "str.h" #include "keymap.h" #define DLGKEY_SYMBOLIC 4 #define DLGKEY_SCANCODE 5 #define DLGKEY_CLEARFILE 6 #define DLGKEY_MAPNAME 8 #define DLGKEY_MAPBROWSE 9 #define DLGKEY_SCPREV 13 #define DLGKEY_SCNAME 14 #define DLGKEY_SCNEXT 15 #define DLGKEY_SCMODVAL 17 #define DLGKEY_SCMODDEF 18 #define DLGKEY_SCNOMODVAL 20 #define DLGKEY_SCNOMODDEF 21 #define DLGKEY_KEYREPEAT 22 #define DLGKEY_EXIT 23 static char sc_modval[16]; static char sc_nomodval[16]; /* The keyboard dialog: */ static SGOBJ keyboarddlg[] = { { SGBOX, 0, 0, 0,0, 46,24, NULL }, { SGTEXT, 0, 0, 16,1, 14,1, "Keyboard setup" }, { SGBOX, 0, 0, 1,3, 44,7, NULL }, { SGTEXT, 0, 0, 2,3, 17,1, "Keyboard mapping:" }, { SGRADIOBUT, 0, 0, 4,5, 10,1, "_Symbolic" }, { SGRADIOBUT, 0, 0, 17,5, 10,1, "S_cancode" }, { SGBUTTON, 0, 0, 36,5, 8,1, "Cle_ar" }, { SGTEXT, 0, 0, 2,7, 13,1, "Mapping file:" }, { SGTEXT, 0, 0, 2,8, 42,1, NULL }, { SGBUTTON, 0, 0, 36, 7, 8,1, "_Browse" }, { SGBOX, 0, 0, 1,11, 44,8, NULL }, { SGTEXT, 0, 0, 2,11, 12,1, "Shortcuts:" }, { SGBOX, 0, 0, 2,13, 42,1, NULL }, { SGBUTTON, 0, 0, 2,13, 1,1, "\x04", SG_SHORTCUT_LEFT }, { SGTEXT, 0, 0, 4,13, 20,1, NULL }, { SGBUTTON, 0, 0, 43,13, 1,1, "\x03", SG_SHORTCUT_RIGHT }, { SGTEXT, 0, 0, 2,15, 17,1, "With modifier:" }, { SGTEXT, 0, 0, 20,15, 12,1, sc_modval }, { SGBUTTON, 0, 0, 36,15, 8,1, "_Define" }, { SGTEXT, 0, 0, 2,17, 17,1, "Without modifier:" }, { SGTEXT, 0, 0, 20,17, 12,1, sc_nomodval }, { SGBUTTON, 0, 0, 36,17, 8,1, "D_efine" }, { SGCHECKBOX, 0, 0, 2,20, 41,1, "Use key _repeat in fast forward mode" }, { SGBUTTON, SG_DEFAULT, 0, 13,22, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; static char *sc_names[SHORTCUT_KEYS] = { "Edit settings", "Toggle fullscreen", "Toggle borders", "Grab mouse", "Cold reset", "Warm reset", "Take screenshot", "Boss key", "Joystick cursor emulation", "Fast forward", "Record animation", "Record sound", "Toggle sound", "Enter debugger", "Pause emulation", "Quit emulator", "Load memory snapshot", "Save memory snapshot", "Insert disk A:", "Toggle joystick 0", "Toggle joystick 1", "Toggle joypad A", "Toggle joypad B" }; static char sScKeyType[28]; static char sScKeyName[28]; static SGOBJ sckeysdlg[] = { { SGBOX, 0, 0, 0,0, 30,6, NULL }, { SGTEXT, 0, 0, 2,1, 28,1, "Press key for:" }, { SGTEXT, 0, 0, 2,2, 28,1, sScKeyType }, { SGTEXT, 0, 0, 2,4, 28,1, sScKeyName }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /** * Show dialogs for defining shortcut keys and wait for a key press. */ static void DlgKbd_DefineShortcutKey(int sc, bool withMod) { SDL_Event sdlEvent; int *pscs; int i; if (bQuitProgram) return; SDLGui_CenterDlg(sckeysdlg); if (withMod) pscs = ConfigureParams.Shortcut.withModifier; else pscs = ConfigureParams.Shortcut.withoutModifier; snprintf(sScKeyType, sizeof(sScKeyType), "'%s'", sc_names[sc]); snprintf(sScKeyName, sizeof(sScKeyName), "(was: '%s')", Keymap_GetKeyName(pscs[sc])); SDLGui_DrawDialog(sckeysdlg); /* drain buffered key events */ SDL_Delay(200); while (SDL_PollEvent(&sdlEvent)) { if (sdlEvent.type == SDL_KEYUP || sdlEvent.type == SDL_KEYDOWN) break; } /* get the real key */ do { SDL_WaitEvent(&sdlEvent); switch (sdlEvent.type) { case SDL_KEYDOWN: pscs[sc] = sdlEvent.key.keysym.sym; snprintf(sScKeyName, sizeof(sScKeyName), "(now: '%s')", Keymap_GetKeyName(sdlEvent.key.keysym.sym)); SDLGui_DrawDialog(sckeysdlg); break; case SDL_MOUSEBUTTONDOWN: if (sdlEvent.button.button == SDL_BUTTON_RIGHT) { pscs[sc] = 0; return; } else if (sdlEvent.button.button == SDL_BUTTON_LEFT) { return; } break; case SDL_QUIT: bQuitProgram = true; return; } } while (sdlEvent.type != SDL_KEYUP); /* Make sure that no other shortcut key has the same value */ for (i = 0; i < SHORTCUT_KEYS; i++) { if (i == sc) continue; if (pscs[i] == pscs[sc]) { pscs[i] = 0; DlgAlert_Notice("Removing key from other shortcut!"); } } } /** * Set name for given shortcut, or show it's unset */ static void DlgKbd_SetName(char *str, size_t maxlen, int keysym) { if (keysym) Str_Copy(str, Keymap_GetKeyName(keysym), maxlen); else Str_Copy(str, "", maxlen); } /** * Refresh the shortcut texts in the dialog */ static void DlgKbd_RefreshShortcuts(int sc) { int keysym; /* with modifier */ keysym = ConfigureParams.Shortcut.withModifier[sc]; DlgKbd_SetName(sc_modval, sizeof(sc_modval), keysym); /* without modifier */ keysym = ConfigureParams.Shortcut.withoutModifier[sc]; DlgKbd_SetName(sc_nomodval, sizeof(sc_nomodval), keysym); keyboarddlg[DLGKEY_SCNAME].txt = sc_names[sc]; } /** * Show and process the "Keyboard" dialog. */ void Dialog_KeyboardDlg(void) { int but; char dlgmapfile[44]; int cur_sc = 0; SDLGui_CenterDlg(keyboarddlg); /* Set up dialog from actual values: */ keyboarddlg[DLGKEY_SYMBOLIC].state &= ~SG_SELECTED; keyboarddlg[DLGKEY_SCANCODE].state &= ~SG_SELECTED; keyboarddlg[DLGKEY_SYMBOLIC+ConfigureParams.Keyboard.nKeymapType].state |= SG_SELECTED; File_ShrinkName(dlgmapfile, ConfigureParams.Keyboard.szMappingFileName, keyboarddlg[DLGKEY_MAPNAME].w); keyboarddlg[DLGKEY_MAPNAME].txt = dlgmapfile; DlgKbd_RefreshShortcuts(cur_sc); if (ConfigureParams.Keyboard.bFastForwardKeyRepeat) keyboarddlg[DLGKEY_KEYREPEAT].state |= SG_SELECTED; else keyboarddlg[DLGKEY_KEYREPEAT].state &= ~SG_SELECTED; /* Show the dialog: */ do { but = SDLGui_DoDialog(keyboarddlg); switch (but) { case DLGKEY_MAPBROWSE: SDLGui_FileConfSelect("Keyboard mapping file:", dlgmapfile, ConfigureParams.Keyboard.szMappingFileName, keyboarddlg[DLGKEY_MAPNAME].w, false); break; case DLGKEY_CLEARFILE: ConfigureParams.Keyboard.szMappingFileName[0] = 0; dlgmapfile[0] = 0; break; case DLGKEY_SCPREV: if (cur_sc > 0) { --cur_sc; DlgKbd_RefreshShortcuts(cur_sc); } break; case DLGKEY_SCNEXT: if (cur_sc < SHORTCUT_KEYS-1) { ++cur_sc; DlgKbd_RefreshShortcuts(cur_sc); } break; case DLGKEY_SCMODDEF: DlgKbd_DefineShortcutKey(cur_sc, true); DlgKbd_RefreshShortcuts(cur_sc); break; case DLGKEY_SCNOMODDEF: DlgKbd_DefineShortcutKey(cur_sc, false); DlgKbd_RefreshShortcuts(cur_sc); break; } } while (but != DLGKEY_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Read values from dialog: */ if (keyboarddlg[DLGKEY_SYMBOLIC].state & SG_SELECTED) ConfigureParams.Keyboard.nKeymapType = KEYMAP_SYMBOLIC; else ConfigureParams.Keyboard.nKeymapType = KEYMAP_SCANCODE; ConfigureParams.Keyboard.bFastForwardKeyRepeat = (keyboarddlg[DLGKEY_KEYREPEAT].state & SG_SELECTED); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgMain.c000066400000000000000000000105241504763705000244150ustar00rootroot00000000000000/* Hatari - dlgMain.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. The main dialog. */ const char DlgMain_fileid[] = "Hatari dlgMain.c"; #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "screen.h" #define MAINDLG_SYSTEM 2 #define MAINDLG_CPU 3 #define MAINDLG_ROM 4 #define MAINDLG_MEMORY 5 #define MAINDLG_FLOPPYS 6 #define MAINDLG_HARDDISK 7 #define MAINDLG_MONITOR 8 #define MAINDLG_WINDOW 9 #define MAINDLG_JOY 10 #define MAINDLG_KEYBD 11 #define MAINDLG_DEVICES 12 #define MAINDLG_SOUND 13 #define MAINDLG_ABOUT 14 #define MAINDLG_LOADCFG 15 #define MAINDLG_SAVECFG 16 #define MAINDLG_NORESET 17 #define MAINDLG_RESET 18 #define MAINDLG_OK 19 #define MAINDLG_QUIT 20 #define MAINDLG_CANCEL 21 /* The main dialog: */ static SGOBJ maindlg[] = { { SGBOX, 0, 0, 0,0, 50,19, NULL }, { SGTEXT, 0, 0, 17,1, 16,1, "Hatari main menu" }, { SGBUTTON, 0, 0, 2, 4, 13,1, "S_ystem" }, { SGBUTTON, 0, 0, 2, 6, 13,1, "CP_U" }, { SGBUTTON, 0, 0, 2, 8, 13,1, "_ROM" }, { SGBUTTON, 0, 0, 2,10, 13,1, "_Memory" }, { SGBUTTON, 0, 0, 17, 4, 16,1, "_Floppy disks" }, { SGBUTTON, 0, 0, 17, 6, 16,1, "Hard _disks" }, { SGBUTTON, 0, 0, 17, 8, 16,1, "_Atari screen" }, { SGBUTTON, 0, 0, 17,10, 16,1, "_Hatari screen" }, { SGBUTTON, 0, 0, 35, 4, 13,1, "_Joysticks" }, { SGBUTTON, 0, 0, 35, 6, 13,1, "_Keyboard" }, { SGBUTTON, 0, 0, 35, 8, 13,1, "D_evices" }, { SGBUTTON, 0, 0, 35,10, 13,1, "S_ound" }, { SGBUTTON, 0, 0, 2,13, 13,1, "A_bout" }, { SGBUTTON, 0, 0, 17,13, 16,1, "_Load config" }, { SGBUTTON, 0, 0, 35,13, 13,1, "_Save config" }, { SGRADIOBUT, 0, 0, 3,15, 10,1, "_No Reset" }, { SGRADIOBUT, 0, 0, 3,17, 15,1, "Reset ma_chine" }, { SGBUTTON, SG_DEFAULT, 0, 21,15, 8,3, "OK" }, { SGBUTTON, 0, 0, 36,15, 10,1, "_Quit" }, { SGBUTTON, SG_CANCEL, 0, 36,17, 10,1, "Cancel" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /** * This functions sets up the actual font and then displays the main dialog. */ int Dialog_MainDlg(bool *bReset, bool *bLoadedSnapshot) { int retbut; bool bOldMouseVisibility; int nOldMouseX, nOldMouseY; char *psNewCfg; *bReset = false; *bLoadedSnapshot = false; if (SDLGui_SetScreen(sdlscrn)) return false; SDL_GetMouseState(&nOldMouseX, &nOldMouseY); bOldMouseVisibility = Main_ShowCursor(true); SDLGui_CenterDlg(maindlg); maindlg[MAINDLG_NORESET].state |= SG_SELECTED; maindlg[MAINDLG_RESET].state &= ~SG_SELECTED; do { retbut = SDLGui_DoDialog(maindlg); switch (retbut) { case MAINDLG_ABOUT: Dialog_AboutDlg(); break; case MAINDLG_CPU: DlgCpu_Main(); break; case MAINDLG_FLOPPYS: DlgFloppy_Main(); break; case MAINDLG_HARDDISK: DlgHardDisk_Main(); break; case MAINDLG_ROM: DlgRom_Main(); break; case MAINDLG_MONITOR: Dialog_MonitorDlg(); break; case MAINDLG_WINDOW: Dialog_WindowDlg(); break; case MAINDLG_SYSTEM: DlgSystem_Main(); break; case MAINDLG_MEMORY: if (Dialog_MemDlg()) { /* Memory snapshot has been loaded - leave GUI immediately */ *bLoadedSnapshot = true; Main_ShowCursor(bOldMouseVisibility); Main_WarpMouse(nOldMouseX, nOldMouseY, true); return true; } break; case MAINDLG_JOY: Dialog_JoyDlg(); break; case MAINDLG_KEYBD: Dialog_KeyboardDlg(); break; case MAINDLG_DEVICES: Dialog_DeviceDlg(); break; case MAINDLG_SOUND: Dialog_SoundDlg(); break; case MAINDLG_LOADCFG: psNewCfg = SDLGui_FileSelect("Load configuration:", sConfigFileName, NULL, false); if (psNewCfg) { strcpy(sConfigFileName, psNewCfg); Configuration_Load(NULL); free(psNewCfg); } break; case MAINDLG_SAVECFG: psNewCfg = SDLGui_FileSelect("Save configuration:", sConfigFileName, NULL, true); if (psNewCfg) { strcpy(sConfigFileName, psNewCfg); Configuration_Save(); free(psNewCfg); } break; case MAINDLG_QUIT: bQuitProgram = true; break; } } while (retbut != MAINDLG_OK && retbut != MAINDLG_CANCEL && retbut != SDLGUI_QUIT && retbut != SDLGUI_ERROR && !bQuitProgram); if (maindlg[MAINDLG_RESET].state & SG_SELECTED) *bReset = true; Main_ShowCursor(bOldMouseVisibility); Main_WarpMouse(nOldMouseX, nOldMouseY, true); return (retbut == MAINDLG_OK); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgMemory.c000066400000000000000000000140031504763705000247750ustar00rootroot00000000000000/* Hatari - dlgMemory.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgMemory_fileid[] = "Hatari dlgMemory.c"; #include "main.h" #include "dialog.h" #include "sdlgui.h" #include "memorySnapShot.h" #include "file.h" #include "options.h" #define DLGMEM_256KB 4 #define DLGMEM_512KB 5 #define DLGMEM_1MB 6 #define DLGMEM_2MB 7 #define DLGMEM_2_5MB 8 #define DLGMEM_4MB 9 #define DLGMEM_8MB 10 #define DLGMEM_10MB 11 #define DLGMEM_14MB 12 #define DLGMEM_TTRAM_LESS 14 #define DLGMEM_TTRAM_TEXT 15 #define DLGMEM_TTRAM_MORE 16 #define DLGMEM_FILENAME 21 #define DLGMEM_SAVE 22 #define DLGMEM_RESTORE 23 #define DLGMEM_AUTOSAVE 24 #define DLGMEM_EXIT 25 /* String for TT RAM size */ static char sTTRamSize[5]; #define DLG_TTRAM_STEP 4 #define DLG_TTRAM_MIN 0 #define DLG_TTRAM_MAX 1024 static char dlgSnapShotName[36+1]; /* The memory dialog: */ static SGOBJ memorydlg[] = { { SGBOX, 0, 0, 0,0, 40,25, NULL }, { SGBOX, 0, 0, 1,1, 38,10, NULL }, { SGTEXT, 0, 0, 15,2, 12,1, "Memory setup" }, { SGTEXT, 0, 0, 4,4, 12,1, "ST-RAM size:" }, { SGRADIOBUT, 0, 0, 7,6, 9,1, "256 _KiB" }, { SGRADIOBUT, 0, 0, 7,7, 9,1, "512 Ki_B" }, { SGRADIOBUT, 0, 0, 18,4, 9,1, " _1 MiB" }, { SGRADIOBUT, 0, 0, 18,5, 9,1, " _2 MiB" }, { SGRADIOBUT, 0, 0, 18,6, 9,1, "2._5 MiB" }, { SGRADIOBUT, 0, 0, 18,7, 9,1, " _4 MiB" }, { SGRADIOBUT, 0, 0, 29,4, 9,1, " _8 MiB" }, { SGRADIOBUT, 0, 0, 29,5, 9,1, "1_0 MiB" }, { SGRADIOBUT, 0, 0, 29,6, 9,1, "14 _MiB" }, { SGTEXT, 0, 0, 4,9,12,1, "TT-RAM size:" }, { SGBUTTON, 0, 0, 18,9, 1,1, "\x04", SG_SHORTCUT_LEFT }, { SGTEXT, 0, 0, 20,9, 4,1, sTTRamSize }, { SGBUTTON, 0, 0, 25,9, 1,1, "\x03", SG_SHORTCUT_RIGHT }, { SGTEXT, 0, 0, 27,9,12,1, "MiB" }, { SGBOX, 0, 0, 1,12, 38,10, NULL }, { SGTEXT, 0, 0, 12,13, 17,1, "Memory state save" }, { SGTEXT, 0, 0, 2,15, 20,1, "Snap-shot file name:" }, { SGTEXT, 0, 0, 2,16, 36,1, dlgSnapShotName }, { SGBUTTON, 0, 0, 8,18, 10,1, "_Save" }, { SGBUTTON, 0, 0, 22,18, 10,1, "_Restore" }, { SGCHECKBOX, 0, 0, 2,20, 34,1, "_Load/save state at start-up/exit" }, { SGBUTTON, SG_DEFAULT, 0, 10,23, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /** * Show and process the memory dialog. * @return true if a memory snapshot has been loaded, false otherwise */ bool Dialog_MemDlg(void) { int i, memsize; int but; SDLGui_CenterDlg(memorydlg); for (i = DLGMEM_256KB; i <= DLGMEM_14MB; i++) { memorydlg[i].state &= ~SG_SELECTED; } switch (ConfigureParams.Memory.STRamSize_KB) { case 256: memorydlg[DLGMEM_256KB].state |= SG_SELECTED; break; case 512: memorydlg[DLGMEM_512KB].state |= SG_SELECTED; break; case 1024: memorydlg[DLGMEM_1MB].state |= SG_SELECTED; break; case 2*1024: memorydlg[DLGMEM_2MB].state |= SG_SELECTED; break; case 2*1024+512: memorydlg[DLGMEM_2_5MB].state |= SG_SELECTED; break; case 4*1024: memorydlg[DLGMEM_4MB].state |= SG_SELECTED; break; case 8*1024: memorydlg[DLGMEM_8MB].state |= SG_SELECTED; break; case 10*1024: memorydlg[DLGMEM_10MB].state |= SG_SELECTED; break; default: memorydlg[DLGMEM_14MB].state |= SG_SELECTED; break; } memsize = ConfigureParams.Memory.TTRamSize_KB/1024; if (memsize < DLG_TTRAM_MIN) memsize = DLG_TTRAM_MIN; else if (memsize > DLG_TTRAM_MAX) memsize = DLG_TTRAM_MAX; sprintf(sTTRamSize, "%4i", memsize); File_ShrinkName(dlgSnapShotName, ConfigureParams.Memory.szMemoryCaptureFileName, memorydlg[DLGMEM_FILENAME].w); if (ConfigureParams.Memory.bAutoSave) memorydlg[DLGMEM_AUTOSAVE].state |= SG_SELECTED; else memorydlg[DLGMEM_AUTOSAVE].state &= ~SG_SELECTED; do { but = SDLGui_DoDialog(memorydlg); switch (but) { case DLGMEM_TTRAM_LESS: memsize = Opt_ValueAlignMinMax(memsize - DLG_TTRAM_STEP, DLG_TTRAM_STEP, DLG_TTRAM_MIN, DLG_TTRAM_MAX); sprintf(sTTRamSize, "%4i", memsize); break; case DLGMEM_TTRAM_MORE: memsize = Opt_ValueAlignMinMax(memsize + DLG_TTRAM_STEP, DLG_TTRAM_STEP, DLG_TTRAM_MIN, DLG_TTRAM_MAX); sprintf(sTTRamSize, "%4i", memsize); break; case DLGMEM_SAVE: /* Save memory snap-shot */ if (SDLGui_FileConfSelect("Save memory snapshot:", dlgSnapShotName, ConfigureParams.Memory.szMemoryCaptureFileName, memorydlg[DLGMEM_FILENAME].w, true)) { MemorySnapShot_Capture(ConfigureParams.Memory.szMemoryCaptureFileName, true); } break; case DLGMEM_RESTORE: /* Load memory snap-shot */ if (SDLGui_FileConfSelect("Load memory snapshot:", dlgSnapShotName, ConfigureParams.Memory.szMemoryCaptureFileName, memorydlg[DLGMEM_FILENAME].w, false)) { MemorySnapShot_Restore(ConfigureParams.Memory.szMemoryCaptureFileName, true); return true; } break; } } while (but != DLGMEM_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram ); /* Read new values from dialog: */ if (memorydlg[DLGMEM_256KB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 256; else if (memorydlg[DLGMEM_512KB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 512; else if (memorydlg[DLGMEM_1MB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 1024; else if (memorydlg[DLGMEM_2MB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 2*1024; else if (memorydlg[DLGMEM_2_5MB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 2*1024+512; else if (memorydlg[DLGMEM_4MB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 4*1024; else if (memorydlg[DLGMEM_8MB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 8*1024; else if (memorydlg[DLGMEM_10MB].state & SG_SELECTED) ConfigureParams.Memory.STRamSize_KB = 10*1024; else ConfigureParams.Memory.STRamSize_KB = 14*1024; ConfigureParams.Memory.TTRamSize_KB = memsize*1024; ConfigureParams.Memory.bAutoSave = (memorydlg[DLGMEM_AUTOSAVE].state & SG_SELECTED); return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgNewDisk.c000066400000000000000000000104221504763705000250720ustar00rootroot00000000000000/* Hatari - dlgNewDisk.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgNewDisk_fileid[] = "Hatari dlgNewDisk.c"; #include "main.h" #include "configuration.h" #include "createBlankImage.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #include "log.h" #define DLGNEWDISK_DECTRACK 3 #define DLGNEWDISK_TRACKSTR 4 #define DLGNEWDISK_INCTRACK 5 #define DLGNEWDISK_SECTORS9 7 #define DLGNEWDISK_SECTORS10 8 #define DLGNEWDISK_SECTORS11 9 #define DLGNEWDISK_SECTORS18 10 #define DLGNEWDISK_SECTORS36 11 #define DLGNEWDISK_SIDES1 13 #define DLGNEWDISK_SIDES2 14 #define DLGNEWDISK_LABEL 16 #define DLGNEWDISK_SAVE 17 #define DLGNEWDISK_EXIT 18 static char szTracks[12]; /* We only use 3 chars but we need a bigger buffer to silence gcc warnings */ static int nTracks = 80; #define DLGNEWDISK_LABEL_SIZE (8+3) static char dlgLabel[ DLGNEWDISK_LABEL_SIZE+1 ]; /* The new disk image dialog: */ static SGOBJ newdiskdlg[] = { { SGBOX, 0, 0, 0,0, 29,16, NULL }, { SGTEXT, 0, 0, 6,1, 16,1, "New floppy image" }, { SGTEXT, 0, 0, 2,3, 7,1, "Tracks:" }, { SGBUTTON, 0, 0, 12,3, 1,1, "\x04", SG_SHORTCUT_LEFT }, { SGTEXT, 0, 0, 14,3, 2,1, szTracks }, { SGBUTTON, 0, 0, 17,3, 1,1, "\x03", SG_SHORTCUT_RIGHT }, { SGTEXT, 0, 0, 2,5, 8,1, "Sectors:" }, { SGRADIOBUT, 0, SG_SELECTED, 12,5, 4,1, " _9" }, { SGRADIOBUT, 0, 0, 17,5, 4,1, "1_0" }, { SGRADIOBUT, 0, 0, 22,5, 4,1, "11" }, { SGRADIOBUT, 0, 0, 12,6, 9,1, "1_8 (HD)" }, { SGRADIOBUT, 0, 0, 12,7, 9,1, "3_6 (ED)" }, { SGTEXT, 0, 0, 2,9, 6,1, "Sides:" }, { SGRADIOBUT, 0, 0, 12,9, 3,1, "_1" }, { SGRADIOBUT, 0, SG_SELECTED, 17,9, 3,1, "_2" }, { SGTEXT, 0, 0, 2,11, 6,1, "Label:" }, { SGEDITFIELD, 0, 0, 12,11, DLGNEWDISK_LABEL_SIZE,1, dlgLabel }, { SGBUTTON, SG_DEFAULT, 0, 4,14, 8,1, "_Create" }, { SGBUTTON, SG_CANCEL, 0, 18,14, 6,1, "_Back" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; #define DEFAULT_DISK_NAME "new_disk.st" /*-----------------------------------------------------------------------*/ /** * Handle creation of a the "new blank disk image". * return true if disk created, false otherwise. */ static bool DlgNewDisk_CreateDisk(const char *path) { int nSectors, nSides; /* (potentially non-existing) filename? */ if (File_DirExists(path)) { Log_AlertDlg(LOG_ERROR, "ERROR: '%s' isn't a file!", path); return false; } /* Get number of sectors */ if (newdiskdlg[DLGNEWDISK_SECTORS36].state & SG_SELECTED) nSectors = 36; else if (newdiskdlg[DLGNEWDISK_SECTORS18].state & SG_SELECTED) nSectors = 18; else if (newdiskdlg[DLGNEWDISK_SECTORS11].state & SG_SELECTED) nSectors = 11; else if (newdiskdlg[DLGNEWDISK_SECTORS10].state & SG_SELECTED) nSectors = 10; else nSectors = 9; /* Get number of sides */ if (newdiskdlg[DLGNEWDISK_SIDES1].state & SG_SELECTED) nSides = 1; else nSides = 2; return CreateBlankImage_CreateFile(path, nTracks, nSectors, nSides, dlgLabel); } /*-----------------------------------------------------------------------*/ /** * Show and process the "new blank disk image" dialog. * Return file name of last created diskimage or NULL if none created. * Caller needs to free the name. */ char *DlgNewDisk_Main(void) { int but; char *szNewDiskName, *tmpname, *retname = NULL; sprintf(szTracks, "%i", nTracks); SDLGui_CenterDlg(newdiskdlg); /* Initialize disk image name: */ szNewDiskName = File_MakePath(ConfigureParams.DiskImage.szDiskImageDirectory, "new_disk.st", NULL); if (!szNewDiskName) return NULL; /* Draw and process the dialog */ do { but = SDLGui_DoDialog(newdiskdlg); switch(but) { case DLGNEWDISK_DECTRACK: if (nTracks > 40) nTracks -= 1; sprintf(szTracks, "%i", nTracks); break; case DLGNEWDISK_INCTRACK: if (nTracks < 85) nTracks += 1; sprintf(szTracks, "%i", nTracks); break; case DLGNEWDISK_SAVE: tmpname = SDLGui_FileSelect("New floppy image:", szNewDiskName, NULL, true); if (tmpname) { if (DlgNewDisk_CreateDisk(tmpname)) { free(retname); retname = tmpname; } else free(tmpname); } break; } } while (but != DLGNEWDISK_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); free(szNewDiskName); return retname; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgRom.c000066400000000000000000000051131504763705000242640ustar00rootroot00000000000000/* Hatari - dlgRom.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgRom_fileid[] = "Hatari dlgRom.c"; #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #define DLGROM_TOSBROWSE 4 #define DLGROM_TOSNAME 5 #define DLGROM_CARTEJECT 9 #define DLGROM_CARTBROWSE 10 #define DLGROM_CARTNAME 11 #define DLGROM_EXIT 14 /* The ROM dialog: */ static SGOBJ romdlg[] = { { SGBOX, 0, 0, 0,0, 52,24, NULL }, { SGBOX, 0, 0, 1,1, 50,8, NULL }, { SGTEXT, 0, 0, 22,2, 9,1, "TOS setup" }, { SGTEXT, 0, 0, 2,5, 25,1, "TOS image:" }, { SGBUTTON, 0, 0, 42,5, 8,1, "_Browse" }, { SGTEXT, 0, 0, 2,7, 46,1, NULL }, { SGBOX, 0, 0, 1,10, 50,9, NULL }, { SGTEXT, 0, 0, 18,11, 15,1, "Cartridge setup" }, { SGTEXT, 0, 0, 2,13, 25,1, "Cartridge image:" }, { SGBUTTON, 0, 0, 32,13, 8,1, "_Eject" }, { SGBUTTON, 0, 0, 42,13, 8,1, "B_rowse" }, { SGTEXT, 0, 0, 2,15, 46,1, NULL }, { SGTEXT, 0, 0, 2,17, 25,1, "NOTE: To use, disable both VDI mode & GEMDOS HD!" }, { SGTEXT, 0, 0, 2,20, 25,1, "A reset is needed after changing these options." }, { SGBUTTON, SG_DEFAULT, 0, 16,22, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Show and process the ROM dialog. */ void DlgRom_Main(void) { char szDlgTosName[47]; char szDlgCartName[47]; int but; SDLGui_CenterDlg(romdlg); File_ShrinkName(szDlgTosName, ConfigureParams.Rom.szTosImageFileName, sizeof(szDlgTosName)-1); romdlg[DLGROM_TOSNAME].txt = szDlgTosName; File_ShrinkName(szDlgCartName, ConfigureParams.Rom.szCartridgeImageFileName, sizeof(szDlgCartName)-1); romdlg[DLGROM_CARTNAME].txt = szDlgCartName; do { but = SDLGui_DoDialog(romdlg); switch (but) { case DLGROM_TOSBROWSE: /* Show and process the file selection dlg */ SDLGui_FileConfSelect("TOS ROM image:", szDlgTosName, ConfigureParams.Rom.szTosImageFileName, sizeof(szDlgTosName)-1, false); break; case DLGROM_CARTEJECT: szDlgCartName[0] = 0; ConfigureParams.Rom.szCartridgeImageFileName[0] = 0; break; case DLGROM_CARTBROWSE: /* Show and process the file selection dlg */ SDLGui_FileConfSelect("Cartridge image:", szDlgCartName, ConfigureParams.Rom.szCartridgeImageFileName, sizeof(szDlgCartName)-1, false); break; } } while (but != DLGROM_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgScreen.c000066400000000000000000000422651504763705000247570ustar00rootroot00000000000000/* Hatari - dlgScreen.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Atari monitor and Hatari window settings. */ const char DlgScreen_fileid[] = "Hatari dlgScreen.c"; #include #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "options.h" #include "screen.h" #include "screenSnapShot.h" #include "resolution.h" #include "vdi.h" #include "video.h" #include "avi_record.h" #include "statusbar.h" #include "clocks_timings.h" #include "file.h" #include "paths.h" #include "str.h" /* how many pixels to increment VDI mode * width/height on each click */ #define VDI_SIZE_INC 16 /* The Monitor dialog: */ #define DLGSCRN_MONO 3 #define DLGSCRN_RGB 4 #define DLGSCRN_VGA 5 #define DLGSCRN_TV 6 #define DLGSCRN_OVERSCAN 7 #define DLGSCRN_USEVDIRES 9 #define DLGSCRN_VDI_WLESS 11 #define DLGSCRN_VDI_WTEXT 12 #define DLGSCRN_VDI_WMORE 13 #define DLGSCRN_VDI_HLESS 15 #define DLGSCRN_VDI_HTEXT 16 #define DLGSCRN_VDI_HMORE 17 #define DLGSCRN_BPP1 18 #define DLGSCRN_BPP2 19 #define DLGSCRN_BPP4 20 #define DLGSCRN_EXIT_MONITOR 21 /* Strings for VDI resolution width and height */ static char sVdiWidth[5]; static char sVdiHeight[5]; static SGOBJ monitordlg[] = { { SGBOX, 0, 0, 0,0, 34,18, NULL }, { SGBOX, 0, 0, 1,1, 32,6, NULL }, { SGTEXT, 0, 0, 10,1, 14,1, "Atari monitor" }, { SGRADIOBUT, 0, 0, 4,3, 6,1, "_Mono" }, { SGRADIOBUT, 0, 0, 12,3, 5,1, "_RGB" }, { SGRADIOBUT, 0, 0, 19,3, 5,1, "_VGA" }, { SGRADIOBUT, 0, 0, 26,3, 4,1, "_TV" }, { SGCHECKBOX, 0, 0, 12,5, 14,1, "Show _borders" }, { SGBOX, 0, 0, 1,8, 32,7, NULL }, { SGCHECKBOX, 0, 0, 4,9, 25,1, "Use _extended VDI screen" }, { SGTEXT, 0, 0, 4,11, 5,1, "Size:" }, { SGBUTTON, 0, 0, 6,12, 1,1, "\x04", SG_SHORTCUT_LEFT }, { SGTEXT, 0, 0, 8,12, 4,1, sVdiWidth }, { SGBUTTON, 0, 0, 13,12, 1,1, "\x03", SG_SHORTCUT_RIGHT }, { SGTEXT, 0, 0, 4,13, 1,1, "x" }, { SGBUTTON, 0, 0, 6,13, 1,1, "\x04", SG_SHORTCUT_UP }, { SGTEXT, 0, 0, 8,13, 4,1, sVdiHeight }, { SGBUTTON, 0, 0, 13,13, 1,1, "\x03", SG_SHORTCUT_DOWN }, { SGRADIOBUT, SG_EXIT, 0, 18,11, 11,1, " _2 colors" }, { SGRADIOBUT, SG_EXIT, 0, 18,12, 11,1, " _4 colors" }, { SGRADIOBUT, SG_EXIT, 0, 18,13, 11,1, "1_6 colors" }, { SGBUTTON, SG_DEFAULT, 0, 7,16, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /* The window dialog: */ #define DLGSCRN_FULLSCRN 3 #define DLGSCRN_STATUSBAR 5 #define DLGSCRN_DRIVELED 6 #define DLGSCRN_NONE 7 #define DLGSCRN_SKIP0 9 #define DLGSCRN_SKIP1 10 #define DLGSCRN_SKIP2 11 #define DLGSCRN_SKIP3 12 #define DLGSCRN_SKIP4 13 #define DLGSCRN_KEEP_RES_ST 16 #define DLGSCRN_KEEP_RES 17 #define DLGSCRN_MAX_WLESS 19 #define DLGSCRN_MAX_WTEXT 20 #define DLGSCRN_MAX_WMORE 21 #define DLGSCRN_MAX_HLESS 23 #define DLGSCRN_MAX_HTEXT 24 #define DLGSCRN_MAX_HMORE 25 #define DLGSCRN_CAPTURE 27 #define DLGSCRN_FORMAT_PNG 28 #define DLGSCRN_FORMAT_BMP 29 #define DLGSCRN_FORMAT_NEO 30 #define DLGSCRN_FORMAT_XIMG 31 #define DLGSCRN_CAPTURE_DIR 32 #define DLGSCRN_RECANIM 34 #define DLGSCRN_CROP 35 #define DLGSCRN_GPUSCALE 37 #define DLGSCRN_RESIZABLE 38 #define DLGSCRN_VSYNC 39 #define DLGSCRN_EXIT_WINDOW 40 /* needs to match Frame skip values in windowdlg[]! */ static const int skip_frames[] = { 0, 1, 2, 4, AUTO_FRAMESKIP_LIMIT }; /* Strings for doubled resolution max width and height */ static char sMaxWidth[5]; static char sMaxHeight[5]; /* Screenshot path name shown in the dialog */ static char sScreenShotDir[29]; #define MAX_SIZE_STEP 8 /* The window dialog: */ static SGOBJ windowdlg[] = { { SGBOX, 0, 0, 0,0, 52,24, NULL }, { SGBOX, 0, 0, 1,1, 50,10, NULL }, { SGTEXT, 0, 0, 4,2, 20,1, "Hatari screen options" }, { SGCHECKBOX, 0, 0, 4,4, 12,1, "_Fullscreen" }, { SGTEXT, 0, 0, 4,6, 12,1, "Indicators:" }, { SGRADIOBUT, 0, 0, 6,7, 11,1, "Status_bar" }, { SGRADIOBUT, 0, 0, 6,8, 11,1, "Drive _led" }, { SGRADIOBUT, 0, 0, 6,9, 6,1, "_None" }, { SGTEXT, 0, 0, 19,4, 12,1, "Frame skip:" }, { SGRADIOBUT, 0, 0, 21,5, 5,1, "_Off" }, { SGRADIOBUT, 0, 0, 21,6, 3,1, "_1" }, { SGRADIOBUT, 0, 0, 21,7, 3,1, "_2" }, { SGRADIOBUT, 0, 0, 21,8, 3,1, "_4" }, { SGRADIOBUT, 0, 0, 21,9, 6,1, "_Auto" }, { SGTEXT, 0, 0, 35,4, 10,1, "resolution" }, { SGTEXT, 0, 0, 35,5, 13,1, "in fullscreen" }, { SGTEXT, 0, 0, 33,2, 1,1, "" }, { SGCHECKBOX, 0, 0, 33,3, 14,1, "_Keep desktop" }, { SGTEXT, 0, 0, 33,7, 15,1, "Max zoomed win:" }, { SGBUTTON, 0, 0, 35,8, 1,1, "\x04", SG_SHORTCUT_LEFT }, { SGTEXT, 0, 0, 37,8, 4,1, sMaxWidth }, { SGBUTTON, 0, 0, 43,8, 1,1, "\x03", SG_SHORTCUT_RIGHT }, { SGTEXT, 0, 0, 33,9, 1,1, "x" }, { SGBUTTON, 0, 0, 35,9, 1,1, "\x04", SG_SHORTCUT_UP }, { SGTEXT, 0, 0, 37,9, 4,1, sMaxHeight }, { SGBUTTON, 0, 0, 43,9, 1,1, "\x03", SG_SHORTCUT_DOWN }, { SGBOX, 0, 0, 1,12, 50,7, NULL }, { SGBUTTON, 0, 0, 4,13, 14,1, "_Screenshot" }, { SGRADIOBUT, 0, 0, 21,13, 5,1, "PNG" }, { SGRADIOBUT, 0, 0, 27,13, 5,1, "BMP" }, { SGRADIOBUT, 0, 0, 33,13, 5,1, "NEO" }, { SGRADIOBUT, 0, 0, 39,13, 5,1, "XIMG" }, { SGBUTTON, 0, 0, 4,15, 14,1, "Directory:" }, { SGTEXT, 0, 0, 21,15, sizeof(sScreenShotDir)-1,1, sScreenShotDir }, { SGBUTTON, 0, 0, 4,17, 14,1, NULL }, /* Record text set later */ { SGCHECKBOX, 0, 0, 21,17, 16,1, "_Crop statusbar" }, { SGTEXT, 0, 0, 4,20, 12,1, "SDL2:" }, { SGCHECKBOX, 0, 0, 12,20, 20,1, "GPU scal_ing" }, { SGCHECKBOX, 0, 0, 27,20, 20,1, "Resi_zable" }, { SGCHECKBOX, 0, 0, 40,20, 11,1, "_VSync" }, { SGBUTTON, SG_DEFAULT, 0, 17,22, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /* for record button */ #define RECORD_START "_Record AVI" #define RECORD_STOP "Stop _record" /* ---------------------------------------------------------------- */ /** * To be called when changing VDI mode bit-depth. * Sets width & height stepping for VDI resolution changing, * and returns number of planes. See vdi.[ch] for details. */ static int DlgMonitor_SetVdiStepping(int *stepx, int *stepy) { int planes; if (monitordlg[DLGSCRN_BPP1].state & SG_SELECTED) planes = 1; else if (monitordlg[DLGSCRN_BPP2].state & SG_SELECTED) planes = 2; else planes = 4; *stepx = VDI_ALIGN_WIDTH; *stepy = VDI_ALIGN_HEIGHT; return planes; } /*-----------------------------------------------------------------------*/ /** * Show and process the monitor dialog. */ void Dialog_MonitorDlg(void) { int but, vdiw, vdih, stepx, stepy, planes; unsigned int i; MONITORTYPE mti; SDLGui_CenterDlg(monitordlg); /* Set up general monitor options in the dialog from actual values: */ if (ConfigureParams.Screen.bAllowOverscan) monitordlg[DLGSCRN_OVERSCAN].state |= SG_SELECTED; else monitordlg[DLGSCRN_OVERSCAN].state &= ~SG_SELECTED; for (i = DLGSCRN_MONO; i <= DLGSCRN_TV; i++) monitordlg[i].state &= ~SG_SELECTED; monitordlg[DLGSCRN_MONO+ConfigureParams.Screen.nMonitorType].state |= SG_SELECTED; /* Initialize VDI resolution options: */ if (ConfigureParams.Screen.bUseExtVdiResolutions) monitordlg[DLGSCRN_USEVDIRES].state |= SG_SELECTED; else monitordlg[DLGSCRN_USEVDIRES].state &= ~SG_SELECTED; for (i=0; i<3; i++) monitordlg[DLGSCRN_BPP1 + i].state &= ~SG_SELECTED; monitordlg[DLGSCRN_BPP1 + ConfigureParams.Screen.nVdiColors - GEMCOLOR_2].state |= SG_SELECTED; vdiw = ConfigureParams.Screen.nVdiWidth; vdih = ConfigureParams.Screen.nVdiHeight; planes = DlgMonitor_SetVdiStepping(&stepx, &stepy); assert(VDI_SIZE_INC >= stepx && VDI_SIZE_INC >= stepy); sprintf(sVdiWidth, "%4i", vdiw); sprintf(sVdiHeight, "%4i", vdih); /* The monitor dialog main loop */ do { bool update = true; but = SDLGui_DoDialog(monitordlg); switch (but) { case DLGSCRN_VDI_WLESS: vdiw -= VDI_SIZE_INC; break; case DLGSCRN_VDI_WMORE: vdiw += VDI_SIZE_INC; break; case DLGSCRN_VDI_HLESS: vdih -= VDI_SIZE_INC; break; case DLGSCRN_VDI_HMORE: vdih += VDI_SIZE_INC; break; case DLGSCRN_BPP1: case DLGSCRN_BPP2: case DLGSCRN_BPP4: planes = DlgMonitor_SetVdiStepping(&stepx, &stepy); break; default: update = false; } if (update) { /* clamp & align */ VDI_ByteLimit(&vdiw, &vdih, planes); vdiw = Opt_ValueAlignMinMax(vdiw, stepx, MIN_VDI_WIDTH, MAX_VDI_WIDTH); vdih = Opt_ValueAlignMinMax(vdih, stepy, MIN_VDI_HEIGHT, MAX_VDI_HEIGHT); sprintf(sVdiWidth, "%4i", vdiw); sprintf(sVdiHeight, "%4i", vdih); } } while (but != DLGSCRN_EXIT_MONITOR && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Read new values from dialog: */ ConfigureParams.Screen.bAllowOverscan = (monitordlg[DLGSCRN_OVERSCAN].state & SG_SELECTED); for (mti = MONITOR_TYPE_MONO; mti <= MONITOR_TYPE_TV; mti++) { if (monitordlg[mti + DLGSCRN_MONO].state & SG_SELECTED) { ConfigureParams.Screen.nMonitorType = mti; break; } } ConfigureParams.Screen.nVdiWidth = vdiw; ConfigureParams.Screen.nVdiHeight = vdih; ConfigureParams.Screen.bUseExtVdiResolutions = (monitordlg[DLGSCRN_USEVDIRES].state & SG_SELECTED); for (i=0; i<3; i++) { if (monitordlg[DLGSCRN_BPP1 + i].state & SG_SELECTED) ConfigureParams.Screen.nVdiColors = GEMCOLOR_2 + i; } } /*-----------------------------------------------------------------------*/ /** * Set ScreenShotFormat depending on which button is selected */ static void DlgWindow_SetScreenShotFormat(void) { if ( windowdlg[DLGSCRN_FORMAT_NEO].state & SG_SELECTED ) ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_NEO; else if ( windowdlg[DLGSCRN_FORMAT_XIMG].state & SG_SELECTED ) ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_XIMG; #if HAVE_LIBPNG else if ( windowdlg[DLGSCRN_FORMAT_PNG].state & SG_SELECTED ) ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_PNG; #endif else ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_BMP; } /*-----------------------------------------------------------------------*/ /** * If screenshot dir path given, set it to screenshot dir config string, * and update dialog screenshot dir field accordingly. */ static void DlgWindow_UpdateScreenShotDir(void) { if (ConfigureParams.Screen.szScreenShotDir[0]) { File_MakeValidPathName(ConfigureParams.Screen.szScreenShotDir); File_CleanFileName(ConfigureParams.Screen.szScreenShotDir); File_ShrinkName(sScreenShotDir, ConfigureParams.Screen.szScreenShotDir, sizeof(sScreenShotDir)-1); } else { static const char base[] = "(default) "; const int len = sizeof(base) - 1; assert(len < sizeof(sScreenShotDir)); Str_Copy(sScreenShotDir, base, sizeof(sScreenShotDir)); File_ShrinkName(sScreenShotDir + len, Paths_GetScreenShotDir(), sizeof(sScreenShotDir)-len-1); } } /*-----------------------------------------------------------------------*/ /** * Show and process the window dialog. */ void Dialog_WindowDlg(void) { int maxw, maxh, deskw, deskh, but, skip = 0; unsigned int i; char *selname; SDLGui_CenterDlg(windowdlg); /* Set up general window options in the dialog from actual values: */ if (ConfigureParams.Screen.bFullScreen) windowdlg[DLGSCRN_FULLSCRN].state |= SG_SELECTED; else windowdlg[DLGSCRN_FULLSCRN].state &= ~SG_SELECTED; if (ConfigureParams.Screen.bKeepResolution) windowdlg[DLGSCRN_KEEP_RES].state |= SG_SELECTED; else windowdlg[DLGSCRN_KEEP_RES].state &= ~SG_SELECTED; windowdlg[DLGSCRN_STATUSBAR].state &= ~SG_SELECTED; windowdlg[DLGSCRN_DRIVELED].state &= ~SG_SELECTED; windowdlg[DLGSCRN_NONE].state &= ~SG_SELECTED; if (ConfigureParams.Screen.bShowStatusbar) windowdlg[DLGSCRN_STATUSBAR].state |= SG_SELECTED; else if (ConfigureParams.Screen.bShowDriveLed) windowdlg[DLGSCRN_DRIVELED].state |= SG_SELECTED; else windowdlg[DLGSCRN_NONE].state |= SG_SELECTED; for (i = 0; i < ARRAY_SIZE(skip_frames); i++) { if (ConfigureParams.Screen.nFrameSkips >= skip_frames[i]) skip = i; windowdlg[i+DLGSCRN_SKIP0].state &= ~SG_SELECTED; } windowdlg[DLGSCRN_SKIP0+skip].state |= SG_SELECTED; Resolution_GetDesktopSize(&deskw, &deskh); maxw = ConfigureParams.Screen.nMaxWidth; maxh = ConfigureParams.Screen.nMaxHeight; sprintf(sMaxWidth, "%4i", maxw); sprintf(sMaxHeight, "%4i", maxh); /* Initialize window capture options: */ DlgWindow_UpdateScreenShotDir(); windowdlg[DLGSCRN_FORMAT_PNG].state &= ~SG_SELECTED; windowdlg[DLGSCRN_FORMAT_BMP].state &= ~SG_SELECTED; windowdlg[DLGSCRN_FORMAT_NEO].state &= ~SG_SELECTED; windowdlg[DLGSCRN_FORMAT_XIMG].state &= ~SG_SELECTED; if (ConfigureParams.Screen.ScreenShotFormat == SCREEN_SNAPSHOT_NEO ) windowdlg[DLGSCRN_FORMAT_NEO].state |= SG_SELECTED; else if (ConfigureParams.Screen.ScreenShotFormat == SCREEN_SNAPSHOT_XIMG ) windowdlg[DLGSCRN_FORMAT_XIMG].state |= SG_SELECTED; #if HAVE_LIBPNG else if (ConfigureParams.Screen.ScreenShotFormat == SCREEN_SNAPSHOT_PNG) windowdlg[DLGSCRN_FORMAT_PNG].state |= SG_SELECTED; #endif else windowdlg[DLGSCRN_FORMAT_BMP].state |= SG_SELECTED; if (ConfigureParams.Screen.bCrop) windowdlg[DLGSCRN_CROP].state |= SG_SELECTED; else windowdlg[DLGSCRN_CROP].state &= ~SG_SELECTED; if (Avi_AreWeRecording()) windowdlg[DLGSCRN_RECANIM].txt = RECORD_STOP; else windowdlg[DLGSCRN_RECANIM].txt = RECORD_START; /* SDL2 options */ if (ConfigureParams.Screen.bResizable) windowdlg[DLGSCRN_RESIZABLE].state |= SG_SELECTED; else windowdlg[DLGSCRN_RESIZABLE].state &= ~SG_SELECTED; if (ConfigureParams.Screen.bUseSdlRenderer) windowdlg[DLGSCRN_GPUSCALE].state |= SG_SELECTED; else windowdlg[DLGSCRN_GPUSCALE].state &= ~SG_SELECTED; if (ConfigureParams.Screen.bUseVsync) windowdlg[DLGSCRN_VSYNC].state |= SG_SELECTED; else windowdlg[DLGSCRN_VSYNC].state &= ~SG_SELECTED; /* The window dialog main loop */ do { but = SDLGui_DoDialog(windowdlg); switch (but) { case DLGSCRN_MAX_WLESS: maxw = Opt_ValueAlignMinMax(maxw - MAX_SIZE_STEP, MAX_SIZE_STEP, MIN_VDI_WIDTH, deskw); sprintf(sMaxWidth, "%4i", maxw); break; case DLGSCRN_MAX_WMORE: maxw = Opt_ValueAlignMinMax(maxw + MAX_SIZE_STEP, MAX_SIZE_STEP, MIN_VDI_WIDTH, deskw); sprintf(sMaxWidth, "%4i", maxw); break; case DLGSCRN_MAX_HLESS: maxh = Opt_ValueAlignMinMax(maxh - MAX_SIZE_STEP, MAX_SIZE_STEP, MIN_VDI_HEIGHT, deskh); sprintf(sMaxHeight, "%4i", maxh); break; case DLGSCRN_MAX_HMORE: maxh = Opt_ValueAlignMinMax(maxh + MAX_SIZE_STEP, MAX_SIZE_STEP, MIN_VDI_HEIGHT, deskh); sprintf(sMaxHeight, "%4i", maxh); break; case DLGSCRN_CAPTURE_DIR: selname = SDLGui_FileSelect("Screenshot Directory", Paths_GetScreenShotDir(), NULL, false); if (selname) { Str_Copy(ConfigureParams.Screen.szScreenShotDir, selname, sizeof(ConfigureParams.Screen.szScreenShotDir)); free(selname); } DlgWindow_UpdateScreenShotDir(); break; case DLGSCRN_CAPTURE: DlgWindow_SetScreenShotFormat(); /* Take latest choice into account */ Screen_UpdateRect(sdlscrn, 0,0,0,0); ConfigureParams.Screen.bCrop = (windowdlg[DLGSCRN_CROP].state & SG_SELECTED); ScreenSnapShot_SaveScreen(); break; case DLGSCRN_RECANIM: if (Avi_AreWeRecording()) { /* AVI indexing can take a while for larger files */ Statusbar_AddMessage("Finishing AVI file...", 100); Statusbar_Update(sdlscrn, true); Avi_StopRecording(); windowdlg[DLGSCRN_RECANIM].txt = RECORD_START; Statusbar_AddMessage("Emulation paused", 100); Statusbar_Update(sdlscrn, true); } else { ConfigureParams.Screen.bCrop = (windowdlg[DLGSCRN_CROP].state & SG_SELECTED); selname = SDLGui_FileSelect("Record to AVI file...", ConfigureParams.Video.AviRecordFile, NULL, true); if (!selname || File_DoesFileNameEndWithSlash(selname)) break; if (!File_QueryOverwrite(selname)) break; Str_Copy(ConfigureParams.Video.AviRecordFile, selname, sizeof(ConfigureParams.Video.AviRecordFile)); Avi_StartRecording_WithConfig (); windowdlg[DLGSCRN_RECANIM].txt = RECORD_STOP; } break; } } while (but != DLGSCRN_EXIT_WINDOW && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram); /* Read new values from dialog: */ ConfigureParams.Screen.bFullScreen = (windowdlg[DLGSCRN_FULLSCRN].state & SG_SELECTED); ConfigureParams.Screen.bKeepResolution = (windowdlg[DLGSCRN_KEEP_RES].state & SG_SELECTED); ConfigureParams.Screen.nMaxWidth = maxw; ConfigureParams.Screen.nMaxHeight = maxh; ConfigureParams.Screen.bShowStatusbar = false; ConfigureParams.Screen.bShowDriveLed = false; if (windowdlg[DLGSCRN_STATUSBAR].state & SG_SELECTED) ConfigureParams.Screen.bShowStatusbar = true; else if (windowdlg[DLGSCRN_DRIVELED].state & SG_SELECTED) ConfigureParams.Screen.bShowDriveLed = true; for (i = DLGSCRN_SKIP0; i <= DLGSCRN_SKIP4; i++) { if (windowdlg[i].state & SG_SELECTED) { ConfigureParams.Screen.nFrameSkips = skip_frames[i-DLGSCRN_SKIP0]; break; } } DlgWindow_SetScreenShotFormat(); ConfigureParams.Screen.bCrop = (windowdlg[DLGSCRN_CROP].state & SG_SELECTED); ConfigureParams.Screen.bResizable = (windowdlg[DLGSCRN_RESIZABLE].state & SG_SELECTED); ConfigureParams.Screen.bUseSdlRenderer = (windowdlg[DLGSCRN_GPUSCALE].state & SG_SELECTED); ConfigureParams.Screen.bUseVsync = (windowdlg[DLGSCRN_VSYNC].state & SG_SELECTED); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgSound.c000066400000000000000000000132211504763705000246160ustar00rootroot00000000000000/* Hatari - dlgSound.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ const char DlgSound_fileid[] = "Hatari dlgSound.c"; #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #include "file.h" #include "sound.h" #define DLGSOUND_ENABLE 3 #define DLGSOUND_SYNC 4 #define DLGSOUND_11KHZ 6 #define DLGSOUND_12KHZ 7 #define DLGSOUND_16KHZ 8 #define DLGSOUND_22KHZ 9 #define DLGSOUND_25KHZ 10 #define DLGSOUND_32KHZ 11 #define DLGSOUND_44KHZ 12 #define DLGSOUND_48KHZ 13 #define DLGSOUND_50KHZ 14 #define DLGSOUND_MODEL 16 #define DLGSOUND_TABLE 17 #define DLGSOUND_LINEAR 18 #define DLGSOUND_RECNAME 22 #define DLGSOUND_RECBROWSE 23 #define DLGSOUND_RECORD 24 #define DLGSOUND_EXIT 25 static char dlgRecordName[35]; /* The sound dialog: */ static SGOBJ sounddlg[] = { { SGBOX, 0,0, 0, 0, 40,25, NULL }, { SGBOX, 0,0, 1, 1, 38,13, NULL }, { SGTEXT, 0,0, 4, 2, 5,1, "SOUND" }, { SGCHECKBOX, 0,0, 13, 2, 9,1, "_Enabled" }, { SGCHECKBOX, 0,0, 25, 2, 13,1, "Syn_chronize" }, { SGTEXT, 0,0, 4, 4, 17,1, "Playback quality:" }, { SGRADIOBUT, 0,0, 2, 6, 10,1, "11_025 Hz" }, { SGRADIOBUT, 0,0, 2, 7, 10,1, "_12517 Hz" }, { SGRADIOBUT, 0,0, 2, 8, 10,1, "1_6000 Hz" }, { SGRADIOBUT, 0,0, 15, 6, 10,1, "_22050 Hz" }, { SGRADIOBUT, 0,0, 15, 7, 10,1, "25033 _Hz" }, { SGRADIOBUT, 0,0, 15, 8, 10,1, "_32000 Hz" }, { SGRADIOBUT, 0,0, 28, 6, 10,1, "_44100 Hz" }, { SGRADIOBUT, 0,0, 28, 7, 10,1, "4_8000 Hz" }, { SGRADIOBUT, 0,0, 28, 8, 10,1, "_50066 Hz" }, { SGTEXT, 0,0, 4,10, 10,1, "YM voices mixing:" }, { SGRADIOBUT, 0,0, 2,12, 12,1, "_Math model" }, { SGRADIOBUT, 0,0, 15,12, 10,1, "_ST table" }, { SGRADIOBUT, 0,0, 28,12, 8,1, "_Linear" }, { SGBOX, 0,0, 1,15, 38,7, NULL }, { SGTEXT, 0,0, 13,16, 14,1, "Capture YM/WAV" }, { SGTEXT, 0,0, 2,17, 26,1, "File name (*.wav / *.ym):" }, { SGTEXT, 0,0, 2,18, 34,1, dlgRecordName }, { SGBUTTON, 0,0, 28,17, 8,1, " _Browse " }, { SGBUTTON, 0,0, 12,20, 16,1, NULL }, /* text set later, see below */ { SGBUTTON, SG_DEFAULT, 0, 10,23, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; #define RECORD_START "_Record sound" #define RECORD_STOP "Stop _recording" static const int nSoundFreqs[] = { 11025, 12517, 16000, 22050, 25033, 32000, 44100, 48000, 50066 }; /*-----------------------------------------------------------------------*/ /** * Show and process the sound dialog. */ void Dialog_SoundDlg(void) { int but, i; SDLGui_CenterDlg(sounddlg); /* Set up dialog from actual values: */ if (ConfigureParams.Sound.bEnableSound) sounddlg[DLGSOUND_ENABLE].state |= SG_SELECTED; else sounddlg[DLGSOUND_ENABLE].state &= ~SG_SELECTED; if (ConfigureParams.Sound.bEnableSoundSync) sounddlg[DLGSOUND_SYNC].state |= SG_SELECTED; else sounddlg[DLGSOUND_SYNC].state &= ~SG_SELECTED; for (i = DLGSOUND_11KHZ; i <= DLGSOUND_50KHZ; i++) sounddlg[i].state &= ~SG_SELECTED; for (i = 0; i <= DLGSOUND_50KHZ-DLGSOUND_11KHZ; i++) { if (ConfigureParams.Sound.nPlaybackFreq > nSoundFreqs[i]-500 && ConfigureParams.Sound.nPlaybackFreq < nSoundFreqs[i]+500) { sounddlg[DLGSOUND_11KHZ + i].state |= SG_SELECTED; break; } } sounddlg[DLGSOUND_MODEL].state &= ~SG_SELECTED; sounddlg[DLGSOUND_TABLE].state &= ~SG_SELECTED; sounddlg[DLGSOUND_LINEAR].state &= ~SG_SELECTED; if (ConfigureParams.Sound.YmVolumeMixing == YM_MODEL_MIXING) sounddlg[DLGSOUND_MODEL].state |= SG_SELECTED; else if (ConfigureParams.Sound.YmVolumeMixing == YM_TABLE_MIXING) sounddlg[DLGSOUND_TABLE].state |= SG_SELECTED; else sounddlg[DLGSOUND_LINEAR].state |= SG_SELECTED; File_ShrinkName(dlgRecordName, ConfigureParams.Sound.szYMCaptureFileName, sounddlg[DLGSOUND_RECNAME].w); if ( Sound_AreWeRecording() ) sounddlg[DLGSOUND_RECORD].txt = RECORD_STOP; else sounddlg[DLGSOUND_RECORD].txt = RECORD_START; /* The sound dialog main loop */ do { but = SDLGui_DoDialog(sounddlg); switch (but) { case DLGSOUND_RECBROWSE: /* Choose a new record file */ SDLGui_FileConfSelect("Capture file:", dlgRecordName, ConfigureParams.Sound.szYMCaptureFileName, sounddlg[DLGSOUND_RECNAME].w, true); break; case DLGSOUND_RECORD: if (Sound_AreWeRecording()) { sounddlg[DLGSOUND_RECORD].txt = RECORD_START; Sound_EndRecording(); } else { /* make sure that we have a valid file name... */ if (strlen(ConfigureParams.Sound.szYMCaptureFileName) < 4) { strcpy(ConfigureParams.Sound.szYMCaptureFileName, "./hatari.wav"); } sounddlg[DLGSOUND_RECORD].txt = RECORD_STOP; Sound_BeginRecording(ConfigureParams.Sound.szYMCaptureFileName); } break; } } while (but != DLGSOUND_EXIT && but != SDLGUI_QUIT && but != SDLGUI_ERROR && !bQuitProgram ); /* Read values from dialog */ ConfigureParams.Sound.bEnableSound = (sounddlg[DLGSOUND_ENABLE].state & SG_SELECTED); ConfigureParams.Sound.bEnableSoundSync = (sounddlg[DLGSOUND_SYNC].state & SG_SELECTED); for (i = DLGSOUND_11KHZ; i <= DLGSOUND_50KHZ; i++) { if (sounddlg[i].state & SG_SELECTED) { ConfigureParams.Sound.nPlaybackFreq = nSoundFreqs[i-DLGSOUND_11KHZ]; break; } } if (sounddlg[DLGSOUND_MODEL].state & SG_SELECTED) ConfigureParams.Sound.YmVolumeMixing = YM_MODEL_MIXING; else if (sounddlg[DLGSOUND_TABLE].state & SG_SELECTED) ConfigureParams.Sound.YmVolumeMixing = YM_TABLE_MIXING; else ConfigureParams.Sound.YmVolumeMixing = YM_LINEAR_MIXING; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/dlgSystem.c000066400000000000000000000110431504763705000250120ustar00rootroot00000000000000/* Hatari - dlgSystem.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Dialog for setting various system options */ const char DlgSystem_fileid[] = "Hatari dlgSystem.c"; #include "main.h" #include "configuration.h" #include "dialog.h" #include "sdlgui.h" #define DLGSYS_ST 4 #define DLGSYS_MEGA_ST 5 #define DLGSYS_STE 6 #define DLGSYS_MEGA_STE 7 #define DLGSYS_TT 8 #define DLGSYS_FALCON 9 #define DLGSYS_WSRND 12 #define DLGSYS_WS1 13 #define DLGSYS_WS2 14 #define DLGSYS_WS3 15 #define DLGSYS_WS4 16 #define DLGSYS_DSPOFF 19 #define DLGSYS_DSPDUMMY 20 #define DLGSYS_DSPON 21 #define DLGSYS_BLITTER 22 #define DLGSYS_TIMERD 23 #define DLGSYS_FASTBOOT 24 static SGOBJ systemdlg[] = { { SGBOX, 0, 0, 0,0, 50,18, NULL }, { SGTEXT, 0, 0, 18,1, 14,1, "System options" }, { SGBOX, 0, 0, 2,3, 15,8, NULL }, { SGTEXT, 0, 0, 3,3, 13,1, "Machine type:" }, { SGRADIOBUT, 0, 0, 3,5, 4,1, "_ST" }, { SGRADIOBUT, 0, 0, 3,6, 9,1, "Meg_a ST" }, { SGRADIOBUT, 0, 0, 3,7, 5,1, "ST_E" }, { SGRADIOBUT, 0, 0, 3,8, 10,1, "Me_ga STE" }, { SGRADIOBUT, 0, 0, 3,9, 4,1, "_TT" }, { SGRADIOBUT, 0, 0, 3,10, 8,1, "_Falcon" }, { SGBOX, 0, 0, 18,3, 15,8, NULL }, { SGTEXT, 0, 0, 19,3, 13,1, "Video timing:" }, { SGRADIOBUT, 0, 0, 19,5, 8,1, "_Random" }, { SGRADIOBUT, 0, 0, 19,6, 12,1, "Wakestate_1" }, { SGRADIOBUT, 0, 0, 19,7, 12,1, "Wakestate_2" }, { SGRADIOBUT, 0, 0, 19,8, 12,1, "Wakestate_3" }, { SGRADIOBUT, 0, 0, 19,9, 12,1, "Wakestate_4" }, { SGBOX, 0, 0, 34,3, 14,8, NULL }, { SGTEXT, 0, 0, 35,3, 12,1, "Falcon DSP:" }, { SGRADIOBUT, 0, 0, 35,5, 6,1, "_None" }, { SGRADIOBUT, 0, 0, 35,6, 7,1, "Dumm_y" }, { SGRADIOBUT, 0, 0, 35,7, 6,1, "Ful_l" }, { SGCHECKBOX, 0, 0, 3,12, 20,1, "_Blitter in ST mode" }, { SGCHECKBOX, 0, 0, 3,13, 15,1, "Patch Timer-_D" }, { SGCHECKBOX, 0, 0, 3,14, 39,1, "Boot faster by _patching TOS & sysvars" }, { SGBUTTON, SG_DEFAULT, 0, 16,16, 20,1, "Back to main menu" }, { SGSTOP, 0, 0, 0,0, 0,0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Show and process the "System" dialog */ void DlgSystem_Main(void) { int i; MACHINETYPE mti; SDLGui_CenterDlg(systemdlg); /* Set up machine type */ for (i = DLGSYS_ST; i <= DLGSYS_FALCON; i++) { systemdlg[i].state &= ~SG_SELECTED; } systemdlg[DLGSYS_ST + ConfigureParams.System.nMachineType].state |= SG_SELECTED; /* DSP mode: */ for (i = DLGSYS_DSPOFF; i <= DLGSYS_DSPON; i++) { systemdlg[i].state &= ~SG_SELECTED; } if (ConfigureParams.System.nDSPType == DSP_TYPE_NONE) systemdlg[DLGSYS_DSPOFF].state |= SG_SELECTED; else if (ConfigureParams.System.nDSPType == DSP_TYPE_DUMMY) systemdlg[DLGSYS_DSPDUMMY].state |= SG_SELECTED; else systemdlg[DLGSYS_DSPON].state |= SG_SELECTED; /* Video timing */ for (i = DLGSYS_WSRND; i <= DLGSYS_WS4; i++) { systemdlg[i].state &= ~SG_SELECTED; } systemdlg[DLGSYS_WSRND + ConfigureParams.System.VideoTimingMode].state |= SG_SELECTED; /* Emulate Blitter */ if (ConfigureParams.System.bBlitter) systemdlg[DLGSYS_BLITTER].state |= SG_SELECTED; else systemdlg[DLGSYS_BLITTER].state &= ~SG_SELECTED; /* Patch timer D */ if (ConfigureParams.System.bPatchTimerD) systemdlg[DLGSYS_TIMERD].state |= SG_SELECTED; else systemdlg[DLGSYS_TIMERD].state &= ~SG_SELECTED; /* Boot faster by patching system variables */ if (ConfigureParams.System.bFastBoot) systemdlg[DLGSYS_FASTBOOT].state |= SG_SELECTED; else systemdlg[DLGSYS_FASTBOOT].state &= ~SG_SELECTED; /* Show the dialog: */ SDLGui_DoDialog(systemdlg); /* Read values from dialog: */ for (mti = MACHINE_ST; mti <= MACHINE_FALCON; mti++) { if (systemdlg[mti + DLGSYS_ST].state&SG_SELECTED) { ConfigureParams.System.nMachineType = mti; break; } } if (systemdlg[DLGSYS_DSPOFF].state & SG_SELECTED) ConfigureParams.System.nDSPType = DSP_TYPE_NONE; else if (systemdlg[DLGSYS_DSPDUMMY].state & SG_SELECTED) ConfigureParams.System.nDSPType = DSP_TYPE_DUMMY; else ConfigureParams.System.nDSPType = DSP_TYPE_EMU; for (i = DLGSYS_WSRND; i <= DLGSYS_WS4; i++) { if (systemdlg[i].state & SG_SELECTED) { ConfigureParams.System.VideoTimingMode = i - DLGSYS_WSRND; break; } } ConfigureParams.System.bBlitter = (systemdlg[DLGSYS_BLITTER].state & SG_SELECTED); ConfigureParams.System.bPatchTimerD = (systemdlg[DLGSYS_TIMERD].state & SG_SELECTED); ConfigureParams.System.bFastBoot = (systemdlg[DLGSYS_FASTBOOT].state & SG_SELECTED); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/font10x16.bmp000066400000000000000000000121001504763705000250340ustar00rootroot00000000000000BM@>(   cax^avcv3c03 003 830afaf0qfacafafifac3fafefac03 13fac1fxfac<0 03 003<0`x3fC @00p0>>p||afaf1c 0afaf` 0?? 0``ـaf 0afad3 0 0??ð|x<03 0 0 3 d 00~axxcc03 1 03aafafaaafpafaafafqfafafifaafafefaac03 cfacaax0afacc3al~ ``3 A8A C0`03p0afafxx?afaf`` 0afafa` 0a` 0afafa~ 0afag` 03 0aa` 0x3a` 0 0a?0  0x 000d3 0 xqf%BQ83!0p80f `38@03"03`0!C3` 0`0p 0x"[@f '(`c8H`cl#0c(`&fpCff`"<3x3@30`L00L0O0 L`0L0x03 `$$& @al6? !` 0` $`6c``0A73```020La`00L`` @0`L`0L``0`O0 0L```30L0af0aL3f0 L<0700```````nc`ac s 3 0 acaa0 acc1 `0 acc1``0 s 㙆f3`1 pnffac0 0 0 0  |afxg03aa ffaa fa၌a ffaaa f g03s 0|ny x` ` ` `<``x a`  a`1 a3`f` a ~g c1 cf0x c1 af c1`af f3 00af f3 00c 0 fa``~xfag`gx~zafx1f0caa`f`faa1`f9ofaa1`fym1aa1`fymga1`fنmaaa1`fنg1aa1`癆a1aa1`3`ca1`gx~xa`fx` x00 0`3003f`a` af0a`0a cf03`0`0a0830 0`0a 3`00` a a`3 a` 03  xx  ?gx `mf0 0` x 000l 00l?l006l`0 00 06mc`0 0 06?#`00 0 ? ? p'X#!H3?1@ `xyo |o':|o!"`xyo A#3?1@A X#!H p' hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/font10x16.h000066400000000000000000000765441504763705000245330ustar00rootroot00000000000000#define font10x16_width 160 #define font10x16_height 256 static const Uint8 font10x16_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x07, 0x00, 0x00, 0xF0, 0xF3, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x81, 0x04, 0x00, 0x00, 0xF0, 0xF3, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x81, 0x04, 0x0E, 0x70, 0xE4, 0xF9, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xE0, 0xF1, 0xFF, 0xFF, 0x00, 0x10, 0x82, 0x04, 0x1A, 0x58, 0xC4, 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x10, 0x12, 0x60, 0x80, 0x00, 0x10, 0x82, 0xC4, 0x33, 0xCC, 0x0F, 0xFC, 0x5F, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE9, 0x15, 0x60, 0xA0, 0x00, 0x08, 0x84, 0x44, 0x60, 0x06, 0x1E, 0xFE, 0x5F, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF6, 0x1B, 0x60, 0x90, 0x00, 0x08, 0xE4, 0x5C, 0xC0, 0x03, 0x3E, 0xFF, 0x07, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF6, 0x1B, 0x60, 0x89, 0x00, 0x38, 0x27, 0x50, 0xC0, 0x03, 0x3E, 0xFF, 0x07, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF6, 0x1B, 0x60, 0x85, 0x00, 0x20, 0x21, 0x50, 0x60, 0x06, 0x1E, 0xFE, 0x63, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF6, 0x1B, 0x60, 0x83, 0x00, 0x20, 0x41, 0xC8, 0x33, 0xCC, 0x0F, 0xFC, 0x63, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE9, 0x15, 0x60, 0x81, 0x00, 0x20, 0x41, 0x08, 0x1A, 0x58, 0xC4, 0xF8, 0xF9, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x10, 0x12, 0x60, 0x80, 0x00, 0x20, 0x81, 0x04, 0x0E, 0x70, 0xE4, 0xF9, 0xF9, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xE0, 0xF1, 0xFF, 0xFF, 0x00, 0x20, 0x81, 0x04, 0x00, 0x00, 0xF0, 0xF3, 0xFC, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x03, 0x00, 0x00, 0xF0, 0xF3, 0xFC, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x0C, 0x00, 0x30, 0x70, 0x80, 0x03, 0x0C, 0xC0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xC0, 0xC0, 0x0C, 0x6C, 0xFC, 0xD8, 0xC4, 0x06, 0x0C, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xC0, 0xC0, 0x0C, 0x6C, 0xB6, 0xD9, 0xC6, 0x06, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xC0, 0xC0, 0x0C, 0x6C, 0x36, 0x70, 0xC3, 0x06, 0x0C, 0x30, 0xC0, 0xC0, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x00, 0xFF, 0x36, 0x80, 0x81, 0x03, 0x00, 0x18, 0x80, 0x81, 0x07, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xC0, 0x00, 0x00, 0x36, 0xFC, 0xC0, 0xC0, 0x21, 0x00, 0x18, 0x80, 0xE1, 0x9F, 0x7F, 0x00, 0xF0, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x00, 0x00, 0x36, 0xB0, 0x61, 0x60, 0x33, 0x00, 0x18, 0x80, 0x81, 0x07, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xC0, 0x00, 0x80, 0x7F, 0xB0, 0xB1, 0x63, 0x1E, 0x00, 0x30, 0xC0, 0xC0, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xB6, 0xD9, 0x66, 0x0C, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xC0, 0x00, 0x00, 0x1B, 0xFC, 0xC8, 0xE6, 0x1E, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x03, 0x06, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x30, 0x80, 0xC3, 0x33, 0x00, 0xC0, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xC0, 0x80, 0x07, 0x1E, 0x80, 0xF8, 0x87, 0x87, 0x7F, 0x78, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x60, 0x00, 0x1E, 0x78, 0xE0, 0xC0, 0x0C, 0x33, 0xC0, 0x18, 0xC0, 0x08, 0x60, 0xCC, 0x30, 0x03, 0x00, 0x00, 0x60, 0x00, 0xC0, 0x00, 0x33, 0xCC, 0xF0, 0x60, 0x98, 0x61, 0xE0, 0x18, 0x60, 0x00, 0x30, 0x86, 0x19, 0x06, 0x00, 0x00, 0x30, 0x00, 0x80, 0x81, 0x61, 0x86, 0xD9, 0x60, 0x98, 0x61, 0xF0, 0x18, 0x60, 0x00, 0x30, 0x86, 0x19, 0x06, 0x00, 0x00, 0x18, 0xF8, 0x07, 0x83, 0x61, 0x86, 0xC1, 0x00, 0x18, 0x30, 0xD8, 0xD8, 0x61, 0x07, 0x18, 0xCC, 0x18, 0x06, 0x03, 0x0C, 0x0C, 0x00, 0x00, 0x06, 0x30, 0x86, 0xC1, 0x00, 0x0C, 0x1C, 0xCC, 0x38, 0xE3, 0x0C, 0x18, 0x78, 0x30, 0x07, 0x03, 0x0C, 0x06, 0x00, 0x00, 0x0C, 0x18, 0x86, 0xC1, 0x80, 0x07, 0x30, 0xC6, 0x00, 0x66, 0x18, 0x0C, 0xCC, 0xE0, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x06, 0x0C, 0x86, 0xC1, 0xC0, 0x00, 0x60, 0xFE, 0x01, 0x66, 0x18, 0x0C, 0x86, 0x01, 0x06, 0x00, 0x00, 0x18, 0xF8, 0x07, 0x03, 0x0C, 0xCC, 0xC0, 0x60, 0x80, 0x61, 0xC0, 0x18, 0x66, 0x18, 0x06, 0x86, 0x01, 0x06, 0x00, 0x00, 0x30, 0x00, 0x80, 0x01, 0x00, 0x78, 0xC0, 0x60, 0x00, 0x33, 0xC0, 0x30, 0xC3, 0x0C, 0x06, 0xCC, 0x10, 0x03, 0x03, 0x0C, 0x60, 0x00, 0xC0, 0x00, 0x0C, 0x30, 0xF8, 0xE7, 0x1F, 0x1E, 0xC0, 0xE0, 0x81, 0x07, 0x03, 0x78, 0xE0, 0x01, 0x03, 0x0C, 0xC0, 0x00, 0x60, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xC0, 0xE0, 0x03, 0x1E, 0x7E, 0xF8, 0xE7, 0x1F, 0x1E, 0x86, 0xF9, 0x07, 0xBF, 0x61, 0x06, 0x18, 0x66, 0x18, 0x1E, 0xCC, 0xE0, 0x61, 0x06, 0x33, 0xC6, 0x18, 0x60, 0x00, 0x33, 0x86, 0xC1, 0x00, 0x8C, 0x61, 0x06, 0x18, 0xE6, 0x18, 0x33, 0x86, 0xE1, 0x61, 0x8C, 0x61, 0x86, 0x19, 0x60, 0x80, 0x61, 0x86, 0xC1, 0x00, 0x8C, 0x31, 0x06, 0x38, 0xE7, 0x98, 0x61, 0xE6, 0x31, 0x63, 0x8C, 0x01, 0x86, 0x19, 0x60, 0x80, 0x01, 0x86, 0xC1, 0x00, 0x8C, 0x31, 0x06, 0x38, 0xE7, 0x99, 0x61, 0xB6, 0x31, 0x63, 0x86, 0x01, 0x86, 0x19, 0x60, 0x80, 0x01, 0x86, 0xC1, 0x00, 0x8C, 0x19, 0x06, 0xD8, 0x66, 0x9B, 0x61, 0xB6, 0x19, 0xE6, 0x87, 0x01, 0x86, 0xF9, 0xE1, 0x87, 0x79, 0xFE, 0xC1, 0x00, 0x8C, 0x0F, 0x06, 0xD8, 0x66, 0x9B, 0x61, 0xB6, 0xF9, 0x67, 0x8C, 0x01, 0x86, 0x19, 0x60, 0x80, 0x61, 0x86, 0xC1, 0x00, 0x8C, 0x19, 0x06, 0xD8, 0x66, 0x9E, 0x61, 0xF6, 0x18, 0x66, 0x98, 0x01, 0x86, 0x19, 0x60, 0x80, 0x61, 0x86, 0xC1, 0x00, 0x8C, 0x19, 0x06, 0xD8, 0x66, 0x9E, 0x61, 0x06, 0x18, 0x66, 0x98, 0x61, 0x86, 0x19, 0x60, 0x80, 0x61, 0x86, 0xC1, 0x60, 0x8C, 0x31, 0x06, 0x18, 0x66, 0x9C, 0x61, 0x8C, 0x19, 0x66, 0x0C, 0x33, 0xC6, 0x18, 0x60, 0x00, 0x73, 0x86, 0xC1, 0xC0, 0x86, 0x61, 0x06, 0x18, 0x66, 0x18, 0x33, 0xF8, 0x18, 0xE6, 0x07, 0x1E, 0x7E, 0xF8, 0x67, 0x00, 0x5E, 0x86, 0xF9, 0x87, 0x83, 0x61, 0xFE, 0x19, 0x66, 0x18, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xE0, 0xE1, 0x07, 0x1E, 0xFE, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xE6, 0x1F, 0x3F, 0x06, 0xF0, 0x03, 0x03, 0x00, 0xC6, 0x30, 0x63, 0x0C, 0x33, 0x30, 0x18, 0x66, 0x98, 0x61, 0x86, 0x19, 0x06, 0x18, 0x03, 0x06, 0x00, 0x83, 0x07, 0x00, 0x86, 0x19, 0x66, 0x98, 0x61, 0x30, 0x18, 0x66, 0x98, 0x61, 0xCC, 0x30, 0x03, 0x18, 0x03, 0x0C, 0x00, 0xC3, 0x0C, 0x00, 0x86, 0x19, 0x66, 0x98, 0x01, 0x30, 0x18, 0x66, 0x98, 0x61, 0xCC, 0x30, 0x03, 0x0C, 0x03, 0x0C, 0x00, 0x63, 0x18, 0x00, 0x86, 0x19, 0x66, 0x18, 0x03, 0x30, 0x18, 0xC6, 0x8C, 0x6D, 0x78, 0xE0, 0x01, 0x06, 0x03, 0x18, 0x00, 0x03, 0x00, 0x00, 0xC6, 0x18, 0x66, 0x0C, 0x1E, 0x30, 0x18, 0xC6, 0x8C, 0x6D, 0x30, 0xC0, 0x00, 0x03, 0x03, 0x18, 0x00, 0x03, 0x00, 0x00, 0x7E, 0x18, 0xE6, 0x07, 0x30, 0x30, 0x18, 0xC6, 0x8C, 0x6D, 0x78, 0xC0, 0x80, 0x01, 0x03, 0x30, 0x00, 0x03, 0x00, 0x00, 0x06, 0x18, 0x66, 0x06, 0x60, 0x30, 0x18, 0x86, 0x87, 0x6D, 0x78, 0xC0, 0xC0, 0x00, 0x03, 0x30, 0x00, 0x03, 0x00, 0x00, 0x06, 0x98, 0x67, 0x8C, 0x61, 0x30, 0x18, 0x86, 0x87, 0x73, 0xCC, 0xC0, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0x00, 0x00, 0x06, 0x30, 0x63, 0x18, 0x33, 0x30, 0x30, 0x03, 0x83, 0x73, 0x86, 0xC1, 0x60, 0x00, 0x03, 0x60, 0x00, 0x03, 0x00, 0x00, 0x06, 0xE0, 0x63, 0x18, 0x1E, 0x30, 0xE0, 0x01, 0x83, 0x61, 0x86, 0xC1, 0xE0, 0x1F, 0x3F, 0xC0, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x60, 0x00, 0x00, 0x80, 0x01, 0x00, 0x0F, 0x00, 0x06, 0x00, 0x00, 0x80, 0x01, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x80, 0x01, 0x80, 0x19, 0x00, 0x06, 0xC0, 0x00, 0x98, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x06, 0xC0, 0x00, 0x98, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x06, 0x00, 0x00, 0x80, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x63, 0x07, 0x3E, 0xB8, 0xE1, 0xE1, 0x07, 0x7F, 0x76, 0xF0, 0x00, 0x9E, 0x31, 0x30, 0x68, 0x63, 0x07, 0x1E, 0x00, 0x30, 0xE6, 0x0C, 0x63, 0xCC, 0x31, 0x83, 0x81, 0x31, 0xCE, 0xC0, 0x00, 0x98, 0x19, 0x30, 0xF8, 0xE7, 0x0C, 0x33, 0x00, 0x00, 0x66, 0x98, 0x01, 0x86, 0x19, 0x86, 0x81, 0x31, 0x86, 0xC1, 0x00, 0x98, 0x0D, 0x30, 0xD8, 0x66, 0x98, 0x61, 0x00, 0xF0, 0x67, 0x98, 0x01, 0x86, 0xF9, 0x87, 0x81, 0x31, 0x86, 0xC1, 0x00, 0x98, 0x0F, 0x30, 0xD8, 0x66, 0x98, 0x61, 0x00, 0x18, 0x66, 0x98, 0x01, 0x86, 0x19, 0x80, 0x01, 0x1F, 0x86, 0xC1, 0x00, 0x98, 0x19, 0x30, 0xD8, 0x66, 0x98, 0x61, 0x00, 0x18, 0xE6, 0x0C, 0x63, 0xCC, 0x31, 0x86, 0x81, 0x01, 0x86, 0xC1, 0x00, 0x98, 0x31, 0x30, 0xD8, 0x66, 0x18, 0x33, 0x00, 0xF0, 0x65, 0x07, 0x3E, 0xB8, 0xE1, 0x83, 0x01, 0x3F, 0x86, 0xF9, 0x07, 0x98, 0x61, 0xFE, 0xD9, 0x66, 0x18, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x61, 0x00, 0x00, 0xC0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x61, 0x00, 0x00, 0xC0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x30, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0xC0, 0xC0, 0x19, 0x00, 0x76, 0xE0, 0x66, 0x0F, 0x3F, 0xFE, 0x18, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x1F, 0x0C, 0x30, 0xC0, 0x60, 0x1B, 0x00, 0xCE, 0x30, 0xC7, 0x99, 0x61, 0x18, 0x18, 0x66, 0x98, 0x61, 0xCC, 0x18, 0x06, 0x8C, 0x07, 0x30, 0x80, 0x67, 0x0E, 0x00, 0x86, 0x19, 0xC6, 0x80, 0x01, 0x18, 0x18, 0xC6, 0x8C, 0x61, 0x78, 0x18, 0x06, 0x06, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x86, 0x19, 0xC6, 0x00, 0x3F, 0x18, 0x18, 0xC6, 0x8C, 0x6D, 0x30, 0x18, 0x06, 0x03, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x86, 0x19, 0xC6, 0x00, 0x60, 0x18, 0x18, 0x86, 0x87, 0x6D, 0x78, 0x18, 0x86, 0x01, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0xCE, 0x30, 0xC7, 0x80, 0x61, 0x98, 0x31, 0x87, 0x87, 0x7F, 0xCC, 0x30, 0xC7, 0x00, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x76, 0xE0, 0xC6, 0x00, 0x3F, 0xF0, 0xE0, 0x06, 0x03, 0x33, 0x86, 0xE1, 0xC6, 0x1F, 0x78, 0x30, 0x78, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0C, 0x00, 0x00, 0x00, 0xC0, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x78, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0xEC, 0x01, 0x00, 0x03, 0x00, 0xCC, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x30, 0x30, 0x04, 0x00, 0x00, 0x32, 0x00, 0xE0, 0x1F, 0x00, 0x86, 0x01, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x78, 0x48, 0x86, 0x07, 0x10, 0x32, 0x00, 0x00, 0x18, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xE0, 0x9F, 0x7F, 0xCC, 0x48, 0xC3, 0x0C, 0x18, 0x32, 0x00, 0x00, 0x0C, 0x00, 0x7F, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0xB0, 0x61, 0x18, 0x0C, 0x32, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0xC0, 0xC0, 0x00, 0x06, 0xF2, 0x00, 0x00, 0x03, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x60, 0x80, 0x07, 0x03, 0x32, 0x00, 0x80, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x83, 0x7F, 0x00, 0xB0, 0x02, 0x0C, 0x06, 0x32, 0x00, 0xC0, 0x00, 0x00, 0x86, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x58, 0x65, 0x18, 0x0C, 0x32, 0x00, 0x60, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x06, 0x06, 0xD8, 0xD8, 0x06, 0x03, 0x0C, 0x00, 0x4C, 0xC5, 0x0C, 0x18, 0x32, 0x00, 0xE0, 0x1F, 0x00, 0x78, 0x00, 0x00, 0xC6, 0x06, 0xD8, 0xD8, 0x06, 0x03, 0x0C, 0x00, 0x80, 0x82, 0x07, 0x10, 0xEC, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x06, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x03, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x1B, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0xC0, 0x00, 0x06, 0x1B, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x98, 0x90, 0xC7, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x80, 0x01, 0x03, 0x36, 0x6C, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x90, 0x84, 0x07, 0x00, 0x00, 0x00, 0x00, 0x8F, 0x61, 0x00, 0x00, 0x01, 0x01, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x64, 0x90, 0x04, 0x03, 0x02, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x0C, 0xCC, 0x00, 0xC0, 0x1F, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xE1, 0xDF, 0xFF, 0x00, 0x00, 0x60, 0x18, 0x18, 0x32, 0x01, 0x00, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x32, 0x01, 0x00, 0x06, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x18, 0xF2, 0x01, 0x00, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0C, 0x32, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x18, 0x06, 0x32, 0x01, 0xC0, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x02, 0xCC, 0x00, 0xC0, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1E, 0xCC, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7F, 0x00, 0xC0, 0x00, 0x03, 0x3C, 0x00, 0x00, 0x00, 0x03, 0x33, 0xCC, 0x00, 0xC0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x66, 0x00, 0x18, 0x06, 0x03, 0x03, 0x00, 0xE0, 0x01, 0x18, 0x44, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0xC0, 0x0F, 0x66, 0x83, 0x31, 0x03, 0x03, 0x0E, 0x00, 0x10, 0xC2, 0x1F, 0x66, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x60, 0x18, 0x06, 0xFF, 0xE1, 0x01, 0x00, 0x1B, 0x00, 0xC8, 0x64, 0x18, 0x33, 0xFE, 0x01, 0xA0, 0x13, 0x00, 0x00, 0xC0, 0x60, 0x80, 0x1F, 0xC6, 0xC0, 0x00, 0x00, 0x33, 0x00, 0x28, 0x65, 0x98, 0x19, 0x80, 0xF1, 0xA3, 0x14, 0x00, 0x00, 0xC0, 0x60, 0x00, 0x06, 0xC6, 0xF0, 0x03, 0x00, 0x36, 0x00, 0x28, 0xC4, 0xD7, 0x0C, 0x80, 0x01, 0xA0, 0x13, 0x00, 0x00, 0xC0, 0x60, 0x18, 0x06, 0xC6, 0xC0, 0x00, 0x03, 0x1C, 0x00, 0x28, 0x05, 0x80, 0x19, 0x80, 0x01, 0xA0, 0x12, 0x00, 0x00, 0xC0, 0xC0, 0x8F, 0x0F, 0xFF, 0xF1, 0x03, 0x03, 0x30, 0x00, 0xC8, 0xE4, 0x1F, 0x33, 0x00, 0x00, 0xA0, 0x14, 0x00, 0x00, 0xC0, 0x00, 0x83, 0xDA, 0x83, 0xC1, 0x00, 0x03, 0x33, 0x00, 0x10, 0x02, 0x00, 0x66, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x00, 0x83, 0x73, 0x00, 0xC0, 0x00, 0x03, 0x1E, 0x00, 0xE0, 0x01, 0x00, 0x44, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x80, 0x03, 0x0E, 0x60, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x60, 0x80, 0x07, 0x00, 0x04, 0x10, 0xE0, 0x00, 0x0C, 0xCC, 0x00, 0xC0, 0x06, 0x19, 0x30, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0xC0, 0x0C, 0x00, 0x06, 0x18, 0x00, 0x01, 0x0C, 0xCC, 0xC0, 0x00, 0x06, 0x0C, 0x18, 0x00, 0x60, 0x1B, 0x00, 0x00, 0x60, 0x60, 0x98, 0x08, 0x84, 0x10, 0xC2, 0x08, 0x00, 0xCC, 0xC0, 0x80, 0x03, 0x18, 0x00, 0x00, 0xE0, 0x1B, 0x00, 0x00, 0x60, 0x60, 0x98, 0x19, 0x44, 0x10, 0x01, 0x05, 0x0C, 0xCC, 0xC0, 0xC0, 0x00, 0x1B, 0x00, 0x00, 0xC0, 0x1B, 0x1C, 0x00, 0x60, 0x60, 0x18, 0x33, 0x20, 0x80, 0xE0, 0x02, 0x0C, 0x78, 0xF8, 0xC7, 0x07, 0x0E, 0x00, 0x18, 0x03, 0x1B, 0x1C, 0x00, 0xF0, 0xC0, 0x0C, 0x66, 0x90, 0x40, 0x03, 0x09, 0x06, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x18, 0x03, 0x1B, 0x1C, 0x00, 0x00, 0x80, 0x07, 0xCC, 0xC8, 0xA0, 0x84, 0x0C, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x18, 0x03, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xA4, 0x10, 0x42, 0x8A, 0x61, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x18, 0x03, 0x1B, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x33, 0xE0, 0x00, 0x01, 0x8E, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x03, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x80, 0x80, 0x07, 0x08, 0x33, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x78, 0x03, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x03, 0x03, 0x26, 0xCC, 0x30, 0x03, 0x00, 0x00, 0x0C, 0x80, 0x01, 0x03, 0x33, 0x18, 0x80, 0x01, 0x03, 0x33, 0x30, 0xC0, 0xC0, 0x0C, 0x19, 0x00, 0xE0, 0x01, 0x1F, 0x1E, 0x30, 0x60, 0xC0, 0x0C, 0x00, 0x60, 0x60, 0xC0, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x07, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x78, 0xC0, 0xC0, 0x86, 0x61, 0xFE, 0xF9, 0xE7, 0x9F, 0x7F, 0xFC, 0xF0, 0xC3, 0x0F, 0x0C, 0x78, 0xE0, 0x81, 0x07, 0x1E, 0xCC, 0xE0, 0xC1, 0x86, 0x01, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0xCC, 0x30, 0xC3, 0x0C, 0x33, 0x86, 0x31, 0x63, 0x86, 0x01, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xE6, 0x9F, 0x01, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x86, 0x01, 0x7E, 0xF8, 0xE1, 0x87, 0x1F, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0xFE, 0xF9, 0xE7, 0x9F, 0x7F, 0xFE, 0xF9, 0x67, 0x86, 0x01, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x86, 0x61, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x06, 0x33, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x1E, 0x1E, 0xFE, 0xF9, 0xE7, 0x9F, 0x7F, 0xFC, 0xF0, 0xC3, 0x0F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x82, 0x01, 0x18, 0x30, 0x60, 0xC2, 0x0C, 0x00, 0x00, 0x60, 0x00, 0x06, 0x0C, 0xCC, 0x80, 0x01, 0x00, 0x00, 0x7E, 0x90, 0x01, 0x06, 0x06, 0xCC, 0x90, 0x01, 0x00, 0x00, 0x78, 0x82, 0x81, 0x01, 0x33, 0x00, 0x60, 0xC0, 0x00, 0x1C, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0xCC, 0x01, 0x00, 0x00, 0x00, 0x86, 0x01, 0xC0, 0x00, 0x36, 0x86, 0x19, 0x86, 0x07, 0x1E, 0x78, 0xE0, 0xC1, 0x0C, 0x00, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x00, 0x63, 0x86, 0x39, 0xC6, 0x0C, 0x33, 0xCC, 0x30, 0x63, 0x18, 0x00, 0xC6, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x0F, 0x63, 0x86, 0x79, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x98, 0x20, 0xA6, 0x19, 0x66, 0x98, 0x61, 0x86, 0x31, 0xC3, 0x98, 0x33, 0x9F, 0xD9, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x98, 0x31, 0x96, 0x19, 0x66, 0x98, 0x61, 0x86, 0xE1, 0xC1, 0x18, 0x1B, 0x86, 0xD9, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x18, 0x1B, 0x8E, 0x19, 0x66, 0x98, 0x61, 0x86, 0xE1, 0xC1, 0x18, 0x33, 0x86, 0x99, 0x67, 0x98, 0x61, 0x86, 0x19, 0x66, 0x18, 0x0E, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0xC1, 0xC0, 0x0F, 0x63, 0x86, 0x19, 0x67, 0x98, 0x61, 0x86, 0x19, 0x66, 0x18, 0x1B, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0xC1, 0xC0, 0x00, 0x63, 0xC6, 0x18, 0xC6, 0x0C, 0x33, 0xCC, 0x30, 0xC3, 0x8C, 0x31, 0xCD, 0x30, 0xC3, 0x0C, 0x33, 0xCC, 0xC0, 0xC0, 0x00, 0x63, 0x7E, 0x18, 0x86, 0x07, 0x1E, 0x78, 0xE0, 0x81, 0x87, 0x20, 0x78, 0xE0, 0x81, 0x07, 0x1E, 0x78, 0xC0, 0xC0, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x03, 0x26, 0x00, 0x30, 0x03, 0x00, 0x00, 0x0C, 0x00, 0x03, 0x03, 0x00, 0x0C, 0x80, 0x01, 0x03, 0x00, 0x30, 0x80, 0x81, 0x07, 0x3F, 0xCC, 0x30, 0x03, 0x00, 0x00, 0x18, 0x80, 0x81, 0x07, 0x33, 0x18, 0xC0, 0x80, 0x07, 0x33, 0x60, 0xC0, 0xC0, 0x0C, 0x19, 0xCC, 0xE0, 0x01, 0x00, 0x00, 0x30, 0xC0, 0xC0, 0x0C, 0x33, 0x30, 0x60, 0xC0, 0x0C, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xF0, 0xC3, 0x0F, 0x3F, 0xFC, 0xF0, 0xC3, 0x0D, 0x3E, 0x78, 0xE0, 0x81, 0x07, 0x1E, 0x3C, 0xF0, 0xC0, 0x03, 0x0F, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x26, 0x1B, 0x63, 0xCC, 0x30, 0xC3, 0x0C, 0x33, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x80, 0x01, 0x06, 0x18, 0x60, 0x80, 0x01, 0x06, 0x9B, 0x01, 0x86, 0x19, 0x66, 0x98, 0x61, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0xFC, 0xF1, 0xC7, 0x1F, 0x7F, 0xFC, 0xF1, 0xC7, 0x8F, 0x01, 0xFE, 0xF9, 0xE7, 0x9F, 0x7F, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x83, 0x01, 0x06, 0x18, 0x60, 0x80, 0x01, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x13, 0x63, 0x8C, 0x31, 0xC6, 0x18, 0x63, 0x30, 0xC0, 0x00, 0x03, 0x0C, 0x7C, 0xF1, 0xC5, 0x17, 0x5F, 0x7C, 0xF1, 0xC5, 0x0E, 0x3E, 0xF8, 0xE0, 0x83, 0x0F, 0x3E, 0xFE, 0xF9, 0xE7, 0x9F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x60, 0xC2, 0x00, 0x30, 0x30, 0x60, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x0C, 0x0C, 0x00, 0x80, 0xC1, 0x00, 0x00, 0x3C, 0xF0, 0x83, 0x01, 0x18, 0x78, 0xF0, 0xC3, 0x0C, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x1E, 0xCC, 0xC0, 0xC0, 0x00, 0x33, 0x18, 0x90, 0x01, 0x03, 0x0C, 0xCC, 0x90, 0xC1, 0x0C, 0x0C, 0x00, 0x80, 0x01, 0x03, 0x33, 0xCC, 0x60, 0xC0, 0x00, 0x33, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x66, 0xD8, 0x81, 0x07, 0x1E, 0x78, 0xE0, 0x81, 0x07, 0x00, 0xF8, 0x18, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x87, 0x61, 0xF8, 0x38, 0xC3, 0x0C, 0x33, 0xCC, 0x30, 0xC3, 0x8C, 0x7F, 0xCC, 0x18, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x8C, 0x61, 0xCC, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x98, 0x7F, 0xA6, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x98, 0x61, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x18, 0x00, 0x96, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x98, 0x61, 0x86, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0x66, 0x18, 0x0C, 0x8E, 0x19, 0x66, 0x98, 0x61, 0x86, 0x19, 0xC6, 0x98, 0x61, 0xCC, 0x18, 0xC6, 0x0C, 0x33, 0xCC, 0x30, 0xC3, 0x0C, 0x0C, 0xCC, 0x30, 0xC7, 0x1C, 0x73, 0xCC, 0x31, 0xC7, 0x0C, 0x73, 0x78, 0x18, 0x86, 0x07, 0x1E, 0x78, 0xE0, 0x81, 0x07, 0x00, 0x7A, 0xE0, 0x86, 0x1B, 0x6E, 0xB8, 0xE1, 0xC6, 0x07, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xC1, 0x80, 0x1F, }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/font5x8.bmp000066400000000000000000000032021504763705000247040ustar00rootroot00000000000000BMlP BGRs2 d1J)J@ԥ);t)JN)JR1t)KER@)"F(s9c9)J1)Ibs;c10"EH"$JA0At1L)JDԥ)M)JJԥ)T)J@)*TL1)GuPq  F*@)J9)J!{9)J!c29"%@I"$@A0A$@@p 8HOI # JJiQR "ABH(|@`(]G;|@`L@DS E*D%R@Q*0ETLH@*@ K*@HPPL0` @8b`%IUcIUdB nIQB!@`` g1'VLa"VR[VRg1 "k @@ $1<8"I BJUaJQb"JQD @8JJ|80Dz /FL)B)HF B HF s WR)B HWRK8{̓(*L031cAI!"JHbxi/ DDYsddxBK$FBb1yc@"$B*ACA=xA@ A$@*";j;{BW5?*$oI?$W;{B;hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/font5x8.h000066400000000000000000000176351504763705000243740ustar00rootroot00000000000000#define font5x8_width 80 #define font5x8_height 128 static const Uint8 font5x8_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x42, 0xDC, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x11, 0x24, 0xEA, 0xAB, 0x00, 0x00, 0xE0, 0xDC, 0xDE, 0xA0, 0x92, 0xFF, 0xF7, 0x73, 0x00, 0x00, 0x10, 0x7F, 0xFC, 0x80, 0x54, 0x24, 0xF6, 0x75, 0x00, 0x00, 0x10, 0x3F, 0x70, 0x80, 0x38, 0x42, 0xEA, 0xAC, 0x00, 0x00, 0x10, 0x7F, 0xFC, 0x80, 0x10, 0x00, 0xDC, 0x56, 0x00, 0x00, 0xE0, 0xDC, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x28, 0xE5, 0x54, 0x21, 0x44, 0x00, 0x00, 0x00, 0x40, 0x80, 0xA8, 0x5F, 0x50, 0x21, 0x82, 0x24, 0x02, 0x00, 0x40, 0x80, 0x28, 0xE5, 0x88, 0x20, 0x82, 0x18, 0x02, 0x00, 0x20, 0x80, 0x80, 0x4F, 0x45, 0x01, 0x82, 0xBC, 0x0F, 0x1E, 0x10, 0x00, 0x00, 0xE5, 0x54, 0x01, 0x82, 0x18, 0xC2, 0x80, 0x09, 0x80, 0x00, 0x45, 0x80, 0x02, 0x44, 0x24, 0x42, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0x98, 0x47, 0x9E, 0x79, 0xC6, 0x00, 0x80, 0x80, 0x20, 0xD2, 0x24, 0x62, 0x42, 0x40, 0x29, 0x19, 0x46, 0x00, 0x51, 0x9A, 0x20, 0x53, 0xCE, 0x21, 0x26, 0x19, 0x26, 0x1E, 0x42, 0x96, 0x18, 0xF4, 0x50, 0x22, 0xC9, 0x01, 0x20, 0x00, 0x22, 0x92, 0x84, 0x44, 0x52, 0x12, 0x09, 0x19, 0x46, 0x1E, 0x01, 0xCC, 0x3D, 0x43, 0x8C, 0x11, 0xC6, 0x18, 0x82, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x1C, 0x73, 0xDE, 0x33, 0xC9, 0xB9, 0x14, 0x54, 0x32, 0x39, 0xA5, 0x94, 0x42, 0x48, 0x89, 0x90, 0x12, 0xEA, 0x4A, 0x35, 0x9D, 0x90, 0xCE, 0x09, 0x8F, 0x90, 0x11, 0xEA, 0x4A, 0xF5, 0xA5, 0x90, 0x42, 0x68, 0x89, 0x90, 0x12, 0x62, 0x4B, 0x29, 0xA5, 0x94, 0x42, 0x48, 0x89, 0x94, 0x12, 0x62, 0x4B, 0x22, 0x1D, 0x73, 0x5E, 0x30, 0xC9, 0x89, 0xF4, 0x62, 0x32, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x1C, 0xE3, 0x52, 0x52, 0x29, 0x3E, 0x17, 0x1C, 0x01, 0x29, 0xA5, 0x44, 0x52, 0x8A, 0x29, 0x22, 0x11, 0x90, 0x02, 0x29, 0x25, 0x41, 0x52, 0x8A, 0x46, 0x11, 0x21, 0x10, 0x00, 0x67, 0x1D, 0x42, 0x52, 0xAA, 0x86, 0x08, 0x41, 0x10, 0x00, 0xA1, 0xA5, 0x44, 0x92, 0xA9, 0x89, 0x04, 0x81, 0x10, 0x00, 0xC1, 0x24, 0x43, 0x8C, 0x51, 0x89, 0x3C, 0x87, 0x1C, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x80, 0x00, 0x01, 0x81, 0xA0, 0x60, 0x00, 0x00, 0x04, 0x04, 0x80, 0x80, 0x02, 0x01, 0x80, 0x40, 0x00, 0x00, 0xC0, 0x1D, 0xE6, 0x8C, 0x30, 0xC7, 0xA0, 0x44, 0xD6, 0x31, 0x20, 0x25, 0x91, 0xDA, 0x49, 0x89, 0xA0, 0x43, 0x6A, 0x4A, 0x20, 0x25, 0x91, 0x86, 0x70, 0x89, 0xA0, 0x44, 0x6A, 0x4A, 0xC0, 0x1D, 0xE6, 0x8C, 0x40, 0xC9, 0xA9, 0xE4, 0x6A, 0x32, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x41, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x42, 0x84, 0x02, 0xC7, 0x15, 0x76, 0x92, 0x8A, 0x29, 0xBD, 0x41, 0x58, 0x01, 0x29, 0x2D, 0x23, 0x92, 0xAA, 0x26, 0x11, 0x42, 0x04, 0x00, 0xC7, 0x05, 0xA4, 0x92, 0xAA, 0xC6, 0x09, 0x41, 0x08, 0x00, 0x01, 0x05, 0x43, 0x1C, 0x51, 0x29, 0x3D, 0x46, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x80, 0x02, 0x0C, 0x00, 0x06, 0x00, 0x21, 0x04, 0x10, 0xA0, 0x01, 0x01, 0x12, 0x00, 0x0A, 0x80, 0x73, 0x0A, 0x32, 0x58, 0x80, 0x07, 0x07, 0x00, 0x07, 0x00, 0x21, 0x40, 0x09, 0x54, 0x00, 0x02, 0x07, 0x00, 0x02, 0x00, 0x21, 0x80, 0x30, 0xD2, 0x01, 0x01, 0x12, 0x00, 0x02, 0x00, 0x71, 0x40, 0x40, 0x54, 0x80, 0x00, 0x0C, 0xA0, 0xA2, 0x2A, 0x21, 0x80, 0x32, 0xA8, 0x81, 0x07, 0x00, 0x10, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0xA5, 0x00, 0x00, 0x54, 0x2B, 0x00, 0x00, 0x55, 0x00, 0x11, 0xA5, 0x00, 0x00, 0x4A, 0x13, 0x02, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x18, 0x00, 0x40, 0x3B, 0xA4, 0x80, 0x57, 0x00, 0x00, 0x00, 0x98, 0xF3, 0x00, 0x18, 0xD8, 0x01, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x54, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xA2, 0x80, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x30, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x22, 0x09, 0xCA, 0x29, 0x00, 0x80, 0x73, 0x00, 0x10, 0x15, 0x15, 0x39, 0xA0, 0x32, 0x00, 0xC0, 0x05, 0x80, 0xB8, 0xE3, 0x3E, 0x48, 0x60, 0x02, 0x05, 0xC0, 0x06, 0x80, 0x14, 0xA1, 0x08, 0x71, 0x60, 0xBA, 0xE2, 0xDC, 0x05, 0x80, 0x14, 0xE5, 0x3E, 0x41, 0xA0, 0x02, 0x85, 0xC0, 0x06, 0x80, 0xB8, 0x12, 0x09, 0x39, 0xC0, 0x01, 0x80, 0x80, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x80, 0x10, 0x10, 0x42, 0x00, 0x04, 0x28, 0x44, 0x80, 0x07, 0xC0, 0x28, 0x10, 0x82, 0x20, 0x8A, 0x20, 0x23, 0x40, 0x05, 0x80, 0x10, 0x10, 0x4A, 0x00, 0xC4, 0x11, 0x04, 0x52, 0x05, 0x80, 0x80, 0x52, 0x96, 0x21, 0x80, 0x38, 0x03, 0x92, 0x25, 0xC0, 0x39, 0x65, 0x50, 0x11, 0x00, 0x00, 0x00, 0x12, 0x05, 0x00, 0x80, 0xF2, 0xC8, 0x53, 0xC0, 0x01, 0x00, 0x0E, 0x05, 0x04, 0x00, 0x40, 0x1C, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x82, 0x18, 0x95, 0x0C, 0x00, 0x82, 0x98, 0x24, 0x10, 0x51, 0x44, 0xA4, 0x02, 0x92, 0x33, 0x44, 0x24, 0x40, 0x88, 0x02, 0xC6, 0x18, 0x63, 0x4C, 0x49, 0xEF, 0xBD, 0xE7, 0x9C, 0x73, 0x29, 0xA5, 0x94, 0x52, 0x09, 0x21, 0x84, 0x40, 0x08, 0x21, 0xEF, 0xBD, 0xF7, 0xDE, 0x0B, 0xE7, 0x9C, 0x43, 0x08, 0x21, 0x29, 0xA5, 0x94, 0x52, 0x49, 0x21, 0x84, 0x40, 0x08, 0x21, 0x29, 0xA5, 0x94, 0x52, 0x33, 0xEF, 0xBD, 0xE7, 0x9C, 0x73, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x09, 0x62, 0x54, 0x02, 0x40, 0x10, 0x93, 0x10, 0x00, 0xAE, 0x10, 0x91, 0x0A, 0x00, 0x8E, 0x88, 0x04, 0x48, 0x30, 0x32, 0x19, 0x63, 0x8C, 0x01, 0x2D, 0xA5, 0x94, 0xE2, 0x49, 0x77, 0xA5, 0x94, 0x52, 0x02, 0x2D, 0xA5, 0x94, 0x54, 0x2A, 0xB2, 0xA5, 0x94, 0x52, 0x52, 0x2B, 0xA5, 0x94, 0x48, 0x2A, 0x32, 0xA5, 0x94, 0x52, 0x22, 0x2B, 0xA5, 0x94, 0xC8, 0x49, 0x2E, 0x19, 0x63, 0x8C, 0x51, 0xC7, 0x18, 0x63, 0x48, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x10, 0x05, 0x0C, 0x00, 0x82, 0x18, 0x20, 0x10, 0x01, 0x44, 0xA8, 0xA2, 0x12, 0x00, 0x44, 0x24, 0x45, 0x88, 0x52, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0x39, 0xE7, 0xDC, 0x63, 0xC6, 0x18, 0x63, 0x8C, 0x31, 0x29, 0xA5, 0x94, 0x92, 0x15, 0xAD, 0xB5, 0x46, 0x08, 0x21, 0x29, 0xA5, 0x94, 0x52, 0x13, 0x63, 0x8C, 0x41, 0x08, 0x21, 0xCE, 0x39, 0xE7, 0x9C, 0x67, 0xC6, 0x18, 0xE3, 0x9C, 0x73, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x09, 0x62, 0x14, 0x00, 0x40, 0x10, 0x03, 0x08, 0x00, 0xA2, 0x10, 0x91, 0x4A, 0x02, 0x80, 0x88, 0x94, 0x44, 0x48, 0x05, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0xE8, 0x18, 0x63, 0x8C, 0x01, 0x2E, 0xA5, 0x94, 0xD2, 0x49, 0x2E, 0xA5, 0x94, 0x52, 0x72, 0x2D, 0xA5, 0x94, 0x52, 0x4A, 0x29, 0xA5, 0x94, 0x52, 0x02, 0x2B, 0xA5, 0x94, 0xDC, 0x71, 0x26, 0x19, 0x63, 0x8C, 0x21, 0xC7, 0x39, 0xE7, 0x52, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x30, }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-sdl/sdlgui.c000066400000000000000000001152451504763705000243370ustar00rootroot00000000000000/* Hatari - sdlgui.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. A tiny graphical user interface for Hatari. */ const char SDLGui_fileid[] = "Hatari sdlgui.c"; #include #include #include #include #include #include "main.h" #include "screen.h" #include "sdlgui.h" #include "str.h" #include "log.h" #include "font5x8.h" #include "font10x16.h" #define DEBUG_INFO 0 #if DEBUG_INFO # define Dprintf(a) printf a #else # define Dprintf(a) #endif /* sanity check for minimum size */ #define MIN_DIALOG_WIDTH 12 #define MIN_DIALOG_HEIGHT 5 /* Dialogs need to fit into Hatari window. These are max sizes * (with the current font) when both borders & statusbar are disabled. */ #define MAX_DIALOG_WIDTH 64 #define MAX_DIALOG_HEIGHT 25 static SDL_Surface *pSdlGuiScrn; /* Pointer to the actual main SDL screen surface */ static SDL_Surface *pSmallFontGfx = NULL; /* The small font graphics */ static SDL_Surface *pBigFontGfx = NULL; /* The big font graphics */ static SDL_Surface *pFontGfx = NULL; /* The actual font graphics */ static struct { Uint32 darkbar, midbar, lightbar; Uint32 darkgrey, midgrey, lightgrey; Uint32 focus, cursor, underline, editfield; } colors; int sdlgui_fontwidth; /* Width of the actual font */ int sdlgui_fontheight; /* Height of the actual font */ #define UNDERLINE_INDICATOR '_' /*-----------------------------------------------------------------------*/ /** * Load an 1 plane XBM into a 8 planes SDL_Surface. */ static SDL_Surface *SDLGui_LoadXBM(int w, int h, const Uint8 *pXbmBits) { SDL_Surface *bitmap; Uint8 *dstbits; const Uint8 *srcbits; int x, y, srcpitch; int mask; srcbits = pXbmBits; /* Allocate the bitmap */ bitmap = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8, 0, 0, 0, 0); if (bitmap == NULL) { Log_Printf(LOG_ERROR, "SDLGui: failed to allocate bitmap: %s", SDL_GetError()); return NULL; } srcpitch = ((w + 7) / 8); dstbits = (Uint8 *)bitmap->pixels; mask = 1; /* Copy the pixels */ for (y = 0 ; y < h ; y++) { for (x = 0 ; x < w ; x++) { dstbits[x] = (srcbits[x / 8] & mask) ? 1 : 0; mask <<= 1; mask |= (mask >> 8); mask &= 0xFF; } dstbits += bitmap->pitch; srcbits += srcpitch; } return bitmap; } /*-----------------------------------------------------------------------*/ /** * Initialize the GUI. */ int SDLGui_Init(void) { SDL_Color blackWhiteColors[2] = {{255, 255, 255, 255}, {0, 0, 0, 255}}; if (pSmallFontGfx && pBigFontGfx) { /* already initialized */ return 0; } /* Initialize the font graphics: */ pSmallFontGfx = SDLGui_LoadXBM(font5x8_width, font5x8_height, font5x8_bits); pBigFontGfx = SDLGui_LoadXBM(font10x16_width, font10x16_height, font10x16_bits); if (pSmallFontGfx == NULL || pBigFontGfx == NULL) { Log_Printf(LOG_ERROR, "SDLGui: cannot init font graphics!\n"); return -1; } /* Set color palette of the font graphics: */ SDL_SetPaletteColors(pSmallFontGfx->format->palette, blackWhiteColors, 0, 2); SDL_SetPaletteColors(pBigFontGfx->format->palette, blackWhiteColors, 0, 2); /* Set font color 0 as transparent: */ SDL_SetColorKey(pSmallFontGfx, SDL_RLEACCEL, 0); SDL_SetColorKey(pBigFontGfx, SDL_RLEACCEL, 0); return 0; } /*-----------------------------------------------------------------------*/ /** * Uninitialize the GUI. */ int SDLGui_UnInit(void) { if (pSmallFontGfx) { SDL_FreeSurface(pSmallFontGfx); pSmallFontGfx = NULL; } if (pBigFontGfx) { SDL_FreeSurface(pBigFontGfx); pBigFontGfx = NULL; } return 0; } /*-----------------------------------------------------------------------*/ /** * Inform the SDL-GUI about the actual SDL_Surface screen pointer and * prepare the font to suit the actual resolution. */ int SDLGui_SetScreen(SDL_Surface *pScrn) { pSdlGuiScrn = pScrn; /* Decide which font to use - small or big one: */ if (pSdlGuiScrn->w >= 640 && pSdlGuiScrn->h >= 400 && pBigFontGfx != NULL) { pFontGfx = pBigFontGfx; } else { pFontGfx = pSmallFontGfx; } if (pFontGfx == NULL) { Log_Printf(LOG_ERROR, "SDLGui: a problem with the font occurred!\n"); return -1; } /* Get the font width and height: */ sdlgui_fontwidth = pFontGfx->w/16; sdlgui_fontheight = pFontGfx->h/16; /* scrollbar */ colors.darkbar = SDL_MapRGB(pSdlGuiScrn->format, 64, 64, 64); colors.midbar = SDL_MapRGB(pSdlGuiScrn->format,128,128,128); colors.lightbar = SDL_MapRGB(pSdlGuiScrn->format,196,196,196); /* buttons, midgray is also normal bg color */ colors.darkgrey = SDL_MapRGB(pSdlGuiScrn->format,128,128,128); colors.midgrey = SDL_MapRGB(pSdlGuiScrn->format,192,192,192); colors.lightgrey = SDL_MapRGB(pSdlGuiScrn->format,255,255,255); /* others */ colors.focus = SDL_MapRGB(pSdlGuiScrn->format,212,212,212); colors.cursor = SDL_MapRGB(pSdlGuiScrn->format,128,128,128); if (sdlgui_fontheight < 16) colors.underline = SDL_MapRGB(pSdlGuiScrn->format,255,0,255); else colors.underline = SDL_MapRGB(pSdlGuiScrn->format,0,0,0); colors.editfield = SDL_MapRGB(pSdlGuiScrn->format,160,160,160); return 0; } /*-----------------------------------------------------------------------*/ /** * Return character size for current font in given arguments. */ void SDLGui_GetFontSize(int *width, int *height) { *width = sdlgui_fontwidth; *height = sdlgui_fontheight; } /*-----------------------------------------------------------------------*/ /** * Center a dialog so that it appears in the middle of the screen. * Note: We only store the coordinates in the root box of the dialog, * all other objects in the dialog are positioned relatively to this one. */ void SDLGui_CenterDlg(SGOBJ *dlg) { int w = dlg[0].w, h = dlg[0].h; /* catch invalid changes to SDL GUI dialogs */ if (w < MIN_DIALOG_WIDTH || h < MIN_DIALOG_HEIGHT) { Log_Printf(LOG_ERROR, "invalid (too small) dialog size (%dx%d)!", w, h); } if (w > MAX_DIALOG_WIDTH || h > MAX_DIALOG_HEIGHT) { Log_Printf(LOG_ERROR, "dialog too large (%dx%d), max working size is %dx%d!", w, h, MAX_DIALOG_WIDTH, MAX_DIALOG_HEIGHT); } dlg[0].x = (pSdlGuiScrn->w / sdlgui_fontwidth - w) / 2; dlg[0].y = (pSdlGuiScrn->h / sdlgui_fontheight - h) / 2; } /*-----------------------------------------------------------------------*/ /** * Return text length which ignores underlining. */ static int SDLGui_TextLen(const char *str) { int len; for (len = 0; *str; str++) { if (*str != UNDERLINE_INDICATOR) len++; } return len; } /*-----------------------------------------------------------------------*/ /** * Draw a text string (internal version). */ static void SDLGui_TextInt(int x, int y, const char *txt, bool underline) { int i, offset; unsigned char c; SDL_Rect sr, dr; /* underline offset needs to go outside the box for smaller font */ if (sdlgui_fontheight < 16) offset = sdlgui_fontheight - 1; else offset = sdlgui_fontheight - 2; i = 0; while (txt[i]) { dr.x=x; dr.y=y; dr.w=sdlgui_fontwidth; dr.h=sdlgui_fontheight; c = txt[i++]; if (c == UNDERLINE_INDICATOR && underline) { dr.h = 1; dr.y += offset; SDL_FillRect(pSdlGuiScrn, &dr, colors.underline); continue; } /* for now, assume (only) Linux file paths are UTF-8 */ #if !(defined(WIN32) || defined(USE_LOCALE_CHARSET)) /* Quick and dirty conversion for latin1 characters only... */ if ((c & 0xc0) == 0xc0) { c = c << 6; c |= (txt[i++]) & 0x7f; } else if (c >= 0x80) { Log_Printf(LOG_WARN, "Unsupported character '%c' (0x%x)\n", c, c); } #endif x += sdlgui_fontwidth; sr.x=sdlgui_fontwidth*(c%16); sr.y=sdlgui_fontheight*(c/16); sr.w=sdlgui_fontwidth; sr.h=sdlgui_fontheight; SDL_BlitSurface(pFontGfx, &sr, pSdlGuiScrn, &dr); } } /*-----------------------------------------------------------------------*/ /** * Draw a text string (generic). */ void SDLGui_Text(int x, int y, const char *txt) { SDLGui_TextInt(x, y, txt, false); } /*-----------------------------------------------------------------------*/ /** * Draw a dialog text object. */ static void SDLGui_DrawText(const SGOBJ *tdlg, int objnum) { int x, y; x = (tdlg[0].x+tdlg[objnum].x)*sdlgui_fontwidth; y = (tdlg[0].y+tdlg[objnum].y)*sdlgui_fontheight; if (tdlg[objnum].flags & SG_EXIT) { SDL_Rect rect; /* Draw background: */ rect.x = x; rect.y = y; rect.w = tdlg[objnum].w * sdlgui_fontwidth; rect.h = tdlg[objnum].h * sdlgui_fontheight; if (tdlg[objnum].state & SG_FOCUSED) SDL_FillRect(pSdlGuiScrn, &rect, colors.focus); else SDL_FillRect(pSdlGuiScrn, &rect, colors.midgrey); } SDLGui_Text(x, y, tdlg[objnum].txt); } /*-----------------------------------------------------------------------*/ /** * Draw a edit field object. */ static void SDLGui_DrawEditField(const SGOBJ *edlg, int objnum) { int x, y; SDL_Rect rect; x = (edlg[0].x+edlg[objnum].x)*sdlgui_fontwidth; y = (edlg[0].y+edlg[objnum].y)*sdlgui_fontheight; SDLGui_Text(x, y, edlg[objnum].txt); rect.x = x; rect.y = y + edlg[objnum].h * sdlgui_fontheight; rect.w = edlg[objnum].w * sdlgui_fontwidth; rect.h = 1; SDL_FillRect(pSdlGuiScrn, &rect, colors.editfield); } /*-----------------------------------------------------------------------*/ /** * Draw a dialog box object. */ static void SDLGui_DrawBox(const SGOBJ *bdlg, int objnum) { SDL_Rect rect; int x, y, w, h, offset; Uint32 color, upleftc, downrightc; if (bdlg[objnum].state & SG_FOCUSED) color = colors.focus; else color = colors.midgrey; x = bdlg[objnum].x*sdlgui_fontwidth; y = bdlg[objnum].y*sdlgui_fontheight; if (objnum > 0) /* Since the root object is a box, too, */ { /* we have to look for it now here and only */ x += bdlg[0].x*sdlgui_fontwidth; /* add its absolute coordinates if we need to */ y += bdlg[0].y*sdlgui_fontheight; } w = bdlg[objnum].w*sdlgui_fontwidth; h = bdlg[objnum].h*sdlgui_fontheight; if (bdlg[objnum].state & SG_SELECTED) { upleftc = colors.darkgrey; downrightc = colors.lightgrey; } else { upleftc = colors.lightgrey; downrightc = colors.darkgrey; } /* The root box should be bigger than the screen, so we disable the offset there: */ if (objnum != 0) offset = 1; else offset = 0; /* Draw background: */ rect.x = x; rect.y = y; rect.w = w; rect.h = h; SDL_FillRect(pSdlGuiScrn, &rect, color); /* Draw upper border: */ rect.x = x; rect.y = y - offset; rect.w = w; rect.h = 1; SDL_FillRect(pSdlGuiScrn, &rect, upleftc); /* Draw left border: */ rect.x = x - offset; rect.y = y; rect.w = 1; rect.h = h; SDL_FillRect(pSdlGuiScrn, &rect, upleftc); /* Draw bottom border: */ rect.x = x; rect.y = y + h - 1 + offset; rect.w = w; rect.h = 1; SDL_FillRect(pSdlGuiScrn, &rect, downrightc); /* Draw right border: */ rect.x = x + w - 1 + offset; rect.y = y; rect.w = 1; rect.h = h; SDL_FillRect(pSdlGuiScrn, &rect, downrightc); } /*-----------------------------------------------------------------------*/ /** * Draw a normal button. */ static void SDLGui_DrawButton(const SGOBJ *bdlg, int objnum) { int x,y; SDLGui_DrawBox(bdlg, objnum); x = (bdlg[0].x + bdlg[objnum].x + (bdlg[objnum].w-SDLGui_TextLen(bdlg[objnum].txt))/2) * sdlgui_fontwidth; y = (bdlg[0].y + bdlg[objnum].y + (bdlg[objnum].h-1)/2) * sdlgui_fontheight; if (bdlg[objnum].state & SG_SELECTED) { x+=1; y+=1; } SDLGui_TextInt(x, y, bdlg[objnum].txt, true); } /*-----------------------------------------------------------------------*/ /** * If object is focused, draw a focused background to it */ static void SDLGui_DrawFocusBg(const SGOBJ *obj, int x, int y) { SDL_Rect rect; Uint32 color; if (obj->state & SG_WASFOCUSED) color = colors.midgrey; else if (obj->state & SG_FOCUSED) color = colors.focus; else return; rect.x = x; rect.y = y; rect.w = obj->w * sdlgui_fontwidth; rect.h = obj->h * sdlgui_fontheight; SDL_FillRect(pSdlGuiScrn, &rect, color); } /*-----------------------------------------------------------------------*/ /** * Draw a dialog radio button object. */ static void SDLGui_DrawRadioButton(const SGOBJ *rdlg, int objnum) { char str[80]; int x, y; x = (rdlg[0].x + rdlg[objnum].x) * sdlgui_fontwidth; y = (rdlg[0].y + rdlg[objnum].y) * sdlgui_fontheight; SDLGui_DrawFocusBg(&(rdlg[objnum]), x, y); if (rdlg[objnum].state & SG_SELECTED) str[0]=SGRADIOBUTTON_SELECTED; else str[0]=SGRADIOBUTTON_NORMAL; str[1]=' '; Str_Copy(&str[2], rdlg[objnum].txt, sizeof(str) - 2); SDLGui_TextInt(x, y, str, true); } /*-----------------------------------------------------------------------*/ /** * Draw a dialog check box object. */ static void SDLGui_DrawCheckBox(const SGOBJ *cdlg, int objnum) { char str[80]; int x, y; x = (cdlg[0].x + cdlg[objnum].x) * sdlgui_fontwidth; y = (cdlg[0].y + cdlg[objnum].y) * sdlgui_fontheight; SDLGui_DrawFocusBg(&(cdlg[objnum]), x, y); if ( cdlg[objnum].state&SG_SELECTED ) str[0]=SGCHECKBOX_SELECTED; else str[0]=SGCHECKBOX_NORMAL; str[1]=' '; Str_Copy(&str[2], cdlg[objnum].txt, sizeof(str) - 2); SDLGui_TextInt(x, y, str, true); } /*-----------------------------------------------------------------------*/ /** * Draw a scrollbar button. */ static void SDLGui_DrawScrollbar(const SGOBJ *bdlg, int objnum) { SDL_Rect rect; int x, y, w, h; x = bdlg[objnum].x * sdlgui_fontwidth; y = bdlg[objnum].y * sdlgui_fontheight + bdlg[objnum].h; x += bdlg[0].x*sdlgui_fontwidth; /* add mainbox absolute coordinates */ y += bdlg[0].y*sdlgui_fontheight; /* add mainbox absolute coordinates */ w = 1 * sdlgui_fontwidth; h = bdlg[objnum].w; /* Draw background: */ rect.x = x; rect.y = y; rect.w = w; rect.h = h; SDL_FillRect(pSdlGuiScrn, &rect, colors.midbar); /* Draw upper border: */ rect.x = x; rect.y = y; rect.w = w; rect.h = 1; SDL_FillRect(pSdlGuiScrn, &rect, colors.lightbar); /* Draw bottom border: */ rect.x = x; rect.y = y + h - 1; rect.w = w; rect.h = 1; SDL_FillRect(pSdlGuiScrn, &rect, colors.darkbar); } /*-----------------------------------------------------------------------*/ /** * Draw a dialog popup button object. */ static void SDLGui_DrawPopupButton(const SGOBJ *pdlg, int objnum) { int x, y, w; const char *downstr = "\x02"; SDLGui_DrawBox(pdlg, objnum); x = (pdlg[0].x + pdlg[objnum].x) * sdlgui_fontwidth; y = (pdlg[0].y + pdlg[objnum].y) * sdlgui_fontheight; w = pdlg[objnum].w * sdlgui_fontwidth; SDLGui_TextInt(x, y, pdlg[objnum].txt, true); SDLGui_Text(x+w-sdlgui_fontwidth, y, downstr); } /*-----------------------------------------------------------------------*/ /** * Let the user insert text into an edit field object. * NOTE: The dlg[objnum].txt must point to an an array that is big enough * for dlg[objnum].w characters! */ static void SDLGui_EditField(SGOBJ *dlg, int objnum) { size_t cursorPos; /* Position of the cursor in the edit field */ int blinkState = 0; /* Used for cursor blinking */ int bStopEditing = false; /* true if user wants to exit the edit field */ char *txt; /* Shortcut for dlg[objnum].txt */ SDL_Rect rect; SDL_Event event; rect.x = (dlg[0].x + dlg[objnum].x) * sdlgui_fontwidth; rect.y = (dlg[0].y + dlg[objnum].y) * sdlgui_fontheight; rect.w = (dlg[objnum].w + 1) * sdlgui_fontwidth - 1; rect.h = dlg[objnum].h * sdlgui_fontheight; SDL_SetTextInputRect(&rect); SDL_StartTextInput(); txt = dlg[objnum].txt; cursorPos = strlen(txt); do { /* Look for events */ if (SDL_PollEvent(&event) == 0) { /* No event: Wait some time for cursor blinking */ SDL_Delay(250); blinkState ^= 1; } else { /* Handle events */ do { switch (event.type) { case SDL_QUIT: /* User wants to quit */ bQuitProgram = true; bStopEditing = true; break; case SDL_MOUSEBUTTONDOWN: /* Mouse pressed -> stop editing */ bStopEditing = true; break; case SDL_TEXTINPUT: if (strlen(txt) < (size_t)dlg[objnum].w) { memmove(&txt[cursorPos+1], &txt[cursorPos], strlen(&txt[cursorPos])+1); txt[cursorPos] = event.text.text[0]; cursorPos += 1; } break; case SDL_KEYDOWN: /* Key pressed */ switch (event.key.keysym.sym) { case SDLK_RETURN: case SDLK_KP_ENTER: bStopEditing = true; break; case SDLK_LEFT: if (cursorPos > 0) cursorPos -= 1; break; case SDLK_RIGHT: if (cursorPos < strlen(txt)) cursorPos += 1; break; case SDLK_BACKSPACE: if (cursorPos > 0) { memmove(&txt[cursorPos-1], &txt[cursorPos], strlen(&txt[cursorPos])+1); cursorPos -= 1; } break; case SDLK_DELETE: if (cursorPos < strlen(txt)) memmove(&txt[cursorPos], &txt[cursorPos+1], strlen(&txt[cursorPos+1])+1); break; default: break; } break; } } while (SDL_PollEvent(&event)); blinkState = 1; } /* Redraw the text field: */ SDL_FillRect(pSdlGuiScrn, &rect, colors.midgrey); /* Draw background */ /* Draw the cursor: */ if (blinkState && !bStopEditing) { SDL_Rect cursorrect; cursorrect.x = rect.x + cursorPos * sdlgui_fontwidth; cursorrect.y = rect.y; cursorrect.w = sdlgui_fontwidth; cursorrect.h = rect.h; SDL_FillRect(pSdlGuiScrn, &cursorrect, colors.cursor); } SDLGui_Text(rect.x, rect.y, dlg[objnum].txt); /* Draw text */ Screen_UpdateRects(pSdlGuiScrn, 1, &rect); } while (!bStopEditing); SDL_StopTextInput(); } /*-----------------------------------------------------------------------*/ /** * Draw single object based on its type */ static void SDLGui_DrawObj(const SGOBJ *dlg, int i) { switch (dlg[i].type) { case SGBOX: SDLGui_DrawBox(dlg, i); break; case SGTEXT: SDLGui_DrawText(dlg, i); break; case SGEDITFIELD: SDLGui_DrawEditField(dlg, i); break; case SGBUTTON: SDLGui_DrawButton(dlg, i); break; case SGRADIOBUT: SDLGui_DrawRadioButton(dlg, i); break; case SGCHECKBOX: SDLGui_DrawCheckBox(dlg, i); break; case SGPOPUP: SDLGui_DrawPopupButton(dlg, i); break; case SGSCROLLBAR: SDLGui_DrawScrollbar(dlg, i); break; } } /*-----------------------------------------------------------------------*/ /** * Draw a whole dialog. */ void SDLGui_DrawDialog(const SGOBJ *dlg) { int i; for (i = 0; dlg[i].type != SGSTOP; i++) { SDLGui_DrawObj(dlg, i); } Screen_UpdateRect(pSdlGuiScrn, 0,0,0,0); } /*-----------------------------------------------------------------------*/ /** * Search an object at a certain position. * If found, return its index, otherwise SDLGUI_NOTFOUND. */ static int SDLGui_FindObj(const SGOBJ *dlg, int fx, int fy) { int len, i; int ob = SDLGUI_NOTFOUND; int xpos, ypos; len = 0; while (dlg[len].type != SGSTOP) len++; xpos = fx / sdlgui_fontwidth; ypos = fy / sdlgui_fontheight; /* Now search for the object. * Searching is done from end to start, * as later objects cover earlier ones */ for (i = len; i >= 0; i--) { /* clicked on a scrollbar ? */ if (dlg[i].type == SGSCROLLBAR) { if (xpos >= dlg[0].x+dlg[i].x && xpos < dlg[0].x+dlg[i].x+1) { ypos = dlg[i].y * sdlgui_fontheight + dlg[i].h + dlg[0].y * sdlgui_fontheight; if (fy >= ypos && fy < ypos + dlg[i].w) { ob = i; break; } } } /* clicked on another object ? */ else if (xpos >= dlg[0].x+dlg[i].x && ypos >= dlg[0].y+dlg[i].y && xpos < dlg[0].x+dlg[i].x+dlg[i].w && ypos < dlg[0].y+dlg[i].y+dlg[i].h) { ob = i; break; } } return ob; } /*-----------------------------------------------------------------------*/ /** * Search an object with a special flag (e.g. SG_DEFAULT or SG_CANCEL). * If found, return its index, otherwise SDLGUI_NOTFOUND. */ static int SDLGui_SearchFlags(const SGOBJ *dlg, int flag) { int i = 0; while (dlg[i].type != SGSTOP) { if (dlg[i].flags & flag) return i; i++; } return SDLGUI_NOTFOUND; } /*-----------------------------------------------------------------------*/ /** * Search an object with a special state (e.g. SG_FOCUSED). * If found, return its index, otherwise SDLGUI_NOTFOUND. */ static int SDLGui_SearchState(const SGOBJ *dlg, int state) { int i = 0; while (dlg[i].type != SGSTOP) { if (dlg[i].state & state) return i; i++; } return SDLGUI_NOTFOUND; } /*-----------------------------------------------------------------------*/ /** * Print dialog object flags & state for debug purposes. */ static void SDLGui_DebugPrintDialog(const SGOBJ *dlg) { #if DEBUG_INFO int i; printf("obj: flags | state\n"); for (i = 0; dlg[i].type != SGSTOP; i++) printf("%3d: 0x%02x | 0x%02x\n", i, dlg[i].flags, dlg[i].state); #endif } /*-----------------------------------------------------------------------*/ /** * For given dialog object type, returns whether it could have shortcut key */ static bool SDLGui_CanHaveShortcut(int kind) { if (kind == SGBUTTON || kind == SGRADIOBUT || kind == SGCHECKBOX) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Check & set dialog item shortcut values based on their text strings. * Asserts if dialog has same shortcut defined multiple times. */ static void SDLGui_SetShortcuts(SGOBJ *dlg) { unsigned chr, used[256]; const char *str; unsigned int i; memset(used, 0, sizeof(used)); for (i = 0; dlg[i].type != SGSTOP; i++) { if (!SDLGui_CanHaveShortcut(dlg[i].type)) continue; if (!(str = dlg[i].txt)) continue; while(*str) { if (*str++ == UNDERLINE_INDICATOR) { /* TODO: conversion */ chr = toupper(*str); dlg[i].shortcut = chr; if (used[chr]) { fprintf(stderr, "ERROR: Duplicate Hatari SDL GUI shortcut in '%s'!\n", dlg[i].txt); exit(1); } used[chr] = 1; } } } } /*-----------------------------------------------------------------------*/ /** * Unfocus given button */ static void SDLGui_RemoveFocus(SGOBJ *dlg, int old) { if (old == SDLGUI_NOTFOUND) return; dlg[old].state &= ~SG_FOCUSED; dlg[old].state |= SG_WASFOCUSED; SDLGui_DrawObj(dlg, old); dlg[old].state ^= SG_WASFOCUSED; } /*-----------------------------------------------------------------------*/ /** * Search next button to focus, and focus it. * If found, return its index, otherwise given starting index. */ static int SDLGui_FocusNext(SGOBJ *dlg, int i, int inc) { int old = i; if (i == SDLGUI_NOTFOUND) return i; for (;;) { i += inc; /* wrap */ if (dlg[i].type == SGSTOP) { assert(inc > 0); i = 0; } else if (i == 0) { assert(inc < 0); while (dlg[i].type != SGSTOP) i++; i--; } /* change focus for items that can have shortcuts * and for items in Fsel lists */ if (SDLGui_CanHaveShortcut(dlg[i].type) || (dlg[i].flags & SG_EXIT) != 0) { dlg[i].state |= SG_FOCUSED; SDLGui_DrawObj(dlg, i); Screen_UpdateRect(pSdlGuiScrn, 0,0,0,0); return i; } /* wrapped around without even initial one matching */ if (i == old) return 0; } return old; } /*-----------------------------------------------------------------------*/ /** * Handle button selection, either with mouse or keyboard. * If handled, return its index, otherwise SDLGUI_NOTFOUND. */ static int SDLGui_HandleSelection(SGOBJ *dlg, int obj, int oldbutton) { SDL_Rect rct; int i, retbutton = SDLGUI_NOTFOUND; switch (dlg[obj].type) { case SGBUTTON: if (oldbutton==obj) retbutton=obj; break; case SGSCROLLBAR: dlg[obj].state &= ~SG_MOUSEDOWN; if (oldbutton==obj) retbutton=obj; break; case SGEDITFIELD: SDLGui_EditField(dlg, obj); break; case SGRADIOBUT: for (i = obj-1; i > 0 && dlg[i].type == SGRADIOBUT; i--) { dlg[i].state &= ~SG_SELECTED; /* Deselect all radio buttons in this group */ rct.x = (dlg[0].x+dlg[i].x)*sdlgui_fontwidth; rct.y = (dlg[0].y+dlg[i].y)*sdlgui_fontheight; rct.w = sdlgui_fontwidth; rct.h = sdlgui_fontheight; SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ SDLGui_DrawRadioButton(dlg, i); Screen_UpdateRects(pSdlGuiScrn, 1, &rct); } for (i = obj+1; dlg[i].type == SGRADIOBUT; i++) { dlg[i].state &= ~SG_SELECTED; /* Deselect all radio buttons in this group */ rct.x = (dlg[0].x+dlg[i].x)*sdlgui_fontwidth; rct.y = (dlg[0].y+dlg[i].y)*sdlgui_fontheight; rct.w = sdlgui_fontwidth; rct.h = sdlgui_fontheight; SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ SDLGui_DrawRadioButton(dlg, i); Screen_UpdateRects(pSdlGuiScrn, 1, &rct); } dlg[obj].state |= SG_SELECTED; /* Select this radio button */ rct.x = (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth; rct.y = (dlg[0].y+dlg[obj].y)*sdlgui_fontheight; rct.w = sdlgui_fontwidth; rct.h = sdlgui_fontheight; SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ SDLGui_DrawRadioButton(dlg, obj); Screen_UpdateRects(pSdlGuiScrn, 1, &rct); break; case SGCHECKBOX: dlg[obj].state ^= SG_SELECTED; rct.x = (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth; rct.y = (dlg[0].y+dlg[obj].y)*sdlgui_fontheight; rct.w = sdlgui_fontwidth; rct.h = sdlgui_fontheight; SDL_FillRect(pSdlGuiScrn, &rct, colors.midgrey); /* Clear old */ SDLGui_DrawCheckBox(dlg, obj); Screen_UpdateRects(pSdlGuiScrn, 1, &rct); break; case SGPOPUP: dlg[obj].state |= SG_SELECTED; SDLGui_DrawPopupButton(dlg, obj); Screen_UpdateRect(pSdlGuiScrn, (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth-2, (dlg[0].y+dlg[obj].y)*sdlgui_fontheight-2, dlg[obj].w*sdlgui_fontwidth+4, dlg[obj].h*sdlgui_fontheight+4); retbutton=obj; break; } if (retbutton == SDLGUI_NOTFOUND && (dlg[obj].flags & SG_EXIT) != 0) { retbutton = obj; } return retbutton; } /*-----------------------------------------------------------------------*/ /** * If object with given shortcut is found, handle that. * If handled, return its index, otherwise SDLGUI_NOTFOUND. */ static int SDLGui_HandleShortcut(SGOBJ *dlg, int key) { int i = 0; while (dlg[i].type != SGSTOP) { if (dlg[i].shortcut == key) return SDLGui_HandleSelection(dlg, i, i); i++; } return SDLGUI_NOTFOUND; } /** * Scale mouse state coordinates in case we've got a re-sized SDL2 window * * NOTE: while scaling done here fixes SDL2 reported mouse coords to * match Hatari framebuffer coords in scaled SDL2 windows, there's * another issue with (mouse _state_) coords in _fullscreen_. * * SDL2 deducts fullscreen letterboxing borders from those coords, * but not from the values returns by SDL2 window size functions * (and there's no function providing the letterbox border size). * * Atari resolutions are more narrow than today's widescreen monitor * resolutions, so typically fullscreen letterboxing borders are on * the sides => y-coord gets scaled OK, x-coord will be too small. */ void SDLGui_ScaleMouseStateCoordinates(int *x, int *y) { int win_width, win_height; SDL_GetWindowSize(sdlWindow, &win_width, &win_height); *x = *x * pSdlGuiScrn->w / win_width; *y = *y * pSdlGuiScrn->h / win_height; } /** * Scale mouse event coordinates in case we've got a re-sized SDL2 window */ static void SDLGui_ScaleMouseButtonCoordinates(SDL_MouseButtonEvent *bev) { if (bInFullScreen) return; int x = bev->x, y = bev->y; SDLGui_ScaleMouseStateCoordinates(&x, &y); bev->x = x; bev->y = y; } /*-----------------------------------------------------------------------*/ /** * Show and process a dialog. * * Dialogs using a scrollbar, or other objects with SG_REPEAT flag, * must return the previous return value in 'current_object' arg, as * the same dialog is displayed in a loop to handle scrolling. Other * dialogs should give zero as 'current_object' (ie no object * selected at start when displaying the dialog) * * Returns either: * - index of the GUI item that was invoked * - SDLGUI_QUIT if user wants to close Hatari * - SDLGUI_ERROR if unable to show dialog * - for events not handled here, 'isEventOut' callback is checked * for whether caller is interested about given event type: * => event is stored to pEventOut and SDLGUI_UNKNOWNEVENT returned * GUI item indices are positive, other return values are negative */ int SDLGui_DoDialogExt(SGOBJ *dlg, bool (*isEventOut)(SDL_EventType), SDL_Event *pEventOut, int current_object) { int oldbutton = SDLGUI_NOTFOUND; int retbutton = SDLGUI_NOTFOUND; int b, x, y, value, obj; SDL_Keycode key; int focused; SDL_Event sdlEvent; SDL_Surface *pBgSurface; SDL_Rect dlgrect, bgrect; SDL_Joystick *joy = NULL; const Uint8 *keystates; bool ignore_first_keyup; /* either both, or neither of these should be present */ assert((isEventOut && pEventOut) || (!isEventOut && !pEventOut)); if (pSdlGuiScrn->h / sdlgui_fontheight < dlg[0].h) { Log_Printf(LOG_ERROR, "Screen size too small for dialog!\n"); return SDLGUI_ERROR; } dlgrect.x = dlg[0].x * sdlgui_fontwidth; dlgrect.y = dlg[0].y * sdlgui_fontheight; dlgrect.w = dlg[0].w * sdlgui_fontwidth; dlgrect.h = dlg[0].h * sdlgui_fontheight; bgrect.x = bgrect.y = 0; bgrect.w = dlgrect.w; bgrect.h = dlgrect.h; /* Save background */ pBgSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, dlgrect.w, dlgrect.h, pSdlGuiScrn->format->BitsPerPixel, pSdlGuiScrn->format->Rmask, pSdlGuiScrn->format->Gmask, pSdlGuiScrn->format->Bmask, pSdlGuiScrn->format->Amask); if (pBgSurface != NULL) { if (pSdlGuiScrn->format->palette != NULL) { SDL_SetPaletteColors(pBgSurface->format->palette, pSdlGuiScrn->format->palette->colors, 0, pSdlGuiScrn->format->palette->ncolors-1); } SDL_BlitSurface(pSdlGuiScrn, &dlgrect, pBgSurface, &bgrect); } else { Log_Printf(LOG_ERROR, "SDLGUI_DoDialog: CreateRGBSurface failed: %s\n", SDL_GetError()); } SDLGui_DebugPrintDialog(dlg); /* focus default button if nothing else is focused */ focused = SDLGui_SearchState(dlg, SG_FOCUSED); if (focused == SDLGUI_NOTFOUND) { int defbutton = SDLGui_SearchFlags(dlg, SG_DEFAULT); if (defbutton != SDLGUI_NOTFOUND) { dlg[defbutton].state |= SG_FOCUSED; focused = defbutton; } } Dprintf(("focused: %d\n", focused)); SDLGui_SetShortcuts(dlg); /* (Re-)draw the dialog */ SDLGui_DrawDialog(dlg); /* If one of the keys that could exit the dialog is already held * before we start, then ignore the first keyup event since the * key press does not belong to the dialog, but rather to whatever * happened before the dialog. * * Cannot be used when asked to return already on key down * (e.g. with file selector). */ keystates = SDL_GetKeyboardState(NULL); ignore_first_keyup = !(isEventOut && isEventOut(SDL_KEYDOWN)) && ( keystates[SDL_GetScancodeFromKey(SDLK_RETURN)] || keystates[SDL_GetScancodeFromKey(SDLK_KP_ENTER)] || keystates[SDL_GetScancodeFromKey(SDLK_SPACE)] || keystates[SDL_GetScancodeFromKey(SDLK_ESCAPE)] ); /* Is the left mouse button still pressed? Yes -> Handle TOUCHEXIT objects here */ SDL_PumpEvents(); b = SDL_GetMouseState(&x, &y); /* Report repeat objects until mouse button is released, * regardless of mouse position. Used for scrollbar * object interactions */ if (current_object >= 0 && (dlg[current_object].flags & SG_REPEAT)) { obj = current_object; oldbutton = obj; if (b & SDL_BUTTON(1)) { retbutton = obj; dlg[obj].state |= SG_MOUSEDOWN; } else { dlg[obj].state &= ~SG_MOUSEDOWN; } } else { SDLGui_ScaleMouseStateCoordinates(&x, &y); obj = SDLGui_FindObj(dlg, x, y); if (obj != SDLGUI_NOTFOUND && (dlg[obj].flags&SG_TOUCHEXIT) ) { oldbutton = obj; if (b & SDL_BUTTON(1)) { retbutton = obj; dlg[obj].state |= SG_SELECTED; } } } if (SDL_NumJoysticks() > 0) joy = SDL_JoystickOpen(0); Dprintf(("ENTER - obj: %d, old: %d, ret: %d\n", obj, oldbutton, retbutton)); /* The main loop */ while (retbutton == SDLGUI_NOTFOUND && !bQuitProgram) { if (SDL_WaitEvent(&sdlEvent) == 1) /* Wait for events */ { switch (sdlEvent.type) { case SDL_QUIT: retbutton = SDLGUI_QUIT; break; case SDL_MOUSEBUTTONDOWN: if (sdlEvent.button.button != SDL_BUTTON_LEFT) { /* Not left mouse button -> unsupported event */ retbutton = SDLGUI_UNKNOWNEVENT; break; } /* It was the left button: Find the object under the mouse cursor */ SDLGui_ScaleMouseButtonCoordinates(&sdlEvent.button); obj = SDLGui_FindObj(dlg, sdlEvent.button.x, sdlEvent.button.y); if (obj != SDLGUI_NOTFOUND) { if (dlg[obj].type==SGBUTTON) { dlg[obj].state |= SG_SELECTED; SDLGui_DrawButton(dlg, obj); Screen_UpdateRect(pSdlGuiScrn, (dlg[0].x+dlg[obj].x)*sdlgui_fontwidth-2, (dlg[0].y+dlg[obj].y)*sdlgui_fontheight-2, dlg[obj].w*sdlgui_fontwidth+4, dlg[obj].h*sdlgui_fontheight+4); oldbutton=obj; } if (dlg[obj].type==SGSCROLLBAR) { dlg[obj].state |= SG_MOUSEDOWN; oldbutton=obj; } if ( dlg[obj].flags&SG_TOUCHEXIT ) { dlg[obj].state |= SG_SELECTED; retbutton = obj; } } break; case SDL_MOUSEBUTTONUP: if (sdlEvent.button.button != SDL_BUTTON_LEFT) { /* Not left mouse button -> unsupported event */ retbutton = SDLGUI_UNKNOWNEVENT; break; } /* It was the left button: Find the object under the mouse cursor */ SDLGui_ScaleMouseButtonCoordinates(&sdlEvent.button); obj = SDLGui_FindObj(dlg, sdlEvent.button.x, sdlEvent.button.y); if (obj != SDLGUI_NOTFOUND) { retbutton = SDLGui_HandleSelection(dlg, obj, oldbutton); } if (oldbutton != SDLGUI_NOTFOUND && dlg[oldbutton].type == SGBUTTON) { dlg[oldbutton].state &= ~SG_SELECTED; SDLGui_DrawButton(dlg, oldbutton); Screen_UpdateRect(pSdlGuiScrn, (dlg[0].x+dlg[oldbutton].x)*sdlgui_fontwidth-2, (dlg[0].y+dlg[oldbutton].y)*sdlgui_fontheight-2, dlg[oldbutton].w*sdlgui_fontwidth+4, dlg[oldbutton].h*sdlgui_fontheight+4); oldbutton = SDLGUI_NOTFOUND; } break; case SDL_JOYAXISMOTION: value = sdlEvent.jaxis.value; if (value < -3200 || value > 3200) { if(sdlEvent.jaxis.axis == 0) { /* Left-right movement */ if (value < 0) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_LEFT); else retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_RIGHT); } else if(sdlEvent.jaxis.axis == 1) { /* Up-Down movement */ if (value < 0) { SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, focused, -1); } else { SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, focused, +1); } } } break; case SDL_JOYHATMOTION: if (sdlEvent.jhat.value & SDL_HAT_LEFT) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_LEFT); else if (sdlEvent.jhat.value & SDL_HAT_RIGHT) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_RIGHT); if (sdlEvent.jhat.value & SDL_HAT_UP) { SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, focused, -1); } else if (sdlEvent.jhat.value & SDL_HAT_DOWN) { SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, focused, +1); } break; case SDL_JOYBUTTONDOWN: retbutton = SDLGui_HandleSelection(dlg, focused, focused); break; case SDL_JOYBALLMOTION: case SDL_MOUSEMOTION: case SDL_KEYMAPCHANGED: break; case SDL_KEYDOWN: /* Key pressed */ /* keys that need to support repeat, * need to be checked on press */ key = sdlEvent.key.keysym.sym; /* keyboard shortcuts are with modifiers */ if (sdlEvent.key.keysym.mod & KMOD_LALT || sdlEvent.key.keysym.mod & KMOD_RALT) { if (key == SDLK_LEFT) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_LEFT); else if (key == SDLK_RIGHT) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_RIGHT); else if (key == SDLK_UP) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_UP); else if (key == SDLK_DOWN) retbutton = SDLGui_HandleShortcut(dlg, SG_SHORTCUT_DOWN); else { if (key >= 33 && key <= 126) retbutton = SDLGui_HandleShortcut(dlg, toupper(key)); } if (!retbutton) retbutton = SDLGUI_UNKNOWNEVENT; break; } switch (key) { case SDLK_UP: case SDLK_LEFT: SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, focused, -1); break; case SDLK_TAB: case SDLK_DOWN: case SDLK_RIGHT: SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, focused, +1); break; case SDLK_HOME: SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, 1, +1); break; case SDLK_END: SDLGui_RemoveFocus(dlg, focused); focused = SDLGui_FocusNext(dlg, 1, -1); break; default: retbutton = SDLGUI_UNKNOWNEVENT; break; } break; case SDL_KEYUP: /* Key released */ /* keys potentially exiting dialog need * to be handed only on release, to avoid * leaking release events to emulation */ switch (sdlEvent.key.keysym.sym) { case SDLK_SPACE: case SDLK_RETURN: case SDLK_KP_ENTER: if (ignore_first_keyup) { ignore_first_keyup = false; break; } retbutton = SDLGui_HandleSelection(dlg, focused, focused); break; case SDLK_ESCAPE: if (ignore_first_keyup) { ignore_first_keyup = false; break; } retbutton = SDLGui_SearchFlags(dlg, SG_CANCEL); break; default: retbutton = SDLGUI_UNKNOWNEVENT; break; } break; case SDL_WINDOWEVENT: if (sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || sdlEvent.window.event == SDL_WINDOWEVENT_RESTORED || sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED) { Screen_UpdateRect(pSdlGuiScrn, 0, 0, 0, 0); } break; default: retbutton = SDLGUI_UNKNOWNEVENT; break; } /* continue if unknown events were not not requested * specifically for this event type */ if (retbutton == SDLGUI_UNKNOWNEVENT && !(isEventOut && isEventOut(sdlEvent.type))) retbutton = SDLGUI_NOTFOUND; } } /* Copy event data of unsupported events if caller wants to have it */ if (retbutton == SDLGUI_UNKNOWNEVENT) { memcpy(pEventOut, &sdlEvent, sizeof(SDL_Event)); } /* Restore background */ if (pBgSurface) { SDL_BlitSurface(pBgSurface, &bgrect, pSdlGuiScrn, &dlgrect); SDL_FreeSurface(pBgSurface); } if (retbutton == SDLGUI_QUIT) bQuitProgram = true; if (joy) SDL_JoystickClose(joy); Dprintf(("EXIT - ret: %d\n", retbutton)); return retbutton; } /*-----------------------------------------------------------------------*/ /** * Show and process a dialog. Returns either: * - index of the GUI item that was invoked * - SDLGUI_QUIT if user wants to close Hatari * - SDLGUI_ERROR if unable to show dialog * GUI item indices are positive, other return values are negative */ int SDLGui_DoDialog(SGOBJ *dlg) { return SDLGui_DoDialogExt(dlg, NULL, NULL, 0); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-win/000077500000000000000000000000001504763705000227075ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-win/hatari-winicon.ico000066400000000000000000000013761504763705000263260ustar00rootroot00000000000000 ( @gn*yf=2J,[jrֺ%BUDBBTURADETET"UT"DtWR Ut"GRDGT%ET$UTUT$DEUDUUEUUUbUUEEE%DD373fRTUQQ%UVR$ET%UbR$R$!"T$u"PTUUPE %"UUUV"RUd$UU&3vc3e5BT$QRQ$"R%$EUQQAD%$%T"UTUuWrvu"WT$WUDUT$UDT%%T%REBEg"wyxhatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-win/hatari-winicon.rc000066400000000000000000000000731504763705000261510ustar00rootroot00000000000000 LANGUAGE 0, 0 100 ICON "hatari-winicon.ico" hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-win/opencon.c000066400000000000000000000016651504763705000245240ustar00rootroot00000000000000/* Hatari - opencon.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. The SDL library redirects the stdio normally to the files stdout.txt and stderr.txt. But with this redirection, the debugger of Hatari does not work anymore. So we simply open a new console when the debug mode has been enabled, and we redirect the stdio again - this time to our new console. */ #include #include #include #include "opencon.h" #include "../includes/configuration.h" static void Win_OpenInternal(void) { static bool opened; if (opened) return; opened = true; AllocConsole(); freopen("CON", "w", stdout); freopen("CON", "r", stdin); freopen("CON", "w", stderr); } void Win_OpenCon(void) { if (ConfigureParams.Log.bConsoleWindow) Win_OpenInternal(); } void Win_ForceCon(void) { Win_OpenInternal(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/gui-win/opencon.h000066400000000000000000000001001504763705000245100ustar00rootroot00000000000000 extern void Win_OpenCon(void); extern void Win_ForceCon(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/hatari-icon.bmp000066400000000000000000000011661504763705000242320ustar00rootroot00000000000000BMvv(  gn*yf=2J,[jrֺ%BUDBBTURADETET"UT"DtWR Ut"GRDGT%ET$UTUT$DEUDUUEUUUbUUEEE%DD373fRTUQQ%UVR$ET%UbR$R$!"T$u"PTUUPE %"UUUV"RUd$UU&3vc3e5BT$QRQ$"R%$EUQQAD%$%T"UTUuWrvu"WT$WUDUT$UDT%%T%REBEg"wyxhatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/hdc.c000066400000000000000000000722611504763705000222420ustar00rootroot00000000000000/* Hatari - hdc.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Low-level hard drive emulation */ const char HDC_fileid[] = "Hatari hdc.c"; #include #include #include "main.h" #include "configuration.h" #include "debugui.h" #include "file.h" #include "fdc.h" #include "hdc.h" #include "ioMem.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "mfp.h" #include "ncr5380.h" #include "stMemory.h" #include "tos.h" #include "statusbar.h" /* ACSI emulation: ACSI commands are six byte-packets sent to the hard drive controller (which is on the HD unit, not in the ST) While the hard drive is busy, DRQ is high, polling the DRQ during operation interrupts the current operation. The DRQ status can be polled non-destructively in GPIP. (For simplicity, the operation is finished immediately, this is a potential bug, but I doubt it is significant, we just appear to have a very fast hard drive.) The ACSI command set is a subset of the SCSI standard. (for details, see the X3T9.2 SCSI draft documents from 1985, for an example of writing ACSI commands, see the TOS DMA boot code) */ // #define DISALLOW_HDC_WRITE // #define HDC_VERBOSE /* display operations */ #define HDC_ReadInt16(a, i) (((unsigned) a[i] << 8) | a[i + 1]) #define HDC_ReadInt24(a, i) (((unsigned) a[i] << 16) | ((unsigned) a[i + 1] << 8) | a[i + 2]) #define HDC_ReadInt32(a, i) (((unsigned) a[i] << 24) | ((unsigned) a[i + 1] << 16) | ((unsigned) a[i + 2] << 8) | a[i + 3]) /* HDC globals */ static SCSI_CTRLR AcsiBus; int nAcsiPartitions; bool bAcsiEmuOn; /* Our dummy INQUIRY response data */ static unsigned char inquiry_bytes[] = { 0, /* device type 0 = direct access device */ 0, /* device type qualifier (nonremovable) */ 1, /* ACSI/SCSI version */ 0, /* reserved */ 31, /* length of the following data */ 0, 0, 0, /* Vendor specific data */ 'H','a','t','a','r','i',' ',' ', /* Vendor ID */ 'E','m','u','l','a','t','e','d', /* Product ID 1 */ 'H','a','r','d','d','i','s','k', /* Product ID 2 */ '0','1','8','0', /* Revision */ }; /*---------------------------------------------------------------------*/ /** * Return the LUN (logical unit number) specified in the current * ACSI/SCSI command block. */ static unsigned char HDC_GetLUN(SCSI_CTRLR *ctr) { return (ctr->command[1] & 0xE0) >> 5; } /** * Return the start sector (logical block address) specified in the * current ACSI/SCSI command block. */ static unsigned long HDC_GetLBA(SCSI_CTRLR *ctr) { /* offset = logical block address * physical sector size */ if (ctr->opcode < 0x20) /* Class 0? */ return HDC_ReadInt24(ctr->command, 1) & 0x1FFFFF; else return HDC_ReadInt32(ctr->command, 2); /* Class 1 */ } /** * Return number of bytes for a command block. */ static int HDC_GetCommandByteCount(SCSI_CTRLR *ctr) { if (ctr->opcode == 0x88 || ctr->opcode == 0x8a || ctr->opcode == 0x8f || ctr->opcode == 0x91 || ctr->opcode == 0x9e || ctr->opcode == 0x9f) { return 16; } else if (ctr->opcode == HD_REPORT_LUNS) { return 12; } else if (ctr->opcode == 0x05 || (ctr->opcode >= 0x20 && ctr->opcode <= 0x7d)) { return 10; } else { return 6; } } /** * Return the count specified in the current ACSI command block. */ static int HDC_GetCount(SCSI_CTRLR *ctr) { if (ctr->opcode < 0x20) return ctr->command[4]; /* Class 0 */ else return HDC_ReadInt16(ctr->command, 7); /* Class 1 */ } /** * Get pointer to response buffer, set up size indicator - and allocate * a new buffer if it is not big enough */ static uint8_t *HDC_PrepRespBuf(SCSI_CTRLR *ctr, int size) { ctr->data_len = size; ctr->offset = 0; if (size > ctr->buffer_size) { ctr->buffer_size = size; ctr->buffer = realloc(ctr->buffer, size); } return ctr->buffer; } /** * Get info string for SCSI/ACSI command packets. */ static inline char *HDC_CmdInfoStr(SCSI_CTRLR *ctr) { char cdb[80] = { 0 }; for (int i = 0; i < HDC_GetCommandByteCount(ctr); i++) { char tmp[5]; snprintf(tmp, sizeof(tmp), "%s%02x", i ? ":" : "", ctr->command[i]); strcat(cdb, tmp); } static char str[160]; snprintf(str, sizeof(str), "%s, t=%i, lun=%i, cdb=%s", ctr->typestr, ctr->target, HDC_GetLUN(ctr), cdb); return str; } /** * Seek - move to a sector */ static void HDC_Cmd_Seek(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; dev->nLastBlockAddr = HDC_GetLBA(ctr); LOG_TRACE(TRACE_SCSI_CMD, "HDC: SEEK (%s), LBA=%i", HDC_CmdInfoStr(ctr), dev->nLastBlockAddr); if (dev->nLastBlockAddr < dev->hdSize && fseeko(dev->image_file, (off_t)dev->nLastBlockAddr * dev->blockSize, SEEK_SET) == 0) { LOG_TRACE(TRACE_SCSI_CMD, " -> OK\n"); ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; } else { LOG_TRACE(TRACE_SCSI_CMD, " -> ERROR\n"); ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_INVADDR; } dev->bSetLastBlockAddr = true; } /** * Inquiry - return some disk information */ static void HDC_Cmd_Inquiry(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; uint8_t *buf; int count; count = HDC_GetCount(ctr); LOG_TRACE(TRACE_SCSI_CMD, "HDC: INQUIRY (%s)\n", HDC_CmdInfoStr(ctr)); buf = HDC_PrepRespBuf(ctr, count); if (count > (int)sizeof(inquiry_bytes)) { memset(&buf[sizeof(inquiry_bytes)], 0, count - sizeof(inquiry_bytes)); count = sizeof(inquiry_bytes); } memcpy(buf, inquiry_bytes, count); /* For unsupported LUNs set the Peripheral Qualifier and the * Peripheral Device Type according to the SCSI standard */ buf[0] = HDC_GetLUN(ctr) == 0 ? 0 : 0x7F; buf[2] = dev->scsi_version; buf[4] = count - 5; ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; dev->bSetLastBlockAddr = false; } /** * Request sense - return some disk information */ static void HDC_Cmd_RequestSense(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; int nRetLen; uint8_t *retbuf; nRetLen = HDC_GetCount(ctr); LOG_TRACE(TRACE_SCSI_CMD, "HDC: REQUEST SENSE (%s).\n", HDC_CmdInfoStr(ctr)); if ((nRetLen < 4 && nRetLen != 0) || nRetLen > 252) { Log_Printf(LOG_WARN, "HDC: *** Strange REQUEST SENSE ***!\n"); } /* Limit to sane length */ if (nRetLen == 0 && dev->scsi_version == 1) { nRetLen = 4; } else if (nRetLen > 22) { nRetLen = 22; } retbuf = HDC_PrepRespBuf(ctr, nRetLen); memset(retbuf, 0, nRetLen); if (nRetLen <= 4) { retbuf[0] = dev->nLastError; if (dev->bSetLastBlockAddr) { retbuf[0] |= 0x80; retbuf[1] = dev->nLastBlockAddr >> 16; retbuf[2] = dev->nLastBlockAddr >> 8; retbuf[3] = dev->nLastBlockAddr; } } else { retbuf[0] = 0x70; if (dev->bSetLastBlockAddr) { retbuf[0] |= 0x80; retbuf[4] = dev->nLastBlockAddr >> 16; retbuf[5] = dev->nLastBlockAddr >> 8; retbuf[6] = dev->nLastBlockAddr; } switch (dev->nLastError) { case HD_REQSENS_OK: retbuf[2] = 0; break; case HD_REQSENS_OPCODE: retbuf[2] = 5; break; case HD_REQSENS_INVADDR: retbuf[2] = 5; break; case HD_REQSENS_INVARG: retbuf[2] = 5; break; case HD_REQSENS_INVLUN: retbuf[2] = 5; break; default: retbuf[2] = 4; break; } retbuf[7] = 14; retbuf[12] = dev->nLastError; retbuf[19] = dev->nLastBlockAddr >> 16; retbuf[20] = dev->nLastBlockAddr >> 8; retbuf[21] = dev->nLastBlockAddr; } /* fprintf(stderr,"*** Requested sense packet:\n"); int i; for (i = 0; istatus = HD_STATUS_OK; } /** * Mode sense - Vendor specific page 00h. * (Just enough to make the HDX tool from AHDI 5.0 happy) */ static void HDC_CmdModeSense0x00(SCSI_DEV *dev, SCSI_CTRLR *ctr, uint8_t *buf) { buf[0] = 0; buf[1] = 14; buf[2] = 0; buf[3] = 8; buf[4] = 0; buf[5] = dev->hdSize >> 16; // Number of blocks, high buf[6] = dev->hdSize >> 8; // Number of blocks, med buf[7] = dev->hdSize; // Number of blocks, low buf[8] = 0; buf[9] = 0; // Block size in bytes, high buf[10] = 2; // Block size in bytes, med buf[11] = 0; // Block size in bytes, low buf[12] = 0; buf[13] = 0; buf[14] = 0; buf[15] = 0; } /** * Mode sense - Rigid disk geometry page (requested by ASV). */ static void HDC_CmdModeSense0x04(SCSI_DEV *dev, SCSI_CTRLR *ctr, uint8_t *buf) { buf[0] = 4; buf[1] = 22; buf[2] = dev->hdSize >> 23; // Number of cylinders, high buf[3] = dev->hdSize >> 15; // Number of cylinders, med buf[4] = dev->hdSize >> 7; // Number of cylinders, low buf[5] = 128; // Number of heads buf[6] = 0; buf[7] = 0; buf[8] = 0; buf[9] = 0; buf[10] = 0; buf[11] = 0; buf[12] = 0; buf[13] = 0; buf[14] = 0; buf[15] = 0; buf[16] = 0; buf[17] = 0; buf[18] = 0; buf[19] = 0; buf[20] = 0x1c; // Medium rotation rate 7200 buf[21] = 0x20; buf[22] = 0; buf[23] = 0; } /** * Mode sense - Get parameters from disk. */ static void HDC_Cmd_ModeSense(SCSI_CTRLR *ctr) { uint8_t *buf; SCSI_DEV *dev = &ctr->devs[ctr->target]; LOG_TRACE(TRACE_SCSI_CMD, "HDC: MODE SENSE (%s).\n", HDC_CmdInfoStr(ctr)); dev->bSetLastBlockAddr = false; // Subpages are not supported if(ctr->command[3]) { ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_INVARG; return; } switch(ctr->command[2]) { case 0x00: buf = HDC_PrepRespBuf(ctr, 16); HDC_CmdModeSense0x00(dev, ctr, buf); break; case 0x04: buf = HDC_PrepRespBuf(ctr, 28); HDC_CmdModeSense0x04(dev, ctr, buf + 4); buf[0] = 24; buf[1] = 0; buf[2] = 0; buf[3] = 0; break; case 0x3f: buf = HDC_PrepRespBuf(ctr, 44); HDC_CmdModeSense0x04(dev, ctr, buf + 4); HDC_CmdModeSense0x00(dev, ctr, buf + 28); buf[0] = 43; buf[1] = 0; buf[2] = 0; buf[3] = 0; break; default: Log_Printf(LOG_TODO, "HDC: Unsupported MODE SENSE mode page\n"); ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_INVARG; return; } ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; } /** * Format drive. */ static void HDC_Cmd_FormatDrive(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; LOG_TRACE(TRACE_SCSI_CMD, "HDC: FORMAT DRIVE (%s).\n", HDC_CmdInfoStr(ctr)); /* Should erase the whole image file here... */ ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; dev->bSetLastBlockAddr = false; } /** * Report LUNs. */ static void HDC_Cmd_ReportLuns(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; uint8_t *buf; LOG_TRACE(TRACE_SCSI_CMD, "HDC: REPORT LUNS (%s).\n", HDC_CmdInfoStr(ctr)); buf = HDC_PrepRespBuf(ctr, 16); /* LUN list length, 8 bytes per LUN */ buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 8; memset(&buf[4], 0, 12); ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; dev->bSetLastBlockAddr = false; } /** * Read capacity of our disk. */ static void HDC_Cmd_ReadCapacity(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; int nSectors = dev->hdSize - 1; uint8_t *buf; LOG_TRACE(TRACE_SCSI_CMD, "HDC: READ CAPACITY (%s)\n", HDC_CmdInfoStr(ctr)); buf = HDC_PrepRespBuf(ctr, 8); buf[0] = (nSectors >> 24) & 0xFF; buf[1] = (nSectors >> 16) & 0xFF; buf[2] = (nSectors >> 8) & 0xFF; buf[3] = nSectors & 0xFF; buf[4] = (dev->blockSize >> 24) & 0xFF; buf[5] = (dev->blockSize >> 16) & 0xFF; buf[6] = (dev->blockSize >> 8) & 0xFF; buf[7] = dev->blockSize & 0xFF; ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; dev->bSetLastBlockAddr = false; } /** * Write a sector off our disk - (seek implied) */ static void HDC_Cmd_WriteSector(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; dev->nLastBlockAddr = HDC_GetLBA(ctr); LOG_TRACE(TRACE_SCSI_CMD, "HDC: WRITE SECTOR (%s) with LBA 0x%x", HDC_CmdInfoStr(ctr), dev->nLastBlockAddr); /* seek to the position */ if (dev->nLastBlockAddr >= dev->hdSize || fseeko(dev->image_file, (off_t)dev->nLastBlockAddr * dev->blockSize, SEEK_SET) != 0) { ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_INVADDR; } else { ctr->data_len = HDC_GetCount(ctr) * dev->blockSize; if (ctr->data_len) { HDC_PrepRespBuf(ctr, ctr->data_len); ctr->dmawrite_to_fh = dev->image_file; ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; } else { ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_WRITEERR; } } LOG_TRACE(TRACE_SCSI_CMD, " -> %s (%d)\n", ctr->status == HD_STATUS_OK ? "OK" : "ERROR", dev->nLastError); dev->bSetLastBlockAddr = true; } /** * Read a sector off our disk - (implied seek) */ static void HDC_Cmd_ReadSector(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; uint8_t *buf; int n; dev->nLastBlockAddr = HDC_GetLBA(ctr); LOG_TRACE(TRACE_SCSI_CMD, "HDC: READ SECTOR (%s) with LBA 0x%x", HDC_CmdInfoStr(ctr), dev->nLastBlockAddr); /* seek to the position */ if (dev->nLastBlockAddr >= dev->hdSize || fseeko(dev->image_file, (off_t)dev->nLastBlockAddr * dev->blockSize, SEEK_SET) != 0) { ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_INVADDR; } else { buf = HDC_PrepRespBuf(ctr, dev->blockSize * HDC_GetCount(ctr)); n = fread(buf, dev->blockSize, HDC_GetCount(ctr), dev->image_file); if (n == HDC_GetCount(ctr)) { ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; } else { ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_NOSECTOR; } } LOG_TRACE(TRACE_SCSI_CMD, " -> %s (%d)\n", ctr->status == HD_STATUS_OK ? "OK" : "ERROR", dev->nLastError); dev->bSetLastBlockAddr = true; } /** * Test unit ready */ static void HDC_Cmd_TestUnitReady(SCSI_CTRLR *ctr) { LOG_TRACE(TRACE_SCSI_CMD, "HDC: TEST UNIT READY (%s).\n", HDC_CmdInfoStr(ctr)); ctr->status = HD_STATUS_OK; } /** * Emulation routine for HDC command packets. */ static void HDC_EmulateCommandPacket(SCSI_CTRLR *ctr) { SCSI_DEV *dev = &ctr->devs[ctr->target]; ctr->data_len = 0; switch (ctr->opcode) { case HD_TEST_UNIT_RDY: HDC_Cmd_TestUnitReady(ctr); break; case HD_READ_CAPACITY1: HDC_Cmd_ReadCapacity(ctr); break; case HD_READ_SECTOR: case HD_READ_SECTOR1: HDC_Cmd_ReadSector(ctr); break; case HD_WRITE_SECTOR: case HD_WRITE_SECTOR1: HDC_Cmd_WriteSector(ctr); break; case HD_INQUIRY: HDC_Cmd_Inquiry(ctr); break; case HD_SEEK: HDC_Cmd_Seek(ctr); break; case HD_SHIP: LOG_TRACE(TRACE_SCSI_CMD, "HDC: SHIP (%s).\n", HDC_CmdInfoStr(ctr)); ctr->status = 0xFF; break; case HD_REQ_SENSE: HDC_Cmd_RequestSense(ctr); break; case HD_MODESELECT: LOG_TRACE(TRACE_SCSI_CMD, "HDC: MODE SELECT (%s) TODO!\n", HDC_CmdInfoStr(ctr)); ctr->status = HD_STATUS_OK; dev->nLastError = HD_REQSENS_OK; dev->bSetLastBlockAddr = false; break; case HD_MODESENSE: HDC_Cmd_ModeSense(ctr); break; case HD_FORMAT_DRIVE: HDC_Cmd_FormatDrive(ctr); break; case HD_REPORT_LUNS: HDC_Cmd_ReportLuns(ctr); break; /* as of yet unsupported commands */ case HD_VERIFY_TRACK: case HD_FORMAT_TRACK: case HD_CORRECTION: default: LOG_TRACE(TRACE_SCSI_CMD, "HDC: Unsupported command (%s)!\n", HDC_CmdInfoStr(ctr)); ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_OPCODE; dev->bSetLastBlockAddr = false; break; } /* Update the led each time a command is processed */ Statusbar_EnableHDLed( LED_STATE_ON ); } /*---------------------------------------------------------------------*/ /** * Return given image file (primary) partition count. * With tracing enabled, print also partition table. * * Supports both DOS and Atari master boot record partition * tables (with 4 entries). * * Atari partition type names used for checking * whether drive is / needs to be byte-swapped: * GEM GEMDOS partition < 16MB * BGM GEMDOS partition > 16MB * RAW No file system * F32 TOS compatible FAT32 partition * LNX Linux Ext2 partition, not supported by TOS * MIX Minix partition, not supported by TOS * SWP Swap partition, not supported by TOS * UNX ASV (Atari System V) partition, not supported by TOS * XGM Extended partition * * Other partition types (listed in XHDI spec): * MAC MAC HFS partition, not supported by TOS * QWA Sinclair QL QDOS partition, not supported by TOS * (These haven't been found in the wild.) * * TODO: * - Support also Atari ICD (12 entries, at offset 0x156) * and extended partition schemes * * Linux kernel has code for both: * https://elixir.bootlin.com/linux/v4.0/source/block/partitions/atari.c * * Extended partition tables are described in AHDI release notes: * https://www.dev-docs.org/docs/htm/search.php?find=AHDI */ int HDC_PartitionCount(FILE *fp, const uint64_t tracelevel, int *pIsByteSwapped) { unsigned char *pinfo, bootsector[512]; uint32_t start, sectors, total = 0; int i, parts = 0; off_t offset; if (!fp) return 0; offset = ftello(fp); if (fseeko(fp, 0, SEEK_SET) != 0 || fread(bootsector, sizeof(bootsector), 1, fp) != 1) { perror("HDC_PartitionCount"); return 0; } /* Try to guess whether we've got to swap the image? */ if (pIsByteSwapped) { *pIsByteSwapped = (bootsector[0x1fe] == 0xaa && bootsector[0x1ff] == 0x55) || (bootsector[0x1c6] == 'G' && bootsector[0x1c8] == 'M' && bootsector[0x1c9] == 'E') || (bootsector[0x1c6] == 'B' && bootsector[0x1c8] == 'M' && bootsector[0x1c9] == 'G') || (bootsector[0x1c6] == 'X' && bootsector[0x1c8] == 'M' && bootsector[0x1c9] == 'G') || (bootsector[0x1c6] == 'L' && bootsector[0x1c8] == 'X' && bootsector[0x1c9] == 'N') || (bootsector[0x1c6] == 'R' && bootsector[0x1c8] == 'W' && bootsector[0x1c9] == 'A') || (bootsector[0x1c6] == 'F' && bootsector[0x1c8] == '2' && bootsector[0x1c9] == '3') || (bootsector[0x1c6] == 'U' && bootsector[0x1c8] == 'X' && bootsector[0x1c9] == 'N') || (bootsector[0x1c6] == 'M' && bootsector[0x1c8] == 'X' && bootsector[0x1c9] == 'I') || (bootsector[0x1c6] == 'S' && bootsector[0x1c8] == 'P' && bootsector[0x1c9] == 'W'); if (*pIsByteSwapped) { for (i = 0; i < (int)sizeof(bootsector); i += 2) { uint8_t b = bootsector[i]; bootsector[i] = bootsector[i + 1]; bootsector[i + 1] = b; } } } if (bootsector[0x1FE] == 0x55 && bootsector[0x1FF] == 0xAA) { int ptype, boot; LOG_TRACE_DIRECT_INIT(); LOG_TRACE_DIRECT_LEVEL(tracelevel, "DOS MBR:\n"); /* first partition table entry */ pinfo = bootsector + 0x1BE; for (i = 0; i < 4; i++, pinfo += 16) { boot = pinfo[0]; ptype = pinfo[4]; start = SDL_SwapLE32(*(uint32_t*)(pinfo+8)); sectors = SDL_SwapLE32(*(uint32_t*)(pinfo+12)); total += sectors; LOG_TRACE_DIRECT_LEVEL(tracelevel, "- Partition %d: type=0x%02x, start=0x%08x, size=%.1f MB %s%s\n", i, ptype, start, sectors/2048.0, boot ? "(boot)" : "", sectors ? "" : "(invalid)"); if (ptype) parts++; } LOG_TRACE_DIRECT_LEVEL(tracelevel, "- Total size: %.1f MB in %d partitions\n", total/2048.0, parts); LOG_TRACE_DIRECT_FLUSH(); } else { /* Partition table contains hd size + 4 partition entries * (composed of flag byte, 3 char ID, start offset * and size), this is followed by bad sector list + * count and the root sector checksum. Before this * there's the boot code. */ char c, pid[4]; int j, flags; bool extended; LOG_TRACE_DIRECT_INIT(); LOG_TRACE_DIRECT_LEVEL(tracelevel, "ATARI MBR:\n"); pinfo = bootsector + 0x1C6; for (i = 0; i < 4; i++, pinfo += 12) { flags = pinfo[0]; for (j = 0; j < 3; j++) { c = pinfo[j+1]; if (c < 32 || c >= 127) c = '.'; pid[j] = c; } pid[3] = '\0'; extended = strcmp("XGM", pid) == 0; start = HDC_ReadInt32(pinfo, 4); sectors = HDC_ReadInt32(pinfo, 8); LOG_TRACE_DIRECT_LEVEL(tracelevel, "- Partition %d: ID=%s, start=0x%08x, size=%.1f MB, flags=0x%x %s%s\n", i, pid, start, sectors/2048.0, flags, (flags & 0x80) ? "(boot)": "", extended ? "(extended)" : ""); if (flags & 0x1) parts++; } total = HDC_ReadInt32(bootsector, 0x1C2); LOG_TRACE_DIRECT_LEVEL(tracelevel, "- Total size: %.1f MB in %d partitions\n", total/2048.0, parts); LOG_TRACE_DIRECT_FLUSH(); } if (fseeko(fp, offset, SEEK_SET) != 0) perror("HDC_PartitionCount"); return parts; } /** * Check file size for sane values (non-zero, multiple of 512), * and return the size */ off_t HDC_CheckAndGetSize(const char *hdtype, const char *filename, unsigned long blockSize) { off_t filesize; char shortname[48]; File_ShrinkName(shortname, filename, sizeof(shortname) - 1); filesize = File_Length(filename); if (filesize < 0) { Log_AlertDlg(LOG_ERROR, "Unable to get size of %s HD image file\n'%s'!", hdtype, shortname); if (sizeof(off_t) < 8) { Log_Printf(LOG_ERROR, "Note: This version of Hatari has been built" " _without_ support for large files,\n" " so you can not use HD images > 2 GB.\n"); } return -EFBIG; } if (filesize == 0) { Log_AlertDlg(LOG_ERROR, "Can not use %s HD image file\n'%s'\n" "since the file is empty.", hdtype, shortname); return -EINVAL; } if ((filesize & (blockSize - 1)) != 0) { Log_AlertDlg(LOG_ERROR, "Can not use the %s HD image file\n" "'%s'\nsince its size is not a multiple" " of %ld.", hdtype, shortname, blockSize); return -EINVAL; } return filesize; } /** * Open a disk image file */ int HDC_InitDevice(const char *hdtype, SCSI_DEV *dev, CNF_SCSIDEV *conf) { const char *filename = conf->sDeviceFile; off_t filesize; FILE *fp; dev->enabled = false; Log_Printf(LOG_INFO, "Mounting %s HD image '%s'\n", hdtype, filename); /* Check size for sanity */ filesize = HDC_CheckAndGetSize(hdtype, filename, conf->nBlockSize); if (filesize < 0) return filesize; if (!(fp = fopen(filename, "rb+"))) { if (!(fp = fopen(filename, "rb"))) { Log_AlertDlg(LOG_ERROR, "Cannot open %s HD file for reading\n'%s'!\n", hdtype, filename); return -ENOENT; } Log_AlertDlg(LOG_WARN, "%s HD file is read-only, no writes will go through\n'%s'.\n", hdtype, filename); } else if (!File_Lock(fp)) { Log_AlertDlg(LOG_ERROR, "Locking %s HD file for writing failed\n'%s'!\n", hdtype, filename); fclose(fp); return -ENOLCK; } dev->scsi_version = conf->nScsiVersion; dev->blockSize = conf->nBlockSize; dev->hdSize = filesize / dev->blockSize; dev->image_file = fp; dev->enabled = true; return 0; } /** * Open the disk image files, set partitions. */ bool HDC_Init(void) { int i; /* ACSI */ nAcsiPartitions = 0; bAcsiEmuOn = false; memset(&AcsiBus, 0, sizeof(AcsiBus)); AcsiBus.typestr = "ACSI"; AcsiBus.buffer_size = 512; AcsiBus.buffer = malloc(AcsiBus.buffer_size); if (!AcsiBus.buffer) { perror("HDC_Init"); return false; } for (i = 0; i < MAX_ACSI_DEVS; i++) { if (!ConfigureParams.Acsi[i].bUseDevice) continue; if (HDC_InitDevice("ACSI", &AcsiBus.devs[i], &ConfigureParams.Acsi[i]) == 0) { nAcsiPartitions += HDC_PartitionCount(AcsiBus.devs[i].image_file, TRACE_SCSI_CMD, NULL); bAcsiEmuOn = true; } else ConfigureParams.Acsi[i].bUseDevice = false; } /* set total number of partitions */ nNumDrives += nAcsiPartitions; return bAcsiEmuOn; } /*---------------------------------------------------------------------*/ /** * HDC_UnInit - close image file * */ void HDC_UnInit(void) { int i; for (i = 0; bAcsiEmuOn && i < MAX_ACSI_DEVS; i++) { if (!AcsiBus.devs[i].enabled) continue; File_UnLock(AcsiBus.devs[i].image_file); fclose(AcsiBus.devs[i].image_file); AcsiBus.devs[i].image_file = NULL; AcsiBus.devs[i].enabled = false; } free(AcsiBus.buffer); AcsiBus.buffer = NULL; nNumDrives -= nAcsiPartitions; nAcsiPartitions = 0; bAcsiEmuOn = false; } /*---------------------------------------------------------------------*/ /** * Reset command status. */ void HDC_ResetCommandStatus(void) { if (!Config_IsMachineFalcon()) AcsiBus.status = 0; } /** * Process HDC command packets (SCSI/ACSI) bytes. * @return true if command has been executed. */ bool HDC_WriteCommandPacket(SCSI_CTRLR *ctr, uint8_t b) { bool bDidCmd = false; SCSI_DEV *dev = &ctr->devs[ctr->target]; /* Abort if the target device is not enabled */ if (!dev->enabled) { ctr->status = HD_STATUS_ERROR; return false; } /* Extract ACSI/SCSI opcode */ if (ctr->byteCount == 0) { ctr->opcode = b; ctr->bDmaError = false; } /* Successfully received one byte, and increase the byte-count */ if (ctr->byteCount < (int)sizeof(ctr->command)) ctr->command[ctr->byteCount] = b; ++ctr->byteCount; /* have we received a complete command packet yet? */ if ((ctr->opcode < 0x20 && ctr->byteCount == 6) || (ctr->opcode >= 0x20 && ctr->opcode < 0x60 && ctr->byteCount == 10) || (ctr->opcode == HD_REPORT_LUNS && ctr->byteCount == 12)) { /* We currently only support LUN 0, however INQUIRY must * always be handled, see SCSI standard */ if (HDC_GetLUN(ctr) == 0 || ctr->opcode == HD_INQUIRY) { HDC_EmulateCommandPacket(ctr); bDidCmd = true; } else { Log_Printf(LOG_WARN, "HDC: Access to non-existing LUN." " Command = 0x%02x\n", ctr->opcode); dev->nLastError = HD_REQSENS_INVLUN; /* REQUEST SENSE is still handled for invalid LUNs */ if (ctr->opcode == HD_REQ_SENSE) { HDC_Cmd_RequestSense(ctr); bDidCmd = true; } else { ctr->status = HD_STATUS_ERROR; } } } else if (ctr->opcode >= 0x60 && ctr->opcode != HD_REPORT_LUNS) { /* Commands >= 0x60 are not supported right now */ ctr->status = HD_STATUS_ERROR; dev->nLastError = HD_REQSENS_OPCODE; dev->bSetLastBlockAddr = false; if (ctr->byteCount == 10) { LOG_TRACE(TRACE_SCSI_CMD, "HDC: Unsupported command (%s).\n", HDC_CmdInfoStr(ctr)); } } else { ctr->status = HD_STATUS_OK; } return bDidCmd; } /*---------------------------------------------------------------------*/ static void Acsi_DmaTransfer(void) { uint32_t nDmaAddr = FDC_GetDMAAddress(); uint16_t nDmaMode = FDC_DMA_GetMode(); /* Don't do anything if no DMA to ACSI bus or nothing to transfer */ if ((nDmaMode & 0xc0) != 0x00 || AcsiBus.data_len == 0) return; if ((AcsiBus.dmawrite_to_fh && (nDmaMode & 0x100) == 0) || (!AcsiBus.dmawrite_to_fh && (nDmaMode & 0x100) != 0)) { Log_Printf(LOG_WARN, "DMA direction does not match SCSI command!\n"); return; } if (AcsiBus.dmawrite_to_fh) { /* write - if allowed */ if (STMemory_CheckAreaType(nDmaAddr, AcsiBus.data_len, ABFLAG_RAM | ABFLAG_ROM)) { #ifndef DISALLOW_HDC_WRITE int wlen = fwrite(&STRam[nDmaAddr], 1, AcsiBus.data_len, AcsiBus.dmawrite_to_fh); if (wlen != AcsiBus.data_len) { Log_Printf(LOG_ERROR, "Could not write all bytes to ACSI HD image.\n"); AcsiBus.status = HD_STATUS_ERROR; } #endif } else { Log_Printf(LOG_WARN, "HDC DMA write uses invalid RAM range 0x%x+%i\n", nDmaAddr, AcsiBus.data_len); AcsiBus.bDmaError = true; } AcsiBus.dmawrite_to_fh = NULL; } else if (!STMemory_SafeCopy(nDmaAddr, AcsiBus.buffer, AcsiBus.data_len, "ACSI DMA")) { AcsiBus.bDmaError = true; AcsiBus.status = HD_STATUS_ERROR; } FDC_WriteDMAAddress(nDmaAddr + AcsiBus.data_len); AcsiBus.data_len = 0; FDC_SetDMAStatus(AcsiBus.bDmaError); /* Mark DMA error */ FDC_SetIRQ(FDC_IRQ_SOURCE_HDC); /* For the MegaSTE, using the HDC DMA will flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); } static void Acsi_WriteCommandByte(int addr, uint8_t byte) { /* Clear IRQ initially (will be set again if byte has been accepted) */ FDC_ClearHdcIRQ(); /* When the A1 pin is pushed to 0, we want to start a new command. * We ignore the pin for the second byte in the packet since this * seems to happen on real hardware too (some buggy driver relies * on this behavior). */ if ((addr & 2) == 0 && AcsiBus.byteCount != 1) { AcsiBus.byteCount = 0; AcsiBus.target = ((byte & 0xE0) >> 5); /* Only process the first byte if it is not * an extended ICD command marker byte */ if ((byte & 0x1F) != 0x1F) { HDC_WriteCommandPacket(&AcsiBus, byte & 0x1F); } else { AcsiBus.status = HD_STATUS_OK; AcsiBus.bDmaError = false; } } else { /* Process normal command byte */ bool bDidCmd = HDC_WriteCommandPacket(&AcsiBus, byte); if (bDidCmd && AcsiBus.status == HD_STATUS_OK && AcsiBus.data_len) { Acsi_DmaTransfer(); } } if (AcsiBus.devs[AcsiBus.target].enabled) { FDC_SetDMAStatus(AcsiBus.bDmaError); /* Mark DMA error */ FDC_SetIRQ(FDC_IRQ_SOURCE_HDC); } } /** * Called when command bytes have been written to $FFFF8606 and * the HDC (not the FDC) is selected. */ void HDC_WriteCommandByte(int addr, uint8_t byte) { // fprintf(stderr, "HDC: Write cmd byte addr=%i, byte=%02x\n", addr, byte); if (Config_IsMachineFalcon()) Ncr5380_WriteByte(addr, byte); else if (bAcsiEmuOn) Acsi_WriteCommandByte(addr, byte); } /** * Get command byte. */ short int HDC_ReadCommandByte(int addr) { uint16_t ret; if (Config_IsMachineFalcon()) ret = Ncr5380_ReadByte(addr); else ret = AcsiBus.status; /* ACSI status */ return ret; } /** * Called when DMA transfer has just been enabled for the hard disk in the * $FFFF8606 (DMA mode) register. */ void HDC_DmaTransfer(void) { if (Config_IsMachineFalcon()) Ncr5380_DmaTransfer_Falcon(); else if (bAcsiEmuOn) Acsi_DmaTransfer(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ide.c000066400000000000000000002127621504763705000222470ustar00rootroot00000000000000/* Hatari - ide.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This is where we intercept read/writes to/from the IDE controller hardware. */ #include #include #include #include #include #include #include "main.h" #include "configuration.h" #include "file.h" #include "ide.h" #include "hdc.h" /* for partition counting */ #include "m68000.h" #include "mfp.h" #include "stMemory.h" #include "sysdeps.h" uaecptr ide_addr = 0xf00000; int nIDEPartitions = 0; struct IDEState; typedef void EndTransferFunc(struct IDEState *); typedef struct BlockDriverState BlockDriverState; /* NOTE: IDEState represents in fact one drive */ typedef struct IDEState { /* ide config */ int is_cdrom; int cylinders, heads, sectors; int64_t nb_sectors; int mult_sectors; int identify_set; uint16_t identify_data[256]; int drive_serial; /* ide regs */ uint8_t feature; uint8_t error; uint32_t nsector; uint8_t sector; uint8_t lcyl; uint8_t hcyl; /* other part of tf for lba48 support */ uint8_t hob_feature; uint8_t hob_nsector; uint8_t hob_sector; uint8_t hob_lcyl; uint8_t hob_hcyl; uint8_t select; uint8_t status; /* 0x3f6 command, only meaningful for drive 0 */ uint8_t cmd; /* set for lba48 access */ uint8_t lba48; /* depends on bit 4 in select, only meaningful for drive 0 */ struct IDEState *cur_drive; BlockDriverState *bs; /* ATAPI specific */ uint8_t sense_key; uint8_t asc; int packet_transfer_size; int elementary_transfer_size; int io_buffer_index; int lba; int cd_sector_size; /* ATA DMA state */ int io_buffer_size; /* PIO transfer handling */ int req_nb_sectors; /* number of sectors per interrupt */ EndTransferFunc *end_transfer_func; uint8_t *data_ptr; uint8_t *data_end; uint8_t *io_buffer; int media_changed; } IDEState; static IDEState ide_state[2]; static void ide_ioport_write(IDEState *ide_if, uint32_t addr, uint32_t val); static uint32_t ide_ioport_read(IDEState *ide_if, uint32_t addr1); static uint32_t ide_status_read(IDEState *ide_if, uint32_t addr); static void ide_ctrl_write(IDEState *ide_if, uint32_t addr, uint32_t val); static void ide_data_writew(IDEState *ide_if, uint32_t addr, uint32_t val); static uint32_t ide_data_readw(IDEState *ide_if, uint32_t addr); static void ide_data_writel(IDEState *ide_if, uint32_t addr, uint32_t val); static uint32_t ide_data_readl(IDEState *ide_if, uint32_t addr); /** * Check whether IDE is available: The Falcon always has an IDE controller, * and for the other machines it is normally only available on expansion * cards - we assume that the users want us to emulate an IDE controller * on such an expansion card if one of the IDE drives has been enabled. * Note that we also disable IDE on Falcon if bFastBoot is enabled - TOS * boots much faster if it does not have to scan for IDE devices. */ bool Ide_IsAvailable(void) { return ConfigureParams.Ide[0].bUseDevice || ConfigureParams.Ide[1].bUseDevice || (Config_IsMachineFalcon() && !ConfigureParams.System.bFastBoot); } bool Ide_IsValid(uaecptr address) { return Ide_IsAvailable() && address >= ide_addr && address < ide_addr + 0x40; } /** * Convert Falcon IDE registers to "normal" IDE register numbers. * (taken from Aranym - cheers!) */ static uint32_t fcha2io(uint32_t address) { switch (address) { case 0xf00000: return 0x00; case 0xf00005: return 0x01; case 0xf00009: return 0x02; case 0xf0000d: return 0x03; case 0xf00011: return 0x04; case 0xf00015: return 0x05; case 0xf00019: return 0x06; case 0xf0001d: return 0x07; case 0xf00039: return 0x16; default: return 0xffffffff; } } /** * Handle byte read access from IDE IO memory. * Note: Registers are available from usermode, too, so there is no check for * the supervisor mode required here. */ uae_u32 REGPARAM3 Ide_Mem_bget(uaecptr addr) { int ideport; uint8_t retval; uaecptr addr_in = addr; addr &= 0x00ffffff; /* Use a 24 bit address */ if (!Ide_IsValid(addr)) { /* invalid memory addressing --> bus error */ M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return -1; } ideport = fcha2io(addr); if (ideport >= 1 && ideport <= 7) { retval = ide_ioport_read(ide_state, ideport); } else if (ideport == 8 || ideport == 22) { retval = ide_status_read(ide_state, 0); } else { retval = 0xFF; } LOG_TRACE(TRACE_IDE, "IDE: bget($%x) = $%02x\n", addr, retval); return retval; } /** * Handle word read access from IDE IO memory. */ uae_u32 REGPARAM3 Ide_Mem_wget(uaecptr addr) { uint16_t retval; uaecptr addr_in = addr; addr &= 0x00ffffff; /* Use a 24 bit address */ if (!Ide_IsValid(addr)) { /* invalid memory addressing --> bus error */ M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, 0); return -1; } if (addr == 0xf00000 || addr == 0xf00002) { retval = ide_data_readw(ide_state, 0); } else { retval = 0xFFFF; } LOG_TRACE(TRACE_IDE, "IDE: wget($%x) = $%04x\n", addr, retval); return retval; } /** * Handle long-word read access from IDE IO memory. */ uae_u32 REGPARAM3 Ide_Mem_lget(uaecptr addr) { uint32_t retval; uaecptr addr_in = addr; addr &= 0x00ffffff; /* Use a 24 bit address */ if (!Ide_IsValid(addr)) { /* invalid memory addressing --> bus error */ M68000_BusError(addr_in, BUS_ERROR_READ, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, 0); return -1; } if (addr == 0xf00000) { retval = ide_data_readl(ide_state, 0); } else { retval = 0xFFFFFFFF; } /* word swap for long access to data register */ retval = ((retval >> 16) & 0x0000ffff) | ((retval & 0x0000ffff) << 16); LOG_TRACE(TRACE_IDE, "IDE: lget($%x) = $%08x\n", addr, retval); return retval; } /** * Handle byte write access to IDE IO memory. * Note: Registers are available from usermode, too, so there is no check for * the supervisor mode required here. */ void REGPARAM3 Ide_Mem_bput(uaecptr addr, uae_u32 val) { int ideport; uaecptr addr_in = addr; addr &= 0x00ffffff; /* Use a 24 bit address */ val &= 0x0ff; LOG_TRACE(TRACE_IDE, "IDE: bput($%x, $%x)\n", addr, val); if (!Ide_IsValid(addr)) { /* invalid memory addressing --> bus error */ M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, val); return; } ideport = fcha2io(addr); if (ideport >= 1 && ideport <= 7) { ide_ioport_write(ide_state, ideport, val); } else if (ideport == 8 || ideport == 22) { ide_ctrl_write(ide_state, 0, val); } } /** * Handle word write access to IDE IO memory. */ void REGPARAM3 Ide_Mem_wput(uaecptr addr, uae_u32 val) { uaecptr addr_in = addr; addr &= 0x00ffffff; /* Use a 24 bit address */ val &= 0x0ffff; LOG_TRACE(TRACE_IDE, "IDE: wput($%x, $%x)\n", addr, val); if (!Ide_IsValid(addr)) { /* invalid memory addressing --> bus error */ M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, val); return; } if (addr == 0xf00000 || addr == 0xf00002) { ide_data_writew(ide_state, 0, val); } } /** * Handle long-word write access to IDE IO memory. */ void REGPARAM3 Ide_Mem_lput(uaecptr addr, uae_u32 val) { uaecptr addr_in = addr; addr &= 0x00ffffff; /* Use a 24 bit address */ LOG_TRACE(TRACE_IDE, "IDE: lput($%x, $%x)\n", addr, val); if (!Ide_IsValid(addr)) { /* invalid memory addressing --> bus error */ M68000_BusError(addr_in, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, val); return; } /* word swap for long access to data register */ val = ((val >> 16) & 0x0000ffff) | ((val & 0x0000ffff) << 16); if (addr == 0xf00000) { ide_data_writel(ide_state, 0, val); } } /*----------------------------------------------------------------------------*/ /* * QEMU IDE disk and CD-ROM Emulator * * Copyright (c) 2003 Fabrice Bellard * Copyright (c) 2006 Openedhand Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define FW_VERSION "1.0" #define BDRV_TYPE_HD 0 #define BDRV_TYPE_CDROM 1 #define BDRV_TYPE_FLOPPY 2 #define BIOS_ATA_TRANSLATION_AUTO 0 #define BIOS_ATA_TRANSLATION_NONE 1 #define BIOS_ATA_TRANSLATION_LBA 2 #define BIOS_ATA_TRANSLATION_LARGE 3 #define BIOS_ATA_TRANSLATION_RECHS 4 #ifndef ENOMEDIUM // It's not defined on macOS for example #define ENOMEDIUM ENODEV #endif struct BlockDriverState { int64_t total_sectors; /* if we are reading a disk image, give its size in sectors */ int read_only; /* if true, the media is read only */ int removable; /* if true, the media can be removed */ int locked; /* if true, the media cannot temporarily be ejected */ int sg; /* if true, the device is a /dev/sg* */ /* event callback when inserting/removing */ void (*change_cb)(void *opaque); void *change_opaque; FILE *fhndl; off_t file_size; int media_changed; int byteswap; int sector_size; /* I/O stats (display with "info blockstats"). */ uint64_t rd_bytes; uint64_t wr_bytes; uint64_t rd_ops; uint64_t wr_ops; /* NOTE: the following infos are only hints for real hardware drivers. They are not used by the block driver */ int cyls, heads, secs, translation; int type; }; static inline void cpu_to_be16wu(uint16_t *p, uint16_t v) { uint8_t *p1 = (uint8_t *)p; p1[0] = v >> 8; p1[1] = v; } #define le32_to_cpu SDL_SwapLE32 #define le16_to_cpu SDL_SwapLE16 #define cpu_to_le32 SDL_SwapLE32 #define cpu_to_le16 SDL_SwapLE16 #define MIN(a, b) (((a) < (b)) ? (a) : (b)) /** * return 0 as number of sectors if no device present or error */ static void bdrv_get_geometry(BlockDriverState *bs, uint64_t *nb_sectors_ptr) { off_t length = bs->file_size; if (length < 0) length = 0; else length = length / bs->sector_size; *nb_sectors_ptr = length; } static void bdrv_get_geometry_hint(BlockDriverState *bs, int *pcyls, int *pheads, int *psecs) { *pcyls = bs->cyls; *pheads = bs->heads; *psecs = bs->secs; } static void bdrv_set_translation_hint(BlockDriverState *bs, int translation) { bs->translation = translation; } static void bdrv_set_geometry_hint(BlockDriverState *bs, int cyls, int heads, int secs) { bs->cyls = cyls; bs->heads = heads; bs->secs = secs; } static int bdrv_get_type_hint(BlockDriverState *bs) { return bs->type; } static int bdrv_get_translation_hint(BlockDriverState *bs) { return bs->translation; } /* XXX: no longer used */ static void bdrv_set_change_cb(BlockDriverState *bs, void (*change_cb)(void *opaque), void *opaque) { bs->change_cb = change_cb; bs->change_opaque = opaque; } /** * Return TRUE if the media is present */ static int bdrv_is_inserted(BlockDriverState *bs) { return (bs->fhndl != NULL); } static int bdrv_is_locked(BlockDriverState *bs) { return bs->locked; } /** * Lock or unlock the media (if it is locked, the user won't be able * to eject it manually). */ static void bdrv_set_locked(BlockDriverState *bs, int locked) { bs->locked = locked; } /* return < 0 if error. See bdrv_write() for the return codes */ static int bdrv_read(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, int nb_sectors) { int ret, len; if (!bs->fhndl) return -ENOMEDIUM; len = nb_sectors * bs->sector_size; if (fseeko(bs->fhndl, sector_num * bs->sector_size, SEEK_SET) != 0) { perror("bdrv_read"); return -errno; } ret = fread(buf, 1, len, bs->fhndl); if (ret != len) { Log_Printf(LOG_ERROR, "IDE: bdrv_read error (%d != %d length) at sector %lu!\n", ret, len, (unsigned long)sector_num); return -EINVAL; } bs->rd_bytes += (unsigned) len; bs->rd_ops ++; if (bs->byteswap) { uint16_t *buf16 = (uint16_t *)buf; while (len > 0) { *buf16 = SDL_Swap16(*buf16); buf16++; len -= 2; } } return 0; } /* Return < 0 if error. Important errors are: -EIO generic I/O error (may happen for all errors) -ENOMEDIUM No media inserted. -EINVAL Invalid sector number or nb_sectors -EACCES Trying to write a read-only device */ static int bdrv_write(BlockDriverState *bs, int64_t sector_num, const uint8_t *buf, int nb_sectors) { int ret, len, idx; uint16_t *buf16; if (!bs->fhndl) return -ENOMEDIUM; if (bs->read_only) return -EACCES; len = nb_sectors * bs->sector_size; if (fseeko(bs->fhndl, sector_num * bs->sector_size, SEEK_SET) != 0) { perror("bdrv_write"); return -errno; } if (!bs->byteswap) { ret = fwrite(buf, 1, len, bs->fhndl); } else { buf16 = malloc(len); if (!buf16) return -ENOMEM; for (idx = 0; idx < len; idx += 2) { buf16[idx / 2] = SDL_Swap16(*(const uint16_t *)&buf[idx]); } ret = fwrite(buf16, 1, len, bs->fhndl); free(buf16); } if (ret != len) { Log_Printf(LOG_ERROR, "IDE: bdrv_write error (%d != %d length) at sector %lu!\n", ret, len, (unsigned long)sector_num); return -EIO; } bs->wr_bytes += (unsigned) len; bs->wr_ops ++; return 0; } static int bdrv_open(BlockDriverState *bs, const char *filename, unsigned long blockSize, int flags) { Log_Printf(LOG_INFO, "Mounting IDE hard drive image %s\n", filename); bs->read_only = 0; bs->file_size = HDC_CheckAndGetSize("IDE", filename, blockSize); if (bs->file_size <= 0) return -1; if (bs->file_size < 2 * 16 * 63 * bs->sector_size) { Log_AlertDlg(LOG_ERROR, "IDE disk image size (%"PRId64" bytes) is " "too small for an IDE disk image " "(min. 1032192 byte)", (int64_t)bs->file_size); return -1; } bs->fhndl = fopen(filename, "rb+"); if (!bs->fhndl) { /* Maybe the file is read-only? */ bs->fhndl = fopen(filename, "rb"); if (!bs->fhndl) { perror("bdrv_open"); Log_AlertDlg(LOG_ERROR, "Cannot open IDE HD for reading\n'%s'.\n", filename); return -1; } Log_AlertDlg(LOG_WARN, "IDE HD file is read-only, no writes will go through\n'%s'.\n", filename); bs->read_only = 1; } else if (!File_Lock(bs->fhndl)) { Log_AlertDlg(LOG_ERROR, "Locking IDE HD file for writing failed\n'%s'!\n", filename); fclose(bs->fhndl); bs->fhndl = NULL; return -1; } /* call the change callback */ bs->media_changed = 1; if (bs->change_cb) bs->change_cb(bs->change_opaque); return 0; } static void bdrv_flush(BlockDriverState *bs) { fflush(bs->fhndl); } static void bdrv_close(BlockDriverState *bs) { File_UnLock(bs->fhndl); fclose(bs->fhndl); bs->fhndl = NULL; } /** * If eject_flag is TRUE, eject the media. Otherwise, close the tray */ static void bdrv_eject(BlockDriverState *bs, int eject_flag) { if (eject_flag) bdrv_close(bs); } // #define USE_DMA_CDROM /* Bits of HD_STATUS */ #define ERR_STAT 0x01 #define INDEX_STAT 0x02 #define ECC_STAT 0x04 /* Corrected error */ #define DRQ_STAT 0x08 #define SEEK_STAT 0x10 #define SRV_STAT 0x10 #define WRERR_STAT 0x20 #define READY_STAT 0x40 #define BUSY_STAT 0x80 /* Bits for HD_ERROR */ #define MARK_ERR 0x01 /* Bad address mark */ #define TRK0_ERR 0x02 /* couldn't find track 0 */ #define ABRT_ERR 0x04 /* Command aborted */ #define MCR_ERR 0x08 /* media change request */ #define ID_ERR 0x10 /* ID field not found */ #define MC_ERR 0x20 /* media changed */ #define ECC_ERR 0x40 /* Uncorrectable ECC error */ #define BBD_ERR 0x80 /* pre-EIDE meaning: block marked bad */ #define ICRC_ERR 0x80 /* new meaning: CRC error during transfer */ /* Bits of HD_NSECTOR */ #define CD 0x01 #define IO 0x02 #define REL 0x04 #define TAG_MASK 0xf8 /* Bits of Device Control register */ #define IDE_CTRL_HOB 0x80 #define IDE_CTRL_RESET 0x04 #define IDE_CTRL_DISABLE_IRQ 0x02 /* ATA/ATAPI Commands pre T13 Spec */ #define WIN_NOP 0x00 /* * 0x01->0x02 Reserved */ #define CFA_REQ_EXT_ERROR_CODE 0x03 /* CFA Request Extended Error Code */ /* * 0x04->0x07 Reserved */ #define WIN_SRST 0x08 /* ATAPI soft reset command */ #define WIN_DEVICE_RESET 0x08 /* * 0x09->0x0F Reserved */ #define WIN_RECAL 0x10 #define WIN_RESTORE WIN_RECAL /* * 0x10->0x1F Reserved */ #define WIN_READ 0x20 /* 28-Bit */ #define WIN_READ_ONCE 0x21 /* 28-Bit without retries */ #define WIN_READ_LONG 0x22 /* 28-Bit */ #define WIN_READ_LONG_ONCE 0x23 /* 28-Bit without retries */ #define WIN_READ_EXT 0x24 /* 48-Bit */ #define WIN_READDMA_EXT 0x25 /* 48-Bit */ #define WIN_READDMA_QUEUED_EXT 0x26 /* 48-Bit */ #define WIN_READ_NATIVE_MAX_EXT 0x27 /* 48-Bit */ /* * 0x28 */ #define WIN_MULTREAD_EXT 0x29 /* 48-Bit */ /* * 0x2A->0x2F Reserved */ #define WIN_WRITE 0x30 /* 28-Bit */ #define WIN_WRITE_ONCE 0x31 /* 28-Bit without retries */ #define WIN_WRITE_LONG 0x32 /* 28-Bit */ #define WIN_WRITE_LONG_ONCE 0x33 /* 28-Bit without retries */ #define WIN_WRITE_EXT 0x34 /* 48-Bit */ #define WIN_WRITEDMA_EXT 0x35 /* 48-Bit */ #define WIN_WRITEDMA_QUEUED_EXT 0x36 /* 48-Bit */ #define WIN_SET_MAX_EXT 0x37 /* 48-Bit */ #define CFA_WRITE_SECT_WO_ERASE 0x38 /* CFA Write Sectors without erase */ #define WIN_MULTWRITE_EXT 0x39 /* 48-Bit */ /* * 0x3A->0x3B Reserved */ #define WIN_WRITE_VERIFY 0x3C /* 28-Bit */ /* * 0x3D->0x3F Reserved */ #define WIN_VERIFY 0x40 /* 28-Bit - Read Verify Sectors */ #define WIN_VERIFY_ONCE 0x41 /* 28-Bit - without retries */ #define WIN_VERIFY_EXT 0x42 /* 48-Bit */ /* * 0x43->0x4F Reserved */ #define WIN_FORMAT 0x50 /* * 0x51->0x5F Reserved */ #define WIN_INIT 0x60 /* * 0x61->0x5F Reserved */ #define WIN_SEEK 0x70 /* 0x70-0x7F Reserved */ #define CFA_TRANSLATE_SECTOR 0x87 /* CFA Translate Sector */ #define WIN_DIAGNOSE 0x90 #define WIN_SPECIFY 0x91 /* set drive geometry translation */ #define WIN_DOWNLOAD_MICROCODE 0x92 #define WIN_STANDBYNOW2 0x94 #define CFA_IDLEIMMEDIATE 0x95 /* force drive to become "ready" */ #define WIN_STANDBY2 0x96 #define WIN_SETIDLE2 0x97 #define WIN_CHECKPOWERMODE2 0x98 #define WIN_SLEEPNOW2 0x99 /* * 0x9A VENDOR */ #define WIN_PACKETCMD 0xA0 /* Send a packet command. */ #define WIN_PIDENTIFY 0xA1 /* identify ATAPI device */ #define WIN_QUEUED_SERVICE 0xA2 #define WIN_SMART 0xB0 /* self-monitoring and reporting */ #define CFA_ACCESS_METADATA_STORAGE 0xB8 #define CFA_ERASE_SECTORS 0xC0 /* microdrives implement as NOP */ #define WIN_MULTREAD 0xC4 /* read sectors using multiple mode*/ #define WIN_MULTWRITE 0xC5 /* write sectors using multiple mode */ #define WIN_SETMULT 0xC6 /* enable/disable multiple mode */ #define WIN_READDMA_QUEUED 0xC7 /* read sectors using Queued DMA transfers */ #define WIN_READDMA 0xC8 /* read sectors using DMA transfers */ #define WIN_READDMA_ONCE 0xC9 /* 28-Bit - without retries */ #define WIN_WRITEDMA 0xCA /* write sectors using DMA transfers */ #define WIN_WRITEDMA_ONCE 0xCB /* 28-Bit - without retries */ #define WIN_WRITEDMA_QUEUED 0xCC /* write sectors using Queued DMA transfers */ #define CFA_WRITE_MULTI_WO_ERASE 0xCD /* CFA Write multiple without erase */ #define WIN_GETMEDIASTATUS 0xDA #define WIN_ACKMEDIACHANGE 0xDB /* ATA-1, ATA-2 vendor */ #define WIN_POSTBOOT 0xDC #define WIN_PREBOOT 0xDD #define WIN_DOORLOCK 0xDE /* lock door on removable drives */ #define WIN_DOORUNLOCK 0xDF /* unlock door on removable drives */ #define WIN_STANDBYNOW1 0xE0 #define WIN_IDLEIMMEDIATE 0xE1 /* force drive to become "ready" */ #define WIN_STANDBY 0xE2 /* Set device in Standby Mode */ #define WIN_SETIDLE1 0xE3 #define WIN_READ_BUFFER 0xE4 /* force read only 1 sector */ #define WIN_CHECKPOWERMODE1 0xE5 #define WIN_SLEEPNOW1 0xE6 #define WIN_FLUSH_CACHE 0xE7 #define WIN_WRITE_BUFFER 0xE8 /* force write only 1 sector */ #define WIN_WRITE_SAME 0xE9 /* read ata-2 to use */ /* SET_FEATURES 0x22 or 0xDD */ #define WIN_FLUSH_CACHE_EXT 0xEA /* 48-Bit */ #define WIN_IDENTIFY 0xEC /* ask drive to identify itself */ #define WIN_MEDIAEJECT 0xED #define WIN_IDENTIFY_DMA 0xEE /* same as WIN_IDENTIFY, but DMA */ #define WIN_SETFEATURES 0xEF /* set special drive features */ #define EXABYTE_ENABLE_NEST 0xF0 #define IBM_SENSE_CONDITION 0xF0 /* measure disk temperature */ #define WIN_SECURITY_SET_PASS 0xF1 #define WIN_SECURITY_UNLOCK 0xF2 #define WIN_SECURITY_ERASE_PREPARE 0xF3 #define WIN_SECURITY_ERASE_UNIT 0xF4 #define WIN_SECURITY_FREEZE_LOCK 0xF5 #define CFA_WEAR_LEVEL 0xF5 /* microdrives implement as NOP */ #define WIN_SECURITY_DISABLE 0xF6 #define WIN_READ_NATIVE_MAX 0xF8 /* return the native maximum address */ #define WIN_SET_MAX 0xF9 #define DISABLE_SEAGATE 0xFB /* set to 1 set disable mult support */ #define MAX_MULT_SECTORS 16 /* maximum physical IDE hard disk drive sector size */ #define MAX_SECTOR_SIZE 4096 /* ATAPI defines */ #define ATAPI_PACKET_SIZE 12 /* The generic packet command opcodes for CD/DVD Logical Units, * From Table 57 of the SFF8090 Ver. 3 (Mt. Fuji) draft standard. */ #define GPCMD_BLANK 0xa1 #define GPCMD_CLOSE_TRACK 0x5b #define GPCMD_FLUSH_CACHE 0x35 #define GPCMD_FORMAT_UNIT 0x04 #define GPCMD_GET_CONFIGURATION 0x46 #define GPCMD_GET_EVENT_STATUS_NOTIFICATION 0x4a #define GPCMD_GET_PERFORMANCE 0xac #define GPCMD_INQUIRY 0x12 #define GPCMD_LOAD_UNLOAD 0xa6 #define GPCMD_MECHANISM_STATUS 0xbd #define GPCMD_MODE_SELECT_10 0x55 #define GPCMD_MODE_SENSE_10 0x5a #define GPCMD_PAUSE_RESUME 0x4b #define GPCMD_PLAY_AUDIO_10 0x45 #define GPCMD_PLAY_AUDIO_MSF 0x47 #define GPCMD_PLAY_AUDIO_TI 0x48 #define GPCMD_PLAY_CD 0xbc #define GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e #define GPCMD_READ_10 0x28 #define GPCMD_READ_12 0xa8 #define GPCMD_READ_CDVD_CAPACITY 0x25 #define GPCMD_READ_CD 0xbe #define GPCMD_READ_CD_MSF 0xb9 #define GPCMD_READ_DISC_INFO 0x51 #define GPCMD_READ_DVD_STRUCTURE 0xad #define GPCMD_READ_FORMAT_CAPACITIES 0x23 #define GPCMD_READ_HEADER 0x44 #define GPCMD_READ_TRACK_RZONE_INFO 0x52 #define GPCMD_READ_SUBCHANNEL 0x42 #define GPCMD_READ_TOC_PMA_ATIP 0x43 #define GPCMD_REPAIR_RZONE_TRACK 0x58 #define GPCMD_REPORT_KEY 0xa4 #define GPCMD_REQUEST_SENSE 0x03 #define GPCMD_RESERVE_RZONE_TRACK 0x53 #define GPCMD_SCAN 0xba #define GPCMD_SEEK 0x2b #define GPCMD_SEND_DVD_STRUCTURE 0xad #define GPCMD_SEND_EVENT 0xa2 #define GPCMD_SEND_KEY 0xa3 #define GPCMD_SEND_OPC 0x54 #define GPCMD_SET_READ_AHEAD 0xa7 #define GPCMD_SET_STREAMING 0xb6 #define GPCMD_START_STOP_UNIT 0x1b #define GPCMD_STOP_PLAY_SCAN 0x4e #define GPCMD_TEST_UNIT_READY 0x00 #define GPCMD_VERIFY_10 0x2f #define GPCMD_WRITE_10 0x2a #define GPCMD_WRITE_AND_VERIFY_10 0x2e /* This is listed as optional in ATAPI 2.6, but is (curiously) * missing from Mt. Fuji, Table 57. It _is_ mentioned in Mt. Fuji * Table 377 as an MMC command for SCSi devices though... Most ATAPI * drives support it. */ #define GPCMD_SET_SPEED 0xbb /* This seems to be a SCSI specific CD-ROM opcode * to play data at track/index */ #define GPCMD_PLAYAUDIO_TI 0x48 /* * From MS Media Status Notification Support Specification. For * older drives only. */ #define GPCMD_GET_MEDIA_STATUS 0xda #define GPCMD_MODE_SENSE_6 0x1a /* Mode page codes for mode sense/set */ #define GPMODE_R_W_ERROR_PAGE 0x01 #define GPMODE_WRITE_PARMS_PAGE 0x05 #define GPMODE_AUDIO_CTL_PAGE 0x0e #define GPMODE_POWER_PAGE 0x1a #define GPMODE_FAULT_FAIL_PAGE 0x1c #define GPMODE_TO_PROTECT_PAGE 0x1d #define GPMODE_CAPABILITIES_PAGE 0x2a #define GPMODE_ALL_PAGES 0x3f /* Not in Mt. Fuji, but in ATAPI 2.6 -- deprecated now in favor * of MODE_SENSE_POWER_PAGE */ #define GPMODE_CDROM_PAGE 0x0d #define ATAPI_INT_REASON_CD 0x01 /* 0 = data transfer */ #define ATAPI_INT_REASON_IO 0x02 /* 1 = transfer to the host */ #define ATAPI_INT_REASON_REL 0x04 #define ATAPI_INT_REASON_TAG 0xf8 /* same constants as bochs */ #define ASC_ILLEGAL_OPCODE 0x20 #define ASC_LOGICAL_BLOCK_OOR 0x21 #define ASC_INV_FIELD_IN_CMD_PACKET 0x24 #define ASC_MEDIUM_NOT_PRESENT 0x3a #define ASC_SAVING_PARAMETERS_NOT_SUPPORTED 0x39 #define SENSE_NONE 0 #define SENSE_NOT_READY 2 #define SENSE_ILLEGAL_REQUEST 5 #define SENSE_UNIT_ATTENTION 6 static void padstr(char *str, const char *src, int len) { int i, v; for (i = 0; i < len; i++) { if (*src) v = *src++; else v = ' '; str[i^1] = v; } } static void padstr8(uint8_t *buf, int buf_size, const char *src) { int i; for (i = 0; i < buf_size; i++) { if (*src) buf[i] = *src++; else buf[i] = ' '; } } static void put_le16(uint16_t *p, unsigned int v) { *p = SDL_SwapLE16(v); } static void ide_identify(IDEState *s) { uint16_t *p; unsigned int oldsize; char buf[40]; int64_t nb_sectors_lba28; if (s->identify_set) { memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data)); return; } memset(s->io_buffer, 0, 512); p = (uint16_t *)s->io_buffer; put_le16(p + 0, 0x0040); put_le16(p + 1, s->cylinders); put_le16(p + 3, s->heads); put_le16(p + 4, 512 * s->sectors); /* XXX: retired, remove ? */ put_le16(p + 5, 512); /* XXX: retired, remove ? */ put_le16(p + 6, s->sectors); snprintf(buf, sizeof(buf), "QM%05d", s->drive_serial); padstr((char *)(p + 10), buf, 20); /* serial number */ put_le16(p + 20, 3); /* XXX: retired, remove ? */ put_le16(p + 21, 512); /* cache size in sectors */ put_le16(p + 22, 4); /* ecc bytes */ padstr((char *)(p + 23), FW_VERSION, 8); /* firmware version */ /* Use the same convention for the name as SCSI disks are using: The * first 8 characters should be the vendor, i.e. use 2 spaces here */ snprintf(buf, sizeof(buf), "Hatari IDE disk %liM", (long)(s->nb_sectors / (1024 * 1024 / s->bs->sector_size))); padstr((char *)(p + 27), buf, 40); #if MAX_MULT_SECTORS > 1 put_le16(p + 47, 0x8000 | MAX_MULT_SECTORS); #endif put_le16(p + 48, 1); /* dword I/O */ put_le16(p + 49, (1 << 11) | (1 << 9) | (1 << 8)); /* DMA and LBA supported */ put_le16(p + 51, 0x200); /* PIO transfer cycle */ put_le16(p + 52, 0x200); /* DMA transfer cycle */ put_le16(p + 53, 1 | (1 << 1) | (1 << 2)); /* words 54-58,64-70,88 are valid */ put_le16(p + 54, s->cylinders); put_le16(p + 55, s->heads); put_le16(p + 56, s->sectors); oldsize = s->cylinders * s->heads * s->sectors; put_le16(p + 57, oldsize); put_le16(p + 58, oldsize >> 16); if (s->mult_sectors) put_le16(p + 59, 0x100 | s->mult_sectors); nb_sectors_lba28 = s->nb_sectors; if (nb_sectors_lba28 >= 1 << 28) { nb_sectors_lba28 = (1 << 28) - 1; } put_le16(p + 60, nb_sectors_lba28); put_le16(p + 61, nb_sectors_lba28 >> 16); put_le16(p + 63, 0x07); /* mdma0-2 supported */ put_le16(p + 65, 120); put_le16(p + 66, 120); put_le16(p + 67, 120); put_le16(p + 68, 120); put_le16(p + 80, 0xf0); /* ata3 -> ata6 supported */ put_le16(p + 81, 0x16); /* conforms to ata5 */ put_le16(p + 82, (1 << 14)); /* 13=flush_cache_ext,12=flush_cache,10=lba48 */ put_le16(p + 83, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10)); put_le16(p + 84, (1 << 14)); put_le16(p + 85, (1 << 14)); /* 13=flush_cache_ext,12=flush_cache,10=lba48 */ put_le16(p + 86, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10)); put_le16(p + 87, (1 << 14)); put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */ put_le16(p + 93, 1 | (1 << 14) | 0x2000); /* LBA-48 sector count */ put_le16(p + 100, s->nb_sectors); put_le16(p + 101, s->nb_sectors >> 16); put_le16(p + 102, s->nb_sectors >> 32); put_le16(p + 103, s->nb_sectors >> 48); /* ratio logical/physical: 0, logicalSectorSizeSupported */ put_le16(p + 106, 1 << 12); /* words per logical sector */ put_le16(p + 117, s->bs->sector_size >> 1); put_le16(p + 118, s->bs->sector_size >> 17); memcpy(s->identify_data, p, sizeof(s->identify_data)); s->identify_set = 1; } static void ide_atapi_identify(IDEState *s) { uint16_t *p; char buf[20]; if (s->identify_set) { memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data)); return; } memset(s->io_buffer, 0, 512); p = (uint16_t *)s->io_buffer; /* Removable CDROM, 50us response, 12 byte packets */ put_le16(p + 0, (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0)); snprintf(buf, sizeof(buf), "QM%05d", s->drive_serial); padstr((char *)(p + 10), buf, 20); /* serial number */ put_le16(p + 20, 3); /* buffer type */ put_le16(p + 21, 512); /* cache size in sectors */ put_le16(p + 22, 4); /* ecc bytes */ padstr((char *)(p + 23), FW_VERSION, 8); /* firmware version */ padstr((char *)(p + 27), "Hatari CD-ROM", 40); /* model */ put_le16(p + 48, 1); /* dword I/O (XXX: should not be set on CDROM) */ #ifdef USE_DMA_CDROM put_le16(p + 49, 1 << 9 | 1 << 8); /* DMA and LBA supported */ put_le16(p + 53, 7); /* words 64-70, 54-58, 88 valid */ put_le16(p + 63, 7); /* mdma0-2 supported */ put_le16(p + 64, 0x3f); /* PIO modes supported */ #else put_le16(p + 49, 1 << 9); /* LBA supported, no DMA */ put_le16(p + 53, 3); /* words 64-70, 54-58 valid */ put_le16(p + 63, 0x103); /* DMA modes XXX: may be incorrect */ put_le16(p + 64, 1); /* PIO modes */ #endif put_le16(p + 65, 0xb4); /* minimum DMA multiword tx cycle time */ put_le16(p + 66, 0xb4); /* recommended DMA multiword tx cycle time */ put_le16(p + 67, 0x12c); /* minimum PIO cycle time without flow control */ put_le16(p + 68, 0xb4); /* minimum PIO cycle time with IORDY flow control */ put_le16(p + 71, 30); /* in ns */ put_le16(p + 72, 30); /* in ns */ put_le16(p + 80, 0x1e); /* support up to ATA/ATAPI-4 */ #ifdef USE_DMA_CDROM put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */ #endif memcpy(s->identify_data, p, sizeof(s->identify_data)); s->identify_set = 1; } static void ide_set_signature(IDEState *s) { s->select &= 0xf0; /* clear head */ /* put signature */ s->nsector = 1; s->sector = 1; if (s->is_cdrom) { s->lcyl = 0x14; s->hcyl = 0xeb; } else if (s->bs) { s->lcyl = 0; s->hcyl = 0; } else { s->lcyl = 0xff; s->hcyl = 0xff; } } static inline void ide_abort_command(IDEState *s) { s->status = READY_STAT | ERR_STAT; s->error = ABRT_ERR; } static inline void ide_set_irq(IDEState *s) { if (!(s->cmd & IDE_CTRL_DISABLE_IRQ)) { /* Set IRQ (set line to low) */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_FDC_HDC , MFP_GPIP_STATE_LOW ); } } /* prepare data transfer and tell what to do after */ static void ide_transfer_start(IDEState *s, uint8_t *buf, int size, EndTransferFunc *end_transfer_func) { s->end_transfer_func = end_transfer_func; s->data_ptr = buf; s->data_end = buf + size; if (!(s->status & ERR_STAT)) s->status |= DRQ_STAT; } static void ide_transfer_stop(IDEState *s) { s->end_transfer_func = ide_transfer_stop; s->data_ptr = s->io_buffer; s->data_end = s->io_buffer; s->status &= ~DRQ_STAT; } static int64_t ide_get_sector(IDEState *s) { int64_t sector_num; if (s->select & 0x40) { /* lba */ if (!s->lba48) { sector_num = ((s->select & 0x0f) << 24) | (s->hcyl << 16) | (s->lcyl << 8) | s->sector; } else { sector_num = ((int64_t)s->hob_hcyl << 40) | ((int64_t) s->hob_lcyl << 32) | ((int64_t) s->hob_sector << 24) | ((int64_t) s->hcyl << 16) | ((int64_t) s->lcyl << 8) | s->sector; } } else { sector_num = ((s->hcyl << 8) | s->lcyl) * s->heads * s->sectors + (s->select & 0x0f) * s->sectors + (s->sector - 1); } return sector_num; } static void ide_set_sector(IDEState *s, int64_t sector_num) { unsigned int cyl, r; if (s->select & 0x40) { if (!s->lba48) { s->select = (s->select & 0xf0) | (sector_num >> 24); s->hcyl = (sector_num >> 16); s->lcyl = (sector_num >> 8); s->sector = (sector_num); } else { s->sector = sector_num; s->lcyl = sector_num >> 8; s->hcyl = sector_num >> 16; s->hob_sector = sector_num >> 24; s->hob_lcyl = sector_num >> 32; s->hob_hcyl = sector_num >> 40; } } else { cyl = sector_num / (s->heads * s->sectors); r = sector_num % (s->heads * s->sectors); s->hcyl = cyl >> 8; s->lcyl = cyl; s->select = (s->select & 0xf0) | ((r / s->sectors) & 0x0f); s->sector = (r % s->sectors) + 1; } } static void ide_sector_read(IDEState *s) { int64_t sector_num; int ret, n; s->status = READY_STAT | SEEK_STAT; s->error = 0; /* not needed by IDE spec, but needed by Windows */ sector_num = ide_get_sector(s); n = s->nsector; if (n == 0) { /* no more sector to read from disk */ ide_transfer_stop(s); } else { LOG_TRACE(TRACE_IDE, "IDE: read sector=%"PRId64"\n", sector_num); if (n > s->req_nb_sectors) n = s->req_nb_sectors; ret = bdrv_read(s->bs, sector_num, s->io_buffer, n); if (ret != 0) { ide_abort_command(s); ide_set_irq(s); return; } ide_transfer_start(s, s->io_buffer, s->bs->sector_size * n, ide_sector_read); ide_set_irq(s); ide_set_sector(s, sector_num + n); s->nsector -= n; } } static void ide_sector_write(IDEState *s) { int64_t sector_num; int ret, n, n1; s->status = READY_STAT | SEEK_STAT; sector_num = ide_get_sector(s); LOG_TRACE(TRACE_IDE, "IDE: write sector=%"PRId64"\n", sector_num); n = s->nsector; if (n > s->req_nb_sectors) n = s->req_nb_sectors; ret = bdrv_write(s->bs, sector_num, s->io_buffer, n); if (ret != 0) { ide_abort_command(s); ide_set_irq(s); return; } s->nsector -= n; if (s->nsector == 0) { /* no more sectors to write */ ide_transfer_stop(s); } else { n1 = s->nsector; if (n1 > s->req_nb_sectors) n1 = s->req_nb_sectors; ide_transfer_start(s, s->io_buffer, s->bs->sector_size * n1, ide_sector_write); } ide_set_sector(s, sector_num + n); ide_set_irq(s); } static void ide_atapi_cmd_ok(IDEState *s) { s->error = 0; s->status = READY_STAT; s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD; ide_set_irq(s); } static void ide_atapi_cmd_error(IDEState *s, int sense_key, int asc) { LOG_TRACE(TRACE_IDE, "IDE: ATAPI cmd error sense=0x%x asc=0x%x\n", sense_key, asc); s->error = sense_key << 4; s->status = READY_STAT | ERR_STAT; s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD; s->sense_key = sense_key; s->asc = asc; ide_set_irq(s); } static inline void cpu_to_ube16(uint8_t *buf, int val) { buf[0] = val >> 8; buf[1] = val; } static inline void cpu_to_ube32(uint8_t *buf, unsigned int val) { buf[0] = val >> 24; buf[1] = val >> 16; buf[2] = val >> 8; buf[3] = val; } static inline int ube16_to_cpu(const uint8_t *buf) { return (buf[0] << 8) | buf[1]; } static inline int ube32_to_cpu(const uint8_t *buf) { return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } static void lba_to_msf(uint8_t *buf, int lba) { lba += 150; buf[0] = (lba / 75) / 60; buf[1] = (lba / 75) % 60; buf[2] = lba % 75; } static void cd_data_to_raw(uint8_t *buf, int lba) { /* sync bytes */ buf[0] = 0x00; memset(buf + 1, 0xff, 10); buf[11] = 0x00; buf += 12; /* MSF */ lba_to_msf(buf, lba); buf[3] = 0x01; /* mode 1 data */ buf += 4; /* data */ buf += 2048; /* XXX: ECC not computed */ memset(buf, 0, 288); } static int cd_read_sector(BlockDriverState *bs, int lba, uint8_t *buf, int sector_size) { int ret; switch (sector_size) { case 2048: ret = bdrv_read(bs, (int64_t)lba << 2, buf, 4); break; case 2352: ret = bdrv_read(bs, (int64_t)lba << 2, buf + 16, 4); if (ret < 0) return ret; cd_data_to_raw(buf, lba); break; default: ret = -EIO; break; } return ret; } static void ide_atapi_io_error(IDEState *s, int ret) { /* XXX: handle more errors */ if (ret == -ENOMEDIUM) { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); } else { ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR); } } /* The whole ATAPI transfer logic is handled in this function */ static void ide_atapi_cmd_reply_end(IDEState *s) { int byte_count_limit, size, ret; LOG_TRACE(TRACE_IDE, "IDE: ATAPI reply tx_size=%d elem_tx_size=%d index=%d\n", s->packet_transfer_size, s->elementary_transfer_size, s->io_buffer_index); if (s->packet_transfer_size <= 0) { /* end of transfer */ ide_transfer_stop(s); s->status = READY_STAT; s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD; ide_set_irq(s); LOG_TRACE(TRACE_IDE, "IDE: ATAPI status=0x%x\n", s->status); } else { /* see if a new sector must be read */ if (s->lba != -1 && s->io_buffer_index >= s->cd_sector_size) { ret = cd_read_sector(s->bs, s->lba, s->io_buffer, s->cd_sector_size); if (ret < 0) { ide_transfer_stop(s); ide_atapi_io_error(s, ret); return; } s->lba++; s->io_buffer_index = 0; } if (s->elementary_transfer_size > 0) { /* there are some data left to transmit in this elementary transfer */ size = s->cd_sector_size - s->io_buffer_index; if (size > s->elementary_transfer_size) size = s->elementary_transfer_size; ide_transfer_start(s, s->io_buffer + s->io_buffer_index, size, ide_atapi_cmd_reply_end); s->packet_transfer_size -= size; s->elementary_transfer_size -= size; s->io_buffer_index += size; } else { /* a new transfer is needed */ s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO; byte_count_limit = s->lcyl | (s->hcyl << 8); LOG_TRACE(TRACE_IDE, "IDE: ATAPI byte_count_limit=%d\n", byte_count_limit); if (byte_count_limit == 0xffff) byte_count_limit--; size = s->packet_transfer_size; if (size > byte_count_limit) { /* byte count limit must be even if this case */ if (byte_count_limit & 1) byte_count_limit--; size = byte_count_limit; } s->lcyl = size; s->hcyl = size >> 8; s->elementary_transfer_size = size; /* we cannot transmit more than one sector at a time */ if (s->lba != -1) { if (size > (s->cd_sector_size - s->io_buffer_index)) size = (s->cd_sector_size - s->io_buffer_index); } ide_transfer_start(s, s->io_buffer + s->io_buffer_index, size, ide_atapi_cmd_reply_end); s->packet_transfer_size -= size; s->elementary_transfer_size -= size; s->io_buffer_index += size; ide_set_irq(s); LOG_TRACE(TRACE_IDE, "IDE: ATAPI status=0x%x\n", s->status); } } } /* send a reply of 'size' bytes in s->io_buffer to an ATAPI command */ static void ide_atapi_cmd_reply(IDEState *s, int size, int max_size) { if (size > max_size) size = max_size; s->lba = -1; /* no sector read */ s->packet_transfer_size = size; s->io_buffer_size = size; /* dma: send the reply data as one chunk */ s->elementary_transfer_size = 0; s->io_buffer_index = 0; s->status = READY_STAT; ide_atapi_cmd_reply_end(s); } /* start a CD-CDROM read command */ static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors, int sector_size) { LOG_TRACE(TRACE_IDE, "IDE: ATAPI read pio LBA=%d nb_sectors=%d\n", lba, nb_sectors); s->lba = lba; s->packet_transfer_size = nb_sectors * sector_size; s->elementary_transfer_size = 0; s->io_buffer_index = sector_size; s->cd_sector_size = sector_size; s->status = READY_STAT; ide_atapi_cmd_reply_end(s); } static void ide_atapi_cmd(IDEState *s) { const uint8_t *packet; uint8_t *buf; int max_len; packet = s->io_buffer; buf = s->io_buffer; if (LOG_TRACE_LEVEL(TRACE_IDE)) { int i; LOG_TRACE_DIRECT_INIT(); LOG_TRACE_DIRECT("IDE: ATAPI limit=0x%x packet", s->lcyl | (s->hcyl << 8)); for (i = 0; i < ATAPI_PACKET_SIZE; i++) { LOG_TRACE_DIRECT(" %02x", packet[i]); } LOG_TRACE_DIRECT("\n"); LOG_TRACE_DIRECT_FLUSH(); } switch (s->io_buffer[0]) { case GPCMD_TEST_UNIT_READY: if (bdrv_is_inserted(s->bs)) { ide_atapi_cmd_ok(s); } else { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); } break; case GPCMD_MODE_SENSE_6: case GPCMD_MODE_SENSE_10: { int action, code; if (packet[0] == GPCMD_MODE_SENSE_10) max_len = ube16_to_cpu(packet + 7); else max_len = packet[4]; action = packet[2] >> 6; code = packet[2] & 0x3f; switch (action) { case 0: /* current values */ switch (code) { case 0x01: /* error recovery */ cpu_to_ube16(&buf[0], 16 + 6); buf[2] = 0x70; buf[3] = 0; buf[4] = 0; buf[5] = 0; buf[6] = 0; buf[7] = 0; buf[8] = 0x01; buf[9] = 0x06; buf[10] = 0x00; buf[11] = 0x05; buf[12] = 0x00; buf[13] = 0x00; buf[14] = 0x00; buf[15] = 0x00; ide_atapi_cmd_reply(s, 16, max_len); break; case 0x2a: cpu_to_ube16(&buf[0], 28 + 6); buf[2] = 0x70; buf[3] = 0; buf[4] = 0; buf[5] = 0; buf[6] = 0; buf[7] = 0; buf[8] = 0x2a; buf[9] = 0x12; buf[10] = 0x00; buf[11] = 0x00; buf[12] = 0x70; buf[13] = 3 << 5; buf[14] = (1 << 0) | (1 << 3) | (1 << 5); if (bdrv_is_locked(s->bs)) buf[6] |= 1 << 1; buf[15] = 0x00; cpu_to_ube16(&buf[16], 706); buf[18] = 0; buf[19] = 2; cpu_to_ube16(&buf[20], 512); cpu_to_ube16(&buf[22], 706); buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0; ide_atapi_cmd_reply(s, 28, max_len); break; default: goto error_cmd; } break; case 1: /* changeable values */ goto error_cmd; case 2: /* default values */ goto error_cmd; default: case 3: /* saved values */ ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_SAVING_PARAMETERS_NOT_SUPPORTED); break; } } break; case GPCMD_REQUEST_SENSE: max_len = packet[4]; memset(buf, 0, 18); buf[0] = 0x70 | (1 << 7); buf[2] = s->sense_key; buf[7] = 10; buf[12] = s->asc; ide_atapi_cmd_reply(s, 18, max_len); break; case GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL: if (bdrv_is_inserted(s->bs)) { bdrv_set_locked(s->bs, packet[4] & 1); ide_atapi_cmd_ok(s); } else { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); } break; case GPCMD_READ_10: case GPCMD_READ_12: { int nb_sectors, lba; if (packet[0] == GPCMD_READ_10) nb_sectors = ube16_to_cpu(packet + 7); else nb_sectors = ube32_to_cpu(packet + 6); lba = ube32_to_cpu(packet + 2); if (nb_sectors == 0) { ide_atapi_cmd_ok(s); break; } ide_atapi_cmd_read(s, lba, nb_sectors, 2048); } break; case GPCMD_READ_CD: { int nb_sectors, lba, transfer_request; nb_sectors = (packet[6] << 16) | (packet[7] << 8) | packet[8]; lba = ube32_to_cpu(packet + 2); if (nb_sectors == 0) { ide_atapi_cmd_ok(s); break; } transfer_request = packet[9]; switch (transfer_request & 0xf8) { case 0x00: /* nothing */ ide_atapi_cmd_ok(s); break; case 0x10: /* normal read */ ide_atapi_cmd_read(s, lba, nb_sectors, 2048); break; case 0xf8: /* read all data */ ide_atapi_cmd_read(s, lba, nb_sectors, 2352); break; default: ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET); break; } } break; case GPCMD_SEEK: { unsigned int lba; uint64_t total_sectors; bdrv_get_geometry(s->bs, &total_sectors); if (total_sectors == 0) { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); break; } lba = ube32_to_cpu(packet + 2); if (lba >= total_sectors) { ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR); break; } ide_atapi_cmd_ok(s); } break; case GPCMD_START_STOP_UNIT: { int start, eject; start = packet[4] & 1; eject = (packet[4] >> 1) & 1; if (eject && !start) { /* eject the disk */ bdrv_eject(s->bs, 1); } else if (eject && start) { /* close the tray */ bdrv_eject(s->bs, 0); } ide_atapi_cmd_ok(s); } break; case GPCMD_MECHANISM_STATUS: { max_len = ube16_to_cpu(packet + 8); cpu_to_ube16(buf, 0); /* no current LBA */ buf[2] = 0; buf[3] = 0; buf[4] = 0; buf[5] = 1; cpu_to_ube16(buf + 6, 0); ide_atapi_cmd_reply(s, 8, max_len); } break; case GPCMD_READ_TOC_PMA_ATIP: { int format, len; // int msf, start_track; uint64_t total_sectors; bdrv_get_geometry(s->bs, &total_sectors); if (total_sectors == 0) { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); break; } max_len = ube16_to_cpu(packet + 7); format = packet[9] >> 6; // msf = (packet[1] >> 1) & 1; // start_track = packet[6]; switch (format) { case 0: Log_Printf(LOG_ERROR, "IDE FIXME: cdrom_read_toc not implemented"); len=-1; //len = cdrom_read_toc(total_sectors, buf, msf, start_track); if (len < 0) goto error_cmd; ide_atapi_cmd_reply(s, len, max_len); break; case 1: /* multi session : only a single session defined */ memset(buf, 0, 12); buf[1] = 0x0a; buf[2] = 0x01; buf[3] = 0x01; ide_atapi_cmd_reply(s, 12, max_len); break; case 2: Log_Printf(LOG_ERROR, "IDE FIXME: cdrom_read_toc_raw not implemented"); len=-1; //len = cdrom_read_toc_raw(total_sectors, buf, msf, start_track); if (len < 0) goto error_cmd; ide_atapi_cmd_reply(s, len, max_len); break; default: error_cmd: ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET); break; } } break; case GPCMD_READ_CDVD_CAPACITY: { uint64_t total_sectors; bdrv_get_geometry(s->bs, &total_sectors); if (total_sectors == 0) { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); break; } /* NOTE: it is really the number of sectors minus 1 */ cpu_to_ube32(buf, total_sectors - 1); cpu_to_ube32(buf + 4, 2048); ide_atapi_cmd_reply(s, 8, 8); } break; case GPCMD_READ_DVD_STRUCTURE: { int media = packet[1]; int layer = packet[6]; int format = packet[2]; uint64_t total_sectors; if (media != 0 || layer != 0) { ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET); } switch (format) { case 0: bdrv_get_geometry(s->bs, &total_sectors); if (total_sectors == 0) { ide_atapi_cmd_error(s, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT); break; } memset(buf, 0, 2052); buf[4] = 1; // DVD-ROM, part version 1 buf[5] = 0xf; // 120mm disc, maximum rate unspecified buf[6] = 0; // one layer, embossed data buf[7] = 0; cpu_to_ube32(buf + 8, 0); cpu_to_ube32(buf + 12, total_sectors - 1); cpu_to_ube32(buf + 16, total_sectors - 1); cpu_to_be16wu((uint16_t *)buf, 2048 + 4); ide_atapi_cmd_reply(s, 2048 + 3, 2048 + 4); break; default: ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET); break; } } break; case GPCMD_SET_SPEED: ide_atapi_cmd_ok(s); break; case GPCMD_INQUIRY: max_len = packet[4]; buf[0] = 0x05; /* CD-ROM */ buf[1] = 0x80; /* removable */ buf[2] = 0x00; /* ISO */ buf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */ buf[4] = 31; /* additional length */ buf[5] = 0; /* reserved */ buf[6] = 0; /* reserved */ buf[7] = 0; /* reserved */ padstr8(buf + 8, 8, "Hatari"); padstr8(buf + 16, 16, "CD/DVD-ROM"); padstr8(buf + 32, 4, FW_VERSION); ide_atapi_cmd_reply(s, 36, max_len); break; case GPCMD_GET_CONFIGURATION: { uint64_t total_sectors; /* only feature 0 is supported */ if (packet[2] != 0 || packet[3] != 0) { ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET); break; } memset(buf, 0, 32); bdrv_get_geometry(s->bs, &total_sectors); buf[3] = 16; buf[7] = total_sectors <= 1433600 ? 0x08 : 0x10; /* current profile */ buf[10] = 0x10 | 0x1; buf[11] = 0x08; /* size of profile list */ buf[13] = 0x10; /* DVD-ROM profile */ buf[14] = buf[7] == 0x10; /* (in)active */ buf[17] = 0x08; /* CD-ROM profile */ buf[18] = buf[7] == 0x08; /* (in)active */ ide_atapi_cmd_reply(s, 32, 32); break; } default: ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, ASC_ILLEGAL_OPCODE); break; } } /* called when the inserted state of the media has changed */ static void cdrom_change_cb(void *opaque) { IDEState *s = opaque; uint64_t nb_sectors; /* XXX: send interrupt too */ bdrv_get_geometry(s->bs, &nb_sectors); s->nb_sectors = nb_sectors; } static void ide_cmd_lba48_transform(IDEState *s, int lba48) { s->lba48 = lba48; /* handle the 'magic' 0 nsector count conversion here. to avoid * fiddling with the rest of the read logic, we just store the * full sector count in ->nsector and ignore ->hob_nsector from now */ if (!s->lba48) { if (!s->nsector) s->nsector = 256; } else { if (!s->nsector && !s->hob_nsector) s->nsector = 65536; else { int lo = s->nsector; int hi = s->hob_nsector; s->nsector = (hi << 8) | lo; } } } static void ide_clear_hob(IDEState *ide_if) { /* any write clears HOB high bit of device control register */ ide_if[0].cmd &= ~IDE_CTRL_HOB; } /* IOport [W]rite [R]egisters */ enum ATA_IOPORT_WR { ATA_IOPORT_WR_DATA = 0, ATA_IOPORT_WR_FEATURES = 1, ATA_IOPORT_WR_SECTOR_COUNT = 2, ATA_IOPORT_WR_SECTOR_NUMBER = 3, ATA_IOPORT_WR_CYLINDER_LOW = 4, ATA_IOPORT_WR_CYLINDER_HIGH = 5, ATA_IOPORT_WR_DEVICE_HEAD = 6, ATA_IOPORT_WR_COMMAND = 7, ATA_IOPORT_WR_NUM_REGISTERS, }; const char *ATA_IOPORT_WR_lookup[ATA_IOPORT_WR_NUM_REGISTERS] = { [ATA_IOPORT_WR_DATA] = "Data", [ATA_IOPORT_WR_FEATURES] = "Features", [ATA_IOPORT_WR_SECTOR_COUNT] = "Sector Count", [ATA_IOPORT_WR_SECTOR_NUMBER] = "Sector Number", [ATA_IOPORT_WR_CYLINDER_LOW] = "Cylinder Low", [ATA_IOPORT_WR_CYLINDER_HIGH] = "Cylinder High", [ATA_IOPORT_WR_DEVICE_HEAD] = "Device/Head", [ATA_IOPORT_WR_COMMAND] = "Command" }; static void ide_ioport_write(IDEState *ide_if, uint32_t addr, uint32_t val) { IDEState *s; int unit, n; int lba48 = 0; int reg_num = addr & 7; LOG_TRACE(TRACE_IDE, "IDE: write addr=0x%x reg='%s' val=0x%02x\n", addr, ATA_IOPORT_WR_lookup[reg_num], val); /* NOTE: Device0 and Device1 both receive incoming register writes. * (They're on the same bus! They have to!) */ switch (reg_num) { case 0: break; case ATA_IOPORT_WR_FEATURES: ide_clear_hob(ide_if); ide_if[0].hob_feature = ide_if[0].feature; ide_if[1].hob_feature = ide_if[1].feature; ide_if[0].feature = val; ide_if[1].feature = val; break; case ATA_IOPORT_WR_SECTOR_COUNT: ide_clear_hob(ide_if); ide_if[0].hob_nsector = ide_if[0].nsector; ide_if[1].hob_nsector = ide_if[1].nsector; ide_if[0].nsector = val; ide_if[1].nsector = val; break; case ATA_IOPORT_WR_SECTOR_NUMBER: ide_clear_hob(ide_if); ide_if[0].hob_sector = ide_if[0].sector; ide_if[1].hob_sector = ide_if[1].sector; ide_if[0].sector = val; ide_if[1].sector = val; break; case ATA_IOPORT_WR_CYLINDER_LOW: ide_clear_hob(ide_if); ide_if[0].hob_lcyl = ide_if[0].lcyl; ide_if[1].hob_lcyl = ide_if[1].lcyl; ide_if[0].lcyl = val; ide_if[1].lcyl = val; break; case ATA_IOPORT_WR_CYLINDER_HIGH: ide_clear_hob(ide_if); ide_if[0].hob_hcyl = ide_if[0].hcyl; ide_if[1].hob_hcyl = ide_if[1].hcyl; ide_if[0].hcyl = val; ide_if[1].hcyl = val; break; case ATA_IOPORT_WR_DEVICE_HEAD: ide_clear_hob(ide_if); ide_if[0].select = val | 0xa0; ide_if[1].select = val | 0xa0; /* select drive */ unit = (val >> 4) & 1; s = ide_if + unit; ide_if->cur_drive = s; break; default: case ATA_IOPORT_WR_COMMAND: ide_clear_hob(ide_if); LOG_TRACE(TRACE_IDE, "IDE: CMD=%02x\n", val); s = ide_if->cur_drive; /* ignore commands to non existent IDE device 1 */ if (s != ide_if && !s->bs) { Log_Printf(LOG_INFO, "IDE: Tried to send command to " "non-existent IDE device #1!\n"); break; } switch (val) { case WIN_IDENTIFY: if (s->bs && !s->is_cdrom) { ide_identify(s); s->status = READY_STAT | SEEK_STAT; ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop); } else { if (s->is_cdrom) { ide_set_signature(s); } ide_abort_command(s); } ide_set_irq(s); break; case WIN_SPECIFY: case WIN_RECAL: s->error = 0; s->status = READY_STAT | SEEK_STAT; ide_set_irq(s); break; case WIN_SETMULT: if ((s->nsector & 0xff) != 0 && ((s->nsector & 0xff) > MAX_MULT_SECTORS || (s->nsector & (s->nsector - 1)) != 0)) { ide_abort_command(s); } else { s->mult_sectors = s->nsector & 0xff; s->status = READY_STAT; } ide_set_irq(s); break; case WIN_VERIFY_EXT: lba48 = 1; /* fall through */ case WIN_VERIFY: case WIN_VERIFY_ONCE: /* do sector number check ? */ ide_cmd_lba48_transform(s, lba48); s->status = READY_STAT; ide_set_irq(s); break; case WIN_FORMAT: ide_cmd_lba48_transform(s, lba48); s->error = 0; s->status = READY_STAT | SEEK_STAT; s->req_nb_sectors = s->mult_sectors; n = s->nsector; if (n > s->req_nb_sectors) n = s->req_nb_sectors; ide_transfer_start(s, s->io_buffer, s->bs->sector_size * n, ide_sector_write); s->media_changed = 1; break; case WIN_READ_EXT: lba48 = 1; /* fall through */ case WIN_READ: case WIN_READ_ONCE: if (!s->bs) goto abort_cmd; ide_cmd_lba48_transform(s, lba48); s->req_nb_sectors = 1; ide_sector_read(s); break; case WIN_WRITE_EXT: lba48 = 1; /* fall through */ case WIN_WRITE: case WIN_WRITE_ONCE: case CFA_WRITE_SECT_WO_ERASE: case WIN_WRITE_VERIFY: ide_cmd_lba48_transform(s, lba48); s->error = 0; s->status = SEEK_STAT | READY_STAT; s->req_nb_sectors = 1; ide_transfer_start(s, s->io_buffer, s->bs->sector_size, ide_sector_write); s->media_changed = 1; break; case WIN_MULTREAD_EXT: lba48 = 1; /* fall through */ case WIN_MULTREAD: if (!s->mult_sectors) goto abort_cmd; ide_cmd_lba48_transform(s, lba48); s->req_nb_sectors = s->mult_sectors; ide_sector_read(s); break; case WIN_MULTWRITE_EXT: lba48 = 1; /* fall through */ case WIN_MULTWRITE: case CFA_WRITE_MULTI_WO_ERASE: if (!s->mult_sectors) goto abort_cmd; ide_cmd_lba48_transform(s, lba48); s->error = 0; s->status = SEEK_STAT | READY_STAT; s->req_nb_sectors = s->mult_sectors; n = s->nsector; if (n > s->req_nb_sectors) n = s->req_nb_sectors; ide_transfer_start(s, s->io_buffer, s->bs->sector_size * n, ide_sector_write); s->media_changed = 1; break; case WIN_READDMA_EXT: lba48 = 1; /* fall through */ case WIN_READDMA: case WIN_READDMA_ONCE: if (!s->bs) goto abort_cmd; ide_cmd_lba48_transform(s, lba48); // ide_sector_read_dma(s); Log_Printf(LOG_ERROR, "IDE: DMA read not supported!\n"); break; case WIN_WRITEDMA_EXT: lba48 = 1; /* fall through */ case WIN_WRITEDMA: case WIN_WRITEDMA_ONCE: if (!s->bs) goto abort_cmd; ide_cmd_lba48_transform(s, lba48); // ide_sector_write_dma(s); Log_Printf(LOG_ERROR, "IDE: DMA write not supported!\n"); s->media_changed = 1; break; case WIN_READ_NATIVE_MAX_EXT: lba48 = 1; /* fall through */ case WIN_READ_NATIVE_MAX: ide_cmd_lba48_transform(s, lba48); ide_set_sector(s, s->nb_sectors - 1); s->status = READY_STAT; ide_set_irq(s); break; case WIN_CHECKPOWERMODE1: case WIN_CHECKPOWERMODE2: s->nsector = 0xff; /* device active or idle */ s->status = READY_STAT; ide_set_irq(s); break; case WIN_SETFEATURES: if (!s->bs) goto abort_cmd; /* XXX: valid for CDROM ? */ switch (s->feature) { case 0xcc: /* reverting to power-on defaults enable */ case 0x66: /* reverting to power-on defaults disable */ case 0x02: /* write cache enable */ case 0x82: /* write cache disable */ case 0xaa: /* read look-ahead enable */ case 0x55: /* read look-ahead disable */ case 0x05: /* set advanced power management mode */ case 0x85: /* disable advanced power management mode */ case 0x69: /* NOP */ case 0x67: /* NOP */ case 0x96: /* NOP */ case 0x9a: /* NOP */ case 0x42: /* enable Automatic Acoustic Mode */ case 0xc2: /* disable Automatic Acoustic Mode */ s->status = READY_STAT | SEEK_STAT; ide_set_irq(s); break; case 0x03: /* set transfer mode */ { uint8_t mval = s->nsector & 0x07; switch (s->nsector >> 3) { case 0x00: /* pio default */ case 0x01: /* pio mode */ put_le16(s->identify_data + 63,0x07); put_le16(s->identify_data + 88,0x3f); break; case 0x04: /* mdma mode */ put_le16(s->identify_data + 63,0x07 | (1 << (mval + 8))); put_le16(s->identify_data + 88,0x3f); break; case 0x08: /* udma mode */ put_le16(s->identify_data + 63,0x07); put_le16(s->identify_data + 88,0x3f | (1 << (mval + 8))); break; default: goto abort_cmd; } s->status = READY_STAT | SEEK_STAT; ide_set_irq(s); break; } default: goto abort_cmd; } break; case WIN_FLUSH_CACHE: case WIN_FLUSH_CACHE_EXT: if (s->bs) bdrv_flush(s->bs); s->status = READY_STAT; ide_set_irq(s); break; case WIN_STANDBY: case WIN_STANDBY2: case WIN_STANDBYNOW1: case WIN_STANDBYNOW2: case WIN_IDLEIMMEDIATE: case CFA_IDLEIMMEDIATE: case WIN_SETIDLE1: case WIN_SETIDLE2: case WIN_SLEEPNOW1: case WIN_SLEEPNOW2: s->status = READY_STAT; ide_set_irq(s); break; /* ATAPI commands */ case WIN_PIDENTIFY: if (s->is_cdrom) { ide_atapi_identify(s); s->status = READY_STAT | SEEK_STAT; ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop); } else { ide_abort_command(s); } ide_set_irq(s); break; case WIN_DIAGNOSE: ide_set_signature(s); s->status = 0x00; /* NOTE: READY is _not_ set */ s->error = 0x01; ide_set_irq(s); break; case WIN_SRST: if (!s->is_cdrom) goto abort_cmd; ide_set_signature(s); s->status = 0x00; /* NOTE: READY is _not_ set */ s->error = 0x01; break; case WIN_PACKETCMD: if (!s->is_cdrom) goto abort_cmd; /* overlapping commands not supported */ if (s->feature & 0x02) goto abort_cmd; s->status = READY_STAT; // s->atapi_dma = s->feature & 1; s->nsector = 1; ide_transfer_start(s, s->io_buffer, ATAPI_PACKET_SIZE, ide_atapi_cmd); break; default: abort_cmd: ide_abort_command(s); ide_set_irq(s); break; } } } /* IOport [R]ead [R]egisters */ enum ATA_IOPORT_RR { ATA_IOPORT_RR_DATA = 0, ATA_IOPORT_RR_ERROR = 1, ATA_IOPORT_RR_SECTOR_COUNT = 2, ATA_IOPORT_RR_SECTOR_NUMBER = 3, ATA_IOPORT_RR_CYLINDER_LOW = 4, ATA_IOPORT_RR_CYLINDER_HIGH = 5, ATA_IOPORT_RR_DEVICE_HEAD = 6, ATA_IOPORT_RR_STATUS = 7, ATA_IOPORT_RR_NUM_REGISTERS, }; const char *ATA_IOPORT_RR_lookup[ATA_IOPORT_RR_NUM_REGISTERS] = { [ATA_IOPORT_RR_DATA] = "Data", [ATA_IOPORT_RR_ERROR] = "Error", [ATA_IOPORT_RR_SECTOR_COUNT] = "Sector Count", [ATA_IOPORT_RR_SECTOR_NUMBER] = "Sector Number", [ATA_IOPORT_RR_CYLINDER_LOW] = "Cylinder Low", [ATA_IOPORT_RR_CYLINDER_HIGH] = "Cylinder High", [ATA_IOPORT_RR_DEVICE_HEAD] = "Device/Head", [ATA_IOPORT_RR_STATUS] = "Status" }; static uint32_t ide_ioport_read(IDEState *ide_if, uint32_t addr) { IDEState *s = ide_if->cur_drive; uint32_t reg_num; int ret, hob; reg_num = addr & 7; hob = ide_if[0].cmd & IDE_CTRL_HOB; switch (reg_num) { case ATA_IOPORT_RR_DATA: ret = 0xff; break; case ATA_IOPORT_RR_ERROR: if (!ide_if[0].bs && !ide_if[1].bs) ret = 0; else if (!hob) ret = s->error; else ret = s->hob_feature; break; case ATA_IOPORT_RR_SECTOR_COUNT: if (!ide_if[0].bs && !ide_if[1].bs) ret = 0; else if (!hob) ret = s->nsector & 0xff; else ret = s->hob_nsector; break; case ATA_IOPORT_RR_SECTOR_NUMBER: if (!ide_if[0].bs && !ide_if[1].bs) ret = 0; else if (!hob) ret = s->sector; else ret = s->hob_sector; break; case ATA_IOPORT_RR_CYLINDER_LOW: if (!ide_if[0].bs && !ide_if[1].bs) ret = 0; else if (!hob) ret = s->lcyl; else ret = s->hob_lcyl; break; case ATA_IOPORT_RR_CYLINDER_HIGH: if (!ide_if[0].bs && !ide_if[1].bs) ret = 0; else if (!hob) ret = s->hcyl; else ret = s->hob_hcyl; break; case ATA_IOPORT_RR_DEVICE_HEAD: if (!ide_if[0].bs && !ide_if[1].bs) ret = 0; else ret = s->select; break; default: case ATA_IOPORT_RR_STATUS: if ((!ide_if[0].bs && !ide_if[1].bs) || (s != ide_if && !s->bs)) ret = 0; else ret = s->status; /* Clear IRQ (set line to high) */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_FDC_HDC , MFP_GPIP_STATE_HIGH ); break; } LOG_TRACE(TRACE_IDE, "IDE: read addr=0x%x reg='%s' val=%02x\n", addr, ATA_IOPORT_RR_lookup[reg_num], ret); return ret; } static uint32_t ide_status_read(IDEState *ide_if, uint32_t addr) { IDEState *s = ide_if->cur_drive; int ret; if ((!ide_if[0].bs && !ide_if[1].bs) || (s != ide_if && !s->bs)) ret = 0; else ret = s->status; LOG_TRACE(TRACE_IDE, "IDE: read status addr=0x%x val=%02x\n", addr, ret); return ret; } static void ide_ctrl_write(IDEState *ide_if, uint32_t addr, uint32_t val) { IDEState *s; int i; LOG_TRACE(TRACE_IDE, "IDE: write control addr=0x%x val=%02x\n", addr, val); /* common for both drives */ if (!(ide_if[0].cmd & IDE_CTRL_RESET) && (val & IDE_CTRL_RESET)) { /* reset low to high */ for (i = 0;i < 2; i++) { s = &ide_if[i]; s->status = BUSY_STAT | SEEK_STAT; s->error = 0x01; } } else if ((ide_if[0].cmd & IDE_CTRL_RESET) && !(val & IDE_CTRL_RESET)) { /* high to low */ for (i = 0;i < 2; i++) { s = &ide_if[i]; if (s->is_cdrom) s->status = 0x00; /* NOTE: READY is _not_ set */ else s->status = READY_STAT | SEEK_STAT; ide_set_signature(s); } } ide_if[0].cmd = val; ide_if[1].cmd = val; } static void ide_data_writew(IDEState *ide_if, uint32_t addr, uint32_t val) { IDEState *s = ide_if->cur_drive; uint8_t *p; if (!s->data_ptr || s->data_ptr > s->data_end) return; p = s->data_ptr; *(uint16_t *)p = le16_to_cpu(val); p += 2; s->data_ptr = p; if (p >= s->data_end) s->end_transfer_func(s); } static uint32_t ide_data_readw(IDEState *ide_if, uint32_t addr) { IDEState *s = ide_if->cur_drive; uint8_t *p; int ret; if (!s->data_ptr || s->data_ptr > s->data_end) return 0xffff; p = s->data_ptr; ret = cpu_to_le16(*(uint16_t *)p); p += 2; s->data_ptr = p; if (p >= s->data_end) s->end_transfer_func(s); return ret; } static void ide_data_writel(IDEState *ide_if, uint32_t addr, uint32_t val) { IDEState *s = ide_if->cur_drive; uint8_t *p; if (!s->data_ptr || s->data_ptr > s->data_end) return; p = s->data_ptr; *(uint32_t *)p = le32_to_cpu(val); p += 4; s->data_ptr = p; if (p >= s->data_end) s->end_transfer_func(s); } static uint32_t ide_data_readl(IDEState *ide_if, uint32_t addr) { IDEState *s = ide_if->cur_drive; uint8_t *p; uint32_t ret; if (!s->data_ptr || s->data_ptr > s->data_end) return 0xffffffff; p = s->data_ptr; ret = cpu_to_le32(*(uint32_t *)p); p += 4; s->data_ptr = p; if (p >= s->data_end) s->end_transfer_func(s); return ret; } static void ide_dummy_transfer_stop(IDEState *s) { s->data_ptr = s->io_buffer; s->data_end = s->io_buffer; s->io_buffer[0] = 0xff; s->io_buffer[1] = 0xff; s->io_buffer[2] = 0xff; s->io_buffer[3] = 0xff; } static void ide_reset(IDEState *s) { s->mult_sectors = MAX_MULT_SECTORS; s->cur_drive = s; s->select = 0xa0; s->status = READY_STAT | SEEK_STAT; ide_set_signature(s); /* init the transfer handler so that 0xffff is returned on data accesses */ s->end_transfer_func = ide_dummy_transfer_stop; ide_dummy_transfer_stop(s); s->media_changed = 0; } struct partition { uint8_t boot_ind; /* 0x80 - active */ uint8_t head; /* starting head */ uint8_t sector; /* starting sector */ uint8_t cyl; /* starting cylinder */ uint8_t sys_ind; /* What partition type */ uint8_t end_head; /* end head */ uint8_t end_sector; /* end sector */ uint8_t end_cyl; /* end cylinder */ uint32_t start_sect; /* starting sector counting from 0 */ uint32_t nr_sects; /* nr of sectors in partition */ }; /* try to guess the disk logical geometry from the MSDOS partition table. Return 0 if OK, -1 if could not guess */ static int guess_disk_lchs(IDEState *s, int *pcylinders, int *pheads, int *psectors) { uint8_t *buf; int ret, i, heads, sectors, cylinders; struct partition *p; uint32_t nr_sects; buf = malloc(MAX_SECTOR_SIZE); if (buf == NULL) return -1; ret = bdrv_read(s->bs, 0, buf, 1); if (ret < 0) { free(buf); return -1; } /* test msdos magic */ if (buf[510] != 0x55 || buf[511] != 0xaa) { free(buf); return -1; } for (i = 0; i < 4; i++) { p = ((struct partition *)(buf + 0x1be)) + i; nr_sects = le32_to_cpu(p->nr_sects); if (nr_sects && p->end_head) { /* We make the assumption that the partition terminates on a cylinder boundary */ heads = p->end_head + 1; sectors = p->end_sector & 63; if (sectors == 0) continue; cylinders = s->nb_sectors / (heads * sectors); if (cylinders < 1 || cylinders > 16383) continue; *pheads = heads; *psectors = sectors; *pcylinders = cylinders; free(buf); return 0; } } free(buf); return -1; } static void ide_init_one(IDEState *s, BlockDriverState *bds) { static int drive_serial = 1; int cylinders, heads, secs, translation, lba_detected = 0; uint64_t nb_sectors; s->io_buffer = malloc(MAX_MULT_SECTORS * MAX_SECTOR_SIZE + 4); assert(s->io_buffer); s->bs = bds; bdrv_get_geometry(s->bs, &nb_sectors); s->nb_sectors = nb_sectors; /* if a geometry hint is available, use it */ bdrv_get_geometry_hint(s->bs, &cylinders, &heads, &secs); translation = bdrv_get_translation_hint(s->bs); if (cylinders != 0) { s->cylinders = cylinders; s->heads = heads; s->sectors = secs; } else { if (guess_disk_lchs(s, &cylinders, &heads, &secs) == 0) { if (heads > 16) { /* if heads > 16, it means that a BIOS LBA translation was active, so the default hardware geometry is OK */ lba_detected = 1; goto default_geometry; } else { s->cylinders = cylinders; s->heads = heads; s->sectors = secs; /* disable any translation to be in sync with the logical geometry */ if (translation == BIOS_ATA_TRANSLATION_AUTO) { bdrv_set_translation_hint(s->bs, BIOS_ATA_TRANSLATION_NONE); } } } else { default_geometry: /* if no geometry, use a standard physical disk geometry */ cylinders = nb_sectors / (16 * 63); if (cylinders > 16383) cylinders = 16383; else if (cylinders < 2) cylinders = 2; s->cylinders = cylinders; s->heads = 16; s->sectors = 63; if (lba_detected == 1 && translation == BIOS_ATA_TRANSLATION_AUTO) { if ((s->cylinders * s->heads) <= 131072) { bdrv_set_translation_hint(s->bs, BIOS_ATA_TRANSLATION_LARGE); } else { bdrv_set_translation_hint(s->bs, BIOS_ATA_TRANSLATION_LBA); } } } bdrv_set_geometry_hint(s->bs, s->cylinders, s->heads, s->sectors); } LOG_TRACE(TRACE_IDE, "IDE: using geometry LCHS=%d %d %d\n", s->cylinders, s->heads, s->sectors); if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) { s->is_cdrom = 1; bdrv_set_change_cb(s->bs, cdrom_change_cb, s); } s->drive_serial = drive_serial++; ide_reset(s); } /*----------------------------------------------------------------------------*/ static BlockDriverState *hd_table[2]; /** * Initialize the IDE subsystem */ void Ide_Init(void) { int i; if (!Ide_IsAvailable() ) return; memset(ide_state, 0, sizeof(ide_state)); for (i = 0; i < 2; i++) { hd_table[i] = malloc(sizeof(BlockDriverState)); assert(hd_table[i]); memset(hd_table[i], 0, sizeof(BlockDriverState)); ide_state[i].cur_drive = &ide_state[i]; if (ConfigureParams.Ide[i].bUseDevice) { int is_byteswap; if (bdrv_open(hd_table[i], ConfigureParams.Ide[i].sDeviceFile, ConfigureParams.Ide[i].nBlockSize, 0) < 0) { ConfigureParams.Ide[i].bUseDevice = false; continue; } nIDEPartitions += HDC_PartitionCount(hd_table[i]->fhndl, TRACE_IDE, &is_byteswap); /* Our IDE implementation is little endian by default, * so we need to byteswap if the image is not swapped! */ if (ConfigureParams.Ide[i].nByteSwap == BYTESWAP_AUTO) hd_table[i]->byteswap = !is_byteswap; else hd_table[i]->byteswap = !ConfigureParams.Ide[i].nByteSwap; LOG_TRACE(TRACE_IDE, "IDE: little->big endian byte-swapping %s for drive %d\n", hd_table[i]->byteswap ? "enabled" : "disabled", i); hd_table[i]->sector_size = ConfigureParams.Ide[i].nBlockSize; hd_table[i]->type = ConfigureParams.Ide[i].nDeviceType; ide_init_one(&ide_state[i], hd_table[i]); } } } /** * Free resources from the IDE subsystem */ void Ide_UnInit(void) { int i; for (i = 0; i < 2; i++) { if (hd_table[i]) { if (bdrv_is_inserted(hd_table[i])) { bdrv_close(hd_table[i]); } free(hd_table[i]); hd_table[i] = NULL; } } for (i = 0; i < 2; i++) { if (ide_state[i].io_buffer) { free(ide_state[i].io_buffer); ide_state[i].io_buffer = NULL; } } nIDEPartitions = 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ikbd.c000066400000000000000000003201221504763705000224050ustar00rootroot00000000000000/* Hatari - ikbd.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. The keyboard processor(6301) handles any joystick/mouse/keyboard task and sends bytes to the ACIA(6850). The IKBD has a small ROM which is used to process various commands send by the main CPU to the THE IKBD. Due to lack of real HD6301 emulation, those commands are handled by functionally equivalent code that tries to be as close as possible to a real HD6301. For program using their own HD6301 code, we also use some custom handlers to emulate the expected result. */ const char IKBD_fileid[] = "Hatari ikbd.c"; /* 2007/09/29 [NP] Use the new int.c to add interrupts with INT_CPU_CYCLE / INT_MFP_CYCLE. */ /* 2007/12/09 [NP] If reset is written to ACIA control register, we must call ACIA_Reset to reset */ /* RX/TX status. Reading the control register fffc00 just after a reset should */ /* return the value 0x02 (used in Transbeauce 2 demo loader). */ /* 2008/07/06 [NP] Add support for executing 8 bit code sent to the 6301 processor. */ /* Instead of implementing a full 6301 emulator, we compute a checksum for each */ /* program sent to the 6301 RAM. If the checksum is recognized, we call some */ /* functions to emulate the behaviour of the 6301 in that case. */ /* When the 6301 is in 'Execute' mode (command 0x22), we must stop the normal */ /* reporting of key/mouse/joystick and use our custom handlers for each read or */ /* write to $fffc02. */ /* After a reset command, returns $F1 after $F0 (needed by Dragonnels Demo). */ /* This fixes the Transbeauce 2 demo menu, the Dragonnels demo menu and the */ /* Froggies Over The Fence demo menu (yeah ! enjoy this master piece of demo !). */ /* 2011/05/11 [NP] Add proper support for emulating TX buffer empty/full in status register bit 1 */ /* when writing to $fffc02 (using an internal timer). */ /* 2011/07/14 [NP] Don't clear bytes in transit when ACIA_Reset is called ; if a byte is sent to */ /* the ikbd it should not be cancelled ? FIXME : this would need more tests on a */ /* real ST (fix Froggies Over The Fence's menu when selecting a demo). */ /* 2011/07/31 [NP] Don't clear bytes in transit in the ACIA when the IKBD is reset (fix Overdrive */ /* by Phalanx). */ /* 2011/12/27 [NP] When sending new bytes while a byte is already in transfer from ACIA to IKBD, */ /* don't restart the internal TX timer (fix 'Pandemonium Demos' Intro). */ /* eg : .loop : move.b d0,$fc02.w btst #1,$fc00.w beq.s .loop */ /* 2012/01/22 [NP] Enable both mouse and joystick reporting when commands 0x12 and 0x14 are */ /* received during the IKBD reset. */ /* 2012/02/26 [NP] Handle TX interrupt in the ACIA (eg by sending 0xb6 instead of 0x96 after */ /* resetting the ACIA) (fix the game 'Hades Nebula'). */ /* 2012/10/10 [NP] Use the new ACIA emulation in acia.c ; add support for the IKBD's SCI, which */ /* is similar to the ACIA, with fixed 8 data bits, 1 stop bit and no parity bit. */ /* 2012/12/23 [NP] Fix timings for the commands $16, $1C, $87-$9A. The first byte is returned */ /* between 'min' and 'max' cycles after receiving the full command. The delay */ /* is not fixed to simulate the slight variations measured on a real ST. */ /* 2012/12/24 [NP] Rewrite SetClock and ReadClock commands to behave like the real IKBD. */ /* Instead of using time()/localtime() to handle the clock, we now increment it */ /* on each VBL, taking care of the BCD data (overflows and such) like in the IKBD. */ /* (this new code is based on the HD6301 disassembly of the IKBD's ROM) */ /* 2013/01/02 [NP] - Use IKBD_OutputBuffer_CheckFreeCount to ensure there's enough room in the */ /* output buffer before sending an IKBD packet. If there's not enough bytes to */ /* transfer the whole packet, then the packet must be discarded. */ /* - Don't ignore a new RDR byte if the output buffer is not empty yet. The IKBD */ /* can handle new bytes asynchronously using some interrupt while still processing */ /* another command. New RDR is discarded only if the input buffer is full. */ /* 2013/01/13 [NP] For hardware and software reset, share the common code in IKBD_Boot_ROM(). */ /* 2014/07/06 [NP] Ignore command 0x13 IKBD_Cmd_StopKeyboardTransfer during ikbd's reset. This is */ /* required for the loader of 'Just Bugging' by ACF which sends 0x11 and 0x13 just */ /* after 0x80 0x01 (temporary fix, would need to be measured on a real STF to see */ /* if it's always ignored or just during a specific delay) */ /* 2015/10/10 [NP] When IKBD_Reset / IKBD_Boot_ROM are called, we should not restart the autosend */ /* handler INTERRUPT_IKBD_AUTOSEND if it's already set, else we can loose keyboard */ /* input if IKBD_Reset is called in a loop before any event could be processed */ /* (this could happen if a program called the 'RESET' instruction in a loop, then */ /* we lost the F12 key for example) (fix endless RESET when doing a warm reset */ /* (alt+r) during the 'Vodka Demo' by 'Equinox', only solution was to kill Hatari) */ /* 2017/10/27 [TS] Add Audio Sculpture 6301 program checksum and emulation */ #include #include "main.h" #include "configuration.h" #include "ikbd.h" #include "cycles.h" #include "cycInt.h" #include "ioMem.h" #include "joy.h" #include "m68000.h" #include "memorySnapShot.h" #include "mfp.h" #include "video.h" #include "utils.h" #include "acia.h" #include "clocks_timings.h" #define DBL_CLICK_HISTORY 0x07 /* Number of frames since last click to see if need to send one or two clicks */ #define ACIA_CYCLES 7200 /* Cycles (Multiple of 4) between sent to ACIA from keyboard along serial line - 500Hz/64, (approx' 6920-7200cycles from test program) */ #define ABS_X_ONRESET 0 /* Initial XY for absolute mouse position after RESET command */ #define ABS_Y_ONRESET 0 #define ABS_MAX_X_ONRESET 320 /* Initial absolute mouse limits after RESET command */ #define ABS_MAY_Y_ONRESET 200 /* These values are never actually used as user MUST call 'IKBD_Cmd_AbsMouseMode' before ever using them */ #define ABS_PREVBUTTONS (0x02|0x8) /* Don't report any buttons up on first call to 'IKBD_Cmd_ReadAbsMousePos' */ #define IKBD_RESET_CYCLES 502000 /* Number of cycles (for a 68000 at 8 MHz) between sending the reset command and receiving $F1 */ #define IKBD_ROM_VERSION 0xF1 /* On reset, the IKBD will return either 0xF0 or 0xF1, depending on the IKBD's ROM */ /* version. Only very early ST returned 0xF0, so we use 0xF1 which is the most common case.*/ /* Beside, some programs explicitly wait for 0xF1 after a reset (Dragonnels demo) */ /* Keyboard state */ KEYBOARD Keyboard; /* Keyboard processor */ KEYBOARD_PROCESSOR KeyboardProcessor; /* Keyboard processor details */ /* Pattern of mouse button up/down in ST frames (run off a double-click message) */ static const uint8_t DoubleClickPattern[] = { BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE, 0,0,0,0,BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE,BUTTON_MOUSE }; static bool bMouseDisabled, bJoystickDisabled; static bool bDuringResetCriticalTime, bBothMouseAndJoy; static bool bMouseEnabledDuringReset; /* HD6301 processor by Hitachi References : - HD6301V1, HD63A01V1, HD63B01V1 CMOS MCU datasheet by Hitachi The HD6301 is connected to the ACIA through TX and RX pins. Serial transfers are made with 8 bit word, 1 stop bit, no parity and 7812.5 baud The IKBD's ROM is using 2 buffers to handle input/output on the serial line in an asynchronous way, by using the SCI's interrupt at address $FEE2. This means the IKBD can execute a new command as soon as the current one is completed, as it is the interrupt function that will handle sending bytes to the ACIA. Input buffer : 8 bytes, located at $CD-$D4 in the IKBD's RAM. New bytes received in RDR are added to this buffer, until we have enough bytes to obtain a valid command (with its potential parameters) If the buffer already contains 8 bytes, new bytes are ignored (lost). This buffer is emptied if a valid command was processed or if the first byte in the buffer is not a valid command. Output buffer : 20 bytes as a ring buffer, located at $D9-$ED in the IKBD's RAM. When the IKBD automatically reports events or when a command returns some bytes, those 'n' bytes are added to the ring buffer. If the ring buffer doesn't have enough space to store 'n' new bytes, the 'n' bytes are ignored (lost). Each time a byte is correctly sent in TDR, a new byte is processed, until the ring buffer becomes empty. Special behaviours during the IKBD reset : If the following commands are received during the reset of the IKBD, the IKBD will go in a special mode and report both mouse and joystick at the same time : 0x08 0x14 relative mouse on , joysticks auto 0x08 0x0b 0x14 relative mouse on , mouse threshold , joysticks auto (eg Barbarian 1 by Psygnosis) 0x12 0x14 disable mouse , joysticks auto (eg Hammerfist) 0x12 0x1a disable mouse , disable joysticks In that case mouse and joystick buttons will be reported in a "mouse report" packet and joystick actions (except buttons) will be reported in a "joystick report" packet. */ static void IKBD_RunKeyboardCommand(uint8_t aciabyte); /* List of possible keyboard commands, others are seen as NOPs by keyboard processor */ static void IKBD_Cmd_Reset(void); static void IKBD_Cmd_MouseAction(void); static void IKBD_Cmd_RelMouseMode(void); static void IKBD_Cmd_AbsMouseMode(void); static void IKBD_Cmd_MouseCursorKeycodes(void); static void IKBD_Cmd_SetMouseThreshold(void); static void IKBD_Cmd_SetMouseScale(void); static void IKBD_Cmd_ReadAbsMousePos(void); static void IKBD_Cmd_SetInternalMousePos(void); static void IKBD_Cmd_SetYAxisDown(void); static void IKBD_Cmd_SetYAxisUp(void); static void IKBD_Cmd_StartKeyboardTransfer(void); static void IKBD_Cmd_TurnMouseOff(void); static void IKBD_Cmd_StopKeyboardTransfer(void); static void IKBD_Cmd_ReturnJoystickAuto(void); static void IKBD_Cmd_StopJoystick(void); static void IKBD_Cmd_ReturnJoystick(void); static void IKBD_Cmd_SetJoystickMonitoring(void); static void IKBD_Cmd_SetJoystickFireDuration(void); static void IKBD_Cmd_SetCursorForJoystick(void); static void IKBD_Cmd_DisableJoysticks(void); static void IKBD_Cmd_SetClock(void); static void IKBD_Cmd_ReadClock(void); static void IKBD_Cmd_LoadMemory(void); static void IKBD_Cmd_ReadMemory(void); static void IKBD_Cmd_Execute(void); static void IKBD_Cmd_ReportMouseAction(void); static void IKBD_Cmd_ReportMouseMode(void); static void IKBD_Cmd_ReportMouseThreshold(void); static void IKBD_Cmd_ReportMouseScale(void); static void IKBD_Cmd_ReportMouseVertical(void); static void IKBD_Cmd_ReportMouseAvailability(void); static void IKBD_Cmd_ReportJoystickMode(void); static void IKBD_Cmd_ReportJoystickAvailability(void); /* Keyboard Command */ static const struct { uint8_t Command; uint8_t NumParameters; void (*pCallFunction)(void); } KeyboardCommands[] = { /* Known messages, counts include command byte */ { 0x80,2, IKBD_Cmd_Reset }, { 0x07,2, IKBD_Cmd_MouseAction }, { 0x08,1, IKBD_Cmd_RelMouseMode }, { 0x09,5, IKBD_Cmd_AbsMouseMode }, { 0x0A,3, IKBD_Cmd_MouseCursorKeycodes }, { 0x0B,3, IKBD_Cmd_SetMouseThreshold }, { 0x0C,3, IKBD_Cmd_SetMouseScale }, { 0x0D,1, IKBD_Cmd_ReadAbsMousePos }, { 0x0E,6, IKBD_Cmd_SetInternalMousePos }, { 0x0F,1, IKBD_Cmd_SetYAxisDown }, { 0x10,1, IKBD_Cmd_SetYAxisUp }, { 0x11,1, IKBD_Cmd_StartKeyboardTransfer }, { 0x12,1, IKBD_Cmd_TurnMouseOff }, { 0x13,1, IKBD_Cmd_StopKeyboardTransfer }, { 0x14,1, IKBD_Cmd_ReturnJoystickAuto }, { 0x15,1, IKBD_Cmd_StopJoystick }, { 0x16,1, IKBD_Cmd_ReturnJoystick }, { 0x17,2, IKBD_Cmd_SetJoystickMonitoring }, { 0x18,1, IKBD_Cmd_SetJoystickFireDuration }, { 0x19,7, IKBD_Cmd_SetCursorForJoystick }, { 0x1A,1, IKBD_Cmd_DisableJoysticks }, { 0x1B,7, IKBD_Cmd_SetClock }, { 0x1C,1, IKBD_Cmd_ReadClock }, { 0x20,4, IKBD_Cmd_LoadMemory }, { 0x21,3, IKBD_Cmd_ReadMemory }, { 0x22,3, IKBD_Cmd_Execute }, /* Report message (top bit set) */ { 0x87,1, IKBD_Cmd_ReportMouseAction }, { 0x88,1, IKBD_Cmd_ReportMouseMode }, { 0x89,1, IKBD_Cmd_ReportMouseMode }, { 0x8A,1, IKBD_Cmd_ReportMouseMode }, { 0x8B,1, IKBD_Cmd_ReportMouseThreshold }, { 0x8C,1, IKBD_Cmd_ReportMouseScale }, { 0x8F,1, IKBD_Cmd_ReportMouseVertical }, { 0x90,1, IKBD_Cmd_ReportMouseVertical }, { 0x92,1, IKBD_Cmd_ReportMouseAvailability }, { 0x94,1, IKBD_Cmd_ReportJoystickMode }, { 0x95,1, IKBD_Cmd_ReportJoystickMode }, { 0x99,1, IKBD_Cmd_ReportJoystickMode }, { 0x9A,1, IKBD_Cmd_ReportJoystickAvailability }, { 0xFF,0, NULL } /* Term */ }; /*----------------------------------------------------------------------*/ /* Variables/defines/functions used to transfer data between the */ /* IKBD's SCI and the ACIA. */ /*----------------------------------------------------------------------*/ #define IKBD_TRCSR_BIT_WU 0x01 /* Wake Up */ #define IKBD_TRCSR_BIT_TE 0x02 /* Transmit Enable */ #define IKBD_TRCSR_BIT_TIE 0x04 /* Transmit Interrupt Enable */ #define IKBD_TRCSR_BIT_RE 0x08 /* Receive Enable */ #define IKBD_TRCSR_BIT_RIE 0x10 /* Receive Interrupt Enable */ #define IKBD_TRCSR_BIT_TDRE 0x20 /* Transmit Data Register Empty */ #define IKBD_TRCSR_BIT_ORFE 0x40 /* Over Run Framing Error */ #define IKBD_TRCSR_BIT_RDRF 0x80 /* Receive Data Register Full */ /* Possible states when handling TX/RX in the IKBD's Serial Communication Interface */ enum { IKBD_SCI_STATE_IDLE = 0, IKBD_SCI_STATE_DATA_BIT, IKBD_SCI_STATE_STOP_BIT }; typedef struct { /* IKBD's SCI internal registers */ uint8_t RMCR; /* reg 0x10 : Rate and Mode Control Register */ uint8_t TRCSR; /* reg 0x11 : Transmit/Receive Control and Status Register */ uint8_t TDR; /* reg 0x12 : Transmit Data Register */ uint8_t RDR; /* reg 0x13 : Receive Data Register */ int SCI_TX_State; uint8_t TSR; /* Transmit Shift Register */ uint8_t SCI_TX_Size; /* How many data bits left to transmit in TSR (8 .. 0) */ int SCI_TX_Delay; /* If >0, wait SCI_TX_Delay calls of IKBD_SCI_Set_Line_TX before */ /* transferring a new byte in TDR (to simulate the time needed by */ /* the IKBD to process a command and return the result) */ int SCI_RX_State; uint8_t RSR; /* Receive Shift Register */ uint8_t SCI_RX_Size; /* How many bits left to receive in RSR (8 .. 0) */ /* Date/Time is stored in the IKBD using 6 bytes in BCD format */ /* Clock is cleared on cold reset, but keeps its values on warm reset */ /* Original RAM location : $82=year $83=month $84=day $85=hour $86=minute $87=second */ uint8_t Clock[ 6 ]; int64_t Clock_micro; /* Incremented every VBL to update Clock[] every second */ } IKBD_STRUCT; static IKBD_STRUCT IKBD; static IKBD_STRUCT *pIKBD = &IKBD; static void IKBD_Init_Pointers ( ACIA_STRUCT *pACIA_IKBD ); static void IKBD_Boot_ROM ( bool ClearAllRAM ); static void IKBD_SCI_Get_Line_RX ( int rx_bit ); static uint8_t IKBD_SCI_Set_Line_TX ( void ); static void IKBD_Process_RDR ( uint8_t RDR ); static void IKBD_Check_New_TDR ( void ); static bool IKBD_OutputBuffer_CheckFreeCount ( int Nb ); static int IKBD_Delay_Random ( int min , int max ); static void IKBD_Cmd_Return_Byte ( uint8_t Data ); static void IKBD_Cmd_Return_Byte_Delay ( uint8_t Data , int Delay_Cycles ); static void IKBD_Send_Byte_Delay ( uint8_t Data , int Delay_Cycles ); static bool IKBD_BCD_Check ( uint8_t val ); static uint8_t IKBD_BCD_Adjust ( uint8_t val ); /*-----------------------------------------------------------------------*/ /* Belows part is used to emulate the behaviour of custom 6301 programs */ /* sent to the IKBD's RAM. */ /*-----------------------------------------------------------------------*/ static void IKBD_LoadMemoryByte ( uint8_t aciabyte ); static void IKBD_CustomCodeHandler_CommonBoot ( uint8_t aciabyte ); static void IKBD_CustomCodeHandler_FroggiesMenu_Read ( void ); static void IKBD_CustomCodeHandler_FroggiesMenu_Write ( uint8_t aciabyte ); static void IKBD_CustomCodeHandler_Transbeauce2Menu_Read ( void ); static void IKBD_CustomCodeHandler_Transbeauce2Menu_Write ( uint8_t aciabyte ); static void IKBD_CustomCodeHandler_DragonnelsMenu_Read ( void ); static void IKBD_CustomCodeHandler_DragonnelsMenu_Write ( uint8_t aciabyte ); static void IKBD_CustomCodeHandler_ChaosAD_Read ( void ); static void IKBD_CustomCodeHandler_ChaosAD_Write ( uint8_t aciabyte ); static void IKBD_CustomCodeHandler_AudioSculpture_Color_Read ( void ); static void IKBD_CustomCodeHandler_AudioSculpture_Mono_Read ( void ); static void IKBD_CustomCodeHandler_AudioSculpture_Read ( bool ColorMode ); static void IKBD_CustomCodeHandler_AudioSculpture_Write ( uint8_t aciabyte ); static int MemoryLoadNbBytesTotal = 0; /* total number of bytes to send with the command 0x20 */ static int MemoryLoadNbBytesLeft = 0; /* number of bytes that remain to be sent */ static uint32_t MemoryLoadCrc = 0xffffffff; /* CRC of the bytes sent to the IKBD */ static int MemoryExeNbBytes = 0; /* current number of bytes sent to the IKBD when IKBD_ExeMode is true */ static void (*pIKBD_CustomCodeHandler_Read) ( void ); static void (*pIKBD_CustomCodeHandler_Write) ( uint8_t ); static bool IKBD_ExeMode = false; static uint8_t ScanCodeState[ 128 ]; /* state of each key : 0=released 1=pressed */ /* This array contains all known custom 6301 programs, with their CRC */ static const struct { uint32_t LoadMemCrc; /* CRC of the bytes sent using the command 0x20 */ void (*ExeBootHandler) ( uint8_t ); /* function handling write to $fffc02 during the 'boot' mode */ int MainProgNbBytes; /* number of bytes of the main 6301 program */ uint32_t MainProgCrc; /* CRC of the main 6301 program */ void (*ExeMainHandler_Read) ( void );/* function handling read to $fffc02 in the main 6301 program */ void (*ExeMainHandler_Write) ( uint8_t ); /* function handling write to $fffc02 in the main 6301 program */ const char *Name; } CustomCodeDefinitions[] = { { 0x2efb11b1 , IKBD_CustomCodeHandler_CommonBoot , 167, 0xe7110b6d , IKBD_CustomCodeHandler_FroggiesMenu_Read , IKBD_CustomCodeHandler_FroggiesMenu_Write , "Froggies Over The Fence Main Menu" } , { 0xadb6b503 , IKBD_CustomCodeHandler_CommonBoot , 165, 0x5617c33c , IKBD_CustomCodeHandler_Transbeauce2Menu_Read , IKBD_CustomCodeHandler_Transbeauce2Menu_Write , "Transbeauce 2 Main Menu" } , { 0x33c23cdf , IKBD_CustomCodeHandler_CommonBoot , 83 , 0xdf3e5a88 , IKBD_CustomCodeHandler_DragonnelsMenu_Read , IKBD_CustomCodeHandler_DragonnelsMenu_Write , "Dragonnels Main Menu" }, { 0x9ad7fcdf , IKBD_CustomCodeHandler_CommonBoot , 109 , 0xa11d8be5 , IKBD_CustomCodeHandler_ChaosAD_Read , IKBD_CustomCodeHandler_ChaosAD_Write , "Chaos A.D." }, { 0xbc0c206d, IKBD_CustomCodeHandler_CommonBoot , 91 , 0x119b26ed , IKBD_CustomCodeHandler_AudioSculpture_Color_Read , IKBD_CustomCodeHandler_AudioSculpture_Write , "Audio Sculpture Color" }, { 0xbc0c206d , IKBD_CustomCodeHandler_CommonBoot , 91 , 0x63b5f4df , IKBD_CustomCodeHandler_AudioSculpture_Mono_Read , IKBD_CustomCodeHandler_AudioSculpture_Write , "Audio Sculpture Mono" } }; /*-----------------------------------------------------------------------*/ /** * Init the IKBD processor. * Connect the IKBD RX/TX callback functions to the ACIA. * This is called only once, when the emulator starts. */ void IKBD_Init ( void ) { LOG_TRACE ( TRACE_IKBD_ALL, "ikbd init\n" ); /* Set the callback functions for RX/TX line */ IKBD_Init_Pointers ( pACIA_IKBD ); } /*-----------------------------------------------------------------------*/ /** * Init some functions/memory pointers for the IKBD. * This is called at Init and when restoring a memory snapshot. */ static void IKBD_Init_Pointers ( ACIA_STRUCT *pACIA_IKBD ) { pACIA_IKBD->Get_Line_RX = IKBD_SCI_Set_Line_TX; /* Connect ACIA's RX to IKBD SCI's TX */ pACIA_IKBD->Set_Line_TX = IKBD_SCI_Get_Line_RX; /* Connect ACIA's TX to IKBD SCI's RX */ } /*-----------------------------------------------------------------------*/ /** * Reset the IKBD processor */ /* This function is called after a hardware reset of the IKBD. * Cold reset is when the computer is turned off/on. * Warm reset is when the reset button is pressed or the 68000 * RESET instruction is used. * We clear the serial interface and we execute the function * that emulates booting the ROM at 0xF000. */ void IKBD_Reset ( bool bCold ) { LOG_TRACE ( TRACE_IKBD_ALL , "ikbd reset mode=%s\n" , bCold?"cold":"warm" ); /* Reset the SCI */ pIKBD->TRCSR = IKBD_TRCSR_BIT_TDRE; pIKBD->SCI_TX_State = IKBD_SCI_STATE_IDLE; pIKBD->TSR = 0; pIKBD->SCI_TX_Size = 0; pIKBD->SCI_TX_Delay = 0; pIKBD->SCI_RX_State = IKBD_SCI_STATE_IDLE; pIKBD->RSR = 0; pIKBD->SCI_RX_Size = 0; /* On cold reset, clear the whole RAM (including clock data) */ /* On warm reset, the clock data should be kept */ if ( bCold ) IKBD_Boot_ROM ( true ); else IKBD_Boot_ROM ( false ); } /* This function emulates the boot code stored in the ROM at address $F000. * This boot code is called either after a hardware reset, or when the * reset command ($80 $01) is received. * Depending on the conditions, we should clear the clock data or not (the * real IKBD will test+clear RAM either in range $80-$FF or in range $89-$FF) */ static void IKBD_Boot_ROM ( bool ClearAllRAM ) { int i; LOG_TRACE ( TRACE_IKBD_ALL , "ikbd boot rom clear_all=%s\n" , ClearAllRAM?"yes":"no" ); /* Clear clock data when the 128 bytes of RAM are cleared */ if ( ClearAllRAM ) { /* Clear clock data on cold reset */ for ( i=0 ; i<6 ; i++ ) pIKBD->Clock[ i ] = 0; pIKBD->Clock_micro = 0; } // pIKBD->Clock[ 0 ] = 0x99; // pIKBD->Clock[ 1 ] = 0x12; // pIKBD->Clock[ 2 ] = 0x31; // pIKBD->Clock[ 3 ] = 0x23; // pIKBD->Clock[ 4 ] = 0x59; // pIKBD->Clock[ 5 ] = 0x57; /* Set default reporting mode for mouse/joysticks */ KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL; KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK; KeyboardProcessor.Abs.X = ABS_X_ONRESET; KeyboardProcessor.Abs.Y = ABS_Y_ONRESET; KeyboardProcessor.Abs.MaxX = ABS_MAX_X_ONRESET; KeyboardProcessor.Abs.MaxY = ABS_MAY_Y_ONRESET; KeyboardProcessor.Abs.PrevReadAbsMouseButtons = ABS_PREVBUTTONS; KeyboardProcessor.Mouse.DeltaX = KeyboardProcessor.Mouse.DeltaY = 0; KeyboardProcessor.Mouse.XScale = KeyboardProcessor.Mouse.YScale = 0; KeyboardProcessor.Mouse.XThreshold = KeyboardProcessor.Mouse.YThreshold = 1; KeyboardProcessor.Mouse.KeyCodeDeltaX = KeyboardProcessor.Mouse.KeyCodeDeltaY = 1; KeyboardProcessor.Mouse.YAxis = 1; /* Y origin at top */ KeyboardProcessor.Mouse.Action = 0; KeyboardProcessor.Joy.PrevJoyData[0] = KeyboardProcessor.Joy.PrevJoyData[1] = 0; for ( i=0 ; i<128 ; i++ ) ScanCodeState[ i ] = 0; /* key is released */ /* Reset our keyboard states and clear key state table */ Keyboard.BufferHead = Keyboard.BufferTail = 0; Keyboard.NbBytesInOutputBuffer = 0; Keyboard.nBytesInInputBuffer = 0; Keyboard.PauseOutput = false; memset(Keyboard.KeyStates, 0, sizeof(Keyboard.KeyStates)); Keyboard.bLButtonDown = BUTTON_NULL; Keyboard.bRButtonDown = BUTTON_NULL; Keyboard.bOldLButtonDown = Keyboard.bOldRButtonDown = BUTTON_NULL; Keyboard.LButtonDblClk = Keyboard.RButtonDblClk = 0; Keyboard.LButtonHistory = Keyboard.RButtonHistory = 0; /* Store bool for when disable mouse or joystick */ bMouseDisabled = bJoystickDisabled = false; /* do emulate hardware 'quirk' where if disable both with 'x' time * of a RESET command they are ignored! */ bDuringResetCriticalTime = true; bBothMouseAndJoy = false; bMouseEnabledDuringReset = false; /* Remove any custom handlers used to emulate code loaded to the 6301's RAM */ if ( ( MemoryLoadNbBytesLeft != 0 ) || ( IKBD_ExeMode == true ) ) { LOG_TRACE ( TRACE_IKBD_ALL , "ikbd stop memory load and turn off custom exe\n" ); MemoryLoadNbBytesLeft = 0; pIKBD_CustomCodeHandler_Read = NULL; pIKBD_CustomCodeHandler_Write = NULL; IKBD_ExeMode = false; } /* During the boot, the IKBD will test all the keys to ensure no key */ /* is stuck. We use a timer to emulate the time needed for this part */ /* (eg Lotus Turbo Esprit 2 requires at least a delay of 50000 cycles */ /* or it will crash during start up) */ CycInt_AddRelativeInterrupt( IKBD_RESET_CYCLES , INT_CPU8_CYCLE , INTERRUPT_IKBD_RESETTIMER ); /* Add auto-update function to the queue */ /* We add it only if it was not active, else this can lead to unresponsive keyboard/input */ /* when RESET instruction is called in a loop in less than 150000 cycles */ Keyboard.AutoSendCycles = 150000; /* approx every VBL */ if ( CycInt_InterruptActive ( INTERRUPT_IKBD_AUTOSEND ) == false ) CycInt_AddRelativeInterrupt ( Keyboard.AutoSendCycles, INT_CPU8_CYCLE, INTERRUPT_IKBD_AUTOSEND ); LOG_TRACE ( TRACE_IKBD_ALL , "ikbd reset done, starting reset timer\n" ); } /*-----------------------------------------------------------------------*/ /** * This timer is started by IKBD_Boot_ROM to emulate the time needed * to setup the IKBD in its default state after a reset. * If some IKBD commands are received during the boot phase they may be ignored. */ void IKBD_InterruptHandler_ResetTimer(void) { LOG_TRACE(TRACE_IKBD_ALL, "ikbd reset timer completed, resuming ikbd processing VBLs=%i framecyc=%i\n", nVBLs, Video_GetCyclesSinceVbl()); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Reset timer is over */ bDuringResetCriticalTime = false; bMouseEnabledDuringReset = false; /* Return $F1 when IKBD's boot is complete */ IKBD_Cmd_Return_Byte_Delay ( IKBD_ROM_VERSION , IKBD_Delay_Random ( 0 , 3000 ) ); } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables * ('MemorySnapShot_Store' handles type) */ void IKBD_MemorySnapShot_Capture(bool bSave) { unsigned int i; /* Save/Restore details */ MemorySnapShot_Store(&Keyboard, sizeof(Keyboard)); MemorySnapShot_Store(&KeyboardProcessor, sizeof(KeyboardProcessor)); MemorySnapShot_Store(&bMouseDisabled, sizeof(bMouseDisabled)); MemorySnapShot_Store(&bJoystickDisabled, sizeof(bJoystickDisabled)); MemorySnapShot_Store(&bDuringResetCriticalTime, sizeof(bDuringResetCriticalTime)); MemorySnapShot_Store(&bBothMouseAndJoy, sizeof(bBothMouseAndJoy)); MemorySnapShot_Store(&bMouseEnabledDuringReset, sizeof(bMouseEnabledDuringReset)); /* restore custom 6301 program if needed */ MemorySnapShot_Store(&IKBD_ExeMode, sizeof(IKBD_ExeMode)); MemorySnapShot_Store(&MemoryLoadCrc, sizeof(MemoryLoadCrc)); if ((bSave == false) && (IKBD_ExeMode == true)) /* restoring a snapshot with active 6301 emulation */ { for ( i = 0 ; i < sizeof ( CustomCodeDefinitions ) / sizeof ( CustomCodeDefinitions[0] ); i++ ) if ( CustomCodeDefinitions[ i ].MainProgCrc == MemoryLoadCrc ) { pIKBD_CustomCodeHandler_Read = CustomCodeDefinitions[ i ].ExeMainHandler_Read; pIKBD_CustomCodeHandler_Write = CustomCodeDefinitions[ i ].ExeMainHandler_Write; Keyboard.BufferHead = Keyboard.BufferTail = 0; /* flush all queued bytes that would be read in $fffc02 */ Keyboard.NbBytesInOutputBuffer = 0; break; } if ( i >= sizeof ( CustomCodeDefinitions ) / sizeof ( CustomCodeDefinitions[0] ) ) /* not found (should not happen) */ IKBD_ExeMode = false; /* turn off exe mode */ } /* Save the IKBD's SCI part and restore the callback functions for RX/TX lines with the ACIA */ MemorySnapShot_Store(&IKBD, sizeof(IKBD)); if (!bSave) /* Restoring a snapshot */ { IKBD_Init_Pointers ( pACIA_IKBD ); } } /************************************************************************/ /* This part emulates the IKBD's Serial Communication Interface. */ /* This is a simplified implementation that ignores the RMCR content, */ /* as we assume the IKBD and the ACIA will be using the same baud rate. */ /* The TX/RX baud rate is chosen at the ACIA level, and the IKBD will */ /* use the same rate. */ /* The SCI only supports 8 bits of data, with 1 start bit, 1 stop bit */ /* and no parity bit. */ /************************************************************************/ /*-----------------------------------------------------------------------*/ /** * Prepare a new transfer. Copy TDR to TSR and initialize data size. * Transfer will then start at the next call of IKBD_SCI_Set_Line_TX. */ static void IKBD_SCI_Prepare_TX ( IKBD_STRUCT *pIKBD ) { pIKBD->TSR = pIKBD->TDR; pIKBD->SCI_TX_Size = 8; pIKBD->TRCSR |= IKBD_TRCSR_BIT_TDRE; /* TDR was copied to TSR. TDR is now empty */ LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia prepare tx tsr=0x%02x size=%d VBL=%d HBL=%d\n" , pIKBD->TSR , pIKBD->SCI_TX_Size , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Prepare a new reception. Initialize RSR and data size. */ static void IKBD_SCI_Prepare_RX ( IKBD_STRUCT *pIKBD ) { pIKBD->RSR = 0; pIKBD->SCI_RX_Size = 8; LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia prepare rx size=%d VBL=%d HBL=%d\n" , pIKBD->SCI_RX_Size , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Receive a bit on the IKBD SCI's RX line (this is connected to the ACIA's TX) * This will fill RDR with bits received from the serial line, using RSR. * Incoming bits are stored in bit 7 of RSR, then RSR is shifted to the right. * This is similar to the ACIA's RX, but with fixed parameters : 8 data bits, * no parity bit and 1 stop bit. */ static void IKBD_SCI_Get_Line_RX ( int rx_bit ) { int StateNext; LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia rx_state=%d bit=%d VBL=%d HBL=%d\n" , pIKBD->SCI_RX_State , rx_bit , nVBLs , nHBL ); StateNext = -1; switch ( pIKBD->SCI_RX_State ) { case IKBD_SCI_STATE_IDLE : if ( rx_bit == 0 ) /* Receive one "0" start bit */ { IKBD_SCI_Prepare_RX ( pIKBD ); StateNext = IKBD_SCI_STATE_DATA_BIT; } break; /* If no start bit, we stay in idle state */ case IKBD_SCI_STATE_DATA_BIT : if ( rx_bit ) pIKBD->RSR |= 0x80; pIKBD->SCI_RX_Size--; if ( pIKBD->SCI_RX_Size > 0 ) /* All bits were not received yet */ pIKBD->RSR >>= 1; else StateNext = IKBD_SCI_STATE_STOP_BIT; break; case IKBD_SCI_STATE_STOP_BIT : if ( rx_bit == 1 ) /* Wait for one "1" stop bit */ { pIKBD->TRCSR &= ~IKBD_TRCSR_BIT_ORFE; if ( ( pIKBD->TRCSR & IKBD_TRCSR_BIT_RDRF ) == 0 ) { pIKBD->RDR = pIKBD->RSR; pIKBD->TRCSR |= IKBD_TRCSR_BIT_RDRF; LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia get_rx received rsr=0x%02x VBL=%d HBL=%d\n" , pIKBD->RDR , nVBLs , nHBL ); IKBD_Process_RDR ( pIKBD->RDR ); /* Process this new byte */ } else { pIKBD->TRCSR |= IKBD_TRCSR_BIT_ORFE; /* Overrun Error */ LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia get_rx received rsr=0x%02x : ignored, rdr=0x%02x and rdrf already set VBL=%d HBL=%d\n" , pIKBD->RSR , pIKBD->RDR , nVBLs , nHBL ); IKBD_Process_RDR ( pIKBD->RDR ); /* RSR is lost, try to process the current RDR which was not read yet */ } StateNext = IKBD_SCI_STATE_IDLE; /* Go to idle state and wait for start bit */ } else /* Not a valid stop bit */ { LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia get_rx framing error VBL=%d HBL=%d\n" , nVBLs , nHBL ); pIKBD->TRCSR |= IKBD_TRCSR_BIT_ORFE; /* Framing Error */ StateNext = IKBD_SCI_STATE_IDLE; /* Go to idle state and wait for start bit */ } break; } if ( StateNext >= 0 ) pIKBD->SCI_RX_State = StateNext; /* Go to a new state */ } /*-----------------------------------------------------------------------*/ /** * Send a bit on the IKBD SCI's TX line (this is connected to the ACIA's RX) * When the SCI is idle, we send '1' stop bits. */ static uint8_t IKBD_SCI_Set_Line_TX ( void ) { int StateNext; uint8_t tx_bit = 1; LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia tx_state=%d tx_delay=%d VBL=%d HBL=%d\n" , pIKBD->SCI_TX_State , pIKBD->SCI_TX_Delay , nVBLs , nHBL ); StateNext = -1; switch ( pIKBD->SCI_TX_State ) { case IKBD_SCI_STATE_IDLE : tx_bit = 1; /* In idle state, default is to send '1' stop bits */ if ( pIKBD->SCI_TX_Delay > 0 ) /* Should we delay the next TDR ? */ { pIKBD->SCI_TX_Delay--; /* Don't do anything for now, send a stop bit */ break; } IKBD_Check_New_TDR (); /* Do we have a byte to load in TDR ? */ if ( ( pIKBD->TRCSR & IKBD_TRCSR_BIT_TDRE ) == 0 ) /* We have a new byte in TDR */ { IKBD_SCI_Prepare_TX ( pIKBD ); tx_bit = 0; /* Send one '0' start bit */ StateNext = IKBD_SCI_STATE_DATA_BIT; } break; case IKBD_SCI_STATE_DATA_BIT : tx_bit = pIKBD->TSR & 1; /* New bit to send */ pIKBD->TSR >>= 1; pIKBD->SCI_TX_Size--; if ( pIKBD->SCI_TX_Size == 0 ) StateNext = IKBD_SCI_STATE_STOP_BIT; break; case IKBD_SCI_STATE_STOP_BIT : tx_bit = 1; /* Send 1 stop bit */ StateNext = IKBD_SCI_STATE_IDLE; /* Go to idle state to see if a new TDR need to be sent */ break; } if ( StateNext >= 0 ) pIKBD->SCI_TX_State = StateNext; /* Go to a new state */ return tx_bit; } /*-----------------------------------------------------------------------*/ /** * Handle the byte that was received in the RDR from the ACIA. * Depending on the IKBD's emulation mode, we either pass it to the standard * ROM's emulation layer, or we pass it to the custom handlers. */ static void IKBD_Process_RDR ( uint8_t RDR ) { pIKBD->TRCSR &= ~IKBD_TRCSR_BIT_RDRF; /* RDR was read */ /* If IKBD is executing custom code, send the byte to the function handling this code */ if ( IKBD_ExeMode && pIKBD_CustomCodeHandler_Write ) { (*pIKBD_CustomCodeHandler_Write) ( RDR ); return; } if ( MemoryLoadNbBytesLeft == 0 ) /* No pending MemoryLoad command */ IKBD_RunKeyboardCommand ( RDR ); /* Check for known commands */ else /* MemoryLoad command is not finished yet */ IKBD_LoadMemoryByte ( RDR ); /* Process bytes sent to the IKBD's RAM */ } /*-----------------------------------------------------------------------*/ /** * Check if we have a byte to copy to the IKBD's TDR, to send it to the ACIA. * We get new bytes from the buffer filled by IKBD_Send_Byte_Delay */ static void IKBD_Check_New_TDR ( void ) { // fprintf(stderr , "check new tdr %d %d\n", Keyboard.BufferHead , Keyboard.BufferTail ); if ( ( Keyboard.NbBytesInOutputBuffer > 0 ) && ( Keyboard.PauseOutput == false ) ) { pIKBD->TDR = Keyboard.Buffer[ Keyboard.BufferHead++ ]; Keyboard.BufferHead &= KEYBOARD_BUFFER_MASK; Keyboard.NbBytesInOutputBuffer--; pIKBD->TRCSR &= ~IKBD_TRCSR_BIT_TDRE; } } /*-----------------------------------------------------------------------*/ /** * Return true if the output buffer can store 'Nb' new bytes, * else return false. * Some games like 'Downfall' or 'Fokker' are continually issuing the same * IKBD_Cmd_ReturnJoystick command without waiting for the returned bytes, * which will fill the output buffer faster than the CPU can empty it. * In that case, new messages must be discarded until the buffer has some room * again for a whole packet. */ static bool IKBD_OutputBuffer_CheckFreeCount ( int Nb ) { // fprintf ( stderr , "check %d %d head %d tail %d\n" , Nb , SIZE_KEYBOARD_BUFFER - Keyboard.NbBytesInOutputBuffer , // Keyboard.BufferHead , Keyboard.BufferTail ); if ( SIZE_KEYBOARD_BUFFER - Keyboard.NbBytesInOutputBuffer >= Nb ) return true; else { LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia output buffer is full, can't send %d bytes VBL=%d HBL=%d\n" , Nb, nVBLs , nHBL ); return false; } } /*-----------------------------------------------------------------------*/ /** * Return a random number between 'min' and 'max'. * This is used when the IKBD send bytes to the ACIA, to add some * randomness to the delay (on real hardware, the delay is not constant * when a command return some bytes). */ static int IKBD_Delay_Random ( int min , int max ) { return min + Hatari_rand() % ( max - min ); } /*-----------------------------------------------------------------------*/ /** * This function will buffer all the bytes returned by a specific * IKBD_Cmd_xxx command. If we're using a custom handler, we should filter * these bytes (keyboard, mouse, joystick) as they don't come from the custom handler. */ static void IKBD_Cmd_Return_Byte ( uint8_t Data ) { if ( IKBD_ExeMode ) /* If IKBD is executing custom code, don't add */ return; /* anything to the buffer that comes from an IKBD's command */ IKBD_Send_Byte_Delay ( Data , 0 ); } /** * Same as IKBD_Cmd_Return_Byte, but with a delay before transmitting * the byte. */ static void IKBD_Cmd_Return_Byte_Delay ( uint8_t Data , int Delay_Cycles ) { if ( IKBD_ExeMode ) /* If IKBD is executing custom code, don't add */ return; /* anything to the buffer that comes from an IKBD's command */ IKBD_Send_Byte_Delay ( Data , Delay_Cycles ); } /*-----------------------------------------------------------------------*/ /** * Send bytes from the IKBD to the ACIA. We store the bytes in a buffer * and we pull a new byte each time TDR needs to be re-filled. * * A possible delay can be specified to simulate the fact that some IKBD's * commands don't return immediately the first byte. This delay is given * in 68000 cycles at 8 MHz and should be converted to a number of bits * at the chosen baud rate. */ static void IKBD_Send_Byte_Delay ( uint8_t Data , int Delay_Cycles ) { //fprintf ( stderr , "send byte=0x%02x delay=%d\n" , Data , Delay_Cycles ); /* Is keyboard initialised yet ? Ignore any bytes until it is */ if ( bDuringResetCriticalTime ) { LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd is resetting, can't send byte=0x%02x VBL=%d HBL=%d\n" , Data, nVBLs , nHBL ); return; } /* Is ACIA's serial line initialised yet ? Ignore any bytes until it is */ if ( pACIA_IKBD->Clock_Divider == 0 ) { LOG_TRACE ( TRACE_IKBD_ACIA, "ikbd acia not initialized, can't send byte=0x%02x VBL=%d HBL=%d\n" , Data, nVBLs , nHBL ); return; } if ( Delay_Cycles > 0 ) pIKBD->SCI_TX_Delay = Delay_Cycles / 1024; /* 1 bit at 7812.5 baud = 1024 cpu cycles at 8 MHz */ /* Check we have space to add one byte */ if ( IKBD_OutputBuffer_CheckFreeCount ( 1 ) ) { /* Add byte to our buffer */ Keyboard.Buffer[Keyboard.BufferTail++] = Data; Keyboard.BufferTail &= KEYBOARD_BUFFER_MASK; Keyboard.NbBytesInOutputBuffer++; } else { Log_Printf(LOG_ERROR, "IKBD buffer is full, can't send 0x%02x!\n" , Data ); } } /************************************************************************/ /* End of the Serial Communication Interface */ /************************************************************************/ /** * Check that the value is a correctly encoded BCD number */ static bool IKBD_BCD_Check ( uint8_t val ) { if ( ( ( val & 0x0f ) > 0x09 ) || ( ( val & 0xf0 ) > 0x90 ) ) return false; return true; } /** * After adding an integer number to a BCD number, the result is no more * in BCD format. This function adjusts the value to be a valid BCD number again. * In the HD6301, this is done using the 'DAA' instruction (Decimal Adjust) * to "propagate" values 10-15 to the next 4 bits and keep each nibble * in the 0-9 range. */ static uint8_t IKBD_BCD_Adjust ( uint8_t val ) { if ( ( val & 0x0f ) > 0x09 ) /* low nibble no more in BCD */ val += 0x06; /* clear bit 4 and add 1 to high nibble */ if ( ( val & 0xf0 ) > 0x90 ) /* high nibble no more in BCD */ val += 0x60; /* propagate carry (but bits>7 will be lost) */ return val; } /** * Update the IKBD's internal clock. * * This function is called on every VBL and we add the number of microseconds * per VBL. When we reach 1000000 microseconds (1 sec), we update the Clock[] * array by incrementing the 'second' byte. * * This code uses the same logic as the ROM version in the IKBD, * don't try to optimise/rewrite it in a different way, as the TOS * expects data to be handled this way. * This works directly with BCD numbers and propagates the increment * to the next byte each time the current byte reaches its maximum * value. * - when SetClock is used, the IKBD doesn't check the range of each byte, * just that it's BCD encoded. So it's possible to set month/day/... to * invalid values beyond the maximum allowed. These values will not correctly * propagate to the next byte until they reach 0x99 and start again at 0x00. * - check leap year for the number of days in february if ( year & 3 == 0 ) * - there's no explicit max for year : if year is 99 and increments, * next year will be 00 (due to the BCD overflow) * (used in the game 'Captain Blood' which sets clock to "99 12 31 00 00 00" * and ends the game when clock reaches "00 01 01 00 00 00") */ void IKBD_UpdateClockOnVBL ( void ) { int64_t FrameDuration_micro; int i; uint8_t val; uint8_t max; uint8_t year; uint8_t month; /* Max value for year/month/day/hour/minute/second */ uint8_t val_max[ 6 ] = { 0xFF , 0x13 , 0x00 , 0x24 , 0x60 , 0x60 }; /* Max number of days per month ; 18 entries, because the index for this array is a BCD coded month */ uint8_t day_max[ 18 ] = { 0x32, 0x29, 0x32, 0x31, 0x32, 0x31, 0x32, 0x32, 0x31, 0,0,0,0,0,0, 0x32, 0x31, 0x32 }; /* Check if more than 1 second passed since last increment of date/time */ FrameDuration_micro = ClocksTimings_GetVBLDuration_micro ( ConfigureParams.System.nMachineType , nScreenRefreshRate ); pIKBD->Clock_micro += FrameDuration_micro; if ( pIKBD->Clock_micro < 1000000 ) return; /* Less than 1 second, don't increment date/time yet */ pIKBD->Clock_micro -= 1000000; /* 1 second passed, we can increment the clock data */ // LOG_TRACE(TRACE_IKBD_CMDS, // "IKBD_UpdateClock: %02x %02x %02x %02x %02x %02x -> ", pIKBD->Clock[ 0 ] ,pIKBD->Clock[ 1 ] , pIKBD->Clock[ 2 ] , // pIKBD->Clock[ 3 ] , pIKBD->Clock[ 4 ] , pIKBD->Clock[ 5 ] ); for ( i=5 ; i>=0 ; i-- ) { val = pIKBD->Clock[ i ] + 1; /* Increment current value */ val = IKBD_BCD_Adjust ( val ); /* Convert to BCD */ if ( i != 2 ) max = val_max[ i ]; else /* Special case for days per month */ { /* WARNING : it's possible to set the IKBD with month > 0x12, but in that case */ /* we would access day_max[] out of range. So, if month > 0x12, we limit to 31 days */ /* (this test is not done in the IKBD, but results would not be correct anyway) */ month = pIKBD->Clock[ 1 ]; if ( month > 0x12 ) /* Hatari specific, check range */ month = 0x12; max = day_max[ month - 1 ]; /* Number of days for current month */ if ( pIKBD->Clock[ 1 ] == 2 ) /* For february, check leap year */ { year = pIKBD->Clock[ 0 ]; /* Leap year test comes from the IKBD's ROM */ if ( year & 0x10 ) year += 0x0a; if ( ( year & 0x03 ) == 0 ) max = 0x30; /* This is a leap year, 29 days */ } } if ( val != max ) { pIKBD->Clock[ i ] = val; /* Max not reached, stop here */ break; } else if ( ( i == 1 ) || ( i == 2 ) ) pIKBD->Clock[ i ] = 1; /* day/month start at 1 */ else pIKBD->Clock[ i ] = 0; /* hour/minute/second start at 0 */ } // LOG_TRACE(TRACE_IKBD_CMDS, // "%02x %02x %02x %02x %02x %02x\n", pIKBD->Clock[ 0 ] ,pIKBD->Clock[ 1 ] , pIKBD->Clock[ 2 ] , // pIKBD->Clock[ 3 ] , pIKBD->Clock[ 4 ] , pIKBD->Clock[ 5 ] ); } /*-----------------------------------------------------------------------*/ /** * Calculate out 'delta' that mouse has moved by each frame, and add this to our internal keyboard position */ static void IKBD_UpdateInternalMousePosition(void) { KeyboardProcessor.Mouse.DeltaX = KeyboardProcessor.Mouse.dx; KeyboardProcessor.Mouse.DeltaY = KeyboardProcessor.Mouse.dy; KeyboardProcessor.Mouse.dx = 0; KeyboardProcessor.Mouse.dy = 0; /* Update internal mouse coords - Y axis moves according to YAxis setting(up/down) */ /* Limit to Max X/Y(inclusive) */ if ( KeyboardProcessor.Mouse.XScale > 1 ) KeyboardProcessor.Abs.X += KeyboardProcessor.Mouse.DeltaX * KeyboardProcessor.Mouse.XScale; else KeyboardProcessor.Abs.X += KeyboardProcessor.Mouse.DeltaX; if (KeyboardProcessor.Abs.X < 0) KeyboardProcessor.Abs.X = 0; if (KeyboardProcessor.Abs.X > KeyboardProcessor.Abs.MaxX) KeyboardProcessor.Abs.X = KeyboardProcessor.Abs.MaxX; if ( KeyboardProcessor.Mouse.YScale > 1 ) KeyboardProcessor.Abs.Y += KeyboardProcessor.Mouse.DeltaY*KeyboardProcessor.Mouse.YAxis * KeyboardProcessor.Mouse.YScale; else KeyboardProcessor.Abs.Y += KeyboardProcessor.Mouse.DeltaY*KeyboardProcessor.Mouse.YAxis; if (KeyboardProcessor.Abs.Y < 0) KeyboardProcessor.Abs.Y = 0; if (KeyboardProcessor.Abs.Y > KeyboardProcessor.Abs.MaxY) KeyboardProcessor.Abs.Y = KeyboardProcessor.Abs.MaxY; } /*-----------------------------------------------------------------------*/ /** * When running in maximum speed the emulation will not see 'double-clicks' * of the mouse as it is running so fast. In this case, we check for a * double-click and pass the 'up'/'down' messages in emulation time to * simulate the double-click effect! */ static void IKBD_CheckForDoubleClicks(void) { /* Things get a little complicated when running max speed as a normal double-click is a load of 1's, followed by 0's, 1's and 0's - But the ST does not see this as a double click as the space in 'ST' time between changes is so great. Now, when we see a real double-click in max speed we actually send the down/up/down/up in ST time. To get this correct (and not send three clicks) we look in a history buffer and start at an index which gives the correct number of clicks! Phew! */ /* Handle double clicks!!! */ if (Keyboard.LButtonDblClk) { if (Keyboard.LButtonDblClk == 1) /* First pressed! */ { if ((Keyboard.LButtonHistory&0x3f) == 0) /* If not pressed button in long time do full dbl-click pattern */ Keyboard.LButtonDblClk = 1; else { Keyboard.LButtonDblClk = 4; /* Otherwise, check where to begin to give 1111000011110000 pattern */ if ((Keyboard.LButtonHistory&0x7) == 0) Keyboard.LButtonDblClk = 8; else if ((Keyboard.LButtonHistory&0x3) == 0) Keyboard.LButtonDblClk = 7; else if ((Keyboard.LButtonHistory&0x1) == 0) Keyboard.LButtonDblClk = 6; } } Keyboard.bLButtonDown = DoubleClickPattern[Keyboard.LButtonDblClk]; Keyboard.LButtonDblClk++; if (Keyboard.LButtonDblClk >= ARRAY_SIZE(DoubleClickPattern)) { Keyboard.LButtonDblClk = 0; Keyboard.bLButtonDown = false; } } if (Keyboard.RButtonDblClk) { if (Keyboard.RButtonDblClk == 1) /* First pressed! */ { if ((Keyboard.RButtonHistory&0x3f) == 0) /* If not pressed button in long time do full dbl-click pattern */ Keyboard.RButtonDblClk = 1; else { Keyboard.RButtonDblClk = 4; /* Otherwise, check where to begin to give 1111000011110000 pattern */ if ((Keyboard.RButtonHistory&0x7) == 0) Keyboard.RButtonDblClk = 8; else if ((Keyboard.RButtonHistory&0x3) == 0) Keyboard.RButtonDblClk = 7; else if ((Keyboard.RButtonHistory&0x1) == 0) Keyboard.RButtonDblClk = 6; } } Keyboard.bRButtonDown = DoubleClickPattern[Keyboard.RButtonDblClk]; Keyboard.RButtonDblClk++; if (Keyboard.RButtonDblClk >= ARRAY_SIZE(DoubleClickPattern)) { Keyboard.RButtonDblClk = 0; Keyboard.bRButtonDown = false; } } /* Store presses into history */ Keyboard.LButtonHistory = (Keyboard.LButtonHistory<<1); if (Keyboard.bLButtonDown) Keyboard.LButtonHistory |= 0x1; Keyboard.RButtonHistory = (Keyboard.RButtonHistory<<1); if (Keyboard.bRButtonDown) Keyboard.RButtonHistory |= 0x1; } /*-----------------------------------------------------------------------*/ /** * Convert button to bool value */ static bool IKBD_ButtonBool(int Button) { /* Button pressed? */ if (Button) return true; return false; } /*-----------------------------------------------------------------------*/ /** * Return true if buttons match, use this as buttons are a mask and not boolean */ static bool IKBD_ButtonsEqual(int Button1,int Button2) { /* Return bool compare */ return (IKBD_ButtonBool(Button1) == IKBD_ButtonBool(Button2)); } /*-----------------------------------------------------------------------*/ /** * According to if the mouse is enabled or not the joystick 1 fire * button/right mouse button will become the same button. That means * pressing one will also press the other and vice-versa. * If both mouse and joystick are enabled, report it as a mouse button * (needed by the game Big Run for example). */ static void IKBD_DuplicateMouseFireButtons(void) { /* If mouse is off then joystick fire button goes to joystick */ if (KeyboardProcessor.MouseMode == AUTOMODE_OFF) { /* If pressed right mouse button, should go to joystick 1 */ if (Keyboard.bRButtonDown&BUTTON_MOUSE) KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1] |= ATARIJOY_BITMASK_FIRE; /* And left mouse button, should go to joystick 0 */ if (Keyboard.bLButtonDown&BUTTON_MOUSE) KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK0] |= ATARIJOY_BITMASK_FIRE; } /* If mouse is on, joystick 1 fire button goes to the mouse instead */ else { /* Is fire button pressed? */ if (KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1]&ATARIJOY_BITMASK_FIRE) { KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1] &= ~ATARIJOY_BITMASK_FIRE; /* Clear fire button bit */ Keyboard.bRButtonDown |= BUTTON_JOYSTICK; /* Mimic right mouse button */ } else Keyboard.bRButtonDown &= ~BUTTON_JOYSTICK; } } /*-----------------------------------------------------------------------*/ /** * Send 'relative' mouse position * In case DeltaX or DeltaY are more than 127 units, we send the position * using several packets (with a while loop) */ static void IKBD_SendRelMousePacket(void) { int ByteRelX,ByteRelY; uint8_t Header; while ( true ) { ByteRelX = KeyboardProcessor.Mouse.DeltaX; if ( ByteRelX > 127 ) ByteRelX = 127; if ( ByteRelX < -128 ) ByteRelX = -128; ByteRelY = KeyboardProcessor.Mouse.DeltaY; if ( ByteRelY > 127 ) ByteRelY = 127; if ( ByteRelY < -128 ) ByteRelY = -128; if ( ( ( ByteRelX < 0 ) && ( ByteRelX <= -KeyboardProcessor.Mouse.XThreshold ) ) || ( ( ByteRelX > 0 ) && ( ByteRelX >= KeyboardProcessor.Mouse.XThreshold ) ) || ( ( ByteRelY < 0 ) && ( ByteRelY <= -KeyboardProcessor.Mouse.YThreshold ) ) || ( ( ByteRelY > 0 ) && ( ByteRelY >= KeyboardProcessor.Mouse.YThreshold ) ) || ( !IKBD_ButtonsEqual(Keyboard.bOldLButtonDown,Keyboard.bLButtonDown ) ) || ( !IKBD_ButtonsEqual(Keyboard.bOldRButtonDown,Keyboard.bRButtonDown ) ) ) { Header = 0xf8; if (Keyboard.bLButtonDown) Header |= 0x02; if (Keyboard.bRButtonDown) Header |= 0x01; if ( IKBD_OutputBuffer_CheckFreeCount ( 3 ) ) { IKBD_Cmd_Return_Byte (Header); IKBD_Cmd_Return_Byte (ByteRelX); IKBD_Cmd_Return_Byte (ByteRelY*KeyboardProcessor.Mouse.YAxis); } KeyboardProcessor.Mouse.DeltaX -= ByteRelX; KeyboardProcessor.Mouse.DeltaY -= ByteRelY; /* Store buttons for next time around */ Keyboard.bOldLButtonDown = Keyboard.bLButtonDown; Keyboard.bOldRButtonDown = Keyboard.bRButtonDown; } else break; /* exit the while loop */ } } /** * Get joystick data */ static void IKBD_GetJoystickData(void) { /* Joystick 1 */ KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1] = Joy_GetStickData(JOYID_JOYSTICK1); /* If mouse is on, joystick 0 is not connected */ if (KeyboardProcessor.MouseMode==AUTOMODE_OFF || (bBothMouseAndJoy && KeyboardProcessor.MouseMode==AUTOMODE_MOUSEREL)) KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK0] = Joy_GetStickData(JOYID_JOYSTICK0); else KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK0] = 0x00; } /*-----------------------------------------------------------------------*/ /** * Send 'joysticks' bit masks */ static void IKBD_SendAutoJoysticks(void) { uint8_t JoyData; /* Did joystick 0/mouse change? */ JoyData = KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK0]; if (JoyData!=KeyboardProcessor.Joy.PrevJoyData[JOYID_JOYSTICK0]) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { IKBD_Cmd_Return_Byte (0xFE); /* Joystick 0 / Mouse */ IKBD_Cmd_Return_Byte (JoyData); } KeyboardProcessor.Joy.PrevJoyData[JOYID_JOYSTICK0] = JoyData; } /* Did joystick 1(default) change? */ JoyData = KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1]; if (JoyData!=KeyboardProcessor.Joy.PrevJoyData[JOYID_JOYSTICK1]) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { IKBD_Cmd_Return_Byte (0xFF); /* Joystick 1 */ IKBD_Cmd_Return_Byte (JoyData); } KeyboardProcessor.Joy.PrevJoyData[JOYID_JOYSTICK1] = JoyData; } } /*-----------------------------------------------------------------------*/ /** * Send 'joysticks' bit masks when in monitoring mode * %000000xy ; where y is JOYSTICK1 Fire button * ; and x is JOYSTICK0 Fire button * %nnnnmmmm ; where m is JOYSTICK1 state * ; and n is JOYSTICK0 state */ static void IKBD_SendAutoJoysticksMonitoring(void) { uint8_t Byte1; uint8_t Byte2; Byte1 = ( ( KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK0] & ATARIJOY_BITMASK_FIRE ) >> 6 ) | ( ( KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1] & ATARIJOY_BITMASK_FIRE ) >> 7 ); Byte2 = ( ( KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK0] & 0x0f ) << 4 ) | ( KeyboardProcessor.Joy.JoyData[JOYID_JOYSTICK1] & 0x0f ); IKBD_Cmd_Return_Byte (Byte1); IKBD_Cmd_Return_Byte (Byte2); //fprintf ( stderr , "joystick monitoring %x %x VBL=%d HBL=%d\n" , Byte1 , Byte2 , nVBLs , nHBL ); } /*-----------------------------------------------------------------------*/ /** * Send packets which are generated from the mouse action settings * If relative mode is on, still generate these packets */ static void IKBD_SendOnMouseAction(void) { bool bReportPosition = false; /* Report buttons as keys? Do in relative/absolute mode */ if (KeyboardProcessor.Mouse.Action&0x4) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { /* Left button? */ if ( (IKBD_ButtonBool(Keyboard.bLButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldLButtonDown))) ) IKBD_Cmd_Return_Byte (0x74); /* Left */ else if ( (IKBD_ButtonBool(Keyboard.bOldLButtonDown) && (!IKBD_ButtonBool(Keyboard.bLButtonDown))) ) IKBD_Cmd_Return_Byte (0x74|0x80); /* Right button? */ if ( (IKBD_ButtonBool(Keyboard.bRButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldRButtonDown))) ) IKBD_Cmd_Return_Byte (0x75); /* Right */ else if ( (IKBD_ButtonBool(Keyboard.bOldRButtonDown) && (!IKBD_ButtonBool(Keyboard.bRButtonDown))) ) IKBD_Cmd_Return_Byte (0x75|0x80); } /* Ignore bottom two bits, so return now */ return; } /* Check MouseAction - report position on press/release */ /* MUST do this before update relative positions as buttons get reset */ if (KeyboardProcessor.Mouse.Action&0x3) { /* Check for 'press'? */ if (KeyboardProcessor.Mouse.Action&0x1) { /* Did 'press' mouse buttons? */ if ( (IKBD_ButtonBool(Keyboard.bLButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldLButtonDown))) ) { bReportPosition = true; KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x04; KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x02; } if ( (IKBD_ButtonBool(Keyboard.bRButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldRButtonDown))) ) { bReportPosition = true; KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x01; KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x08; } } /* Check for 'release'? */ if (KeyboardProcessor.Mouse.Action&0x2) { /* Did 'release' mouse buttons? */ if ( (IKBD_ButtonBool(Keyboard.bOldLButtonDown) && (!IKBD_ButtonBool(Keyboard.bLButtonDown))) ) { bReportPosition = true; KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x08; KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x01; } if ( (IKBD_ButtonBool(Keyboard.bOldRButtonDown) && (!IKBD_ButtonBool(Keyboard.bRButtonDown))) ) { bReportPosition = true; KeyboardProcessor.Abs.PrevReadAbsMouseButtons &= ~0x02; KeyboardProcessor.Abs.PrevReadAbsMouseButtons |= 0x04; } } /* Do need to report? */ if (bReportPosition) { /* Only report if mouse in absolute mode */ if (KeyboardProcessor.MouseMode==AUTOMODE_MOUSEABS) { LOG_TRACE(TRACE_IKBD_CMDS, "Report ABS on MouseAction\n"); IKBD_Cmd_ReadAbsMousePos(); } } } } /*-----------------------------------------------------------------------*/ /** * Send mouse movements as cursor keys */ static void IKBD_SendCursorMousePacket(void) { int i=0; /* Run each 'Delta' as cursor presses */ /* Limit to '10' loops as host mouse cursor might have a VERY poor quality. */ /* Eg, a single mouse movement on and ST gives delta's of '1', mostly, */ /* but host mouse might go as high as 20+! */ //fprintf ( stderr , "key %d %d %d %d %d %d\n" , KeyboardProcessor.Mouse.DeltaX , KeyboardProcessor.Mouse.DeltaY, Keyboard.bOldLButtonDown,Keyboard.bLButtonDown, Keyboard.bOldRButtonDown,Keyboard.bRButtonDown ); while ( (i<10) && ((KeyboardProcessor.Mouse.DeltaX!=0) || (KeyboardProcessor.Mouse.DeltaY!=0) || (!IKBD_ButtonsEqual(Keyboard.bOldLButtonDown,Keyboard.bLButtonDown)) || (!IKBD_ButtonsEqual(Keyboard.bOldRButtonDown,Keyboard.bRButtonDown))) ) { if ( KeyboardProcessor.Mouse.DeltaX != 0 ) { /* Left? */ if (KeyboardProcessor.Mouse.DeltaX <= -KeyboardProcessor.Mouse.KeyCodeDeltaX) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { IKBD_Cmd_Return_Byte (75); /* Left cursor */ IKBD_Cmd_Return_Byte (75|0x80); } KeyboardProcessor.Mouse.DeltaX += KeyboardProcessor.Mouse.KeyCodeDeltaX; } /* Right? */ if (KeyboardProcessor.Mouse.DeltaX >= KeyboardProcessor.Mouse.KeyCodeDeltaX) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { IKBD_Cmd_Return_Byte (77); /* Right cursor */ IKBD_Cmd_Return_Byte (77|0x80); } KeyboardProcessor.Mouse.DeltaX -= KeyboardProcessor.Mouse.KeyCodeDeltaX; } } if ( KeyboardProcessor.Mouse.DeltaY != 0 ) { /* Up? */ if (KeyboardProcessor.Mouse.DeltaY <= -KeyboardProcessor.Mouse.KeyCodeDeltaY) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { IKBD_Cmd_Return_Byte (72); /* Up cursor */ IKBD_Cmd_Return_Byte (72|0x80); } KeyboardProcessor.Mouse.DeltaY += KeyboardProcessor.Mouse.KeyCodeDeltaY; } /* Down? */ if (KeyboardProcessor.Mouse.DeltaY >= KeyboardProcessor.Mouse.KeyCodeDeltaY) { if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { IKBD_Cmd_Return_Byte (80); /* Down cursor */ IKBD_Cmd_Return_Byte (80|0x80); } KeyboardProcessor.Mouse.DeltaY -= KeyboardProcessor.Mouse.KeyCodeDeltaY; } } if ( IKBD_OutputBuffer_CheckFreeCount ( 2 ) ) { /* Left button? */ if ( (IKBD_ButtonBool(Keyboard.bLButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldLButtonDown))) ) IKBD_Cmd_Return_Byte (0x74); /* Left */ else if ( (IKBD_ButtonBool(Keyboard.bOldLButtonDown) && (!IKBD_ButtonBool(Keyboard.bLButtonDown))) ) IKBD_Cmd_Return_Byte (0x74|0x80); /* Right button? */ if ( (IKBD_ButtonBool(Keyboard.bRButtonDown) && (!IKBD_ButtonBool(Keyboard.bOldRButtonDown))) ) IKBD_Cmd_Return_Byte (0x75); /* Right */ else if ( (IKBD_ButtonBool(Keyboard.bOldRButtonDown) && (!IKBD_ButtonBool(Keyboard.bRButtonDown))) ) IKBD_Cmd_Return_Byte (0x75|0x80); } Keyboard.bOldLButtonDown = Keyboard.bLButtonDown; Keyboard.bOldRButtonDown = Keyboard.bRButtonDown; /* Count, so exit if try too many times! */ i++; } } /*-----------------------------------------------------------------------*/ /** * Return packets from keyboard for auto, rel mouse, joystick etc... */ static void IKBD_SendAutoKeyboardCommands(void) { /* Don't do anything until processor is first reset */ if ( bDuringResetCriticalTime ) return; /* Read joysticks for this frame */ IKBD_GetJoystickData(); /* Check for double-clicks in maximum speed mode */ IKBD_CheckForDoubleClicks(); /* Handle Joystick/Mouse fire buttons */ IKBD_DuplicateMouseFireButtons(); /* Send any packets which are to be reported by mouse action */ IKBD_SendOnMouseAction(); /* Update internal mouse absolute position by find 'delta' of mouse movement */ IKBD_UpdateInternalMousePosition(); /* If IKBD is monitoring only joysticks, don't report other events */ if ( KeyboardProcessor.JoystickMode == AUTOMODE_JOYSTICK_MONITORING ) { IKBD_SendAutoJoysticksMonitoring(); return; } /* Send automatic joystick packets */ if (KeyboardProcessor.JoystickMode==AUTOMODE_JOYSTICK) IKBD_SendAutoJoysticks(); /* Send automatic relative mouse positions(absolute are not send automatically) */ if (KeyboardProcessor.MouseMode==AUTOMODE_MOUSEREL) IKBD_SendRelMousePacket(); /* Send cursor key directions for movements */ else if (KeyboardProcessor.MouseMode==AUTOMODE_MOUSECURSOR) IKBD_SendCursorMousePacket(); /* Store buttons for next time around */ Keyboard.bOldLButtonDown = Keyboard.bLButtonDown; Keyboard.bOldRButtonDown = Keyboard.bRButtonDown; /* Send joystick button '2' as 'Space bar' key - MUST do here so does not get mixed up in middle of joystick packets! */ if (JoystickSpaceBar==JOYSTICK_SPACE_DOWN) { IKBD_PressSTKey(57, true); /* Press */ JoystickSpaceBar = JOYSTICK_SPACE_DOWNED; /* Pressed */ } else if (JoystickSpaceBar==JOYSTICK_SPACE_UP) { IKBD_PressSTKey(57, false); /* Release */ JoystickSpaceBar = JOYSTICK_SPACE_NULL; /* Complete */ } /* If we're executing a custom IKBD program, call it to process the key/mouse/joystick event */ if ( IKBD_ExeMode && pIKBD_CustomCodeHandler_Read ) (*pIKBD_CustomCodeHandler_Read) (); } /*-----------------------------------------------------------------------*/ /** * When press/release key under host OS, execute this function. */ void IKBD_PressSTKey(uint8_t ScanCode, bool bPress) { /* If IKBD is monitoring only joysticks, don't report key */ if ( KeyboardProcessor.JoystickMode == AUTOMODE_JOYSTICK_MONITORING ) return; /* Store the state of each ST scancode : 1=pressed 0=released */ if ( bPress ) ScanCodeState[ ScanCode & 0x7f ] = 1; else ScanCodeState[ ScanCode & 0x7f ] = 0; if (!bPress) ScanCode |= 0x80; /* Set top bit if released key */ if ( IKBD_OutputBuffer_CheckFreeCount ( 1 ) ) { IKBD_Cmd_Return_Byte (ScanCode); /* Add to the IKBD's output buffer */ } /* If we're executing a custom IKBD program, call it to process the key event */ if ( IKBD_ExeMode && pIKBD_CustomCodeHandler_Read ) (*pIKBD_CustomCodeHandler_Read) (); } /*-----------------------------------------------------------------------*/ /** * Check if a key is pressed in the ScanCodeState array * Return the scancode >= 0 for the first key we find, else return -1 * if no key is pressed */ static int IKBD_CheckPressedKey(void) { unsigned int i; for (i=0 ; i 20) { /* Send automatic keyboard packets for mouse, joysticks etc... */ IKBD_SendAutoKeyboardCommands(); } } /*-----------------------------------------------------------------------*/ /** * On ST if disable Mouse AND Joystick with a set time of a RESET command they are * actually turned back on! (A number of games do this so can get mouse and joystick * packets at the same time) */ static void IKBD_CheckResetDisableBug(void) { /* Have disabled BOTH mouse and joystick? */ if (bMouseDisabled && bJoystickDisabled) { /* And in critical time? */ if (bDuringResetCriticalTime) { /* Emulate relative mouse and joystick reports being turned back on */ KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL; KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK; bBothMouseAndJoy = true; LOG_TRACE(TRACE_IKBD_ALL, "ikbd cancel commands 0x12 and 0x1a received during reset," " enabling joystick and mouse reporting at the same time\n" ); } } } /*-----------------------------------------------------------------------*/ /** * When a byte is received by the IKBD, it is added to a small 8 byte buffer. * - If the first byte is a valid command, we wait for additional bytes if needed * and then we execute the command's handler. * - If the first byte is not a valid command or after a successful command, we * empty the input buffer (extra bytes, if any, are lost) * - If the input buffer is full when a new byte is received, the new byte is lost. * - In case the first byte read is not a valid command then IKBD does nothing * (it doesn't return any byte to indicate the command was not recognized) */ static void IKBD_RunKeyboardCommand(uint8_t aciabyte) { int i=0; /* Write into our keyboard input buffer if it's not full yet */ if ( Keyboard.nBytesInInputBuffer < SIZE_KEYBOARDINPUT_BUFFER ) Keyboard.InputBuffer[Keyboard.nBytesInInputBuffer++] = aciabyte; /* Now check bytes to see if we have a valid/in-valid command string set */ while (KeyboardCommands[i].Command!=0xff) { /* Found command? */ if (KeyboardCommands[i].Command==Keyboard.InputBuffer[0]) { /* If the command is complete (with its possible parameters) we can execute it */ /* Else, we wait for the next bytes until the command is complete */ if (KeyboardCommands[i].NumParameters==Keyboard.nBytesInInputBuffer) { /* Any new valid command will unpause the output (if command 0x13 was used) */ Keyboard.PauseOutput = false; CALL_VAR(KeyboardCommands[i].pCallFunction); Keyboard.nBytesInInputBuffer = 0; /* Clear input buffer after processing a command */ } return; } i++; } /* Command not known, reset buffer(IKBD assumes a NOP) */ Keyboard.nBytesInInputBuffer = 0; } /************************************************************************/ /* List of keyboard commands handled by the standard IKBD's ROM. */ /* Each IKBD's command is emulated to get the same result as if we were */ /* running a full HD6301 emulation. */ /************************************************************************/ /*-----------------------------------------------------------------------*/ /** * RESET * * 0x80 * 0x01 * * Performs self test and checks for stuck (closed) keys, if OK returns * IKBD_ROM_VERSION (0xF1). Otherwise returns break codes for keys (not emulated). */ static void IKBD_Cmd_Reset(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_Reset VBLs=%i framecyc=%i\n", nVBLs, Video_GetCyclesSinceVbl()); /* Check that 0x01 was received after 0x80 */ if (Keyboard.InputBuffer[1] == 0x01) { IKBD_Boot_ROM ( false ); } /* else if not 0x80,0x01 just ignore */ } /*-----------------------------------------------------------------------*/ /** * SET MOUSE BUTTON ACTION * * 0x07 * %00000mss ; mouse button action * ; (m is presumed =1 when in MOUSE KEYCODE mode) * ; mss=0xy, mouse button press or release causes mouse * ; position report * ; where y=1, mouse key press causes absolute report * ; and x=1, mouse key release causes absolute report * ; mss=100, mouse buttons act like keys */ static void IKBD_Cmd_MouseAction(void) { KeyboardProcessor.Mouse.Action = Keyboard.InputBuffer[1]; KeyboardProcessor.Abs.PrevReadAbsMouseButtons = ABS_PREVBUTTONS; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_MouseAction %d\n", (unsigned int)KeyboardProcessor.Mouse.Action); } /*-----------------------------------------------------------------------*/ /** * SET RELATIVE MOUSE POSITION REPORTING * * 0x08 */ static void IKBD_Cmd_RelMouseMode(void) { KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL; /* Some games (like Barbarian by Psygnosis) enable both, mouse and * joystick directly after a reset. This causes the IKBD to send both * type of packets. To emulate this feature, we've got to remember * that the mouse has been enabled during reset. */ if (bDuringResetCriticalTime) bMouseEnabledDuringReset = true; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_RelMouseMode\n"); } /*-----------------------------------------------------------------------*/ /** * SET ABSOLUTE MOUSE POSITIONING * * 0x09 * XMSB ;X maximum (in scaled mouse clicks) * XLSB * YMSB ;Y maximum (in scaled mouse clicks) * YLSB */ static void IKBD_Cmd_AbsMouseMode(void) { /* These maximums are 'inclusive' */ KeyboardProcessor.MouseMode = AUTOMODE_MOUSEABS; KeyboardProcessor.Abs.MaxX = (((unsigned int)Keyboard.InputBuffer[1])<<8) | (unsigned int)Keyboard.InputBuffer[2]; KeyboardProcessor.Abs.MaxY = (((unsigned int)Keyboard.InputBuffer[3])<<8) | (unsigned int)Keyboard.InputBuffer[4]; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_AbsMouseMode %d,%d\n", KeyboardProcessor.Abs.MaxX, KeyboardProcessor.Abs.MaxY); } /*-----------------------------------------------------------------------*/ /** * SET MOUSE KEYCODE MODE * * 0x0A * deltax ; distance in X clicks to return (LEFT) or (RIGHT) * deltay ; distance in Y clicks to return (UP) or (DOWN) */ static void IKBD_Cmd_MouseCursorKeycodes(void) { KeyboardProcessor.MouseMode = AUTOMODE_MOUSECURSOR; KeyboardProcessor.Mouse.KeyCodeDeltaX = Keyboard.InputBuffer[1]; KeyboardProcessor.Mouse.KeyCodeDeltaY = Keyboard.InputBuffer[2]; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_MouseCursorKeycodes %d,%d\n", (int)KeyboardProcessor.Mouse.KeyCodeDeltaX, (int)KeyboardProcessor.Mouse.KeyCodeDeltaY); } /*-----------------------------------------------------------------------*/ /** * SET MOUSE THRESHOLD * * 0x0B * X ; x threshold in mouse ticks (positive integers) * Y ; y threshold in mouse ticks (positive integers) */ static void IKBD_Cmd_SetMouseThreshold(void) { KeyboardProcessor.Mouse.XThreshold = (unsigned int)Keyboard.InputBuffer[1]; KeyboardProcessor.Mouse.YThreshold = (unsigned int)Keyboard.InputBuffer[2]; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetMouseThreshold %d,%d\n", KeyboardProcessor.Mouse.XThreshold, KeyboardProcessor.Mouse.YThreshold); } /*-----------------------------------------------------------------------*/ /** * SET MOUSE SCALE * * 0x0C * X ; horizontal mouse ticks per internal X * Y ; vertical mouse ticks per internal Y */ static void IKBD_Cmd_SetMouseScale(void) { KeyboardProcessor.Mouse.XScale = (unsigned int)Keyboard.InputBuffer[1]; KeyboardProcessor.Mouse.YScale = (unsigned int)Keyboard.InputBuffer[2]; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetMouseScale %d,%d\n", KeyboardProcessor.Mouse.XScale, KeyboardProcessor.Mouse.YScale); } /*-----------------------------------------------------------------------*/ /** * INTERROGATE MOUSE POSITION * * 0x0D * Returns: 0xF7 ; absolute mouse position header * BUTTONS * 0000dcba * where a is right button down since last interrogation * b is right button up since last * c is left button down since last * d is left button up since last * XMSB ; X coordinate * XLSB * YMSB ; Y coordinate * YLSB */ static void IKBD_Cmd_ReadAbsMousePos(void) { uint8_t Buttons,PrevButtons; /* Test buttons */ Buttons = 0; /* Set buttons to show if up/down */ if (Keyboard.bRButtonDown) Buttons |= 0x01; else Buttons |= 0x02; if (Keyboard.bLButtonDown) Buttons |= 0x04; else Buttons |= 0x08; /* Mask off it didn't send last time */ PrevButtons = KeyboardProcessor.Abs.PrevReadAbsMouseButtons; KeyboardProcessor.Abs.PrevReadAbsMouseButtons = Buttons; Buttons &= ~PrevButtons; /* And send packet */ if ( IKBD_OutputBuffer_CheckFreeCount ( 6 ) ) { IKBD_Cmd_Return_Byte_Delay (0xf7, 18000-ACIA_CYCLES); IKBD_Cmd_Return_Byte (Buttons); IKBD_Cmd_Return_Byte ((unsigned int)KeyboardProcessor.Abs.X>>8); IKBD_Cmd_Return_Byte ((unsigned int)KeyboardProcessor.Abs.X&0xff); IKBD_Cmd_Return_Byte ((unsigned int)KeyboardProcessor.Abs.Y>>8); IKBD_Cmd_Return_Byte ((unsigned int)KeyboardProcessor.Abs.Y&0xff); } LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReadAbsMousePos %d,%d 0x%X\n", KeyboardProcessor.Abs.X, KeyboardProcessor.Abs.Y, Buttons); } /*-----------------------------------------------------------------------*/ /** * LOAD MOUSE POSITION * * 0x0E * 0x00 ; filler * XMSB ; X coordinate * XLSB ; (in scaled coordinate system) * YMSB ; Y coordinate * YLSB */ static void IKBD_Cmd_SetInternalMousePos(void) { /* Setting these do not clip internal position(this happens on next update) */ KeyboardProcessor.Abs.X = (((unsigned int)Keyboard.InputBuffer[2])<<8) | (unsigned int)Keyboard.InputBuffer[3]; KeyboardProcessor.Abs.Y = (((unsigned int)Keyboard.InputBuffer[4])<<8) | (unsigned int)Keyboard.InputBuffer[5]; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetInternalMousePos %d,%d\n", KeyboardProcessor.Abs.X, KeyboardProcessor.Abs.Y); } /*-----------------------------------------------------------------------*/ /** * SET Y=0 AT BOTTOM * * 0x0F */ static void IKBD_Cmd_SetYAxisDown(void) { KeyboardProcessor.Mouse.YAxis = -1; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetYAxisDown\n"); } /*-----------------------------------------------------------------------*/ /** * SET Y=0 AT TOP * * 0x10 */ static void IKBD_Cmd_SetYAxisUp(void) { KeyboardProcessor.Mouse.YAxis = 1; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetYAxisUp\n"); } /*-----------------------------------------------------------------------*/ /** * RESUME * * Any command received by the IKBD will also resume the output if it was * paused by command 0x13, so this command is redundant. * * 0x11 */ static void IKBD_Cmd_StartKeyboardTransfer(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_StartKeyboardTransfer\n"); Keyboard.PauseOutput = false; } /*-----------------------------------------------------------------------*/ /** * DISABLE MOUSE * * 0x12 */ static void IKBD_Cmd_TurnMouseOff(void) { KeyboardProcessor.MouseMode = AUTOMODE_OFF; bMouseDisabled = true; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_TurnMouseOff\n"); IKBD_CheckResetDisableBug(); } /*-----------------------------------------------------------------------*/ /** * PAUSE OUTPUT * * 0x13 */ static void IKBD_Cmd_StopKeyboardTransfer(void) { if (bDuringResetCriticalTime) { /* Required for the loader of 'Just Bugging' by ACF */ LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_StopKeyboardTransfer ignored during ikbd reset\n"); return; } LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_StopKeyboardTransfer\n"); Keyboard.PauseOutput = true; } /*-----------------------------------------------------------------------*/ /** * SET JOYSTICK EVENT REPORTING * * 0x14 */ static void IKBD_Cmd_ReturnJoystickAuto(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReturnJoystickAuto\n"); KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK; KeyboardProcessor.MouseMode = AUTOMODE_OFF; /* If mouse was also enabled within time of a reset (0x08 command) it isn't disabled now! * (used by the game Barbarian 1 by Psygnosis for example) */ if ( bDuringResetCriticalTime && bMouseEnabledDuringReset ) { KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL; bBothMouseAndJoy = true; LOG_TRACE(TRACE_IKBD_ALL, "ikbd commands 0x08 and 0x14 received during reset," " enabling joystick and mouse reporting at the same time\n" ); } /* If mouse was disabled during the reset (0x12 command) it is enabled again * (used by the game Hammerfist for example) */ else if ( bDuringResetCriticalTime && bMouseDisabled ) { KeyboardProcessor.MouseMode = AUTOMODE_MOUSEREL; bBothMouseAndJoy = true; LOG_TRACE(TRACE_IKBD_ALL, "ikbd commands 0x12 and 0x14 received during reset," " enabling joystick and mouse reporting at the same time\n" ); } /* This command resets the internally previously stored joystick states */ KeyboardProcessor.Joy.PrevJoyData[JOYID_JOYSTICK0] = KeyboardProcessor.Joy.PrevJoyData[JOYID_JOYSTICK1] = 0; /* This is a hack for the STE Utopos (=> v1.50) and Falcon Double Bubble * 2000 games. They expect the joystick data to be sent within a certain * amount of time after this command, without checking the ACIA control * register first. */ IKBD_GetJoystickData(); IKBD_SendAutoJoysticks(); } /*-----------------------------------------------------------------------*/ /** * SET JOYSTICK INTERROGATION MODE * * 0x15 */ static void IKBD_Cmd_StopJoystick(void) { KeyboardProcessor.JoystickMode = AUTOMODE_OFF; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_StopJoystick\n"); } /*-----------------------------------------------------------------------*/ /** * JOYSTICK INTERROGATE * * 0x16 */ static void IKBD_Cmd_ReturnJoystick(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReturnJoystick\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 3 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xFD , IKBD_Delay_Random ( 7500 , 10000 ) ); IKBD_Cmd_Return_Byte (Joy_GetStickData(JOYID_JOYSTICK0)); IKBD_Cmd_Return_Byte (Joy_GetStickData(JOYID_JOYSTICK1)); } } /*-----------------------------------------------------------------------*/ /** * SET JOYSTICK MONITORING * * 0x17 * rate ; time between samples in hundredths of a second * Returns: (in packets of two as long as in mode) * %000000xy where y is JOYSTICK1 Fire button * and x is JOYSTICK0 Fire button * %nnnnmmmm where m is JOYSTICK1 state * and n is JOYSTICK0 state * * TODO : we use a fixed 8 MHz clock to convert rate in 1/100th of sec into cycles. * This should be replaced by using MachineClocks.CPU_Freq. */ static void IKBD_Cmd_SetJoystickMonitoring(void) { int Rate; int Cycles; Rate = (unsigned int)Keyboard.InputBuffer[1]; KeyboardProcessor.JoystickMode = AUTOMODE_JOYSTICK_MONITORING; KeyboardProcessor.MouseMode = AUTOMODE_OFF; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetJoystickMonitoring %d\n" , Rate ); if ( Rate == 0 ) Rate = 1; Cycles = 8021247 * Rate / 100; CycInt_AddRelativeInterrupt ( Cycles, INT_CPU8_CYCLE, INTERRUPT_IKBD_AUTOSEND ); Keyboard.AutoSendCycles = Cycles; } /*-----------------------------------------------------------------------*/ /** * SET FIRE BUTTON MONITORING * * 0x18 * Returns: (as long as in mode) * %bbbbbbbb ; state of the JOYSTICK1 fire button packed * ; 8 bits per byte, the first sample if the MSB */ static void IKBD_Cmd_SetJoystickFireDuration(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetJoystickFireDuration (not implemented)\n"); } /*-----------------------------------------------------------------------*/ /** * SET JOYSTICK KEYCODE MODE * * 0x19 * RX ; length of time (in tenths of seconds) until * ; horizontal velocity breakpoint is reached * RY ; length of time (in tenths of seconds) until * ; vertical velocity breakpoint is reached * TX ; length (in tenths of seconds) of joystick closure * ; until horizontal cursor key is generated before RX * ; has elapsed * TY ; length (in tenths of seconds) of joystick closure * ; until vertical cursor key is generated before RY * ; has elapsed * VX ; length (in tenths of seconds) of joystick closure * ; until horizontal cursor keystrokes are generated after RX * ; has elapsed * VY ; length (in tenths of seconds) of joystick closure * ; until vertical cursor keystrokes are generated after RY * ; has elapsed */ static void IKBD_Cmd_SetCursorForJoystick(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetCursorForJoystick (not implemented)\n"); } /*-----------------------------------------------------------------------*/ /** * DISABLE JOYSTICKS * * 0x1A */ static void IKBD_Cmd_DisableJoysticks(void) { KeyboardProcessor.JoystickMode = AUTOMODE_OFF; bJoystickDisabled = true; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_DisableJoysticks\n"); IKBD_CheckResetDisableBug(); } /*-----------------------------------------------------------------------*/ /** * TIME-OF-DAY CLOCK SET * * 0x1B * YY ; year (2 least significant digits) * MM ; month * DD ; day * hh ; hour * mm ; minute * ss ; second * * All bytes are stored in BCD format. If a byte is not in BCD, we ignore it * but we process the rest of the bytes. * Note that the IKBD doesn't check that month/day/hour/second/minute are in * their correct range, just that they're BCD encoded (so you can store 0x30 in hour * for example, see IKBD_UpdateClockOnVBL()) */ static void IKBD_Cmd_SetClock(void) { int i; uint8_t val; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_SetClock: %02x %02x %02x %02x %02x %02x\n", Keyboard.InputBuffer[1], Keyboard.InputBuffer[2], Keyboard.InputBuffer[3], Keyboard.InputBuffer[4], Keyboard.InputBuffer[5], Keyboard.InputBuffer[6]); for ( i=1 ; i<=6 ; i++ ) { val = Keyboard.InputBuffer[ i ]; if ( IKBD_BCD_Check ( val ) ) /* Check if valid BCD, else ignore */ pIKBD->Clock[ i-1 ] = val; /* Store new value */ } } /*-----------------------------------------------------------------------*/ /** * INTERROGATE TIME-OF-DAY CLOCK * * 0x1C * Returns: * 0xFC ; time-of-day event header * YY ; year (2 least significant digits) * MM ; month * DD ; day * hh ; hour * mm ; minute * ss ; second * * All bytes are stored/returned in BCD format. * Date/Time is updated in IKBD_UpdateClockOnVBL() */ static void IKBD_Cmd_ReadClock(void) { int i; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReadClock: %02x %02x %02x %02x %02x %02x\n", pIKBD->Clock[ 0 ] ,pIKBD->Clock[ 1 ] , pIKBD->Clock[ 2 ] , pIKBD->Clock[ 3 ] , pIKBD->Clock[ 4 ] , pIKBD->Clock[ 5 ] ); /* Return packet header */ if ( IKBD_OutputBuffer_CheckFreeCount ( 7 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xFC , IKBD_Delay_Random ( 7000 , 7500 ) ); /* Return the 6 clock bytes */ for ( i=0 ; i<6 ; i++ ) IKBD_Cmd_Return_Byte ( pIKBD->Clock[ i ] ); } } /*-----------------------------------------------------------------------*/ /** * MEMORY LOAD * * 0x20 * ADRMSB ; address in controller * ADRLSB ; memory to be loaded * NUM ; number of bytes (0-128) * { data } */ static void IKBD_Cmd_LoadMemory(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_LoadMemory addr 0x%x count %d\n", (Keyboard.InputBuffer[1] << 8) + Keyboard.InputBuffer[2], Keyboard.InputBuffer[3]); MemoryLoadNbBytesTotal = Keyboard.InputBuffer[3]; MemoryLoadNbBytesLeft = MemoryLoadNbBytesTotal; crc32_reset ( &MemoryLoadCrc ); } /*-----------------------------------------------------------------------*/ /** * MEMORY READ * * 0x21 * ADRMSB ; address in controller * ADRLSB ; memory to be read * Returns: * 0xF6 ; status header * 0x20 ; memory access * { data } ; 6 data bytes starting at ADR * * NOTE : This function requires to handle the IKBD's RAM, which is only * possible when emulating a real HD6301 CPU. For now, we only return * the correct header and 6 empty bytes. */ static void IKBD_Cmd_ReadMemory(void) { int i; LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReadMemory (not implemented)\n"); /* Return packet header */ if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); IKBD_Cmd_Return_Byte ( 0x20 ); /* Return 6 empty bytes */ for ( i=0 ; i<6 ; i++ ) IKBD_Cmd_Return_Byte ( 0x00 ); } } /*-----------------------------------------------------------------------*/ /** * CONTROLLER EXECUTE * * 0x22 * ADRMSB ; address of subroutine in * ADRLSB ; controller memory to be called */ static void IKBD_Cmd_Execute(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_Execute addr 0x%x\n", (Keyboard.InputBuffer[1] << 8) + Keyboard.InputBuffer[2]); if ( pIKBD_CustomCodeHandler_Write ) { LOG_TRACE(TRACE_IKBD_EXEC, "ikbd execute addr 0x%x using custom handler\n", (Keyboard.InputBuffer[1] << 8) + Keyboard.InputBuffer[2]); IKBD_ExeMode = true; /* turn 6301's custom mode ON */ } else /* unknown code uploaded to ikbd RAM */ { LOG_TRACE(TRACE_IKBD_EXEC, "ikbd execute addr 0x%x ignored, no custom handler found\n", (Keyboard.InputBuffer[1] << 8) + Keyboard.InputBuffer[2]); } } /*-----------------------------------------------------------------------*/ /** * REPORT MOUSE BUTTON ACTION * * 0x87 */ static void IKBD_Cmd_ReportMouseAction(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportMouseAction\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); IKBD_Cmd_Return_Byte (7); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.Action); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); } } /*-----------------------------------------------------------------------*/ /** * REPORT MOUSE MODE * * 0x88 or 0x89 or 0x8A */ static void IKBD_Cmd_ReportMouseMode(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportMouseMode\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); switch (KeyboardProcessor.MouseMode) { case AUTOMODE_MOUSEREL: IKBD_Cmd_Return_Byte (8); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); break; case AUTOMODE_MOUSEABS: IKBD_Cmd_Return_Byte (9); IKBD_Cmd_Return_Byte (KeyboardProcessor.Abs.MaxX >> 8); IKBD_Cmd_Return_Byte (KeyboardProcessor.Abs.MaxX); IKBD_Cmd_Return_Byte (KeyboardProcessor.Abs.MaxY >> 8); IKBD_Cmd_Return_Byte (KeyboardProcessor.Abs.MaxY); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); break; case AUTOMODE_MOUSECURSOR: IKBD_Cmd_Return_Byte (10); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.KeyCodeDeltaX); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.KeyCodeDeltaY); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); break; } } } /*-----------------------------------------------------------------------*/ /** * REPORT MOUSE THRESHOLD * * 0x8B */ static void IKBD_Cmd_ReportMouseThreshold(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportMouseThreshold\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); IKBD_Cmd_Return_Byte (0x0B); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.XThreshold); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.YThreshold); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); } } /*-----------------------------------------------------------------------*/ /** * REPORT MOUSE SCALE * * 0x8C */ static void IKBD_Cmd_ReportMouseScale(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportMouseScale\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); IKBD_Cmd_Return_Byte (0x0C); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.XScale); IKBD_Cmd_Return_Byte (KeyboardProcessor.Mouse.YScale); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); } } /*-----------------------------------------------------------------------*/ /** * REPORT MOUSE VERTICAL COORDINATES * * 0x8F and 0x90 */ static void IKBD_Cmd_ReportMouseVertical(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportMouseVertical\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); if (KeyboardProcessor.Mouse.YAxis == -1) IKBD_Cmd_Return_Byte (0x0F); else IKBD_Cmd_Return_Byte (0x10); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); } } /*-----------------------------------------------------------------------*/ /** * REPORT MOUSE AVAILABILITY * * 0x92 */ static void IKBD_Cmd_ReportMouseAvailability(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportMouseAvailability\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); if (KeyboardProcessor.MouseMode == AUTOMODE_OFF) IKBD_Cmd_Return_Byte (0x12); else IKBD_Cmd_Return_Byte (0x00); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); } } /*-----------------------------------------------------------------------*/ /** * REPORT JOYSTICK MODE * * 0x94 or 0x95 or 0x99 */ static void IKBD_Cmd_ReportJoystickMode(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportJoystickMode\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); switch (KeyboardProcessor.JoystickMode) { case AUTOMODE_JOYSTICK: IKBD_Cmd_Return_Byte (0x14); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); break; default: /* TODO: Joystick keycodes mode not supported yet! */ IKBD_Cmd_Return_Byte (0x15); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); break; } } } /*-----------------------------------------------------------------------*/ /** * REPORT JOYSTICK AVAILABILITY * * 0x9A */ static void IKBD_Cmd_ReportJoystickAvailability(void) { LOG_TRACE(TRACE_IKBD_CMDS, "IKBD_Cmd_ReportJoystickAvailability\n"); if ( IKBD_OutputBuffer_CheckFreeCount ( 8 ) ) { IKBD_Cmd_Return_Byte_Delay ( 0xF6 , IKBD_Delay_Random ( 7000 , 7500 ) ); if (KeyboardProcessor.JoystickMode == AUTOMODE_OFF) IKBD_Cmd_Return_Byte (0x1A); else IKBD_Cmd_Return_Byte (0x00); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); IKBD_Cmd_Return_Byte (0); } } /************************************************************************/ /* End of the IKBD's commands emulation. */ /************************************************************************/ /*************************************************************************/ /** * Below part is for emulating custom 6301 program sent to the IKBD's RAM * Specific read/write functions for each demo/game should be added here, * after being defined in the CustomCodeDefinitions[] array. * * The 6301 has 256 bytes of RAM, but only 128 bytes are available to * put a program (from $80 to $ff). * * Executing a program in the 6301 is a 2 steps process : * 1) a very small program is sent to the RAM using the 0x20 command. * This is often loaded at address $b0. * This program will stop interruptions in the 6301 and will accept * a second small program that will relocate itself to $80. * 2) the relocated program at address $80 will accept a third (main) * program and will execute it once reception is complete. * * Writes during step 1 are handled with the ExeBootHandler matching the * LoadMemory CRC. * ExeBootHandler will compute a 2nd CRC for the writes corresponding to * the 2nd and 3rd programs sent to the 6301's RAM. * * If a match is found for this 2nd CRC, we will override default IKBD's behaviour * for reading/writing to $fffc02 with ExeMainHandler_Read / ExeMainHandler_Write * (once the Execute command 0x22 is received). * * When using custom program (ExeMode==true), we must ignore all keyboard/mouse/joystick * events sent to IKBD_Cmd_Return_Byte . Only our functions can add bytes * to the keyboard buffer. * * To exit 6301's execution mode, we can use the 68000 'reset' instruction. * Some 6301's programs also handle a write to $fffc02 as an exit signal. */ /*-----------------------------------------------------------------------*/ /** * Handle writes to $fffc02 when loading bytes in the IKBD's RAM. * We compute a CRC of the bytes that are sent until MemoryLoadNbBytesLeft * reaches 0. * When all bytes are loaded, we look for a matching CRC ; if found, we * use the ExeBootHandler defined for this CRC to process the next writes * that will occur in $fffc02. * LoadMemory is often used to load a small boot code into the 6301's RAM. * This small program will be executed later using the command 0x22. */ static void IKBD_LoadMemoryByte ( uint8_t aciabyte ) { unsigned int i; /* Write received bytes to a file for debug */ // FILE *f = fopen ( "/tmp/ikbd_loadmemory.dump" , "ab" ) ; fprintf ( f , "%c" , aciabyte ) ; fclose ( f ); crc32_add_byte ( &MemoryLoadCrc , aciabyte ); MemoryLoadNbBytesLeft--; if ( MemoryLoadNbBytesLeft == 0 ) /* all bytes were received */ { /* Search for a match amongst the known custom routines */ for ( i = 0 ; i < sizeof ( CustomCodeDefinitions ) / sizeof ( CustomCodeDefinitions[0] ) ; i++ ) if ( CustomCodeDefinitions[ i ].LoadMemCrc == MemoryLoadCrc ) break; if ( i < sizeof ( CustomCodeDefinitions ) / sizeof ( CustomCodeDefinitions[0] ) ) /* found */ { LOG_TRACE(TRACE_IKBD_EXEC, "ikbd loadmemory %d bytes crc=0x%x matches <%s>\n", MemoryLoadNbBytesTotal, MemoryLoadCrc, CustomCodeDefinitions[ i ].Name); crc32_reset ( &MemoryLoadCrc ); MemoryExeNbBytes = 0; pIKBD_CustomCodeHandler_Read = NULL; pIKBD_CustomCodeHandler_Write = CustomCodeDefinitions[ i ].ExeBootHandler; } else /* unknown code uploaded to IKBD's RAM */ { LOG_TRACE(TRACE_IKBD_EXEC, "ikbd loadmemory %d bytes crc=0x%x : unknown code\n", MemoryLoadNbBytesTotal, MemoryLoadCrc); pIKBD_CustomCodeHandler_Read = NULL; pIKBD_CustomCodeHandler_Write = NULL; } } } /*-----------------------------------------------------------------------*/ /** * Handle writes to $fffc02 when executing custom code in the IKBD's RAM. * This is used to send the small IKBD program that will handle * keyboard/mouse/joystick input. * We compute a CRC of the bytes that are sent until we found a match * with a known custom IKBD program. */ static void IKBD_CustomCodeHandler_CommonBoot ( uint8_t aciabyte ) { unsigned int i; /* Write received bytes to a file for debug */ // FILE *f = fopen ( "/tmp/ikbd_custom_program.dump" , "ab" ) ; fprintf ( f , "%c" , aciabyte ) ; fclose ( f ); crc32_add_byte ( &MemoryLoadCrc , aciabyte ); MemoryExeNbBytes++; LOG_TRACE(TRACE_IKBD_EXEC, "ikbd custom exe common boot write 0x%02x count %d crc=0x%x\n", aciabyte, MemoryExeNbBytes, MemoryLoadCrc); /* Search for a match amongst the known custom routines */ for ( i = 0 ; i < sizeof ( CustomCodeDefinitions ) / sizeof ( CustomCodeDefinitions[0] ) ; i++ ) if ( ( CustomCodeDefinitions[ i ].MainProgNbBytes == MemoryExeNbBytes ) && ( CustomCodeDefinitions[ i ].MainProgCrc == MemoryLoadCrc ) ) break; if ( i < sizeof ( CustomCodeDefinitions ) / sizeof ( CustomCodeDefinitions[0] ) ) /* found */ { LOG_TRACE(TRACE_IKBD_EXEC, "ikbd custom exe common boot, uploaded code matches <%s>\n", CustomCodeDefinitions[i].Name); pIKBD_CustomCodeHandler_Read = CustomCodeDefinitions[ i ].ExeMainHandler_Read; pIKBD_CustomCodeHandler_Write = CustomCodeDefinitions[ i ].ExeMainHandler_Write; Keyboard.BufferHead = Keyboard.BufferTail = 0; /* flush all queued bytes that would be read in $fffc02 */ Keyboard.NbBytesInOutputBuffer = 0; } /* If not found, we keep on accumulating bytes until we find a matching crc */ } /*----------------------------------------------------------------------*/ /* Froggies Over The Fence menu. */ /* Returns 'n' bytes with the mouse position, keyboard can be used too. */ /* Writing a <0 byte to $fffc02 will cause the 6301 to exit custom exe */ /* mode (jmp $f000). */ /* When writing byte 'n' >0 to $fffc02, the 6301 will return the content*/ /* of RAM $7f+n to $7f+1. */ /* $80/$81 contains deltaY/deltaX + left mouse button in bit 7, $82 */ /* contains LMB in bit 7 and $83 contains a fixed value 0xfc. */ /* On each VBL, the demo will ask for 1 byte, then for 4 bytes ; only */ /* the last 2 bytes ($81/$80) will be used, $83/$82 are ignored. */ /* IKBD's $81 will be stored in $600 (CPU RAM), and $80 in $601. */ /* */ /* TODO : an extra delay of 7000 cycles is necessary to have $81 and $80*/ /* received after the overrun condition was cleared at the 68000 level. */ /* Does it mean some timings are wrong with acia/ikbd ? */ /*----------------------------------------------------------------------*/ static void IKBD_CustomCodeHandler_FroggiesMenu_Read ( void ) { /* Ignore read */ } static void IKBD_CustomCodeHandler_FroggiesMenu_Write ( uint8_t aciabyte ) { uint8_t res80 = 0; uint8_t res81 = 0; uint8_t res82 = 0; uint8_t res83 = 0xfc; /* fixed value, not used */ /* When writing a <0 byte to $fffc02, Froggies ikbd's program will terminate itself */ /* and leave Execution mode (jmp $f000) */ if ( aciabyte & 0x80 ) { IKBD_Boot_ROM ( false ); return; } if ( KeyboardProcessor.Mouse.DeltaY < 0 ) res80 = 0x7a; /* mouse up */ if ( KeyboardProcessor.Mouse.DeltaY > 0 ) res80 = 0x06; /* mouse down */ if ( KeyboardProcessor.Mouse.DeltaX < 0 ) res81 = 0x7a; /* mouse left */ if ( KeyboardProcessor.Mouse.DeltaX > 0 ) res81 = 0x06; /* mouse right */ if ( Keyboard.bLButtonDown & BUTTON_MOUSE ) res82 |= 0x80; /* left mouse button */ if ( ScanCodeState[ 0x48 ] ) res80 |= 0x7a; /* up */ if ( ScanCodeState[ 0x50 ] ) res80 |= 0x06; /* down */ if ( ScanCodeState[ 0x4b ] ) res81 |= 0x7a; /* left */ if ( ScanCodeState[ 0x4d ] ) res81 |= 0x06; /* right */ if ( ScanCodeState[ 0x70 ] ) res82 |= 0x80; /* keypad 0 */ res80 |= res82; /* bit 7 is left mouse button */ res81 |= res82; // res80 = 0x10 ; res81 = 0x11 ; res82 = 0x12 ; res83 = 0x13 ; /* force some discernible values to debug */ if ( aciabyte == 1 ) /* Send 1 byte */ IKBD_Send_Byte_Delay ( res80 , 0 ); /* $80 in IKBD's RAM */ else if ( aciabyte == 4 ) /* Send 4 bytes */ { IKBD_Send_Byte_Delay ( res83 , 7000 ); /* $83 in IKBD's RAM */ IKBD_Send_Byte_Delay ( res82 , 0 ); /* $82 in IKBD's RAM */ IKBD_Send_Byte_Delay ( res81 , 0 ); /* $81 in IKBD's RAM */ IKBD_Send_Byte_Delay ( res80 , 0 ); /* $80 in IKBD's RAM */ } } /*----------------------------------------------------------------------*/ /* Transbeauce II menu. */ /* Returns 1 byte with the joystick position, keyboard can be used too. */ /*----------------------------------------------------------------------*/ static void IKBD_CustomCodeHandler_Transbeauce2Menu_Read ( void ) { uint8_t res = 0; /* keyboard emulation */ if ( ScanCodeState[ 0x48 ] ) res |= 0x01; /* up */ if ( ScanCodeState[ 0x50 ] ) res |= 0x02; /* down */ if ( ScanCodeState[ 0x4b ] ) res |= 0x04; /* left */ if ( ScanCodeState[ 0x4d ] ) res |= 0x08; /* right */ if ( ScanCodeState[ 0x62 ] ) res |= 0x40; /* help */ if ( ScanCodeState[ 0x39 ] ) res |= 0x80; /* space */ /* joystick emulation (bit mapping is same as cursor above, with bit 7 = fire button */ res |= ( Joy_GetStickData(JOYID_JOYSTICK1) & 0x8f ) ; /* keep bits 0-3 and 7 */ IKBD_Send_Byte_Delay ( res , 0 ); } static void IKBD_CustomCodeHandler_Transbeauce2Menu_Write ( uint8_t aciabyte ) { /* Ignore write */ } /*----------------------------------------------------------------------*/ /* Dragonnels demo menu. */ /* When any byte is written in $fffc02, returns one byte with the */ /* Y position of the mouse and the state of the left button. */ /*----------------------------------------------------------------------*/ static void IKBD_CustomCodeHandler_DragonnelsMenu_Read ( void ) { /* Ignore read */ } static void IKBD_CustomCodeHandler_DragonnelsMenu_Write ( uint8_t aciabyte ) { uint8_t res = 0; if ( KeyboardProcessor.Mouse.DeltaY < 0 ) res = 0xfc; /* mouse up */ if ( KeyboardProcessor.Mouse.DeltaY > 0 ) res = 0x04; /* mouse down */ if ( Keyboard.bLButtonDown & BUTTON_MOUSE ) res = 0x80; /* left mouse button */ IKBD_Send_Byte_Delay ( res , 0 ); } /*----------------------------------------------------------------------*/ /* Chaos A.D. protection's decoder */ /* This custom program reads bytes, decode them and send back the result*/ /* to the 68000. */ /* The program first returns $fe to indicate it's ready to receive the */ /* encoded bytes. */ /* The program then receives the 8 bytes used to decode the data and */ /* store them in $f0 - $f7 (KeyBuffer is already initialized, so we */ /* ignore those 8 bytes). */ /* Then for any received byte a XOR is made with one of the byte in the */ /* 8 bytes buffer, by incrementing an index in this buffer. */ /* The decoded byte is written to addr $13 (TDR) to be received by ACIA */ /*----------------------------------------------------------------------*/ static void IKBD_CustomCodeHandler_ChaosAD_Read ( void ) { static bool FirstCall = true; if ( FirstCall == true ) IKBD_Send_Byte_Delay ( 0xfe , 0 ); FirstCall = false; } static void IKBD_CustomCodeHandler_ChaosAD_Write ( uint8_t aciabyte ) { static int IgnoreNb = 8; uint8_t KeyBuffer[] = { 0xca , 0x0a , 0xbc , 0x00 , 0xde , 0xde , 0xfe , 0xca }; static int Index = 0; static int Count = 0; /* We ignore the first 8 bytes we received (they're already in KeyBuffer) */ if ( IgnoreNb > 0 ) { IgnoreNb--; return; } if ( Count <= 6080 ) /* there're 6081 bytes to decode */ { Count++; aciabyte ^= KeyBuffer[ Index ]; Index++; Index &= 0x07; IKBD_Send_Byte_Delay ( aciabyte , 0 ); } else { /* When all bytes were decoded if 0x08 is written to $fffc02 */ /* the program will terminate itself and leave Execution mode */ if ( aciabyte == 0x08 ) IKBD_Boot_ROM ( false ); } } /*----------------------------------------------------------------------*/ /* Audio Sculpture decryption support */ /* The main executable is decrypted with a key extracted from a */ /* previously uploaded program in the 6301. When the magic value 0x42 */ /* is sent to fffc02 it will output the two bytes 0x4b and 0x13 */ /* and exit the custom handler again */ /* [NP] The custom program has 2 parts : */ /* - 1st part is used during the intro and wait for key 'space' in */ /* color mode or any key in mono mode (but intro screen in mono */ /* exits automatically without testing a key !) */ /* - 2nd part wait to receive $42 from the ACIA, then send $4b and $13 */ /*----------------------------------------------------------------------*/ static bool ASmagic = false; static void IKBD_CustomCodeHandler_AudioSculpture_Color_Read ( void ) { IKBD_CustomCodeHandler_AudioSculpture_Read ( true ); } static void IKBD_CustomCodeHandler_AudioSculpture_Mono_Read ( void ) { IKBD_CustomCodeHandler_AudioSculpture_Read ( false ); } static void IKBD_CustomCodeHandler_AudioSculpture_Read ( bool ColorMode ) { uint8_t res = 0; static int ReadCount = 0; if ( ASmagic ) { ReadCount++; if ( ReadCount == 2 ) /* We're done reading out the 2 bytes, exit the custom handler */ { IKBD_Boot_ROM ( false ); ASmagic = false; ReadCount = 0; } } else if ( ( ( ColorMode == false ) && ( IKBD_CheckPressedKey() >= 0 ) ) /* wait for any key in mono mode */ || ScanCodeState[ 0x39 ] ) /* wait for 'space' key in color mode */ { res = 0x39; /* send scancode for 'space' */ IKBD_Send_Byte_Delay ( res , 0 ); } } static void IKBD_CustomCodeHandler_AudioSculpture_Write ( uint8_t aciabyte ) { uint8_t Magic = 0x42; uint8_t Key[] = { 0x4b , 0x13 }; if ( aciabyte == Magic ) { ASmagic = true; IKBD_Send_Byte_Delay ( Key[0] , 0 ); IKBD_Send_Byte_Delay ( Key[1] , 0 ); } } void IKBD_Info(FILE *fp, uint32_t dummy) { int i; fprintf(fp, "Transmit/Receive Control+Status: 0x%02x\n", pIKBD->TRCSR); fprintf(fp, "Rate + Mode Control: 0x%02x\n", pIKBD->RMCR); fprintf(fp, "Transmit: Receive:\n"); fprintf(fp, "- Data: 0x%02x 0x%02x\n", pIKBD->TDR, pIKBD->RDR); fprintf(fp, "- Shift: 0x%02x 0x%02x\n", pIKBD->TSR, pIKBD->RSR); fprintf(fp, "- State: %4d %4d\n", pIKBD->SCI_TX_State, pIKBD->SCI_RX_State); fprintf(fp, "- #Bits: %4d %4d\n", pIKBD->SCI_TX_Size, pIKBD->SCI_RX_Size); fprintf(fp, "- Delay: %4d\n", pIKBD->SCI_TX_Delay); fprintf(fp, "Clock:"); for (i = 0; i < ARRAY_SIZE(pIKBD->Clock); i++) fprintf(fp, " %02x", pIKBD->Clock[i]); fprintf(fp, " (+%" PRId64 ")\n", pIKBD->Clock_micro); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/000077500000000000000000000000001504763705000231365ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/acia.h000066400000000000000000000057711504763705000242160ustar00rootroot00000000000000/* Hatari - acia.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_ACIA_H #define HATARI_ACIA_H typedef struct ACIA { /* MC6850 internal registers */ uint8_t CR; /* Control Register */ uint8_t SR; /* Status Register */ uint8_t TDR; /* Transmit Data Register */ uint8_t RDR; /* Receive Data Register */ uint32_t TX_Clock; /* 500 MHz on ST */ uint32_t RX_Clock; /* 500 MHz on ST */ int Clock_Divider; /* 1, 16 or 64 */ uint8_t FirstMasterReset; /* Set to 1 on first use, always 0 after 1st Master Reset */ uint8_t SR_Read; /* Set to 1 when SR is read and reset to 0 when RDR is read */ int TX_State; uint8_t TSR; /* Transmit Shift Register */ uint8_t TX_Size; /* How many data bits left to transmit in TSR (7/8 .. 0) */ uint8_t TX_Parity; /* Current parity bit value for transmit */ uint8_t TX_StopBits; /* How many stop bits left to transmit (1 or 2) */ uint8_t TX_EnableInt; /* When TDRE goes from 0 to 1 : 0=disable interrupt, 1=enable interrupt */ uint8_t TX_SendBrk; /* Send a break bit in idle state */ int RX_State; uint8_t RSR; /* Receive Shift Register */ uint8_t RX_Size; /* How many bits left to receive in RSR (7/8 .. 0) */ uint8_t RX_Parity; /* Current parity bit value for receive */ uint8_t RX_StopBits; /* How many stop bits left to receive (1 or 2) */ uint8_t RX_Overrun; /* Set to 1 if previous RDR was not read when RSR is full */ uint8_t IRQ_Line; /* 0=IRQ 1=no IRQ */ /* Callback functions */ uint8_t (*Get_Line_RX) ( void ); /* Input : RX */ void (*Set_Line_TX) ( int val ); /* Output : TX */ void (*Set_Line_IRQ) ( struct ACIA *pACIA , int val ); /* Output : IRQ */ uint8_t (*Get_Line_IRQ) ( struct ACIA *pACIA ); /* Output : IRQ */ void (*Set_Timers) ( struct ACIA *pACIA ); /* Start timers to handle RX and TX bits at specified baud rate */ uint8_t (*Get_Line_CTS) ( void ); /* Input : Clear To Send (not connected in ST) */ uint8_t (*Get_Line_DCD) ( void ); /* Input : Data Carrier Detect (not connected in ST) */ void (*Set_Line_RTS) ( int val ); /* Output : Request To Send (not connected in ST) */ /* Other variables */ char ACIA_Name[ 10 ]; /* IKBD or MIDI */ } ACIA_STRUCT; #define ACIA_MAX_NB 2 /* 2 ACIAs in the ST */ extern ACIA_STRUCT ACIA_Array[ ACIA_MAX_NB ]; extern ACIA_STRUCT *pACIA_IKBD; extern ACIA_STRUCT *pACIA_MIDI; void ACIA_Init ( ACIA_STRUCT *pAllACIA , uint32_t TX_Clock , uint32_t RX_Clock ); void ACIA_Reset ( ACIA_STRUCT *pAllACIA ); void ACIA_MemorySnapShot_Capture ( bool bSave ); void ACIA_InterruptHandler_IKBD ( void ); void ACIA_InterruptHandler_MIDI ( void ); void ACIA_AddWaitCycles ( void ); void ACIA_IKBD_Read_SR ( void ); void ACIA_IKBD_Read_RDR ( void ); void ACIA_IKBD_Write_CR ( void ); void ACIA_IKBD_Write_TDR ( void ); void ACIA_Info(FILE *fp, uint32_t dummy); #endif /* ifndef HATARI_ACIA_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/audio.h000066400000000000000000000012331504763705000244070ustar00rootroot00000000000000/* Hatari - audio.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_AUDIO_H #define HATARI_AUDIO_H extern int nAudioFrequency; extern bool bSoundWorking; extern int SoundBufferSize; extern int SdlAudioBufferSize; extern int pulse_swallowing_count; extern void Audio_Init(void); extern void Audio_UnInit(void); extern void Audio_Lock(void); extern void Audio_Unlock(void); extern void Audio_FreeSoundBuffer(void); extern void Audio_SetOutputAudioFreq(int Frequency); extern void Audio_EnableAudio(bool bEnable); #endif /* HATARI_AUDIO_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/avi_record.h000066400000000000000000000015361504763705000254310ustar00rootroot00000000000000/* Hatari - avi_record.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_AVI_RECORD_H #define HATARI_AVI_RECORD_H #define AVI_RECORD_VIDEO_CODEC_BMP 1 #define AVI_RECORD_VIDEO_CODEC_PNG 2 #define AVI_RECORD_AUDIO_CODEC_PCM 1 extern char AviRecordFile[FILENAME_MAX]; extern bool Avi_RecordVideoStream ( void ); extern bool Avi_RecordAudioStream ( int16_t pSamples[][2] , int SampleIndex , int SampleLength ); extern bool Avi_AreWeRecording ( void ); extern bool Avi_SetCompressionLevel(const char *str); extern bool Avi_StartRecording_WithConfig ( void ); extern bool Avi_StopRecording ( void ); extern bool Avi_StopRecording_WithMsg ( void ); extern void Avi_SetSurface(SDL_Surface *surf); #endif /* ifndef HATARI_AVI_RECORD_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/bios.h000066400000000000000000000001311504763705000242360ustar00rootroot00000000000000/* Hatari */ extern bool Bios(void); extern void Bios_Info(FILE *fp, uint32_t dummy); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/blitter.h000066400000000000000000000072311504763705000247570ustar00rootroot00000000000000/* Hatari - blitter.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Blitter emulation. */ #ifndef BLITTER_H #define BLITTER_H extern uint16_t BlitterPhase; extern void Blitter_Reset ( void ); extern void Blitter_StatsUpdateRate ( int period_cycles ); extern int Blitter_StatsGetRate ( void ); extern void Blitter_Halftone00_ReadWord(void); extern void Blitter_Halftone01_ReadWord(void); extern void Blitter_Halftone02_ReadWord(void); extern void Blitter_Halftone03_ReadWord(void); extern void Blitter_Halftone04_ReadWord(void); extern void Blitter_Halftone05_ReadWord(void); extern void Blitter_Halftone06_ReadWord(void); extern void Blitter_Halftone07_ReadWord(void); extern void Blitter_Halftone08_ReadWord(void); extern void Blitter_Halftone09_ReadWord(void); extern void Blitter_Halftone10_ReadWord(void); extern void Blitter_Halftone11_ReadWord(void); extern void Blitter_Halftone12_ReadWord(void); extern void Blitter_Halftone13_ReadWord(void); extern void Blitter_Halftone14_ReadWord(void); extern void Blitter_Halftone15_ReadWord(void); extern void Blitter_SourceXInc_ReadWord(void); extern void Blitter_SourceYInc_ReadWord(void); extern void Blitter_SourceAddr_ReadLong(void); extern void Blitter_Endmask1_ReadWord(void); extern void Blitter_Endmask2_ReadWord(void); extern void Blitter_Endmask3_ReadWord(void); extern void Blitter_DestXInc_ReadWord(void); extern void Blitter_DestYInc_ReadWord(void); extern void Blitter_DestAddr_ReadLong(void); extern void Blitter_WordsPerLine_ReadWord(void); extern void Blitter_LinesPerBitblock_ReadWord(void); extern void Blitter_HalftoneOp_ReadByte(void); extern void Blitter_LogOp_ReadByte(void); extern void Blitter_Control_ReadByte(void); extern void Blitter_Skew_ReadByte(void); extern void Blitter_Halftone00_WriteWord(void); extern void Blitter_Halftone01_WriteWord(void); extern void Blitter_Halftone02_WriteWord(void); extern void Blitter_Halftone03_WriteWord(void); extern void Blitter_Halftone04_WriteWord(void); extern void Blitter_Halftone05_WriteWord(void); extern void Blitter_Halftone06_WriteWord(void); extern void Blitter_Halftone07_WriteWord(void); extern void Blitter_Halftone08_WriteWord(void); extern void Blitter_Halftone09_WriteWord(void); extern void Blitter_Halftone10_WriteWord(void); extern void Blitter_Halftone11_WriteWord(void); extern void Blitter_Halftone12_WriteWord(void); extern void Blitter_Halftone13_WriteWord(void); extern void Blitter_Halftone14_WriteWord(void); extern void Blitter_Halftone15_WriteWord(void); extern void Blitter_SourceXInc_WriteWord(void); extern void Blitter_SourceYInc_WriteWord(void); extern void Blitter_SourceAddr_WriteLong(void); extern void Blitter_Endmask1_WriteWord(void); extern void Blitter_Endmask2_WriteWord(void); extern void Blitter_Endmask3_WriteWord(void); extern void Blitter_DestXInc_WriteWord(void); extern void Blitter_DestYInc_WriteWord(void); extern void Blitter_DestAddr_WriteLong(void); extern void Blitter_WordsPerLine_WriteWord(void); extern void Blitter_LinesPerBitblock_WriteWord(void); extern void Blitter_HalftoneOp_WriteByte(void); extern void Blitter_LogOp_WriteByte(void); extern void Blitter_Control_WriteByte(void); extern void Blitter_Skew_WriteByte(void); extern void Blitter_MemorySnapShot_Capture(bool bSave); extern void Blitter_InterruptHandler(void); extern void Blitter_Info(FILE *fp, uint32_t arg); extern void Blitter_HOG_CPU_mem_access_before ( int bus_count ); extern void Blitter_HOG_CPU_mem_access_after ( int bus_count ); extern int Blitter_Check_Simultaneous_CPU ( void ); extern void Blitter_HOG_CPU_do_cycles_after ( int cycles ); #endif /* BLITTER_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/cart.h000066400000000000000000000006331504763705000242420ustar00rootroot00000000000000/* Hatari - cart.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ /* Workspace in our 'cart_asm.s' internal program */ #define CART_OLDGEMDOS 0xfa0024 #define CART_VDI_OPCODE_ADDR 0xfa0028 #define CART_GEMDOS 0xfa002a void Cart_ResetImage(void); bool Cart_UseBuiltinCartridge(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/cfgopts.h000066400000000000000000000011271504763705000247550ustar00rootroot00000000000000/* * Hatari - cfgopts.h */ #ifndef HATARI_CFGOPTS_H #define HATARI_CFGOPTS_H typedef enum { Error_Tag, Bool_Tag, Char_Tag, Short_Tag, Int_Tag, Long_Tag, Float_Tag, Double_Tag, String_Tag, Key_Tag } TAG_TYPE; struct Config_Tag { const char *code; /* Option switch */ TAG_TYPE type; /* Type of option */ void *buf; /* Storage location */ }; int input_config(const char *, const struct Config_Tag *, const char *); int update_config(const char *, const struct Config_Tag *, const char *); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/change.h000066400000000000000000000010141504763705000245300ustar00rootroot00000000000000/* Hatari - change.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CHANGE_H #define HATARI_CHANGE_H #include "configuration.h" extern bool Change_DoNeedReset(CNF_PARAMS *current, CNF_PARAMS *changed); extern void Change_CopyChangedParamsToConfiguration(CNF_PARAMS *current, CNF_PARAMS *changed, bool bForceReset); extern bool Change_ApplyCommandline(char *cmdline); #endif /* HATARI_CHANGE_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/clocks_timings.h000066400000000000000000000051511504763705000263210ustar00rootroot00000000000000/* Hatari - clocks_timings.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CLOCKS_TIMINGS_H #define HATARI_CLOCKS_TIMINGS_H /* All the possible clock frequencies in Hz used in the supported machines. */ /* When a value is 0, the corresponding part is not available in this model */ typedef struct { /* Common to all machines */ uint32_t MCLK_Freq; uint32_t BUS_Freq; uint32_t CPU_Freq; /* 'normal' CPU Freq (eg 8 MHz for ST or 16 MHz for Falcon) */ uint32_t FPU_Freq; uint32_t DMA_Freq; uint32_t MFP_Freq; uint32_t MFP_Timer_Freq; uint32_t FDC_Freq; uint32_t BLITTER_Freq; uint32_t YM_Freq; uint32_t ACIA_Freq; uint32_t IKBD_Freq; /* STF specific */ uint32_t MMU_Freq; /* STF only */ uint32_t GLUE_Freq; /* STF only */ uint32_t SHIFTER_Freq; /* STF/STE */ /* STE specific */ uint32_t MCU_Freq; /* replaces MMU+GLUE in STF */ uint32_t DMA_Audio_Freq; /* also used for SND SHIFTER in TT */ /* TT specific */ uint32_t TTVIDEO_Freq; /* Falcon specific */ uint32_t COMBEL_Freq; /* includes the BLITTER */ uint32_t VIDEL_Freq; uint32_t CODEC_Freq; uint32_t DSP_Freq; /* Mega STE, TT, Falcon specific */ uint32_t SCC_Freq; /* Common to all machines, runtime variables */ uint32_t CPU_Freq_Emul; /* Freq in Hz at which the CPU is emulated (taking nCpuFreqShift and CPU_Freq into account) */ } CLOCKS_STRUCT; extern CLOCKS_STRUCT MachineClocks; typedef struct { uint64_t Cycles; uint64_t Remainder; } CLOCKS_CYCLES_STRUCT; extern bool RoundVBLPerSec; #define CLOCKS_TIMINGS_SHIFT_VBL 24 /* The value returned by ClocksTimings_GetVBLPerSec is << 24 to increase precision */ /* Functions' prototypes */ void ClocksTimings_InitMachine ( MACHINETYPE MachineType ); void ClocksTimings_UpdateCpuFreqEmul ( MACHINETYPE MachineType , int nCpuFreqShift ); uint32_t ClocksTimings_GetCyclesPerVBL ( MACHINETYPE MachineType , int ScreenRefreshRate ); uint32_t ClocksTimings_GetVBLPerSec ( MACHINETYPE MachineType , int ScreenRefreshRate ); uint32_t ClocksTimings_GetVBLDuration_micro ( MACHINETYPE MachineType , int ScreenRefreshRate ); int64_t ClocksTimings_GetSamplesPerVBL ( MACHINETYPE MachineType , int ScreenRefreshRate , int AudioFreq ); void ClocksTimings_ConvertCycles ( uint64_t CyclesIn , uint64_t ClockFreqIn , CLOCKS_CYCLES_STRUCT *CyclesStructOut , uint64_t ClockFreqOut ); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/configuration.h000066400000000000000000000245461504763705000261710ustar00rootroot00000000000000/* Hatari - configuration.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CONFIGURATION_H #define HATARI_CONFIGURATION_H /* if header's struct contents depend on configuration options, header must include config.h */ #include "config.h" /* Logging and tracing */ typedef struct { char sLogFileName[FILENAME_MAX]; char sTraceFileName[FILENAME_MAX]; int nTextLogLevel; int nAlertDlgLogLevel; bool bConfirmQuit; bool bNatFeats; bool bConsoleWindow; /* for now, used just for Windows */ } CNF_LOG; /* debugger */ typedef struct { int nNumberBase; int nSymbolLines; int nMemdumpLines; int nFindLines; int nDisasmLines; int nBacktraceLines; int nExceptionDebugMask; int nDisasmOptions; bool bDisasmUAE; /* load and free symbols for GEMDOS HD loaded programs automatically */ bool bSymbolsAutoLoad; /* whether to match all symbols or just types relevant for given command */ bool bMatchAllSymbols; } CNF_DEBUGGER; /* ROM (TOS + cartridge) configuration */ typedef struct { char szTosImageFileName[FILENAME_MAX]; bool bPatchTos; char szCartridgeImageFileName[FILENAME_MAX]; } CNF_ROM; /* LILO (Linux loader) configuration */ typedef struct { char szCommandLine[256]; /* bootinfo CL_SIZE */ char szKernelFileName[FILENAME_MAX]; char szKernelSymbols[FILENAME_MAX]; char szRamdiskFileName[FILENAME_MAX]; bool bRamdiskToFastRam; bool bKernelToFastRam; bool bHaltOnReboot; } CNF_LILO; /* Sound configuration */ typedef struct { bool bEnableMicrophone; bool bEnableSound; bool bEnableSoundSync; int nPlaybackFreq; int SdlAudioBufferSize; char szYMCaptureFileName[FILENAME_MAX]; int YmVolumeMixing; } CNF_SOUND; /* RS232 / SCC configuration */ #define CNF_SCC_CHANNELS_MAX 3 #define CNF_SCC_CHANNELS_A_SERIAL 0 #define CNF_SCC_CHANNELS_A_LAN 1 #define CNF_SCC_CHANNELS_B 2 typedef struct { bool bEnableRS232; char szOutFileName[FILENAME_MAX]; char szInFileName[FILENAME_MAX]; bool EnableScc[CNF_SCC_CHANNELS_MAX]; char SccInFileName[CNF_SCC_CHANNELS_MAX][FILENAME_MAX]; char SccOutFileName[CNF_SCC_CHANNELS_MAX][FILENAME_MAX]; } CNF_RS232; /* Dialog Keyboard */ typedef enum { KEYMAP_SYMBOLIC, /* Use keymapping with symbolic (ASCII) key codes */ KEYMAP_SCANCODE, /* Use keymapping with PC keyboard scancodes */ KEYMAP_OLD_LOADED /* Used for config file in former versions */ } KEYMAPTYPE; typedef struct { bool bFastForwardKeyRepeat; KEYMAPTYPE nKeymapType; int nCountryCode; int nKbdLayout; int nLanguage; char szMappingFileName[FILENAME_MAX]; } CNF_KEYBOARD; typedef enum { SHORTCUT_OPTIONS, SHORTCUT_FULLSCREEN, SHORTCUT_BORDERS, SHORTCUT_MOUSEGRAB, SHORTCUT_COLDRESET, SHORTCUT_WARMRESET, SHORTCUT_SCREENSHOT, SHORTCUT_BOSSKEY, SHORTCUT_CURSOREMU, SHORTCUT_FASTFORWARD, SHORTCUT_RECANIM, SHORTCUT_RECSOUND, SHORTCUT_SOUND, SHORTCUT_DEBUG, SHORTCUT_PAUSE, SHORTCUT_QUIT, SHORTCUT_LOADMEM, SHORTCUT_SAVEMEM, SHORTCUT_INSERTDISKA, SHORTCUT_JOY_0, SHORTCUT_JOY_1, SHORTCUT_PAD_A, SHORTCUT_PAD_B, SHORTCUT_KEYS, /* number of shortcuts */ SHORTCUT_NONE } SHORTCUTKEYIDX; typedef struct { int withModifier[SHORTCUT_KEYS]; int withoutModifier[SHORTCUT_KEYS]; } CNF_SHORTCUT; typedef struct { int STRamSize_KB; int TTRamSize_KB; bool bAutoSave; char szMemoryCaptureFileName[FILENAME_MAX]; char szAutoSaveFileName[FILENAME_MAX]; } CNF_MEMORY; /* Joystick configuration */ typedef enum { JOYSTICK_DISABLED, JOYSTICK_REALSTICK, JOYSTICK_KEYBOARD } JOYSTICKMODE; #define JOYSTICK_MODES 3 #define JOYSTICK_BUTTONS 3 typedef struct { JOYSTICKMODE nJoystickMode; bool bEnableAutoFire; bool bEnableJumpOnFire2; int nJoyId; int nJoyButMap[JOYSTICK_BUTTONS]; int nKeyCodeUp, nKeyCodeDown, nKeyCodeLeft, nKeyCodeRight, nKeyCodeFire; int nKeyCodeB, nKeyCodeC, nKeyCodeOption, nKeyCodePause; int nKeyCodeStar, nKeyCodeHash, nKeyCodeNum[10]; } JOYSTICK; enum { JOYID_JOYSTICK0, JOYID_JOYSTICK1, JOYID_JOYPADA, JOYID_JOYPADB, JOYID_PARPORT1, JOYID_PARPORT2, JOYSTICK_COUNT }; typedef struct { JOYSTICK Joy[JOYSTICK_COUNT]; } CNF_JOYSTICKS; /* Disk image configuration */ typedef enum { WRITEPROT_OFF, WRITEPROT_ON, WRITEPROT_AUTO } WRITEPROTECTION; #define MAX_FLOPPYDRIVES 2 typedef struct { bool bAutoInsertDiskB; bool FastFloppy; /* true to speed up FDC emulation */ bool EnableDriveA; bool EnableDriveB; int DriveA_NumberOfHeads; int DriveB_NumberOfHeads; WRITEPROTECTION nWriteProtection; char szDiskZipPath[MAX_FLOPPYDRIVES][FILENAME_MAX]; char szDiskFileName[MAX_FLOPPYDRIVES][FILENAME_MAX]; char szDiskImageDirectory[FILENAME_MAX]; } CNF_DISKIMAGE; /* Hard drives configuration: C: - Z: */ #define MAX_HARDDRIVES 24 #define DRIVE_C 0 #define DRIVE_SKIP -1 typedef enum { GEMDOS_NOP, GEMDOS_UPPER, GEMDOS_LOWER } GEMDOS_CHR_CONV; typedef struct { int nGemdosDrive; bool bUseHardDiskDirectories; WRITEPROTECTION nWriteProtection; GEMDOS_CHR_CONV nGemdosCase; bool bFilenameConversion; bool bGemdosHostTime; bool bBootFromHardDisk; char szHardDiskDirectories[MAX_HARDDRIVES][FILENAME_MAX]; } CNF_HARDDISK; /* SCSI/ACSI/IDE configuration */ #define MAX_ACSI_DEVS 8 #define MAX_SCSI_DEVS 8 #define MAX_IDE_DEVS 2 typedef struct { bool bUseDevice; char sDeviceFile[FILENAME_MAX]; int nBlockSize; int nScsiVersion; } CNF_SCSIDEV; typedef enum { BYTESWAP_OFF, BYTESWAP_ON, BYTESWAP_AUTO } BYTESWAPPING; typedef struct { bool bUseDevice; BYTESWAPPING nByteSwap; char sDeviceFile[FILENAME_MAX]; int nBlockSize; int nDeviceType; } CNF_IDEDEV; /* Falcon register $FFFF8006 bits 6 & 7 (mirrored in $FFFF82C0 bits 0 & 1): * 00 Monochrome * 01 RGB - Colormonitor * 10 VGA - Colormonitor * 11 TV */ #define FALCON_MONITOR_MONO 0x00 /* SM124 */ #define FALCON_MONITOR_RGB 0x40 #define FALCON_MONITOR_VGA 0x80 #define FALCON_MONITOR_TV 0xC0 typedef enum { MONITOR_TYPE_MONO, MONITOR_TYPE_RGB, MONITOR_TYPE_VGA, MONITOR_TYPE_TV } MONITORTYPE; /* Screen configuration */ typedef struct { MONITORTYPE nMonitorType; bool DisableVideo; bool bFullScreen; bool bAllowOverscan; bool bAspectCorrect; bool bShowStatusbar; bool bShowDriveLed; bool bMouseWarp; bool bCrop; bool bForceMax; bool bUseExtVdiResolutions; bool bKeepResolution; bool bResizable; bool bUseVsync; bool bUseSdlRenderer; int ScreenShotFormat; char szScreenShotDir[FILENAME_MAX]; float nZoomFactor; int nSpec512Threshold; int nVdiColors; int nVdiWidth; int nVdiHeight; int nMaxWidth; int nMaxHeight; int nFrameSkips; } CNF_SCREEN; /* Printer configuration */ typedef struct { bool bEnablePrinting; char szPrintToFileName[FILENAME_MAX]; } CNF_PRINTER; #define MAX_MIDI_PORT_NAME 256 /* a guess */ /* Midi configuration */ typedef struct { bool bEnableMidi; char sMidiInFileName[FILENAME_MAX]; char sMidiOutFileName[FILENAME_MAX]; char sMidiInPortName[MAX_MIDI_PORT_NAME]; char sMidiOutPortName[MAX_MIDI_PORT_NAME]; } CNF_MIDI; /* Dialog System */ typedef enum { MACHINE_ST, MACHINE_MEGA_ST, MACHINE_STE, MACHINE_MEGA_STE, MACHINE_TT, MACHINE_FALCON } MACHINETYPE; typedef enum { DSP_TYPE_NONE, DSP_TYPE_DUMMY, DSP_TYPE_EMU } DSPTYPE; typedef enum { FPU_NONE = 0, FPU_68881 = 68881, FPU_68882 = 68882, FPU_CPU = 68040 } FPUTYPE; typedef enum { VIDEO_TIMING_MODE_RANDOM = 0, VIDEO_TIMING_MODE_WS1, VIDEO_TIMING_MODE_WS2, VIDEO_TIMING_MODE_WS3, VIDEO_TIMING_MODE_WS4, } VIDEOTIMINGMODE; typedef struct { int nCpuLevel; int nCpuFreq; bool bCompatibleCpu; /* Prefetch mode */ MACHINETYPE nMachineType; bool bBlitter; /* TRUE if Blitter is enabled */ DSPTYPE nDSPType; /* how to "emulate" DSP */ int nRtcYear; bool bPatchTimerD; bool bFastBoot; /* Enable to patch TOS for fast boot */ bool bFastForward; bool bAddressSpace24; /* true if using a 24-bit address bus */ VIDEOTIMINGMODE VideoTimingMode; bool bCycleExactCpu; bool bCpuDataCache; FPUTYPE n_FPUType; bool bCompatibleFPU; /* More compatible FPU */ bool bSoftFloatFPU; bool bMMU; /* TRUE if MMU is enabled */ } CNF_SYSTEM; typedef struct { int AviRecordVcodec; int AviRecordFps; char AviRecordFile[FILENAME_MAX]; } CNF_VIDEO; /* State of system is stored in this structure */ /* On reset, variables are copied into system globals and used. */ typedef struct { /* Configure */ CNF_LOG Log; CNF_DEBUGGER Debugger; CNF_SCREEN Screen; CNF_JOYSTICKS Joysticks; CNF_KEYBOARD Keyboard; CNF_SHORTCUT Shortcut; CNF_SOUND Sound; CNF_MEMORY Memory; CNF_DISKIMAGE DiskImage; CNF_HARDDISK HardDisk; CNF_SCSIDEV Acsi[MAX_ACSI_DEVS]; CNF_SCSIDEV Scsi[MAX_SCSI_DEVS]; CNF_IDEDEV Ide[MAX_IDE_DEVS]; CNF_ROM Rom; CNF_LILO Lilo; CNF_RS232 RS232; CNF_PRINTER Printer; CNF_MIDI Midi; CNF_SYSTEM System; CNF_VIDEO Video; } CNF_PARAMS; extern CNF_PARAMS ConfigureParams; extern char sConfigFileName[FILENAME_MAX]; static inline bool Config_IsMachineST(void) { return ConfigureParams.System.nMachineType == MACHINE_ST || ConfigureParams.System.nMachineType == MACHINE_MEGA_ST; } static inline bool Config_IsMachineSTE(void) { return ConfigureParams.System.nMachineType == MACHINE_STE || ConfigureParams.System.nMachineType == MACHINE_MEGA_STE; } static inline bool Config_IsMachineMegaSTE(void) { return ConfigureParams.System.nMachineType == MACHINE_MEGA_STE; } static inline bool Config_IsMachineTT(void) { return ConfigureParams.System.nMachineType == MACHINE_TT; } static inline bool Config_IsMachineFalcon(void) { return ConfigureParams.System.nMachineType == MACHINE_FALCON; } extern void Configuration_SetDefault(void); extern void Configuration_Apply(bool bReset); extern void Configuration_Load(const char *psFileName); extern void Configuration_Save(void); extern void Configuration_MemorySnapShot_Capture(bool bSave); extern void Configuration_ChangeCpuFreq ( int CpuFreq_new ); #ifdef EMSCRIPTEN extern void Configuration_ChangeMemory(int RamSizeKb); extern void Configuration_ChangeSystem(int nMachineType); extern void Configuration_ChangeTos(const char* szTosImageFileName); extern void Configuration_ChangeUseHardDiskDirectories(bool bUseHardDiskDirectories); extern void Configuration_ChangeFastForward(bool bFastForwardActive); #endif #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/control.h000066400000000000000000000020141504763705000247640ustar00rootroot00000000000000/* Hatari - change.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CONTROL_H #define HATARI_CONTROL_H #include "main.h" extern void Control_ProcessBuffer(const char *buffer); /* supported only on BSD compatible / POSIX compliant systems */ #if HAVE_UNIX_DOMAIN_SOCKETS extern bool Control_CheckUpdates(void); extern void Control_RemoveFifo(void); extern const char* Control_SetFifo(const char *fifopath); extern const char* Control_SetSocket(const char *socketpath); extern void Control_ReparentWindow(int width, int height, bool noembed); #else #define Control_CheckUpdates() false #define Control_RemoveFifo() false #define Control_SetFifo(path) "Command FIFO is not supported on this platform." #define Control_SetSocket(path) "Control socket is not supported on this platform." #define Control_ReparentWindow(width, height, noembed); #endif /* HAVE_UNIX_DOMAIN_SOCKETS */ #endif /* HATARI_CONTROL_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/createBlankImage.h000066400000000000000000000004751504763705000264730ustar00rootroot00000000000000/* Hatari - createBlankImage.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern bool CreateBlankImage_CreateFile(const char *pszFileName, int nTracks, int nSectors, int nSides, const char *VolumeLabel); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/cycInt.h000066400000000000000000000073251504763705000245470ustar00rootroot00000000000000/* Hatari - cycInt.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CYCINT_H #define HATARI_CYCINT_H /* Interrupt handlers in system */ typedef enum { INTERRUPT_NULL, /* should always be the first of the list with value '0' */ INTERRUPT_VIDEO_VBL, INTERRUPT_VIDEO_HBL, INTERRUPT_VIDEO_ENDLINE, INTERRUPT_MFP_MAIN_TIMERA, INTERRUPT_MFP_MAIN_TIMERB, INTERRUPT_MFP_MAIN_TIMERC, INTERRUPT_MFP_MAIN_TIMERD, INTERRUPT_MFP_TT_TIMERA, INTERRUPT_MFP_TT_TIMERB, INTERRUPT_MFP_TT_TIMERC, INTERRUPT_MFP_TT_TIMERD, INTERRUPT_ACIA_IKBD, INTERRUPT_IKBD_RESETTIMER, INTERRUPT_IKBD_AUTOSEND, INTERRUPT_DMASOUND_MICROWIRE, /* Used for both STE and Falcon Microwire emulation */ INTERRUPT_CROSSBAR_25MHZ, INTERRUPT_CROSSBAR_32MHZ, INTERRUPT_FDC, INTERRUPT_BLITTER, INTERRUPT_MIDI, INTERRUPT_SCC_BRG_A, INTERRUPT_SCC_TX_RX_A, INTERRUPT_SCC_RX_A, INTERRUPT_SCC_BRG_B, INTERRUPT_SCC_TX_RX_B, INTERRUPT_SCC_RX_B, MAX_INTERRUPTS } interrupt_id; #define INT_CPU_CYCLE 1 #define INT_MFP_CYCLE 2 #define INT_CPU8_CYCLE 3 /* Simulate extra bits of internal decimal precision, as MFP cycles don't convert to an integer number of CPU cycles */ #define CYCINT_SHIFT 8 /* Convert CPU or MFP cycles to internal cycles */ #define INT_CONVERT_TO_INTERNAL( cyc , type ) ( type == INT_CPU_CYCLE ? (cyc) << CYCINT_SHIFT : \ type == INT_MFP_CYCLE ? (int)( ( (uint64_t)( (cyc) << CYCINT_SHIFT ) * MachineClocks.CPU_Freq_Emul ) / MachineClocks.MFP_Timer_Freq ) : \ (cyc) << ( nCpuFreqShift + CYCINT_SHIFT ) ) /* Convert internal cycles to real CPU or MFP cycles */ #define INT_CONVERT_FROM_INTERNAL( cyc , type ) ( type == INT_CPU_CYCLE ? (cyc) >> CYCINT_SHIFT : \ type == INT_MFP_CYCLE ? (int)( ( (uint64_t)(cyc) * MachineClocks.MFP_Timer_Freq ) / MachineClocks.CPU_Freq_Emul ) >> CYCINT_SHIFT : \ (cyc) >> ( nCpuFreqShift + CYCINT_SHIFT ) ) extern void (*PendingInterruptFunction)(void); extern int PendingInterruptCount; extern uint64_t CycInt_ActiveInt_Cycles; extern void CycInt_Reset(void); extern void CycInt_MemorySnapShot_Capture(bool bSave); extern void CycInt_AcknowledgeInterrupt(void); extern void CycInt_AddAbsoluteInterrupt(int CycleTime, int CycleType, interrupt_id Handler); extern void CycInt_AddRelativeInterrupt(int CycleTime, int CycleType, interrupt_id Handler); extern void CycInt_AddRelativeInterruptWithOffset(int CycleTime, int CycleType, interrupt_id Handler, int CycleOffset); extern void CycInt_ModifyInterrupt(int CycleTime, int CycleType, interrupt_id Handler); extern void CycInt_RemovePendingInterrupt(interrupt_id Handler); extern int CycInt_FindCyclesRemaining(interrupt_id Handler, int CycleType); extern bool CycInt_InterruptActive(interrupt_id Handler); extern int CycInt_GetActiveInt(void); extern void CycInt_CallActiveHandler(uint64_t Clock); static inline void CycInt_Process(void) { while ( CycInt_ActiveInt_Cycles <= ( CyclesGlobalClockCounter << CYCINT_SHIFT ) ) CycInt_CallActiveHandler( CyclesGlobalClockCounter ); } static inline void CycInt_Process_stop(int stop_cond) { while ( ( CycInt_ActiveInt_Cycles <= ( CyclesGlobalClockCounter << CYCINT_SHIFT ) ) && ( stop_cond == 0 ) ) CycInt_CallActiveHandler( CyclesGlobalClockCounter ); } /* Same as CycInt_Process but use a specific cycles clock value */ static inline void CycInt_Process_Clock(uint64_t Clock) { while ( CycInt_ActiveInt_Cycles <= ( Clock << CYCINT_SHIFT ) ) CycInt_CallActiveHandler( Clock ); } /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ extern bool CycInt_From_Opcode; /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ #endif /* ifndef HATARI_CYCINT_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/cycles.h000066400000000000000000000020051504763705000245660ustar00rootroot00000000000000/* Hatari - cycles.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_CYCLES_H #define HATARI_CYCLES_H #include enum { CYCLES_COUNTER_VIDEO, CYCLES_COUNTER_MAX }; extern int nCyclesMainCounter; // TODO : remove, use CyclesGlobalClockCounter instead extern uint64_t CyclesGlobalClockCounter; extern int CurrentInstrCycles; extern void Cycles_MemorySnapShot_Capture(bool bSave); extern void Cycles_SetCounter(int nId, int nValue); extern int Cycles_GetCounter(int nId); extern int Cycles_GetInternalCycleOnReadAccess(void); extern int Cycles_GetInternalCycleOnWriteAccess(void); extern int Cycles_GetCounterOnReadAccess(int nId); extern int Cycles_GetCounterOnWriteAccess(int nId); extern uint64_t Cycles_GetClockCounterOnReadAccess(void); extern uint64_t Cycles_GetClockCounterOnWriteAccess(void); extern uint64_t Cycles_GetClockCounterImmediate(void); #endif /* HATARI_CYCLES_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/dialog.h000066400000000000000000000020211504763705000245410ustar00rootroot00000000000000/* Hatari - dialog.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_DIALOG_H #define HATARI_DIALOG_H #include "configuration.h" /* prototypes for gui-sdl/dlg*.c functions: */ extern int Dialog_MainDlg(bool *bReset, bool *bLoadedSnapshot); extern void Dialog_AboutDlg(void); extern void DlgCpu_Main(void); extern bool DlgAlert_Notice(const char *text); extern bool DlgAlert_Query(const char *text); extern void Dialog_DeviceDlg(void); extern void DlgFloppy_Main(void); extern void Dialog_HaltDlg(void); extern void DlgHardDisk_Main(void); extern void Dialog_JoyDlg(void); extern void Dialog_KeyboardDlg(void); extern bool Dialog_MemDlg(void); extern char* DlgNewDisk_Main(void); extern void Dialog_MonitorDlg(void); extern void Dialog_WindowDlg(void); extern void Dialog_SoundDlg(void); extern void DlgSystem_Main(void); extern void DlgRom_Main(void); /* and dialog.c */ extern bool Dialog_DoProperty(void); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/dim.h000066400000000000000000000006721504763705000240650ustar00rootroot00000000000000/* Hatari - dim.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern bool DIM_FileNameIsDIM(const char *pszFileName, bool bAllowGZ); extern uint8_t *DIM_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType); extern bool DIM_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/dmaSnd.h000066400000000000000000000034221504763705000245160ustar00rootroot00000000000000/* Hatari - dmaSnd.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_DMASND_H #define HATARI_DMASND_H #define DMASNDCTRL_PLAY 0x01 #define DMASNDCTRL_PLAYLOOP 0x02 #define DMASNDMODE_MONO 0x80 extern uint16_t nDmaSoundControl; extern void DmaSnd_Reset(bool bCold); extern void DmaSnd_MemorySnapShot_Capture(bool bSave); extern uint8_t DmaSnd_Get_XSINT_Line(void); extern void DmaSnd_GenerateSamples(int nMixBufIdx, int nSamplesToGenerate); extern void DmaSnd_STE_HBL_Update(void); extern void DmaSnd_SoundControl_ReadWord(void); extern void DmaSnd_SoundControl_WriteWord(void); extern void DmaSnd_FrameCountHigh_ReadByte(void); extern void DmaSnd_FrameCountMed_ReadByte(void); extern void DmaSnd_FrameCountLow_ReadByte(void); extern void DmaSnd_FrameStartHigh_WriteByte(void); extern void DmaSnd_FrameStartMed_WriteByte(void); extern void DmaSnd_FrameStartLow_WriteByte(void); extern void DmaSnd_FrameCountHigh_WriteByte(void); extern void DmaSnd_FrameCountMed_WriteByte(void); extern void DmaSnd_FrameCountLow_WriteByte(void); extern void DmaSnd_FrameEndHigh_WriteByte(void); extern void DmaSnd_FrameEndMed_WriteByte(void); extern void DmaSnd_FrameEndLow_WriteByte(void); extern void DmaSnd_SoundModeCtrl_ReadByte(void); extern void DmaSnd_SoundModeCtrl_WriteByte(void); extern void DmaSnd_InterruptHandler_Microwire(void); extern void DmaSnd_MicrowireData_ReadWord(void); extern void DmaSnd_MicrowireData_WriteWord(void); extern void DmaSnd_MicrowireMask_ReadWord(void); extern void DmaSnd_MicrowireMask_WriteWord(void); extern void DmaSnd_Init_Bass_and_Treble_Tables(void); extern void DmaSnd_Info(FILE *fp, uint32_t dummy); #endif /* HATARI_DMASND_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/fdc.h000066400000000000000000000125521504763705000240500ustar00rootroot00000000000000/* Hatari - fdc.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_FDC_H #define HATARI_FDC_H /* Values for the Size byte in the Address Field of a sector */ #define FDC_SECTOR_SIZE_MASK 0x03 /* Only bits 0-1 of the Sector size in the ID field are used by the WD1772 */ #define FDC_SECTOR_SIZE_128 0 /* Sector size used in the ID fields */ #define FDC_SECTOR_SIZE_256 1 #define FDC_SECTOR_SIZE_512 2 #define FDC_SECTOR_SIZE_1024 3 /* These are some standard GAP values to format a track with 9 or 10 sectors */ /* When handling ST/MSA disk images, those values are required to get accurate */ /* timings when emulating disk's spin and index's position. */ /* Those values are also use to build standard sector in STX disk images when */ /* track contains only the sector data and no sector info. */ #define FDC_TRACK_LAYOUT_STANDARD_GAP1 60 /* Track Pre GAP : 0x4e */ #define FDC_TRACK_LAYOUT_STANDARD_GAP2 12 /* Sector ID Pre GAP : 0x00 */ #define FDC_TRACK_LAYOUT_STANDARD_GAP3a 22 /* Sector ID Post GAP : 0x4e */ #define FDC_TRACK_LAYOUT_STANDARD_GAP3b 12 /* Sector DATA Pre GAP : 0x00 */ #define FDC_TRACK_LAYOUT_STANDARD_GAP4 40 /* Sector DATA Pre GAP : 0x4e */ #define FDC_TRACK_LAYOUT_STANDARD_GAP5 0 /* Track Post GAP : 0x4e (to fill the rest of the track, value is variable) */ /* GAP5 is 664 bytes for 9 sectors or 50 bytes for 10 sectors */ /* Size of a raw standard 512 byte sector in a track, including ID field and all GAPs : 614 bytes */ /* (this must be the same as the data returned in FDC_UpdateReadTrackCmd() ) */ #define FDC_TRACK_LAYOUT_STANDARD_RAW_SECTOR_512 ( FDC_TRACK_LAYOUT_STANDARD_GAP2 \ + 3 + 1 + 6 + FDC_TRACK_LAYOUT_STANDARD_GAP3a + FDC_TRACK_LAYOUT_STANDARD_GAP3b \ + 3 + 1 + 512 + 2 + FDC_TRACK_LAYOUT_STANDARD_GAP4 ) #define FDC_IRQ_SOURCE_COMPLETE (1<<0) /* IRQ set after completing a command */ #define FDC_IRQ_SOURCE_INDEX (1<<1) /* IRQ set when COND_IP is set and index is reached */ #define FDC_IRQ_SOURCE_FORCED (1<<2) /* IRQ was forced by a previous Dx command with COND_IMMEDIATE */ #define FDC_IRQ_SOURCE_HDC (1<<3) /* IRQ set by HDC */ #define FDC_IRQ_SOURCE_OTHER (1<<4) /* IRQ set by other parts (IPF) */ /* Option bits in the command register */ #define FDC_COMMAND_BIT_VERIFY (1<<2) /* 0=no verify after type I, 1=verify after type I */ #define FDC_COMMAND_BIT_HEAD_LOAD (1<<2) /* for type II/III 0=no extra delay, 1=add 30 ms delay to set the head */ #define FDC_COMMAND_BIT_SPIN_UP (1<<3) /* 0=enable motor's spin up, 1=disable motor's spin up */ #define FDC_COMMAND_BIT_UPDATE_TRACK (1<<4) /* 0=don't update TR after type I, 1=update TR after type I */ #define FDC_COMMAND_BIT_MULTIPLE_SECTOR (1<<4) /* 0=read/write only 1 sector, 1=read/write many sectors */ #define FDC_INTERRUPT_COND_IP (1<<2) /* Force interrupt on Index Pulse */ #define FDC_INTERRUPT_COND_IMMEDIATE (1<<3) /* Force interrupt immediate */ #define FDC_DC_SIGNAL_EJECTED 0 #define FDC_DC_SIGNAL_INSERTED 1 extern int FDC_StepRate_ms[]; extern void FDC_MemorySnapShot_Capture ( bool bSave ); extern void FDC_Init ( void ); extern void FDC_Reset ( bool bCold ); extern void FDC_SetDMAStatus ( bool bError ); extern void FDC_SetIRQ ( uint8_t IRQ_Source ); extern void FDC_ClearIRQ ( void ); extern void FDC_ClearHdcIRQ(void); extern void FDC_InterruptHandler_Update ( void ); extern void FDC_Drive_Set_BusyLed ( uint8_t SR ); extern int FDC_Get_Statusbar_Text ( char *text, size_t maxlen ); extern void FDC_Drive_Set_Enable ( int Drive , bool value ); extern void FDC_Drive_Set_NumberOfHeads ( int Drive , int NbrHeads ); extern void FDC_InsertFloppy ( int Drive ); extern void FDC_EjectFloppy ( int Drive ); extern void FDC_SetDriveSide ( uint8_t io_porta_old , uint8_t io_porta_new ); extern int FDC_GetBytesPerTrack ( uint8_t Drive , uint8_t Track , uint8_t Side ); extern int FDC_GetFloppyDensity ( uint8_t Drive ); extern int FDC_MachineHandleDensity ( uint8_t Drive ); extern int FDC_IndexPulse_GetCurrentPos_FdcCycles ( uint32_t *pFdcCyclesPerRev ); extern int FDC_IndexPulse_GetCurrentPos_NbBytes ( void ); extern int FDC_IndexPulse_GetState ( void ); extern int FDC_NextIndexPulse_FdcCycles ( void ); extern uint8_t FDC_GetCmdType ( uint8_t CR ); extern void FDC_DiskController_WriteWord ( void ); extern void FDC_DiskControllerStatus_ReadWord ( void ); extern void FDC_DmaModeControl_WriteWord ( void ); extern void FDC_DmaStatus_ReadWord ( void ); extern int FDC_DMA_GetModeControl_R_WR ( void ); extern int FDC_DMA_GetMode(void); extern void FDC_DMA_FIFO_Push ( uint8_t Byte ); extern uint8_t FDC_DMA_FIFO_Pull ( void ); extern int FDC_DMA_GetSectorCount ( void ); extern void FDC_Buffer_Reset ( void ); extern void FDC_Buffer_Add_Timing ( uint8_t Byte , uint16_t Timing ); extern void FDC_Buffer_Add ( uint8_t Byte ); extern uint16_t FDC_Buffer_Read_Timing ( void ); extern uint8_t FDC_Buffer_Read_Byte ( void ); extern uint8_t FDC_Buffer_Read_Byte_pos ( int pos ); extern int FDC_Buffer_Get_Size ( void ); extern void FDC_DmaAddress_ReadByte ( void ); extern void FDC_DmaAddress_WriteByte ( void ); extern uint32_t FDC_GetDMAAddress ( void ); extern void FDC_WriteDMAAddress ( uint32_t Address ); extern void FDC_DensityMode_WriteWord ( void ); extern void FDC_DensityMode_ReadWord ( void ); #endif /* ifndef HATARI_FDC_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/file.h000066400000000000000000000047231504763705000242340ustar00rootroot00000000000000/* Hatari - file.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_FILE_H #define HATARI_FILE_H #include "config.h" #include /* Needed for off_t */ #ifndef HAVE_FSEEKO #define fseeko fseek #endif #ifndef HAVE_FTELLO #define ftello ftell #endif extern void File_CleanFileName(char *pszFileName); extern void File_AddSlashToEndFileName(char *pszFileName); extern bool File_DoesFileExtensionMatch(const char *pszFileName, const char *pszExtension); extern bool File_ChangeFileExtension(const char *Filename_old, const char *Extension_old , char *Filename_new , const char *Extension_new); extern const char *File_RemoveFileNameDrive(const char *pszFileName); extern bool File_DoesFileNameEndWithSlash(char *pszFileName); extern uint8_t *File_ZlibRead(const char *pszFileName, long *pFileSize); extern uint8_t *File_ReadAsIs(const char *pszFileName, long *pFileSize); extern uint8_t *File_Read(const char *pszFileName, long *pFileSize, const char * const ppszExts[]); extern bool File_Save(const char *pszFileName, const uint8_t *pAddress, size_t Size, bool bQueryOverwrite); extern off_t File_Length(const char *pszFileName); extern bool File_Exists(const char *pszFileName); extern bool File_DirExists(const char *psDirName); extern bool File_QueryOverwrite(const char *pszFileName); extern char* File_FindPossibleExtFileName(const char *pszFileName,const char * const ppszExts[]); extern void File_SplitPath(const char *pSrcFileName, char *pDir, char *pName, char *Ext); extern char* File_MakePath(const char *pDir, const char *pName, const char *pExt); extern int File_MakePathBuf(char *buf, size_t buflen, const char *pDir, const char *pName, const char *pExt); extern void File_ShrinkName(char *pDestFileName, const char *pSrcFileName, int maxlen); extern FILE *File_Open(const char *path, const char *mode); extern FILE *File_Close(FILE *fp); extern bool File_Lock(FILE *fp); extern void File_UnLock(FILE *fp); extern bool File_InputAvailable(FILE *fp); extern const char *File_Basename(const char *path); extern void File_MakeAbsoluteSpecialName(char *pszFileName); extern void File_MakeAbsoluteName(char *pszFileName); extern void File_MakeValidPathName(char *pPathName); extern void File_PathShorten(char *path, int dirs); extern void File_HandleDotDirs(char *path); extern FILE *File_OpenTempFile(char **name); #endif /* HATARI_FILE_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/floppy.h000066400000000000000000000045271504763705000246300ustar00rootroot00000000000000/* Hatari - floppy.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_FLOPPY_H #define HATARI_FLOPPY_H #include "configuration.h" #define MAX_FLOPPYDRIVES 2 /* A: and B: */ #define NUMBYTESPERSECTOR 512 /* All supported disk images are 512 bytes per sector */ #define FLOPPY_DRIVE_TRANSITION_STATE_INSERT 1 #define FLOPPY_DRIVE_TRANSITION_STATE_EJECT 2 #define FLOPPY_DRIVE_TRANSITION_DELAY_VBL 18 /* min of 16 VBLs */ #define FLOPPY_IMAGE_TYPE_NONE 0 /* no recognized image inserted */ #define FLOPPY_IMAGE_TYPE_ST 1 #define FLOPPY_IMAGE_TYPE_MSA 2 #define FLOPPY_IMAGE_TYPE_DIM 3 #define FLOPPY_IMAGE_TYPE_IPF 4 /* handled by capsimage library */ #define FLOPPY_IMAGE_TYPE_STX 5 #define FLOPPY_BOOT_SECTOR_EXE_SUM 0x1234 /* Structure for each drive connected as emulation */ typedef struct { int ImageType; uint8_t *pBuffer; char sFileName[FILENAME_MAX]; int nImageBytes; bool bDiskInserted; bool bContentsChanged; bool bOKToSave; /* For the emulation of the WPRT bit when a disk is changed */ int TransitionState1; int TransitionState1_VBL; int TransitionState2; int TransitionState2_VBL; } EMULATION_DRIVE; extern EMULATION_DRIVE EmulationDrives[MAX_FLOPPYDRIVES]; extern int nBootDrive; extern void Floppy_Init(void); extern void Floppy_UnInit(void); extern void Floppy_Reset(void); extern void Floppy_MemorySnapShot_Capture(bool bSave); extern void Floppy_GetBootDrive(void); extern bool Floppy_IsWriteProtected(int Drive); extern const char* Floppy_SetDiskFileNameNone(int Drive); extern const char* Floppy_SetDiskFileName(int Drive, const char *pszFileName, const char *pszZipPath); extern int Floppy_DriveTransitionUpdateState ( int Drive ); extern bool Floppy_InsertDiskIntoDrive(int Drive); extern bool Floppy_EjectDiskFromDrive(int Drive); extern void Floppy_FindDiskDetails(const uint8_t *pBuffer, int nImageBytes, uint16_t *pnSectorsPerTrack, uint16_t *pnSides); extern bool Floppy_ReadSectors(int Drive, uint8_t **pBuffer, uint16_t Sector, uint16_t Track, uint16_t Side, short Count, int *pnSectorsPerTrack, int *pSectorSize); extern bool Floppy_WriteSectors(int Drive, uint8_t *pBuffer, uint16_t Sector, uint16_t Track, uint16_t Side, short Count, int *pnSectorsPerTrack, int *pSectorSize); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/floppy_ipf.h000066400000000000000000000022501504763705000254550ustar00rootroot00000000000000/* Hatari - floppy_ipf.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern void IPF_MemorySnapShot_Capture(bool bSave); extern bool IPF_FileNameIsIPF(const char *pszFileName, bool bAllowGZ); extern uint8_t *IPF_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType); extern bool IPF_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize); extern bool IPF_Init ( void ); extern void IPF_Exit ( void ); extern bool IPF_Insert ( int Drive , uint8_t *pImageBuffer , long ImageSize ); extern bool IPF_Eject ( int Drive ); extern void IPF_Reset ( bool bCold ); extern void IPF_Drive_Set_Enable ( int Drive , bool value ); extern void IPF_Drive_Set_DoubleSided ( int Drive , bool value ); extern void IPF_SetDriveSide ( uint8_t io_porta_old , uint8_t io_porta_new ); extern void IPF_FDC_WriteReg ( uint8_t Reg , uint8_t Byte ); extern uint8_t IPF_FDC_ReadReg ( uint8_t Reg ); extern void IPF_FDC_StatusBar ( uint8_t *pCommand , uint8_t *pHead , uint8_t *pTrack , uint8_t *pSector , uint8_t *pSide ); extern void IPF_Emulate ( void ); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/floppy_stx.h000066400000000000000000000201471504763705000255220ustar00rootroot00000000000000/* Hatari - floppy_stx.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ typedef struct { /* Content of the STX sector block (16 bytes) */ uint32_t DataOffset; /* Offset of sector data in the track data */ uint16_t BitPosition; /* Position in bits from the start of the track */ /* (this seems to be the position of the start of the ID field, */ /* just after the IDAM, but it's not always precise) */ uint16_t ReadTime; /* in ms */ uint8_t ID_Track; /* Content of the Address Field */ uint8_t ID_Head; uint8_t ID_Sector; uint8_t ID_Size; uint16_t ID_CRC; uint8_t FDC_Status; /* FDC status and flags for this sector */ uint8_t Reserved; /* Unused, always 0 */ /* Other internal variables */ uint16_t SectorSize; /* In bytes, depends on ID_Size */ uint8_t *pData; /* Bytes for this sector or null if RNF */ uint8_t *pFuzzyData; /* Fuzzy mask for this sector or null if no fuzzy bits */ uint8_t *pTimingData; /* Data for variable bit width or null */ int32_t SaveSectorIndex; /* Index in STX_SaveStruct[].pSaveSectorsStruct or -1 if not used */ } STX_SECTOR_STRUCT; #define STX_SECTOR_BLOCK_SIZE ( 4+2+2+1+1+1+1+2+1+1 ) /* Size of the sector block in an STX file = 16 bytes */ /* NOTE : bits 3,4,5 have the same meaning as in the FDC's Status register */ #define STX_SECTOR_FLAG_VARIABLE_TIME (1<<0) /* bit 0, if set, this sector has variable bit width */ #define STX_SECTOR_FLAG_LOST_DATA (1<<3) /* bit 3, if set, data were lost while reading/writing */ #define STX_SECTOR_FLAG_CRC (1<<3) /* bit 3, if set, there's a CRC error */ #define STX_SECTOR_FLAG_RNF (1<<4) /* bit 4, if set, there's no sector data */ #define STX_SECTOR_FLAG_RECORD_TYPE (1<<5) /* bit 5, if set, deleted data */ #define STX_SECTOR_FLAG_FUZZY (1<<7) /* bit 7, if set, this sector has fuzzy bits */ #define STX_SECTOR_READ_TIME_DEFAULT 16384 /* Default value if ReadTime==0 */ typedef struct { /* Content of the STX track block (16 bytes) */ uint32_t BlockSize; /* Number of bytes in this track block */ uint32_t FuzzySize; /* Number of bytes in fuzzy mask */ uint16_t SectorsCount; /* Number of sector blocks in this track */ uint16_t Flags; /* Flags for this track */ uint16_t MFMSize; /* Number of MFM bytes in this track */ uint8_t TrackNumber; /* bits 0-6 = track number bit 7 = side */ uint8_t RecordType; /* Unused */ /* Other internal variables */ STX_SECTOR_STRUCT *pSectorsStruct; /* All the sectors struct for this track or null */ uint8_t *pFuzzyData; /* Fuzzy mask data for all the fuzzy sectors of the track */ uint8_t *pTrackData; /* Track data (after sectors data and fuzzy data) */ uint16_t TrackImageSyncPosition; uint16_t TrackImageSize; /* Number of bytes in pTrackImageData */ uint8_t *pTrackImageData; /* Optional data as returned by the read track command */ uint8_t *pSectorsImageData; /* Optional data for the sectors of this track */ uint8_t *pTiming; uint16_t TimingFlags; /* always '5' ? */ uint16_t TimingSize; uint8_t *pTimingData; /* Timing data for all the sectors of the track ; each timing */ /* consists of 2 bytes per 16 FDC bytes */ int32_t SaveTrackIndex; /* Index in STX_SaveStruct[].pSaveTracksStruct or -1 if not used */ } STX_TRACK_STRUCT; #define STX_TRACK_BLOCK_SIZE ( 4+4+2+2+2+1+1 ) /* Size of the track block in an STX file = 16 bytes */ #define STX_TRACK_FLAG_SECTOR_BLOCK (1<<0) /* bit 0, if set, this track contains sector blocks */ #define STX_TRACK_FLAG_TRACK_IMAGE (1<<6) /* bit 6, if set, this track contains a track image */ #define STX_TRACK_FLAG_TRACK_IMAGE_SYNC (1<<7) /* bit 7, if set, the track image has a sync position */ #define STX_HEADER_ID "RSY\0" /* All STX files should start with these 4 bytes */ #define STX_HEADER_ID_LEN 4 /* Header ID has 4 bytes */ typedef struct { /* Content of the STX header block (16 bytes) */ char FileID[ 4 ]; /* Should be "RSY\0" */ uint16_t Version; /* Only version 3 is supported */ uint16_t ImagingTool; /* 0x01 (Atari Tool) or 0xCC (Discovery Cartridge) */ uint16_t Reserved_1; /* Unused */ uint8_t TracksCount; /* Number of track blocks in this file */ uint8_t Revision; /* 0x00 (old Pasti file) 0x02 (new Pasti file) */ uint32_t Reserved_2; /* Unused */ /* Other internal variables */ STX_TRACK_STRUCT *pTracksStruct; /* These variable are used to warn the user only one time if a write command is made */ bool WarnedWriteSector; /* True if a 'write sector' command was made and user was warned */ bool WarnedWriteTrack; /* True if a 'write track' command was made and user was warned */ } STX_MAIN_STRUCT; #define STX_MAIN_BLOCK_SIZE ( 4+2+2+2+1+1+4 ) /* Size of the header block in an STX file = 16 bytes */ /* Additional structures used to save the data for the 'write sector' and 'write track' commands */ /* TODO : data are only saved in memory / snapshot and will be lost when exiting. */ /* We should have a file format to store them with the .STX file */ typedef struct { /* Copy track/side + ID field + BitPosition to uniquely identify each sector */ uint8_t Track; uint8_t Side; uint16_t BitPosition; uint8_t ID_Track; /* Content of the Address Field */ uint8_t ID_Head; uint8_t ID_Sector; uint8_t ID_Size; uint16_t ID_CRC; uint16_t SectorSize; /* Number of bytes in this sector */ uint8_t *pData; /* Data written for this sector */ uint8_t StructIsUsed; /* >0 : this structure contains info (and must be saved) */ /* =0 : this structure is free and can be reused for another sector */ } STX_SAVE_SECTOR_STRUCT; typedef struct { uint8_t Track; uint8_t Side; uint16_t TrackSizeWrite; /* Number of bytes in this track (when writing) */ /* (can be rounded to 16 because of DMA buffering */ uint8_t *pDataWrite; /* Data written for this track */ uint16_t TrackSizeRead; /* Number of bytes in this track (when reading) */ /* Due to interpreting bytes $F5-$FF, TrackSizeRead will often be > TrackSizeWrite */ uint8_t *pDataRead; /* Data saved for this track as they will be read */ /* (after interpreting bytes $F5-$FF) */ } STX_SAVE_TRACK_STRUCT; typedef struct { uint32_t SaveSectorsCount; STX_SAVE_SECTOR_STRUCT *pSaveSectorsStruct; uint32_t SaveTracksCount; STX_SAVE_TRACK_STRUCT *pSaveTracksStruct; } STX_SAVE_STRUCT; extern void STX_MemorySnapShot_Capture(bool bSave); extern bool STX_FileNameIsSTX(const char *pszFileName, bool bAllowGZ); extern bool STX_FileNameToSave ( const char *FilenameSTX , char *FilenameSave ); extern uint8_t *STX_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType); extern bool STX_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize); extern bool STX_Init ( void ); extern bool STX_Insert ( int Drive , const char *FilenameSTX , uint8_t *pImageBuffer , long ImageSize ); extern bool STX_Eject ( int Drive ); extern STX_MAIN_STRUCT *STX_BuildStruct ( uint8_t *pFileBuffer , int Debug ); extern int FDC_GetBytesPerTrack_STX ( uint8_t Drive , uint8_t Track , uint8_t Side ); extern uint32_t FDC_GetCyclesPerRev_FdcCycles_STX ( uint8_t Drive , uint8_t Track , uint8_t Side ); extern int FDC_NextSectorID_FdcCycles_STX ( uint8_t Drive , uint8_t NumberOfHeads , uint8_t Track , uint8_t Side ); extern uint8_t FDC_NextSectorID_TR_STX ( void ); extern uint8_t FDC_NextSectorID_SR_STX ( void ); extern uint8_t FDC_NextSectorID_LEN_STX ( void ); extern uint8_t FDC_NextSectorID_CRC_OK_STX ( void ); extern uint8_t FDC_ReadSector_STX ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side , int *pSectorSize ); extern uint8_t FDC_WriteSector_STX ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side , int SectorSize ); extern uint8_t FDC_ReadAddress_STX ( uint8_t Drive , uint8_t Track , uint8_t Sector , uint8_t Side ); extern uint8_t FDC_ReadTrack_STX ( uint8_t Drive , uint8_t Track , uint8_t Side ); extern uint8_t FDC_WriteTrack_STX ( uint8_t Drive , uint8_t Track , uint8_t Side , int TrackSize ); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/gemdos.h000066400000000000000000000024231504763705000245660ustar00rootroot00000000000000/* Hatari - gemdos.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_GEMDOS_H #define HATARI_GEMDOS_H typedef struct { char hd_emulation_dir[FILENAME_MAX]; /* hd emulation directory (Host OS) */ char fs_currpath[FILENAME_MAX]; /* current path (Host OS) */ int drive_number; /* drive number (C: = 2, D: = 3...) */ } EMULATEDDRIVE; extern EMULATEDDRIVE **emudrives; #define GEMDOS_EMU_ON (emudrives != NULL) extern bool bInitGemDOS; extern void GemDOS_Init(void); extern void GemDOS_Reset(void); extern void GemDOS_InitDrives(void); extern void GemDOS_UnInitDrives(void); extern void GemDOS_MemorySnapShot_Capture(bool bSave); extern void GemDOS_CreateHardDriveFileName(int Drive, const char *pszFileName, char *pszDestName, int nDestNameLen); extern bool GemDOS_IsDriveEmulated(int drive); extern void GemDOS_Info(FILE *fp, uint32_t bShowOpcodes); extern void GemDOS_InfoDTA(FILE *fp, uint32_t addrDTA); extern int GemDOS_Trap(void); extern void GemDOS_Boot(void); extern int GemDOS_LoadAndReloc(const char *psPrgName, uint32_t baseaddr, bool bFullBpSetup); extern void GemDOS_PexecBpCreated(void); #endif /* HATARI_GEMDOS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/gemdos_defines.h000066400000000000000000000046201504763705000262640ustar00rootroot00000000000000/* Hatari - gemdos_defines.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_GEMDOS_DEFINES_H #define HATARI_GEMDOS_DEFINES_H /* GEMDOS error codes, See 'The Atari Compendium' D.3 */ #define GEMDOS_EOK 0 // OK #define GEMDOS_ERROR -1 // Generic error #define GEMDOS_EDRVNR -2 // Drive not ready #define GEMDOS_EUNCMD -3 // Unknown command #define GEMDOS_E_CRC -4 // CRC error #define GEMDOS_EBADRQ -5 // Bad request #define GEMDOS_E_SEEK -6 // Seek error #define GEMDOS_EMEDIA -7 // Unknown media #define GEMDOS_ESECNF -8 // Sector not found #define GEMDOS_EPAPER -9 // Out of paper #define GEMDOS_EWRITF -10 // Write fault #define GEMDOS_EREADF -11 // Read fault #define GEMDOS_EWRPRO -13 // Device is write protected #define GEMDOS_E_CHNG -14 // Media change detected #define GEMDOS_EUNDEV -15 // Unknown device #define GEMDOS_EINVFN -32 // Invalid function #define GEMDOS_EFILNF -33 // File not found #define GEMDOS_EPTHNF -34 // Path not found #define GEMDOS_ENHNDL -35 // No more handles #define GEMDOS_EACCDN -36 // Access denied #define GEMDOS_EIHNDL -37 // Invalid handle #define GEMDOS_ENSMEM -39 // Insufficient memory #define GEMDOS_EIMBA -40 // Invalid memory block address #define GEMDOS_EDRIVE -46 // Invalid drive specification #define GEMDOS_ENSAME -48 // Cross device rename #define GEMDOS_ENMFIL -49 // No more files #define GEMDOS_ELOCKED -58 // Record is already locked #define GEMDOS_ENSLOCK -59 // Invalid lock removal request #define GEMDOS_ERANGE -64 // Range error #define GEMDOS_EINTRN -65 // Internal error #define GEMDOS_EPLFMT -66 // Invalid program load format #define GEMDOS_EGSBF -67 // Memory block growth failure #define GEMDOS_ELOOP -80 // Too many symbolic links #define GEMDOS_EMOUNT -200 // Mount point crossed (indicator) /* GemDOS file attributes */ #define GEMDOS_FILE_ATTRIB_READONLY 0x01 #define GEMDOS_FILE_ATTRIB_HIDDEN 0x02 #define GEMDOS_FILE_ATTRIB_SYSTEM_FILE 0x04 #define GEMDOS_FILE_ATTRIB_VOLUME_LABEL 0x08 #define GEMDOS_FILE_ATTRIB_SUBDIRECTORY 0x10 /* archive bit, file was written and closed correctly * (used automatically on gemdos >=0.15) */ #define GEMDOS_FILE_ATTRIB_WRITECLOSE 0x20 #endif /* HATARI_GEMDOS_DEFINES_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/hdc.h000066400000000000000000000075411504763705000240540ustar00rootroot00000000000000/* Hatari - hdc.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This file contains definitions which are used for hardware-level harddrive emulation. */ #ifndef HATARI_HDC_H #define HATARI_HDC_H #include /* For off_t */ /* Opcodes */ /* The following are multi-sector transfers with seek implied */ #define HD_VERIFY_TRACK 0x05 /* Verify track */ #define HD_FORMAT_TRACK 0x06 /* Format track */ #define HD_READ_SECTOR 0x08 /* Read sector */ #define HD_READ_SECTOR1 0x28 /* Read sector (class 1) */ #define HD_WRITE_SECTOR 0x0A /* Write sector */ #define HD_WRITE_SECTOR1 0x2A /* Write sector (class 1) */ /* other codes */ #define HD_TEST_UNIT_RDY 0x00 /* Test unit ready */ #define HD_FORMAT_DRIVE 0x04 /* Format the whole drive */ #define HD_SEEK 0x0B /* Seek */ #define HD_CORRECTION 0x0D /* Correction */ #define HD_INQUIRY 0x12 /* Inquiry */ #define HD_MODESELECT 0x15 /* Mode select */ #define HD_MODESENSE 0x1A /* Mode sense */ #define HD_REQ_SENSE 0x03 /* Request sense */ #define HD_SHIP 0x1B /* Ship drive */ #define HD_READ_CAPACITY1 0x25 /* Read capacity (class 1) */ #define HD_REPORT_LUNS 0xa0 /* Report Luns */ /* Status codes */ #define HD_STATUS_OK 0x00 #define HD_STATUS_ERROR 0x02 #define HD_STATUS_BUSY 0x08 /* Error codes for REQUEST SENSE: */ #define HD_REQSENS_OK 0x00 /* OK return status */ #define HD_REQSENS_NOSECTOR 0x01 /* No index or sector */ #define HD_REQSENS_WRITEERR 0x03 /* Write fault */ #define HD_REQSENS_OPCODE 0x20 /* Opcode not supported */ #define HD_REQSENS_INVADDR 0x21 /* Invalid block address */ #define HD_REQSENS_INVARG 0x24 /* Invalid argument */ #define HD_REQSENS_INVLUN 0x25 /* Invalid LUN */ /** * Information about a ACSI/SCSI drive */ typedef struct scsi_data { bool enabled; FILE *image_file; uint32_t nLastBlockAddr; /* The specified sector number */ bool bSetLastBlockAddr; uint8_t nLastError; unsigned long hdSize; /* Size of the hard disk in sectors */ unsigned long blockSize; /* Size of a sector in bytes */ int scsi_version; /* For NCR5380 emulation: */ int direction; uint8_t msgout[4]; uint8_t cmd[16]; int cmd_len; } SCSI_DEV; /** * Status of the ACSI/SCSI bus/controller including the current command block. */ typedef struct { const char *typestr; /* "ACSI" or "SCSI" */ int target; int byteCount; /* number of command bytes received */ uint8_t command[16]; uint8_t opcode; bool bDmaError; short int status; /* return code from the HDC operation */ uint8_t *buffer; /* Response buffer */ int buffer_size; int data_len; int offset; /* Current offset into data buffer */ FILE *dmawrite_to_fh; SCSI_DEV devs[8]; } SCSI_CTRLR; extern int nAcsiPartitions; extern bool bAcsiEmuOn; /** * API. */ extern bool HDC_Init(void); extern void HDC_UnInit(void); extern int HDC_InitDevice(const char *hdtype, SCSI_DEV *dev, CNF_SCSIDEV *conf); extern void HDC_ResetCommandStatus(void); extern short int HDC_ReadCommandByte(int addr); extern void HDC_WriteCommandByte(int addr, uint8_t byte); extern int HDC_PartitionCount(FILE *fp, const uint64_t tracelevel, int *pIsByteSwapped); extern off_t HDC_CheckAndGetSize(const char *hdtype, const char *filename, unsigned long blockSize); extern bool HDC_WriteCommandPacket(SCSI_CTRLR *ctr, uint8_t b); extern void HDC_DmaTransfer(void); #endif /* HATARI_HDC_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ide.h000066400000000000000000000014061504763705000240510ustar00rootroot00000000000000/* Hatari - ide.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_IDE_H #define HATARI_IDE_H #include "sysdeps.h" extern int nIDEPartitions; extern void Ide_Init(void); extern void Ide_UnInit(void); extern bool Ide_IsAvailable(void); extern bool Ide_IsValid(uaecptr addr); extern uae_u32 REGPARAM3 Ide_Mem_bget(uaecptr addr); extern uae_u32 REGPARAM3 Ide_Mem_wget(uaecptr addr); extern uae_u32 REGPARAM3 Ide_Mem_lget(uaecptr addr); extern void REGPARAM3 Ide_Mem_bput(uaecptr addr, uae_u32 val); extern void REGPARAM3 Ide_Mem_wput(uaecptr addr, uae_u32 val); extern void REGPARAM3 Ide_Mem_lput(uaecptr addr, uae_u32 val); #endif /* HATARI_IDE_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ikbd.h000066400000000000000000000072601504763705000242250ustar00rootroot00000000000000/* Hatari - ikbd.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_IKBD_H #define HATARI_IKBD_H /* Keyboard processor details */ typedef struct { int X,Y; /* Position of mouse */ int MaxX,MaxY; /* Max limits of mouse */ uint8_t PrevReadAbsMouseButtons; /* Previous button mask for 'IKBD_Cmd_ReadAbsMousePos' */ } ABS_MOUSE; typedef struct { int dx, dy; /* Mouse delta to be added */ int DeltaX,DeltaY; /* Final XY mouse position delta */ int XScale,YScale; /* Scale of mouse */ int XThreshold,YThreshold; /* Threshold */ uint8_t KeyCodeDeltaX,KeyCodeDeltaY; /* Delta X,Y for mouse keycode mode */ int YAxis; /* Y-Axis direction */ uint8_t Action; /* Bit 0-Report abs position of press, Bit 1-Report abs on release */ } MOUSE; typedef struct { uint8_t JoyData[2]; /* Joystick details */ uint8_t PrevJoyData[2]; /* Previous joystick details, used to check for 'IKBD_SendAutoJoysticks' */ } JOY; typedef struct { ABS_MOUSE Abs; MOUSE Mouse; JOY Joy; int MouseMode; /* AUTOMODE_xxxx */ int JoystickMode; /* AUTOMODE_xxxx */ } KEYBOARD_PROCESSOR; /* Keyboard state */ #define KBD_MAX_SCANCODE 0x72 #define SIZE_KEYBOARD_BUFFER 1024 /* Allow this many bytes to be stored in buffer (waiting to send to ACIA) */ #define KEYBOARD_BUFFER_MASK (SIZE_KEYBOARD_BUFFER-1) #define SIZE_KEYBOARDINPUT_BUFFER 8 typedef struct { uint8_t KeyStates[KBD_MAX_SCANCODE + 1]; /* State of ST keys, TRUE is down */ uint8_t Buffer[SIZE_KEYBOARD_BUFFER]; /* Keyboard output buffer */ int BufferHead,BufferTail; /* Pointers into above buffer */ int NbBytesInOutputBuffer; /* Number of bytes in output buffer */ bool PauseOutput; /* If true, don't send bytes anymore (see command 0x13) */ uint8_t InputBuffer[SIZE_KEYBOARDINPUT_BUFFER]; /* Buffer for data send from CPU to keyboard processor (commands) */ int nBytesInInputBuffer; /* Number of command bytes in above buffer */ int bLButtonDown,bRButtonDown; /* Mouse states in emulation system, BUTTON_xxxx */ int bOldLButtonDown,bOldRButtonDown; int LButtonDblClk,RButtonDblClk; unsigned int LButtonHistory, RButtonHistory; int AutoSendCycles; /* Number of cpu cycles to call INTERRUPT_IKBD_AUTOSEND */ } KEYBOARD; /* Button states, a bit mask so can mimic joystick/right mouse button duplication */ #define BUTTON_NULL 0x00 /* Button states, so can OR together mouse/joystick buttons */ #define BUTTON_MOUSE 0x01 #define BUTTON_JOYSTICK 0x02 /* Mouse/Joystick modes */ #define AUTOMODE_OFF 0 #define AUTOMODE_MOUSEREL 1 #define AUTOMODE_MOUSEABS 2 #define AUTOMODE_MOUSECURSOR 3 #define AUTOMODE_JOYSTICK 4 #define AUTOMODE_JOYSTICK_MONITORING 5 /* 0xfffc00 (read status from ACIA) */ #define ACIA_STATUS_REGISTER__RX_BUFFER_FULL 0x01 #define ACIA_STATUS_REGISTER__TX_BUFFER_EMPTY 0x02 #define ACIA_STATUS_REGISTER__OVERRUN_ERROR 0x20 #define ACIA_STATUS_REGISTER__INTERRUPT_REQUEST 0x80 extern KEYBOARD_PROCESSOR KeyboardProcessor; extern KEYBOARD Keyboard; extern void IKBD_Init ( void ); extern void IKBD_Reset(bool bCold); extern void IKBD_MemorySnapShot_Capture(bool bSave); extern void IKBD_InterruptHandler_ResetTimer(void); extern void IKBD_InterruptHandler_AutoSend(void); extern void IKBD_UpdateClockOnVBL ( void ); extern void IKBD_PressSTKey(uint8_t ScanCode, bool bPress); extern void IKBD_Info(FILE *fp, uint32_t dummy); #endif /* HATARI_IKBD_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/inffile.h000066400000000000000000000013231504763705000247220ustar00rootroot00000000000000/* Hatari - inffile.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_INFFILE_H #define HATARI_INFFILE_H typedef enum { AUTOSTART_INTERCEPT, AUTOSTART_FOPEN } autostart_t; extern bool INF_SetAutoStart(const char *prgname, int opt_id); extern bool INF_SetResolution(const char *resolution, int opt_id); extern void INF_SetVdiMode(int vdi_res); extern int INF_ValidateAutoStart(const char **val, const char **err); extern void INF_CreateOverride(void); extern bool INF_Overriding(autostart_t t); extern FILE *INF_OpenOverride(const char *filename); extern bool INF_CloseOverride(FILE *fp); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ioMem.h000066400000000000000000000062511504763705000243610ustar00rootroot00000000000000/* Hatari - ioMem.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_IOMEM_H #define HATARI_IOMEM_H #include "config.h" #include "sysdeps.h" #include "maccess.h" #include "main.h" extern uae_u8 *IOmemory; #define IoMem (IOmemory-0xff0000) extern int nIoMemAccessSize; extern uint32_t IoAccessFullAddress; extern uint32_t IoAccessBaseAddress; extern uint32_t IoAccessCurrentAddress; extern int IoAccessInstrCount; enum FALCON_BUS_MODE { STE_BUS_COMPATIBLE, FALCON_ONLY_BUS }; /** * Read 32-bit word from IO memory space without interception. * NOTE - value will be converted to PC endian. */ static inline uint32_t IoMem_ReadLong(uint32_t Address) { Address &= 0x0ffffff; return do_get_mem_long(&IoMem[Address]); } /** * Read 16-bit word from IO memory space without interception. * NOTE - value will be converted to PC endian. */ static inline uint16_t IoMem_ReadWord(uint32_t Address) { Address &= 0x0ffffff; return do_get_mem_word(&IoMem[Address]); } /** * Read 8-bit byte from IO memory space without interception. */ static inline uint8_t IoMem_ReadByte(uint32_t Address) { Address &= 0x0ffffff; return IoMem[Address]; } /** * Write 32-bit word into IO memory space without interception. * NOTE - value will be convert to 68000 endian */ static inline void IoMem_WriteLong(uint32_t Address, uint32_t Var) { Address &= 0x0ffffff; do_put_mem_long(&IoMem[Address], Var); } /** * Write 16-bit word into IO memory space without interception. * NOTE - value will be convert to 68000 endian. */ static inline void IoMem_WriteWord(uint32_t Address, uint16_t Var) { Address &= 0xffffff; do_put_mem_word(&IoMem[Address], Var); } /** * Write 8-bit byte into IO memory space without interception. */ static inline void IoMem_WriteByte(uint32_t Address, uint8_t Var) { Address &= 0x0ffffff; IoMem[Address] = Var; } extern void IoMem_Intercept ( uint32_t addr , void (*read_f)(void) , void (*write_f)(void) ); extern void IoMem_Init(void); extern void IoMem_UnInit (int MachineType); extern void IoMem_Reset(void); extern uint8_t IoMemTabMegaSTE_DIPSwitches_Read(void); extern uint8_t IoMemTabFalcon_DIPSwitches_Read(void); extern void IoMem_SetFalconBusMode(enum FALCON_BUS_MODE mode); extern bool IoMem_IsFalconBusMode(void); extern uae_u32 REGPARAM3 IoMem_bget(uaecptr addr); extern uae_u32 REGPARAM3 IoMem_wget(uaecptr addr); extern uae_u32 REGPARAM3 IoMem_lget(uaecptr addr); extern void REGPARAM3 IoMem_bput(uaecptr addr, uae_u32 val); extern void REGPARAM3 IoMem_wput(uaecptr addr, uae_u32 val); extern void REGPARAM3 IoMem_lput(uaecptr addr, uae_u32 val); extern bool IoMem_CheckBusError ( uint32_t addr ); extern void IoMem_BusErrorEvenReadAccess(void); extern void IoMem_BusErrorOddReadAccess(void); extern void IoMem_BusErrorEvenWriteAccess(void); extern void IoMem_BusErrorOddWriteAccess(void); extern void IoMem_VoidRead(void); extern void IoMem_VoidRead_00(void); extern void IoMem_VoidWrite(void); extern void IoMem_WriteWithoutInterception(void); extern void IoMem_ReadWithoutInterception(void); extern void IoMem_MemorySnapShot_Capture(bool bSave); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ioMemTables.h000066400000000000000000000021161504763705000255100ustar00rootroot00000000000000/* Hatari - ioMemTables.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_IOMEMTABLES_H #define HATARI_IOMEMTABLES_H /* Hardware address details */ typedef struct { const uint32_t Address; /* ST hardware address */ const int SpanInBytes; /* E.g. SIZE_BYTE, SIZE_WORD or SIZE_LONG */ void (*ReadFunc)(void); /* Read function */ void (*WriteFunc)(void); /* Write function */ } INTERCEPT_ACCESS_FUNC; extern const INTERCEPT_ACCESS_FUNC IoMemTable_ST[]; extern const INTERCEPT_ACCESS_FUNC IoMemTable_STE[]; extern const INTERCEPT_ACCESS_FUNC IoMemTable_TT[]; extern const INTERCEPT_ACCESS_FUNC IoMemTable_Falcon[]; extern void IoMemTabFalcon_DSPnone(void (**readtab)(void), void (**writetab)(void)); extern void IoMemTabFalcon_DSPdummy(void (**readtab)(void), void (**writetab)(void)); #if ENABLE_DSP_EMU extern void IoMemTabFalcon_DSPemulation(void (**readtab)(void), void (**writetab)(void)); #endif extern void IoMemTabMegaSTE_CacheCpuCtrl_WriteByte(void); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/joy.h000066400000000000000000000031611504763705000241110ustar00rootroot00000000000000/* Hatari - joy.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_JOY_H #define HATARI_JOY_H enum { JOYSTICK_SPACE_NULL, /* Not up/down */ JOYSTICK_SPACE_DOWN, JOYSTICK_SPACE_DOWNED, JOYSTICK_SPACE_UP }; #define JOYRANGE_UP_VALUE -16384 /* Joystick ranges in XY */ #define JOYRANGE_DOWN_VALUE 16383 #define JOYRANGE_LEFT_VALUE -16384 #define JOYRANGE_RIGHT_VALUE 16383 #define ATARIJOY_BITMASK_UP 0x01 #define ATARIJOY_BITMASK_DOWN 0x02 #define ATARIJOY_BITMASK_LEFT 0x04 #define ATARIJOY_BITMASK_RIGHT 0x08 #define ATARIJOY_BITMASK_FIRE 0x80 extern int JoystickSpaceBar; extern void Joy_Init(void); extern void Joy_UnInit(void); extern const char *Joy_GetName(int id); extern int Joy_GetMaxId(void); extern bool Joy_ValidateJoyId(int i); extern uint8_t Joy_GetStickData(int nStJoyId); extern bool Joy_SetCursorEmulation(int port); extern void Joy_ToggleCursorEmulation(void); extern bool Joy_SwitchMode(int port); extern bool Joy_KeyDown(int symkey, int modkey); extern bool Joy_KeyUp(int symkey, int modkey); extern void Joy_StePadButtons_DIPSwitches_ReadWord(void); extern void Joy_StePadButtons_DIPSwitches_WriteWord(void); extern void Joy_StePadMulti_ReadWord(void); extern void Joy_StePadMulti_WriteWord(void); void Joy_SteLightpenX_ReadWord(void); void Joy_SteLightpenY_ReadWord(void); void Joy_StePadAnalog0X_ReadByte(void); void Joy_StePadAnalog0Y_ReadByte(void); void Joy_StePadAnalog1X_ReadByte(void); void Joy_StePadAnalog1Y_ReadByte(void); #endif /* ifndef HATARI_JOY_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/keymap.h000066400000000000000000000013111504763705000245710ustar00rootroot00000000000000/* Hatari - keymap.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_KEYMAP_H #define HATARI_KEYMAP_H #include extern void Keymap_Init(void); extern void Keymap_LoadRemapFile(const char *pszFileName); extern void Keymap_DebounceAllKeys(void); extern void Keymap_KeyDown(const SDL_Keysym *sdlkey); extern void Keymap_KeyUp(const SDL_Keysym *sdlkey); extern void Keymap_SimulateCharacter(char asckey, bool press); extern int Keymap_GetKeyFromName(const char *name); extern const char *Keymap_GetKeyName(int keycode); extern void Keymap_SetCountry(int countrycode); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/lilo.h000066400000000000000000000005051504763705000242460ustar00rootroot00000000000000/* Hatari - lilo.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef LILO_H #define LILO_H extern bool bUseLilo; /** * return true if Linux loading succeeds */ extern bool lilo_init(void); #endif /* LILO_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/m68000.h000066400000000000000000000406671504763705000241560ustar00rootroot00000000000000/* Hatari - m68000.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ /* 2007/11/10 [NP] Add pairing for lsr / dbcc (and all variants */ /* working on register, not on memory). */ /* 2008/01/07 [NP] Use PairingArray to store all valid pairing */ /* combinations (in m68000.c) */ /* 2010/04/05 [NP] Rework the pairing code to take BusCyclePenalty */ /* into account when using d8(an,ix). */ /* 2010/05/07 [NP] Add BusCyclePenalty to LastInstrCycles to detect*/ /* a possible pairing between add.l (a5,d1.w),d0 */ /* and move.b 7(a5,d1.w),d5. */ #ifndef HATARI_M68000_H #define HATARI_M68000_H #include "cycles.h" /* for nCyclesMainCounter */ #include "sysdeps.h" #include "memory.h" #define HATARI_NO_ENUM_BITVALS /* Don't define 'bitvals' and 'bits' in newcpu.h / readcpu.h */ #include "newcpu.h" /* for regs */ #include "cycInt.h" #include "log.h" /* 68000 register defines */ enum { REG_D0, /* D0.. */ REG_D1, REG_D2, REG_D3, REG_D4, REG_D5, REG_D6, REG_D7, /* ..D7 */ REG_A0, /* A0.. */ REG_A1, REG_A2, REG_A3, REG_A4, REG_A5, REG_A6, REG_A7 /* ..A7 (also SP) */ }; /* 68000 Condition code's */ #define SR_AUX 0x0010 #define SR_NEG 0x0008 #define SR_ZERO 0x0004 #define SR_OVERFLOW 0x0002 #define SR_CARRY 0x0001 #define SR_CCODE_MASK (SR_AUX|SR_NEG|SR_ZERO|SR_OVERFLOW|SR_CARRY) #define SR_MASK 0xFFE0 #define SR_TRACEMODE 0x8000 #define SR_SUPERMODE 0x2000 #define SR_IPL 0x0700 #define SR_CLEAR_IPL 0xf8ff #define SR_CLEAR_TRACEMODE 0x7fff #define SR_CLEAR_SUPERMODE 0xdfff /* Exception numbers most commonly used in ST */ #define EXCEPTION_NR_BUSERROR 2 #define EXCEPTION_NR_ADDRERROR 3 #define EXCEPTION_NR_ILLEGALINS 4 #define EXCEPTION_NR_DIVZERO 5 #define EXCEPTION_NR_CHK 6 #define EXCEPTION_NR_TRAPV 7 #define EXCEPTION_NR_TRACE 9 #define EXCEPTION_NR_LINE_A 10 #define EXCEPTION_NR_LINE_F 11 #define EXCEPTION_NR_SCU_SOFT_INT 25 /* Level 1 interrupt */ #define EXCEPTION_NR_HBLANK 26 /* Level 2 interrupt */ #define EXCEPTION_NR_VBLANK 28 /* Level 4 interrupt */ #define EXCEPTION_NR_SCC 29 /* Level 5 interrupt */ #define EXCEPTION_NR_MFP_DSP 30 /* Level 6 interrupt */ #define EXCEPTION_NR_TRAP0 32 #define EXCEPTION_NR_TRAP1 33 #define EXCEPTION_NR_TRAP2 34 #define EXCEPTION_NR_TRAP13 45 #define EXCEPTION_NR_TRAP14 46 /* Size of 68000 instructions */ #define MAX_68000_INSTRUCTION_SIZE 10 /* Longest 68000 instruction is 10 bytes(6+4) */ #define MIN_68000_INSTRUCTION_SIZE 2 /* Smallest 68000 instruction is 2 bytes(ie NOP) */ /* Illegal Opcode used to help emulation. eg. free entries are 8 to 15 inc' */ #define GEMDOS_OPCODE 8 /* Free op-code to intercept GemDOS trap */ #define PEXEC_OPCODE 9 /* Free op-code to intercept Pexec calls */ #define SYSINIT_OPCODE 10 /* Free op-code to initialize system (connected drives etc.) */ #define VDI_OPCODE 12 /* Free op-code to call VDI handlers AFTER Trap#2 */ /* Illegal opcodes used for Native Features emulation. * See debug/natfeats.c and tests/natfeats/ for more info. */ #define NATFEAT_ID_OPCODE 0x7300 #define NATFEAT_CALL_OPCODE 0x7301 /* Ugly hacks to adapt the main code to the different CPU cores: */ #define Regs regs.regs # define M68000_GetPC() m68k_getpc() # define M68000_InstrPC regs.instruction_pc # define M68000_CurrentOpcode regs.opcode # define M68000_SetSpecial(flags) set_special(flags) # define M68000_UnsetSpecial(flags) unset_special(flags) /* Some define's for bus error (see newcpu.c) */ /* Bus error read/write mode */ #define BUS_ERROR_WRITE 0 #define BUS_ERROR_READ 1 /* Bus error access size */ #define BUS_ERROR_SIZE_BYTE 1 #define BUS_ERROR_SIZE_WORD 2 #define BUS_ERROR_SIZE_LONG 4 /* Bus error access type */ #define BUS_ERROR_ACCESS_INSTR 0 #define BUS_ERROR_ACCESS_DATA 1 /* Bus access mode */ #define BUS_MODE_CPU 0 /* bus is owned by the cpu */ #define BUS_MODE_BLITTER 1 /* bus is owned by the blitter */ #define BUS_MODE_DEBUGGER 2 /* bus is owned by debugger : special case to access RAM 0-0x7FF */ /* without doing a bus error when not in supervisor mode */ /* eg : this is used in some debug functions that do get_long/word/byte */ /* [NP] Notes on IACK : * When an interrupt happens, it's possible a similar interrupt happens again * between the start of the exception and the IACK sequence. In that case, we * might have to set pending bit twice and change the interrupt vector. * * From the 68000's doc, IACK starts after 10 cycles (12 cycles on STF due to 2 cycle * bus penalty) and is supposed to take 4 cycles if the interrupt takes a total of 44 cycles. * * On Atari STF, interrupts take 56 cycles instead of 44, which means it takes * 12 extra cycles to fetch the vector number and to handle non-aligned memory accesses. * From WinUAE's CE mode, we have 2 non-aligned memory accesses to wait for (ie 2+2 cycles), * which leaves a total of 12 cycles to fetch the vector. * * As seen with a custom program on STF that measures HBL's jitter, we get the same results with Hatari * in CE mode if we use 10 cycles to fetch the vector (step 3), which will also add 2 cycle penalty (step 4b) * This means we have at max 12+10=22 cycles after the start of the exception where some * changes can happen (maybe it's a little less, depending on when the interrupt * vector is written on the bus). * * Additionally, auto vectored interrupts (HBL and VBL) require to be in sync with E-clock, * which can add 0 to 8 cycles (step 3a). In that case we have between 22+0 and 22+8 cycles * to get another interrupt before vector is written on the bus. * * The values we use were not entirely measured on real ST hardware, they were guessed/adjusted * to get the correct behaviour in some games/demos relying on this. * These values are when running in CE mode (2 cycle precision) ; when CPU runs in prefetch * mode, values need to be rounded to 4). * * Interrupt steps + WinUAE cycles (measured on real A500 HW) + ST specific values : * * 1 6 idle cycles * 1b 2(*) ST bus access penalty (if necessary) * 2 4 write PC low word * 3a 0-8(*) wait for E-clock for auto vectored interrupt * 3 10(*) read exception number * 4 4 idle cycles * 4b 2(*) ST bus access penalty * 5 4 write SR * 6 4 write PC high word * 7 4 read exception address high word * 8 4 read exception address low word * 9 4 prefetch * 10 2 idle cycles * 10b 2(*) ST bus access penalty * 11 4 prefetch * TOTAL = 56 * * (*) ST specific timings */ /* Values for IACK sequence when running in cycle exact mode */ #define CPU_IACK_CYCLES_MFP_CE 12 /* vector sent by the MFP (TODO value not measured on real STF) */ #define CPU_IACK_CYCLES_VIDEO_CE 10 /* auto vectored for HBL/VBL (value measured on real STF) */ /* Values for IACK sequence when running in normal/prefetch mode */ #define CPU_IACK_CYCLES_START 12 /* number of cycles before starting the IACK when not using CE mode */ /* (this should be a multiple of 4, else it will be rounded by M68000_AddCycles) */ #define CPU_IACK_CYCLES_MFP 12 /* vector sent by the MFP */ #define CPU_IACK_CYCLES_VIDEO 12 /* auto vectored for HBL/VBL */ /* Information about current CPU instruction */ typedef struct { /* These are provided only by WinUAE CPU core */ int I_Cache_miss; /* Instruction cache for 68020/30/40/60 */ int I_Cache_hit; int D_Cache_miss; /* Data cache for 68030/40/60 */ int D_Cache_hit; /* TODO: move other instruction specific Hatari variables here */ } cpu_instruction_t; extern cpu_instruction_t CpuInstruction; extern uint32_t BusErrorAddress; extern bool bBusErrorReadWrite; extern int nCpuFreqShift; extern int WaitStateCycles; extern int BusMode; extern bool CPU_IACK; extern bool CpuRunCycleExact; extern bool CpuRunFuncNoret; extern int LastOpcodeFamily; extern int LastInstrCycles; extern int Pairing; extern char PairingArray[ MAX_OPCODE_FAMILY ][ MAX_OPCODE_FAMILY ]; extern const char *OpcodeName[]; /*-----------------------------------------------------------------------*/ /** * Add CPU cycles. * NOTE: All times are rounded up to nearest 4 cycles. */ static inline void M68000_AddCycles(int cycles) { cycles = (cycles + 3) & ~3; nCyclesMainCounter += cycles; CyclesGlobalClockCounter += cycles; } /*-----------------------------------------------------------------------*/ /** * Add CPU cycles, take cycles pairing into account. Pairing will make * some specific instructions take 4 cycles less when run one after the other. * Pairing happens when the 2 instructions are "aligned" on different bus accesses. * Candidates are : * - 2 instructions taking 4n+2 cycles * - 1 instruction taking 4n+2 cycles, followed by 1 instruction using d8(an,ix) * * Not all the candidate instructions can pair, only the opcodes listed in PairingArray. * On ST, when using d8(an,ix), we get an extra 2 cycle penalty for misaligned bus access. * The only instruction that can generate BusCyclePenalty=4 is move d8(an,ix),d8(an,ix) * and although it takes 4n cycles (24 for .b/.w or 32 for .l) it can pair with * a previous 4n+2 instruction (but it will still have 1 misaligned bus access in the end). * * Verified pairing on an STF : * - lsl.w #4,d1 + move.w 0(a4,d2.w),d1 motorola=14+14=28 stf=28 * - lsl.w #4,d1 + move.w 0(a4,d2.w),(a4) motorola=14+18=32 stf=32 * - lsl.w #4,d1 + move.w 0(a4,d2.w),0(a4,d2.w) motorola=14+24=38 stf=40 * - add.l (a5,d1.w),d0 + move.b 7(a5,d1.w),d5) motorola=20+14=34 stf=36 * * d8(an,ix) timings without pairing (2 cycles penalty) : * - add.l 0(a4,d2.w),a1 motorola=20 stf=24 * - move.w 0(a4,d2.w),d1 motorola=14 stf=16 * - move.w 0(a4,d2.w),(a4) motorola=18 stf=20 * - move.w 0(a4,d2.w),0(a4,d2.w) motorola=24 stf=28 * * NOTE: All times are rounded up to nearest 4 cycles. */ static inline void M68000_AddCyclesWithPairing(int cycles) { Pairing = 0; /* Check if number of cycles for current instr and for */ /* the previous one is of the form 4+2n */ /* If so, a pairing could be possible depending on the opcode */ /* A pairing is also possible if current instr is 4n but with BusCyclePenalty > 0 */ if ( ( PairingArray[ LastOpcodeFamily ][ OpcodeFamily ] == 1 ) && ( ( LastInstrCycles & 3 ) == 2 ) && ( ( ( cycles & 3 ) == 2 ) || ( BusCyclePenalty > 0 ) ) ) { Pairing = 1; LOG_TRACE(TRACE_CPU_PAIRING, "cpu pairing detected pc=%x family %s/%s cycles %d/%d\n", m68k_getpc(), OpcodeName[LastOpcodeFamily], OpcodeName[OpcodeFamily], LastInstrCycles, cycles); } /* [NP] This part is only needed to track possible pairing instructions, */ /* we can keep it disabled most of the time */ #if 0 if ( (LastOpcodeFamily!=OpcodeFamily) && ( Pairing == 0 ) && ( ( cycles & 3 ) == 2 ) && ( ( LastInstrCycles & 3 ) == 2 ) ) { LOG_TRACE(TRACE_CPU_PAIRING, "cpu could pair pc=%x family %s/%s cycles %d/%d\n", m68k_getpc(), OpcodeName[LastOpcodeFamily], OpcodeName[OpcodeFamily], LastInstrCycles, cycles); } #endif /* Store current instr (not rounded) to check next time */ LastInstrCycles = cycles + BusCyclePenalty; LastOpcodeFamily = OpcodeFamily; /* If pairing is true, we need to subtract 2 cycles for the */ /* previous instr which was rounded to 4 cycles while it wasn't */ /* needed (and we don't round the current one) */ /* -> both instr will take 4 cycles less on the ST than if ran */ /* separately. */ if (Pairing == 1) { if ( ( cycles & 3 ) == 2 ) /* pairing between 4n+2 and 4n+2 instructions */ cycles -= 2; /* if we have a pairing, we should not count the misaligned bus access */ else /* this is the case of move d8(an,ix),d8(an,ix) where BusCyclePenalty=4 */ /*do nothing */; /* we gain 2 cycles for the pairing with 1st d8(an,ix) */ /* and we have 1 remaining misaligned access for the 2nd d8(an,ix). So in the end, we keep */ /* cycles unmodified as 4n cycles (eg lsl.w #4,d1 + move.w 0(a4,d2.w),0(a4,d2.w) takes 40 cycles) */ } else { cycles += BusCyclePenalty; /* >0 if d8(an,ix) was used */ cycles = (cycles + 3) & ~3; /* no pairing, round current instr to 4 cycles */ } nCyclesMainCounter += cycles; CyclesGlobalClockCounter += cycles; BusCyclePenalty = 0; } /*-----------------------------------------------------------------------*/ /** * Add CPU cycles when using WinUAE CPU 'cycle exact' mode. * In this mode, we should not round cycles to the next nearest 4 cycles * because all memory accesses will already be aligned to 4 cycles when * using CE mode. * The CE mode will also give the correct 'instruction pairing' for all * opcodes/addressing mode, without requiring tables/heuristics (in the * same way that it's done in real hardware) */ static inline void M68000_AddCycles_CE(int cycles) { nCyclesMainCounter += cycles; CyclesGlobalClockCounter += cycles; } /*-----------------------------------------------------------------------*/ /* Mega STE at 16 MHz and external cache */ /*-----------------------------------------------------------------------*/ extern void MegaSTE_CPU_Cache_Update ( uint8_t val ); extern void MegaSTE_CPU_Cache_Reset ( void ); extern void MegaSTE_CPU_Set_16Mhz ( bool set_16 ); extern void MegaSTE_Cache_Flush ( void ); /* * High level memory access functions for Mega STE when 68000 in cycle exact mode * is running at 16 MHz instead of 8 MHz, with external cache enabled or disabled * * Similar to _ce000 functions in cpu/cpu_prefetch.h */ uae_u32 mem_access_delay_word_read_megaste_16 (uaecptr addr); uae_u32 mem_access_delay_wordi_read_megaste_16 (uaecptr addr); uae_u32 mem_access_delay_byte_read_megaste_16 (uaecptr addr); void mem_access_delay_byte_write_megaste_16 (uaecptr addr, uae_u32 v); void mem_access_delay_word_write_megaste_16 (uaecptr addr, uae_u32 v); STATIC_INLINE uae_u32 get_long_ce000_megaste_16 (uaecptr addr) { uae_u32 v = mem_access_delay_word_read_megaste_16 (addr) << 16; v |= mem_access_delay_word_read_megaste_16 (addr + 2); return v; } STATIC_INLINE uae_u32 get_word_ce000_megaste_16 (uaecptr addr) { return mem_access_delay_word_read_megaste_16 (addr); } STATIC_INLINE uae_u32 get_wordi_ce000_megaste_16 (int offset) { return mem_access_delay_wordi_read_megaste_16 (m68k_getpci () + offset); } STATIC_INLINE uae_u32 get_byte_ce000_megaste_16 (uaecptr addr) { return mem_access_delay_byte_read_megaste_16 (addr); } STATIC_INLINE void put_long_ce000_megaste_16 (uaecptr addr, uae_u32 v) { mem_access_delay_word_write_megaste_16 (addr, v >> 16); mem_access_delay_word_write_megaste_16 (addr + 2, v); } STATIC_INLINE void put_word_ce000_megaste_16 (uaecptr addr, uae_u32 v) { mem_access_delay_word_write_megaste_16 (addr, v); } STATIC_INLINE void put_byte_ce000_megaste_16 (uaecptr addr, uae_u32 v) { mem_access_delay_byte_write_megaste_16 (addr, v); } /*-----------------------------------------------------------------------*/ /* Mega STE at 16 MHz and external cache */ /*-----------------------------------------------------------------------*/ extern void M68000_Init(void); extern void M68000_Reset(bool bCold); extern void M68000_SetDebugger(bool debug); extern void M68000_RestoreDebugger(void); extern void M68000_Start(void); extern void M68000_CheckCpuSettings(void); extern void M68000_PatchCpuTables(void); extern void M68000_MemorySnapShot_Capture(bool bSave); extern bool M68000_IsVerboseBusError(uint32_t pc, uint32_t addr); extern void M68000_BusError ( uint32_t addr , int ReadWrite , int Size , int AccessType , uae_u32 val ); extern void M68000_SetIRQ ( int IntNr ); extern void M68000_ClearIRQ ( int IntNr ); extern void M68000_Exception(uint32_t ExceptionNr , int ExceptionSource); extern void M68000_Update_intlev ( void ); extern void M68000_WaitState(int WaitCycles); extern int M68000_WaitEClock ( void ); extern void M68000_SyncCpuBus_OnReadAccess ( void ); extern void M68000_SyncCpuBus_OnWriteAccess ( void ); extern void M68000_Flush_Instr_Cache ( uaecptr addr , int size ); extern void M68000_Flush_Data_Cache ( uaecptr addr , int size ); extern void M68000_Flush_All_Caches ( uaecptr addr , int size ); extern void M68000_SetBlitter_CE ( bool ce_mode ); extern int DMA_MaskAddressHigh ( void ); extern void M68000_ChangeCpuFreq ( void ); extern uint16_t M68000_GetSR ( void ); extern void M68000_SetSR ( uint16_t v ); extern void M68000_SetPC ( uaecptr v ); extern void M68000_MMU_Info(FILE *fp, uint32_t flags); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/main.h000066400000000000000000000032241504763705000242340ustar00rootroot00000000000000/* Hatari - main.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_MAIN_H #define HATARI_MAIN_H #include "config.h" #include #include #include #include #include #include #if defined(_MSC_VER) #include "vs-fix.h" #endif #if __GNUC__ >= 3 # define likely(x) __builtin_expect (!!(x), 1) # define unlikely(x) __builtin_expect (!!(x), 0) #else # define likely(x) (x) # define unlikely(x) (x) #endif /* avoid warnings with variables used only in asserts */ #ifdef NDEBUG # define ASSERT_VARIABLE(x) (void)(x) #else # define ASSERT_VARIABLE(x) assert(x) #endif #ifdef WIN32 #define PATHSEP '\\' #else #define PATHSEP '/' #endif #define CALL_VAR(func) { ((void(*)(void))func)(); } #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (int)(sizeof(x)/sizeof(x[0])) #endif /* 68000 operand sizes */ #define SIZE_BYTE 1 #define SIZE_WORD 2 #define SIZE_LONG 4 extern bool bQuitProgram; extern bool Main_PauseEmulation(bool visualize); extern bool Main_UnPauseEmulation(void); extern void Main_RequestQuit(int exitval); extern void Main_SetQuitValue(int exitval); extern uint32_t Main_SetRunVBLs(uint32_t vbls); extern const char* Main_SetVBLSlowdown(int factor); extern void Main_WaitOnVbl(void); extern void Main_WarpMouse(int x, int y, bool restore); extern bool Main_ShowCursor(bool show); extern void Main_EventHandler(void); extern void Main_SetTitle(const char *title); extern void Main_ErrorExit(const char *msg1, const char *msg2, int errval); #endif /* ifndef HATARI_MAIN_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/memorySnapShot.h000066400000000000000000000011441504763705000262770ustar00rootroot00000000000000/* Hatari - memorySnapShot.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern void MemorySnapShot_Skip(int Nb); extern void MemorySnapShot_Store(void *pData, int Size); extern void MemorySnapShot_Capture(const char *pszFileName, bool bConfirm); extern void MemorySnapShot_Capture_Immediate(const char *pszFileName, bool bConfirm); extern void MemorySnapShot_Capture_Do(void); extern void MemorySnapShot_Restore(const char *pszFileName, bool bConfirm); extern void MemorySnapShot_Restore_Do(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/mfp.h000066400000000000000000000176071504763705000241040ustar00rootroot00000000000000/* Hatari - mfp.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_MFP_H #define HATARI_MFP_H /* List of MFP interrupts (GPIP is General Purpose I/O Interrupt Port) */ #define MFP_INT_MAX 15 /* We have 16 ints from 0 to 15 */ #define MFP_INT_GPIP7 15 /* Highest Priority */ #define MFP_INT_GPIP6 14 #define MFP_INT_TIMER_A 13 #define MFP_INT_RCV_BUF_FULL 12 #define MFP_INT_RCV_ERR 11 #define MFP_INT_TRN_BUF_EMPTY 10 #define MFP_INT_TRN_ERR 9 #define MFP_INT_TIMER_B 8 #define MFP_INT_GPIP5 7 #define MFP_INT_GPIP4 6 #define MFP_INT_TIMER_C 5 #define MFP_INT_TIMER_D 4 #define MFP_INT_GPIP3 3 #define MFP_INT_GPIP2 2 #define MFP_INT_GPIP1 1 #define MFP_INT_GPIP0 0 /* Lowest Priority */ /* MFP register defines ( 1 << Int ) */ #define MFP_GPIP7_BIT 0x80 #define MFP_GPIP6_BIT 0x40 #define MFP_TIMER_A_BIT 0x20 #define MFP_RCV_BUF_FULL_BIT 0x10 #define MFP_RCV_ERR_BIT 0x08 #define MFP_TRN_BUF_EMPTY_BIT 0x04 #define MFP_TRN_ERR_BIT 0x02 #define MFP_TIMER_B_BIT 0x01 #define MFP_GPIP5_BIT 0x80 #define MFP_GPIP4_BIT 0x40 #define MFP_TIMER_C_BIT 0x20 #define MFP_TIMER_D_BIT 0x10 #define MFP_GPIP3_BIT 0x08 #define MFP_GPIP2_BIT 0x04 #define MFP_GPIP1_BIT 0x02 #define MFP_GPIP0_BIT 0x01 /* List of the GPIP lines */ #define MFP_GPIP_LINE7 7 #define MFP_GPIP_LINE6 6 #define MFP_GPIP_LINE5 5 #define MFP_GPIP_LINE4 4 #define MFP_GPIP_LINE3 3 #define MFP_GPIP_LINE2 2 #define MFP_GPIP_LINE1 1 #define MFP_GPIP_LINE0 0 /* Aliases for some GPIP lines */ #define MFP_GPIP_LINE_FDC_HDC MFP_GPIP_LINE5 #define MFP_GPIP_LINE_ACIA MFP_GPIP_LINE4 #define MFP_GPIP_LINE_GPU_DONE MFP_GPIP_LINE3 #define MFP_TT_GPIP_LINE_SCSI_NCR MFP_GPIP_LINE7 #define MFP_TT_GPIP_LINE_RTC MFP_GPIP_LINE6 #define MFP_TT_GPIP_LINE_SCSI_DMAC MFP_GPIP_LINE5 #define MFP_TT_GPIP_LINE_DC MFP_GPIP_LINE4 #define MFP_TT_GPIP_LINE_SCC_B MFP_GPIP_LINE3 #define MFP_TT_GPIP_LINE_SCC_DMAC MFP_GPIP_LINE2 #define MFP_GPIP_STATE_LOW 0 #define MFP_GPIP_STATE_HIGH 1 typedef struct { /* MFP 68901 internal registers */ uint8_t GPIP; /* General Purpose Pins / GPDR 0x01 */ uint8_t AER; /* Active Edge Register 0x03*/ uint8_t DDR; /* Data Direction Register */ uint8_t IERA; /* Interrupt Enable Register A 0x07 */ uint8_t IERB; /* Interrupt Enable Register B 0x09 */ uint8_t IPRA; /* Interrupt Pending Register A 0x0B */ uint8_t IPRB; /* Interrupt Pending Register B 0x0D */ uint8_t ISRA; /* Interrupt In-Service Register A 0x0F */ uint8_t ISRB; /* Interrupt In-Service Register B 0x11 */ uint8_t IMRA; /* Interrupt Mask Register A 0x13 */ uint8_t IMRB; /* Interrupt Mask Register B 0x15 */ uint8_t VR; /* Vector Register 0x17 */ uint8_t TACR; /* Timer A Control Register 0x19 */ uint8_t TBCR; /* Timer B Control Register 0x1B */ uint8_t TCDCR; /* Timer C/D Control Register 0x1D */ uint8_t TADR; /* Timer A Data Register 0x1F */ uint8_t TBDR; /* Timer B Data Register 0x21 */ uint8_t TCDR; /* Timer C Data Register 0x23 */ uint8_t TDDR; /* Timer D Data Register 0x25 */ uint8_t SCR; /* Synchronous Data Register 0x27 */ uint8_t UCR; /* USART Control Register 0x29 */ uint8_t RSR; /* Receiver Status Register 0x2B */ uint8_t TSR; /* Transmitter Status Register 0x2D */ uint8_t UDR; /* USART Data Register 0x2F */ uint8_t IRQ; /* IRQ signal (output) 1=IRQ requested*/ uint8_t TAI; /* Input signal on Timer A (for event count mode) */ uint8_t TBI; /* Input signal on Timer B (for event count mode) */ /* Emulation variables */ uint8_t TA_MAINCOUNTER; uint8_t TB_MAINCOUNTER; uint8_t TC_MAINCOUNTER; uint8_t TD_MAINCOUNTER; // TODO drop those 4 variables, as they are not really used in MFP_ReadTimer_xx uint32_t TimerAClockCycles; uint32_t TimerBClockCycles; uint32_t TimerCClockCycles; uint32_t TimerDClockCycles; uint8_t PatchTimerD_Done; /* 0=false 1=true */ uint8_t PatchTimerD_TDDR_old; /* Value of TDDR before forcing it to PATCH_TIMER_TDDR_FAKE */ int16_t Current_Interrupt; uint64_t IRQ_Time; /* Time when IRQ was set to 1 */ uint8_t IRQ_CPU; /* Value of IRQ as seen by the CPU. There's a 4 cycle delay */ /* between a change of IRQ and its visibility at the CPU side */ uint64_t Pending_Time_Min; /* Clock value of the oldest pending int since last MFP_UpdateIRQ() */ uint64_t Pending_Time[ MFP_INT_MAX+1 ]; /* Clock value when pending is set to 1 for each non-masked int */ /* Other variables */ char NameSuffix[ 10 ]; /* "" or "_tt" */ } MFP_STRUCT; #define MFP_MAX_NB 2 /* 1 MFP in all machines, except TT with 2 MFP */ extern MFP_STRUCT MFP_Array[ MFP_MAX_NB ]; extern MFP_STRUCT *pMFP_Main; extern MFP_STRUCT *pMFP_TT; /* MFP Registers */ extern bool MFP_UpdateNeeded; extern int MFP_ConvertCycle_CPU_MFP_TIMER ( int CPU_Cycles ); extern int MFP_ConvertCycle_MFP_TIMER_CPU ( int MFP_Cycles ); extern void MFP_Init ( MFP_STRUCT *pAllMFP ); extern void MFP_Reset_All ( void ); extern void MFP_MemorySnapShot_Capture ( bool bSave ); extern uint8_t MFP_GetIRQ_CPU ( void ); extern void MFP_DelayIRQ ( void ); extern int MFP_ProcessIACK ( int OldVecNr ); extern bool MFP_ProcessIRQ_All ( void ); extern void MFP_UpdateIRQ_All ( uint64_t Event_Time ); extern void MFP_InputOnChannel ( MFP_STRUCT *pMFP , int Interrupt , int Interrupt_Delayed_Cycles ); extern void MFP_GPIP_Set_Line_Input ( MFP_STRUCT *pMFP , uint8_t LineNr , uint8_t Bit ); extern void MFP_TimerA_Set_Line_Input ( MFP_STRUCT *pMFP , uint8_t Bit ); extern void MFP_TimerA_EventCount( MFP_STRUCT *pMFP ); extern void MFP_TimerB_EventCount( MFP_STRUCT *pMFP , int Delayed_Cycles ); extern uint32_t MFP_TT_TimerC_Get_Freq ( void ); extern void MFP_Main_InterruptHandler_TimerA(void); extern void MFP_Main_InterruptHandler_TimerB(void); extern void MFP_Main_InterruptHandler_TimerC(void); extern void MFP_Main_InterruptHandler_TimerD(void); extern void MFP_TT_InterruptHandler_TimerA(void); extern void MFP_TT_InterruptHandler_TimerB(void); extern void MFP_TT_InterruptHandler_TimerC(void); extern void MFP_TT_InterruptHandler_TimerD(void); extern void MFP_GPIP_ReadByte ( void ); extern void MFP_ActiveEdge_ReadByte ( void ); extern void MFP_DataDirection_ReadByte ( void ); extern void MFP_EnableA_ReadByte ( void ); extern void MFP_EnableB_ReadByte ( void ); extern void MFP_PendingA_ReadByte ( void ); extern void MFP_PendingB_ReadByte ( void ); extern void MFP_InServiceA_ReadByte ( void ); extern void MFP_InServiceB_ReadByte ( void ); extern void MFP_MaskA_ReadByte ( void ); extern void MFP_MaskB_ReadByte ( void ); extern void MFP_VectorReg_ReadByte ( void ); extern void MFP_TimerACtrl_ReadByte ( void ); extern void MFP_TimerBCtrl_ReadByte ( void ); extern void MFP_TimerCDCtrl_ReadByte ( void ); extern void MFP_TimerAData_ReadByte ( void ); extern void MFP_TimerBData_ReadByte ( void ); extern void MFP_TimerCData_ReadByte ( void ); extern void MFP_TimerDData_ReadByte ( void ); extern void MFP_GPIP_WriteByte ( void ); extern void MFP_ActiveEdge_WriteByte ( void ); extern void MFP_DataDirection_WriteByte ( void ); extern void MFP_EnableA_WriteByte ( void ); extern void MFP_EnableB_WriteByte ( void ); extern void MFP_PendingA_WriteByte ( void ); extern void MFP_PendingB_WriteByte ( void ); extern void MFP_InServiceA_WriteByte ( void ); extern void MFP_InServiceB_WriteByte ( void ); extern void MFP_MaskA_WriteByte ( void ); extern void MFP_MaskB_WriteByte ( void ); extern void MFP_VectorReg_WriteByte ( void ); extern void MFP_TimerACtrl_WriteByte ( void ); extern void MFP_TimerBCtrl_WriteByte ( void ); extern void MFP_TimerCDCtrl_WriteByte ( void ); extern void MFP_TimerAData_WriteByte ( void ); extern void MFP_TimerBData_WriteByte ( void ); extern void MFP_TimerCData_WriteByte ( void ); extern void MFP_TimerDData_WriteByte ( void ); extern void MFP_Info(FILE *fp, uint32_t dummy); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/midi.h000066400000000000000000000015561504763705000242400ustar00rootroot00000000000000/* Hatari - midi.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_MIDI_H #define HATARI_MIDI_H extern void Midi_Init(void); extern void Midi_UnInit(void); extern void Midi_Reset(void); extern void MIDI_MemorySnapShot_Capture(bool bSave); extern void Midi_Control_ReadByte(void); extern void Midi_Data_ReadByte(void); extern void Midi_Control_WriteByte(void); extern void Midi_Data_WriteByte(void); extern void Midi_InterruptHandler_Update(void); #ifdef HAVE_PORTMIDI typedef enum { MIDI_FOR_INPUT, MIDI_FOR_OUTPUT } midi_dir_t; typedef enum { MIDI_NAME_PREV = -1, MIDI_NAME_FIND = 0, MIDI_NAME_NEXT = +1 } midi_name_offset_t; extern const char* Midi_Host_GetPortName(const char *name, midi_name_offset_t offset, midi_dir_t dir); #endif #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/msa.h000066400000000000000000000010201504763705000240600ustar00rootroot00000000000000/* Hatari - msa.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern bool MSA_FileNameIsMSA(const char *pszFileName, bool bAllowGZ); extern uint8_t *MSA_UnCompress(uint8_t *pMSAFile, long *pImageSize, long nBytesLeft); extern uint8_t *MSA_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType); extern bool MSA_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ncr5380.h000066400000000000000000000011611504763705000244100ustar00rootroot00000000000000/* * Hatari - NCR 5380 SCSI controller emulation * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef NCR5380_H #define NCR5380_H extern int nScsiPartitions; extern bool bScsiEmuOn; bool Ncr5380_Init(void); void Ncr5380_UnInit(void); void Ncr5380_Reset(void); void Ncr5380_WriteByte(int addr, uint8_t byte); uint8_t Ncr5380_ReadByte(int addr); void Ncr5380_DmaTransfer_Falcon(void); void Ncr5380_IoMemTT_WriteByte(void); void Ncr5380_IoMemTT_ReadByte(void); void Ncr5380_TT_DMA_Ctrl_WriteWord(void); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/nf_scsidrv.h000066400000000000000000000002621504763705000254470ustar00rootroot00000000000000#ifndef HATARI_SCSIDRV_H #define HATARI_SCSIDRV_H bool nf_scsidrv(uint32_t stack, uint32_t subid, uint32_t *retval); void nf_scsidrv_reset(void); #endif /* HATARI_SCSIDRV_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/options.h000066400000000000000000000013101504763705000247750ustar00rootroot00000000000000/* Hatari - options.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_OPTIONS_H #define HATARI_OPTIONS_H extern bool bLoadAutoSave; extern bool bLoadMemorySave; extern bool AviRecordOnStartup; extern bool BenchmarkMode; extern bool Opt_IsAtariProgram(const char *path); extern bool Opt_ShowError(unsigned int optid, const char *value, const char *error); extern int Opt_ValueAlignMinMax(int value, int align, int min, int max); extern bool Opt_ParseParameters(int argc, const char * const argv[]); extern char *Opt_MatchOption(const char *text, int state); #endif /* HATARI_OPTIONS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/paths.h000066400000000000000000000011301504763705000244210ustar00rootroot00000000000000/* Hatari This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_PATHS_H #define HATARI_PATHS_H extern void Paths_Init(const char *argv0); extern void Paths_UnInit(void); extern const char *Paths_GetWorkingDir(void); extern const char *Paths_GetDataDir(void); extern const char *Paths_GetUserHome(void); extern const char *Paths_GetHatariHome(void); extern const char *Paths_GetScreenShotDir(void); #if defined(__APPLE__) extern char *Paths_GetMacScreenShotDir(void); #endif #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/pixel_convert.h000066400000000000000000000045511504763705000261750ustar00rootroot00000000000000/* Hatari - pixel_convert.h Functions to convert pixels from different bit depths to 24 bits RGB or BGR. Used to save png screenshot and to record avi. This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ /*----------------------------------------------------------------------*/ /* Convert pixels to 24-bit RGB (3 bytes per pixel) */ /*----------------------------------------------------------------------*/ /** * unpack 32-bit RGBA pixels to 24-bit RGB pixels */ static inline void PixelConvert_32to24Bits(Uint8 *dst, Uint32 *src, int dw, SDL_Surface *surf) { SDL_PixelFormat *fmt = surf->format; Uint32 sval; int dx; for (dx = 0; dx < dw; dx++) { sval = src[(dx * surf->w + dw/2) / dw]; *dst++ = (((sval & fmt->Rmask) >> fmt->Rshift) << fmt->Rloss); *dst++ = (((sval & fmt->Gmask) >> fmt->Gshift) << fmt->Gloss); *dst++ = (((sval & fmt->Bmask) >> fmt->Bshift) << fmt->Bloss); } } /** * Remap 32-bit RGBA pixels back to 16-color ST palette if possible, false if failed. * Note that this cannot disambiguate indices if the palette has duplicate colors. */ static inline bool PixelConvert_32to8Bits(Uint8 *dst, Uint32 *src, int dw, SDL_Surface *surf) { Uint32 sval; int dval; int i,dx; bool valid = true; for (dx = 0; dx < dw; dx++) { sval = src[(dx * surf->w + dw/2) / dw]; dval = ConvertPaletteSize; for (i = 0; i < ConvertPaletteSize; i++) { if (sval == ConvertPalette[i]) { dval = i; break; } } if (dval >= ConvertPaletteSize) { valid = false; dval = 0; } *dst++ = (Uint8)dval; } return valid; } /*----------------------------------------------------------------------*/ /* Convert pixels to 24-bit BGR (3 bytes per pixel, used in BMP format) */ /*----------------------------------------------------------------------*/ /** * unpack 32-bit RGBA pixels to 24-bit BGR pixels */ static inline void PixelConvert_32to24Bits_BGR(Uint8 *dst, Uint32 *src, int dw, SDL_Surface *surf) { SDL_PixelFormat *fmt = surf->format; Uint32 sval; int dx; for (dx = 0; dx < dw; dx++) { sval = src[(dx * surf->w + dw/2) / dw]; *dst++ = (((sval & fmt->Bmask) >> fmt->Bshift) << fmt->Bloss); *dst++ = (((sval & fmt->Gmask) >> fmt->Gshift) << fmt->Gloss); *dst++ = (((sval & fmt->Rmask) >> fmt->Rshift) << fmt->Rloss); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/printer.h000066400000000000000000000003661504763705000247770ustar00rootroot00000000000000/* Hatari - printer.h function prototypes for pritner.c / Printing interface */ extern void Printer_Init(void); extern void Printer_UnInit(void); extern bool Printer_TransferByteTo(uint8_t Byte); extern void Printer_CheckIdleStatus(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/psg.h000066400000000000000000000031221504763705000240760ustar00rootroot00000000000000/* Hatari This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_PSG_H #define HATARI_PSG_H enum { PSG_REG_CHANNEL_A_FINE, /* 0x0000 */ PSG_REG_CHANNEL_A_COARSE, /* 0x0001 */ PSG_REG_CHANNEL_B_FINE, /* 0x0010 */ PSG_REG_CHANNEL_B_COARSE, /* 0x0011 */ PSG_REG_CHANNEL_C_FINE, /* 0x0100 */ PSG_REG_CHANNEL_C_COARSE, /* 0x0101 */ PSG_REG_NOISE_GENERATOR, /* 0x0110 */ PSG_REG_MIXER_CONTROL, /* 0x0111 */ PSG_REG_CHANNEL_A_AMP, /* 0x1000 */ PSG_REG_CHANNEL_B_AMP, /* 0x1001 */ PSG_REG_CHANNEL_C_AMP, /* 0x1010 */ PSG_REG_ENV_FINE, /* 0x1011 */ PSG_REG_ENV_COARSE, /* 0x1100 */ PSG_REG_ENV_SHAPE, /* 0x1101 */ PSG_REG_IO_PORTA, /* 0x1110 */ PSG_REG_IO_PORTB, /* 0x1111 */ MAX_PSG_REGISTERS }; #define NUM_PSG_SOUND_REGISTERS 14 /* Number of sound related registers, not including IO ports */ extern uint8_t PSGRegisters[MAX_PSG_REGISTERS]; extern void PSG_Reset(void); extern void PSG_MemorySnapShot_Capture(bool bSave); extern void PSG_Set_SelectRegister(uint8_t val); extern uint8_t PSG_Get_DataRegister(void); extern void PSG_Set_DataRegister(uint8_t val); extern void PSG_ff8800_ReadByte(void); extern void PSG_ff880x_ReadByte(void); extern void PSG_ff8800_WriteByte(void); extern void PSG_ff8801_WriteByte(void); extern void PSG_ff8802_WriteByte(void); extern void PSG_ff8803_WriteByte(void); extern void PSG_Info(FILE *fp, uint32_t dummy); #endif /* HATARI_PSG_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/reset.h000066400000000000000000000004411504763705000244300ustar00rootroot00000000000000/* Hatari - reset.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_RESET_H #define HATARI_RESET_H extern int Reset_Cold(void); extern int Reset_Warm(void); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/resolution.h000066400000000000000000000006461504763705000255200ustar00rootroot00000000000000/* Hatari - resolution.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_RESOLUTION_H #define HATARI_RESOLUTION_H extern void Resolution_Init(void); extern void Resolution_GetDesktopSize(int *width, int *height); extern void Resolution_GetLimits(int *width, int *height, bool keepDesktop); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/rs232.h000066400000000000000000000015231504763705000241630ustar00rootroot00000000000000/* Hatari - rs232.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_RS232_H #define HATARI_RS232_H extern void RS232_Init(void); extern void RS232_UnInit(void); extern void RS232_Update(void); extern void RS232_SetBaudRateFromTimerD(void); extern void RS232_Get_DCD_CTS ( uint8_t *pDCD , uint8_t *pCTS ); extern void RS232_SCR_ReadByte(void); extern void RS232_SCR_WriteByte(void); extern void RS232_UCR_ReadByte(void); extern void RS232_UCR_WriteByte(void); extern void RS232_RSR_ReadByte(void); extern void RS232_RSR_WriteByte(void); extern void RS232_TSR_ReadByte(void); extern void RS232_TSR_WriteByte(void); extern void RS232_UDR_ReadByte(void); extern void RS232_UDR_WriteByte(void); #endif /* ifndef HATARI_RS232_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/rtc.h000066400000000000000000000020741504763705000241020ustar00rootroot00000000000000/* Hatari - rtc.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Prototypes for the Mega-ST real time clock emulation. */ #ifndef HATARI_RTC_H #define HATARI_RTC_H extern void Rtc_Init(void); extern void Rtc_SecondsUnits_ReadByte(void); extern void Rtc_SecondsTens_ReadByte(void); extern void Rtc_MinutesUnits_ReadByte(void); extern void Rtc_MinutesUnits_WriteByte(void); extern void Rtc_MinutesTens_ReadByte(void); extern void Rtc_MinutesTens_WriteByte(void); extern void Rtc_HoursUnits_ReadByte(void); extern void Rtc_HoursTens_ReadByte(void); extern void Rtc_Weekday_ReadByte(void); extern void Rtc_DayUnits_ReadByte(void); extern void Rtc_DayTens_ReadByte(void); extern void Rtc_MonthUnits_ReadByte(void); extern void Rtc_MonthTens_ReadByte(void); extern void Rtc_YearUnits_ReadByte(void); extern void Rtc_YearTens_ReadByte(void); extern void Rtc_ClockMod_ReadByte(void); extern void Rtc_ClockMod_WriteByte(void); extern void Rtc_Info(FILE *fp, uint32_t dummy); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/scandir.h000066400000000000000000000013171504763705000247340ustar00rootroot00000000000000/* Hatari - scandir.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_SCANDIR_H #define HATARI_SCANDIR_H #include "config.h" #include #ifdef QNX #include #include #define dirent direct #endif #if !HAVE_ALPHASORT extern int alphasort(const struct dirent **d1, const struct dirent **d2); #endif #if !HAVE_SCANDIR extern int scandir(const char *dirname, struct dirent ***namelist, int (*sdfilter)(const struct dirent *), int (*comp)(const struct dirent **, const struct dirent **)); #endif #endif /* HATARI_SCANDIR_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/scc.h000066400000000000000000000020421504763705000240550ustar00rootroot00000000000000/* * Hatari scc.h * * 85C30 emulation code - declarations * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef SCC_H #define SCC_H #define SCC_IRQ_ON 0 /* O/low sets IRQ line */ #define SCC_IRQ_OFF 1 /* 1/high clears IRQ line */ bool SCC_IsAvailable(CNF_PARAMS *cnf); void SCC_Check_Lan_IsEnabled ( void ); void SCC_Update_TimerC_Clock ( void ); void SCC_Init(void); void SCC_UnInit(void); void SCC_MemorySnapShot_Capture(bool bSave); void SCC_Reset(void); void SCC_InterruptHandler_BRG_A(void); void SCC_InterruptHandler_BRG_B(void); void SCC_InterruptHandler_TX_RX_A(void); void SCC_InterruptHandler_TX_RX_B(void); void SCC_InterruptHandler_RX_A(void); void SCC_InterruptHandler_RX_B(void); int SCC_Get_Line_IRQ ( void ); int SCC_Process_IACK ( void ); void SCC_IRQ(void); int SCC_doInterrupt(void); void SCC_IoMem_ReadByte(void); void SCC_IoMem_WriteByte(void); void SCC_Info(FILE *fp, uint32_t dummy); #endif /* SCC_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/screen.h000066400000000000000000000112571504763705000245740ustar00rootroot00000000000000/* Hatari - screen.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_SCREEN_H #define HATARI_SCREEN_H #include /* for SDL_Surface */ extern SDL_Window *sdlWindow; /* TODO: Get rid of the following wrappers: */ void Screen_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects); void Screen_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h); /* The 'screen' is a representation of the ST video memory */ /* taking into account all the border tricks. Data are stored */ /* in 'planar' format (1 word per plane) and are then converted */ /* to an SDL buffer that will be displayed. */ /* So, all video lines are converted to a unique line of */ /* SCREENBYTES_LINE bytes in planar format. */ /* SCREENBYTES_LINE should always be a multiple of 8. */ /* left/right borders must be multiple of 8 bytes */ #define SCREENBYTES_LEFT (nBorderPixelsLeft/2) /* Bytes for left border */ #define SCREENBYTES_MIDDLE 160 /* Middle (320 pixels) */ #define SCREENBYTES_RIGHT (nBorderPixelsRight/2) /* Right border */ #define SCREENBYTES_LINE (SCREENBYTES_LEFT+SCREENBYTES_MIDDLE+SCREENBYTES_RIGHT) #define SCREENBYTES_MONOLINE 80 /* Byte per line in ST-high resolution */ /* Overscan values */ #define OVERSCAN_TOP 29 #define MAX_OVERSCAN_BOTTOM 47 /* number of bottom lines to display on screen */ /* Number of visible screen lines including top/bottom borders */ #define NUM_VISIBLE_LINES (OVERSCAN_TOP+200+MAX_OVERSCAN_BOTTOM) /* Number of visible pixels on each screen line including left/right borders */ #define NUM_VISIBLE_LINE_PIXELS (48+320+48) /* 1x16 colour palette per screen line, +1 line as may write after line 200 */ #define HBL_PALETTE_LINES ((NUM_VISIBLE_LINES+1 +3 )*16) /* [NP] FIXME we need to handle 313 hbl, not 310 ; palette code is a mess it should be removed */ /* Bit mask of palette colours changes, top bit set is resolution change */ #define HBL_PALETTE_MASKS (NUM_VISIBLE_LINES+1 +3 ) /* [NP] FIXME we need to handle 313 hbl, not 310 ; palette code is a mess it should be removed */ /* Frame buffer, used to store details in screen conversion */ typedef struct { Uint16 HBLPalettes[HBL_PALETTE_LINES]; Uint32 HBLPaletteMasks[HBL_PALETTE_MASKS]; Uint8 *pSTScreen; /* Copy of screen built up during frame (copy each line on HBL to simulate monitor raster) */ Uint8 *pSTScreenCopy; /* Previous frames copy of above */ int VerticalOverscanCopy; /* Previous screen overscan mode */ bool bFullUpdate; /* Set TRUE to cause full update on next draw */ } FRAMEBUFFER; /* ST/TT resolution defines */ enum { ST_LOW_RES, ST_MEDIUM_RES, ST_HIGH_RES, TT_MEDIUM_RES = 4, TT_HIGH_RES = 6, TT_LOW_RES }; #define ST_MEDIUM_RES_BIT 0x1 #define ST_RES_MASK 0x3 /* Palette mask values for 'HBLPaletteMask[]' */ #define PALETTEMASK_RESOLUTION 0x00040000 #define PALETTEMASK_PALETTE 0x0000ffff #define PALETTEMASK_UPDATERES 0x20000000 #define PALETTEMASK_UPDATEPAL 0x40000000 #define PALETTEMASK_UPDATEFULL 0x80000000 #define PALETTEMASK_UPDATEMASK (PALETTEMASK_UPDATEFULL|PALETTEMASK_UPDATEPAL|PALETTEMASK_UPDATERES) extern bool bGrabMouse; extern bool bInFullScreen; extern int nScreenZoomX, nScreenZoomY; extern int nBorderPixelsLeft, nBorderPixelsRight; extern int STScreenStartHorizLine; extern int STScreenLeftSkipBytes; extern FRAMEBUFFER *pFrameBuffer; extern Uint8 *pSTScreen; extern SDL_Surface *sdlscrn; extern Uint32 STRGBPalette[16]; extern Uint32 ST2RGB[4096]; extern Uint32* ConvertPalette; extern int ConvertPaletteSize; extern uint16_t HBLPalettes[HBL_PALETTE_LINES]; extern uint16_t *pHBLPalettes; extern uint32_t HBLPaletteMasks[HBL_PALETTE_MASKS]; extern uint32_t *pHBLPaletteMasks; extern int STScreenLineOffset[NUM_VISIBLE_LINES]; extern void Screen_Init(void); extern void Screen_UnInit(void); extern void Screen_Reset(void); extern bool Screen_Lock(void); extern void Screen_UnLock(void); extern void Screen_SetFullUpdate(void); extern void Screen_EnterFullScreen(void); extern void Screen_ReturnFromFullScreen(void); extern void Screen_ModeChanged(bool bForceChange); extern bool Screen_Draw(void); extern void Screen_SetTextureScale(int width, int height, int win_width, int win_height, bool bForceCreation); extern void Screen_SetGenConvSize(int width, int height, bool bForceChange); extern void Screen_GenConvUpdate(SDL_Rect *extra, bool forced); extern Uint32 Screen_GetGenConvWidth(void); extern Uint32 Screen_GetGenConvHeight(void); extern bool Screen_UseGenConvScreen(void); #endif /* ifndef HATARI_SCREEN_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/screenConvert.h000066400000000000000000000016771504763705000261420ustar00rootroot00000000000000/* Hatari - screenConvert.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ /* Last used vw/vh/vbpp for Screen_GenConvert */ extern int ConvertW, ConvertH, ConvertBPP, ConvertNextLine; void Screen_RemapPalette(void); void Screen_SetPaletteColor(Uint8 idx, Uint8 red, Uint8 green, Uint8 blue); SDL_Color Screen_GetPaletteColor(Uint8 idx); void ScreenConv_MemorySnapShot_Capture(bool bSave); void Screen_GenConvert(uint32_t vaddr, void *fvram, int vw, int vh, int vbpp, int nextline, int hscroll, int leftBorderSize, int rightBorderSize, int upperBorderSize, int lowerBorderSize); bool Screen_GenDraw(uint32_t vaddr, int vw, int vh, int vbpp, int nextline, int leftBorderSize, int rightBorderSize, int upperBorderSize, int lowerBorderSize); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/screenSnapShot.h000066400000000000000000000013611504763705000262470ustar00rootroot00000000000000/* Hatari - screenSnapShot.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_SCREENSNAPSHOT_H #define HATARI_SCREENSNAPSHOT_H #include #define SCREEN_SNAPSHOT_BMP 1 #define SCREEN_SNAPSHOT_PNG 2 #define SCREEN_SNAPSHOT_NEO 3 #define SCREEN_SNAPSHOT_XIMG 4 extern int ScreenSnapShot_SavePNG_ToFile(SDL_Surface *surface, int destw, int desth, FILE *fp, int png_compression_level, int png_filter, int CropLeft , int CropRight , int CropTop , int CropBottom ); extern void ScreenSnapShot_SaveScreen(void); extern void ScreenSnapShot_SaveToFile(const char *filename); #endif /* ifndef HATARI_SCREENSNAPSHOT_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/scu_vme.h000066400000000000000000000024151504763705000247520ustar00rootroot00000000000000/* Hatari - scu_vme.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_SCU_VME_H #define HATARI_SCU_VME_H extern bool SCU_IsEnabled ( void ); extern void SCU_Reset ( bool bCold ); extern void SCU_UpdatePendingInts_CPU ( void ); extern void SCU_SetIRQ_CPU ( int IntNr ); extern void SCU_ClearIRQ_CPU ( int IntNr ); extern void SCU_SysIntMask_ReadByte ( void ); extern void SCU_SysIntMask_WriteByte ( void ); extern void SCU_SysIntState_ReadByte ( void ); extern void SCU_SysIntState_WriteByte ( void ); extern void SCU_SysInterrupter_ReadByte ( void ); extern void SCU_SysInterrupter_WriteByte ( void ); extern void SCU_VmeInterrupter_ReadByte ( void ); extern void SCU_VmeInterrupter_WriteByte ( void ); extern void SCU_GPR1_ReadByte ( void ); extern void SCU_GPR1_WriteByte ( void ); extern void SCU_GPR2_ReadByte ( void ); extern void SCU_GPR2_WriteByte ( void ); extern void SCU_VmeIntMask_Readyte ( void ); extern void SCU_VmeIntMask_WriteByte ( void ); extern void SCU_VmeIntState_ReadByte ( void ); extern void SCU_VmeIntState_WriteByte ( void ); extern void SCU_MemorySnapShot_Capture ( bool bSave ); extern void SCU_Info ( FILE *fp, uint32_t arg ) ; #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/sdlgui.h000066400000000000000000000063151504763705000246030ustar00rootroot00000000000000/* Hatari - sdlgui.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Header for the tiny graphical user interface for Hatari. */ #ifndef HATARI_SDLGUI_H #define HATARI_SDLGUI_H #include /* object types: */ enum { SGSTOP = -1, /* type used at end of dialog to terminate it */ SGBOX, SGTEXT, SGEDITFIELD, SGBUTTON, SGRADIOBUT, SGCHECKBOX, SGPOPUP, SGSCROLLBAR }; /* Object flags: */ #define SG_TOUCHEXIT 1 /* Exit immediately when mouse button is pressed down */ #define SG_EXIT 2 /* Exit when mouse button has been pressed (and released) */ #define SG_DEFAULT 4 /* Marks a default button, selectable with Enter & Return keys */ #define SG_CANCEL 8 /* Marks a cancel button, selectable with ESC key */ #define SG_REPEAT 16 /* (Scrollbar) buttons which repeat regardless of mouse position */ /* Object states: */ #define SG_SELECTED 1 #define SG_MOUSEDOWN 2 #define SG_FOCUSED 4 /* Marks an object that has selection focus */ #define SG_WASFOCUSED 8 /* Marks an object that had selection focus & its bg needs redraw */ /* special shortcut keys, something that won't conflict with text shortcuts */ #define SG_SHORTCUT_LEFT '<' #define SG_SHORTCUT_RIGHT '>' #define SG_SHORTCUT_UP '^' #define SG_SHORTCUT_DOWN '|' /* Special characters: */ #define SGRADIOBUTTON_NORMAL 12 #define SGRADIOBUTTON_SELECTED 13 #define SGCHECKBOX_NORMAL 14 #define SGCHECKBOX_SELECTED 15 #define SGARROWUP 1 #define SGARROWDOWN 2 #define SGFOLDER 5 /* Object matching return codes: (negative so they aren't mixed with object indices) */ #define SDLGUI_ERROR -1 #define SDLGUI_QUIT -2 #define SDLGUI_UNKNOWNEVENT -3 #define SDLGUI_NOTFOUND -4 typedef struct { int type; /* What type of object */ int flags; /* Object flags */ int state; /* Object state */ int x, y; /* The offset to the upper left corner */ int w, h; /* Width and height (for scrollbar : height and position) */ char *txt; /* Text string */ int shortcut; /* shortcut key */ } SGOBJ; extern int sdlgui_fontwidth; /* Width of the actual font */ extern int sdlgui_fontheight; /* Height of the actual font */ extern int SDLGui_Init(void); extern int SDLGui_UnInit(void); extern int SDLGui_SetScreen(SDL_Surface *pScrn); extern void SDLGui_GetFontSize(int *width, int *height); extern void SDLGui_Text(int x, int y, const char *txt); extern void SDLGui_DrawDialog(const SGOBJ *dlg); extern void SDLGui_ScaleMouseStateCoordinates(int *x, int *y); extern int SDLGui_DoDialogExt(SGOBJ *dlg, bool (*isEventOut)(SDL_EventType), SDL_Event *pEventOut, int current_object); extern int SDLGui_DoDialog(SGOBJ *dlg); extern void SDLGui_CenterDlg(SGOBJ *dlg); extern char* SDLGui_FileSelect(const char *title, const char *path_and_name, char **zip_path, bool bAllowNew); extern bool SDLGui_FileConfSelect(const char *title, char *dlgname, char *confname, int maxlen, bool bAllowNew); extern bool SDLGui_DirConfSelect(const char *title, char *dlgname, char *confname, int maxlen); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/shortcut.h000066400000000000000000000011231504763705000251570ustar00rootroot00000000000000/* Hatari - shortcut.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ /* If pressed short-cut key modifier or standalone short-cut key, * retain keypress until safe to execute (start of VBL). Returns * true if key was shortcut, false otherwise */ extern bool ShortCut_CheckKeys(int modkey, int symkey, bool press); /* Invoke shortcut identified by name */ extern bool Shortcut_Invoke(const char *shortcut); /* Act on the stored keypress (in VBL) */ extern void ShortCut_ActKey(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/sound.h000066400000000000000000000052021504763705000244360ustar00rootroot00000000000000/* Hatari - sound.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Matthias Arndt 2008-08-15 - cleanup to have definitions and declarations for both sound cores in one place */ #ifndef HATARI_SOUND_H #define HATARI_SOUND_H /* definitions common for all sound rendering engines */ extern uint8_t SoundRegs[14]; /* store YM regs 0 to 13 */ extern int nGeneratedSamples; extern bool bEnvelopeFreqFlag; #define AUDIOMIXBUFFER_SIZE 16384 /* Size of circular buffer to store samples (eg 44Khz), must be a power of 2 */ #define AUDIOMIXBUFFER_SIZE_MASK ( AUDIOMIXBUFFER_SIZE - 1 ) /* To limit index values inside AudioMixBuffer[] */ extern int16_t AudioMixBuffer[AUDIOMIXBUFFER_SIZE][2]; /* Ring buffer to store mixed audio output (YM2149, DMA sound, ...) */ extern int AudioMixBuffer_pos_write; /* Current writing position into above buffer */ extern int AudioMixBuffer_pos_read; /* Current reading position into above buffer */ extern bool Sound_BufferIndexNeedReset; /* Internal data types */ typedef int64_t yms64; typedef int8_t yms8; typedef int16_t yms16; typedef int32_t yms32; typedef uint8_t ymu8; typedef uint16_t ymu16; typedef uint32_t ymu32; typedef yms16 ymsample; /* Output samples are mono 16bits signed PCM */ #define YM_LINEAR_MIXING 1 /* Use ymout1c5bit[] to build ymout5[] */ #define YM_TABLE_MIXING 2 /* Use volumetable_original to build ymout5[] */ #define YM_MODEL_MIXING 3 /* Use circuit analysis model to build ymout5[] */ extern int YmVolumeMixing; #define YM2149_LPF_FILTER_NONE 0 #define YM2149_LPF_FILTER_LPF_STF 1 #define YM2149_LPF_FILTER_PWM 2 extern int YM2149_LPF_Filter; #define YM2149_HPF_FILTER_NONE 0 #define YM2149_HPF_FILTER_IIR 1 extern int YM2149_HPF_Filter; #define YM2149_RESAMPLE_METHOD_NEAREST 0 #define YM2149_RESAMPLE_METHOD_WEIGHTED_AVERAGE_2 1 #define YM2149_RESAMPLE_METHOD_WEIGHTED_AVERAGE_N 2 extern int YM2149_Resample_Method; extern void Sound_Init(void); extern void Sound_Reset(void); extern void Sound_ResetBufferIndex(void); extern void Sound_MemorySnapShot_Capture(bool bSave); extern void Sound_Stats_Show (void); extern void Sound_Update(uint64_t CPU_Clock); extern void Sound_Update_VBL(void); extern void Sound_WriteReg(int reg, uint8_t data); extern bool Sound_BeginRecording(char *pszCaptureFileName); extern void Sound_EndRecording(void); extern bool Sound_AreWeRecording(void); extern void Sound_SetYmVolumeMixing(void); extern ymsample Subsonic_IIR_HPF_Left(ymsample x0); extern ymsample Subsonic_IIR_HPF_Right(ymsample x0); #endif /* HATARI_SOUND_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/spec512.h000066400000000000000000000011301504763705000244640ustar00rootroot00000000000000/* Hatari - spec512.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_SPEC512_H #define HATARI_SPEC512_H extern bool Spec512_IsImage(void); extern void Spec512_StartVBL(void); extern void Spec512_StoreCyclePalette(Uint16 col, Uint32 addr); extern void Spec512_StartFrame(void); extern void Spec512_ScanWholeLine(void); extern void Spec512_StartScanLine(void); extern void Spec512_EndScanLine(void); extern void Spec512_UpdatePaletteSpan(void); #endif /* HATARI_SPEC512_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/st.h000066400000000000000000000006651504763705000237440ustar00rootroot00000000000000/* Hatari - st.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern bool ST_FileNameIsST(const char *pszFileName, bool bAllowGZ); extern uint8_t *ST_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType); extern bool ST_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/stMemory.h000066400000000000000000000050321504763705000251260ustar00rootroot00000000000000/* Hatari - stMemory.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_STMEMORY_H #define HATARI_STMEMORY_H #include "main.h" #include "sysdeps.h" #include "maccess.h" #include "memory.h" extern uint8_t *STRam; extern uae_u8 *ROMmemory; #define RomMem (ROMmemory-0xe00000) extern uae_u8 *TTmemory; extern uae_u32 TTmem_size; extern uint32_t STRamEnd; #define MEM_BANK_SIZE_128 ( 128 * 1024 ) /* 00b */ #define MEM_BANK_SIZE_512 ( 512 * 1024 ) /* 01b */ #define MEM_BANK_SIZE_2048 ( 2048 * 1024 ) /* 10b */ #define MEM_BANK_SIZE_8192 ( 8192 * 1024 ) /* for TT */ extern uint32_t RAM_Bank0_Size; extern uint32_t RAM_Bank1_Size; extern uint32_t MMU_Bank0_Size; extern uint32_t MMU_Bank1_Size; extern void STMemory_Init ( int RAM_Size_Byte ); extern void STMemory_Reset ( bool bCold ); extern bool STMemory_SafeClear(uint32_t addr, unsigned int len); extern bool STMemory_SafeCopy(uint32_t addr, uint8_t *src, unsigned int len, const char *name); extern void STMemory_MemorySnapShot_Capture(bool bSave); extern void STMemory_SetDefaultConfig(void); extern int STMemory_CorrectSTRamSize(void); extern bool STMemory_CheckAreaType ( uint32_t addr , int size , int mem_type ); extern bool STMemory_CheckAddrBusError ( uint32_t addr ); extern void *STMemory_STAddrToPointer ( uint32_t addr ); extern char *STMemory_GetStringPointer(uint32_t addr); extern void STMemory_Write ( uint32_t addr , uint32_t val , int size ); extern void STMemory_WriteLong ( uint32_t addr , uint32_t val ); extern void STMemory_WriteWord ( uint32_t addr , uint16_t val ); extern void STMemory_WriteByte ( uint32_t addr , uint8_t val ); extern uint32_t STMemory_Read ( uint32_t addr , int size ); extern uint32_t STMemory_ReadLong ( uint32_t addr ); extern uint16_t STMemory_ReadWord ( uint32_t addr ); extern uint8_t STMemory_ReadByte ( uint32_t addr ); extern uint16_t STMemory_DMA_ReadWord ( uint32_t addr ); extern void STMemory_DMA_WriteWord ( uint32_t addr , uint16_t value ); extern uint8_t STMemory_DMA_ReadByte ( uint32_t addr ); extern void STMemory_DMA_WriteByte ( uint32_t addr , uint8_t value ); extern void STMemory_MMU_Config_ReadByte ( void ); extern void STMemory_MMU_Config_WriteByte ( void ); extern int STMemory_RAM_Validate_Size_KB ( int TotalMem ); extern bool STMemory_RAM_SetBankSize ( int TotalMem , uint32_t *pBank0_Size , uint32_t *pBank1_Size , uint8_t *pMMU_Conf ); extern uint32_t STMemory_MMU_Translate_Addr ( uint32_t addr_logical ); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/statusbar.h000066400000000000000000000023111504763705000253140ustar00rootroot00000000000000/* Hatari - statusbar.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_STATUSBAR_H #define HATARI_STATUSBAR_H #include /* must correspond to max value returned by Statusbar_GetHeightForSize() */ #define STATUSBAR_MAX_HEIGHT (2*(2*8+2)) typedef enum { DRIVE_LED_A, DRIVE_LED_B, DRIVE_LED_HD } drive_index_t; typedef enum { LED_STATE_OFF, LED_STATE_ON, LED_STATE_ON_BUSY, MAX_LED_STATE } drive_led_t; extern int Statusbar_SetHeight(int ScreenWidth, int ScreenHeight); extern int Statusbar_GetHeightForSize(int width, int height); extern int Statusbar_GetHeight(void); extern void Statusbar_EnableHDLed(drive_led_t state); extern void Statusbar_SetFloppyLed(drive_index_t drive, drive_led_t state); extern void Statusbar_Init(SDL_Surface *screen); extern void Statusbar_UpdateInfo(void); extern void Statusbar_AddMessage(const char *msg, Uint32 msecs); extern void Statusbar_OverlayBackup(SDL_Surface *screen); extern SDL_Rect* Statusbar_Update(SDL_Surface *screen, bool do_update); extern void Statusbar_OverlayRestore(SDL_Surface *screen); #endif /* HATARI_STATUSBAR_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/str.h000066400000000000000000000042331504763705000241210ustar00rootroot00000000000000/* Hatari - str.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_STR_H #define HATARI_STR_H #include "config.h" #include #if HAVE_STRINGS_H # include #endif /* Define this only for an old Linux system which does not store * pathnames in UTF-8. If this is defined, pathnames are converted * to the host character set as defined by the locale. * Do not define this for OSX, as the unicode pathnames then won't * be converted from the decomposed to the precomposed form. * * TODO: this should have option / be set in CMake config. */ /* #define USE_LOCALE_CHARSET 1 */ /* Invalid characters in paths & filenames are replaced by this, * a valid, but uncommon GEMDOS file name character, * which hopefully shouldn't cause problems in: * - TOS *.INF files used for autostarting * - GEM file selectors (TOS or replacement ones) * - path/file handling code of common programming languages */ #define INVALID_CHAR '+' /* String processing helper macros/functions */ #define Str_Free(s) { free(s); s = NULL; } extern char *Str_Trim(char *buffer); extern char *Str_ToUpper(char *pString); extern char *Str_ToLower(char *pString); extern char *Str_Alloc(int len); extern char *Str_Dup(const char *str); extern long Str_Copy(char *pDest, const char *pSrc, long nBufLen); extern char *Str_Trunc(char *str); extern bool Str_IsHex(const char *str); extern void Str_UnEscape(char *str); extern void Str_Dump_Hex_Ascii ( char *p , int Len , int Width , const char *Suffix , FILE *pFile ); /* Interface of character set conversions */ extern void Str_Filename_Host2Atari(const char *source, char *dest); extern void Str_Filename_Atari2Host(const char *source, char *dest, int destLen, char replacementChar); extern bool Str_AtariToHost(const char *source, char *dest, int destLen, char replacementChar); extern bool Str_HostToAtari(const char *source, char *dest, char replacementChar); extern void Str_DecomposedToPrecomposedUtf8(const char *source, char *dest); /* Setup for string conversions */ extern void Str_Init(void); #endif /* HATARI_STR_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/tos.h000066400000000000000000000031621504763705000241160ustar00rootroot00000000000000/* Hatari - tos.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_TOS_H #define HATARI_TOS_H /* * List of language and keyboard layout country codes supported * by currently available TOS versions. See: * https://github.com/emutos/emutos/blob/master/include/ctrycodes.h * https://freemint.github.io/tos.hyp/en/bios_cookiejar.html#Cookie_2C_20_AKP */ enum { TOS_LANG_US = 0, TOS_LANG_DE = 1, TOS_LANG_FR = 2, TOS_LANG_UK = 3, TOS_LANG_ES = 4, TOS_LANG_IT = 5, TOS_LANG_SE = 6, TOS_LANG_CH_FR = 7, TOS_LANG_CH_DE = 8, TOS_LANG_TR = 9, TOS_LANG_FI = 10, TOS_LANG_NO = 11, TOS_LANG_DK = 12, TOS_LANG_SA = 13, TOS_LANG_NL = 14, TOS_LANG_CS = 15, TOS_LANG_HU = 16, TOS_LANG_PL = 17, TOS_LANG_RU = 19, TOS_LANG_RO = 24, TOS_LANG_GR = 31, TOS_LANG_CA = 54, TOS_LANG_ALL = 127, TOS_LANG_UNKNOWN = -1 }; extern bool bIsEmuTOS; extern uint32_t EmuTosVersion; extern uint16_t TosVersion; extern uint32_t TosAddress, TosSize; extern bool bTosImageLoaded; extern bool bRamTosImage; extern bool bUseTos; extern unsigned int ConnectedDriveMask; extern int nNumDrives; extern void TOS_MemorySnapShot_Capture(bool bSave); extern int TOS_InitImage(void); extern void TOS_SetTestPrgName(const char *testprg); extern int TOS_DefaultLanguage(void); extern int TOS_ParseCountryCode(const char *code); extern void TOS_ShowCountryCodes(void); extern const char *TOS_LanguageName(int code); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/unzip.h000066400000000000000000000217021504763705000244560ustar00rootroot00000000000000/* Hatari - unzip.h Support for *.zip files, using zlib. This file is originally from the minizip code by Gilles Vollant. */ /* unzip.h -- IO for uncompress .zip files using zlib Version 0.15 beta, Mar 19th, 1998, Copyright (C) 1998 Gilles Vollant This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g WinZip, InfoZip tools and compatible. Encryption and multi volume ZipFile (span) are not supported. Old compressions used by old PKZip 1.x are not supported THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPMENT, SOME API OR STRUCTURE CAN CHANGE IN FUTURE VERSION !! I WAIT FEEDBACK at mail info@winimage.com Visit also http://www.winimage.com/zLibDll/unzip.html for evolution Condition of use and distribution are the same than zlib : This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* for more info about .ZIP format, see ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip PkWare has also a specification at : ftp://ftp.pkware.com/probdesc.zip */ #if !defined(_unz_H) && defined(HAVE_ZLIB_H) #define _unz_H #ifdef __cplusplus extern "C" { #endif #ifndef _ZLIB_H #include #endif #if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagunzFile__ { int unused; } unzFile__; typedef unzFile__ *unzFile; #else typedef voidp unzFile; #endif #define UNZ_OK (0) #define UNZ_END_OF_LIST_OF_FILE (-100) #define UNZ_ERRNO (Z_ERRNO) #define UNZ_EOF (0) #define UNZ_PARAMERROR (-102) #define UNZ_BADZIPFILE (-103) #define UNZ_INTERNALERROR (-104) #define UNZ_CRCERROR (-105) /* tm_unz contain date/time info */ typedef struct tm_unz_s { uInt tm_sec; /* seconds after the minute - [0,59] */ uInt tm_min; /* minutes after the hour - [0,59] */ uInt tm_hour; /* hours since midnight - [0,23] */ uInt tm_mday; /* day of the month - [1,31] */ uInt tm_mon; /* months since January - [0,11] */ uInt tm_year; /* years - [1980..2044] */ } tm_unz; /* unz_global_info structure contain global data about the ZIPfile These data comes from the end of central dir */ typedef struct unz_global_info_s { uLong number_entry; /* total number of entries in the central dir on this disk */ uLong size_comment; /* size of the global comment of the zipfile */ } unz_global_info; /* unz_file_info contain information about a file in the zipfile */ typedef struct unz_file_info_s { uLong version; /* version made by 2 bytes */ uLong version_needed; /* version needed to extract 2 bytes */ uLong flag; /* general purpose bit flag 2 bytes */ uLong compression_method; /* compression method 2 bytes */ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ uLong crc; /* crc-32 4 bytes */ uLong compressed_size; /* compressed size 4 bytes */ uLong uncompressed_size; /* uncompressed size 4 bytes */ uLong size_filename; /* filename length 2 bytes */ uLong size_file_extra; /* extra field length 2 bytes */ uLong size_file_comment; /* file comment length 2 bytes */ uLong disk_num_start; /* disk number start 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ tm_unz tmu_date; } unz_file_info; extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); /* Compare two filename (fileName1,fileName2). If iCaseSenisivity = 1, comparison is case sensitivity (like strcmp) If iCaseSenisivity = 2, comparison is not case sensitivity (like strcmpi or strcasecmp) If iCaseSenisivity = 0, case sensitivity is default of your operating system (like 1 on Unix, 2 on Windows) */ extern unzFile ZEXPORT unzOpen (const char *path); /* Open a Zip file. path contain the full pathname (by example, on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer "zlib/zlib111.zip". If the zipfile cannot be opened (file don't exist or in not valid), the return value is NULL. Else, the return value is a unzFile Handle, usable with other function of this unzip package. */ extern int ZEXPORT unzClose (unzFile file); /* Close a ZipFile opened with unzipOpen. If there is files inside the .Zip opened with unzOpenCurrentFile (see later), these files MUST be closed with unzipCloseCurrentFile before call unzipClose. return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ /***************************************************************************/ /* Unzip package allow you browse the directory of the zipfile */ extern int ZEXPORT unzGoToFirstFile (unzFile file); /* Set the current file of the zipfile to the first file. return UNZ_OK if there is no problem */ extern int ZEXPORT unzGoToNextFile (unzFile file); /* Set the current file of the zipfile to the next file. return UNZ_OK if there is no problem return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. */ extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); /* Try locate the file szFileName in the zipfile. For the iCaseSensitivity signification, see unzStringFileNameCompare return value : UNZ_OK if the file is found. It becomes the current file. UNZ_END_OF_LIST_OF_FILE if the file is not found */ extern int ZEXPORT unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize); /* Get Info about the current file if pfile_info!=NULL, the *pfile_info structure will contain some info about the current file if szFileName!=NULL, the filemane string will be copied in szFileName (fileNameBufferSize is the size of the buffer) if extraField!=NULL, the extra field information will be copied in extraField (extraFieldBufferSize is the size of the buffer). This is the Central-header version of the extra field if szComment!=NULL, the comment string of the file will be copied in szComment (commentBufferSize is the size of the buffer) */ /***************************************************************************/ /* for reading the content of the current zipfile, you can open it, read data from it, and close it (you can close it before reading all the file) */ extern int ZEXPORT unzOpenCurrentFile (unzFile file); /* Open for reading data the current file in the zipfile. If there is no error, the return value is UNZ_OK. */ extern int ZEXPORT unzCloseCurrentFile (unzFile file); /* Close the file in zip opened with unzOpenCurrentFile Return UNZ_CRCERROR if all the file was read but the CRC is not good */ extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len); /* Read bytes from the current file (opened by unzOpenCurrentFile) buf contain buffer where data must be copied len the size of buf. return the number of byte copied if some bytes are copied return 0 if the end of file was reached return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ extern z_off_t ZEXPORT unztell (unzFile file); /* Give the current position in uncompressed data */ extern int ZEXPORT unzeof (unzFile file); /* return 1 if the end of file was reached, 0 elsewhere */ #ifdef __cplusplus } #endif #endif /* _unz_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/utils.h000066400000000000000000000012541504763705000244510ustar00rootroot00000000000000/* Hatari - utils.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_UTILS_H #define HATARI_UTILS_H #include #include #define CRC32_POLY 0x04c11db7 /* IEEE 802.3 recommendation */ extern void crc32_reset ( uint32_t *crc ); extern void crc32_add_byte ( uint32_t *crc , uint8_t c ); #define CRC16_POLY 0x1021 /* CCITT */ extern void crc16_reset ( uint16_t *crc ); extern void crc16_add_byte ( uint16_t *crc , uint8_t c ); void Hatari_srand ( unsigned int seed ); extern int Hatari_rand ( void ); #endif /* HATARI_UTILS_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/vdi.h000066400000000000000000000041031504763705000240670ustar00rootroot00000000000000/* Hatari - vdi.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_VDI_H #define HATARI_VDI_H /* Bitplanes are interleaved in chunks of 16 pixels and height should * be aligned to VDI text height (8, smaller of these, is used in * calculating the limits in Hatari). * * Earlier programs have been determined to have issues when screen * size goes over 300kB, so that is taken as limit too. * * Below MAX_VDI_* values are reasonable limits for monochrome resolutions. * 300kB limits screen size to max. resolution of 2048x1200 or 1920x1280, * which allows FHD (1920x1080), WUXGA (1920x1200) and QWXGA (2048x1152) * standard resolutions. * * On 4-color mode, 300kB limits screen size to 1280x960 or 1200x1024 * which allows HD (1280x720), WXGA (1280x768) and XGA+ (1152x864) * standard resolutions. * * On 16-color mode, 300kB limits screen size to 1024x576 or 800x768, * which fills nicely qHD (960x540), DVGA (960x640) and WSVGA (1024x600) * standard resolution displays. */ #define MAX_VDI_BYTES 300*1024 #define MAX_VDI_WIDTH 2048 #define MAX_VDI_HEIGHT 1280 /* smaller doesn't make sense even for testing */ #define MIN_VDI_WIDTH 320 #define MIN_VDI_HEIGHT 160 /* bitplanes are interleaved in chunks of 16 pixels */ #define VDI_ALIGN_WIDTH 16 /* height should be multiple of cell height (8 or 16) */ #define VDI_ALIGN_HEIGHT 8 enum { GEMCOLOR_2, GEMCOLOR_4, GEMCOLOR_16 }; extern uint32_t VDI_OldPC; extern bool bUseVDIRes, bVdiAesIntercept; extern int VDIWidth,VDIHeight; extern int VDIRes,VDIPlanes; extern bool VDI_ByteLimit(int *width, int *height, int planes); extern void VDI_SetResolution(int GEMColor, int WidthRequest, int HeightRequest); extern void AES_Info(FILE *fp, uint32_t bShowOpcodes); extern void VDI_Info(FILE *fp, uint32_t bShowOpcodes); extern bool VDI_AES_Entry(void); extern void VDI_LineA(uint32_t LineABase, uint32_t FontBase); extern void VDI_Complete(void); extern void VDI_Reset(void); #endif /* HATARI_VDI_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/version.h000066400000000000000000000003131504763705000247710ustar00rootroot00000000000000/* Hatari - version.h Name and version for window title (and other outputs) */ /* devel */ //#define PROG_NAME "Hatari v2.6.0-devel (" __DATE__ ")" /* release */ #define PROG_NAME "Hatari v2.6.1" hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/video.h000066400000000000000000000256611504763705000244270ustar00rootroot00000000000000/* Hatari - video.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_VIDEO_H #define HATARI_VIDEO_H /* All the following processor timings are based on a bog standard 8MHz 68000 as found in all standard STs: Clock cycles per line (50Hz) : 512 NOPs per scan line (50Hz) : 128 Scan lines per VBL (50Hz) : 313 (63 at top,200 screen,47 bottom) Clock cycles per line (60Hz) : 508 NOPs per scan line (60Hz) : 127 Scan lines per VBL (60Hz) : 263 (34 at top,200 screen,26 bottom) Clock cycles per VBL (50Hz) : 160256 NOPs per VBL (50Hz) : 40064 Clock cycles per VBL (60Hz) : 133604 NOPs per VBL (50Hz) : 33401 Pixels per clock cycle (low res) : 1 Pixels per clock cycle (med res) : 2 Pixels per clock cycle (high res) : 4 Pixels per NOP (low res) : 4 Pixels per NOP (med res) : 8 Pixels per NOP (high res) : 16 */ #define VIDEO_50HZ 50 #define VIDEO_60HZ 60 #define VIDEO_71HZ 71 /* Scan lines per frame */ #define SCANLINES_PER_FRAME_50HZ 313 /* Number of scan lines per frame in 50 Hz */ #define SCANLINES_PER_FRAME_60HZ 263 /* Number of scan lines per frame in 60 Hz */ #define SCANLINES_PER_FRAME_71HZ 501 /* could also be 500 ? */ #define MAX_SCANLINES_PER_FRAME SCANLINES_PER_FRAME_71HZ /* Max. number of scan lines per frame */ /* Cycles per line */ #define CYCLES_PER_LINE_50HZ 512 #define CYCLES_PER_LINE_60HZ 508 #define CYCLES_PER_LINE_71HZ 224 /* Vertical border/display enable/disable: * Normal screen starts 63 lines in, top border is 29 lines */ #define VIDEO_START_HBL_50HZ 63 /* Usually the first line of the displayed screen in 50 Hz */ #define VIDEO_START_HBL_60HZ 34 /* The first line of the displayed screen in 60 Hz */ #define VIDEO_START_HBL_71HZ 34 /* FIXME: 34 is not verified */ #define FIRST_VISIBLE_HBL_50HZ 34 /* At this line we start rendering our screen in 50 Hz */ #define FIRST_VISIBLE_HBL_60HZ (34-29) /* At this line we start rendering our screen in 60 Hz (29 = 63-34) */ #define FIRST_VISIBLE_HBL_71HZ 34 /* FIXME: 34 is not verified */ #define VIDEO_HEIGHT_HBL_COLOR 200 /* This is usually the height of the screen */ #define VIDEO_HEIGHT_HBL_MONO 400 #define VIDEO_HEIGHT_BOTTOM_50HZ 47 /* number of extra pixel lines in a 50 Hz screen when removing bottom border */ /* (the 2 last lines are masked by the vblank signal and can be displayed */ /* by doing a 2nd 60/50 Hz switch at the end of line 308) */ #define VIDEO_HEIGHT_BOTTOM_60HZ 26 /* number of extra pixel lines in a 60 Hz screen when removing bottom border */ /* (the 2 last lines are masked by the vblank signal and can be displayed */ /* by doing a 2nd 50/60 Hz switch at the end of line 258) */ #define VIDEO_END_HBL_50HZ ( VIDEO_START_HBL_50HZ + VIDEO_HEIGHT_HBL_COLOR ) /* 263 */ #define VIDEO_END_HBL_60HZ ( VIDEO_START_HBL_60HZ + VIDEO_HEIGHT_HBL_COLOR ) /* 234 */ #define VIDEO_END_HBL_71HZ ( VIDEO_START_HBL_71HZ + VIDEO_HEIGHT_HBL_MONO ) /* 434 */ #define LINE_REMOVE_TOP_CYCLE_STF 504 /* switch to 60 Hz on line 33 should not occur after cycle 504 to remove top border */ /* switch to 50 Hz should occur after cycle 504 on line 33 */ #define LINE_REMOVE_BOTTOM_CYCLE_STF 504 /* same value than top border, but on line 262 (50 Hz) or 233 (60 Hz) */ #define LINE_REMOVE_TOP_CYCLE_STE 500 /* on STE, switch can occur 4 cycles earlier than STF */ #define LINE_REMOVE_BOTTOM_CYCLE_STE 500 /* Values for VerticalOverscan */ #define V_OVERSCAN_NONE 0x00 #define V_OVERSCAN_NO_TOP 0x01 #define V_OVERSCAN_NO_BOTTOM_50 0x02 #define V_OVERSCAN_NO_BOTTOM_60 0x04 #define V_OVERSCAN_BOTTOM_SHORT_50 0x08 #define V_OVERSCAN_NO_DE 0x10 #define LINE_START_CYCLE_50 56 #define LINE_START_CYCLE_60 52 #define LINE_START_CYCLE_71 0 #define LINE_END_CYCLE_50 376 /* LINE_START_CYCLE_50 + 320 */ #define LINE_END_CYCLE_60 372 /* LINE_START_CYCLE_60 + 320 */ #define LINE_END_CYCLE_71 160 #define LINE_END_CYCLE_NO_RIGHT 460 /* 372 + 44*2 */ #define LINE_END_CYCLE_50_2 (LINE_END_CYCLE_50+44*2) /* 464, used in enchanted lands */ #define LINE_END_CYCLE_FULL 512 /* used in enchanted lands */ #define LINE_LEFT_STAB_LOW 16 /* remove left + med res stab using hi/med/lo switches */ #define LINE_SCROLL_13_CYCLE_50 20 /* 13 pixels right "hardware" scrolling */ #define LINE_SCROLL_9_CYCLE_50 24 /* 9 pixels right "hardware" scrolling */ #define LINE_SCROLL_5_CYCLE_50 28 /* 5 pixels right "hardware" scrolling */ #define LINE_SCROLL_1_CYCLE_50 32 /* 1 pixels right "hardware" scrolling */ #define LINE_LEFT_MED_CYCLE_1 20 /* med res overscan, shifts display by 0 byte */ #define LINE_LEFT_MED_CYCLE_2 28 /* med res overscan, shifts display by 2 bytes */ #define LINE_EMPTY_CYCLE_71_STF 28 /* on STF switch to hi/lo will create an empty line */ #define LINE_EMPTY_CYCLE_71_STE (28+4) /* on STE switch to hi/lo will create an empty line */ /* Bytes for opened left and right border: */ #define BORDERBYTES_NORMAL 160 /* size of a "normal" line */ #define BORDERBYTES_LEFT 26 #define BORDERBYTES_LEFT_2_STE 20 #define BORDERBYTES_RIGHT 44 #define BORDERBYTES_RIGHT_FULL 22 #define VBL_VIDEO_CYCLE_OFFSET_STF 64 /* value of cycle counter when VBL signal is sent */ #define VBL_VIDEO_CYCLE_OFFSET_STE (64+4) /* 4 cycles difference on STE */ #define HBL_VIDEO_CYCLE_OFFSET 0 /* cycles after end of current line (ie on every 512 cycles in 50 Hz) */ #define TIMERB_VIDEO_CYCLE_OFFSET 24 /* cycles after last displayed pixels : 376+24 in 50 Hz or 372+24 in 60 Hz */ /* This is when ff8205/07/09 are reloaded with the content of ff8201/03 : on line 310 cycle 56/60 in 50 Hz and on line 260 cycle 56/60 in 60 Hz */ /* (values were measured on real STF/STE) */ #define RESTART_VIDEO_COUNTER_LINE_50HZ ( SCANLINES_PER_FRAME_50HZ-3 ) #define RESTART_VIDEO_COUNTER_LINE_60HZ ( SCANLINES_PER_FRAME_60HZ-3 ) #define RESTART_VIDEO_COUNTER_CYCLE_STF ( 56 ) #define RESTART_VIDEO_COUNTER_CYCLE_STE ( 56 + 4 ) /* 4 cycles later than STF */ /* Values for some HW video signals, should be 0 or 1 */ #define VSYNC_SIGNAL_OFF 0 #define VSYNC_SIGNAL_ON 1 #define VBLANK_SIGNAL_OFF 0 #define VBLANK_SIGNAL_ON 1 /* anything above 4 uses automatic frameskip */ #define AUTO_FRAMESKIP_LIMIT 5 extern int STRes; extern int TTRes; extern int nFrameSkips; extern bool bUseHighRes; extern int nVBLs; extern int nHBL; extern int nStartHBL; extern int nEndHBL; extern int VerticalOverscan; extern uint32_t VideoBase; extern int nScreenRefreshRate; extern int nScanlinesPerFrame; extern int nCyclesPerLine; extern int TTSpecialVideoMode; extern int LineTimerBPos; extern int TimerBEventCountCycleStart; #define HBL_JITTER_ARRAY_SIZE 5 extern int HblJitterIndex; extern const int HblJitterArray[HBL_JITTER_ARRAY_SIZE]; extern const int HblJitterArrayPending[HBL_JITTER_ARRAY_SIZE]; #define VBL_JITTER_ARRAY_SIZE 5 extern int VblJitterIndex; extern const int VblJitterArray[VBL_JITTER_ARRAY_SIZE]; extern const int VblJitterArrayPending[VBL_JITTER_ARRAY_SIZE]; /*--------------------------------------------------------------*/ /* Functions prototypes */ /*--------------------------------------------------------------*/ extern void Video_MemorySnapShot_Capture(bool bSave); extern void Video_Reset(void); extern void Video_Reset_Glue(void); extern void Video_InitTimings(void); extern void Video_SetTimings( MACHINETYPE MachineType , VIDEOTIMINGMODE Mode ); extern const char* Video_GetTimings_Name ( void ); extern uint8_t Video_Get_MONO_Line ( void ); extern void Video_ConvertPosition( int FrameCycles , int *pHBL , int *pLineCycles ); extern int Video_GetCyclesSinceVbl ( void ); extern int Video_GetCyclesSinceVbl_OnWriteAccess ( void ); extern int Video_GetCyclesSinceVbl_OnReadAccess ( void ); extern void Video_GetPosition( int *pFrameCycles , int *pHBL , int *pLineCycles ); extern void Video_GetPosition_OnWriteAccess( int *pFrameCycles , int *pHBL , int *pLineCycles ); extern void Video_GetPosition_OnReadAccess( int *pFrameCycles , int *pHBL , int *pLineCycles ); extern void Video_Sync_WriteByte(void); extern int Video_TimerB_GetPos( int LineNumber ); extern void Video_InterruptHandler_HBL(void); extern void Video_InterruptHandler_EndLine(void); extern void Video_Set_Memcpy ( bool Force_MMU_Translation ); extern void Video_SetScreenRasters(void); extern void Video_GetTTRes(int *width, int *height, int *bpp); extern bool Video_RenderTTScreen(void); extern void Video_AddInterruptTimerB ( int LineVideo , int CycleVideo , int Pos ); extern void Video_StartInterrupts ( int PendingCyclesOver ); extern void Video_InterruptHandler_VBL(void); extern uint32_t Video_GetScreenBaseAddr(void); extern void Video_ScreenBase_WriteByte(void); extern void Video_ScreenCounter_ReadByte(void); extern void Video_ScreenCounter_WriteByte(void); extern void Video_Sync_ReadByte(void); extern void Video_BaseLow_ReadByte(void); extern void Video_LineWidth_ReadByte(void); extern void Video_ResGlueShifter_ReadByte(void); extern void Video_ResShifter_ReadByte(void); extern void Video_HorScroll_Read(void); extern void Video_LineWidth_WriteByte(void); extern void Video_Color0_WriteWord(void); extern void Video_Color1_WriteWord(void); extern void Video_Color2_WriteWord(void); extern void Video_Color3_WriteWord(void); extern void Video_Color4_WriteWord(void); extern void Video_Color5_WriteWord(void); extern void Video_Color6_WriteWord(void); extern void Video_Color7_WriteWord(void); extern void Video_Color8_WriteWord(void); extern void Video_Color9_WriteWord(void); extern void Video_Color10_WriteWord(void); extern void Video_Color11_WriteWord(void); extern void Video_Color12_WriteWord(void); extern void Video_Color13_WriteWord(void); extern void Video_Color14_WriteWord(void); extern void Video_Color15_WriteWord(void); extern void Video_Color0_ReadWord(void); extern void Video_Color1_ReadWord(void); extern void Video_Color2_ReadWord(void); extern void Video_Color3_ReadWord(void); extern void Video_Color4_ReadWord(void); extern void Video_Color5_ReadWord(void); extern void Video_Color6_ReadWord(void); extern void Video_Color7_ReadWord(void); extern void Video_Color8_ReadWord(void); extern void Video_Color9_ReadWord(void); extern void Video_Color10_ReadWord(void); extern void Video_Color11_ReadWord(void); extern void Video_Color12_ReadWord(void); extern void Video_Color13_ReadWord(void); extern void Video_Color14_ReadWord(void); extern void Video_Color15_ReadWord(void); extern void Video_ResGlueShifter_WriteByte(void); extern void Video_ResShifter_WriteByte(void); extern void Video_HorScroll_Read_8264(void); extern void Video_HorScroll_Read_8265(void); extern void Video_HorScroll_Write_8264(void); extern void Video_HorScroll_Write_8265(void); extern void Video_HorScroll_Write(void); extern void Video_TTShiftMode_WriteWord(void); extern void Video_TTColorRegs_Write(void); extern void Video_TTColorRegs_STRegWrite(void); extern void Video_Info(FILE *fp, uint32_t dummy); #endif /* HATARI_VIDEO_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/vs-fix.h000077500000000000000000000027551504763705000245370ustar00rootroot00000000000000/* * Hatari - Fix compilation with Visual Studio * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #ifndef VS_FIX_H #define VS_FIX_H #define WIN32_LEAN_AND_MEAN #include /* For getting SDK version */ #include /* For rmdir(), mkdir(), etc. */ // Stop Visual Studio complaining #pragma warning (disable:4018) /* signed / unsigned mismatch */ #pragma warning (disable:4049) /* compiler limit, end of line numbering */ #pragma warning (disable:4101) /* unreferenced local variable */ #pragma warning (disable:4102) /* ignore unused label warning */ #pragma warning (disable:4146) /* unary minus operator applied to unsigned type */ #pragma warning (disable:4244) /* conversion with potential data loss */ #pragma warning (disable:4267) /* conversion from 'size_t' to 'int' */ #pragma warning (disable:4761) /* integral size mismatch in argument */ #pragma warning (disable:4800) /* Performance Warning on Conversion of bool to int */ #pragma warning (disable:4996) /* Unsafe functions */ #ifndef NTDDI_WIN10_19H1 /* this makes compilation error in newer SDK's and obviously is not needed anymore (at least from SDK NTDDI_WIN10_19H1, but maybe even from older - not tested) */ #undef _DEBUG /* Visual Studio is doing some macro redefinition otherwise */ #endif typedef unsigned short mode_t; #define strncasecmp _strnicmp #define strcasecmp _stricmp #define strtok_r strtok_s #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/wavFormat.h000066400000000000000000000006701504763705000252600ustar00rootroot00000000000000/* Hatari - wavFormat.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_WAVFORMAT_H #define HATARI_WAVFORMAT_H extern bool bRecordingWav; extern bool WAVFormat_OpenFile(char *pszWavFileName); extern void WAVFormat_CloseFile(void); extern void WAVFormat_Update(int16_t pSamples[][2], int Index, int Length); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/xbios.h000066400000000000000000000005411504763705000244330ustar00rootroot00000000000000/* Hatari - xbios.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_XBIOS_H #define HATARI_XBIOS_H extern bool XBios(void); extern void XBios_Info(FILE *fp, uint32_t dummy); extern void XBios_EnableCommands(bool enable); #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ym2149_fixed_vol.h000066400000000000000000000741441504763705000263250ustar00rootroot00000000000000/** * Sixteen level by Three voice YM2149 volume_table[C][B][A] * This file is #include'd in sound.c * Data measured by Paulo Simoes. Copyright 2012 Paulo Simoes. * * Note that Hatari only uses the ST Replay table because Hatari * indexes 'one less' into the Replay table when Quartet values * are needed. This allows any voice to be Quartet or Replay, * independent of the others. Geometric mean interpolation is * used to expand this 16*16*16 table to 32*32*32. */ { { { 0, 120, 251, 385, 598, 840, 1258, 1725, 2536, 3483, 5151, 7054, 10367, 14634, 22931, 34469 }, { 120, 283, 419, 556, 771, 1009, 1433, 1894, 2702, 3645, 5310, 7207, 10508, 14757, 23015, 34480 }, { 251, 419, 553, 692, 903, 1147, 1565, 2026, 2833, 3775, 5439, 7325, 10619, 14857, 23082, 34493 }, { 385, 556, 692, 828, 1038, 1286, 1704, 2164, 2967, 3906, 5564, 7450, 10733, 14958, 23156, 34509 }, { 598, 771, 903, 1038, 1261, 1502, 1925, 2382, 3176, 4113, 5764, 7649, 10915, 15123, 23272, 34538 }, { 840, 1009, 1147, 1286, 1502, 1747, 2167, 2626, 3417, 4348, 5985, 7851, 11109, 15302, 23400, 34570 }, { 1258, 1433, 1565, 1704, 1925, 2167, 2572, 3019, 3812, 4743, 6367, 8205, 11457, 15616, 23639, 34646 }, { 1725, 1894, 2026, 2164, 2382, 2626, 3019, 3487, 4266, 5173, 6792, 8517, 11852, 15979, 23925, 34754 }, { 2536, 2702, 2833, 2967, 3176, 3417, 3812, 4266, 5048, 5947, 7542, 9227, 12543, 16613, 24440, 34992 }, { 3483, 3645, 3775, 3906, 4113, 4348, 4743, 5173, 5947, 6847, 8353, 10089, 13357, 17288, 25084, 35355 }, { 5151, 5310, 5439, 5564, 5764, 5985, 6367, 6792, 7542, 8353, 9780, 11549, 14769, 18591, 26237, 36119 }, { 7054, 7207, 7325, 7450, 7649, 7851, 8205, 8517, 9227, 10089, 11549, 13292, 16452, 20206, 27628, 37143 }, { 10367, 10508, 10619, 10733, 10915, 11109, 11457, 11852, 12543, 13357, 14769, 16452, 19431, 23228, 30481, 39499 }, { 14634, 14757, 14857, 14958, 15123, 15302, 15616, 15979, 16613, 17288, 18591, 20206, 23228, 26871, 34097, 42635 }, { 22931, 23015, 23082, 23156, 23272, 23400, 23639, 23925, 24440, 25084, 26237, 27628, 30481, 34097, 40524, 48336 }, { 34469, 34480, 34493, 34509, 34538, 34570, 34646, 34754, 34992, 35355, 36119, 37143, 39499, 42635, 48336, 55377 } }, { { 120, 283, 419, 556, 771, 1009, 1433, 1894, 2702, 3645, 5310, 7207, 10508, 14757, 23015, 34480 }, { 283, 453, 589, 723, 941, 1179, 1601, 2059, 2864, 3811, 5472, 7358, 10646, 14884, 23101, 34500 }, { 419, 589, 727, 863, 1077, 1316, 1737, 2194, 2998, 3938, 5599, 7478, 10755, 14980, 23168, 34509 }, { 556, 723, 863, 997, 1212, 1453, 1874, 2331, 3130, 4067, 5726, 7602, 10872, 15084, 23243, 34526 }, { 771, 941, 1077, 1212, 1427, 1674, 2089, 2551, 3345, 4273, 5916, 7792, 11056, 15245, 23354, 34556 }, { 1009, 1179, 1316, 1453, 1674, 1915, 2333, 2788, 3579, 4509, 6142, 7993, 11249, 15426, 23486, 34585 }, { 1433, 1601, 1737, 1874, 2089, 2333, 2735, 3188, 3976, 4898, 6516, 8315, 11594, 15735, 23718, 34662 }, { 1894, 2059, 2194, 2331, 2551, 2788, 3188, 3649, 4423, 5330, 6948, 8656, 11984, 16094, 24004, 34771 }, { 2702, 2864, 2998, 3130, 3345, 3579, 3976, 4423, 5202, 6104, 7684, 9368, 12668, 16724, 24512, 35007 }, { 3645, 3811, 3938, 4067, 4273, 4509, 4898, 5330, 6104, 6999, 8449, 10221, 13483, 17379, 25149, 35366 }, { 5310, 5472, 5599, 5726, 5916, 6142, 6516, 6948, 7684, 8449, 9917, 11680, 14885, 18689, 26297, 36126 }, { 7207, 7358, 7478, 7602, 7792, 7993, 8315, 8656, 9368, 10221, 11680, 13417, 16558, 20295, 27677, 37144 }, { 10508, 10646, 10755, 10872, 11056, 11249, 11594, 11984, 12668, 13483, 14885, 16558, 19523, 23300, 30508, 39500 }, { 14757, 14884, 14980, 15084, 15245, 15426, 15735, 16094, 16724, 17379, 18689, 20295, 23300, 26918, 34108, 42636 }, { 23015, 23101, 23168, 23243, 23354, 23486, 23718, 24004, 24512, 25149, 26297, 27677, 30508, 34108, 40527, 48337 }, { 34480, 34500, 34509, 34526, 34556, 34585, 34662, 34771, 35007, 35366, 36126, 37144, 39500, 42636, 48337, 55377 } }, { { 251, 419, 553, 692, 903, 1147, 1565, 2026, 2833, 3775, 5439, 7325, 10619, 14857, 23082, 34493 }, { 419, 589, 727, 863, 1077, 1316, 1737, 2194, 2998, 3938, 5599, 7478, 10755, 14980, 23168, 34509 }, { 553, 727, 863, 999, 1213, 1454, 1873, 2329, 3129, 4071, 5727, 7600, 10871, 15085, 23243, 34524 }, { 692, 863, 999, 1135, 1351, 1592, 2011, 2466, 3264, 4199, 5849, 7724, 10990, 15187, 23315, 34541 }, { 903, 1077, 1213, 1351, 1567, 1811, 2230, 2689, 3476, 4402, 6041, 7910, 11165, 15342, 23425, 34568 }, { 1147, 1316, 1454, 1592, 1811, 2054, 2469, 2924, 3716, 4640, 6265, 8109, 11365, 15526, 23554, 34601 }, { 1565, 1737, 1873, 2011, 2230, 2469, 2873, 3323, 4105, 5023, 6639, 8395, 11700, 15831, 23786, 34675 }, { 2026, 2194, 2329, 2466, 2689, 2924, 3323, 3777, 4553, 5456, 7069, 8770, 12097, 16188, 24070, 34781 }, { 2833, 2998, 3129, 3264, 3476, 3716, 4105, 4553, 5330, 6231, 7804, 9480, 12776, 16820, 24574, 35020 }, { 3775, 3938, 4071, 4199, 4402, 4640, 5023, 5456, 6231, 7115, 8554, 10329, 13586, 17460, 25207, 35381 }, { 5439, 5599, 5727, 5849, 6041, 6265, 6639, 7069, 7804, 8554, 10029, 11784, 14980, 18770, 26344, 36134 }, { 7325, 7478, 7600, 7724, 7910, 8109, 8395, 8770, 9480, 10329, 11784, 13519, 16643, 20370, 27719, 37151 }, { 10619, 10755, 10871, 10990, 11165, 11365, 11700, 12097, 12776, 13586, 14980, 16643, 19601, 23359, 30533, 39501 }, { 14857, 14980, 15085, 15187, 15342, 15526, 15831, 16188, 16820, 17460, 18770, 20370, 23359, 26957, 34125, 42637 }, { 23082, 23168, 23243, 23315, 23425, 23554, 23786, 24070, 24574, 25207, 26344, 27719, 30533, 34125, 40529, 48338 }, { 34493, 34509, 34524, 34541, 34568, 34601, 34675, 34781, 35020, 35381, 36134, 37151, 39501, 42637, 48338, 55378 } }, { { 385, 556, 692, 828, 1038, 1286, 1704, 2164, 2967, 3906, 5564, 7450, 10733, 14958, 23156, 34509 }, { 556, 723, 863, 997, 1212, 1453, 1874, 2331, 3130, 4067, 5726, 7602, 10872, 15084, 23243, 34526 }, { 692, 863, 999, 1135, 1351, 1592, 2011, 2466, 3264, 4199, 5849, 7724, 10990, 15187, 23315, 34541 }, { 828, 997, 1135, 1276, 1482, 1731, 2146, 2605, 3397, 4324, 5970, 7841, 11105, 15285, 23389, 34561 }, { 1038, 1212, 1351, 1482, 1703, 1945, 2363, 2819, 3606, 4535, 6167, 8026, 11271, 15445, 23493, 34587 }, { 1286, 1453, 1592, 1731, 1945, 2185, 2605, 3053, 3844, 4769, 6395, 8222, 11476, 15625, 23625, 34620 }, { 1704, 1874, 2011, 2146, 2363, 2605, 3004, 3452, 4232, 5154, 6762, 8488, 11803, 15932, 23851, 34692 }, { 2164, 2331, 2466, 2605, 2819, 3053, 3452, 3909, 4682, 5580, 7190, 8888, 12204, 16280, 24134, 34798 }, { 2967, 3130, 3264, 3397, 3606, 3844, 4232, 4682, 5456, 6350, 7915, 9593, 12882, 16907, 24637, 35034 }, { 3906, 4067, 4199, 4324, 4535, 4769, 5154, 5580, 6350, 7235, 8667, 10439, 13680, 17548, 25267, 35392 }, { 5564, 5726, 5849, 5970, 6167, 6395, 6762, 7190, 7915, 8667, 10135, 11887, 15073, 18851, 26391, 36142 }, { 7450, 7602, 7724, 7841, 8026, 8222, 8488, 8888, 9593, 10439, 11887, 13620, 16729, 20446, 27762, 37157 }, { 10733, 10872, 10990, 11105, 11271, 11476, 11803, 12204, 12882, 13680, 15073, 16729, 19676, 23419, 30559, 39503 }, { 14958, 15084, 15187, 15285, 15445, 15625, 15932, 16280, 16907, 17548, 18851, 20446, 23419, 26998, 34139, 42639 }, { 23156, 23243, 23315, 23389, 23493, 23625, 23851, 24134, 24637, 25267, 26391, 27762, 30559, 34139, 40534, 48339 }, { 34509, 34526, 34541, 34561, 34587, 34620, 34692, 34798, 35034, 35392, 36142, 37157, 39503, 42639, 48339, 55378 } }, { { 598, 771, 903, 1038, 1261, 1502, 1925, 2382, 3176, 4113, 5764, 7649, 10915, 15123, 23272, 34538 }, { 771, 941, 1077, 1212, 1427, 1674, 2089, 2551, 3345, 4273, 5916, 7792, 11056, 15245, 23354, 34556 }, { 903, 1077, 1213, 1351, 1567, 1811, 2230, 2689, 3476, 4402, 6041, 7910, 11165, 15342, 23425, 34568 }, { 1038, 1212, 1351, 1482, 1703, 1945, 2363, 2819, 3606, 4535, 6167, 8026, 11271, 15445, 23493, 34587 }, { 1261, 1427, 1567, 1703, 1916, 2162, 2575, 3032, 3817, 4738, 6364, 8201, 11449, 15605, 23608, 34613 }, { 1502, 1674, 1811, 1945, 2162, 2399, 2817, 3263, 4051, 4974, 6588, 8362, 11657, 15784, 23741, 34649 }, { 1925, 2089, 2230, 2363, 2575, 2817, 3212, 3664, 4440, 5348, 6954, 8660, 11979, 16087, 23965, 34720 }, { 2382, 2551, 2689, 2819, 3032, 3263, 3664, 4111, 4880, 5781, 7385, 9072, 12371, 16431, 24240, 34823 }, { 3176, 3345, 3476, 3606, 3817, 4051, 4440, 4880, 5653, 6548, 8104, 9776, 13045, 17045, 24740, 35060 }, { 4113, 4273, 4402, 4535, 4738, 4974, 5348, 5781, 6548, 7426, 8845, 10614, 13842, 17683, 25362, 35414 }, { 5764, 5916, 6041, 6167, 6364, 6588, 6954, 7385, 8104, 8845, 10314, 12061, 15219, 18983, 26463, 36154 }, { 7649, 7792, 7910, 8026, 8201, 8362, 8660, 9072, 9776, 10614, 12061, 13783, 16862, 20567, 27833, 37166 }, { 10915, 11056, 11165, 11271, 11449, 11657, 11979, 12371, 13045, 13842, 15219, 16862, 19799, 23517, 30606, 39513 }, { 15123, 15245, 15342, 15445, 15605, 15784, 16087, 16431, 17045, 17683, 18983, 20567, 23517, 27066, 34164, 42641 }, { 23272, 23354, 23425, 23493, 23608, 23741, 23965, 24240, 24740, 25362, 26463, 27833, 30606, 34164, 40537, 48340 }, { 34538, 34556, 34568, 34587, 34613, 34649, 34720, 34823, 35060, 35414, 36154, 37166, 39513, 42641, 48340, 55379 } }, { { 840, 1009, 1147, 1286, 1502, 1747, 2167, 2626, 3417, 4348, 5985, 7851, 11109, 15302, 23400, 34570 }, { 1009, 1179, 1316, 1453, 1674, 1915, 2333, 2788, 3579, 4509, 6142, 7993, 11249, 15426, 23486, 34585 }, { 1147, 1316, 1454, 1592, 1811, 2054, 2469, 2924, 3716, 4640, 6265, 8109, 11365, 15526, 23554, 34601 }, { 1286, 1453, 1592, 1731, 1945, 2185, 2605, 3053, 3844, 4769, 6395, 8222, 11476, 15625, 23625, 34620 }, { 1502, 1674, 1811, 1945, 2162, 2399, 2817, 3263, 4051, 4974, 6588, 8362, 11657, 15784, 23741, 34649 }, { 1747, 1915, 2054, 2185, 2399, 2640, 3054, 3501, 4282, 5198, 6806, 8530, 11855, 15961, 23871, 34688 }, { 2167, 2333, 2469, 2605, 2817, 3054, 3449, 3898, 4667, 5571, 7172, 8864, 12172, 16261, 24094, 34758 }, { 2626, 2788, 2924, 3053, 3263, 3501, 3898, 4342, 5110, 6002, 7594, 9274, 12564, 16600, 24361, 34858 }, { 3417, 3579, 3716, 3844, 4051, 4282, 4667, 5110, 5878, 6764, 8289, 9978, 13233, 17183, 24857, 35090 }, { 4348, 4509, 4640, 4769, 4974, 5198, 5571, 6002, 6764, 7634, 9046, 10809, 14023, 17843, 25474, 35441 }, { 5985, 6142, 6265, 6395, 6588, 6806, 7172, 7594, 8289, 9046, 10510, 12249, 15389, 19129, 26571, 36174 }, { 7851, 7993, 8109, 8222, 8362, 8530, 8864, 9274, 9978, 10809, 12249, 13962, 17018, 20703, 27915, 37183 }, { 11109, 11249, 11365, 11476, 11657, 11855, 12172, 12564, 13233, 14023, 15389, 17018, 19935, 23632, 30665, 39517 }, { 15302, 15426, 15526, 15625, 15784, 15961, 16261, 16600, 17183, 17843, 19129, 20703, 23632, 27149, 34193, 42643 }, { 23400, 23486, 23554, 23625, 23741, 23871, 24094, 24361, 24857, 25474, 26571, 27915, 30665, 34193, 40540, 48341 }, { 34570, 34585, 34601, 34620, 34649, 34688, 34758, 34858, 35090, 35441, 36174, 37183, 39517, 42643, 48341, 55379 } }, { { 1258, 1433, 1565, 1704, 1925, 2167, 2572, 3019, 3812, 4743, 6367, 8205, 11457, 15616, 23639, 34646 }, { 1433, 1601, 1737, 1874, 2089, 2333, 2735, 3188, 3976, 4898, 6516, 8315, 11594, 15735, 23718, 34662 }, { 1565, 1737, 1873, 2011, 2230, 2469, 2873, 3323, 4105, 5023, 6639, 8395, 11700, 15831, 23786, 34675 }, { 1704, 1874, 2011, 2146, 2363, 2605, 3004, 3452, 4232, 5154, 6762, 8488, 11803, 15932, 23851, 34692 }, { 1925, 2089, 2230, 2363, 2575, 2817, 3212, 3664, 4440, 5348, 6954, 8660, 11979, 16087, 23965, 34720 }, { 2167, 2333, 2469, 2605, 2817, 3054, 3449, 3898, 4667, 5571, 7172, 8864, 12172, 16261, 24094, 34758 }, { 2572, 2735, 2873, 3004, 3212, 3449, 3854, 4302, 5067, 5961, 7550, 9224, 12519, 16573, 24328, 34836 }, { 3019, 3188, 3323, 3452, 3664, 3898, 4302, 4742, 5503, 6387, 7962, 9634, 12895, 16899, 24587, 34932 }, { 3812, 3976, 4105, 4232, 4440, 4667, 5067, 5503, 6267, 7143, 8571, 10335, 13563, 17414, 25071, 35162 }, { 4743, 4898, 5023, 5154, 5348, 5571, 5961, 6387, 7143, 8004, 9406, 11158, 14338, 18124, 25673, 35504 }, { 6367, 6516, 6639, 6762, 6954, 7172, 7550, 7962, 8571, 9406, 10856, 12580, 15691, 19396, 26692, 36217 }, { 8205, 8315, 8395, 8488, 8660, 8864, 9224, 9634, 10335, 11158, 12580, 14275, 17244, 20952, 28071, 37216 }, { 11457, 11594, 11700, 11803, 11979, 12172, 12519, 12895, 13563, 14338, 15691, 17244, 20186, 23839, 30781, 39535 }, { 15616, 15735, 15831, 15932, 16087, 16261, 16573, 16899, 17414, 18124, 19396, 20952, 23839, 27307, 34258, 42647 }, { 23639, 23718, 23786, 23851, 23965, 24094, 24328, 24587, 25071, 25673, 26692, 28071, 30781, 34258, 40553, 48342 }, { 34646, 34662, 34675, 34692, 34720, 34758, 34836, 34932, 35162, 35504, 36217, 37216, 39535, 42647, 48342, 55380 } }, { { 1725, 1894, 2026, 2164, 2382, 2626, 3019, 3487, 4266, 5173, 6792, 8517, 11852, 15979, 23925, 34754 }, { 1894, 2059, 2194, 2331, 2551, 2788, 3188, 3649, 4423, 5330, 6948, 8656, 11984, 16094, 24004, 34771 }, { 2026, 2194, 2329, 2466, 2689, 2924, 3323, 3777, 4553, 5456, 7069, 8770, 12097, 16188, 24070, 34781 }, { 2164, 2331, 2466, 2605, 2819, 3053, 3452, 3909, 4682, 5580, 7190, 8888, 12204, 16280, 24134, 34798 }, { 2382, 2551, 2689, 2819, 3032, 3263, 3664, 4111, 4880, 5781, 7385, 9072, 12371, 16431, 24240, 34823 }, { 2626, 2788, 2924, 3053, 3263, 3501, 3898, 4342, 5110, 6002, 7594, 9274, 12564, 16600, 24361, 34858 }, { 3019, 3188, 3323, 3452, 3664, 3898, 4302, 4742, 5503, 6387, 7962, 9634, 12895, 16899, 24587, 34932 }, { 3487, 3649, 3777, 3909, 4111, 4342, 4742, 5177, 5934, 6818, 8332, 10028, 13268, 17198, 24845, 35038 }, { 4266, 4423, 4553, 4682, 4880, 5110, 5503, 5934, 6687, 7559, 8964, 10727, 13927, 17732, 25320, 35258 }, { 5173, 5330, 5456, 5580, 5781, 6002, 6387, 6818, 7559, 8353, 9797, 11537, 14695, 18443, 25905, 35591 }, { 6792, 6948, 7069, 7190, 7385, 7594, 7962, 8332, 8964, 9797, 11242, 12949, 16028, 19695, 26891, 36270 }, { 8517, 8656, 8770, 8888, 9072, 9274, 9634, 10028, 10727, 11537, 12949, 14625, 17520, 21231, 28260, 37278 }, { 11852, 11984, 12097, 12204, 12371, 12564, 12895, 13268, 13927, 14695, 16028, 17520, 20469, 24080, 30926, 39567 }, { 15979, 16094, 16188, 16280, 16431, 16600, 16899, 17198, 17732, 18443, 19695, 21231, 24080, 27498, 34345, 42662 }, { 23925, 24004, 24070, 24134, 24240, 24361, 24587, 24845, 25320, 25905, 26891, 28260, 30926, 34345, 40575, 48344 }, { 34754, 34771, 34781, 34798, 34823, 34858, 34932, 35038, 35258, 35591, 36270, 37278, 39567, 42662, 48344, 55381 } }, { { 2536, 2702, 2833, 2967, 3176, 3417, 3812, 4266, 5048, 5947, 7542, 9227, 12543, 16613, 24440, 34992 }, { 2702, 2864, 2998, 3130, 3345, 3579, 3976, 4423, 5202, 6104, 7684, 9368, 12668, 16724, 24512, 35007 }, { 2833, 2998, 3129, 3264, 3476, 3716, 4105, 4553, 5330, 6231, 7804, 9480, 12776, 16820, 24574, 35020 }, { 2967, 3130, 3264, 3397, 3606, 3844, 4232, 4682, 5456, 6350, 7915, 9593, 12882, 16907, 24637, 35034 }, { 3176, 3345, 3476, 3606, 3817, 4051, 4440, 4880, 5653, 6548, 8104, 9776, 13045, 17045, 24740, 35060 }, { 3417, 3579, 3716, 3844, 4051, 4282, 4667, 5110, 5878, 6764, 8289, 9978, 13233, 17183, 24857, 35090 }, { 3812, 3976, 4105, 4232, 4440, 4667, 5067, 5503, 6267, 7143, 8571, 10335, 13563, 17414, 25071, 35162 }, { 4266, 4423, 4553, 4682, 4880, 5110, 5503, 5934, 6687, 7559, 8964, 10727, 13927, 17732, 25320, 35258 }, { 5048, 5202, 5330, 5456, 5653, 5878, 6267, 6687, 7432, 8260, 9665, 11412, 14575, 18313, 25781, 35482 }, { 5947, 6104, 6231, 6350, 6548, 6764, 7143, 7559, 8260, 8997, 10490, 12209, 15324, 19011, 26331, 35795 }, { 7542, 7684, 7804, 7915, 8104, 8289, 8571, 8964, 9665, 10490, 11913, 13590, 16629, 20233, 27289, 36423 }, { 9227, 9368, 9480, 9593, 9776, 9978, 10335, 10727, 11412, 12209, 13590, 15237, 18080, 21736, 28624, 37418 }, { 12543, 12668, 12776, 12882, 13045, 13233, 13563, 13927, 14575, 15324, 16629, 18080, 20977, 24523, 31218, 39649 }, { 16613, 16724, 16820, 16907, 17045, 17183, 17414, 17732, 18313, 19011, 20233, 21736, 24523, 27863, 34544, 42695 }, { 24440, 24512, 24574, 24637, 24740, 24857, 25071, 25320, 25781, 26331, 27289, 28624, 31218, 34544, 40634, 48348 }, { 34992, 35007, 35020, 35034, 35060, 35090, 35162, 35258, 35482, 35795, 36423, 37418, 39649, 42695, 48348, 55382 } }, { { 3483, 3645, 3775, 3906, 4113, 4348, 4743, 5173, 5947, 6847, 8353, 10089, 13357, 17288, 25084, 35355 }, { 3645, 3811, 3938, 4067, 4273, 4509, 4898, 5330, 6104, 6999, 8449, 10221, 13483, 17379, 25149, 35366 }, { 3775, 3938, 4071, 4199, 4402, 4640, 5023, 5456, 6231, 7115, 8554, 10329, 13586, 17460, 25207, 35381 }, { 3906, 4067, 4199, 4324, 4535, 4769, 5154, 5580, 6350, 7235, 8667, 10439, 13680, 17548, 25267, 35392 }, { 4113, 4273, 4402, 4535, 4738, 4974, 5348, 5781, 6548, 7426, 8845, 10614, 13842, 17683, 25362, 35414 }, { 4348, 4509, 4640, 4769, 4974, 5198, 5571, 6002, 6764, 7634, 9046, 10809, 14023, 17843, 25474, 35441 }, { 4743, 4898, 5023, 5154, 5348, 5571, 5961, 6387, 7143, 8004, 9406, 11158, 14338, 18124, 25673, 35504 }, { 5173, 5330, 5456, 5580, 5781, 6002, 6387, 6818, 7559, 8353, 9797, 11537, 14695, 18443, 25905, 35591 }, { 5947, 6104, 6231, 6350, 6548, 6764, 7143, 7559, 8260, 8997, 10490, 12209, 15324, 19011, 26331, 35795 }, { 6847, 6999, 7115, 7235, 7426, 7634, 8004, 8353, 8997, 9829, 11306, 12998, 16067, 19696, 26808, 36099 }, { 8353, 8449, 8554, 8667, 8845, 9046, 9406, 9797, 10490, 11306, 12706, 14348, 17258, 20884, 27805, 36672 }, { 10089, 10221, 10329, 10439, 10614, 10809, 11158, 11537, 12209, 12998, 14348, 15967, 18756, 22352, 29098, 37647 }, { 13357, 13483, 13586, 13680, 13842, 14023, 14338, 14695, 15324, 16067, 17258, 18756, 21599, 25072, 31613, 39810 }, { 17288, 17379, 17460, 17548, 17683, 17843, 18124, 18443, 19011, 19696, 20884, 22352, 25072, 28332, 34841, 42772 }, { 25084, 25149, 25207, 25267, 25362, 25474, 25673, 25905, 26331, 26808, 27805, 29098, 31613, 34841, 40758, 48353 }, { 35355, 35366, 35381, 35392, 35414, 35441, 35504, 35591, 35795, 36099, 36672, 37647, 39810, 42772, 48353, 55383 } }, { { 5151, 5310, 5439, 5564, 5764, 5985, 6367, 6792, 7542, 8353, 9780, 11549, 14769, 18591, 26237, 36119 }, { 5310, 5472, 5599, 5726, 5916, 6142, 6516, 6948, 7684, 8449, 9917, 11680, 14885, 18689, 26297, 36126 }, { 5439, 5599, 5727, 5849, 6041, 6265, 6639, 7069, 7804, 8554, 10029, 11784, 14980, 18770, 26344, 36134 }, { 5564, 5726, 5849, 5970, 6167, 6395, 6762, 7190, 7915, 8667, 10135, 11887, 15073, 18851, 26391, 36142 }, { 5764, 5916, 6041, 6167, 6364, 6588, 6954, 7385, 8104, 8845, 10314, 12061, 15219, 18983, 26463, 36154 }, { 5985, 6142, 6265, 6395, 6588, 6806, 7172, 7594, 8289, 9046, 10510, 12249, 15389, 19129, 26571, 36174 }, { 6367, 6516, 6639, 6762, 6954, 7172, 7550, 7962, 8571, 9406, 10856, 12580, 15691, 19396, 26692, 36217 }, { 6792, 6948, 7069, 7190, 7385, 7594, 7962, 8332, 8964, 9797, 11242, 12949, 16028, 19695, 26891, 36270 }, { 7542, 7684, 7804, 7915, 8104, 8289, 8571, 8964, 9665, 10490, 11913, 13590, 16629, 20233, 27289, 36423 }, { 8353, 8449, 8554, 8667, 8845, 9046, 9406, 9797, 10490, 11306, 12706, 14348, 17258, 20884, 27805, 36672 }, { 9780, 9917, 10029, 10135, 10314, 10510, 10856, 11242, 11913, 12706, 14111, 15703, 18497, 22076, 28804, 37321 }, { 11549, 11680, 11784, 11887, 12061, 12249, 12580, 12949, 13590, 14348, 15703, 17212, 19984, 23485, 30030, 38230 }, { 14769, 14885, 14980, 15073, 15219, 15389, 15691, 16028, 16629, 17258, 18497, 19984, 22737, 26094, 32422, 40256 }, { 18591, 18689, 18770, 18851, 18983, 19129, 19396, 19695, 20233, 20884, 22076, 23485, 26094, 29241, 35499, 43067 }, { 26237, 26297, 26344, 26391, 26463, 26571, 26692, 26891, 27289, 27805, 28804, 30030, 32422, 35499, 41131, 48449 }, { 36119, 36126, 36134, 36142, 36154, 36174, 36217, 36270, 36423, 36672, 37321, 38230, 40256, 43067, 48449, 55385 } }, { { 7054, 7207, 7325, 7450, 7649, 7851, 8205, 8517, 9227, 10089, 11549, 13292, 16452, 20206, 27628, 37143 }, { 7207, 7358, 7478, 7602, 7792, 7993, 8315, 8656, 9368, 10221, 11680, 13417, 16558, 20295, 27677, 37144 }, { 7325, 7478, 7600, 7724, 7910, 8109, 8395, 8770, 9480, 10329, 11784, 13519, 16643, 20370, 27719, 37151 }, { 7450, 7602, 7724, 7841, 8026, 8222, 8488, 8888, 9593, 10439, 11887, 13620, 16729, 20446, 27762, 37157 }, { 7649, 7792, 7910, 8026, 8201, 8362, 8660, 9072, 9776, 10614, 12061, 13783, 16862, 20567, 27833, 37166 }, { 7851, 7993, 8109, 8222, 8362, 8530, 8864, 9274, 9978, 10809, 12249, 13962, 17018, 20703, 27915, 37183 }, { 8205, 8315, 8395, 8488, 8660, 8864, 9224, 9634, 10335, 11158, 12580, 14275, 17244, 20952, 28071, 37216 }, { 8517, 8656, 8770, 8888, 9072, 9274, 9634, 10028, 10727, 11537, 12949, 14625, 17520, 21231, 28260, 37278 }, { 9227, 9368, 9480, 9593, 9776, 9978, 10335, 10727, 11412, 12209, 13590, 15237, 18080, 21736, 28624, 37418 }, { 10089, 10221, 10329, 10439, 10614, 10809, 11158, 11537, 12209, 12998, 14348, 15967, 18756, 22352, 29098, 37647 }, { 11549, 11680, 11784, 11887, 12061, 12249, 12580, 12949, 13590, 14348, 15703, 17212, 19984, 23485, 30030, 38230 }, { 13292, 13417, 13519, 13620, 13783, 13962, 14275, 14625, 15237, 15967, 17212, 18672, 21436, 24840, 31211, 39115 }, { 16452, 16558, 16643, 16729, 16862, 17018, 17244, 17520, 18080, 18756, 19984, 21436, 24097, 27224, 33479, 40993 }, { 20206, 20295, 20370, 20446, 20567, 20703, 20952, 21231, 21736, 22352, 23485, 24840, 27224, 30386, 36330, 43638 }, { 27628, 27677, 27719, 27762, 27833, 27915, 28071, 28260, 28624, 29098, 30030, 31211, 33479, 36330, 41775, 48724 }, { 37143, 37144, 37151, 37157, 37166, 37183, 37216, 37278, 37418, 37647, 38230, 39115, 40993, 43638, 48724, 55407 } }, { { 10367, 10508, 10619, 10733, 10915, 11109, 11457, 11852, 12543, 13357, 14769, 16452, 19431, 23228, 30481, 39499 }, { 10508, 10646, 10755, 10872, 11056, 11249, 11594, 11984, 12668, 13483, 14885, 16558, 19523, 23300, 30508, 39500 }, { 10619, 10755, 10871, 10990, 11165, 11365, 11700, 12097, 12776, 13586, 14980, 16643, 19601, 23359, 30533, 39501 }, { 10733, 10872, 10990, 11105, 11271, 11476, 11803, 12204, 12882, 13680, 15073, 16729, 19676, 23419, 30559, 39503 }, { 10915, 11056, 11165, 11271, 11449, 11657, 11979, 12371, 13045, 13842, 15219, 16862, 19799, 23517, 30606, 39513 }, { 11109, 11249, 11365, 11476, 11657, 11855, 12172, 12564, 13233, 14023, 15389, 17018, 19935, 23632, 30665, 39517 }, { 11457, 11594, 11700, 11803, 11979, 12172, 12519, 12895, 13563, 14338, 15691, 17244, 20186, 23839, 30781, 39535 }, { 11852, 11984, 12097, 12204, 12371, 12564, 12895, 13268, 13927, 14695, 16028, 17520, 20469, 24080, 30926, 39567 }, { 12543, 12668, 12776, 12882, 13045, 13233, 13563, 13927, 14575, 15324, 16629, 18080, 20977, 24523, 31218, 39649 }, { 13357, 13483, 13586, 13680, 13842, 14023, 14338, 14695, 15324, 16067, 17258, 18756, 21599, 25072, 31613, 39810 }, { 14769, 14885, 14980, 15073, 15219, 15389, 15691, 16028, 16629, 17258, 18497, 19984, 22737, 26094, 32422, 40256 }, { 16452, 16558, 16643, 16729, 16862, 17018, 17244, 17520, 18080, 18756, 19984, 21436, 24097, 27224, 33479, 40993 }, { 19431, 19523, 19601, 19676, 19799, 19935, 20186, 20469, 20977, 21599, 22737, 24097, 26557, 29604, 35584, 42710 }, { 23228, 23300, 23359, 23419, 23517, 23632, 23839, 24080, 24523, 25072, 26094, 27224, 29604, 32602, 38145, 45081 }, { 30481, 30508, 30533, 30559, 30606, 30665, 30781, 30926, 31218, 31613, 32422, 33479, 35584, 38145, 43319, 49733 }, { 39499, 39500, 39501, 39503, 39513, 39517, 39535, 39567, 39649, 39810, 40256, 40993, 42710, 45081, 49733, 55763 } }, { { 14634, 14757, 14857, 14958, 15123, 15302, 15616, 15979, 16613, 17288, 18591, 20206, 23228, 26871, 34097, 42635 }, { 14757, 14884, 14980, 15084, 15245, 15426, 15735, 16094, 16724, 17379, 18689, 20295, 23300, 26918, 34108, 42636 }, { 14857, 14980, 15085, 15187, 15342, 15526, 15831, 16188, 16820, 17460, 18770, 20370, 23359, 26957, 34125, 42637 }, { 14958, 15084, 15187, 15285, 15445, 15625, 15932, 16280, 16907, 17548, 18851, 20446, 23419, 26998, 34139, 42639 }, { 15123, 15245, 15342, 15445, 15605, 15784, 16087, 16431, 17045, 17683, 18983, 20567, 23517, 27066, 34164, 42641 }, { 15302, 15426, 15526, 15625, 15784, 15961, 16261, 16600, 17183, 17843, 19129, 20703, 23632, 27149, 34193, 42643 }, { 15616, 15735, 15831, 15932, 16087, 16261, 16573, 16899, 17414, 18124, 19396, 20952, 23839, 27307, 34258, 42647 }, { 15979, 16094, 16188, 16280, 16431, 16600, 16899, 17198, 17732, 18443, 19695, 21231, 24080, 27498, 34345, 42662 }, { 16613, 16724, 16820, 16907, 17045, 17183, 17414, 17732, 18313, 19011, 20233, 21736, 24523, 27863, 34544, 42695 }, { 17288, 17379, 17460, 17548, 17683, 17843, 18124, 18443, 19011, 19696, 20884, 22352, 25072, 28332, 34841, 42772 }, { 18591, 18689, 18770, 18851, 18983, 19129, 19396, 19695, 20233, 20884, 22076, 23485, 26094, 29241, 35499, 43067 }, { 20206, 20295, 20370, 20446, 20567, 20703, 20952, 21231, 21736, 22352, 23485, 24840, 27224, 30386, 36330, 43638 }, { 23228, 23300, 23359, 23419, 23517, 23632, 23839, 24080, 24523, 25072, 26094, 27224, 29604, 32602, 38145, 45081 }, { 26871, 26918, 26957, 26998, 27066, 27149, 27307, 27498, 27863, 28332, 29241, 30386, 32602, 35434, 40696, 47134 }, { 34097, 34108, 34125, 34139, 34164, 34193, 34258, 34345, 34544, 34841, 35499, 36330, 38145, 40696, 45450, 51542 }, { 42635, 42636, 42637, 42639, 42641, 42643, 42647, 42662, 42695, 42772, 43067, 43638, 45081, 47134, 51542, 56999 } }, { { 22931, 23015, 23082, 23156, 23272, 23400, 23639, 23925, 24440, 25084, 26237, 27628, 30481, 34097, 40524, 48336 }, { 23015, 23101, 23168, 23243, 23354, 23486, 23718, 24004, 24512, 25149, 26297, 27677, 30508, 34108, 40527, 48337 }, { 23082, 23168, 23243, 23315, 23425, 23554, 23786, 24070, 24574, 25207, 26344, 27719, 30533, 34125, 40529, 48338 }, { 23156, 23243, 23315, 23389, 23493, 23625, 23851, 24134, 24637, 25267, 26391, 27762, 30559, 34139, 40534, 48339 }, { 23272, 23354, 23425, 23493, 23608, 23741, 23965, 24240, 24740, 25362, 26463, 27833, 30606, 34164, 40537, 48340 }, { 23400, 23486, 23554, 23625, 23741, 23871, 24094, 24361, 24857, 25474, 26571, 27915, 30665, 34193, 40540, 48341 }, { 23639, 23718, 23786, 23851, 23965, 24094, 24328, 24587, 25071, 25673, 26692, 28071, 30781, 34258, 40553, 48342 }, { 23925, 24004, 24070, 24134, 24240, 24361, 24587, 24845, 25320, 25905, 26891, 28260, 30926, 34345, 40575, 48344 }, { 24440, 24512, 24574, 24637, 24740, 24857, 25071, 25320, 25781, 26331, 27289, 28624, 31218, 34544, 40634, 48348 }, { 25084, 25149, 25207, 25267, 25362, 25474, 25673, 25905, 26331, 26808, 27805, 29098, 31613, 34841, 40758, 48353 }, { 26237, 26297, 26344, 26391, 26463, 26571, 26692, 26891, 27289, 27805, 28804, 30030, 32422, 35499, 41131, 48449 }, { 27628, 27677, 27719, 27762, 27833, 27915, 28071, 28260, 28624, 29098, 30030, 31211, 33479, 36330, 41775, 48724 }, { 30481, 30508, 30533, 30559, 30606, 30665, 30781, 30926, 31218, 31613, 32422, 33479, 35584, 38145, 43319, 49733 }, { 34097, 34108, 34125, 34139, 34164, 34193, 34258, 34345, 34544, 34841, 35499, 36330, 38145, 40696, 45450, 51542 }, { 40524, 40527, 40529, 40534, 40537, 40540, 40553, 40575, 40634, 40758, 41131, 41775, 43319, 45450, 49971, 55452 }, { 48336, 48337, 48338, 48339, 48340, 48341, 48342, 48344, 48348, 48353, 48449, 48724, 49733, 51542, 55452, 60505 } }, { { 34469, 34480, 34493, 34509, 34538, 34570, 34646, 34754, 34992, 35355, 36119, 37143, 39499, 42635, 48336, 55377 }, { 34480, 34500, 34509, 34526, 34556, 34585, 34662, 34771, 35007, 35366, 36126, 37144, 39500, 42636, 48337, 55377 }, { 34493, 34509, 34524, 34541, 34568, 34601, 34675, 34781, 35020, 35381, 36134, 37151, 39501, 42637, 48338, 55378 }, { 34509, 34526, 34541, 34561, 34587, 34620, 34692, 34798, 35034, 35392, 36142, 37157, 39503, 42639, 48339, 55378 }, { 34538, 34556, 34568, 34587, 34613, 34649, 34720, 34823, 35060, 35414, 36154, 37166, 39513, 42641, 48340, 55379 }, { 34570, 34585, 34601, 34620, 34649, 34688, 34758, 34858, 35090, 35441, 36174, 37183, 39517, 42643, 48341, 55379 }, { 34646, 34662, 34675, 34692, 34720, 34758, 34836, 34932, 35162, 35504, 36217, 37216, 39535, 42647, 48342, 55380 }, { 34754, 34771, 34781, 34798, 34823, 34858, 34932, 35038, 35258, 35591, 36270, 37278, 39567, 42662, 48344, 55381 }, { 34992, 35007, 35020, 35034, 35060, 35090, 35162, 35258, 35482, 35795, 36423, 37418, 39649, 42695, 48348, 55382 }, { 35355, 35366, 35381, 35392, 35414, 35441, 35504, 35591, 35795, 36099, 36672, 37647, 39810, 42772, 48353, 55383 }, { 36119, 36126, 36134, 36142, 36154, 36174, 36217, 36270, 36423, 36672, 37321, 38230, 40256, 43067, 48449, 55385 }, { 37143, 37144, 37151, 37157, 37166, 37183, 37216, 37278, 37418, 37647, 38230, 39115, 40993, 43638, 48724, 55407 }, { 39499, 39500, 39501, 39503, 39513, 39517, 39535, 39567, 39649, 39810, 40256, 40993, 42710, 45081, 49733, 55763 }, { 42635, 42636, 42637, 42639, 42641, 42643, 42647, 42662, 42695, 42772, 43067, 43638, 45081, 47134, 51542, 56999 }, { 48336, 48337, 48338, 48339, 48340, 48341, 48342, 48344, 48348, 48353, 48449, 48724, 49733, 51542, 55452, 60505 }, { 55377, 55377, 55378, 55378, 55379, 55379, 55380, 55381, 55382, 55383, 55385, 55407, 55763, 56999, 60505, 65119 } } }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/ymFormat.h000066400000000000000000000005431504763705000251070ustar00rootroot00000000000000/* Hatari - ymFormat.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ extern bool bRecordingYM; extern bool YMFormat_BeginRecording(const char *pszYMFileName); extern void YMFormat_EndRecording(void); extern void YMFormat_UpdateRecording(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/includes/zip.h000066400000000000000000000016121504763705000241110ustar00rootroot00000000000000/* Hatari - zip.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HATARI_ZIP_H #define HATARI_ZIP_H #include typedef struct { char **names; int nfiles; } zip_dir; extern bool ZIP_FileNameIsZIP(const char *pszFileName); extern struct dirent **ZIP_GetFilesDir(const zip_dir *files, const char *dir, int *entries); extern void ZIP_FreeZipDir(zip_dir *zd); extern zip_dir *ZIP_GetFiles(const char *pszFileName); extern uint8_t *ZIP_ReadDisk(int Drive, const char *pszFileName, const char *pszZipPath, long *pImageSize, int *pImageType); extern bool ZIP_WriteDisk(int Drive, const char *pszFileName, unsigned char *pBuffer, int ImageSize); extern uint8_t *ZIP_ReadFirstFile(const char *pszFileName, long *pImageSize, const char * const ppszExts[]); #endif /* HATARI_ZIP_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/inffile.c000066400000000000000000000573621504763705000231250ustar00rootroot00000000000000/* Hatari - inffile.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. TOS *.INF file overloading for autostarting & TOS resolution overriding. */ const char INFFILE_fileid[] = "Hatari inffile.c"; #include #include #include #include "main.h" #include "configuration.h" #include "inffile.h" #include "options.h" #include "gemdos.h" #include "file.h" #include "log.h" #include "str.h" #include "screen.h" #include "tos.h" #include "vdi.h" /* debug output + leaves virtual INF file behind */ #define INF_DEBUG 0 /* TOS resolution numbers used in Atari TOS INF files. * * Note: EmuTOS uses different numbers, so these are re-mapped. */ typedef enum { RES_UNSET, /* 0 */ RES_ST_LOW, /* 1 */ RES_ST_MED, /* 2 */ RES_ST_HIGH, /* 3 */ RES_TT_MED, /* 4, also Falcon 80 cols mode */ RES_TT_HIGH, /* 5 */ RES_TT_LOW, /* 6, also Falcon 40 cols mode */ RES_COUNT } res_value_t; /* map values 0-6 to EmuTOS: N/A, ST low, med, high, TT med, high, low */ static const res_value_t emutos_map[] = { 0, 0, 1, 2, 4, 6, 7 }; static struct { FILE *file; /* file pointer to contents of INF file */ char *prgname; /* TOS name of the program to auto start */ const char *infname; /* name of the INF file TOS will try to match */ res_value_t reso; /* resolution setting value request for #E line */ /* for validation */ int reso_id; const char *reso_str; int prgname_id; } TosOverride; /* autostarted program name will be added before the first * '@' character in the INF files #Z line * (first value is 00: TOS, 01: GEM). * * #E line content differs between TOS versions: * + Atari TOS: * - Resolution is specified in the 2nd hex value * - Blitter enabling is 0x10 bit for that * + EmuTOS v0.9.7 or newer: * - Resolution is specified in the 4th hex value * - Blitter enabling is 0x80 bit in the 2nd hex value * + Older EmuTOS versions (not supported!): * - Resolution is in the 2nd hex value * * TOS versions expect both of these to be within certain * number of bytes from the beginning of the file, and there * are also TOS version specific limits on the INF file sizes. * * More documentation on the DESKTOP.INF file content: * http://st-news.com/issues/st-news-volume-2-issue-6/education/the-desktopinf-file/ * * EmuTOS INF file content is documented only in the sources: * https://github.com/emutos/emutos/blob/master/desk/deskapp.c */ /* EmuDesk INF file format and values differ from normal TOS */ static const char emudesk_inf[] = "#R 01\r\n" "#E 1A E1 FF 00 00\r\n" "#W 00 00 02 08 26 0C 00 @\r\n" "#W 00 00 02 0A 26 0C 00 @\r\n" "#W 00 00 02 0D 26 0C 00 @\r\n" "#W 00 00 00 00 14 0B 00 @\r\n" "#W 00 00 00 00 14 0B 00 @\r\n" "#W 00 00 00 00 14 0B 00 @\r\n" "#M 00 00 01 FF A DISK A@ @\r\n" "#M 01 00 01 FF B DISK B@ @\r\n" "#M 02 00 00 FF C DISK C@ @\r\n" "#F FF 07 @ *.*@ 000 @\r\n" "#N FF 07 @ *.*@ 000 @\r\n" "#D FF 02 @ *.*@\r\n" "#Y 06 FF *.GTP@ @ 000 @\r\n" "#G 06 FF *.APP@ @ 000 @\r\n" "#G 06 FF *.PRG@ @ 000 @\r\n" "#P 06 FF *.TTP@ @ 000 @\r\n" "#F 06 FF *.TOS@ @ 000 @\r\n" "#T 00 03 03 FF TRASH@ @\r\n"; /* TOS v1.04 works only with DESKTOP.INF from that version * (it crashes with newer INF after autobooted program exits), * later v1.x TOS versions work also with this. * * Trailing spaces are significant for TOS parsing. */ static const char desktop_inf[] = "#a000000\r\n" "#b000000\r\n" "#c7770007000600070055200505552220770557075055507703111103\r\n" "#d \r\n" "#E 18 11 \r\n" "#W 00 00 02 0B 26 09 00 @\r\n" "#W 00 00 0A 0F 1A 09 00 @\r\n" "#W 00 00 0E 01 1A 09 00 @\r\n" "#M 01 00 00 FF C HARD DISK@ @ \r\n" "#M 00 00 00 FF A FLOPPY DISK@ @ \r\n" "#M 00 01 00 FF B FLOPPY DISK@ @ \r\n" "#T 00 03 02 FF TRASH@ @ \r\n" "#F FF 04 @ *.*@ \r\n" "#D FF 01 @ *.*@ \r\n" "#G 03 FF *.APP@ @ \r\n" "#G 03 FF *.PRG@ @ \r\n" "#P 03 FF *.TTP@ @ \r\n" "#F 03 04 *.TOS@ @ \r\n" "\032"; /* TOS v2.x and newer have also different format, using * TOS v1.04 INF file would result in bogus resolution with TOS v4 */ static const char newdesk_inf[] = "#a000000\r\n" "#b000000\r\n" "#c7770007000600070055200505552220770557075055507703111103\r\n" "#d \r\n" "#K 4F 53 4C 00 46 42 43 57 45 58 00 00 00 00 00 00 00 00 00 00 00 00 00 52 00 00 4D 56 50 00 @\r\n" "#E 18 01 00 06 \r\n" "#Q 41 40 43 40 43 40 \r\n" "#W 00 00 02 0B 26 09 00 @\r\n" "#W 00 00 0A 0F 1A 09 00 @\r\n" "#W 00 00 0E 01 1A 09 00 @\r\n" "#W 00 00 04 07 26 0C 00 @\r\n" "#W 00 00 0C 0B 26 09 00 @\r\n" "#W 00 00 08 0F 1A 09 00 @\r\n" "#W 00 00 06 01 1A 09 00 @\r\n" "#N FF 04 000 @ *.*@ @ \r\n" "#D FF 01 000 @ *.*@ @ \r\n" "#G 03 FF 000 *.APP@ @ @ \r\n" "#G 03 FF 000 *.PRG@ @ @ \r\n" "#Y 03 FF 000 *.GTP@ @ @ \r\n" "#P 03 FF 000 *.TTP@ @ @ \r\n" "#F 03 04 000 *.TOS@ @ @ \r\n" "#M 00 01 00 FF C HARD DISK@ @ \r\n" "#M 00 00 00 FF A FLOPPY DISK@ @ \r\n" "#M 01 00 00 FF B FLOPPY DISK@ @ \r\n" "#T 00 03 02 FF TRASH@ @ \r\n"; /* TODO: when support for Falcon resolutions is added, * builtin TOS v4 NEWDESK.INF file contents are needed too */ /*-----------------------------------------------------------------------*/ /** * Set name of program that will be auto started after TOS boots. * Supported only from TOS 1.04 forward. * * If program lacks a path, "C:\" will be added. * * Returns true if OK, false for obviously invalid path specification. */ bool INF_SetAutoStart(const char *name, int opt_id) { char *prgname; int len = strlen(name); char drive = toupper(name[0]); if (drive >= 'A' && drive <= 'Z' && name[1] == ':') { /* full path */ const char *ptr; int offset; prgname = Str_Alloc(len + 1); /* +1 for additional backslash */ ptr = strrchr(name, '\\'); if (ptr) offset = ptr - name + 1; else offset = 2; /* copy/upcase path part */ memcpy(prgname, name, offset); prgname[offset] = '\0'; Str_ToUpper(prgname); if (name[2] != '\\') { /* NOT OK: A:DIR\NAME.PRG */ if (ptr) { Log_Printf(LOG_WARN, "rejecting auto-start path that doesn't have '\\' after drive ID:\n\t%s\n", name); free(prgname); return false; } /* A:NAME.PRG -> A:\NAME.PRG */ prgname[offset] = '\\'; /* copy/upcase file part */ Str_Filename_Host2Atari(name+offset, prgname+offset+1); } else { /* copy/upcase file part */ Str_Filename_Host2Atari(name+offset, prgname+offset); } } else if (strchr(name, '\\')) { /* partial path not accepted */ Log_Printf(LOG_WARN, "rejecting auto-start path starting with '\\', but without drive ID:\n\t%s\n", name); return false; } else { /* just program -> add path */ prgname = Str_Alloc(3 + len); strcpy(prgname, "C:\\"); Str_Filename_Host2Atari(name, prgname+3); } if (TosOverride.prgname) free(TosOverride.prgname); TosOverride.prgname = prgname; TosOverride.prgname_id = opt_id; return true; } /*-----------------------------------------------------------------------*/ /** * Set specified TOS resolution override. * * Return true for success, false otherwise. */ bool INF_SetResolution(const char *str, int opt_id) { int reso; /* map to values used by real TOS INF files */ if (strcmp(str, "low") == 0) reso = RES_ST_LOW; else if (strcmp(str, "med") == 0) reso = RES_ST_MED; else if (strcmp(str, "high") == 0) reso = RES_ST_HIGH; else if (strcmp(str, "ttmed") == 0) reso = RES_TT_MED; else if (strcmp(str, "ttlow") == 0) reso = RES_TT_LOW; else { reso = atoi(str); if (reso <= RES_UNSET || reso >= RES_COUNT) return false; } TosOverride.reso = reso; TosOverride.reso_id = opt_id; TosOverride.reso_str = str; return true; } /*-----------------------------------------------------------------------*/ /** * Validate autostart options against Hatari settings: * - program drive * * If there's a problem, return problematic option ID * and set val & err strings, otherwise just return zero. */ int INF_ValidateAutoStart(const char **val, const char **err) { const char *path = TosOverride.prgname; char drive; if (!path) return 0; /* validate autostart program drive */ drive = path[0]; if (drive == 'A') { if (ConfigureParams.DiskImage.EnableDriveA && ConfigureParams.DiskImage.szDiskFileName[0][0]) return 0; } else if (drive == 'B') { if (ConfigureParams.DiskImage.EnableDriveB && ConfigureParams.DiskImage.szDiskFileName[1][0]) return 0; } /* exact drive checking for hard drives would require: * * For images: * - finding out what partitions each of the 2 IDE, 8 ACSI, and * 8 SCSI images do have, *and* * - finding out which of those partitions the native Atari * harddisk driver happens to support... * -> not feasible * * For GEMDOS HD: * - If multiple partitions are specified, which ones * - If not, what is the single partition drive letter * * So, just check that some harddisk is enabled for C: -> */ /* GEMDOS HD */ else if (ConfigureParams.HardDisk.bUseHardDiskDirectories && ConfigureParams.HardDisk.szHardDiskDirectories[0][0]) { return 0; } /* IDE */ else if (ConfigureParams.Ide[0].bUseDevice && ConfigureParams.Ide[0].sDeviceFile[0]) { return 0; } else if (ConfigureParams.Ide[1].bUseDevice && ConfigureParams.Ide[1].sDeviceFile[0]) { return 0; } else { /* ACSI / SCSI */ int i; for (i = 0; i < MAX_ACSI_DEVS; i++) { if (ConfigureParams.Acsi[i].bUseDevice && ConfigureParams.Acsi[i].sDeviceFile[0]) return 0; if (ConfigureParams.Scsi[i].bUseDevice && ConfigureParams.Scsi[i].sDeviceFile[0]) return 0; } } /* error */ *val = TosOverride.prgname; *err = "Required autostart drive isn't enabled"; return TosOverride.prgname_id; } /** * Map VDI / HW resolution to INF file resolution value */ static res_value_t vdi2inf(res_value_t mode) { res_value_t newres, res = TosOverride.reso; switch (mode) { case ST_LOW_RES: newres = RES_ST_LOW; break; case ST_MEDIUM_RES: newres = RES_ST_MED; break; case ST_HIGH_RES: newres = RES_ST_HIGH; break; case TT_LOW_RES: newres = RES_TT_LOW; break; case TT_MEDIUM_RES: newres = RES_TT_MED; break; case TT_HIGH_RES: newres = RES_TT_HIGH; break; default: newres = res; } if (res != newres) { if (res) Log_Printf(LOG_WARN, "Overriding TOS INF resolution %d with VDI resolution %d\n", res, newres); res = newres; } return res; } /** * Map / set VDI to INF file resolution */ extern void INF_SetVdiMode(int vdi_res) { TosOverride.reso = vdi2inf(vdi_res); } /** * Resolution needs to be validated later, here, because we don't * know the final machine type when options are parsed, as it can * change later when TOS is loaded. * * Resolution settings are: * 0: no override * 1-3: ST/STE resolutions: * - ST low, med, high * 4-6: TT/Falcon resolutions: * - TT med, high, low * - Falcon 80 cols, N/A, 40 cols * * If there's a problem, return problematic option ID * and set val & err strings, otherwise just return zero. */ static int INF_ValidateResolution(int *set_res, const char **val, const char **err) { #define MONO_WARN_STR "Correcting virtual INF file resolution to mono on mono monitor\n" res_value_t res = TosOverride.reso; *set_res = 0; /* VDI resolution overrides TOS resolution setting */ if (bUseVDIRes) { res = vdi2inf(VDIRes); } else { int monitor = ConfigureParams.Screen.nMonitorType; /* validate given TOS resolution */ if (!res) return 0; *val = TosOverride.reso_str; switch(ConfigureParams.System.nMachineType) { case MACHINE_STE: case MACHINE_MEGA_STE: case MACHINE_ST: case MACHINE_MEGA_ST: if (monitor == MONITOR_TYPE_MONO) { if (res != RES_ST_HIGH) { res = RES_ST_HIGH; Log_Printf(LOG_WARN, MONO_WARN_STR); } } else if (res >= RES_ST_HIGH) { *err = "invalid TOS resolution for ST/STE color monitor"; return TosOverride.reso_id; } break; case MACHINE_TT: if (monitor == MONITOR_TYPE_MONO) { if (res != RES_TT_HIGH) { res = RES_TT_HIGH; Log_Printf(LOG_WARN, MONO_WARN_STR); } } else if (res == RES_TT_HIGH) { *err = "invalid TOS resolution for TT color monitor"; return TosOverride.reso_id; } break; case MACHINE_FALCON: if (monitor == MONITOR_TYPE_MONO && res != RES_ST_HIGH) { res = RES_ST_HIGH; Log_Printf(LOG_WARN, MONO_WARN_STR); } else if (res == RES_TT_HIGH) { *err = "TT-mono is invalid TOS resolution for Falcon"; return TosOverride.reso_id; } else { Log_Printf(LOG_WARN, "TOS resolution setting doesn't work with Falcon (yet)\n"); } /* TODO: * Falcon resolution setting doesn't have effect, * seems that #E Falcon settings in columns 6 & 7 * (5th & 6th hex values) are also needed: * - line doubling / interlace * - ST compat, RGB/VGA, columns & #colors * These should be same as for VsetMode: * http://toshyp.atari.org/en/Screen_functions.html#Vsetmode */ break; } } if (bIsEmuTOS) { res = emutos_map[res]; Log_Printf(LOG_DEBUG, "Remapped INF file TOS resolution for EmuTOS\n"); } else if (TosVersion >= 0x0160) { switch(ConfigureParams.System.nMachineType) { case MACHINE_STE: case MACHINE_MEGA_STE: case MACHINE_FALCON: /* enable blitter */ res |= 0x10; break; default: break; } } Log_Printf(LOG_DEBUG, "Resulting INF file TOS resolution: 0x%02x -> 0x%02x.\n", TosOverride.reso, res); *set_res = res; return 0; } /*-----------------------------------------------------------------------*/ /** * Get builtin INF file contents which open window for the boot drive, * if any. * * TODO: this won't work for EmuTOS because it opens INF file second * time to read window info, at which point the temporary virtual INF * file has already disappeared. Real TOS versions read INF file * only once and work fine. */ static char *get_builtin_inf(const char *contents) { /* line to open window (for boot drive) */ static const char drivewin[] = "#W 00 00 02 06 26 0C 00 X:\\*.*@\r\n"; int winlen, inflen, winoffset1, winoffset2, driveoffset; const char *winline; char *inf; assert(contents); inflen = strlen(contents); winlen = strlen(drivewin); inf = Str_Alloc(inflen + winlen); /* drive letter offset on drive window line */ driveoffset = strchr(drivewin, 'X') - drivewin; /* first copy everything until first window line */ winline = strstr(contents, "#W"); assert(winline); winoffset2 = winoffset1 = winline - contents; memcpy(inf, contents, winoffset1); /* then comes boot drive window line, if any */ if (ConfigureParams.HardDisk.bBootFromHardDisk) { /* C:, ignore IDE/ACSI for now */ if (GemDOS_IsDriveEmulated(2)) { strcpy(inf + winoffset1, drivewin); inf[winoffset1 + driveoffset] = 'C'; winoffset2 += winlen; } } else if (ConfigureParams.DiskImage.EnableDriveA && ConfigureParams.DiskImage.szDiskFileName[0][0]) { /* A: */ strcpy(inf + winoffset1, drivewin); inf[winoffset1 + driveoffset] = 'A'; winoffset2 += winlen; } /* finally copy rest */ strcpy(inf + winoffset2, contents + winoffset1); return inf; } /** * Get suitable Atari desktop configuration file for current TOS version, * either by loading existing file, or creating default one if there isn't * a pre-existing one. * * Return INF file contents and set its name & size to args. */ static char *get_inf_file(const char **set_infname, int *set_size, int *res_col) { char *hostname; const char *contents, *infname; uint8_t *host_content; long host_size; int size; /* default position of the 2 digit hex code for resolution on #E line */ *res_col = 6; /* infname needs to be exactly the same string that given * TOS version gives for GEMDOS to find. */ if (bIsEmuTOS) { if (ConfigureParams.HardDisk.bBootFromHardDisk) infname = "C:\\EMUDESK.INF"; else infname = "A:\\EMUDESK.INF"; size = sizeof(emudesk_inf); contents = emudesk_inf; *res_col = 12; } /* need to match file TOS searches first */ else if (TosVersion >= 0x0200 && TosVersion != 0x300) { infname = "NEWDESK.INF"; size = sizeof(newdesk_inf); contents = newdesk_inf; } else { infname = "DESKTOP.INF"; size = sizeof(desktop_inf); contents = desktop_inf; } *set_infname = infname; *set_size = size; /* Existing INF can be modified only through GEMDOS hard disk, * i.e. boot needs to be from C:, which needs to be GEMDOS HD */ if (!(ConfigureParams.HardDisk.bBootFromHardDisk && GemDOS_IsDriveEmulated(2))) { Log_Printf(LOG_DEBUG, "No GEMDOS HD boot drive, using builtin INF autostart file.\n"); return get_builtin_inf(contents); } hostname = Str_Alloc(FILENAME_MAX); /* convert to host file name, and read that */ GemDOS_CreateHardDriveFileName(2, infname, hostname, FILENAME_MAX); #if INF_DEBUG GemDOS_Info(stderr, 0); fprintf(stderr, "\nChecking for existing INF file '%s' -> '%s'...\n", infname, hostname); #endif host_content = File_ReadAsIs(hostname, &host_size); if (host_content) { Log_Printf(LOG_DEBUG, "Going to modify '%s'.\n", hostname); free(hostname); *set_size = host_size; return (char *)host_content; } Log_Printf(LOG_DEBUG, "Using builtin '%s'.\n", infname); free(hostname); return get_builtin_inf(contents); } /*-----------------------------------------------------------------------*/ /** * Skip rest of INF file line. * Return index after its end, or zero for error. */ static int skip_line(const char *contents, int offset, int size) { int orig = offset; char chr; for (; offset < size; offset++) { chr = contents[offset]; if (chr == '\r' || chr == '\n') { chr = contents[++offset]; if (chr == '\r' || chr == '\n') offset++; return offset; } } Log_Printf(LOG_WARN, "Malformed INF file '%s', no line end at offsets %d-%d!\n", TosOverride.prgname, orig, offset); return 0; } /** * Return INF file autostart line format suitable for given * program type, based on program name extension. */ static const char *prg_format(const char *prgname) { const char *ext; int size; size = strlen(prgname); if (size > 4) ext = prgname + size - 4; else ext = prgname; if (strcmp(ext, ".TTP") == 0 ||strcmp(ext, ".TOS") == 0) return "#Z 00 %s@ \r\n"; /* TOS program */ else return "#Z 01 %s@ \r\n"; /* GEM program */ } /** * Create modified, temporary INF file that contains the required * autostart and resolution information. * * Return FILE* pointer to it. */ static FILE* write_inf_file(const char *contents, int size, int res, int res_col) { const char *infname, *prgname, *format = NULL; int offset, off_prg, off_rez, endcheck; FILE *fp; #if INF_DEBUG { /* insecure file path + leaving it behind for debugging */ const char *debugfile = "/tmp/hatari-desktop-inf.txt"; fprintf(stderr, "Virtual INF file: '%s'\n", debugfile); fp = fopen(debugfile, "w+b"); } #else fp = File_OpenTempFile(NULL); #endif prgname = TosOverride.prgname; infname = TosOverride.infname; if (!fp) { Log_Printf(LOG_ERROR, "Failed to create virtual INF file '%s': %s!\n", infname, strerror(errno)); return NULL; } if (prgname) format = prg_format(prgname); /* need to fit at least 2 res digits + \r\n */ endcheck = size-res_col-2-2; /* positions after prog info & resolution info */ off_prg = off_rez = 0; /* find where to insert the program name and resolution */ for (offset = 0; offset < endcheck; offset++) { if (contents[offset] != '#') continue; /* replace autostart line only when requested */ if (prgname && contents[offset+1] == 'Z') { fwrite(contents+off_prg, offset-off_prg, 1, fp); /* write only first #Z line, skip rest */ if (!off_prg) fprintf(fp, format, prgname); offset = skip_line(contents, offset, size-1); if (!offset) break; off_prg = offset; } /* resolution line always written */ if (contents[offset+1] == 'E') { fwrite(contents+off_prg, offset-off_prg, 1, fp); /* INF file with autostart line missing? * * It's assumed that #Z is always before #E, * if it exits. So write one when requested, * if it hasn't been written yet. */ if (prgname && !off_prg) { off_prg = offset; fprintf(fp, format, prgname); } /* write #E line start */ fwrite(contents+offset, res_col, 1, fp); /* write requested resolution, or default? * * (TosOverride.reso tells if there's request, * 'res' tells the actual value to use) */ if (TosOverride.reso) fprintf(fp, "%02x", res); else fwrite(contents+offset+res_col, 2, 1, fp); /* set point to rest of #E */ offset += res_col + 2; off_rez = offset; break; } } if (!off_rez) { fclose(fp); Log_Printf(LOG_ERROR, "'%s' not a valid INF file, #E resolution line missing -> autostarting / resolution overriding not possible!\n", infname); return NULL; } /* write rest of INF file & seek back to start */ if (!(fwrite(contents+offset, size-offset-1, 1, fp) && fseek(fp, 0, SEEK_SET) == 0)) { fclose(fp); Log_Printf(LOG_ERROR, "Virtual '%s' INF file writing failed!\n", infname); return NULL; } if (prgname) Log_Printf(LOG_DEBUG, "Virtual '%s' autostart INF file created for '%s'\n", infname, prgname); else Log_Printf(LOG_DEBUG, "Virtual '%s' TOS resolution override INF file created\n", infname); return fp; } /*-----------------------------------------------------------------------*/ /** * Create a temporary TOS INF file for autostarting and resolution overriding. * * File has TOS version specific differences, so it needs to be re-created * on each boot in case user changed TOS version. * * Called at end of TOS ROM loading. */ void INF_CreateOverride(void) { char *contents; const char *err, *val; int size, res, res_col, opt_id; if ((opt_id = INF_ValidateResolution(&res, &val, &err))) { Opt_ShowError(opt_id, val, err); bQuitProgram = true; return; } /* in case TOS didn't for some reason close it on previous boot */ INF_CloseOverride(TosOverride.file); /* INF overriding needed? */ if (!(TosOverride.prgname || TosOverride.reso)) return; /* GEMDOS HD / INF overriding not supported? */ if (bUseTos && TosVersion < 0x0104) { Log_Printf(LOG_WARN, "Only TOS versions >= 1.04 support autostarting & resolution overriding!\n"); return; } contents = get_inf_file(&TosOverride.infname, &size, &res_col); if (contents) { TosOverride.file = write_inf_file(contents, size, res, res_col); free(contents); } } /*-----------------------------------------------------------------------*/ /** * Whether INF file overriding needs GEMDOS * interception or Fopen() check enabling */ bool INF_Overriding(autostart_t t) { if (t == AUTOSTART_FOPEN) return (bool)TosOverride.file; return (((bool)TosOverride.prgname) || ((bool)TosOverride.reso)); } /*-----------------------------------------------------------------------*/ /** * If given name matches virtual INF file name, return its handle, NULL otherwise */ FILE *INF_OpenOverride(const char *filename) { if (TosOverride.file && strcmp(filename, TosOverride.infname) == 0) { /* whether to "autostart" also exception debugging? */ if (ConfigureParams.Debugger.nExceptionDebugMask & EXCEPT_AUTOSTART) { ExceptionDebugMask = ConfigureParams.Debugger.nExceptionDebugMask & ~EXCEPT_AUTOSTART; Log_Printf(LOG_INFO, "Exception debugging enabled (0x%x).\n", ExceptionDebugMask); } Log_Printf(LOG_DEBUG, "Virtual INF file '%s' matched.\n", filename); return TosOverride.file; } return NULL; } /*-----------------------------------------------------------------------*/ /** * If given handle matches virtual INF file, close it and return true, * false otherwise. */ bool INF_CloseOverride(FILE *fp) { if (fp && fp == TosOverride.file) { /* Remove virtual INF file after TOS has * read it enough times to do autostarting etc. * Otherwise user may try change desktop settings * and save them, but they would be lost. */ fclose(TosOverride.file); TosOverride.file = NULL; Log_Printf(LOG_DEBUG, "Virtual INF file removed.\n"); return true; } return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ioMem.c000066400000000000000000000736471504763705000225630ustar00rootroot00000000000000/* Hatari - ioMem.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This is where we intercept read/writes to/from the hardware. The ST's memory is nicely split into four main parts - the bottom area of RAM is for user programs. This is followed by a large area which causes a Bus Error. After this is the ROM addresses for TOS and finally an area for hardware mapping. To gain speed any address in the user area can simply read/write, but anything above this range needs to be checked for validity and sent to the various handlers. A big problem for ST emulation is the use of the hardware registers. These often consist of an 'odd' byte in memory and is usually addressed as a single byte. A number of applications, however, write to the address using a word or even long word. So we have a list of handlers that take care of each address that has to be intercepted. Eg, a long write to a PSG register (which access two registers) will write the long into IO memory space and then call the two handlers which read off the bytes for each register. This means that any access to any hardware register in such a way will work correctly - it certainly fixes a lot of bugs and means writing just one routine for each hardware register we mean to intercept! Phew! You have also to take into consideration that some hardware registers are bigger than 1 byte (there are also word and longword registers) and that a lot of addresses in between can cause a bus error - so it's not so easy to cope with all type of handlers in a straight forward way. Also note the 'mirror' (or shadow) registers of the PSG - this is used by most games. */ const char IoMem_fileid[] = "Hatari ioMem.c"; #include "main.h" #include "configuration.h" #include "ioMem.h" #include "ioMemTables.h" #include "memorySnapShot.h" #include "m68000.h" #include "sysdeps.h" #include "newcpu.h" #include "log.h" #include "scc.h" #include "fdc.h" #include "scu_vme.h" #define IO_MEM_INTERCEPT_START 0xff8000 #define IO_MEM_INTERCEPT_END 0xffffff static void (*pInterceptReadTable[0x8000])(void); /* Table with read access handlers for IO regs FF8000 - FFFFFF */ static void (*pInterceptWriteTable[0x8000])(void); /* Table with write access handlers for IO regs FF8000 - FFFFFF */ int nIoMemAccessSize; /* Set to 1, 2 or 4 according to byte, word or long word access */ uint32_t IoAccessFullAddress; /* Store the complete 32 bit address received in the IoMem_xxx() handler */ /* (this is the address to write on the stack in case of a bus error) */ uint32_t IoAccessBaseAddress; /* Stores the base address of the IO mem access (masked on 24 bits) */ uint32_t IoAccessCurrentAddress; /* Current byte address while handling WORD and LONG accesses (masked on 24 bits) */ static int nBusErrorAccesses; /* Needed to count bus error accesses */ /* Heuristics for better cycle accuracy when "cycle exact mode" is not used Some instructions can do several IO accesses that will be seen as several independent accesses, instead of one whole word or long word access as in the size of the instruction. For example : - movep.w and move.l will do 2 or 4 BYTE accesses (and not 1 WORD or LONG WORD access) - move.l will do 2 WORD accesses (and not 1 LONG WORD, because ST's bus is 16 bit) So, when a BYTE access is made, we need to know if it comes from an instruction where size=byte or if it comes from a word or long word instruction. In order to emulate correct read/write cycles when IO regs are accessed this way, we need to keep track of how many accesses were made by the same instruction. This will be used when CPU runs in "prefetch mode" and we try to approximate internal cycles (see cycles.c for heuristics using this). When CPU runs in "cycle exact mode", this is not used because the internal cycles will be computed precisely at the CPU emulation level. */ static uint64_t IoAccessInstrPrevClock; int IoAccessInstrCount; /* Number of the accesses made in the current instruction (1..4) */ /* 0 means no multiple accesses in the current instruction */ /* Falcon bus mode (Falcon STe compatible bus or Falcon only bus) */ static enum FALCON_BUS_MODE falconBusMode = FALCON_ONLY_BUS; /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void IoMem_MemorySnapShot_Capture(bool bSave) { enum FALCON_BUS_MODE mode = falconBusMode; /* Save/Restore details */ MemorySnapShot_Store(&mode, sizeof(mode)); MemorySnapShot_Store(&IoAccessInstrPrevClock,sizeof(IoAccessInstrPrevClock)); MemorySnapShot_Store(&IoAccessInstrCount,sizeof(IoAccessInstrCount)); if (!bSave) IoMem_SetFalconBusMode(mode); } /*-----------------------------------------------------------------------*/ /** * Set the read and write functions associated with a given 'addr' in IO mem */ void IoMem_Intercept ( uint32_t addr , void (*read_f)(void) , void (*write_f)(void) ) { pInterceptReadTable[addr - IO_MEM_INTERCEPT_START] = read_f; pInterceptWriteTable[addr - IO_MEM_INTERCEPT_START] = write_f; } /*-----------------------------------------------------------------------*/ /** * Fill a region with bus error handlers. */ static void IoMem_SetBusErrorRegion(uint32_t startaddr, uint32_t endaddr) { uint32_t a; for (a = startaddr; a <= endaddr; a++) { if (a & 1) IoMem_Intercept ( a , IoMem_BusErrorOddReadAccess , IoMem_BusErrorOddWriteAccess ); else IoMem_Intercept ( a , IoMem_BusErrorEvenReadAccess , IoMem_BusErrorEvenWriteAccess ); } } /** * Fill a region with void handlers. */ static void IoMem_SetVoidRegion(uint32_t startaddr, uint32_t endaddr) { uint32_t addr; for (addr = startaddr; addr <= endaddr; addr++) { IoMem_Intercept ( addr , IoMem_VoidRead , IoMem_VoidWrite ); } } /** * Normal ST (with Ricoh chipset) has two address which don't generate a bus * error when compared to the Mega-ST (with IMP chipset). Mark them as void * handlers here. */ static void IoMem_FixVoidAccessForST(void) { IoMem_SetVoidRegion(0xff820f, 0xff820f); IoMem_SetVoidRegion(0xff860f, 0xff860f); } /** * We emulate the Mega-ST with IMP chipset, and this has slightly different * behavior with regards to bus errors compared to the normal ST, which we * emulate with Ricoh chipset. Here we fix up the table accordingly. * Note that there are also normal STs with the IMP chipset, and Mega-STs * with the Ricoh chipset available, so in real life this can also be the * other way round. But since the Ricoh chipset is likely the older one * and the Mega-STs are the later machines, we've chosen to use IMP for the * Mega and Ricoh for normal STs in Hatari. */ static void IoMem_FixVoidAccessForMegaST(void) { int i; uint32_t no_be_addrs[] = { 0xff8200, 0xff8202, 0xff8204, 0xff8206, 0xff8208, 0xff820c, 0xff8608, 0xff860a, 0xff860c, 0 }; uint32_t no_be_regions[][2] = { { 0xff8000, 0xff8000 }, { 0xff8002, 0xff800d }, { 0xff8a3e, 0xff8a3f }, { 0, 0 } }; for (i = 0; no_be_addrs[i] != 0; i++) { IoMem_SetVoidRegion(no_be_addrs[i], no_be_addrs[i]); } for (i = 0; no_be_regions[i][0] != 0; i++) { IoMem_SetVoidRegion(no_be_regions[i][0], no_be_regions[i][1]); } } /** * Fix up the IO memory access table for the MegaSTE compared to the STE */ static void IoMem_FixAccessForMegaSTE(void) { int addr; /* MegaSTE has an additional Cache/CPU control register compared to * the normal STE. The addresses before and after 0xff8e21 also do not * produce a bus error on the MegaSTE. */ IoMem_Intercept ( 0xff8e20 , IoMem_VoidRead , IoMem_VoidWrite ); IoMem_Intercept ( 0xff8e21 , IoMem_ReadWithoutInterception , IoMemTabMegaSTE_CacheCpuCtrl_WriteByte ); IoMem_Intercept ( 0xff8e22 , IoMem_VoidRead , IoMem_VoidWrite ); IoMem_Intercept ( 0xff8e23 , IoMem_VoidRead , IoMem_VoidWrite ); /* The MegaSTE has a SCU at 0xff8e01-0xff8e0f (like the TT) */ IoMem_Intercept ( 0xff8e01 , SCU_SysIntMask_ReadByte , SCU_SysIntMask_WriteByte ); IoMem_Intercept ( 0xff8e03 , SCU_SysIntState_ReadByte , SCU_SysIntState_WriteByte ); IoMem_Intercept ( 0xff8e05 , SCU_SysInterrupter_ReadByte , SCU_SysInterrupter_WriteByte ); IoMem_Intercept ( 0xff8e07 , SCU_VmeInterrupter_ReadByte , SCU_VmeInterrupter_WriteByte ); IoMem_Intercept ( 0xff8e09 , SCU_GPR1_ReadByte , SCU_GPR1_WriteByte ); IoMem_Intercept ( 0xff8e0b , SCU_GPR2_ReadByte , SCU_GPR2_WriteByte ); IoMem_Intercept ( 0xff8e0d , SCU_VmeIntMask_Readyte , SCU_VmeIntMask_WriteByte ); IoMem_Intercept ( 0xff8e0f , SCU_VmeIntState_ReadByte , SCU_VmeIntState_WriteByte ); /* The MegaSTE has a Z85C30 SCC serial port, too: */ for (addr = 0xff8c80; addr <= 0xff8c87; addr++) { IoMem_Intercept ( addr , SCC_IoMem_ReadByte , SCC_IoMem_WriteByte ); } /* The MegaSTE can choose between DD and HD mode when reading floppy */ /* This uses word register at 0xff860e */ for (addr = 0xff860e; addr <= 0xff860f; addr++) { IoMem_Intercept ( addr , FDC_DensityMode_ReadWord , FDC_DensityMode_WriteWord ); } } /** * Fix up table for Falcon in STE compatible bus mode (i.e. less bus errors) */ static void IoMem_FixVoidAccessForCompatibleFalcon(void) { int i; uint32_t no_be_regions[][2] = { { 0xff8002, 0xff8005 }, { 0xff8008, 0xff800b }, { 0xff800e, 0xff805f }, { 0xff8064, 0xff81ff }, { 0xff82c4, 0xff83ff }, { 0xff8804, 0xff88ff }, { 0xff8964, 0xff896f }, { 0xff8c00, 0xff8c7f }, { 0xff8c88, 0xff8cff }, { 0xff9000, 0xff91ff }, { 0xff9204, 0xff920f }, { 0xff9218, 0xff921f }, { 0xff9224, 0xff97ff }, { 0xff9c00, 0xff9fff }, { 0xffa200, 0xffa207 }, { 0, 0 } }; for (i = 0; no_be_regions[i][0] != 0; i++) { IoMem_SetVoidRegion(no_be_regions[i][0], no_be_regions[i][1]); } } /** * Create 'intercept' tables for hardware address access. Each 'intercept * table is a list of 0x8000 pointers to a list of functions to call when * that location in the ST's memory is accessed. */ void IoMem_Init(void) { uint32_t addr; int i; const INTERCEPT_ACCESS_FUNC *pInterceptAccessFuncs = NULL; /* Set default IO access handler (-> bus error) between 0xff8000 and 0xffffff */ IoMem_SetBusErrorRegion(IO_MEM_INTERCEPT_START, IO_MEM_INTERCEPT_END); switch (ConfigureParams.System.nMachineType) { case MACHINE_ST: pInterceptAccessFuncs = IoMemTable_ST; break; case MACHINE_MEGA_ST: pInterceptAccessFuncs = IoMemTable_ST; break; case MACHINE_STE: pInterceptAccessFuncs = IoMemTable_STE; break; case MACHINE_MEGA_STE: pInterceptAccessFuncs = IoMemTable_STE; break; case MACHINE_TT: pInterceptAccessFuncs = IoMemTable_TT; break; case MACHINE_FALCON: pInterceptAccessFuncs = IoMemTable_Falcon; break; default: abort(); /* bug */ } /* Now set the correct handlers */ for (addr=IO_MEM_INTERCEPT_START; addr <= IO_MEM_INTERCEPT_END; addr++) { /* Does this hardware location/span appear in our list of possible intercepted functions? */ for (i=0; pInterceptAccessFuncs[i].Address != 0; i++) { if (addr >= pInterceptAccessFuncs[i].Address && addr < pInterceptAccessFuncs[i].Address+pInterceptAccessFuncs[i].SpanInBytes) { /* Security checks... */ if (pInterceptReadTable[addr-IO_MEM_INTERCEPT_START] != IoMem_BusErrorEvenReadAccess && pInterceptReadTable[addr-IO_MEM_INTERCEPT_START] != IoMem_BusErrorOddReadAccess) Log_Printf(LOG_WARN, "IoMem_Init: $%x (R) already defined\n", addr); if (pInterceptWriteTable[addr-IO_MEM_INTERCEPT_START] != IoMem_BusErrorEvenWriteAccess && pInterceptWriteTable[addr-IO_MEM_INTERCEPT_START] != IoMem_BusErrorOddWriteAccess) Log_Printf(LOG_WARN, "IoMem_Init: $%x (W) already defined\n", addr); /* This location needs to be intercepted, so add entry to list */ IoMem_Intercept ( addr , pInterceptAccessFuncs[i].ReadFunc , pInterceptAccessFuncs[i].WriteFunc ); } } } /* After the IO access handlers were set, some machines with common IoMemTable_xxx */ /* will require some extra changes (eg: ST vs MegaST, STE ve MegaSTE) */ if ( ConfigureParams.System.nMachineType == MACHINE_ST ) IoMem_FixVoidAccessForST(); else if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_ST ) IoMem_FixVoidAccessForMegaST(); else if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) IoMem_FixAccessForMegaSTE(); /* Set registers for Falcon */ if (Config_IsMachineFalcon()) { if (falconBusMode == STE_BUS_COMPATIBLE) IoMem_FixVoidAccessForCompatibleFalcon(); /* Set registers for Falcon DSP emulation */ switch (ConfigureParams.System.nDSPType) { #if ENABLE_DSP_EMU case DSP_TYPE_EMU: IoMemTabFalcon_DSPemulation(pInterceptReadTable, pInterceptWriteTable); break; #endif case DSP_TYPE_DUMMY: IoMemTabFalcon_DSPdummy(pInterceptReadTable, pInterceptWriteTable); break; default: /* none */ IoMemTabFalcon_DSPnone(pInterceptReadTable, pInterceptWriteTable); } } /* Disable blitter? */ if (!ConfigureParams.System.bBlitter && ConfigureParams.System.nMachineType == MACHINE_ST) { IoMem_SetBusErrorRegion(0xff8a00, 0xff8a3f); } /* Disable real time clock on non-Mega machines */ if (ConfigureParams.System.nMachineType == MACHINE_ST || ConfigureParams.System.nMachineType == MACHINE_STE) { for (addr = 0xfffc21; addr <= 0xfffc3f; addr++) { IoMem_Intercept ( addr , IoMem_VoidRead , IoMem_VoidWrite ); } } /* Falcon PSG shadow register range setup (to void access) is already * done above as part of the IoMem_FixVoidAccessForCompatibleFalcon() * call (in STE bus compatible mode, otherwise they bus error) */ if (!Config_IsMachineFalcon()) { /* Initialize PSG shadow registers for ST, STe, TT machines */ for (addr = 0xff8804; addr < 0xff8900; addr++) { IoMem_Intercept ( addr , pInterceptReadTable[(addr & 0xfff803) - IO_MEM_INTERCEPT_START] , pInterceptWriteTable[(addr & 0xfff803) - IO_MEM_INTERCEPT_START] ); } } } /** * Uninitialize the IoMem code for the current machine */ void IoMem_UnInit(int MachineType) { if ( MachineType == MACHINE_MEGA_STE ) MegaSTE_CPU_Set_16Mhz ( false ); } /** * This function is called to fix falconBusMode. This value comes from register * $ff8007.b (Bit 5) and is called from ioMemTabFalcon.c. */ void IoMem_SetFalconBusMode(enum FALCON_BUS_MODE mode) { if (mode != falconBusMode) { falconBusMode = mode; IoMem_UnInit(MACHINE_FALCON); IoMem_Init(); } } bool IoMem_IsFalconBusMode(void) { return falconBusMode == FALCON_ONLY_BUS; } /** * During (cold) reset, we have to clean up the Falcon bus mode if necessary. */ void IoMem_Reset(void) { if (Config_IsMachineFalcon()) { IoMem_SetFalconBusMode(FALCON_ONLY_BUS); } } /*-----------------------------------------------------------------------*/ /** * Handle byte read access from IO memory. */ uae_u32 REGPARAM3 IoMem_bget(uaecptr addr) { uint8_t val; IoAccessFullAddress = addr; /* Store initial 32 bits address (eg for bus error stack) */ /* Check if access is made by a new instruction or by the same instruction doing multiple byte accesses */ if ( IoAccessInstrPrevClock == CyclesGlobalClockCounter ) IoAccessInstrCount++; /* Same instruction, increase access count */ else { IoAccessInstrPrevClock = CyclesGlobalClockCounter; if ( table68k[ M68000_CurrentOpcode ].size == 0 ) IoAccessInstrCount = 0; /* Instruction size is byte : no multiple accesses */ else IoAccessInstrCount = 1; /* 1st access */ } addr &= 0x00ffffff; /* Use a 24 bit address */ if (addr < IO_MEM_INTERCEPT_START || !is_super_access(true)) { /* invalid memory addressing --> bus error */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return -1; } IoAccessBaseAddress = addr; /* Store access location */ nIoMemAccessSize = SIZE_BYTE; nBusErrorAccesses = 0; IoAccessCurrentAddress = addr; pInterceptReadTable[addr-IO_MEM_INTERCEPT_START](); /* Call handler */ /* Check if we read from a bus-error region */ if (nBusErrorAccesses == 1) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return -1; } val = IoMem[addr]; LOG_TRACE(TRACE_IOMEM_RD, "IO read.b $%08x = $%02x pc=%x\n", IoAccessFullAddress, val, M68000_GetPC()); return val; } /*-----------------------------------------------------------------------*/ /** * Handle word read access from IO memory. */ uae_u32 REGPARAM3 IoMem_wget(uaecptr addr) { uint32_t idx; uint16_t val; IoAccessFullAddress = addr; /* Store initial 32 bits address (eg for bus error stack) */ /* Check if access is made by a new instruction or by the same instruction doing multiple word accesses */ if ( IoAccessInstrPrevClock == CyclesGlobalClockCounter ) IoAccessInstrCount++; /* Same instruction, increase access count */ else { IoAccessInstrPrevClock = CyclesGlobalClockCounter; if ( ( table68k[ M68000_CurrentOpcode ].size == 1 ) && ( OpcodeFamily != i_MVMEL ) && ( OpcodeFamily != i_MVMLE ) ) IoAccessInstrCount = 0; /* Instruction size is word and not a movem : no multiple accesses */ else IoAccessInstrCount = 1; /* 1st access of a long or movem.w */ } addr &= 0x00ffffff; /* Use a 24 bit address */ if (addr < IO_MEM_INTERCEPT_START || !is_super_access(true)) { /* invalid memory addressing --> bus error */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, 0); return -1; } if (addr > 0xfffffe) { Log_Printf(LOG_WARN, "Illegal IO memory access: IoMem_wget($%x)\n", addr); return -1; } IoAccessBaseAddress = addr; /* Store for exception frame */ nIoMemAccessSize = SIZE_WORD; nBusErrorAccesses = 0; idx = addr - IO_MEM_INTERCEPT_START; IoAccessCurrentAddress = addr; pInterceptReadTable[idx](); /* Call 1st handler */ if (pInterceptReadTable[idx+1] != pInterceptReadTable[idx]) { IoAccessCurrentAddress = addr + 1; pInterceptReadTable[idx+1](); /* Call 2nd handler */ } /* Check if we completely read from a bus-error region */ if (nBusErrorAccesses == 2) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, 0); return -1; } val = IoMem_ReadWord(addr); LOG_TRACE(TRACE_IOMEM_RD, "IO read.w $%08x = $%04x pc=%x\n", IoAccessFullAddress, val, M68000_GetPC()); return val; } /*-----------------------------------------------------------------------*/ /** * Handle long-word read access from IO memory. */ uae_u32 REGPARAM3 IoMem_lget(uaecptr addr) { uint32_t idx; uint32_t val; int n; IoAccessFullAddress = addr; /* Store initial 32 bits address (eg for bus error stack) */ /* Check if access is made by a new instruction or by the same instruction doing multiple long accesses */ if ( IoAccessInstrPrevClock == CyclesGlobalClockCounter ) IoAccessInstrCount++; /* Same instruction, increase access count */ else { IoAccessInstrPrevClock = CyclesGlobalClockCounter; if ( ( OpcodeFamily != i_MVMEL ) && ( OpcodeFamily != i_MVMLE ) ) IoAccessInstrCount = 0; /* Instruction is not a movem : no multiple accesses */ else IoAccessInstrCount = 1; /* 1st access of a movem.l */ } addr &= 0x00ffffff; /* Use a 24 bit address */ if (addr < IO_MEM_INTERCEPT_START || !is_super_access(true)) { /* invalid memory addressing --> bus error */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, 0); return -1; } if (addr > 0xfffffc) { Log_Printf(LOG_WARN, "Illegal IO memory access: IoMem_lget($%x)\n", addr); return -1; } IoAccessBaseAddress = addr; /* Store for exception frame */ nIoMemAccessSize = SIZE_LONG; nBusErrorAccesses = 0; idx = addr - IO_MEM_INTERCEPT_START; IoAccessCurrentAddress = addr; pInterceptReadTable[idx](); /* Call 1st handler */ for (n = 1; n < nIoMemAccessSize; n++) { if (pInterceptReadTable[idx+n] != pInterceptReadTable[idx+n-1]) { IoAccessCurrentAddress = addr + n; pInterceptReadTable[idx+n](); /* Call n-th handler */ } } /* Check if we completely read from a bus-error region */ if (nBusErrorAccesses == 4) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, 0); return -1; } val = IoMem_ReadLong(addr); LOG_TRACE(TRACE_IOMEM_RD, "IO read.l $%08x = $%08x pc=%x\n", IoAccessFullAddress, val, M68000_GetPC()); return val; } /*-----------------------------------------------------------------------*/ /** * Handle byte write access to IO memory. */ void REGPARAM3 IoMem_bput(uaecptr addr, uae_u32 val) { IoAccessFullAddress = addr; /* Store initial 32 bits address (eg for bus error stack) */ /* Check if access is made by a new instruction or by the same instruction doing multiple byte accesses */ if ( IoAccessInstrPrevClock == CyclesGlobalClockCounter ) IoAccessInstrCount++; /* Same instruction, increase access count */ else { IoAccessInstrPrevClock = CyclesGlobalClockCounter; if ( table68k[ M68000_CurrentOpcode ].size == 0 ) IoAccessInstrCount = 0; /* Instruction size is byte : no multiple accesses */ else IoAccessInstrCount = 1; /* 1st access */ } addr &= 0x00ffffff; /* Use a 24 bit address */ LOG_TRACE(TRACE_IOMEM_WR, "IO write.b $%08x = $%02x pc=%x\n", IoAccessFullAddress, val&0xff, M68000_GetPC()); if (addr < IO_MEM_INTERCEPT_START || !is_super_access(false)) { /* invalid memory addressing --> bus error */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, val); return; } IoAccessBaseAddress = addr; /* Store for exception frame, just in case */ nIoMemAccessSize = SIZE_BYTE; nBusErrorAccesses = 0; IoMem[addr] = val; IoAccessCurrentAddress = addr; pInterceptWriteTable[addr-IO_MEM_INTERCEPT_START](); /* Call handler */ /* Check if we wrote to a bus-error region */ if (nBusErrorAccesses == 1) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, val); } } /*-----------------------------------------------------------------------*/ /** * Handle word write access to IO memory. */ void REGPARAM3 IoMem_wput(uaecptr addr, uae_u32 val) { uint32_t idx; IoAccessFullAddress = addr; /* Store initial 32 bits address (eg for bus error stack) */ /* Check if access is made by a new instruction or by the same instruction doing multiple word accesses */ if ( IoAccessInstrPrevClock == CyclesGlobalClockCounter ) IoAccessInstrCount++; /* Same instruction, increase access count */ else { IoAccessInstrPrevClock = CyclesGlobalClockCounter; if ( ( table68k[ M68000_CurrentOpcode ].size == 1 ) && ( OpcodeFamily != i_MVMEL ) && ( OpcodeFamily != i_MVMLE ) ) IoAccessInstrCount = 0; /* Instruction size is word and not a movem : no multiple accesses */ else IoAccessInstrCount = 1; /* 1st access of a long or movem.w */ } addr &= 0x00ffffff; /* Use a 24 bit address */ LOG_TRACE(TRACE_IOMEM_WR, "IO write.w $%08x = $%04x pc=%x\n", IoAccessFullAddress, val&0xffff, M68000_GetPC()); if (addr < 0x00ff8000 || !is_super_access(false)) { /* invalid memory addressing --> bus error */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, val); return; } if (addr > 0xfffffe) { Log_Printf(LOG_WARN, "Illegal IO memory access: IoMem_wput($%x)\n", addr); return; } IoAccessBaseAddress = addr; /* Store for exception frame, just in case */ nIoMemAccessSize = SIZE_WORD; nBusErrorAccesses = 0; IoMem_WriteWord(addr, val); idx = addr - IO_MEM_INTERCEPT_START; IoAccessCurrentAddress = addr; pInterceptWriteTable[idx](); /* Call 1st handler */ if (pInterceptWriteTable[idx+1] != pInterceptWriteTable[idx]) { IoAccessCurrentAddress = addr + 1; pInterceptWriteTable[idx+1](); /* Call 2nd handler */ } /* Check if we wrote to a bus-error region */ if (nBusErrorAccesses == 2) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_WORD, BUS_ERROR_ACCESS_DATA, val); } } /*-----------------------------------------------------------------------*/ /** * Handle long-word write access to IO memory. */ void REGPARAM3 IoMem_lput(uaecptr addr, uae_u32 val) { uint32_t idx; int n; IoAccessFullAddress = addr; /* Store initial 32 bits address (eg for bus error stack) */ /* Check if access is made by a new instruction or by the same instruction doing multiple long accesses */ if ( IoAccessInstrPrevClock == CyclesGlobalClockCounter ) IoAccessInstrCount++; /* Same instruction, increase access count */ else { IoAccessInstrPrevClock = CyclesGlobalClockCounter; if ( ( OpcodeFamily != i_MVMEL ) && ( OpcodeFamily != i_MVMLE ) ) IoAccessInstrCount = 0; /* Instruction is not a movem : no multiple accesses */ else IoAccessInstrCount = 1; /* 1st access of a movem.l */ } addr &= 0x00ffffff; /* Use a 24 bit address */ LOG_TRACE(TRACE_IOMEM_WR, "IO write.l $%08x = $%08x pc=%x\n", IoAccessFullAddress, val, M68000_GetPC()); if (addr < IO_MEM_INTERCEPT_START || !is_super_access(false)) { /* invalid memory addressing --> bus error */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, val); return; } if (addr > 0xfffffc) { Log_Printf(LOG_WARN, "Illegal IO memory access: IoMem_lput($%x)\n", addr); return; } IoAccessBaseAddress = addr; /* Store for exception frame, just in case */ nIoMemAccessSize = SIZE_LONG; nBusErrorAccesses = 0; IoMem_WriteLong(addr, val); idx = addr - IO_MEM_INTERCEPT_START; IoAccessCurrentAddress = addr; pInterceptWriteTable[idx](); /* Call first handler */ for (n = 1; n < nIoMemAccessSize; n++) { if (pInterceptWriteTable[idx+n] != pInterceptWriteTable[idx+n-1]) { IoAccessCurrentAddress = addr + n; pInterceptWriteTable[idx+n](); /* Call n-th handler */ } } /* Check if we wrote to a bus-error region */ if (nBusErrorAccesses == 4) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_LONG, BUS_ERROR_ACCESS_DATA, val); } } /*-------------------------------------------------------------------------*/ /** * Check if an address inside the IO mem region would return a bus error in case of a read/write access * We only check if it would give a bus error on read access, as in our case it would give * a bus error too in case of a write */ bool IoMem_CheckBusError ( uint32_t addr ) { addr &= 0xffff; if ( addr < 0x8000 ) return true; if ( ( pInterceptReadTable[ addr - 0x8000 ] == IoMem_BusErrorOddReadAccess ) || ( pInterceptReadTable[ addr - 0x8000 ] == IoMem_BusErrorEvenReadAccess ) ) return true; return false; } /*-------------------------------------------------------------------------*/ /** * This handler will be called if a ST program tries to read from an address * that causes a bus error on a real ST. However, we can't call M68000_BusError() * directly: For example, a "move.b $ff8204,d0" triggers a bus error on a real ST, * while a "move.w $ff8204,d0" works! So we have to count the accesses to bus error * addresses and we only trigger a bus error later if the count matches the complete * access size (e.g. nBusErrorAccesses==4 for a long word access). */ void IoMem_BusErrorEvenReadAccess(void) { nBusErrorAccesses += 1; IoMem[IoAccessCurrentAddress] = 0xff; } /** * We need two handler so that the IoMem_*get functions can distinguish * consecutive addresses. */ void IoMem_BusErrorOddReadAccess(void) { nBusErrorAccesses += 1; IoMem[IoAccessCurrentAddress] = 0xff; } /*-------------------------------------------------------------------------*/ /** * Same as IoMem_BusErrorReadAccess() but for write access this time. */ void IoMem_BusErrorEvenWriteAccess(void) { nBusErrorAccesses += 1; } /** * We need two handler so that the IoMem_*put functions can distinguish * consecutive addresses. */ void IoMem_BusErrorOddWriteAccess(void) { nBusErrorAccesses += 1; } /*-------------------------------------------------------------------------*/ /** * This is the read handler for the IO memory locations without an assigned * IO register and which also do not generate a bus error. Reading from such * a register will return the result 0xff. */ void IoMem_VoidRead(void) { uint32_t a; /* handler is probably called only once, so we have to take care of the neighbour "void IO registers" */ for (a = IoAccessBaseAddress; a < IoAccessBaseAddress + nIoMemAccessSize; a++) { if (pInterceptReadTable[a - IO_MEM_INTERCEPT_START] == IoMem_VoidRead) { IoMem[a] = 0xff; } } } /*-------------------------------------------------------------------------*/ /** * This is the same function as IoMem_VoidRead, but for IO registers that * return 0x00 instead of 0xff when read (this is the case for some video * registers on STE, Falcon, ...) */ void IoMem_VoidRead_00(void) { uint32_t a; /* handler is probably called only once, so we have to take care of the neighbour "void IO registers" */ for (a = IoAccessBaseAddress; a < IoAccessBaseAddress + nIoMemAccessSize; a++) { if (pInterceptReadTable[a - IO_MEM_INTERCEPT_START] == IoMem_VoidRead_00) { IoMem[a] = 0x00; } } } /*-------------------------------------------------------------------------*/ /** * This is the write handler for the IO memory locations without an assigned * IO register and which also do not generate a bus error. We simply ignore * a write access to these registers. */ void IoMem_VoidWrite(void) { /* Nothing... */ } /*-------------------------------------------------------------------------*/ /** * A dummy function that does nothing at all - for memory regions that don't * need a special handler for read access. */ void IoMem_ReadWithoutInterception(void) { /* Nothing... */ } /*-------------------------------------------------------------------------*/ /** * A dummy function that does nothing at all - for memory regions that don't * need a special handler for write access. */ void IoMem_WriteWithoutInterception(void) { /* Nothing... */ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ioMemTabFalcon.c000066400000000000000000000762001504763705000243210ustar00rootroot00000000000000/* Hatari - ioMemTabFalcon.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Table with hardware IO handlers for the Falcon. */ const char IoMemTabFalc_fileid[] = "Hatari ioMemTabFalcon.c"; #include "main.h" #include "configuration.h" #include "fdc.h" #include "acia.h" #include "ioMem.h" #include "ioMemTables.h" #include "m68000.h" #include "joy.h" #include "mfp.h" #include "midi.h" #include "nvram.h" #include "psg.h" #include "rs232.h" #include "rtc.h" #include "scc.h" #include "blitter.h" #include "crossbar.h" #include "falcon/videl.h" #include "configuration.h" #include "statusbar.h" #include "stMemory.h" #if ENABLE_DSP_EMU #include "falcon/dsp.h" #endif /** * no DSP */ void IoMemTabFalcon_DSPnone(void (**readtab)(void), void (**writetab)(void)) { IoMem_Intercept ( 0xffa200 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa201 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa202 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa203 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa204 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa205 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa206 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa207 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); /* TODO: why is this needed ? */ IoMem_Intercept ( 0xffa202 , IoMem_VoidRead , IoMem_WriteWithoutInterception ); } /** * Just a temporary hack - some programs are polling on this register and * are expecting the handshake bit (#7) to change after a while... */ static void DSP_DummyHostCommand_ReadByte(void) { IoMem[0xffa201] ^= 0x80; } /** * Just a temporary hack - some programs are polling on this register and * are expecting some bits to change after a while... */ static void DSP_DummyInterruptStatus_ReadByte(void) { IoMem[0xffa202] ^= 0xff; } /** * dummy IO when DSP emulation is not enabled */ void IoMemTabFalcon_DSPdummy(void (**readtab)(void), void (**writetab)(void)) { IoMem_Intercept ( 0xffa200 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa201 , DSP_DummyHostCommand_ReadByte , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa202 , DSP_DummyInterruptStatus_ReadByte , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa203 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa204 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa205 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa206 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); IoMem_Intercept ( 0xffa207 , IoMem_ReadWithoutInterception , IoMem_WriteWithoutInterception ); } #if ENABLE_DSP_EMU /** * enable DSP emulation */ void IoMemTabFalcon_DSPemulation(void (**readtab)(void), void (**writetab)(void)) { int i; for (i = 0; i < 8; i++) { IoMem_Intercept ( 0xffa200+i , DSP_HandleReadAccess , DSP_HandleWriteAccess ); } } #endif /** * Take into account the Falcon Bus Control register $ff8007.b $FFFF8007 Falcon Bus Control BIT 6 : F30 Start (0=Cold, 1=Warm) BIT 5 : STe Bus Emulation (0=on) BIT 3 : Blitter Flag (0=on, 1=off) BIT 2 : Blitter (0=8mhz, 1=16mhz) BIT 0 : 68030 (0=8mhz, 1=16mhz) */ static void IoMemTabFalcon_BusCtrl_WriteByte(void) { uint8_t busCtrl = IoMem_ReadByte(0xff8007); /* Set Falcon bus or STE compatible bus emulation */ if ((busCtrl & 0x20) == 0) IoMem_SetFalconBusMode(STE_BUS_COMPATIBLE); else IoMem_SetFalconBusMode(FALCON_ONLY_BUS); /* 68030 Frequency changed ? We change freq only in 68030 mode * for a normal Falcon, not if CPU is 68040 or 68060 is used, * or if the user requested a faster frequency manually */ if (ConfigureParams.System.nCpuLevel == 3 && ConfigureParams.System.nCpuFreq <= 16) { if ((busCtrl & 0x1) == 1) { /* 16 Mhz bus for 68030 */ Configuration_ChangeCpuFreq ( 16 ); } else { /* 8 Mhz bus for 68030 */ Configuration_ChangeCpuFreq ( 8 ); } } Statusbar_UpdateInfo(); /* Update clock speed in the status bar */ } static void IoMemTabFalcon_BusCtrl_ReadByte(void) { uint8_t nBusCtrl = IoMem_ReadByte(0xff8007); /* Set the bit manually to get it right after cold boot */ if (IoMem_IsFalconBusMode()) nBusCtrl |= 0x20; else nBusCtrl &= ~0x20; if (ConfigureParams.System.nCpuFreq == 8) nBusCtrl &= ~1; else nBusCtrl |= 1; IoMem_WriteByte(0xff8007, nBusCtrl); } /** * This register represents the configuration switches ("half moon" soldering * points) on the Falcon's motherboard at location U46 and U47. The meaning * of the switches is the following: * * 1-5 Not used * 6 Connected = Quad Density Floppy; not connected = Don't care * 7 Connected = AJAX FDC (1.44MB); not connected = 1772 FDC (720K) * 8 Connected = No DMA sound; not connected = DMA Sound available * * Logic is inverted, i.e. connected means the corresponding bit is 0. * Switch 8 is represented by the highest bit in the register. */ uint8_t IoMemTabFalcon_DIPSwitches_Read(void) { return 0xbf; } /** * Some IO memory ranges do not result in a bus error when accessed * in STE-compatible bus mode and with single byte access. */ static void IoMemTabFalc_Compatible_ReadByte(void) { if (nIoMemAccessSize != SIZE_BYTE || IoMem_IsFalconBusMode()) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, nIoMemAccessSize, BUS_ERROR_ACCESS_DATA, 0); } } static void IoMemTabFalc_Compatible_WriteByte(void) { if (nIoMemAccessSize != SIZE_BYTE || IoMem_IsFalconBusMode()) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, nIoMemAccessSize, BUS_ERROR_ACCESS_DATA, 0); } } /** * Some IO memory ranges do not result in a bus error when * accessed in STE-compatible bus mode and with word access. */ static void IoMemTabFalc_Compatible_ReadWord(void) { if (nIoMemAccessSize == SIZE_BYTE || IoMem_IsFalconBusMode()) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, nIoMemAccessSize, BUS_ERROR_ACCESS_DATA, 0); } } static void IoMemTabFalc_Compatible_WriteWord(void) { if (nIoMemAccessSize == SIZE_BYTE || IoMem_IsFalconBusMode()) { M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, nIoMemAccessSize, BUS_ERROR_ACCESS_DATA,0); } } /*-----------------------------------------------------------------------*/ /* List of functions to handle read/write hardware interceptions for a Falcon. */ const INTERCEPT_ACCESS_FUNC IoMemTable_Falcon[] = { { 0xff8000, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8001, SIZE_BYTE, STMemory_MMU_Config_ReadByte, STMemory_MMU_Config_WriteByte }, /* Memory configuration */ { 0xff8006, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_Monitor_WriteByte }, /* Falcon monitor and memory configuration */ { 0xff8007, SIZE_BYTE, IoMemTabFalcon_BusCtrl_ReadByte, IoMemTabFalcon_BusCtrl_WriteByte }, /* Falcon bus configuration */ { 0xff800C, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8200, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8201, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_ScreenBase_WriteByte }, /* Video base high byte */ { 0xff8202, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8203, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_ScreenBase_WriteByte }, /* Video base med byte */ { 0xff8204, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8205, SIZE_BYTE, VIDEL_ScreenCounter_ReadByte, VIDEL_ScreenCounter_WriteByte }, { 0xff8206, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8207, SIZE_BYTE, VIDEL_ScreenCounter_ReadByte, VIDEL_ScreenCounter_WriteByte }, { 0xff8208, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8209, SIZE_BYTE, VIDEL_ScreenCounter_ReadByte, VIDEL_ScreenCounter_WriteByte }, { 0xff820a, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_SyncMode_WriteByte }, /* VIDEL Synch mode */ { 0xff820b, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here : return 0 not ff */ { 0xff820c, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here : return 0 not ff */ { 0xff820d, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_ScreenBase_WriteByte }, /* Video base low byte */ { 0xff820e, SIZE_WORD, VIDEL_LineOffset_ReadWord, VIDEL_LineOffset_WriteWord }, /* Falcon line offset */ { 0xff8210, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_Line_Width_WriteWord }, /* Falcon line width */ { 0xff8212, 46 , IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8240, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color0_WriteWord }, /* ST COLOR 0 */ { 0xff8242, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color1_WriteWord }, /* ST COLOR 1 */ { 0xff8244, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color2_WriteWord }, /* ST COLOR 2 */ { 0xff8246, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color3_WriteWord }, /* ST COLOR 3 */ { 0xff8248, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color4_WriteWord }, /* ST COLOR 4 */ { 0xff824a, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color5_WriteWord }, /* ST COLOR 5 */ { 0xff824c, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color6_WriteWord }, /* ST COLOR 6 */ { 0xff824e, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color7_WriteWord }, /* ST COLOR 7 */ { 0xff8250, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color8_WriteWord }, /* ST COLOR 8 */ { 0xff8252, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color9_WriteWord }, /* ST COLOR 9 */ { 0xff8254, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color10_WriteWord }, /* ST COLOR 10 */ { 0xff8256, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color11_WriteWord }, /* ST COLOR 11 */ { 0xff8258, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color12_WriteWord }, /* ST COLOR 12 */ { 0xff825a, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color13_WriteWord }, /* ST COLOR 13 */ { 0xff825c, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color14_WriteWord }, /* ST COLOR 14 */ { 0xff825e, SIZE_WORD, IoMem_ReadWithoutInterception, Videl_Color15_WriteWord }, /* ST COLOR 15 */ { 0xff8260, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_ST_ShiftModeWriteByte }, { 0xff8261, 3 , IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus errors here : return 0 not ff */ { 0xff8264, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_HorScroll64_WriteByte }, /* Falcon horizontal fine scrolling high ? */ { 0xff8265, SIZE_BYTE, IoMem_ReadWithoutInterception, VIDEL_HorScroll65_WriteByte }, /* horizontal fine scrolling */ { 0xff8266, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_Falcon_ShiftMode_WriteWord }, /* Falcon shift mode */ { 0xff8268, 24 , IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus errors here : return 0 not ff */ { 0xff8280, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HHC_WriteWord }, /* HHC : Horizontal Hold Counter */ { 0xff8282, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HHT_WriteWord }, /* HHT : Horizontal Hold Timer */ { 0xff8284, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HBB_WriteWord }, /* HBB : Horizontal Border Begin */ { 0xff8286, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HBE_WriteWord }, /* HBE : Horizontal Border End */ { 0xff8288, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HDB_WriteWord }, /* HDB : Horizontal Display Begin */ { 0xff828a, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HDE_WriteWord }, /* HDE : Horizontal Display End */ { 0xff828c, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HSS_WriteWord }, /* HSS : Horizontal SS */ { 0xff828e, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HFS_WriteWord }, /* HFS : Horizontal FS */ { 0xff8290, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_HEE_WriteWord }, /* HEE : Horizontal EE */ { 0xff8292, 14, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff82a0, SIZE_WORD, VIDEL_VFC_ReadWord, IoMem_VoidWrite }, /* VFC - Vertical Frequency Counter */ { 0xff82a2, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VFT_WriteWord }, /* VFT - Vertical Frequency Timer */ { 0xff82a4, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VBB_WriteWord }, /* VBB - Vertical Border Begin */ { 0xff82a6, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VBE_WriteWord }, /* VBE - Vertical Border End */ { 0xff82a8, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VDB_WriteWord }, /* VDB - Vertical Display Begin */ { 0xff82aa, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VDE_WriteWord }, /* VDE - Vertical Display End */ { 0xff82ac, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VSS_WriteWord }, /* VSS - Vertical SS */ { 0xff82ae, 18, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff82c0, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VCO_WriteWord }, /* VCO - Video control */ { 0xff82c2, SIZE_WORD, IoMem_ReadWithoutInterception, VIDEL_VMD_WriteWord }, /* VMD - Video mode */ { 0xff8560, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xff8564, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xff8604, SIZE_WORD, FDC_DiskControllerStatus_ReadWord, FDC_DiskController_WriteWord }, { 0xff8606, SIZE_WORD, FDC_DmaStatus_ReadWord, FDC_DmaModeControl_WriteWord }, { 0xff8608, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8609, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter high byte */ { 0xff860a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860b, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter med byte */ { 0xff860c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860d, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter low byte */ { 0xff860e, SIZE_WORD, FDC_DensityMode_ReadWord , FDC_DensityMode_WriteWord }, /* Choose DD/HD mode */ { 0xff8800, SIZE_BYTE, PSG_ff8800_ReadByte, PSG_ff8800_WriteByte }, { 0xff8801, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8801_WriteByte }, { 0xff8802, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8802_WriteByte }, { 0xff8803, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8803_WriteByte }, { 0xff8900, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_BufferInter_WriteByte }, /* Crossbar Buffer interrupts */ { 0xff8901, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_DmaCtrlReg_WriteByte }, /* Crossbar control register */ { 0xff8902, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8903, SIZE_BYTE, Crossbar_FrameStartHigh_ReadByte, Crossbar_FrameStartHigh_WriteByte }, /* DMA sound frame start high */ { 0xff8904, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8905, SIZE_BYTE, Crossbar_FrameStartMed_ReadByte, Crossbar_FrameStartMed_WriteByte }, /* DMA sound frame start med */ { 0xff8906, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8907, SIZE_BYTE, Crossbar_FrameStartLow_ReadByte, Crossbar_FrameStartLow_WriteByte }, /* DMA sound frame start low */ { 0xff8908, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8909, SIZE_BYTE, Crossbar_FrameCountHigh_ReadByte, Crossbar_FrameCountHigh_WriteByte }, /* DMA sound frame count high */ { 0xff890a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890b, SIZE_BYTE, Crossbar_FrameCountMed_ReadByte, Crossbar_FrameCountMed_WriteByte }, /* DMA sound frame count med */ { 0xff890c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890d, SIZE_BYTE, Crossbar_FrameCountLow_ReadByte, Crossbar_FrameCountLow_WriteByte }, /* DMA sound frame count low */ { 0xff890e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890f, SIZE_BYTE, Crossbar_FrameEndHigh_ReadByte, Crossbar_FrameEndHigh_WriteByte }, /* DMA sound frame end high */ { 0xff8910, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8911, SIZE_BYTE, Crossbar_FrameEndMed_ReadByte, Crossbar_FrameEndMed_WriteByte }, /* DMA sound frame end med */ { 0xff8912, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8913, SIZE_BYTE, Crossbar_FrameEndLow_ReadByte, Crossbar_FrameEndLow_WriteByte }, /* DMA sound frame end low */ { 0xff8920, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_DmaTrckCtrl_WriteByte }, /* Crossbar track control */ { 0xff8921, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_SoundModeCtrl_WriteByte }, /* DMA sound mode control */ { 0xff8922, SIZE_WORD, IoMem_VoidRead_00, IoMem_VoidWrite }, /* Microwire data - n/a on Falcon, alwayes read 0 */ { 0xff8924, SIZE_WORD, IoMem_ReadWithoutInterception, Crossbar_Microwire_WriteWord }, /* Microwire mask - n/a on Falcon, see crossbar.c */ { 0xff8930, SIZE_WORD, IoMem_ReadWithoutInterception, Crossbar_SrcControler_WriteWord }, /* Crossbar source controller */ { 0xff8932, SIZE_WORD, IoMem_ReadWithoutInterception, Crossbar_DstControler_WriteWord }, /* Crossbar destination controller */ { 0xff8934, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_FreqDivExt_WriteByte }, /* External clock divider */ { 0xff8935, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_FreqDivInt_WriteByte }, /* Internal clock divider */ { 0xff8936, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_TrackRecSelect_WriteByte }, /* Track record select */ { 0xff8937, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_CodecInput_WriteByte }, /* CODEC input source from 16 bits adder */ { 0xff8938, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_AdcInput_WriteByte }, /* ADC converter input for L+R channel */ { 0xff8939, SIZE_BYTE, IoMem_ReadWithoutInterception, Crossbar_InputAmp_WriteByte }, /* Input amplifier (+1.5 dB step) */ { 0xff893a, SIZE_WORD, IoMem_ReadWithoutInterception, Crossbar_OutputReduct_WriteWord }, /* Output reduction (-1.5 dB step) */ { 0xff893c, SIZE_WORD, IoMem_ReadWithoutInterception, Crossbar_CodecStatus_WriteWord }, /* CODEC status */ { 0xff893e, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* No bus error here */ { 0xff8940, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* GPx direction */ { 0xff8942, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* GPx port */ { 0xff8960, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8961, SIZE_BYTE, NvRam_Select_ReadByte, NvRam_Select_WriteByte }, /* NVRAM/RTC chip */ { 0xff8962, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8963, SIZE_BYTE, NvRam_Data_ReadByte, NvRam_Data_WriteByte }, /* NVRAM/RTC chip */ { 0xff8a00, SIZE_WORD, Blitter_Halftone00_ReadWord, Blitter_Halftone00_WriteWord }, /* Blitter halftone RAM 0 */ { 0xff8a02, SIZE_WORD, Blitter_Halftone01_ReadWord, Blitter_Halftone01_WriteWord }, /* Blitter halftone RAM 1 */ { 0xff8a04, SIZE_WORD, Blitter_Halftone02_ReadWord, Blitter_Halftone02_WriteWord }, /* Blitter halftone RAM 2 */ { 0xff8a06, SIZE_WORD, Blitter_Halftone03_ReadWord, Blitter_Halftone03_WriteWord }, /* Blitter halftone RAM 3 */ { 0xff8a08, SIZE_WORD, Blitter_Halftone04_ReadWord, Blitter_Halftone04_WriteWord }, /* Blitter halftone RAM 4 */ { 0xff8a0a, SIZE_WORD, Blitter_Halftone05_ReadWord, Blitter_Halftone05_WriteWord }, /* Blitter halftone RAM 5 */ { 0xff8a0c, SIZE_WORD, Blitter_Halftone06_ReadWord, Blitter_Halftone06_WriteWord }, /* Blitter halftone RAM 6 */ { 0xff8a0e, SIZE_WORD, Blitter_Halftone07_ReadWord, Blitter_Halftone07_WriteWord }, /* Blitter halftone RAM 7 */ { 0xff8a10, SIZE_WORD, Blitter_Halftone08_ReadWord, Blitter_Halftone08_WriteWord }, /* Blitter halftone RAM 8 */ { 0xff8a12, SIZE_WORD, Blitter_Halftone09_ReadWord, Blitter_Halftone09_WriteWord }, /* Blitter halftone RAM 9 */ { 0xff8a14, SIZE_WORD, Blitter_Halftone10_ReadWord, Blitter_Halftone10_WriteWord }, /* Blitter halftone RAM 10 */ { 0xff8a16, SIZE_WORD, Blitter_Halftone11_ReadWord, Blitter_Halftone11_WriteWord }, /* Blitter halftone RAM 11 */ { 0xff8a18, SIZE_WORD, Blitter_Halftone12_ReadWord, Blitter_Halftone12_WriteWord }, /* Blitter halftone RAM 12 */ { 0xff8a1a, SIZE_WORD, Blitter_Halftone13_ReadWord, Blitter_Halftone13_WriteWord }, /* Blitter halftone RAM 13 */ { 0xff8a1c, SIZE_WORD, Blitter_Halftone14_ReadWord, Blitter_Halftone14_WriteWord }, /* Blitter halftone RAM 14 */ { 0xff8a1e, SIZE_WORD, Blitter_Halftone15_ReadWord, Blitter_Halftone15_WriteWord }, /* Blitter halftone RAM 15 */ { 0xff8a20, SIZE_WORD, Blitter_SourceXInc_ReadWord, Blitter_SourceXInc_WriteWord }, /* Blitter source x increment */ { 0xff8a22, SIZE_WORD, Blitter_SourceYInc_ReadWord, Blitter_SourceYInc_WriteWord }, /* Blitter source y increment */ { 0xff8a24, SIZE_LONG, Blitter_SourceAddr_ReadLong, Blitter_SourceAddr_WriteLong }, /* Blitter source address */ { 0xff8a28, SIZE_WORD, Blitter_Endmask1_ReadWord, Blitter_Endmask1_WriteWord }, { 0xff8a2a, SIZE_WORD, Blitter_Endmask2_ReadWord, Blitter_Endmask2_WriteWord }, { 0xff8a2c, SIZE_WORD, Blitter_Endmask3_ReadWord, Blitter_Endmask3_WriteWord }, { 0xff8a2e, SIZE_WORD, Blitter_DestXInc_ReadWord, Blitter_DestXInc_WriteWord }, /* Blitter dest. x increment */ { 0xff8a30, SIZE_WORD, Blitter_DestYInc_ReadWord, Blitter_DestYInc_WriteWord }, /* Blitter dest. y increment */ { 0xff8a32, SIZE_LONG, Blitter_DestAddr_ReadLong, Blitter_DestAddr_WriteLong }, { 0xff8a36, SIZE_WORD, Blitter_WordsPerLine_ReadWord, Blitter_WordsPerLine_WriteWord }, { 0xff8a38, SIZE_WORD, Blitter_LinesPerBitblock_ReadWord, Blitter_LinesPerBitblock_WriteWord }, { 0xff8a3a, SIZE_BYTE, Blitter_HalftoneOp_ReadByte, Blitter_HalftoneOp_WriteByte }, { 0xff8a3b, SIZE_BYTE, Blitter_LogOp_ReadByte, Blitter_LogOp_WriteByte }, { 0xff8a3c, SIZE_BYTE, Blitter_Control_ReadByte, Blitter_Control_WriteByte }, { 0xff8a3d, SIZE_BYTE, Blitter_Skew_ReadByte, Blitter_Skew_WriteByte }, { 0xff8a3e, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c80, 8, SCC_IoMem_ReadByte, SCC_IoMem_WriteByte }, /* SCC */ { 0xff9200, SIZE_WORD, Joy_StePadButtons_DIPSwitches_ReadWord, Joy_StePadButtons_DIPSwitches_WriteWord }, /* Joypad fire buttons + Falcon DIP Switches */ { 0xff9202, SIZE_WORD, Joy_StePadMulti_ReadWord, Joy_StePadMulti_WriteWord }, /* Joypad directions/buttons/selection */ { 0xff9210, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff9211, SIZE_BYTE, Joy_StePadAnalog0X_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 0 Analog/Paddle X position */ { 0xff9212, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff9213, SIZE_BYTE, Joy_StePadAnalog0Y_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 0 Analog/Paddle Y position */ { 0xff9214, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff9215, SIZE_BYTE, Joy_StePadAnalog1X_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 1 Analog/Paddle X position */ { 0xff9216, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff9217, SIZE_BYTE, Joy_StePadAnalog1Y_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 1 Analog/Paddle Y position */ { 0xff9220, SIZE_WORD, IoMem_VoidRead, IoMem_WriteWithoutInterception }, /* Lightpen X position */ { 0xff9222, SIZE_WORD, IoMem_VoidRead, IoMem_WriteWithoutInterception }, /* Lightpen Y position */ { 0xff9800, 0x400, IoMem_ReadWithoutInterception, VIDEL_FalconColorRegsWrite }, /* Falcon Videl palette */ { 0xffc020, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xffc021, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xffd020, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xffd074, SIZE_WORD, IoMemTabFalc_Compatible_ReadWord, IoMemTabFalc_Compatible_WriteWord }, { 0xffd420, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xffd425, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte }, { 0xffd520, SIZE_WORD, IoMemTabFalc_Compatible_ReadWord, IoMemTabFalc_Compatible_WriteWord }, { 0xffd530, SIZE_WORD, IoMemTabFalc_Compatible_ReadWord, IoMemTabFalc_Compatible_WriteWord }, { 0xfffa00, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte }, { 0xfffa02, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte }, { 0xfffa04, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte }, { 0xfffa06, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa07, SIZE_BYTE, MFP_EnableA_ReadByte, MFP_EnableA_WriteByte }, { 0xfffa08, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa09, SIZE_BYTE, MFP_EnableB_ReadByte, MFP_EnableB_WriteByte }, { 0xfffa0a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa0b, SIZE_BYTE, MFP_PendingA_ReadByte, MFP_PendingA_WriteByte }, { 0xfffa0c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa0d, SIZE_BYTE, MFP_PendingB_ReadByte, MFP_PendingB_WriteByte }, { 0xfffa0e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa0f, SIZE_BYTE, MFP_InServiceA_ReadByte, MFP_InServiceA_WriteByte }, { 0xfffa10, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa11, SIZE_BYTE, MFP_InServiceB_ReadByte, MFP_InServiceB_WriteByte }, { 0xfffa12, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa13, SIZE_BYTE, MFP_MaskA_ReadByte, MFP_MaskA_WriteByte }, { 0xfffa14, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa15, SIZE_BYTE, MFP_MaskB_ReadByte, MFP_MaskB_WriteByte }, { 0xfffa16, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa17, SIZE_BYTE, MFP_VectorReg_ReadByte, MFP_VectorReg_WriteByte }, { 0xfffa18, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa19, SIZE_BYTE, MFP_TimerACtrl_ReadByte, MFP_TimerACtrl_WriteByte }, { 0xfffa1a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa1b, SIZE_BYTE, MFP_TimerBCtrl_ReadByte, MFP_TimerBCtrl_WriteByte }, { 0xfffa1c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa1d, SIZE_BYTE, MFP_TimerCDCtrl_ReadByte, MFP_TimerCDCtrl_WriteByte }, { 0xfffa1e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa1f, SIZE_BYTE, MFP_TimerAData_ReadByte, MFP_TimerAData_WriteByte }, { 0xfffa20, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa21, SIZE_BYTE, MFP_TimerBData_ReadByte, MFP_TimerBData_WriteByte }, { 0xfffa22, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa23, SIZE_BYTE, MFP_TimerCData_ReadByte, MFP_TimerCData_WriteByte }, { 0xfffa24, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa25, SIZE_BYTE, MFP_TimerDData_ReadByte, MFP_TimerDData_WriteByte }, { 0xfffa26, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa27, SIZE_BYTE, RS232_SCR_ReadByte, RS232_SCR_WriteByte }, /* Sync character register */ { 0xfffa28, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa29, SIZE_BYTE, RS232_UCR_ReadByte, RS232_UCR_WriteByte }, /* USART control register */ { 0xfffa2a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa2b, SIZE_BYTE, RS232_RSR_ReadByte, RS232_RSR_WriteByte }, /* Receiver status register */ { 0xfffa2c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa2d, SIZE_BYTE, RS232_TSR_ReadByte, RS232_TSR_WriteByte }, /* Transmitter status register */ { 0xfffa2e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa2f, SIZE_BYTE, RS232_UDR_ReadByte, RS232_UDR_WriteByte }, /* USART data register */ { 0xfffc00, SIZE_BYTE, ACIA_IKBD_Read_SR, ACIA_IKBD_Write_CR }, { 0xfffc01, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc02, SIZE_BYTE, ACIA_IKBD_Read_RDR, ACIA_IKBD_Write_TDR }, { 0xfffc03, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc04, SIZE_BYTE, Midi_Control_ReadByte, Midi_Control_WriteByte }, { 0xfffc05, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc06, SIZE_BYTE, Midi_Data_ReadByte, Midi_Data_WriteByte }, { 0xfffc07, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xffff82, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0, 0, NULL, NULL } }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ioMemTabST.c000066400000000000000000000342151504763705000234450ustar00rootroot00000000000000/* Hatari - ioMemTabST.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Table with hardware IO handlers for the ST. */ /* 2007/04/29 [NP] Functions PSG_Void_WriteByte and PSG_Void_ReadByte to handle */ /* accesses to $ff8801/03. These addresses have no effect, but */ /* they give some wait states (e.g. move.l d0,ff8800). */ /* 2007/12/16 [NP] 0xff820d/0xff820f are only available on STE, not on ST. We call */ /* IoMem_VoidRead and IoMem_VoidWrite for these addresses. */ /* 2008/12/21 [NP] Change functions used to access 0xff88xx (see psg.c) */ const char IoMemTabST_fileid[] = "Hatari ioMemTabST.c"; #include "main.h" #include "configuration.h" #include "dmaSnd.h" #include "fdc.h" #include "acia.h" #include "ioMem.h" #include "ioMemTables.h" #include "joy.h" #include "mfp.h" #include "midi.h" #include "psg.h" #include "rs232.h" #include "rtc.h" #include "video.h" #include "blitter.h" #include "stMemory.h" /*-----------------------------------------------------------------------*/ /* List of functions to handle read/write hardware interceptions for a plain ST. */ const INTERCEPT_ACCESS_FUNC IoMemTable_ST[] = { { 0xff8001, SIZE_BYTE, STMemory_MMU_Config_ReadByte, STMemory_MMU_Config_WriteByte }, /* Memory configuration */ { 0xff8201, SIZE_BYTE, IoMem_ReadWithoutInterception, Video_ScreenBase_WriteByte }, /* Video base high byte */ { 0xff8203, SIZE_BYTE, IoMem_ReadWithoutInterception, Video_ScreenBase_WriteByte }, /* Video base med byte */ { 0xff8205, SIZE_BYTE, Video_ScreenCounter_ReadByte, IoMem_WriteWithoutInterception }, { 0xff8207, SIZE_BYTE, Video_ScreenCounter_ReadByte, IoMem_WriteWithoutInterception }, { 0xff8209, SIZE_BYTE, Video_ScreenCounter_ReadByte, IoMem_WriteWithoutInterception }, { 0xff820a, SIZE_BYTE, Video_Sync_ReadByte, Video_Sync_WriteByte }, { 0xff820b, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff820d, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, { 0xff8240, SIZE_WORD, Video_Color0_ReadWord, Video_Color0_WriteWord }, /* COLOR 0 */ { 0xff8242, SIZE_WORD, Video_Color1_ReadWord, Video_Color1_WriteWord }, /* COLOR 1 */ { 0xff8244, SIZE_WORD, Video_Color2_ReadWord, Video_Color2_WriteWord }, /* COLOR 2 */ { 0xff8246, SIZE_WORD, Video_Color3_ReadWord, Video_Color3_WriteWord }, /* COLOR 3 */ { 0xff8248, SIZE_WORD, Video_Color4_ReadWord, Video_Color4_WriteWord }, /* COLOR 4 */ { 0xff824a, SIZE_WORD, Video_Color5_ReadWord, Video_Color5_WriteWord }, /* COLOR 5 */ { 0xff824c, SIZE_WORD, Video_Color6_ReadWord, Video_Color6_WriteWord }, /* COLOR 6 */ { 0xff824e, SIZE_WORD, Video_Color7_ReadWord, Video_Color7_WriteWord }, /* COLOR 7 */ { 0xff8250, SIZE_WORD, Video_Color8_ReadWord, Video_Color8_WriteWord }, /* COLOR 8 */ { 0xff8252, SIZE_WORD, Video_Color9_ReadWord, Video_Color9_WriteWord }, /* COLOR 9 */ { 0xff8254, SIZE_WORD, Video_Color10_ReadWord, Video_Color10_WriteWord }, /* COLOR 10 */ { 0xff8256, SIZE_WORD, Video_Color11_ReadWord, Video_Color11_WriteWord }, /* COLOR 11 */ { 0xff8258, SIZE_WORD, Video_Color12_ReadWord, Video_Color12_WriteWord }, /* COLOR 12 */ { 0xff825a, SIZE_WORD, Video_Color13_ReadWord, Video_Color13_WriteWord }, /* COLOR 13 */ { 0xff825c, SIZE_WORD, Video_Color14_ReadWord, Video_Color14_WriteWord }, /* COLOR 14 */ { 0xff825e, SIZE_WORD, Video_Color15_ReadWord, Video_Color15_WriteWord }, /* COLOR 15 */ { 0xff8260, SIZE_BYTE, Video_ResGlueShifter_ReadByte, Video_ResGlueShifter_WriteByte }, /* Resolution in GLUE/Shifter */ { 0xff8261, SIZE_BYTE, Video_ResShifter_ReadByte, Video_ResShifter_WriteByte }, /* Resolution in Shifter only */ { 0xff8262, 30, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff8604, SIZE_WORD, FDC_DiskControllerStatus_ReadWord, FDC_DiskController_WriteWord }, { 0xff8606, SIZE_WORD, FDC_DmaStatus_ReadWord, FDC_DmaModeControl_WriteWord }, { 0xff8609, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter high byte */ { 0xff860b, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter med byte */ { 0xff860d, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter low byte */ { 0xff8800, SIZE_BYTE, PSG_ff8800_ReadByte, PSG_ff8800_WriteByte }, { 0xff8801, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8801_WriteByte }, { 0xff8802, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8802_WriteByte }, { 0xff8803, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8803_WriteByte }, { 0xff8a00, SIZE_WORD, Blitter_Halftone00_ReadWord, Blitter_Halftone00_WriteWord }, /* Blitter halftone RAM 0 */ { 0xff8a02, SIZE_WORD, Blitter_Halftone01_ReadWord, Blitter_Halftone01_WriteWord }, /* Blitter halftone RAM 1 */ { 0xff8a04, SIZE_WORD, Blitter_Halftone02_ReadWord, Blitter_Halftone02_WriteWord }, /* Blitter halftone RAM 2 */ { 0xff8a06, SIZE_WORD, Blitter_Halftone03_ReadWord, Blitter_Halftone03_WriteWord }, /* Blitter halftone RAM 3 */ { 0xff8a08, SIZE_WORD, Blitter_Halftone04_ReadWord, Blitter_Halftone04_WriteWord }, /* Blitter halftone RAM 4 */ { 0xff8a0a, SIZE_WORD, Blitter_Halftone05_ReadWord, Blitter_Halftone05_WriteWord }, /* Blitter halftone RAM 5 */ { 0xff8a0c, SIZE_WORD, Blitter_Halftone06_ReadWord, Blitter_Halftone06_WriteWord }, /* Blitter halftone RAM 6 */ { 0xff8a0e, SIZE_WORD, Blitter_Halftone07_ReadWord, Blitter_Halftone07_WriteWord }, /* Blitter halftone RAM 7 */ { 0xff8a10, SIZE_WORD, Blitter_Halftone08_ReadWord, Blitter_Halftone08_WriteWord }, /* Blitter halftone RAM 8 */ { 0xff8a12, SIZE_WORD, Blitter_Halftone09_ReadWord, Blitter_Halftone09_WriteWord }, /* Blitter halftone RAM 9 */ { 0xff8a14, SIZE_WORD, Blitter_Halftone10_ReadWord, Blitter_Halftone10_WriteWord }, /* Blitter halftone RAM 10 */ { 0xff8a16, SIZE_WORD, Blitter_Halftone11_ReadWord, Blitter_Halftone11_WriteWord }, /* Blitter halftone RAM 11 */ { 0xff8a18, SIZE_WORD, Blitter_Halftone12_ReadWord, Blitter_Halftone12_WriteWord }, /* Blitter halftone RAM 12 */ { 0xff8a1a, SIZE_WORD, Blitter_Halftone13_ReadWord, Blitter_Halftone13_WriteWord }, /* Blitter halftone RAM 13 */ { 0xff8a1c, SIZE_WORD, Blitter_Halftone14_ReadWord, Blitter_Halftone14_WriteWord }, /* Blitter halftone RAM 14 */ { 0xff8a1e, SIZE_WORD, Blitter_Halftone15_ReadWord, Blitter_Halftone15_WriteWord }, /* Blitter halftone RAM 15 */ { 0xff8a20, SIZE_WORD, Blitter_SourceXInc_ReadWord, Blitter_SourceXInc_WriteWord }, /* Blitter source x increment */ { 0xff8a22, SIZE_WORD, Blitter_SourceYInc_ReadWord, Blitter_SourceYInc_WriteWord }, /* Blitter source y increment */ { 0xff8a24, SIZE_LONG, Blitter_SourceAddr_ReadLong, Blitter_SourceAddr_WriteLong }, /* Blitter source address */ { 0xff8a28, SIZE_WORD, Blitter_Endmask1_ReadWord, Blitter_Endmask1_WriteWord }, { 0xff8a2a, SIZE_WORD, Blitter_Endmask2_ReadWord, Blitter_Endmask2_WriteWord }, { 0xff8a2c, SIZE_WORD, Blitter_Endmask3_ReadWord, Blitter_Endmask3_WriteWord }, { 0xff8a2e, SIZE_WORD, Blitter_DestXInc_ReadWord, Blitter_DestXInc_WriteWord }, /* Blitter dest. x increment */ { 0xff8a30, SIZE_WORD, Blitter_DestYInc_ReadWord, Blitter_DestYInc_WriteWord }, /* Blitter dest. y increment */ { 0xff8a32, SIZE_LONG, Blitter_DestAddr_ReadLong, Blitter_DestAddr_WriteLong }, { 0xff8a36, SIZE_WORD, Blitter_WordsPerLine_ReadWord, Blitter_WordsPerLine_WriteWord }, { 0xff8a38, SIZE_WORD, Blitter_LinesPerBitblock_ReadWord, Blitter_LinesPerBitblock_WriteWord }, { 0xff8a3a, SIZE_BYTE, Blitter_HalftoneOp_ReadByte, Blitter_HalftoneOp_WriteByte }, { 0xff8a3b, SIZE_BYTE, Blitter_LogOp_ReadByte, Blitter_LogOp_WriteByte }, { 0xff8a3c, SIZE_BYTE, Blitter_Control_ReadByte, Blitter_Control_WriteByte }, { 0xff8a3d, SIZE_BYTE, Blitter_Skew_ReadByte, Blitter_Skew_WriteByte }, { 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte }, { 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte }, { 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte }, { 0xfffa07, SIZE_BYTE, MFP_EnableA_ReadByte, MFP_EnableA_WriteByte }, { 0xfffa09, SIZE_BYTE, MFP_EnableB_ReadByte, MFP_EnableB_WriteByte }, { 0xfffa0b, SIZE_BYTE, MFP_PendingA_ReadByte, MFP_PendingA_WriteByte }, { 0xfffa0d, SIZE_BYTE, MFP_PendingB_ReadByte, MFP_PendingB_WriteByte }, { 0xfffa0f, SIZE_BYTE, MFP_InServiceA_ReadByte, MFP_InServiceA_WriteByte }, { 0xfffa11, SIZE_BYTE, MFP_InServiceB_ReadByte, MFP_InServiceB_WriteByte }, { 0xfffa13, SIZE_BYTE, MFP_MaskA_ReadByte, MFP_MaskA_WriteByte }, { 0xfffa15, SIZE_BYTE, MFP_MaskB_ReadByte, MFP_MaskB_WriteByte }, { 0xfffa17, SIZE_BYTE, MFP_VectorReg_ReadByte, MFP_VectorReg_WriteByte }, { 0xfffa19, SIZE_BYTE, MFP_TimerACtrl_ReadByte, MFP_TimerACtrl_WriteByte }, { 0xfffa1b, SIZE_BYTE, MFP_TimerBCtrl_ReadByte, MFP_TimerBCtrl_WriteByte }, { 0xfffa1d, SIZE_BYTE, MFP_TimerCDCtrl_ReadByte, MFP_TimerCDCtrl_WriteByte }, { 0xfffa1f, SIZE_BYTE, MFP_TimerAData_ReadByte, MFP_TimerAData_WriteByte }, { 0xfffa21, SIZE_BYTE, MFP_TimerBData_ReadByte, MFP_TimerBData_WriteByte }, { 0xfffa23, SIZE_BYTE, MFP_TimerCData_ReadByte, MFP_TimerCData_WriteByte }, { 0xfffa25, SIZE_BYTE, MFP_TimerDData_ReadByte, MFP_TimerDData_WriteByte }, { 0xfffa27, SIZE_BYTE, RS232_SCR_ReadByte, RS232_SCR_WriteByte }, /* Sync character register */ { 0xfffa29, SIZE_BYTE, RS232_UCR_ReadByte, RS232_UCR_WriteByte }, /* USART control register */ { 0xfffa2b, SIZE_BYTE, RS232_RSR_ReadByte, RS232_RSR_WriteByte }, /* Receiver status register */ { 0xfffa2d, SIZE_BYTE, RS232_TSR_ReadByte, RS232_TSR_WriteByte }, /* Transmitter status register */ { 0xfffa2f, SIZE_BYTE, RS232_UDR_ReadByte, RS232_UDR_WriteByte }, /* USART data register */ { 0xfffa31, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa33, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa35, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa37, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa39, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3b, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3d, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3f, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc00, SIZE_BYTE, ACIA_IKBD_Read_SR, ACIA_IKBD_Write_CR }, { 0xfffc01, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc02, SIZE_BYTE, ACIA_IKBD_Read_RDR, ACIA_IKBD_Write_TDR }, { 0xfffc03, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc04, SIZE_BYTE, Midi_Control_ReadByte, Midi_Control_WriteByte }, { 0xfffc05, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc06, SIZE_BYTE, Midi_Data_ReadByte, Midi_Data_WriteByte }, { 0xfffc07, 26, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xfffc21, SIZE_BYTE, Rtc_SecondsUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc22, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc23, SIZE_BYTE, Rtc_SecondsTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc24, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc25, SIZE_BYTE, Rtc_MinutesUnits_ReadByte, Rtc_MinutesUnits_WriteByte }, { 0xfffc26, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc27, SIZE_BYTE, Rtc_MinutesTens_ReadByte, Rtc_MinutesTens_WriteByte }, { 0xfffc28, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc29, SIZE_BYTE, Rtc_HoursUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc2a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc2b, SIZE_BYTE, Rtc_HoursTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc2c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc2d, SIZE_BYTE, Rtc_Weekday_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc2e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc2f, SIZE_BYTE, Rtc_DayUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc30, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc31, SIZE_BYTE, Rtc_DayTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc32, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc33, SIZE_BYTE, Rtc_MonthUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc34, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc35, SIZE_BYTE, Rtc_MonthTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc36, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc37, SIZE_BYTE, Rtc_YearUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc38, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc39, SIZE_BYTE, Rtc_YearTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc3a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc3b, SIZE_BYTE, Rtc_ClockMod_ReadByte, Rtc_ClockMod_WriteByte }, { 0xfffc3c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc3d, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* Clock test */ { 0xfffc3e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc3f, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* Clock reset */ { 0xfffc40, 448, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0, 0, NULL, NULL } }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ioMemTabSTE.c000066400000000000000000000527511504763705000235570ustar00rootroot00000000000000/* Hatari - ioMemTabSTE.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Table with hardware IO handlers for the STE. */ const char IoMemTabSTE_fileid[] = "Hatari ioMemTabSTE.c"; #include "main.h" #include "configuration.h" #include "dmaSnd.h" #include "fdc.h" #include "acia.h" #include "ioMem.h" #include "ioMemTables.h" #include "m68000.h" #include "joy.h" #include "mfp.h" #include "midi.h" #include "psg.h" #include "rs232.h" #include "rtc.h" #include "video.h" #include "blitter.h" #include "stMemory.h" /** * Take into account Mega STe Cache/Processor Control register $ff8e21.b $FFFF8E21 Mega STe Cache/Processor Control BIT 0 : Cache (0 - disabled, 1 - enabled) BIT 1 : CPU Speed (0 - 8MHz, 1 - 16MHz) Cache can only be enabled if CPU speed is 16 MHz. If CPU is set to 8 MHz then cache bit will be forced to 0 : if we write $FD at $FF8E21 (8 MHz with cache) then reading $FF8E21 will return $FC (8 MHz no cache) */ void IoMemTabMegaSTE_CacheCpuCtrl_WriteByte(void) { uint8_t Ctrl = IoMem_ReadByte(0xff8e21); /* If cache is enabled at 8 MHz then disable cache */ if ( ( ( Ctrl & 0x2 ) == 0 ) && ( Ctrl & 0x1 ) ) { LOG_TRACE ( TRACE_MEM, "cache : megaste cache can't be enabled at 8 MHz $ff8e21=0x%02x pc=%x\n" , Ctrl , M68000_GetPC() ); Ctrl &= 0xFE; /* clear bit 0 */ IoMem_WriteByte ( 0xff8e21 , Ctrl ); } MegaSTE_CPU_Cache_Update ( Ctrl ); } /** * The register at $FF9200.b represents the DIP switches from the * MegaSTE motherboard. The meaning of the switches is as follows: * * 1 - 6 off * 7 on = 1.4mb HD floppy drive fitted * 8 off (on = disable the DMA sound hardware) * * Switch 1 is represented by the lowest bit in the $FF9200 register, * and switch 8 is represented by the highest bit. Logic is inverted, * i.e. when the switch is "on", the bit is 0. * * We set the value to 0xBF to enable an HD floppy drive by default * (earliest MegaSTE produced had a DD floppy drive, but later * it was replaced by an HD drive) */ uint8_t IoMemTabMegaSTE_DIPSwitches_Read(void) { return 0xbf; } /*-----------------------------------------------------------------------*/ /* List of functions to handle read/write hardware interceptions for a STE. */ const INTERCEPT_ACCESS_FUNC IoMemTable_STE[] = { { 0xff8000, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8001, SIZE_BYTE, STMemory_MMU_Config_ReadByte, STMemory_MMU_Config_WriteByte }, /* Memory configuration */ { 0xff8002, 14, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff8200, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8201, SIZE_BYTE, IoMem_ReadWithoutInterception, Video_ScreenBase_WriteByte }, /* Video base high byte */ { 0xff8202, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8203, SIZE_BYTE, IoMem_ReadWithoutInterception, Video_ScreenBase_WriteByte }, /* Video base med byte */ { 0xff8204, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8205, SIZE_BYTE, Video_ScreenCounter_ReadByte, Video_ScreenCounter_WriteByte }, { 0xff8206, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8207, SIZE_BYTE, Video_ScreenCounter_ReadByte, Video_ScreenCounter_WriteByte }, { 0xff8208, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8209, SIZE_BYTE, Video_ScreenCounter_ReadByte, Video_ScreenCounter_WriteByte }, { 0xff820a, SIZE_BYTE, Video_Sync_ReadByte, Video_Sync_WriteByte }, { 0xff820b, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here : return 0 not ff */ { 0xff820c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff820d, SIZE_BYTE, Video_BaseLow_ReadByte, Video_ScreenBase_WriteByte }, { 0xff820e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff820f, SIZE_BYTE, Video_LineWidth_ReadByte, Video_LineWidth_WriteByte }, { 0xff8240, SIZE_WORD, Video_Color0_ReadWord, Video_Color0_WriteWord }, /* COLOR 0 */ { 0xff8242, SIZE_WORD, Video_Color1_ReadWord, Video_Color1_WriteWord }, /* COLOR 1 */ { 0xff8244, SIZE_WORD, Video_Color2_ReadWord, Video_Color2_WriteWord }, /* COLOR 2 */ { 0xff8246, SIZE_WORD, Video_Color3_ReadWord, Video_Color3_WriteWord }, /* COLOR 3 */ { 0xff8248, SIZE_WORD, Video_Color4_ReadWord, Video_Color4_WriteWord }, /* COLOR 4 */ { 0xff824a, SIZE_WORD, Video_Color5_ReadWord, Video_Color5_WriteWord }, /* COLOR 5 */ { 0xff824c, SIZE_WORD, Video_Color6_ReadWord, Video_Color6_WriteWord }, /* COLOR 6 */ { 0xff824e, SIZE_WORD, Video_Color7_ReadWord, Video_Color7_WriteWord }, /* COLOR 7 */ { 0xff8250, SIZE_WORD, Video_Color8_ReadWord, Video_Color8_WriteWord }, /* COLOR 8 */ { 0xff8252, SIZE_WORD, Video_Color9_ReadWord, Video_Color9_WriteWord }, /* COLOR 9 */ { 0xff8254, SIZE_WORD, Video_Color10_ReadWord, Video_Color10_WriteWord }, /* COLOR 10 */ { 0xff8256, SIZE_WORD, Video_Color11_ReadWord, Video_Color11_WriteWord }, /* COLOR 11 */ { 0xff8258, SIZE_WORD, Video_Color12_ReadWord, Video_Color12_WriteWord }, /* COLOR 12 */ { 0xff825a, SIZE_WORD, Video_Color13_ReadWord, Video_Color13_WriteWord }, /* COLOR 13 */ { 0xff825c, SIZE_WORD, Video_Color14_ReadWord, Video_Color14_WriteWord }, /* COLOR 14 */ { 0xff825e, SIZE_WORD, Video_Color15_ReadWord, Video_Color15_WriteWord }, /* COLOR 15 */ { 0xff8260, SIZE_BYTE, Video_ResGlueShifter_ReadByte, Video_ResGlueShifter_WriteByte }, /* Resolution in GLUE-GSTMCU/Shifter */ { 0xff8261, SIZE_BYTE, Video_ResShifter_ReadByte, Video_ResShifter_WriteByte }, /* Resolution in Shifter only */ { 0xff8262, 2, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus errors here : return 0 not ff */ { 0xff8264, SIZE_BYTE, Video_HorScroll_Read_8264, Video_HorScroll_Write_8264 }, /* STE horizontal fine scrolling (no prefetch) */ { 0xff8265, SIZE_BYTE, Video_HorScroll_Read_8265, Video_HorScroll_Write_8265 }, /* STE horizontal fine scrolling */ { 0xff8266, 26, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus errors here : return 0 not ff */ { 0xff8604, SIZE_WORD, FDC_DiskControllerStatus_ReadWord, FDC_DiskController_WriteWord }, { 0xff8606, SIZE_WORD, FDC_DmaStatus_ReadWord, FDC_DmaModeControl_WriteWord }, { 0xff8608, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8609, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter high byte */ { 0xff860a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860b, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter med byte */ { 0xff860c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860d, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter low byte */ { 0xff860e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860f, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8800, SIZE_BYTE, PSG_ff8800_ReadByte, PSG_ff8800_WriteByte }, { 0xff8801, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8801_WriteByte }, { 0xff8802, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8802_WriteByte }, { 0xff8803, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8803_WriteByte }, { 0xff8900, SIZE_WORD, DmaSnd_SoundControl_ReadWord, DmaSnd_SoundControl_WriteWord }, /* DMA sound control */ { 0xff8902, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8903, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameStartHigh_WriteByte },/* DMA sound frame start high */ { 0xff8904, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8905, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameStartMed_WriteByte }, /* DMA sound frame start med */ { 0xff8906, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8907, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameStartLow_WriteByte }, /* DMA sound frame start low */ { 0xff8908, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8909, SIZE_BYTE, DmaSnd_FrameCountHigh_ReadByte, DmaSnd_FrameCountHigh_WriteByte },/* DMA sound frame count high */ { 0xff890a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890b, SIZE_BYTE, DmaSnd_FrameCountMed_ReadByte, DmaSnd_FrameCountMed_WriteByte }, /* DMA sound frame count med */ { 0xff890c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890d, SIZE_BYTE, DmaSnd_FrameCountLow_ReadByte, DmaSnd_FrameCountLow_WriteByte }, /* DMA sound frame count low */ { 0xff890e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890f, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameEndHigh_WriteByte }, /* DMA sound frame end high */ { 0xff8910, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8911, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameEndMed_WriteByte }, /* DMA sound frame end med */ { 0xff8912, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8913, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameEndLow_WriteByte }, /* DMA sound frame end low */ { 0xff8914, 12, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff8920, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* DMA sound mode control (contains 0) */ { 0xff8921, SIZE_BYTE, DmaSnd_SoundModeCtrl_ReadByte, DmaSnd_SoundModeCtrl_WriteByte }, /* DMA sound mode control */ { 0xff8922, SIZE_WORD, DmaSnd_MicrowireData_ReadWord, DmaSnd_MicrowireData_WriteWord }, /* Microwire data */ { 0xff8924, SIZE_WORD, DmaSnd_MicrowireMask_ReadWord, DmaSnd_MicrowireMask_WriteWord }, /* Microwire mask */ { 0xff8926, 26, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff8a00, SIZE_WORD, Blitter_Halftone00_ReadWord, Blitter_Halftone00_WriteWord }, /* Blitter halftone RAM 0 */ { 0xff8a02, SIZE_WORD, Blitter_Halftone01_ReadWord, Blitter_Halftone01_WriteWord }, /* Blitter halftone RAM 1 */ { 0xff8a04, SIZE_WORD, Blitter_Halftone02_ReadWord, Blitter_Halftone02_WriteWord }, /* Blitter halftone RAM 2 */ { 0xff8a06, SIZE_WORD, Blitter_Halftone03_ReadWord, Blitter_Halftone03_WriteWord }, /* Blitter halftone RAM 3 */ { 0xff8a08, SIZE_WORD, Blitter_Halftone04_ReadWord, Blitter_Halftone04_WriteWord }, /* Blitter halftone RAM 4 */ { 0xff8a0a, SIZE_WORD, Blitter_Halftone05_ReadWord, Blitter_Halftone05_WriteWord }, /* Blitter halftone RAM 5 */ { 0xff8a0c, SIZE_WORD, Blitter_Halftone06_ReadWord, Blitter_Halftone06_WriteWord }, /* Blitter halftone RAM 6 */ { 0xff8a0e, SIZE_WORD, Blitter_Halftone07_ReadWord, Blitter_Halftone07_WriteWord }, /* Blitter halftone RAM 7 */ { 0xff8a10, SIZE_WORD, Blitter_Halftone08_ReadWord, Blitter_Halftone08_WriteWord }, /* Blitter halftone RAM 8 */ { 0xff8a12, SIZE_WORD, Blitter_Halftone09_ReadWord, Blitter_Halftone09_WriteWord }, /* Blitter halftone RAM 9 */ { 0xff8a14, SIZE_WORD, Blitter_Halftone10_ReadWord, Blitter_Halftone10_WriteWord }, /* Blitter halftone RAM 10 */ { 0xff8a16, SIZE_WORD, Blitter_Halftone11_ReadWord, Blitter_Halftone11_WriteWord }, /* Blitter halftone RAM 11 */ { 0xff8a18, SIZE_WORD, Blitter_Halftone12_ReadWord, Blitter_Halftone12_WriteWord }, /* Blitter halftone RAM 12 */ { 0xff8a1a, SIZE_WORD, Blitter_Halftone13_ReadWord, Blitter_Halftone13_WriteWord }, /* Blitter halftone RAM 13 */ { 0xff8a1c, SIZE_WORD, Blitter_Halftone14_ReadWord, Blitter_Halftone14_WriteWord }, /* Blitter halftone RAM 14 */ { 0xff8a1e, SIZE_WORD, Blitter_Halftone15_ReadWord, Blitter_Halftone15_WriteWord }, /* Blitter halftone RAM 15 */ { 0xff8a20, SIZE_WORD, Blitter_SourceXInc_ReadWord, Blitter_SourceXInc_WriteWord }, /* Blitter source x increment */ { 0xff8a22, SIZE_WORD, Blitter_SourceYInc_ReadWord, Blitter_SourceYInc_WriteWord }, /* Blitter source y increment */ { 0xff8a24, SIZE_LONG, Blitter_SourceAddr_ReadLong, Blitter_SourceAddr_WriteLong }, /* Blitter source address */ { 0xff8a28, SIZE_WORD, Blitter_Endmask1_ReadWord, Blitter_Endmask1_WriteWord }, { 0xff8a2a, SIZE_WORD, Blitter_Endmask2_ReadWord, Blitter_Endmask2_WriteWord }, { 0xff8a2c, SIZE_WORD, Blitter_Endmask3_ReadWord, Blitter_Endmask3_WriteWord }, { 0xff8a2e, SIZE_WORD, Blitter_DestXInc_ReadWord, Blitter_DestXInc_WriteWord }, /* Blitter dest. x increment */ { 0xff8a30, SIZE_WORD, Blitter_DestYInc_ReadWord, Blitter_DestYInc_WriteWord }, /* Blitter dest. y increment */ { 0xff8a32, SIZE_LONG, Blitter_DestAddr_ReadLong, Blitter_DestAddr_WriteLong }, { 0xff8a36, SIZE_WORD, Blitter_WordsPerLine_ReadWord, Blitter_WordsPerLine_WriteWord }, { 0xff8a38, SIZE_WORD, Blitter_LinesPerBitblock_ReadWord, Blitter_LinesPerBitblock_WriteWord }, { 0xff8a3a, SIZE_BYTE, Blitter_HalftoneOp_ReadByte, Blitter_HalftoneOp_WriteByte }, { 0xff8a3b, SIZE_BYTE, Blitter_LogOp_ReadByte, Blitter_LogOp_WriteByte }, { 0xff8a3c, SIZE_BYTE, Blitter_Control_ReadByte, Blitter_Control_WriteByte }, { 0xff8a3d, SIZE_BYTE, Blitter_Skew_ReadByte, Blitter_Skew_WriteByte }, { 0xff8a3e, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ /* SCU 0xff8e01-0xff8e0f registers set at run-time in ioMem.c for MegaSTE */ { 0xff9000, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff9200, SIZE_WORD, Joy_StePadButtons_DIPSwitches_ReadWord, Joy_StePadButtons_DIPSwitches_WriteWord }, /* Joypad fire buttons + MegaSTE DIP Switches */ { 0xff9202, SIZE_WORD, Joy_StePadMulti_ReadWord, Joy_StePadMulti_WriteWord }, /* Joypad directions/buttons/selection */ { 0xff9211, SIZE_BYTE, Joy_StePadAnalog0X_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 0 Analog/Paddle X position */ { 0xff9213, SIZE_BYTE, Joy_StePadAnalog0Y_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 0 Analog/Paddle Y position */ { 0xff9215, SIZE_BYTE, Joy_StePadAnalog1X_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 1 Analog/Paddle X position */ { 0xff9217, SIZE_BYTE, Joy_StePadAnalog1Y_ReadByte, IoMem_WriteWithoutInterception }, /* Joypad 1 Analog/Paddle Y position */ { 0xff9220, SIZE_WORD, Joy_SteLightpenX_ReadWord, IoMem_WriteWithoutInterception }, /* Lightpen X position */ { 0xff9222, SIZE_WORD, Joy_SteLightpenY_ReadWord, IoMem_WriteWithoutInterception }, /* Lightpen Y position */ { 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte }, { 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte }, { 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte }, { 0xfffa07, SIZE_BYTE, MFP_EnableA_ReadByte, MFP_EnableA_WriteByte }, { 0xfffa09, SIZE_BYTE, MFP_EnableB_ReadByte, MFP_EnableB_WriteByte }, { 0xfffa0b, SIZE_BYTE, MFP_PendingA_ReadByte, MFP_PendingA_WriteByte }, { 0xfffa0d, SIZE_BYTE, MFP_PendingB_ReadByte, MFP_PendingB_WriteByte }, { 0xfffa0f, SIZE_BYTE, MFP_InServiceA_ReadByte, MFP_InServiceA_WriteByte }, { 0xfffa11, SIZE_BYTE, MFP_InServiceB_ReadByte, MFP_InServiceB_WriteByte }, { 0xfffa13, SIZE_BYTE, MFP_MaskA_ReadByte, MFP_MaskA_WriteByte }, { 0xfffa15, SIZE_BYTE, MFP_MaskB_ReadByte, MFP_MaskB_WriteByte }, { 0xfffa17, SIZE_BYTE, MFP_VectorReg_ReadByte, MFP_VectorReg_WriteByte }, { 0xfffa19, SIZE_BYTE, MFP_TimerACtrl_ReadByte, MFP_TimerACtrl_WriteByte }, { 0xfffa1b, SIZE_BYTE, MFP_TimerBCtrl_ReadByte, MFP_TimerBCtrl_WriteByte }, { 0xfffa1d, SIZE_BYTE, MFP_TimerCDCtrl_ReadByte, MFP_TimerCDCtrl_WriteByte }, { 0xfffa1f, SIZE_BYTE, MFP_TimerAData_ReadByte, MFP_TimerAData_WriteByte }, { 0xfffa21, SIZE_BYTE, MFP_TimerBData_ReadByte, MFP_TimerBData_WriteByte }, { 0xfffa23, SIZE_BYTE, MFP_TimerCData_ReadByte, MFP_TimerCData_WriteByte }, { 0xfffa25, SIZE_BYTE, MFP_TimerDData_ReadByte, MFP_TimerDData_WriteByte }, { 0xfffa27, SIZE_BYTE, RS232_SCR_ReadByte, RS232_SCR_WriteByte }, /* Sync character register */ { 0xfffa29, SIZE_BYTE, RS232_UCR_ReadByte, RS232_UCR_WriteByte }, /* USART control register */ { 0xfffa2b, SIZE_BYTE, RS232_RSR_ReadByte, RS232_RSR_WriteByte }, /* Receiver status register */ { 0xfffa2d, SIZE_BYTE, RS232_TSR_ReadByte, RS232_TSR_WriteByte }, /* Transmitter status register */ { 0xfffa2f, SIZE_BYTE, RS232_UDR_ReadByte, RS232_UDR_WriteByte }, /* USART data register */ { 0xfffa31, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa33, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa35, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa37, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa39, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3b, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3d, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3f, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc00, SIZE_BYTE, ACIA_IKBD_Read_SR, ACIA_IKBD_Write_CR }, { 0xfffc01, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc02, SIZE_BYTE, ACIA_IKBD_Read_RDR, ACIA_IKBD_Write_TDR }, { 0xfffc03, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc04, SIZE_BYTE, Midi_Control_ReadByte, Midi_Control_WriteByte }, { 0xfffc05, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc06, SIZE_BYTE, Midi_Data_ReadByte, Midi_Data_WriteByte }, { 0xfffc07, 26, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xfffc21, SIZE_BYTE, Rtc_SecondsUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc22, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc23, SIZE_BYTE, Rtc_SecondsTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc24, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc25, SIZE_BYTE, Rtc_MinutesUnits_ReadByte, Rtc_MinutesUnits_WriteByte }, { 0xfffc26, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc27, SIZE_BYTE, Rtc_MinutesTens_ReadByte, Rtc_MinutesTens_WriteByte }, { 0xfffc28, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc29, SIZE_BYTE, Rtc_HoursUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc2a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc2b, SIZE_BYTE, Rtc_HoursTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc2c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc2d, SIZE_BYTE, Rtc_Weekday_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc2e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc2f, SIZE_BYTE, Rtc_DayUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc30, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc31, SIZE_BYTE, Rtc_DayTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc32, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc33, SIZE_BYTE, Rtc_MonthUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc34, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc35, SIZE_BYTE, Rtc_MonthTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc36, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc37, SIZE_BYTE, Rtc_YearUnits_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc38, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc39, SIZE_BYTE, Rtc_YearTens_ReadByte, IoMem_WriteWithoutInterception }, { 0xfffc3a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc3b, SIZE_BYTE, Rtc_ClockMod_ReadByte, Rtc_ClockMod_WriteByte }, { 0xfffc3c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc3d, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* Clock test */ { 0xfffc3e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc3f, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* Clock reset */ { 0xfffc40, 448, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0, 0, NULL, NULL } }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ioMemTabTT.c000066400000000000000000000514331504763705000234470ustar00rootroot00000000000000/* Hatari - ioMemTabTT.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Table with hardware IO handlers for the TT. NOTE [NP] : contrary to some unofficial documentations, the TT doesn't have hardware scrolling similar to the STE. As such, registers FF820E, FF820F, FF8264 and FF8265 are not available and seem to return undefined values based on the data last seen on the bus (this would need more tests on a TT) move.b $ff820e,d0 -> FF move.b $ff820f,d0 -> 01 move.b $ff8264,d0 -> 82 move.b $ff8265,d0 -> 65 */ const char IoMemTabTT_fileid[] = "Hatari ioMemTabTT.c"; #include "main.h" #include "configuration.h" #include "dmaSnd.h" #include "fdc.h" #include "acia.h" #include "ioMem.h" #include "ioMemTables.h" #include "joy.h" #include "mfp.h" #include "midi.h" #include "ncr5380.h" #include "nvram.h" #include "psg.h" #include "rs232.h" #include "rtc.h" #include "scc.h" #include "scu_vme.h" #include "video.h" #include "stMemory.h" /** * The register at $FF9200.b represents the DIP switches from the * TT motherboard. The meaning of the switches is as follows: * * 1 off (on = CaTTamaran installed, not an official setting) * 2 - 6 off * 7 on = 1.4mb HD floppy drive fitted * 8 off (on = disable the DMA sound hardware) * * Switch 1 is represented by the lowest bit in the $FF9200 register, * and switch 8 is represented by the highest bit. Logic is inverted, * i.e. when the switch is "on", the bit is 0. */ static void IoMemTabTT_ReadDIPSwitches(void) { IoMem_WriteWord(0xff9200, 0xbf00); } /** * List of functions to handle read/write hardware interceptions for a TT. */ const INTERCEPT_ACCESS_FUNC IoMemTable_TT[] = { { 0xff8000, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8001, SIZE_BYTE, STMemory_MMU_Config_ReadByte, STMemory_MMU_Config_WriteByte }, /* Memory configuration */ { 0xff8200, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8201, SIZE_BYTE, IoMem_ReadWithoutInterception, Video_ScreenBase_WriteByte }, /* Video base high byte */ { 0xff8202, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8203, SIZE_BYTE, IoMem_ReadWithoutInterception, Video_ScreenBase_WriteByte }, /* Video base med byte */ { 0xff8204, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8205, SIZE_BYTE, Video_ScreenCounter_ReadByte, Video_ScreenCounter_WriteByte }, { 0xff8206, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8207, SIZE_BYTE, Video_ScreenCounter_ReadByte, Video_ScreenCounter_WriteByte }, { 0xff8208, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8209, SIZE_BYTE, Video_ScreenCounter_ReadByte, Video_ScreenCounter_WriteByte }, { 0xff820a, SIZE_BYTE, Video_Sync_ReadByte, Video_Sync_WriteByte }, { 0xff820b, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here : return 0 not ff */ { 0xff820c, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here : return 0 not ff */ { 0xff820d, SIZE_BYTE, Video_BaseLow_ReadByte, Video_ScreenBase_WriteByte }, { 0xff820e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff820f, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8240, 16*SIZE_WORD, IoMem_ReadWithoutInterception, Video_TTColorRegs_STRegWrite },/* 16 TT ST-palette entries */ { 0xff8260, SIZE_BYTE, Video_ResGlueShifter_ReadByte, Video_ResGlueShifter_WriteByte }, /* Resolution */ { 0xff8261, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus errors here : return 0 not ff */ { 0xff8262, SIZE_WORD, IoMem_ReadWithoutInterception, Video_TTShiftMode_WriteWord }, /* TT screen mode */ { 0xff8264, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, { 0xff8265, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* Horizontal fine scrolling */ { 0xff8266, 26, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus errors here : return 0 not ff */ { 0xff8400, 512, IoMem_ReadWithoutInterception, Video_TTColorRegs_Write }, /* 256 TT palette entries */ { 0xff8604, SIZE_WORD, FDC_DiskControllerStatus_ReadWord, FDC_DiskController_WriteWord }, { 0xff8606, SIZE_WORD, FDC_DmaStatus_ReadWord, FDC_DmaModeControl_WriteWord }, { 0xff8608, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8609, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter high byte */ { 0xff860a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860b, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter med byte */ { 0xff860c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff860d, SIZE_BYTE, FDC_DmaAddress_ReadByte, FDC_DmaAddress_WriteByte }, /* DMA base and counter low byte */ { 0xff860e, SIZE_WORD, FDC_DensityMode_ReadWord , FDC_DensityMode_WriteWord }, /* Choose DD/HD mode */ { 0xff8700, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8701, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Address Pointer (Highest byte) */ { 0xff8702, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8703, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Address Pointer (High byte) */ { 0xff8704, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8705, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Address Pointer (Low byte) */ { 0xff8706, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8707, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Address Pointer (Lowest byte) */ { 0xff8708, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8709, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Byte Count (Highest byte) */ { 0xff870a, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff870b, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Byte Count (High byte) */ { 0xff870c, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff870d, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Byte Count (Low byte) */ { 0xff870e, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff870f, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI DMA Byte Count (Lowest byte) */ { 0xff8710, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI Residue Data Register (High Word) */ { 0xff8712, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCSI Residue Data Register (Low Word) */ { 0xff8714, SIZE_WORD, IoMem_ReadWithoutInterception, Ncr5380_TT_DMA_Ctrl_WriteWord }, /* SCSI Control register */ { 0xff8716, 10, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8780, 16, Ncr5380_IoMemTT_ReadByte, Ncr5380_IoMemTT_WriteByte }, /* TT SCSI controller */ { 0xff8800, SIZE_BYTE, PSG_ff8800_ReadByte, PSG_ff8800_WriteByte }, { 0xff8802, SIZE_BYTE, PSG_ff880x_ReadByte, PSG_ff8802_WriteByte }, { 0xff8900, SIZE_WORD, DmaSnd_SoundControl_ReadWord, DmaSnd_SoundControl_WriteWord }, /* DMA sound control */ { 0xff8902, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8903, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameStartHigh_WriteByte },/* DMA sound frame start high */ { 0xff8904, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8905, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameStartMed_WriteByte }, /* DMA sound frame start med */ { 0xff8906, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8907, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameStartLow_WriteByte }, /* DMA sound frame start low */ { 0xff8908, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8909, SIZE_BYTE, DmaSnd_FrameCountHigh_ReadByte, DmaSnd_FrameCountHigh_WriteByte },/* DMA sound frame count high */ { 0xff890a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890b, SIZE_BYTE, DmaSnd_FrameCountMed_ReadByte, DmaSnd_FrameCountMed_WriteByte }, /* DMA sound frame count med */ { 0xff890c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890d, SIZE_BYTE, DmaSnd_FrameCountLow_ReadByte, DmaSnd_FrameCountLow_WriteByte }, /* DMA sound frame count low */ { 0xff890e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff890f, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameEndHigh_WriteByte }, /* DMA sound frame end high */ { 0xff8910, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8911, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameEndMed_WriteByte }, /* DMA sound frame end med */ { 0xff8912, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff8913, SIZE_BYTE, IoMem_ReadWithoutInterception, DmaSnd_FrameEndLow_WriteByte }, /* DMA sound frame end low */ { 0xff8920, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* DMA sound mode control (contains 0) */ { 0xff8921, SIZE_BYTE, DmaSnd_SoundModeCtrl_ReadByte, DmaSnd_SoundModeCtrl_WriteByte }, /* DMA sound mode control */ { 0xff8922, SIZE_WORD, DmaSnd_MicrowireData_ReadWord, DmaSnd_MicrowireData_WriteWord }, /* Microwire data */ { 0xff8924, SIZE_WORD, DmaSnd_MicrowireMask_ReadWord, DmaSnd_MicrowireMask_WriteWord }, /* Microwire mask */ { 0xff8926, 26, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus errors here */ { 0xff8961, SIZE_BYTE, NvRam_Select_ReadByte, NvRam_Select_WriteByte }, /* NVRAM/RTC chip */ { 0xff8963, SIZE_BYTE, NvRam_Data_ReadByte, NvRam_Data_WriteByte }, /* NVRAM/RTC chip */ /* Note: The TT does not have a blitter (0xff8a00 - 0xff8a3e) */ { 0xff8c00, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c01, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Address Pointer (Highest byte) */ { 0xff8c02, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c03, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Address Pointer (High byte) */ { 0xff8c04, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c05, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Address Pointer (Low byte) */ { 0xff8c06, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c07, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Address Pointer (Lowest byte) */ { 0xff8c08, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c09, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Byte Count (Highest byte) */ { 0xff8c0a, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c0b, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Byte Count (High byte) */ { 0xff8c0c, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c0d, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Byte Count (Low byte) */ { 0xff8c0e, SIZE_BYTE, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c0f, SIZE_BYTE, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC DMA Byte Count (Lowest byte) */ { 0xff8c10, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC Residue Data Register (High Word) */ { 0xff8c12, SIZE_WORD, IoMem_ReadWithoutInterception, IoMem_WriteWithoutInterception }, /* SCC Residue Data Register (Low Word) */ { 0xff8c14, SIZE_WORD, IoMem_VoidRead_00, IoMem_WriteWithoutInterception }, /* SCC Control register */ { 0xff8c16, 10, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8c80, 8, SCC_IoMem_ReadByte, SCC_IoMem_WriteByte }, /* SCC */ { 0xff8c88, 8, IoMem_VoidRead_00, IoMem_VoidWrite }, /* No bus error here */ { 0xff8e01, SIZE_BYTE, SCU_SysIntMask_ReadByte, SCU_SysIntMask_WriteByte }, { 0xff8e03, SIZE_BYTE, SCU_SysIntState_ReadByte, SCU_SysIntState_WriteByte }, { 0xff8e05, SIZE_BYTE, SCU_SysInterrupter_ReadByte, SCU_SysInterrupter_WriteByte }, { 0xff8e07, SIZE_BYTE, SCU_VmeInterrupter_ReadByte, SCU_VmeInterrupter_WriteByte }, { 0xff8e09, SIZE_BYTE, SCU_GPR1_ReadByte, SCU_GPR1_WriteByte }, { 0xff8e0b, SIZE_BYTE, SCU_GPR2_ReadByte, SCU_GPR2_WriteByte }, { 0xff8e0d, SIZE_BYTE, SCU_VmeIntMask_Readyte, SCU_VmeIntMask_WriteByte }, { 0xff8e0f, SIZE_BYTE, SCU_VmeIntState_ReadByte, SCU_VmeIntState_WriteByte }, { 0xff9000, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xff9200, SIZE_WORD, IoMemTabTT_ReadDIPSwitches, IoMem_VoidWrite }, /* DIP switches */ { 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte }, { 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte }, { 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte }, { 0xfffa07, SIZE_BYTE, MFP_EnableA_ReadByte, MFP_EnableA_WriteByte }, { 0xfffa09, SIZE_BYTE, MFP_EnableB_ReadByte, MFP_EnableB_WriteByte }, { 0xfffa0b, SIZE_BYTE, MFP_PendingA_ReadByte, MFP_PendingA_WriteByte }, { 0xfffa0d, SIZE_BYTE, MFP_PendingB_ReadByte, MFP_PendingB_WriteByte }, { 0xfffa0f, SIZE_BYTE, MFP_InServiceA_ReadByte, MFP_InServiceA_WriteByte }, { 0xfffa11, SIZE_BYTE, MFP_InServiceB_ReadByte, MFP_InServiceB_WriteByte }, { 0xfffa13, SIZE_BYTE, MFP_MaskA_ReadByte, MFP_MaskA_WriteByte }, { 0xfffa15, SIZE_BYTE, MFP_MaskB_ReadByte, MFP_MaskB_WriteByte }, { 0xfffa17, SIZE_BYTE, MFP_VectorReg_ReadByte, MFP_VectorReg_WriteByte }, { 0xfffa19, SIZE_BYTE, MFP_TimerACtrl_ReadByte, MFP_TimerACtrl_WriteByte }, { 0xfffa1b, SIZE_BYTE, MFP_TimerBCtrl_ReadByte, MFP_TimerBCtrl_WriteByte }, { 0xfffa1d, SIZE_BYTE, MFP_TimerCDCtrl_ReadByte, MFP_TimerCDCtrl_WriteByte }, { 0xfffa1f, SIZE_BYTE, MFP_TimerAData_ReadByte, MFP_TimerAData_WriteByte }, { 0xfffa21, SIZE_BYTE, MFP_TimerBData_ReadByte, MFP_TimerBData_WriteByte }, { 0xfffa23, SIZE_BYTE, MFP_TimerCData_ReadByte, MFP_TimerCData_WriteByte }, { 0xfffa25, SIZE_BYTE, MFP_TimerDData_ReadByte, MFP_TimerDData_WriteByte }, { 0xfffa27, SIZE_BYTE, RS232_SCR_ReadByte, RS232_SCR_WriteByte }, /* Sync character register */ { 0xfffa29, SIZE_BYTE, RS232_UCR_ReadByte, RS232_UCR_WriteByte }, /* USART control register */ { 0xfffa2b, SIZE_BYTE, RS232_RSR_ReadByte, RS232_RSR_WriteByte }, /* Receiver status register */ { 0xfffa2d, SIZE_BYTE, RS232_TSR_ReadByte, RS232_TSR_WriteByte }, /* Transmitter status register */ { 0xfffa2f, SIZE_BYTE, RS232_UDR_ReadByte, RS232_UDR_WriteByte }, /* USART data register */ { 0xfffa31, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa33, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa35, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa37, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa39, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3b, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3d, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa3f, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffa81, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte }, /* TT MFP GPIP */ { 0xfffa83, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte }, /* TT MFP AER */ { 0xfffa85, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte /* TT MFP DDR */}, { 0xfffa87, SIZE_BYTE, MFP_EnableA_ReadByte, MFP_EnableA_WriteByte }, /* TT MFP IERA */ { 0xfffa89, SIZE_BYTE, MFP_EnableB_ReadByte, MFP_EnableB_WriteByte }, /* TT MFP IERB */ { 0xfffa8b, SIZE_BYTE, MFP_PendingA_ReadByte, MFP_PendingA_WriteByte }, /* TT MFP IPRA */ { 0xfffa8d, SIZE_BYTE, MFP_PendingB_ReadByte, MFP_PendingB_WriteByte }, /* TT MFP IPRB */ { 0xfffa8f, SIZE_BYTE, MFP_InServiceA_ReadByte, MFP_InServiceA_WriteByte }, /* TT MFP ISRA */ { 0xfffa91, SIZE_BYTE, MFP_InServiceB_ReadByte, MFP_InServiceB_WriteByte }, /* TT MFP ISRB */ { 0xfffa93, SIZE_BYTE, MFP_MaskA_ReadByte, MFP_MaskA_WriteByte }, /* TT MFP IMRA */ { 0xfffa95, SIZE_BYTE, MFP_MaskB_ReadByte, MFP_MaskB_WriteByte }, /* TT MFP IMRB */ { 0xfffa97, SIZE_BYTE, MFP_VectorReg_ReadByte, MFP_VectorReg_WriteByte }, /* TT MFP VR */ { 0xfffa99, SIZE_BYTE, MFP_TimerACtrl_ReadByte, MFP_TimerACtrl_WriteByte }, /* TT MFP TACR */ { 0xfffa9b, SIZE_BYTE, MFP_TimerBCtrl_ReadByte, MFP_TimerBCtrl_WriteByte }, /* TT MFP TBCR */ { 0xfffa9d, SIZE_BYTE, MFP_TimerCDCtrl_ReadByte, MFP_TimerCDCtrl_WriteByte }, /* TT MFP TCDCR */ { 0xfffa9f, SIZE_BYTE, MFP_TimerAData_ReadByte, MFP_TimerAData_WriteByte }, /* TT MFP TADR */ { 0xfffaa1, SIZE_BYTE, MFP_TimerBData_ReadByte, MFP_TimerBData_WriteByte }, /* TT MFP TBDR */ { 0xfffaa3, SIZE_BYTE, MFP_TimerCData_ReadByte, MFP_TimerCData_WriteByte }, /* TT MFP TCDR */ { 0xfffaa5, SIZE_BYTE, MFP_TimerDData_ReadByte, MFP_TimerDData_WriteByte }, /* TT MFP TDDR */ { 0xfffaa7, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* TT MFP SCR */ { 0xfffaa9, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* TT MFP UCR */ { 0xfffaab, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* TT MFP RSR */ { 0xfffaad, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* TT MFP TSR */ { 0xfffaaf, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* TT MFP UDR */ { 0xfffab1, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffab3, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffab5, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffab7, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffab9, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffabb, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffabd, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffabf, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc00, SIZE_BYTE, ACIA_IKBD_Read_SR, ACIA_IKBD_Write_CR }, { 0xfffc02, SIZE_BYTE, ACIA_IKBD_Read_RDR, ACIA_IKBD_Write_TDR }, { 0xfffc04, SIZE_BYTE, Midi_Control_ReadByte, Midi_Control_WriteByte }, { 0xfffc06, SIZE_BYTE, Midi_Data_ReadByte, Midi_Data_WriteByte }, { 0xfffc08, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc0a, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc0c, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0xfffc0e, SIZE_BYTE, IoMem_VoidRead, IoMem_VoidWrite }, /* No bus error here */ { 0, 0, NULL, NULL } }; hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/joy.c000066400000000000000000000666621504763705000223150ustar00rootroot00000000000000/* Hatari - joy.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Joystick routines. NOTE: The ST uses the joystick port 1 as the default controller. */ const char Joy_fileid[] = "Hatari joy.c"; #include #include "main.h" #include "configuration.h" #include "ioMem.h" #include "joy.h" #include "log.h" #include "m68000.h" #include "video.h" #include "statusbar.h" #define JOY_DEBUG 0 #if JOY_DEBUG #define Dprintf(a) printf a #else #define Dprintf(a) #endif #define JOYREADING_BUTTON1 1 /* bit 0, regular fire button */ #define JOYREADING_BUTTON2 2 /* bit 1, space / jump button */ #define JOYREADING_BUTTON3 4 /* bit 2, autofire button */ #define STE_JOY_ANALOG_MIN_VALUE 0x04 /* minimum value for STE analog joystick/paddle axis */ #define STE_JOY_ANALOG_MID_VALUE 0x24 /* neutral mid value for STE analog joystick/paddle axis */ #define STE_JOY_ANALOG_MAX_VALUE 0x43 /* maximum value for STE analog joystick/paddle axis */ typedef struct { int XPos,YPos; /* the actually read axis values in range of -32768...0...32767 */ int XAxisID,YAxisID; /* the IDs of the physical PC joystick's axis to be used to gain ST joystick axis input */ int Buttons; /* JOYREADING_BUTTON1 */ } JOYREADING; typedef struct { const char *SDLJoystickName; int XAxisID,YAxisID; /* the IDs associated with a certain SDL joystick */ } JOYAXISMAPPING; static SDL_Joystick *sdlJoystick[ JOYSTICK_COUNT ] = /* SDL's joystick structures */ { NULL, NULL, NULL, NULL, NULL, NULL }; /* Further explanation see JoyInit() */ static JOYAXISMAPPING const *sdlJoystickMapping[ JOYSTICK_COUNT ] = /* references which axis are actually in use by the selected SDL joystick */ { NULL, NULL, NULL, NULL, NULL, NULL }; static bool bJoystickWorking[ JOYSTICK_COUNT ] = /* Is joystick plugged in and working? */ { false, false, false, false, false, false }; int JoystickSpaceBar = JOYSTICK_SPACE_NULL; /* State of space-bar on joystick button 2 */ static uint32_t nJoyKeyEmu[JOYSTICK_COUNT]; static uint16_t nSteJoySelect; /** * Get joystick name */ const char *Joy_GetName(int id) { return SDL_JoystickName(sdlJoystick[id]); } /** * Return maximum available real joystick ID, or * zero on error or no joystick (to avoid invalid array accesses) */ int Joy_GetMaxId(void) { int count = SDL_NumJoysticks(); if (count > JOYSTICK_COUNT) count = JOYSTICK_COUNT; if (count > 0) return count - 1; return 0; } /** * Make sure real Joystick ID is valid, and if not, disable it & return false */ bool Joy_ValidateJoyId(int i) { int joyid = ConfigureParams.Joysticks.Joy[i].nJoyId; /* Unavailable joystick ID -> disable it if necessary */ if (ConfigureParams.Joysticks.Joy[i].nJoystickMode == JOYSTICK_REALSTICK && !bJoystickWorking[joyid]) { Log_Printf(LOG_WARN, "Selected real Joystick %d unavailable, disabling ST joystick %d\n", joyid, i); ConfigureParams.Joysticks.Joy[i].nJoystickMode = JOYSTICK_DISABLED; ConfigureParams.Joysticks.Joy[i].nJoyId = 0; return false; } return true; } /*-----------------------------------------------------------------------*/ /** * This function initialises the (real) joysticks. */ void Joy_Init(void) { /* Joystick axis mapping table */ /* Matthias Arndt */ /* Somehow, not all SDL joysticks are created equal. */ /* Not all pads or sticks use axis 0 for x and axis 1 */ /* for y information. */ /* This table allows to remap the axis to used. */ /* A joystick is identified by its SDL name and */ /* followed by the X axis to use and the Y axis. */ /* Find out the axis number with the tool jstest. */ /* FIXME: Read those settings from a configuration file and make them tunable from the GUI. */ static const JOYAXISMAPPING AxisMappingTable [] = { /* Example entry for mapping joystick axis for a certain device: */ /* USB game pad with ID ID 0079:0011, sold by Speedlink with axis 3 and 4 used */ /*{"USB Gamepad" , 3, 4}, */ /* Default entry used if no other SDL joystick name does match (should be last of this list) */ {"*DEFAULT*" , 0, 1}, }; int i, j, nPadsConnected; /* Initialise SDL's joystick subsystem: */ if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { Log_Printf(LOG_ERROR, "Could not init joysticks: %s\n", SDL_GetError()); return; } /* Scan joystick connection array for working joysticks */ nPadsConnected = SDL_NumJoysticks(); for (i = 0; i < nPadsConnected && i < JOYSTICK_COUNT ; i++) { /* Open the joystick for use */ sdlJoystick[i] = SDL_JoystickOpen(i); /* Is joystick ok? */ if (sdlJoystick[i] != NULL) { /* Set as working */ bJoystickWorking[i] = true; Log_Printf(LOG_DEBUG, "Joystick %i: %s\n", i, Joy_GetName(i)); /* determine joystick axis mapping for given SDL joystick name, last is default: */ for (j = 0; j < ARRAY_SIZE(AxisMappingTable)-1; j++) { /* check if ID string matches the one reported by SDL: */ if(strncmp(AxisMappingTable[j].SDLJoystickName, Joy_GetName(i), strlen(AxisMappingTable[j].SDLJoystickName)) == 0) break; } sdlJoystickMapping[i] = &(AxisMappingTable[j]); Log_Printf(LOG_DEBUG, "Joystick %i maps axis %d and %d (%s)\n", i, sdlJoystickMapping[i]->XAxisID, sdlJoystickMapping[i]->YAxisID, sdlJoystickMapping[i]->SDLJoystickName ); } } for (i = 0; i < JOYSTICK_COUNT ; i++) Joy_ValidateJoyId(i); JoystickSpaceBar = JOYSTICK_SPACE_NULL; } /*-----------------------------------------------------------------------*/ /** * Close the (real) joysticks. */ void Joy_UnInit(void) { int i, nPadsConnected; nPadsConnected = SDL_NumJoysticks(); for (i = 0; i < nPadsConnected && i < JOYSTICK_COUNT ; i++) { if (bJoystickWorking[i] == true) { bJoystickWorking[i] = false; SDL_JoystickClose(sdlJoystick[i]); } sdlJoystick[i] = NULL; sdlJoystickMapping[i] = NULL; } } /*-----------------------------------------------------------------------*/ /** * Read details from joystick using SDL calls * NOTE ID is that of SDL */ static bool Joy_ReadJoystick(int nStJoyId, JOYREADING *pJoyReading) { int nSdlJoyID = ConfigureParams.Joysticks.Joy[nStJoyId].nJoyId; unsigned hat = SDL_JoystickGetHat(sdlJoystick[nSdlJoyID], 0); /* Joystick is OK, read position from the configured joystick axis */ pJoyReading->XPos = SDL_JoystickGetAxis(sdlJoystick[nSdlJoyID], pJoyReading->XAxisID); pJoyReading->YPos = SDL_JoystickGetAxis(sdlJoystick[nSdlJoyID], pJoyReading->YAxisID); /* Similarly to other emulators that support hats, override axis readings with hats */ if (hat & SDL_HAT_LEFT) pJoyReading->XPos = -32768; if (hat & SDL_HAT_RIGHT) pJoyReading->XPos = 32767; if (hat & SDL_HAT_UP) pJoyReading->YPos = -32768; if (hat & SDL_HAT_DOWN) pJoyReading->YPos = 32767; pJoyReading->Buttons = 0; /* Sets bits based on pressed buttons */ for (int i = 0; i < JOYSTICK_BUTTONS; i++) { int button = ConfigureParams.Joysticks.Joy[nStJoyId].nJoyButMap[i]; if (button >= 0 && SDL_JoystickGetButton(sdlJoystick[nSdlJoyID], button)) pJoyReading->Buttons |= 1 << i; } return true; } /*-----------------------------------------------------------------------*/ /** * Enable PC Joystick button press to mimic space bar * (For XenonII, Flying Shark etc...) or joystick up (jump) * * Return up bit or zero */ static uint8_t Joy_ButtonSpaceJump(int press, bool jump) { /* If "Jump on Button" is enabled, button acts as "ST Joystick up" */ if (jump) { if (press) return ATARIJOY_BITMASK_UP; return 0; } /* Otherwise, it acts as pressing SPACE on the ST keyboard * * "JoystickSpaceBar" goes through following transitions: * - JOYSTICK_SPACE_NULL (joy.c: init) * - JOYSTICK_SPACE_DOWN (joy.c: button pressed) * - JOYSTICK_SPACE_DOWNED (ikbd.c: space pressed) * - JOYSTICK_SPACE_UP (joy.c: button released) * - JOYSTICK_SPACE_NULL (ikbd.c: space released) */ if (press) { if (JoystickSpaceBar == JOYSTICK_SPACE_NULL) JoystickSpaceBar = JOYSTICK_SPACE_DOWN; } else { if (JoystickSpaceBar == JOYSTICK_SPACE_DOWNED) JoystickSpaceBar = JOYSTICK_SPACE_UP; } return 0; } /*-----------------------------------------------------------------------*/ /** * Read details from joystick using SDL calls. Returns the SDL joystick ID or -1 if not found. */ static int Joy_ReadAxisConfig(int nStJoyId, JOYREADING *pJoyReading) { int nSdlJoyId = ConfigureParams.Joysticks.Joy[nStJoyId].nJoyId; if (nSdlJoyId < 0 || !bJoystickWorking[nSdlJoyId]) return -1; /* How many axes are there on the corresponding SDL joystick? */ int nAxes = SDL_JoystickNumAxes(sdlJoystick[nSdlJoyId]); /* get joystick axis from configuration settings and make them plausible */ pJoyReading->XAxisID = sdlJoystickMapping[nSdlJoyId]->XAxisID; pJoyReading->YAxisID = sdlJoystickMapping[nSdlJoyId]->YAxisID; /* make selected axis IDs plausible */ if( (pJoyReading->XAxisID == pJoyReading->YAxisID) /* same joystick axis for two directions? */ ||(pJoyReading->XAxisID > nAxes) /* ID for x axis beyond nr of existing axes? */ ||(pJoyReading->YAxisID > nAxes) /* ID for y axis beyond nr of existing axes? */ ) { /* define sane SDL joystick axis defaults and prepare them for saving back to the config file: */ pJoyReading->XAxisID = 0; pJoyReading->YAxisID = 1; } return nSdlJoyId; } /*-----------------------------------------------------------------------*/ /** * Read PC joystick and return ST format byte, i.e. lower 4 bits direction * and top bit fire. * NOTE : ID 0 is Joystick 0/Mouse and ID 1 is Joystick 1 (default), * ID 2 and 3 are STE joypads and ID 4 and 5 are parport joysticks. */ uint8_t Joy_GetStickData(int nStJoyId) { uint8_t nData = 0; JOYREADING JoyReading; /* Are we emulating the joystick via the keyboard? */ if (ConfigureParams.Joysticks.Joy[nStJoyId].nJoystickMode == JOYSTICK_KEYBOARD) { /* If holding 'SHIFT' we actually want cursor key movement, so ignore any of this */ if ( !(SDL_GetModState()&(KMOD_LSHIFT|KMOD_RSHIFT)) ) { nData = nJoyKeyEmu[nStJoyId] & 0xff; } } else if (ConfigureParams.Joysticks.Joy[nStJoyId].nJoystickMode == JOYSTICK_REALSTICK) { /* map to SDL stick and Axes */ int nSdlJoyId = Joy_ReadAxisConfig(nStJoyId, &JoyReading); if (nSdlJoyId < 0) { return 0; } /* Read real joystick and map to emulated ST joystick for emulation */ if (!Joy_ReadJoystick(nStJoyId, &JoyReading)) { /* Something is wrong, we cannot read the joystick from SDL */ bJoystickWorking[nSdlJoyId] = false; return 0; } /* Directions */ if (JoyReading.YPos <= JOYRANGE_UP_VALUE) nData |= ATARIJOY_BITMASK_UP; else if (JoyReading.YPos >= JOYRANGE_DOWN_VALUE) nData |= ATARIJOY_BITMASK_DOWN; if (JoyReading.XPos <= JOYRANGE_LEFT_VALUE) nData |= ATARIJOY_BITMASK_LEFT; else if (JoyReading.XPos >= JOYRANGE_RIGHT_VALUE) nData |= ATARIJOY_BITMASK_RIGHT; /* PC Joystick button 1 is set as ST joystick button */ if (JoyReading.Buttons & JOYREADING_BUTTON1) nData |= ATARIJOY_BITMASK_FIRE; /* PC Joystick button 2 mimics space bar or jump */ const bool press = JoyReading.Buttons & JOYREADING_BUTTON2; const bool jump = ConfigureParams.Joysticks.Joy[nStJoyId].bEnableJumpOnFire2; nData |= Joy_ButtonSpaceJump(press, jump); /* PC Joystick button 3 is autofire button for ST joystick button */ if (JoyReading.Buttons & JOYREADING_BUTTON3) { nData |= ATARIJOY_BITMASK_FIRE; if ((nVBLs&0x7)<4) nData &= ~ATARIJOY_BITMASK_FIRE; /* Remove top bit! */ } } /* Ignore fire button every 8 frames if enabled autofire (for both cursor emulation and joystick) */ if (ConfigureParams.Joysticks.Joy[nStJoyId].bEnableAutoFire) { if ((nVBLs&0x7)<4) nData &= ~ATARIJOY_BITMASK_FIRE; /* Remove top bit! */ } return nData; } /*-----------------------------------------------------------------------*/ /** * Get the fire button states. * Note: More than one fire buttons are only supported for real joystick, * not for keyboard emulation! */ static int Joy_GetFireButtons(int nStJoyId) { int nButtons = 0; int nSdlJoyId; int i, nMaxButtons; nSdlJoyId = ConfigureParams.Joysticks.Joy[nStJoyId].nJoyId; /* Are we emulating the joystick via the keyboard? */ if (ConfigureParams.Joysticks.Joy[nStJoyId].nJoystickMode == JOYSTICK_KEYBOARD) { nButtons |= nJoyKeyEmu[nStJoyId] >> 7; } else if (ConfigureParams.Joysticks.Joy[nStJoyId].nJoystickMode == JOYSTICK_REALSTICK && bJoystickWorking[nSdlJoyId]) { nMaxButtons = SDL_JoystickNumButtons(sdlJoystick[nSdlJoyId]); if (nMaxButtons > 17) nMaxButtons = 17; /* Now read all fire buttons and set a bit for each pressed button: */ for (i = 0; i < nMaxButtons; i++) { if (SDL_JoystickGetButton(sdlJoystick[nSdlJoyId], i)) { nButtons |= (1 << i); } } } return nButtons; } /*-----------------------------------------------------------------------*/ /** * Set joystick cursor emulation for given port. This assumes that * if the same keys have been defined for "cursor key emulation" in * other ports, the emulation for them has been switched off. Returns * 1 if the port number was OK, zero for error. */ bool Joy_SetCursorEmulation(int port) { if (port < 0 || port >= JOYSTICK_COUNT) { return false; } ConfigureParams.Joysticks.Joy[port].nJoystickMode = JOYSTICK_KEYBOARD; return true; } /*-----------------------------------------------------------------------*/ /** * Toggle joystick cursor emulation between port 0, port 1 and being off * from them. When it's turned off from them, the port's previous state * is restored */ void Joy_ToggleCursorEmulation(void) { static JOYSTICKMODE saved[2] = { JOYSTICK_DISABLED, JOYSTICK_DISABLED }; JOYSTICKMODE state; int i, port = 2; for (i = 0; i < 2; i++) { state = ConfigureParams.Joysticks.Joy[i].nJoystickMode; if (state == JOYSTICK_KEYBOARD) { port = i; } else { saved[i] = state; } } switch (port) { case 0: /* (only) in port 0, disable cursor emu */ ConfigureParams.Joysticks.Joy[0].nJoystickMode = saved[0]; break; case 1: /* (at least) in port 1, switch cursor emu to port 0 */ ConfigureParams.Joysticks.Joy[1].nJoystickMode = saved[1]; ConfigureParams.Joysticks.Joy[0].nJoystickMode = JOYSTICK_KEYBOARD; break; default: /* neither in port 0 or 1, enable cursor emu to port 1 */ ConfigureParams.Joysticks.Joy[1].nJoystickMode = JOYSTICK_KEYBOARD; } Statusbar_UpdateInfo(); } /*-----------------------------------------------------------------------*/ /** * Switch between joystick types in given joyport */ bool Joy_SwitchMode(int port) { int mode; if (port < 0 || port >= JOYSTICK_COUNT) { return false; } mode = (ConfigureParams.Joysticks.Joy[port].nJoystickMode + 1) % JOYSTICK_MODES; ConfigureParams.Joysticks.Joy[port].nJoystickMode = mode; Statusbar_UpdateInfo(); return true; } /*-----------------------------------------------------------------------*/ /** * Translate key press into joystick/-pad button */ static uint32_t Joy_KeyToButton(int joyid, int symkey) { uint32_t nButtons = 0; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeFire) nButtons |= ATARIJOY_BITMASK_FIRE; if (joyid != JOYID_JOYPADA && joyid != JOYID_JOYPADB) return nButtons; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeB) nButtons |= 0x100; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeC) nButtons |= 0x200; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeOption) nButtons |= 0x400; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodePause) nButtons |= 0x800; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeHash) nButtons |= 0x1000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[9]) nButtons |= 0x2000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[6]) nButtons |= 0x4000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[3]) nButtons |= 0x8000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[0]) nButtons |= 0x10000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[8]) nButtons |= 0x20000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[5]) nButtons |= 0x40000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[2]) nButtons |= 0x80000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeStar) nButtons |= 0x100000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[7]) nButtons |= 0x200000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[4]) nButtons |= 0x400000; if (symkey == ConfigureParams.Joysticks.Joy[joyid].nKeyCodeNum[1]) nButtons |= 0x800000; return nButtons; } /** * A key has been pressed down, check if we use it for joystick emulation * via keyboard. */ bool Joy_KeyDown(int symkey, int modkey) { int i; if (modkey & KMOD_SHIFT) return false; for (i = 0; i < JOYSTICK_COUNT; i++) { if (ConfigureParams.Joysticks.Joy[i].nJoystickMode != JOYSTICK_KEYBOARD) continue; if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeUp) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_DOWN; /* Disable down */ nJoyKeyEmu[i] |= ATARIJOY_BITMASK_UP; /* Enable up */ return true; } else if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeDown) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_UP; /* Disable up */ nJoyKeyEmu[i] |= ATARIJOY_BITMASK_DOWN; /* Enable down */ return true; } else if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeLeft) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_RIGHT; /* Disable right */ nJoyKeyEmu[i] |= ATARIJOY_BITMASK_LEFT; /* Enable left */ return true; } else if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeRight) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_LEFT; /* Disable left */ nJoyKeyEmu[i] |= ATARIJOY_BITMASK_RIGHT; /* Enable right */ return true; } else { uint32_t nButtons = Joy_KeyToButton(i, symkey); if (nButtons) { nJoyKeyEmu[i] |= nButtons; return true; } } } return false; } /** * A key has been released, check if we use it for joystick emulation * via keyboard. */ bool Joy_KeyUp(int symkey, int modkey) { int i; if (modkey & KMOD_SHIFT) return false; for (i = 0; i < JOYSTICK_COUNT; i++) { if (ConfigureParams.Joysticks.Joy[i].nJoystickMode != JOYSTICK_KEYBOARD) continue; if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeUp) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_UP; return true; } else if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeDown) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_DOWN; return true; } else if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeLeft) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_LEFT; return true; } else if (symkey == ConfigureParams.Joysticks.Joy[i].nKeyCodeRight) { nJoyKeyEmu[i] &= ~ATARIJOY_BITMASK_RIGHT; return true; } else { uint32_t nButtons = Joy_KeyToButton(i, symkey); if (nButtons) { nJoyKeyEmu[i] &= ~nButtons; return true; } } } return false; } /*-----------------------------------------------------------------------*/ /** * Read from STE / Falcon joypad buttons register (0xff9200) * - On MegaSTE and Falcon, the byte at $ff9200 also contains the state of the 8 DIP switches * available on the motherboard * - Note that on STE/MegaSTE $ff9200 can only be accessed as word, not byte. $ff9201 can be * accessed as byte */ void Joy_StePadButtons_DIPSwitches_ReadWord(void) { uint16_t nData = 0xff; uint8_t DIP; if ( !Config_IsMachineFalcon() && ( nIoMemAccessSize == SIZE_BYTE ) && ( IoAccessCurrentAddress == 0xff9200 ) ) { /* This register does not like to be accessed in byte mode at $ff9200 */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } if (ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nJoystickMode != JOYSTICK_DISABLED && (nSteJoySelect & 0x0f) != 0x0f) { int nButtons = Joy_GetFireButtons(JOYID_JOYPADA); if (!(nSteJoySelect & 0x1)) { if (nButtons & 0x01) /* Fire button A pressed? */ nData &= ~2; if (nButtons & 0x10) /* Fire button PAUSE pressed? */ nData &= ~1; } else if (!(nSteJoySelect & 0x2)) { if (nButtons & 0x02) /* Fire button B pressed? */ nData &= ~2; } else if (!(nSteJoySelect & 0x4)) { if (nButtons & 0x04) /* Fire button C pressed? */ nData &= ~2; } else if (!(nSteJoySelect & 0x8)) { if (nButtons & 0x08) /* Fire button OPTION pressed? */ nData &= ~2; } } if (ConfigureParams.Joysticks.Joy[JOYID_JOYPADB].nJoystickMode != JOYSTICK_DISABLED && (nSteJoySelect & 0xf0) != 0xf0) { int nButtons = Joy_GetFireButtons(JOYID_JOYPADB); if (!(nSteJoySelect & 0x10)) { if (nButtons & 0x01) /* Fire button A pressed? */ nData &= ~8; if (nButtons & 0x10) /* Fire button PAUSE pressed? */ nData &= ~4; } else if (!(nSteJoySelect & 0x20)) { if (nButtons & 0x02) /* Fire button B pressed? */ nData &= ~8; } else if (!(nSteJoySelect & 0x40)) { if (nButtons & 0x04) /* Fire button C pressed? */ nData &= ~8; } else if (!(nSteJoySelect & 0x80)) { if (nButtons & 0x08) /* Fire button OPTION pressed? */ nData &= ~8; } } /* On MegaSTE and Falcon, add the state of the 8 DIP Switches in upper byte */ if ( Config_IsMachineMegaSTE() ) DIP = IoMemTabMegaSTE_DIPSwitches_Read(); else if ( Config_IsMachineFalcon() ) DIP = IoMemTabFalcon_DIPSwitches_Read(); else DIP = 0xff; /* STE, No DIP switches */ nData |= ( DIP << 8 ); Dprintf(("0xff9200 -> 0x%04x\n", nData)); IoMem_WriteWord(0xff9200, nData); } /*-----------------------------------------------------------------------*/ /** * Write to STE / Falcon joypad buttons register (0xff9200) * This does nothing, but we still check that $ff9200 is not accessed as byte * on STE/MegaSTE, else we trigger a bus error */ void Joy_StePadButtons_DIPSwitches_WriteWord(void) { if ( !Config_IsMachineFalcon() && ( nIoMemAccessSize == SIZE_BYTE ) && ( IoAccessCurrentAddress == 0xff9200 ) ) { /* This register does not like to be accessed in byte mode at $ff9200 */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } } /*-----------------------------------------------------------------------*/ /** * Read from STE joypad direction/buttons register (0xff9202) * * This is used e.g. by Reservoir Gods' Tautology II */ void Joy_StePadMulti_ReadWord(void) { uint16_t nData = 0xff; if (ConfigureParams.Joysticks.Joy[JOYID_JOYPADA].nJoystickMode != JOYSTICK_DISABLED && (nSteJoySelect & 0x0f) != 0x0f) { nData &= 0xf0; if (!(nSteJoySelect & 0x1)) { nData |= ~Joy_GetStickData(JOYID_JOYPADA) & 0x0f; } else if (!(nSteJoySelect & 0x2)) { nData |= ~(Joy_GetFireButtons(JOYID_JOYPADA) >> 13) & 0x0f; } else if (!(nSteJoySelect & 0x4)) { nData |= ~(Joy_GetFireButtons(JOYID_JOYPADA) >> 9) & 0x0f; } else if (!(nSteJoySelect & 0x8)) { nData |= ~(Joy_GetFireButtons(JOYID_JOYPADA) >> 5) & 0x0f; } } if (ConfigureParams.Joysticks.Joy[JOYID_JOYPADB].nJoystickMode != JOYSTICK_DISABLED && (nSteJoySelect & 0xf0) != 0xf0) { nData &= 0x0f; if (!(nSteJoySelect & 0x10)) { nData |= ~Joy_GetStickData(JOYID_JOYPADB) << 4; } else if (!(nSteJoySelect & 0x20)) { nData |= ~(Joy_GetFireButtons(JOYID_JOYPADB) >> (13-4)) & 0xf0; } else if (!(nSteJoySelect & 0x40)) { nData |= ~(Joy_GetFireButtons(JOYID_JOYPADB) >> (9-4)) & 0xf0; } else if (!(nSteJoySelect & 0x80)) { nData |= ~(Joy_GetFireButtons(JOYID_JOYPADB) >> (5-4)) & 0xf0; } } nData = (nData << 8) | 0x0ff; Dprintf(("0xff9202 -> 0x%04x\n", nData)); IoMem_WriteWord(0xff9202, nData); } /*-----------------------------------------------------------------------*/ /** * Write to STE joypad selection register (0xff9202) */ void Joy_StePadMulti_WriteWord(void) { nSteJoySelect = IoMem_ReadWord(0xff9202); Dprintf(("0xff9202 <- 0x%04x\n", nSteJoySelect)); } /*-----------------------------------------------------------------------*/ /** * Read STE lightpen X register (0xff9220) */ void Joy_SteLightpenX_ReadWord(void) { uint16_t nData = 0; /* Lightpen is not supported yet */ Dprintf(("0xff9220 -> 0x%04x\n", nData)); if (nIoMemAccessSize == SIZE_BYTE) { /* This register does not like to be accessed in byte mode */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } IoMem_WriteWord(0xff9220, nData); } /** * Read STE lightpen Y register (0xff9222) */ void Joy_SteLightpenY_ReadWord(void) { uint16_t nData = 0; /* Lightpen is not supported yet */ Dprintf(("0xff9222 -> 0x%04x\n", nData)); if (nIoMemAccessSize == SIZE_BYTE) { /* This register does not like to be accessed in byte mode */ M68000_BusError(IoAccessFullAddress, BUS_ERROR_READ, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); return; } IoMem_WriteWord(0xff9222, nData); } /*-----------------------------------------------------------------------*/ /** * Read PC joystick and return ST format analog value byte */ static uint8_t Joy_GetStickAnalogData(int nStJoyId, bool isXAxis) { /* Only makes sense to call this for STE pads */ assert(nStJoyId == 2 || nStJoyId == 3); /* Default to middle of Axis */ uint8_t nData = STE_JOY_ANALOG_MID_VALUE; /* Are we emulating the joystick via the keyboard? */ if (ConfigureParams.Joysticks.Joy[nStJoyId].nJoystickMode == JOYSTICK_KEYBOARD) { /* If holding 'SHIFT' we actually want cursor key movement, so ignore any of this */ if ( !(SDL_GetModState()&(KMOD_LSHIFT|KMOD_RSHIFT)) ) { uint8_t digiData = nJoyKeyEmu[nStJoyId]; uint8_t bitmaskMin = isXAxis ? ATARIJOY_BITMASK_LEFT : ATARIJOY_BITMASK_UP; uint8_t bitmaskMax = isXAxis ? ATARIJOY_BITMASK_RIGHT : ATARIJOY_BITMASK_DOWN; if (digiData & bitmaskMin) { nData = STE_JOY_ANALOG_MIN_VALUE; } else if (digiData & bitmaskMax) { nData = STE_JOY_ANALOG_MAX_VALUE; } } } else if (ConfigureParams.Joysticks.Joy[nStJoyId].nJoystickMode == JOYSTICK_REALSTICK) { JOYREADING JoyReading; /* map to SDL stick and Axes */ int nSdlJoyId = Joy_ReadAxisConfig(nStJoyId, &JoyReading); if (nSdlJoyId < 0) { return nData; } /* Read real joystick and map to emulated ST joystick for emulation */ if (!Joy_ReadJoystick(nStJoyId, &JoyReading)) { /* Something is wrong, we cannot read the joystick from SDL */ bJoystickWorking[nSdlJoyId] = false; } else { int sdl_reading = isXAxis ? JoyReading.XPos : JoyReading.YPos; if (sdl_reading < -32768) sdl_reading = -32768; unsigned int usdl_reading = 32768 + sdl_reading; nData = STE_JOY_ANALOG_MIN_VALUE + ((usdl_reading & 0xff00) >> 8) / STE_JOY_ANALOG_MIN_VALUE; } } return nData; } /** * Read STE Pad 0 Analog X register (0xff9211) */ void Joy_StePadAnalog0X_ReadByte(void) { uint8_t nData = Joy_GetStickAnalogData(2, true); Dprintf(("0xff9211 -> 0x%02x\n", nData)); IoMem_WriteByte(0xff9211, nData); } /** * Read STE Pad 0 Analog Y register (0xff9213) */ void Joy_StePadAnalog0Y_ReadByte(void) { uint8_t nData = Joy_GetStickAnalogData(2, false); Dprintf(("0xff9213 -> 0x%02x\n", nData)); IoMem_WriteByte(0xff9213, nData); } /** * Read STE Pad 1 Analog X register (0xff9215) */ void Joy_StePadAnalog1X_ReadByte(void) { uint8_t nData = Joy_GetStickAnalogData(3, true); Dprintf(("0xff9215 -> 0x%02x\n", nData)); IoMem_WriteByte(0xff9215, nData); } /** * Read STE Pad 1 Analog Y register (0xff9217) */ void Joy_StePadAnalog1Y_ReadByte(void) { uint8_t nData = Joy_GetStickAnalogData(3, false); Dprintf(("0xff9217 -> 0x%02x\n", nData)); IoMem_WriteByte(0xff9217, nData); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/keymap.c000066400000000000000000000727501504763705000227750ustar00rootroot00000000000000/* Hatari - keymap.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Map SDL key events to ST scancodes and send them to IKBD as pressed/released keys. Based on Hatari configuration options, several different ways can be used to map SDL key events. See https://tho-otto.de/keyboards/ for the Atari ST keyboard layouts. */ const char Keymap_fileid[] = "Hatari keymap.c"; #include #include "main.h" #include "keymap.h" #include "configuration.h" #include "file.h" #include "ikbd.h" #include "nvram.h" #include "joy.h" #include "shortcut.h" #include "str.h" #include "tos.h" #include "debugui.h" #include "log.h" /* if not able to map */ #define ST_NO_SCANCODE 0xff /* Some ST keyboard scancodes */ #define ST_ESC 0x01 #define ST_CONTROL 0x1d #define ST_LSHIFT 0x2a #define ST_RSHIFT 0x36 #define ST_ALTERNATE 0x38 #define ST_CAPSLOCK 0x3a /* Table for loaded keys: */ static int LoadedKeymap[KBD_MAX_SCANCODE][2]; static bool keymap_loaded; /* List of ST scan codes to NOT de-bounce when running in maximum speed */ static const uint8_t DebounceExtendedKeys[] = { ST_CONTROL, ST_LSHIFT, ST_ESC, ST_ALTERNATE, ST_RSHIFT, 0 /* End of list */ }; /*-----------------------------------------------------------------------*/ /** * Initialization. */ void Keymap_Init(void) { Keymap_LoadRemapFile(ConfigureParams.Keyboard.szMappingFileName); } /** * Default function for mapping SDL symbolic key to ST scan code. * This contains the ST keycode used by the majority of TOS regions for * that semantic symbol. */ static uint8_t Keymap_SymbolicToStScanCode_default(const SDL_Keysym* pKeySym) { uint8_t code; switch (pKeySym->sym) { case SDLK_BACKSPACE: code = 0x0E; break; case SDLK_TAB: code = 0x0F; break; case SDLK_CLEAR: code = 0x47; break; case SDLK_RETURN: code = 0x1C; break; case SDLK_ESCAPE: code = ST_ESC; break; case SDLK_SPACE: code = 0x39; break; case SDLK_EXCLAIM: code = 0x09; break; /* on azerty? */ case SDLK_QUOTEDBL: code = 0x04; break; /* on azerty? */ case SDLK_HASH: code = 0x2B; break; /* DE, UK host only, for FR/UK/DK/NL TOS */ case SDLK_DOLLAR: code = 0x1b; break; /* on azerty */ case SDLK_AMPERSAND: code = 0x02; break; /* on azerty? */ case SDLK_QUOTE: code = 0x28; break; case SDLK_LEFTPAREN: code = 0x63; break; case SDLK_RIGHTPAREN: code = 0x64; break; case SDLK_ASTERISK: code = 0x66; break; case SDLK_PLUS: code = 0x4e; break; case SDLK_COMMA: code = 0x33; break; case SDLK_MINUS: code = 0x35; break; /* default for DE/IT/SE/CH/FI/NO/DK/CZ */ case SDLK_PERIOD: code = 0x34; break; case SDLK_SLASH: code = 0x35; break; case SDLK_0: code = 0x0B; break; case SDLK_1: code = 0x02; break; case SDLK_2: code = 0x03; break; case SDLK_3: code = 0x04; break; case SDLK_4: code = 0x05; break; case SDLK_5: code = 0x06; break; case SDLK_6: code = 0x07; break; case SDLK_7: code = 0x08; break; case SDLK_8: code = 0x09; break; case SDLK_9: code = 0x0A; break; case SDLK_COLON: code = 0x34; break; case SDLK_SEMICOLON: code = 0x27; break; case SDLK_LESS: code = 0x60; break; case SDLK_EQUALS: code = 0x0D; break; case SDLK_GREATER : code = 0x34; break; case SDLK_QUESTION: code = 0x35; break; case SDLK_AT: code = 0x28; break; case SDLK_LEFTBRACKET: code = 0x1A; break; case SDLK_BACKSLASH: code = 0x2B; break; case SDLK_RIGHTBRACKET: code = 0x1B; break; case SDLK_CARET: code = 0x2B; break; case SDLK_UNDERSCORE: code = 0x0C; break; case SDLK_BACKQUOTE: code = 0x29; break; case SDLK_a: code = 0x1E; break; case SDLK_b: code = 0x30; break; case SDLK_c: code = 0x2E; break; case SDLK_d: code = 0x20; break; case SDLK_e: code = 0x12; break; case SDLK_f: code = 0x21; break; case SDLK_g: code = 0x22; break; case SDLK_h: code = 0x23; break; case SDLK_i: code = 0x17; break; case SDLK_j: code = 0x24; break; case SDLK_k: code = 0x25; break; case SDLK_l: code = 0x26; break; case SDLK_m: code = 0x32; break; case SDLK_n: code = 0x31; break; case SDLK_o: code = 0x18; break; case SDLK_p: code = 0x19; break; case SDLK_q: code = 0x10; break; case SDLK_r: code = 0x13; break; case SDLK_s: code = 0x1F; break; case SDLK_t: code = 0x14; break; case SDLK_u: code = 0x16; break; case SDLK_v: code = 0x2F; break; case SDLK_w: code = 0x11; break; case SDLK_x: code = 0x2D; break; case SDLK_y: code = 0x15; break; case SDLK_z: code = 0x2C; break; case SDLK_DELETE: code = 0x53; break; /* End of ASCII mapped keysyms */ case 167: code = 0x29; break; /* Swiss § */ case 168: code = 0x1B; break; /* Swiss ¨ */ case 176: code = 0x35; break; /* Spanish ° */ case 178: code = 0x29; break; /* French ² */ case 180: code = 0x0D; break; /* German ' */ case 223: code = 0x0C; break; /* German ß */ case 224: code = 0x0B; break; /* French à */ case 225: code = 0x09; break; /* Czech á */ case 228: code = 0x28; break; /* German ä */ case 229: code = 0x1A; break; /* Swedish å */ case 231: code = 0x0A; break; /* French ç */ case 232: code = 0x08; break; /* French è */ case 233: code = 0x03; break; /* French é */ case 236: code = 0x0D; break; /* Italian ì */ case 237: code = 0x0A; break; /* Czech í */ case 241: code = 0x27; break; /* Spanish ñ */ case 242: code = 0x27; break; /* Italian ò */ case 243: code = 0x02; break; /* Czech ó */ case 246: code = 0x27; break; /* German ö */ case 249: code = 0x28; break; /* French ù */ case 250: code = 0x1A; break; /* Czech ú */ case 252: code = 0x1A; break; /* German ü */ case 253: code = 0x08; break; /* Czech ý */ case 269: code = 0x05; break; /* Czech č */ case 271: code = 0x1B; break; /* Czech ď */ case 283: code = 0x03; break; /* Czech ě */ case 328: code = 0x2B; break; /* Czech ň */ case 345: code = 0x06; break; /* Czech ř */ case 353: code = 0x04; break; /* Czech š */ case 357: code = 0x28; break; /* Czech ť */ case 367: code = 0x27; break; /* Czech ů */ case 382: code = 0x07; break; /* Czech ž */ /* Numeric keypad: */ case SDLK_KP_0: code = 0x70; break; case SDLK_KP_1: code = 0x6D; break; case SDLK_KP_2: code = 0x6E; break; case SDLK_KP_3: code = 0x6F; break; case SDLK_KP_4: code = 0x6A; break; case SDLK_KP_5: code = 0x6B; break; case SDLK_KP_6: code = 0x6C; break; case SDLK_KP_7: code = 0x67; break; case SDLK_KP_8: code = 0x68; break; case SDLK_KP_9: code = 0x69; break; case SDLK_KP_PERIOD: code = 0x71; break; case SDLK_KP_LEFTPAREN: code = 0x63; break; case SDLK_KP_RIGHTPAREN: code = 0x64; break; case SDLK_KP_DIVIDE: code = 0x65; break; case SDLK_KP_MULTIPLY: code = 0x66; break; case SDLK_KP_MINUS: code = 0x4A; break; case SDLK_KP_PLUS: code = 0x4E; break; case SDLK_KP_ENTER: code = 0x72; break; case SDLK_KP_EQUALS: code = 0x61; break; /* Arrows + Home/End pad */ case SDLK_UP: code = 0x48; break; case SDLK_DOWN: code = 0x50; break; case SDLK_RIGHT: code = 0x4D; break; case SDLK_LEFT: code = 0x4B; break; case SDLK_INSERT: code = 0x52; break; case SDLK_HOME: code = 0x47; break; case SDLK_END: code = 0x61; break; /* ST Undo */ case SDLK_PAGEUP: code = 0x63; break; /* ST ( */ case SDLK_PAGEDOWN: code = 0x64; break; /* ST ) */ /* Function keys */ case SDLK_F1: code = 0x3B; break; case SDLK_F2: code = 0x3C; break; case SDLK_F3: code = 0x3D; break; case SDLK_F4: code = 0x3E; break; case SDLK_F5: code = 0x3F; break; case SDLK_F6: code = 0x40; break; case SDLK_F7: code = 0x41; break; case SDLK_F8: code = 0x42; break; case SDLK_F9: code = 0x43; break; case SDLK_F10: code = 0x44; break; case SDLK_F11: code = 0x62; break; /* ST Help */ case SDLK_F12: code = 0x61; break; /* ST Undo */ case SDLK_F13: code = 0x62; break; /* ST Help */ /* Key state modifier keys */ case SDLK_CAPSLOCK: code = ST_CAPSLOCK; break; case SDLK_SCROLLLOCK: code = 0x61; break; /* ST Undo */ case SDLK_RSHIFT: code = ST_RSHIFT; break; case SDLK_LSHIFT: code = ST_LSHIFT; break; case SDLK_RCTRL: code = ST_CONTROL; break; case SDLK_LCTRL: code = ST_CONTROL; break; case SDLK_RALT: code = ST_ALTERNATE; break; case SDLK_LALT: code = ST_ALTERNATE; break; /* Miscellaneous function keys */ case SDLK_HELP: code = 0x62; break; case SDLK_PRINTSCREEN: code = 0x62; break; /* ST Help */ case SDLK_UNDO: code = 0x61; break; default: code = ST_NO_SCANCODE; } return code; } static uint8_t (*Keymap_SymbolicToStScanCode)(const SDL_Keysym* pKeySym) = Keymap_SymbolicToStScanCode_default; static uint8_t Keymap_SymbolicToStScanCode_US(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_MINUS: return 0x0C; default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_DE(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_HASH: return 0x29; case SDLK_PLUS: return 0x1B; case SDLK_SLASH: return 0x65; case SDLK_y: return 0x2C; case SDLK_z: return 0x15; default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_FR(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_QUOTE: return 0x05; case SDLK_LEFTPAREN: return 0x06; case SDLK_RIGHTPAREN: return 0x0c; case SDLK_COMMA: return 0x32; case SDLK_MINUS: return 0x0D; case SDLK_SEMICOLON: return 0x33; case SDLK_EQUALS: return 0x35; case SDLK_CARET: return 0x1A; case SDLK_a: return 0x10; case SDLK_m: return 0x27; case SDLK_q: return 0x1E; case SDLK_w: return 0x2C; case SDLK_z: return 0x11; case 167: return 0x07; /* French § */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_UK(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_MINUS: return 0x0C; case SDLK_BACKSLASH: return 0x60; default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_ES(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_MINUS: return 0x0C; case SDLK_SEMICOLON: return 0x28; case SDLK_BACKQUOTE: return 0x1B; case 231: return 0x29; /* Spanish ç */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_IT(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_QUOTE: return 0x0C; case SDLK_PLUS: return 0x1B; case 224: return 0x28; /* Italian à */ case 232: return 0x1A; /* Italian è */ case 249: return 0x29; /* Italian ù */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_SE(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_QUOTE: return 0x29; case SDLK_PLUS: return 0x0C; case 252: return 0x1b; /* ü */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } /* Mapping for both, French and German variant of Swiss keyboard */ static uint8_t Keymap_SymbolicToStScanCode_CH(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_CARET: return 0x0D; case 224: return 0x28; /* à */ case 232: return 0x1A; /* è */ case 233: return 0x27; /* é */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_NO(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_QUOTE: return 0x29; case SDLK_PLUS: return 0x0C; case 230: return 0x28; /* æ */ case 233: return 0x0D; /* é */ case 248: return 0x27; /* ø */ case 252: return 0x1b; /* ü */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_DK(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_QUOTE: return 0x0D; case SDLK_PLUS: return 0x0C; case SDLK_ASTERISK: return 0x1B; case 230: return 0x27; /* æ */ case 233: return 0x29; /* é */ case 248: return 0x28; /* ø */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_NL(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_MINUS: return 0x0C; case SDLK_BACKSLASH: return 0x60; default: return Keymap_SymbolicToStScanCode_default(keysym); } } static uint8_t Keymap_SymbolicToStScanCode_CZ(const SDL_Keysym* keysym) { switch (keysym->sym) { case SDLK_HASH: return 0x29; case SDLK_QUOTE: return 0x0D; case SDLK_EQUALS: return 0x0C; case SDLK_y: return 0x2C; case SDLK_z: return 0x15; case 233: return 0x0B; /* é */ default: return Keymap_SymbolicToStScanCode_default(keysym); } } /** * Remap SDL scancode key to ST Scan code */ static uint8_t Keymap_PcToStScanCode(const SDL_Keysym* pKeySym) { switch (pKeySym->scancode) { case SDL_SCANCODE_A: return 0x1e; case SDL_SCANCODE_B: return 0x30; case SDL_SCANCODE_C: return 0x2e; case SDL_SCANCODE_D: return 0x20; case SDL_SCANCODE_E: return 0x12; case SDL_SCANCODE_F: return 0x21; case SDL_SCANCODE_G: return 0x22; case SDL_SCANCODE_H: return 0x23; case SDL_SCANCODE_I: return 0x17; case SDL_SCANCODE_J: return 0x24; case SDL_SCANCODE_K: return 0x25; case SDL_SCANCODE_L: return 0x26; case SDL_SCANCODE_M: return 0x32; case SDL_SCANCODE_N: return 0x31; case SDL_SCANCODE_O: return 0x18; case SDL_SCANCODE_P: return 0x19; case SDL_SCANCODE_Q: return 0x10; case SDL_SCANCODE_R: return 0x13; case SDL_SCANCODE_S: return 0x1f; case SDL_SCANCODE_T: return 0x14; case SDL_SCANCODE_U: return 0x16; case SDL_SCANCODE_V: return 0x2f; case SDL_SCANCODE_W: return 0x11; case SDL_SCANCODE_X: return 0x2d; case SDL_SCANCODE_Y: return 0x15; case SDL_SCANCODE_Z: return 0x2c; case SDL_SCANCODE_1: return 0x02; case SDL_SCANCODE_2: return 0x03; case SDL_SCANCODE_3: return 0x04; case SDL_SCANCODE_4: return 0x05; case SDL_SCANCODE_5: return 0x06; case SDL_SCANCODE_6: return 0x07; case SDL_SCANCODE_7: return 0x08; case SDL_SCANCODE_8: return 0x09; case SDL_SCANCODE_9: return 0x0a; case SDL_SCANCODE_0: return 0x0b; case SDL_SCANCODE_RETURN: return 0x1c; case SDL_SCANCODE_ESCAPE: return ST_ESC; case SDL_SCANCODE_BACKSPACE: return 0x0e; case SDL_SCANCODE_TAB: return 0x0f; case SDL_SCANCODE_SPACE: return 0x39; case SDL_SCANCODE_MINUS: return 0x0c; case SDL_SCANCODE_EQUALS: return 0x0d; case SDL_SCANCODE_LEFTBRACKET: return 0x1a; case SDL_SCANCODE_RIGHTBRACKET: return 0x1b; case SDL_SCANCODE_BACKSLASH: return 0x29; /* for 0x60 see NONUSBACKSLASH */ case SDL_SCANCODE_NONUSHASH: return 0x2b; case SDL_SCANCODE_SEMICOLON: return 0x27; case SDL_SCANCODE_APOSTROPHE: return 0x28; case SDL_SCANCODE_GRAVE: return 0x2b; /* ok? */ case SDL_SCANCODE_COMMA: return 0x33; case SDL_SCANCODE_PERIOD: return 0x34; case SDL_SCANCODE_SLASH: return 0x35; case SDL_SCANCODE_CAPSLOCK: return ST_CAPSLOCK; case SDL_SCANCODE_F1: return 0x3b; case SDL_SCANCODE_F2: return 0x3c; case SDL_SCANCODE_F3: return 0x3d; case SDL_SCANCODE_F4: return 0x3e; case SDL_SCANCODE_F5: return 0x3f; case SDL_SCANCODE_F6: return 0x40; case SDL_SCANCODE_F7: return 0x41; case SDL_SCANCODE_F8: return 0x42; case SDL_SCANCODE_F9: return 0x43; case SDL_SCANCODE_F10: return 0x44; case SDL_SCANCODE_F11: return 0x62; case SDL_SCANCODE_F12: return 0x61; case SDL_SCANCODE_PRINTSCREEN: return 0x62; case SDL_SCANCODE_SCROLLLOCK: return 0x61; case SDL_SCANCODE_PAUSE: return 0x61; case SDL_SCANCODE_INSERT: return 0x52; case SDL_SCANCODE_HOME: return 0x47; case SDL_SCANCODE_PAGEUP: return 0x63; case SDL_SCANCODE_DELETE: return 0x53; case SDL_SCANCODE_END: return 0x2b; case SDL_SCANCODE_PAGEDOWN: return 0x64; case SDL_SCANCODE_RIGHT: return 0x4d; case SDL_SCANCODE_LEFT: return 0x4b; case SDL_SCANCODE_DOWN: return 0x50; case SDL_SCANCODE_UP: return 0x48; case SDL_SCANCODE_NUMLOCKCLEAR: return 0x64; case SDL_SCANCODE_KP_DIVIDE: return 0x65; case SDL_SCANCODE_KP_MULTIPLY: return 0x66; case SDL_SCANCODE_KP_MINUS: return 0x4a; case SDL_SCANCODE_KP_PLUS: return 0x4e; case SDL_SCANCODE_KP_ENTER: return 0x72; case SDL_SCANCODE_KP_1: return 0x6d; case SDL_SCANCODE_KP_2: return 0x6e; case SDL_SCANCODE_KP_3: return 0x6f; case SDL_SCANCODE_KP_4: return 0x6a; case SDL_SCANCODE_KP_5: return 0x6b; case SDL_SCANCODE_KP_6: return 0x6c; case SDL_SCANCODE_KP_7: return 0x67; case SDL_SCANCODE_KP_8: return 0x68; case SDL_SCANCODE_KP_9: return 0x69; case SDL_SCANCODE_KP_0: return 0x70; case SDL_SCANCODE_KP_PERIOD: return 0x71; case SDL_SCANCODE_NONUSBACKSLASH: return 0x60; //case SDL_SCANCODE_APPLICATION: return ; case SDL_SCANCODE_KP_EQUALS: return 0x63; case SDL_SCANCODE_F13: return 0x63; case SDL_SCANCODE_F14: return 0x64; case SDL_SCANCODE_HELP: return 0x62; case SDL_SCANCODE_UNDO: return 0x61; case SDL_SCANCODE_KP_COMMA: return 0x71; case SDL_SCANCODE_CLEAR: return 0x47; case SDL_SCANCODE_RETURN2: return 0x1c; case SDL_SCANCODE_KP_LEFTPAREN: return 0x63; case SDL_SCANCODE_KP_RIGHTPAREN: return 0x64; case SDL_SCANCODE_KP_LEFTBRACE: return 0x63; case SDL_SCANCODE_KP_RIGHTBRACE: return 0x64; case SDL_SCANCODE_KP_TAB: return 0x0f; case SDL_SCANCODE_KP_BACKSPACE: return 0x0e; case SDL_SCANCODE_KP_COLON: return 0x33; case SDL_SCANCODE_KP_HASH: return 0x0c; case SDL_SCANCODE_KP_SPACE: return 0x39; case SDL_SCANCODE_KP_CLEAR: return 0x47; case SDL_SCANCODE_LCTRL: return ST_CONTROL; case SDL_SCANCODE_LSHIFT: return ST_LSHIFT; case SDL_SCANCODE_LALT: return ST_ALTERNATE; case SDL_SCANCODE_RCTRL: return ST_CONTROL; case SDL_SCANCODE_RSHIFT: return ST_RSHIFT; default: if (!pKeySym->scancode && pKeySym->sym) { /* assume SimulateKey * -> KeyUp/Down * -> Remap (with scancode mode configured) * -> PcToStScanCode */ return Keymap_SymbolicToStScanCode(pKeySym); } Log_Printf(LOG_WARN, "Unhandled scancode 0x%x!\n", pKeySym->scancode); return ST_NO_SCANCODE; } } /** * Remap a keypad key to ST scan code. We use a separate function for this * so that we can easily toggle between number and cursor mode with the * numlock key. */ static uint8_t Keymap_GetKeyPadScanCode(const SDL_Keysym* pKeySym) { if (SDL_GetModState() & KMOD_NUM) { switch (pKeySym->sym) { case SDLK_KP_1: return 0x6d; /* NumPad 1 */ case SDLK_KP_2: return 0x6e; /* NumPad 2 */ case SDLK_KP_3: return 0x6f; /* NumPad 3 */ case SDLK_KP_4: return 0x6a; /* NumPad 4 */ case SDLK_KP_5: return 0x6b; /* NumPad 5 */ case SDLK_KP_6: return 0x6c; /* NumPad 6 */ case SDLK_KP_7: return 0x67; /* NumPad 7 */ case SDLK_KP_8: return 0x68; /* NumPad 8 */ case SDLK_KP_9: return 0x69; /* NumPad 9 */ default: break; } } else { switch (pKeySym->sym) { case SDLK_KP_1: return 0x6d; /* NumPad 1 */ case SDLK_KP_2: return 0x50; /* Cursor down */ case SDLK_KP_3: return 0x6f; /* NumPad 3 */ case SDLK_KP_4: return 0x4b; /* Cursor left */ case SDLK_KP_5: return 0x50; /* Cursor down (again?) */ case SDLK_KP_6: return 0x4d; /* Cursor right */ case SDLK_KP_7: return 0x52; /* Insert - good for Dungeon Master */ case SDLK_KP_8: return 0x48; /* Cursor up */ case SDLK_KP_9: return 0x47; /* Home - again for Dungeon Master */ default: break; } } return ST_NO_SCANCODE; } /** * Remap SDL Key to ST Scan code */ static uint8_t Keymap_RemapKeyToSTScanCode(const SDL_Keysym* pKeySym) { /* Use loaded keymap? */ if (keymap_loaded) { int i; for (i = 0; i < KBD_MAX_SCANCODE && LoadedKeymap[i][1] != 0; i++) { if (pKeySym->sym == (SDL_Keycode)LoadedKeymap[i][0]) return LoadedKeymap[i][1]; } } /* Check for keypad first so we can handle numlock */ if (pKeySym->sym >= SDLK_KP_1 && pKeySym->sym <= SDLK_KP_9) { return Keymap_GetKeyPadScanCode(pKeySym); } /* Remap from PC scancodes? */ if (ConfigureParams.Keyboard.nKeymapType == KEYMAP_SCANCODE) { return Keymap_PcToStScanCode(pKeySym); } /* Use symbolic mapping */ return Keymap_SymbolicToStScanCode(pKeySym); } /** * Fill host (PC) key based on host "spec" string. * Return true on success */ static bool Keymap_ParseHostSpec(const char *spec, int *scancode) { int key; if (!spec) return false; key = atoi(spec); /* Direct key code? */ if (key < 10) { /* If it's not a valid number >= 10, then * assume we've got a symbolic key name */ int offset = 0; /* quoted character (e.g. comment line char)? */ if (*spec == '\\' && strlen(spec) == 2) offset = 1; key = Keymap_GetKeyFromName(spec+offset); } if (key < 8) { Log_Printf(LOG_WARN, "Invalid PC key: '%s' (%d >= 8)\n", spec, key); return false; } *scancode = key; return true; } /** * Fill guest (ST) key based on guest "spec" string. * Return true on success */ static bool Keymap_ParseGuestSpec(const char *spec, int *scancode) { int key; if (!spec) return false; key = atoi(spec); if (key <= 0 || key > KBD_MAX_SCANCODE) { Log_Printf(LOG_WARN, "Invalid ST scancode: '%s' (0 > %d < %d)\n", spec, key, KBD_MAX_SCANCODE); return false; } *scancode = key; return true; } /** * Load keyboard remap file */ void Keymap_LoadRemapFile(const char *pszFileName) { FILE *in; int idx, linenro, fails; /* Initialize table with default values */ memset(LoadedKeymap, 0, sizeof(LoadedKeymap)); keymap_loaded = false; if (!*pszFileName) return; /* Attempt to load file */ if (!File_Exists(pszFileName)) { Log_Printf(LOG_WARN, "The keymap file '%s' does not exist\n", pszFileName); return; } in = fopen(pszFileName, "r"); if (!in) { Log_Printf(LOG_ERROR, "Failed to open keymap file '%s'\n", pszFileName); return; } idx = linenro = fails = 0; while (!feof(in)) { char *line, *saveptr, buf[1024]; const char *host, *guest; if (idx >= ARRAY_SIZE(LoadedKeymap)) { Log_Printf(LOG_WARN, "Mappings specified already for" "supported number (%d) of keys, skipping" "rest of '%s' at line %d\n", ARRAY_SIZE(LoadedKeymap), pszFileName, linenro); fails++; break; } /* Read line from file */ if (fgets(buf, sizeof(buf), in) == NULL) break; linenro++; /* Remove white-space from start of line */ line = Str_Trim(buf); /* Ignore empty line and comments */ if (strlen(line) == 0 || line[0] == ';' || line[0] == '#') continue; /* get the host (PC) key spec */ host = Str_Trim(strtok_r(line, ",", &saveptr)); if (!Keymap_ParseHostSpec(host, &LoadedKeymap[idx][0])) { Log_Printf(LOG_WARN, "Failed to parse host (PC/SDL) part '%s' of line %d in: %s\n", host, linenro, pszFileName); fails++; continue; } /* Get the guest (ST) key spec */ guest = Str_Trim(strtok_r(NULL, "\n", &saveptr)); if (!Keymap_ParseGuestSpec(guest, &LoadedKeymap[idx][1])) { Log_Printf(LOG_WARN, "Failed to parse guest (ST) part '%s' of line %d in: %s\n", guest, linenro, pszFileName); fails++; continue; } LOG_TRACE(TRACE_KEYMAP, "key mapping from file: host %s => guest %s\n", host, guest); idx += 1; } fclose(in); if (idx > 0) keymap_loaded = true; if (fails) Log_AlertDlg(LOG_ERROR, "%d keymap file parsing failures\n(see console log for details)", fails); } /*-----------------------------------------------------------------------*/ /** * Scan list of keys to NOT de-bounce when running in maximum speed, eg ALT,SHIFT,CTRL etc... * @return true if key requires de-bouncing */ static bool Keymap_DebounceSTKey(uint8_t STScanCode) { int i=0; /* Are we in fast forward, and have disabled key repeat? */ if (ConfigureParams.System.bFastForward && !ConfigureParams.Keyboard.bFastForwardKeyRepeat) { /* We should de-bounce all non extended keys, * e.g. leave ALT, SHIFT, CTRL etc... held */ while (DebounceExtendedKeys[i]) { if (STScanCode == DebounceExtendedKeys[i]) return false; i++; } /* De-bounce key */ return true; } /* Do not de-bounce key */ return false; } /*-----------------------------------------------------------------------*/ /** * Debounce any PC key held down if running with key repeat disabled. * This is called each ST frame, so keys get held down for one VBL which * is enough for 68000 code to scan. */ void Keymap_DebounceAllKeys(void) { uint8_t nScanCode; /* Return if we aren't in fast forward or have not disabled key repeat */ if (!ConfigureParams.System.bFastForward || ConfigureParams.Keyboard.bFastForwardKeyRepeat) { return; } /* Now run through each key looking for ones held down */ for (nScanCode = 1; nScanCode < ARRAY_SIZE(Keyboard.KeyStates); nScanCode++) { /* Is key held? */ if (Keyboard.KeyStates[nScanCode]) { /* Does this require de-bouncing? */ if (Keymap_DebounceSTKey(nScanCode)) { IKBD_PressSTKey(nScanCode, false); Keyboard.KeyStates[nScanCode] = false; } } } } /*-----------------------------------------------------------------------*/ /* Returns false if SDL_Keycode is for modifier key that * won't be converted to ST scancode, true otherwise */ static bool IsKeyTranslatable(SDL_Keycode symkey) { switch (symkey) { case SDLK_RALT: case SDLK_LGUI: case SDLK_RGUI: case SDLK_MODE: case SDLK_NUMLOCKCLEAR: return false; } return true; } /*-----------------------------------------------------------------------*/ /** * User pressed a key down */ void Keymap_KeyDown(const SDL_Keysym *sdlkey) { uint8_t STScanCode; int symkey = sdlkey->sym; int modkey = sdlkey->mod; LOG_TRACE(TRACE_KEYMAP, "key down: sym=%i scan=%i mod=0x%x name='%s'\n", symkey, sdlkey->scancode, modkey, Keymap_GetKeyName(symkey)); if (ShortCut_CheckKeys(modkey, symkey, true)) return; /* If using joystick emulation via keyboard, DON'T send keys to keyboard processor!!! * Some games use keyboard as pause! */ if (Joy_KeyDown(symkey, modkey)) return; /* Ignore modifier keys that are not passed to the ST */ if (!IsKeyTranslatable(symkey)) return; STScanCode = Keymap_RemapKeyToSTScanCode(sdlkey); LOG_TRACE(TRACE_KEYMAP, "key map: sym=0x%x to ST-scan=0x%02x\n", symkey, STScanCode); if (STScanCode != ST_NO_SCANCODE) { if (!Keyboard.KeyStates[STScanCode]) { /* Set down */ Keyboard.KeyStates[STScanCode] = true; IKBD_PressSTKey(STScanCode, true); } } } /*-----------------------------------------------------------------------*/ /** * User released a key */ void Keymap_KeyUp(const SDL_Keysym *sdlkey) { uint8_t STScanCode; int symkey = sdlkey->sym; int modkey = sdlkey->mod; LOG_TRACE(TRACE_KEYMAP, "key up: sym=%i scan=%i mod=0x%x name='%s'\n", symkey, sdlkey->scancode, modkey, Keymap_GetKeyName(symkey)); /* Ignore short-cut keys here */ if (ShortCut_CheckKeys(modkey, symkey, false)) return; /* If using keyboard emulation, DON'T send keys to keyboard processor!!! * Some games use keyboard as pause! */ if (Joy_KeyUp(symkey, modkey)) return; /* Ignore modifier keys that are not passed to the ST */ if (!IsKeyTranslatable(symkey)) return; STScanCode = Keymap_RemapKeyToSTScanCode(sdlkey); /* Release key (only if was pressed) */ if (STScanCode != ST_NO_SCANCODE) { if (Keyboard.KeyStates[STScanCode]) { IKBD_PressSTKey(STScanCode, false); Keyboard.KeyStates[STScanCode] = false; } } } /*-----------------------------------------------------------------------*/ /** * Simulate press or release of a key corresponding to given character */ void Keymap_SimulateCharacter(char asckey, bool press) { SDL_Keysym sdlkey; sdlkey.mod = KMOD_NONE; sdlkey.scancode = 0; if (isupper((unsigned char)asckey)) { if (press) { sdlkey.sym = SDLK_LSHIFT; Keymap_KeyDown(&sdlkey); } sdlkey.sym = tolower((unsigned char)asckey); sdlkey.mod = KMOD_LSHIFT; } else { sdlkey.sym = asckey; } if (press) { Keymap_KeyDown(&sdlkey); } else { Keymap_KeyUp(&sdlkey); if (isupper((unsigned char)asckey)) { sdlkey.sym = SDLK_LSHIFT; Keymap_KeyUp(&sdlkey); } } } /** * Maps a key name to its SDL keycode */ int Keymap_GetKeyFromName(const char *name) { return SDL_GetKeyFromName(name); } /** * Maps an SDL keycode to a name */ const char *Keymap_GetKeyName(int keycode) { if (!keycode) return ""; return SDL_GetKeyName(keycode); } /** * Informs symbolic keymap of loaded TOS country. */ void Keymap_SetCountry(int countrycode) { uint8_t (*func)(const SDL_Keysym* pKeySym); /* Prefer keyboard layout selected by user */ if (ConfigureParams.Keyboard.nKbdLayout >= 0 && ConfigureParams.Keyboard.nKbdLayout <= 31) { countrycode = ConfigureParams.Keyboard.nKbdLayout; } else if (countrycode == TOS_LANG_ALL) { if (NvRam_Present()) { countrycode = NvRam_GetKbdLayoutCode(); } else if (ConfigureParams.Keyboard.nCountryCode >= 0 && ConfigureParams.Keyboard.nCountryCode <= 31) { countrycode = ConfigureParams.Keyboard.nCountryCode; } } switch (countrycode) { case TOS_LANG_US: func = Keymap_SymbolicToStScanCode_US; break; case TOS_LANG_DE: func = Keymap_SymbolicToStScanCode_DE; break; case TOS_LANG_FR: func = Keymap_SymbolicToStScanCode_FR; break; case TOS_LANG_UK: func = Keymap_SymbolicToStScanCode_UK; break; case TOS_LANG_ES: func = Keymap_SymbolicToStScanCode_ES; break; case TOS_LANG_IT: func = Keymap_SymbolicToStScanCode_IT; break; case TOS_LANG_FI: /* Finish seems to be the same as Swedish */ case TOS_LANG_SE: func = Keymap_SymbolicToStScanCode_SE; break; case TOS_LANG_CH_FR: case TOS_LANG_CH_DE: func = Keymap_SymbolicToStScanCode_CH; break; case TOS_LANG_NO: func = Keymap_SymbolicToStScanCode_NO; break; case TOS_LANG_DK: func = Keymap_SymbolicToStScanCode_DK; break; case TOS_LANG_NL: func = Keymap_SymbolicToStScanCode_NL; break; case TOS_LANG_CS: func = Keymap_SymbolicToStScanCode_CZ; break; default: func = Keymap_SymbolicToStScanCode_default; break; } Keymap_SymbolicToStScanCode = func; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/lilo.c000066400000000000000000000620151504763705000224370ustar00rootroot00000000000000/* Linux/m68k OS loader Copyright 1992 by Greg Harp (bootinfo definitions) ARAnyM (C) 2005-2008 Patrice Mandin ARAnyM (C) 2014 Andreas Schwab Adaption from ARAnyM (bootos_linux.cpp) to Hatari (C) 2019 Eero Tamminen This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #include "main.h" #include "configuration.h" #include "file.h" #include "lilo.h" #include "log.h" #include "tos.h" /* TosAddress */ #include "stMemory.h" /* STRam etc */ #include "symbols.h" #include bool bUseLilo; #define LILO_DEBUG 1 #if LILO_DEBUG #define Dprintf(a) printf a #else #define Dprintf(a) #endif /*--- Rip from elf.h ---*/ /* Type for a 16-bit quantity. */ typedef uint16_t Elf32_Half; /* Types for signed and unsigned 32-bit quantities. */ typedef uint32_t Elf32_Word; typedef int32_t Elf32_Sword; /* Types for signed and unsigned 64-bit quantities. */ typedef uint64_t Elf32_Xword; typedef int64_t Elf32_Sxword; /* Type of addresses. */ typedef uint32_t Elf32_Addr; /* Type of file offsets. */ typedef uint32_t Elf32_Off; /* Type for section indices, which are 16-bit quantities. */ typedef uint16_t Elf32_Section; /* Type for version symbol information. */ typedef Elf32_Half Elf32_Versym; /* The ELF file header. This appears at the start of every ELF file. */ #define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr; /* Program segment header. */ typedef struct { Elf32_Word p_type; /* Segment type */ Elf32_Off p_offset; /* Segment file offset */ Elf32_Addr p_vaddr; /* Segment virtual address */ Elf32_Addr p_paddr; /* Segment physical address */ Elf32_Word p_filesz; /* Segment size in file */ Elf32_Word p_memsz; /* Segment size in memory */ Elf32_Word p_flags; /* Segment flags */ Elf32_Word p_align; /* Segment alignment */ } Elf32_Phdr; #define EI_MAG0 0 /* File identification byte 0 index */ #define ELFMAG "\177ELF" #define SELFMAG 4 #define ET_EXEC 2 /* Executable file */ #define EM_68K 4 /* Motorola m68k family */ #define EV_CURRENT 1 /* Current version */ /* * Tag Definitions * * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/m68k/include/uapi/asm/bootinfo.h * * Machine independent tags start counting from 0x0000 * Machine dependent tags start counting from 0x8000 */ struct bi_record { uint16_t tag; /* tag ID */ uint16_t size; /* size of record (in bytes) */ uint32_t data[0]; /* data */ }; #define BI_LAST 0x0000 /* last record (sentinel) */ #define BI_MACHTYPE 0x0001 /* machine type (u_long) */ #define BI_CPUTYPE 0x0002 /* cpu type (u_long) */ #define BI_FPUTYPE 0x0003 /* fpu type (u_long) */ #define BI_MMUTYPE 0x0004 /* mmu type (u_long) */ #define BI_MEMCHUNK 0x0005 /* memory chunk address and size */ /* (struct mem_info) */ #define BI_RAMDISK 0x0006 /* ramdisk address and size */ /* (struct mem_info) */ #define BI_COMMAND_LINE 0x0007 /* kernel command line parameters */ /* (string) */ /* * Linux/m68k Architectures (BI_MACHTYPE) */ #define MACH_ATARI 2 /* * CPU, FPU and MMU types (BI_CPUTYPE, BI_FPUTYPE, BI_MMUTYPE) * * Note: we may rely on the following equalities: * * CPU_68020 == MMU_68851 * CPU_68030 == MMU_68030 * CPU_68040 == FPU_68040 == MMU_68040 * CPU_68060 == FPU_68060 == MMU_68060 */ #define CPUB_68020 0 #define CPUB_68030 1 #define CPUB_68040 2 #define CPUB_68060 3 #define BI_CPU_68020 (1 << CPUB_68020) #define BI_CPU_68030 (1 << CPUB_68030) #define BI_CPU_68040 (1 << CPUB_68040) #define BI_CPU_68060 (1 << CPUB_68060) #define FPUB_68881 0 #define FPUB_68882 1 #define FPUB_68040 2 /* Internal FPU */ #define FPUB_68060 3 /* Internal FPU */ #define BI_FPU_68881 (1 << FPUB_68881) #define BI_FPU_68882 (1 << FPUB_68882) #define BI_FPU_68040 (1 << FPUB_68040) #define BI_FPU_68060 (1 << FPUB_68060) #define MMUB_68851 0 #define MMUB_68030 1 /* Internal MMU */ #define MMUB_68040 2 /* Internal MMU */ #define MMUB_68060 3 /* Internal MMU */ #define BI_MMU_68851 (1 << MMUB_68851) #define BI_MMU_68030 (1 << MMUB_68030) #define BI_MMU_68040 (1 << MMUB_68040) #define BI_MMU_68060 (1 << MMUB_68060) /* * Stuff for bootinfo interface versioning * * At the start of kernel code, a 'struct bootversion' is located. */ #define BOOTINFOV_MAGIC 0x4249561A /* 'BIV^Z' */ #define MK_BI_VERSION(major,minor) (((major)<<16)+(minor)) #define BI_VERSION_MAJOR(v) (((v) >> 16) & 0xffff) #define BI_VERSION_MINOR(v) ((v) & 0xffff) /* * Atari-specific tags * * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/m68k/include/uapi/asm/bootinfo-atari.h */ #define ATARI_BOOTI_VERSION MK_BI_VERSION(2, 1) /* (values are ATARI_MACH_* defines) */ #define BI_ATARI_MCH_COOKIE 0x8000 /* _MCH cookie from TOS (u_long) */ #define BI_ATARI_MCH_TYPE 0x8001 /* special machine type (u_long) */ /* mch_cookie values (upper word) */ #define ATARI_MCH_ST 0 #define ATARI_MCH_STE 1 #define ATARI_MCH_TT 2 #define ATARI_MCH_FALCON 3 /* mch_type values */ #define ATARI_MACH_NORMAL 0 /* no special machine type */ #define ATARI_MACH_MEDUSA 1 /* Medusa 040 */ #define ATARI_MACH_HADES 2 /* Hades 040 or 060 */ #define ATARI_MACH_AB40 3 /* Afterburner040 on Falcon */ /*--- Other defines ---*/ #define NUM_MEMINFO 4 #define CL_SIZE (256) #undef PAGE_SIZE #define PAGE_SIZE 4096 #define M68K_EMUL_RESET 0x7102 /* Start address of kernel in Atari RAM */ #define KERNEL_START PAGE_SIZE /* Offset to start of fs in ramdisk file (no microcode on Atari) */ #define RAMDISK_FS_START 0 #define MAX_BI_SIZE (4096) #define GRANULARITY (256*1024) /* min unit for memory */ /*--- Structures ---*/ static union { struct bi_record record; unsigned char fake[MAX_BI_SIZE]; } bi_union; struct mem_info { uint32_t addr; /* physical address of memory chunk */ uint32_t size; /* length of memory chunk (in bytes) */ }; struct atari_bootinfo { uint32_t machtype; /* machine type */ uint32_t cputype; /* system CPU */ uint32_t fputype; /* system FPU */ uint32_t mmutype; /* system MMU */ int32_t num_memory; /* # of memory blocks found */ /* memory description */ struct mem_info memory[NUM_MEMINFO]; /* ramdisk description */ struct mem_info ramdisk; /* kernel command line parameters */ char command_line[CL_SIZE]; uint32_t mch_cookie; /* _MCH cookie from TOS */ uint32_t mch_type; /* special machine types */ }; static struct atari_bootinfo bi; static uint32_t bi_size; static bool lilo_load(void); static void *load_file(const char *filename, uint32_t *length); static bool setup_kernel(void *kernel, Elf32_Addr *offset, void *ramdisk, uint32_t ramdisk_len); static bool create_bootinfo(void); static bool set_machine_type(void); static bool add_bi_record(uint16_t tag, uint16_t size, const void *data); static bool add_bi_string(uint16_t tag, const char *s); /* Linux/m68k loader */ bool lilo_init(void) { uint8_t *ROMBaseHost = &RomMem[TosAddress]; if (!ConfigureParams.System.bMMU || ConfigureParams.Memory.STRamSize_KB < 8*1024) { Log_AlertDlg(LOG_FATAL, "Linux requires MMU and at least 8MB of RAM!"); return false; } /* RESET + jmp to Linux/m68k boot */ ROMBaseHost[0x0000] = 0x4e; /* reset */ ROMBaseHost[0x0001] = 0x70; ROMBaseHost[0x0002] = 0x4e; /* jmp */ ROMBaseHost[0x0003] = 0xf9; /* jmp address is set after loading kernel, in setup_kernel() */ /* TODO: ROM + 0x30 is Linux reset address on AB40, 0x4 on Falcon/TT */ #if 0 if (!(ConfigureParams.Log.bNatFeats && ConfigureParams.Lilo.bHaltOnReboot)) { /* set up a minimal OS for successful Linux/m68k reboot */ ROMBaseHost[0x0030] = 0x46; /* move.w #$2700,sr */ ROMBaseHost[0x0031] = 0xfc; ROMBaseHost[0x0032] = 0x27; ROMBaseHost[0x0033] = 0x00; ROMBaseHost[0x0034] = 0x4e; /* reset */ ROMBaseHost[0x0035] = 0x70; ROMBaseHost[0x0036] = M68K_EMUL_RESET >> 8; ROMBaseHost[0x0037] = M68K_EMUL_RESET & 0xff; } else { /* quit Hatari with NatFeats when Linux/m68k tries to reboot */ ROMBaseHost[0x0030] = 0x48; /* pea.l NF_SHUTDOWN(pc) */ ROMBaseHost[0x0031] = 0x7a; ROMBaseHost[0x0032] = 0x00; ROMBaseHost[0x0033] = 0x0c; ROMBaseHost[0x0034] = 0x59; /* subq.l #4,sp */ ROMBaseHost[0x0035] = 0x8f; ROMBaseHost[0x0036] = 0x73; /* NF_ID */ ROMBaseHost[0x0037] = 0x00; ROMBaseHost[0x0038] = 0x2f; /* move.l d0,-(sp) */ ROMBaseHost[0x0039] = 0x00; ROMBaseHost[0x003a] = 0x59; /* subq.l #4,sp */ ROMBaseHost[0x003b] = 0x8f; ROMBaseHost[0x003c] = 0x73; /* NF_CALL */ ROMBaseHost[0x003d] = 0x01; ROMBaseHost[0x003e] = 'N'; /* "NF_SHUTDOWN" */ ROMBaseHost[0x003f] = 'F'; ROMBaseHost[0x0040] = '_'; ROMBaseHost[0x0041] = 'S'; ROMBaseHost[0x0042] = 'H'; ROMBaseHost[0x0043] = 'U'; ROMBaseHost[0x0044] = 'T'; ROMBaseHost[0x0045] = 'D'; ROMBaseHost[0x0046] = 'O'; ROMBaseHost[0x0047] = 'W'; ROMBaseHost[0x0048] = 'N'; ROMBaseHost[0x0049] = 0; } #endif return lilo_load(); } /*--- Private functions ---*/ static bool lilo_load(void) { const char *kernel_s = ConfigureParams.Lilo.szKernelFileName; const char *ramdisk_s = ConfigureParams.Lilo.szRamdiskFileName; char *symbols_s = ConfigureParams.Lilo.szKernelSymbols; Elf32_Addr kernel_offset; bool loaded; void *kernel, *ramdisk = NULL; uint32_t kernel_length = 0; uint32_t ramdisk_length = 0; /* Load the kernel */ kernel = load_file(kernel_s, &kernel_length); if (!kernel) { Log_AlertDlg(LOG_FATAL, "LILO: error loading Linux kernel:\n'%s'", kernel_s); return false; } /* Load the ramdisk */ if (strlen(ramdisk_s) > 0) { ramdisk = load_file(ramdisk_s, &ramdisk_length); if (!ramdisk) { Log_AlertDlg(LOG_ERROR, "LILO: error loading ramdisk:\n'%s'", ramdisk_s); } } /* set up the kernel + ramdisk */ loaded = setup_kernel(kernel, &kernel_offset, ramdisk, ramdisk_length); /* Kernel and ramdisk copied in Atari RAM, we can free them */ if (ramdisk != NULL) { free(ramdisk); } free(kernel); if (loaded) { if (strlen(symbols_s) > 0) { char offstr[12]; static char symstr[] = "symbols"; char *cmd[] = { symstr, symbols_s, offstr, NULL }; sprintf(offstr, "0x%x", kernel_offset); Symbols_Command(3, cmd); } } else { Log_AlertDlg(LOG_FATAL, "LILO: error setting up kernel!"); } return true; } static void *load_file(const char *filename, uint32_t *length) { void *buffer = NULL; long nFileLength = 0; if (strlen(filename) == 0) { Dprintf(("LILO: empty filename\n")); return NULL; } #ifdef HAVE_LIBZ buffer = File_ZlibRead(filename, &nFileLength); #else buffer = File_ReadAsIs(filename, &nFileLength); #endif *length = nFileLength; if (buffer) { Dprintf(("LILO: (uncompressed) '%s' size: %d bytes\n", filename, *length)); } return buffer; } /** * Add bootinfo chunk */ static void add_chunk(uint32_t start, uint32_t size) { size = (size) & ~(GRANULARITY-1); if (size > 0) { bi.memory[bi.num_memory].addr = be_swap32(start); bi.memory[bi.num_memory].size = be_swap32(size); bi.num_memory++; } } /** * Set up loaded kernel code and ramdisk to suitable memory area, * and update bootinfo accordingly. * Return true for success */ static bool setup_kernel(void *kernel, Elf32_Addr *kernel_offset, void *ramdisk, uint32_t ramdisk_len) { /* map Hatari variables to Aranym code */ const uint32_t RAMSize = 1024 * ConfigureParams.Memory.STRamSize_KB; uint8_t *hostkbase, *RAMBaseHost = STRam; const uint32_t FastRAMBase = 0x01000000; uint8_t *FastRAMBaseHost = TTmemory; /* TODO: separate FastRAM setting for kernel & ramdisk? */ const uint32_t FastRAMSize = TTmemory ? 1024 * ConfigureParams.Memory.TTRamSize_KB : 0; bool kernel_to_fastram = (ConfigureParams.Lilo.bKernelToFastRam && FastRAMSize > 0); bool ramdisk_to_fastram = (ConfigureParams.Lilo.bRamdiskToFastRam && FastRAMSize > 0); Elf32_Ehdr *kexec_elf; /* header of kernel executable */ Elf32_Phdr *kernel_phdrs; Elf32_Addr min_addr = 0xffffffff, max_addr = 0; Elf32_Addr kernel_size; Elf32_Addr mem_ptr; const char *kname, *kernel_name = "vmlinux"; uint32_t *tmp; int i; bi_size = 0; bi.ramdisk.addr = 0; bi.ramdisk.size = 0; if (!set_machine_type()) { return false; } kexec_elf = (Elf32_Ehdr *) kernel; if (memcmp(&kexec_elf->e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 || be_swap16(kexec_elf->e_type) != ET_EXEC || be_swap16(kexec_elf->e_machine) != EM_68K || be_swap32(kexec_elf->e_version) != EV_CURRENT) { fprintf(stderr, "LILO: Invalid ELF header contents in kernel\n"); return false; } /*--- Copy the kernel at start of RAM ---*/ /* Load the program headers */ kernel_phdrs = (Elf32_Phdr *) (((char *) kexec_elf) + be_swap32(kexec_elf->e_phoff)); /* calculate the total required amount of memory */ Dprintf(("LILO: kexec_elf->e_phnum = 0x%08x\n", be_swap16(kexec_elf->e_phnum))); for (i = 0; i < be_swap16(kexec_elf->e_phnum); i++) { Dprintf(("LILO: kernel_phdrs[%d].p_vaddr = 0x%08x\n", i, be_swap32(kernel_phdrs[i].p_vaddr))); Dprintf(("LILO: kernel_phdrs[%d].p_offset = 0x%08x\n", i, be_swap32(kernel_phdrs[i].p_offset))); Dprintf(("LILO: kernel_phdrs[%d].p_filesz = 0x%08x\n", i, be_swap32(kernel_phdrs[i].p_filesz))); Dprintf(("LILO: kernel_phdrs[%d].p_memsz = 0x%08x\n", i, be_swap32(kernel_phdrs[i].p_memsz))); if (min_addr > be_swap32(kernel_phdrs[i].p_vaddr)) { min_addr = be_swap32(kernel_phdrs[i].p_vaddr); } if (max_addr < be_swap32(kernel_phdrs[i].p_vaddr) + be_swap32(kernel_phdrs[i].p_memsz)) { max_addr = be_swap32(kernel_phdrs[i].p_vaddr) + be_swap32(kernel_phdrs[i].p_memsz); } } /* This is needed for newer linkers that include the header * in the first segment. */ Dprintf(("LILO: min_addr = 0x%08x\n", min_addr)); Dprintf(("LILO: max_addr = 0x%08x\n", max_addr)); if (min_addr == 0) { Dprintf(("LILO: new linker:\n")); Dprintf(("LILO: kernel_phdrs[0].p_vaddr = 0x%08x\n", be_swap32(kernel_phdrs[0].p_vaddr))); Dprintf(("LILO: kernel_phdrs[0].p_offset = 0x%08x\n", be_swap32(kernel_phdrs[0].p_offset))); Dprintf(("LILO: kernel_phdrs[0].p_filesz = 0x%08x\n", be_swap32(kernel_phdrs[0].p_filesz))); Dprintf(("LILO: kernel_phdrs[0].p_memsz = 0x%08x\n", be_swap32(kernel_phdrs[0].p_memsz))); min_addr = PAGE_SIZE; /*kernel_phdrs[0].p_vaddr += PAGE_SIZE;*/ kernel_phdrs[0].p_vaddr = be_swap32(be_swap32(kernel_phdrs[0].p_vaddr) + PAGE_SIZE); /*kernel_phdrs[0].p_offset += PAGE_SIZE;*/ kernel_phdrs[0].p_offset = be_swap32(be_swap32(kernel_phdrs[0].p_offset) + PAGE_SIZE); /*kernel_phdrs[0].p_filesz -= PAGE_SIZE;*/ kernel_phdrs[0].p_filesz = be_swap32(be_swap32(kernel_phdrs[0].p_filesz) - PAGE_SIZE); /*kernel_phdrs[0].p_memsz -= PAGE_SIZE;*/ kernel_phdrs[0].p_memsz = be_swap32(be_swap32(kernel_phdrs[0].p_memsz) - PAGE_SIZE); Dprintf(("LILO: modified to:\n")); Dprintf(("LILO: kernel_phdrs[0].p_vaddr = 0x%08x\n", be_swap32(kernel_phdrs[0].p_vaddr))); Dprintf(("LILO: kernel_phdrs[0].p_offset = 0x%08x\n", be_swap32(kernel_phdrs[0].p_offset))); Dprintf(("LILO: kernel_phdrs[0].p_filesz = 0x%08x\n", be_swap32(kernel_phdrs[0].p_filesz))); Dprintf(("LILO: kernel_phdrs[0].p_memsz = 0x%08x\n", be_swap32(kernel_phdrs[0].p_memsz))); } kernel_size = max_addr - min_addr; Dprintf(("LILO: kernel_size = %u\n", kernel_size)); Dprintf(("LILO: %d kB ST-RAM, %d kB TT-RAM\n", ConfigureParams.Memory.STRamSize_KB, ConfigureParams.Memory.TTRamSize_KB)); if (kernel_to_fastram) { if (KERNEL_START + kernel_size > FastRAMSize) { fprintf(stderr, "LILO: kernel of size %x does not fit in TT-RAM of size %x\n", kernel_size, FastRAMSize); kernel_to_fastram = false; } } if (!kernel_to_fastram) { if (KERNEL_START + kernel_size > RAMSize) { fprintf(stderr, "LILO: kernel of size %x does not fit in RAM of size %x\n", kernel_size, RAMSize); return false; } } if (kernel_to_fastram) { *kernel_offset = FastRAMBase; hostkbase = FastRAMBaseHost; } else { *kernel_offset = 0; hostkbase = RAMBaseHost; } mem_ptr = KERNEL_START; int segments = be_swap16(kexec_elf->e_phnum); Dprintf(("LILO: copying %d segments to %s...\n", segments, kernel_to_fastram ? "FastRAM" : "ST-RAM")); for (i = 0; i < segments; i++) { Elf32_Word segment_length; Elf32_Addr segment_ptr; Elf32_Off segment_offset; segment_offset = be_swap32(kernel_phdrs[i].p_offset); segment_length = be_swap32(kernel_phdrs[i].p_filesz); if (segment_offset == 0xffffffffu) { fprintf(stderr, "LILO: Failed to seek to segment %d\n", i); return false; } segment_ptr = be_swap32(kernel_phdrs[i].p_vaddr) - PAGE_SIZE; memcpy(hostkbase + mem_ptr + segment_ptr, (char *) kexec_elf + segment_offset, segment_length); Dprintf(("LILO: Copied segment %d: 0x%08x + 0x%08x to 0x%08x\n", i, segment_offset, segment_length, *kernel_offset + mem_ptr + segment_ptr)); } /*--- Copy the ramdisk after kernel (and reserved bootinfo) ---*/ if (ramdisk && ramdisk_len) { Elf32_Addr rd_start; Elf32_Word rd_len; Elf32_Off rd_offset; const char *to_ram_s; if (kernel_to_fastram && ramdisk_to_fastram) { rd_offset = KERNEL_START + kernel_size + MAX_BI_SIZE; } else { rd_offset = 0; } rd_len = ramdisk_len - RAMDISK_FS_START; if (ramdisk_to_fastram && FastRAMSize > rd_offset + rd_len) { /* Load at end of FastRAM */ rd_start = FastRAMBase + FastRAMSize - rd_len; memcpy(FastRAMBaseHost + rd_start - FastRAMBase, (unsigned char *)ramdisk + RAMDISK_FS_START, rd_len); to_ram_s = "FastRAM"; } else { /* Load at end of ST-RAM */ if (kernel_to_fastram) { rd_offset = PAGE_SIZE; } else { rd_offset = KERNEL_START + kernel_size + MAX_BI_SIZE; } if (RAMSize < rd_offset + rd_len) { Log_AlertDlg(LOG_FATAL, "LILO: not enough memory to load ramdisk of size %u\n", rd_len); return false; } rd_start = RAMSize - rd_len; memcpy(RAMBaseHost + rd_start, ((unsigned char *)ramdisk) + RAMDISK_FS_START, rd_len); to_ram_s = "ST-RAM"; } bi.ramdisk.addr = be_swap32(rd_start); bi.ramdisk.size = be_swap32(rd_len); Dprintf(("lilo: Ramdisk at 0x%08x in %s, length=0x%08x\n", rd_start, to_ram_s, rd_len)); } else { bi.ramdisk.addr = 0; bi.ramdisk.size = 0; Dprintf(("LILO: No ramdisk\n")); } /*--- Create the bootinfo structure ---*/ /* Command line */ kname = kernel_name; if (strncmp(kernel_name, "local:", 6) == 0) { kname += 6; } if (strlen(ConfigureParams.Lilo.szCommandLine) > CL_SIZE-1) { Log_AlertDlg(LOG_FATAL, "LILO: kernel command line too long\n(max %d chars)\n", CL_SIZE-1); return false; } strcpy(bi.command_line, ConfigureParams.Lilo.szCommandLine); if (strlen(bi.command_line) + 1 + strlen(kname) + 12 < CL_SIZE-1) { if (*bi.command_line) { strcat(bi.command_line, " "); } strcat(bi.command_line, "BOOT_IMAGE="); strcat(bi.command_line, kname); } else { fprintf(stderr, "LILO: kernel command line too long to include kernel name\n"); } Dprintf(("LILO: config_file command line: %s\n", ConfigureParams.Lilo.szCommandLine)); Dprintf(("LILO: kernel command line: %s\n", bi.command_line)); /* Memory banks */ bi.num_memory = 0; /* RAM must be listed in bootinfo with the chunk holding the kernel first, * NOT in ascending address order. */ if (!kernel_to_fastram) { add_chunk(0, RAMSize); } if (FastRAMSize > 0) { add_chunk(FastRAMBase, FastRAMSize); } if (kernel_to_fastram) { add_chunk(0, RAMSize); } bi.num_memory = be_swap32(bi.num_memory); if (!create_bootinfo()) { fprintf(stderr, "LILO: Can not create bootinfo structure\n"); return false; } /*--- Copy boot info to RAM after kernel ---*/ memcpy(hostkbase + KERNEL_START + kernel_size, &bi_union.record, bi_size); Dprintf(("LILO: bootinfo at 0x%08x\n", *kernel_offset + KERNEL_START + kernel_size)); #if LILO_DEBUG tmp = (uint32_t *)(hostkbase + KERNEL_START + kernel_size); for (i = 0; i < 16; i++) { Dprintf(("LILO: bi_union.record[%2d] = 0x%08x\n", i, be_swap32(tmp[i]))); } #endif /*--- Init SP & PC for reset ---*/ tmp = (uint32_t *)RAMBaseHost; tmp[0] = be_swap32(*kernel_offset + KERNEL_START); /* SP */ tmp[1] = be_swap32(TosAddress); /* PC = ROMBase */ uint8_t *ROMBaseHost = &RomMem[TosAddress]; /* lilo_init() sets reset + jmp instructions to earlier addresses */ ROMBaseHost[4] = (*kernel_offset + KERNEL_START) >> 24; ROMBaseHost[5] = (*kernel_offset + KERNEL_START) >> 16; ROMBaseHost[6] = (*kernel_offset + KERNEL_START) >> 8; ROMBaseHost[7] = (*kernel_offset + KERNEL_START) & 0xff; Dprintf(("LILO: OK\n")); return true; } /** * Set machine type settings to bootinfo based on Hatari configuration * Return true for success */ static bool set_machine_type(void) { bi.machtype = be_swap32(MACH_ATARI); bi.mch_type = be_swap32(ATARI_MACH_NORMAL); switch (ConfigureParams.System.nMachineType) { case MACHINE_FALCON: bi.mch_cookie = be_swap32(ATARI_MCH_FALCON); break; case MACHINE_TT: bi.mch_cookie = be_swap32(ATARI_MCH_TT); break; case MACHINE_STE: case MACHINE_MEGA_STE: bi.mch_cookie = be_swap32(ATARI_MCH_STE); break; case MACHINE_ST: case MACHINE_MEGA_ST: bi.mch_cookie = be_swap32(ATARI_MCH_ST); break; } switch(ConfigureParams.System.nCpuLevel) { case 3: bi.cputype = be_swap32(BI_CPU_68030); bi.mmutype = be_swap32(BI_MMU_68030); break; case 4: bi.cputype = be_swap32(BI_CPU_68040); bi.mmutype = be_swap32(BI_MMU_68040); #if 0 /* * AB40 has different reset address handling: * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/m68k/atari/config.c#n494 */ if (ConfigureParams.System.nMachineType == MACHINE_FALCON) { /* let's try claiming it's Falcon AfterBurner like Aranym does */ bi.mch_type = be_swap32(ATARI_MACH_AB40); } #endif break; case 5: /* special case: 060 */ bi.cputype = be_swap32(BI_CPU_68060); bi.mmutype = be_swap32(BI_MMU_68060); break; default: Log_AlertDlg(LOG_FATAL, "LILO: Linux requires at least 030 CPU (for MMU), not 0%d0!", ConfigureParams.System.nCpuLevel); return false; } switch(ConfigureParams.System.n_FPUType) { case FPU_68881: bi.fputype = be_swap32(BI_FPU_68881); break; case FPU_68882: bi.fputype = be_swap32(BI_FPU_68882); break; case FPU_CPU: if (ConfigureParams.System.nCpuLevel == 4) { bi.fputype = be_swap32(BI_FPU_68040); } else if (ConfigureParams.System.nCpuLevel == 5) { /* special case: 060 */ bi.fputype = be_swap32(BI_FPU_68060); } /* TODO: else -> fail? */ break; case FPU_NONE: bi.fputype = 0; /* TODO */ break; } return true; } /** * Create the Bootinfo Structure * Return true for success */ static bool create_bootinfo(void) { unsigned int i; struct bi_record *record; /* Initialization */ bi_size = 0; /* Generic tags */ if (!add_bi_record(BI_MACHTYPE, sizeof(bi.machtype), &bi.machtype)) { return false; } if (!add_bi_record(BI_CPUTYPE, sizeof(bi.cputype), &bi.cputype)) { return false; } if (!add_bi_record(BI_FPUTYPE, sizeof(bi.fputype), &bi.fputype)) { return false; } if (!add_bi_record(BI_MMUTYPE, sizeof(bi.mmutype), &bi.mmutype)) { return false; } for (i = 0; i < be_swap32((uint32_t)bi.num_memory); i++) { if (!add_bi_record(BI_MEMCHUNK, sizeof(bi.memory[i]), &bi.memory[i])) return false; } if (be_swap32(bi.ramdisk.size)) { if (!add_bi_record(BI_RAMDISK, sizeof(bi.ramdisk), &bi.ramdisk)) return false; } if (!add_bi_string(BI_COMMAND_LINE, bi.command_line)) { return false; } /* Atari tags */ if (!add_bi_record(BI_ATARI_MCH_COOKIE, sizeof(bi.mch_cookie), &bi.mch_cookie)) { return false; } if (!add_bi_record(BI_ATARI_MCH_TYPE, sizeof(bi.mch_type), &bi.mch_type)) { return false; } /* Trailer */ record = (struct bi_record *)((char *)&bi_union.record + bi_size); record->tag = be_swap16(BI_LAST); bi_size += sizeof(bi_union.record.tag); return true; } /** * Add a Record to the Bootinfo Structure * Return true for success */ static bool add_bi_record(uint16_t tag, uint16_t size, const void *data) { struct bi_record *record; unsigned short size2; size2 = (sizeof(struct bi_record) + size + 3) & -4; if (bi_size + size2 + sizeof(bi_union.record.tag) > MAX_BI_SIZE) { fprintf (stderr, "LILO: can't add bootinfo record. Ask a wizard to enlarge me.\n"); return false; } record = (struct bi_record *)((char *)&bi_union.record + bi_size); record->tag = be_swap16(tag); record->size = be_swap16(size2); memcpy((char *)record + sizeof(struct bi_record), data, size); bi_size += size2; return true; } /** * Add a String Record to the Bootinfo Structure * return true for success */ static bool add_bi_string(uint16_t tag, const char *s) { return add_bi_record(tag, strlen(s) + 1, s); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/m68000.c000066400000000000000000001645561504763705000223470ustar00rootroot00000000000000/* Hatari - m68000.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. These routines originally (in WinSTon) handled exceptions as well as some few OpCode's such as Line-F and Line-A. In Hatari it has mainly become a wrapper between the WinSTon sources and the UAE CPU code. */ /* 2007/03/xx [NP] Possibility to add several wait states for the same instruction in */ /* M68000_WaitState (e.g. clr.b $fa1b.w in Decade Demo Menu). */ /* 2007/04/14 [NP] Add support for instruction pairing in M68000_AddCycles, using OpcodeFamily and */ /* LastOpcodeFamily (No Cooper Loader, Oh Crickey ... Hidden Screen). */ /* 2007/04/24 [NP] Add pairing for BCLR/Bcc. */ /* 2007/09/29 [NP] Use the new int.c and INT_CONVERT_TO_INTERNAL. */ /* 2007/11/26 [NP] We set BusErrorPC in m68k_run_1 instead of M68000_BusError, else the BusErrorPC */ /* will not point to the opcode that generated the bus error. */ /* In M68000_BusError, if we have 'move.l $0,$24', we need to generate a bus error */ /* for the read, not for the write that should occur after (TransBeauce 2 Demo). */ /* 2008/01/07 [NP] Function 'M68000_InitPairing' and 'PairingArray' as a lookup table for fast */ /* determination of valid pairing combinations (replace lots of 'if' tests in */ /* m68000.h). */ /* 2008/01/25 [NP] Add pairing for LSR/MOVE (and all other bit shifting instr) (Anomaly Demo Intro)*/ /* 2008/02/02 [NP] Add pairing for CMP/Bcc (Level 16 Fullscreen (1988)). */ /* 2008/02/08 [NP] Add pairing for LSL/LEA (and all other bit shifting instr) (TVI 2 - The Year */ /* After Demo). */ /* 2008/02/11 [NP] Add pairing for MULS/MOVEA (Delirious Demo IV Loader). */ /* 2008/01/25 [NP] Add pairing for LSR/MOVEA (and all other bit shifting instr) (Decade Demo Reset)*/ /* 2008/02/16 [NP] Add pairing for MULS/DIVS (fixes e605 demo part 3). */ /* 2008/03/08 [NP] In M68000_Exception, we need to know if the exception was triggered by an MFP */ /* interrupt or by a video interrupt. In the case MFP vector base was changed in */ /* fffa17 to an other value than the default $40, testing exceptionNr is not enough*/ /* to correctly process the exception. For example, if vector base is set to $10 */ /* then MFP Timer A will call vector stored at address $74, which would be wrongly */ /* interpreted as a level 5 int (which doesn't exist on Atari and will cause an */ /* assert to fail in intlevel()). We use InterruptSource to correctly recognize the*/ /* MFP interrupts (fix 'Toki' end part fullscreen which sets vector base to $10). */ /* 2008/04/14 [NP] Add pairing for BTST/Bcc (eg btst #7,d0 + bne.s label (with branch taken)). */ /* 2008/04/15 [NP] As tested on a real STF : */ /* - MUL/DIV can pair (but DIV/MUL can't) */ /* (eg mulu d0,d0 + divs d1,d1 with d0=0 and d1=1) */ /* - MUL/MOVE can pair, but not DIV/MOVE */ /* - EXG/MOVE can pair (eg exg d3,d4 + move.l 0(a3,d1.w),a4) */ /* - MOVE/DBcc can't pair */ /* 2008/04/16 [NP] Functions 'M68000_InitPairing_BitShift' to ease code maintenance. */ /* Tested on STF : add pairing between bit shift instr and ADD/SUB/OR/AND/EOR/NOT */ /* CLR/NEG (certainly some more possible, haven't tested everything) */ /* (fixes lsr.w #4,d4 + add.b $f0(a4,d4),d7 used in Zoolook part of ULM New Year). */ /* 2008/07/08 [NP] Add pairing between bit shift instr and ADDX/SUBX/ABCD/SBCD (fixes lsl.l #1,d0 */ /* + abcd d1,d1 used in Dragonnels - Rainbow Wall). */ /* 2008/10/05 [NP] Pass the 'ExceptionSource' parameter to Exception() in cpu/newcpu.c */ /* 2010/05/07 [NP] Add pairing for ADD/MOVE ; such pairing should only be possible when combined */ /* with d8(an,ix) address mode (eg: add.l (a5,d1.w),d0 + move.b 7(a5,d1.w),d5) */ /* (fixes Sommarhack 2010 Invitation by DHS). */ /* 2010/11/07 [NP] Add pairing between bit shift instr and JMP (fixes lsl.w #2,d0 + jmp 2(pc,d0) */ /* used in Fullparts by Hemoroids). */ /* 2011/12/11 [NP] Add pairing between MUL and JSR (fixes muls #52,d2 + jsr 0(a1,d2.w) used in */ /* Lemmings Compilation 40's Intro). */ /* 2014/05/07 [NP] In M68000_WaitEClock, use CyclesGlobalClockCounter instead of the VBL video */ /* counter (else for a given position in a VBL we would always get the same value */ /* for the E clock). */ /* 2015/02/01 [NP] When using the new WinUAE's cpu, don't handle MFP/DSP interrupts by calling */ /* directly Exception(), we must set bit 6 in pendingInterrupts and use the IACK */ /* sequence to get the exception's vector number. */ /* 2015/02/05 [NP] For the new WinUAE's cpu, don't use ExceptionSource anymore when calling */ /* Exception(). */ /* 2015/02/11 [NP] Replace BusErrorPC by regs.instruction_pc, to get similar code to WinUAE's cpu */ /* 2015/10/08 [NP] Add M68000_AddCycles_CE() to handle cycles when running with WinUAE's cpu in */ /* 'cycle exact' mode. In that case, instruction pairing don't have to be handled */ /* with some tables/heuristics anymore. */ const char M68000_fileid[] = "Hatari m68000.c"; #include #include "main.h" #include "configuration.h" #include "gemdos.h" #include "hatari-glue.h" #include "cycInt.h" #include "m68000.h" #include "memorySnapShot.h" #include "mfp.h" #include "mmu_common.h" #include "options.h" #include "savestate.h" #include "stMemory.h" #include "statusbar.h" #include "tos.h" #include "falcon/crossbar.h" #include "cart.h" #include "cpu/cpummu.h" #include "cpu/cpummu030.h" #include "scc.h" #include "scu_vme.h" #include "blitter.h" #include "ioMem.h" #if ENABLE_DSP_EMU #include "dsp.h" #endif /* information about current CPU instruction */ cpu_instruction_t CpuInstruction; uint32_t BusErrorAddress; /* Stores the offending address for bus-/address errors */ bool bBusErrorReadWrite; /* 0 for write error, 1 for read error */ int nCpuFreqShift; /* Used to emulate higher CPU frequencies: 0=8MHz, 1=16MHz, 2=32Mhz */ int WaitStateCycles = 0; /* Used to emulate the wait state cycles of certain IO registers */ int BusMode = BUS_MODE_CPU; /* Used to tell which part is owning the bus (cpu, blitter, ...) */ bool CPU_IACK = false; /* Set to true during an exception when getting the interrupt's vector number */ bool CpuRunCycleExact; /* true if the cpu core is running in cycle exact mode (ie m68k_run_1_ce, m68k_run_2ce, ...) */ bool CpuRunFuncNoret; /* true if the cpu core is using cpufunctbl_noret instead of cpufunctbl to execute opcode */ static bool M68000_DebuggerFlag;/* Is debugger enabled or not ? */ int LastOpcodeFamily = i_NOP; /* see the enum in readcpu.h i_XXX */ int LastInstrCycles = 0; /* number of cycles for previous instr. (not rounded to 4) */ int Pairing = 0; /* set to 1 if the latest 2 intr paired */ char PairingArray[ MAX_OPCODE_FAMILY ][ MAX_OPCODE_FAMILY ]; /* to convert the enum from OpcodeFamily to a readable value for pairing's debug */ const char *OpcodeName[] = { "ILLG", "OR","AND","EOR","ORSR","ANDSR","EORSR", "SUB","SUBA","SUBX","SBCD", "ADD","ADDA","ADDX","ABCD", "NEG","NEGX","NBCD","CLR","NOT","TST", "BTST","BCHG","BCLR","BSET", "CMP","CMPM","CMPA", "MVPRM","MVPMR","MOVE","MOVEA","MVSR2","MV2SR", "SWAP","EXG","EXT","MVMEL","MVMLE", "TRAP","MVR2USP","MVUSP2R","RESET","NOP","STOP","RTE","RTD", "LINK","UNLK", "RTS","TRAPV","RTR", "JSR","JMP","BSR","Bcc", "LEA","PEA","DBcc","Scc", "DIVU","DIVS","MULU","MULS", "ASR","ASL","LSR","LSL","ROL","ROR","ROXL","ROXR", "ASRW","ASLW","LSRW","LSLW","ROLW","RORW","ROXLW","ROXRW", "CHK","CHK2", "MOVEC2","MOVE2C","CAS","CAS2","DIVL","MULL", "BFTST","BFEXTU","BFCHG","BFEXTS","BFCLR","BFFFO","BFSET","BFINS", "PACK","UNPK","TAS","BKPT","CALLM","RTM","TRAPcc","MOVES", "FPP","FDBcc","FScc","FTRAPcc","FBcc","FSAVE","FRESTORE", "CINVL","CINVP","CINVA","CPUSHL","CPUSHP","CPUSHA","MOVE16", "MMUOP" }; #define MEGA_STE_CACHE_SIZE 8192 /* Size in 16-bits words */ struct { uint8_t Valid[ MEGA_STE_CACHE_SIZE ]; uint16_t Tag[ MEGA_STE_CACHE_SIZE ]; uint16_t Value[ MEGA_STE_CACHE_SIZE ]; } MegaSTE_Cache; static bool MegaSTE_Cache_Is_Enabled ( void ); static bool MegaSTE_Cache_Addr_Cacheable ( uint32_t addr , int Size , int DoWrite ); static void MegaSTE_Cache_Addr_Convert ( uint32_t Addr , uint16_t *pLineNbr , uint16_t *pTag ); static bool MegaSTE_Cache_Update ( uint32_t Addr , int Size , uint16_t Val , int DoWrite ); static bool MegaSTE_Cache_Write ( uint32_t Addr , int Size , uint16_t Val ); static bool MegaSTE_Cache_Read ( uint32_t Addr , int Size , uint16_t *pVal ); uae_u32 (*x_get_iword_megaste_save)(int); uae_u32 (*x_get_long_megaste_save)(uaecptr); uae_u32 (*x_get_word_megaste_save)(uaecptr); uae_u32 (*x_get_byte_megaste_save)(uaecptr); void (*x_put_long_megaste_save)(uaecptr,uae_u32); void (*x_put_word_megaste_save)(uaecptr,uae_u32); void (*x_put_byte_megaste_save)(uaecptr,uae_u32); uae_u32 mem_access_delay_word_read_megaste_16 (uaecptr addr); uae_u32 mem_access_delay_wordi_read_megaste_16 (uaecptr addr); uae_u32 mem_access_delay_byte_read_megaste_16 (uaecptr addr); void mem_access_delay_byte_write_megaste_16 (uaecptr addr, uae_u32 v); void mem_access_delay_word_write_megaste_16 (uaecptr addr, uae_u32 v); uae_u32 wait_cpu_cycle_read_megaste_16 (uaecptr addr, int mode); void wait_cpu_cycle_write_megaste_16 (uaecptr addr, int mode, uae_u32 v); /*-----------------------------------------------------------------------*/ /** * Add pairing between all the bit shifting instructions and a given Opcode */ static void M68000_InitPairing_BitShift ( int OpCode ) { PairingArray[ i_ASR ][ OpCode ] = 1; PairingArray[ i_ASL ][ OpCode ] = 1; PairingArray[ i_LSR ][ OpCode ] = 1; PairingArray[ i_LSL ][ OpCode ] = 1; PairingArray[ i_ROL ][ OpCode ] = 1; PairingArray[ i_ROR ][ OpCode ] = 1; PairingArray[ i_ROXR ][ OpCode ] = 1; PairingArray[ i_ROXL ][ OpCode ] = 1; } /** * Init the pairing matrix * Two instructions can pair if PairingArray[ LastOpcodeFamily ][ OpcodeFamily ] == 1 */ static void M68000_InitPairing(void) { /* First, clear the matrix (pairing is false) */ memset(PairingArray , 0 , MAX_OPCODE_FAMILY * MAX_OPCODE_FAMILY); /* Set all valid pairing combinations to 1 */ PairingArray[ i_EXG ][ i_DBcc ] = 1; PairingArray[ i_EXG ][ i_MOVE ] = 1; PairingArray[ i_EXG ][ i_MOVEA] = 1; PairingArray[ i_CMPA ][ i_Bcc ] = 1; PairingArray[ i_CMP ][ i_Bcc ] = 1; M68000_InitPairing_BitShift ( i_DBcc ); M68000_InitPairing_BitShift ( i_MOVE ); M68000_InitPairing_BitShift ( i_MOVEA ); M68000_InitPairing_BitShift ( i_LEA ); M68000_InitPairing_BitShift ( i_JMP ); PairingArray[ i_MULU ][ i_MOVEA] = 1; PairingArray[ i_MULS ][ i_MOVEA] = 1; PairingArray[ i_MULU ][ i_MOVE ] = 1; PairingArray[ i_MULS ][ i_MOVE ] = 1; PairingArray[ i_MULU ][ i_DIVU ] = 1; PairingArray[ i_MULU ][ i_DIVS ] = 1; PairingArray[ i_MULS ][ i_DIVU ] = 1; PairingArray[ i_MULS ][ i_DIVS ] = 1; PairingArray[ i_MULU ][ i_JSR ] = 1; PairingArray[ i_MULS ][ i_JSR ] = 1; PairingArray[ i_BTST ][ i_Bcc ] = 1; M68000_InitPairing_BitShift ( i_ADD ); M68000_InitPairing_BitShift ( i_SUB ); M68000_InitPairing_BitShift ( i_OR ); M68000_InitPairing_BitShift ( i_AND ); M68000_InitPairing_BitShift ( i_EOR ); M68000_InitPairing_BitShift ( i_NOT ); M68000_InitPairing_BitShift ( i_CLR ); M68000_InitPairing_BitShift ( i_NEG ); M68000_InitPairing_BitShift ( i_ADDX ); M68000_InitPairing_BitShift ( i_SUBX ); M68000_InitPairing_BitShift ( i_ABCD ); M68000_InitPairing_BitShift ( i_SBCD ); PairingArray[ i_ADD ][ i_MOVE ] = 1; /* when using xx(an,dn) addr mode */ PairingArray[ i_SUB ][ i_MOVE ] = 1; PairingArray[ i_ABCD ][ i_DBcc ] = 1; PairingArray[ i_SBCD ][ i_DBcc ] = 1; } /** * One-time CPU initialization. */ void M68000_Init(void) { /* Init UAE CPU core */ Init680x0(); /* Init the pairing matrix */ M68000_InitPairing(); } /*-----------------------------------------------------------------------*/ /** * Reset CPU 68000 variables */ void M68000_Reset(bool bCold) { //fprintf( stderr,"M68000_Reset in cold=%d" , bCold ); UAE_Set_Quit_Reset ( bCold ); set_special(SPCFLAG_MODE_CHANGE); /* exit m68k_run_xxx() loop and check for cpu changes / reset / quit */ BusMode = BUS_MODE_CPU; CPU_IACK = false; //fprintf( stderr,"M68000_Reset out cold=%d\n" , bCold ); } /*-----------------------------------------------------------------------*/ /** * Enable/disable breakpoints in the debugger */ void M68000_SetDebugger(bool debug) { M68000_DebuggerFlag = debug; if ( debug ) M68000_SetSpecial(SPCFLAG_DEBUGGER); else M68000_UnsetSpecial(SPCFLAG_DEBUGGER); } /*-----------------------------------------------------------------------*/ /** * Restore debugger state (breakpoints) * This is called from CPU core after a reset, because CPU core clears regs.spcflags */ void M68000_RestoreDebugger(void) { if ( M68000_DebuggerFlag ) M68000_SetSpecial(SPCFLAG_DEBUGGER); else M68000_UnsetSpecial(SPCFLAG_DEBUGGER); } /*-----------------------------------------------------------------------*/ /** * Start 680x0 emulation */ void M68000_Start(void) { //fprintf (stderr, "M68000_Start\n" ); /* Load initial memory snapshot */ if (bLoadMemorySave) { MemorySnapShot_Restore(ConfigureParams.Memory.szMemoryCaptureFileName, false); } else if (bLoadAutoSave) { MemorySnapShot_Restore(ConfigureParams.Memory.szAutoSaveFileName, false); } UAE_Set_Quit_Reset ( false ); m68k_go(true); } /*-----------------------------------------------------------------------*/ /** * Check whether CPU settings have been changed. * Possible values for WinUAE : * cpu_model : 68000 , 68010, 68020, 68030, 68040, 68060 * cpu_compatible : 0/false (no prefetch for 68000/20/30) 1/true (prefetch opcode for 68000/20/30) * cpu_cycle_exact : 0/false 1/true (most accurate, implies cpu_compatible) * cpu_memory_cycle_exact : 0/false 1/true (less accurate than cpu_cycle_exact) * cpu_data_cache : 0/false (don't emulate data cache) 1/true (emulate data cache for 30/40/60) * address_space_24 : 1 (68000/10 and 68030 LC for Falcon), 0 (68020/30/40/60) * fpu_model : 0, 68881 (external), 68882 (external), 68040 (cpu) , 68060 (cpu) * fpu_strict : true/false (more accurate rounding) * fpu_mode : 0 faster but less accurate, use host's cpu/fpu with 64 bit precision) * 1 most accurate but slower, use softfloat library) * -1 similar to 0 but with extended 80 bit precision, only for x86 CPU) * (TODO [NP] not in Hatari for now, require fpp_native_msvc_80bit.cpp / fpux64_80.asm / fpux86_80.asm) * mmu_model : 0, 68030, 68040, 68060 * * m68k_speed : -1=don't adjust cycle >=0 use m68k_speed_throttle to precisely adjust cycles * m68k_speed_throttle : if not 0, used to set cycles_mult. In Hatari, set it to 0 * cpu_frequency : in CE mode, fine control of cpu freq, set it to freq/2. Not used in Hatari, set it to 0. * cpu_clock_multiplier : used to speed up/slow down clock by multiple of 2 in CE mode. In Hatari * we use nCpuFreqShift, so this should always be set to 2<<8 = 512 to get the same * cpucycleunit as in non CE mode. * cachesize : size of cache in MB when using JIT. Not used in Hatari at the moment, set it to 0 */ void M68000_CheckCpuSettings(void) { //fprintf ( stderr,"M68000_CheckCpuSettings in\n" ); /* WinUAE core uses cpu_model instead of cpu_level, so we've got to * convert these values here: */ switch (ConfigureParams.System.nCpuLevel) { case 0 : changed_prefs.cpu_model = 68000; break; case 1 : changed_prefs.cpu_model = 68010; break; case 2 : changed_prefs.cpu_model = 68020; break; case 3 : changed_prefs.cpu_model = 68030; break; case 4 : changed_prefs.cpu_model = 68040; break; case 5 : changed_prefs.cpu_model = 68060; break; default: fprintf (stderr, "M68000_CheckCpuSettings() : Error, cpu_level %d unknown\n" , ConfigureParams.System.nCpuLevel); } /* 68000/010 can't have any FPU */ if (changed_prefs.cpu_model < 68020 && ConfigureParams.System.n_FPUType != FPU_NONE) { Log_Printf(LOG_WARN, "FPU is not supported in 68000/010 configurations, disabling FPU\n"); ConfigureParams.System.n_FPUType = FPU_NONE; } /* 68020/030 can't have 'internal' FPU */ else if (changed_prefs.cpu_model < 68040 && ConfigureParams.System.n_FPUType == FPU_CPU) { Log_Printf(LOG_WARN, "Internal FPU is supported only for 040/060, " "using 68882 FPU instead\n"); ConfigureParams.System.n_FPUType = FPU_68882; } /* 68040/060 can't have an external FPU */ else if (changed_prefs.cpu_model >= 68040 && (ConfigureParams.System.n_FPUType == FPU_68881 || ConfigureParams.System.n_FPUType == FPU_68882)) { Log_Printf(LOG_WARN, "68881/68882 FPU is only supported for 020/030 CPUs, " "using internal FPU instead\n"); ConfigureParams.System.n_FPUType = FPU_CPU; } changed_prefs.int_no_unimplemented = true; changed_prefs.fpu_no_unimplemented = true; changed_prefs.cpu_data_cache = ConfigureParams.System.bCpuDataCache; changed_prefs.cpu_compatible = ConfigureParams.System.bCompatibleCpu; changed_prefs.cpu_cycle_exact = ConfigureParams.System.bCycleExactCpu; changed_prefs.cpu_memory_cycle_exact = ConfigureParams.System.bCycleExactCpu; changed_prefs.address_space_24 = ConfigureParams.System.bAddressSpace24; changed_prefs.fpu_model = ConfigureParams.System.n_FPUType; changed_prefs.fpu_strict = ConfigureParams.System.bCompatibleFPU; changed_prefs.fpu_mode = ( ConfigureParams.System.bSoftFloatFPU ? 1 : 0 ); /* Update the MMU model by taking the same value as CPU model */ /* MMU is only supported for CPU >=68030, this is later checked in custom.c fixup_cpu() */ if ( !ConfigureParams.System.bMMU ) changed_prefs.mmu_model = 0; /* MMU disabled */ else changed_prefs.mmu_model = changed_prefs.cpu_model; /* MMU enabled */ /* Set cpu speed to default values (only used in WinUAE, not in Hatari) */ changed_prefs.m68k_speed = 0; changed_prefs.cpu_clock_multiplier = 2 << 8; /* We don't use JIT */ changed_prefs.cachesize = 0; /* while 020 had i-cache, only 030+ had also d-cache */ if (changed_prefs.cpu_model < 68030 || !ConfigureParams.System.bCpuDataCache || !(changed_prefs.cpu_compatible || changed_prefs.cpu_cycle_exact)) changed_prefs.cpu_data_cache = false; else changed_prefs.cpu_data_cache = true; /* Update SPCFLAG_MODE_CHANGE flag if needed */ check_prefs_changed_cpu(); //fprintf ( stderr, "M68000_CheckCpuSettings out\n" ); } /** * Patch the cpu tables to intercept some opcodes used for Gemdos HD * emulation, extended VDI more or for NatFeats. */ void M68000_PatchCpuTables(void) { if (Cart_UseBuiltinCartridge()) { /* Hatari's specific illegal opcodes */ cpufunctbl[GEMDOS_OPCODE] = OpCode_GemDos; /* 0x0008 */ cpufunctbl_noret[GEMDOS_OPCODE] = OpCode_GemDos_noret; /* 0x0008 */ cpufunctbl[PEXEC_OPCODE] = OpCode_Pexec; /* 0x0009 */ cpufunctbl_noret[PEXEC_OPCODE] = OpCode_Pexec_noret; /* 0x0009 */ cpufunctbl[SYSINIT_OPCODE] = OpCode_SysInit; /* 0x000a */ cpufunctbl_noret[SYSINIT_OPCODE] = OpCode_SysInit_noret; /* 0x000a */ cpufunctbl[VDI_OPCODE] = OpCode_VDI; /* 0x000c */ cpufunctbl_noret[VDI_OPCODE] = OpCode_VDI_noret; /* 0x000c */ } else { /* No built-in cartridge loaded : set same handler as 0x4afc (illegal) */ cpufunctbl[GEMDOS_OPCODE] = cpufunctbl[0x4afc]; /* 0x0008 */ cpufunctbl_noret[GEMDOS_OPCODE] = cpufunctbl_noret[0x4afc]; /* 0x0008 */ cpufunctbl[PEXEC_OPCODE] = cpufunctbl[0x4afc]; /* 0x0009*/ cpufunctbl_noret[PEXEC_OPCODE] = cpufunctbl_noret[0x4afc]; /* 0x0009*/ cpufunctbl[SYSINIT_OPCODE] = cpufunctbl[0x4afc]; /* 0x000a */ cpufunctbl_noret[SYSINIT_OPCODE] = cpufunctbl_noret[0x4afc]; /* 0x000a */ cpufunctbl[VDI_OPCODE] = cpufunctbl[0x4afc]; /* 0x000c */ cpufunctbl_noret[VDI_OPCODE] = cpufunctbl_noret[0x4afc]; /* 0x000c */ } /* Install opcodes for Native Features? */ if (ConfigureParams.Log.bNatFeats) { /* illegal opcodes for emulators Native Features */ cpufunctbl[NATFEAT_ID_OPCODE] = OpCode_NatFeat_ID; /* 0x7300 */ cpufunctbl_noret[NATFEAT_ID_OPCODE] = OpCode_NatFeat_ID_noret; /* 0x7300 */ cpufunctbl[NATFEAT_CALL_OPCODE] = OpCode_NatFeat_Call; /* 0x7301 */ cpufunctbl_noret[NATFEAT_CALL_OPCODE] = OpCode_NatFeat_Call_noret; /* 0x7301 */ } else { /* No Native Features : set same handler as 0x4afc (illegal) */ cpufunctbl[NATFEAT_ID_OPCODE] = cpufunctbl[ 0x4afc ]; /* 0x7300 */ cpufunctbl_noret[NATFEAT_ID_OPCODE] = cpufunctbl_noret[ 0x4afc ]; /* 0x7300 */ cpufunctbl[NATFEAT_CALL_OPCODE] = cpufunctbl[ 0x4afc ]; /* 0x7301 */ cpufunctbl_noret[NATFEAT_CALL_OPCODE] = cpufunctbl_noret[ 0x4afc ]; /* 0x7301 */ } } /** * Save/Restore snapshot of CPU variables ('MemorySnapShot_Store' handles type) */ void M68000_MemorySnapShot_Capture(bool bSave) { size_t len; uae_u8 chunk[ 1000 ]; int i; MemorySnapShot_Store(&pendingInterrupts, sizeof(pendingInterrupts)); /* for intlev() */ if (bSave) { //m68k_dumpstate_file(stderr, NULL); save_cpu (&len,chunk); //printf ( "save cpu done\n" ); save_cpu_extra (&len,chunk); //printf ( "save cpux done\n" ); save_fpu (&len,chunk); //printf ( "save fpu done\n" ); save_mmu (&len,chunk); //printf ( "save mmu done\n" ); //m68k_dumpstate_file(stderr, NULL); } else { //m68k_dumpstate_file(stderr, NULL); restore_cpu (chunk); //printf ( "restore cpu done\n" ); restore_cpu_extra (chunk); //printf ( "restore cpux done\n" ); restore_fpu (chunk); //printf ( "restore fpu done\n" ); restore_mmu (chunk); //printf ( "restore mmu done\n" ); //m68k_dumpstate_file(stderr, NULL); } MemorySnapShot_Store(&WaitStateCycles,sizeof(WaitStateCycles)); MemorySnapShot_Store(&BusMode,sizeof(BusMode)); MemorySnapShot_Store(&CPU_IACK,sizeof(CPU_IACK)); MemorySnapShot_Store(&LastInstrCycles,sizeof(LastInstrCycles)); MemorySnapShot_Store(&Pairing,sizeof(Pairing)); /* From cpu/custom.c and cpu/events.c */ MemorySnapShot_Store(&currcycle,sizeof(currcycle)); MemorySnapShot_Store(&extra_cycle,sizeof(extra_cycle)); /* From cpu/newcpu.c */ MemorySnapShot_Store(&BusCyclePenalty,sizeof(BusCyclePenalty)); /* Save/Restore MegaSTE's cache */ for ( i=0 ; i < MEGA_STE_CACHE_SIZE ; i++ ) { MemorySnapShot_Store(&MegaSTE_Cache.Valid[ i ] , sizeof(MegaSTE_Cache.Valid[ i ]) ); MemorySnapShot_Store(&MegaSTE_Cache.Tag[ i ] , sizeof(MegaSTE_Cache.Tag[ i ]) ); MemorySnapShot_Store(&MegaSTE_Cache.Value[ i ] , sizeof(MegaSTE_Cache.Value[ i ]) ); } } /*-----------------------------------------------------------------------*/ /** * Check whether bus error reporting should be reported or not. * We do not want to print messages when TOS is testing for available HW * or when a program just checks for the floating point co-processor. */ bool M68000_IsVerboseBusError(uint32_t pc, uint32_t addr) { const uint32_t nTosProbeAddrs[] = { 0xf00039, 0xff8900, 0xff8a00, 0xff8c83, 0xff8e0d, 0xff8e09, 0xfffa40 }; const uint32_t nEmuTosProbeAddrs[] = { 0xf0001d, 0xf0005d, 0xf0009d, 0xf000dd, 0xff8006, 0xff8282, 0xff8400, 0xff8701, 0xff8901, 0xff8943, 0xff8961, 0xff8c80, 0xff8a3c, 0xff9201, 0xfffa81, 0xfffe00 }; int idx; if (ConfigureParams.Log.nTextLogLevel == LOG_DEBUG) return true; if (ConfigureParams.System.bAddressSpace24 || (addr & 0xff000000) == 0xff000000) { addr &= 0xffffff; } /* Program just probing for FPU? A lot of C startup code is always * doing this, so reporting bus errors here would be annoying */ if (addr == 0xfffa42) return false; /* Always report other bus errors from normal programs */ if (pc < TosAddress || pc > TosAddress + TosSize) return true; for (idx = 0; idx < ARRAY_SIZE(nTosProbeAddrs); idx++) { if (nTosProbeAddrs[idx] == addr) return false; } if (bIsEmuTOS) { for (idx = 0; idx < ARRAY_SIZE(nEmuTosProbeAddrs); idx++) { if (nEmuTosProbeAddrs[idx] == addr) return false; } } return true; } /** * BUSERROR - Access outside valid memory range. * ReadWrite : BUS_ERROR_READ in case of a read or BUS_ERROR_WRITE in case of write * Size : BUS_ERROR_SIZE_BYTE or BUS_ERROR_SIZE_WORD or BUS_ERROR_SIZE_LONG * AccessType : BUS_ERROR_ACCESS_INSTR or BUS_ERROR_ACCESS_DATA * val : value we wanted to write in case of a BUS_ERROR_WRITE */ void M68000_BusError ( uint32_t addr , int ReadWrite , int Size , int AccessType , uae_u32 val ) { LOG_TRACE(TRACE_CPU_EXCEPTION, "Bus error %s at address $%x PC=$%x.\n", ReadWrite ? "reading" : "writing", addr, M68000_InstrPC); /* For the MegaSTE, a bus error will flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); #define WINUAE_HANDLE_BUS_ERROR #ifdef WINUAE_HANDLE_BUS_ERROR bool read , ins; int size; if ( ReadWrite == BUS_ERROR_READ ) read = true; else read = false; if ( AccessType == BUS_ERROR_ACCESS_INSTR ) ins = true; else ins = false; if ( Size == BUS_ERROR_SIZE_BYTE ) size = sz_byte; else if ( Size == BUS_ERROR_SIZE_WORD ) size = sz_word; else size = sz_long; hardware_exception2 ( addr , val , read , ins , size ); #else /* With WinUAE's cpu, on a bus error instruction will be correctly aborted before completing, */ /* so we don't need to check if the opcode already generated a bus error or not */ exception2 ( addr , ReadWrite , Size , AccessType ); #endif } /*-----------------------------------------------------------------------*/ /** * Set/clear interrupt request for IntNr (between 1 and 7) * - For STF/STE/Falcon : we update the corresponding bits in "pendingInterrupts", which is * directly "connected" to the CPU core * - For MegaSTE/TT : interrupts are first handled by the SCU chip which include a dedicated mask * for every interrupt source. The masked result is then copied to "pendingInterrupts" and * only masked interrupts will be visible to the CPU core */ void M68000_SetIRQ ( int IntNr ) { if ( !SCU_IsEnabled() ) pendingInterrupts |= ( 1 << IntNr ); else SCU_SetIRQ_CPU ( IntNr ); /* MegaSTE / TT */ } void M68000_ClearIRQ ( int IntNr ) { if ( !SCU_IsEnabled() ) pendingInterrupts &= ~( 1 << IntNr ); else SCU_ClearIRQ_CPU ( IntNr ); /* MegaSTE / TT */ } /*-----------------------------------------------------------------------*/ /** * Exception handler * If ExceptionNr matches level 1-7 interrupts then we call M68000_SetIRQ * Else we call Exception() in the CPU core in newcpu.c */ void M68000_Exception(uint32_t ExceptionNr , int ExceptionSource) { if ( ExceptionNr > 24 && ExceptionNr < 32 ) /* Level 1-7 interrupts */ { /* In our case, this part is called for HBL, VBL and MFP/DSP interrupts */ /* For WinUAE CPU, we must call M68000_Update_intlev after changing pendingInterrupts */ /* (in order to call doint() and to update regs.ipl with regs.ipl_pin, else */ /* the exception might be delayed by one instruction in do_specialties()) */ M68000_SetIRQ ( ExceptionNr - 24 ); M68000_Update_intlev(); } else /* direct CPU exceptions */ { Exception(ExceptionNr); } } /*-----------------------------------------------------------------------*/ /** * Update the list of pending interrupts. * Level 2 (HBL) and 4 (VBL) are only cleared when the interrupt is processed, * but level 6 is shared between MFP and DSP and can be cleared by MFP or DSP * before being processed. * So, we need to check which IRQ are set/cleared at the same time * and update level 6 accordingly : level 6 = MFP_IRQ OR DSP_IRQ * Level 5 (SCC) is only used on Mega STE, TT and Falcon * * [NP] NOTE : temporary case for interrupts with WinUAE CPU in cycle exact mode * In CE mode, interrupt state should be updated on each subcycle of every opcode * then ipl_fetch() is called in each opcode. * For now, Hatari with WinUAE CPU in CE mode only evaluates the interrupt state * after the end of each opcode. So we need to call ipl_fetch() ourselves at the moment. */ void M68000_Update_intlev ( void ) { uint8_t Level6_IRQ; #if ENABLE_DSP_EMU Level6_IRQ = MFP_GetIRQ_CPU() | DSP_GetHREQ(); #else Level6_IRQ = MFP_GetIRQ_CPU(); #endif if ( Level6_IRQ == 1 ) M68000_SetIRQ ( 6 ); else M68000_ClearIRQ ( 6 ); if ( SCC_Get_Line_IRQ() == SCC_IRQ_ON ) M68000_SetIRQ ( 5 ); else M68000_ClearIRQ ( 5 ); if ( pendingInterrupts ) doint(); else M68000_UnsetSpecial ( SPCFLAG_INT | SPCFLAG_DOINT ); /* Temporary case for WinUAE CPU handling IPL in CE mode */ /* doint() will update regs.ipl_pin, so copy it into regs.ipl[0] */ /* TODO : see ipl_fetch_next / update_ipl, we should not reset currcycle */ /* (when counting Hatari's internal cycles) to have correct value */ /* in regs.ipl_pin_change_evt. In the meantime we always copy regs.ipl_pin */ /* to regs.ipl_pin_p, else ipl_fetch_next can return an incorrect ipl */ if ( CpuRunCycleExact ) { regs.ipl[0] = regs.ipl_pin; /* See ipl_fetch() in cpu/cpu_prefetch.h */ regs.ipl_pin_p = regs.ipl_pin; /* See ipl_fetch_next() */ } } /*-----------------------------------------------------------------------*/ /** * There are some wait states when accessing certain hardware registers on the ST. * This function simulates these wait states and add the corresponding cycles. * * [NP] with some instructions like CLR, we have a read then a write at the * same location, so we may have 2 wait states (read and write) to add * (WaitStateCycles should be reset to 0 after all the cycles were added * in run_xx() in newcpu.c). * * - When CPU runs in cycle exact mode, wait states are added immediately. * - For other less precise modes, all the wait states are cumulated and added * after the instruction was processed. * * NOTE : this function should only be called in the context of emulating an opcode, * it should not be called in the context of an internal timer called by CycInt_Process() * because cycles would not be correctly added to CyclesGlobalClockCounter */ void M68000_WaitState(int WaitCycles) { if ( CpuRunCycleExact ) currcycle += ( WaitCycles * CYCLE_UNIT / 2 ); /* Add wait states immediately to the CE cycles counter */ else { WaitStateCycles += WaitCycles; /* Cumulate all the wait states for this instruction */ } } /*-----------------------------------------------------------------------*/ /** * Some components (HBL/VBL interrupts, access to the ACIA) require an * extra delay to be synchronized with the E Clock. * E Clock's frequency is 1/10th of the CPU, ie 0.8 MHz in an STF/STE * This delay is a multiple of 2 and will follow the pattern [ 0 8 6 4 2 ] */ int M68000_WaitEClock ( void ) { int CyclesToNextE; /* We must wait for the next multiple of 10 cycles to be synchronised with E Clock */ CyclesToNextE = 10 - Cycles_GetClockCounterImmediate() % 10; if ( CyclesToNextE == 10 ) /* we're already synchronised with E Clock */ CyclesToNextE = 0; //fprintf ( stderr , "wait eclock delay=%d clock=%"PRIu64"\n" , CyclesToNextE, Cycles_GetClockCounterImmediate() ); return CyclesToNextE; } /*-----------------------------------------------------------------------*/ /** * Some hardware registers can only be accessed on a 4 cycles boundary * (shifter color regs and shifter res reg). * An extra delay should be added when needed if current cycle * count is not multiple of 4. */ static void M68000_SyncCpuBus ( bool read ) { uint64_t Cycles; int CyclesToNextBus; if ( read ) Cycles = Cycles_GetClockCounterOnReadAccess(); else Cycles = Cycles_GetClockCounterOnWriteAccess(); CyclesToNextBus = Cycles & 3; //fprintf ( stderr , "sync bus %lld %d\n" , Cycles, CyclesToNextBus ); if ( CyclesToNextBus != 0 ) { //fprintf ( stderr , "sync bus wait %lld %d\n" ,Cycles, 4-CyclesToNextBus ); M68000_WaitState ( 4 - CyclesToNextBus ); } } void M68000_SyncCpuBus_OnReadAccess ( void ) { M68000_SyncCpuBus ( true ); } void M68000_SyncCpuBus_OnWriteAccess ( void ) { M68000_SyncCpuBus ( false ); } /*-----------------------------------------------------------------------*/ /** * In case we modified the memory by accessing it directly (and bypassing * the CPU's cache mechanism), we need to flush the instruction and data * caches to force an update of the caches on the next accesses. * * [NP] NOTE : for now, flush_instr_caches and flush_dcache flush * the whole caches, not just 'addr' */ void M68000_Flush_All_Caches ( uaecptr addr , int size ) { //fprintf ( stderr , "M68000_Flush_All_Caches\n" ); flush_cpu_caches(true); invalidate_cpu_data_caches(); /* For the MegaSTE, we also flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); } void M68000_Flush_Instr_Cache ( uaecptr addr , int size ) { //fprintf ( stderr , "M68000_Flush_Instr_Cache\n" ); /* Instruction cache for cpu >= 68020 */ flush_cpu_caches(true); /* For the MegaSTE, we also flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); } void M68000_Flush_Data_Cache ( uaecptr addr , int size ) { //fprintf ( stderr , "M68000_Flush_Data_Cache\n" ); /* Data cache for cpu >= 68030 */ invalidate_cpu_data_caches(); /* For the MegaSTE, we also flush the external cache */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) MegaSTE_Cache_Flush (); } /*-----------------------------------------------------------------------*/ /** * When running in 68000 CE mode, allow to change the "do_cycles" functions * in the cpu emulation depending on the blitter state. * - if the blitter is not busy, we keep the 'normal' 68000 CE "do_cycles" functions * - if the blitter is busy, we use a slightly slower "do_cycles" to accurately * count bus accesses made by the blitter and the CPU * * This limits the overhead of emulating cycle exact blitter bus accesses when blitter is OFF. */ void M68000_SetBlitter_CE ( bool state ) { //fprintf ( stderr , "M68000_SetBlitter_CE state=%s\n" , state ? "on" : "off" ); if ( state ) { set_x_funcs_hatari_blitter ( 1 ); /* on */ } else { set_x_funcs_hatari_blitter ( 0 ); /* off */ } } /*-----------------------------------------------------------------------*/ /** * On real STF/STE hardware, DMA accesses are restricted to 4 MB (video addresses, * FDC, STE DMA sound) in the range 0 - $3fffff (22 bits of address) * When STF/STE are expanded beyond 4 MB, some special '_FRB' cookies variables * need to be set in TOS to allocate an intermediate buffer in lower 4 MB * that will be used to transfer data in RAM between 4 MB and 16 MB. * This buffer is needed because real HW can't access RAM beyond 4 MB. * * In Hatari, we allow DMA addresses to use 24 bits when RAM size is set * to 8 or 16 MB. This way any program / TOS version can make use of extra * RAM beyond 4 MB without requiring an intermediate buffer. * * But it should be noted that programs using 24 bits of DMA addresses would * not work on real HW ; this is just to make RAM expansion more transparent * under emulation. * * We return a mask for bits 16-23 : * - 0x3f for compatibility with real HW and limit of 4 MB * - 0xff to allow DMA addresses beyond 4 MB (or for Falcon / TT) */ int DMA_MaskAddressHigh ( void ) { if (Config_IsMachineTT() || Config_IsMachineFalcon()) return 0xff; /* Falcon / TT can access 24 bits with DMA */ else if (ConfigureParams.Memory.STRamSize_KB > 8*1024) /* ST/STE with more than 8 MB */ return 0xff; /* Allow 'fake' 24 bits for DMA */ else if (ConfigureParams.Memory.STRamSize_KB > 4*1024) /* ST/STE with more than 4 MB */ return 0x7f; /* Allow 'fake' 23 bits for DMA */ else /* ST/STE with <= 4 MB */ return 0x3f; /* Limit DMA range to 22 bits (same as real HW) */ } /*-----------------------------------------------------------------------*/ /** * This function should be called when the cpu freq is changed, to update * other components that depend on it. * * For now, only Falcon mode requires some updates for the crossbar */ void M68000_ChangeCpuFreq ( void ) { if ( Config_IsMachineFalcon() ) { Crossbar_Recalculate_Clocks_Cycles(); } } /*-----------------------------------------------------------------------*/ /** * Some CPU registers can't be read or modified directly, some additional * actions are required. */ uint16_t M68000_GetSR ( void ) { MakeSR(); return regs.sr; } void M68000_SetSR ( uint16_t v ) { regs.sr = v; MakeFromSR(); } void M68000_SetPC ( uaecptr v ) { m68k_setpc ( v ); fill_prefetch(); } /** * Dump the contents of the MMU registers */ void M68000_MMU_Info(FILE *fp, uint32_t flags) { if (!ConfigureParams.System.bMMU || ConfigureParams.System.nCpuLevel < 2) { fprintf(fp, "MMU is not enabled.\n"); return; } else if (ConfigureParams.System.nCpuLevel <= 3) /* 68020/68030 mode? */ { fprintf(fp, "MMUSR:\t0x%04x\n", mmusr_030); fprintf(fp, "SRP:\t0x%016" PRIx64 "\n", (uint64_t)srp_030); fprintf(fp, "CRP:\t0x%016" PRIx64 "\n", (uint64_t)crp_030); fprintf(fp, "TC:\t0x%08x\n", tc_030); fprintf(fp, "TT0:\t0x%08x\n", tt0_030); fprintf(fp, "TT1:\t0x%08x\n", tt1_030); } else /* 68040 / 68060 mode */ { fprintf(fp, "MMUSR:\t0x%04x\n", regs.mmusr); fprintf(fp, "SRP:\t0x%08x\n", regs.srp); fprintf(fp, "URP:\t0x%08x\n", regs.urp); fprintf(fp, "TC:\t0x%08x\n", regs.tcr); fprintf(fp, "DTT0:\t0x%08x\n", regs.dtt0); fprintf(fp, "DTT1:\t0x%08x\n", regs.dtt1); fprintf(fp, "ITT0:\t0x%08x\n", regs.itt0); fprintf(fp, "ITT0:\t0x%08x\n", regs.itt1); /* TODO: Also call mmu_dump_tables() here? */ } } /*------------------------------------------------------------------------------*/ /* */ /* MegaSTE 16MHz and cache */ /* */ /* Based on MegaSTE schematics as well as documentation by Christian Zietz */ /* */ /* The MegaSTE can change its CPU clock from 8 MHz to 16 MHz, but as the RAM */ /* still have a "slow" speed that was designed for a 8 MHz CPU, the CPU will */ /* have a lot of wait states to get a slot to access RAM (accesses are shared */ /* between the CPU and the shifter */ /* As such, when the CPU runs at 16 MHz the overall performance of the MegaSTE */ /* is roughly the same as when the CPU runs at 8 MHz. */ /* */ /* To improve RAM accesses, an external 16 KB cache was added to the MegaSTE, */ /* with faster RAM that don't add wait states. */ /* */ /* As seen on the MegaSTE schematics, the cache is made of the following */ /* components : */ /* - 2 8KB x 8 bits 35 ns TAG RAM chips, ref MK48S74N-25 (named u004 and u005) */ /* - 2 8KB x 8 bits 85 ns RAM chips, ref HM6265L (u008 and u009). One RAM chip */ /* will store lower bytes, the other the upper byte for the same cache entry */ /* - some PAL for additional logic on the various signals (u011, u012, ...) */ /* */ /* The cache is made of 8192 lines, each line is 1 word (2 bytes) */ /* When a physical address is accessed, the following bits are used to select */ /* an entry in the TAG RAM : */ /* - bits 15-24 : tag (10 bits) */ /* - bits 1-14 : line (0 to 8191) */ /* - bit 0 : ignored (because the cache stores 16 bit words) */ /* */ /* - Each time a word is accessed (read or write), the corresponding tag value */ /* is stored in the line entry and the 2 bytes are stored in each 8KB RAM chip*/ /* - When bytes are read, the cache will also be updated as a word / 2 bytes, */ /* this is because in the case of 8 bit accesses in RAM/ROM the bus will */ /* carry the 16 bits at this RAM/ROM address (not just the 8 bits of the byte)*/ /* - When bytes are written, the cache will be updated only if it already stores*/ /* an entry with the same tag. In that case, upper or lower byte will be */ /* updated in the previously cached word. */ /* */ /* - RAM (up to 4 MB) and ROM regions can be cached */ /* - IO or cartridge regions can't be cached */ /* */ /*------------------------------------------------------------------------------*/ /* Uncomment the next line to check all entries after every cache update */ //#define MEGA_STE_CACHE_DEBUG_CHECK_ENTRIES /* Update the CPU freq and cache status, depending on content of $ff8e21 * $ff8e21 Mega STe Cache/Processor Control * BIT 0 : Cache (0 - disabled, 1 - enabled) * BIT 1 : CPU Speed (0 - 8MHz, 1 - 16MHz) */ void MegaSTE_CPU_Cache_Update ( uint8_t val ) { //fprintf ( stderr , "MegaSTE_CPU_Cache_Update 0x%x\n" , val ); /* If cache is disabled, flush all entries */ if ( ( val & 1 ) == 0 ) MegaSTE_Cache_Flush (); /* 68000 Frequency changed ? We change freq only in 68000 mode for a * normal MegaSTE, if the user did not request a faster one manually */ if (ConfigureParams.System.nCpuLevel == 0 && ConfigureParams.System.nCpuFreq <= 16) { if ((val & 0x2) != 0) { LOG_TRACE ( TRACE_MEM, "cpu : megaste set to 16 MHz pc=%x\n" , M68000_GetPC() ); /* 16 Mhz bus for 68000 */ Configuration_ChangeCpuFreq ( 16 ); MegaSTE_CPU_Set_16Mhz ( true ); } else { /* 8 Mhz bus for 68000 */ LOG_TRACE ( TRACE_MEM, "cpu : megaste set to 8 MHz pc=%x\n" , M68000_GetPC() ); Configuration_ChangeCpuFreq ( 8 ); MegaSTE_CPU_Set_16Mhz ( false ); } } Statusbar_UpdateInfo(); /* Update clock speed in the status bar */ } void MegaSTE_CPU_Cache_Reset ( void ) { //fprintf ( stderr , "MegaSTE_CPU_Cache_Reset\n" ); IoMem_WriteByte ( 0xff8e21 , 0 ); /* 8 MHz, no cache */ MegaSTE_CPU_Cache_Update ( 0 ); } void MegaSTE_CPU_Set_16Mhz ( bool set_16 ) { if ( !currprefs.cpu_cycle_exact || ( currprefs.cpu_model != 68000 ) ) return; //fprintf ( stderr , "MegaSTE_CPU_Set_16Mhz %d\n" , set_16); /* Enable 16 MHz mode for 68000 CE */ if ( set_16 && ( x_get_iword != get_wordi_ce000_megaste_16 ) ) { /* save current functions */ x_get_iword_megaste_save = x_get_iword; x_put_long_megaste_save = x_put_long; x_put_word_megaste_save = x_put_word; x_put_byte_megaste_save = x_put_byte; x_get_long_megaste_save = x_get_long; x_get_word_megaste_save = x_get_word; x_get_byte_megaste_save = x_get_byte; /* set mega ste specific functions */ x_get_iword = get_wordi_ce000_megaste_16; x_put_long = put_long_ce000_megaste_16; x_put_word = put_word_ce000_megaste_16; x_put_byte = put_byte_ce000_megaste_16; x_get_long = get_long_ce000_megaste_16; x_get_word = get_word_ce000_megaste_16; x_get_byte = get_byte_ce000_megaste_16; } /* Disable 16 MHz mode, restore functions if needed */ if ( !set_16 && ( x_get_iword == get_wordi_ce000_megaste_16 ) ) { /* save current functions */ x_get_iword = x_get_iword_megaste_save; x_put_long = x_put_long_megaste_save; x_put_word = x_put_word_megaste_save; x_put_byte = x_put_byte_megaste_save; x_get_long = x_get_long_megaste_save; x_get_word = x_get_word_megaste_save; x_get_byte = x_get_byte_megaste_save; } } /* * Return true if the cache is enabled, else return false */ static bool MegaSTE_Cache_Is_Enabled ( void ) { if ( IoMem_ReadByte(0xff8e21) & 0x1 ) return true; return false; } /* * Check cache consistency, useful to debug error in the cache * For each valid cache entry, we compare the stored value with * the content of the RAM for the same physical address. * If there's a difference then something went wrong in the cache */ #ifdef MEGA_STE_CACHE_DEBUG_CHECK_ENTRIES static void MegaSTE_Cache_Check_Entries ( const char *txt ); static void MegaSTE_Cache_Check_Entries ( const char *txt ) { uint16_t Line; uint16_t Tag; uint32_t Addr; for ( Line=0 ; Line < MEGA_STE_CACHE_SIZE ; Line++ ) if ( MegaSTE_Cache.Valid[ Line ] ) { Tag = MegaSTE_Cache.Tag[ Line ]; Addr = ( Line << 1 ) | ( Tag << 14 ); if ( MegaSTE_Cache.Value[ Line ] != get_word(Addr) ) fprintf ( stderr , "mega ste cache bad %s : Line=0x%x Tag=0x%x Addr=%x Val=0x%x != 0x%x pc=%x\n" , txt , Line , Tag , Addr , MegaSTE_Cache.Value[ Line ] , get_word(Addr) , M68000_GetPC() ); } } #else /* Debugging OFF : use an empty inline function */ static inline void MegaSTE_Cache_Check_Entries ( const char *txt ); static inline void MegaSTE_Cache_Check_Entries ( const char *txt ) { } #endif /* * Return true if addr is part of a cacheable region, else false * - RAM (up to 4MB) and ROM regions can be cached * - IO or cartridge regions can't be cached * On a 68000 MegaSTE, only the lowest 24 bits of the address should be used * (except if the user forces a 32 bit setting) * * Accesses that would cause a bus error or an address error should not be cached */ static bool MegaSTE_Cache_Addr_Cacheable ( uint32_t addr , int Size , int DoWrite ) { /* The MegaSTE uses a 68000 with only 24 bits of address, upper 8 bits */ /* should be ignored (except if user explicitely forces 32 bits addressing) */ if ( ConfigureParams.System.bAddressSpace24 ) addr &= 0xFFFFFF; /* Word access on odd address will cause an address error */ if ( ( Size == 2 ) && ( addr & 1 ) ) return false; /* no cache */ /* Writing to bytes 0-3 in RAM will cause a bus error */ if ( ( addr < 0x4 ) && DoWrite ) return false; /* no cache */ /* Accessing RAM 0-0x7FF in user mode will cause a bus error */ if ( ( addr < 0x800 ) && !is_super_access ( DoWrite ? false : true ) ) return false; /* no cache */ /* Available RAM can be cached (up to 4MB) */ if ( ( addr < STRamEnd ) && ( addr < 0x400000 ) ) return true; /* TOS in ROM region can be cached only when reading (writing would cause a bus error) */ if ( ( addr >= 0xE00000 ) && ( addr < 0xF00000 ) && !DoWrite ) return true; /* Other regions can't be cached */ return false; } /* * Flush the cache by setting Valid[] to 0 * * Cache will be flushed / invalidated on the following conditions : * - clearing bit 0 at $ff8e21 to disable the cache * - reset * - use of BGACK (if DMA or Blitter are enabled) * - bus error * * For performance reason we do a global 'memset' instead of a 'for loop' * on each individual entry */ void MegaSTE_Cache_Flush ( void ) { //fprintf ( stderr , "MegaSTE_Cache_Flush\n" ); memset ( MegaSTE_Cache.Valid , 0 , MEGA_STE_CACHE_SIZE ); } /* * Convert a cacheable address into a Line number in the cache and a Tag value * Addr lowest 24 bits are split into : * - bits 14-23 : tag (10 bits) * - bits 1-13 : line (0 to 8191) * - bit 0 : ignored (because the cache stores 16 bit words) */ static void MegaSTE_Cache_Addr_Convert ( uint32_t Addr , uint16_t *pLineNbr , uint16_t *pTag ) { *pLineNbr = ( Addr >> 1 ) & 0x1fff; *pTag = ( Addr >> 14 ) & 0x3ff; } /* * Update the cache for a word or byte access * - if Size==2 (read/write word access) the corresponding cache entry is replaced * - If Size==1 (write byte access) the corresponding cache entry is updated * only if it was already associated to the same Tag value. In that case * we update the lower or upper byte of the cached value depending * on Addr being even or odd. * - If Size==1 (read byte access) the corresponding cache entry is replaced by the * corresponding word at the same address (forced to even). This is because when RAM/ROM * is accessed as byte, the bus will in fact carry the whole word (16 bits) at this * address and the cpu will keep only the upper or lower byte (8 bits). The word * on the bus can be used to update the cache. * * Return true if value was added to the cache, else return false */ static bool MegaSTE_Cache_Update ( uint32_t Addr , int Size , uint16_t Val , int DoWrite ) { uint16_t Line; uint16_t Tag; if ( !MegaSTE_Cache_Addr_Cacheable ( Addr , Size , DoWrite ) ) return false; /* data not cacheable */ MegaSTE_Cache_Addr_Convert ( Addr , &Line , &Tag ); if ( Size == 2 ) /* word access : update cache */ { MegaSTE_Cache.Valid[ Line ] = 1; MegaSTE_Cache.Tag[ Line ] = Tag; MegaSTE_Cache.Value[ Line ] = Val; //fprintf ( stderr , "update w %x %x %x : %x\n" , Addr , Line, Tag, Val ); MegaSTE_Cache_Check_Entries ( "update w out" ); return true; /* cache updated */ } else /* byte access : update if already cached */ { if ( MegaSTE_Cache.Valid[ Line ] && ( MegaSTE_Cache.Tag[ Line ] == Tag ) ) { //fprintf ( stderr , "update b %x %x %d : %x\n" , Addr , Line, Tag, Val ); Val &= 0xff; if ( Addr & 1 ) /* update lower byte of cached value */ MegaSTE_Cache.Value[ Line ] = ( MegaSTE_Cache.Value[ Line ] & 0xff00 ) | Val; else /* update upper byte of cached value */ MegaSTE_Cache.Value[ Line ] = ( MegaSTE_Cache.Value[ Line ] & 0xff ) | ( Val << 8 ); MegaSTE_Cache_Check_Entries ( "update b out" ); return true; /* cache updated */ } } return false; /* not stored in cache */ } static bool MegaSTE_Cache_Write ( uint32_t Addr , int Size , uint16_t Val ) { return MegaSTE_Cache_Update ( Addr , Size , Val , 1 ); } static bool MegaSTE_Cache_Read ( uint32_t Addr , int Size , uint16_t *pVal ) { uint16_t Line; uint16_t Tag; if ( !MegaSTE_Cache_Addr_Cacheable ( Addr , Size , 0 ) ) return false; /* cache miss, data not cacheable */ MegaSTE_Cache_Addr_Convert ( Addr , &Line , &Tag ); if ( MegaSTE_Cache.Valid[ Line ] && ( MegaSTE_Cache.Tag[ Line ] == Tag ) ) { *pVal = MegaSTE_Cache.Value[ Line ]; /* get the cached word */ //fprintf ( stderr , "read cache %x %x %d : %d %x\n" , Addr , Line, Tag, Size, *pVal ); if ( Size == 1 ) /* size is byte, not word */ { if ( Addr & 1 ) *pVal = *pVal & 0xff; /* get lower byte of word */ else *pVal = ( *pVal >> 8 ) & 0xff; /* get upper byte of word */ } MegaSTE_Cache_Check_Entries ( "read out" ); return true; /* cache hit */ } return false; /* data not in cache -> update cache */ } /* * Similar to mem_access_delay_xxx functions for 68000 CE in cpu/newcpu.c * but we handle a faster CPU speed of 16 MHz instead of 8 MHz (compared to the RAM speed) * as well as an external cache. * * When the CPU is set to 16 MHz other components are still running as if the CPU was at 8 MHz. * - At 8 MHz word access to standard RAM takes a total of 4 cycles from the point of view * of the CPU (if there's no additional wait states) * - At 16 MHz word access to standard RAM takes a total of 8 cycles from the point of view * of the CPU (if there's no additional wait states) * * - At 8 MHz the GSTMCU shares every 4 cycles between the CPU and the shifter * (2 cycles for the CPU and 2 cycles for the shifter). If the CPU requires an access * during the shifter's slot then the CPU will have to wait until its own slot. * - At 16 MHz, the CPU runs faster but all other components are still running at the same speed. * This means that RAM accesses still require 4 cycles from the point of view of the GSTMCU, * which means 8 cycles from the point of view of the 16 MHz CPU. * The slots between CPU and shifter will last 4 cycles each (instead of 2) from the point * of view of the CPU. If the CPU tries to access memory outside of its slot then it will * have to wait. * * As can be seen from above, if the CPU runs at 16 MHz it will have nearly no benefit * compared to 8 MHz because most of the time the CPU will be slowed down by memory wait states. * This is why an external cache was added to the MegaSTE, with faster RAM that allows to take * advantage of the CPU 16 MHz speed. * */ uae_u32 mem_access_delay_word_read_megaste_16 (uaecptr addr) { uint16_t v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ( !MegaSTE_Cache_Is_Enabled() ) v = wait_cpu_cycle_read_megaste_16 (addr, 1); else { if ( MegaSTE_Cache_Read ( addr , 2 , &v ) ) { x_do_cycles_post (4 * cpucycleunit, v); CpuInstruction.D_Cache_hit++; } else { v = wait_cpu_cycle_read_megaste_16 (addr, 1); MegaSTE_Cache_Update ( addr , 2 , v , 0 ); CpuInstruction.D_Cache_miss++; } } break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: if ( !MegaSTE_Cache_Is_Enabled() ) { v = get_word (addr); x_do_cycles_post (4 * cpucycleunit, v); } else { if ( MegaSTE_Cache_Read ( addr , 2 , &v ) ) { x_do_cycles_post (4 * cpucycleunit, v); CpuInstruction.D_Cache_hit++; } else { v = get_word (addr); x_do_cycles_post (4 * cpucycleunit, v); MegaSTE_Cache_Update ( addr , 2 , v , 0 ); CpuInstruction.D_Cache_miss++; } } break; default: v = get_word (addr); break; } regs.db = v; regs.read_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return v; } uae_u32 mem_access_delay_wordi_read_megaste_16 (uaecptr addr) { uint16_t v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ( !MegaSTE_Cache_Is_Enabled() ) v = wait_cpu_cycle_read_megaste_16 (addr, 2); else { if ( MegaSTE_Cache_Read ( addr , 2 , &v ) ) { x_do_cycles_post (4 * cpucycleunit, v); CpuInstruction.I_Cache_hit++; } else { v = wait_cpu_cycle_read_megaste_16 (addr, 2); MegaSTE_Cache_Update ( addr , 2 , v , 0 ); CpuInstruction.I_Cache_miss++; } } break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: if ( !MegaSTE_Cache_Is_Enabled() ) { v = get_wordi (addr); x_do_cycles_post (4 * cpucycleunit, v); } else { if ( MegaSTE_Cache_Read ( addr , 2 , &v ) ) { x_do_cycles_post (4 * cpucycleunit, v); CpuInstruction.I_Cache_hit++; } else { v = get_wordi (addr); x_do_cycles_post (4 * cpucycleunit, v); MegaSTE_Cache_Update ( addr , 2 , v , 0 ); CpuInstruction.I_Cache_miss++; } } break; default: v = get_wordi (addr); break; } regs.db = v; regs.read_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return v; } uae_u32 mem_access_delay_byte_read_megaste_16 (uaecptr addr) { uint16_t v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: if ( !MegaSTE_Cache_Is_Enabled() ) v = wait_cpu_cycle_read_megaste_16 (addr, 0); else { if ( MegaSTE_Cache_Read ( addr , 1 , &v ) ) { x_do_cycles_post (4 * cpucycleunit, v); CpuInstruction.D_Cache_hit++; } else { v = wait_cpu_cycle_read_megaste_16 (addr, 0); /* Reading with get_word() could create a bus error, so we must first */ /* check if this address can be cached without bus error */ if ( MegaSTE_Cache_Addr_Cacheable ( addr & ~1 , 2 , 0 ) ) MegaSTE_Cache_Update ( addr , 2 , get_word(addr & ~1) , 0 ); CpuInstruction.D_Cache_miss++; } } break; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: if ( !MegaSTE_Cache_Is_Enabled() ) { v = get_byte (addr); x_do_cycles_post (4 * cpucycleunit, v); } else { if ( MegaSTE_Cache_Read ( addr , 1 , &v ) ) { x_do_cycles_post (4 * cpucycleunit, v); CpuInstruction.D_Cache_hit++; } else { v = get_byte (addr); x_do_cycles_post (4 * cpucycleunit, v); MegaSTE_Cache_Update ( addr , 1 , v , 0 ); CpuInstruction.D_Cache_miss++; } } break; default: v = get_byte (addr); break; } regs.db = (v << 8) | v; regs.read_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return v; } void mem_access_delay_byte_write_megaste_16 (uaecptr addr, uae_u32 v) { regs.db = (v << 8) | v; regs.write_buffer = v; if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: wait_cpu_cycle_write_megaste_16 (addr, 0, v); if ( MegaSTE_Cache_Is_Enabled() ) MegaSTE_Cache_Write ( addr , 1 , v ); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: put_byte (addr, v); x_do_cycles_post (4 * cpucycleunit, v); if ( MegaSTE_Cache_Is_Enabled() ) MegaSTE_Cache_Write ( addr , 1 , v ); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return; } put_byte (addr, v); } void mem_access_delay_word_write_megaste_16 (uaecptr addr, uae_u32 v) { if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_before ( 1 ); regs.db = v; regs.write_buffer = v; switch (ce_banktype[addr >> 16]) { case CE_MEMBANK_CHIP16: case CE_MEMBANK_CHIP32: wait_cpu_cycle_write_megaste_16 (addr, 1, v); if ( MegaSTE_Cache_Is_Enabled() ) MegaSTE_Cache_Write ( addr , 2 , v ); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return; case CE_MEMBANK_FAST16: case CE_MEMBANK_FAST32: put_word (addr, v); x_do_cycles_post (4 * cpucycleunit, v); if ( MegaSTE_Cache_Is_Enabled() ) MegaSTE_Cache_Write ( addr , 2 , v ); if ( BlitterPhase ) Blitter_HOG_CPU_mem_access_after ( 1 ); return; } put_word (addr, v); } /* * Similar to wait_cpu_cycle_read / _write functions for 68000 CE in cpu/custom.c * but we handle a faster CPU speed of 16 MHz instead of 8 MHz (compared to the RAM speed) */ uae_u32 wait_cpu_cycle_read_megaste_16 (uaecptr addr, int mode) { uae_u32 v = 0; int ipl = regs.ipl[0]; evt_t now = get_cycles(); uint64_t cycle_slot; cycle_slot = ( CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ) & 7; // fprintf ( stderr , "mem read ce %x %d %lu %lu\n" , addr , mode ,currcycle / cpucycleunit , currcycle ); if ( cycle_slot != 0 ) { // fprintf ( stderr , "mem wait read %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); x_do_cycles ( ( 8 - cycle_slot ) * cpucycleunit); // fprintf ( stderr , "mem wait read after %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); } switch(mode) { case -1: v = get_long(addr); break; case -2: v = get_longi(addr); break; case 1: v = get_word(addr); break; case 2: v = get_wordi(addr); break; case 0: v = get_byte(addr); break; } x_do_cycles_post (4*CYCLE_UNIT, v); // if IPL fetch was pending and CPU had wait states // Use ipl_pin value from previous cycle if (now == regs.ipl_evt && regs.ipl_pin_change_evt > now + cpuipldelay2) { regs.ipl[0] = ipl; } return v; } void wait_cpu_cycle_write_megaste_16 (uaecptr addr, int mode, uae_u32 v) { int ipl = regs.ipl[0]; evt_t now = get_cycles(); uint64_t cycle_slot; cycle_slot = ( CyclesGlobalClockCounter + currcycle*2/CYCLE_UNIT ) & 7; // fprintf ( stderr , "mem write ce %x %d %lu %lu\n" , addr , mode ,currcycle / cpucycleunit , currcycle ); if ( cycle_slot != 0 ) { // fprintf ( stderr , "mem wait write %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); x_do_cycles ( ( 8 - cycle_slot ) * cpucycleunit); // fprintf ( stderr , "mem wait write after %x %d %lu %lu\n" , addr , mode , currcycle / cpucycleunit , currcycle ); } if (mode > -2) { if (mode < 0) { put_long(addr, v); } else if (mode > 0) { put_word(addr, v); } else if (mode == 0) { put_byte(addr, v); } } x_do_cycles_post (4*CYCLE_UNIT, v); // if IPL fetch was pending and CPU had wait states: // Use ipl_pin value from previous cycle if (now == regs.ipl_evt) { regs.ipl[0] = ipl; } } /*----------------------------------------------------------------------*/ /* MegaSTE 16MHz and cache */ /*----------------------------------------------------------------------*/ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/main.c000066400000000000000000000647051504763705000224340ustar00rootroot00000000000000/* Hatari - main.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Main initialization and event handling routines. */ const char Main_fileid[] = "Hatari main.c"; #include #include #include #include "main.h" #include "version.h" #include "configuration.h" #include "control.h" #include "options.h" #include "dialog.h" #include "audio.h" #include "joy.h" #include "file.h" #include "floppy.h" #include "floppy_ipf.h" #include "floppy_stx.h" #include "gemdos.h" #include "mfp.h" #include "fdc.h" #include "hdc.h" #include "ide.h" #include "acia.h" #include "ikbd.h" #include "ioMem.h" #include "keymap.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "midi.h" #include "ncr5380.h" #include "nvram.h" #include "paths.h" #include "printer.h" #include "reset.h" #include "resolution.h" #include "rs232.h" #include "rtc.h" #include "scc.h" #include "screen.h" #include "sdlgui.h" #include "shortcut.h" #include "sound.h" #include "dmaSnd.h" #include "statusbar.h" #include "stMemory.h" #include "str.h" #include "tos.h" #include "video.h" #include "avi_record.h" #include "debugui.h" #include "clocks_timings.h" #include "utils.h" #include "hatari-glue.h" #include "falcon/dsp.h" #include "falcon/videl.h" #if HAVE_GETTIMEOFDAY #include #endif #ifdef WIN32 #include "gui-win/opencon.h" #endif #ifdef EMSCRIPTEN #include "emscripten.h" #endif bool bQuitProgram = false; /* Flag to quit program cleanly */ static int nQuitValue; /* exit value */ static uint32_t nRunVBLs; /* Whether and how many VBLS to run before exit */ static uint32_t nFirstMilliTick; /* Ticks when VBL counting started */ static uint32_t nVBLCount; /* Frame count */ static int nVBLSlowdown = 1; /* host VBL wait multiplier */ static bool bEmulationActive = true; /* Run emulation when started */ static bool bAccurateDelays; /* Host system has an accurate SDL_Delay()? */ static bool bIgnoreNextMouseMotion = false; /* Next mouse motion will be ignored (needed after SDL_WarpMouse) */ static bool bAllowMouseWarp = true; /* disabled when Hatari window loses mouse pointer / key focus */ /*-----------------------------------------------------------------------*/ /** * Return current time as millisecond for performance measurements. * * (On Unix only time spent by Hatari itself is counted, on other * platforms less accurate SDL "wall clock".) */ #if HAVE_SYS_TIMES_H #include #include static uint32_t Main_GetTicks(void) { static unsigned int ticks_to_msec = 0; struct tms fields; if (!ticks_to_msec) { ticks_to_msec = sysconf(_SC_CLK_TCK); Log_Printf(LOG_INFO, "OS clock ticks / second: %d\n", ticks_to_msec); /* Linux has 100Hz virtual clock so no accuracy loss there */ ticks_to_msec = 1000UL / ticks_to_msec; } /* return milliseconds (clock ticks) spent in this process */ times(&fields); return ticks_to_msec * fields.tms_utime; } #else # define Main_GetTicks SDL_GetTicks #endif //#undef HAVE_GETTIMEOFDAY //#undef HAVE_NANOSLEEP /*-----------------------------------------------------------------------*/ /** * Return a time counter in micro seconds. * If gettimeofday is available, we use it directly, else we convert the * return of SDL_GetTicks in micro sec. */ static int64_t Time_GetTicks(void) { int64_t ticks_micro; #if HAVE_GETTIMEOFDAY struct timeval now; gettimeofday ( &now , NULL ); ticks_micro = (int64_t)now.tv_sec * 1000000 + now.tv_usec; #else ticks_micro = (int64_t)SDL_GetTicks() * 1000; /* milli sec -> micro sec */ #endif return ticks_micro; } /*-----------------------------------------------------------------------*/ /** * Sleep for a given number of micro seconds. * If nanosleep is available, we use it directly, else we use SDL_Delay * (which is portable, but less accurate as is uses milli-seconds) */ static void Time_Delay(int64_t ticks_micro) { #ifdef EMSCRIPTEN emscripten_sleep((uint32_t)(ticks_micro / 1000)); /* micro sec -> milli sec */ #else #if HAVE_NANOSLEEP struct timespec ts; int ret; ts.tv_sec = ticks_micro / 1000000; ts.tv_nsec = (ticks_micro % 1000000) * 1000; /* micro sec -> nano sec */ /* wait until all the delay is elapsed, including possible interruptions by signals */ do { errno = 0; ret = nanosleep(&ts, &ts); } while ( ret && ( errno == EINTR ) ); /* keep on sleeping if we were interrupted */ #else SDL_Delay((uint32_t)(ticks_micro / 1000)) ; /* micro sec -> milli sec */ #endif #endif } /*-----------------------------------------------------------------------*/ /** * Always print speeds in Benchmark mode, otherwise only * if loglevel is "info" or higher (when time is recorded). */ static void Main_PrintSpeed(void) { if (!nFirstMilliTick) return; int interval = Main_GetTicks() - nFirstMilliTick; static float previous; float current; int level = LOG_INFO; if (BenchmarkMode && ConfigureParams.Log.nTextLogLevel < level) level = ConfigureParams.Log.nTextLogLevel; current = (1000.0 * nVBLCount) / interval; Log_Printf(level, "SPEED: %.1f VBL/s (%d/%.1fs), diff=%.1f%%\n", current, nVBLCount, interval/1000.0, previous>0.0 ? 100*(current-previous)/previous : 0.0); nVBLCount = nFirstMilliTick = 0; previous = current; } /*-----------------------------------------------------------------------*/ /** * Pause emulation, stop sound. 'visualize' should be set true, * unless unpause will be called immediately afterwards. * * @return true if paused now, false if was already paused */ bool Main_PauseEmulation(bool visualize) { if ( !bEmulationActive ) return false; Audio_EnableAudio(false); bEmulationActive = false; if (visualize) { Main_PrintSpeed(); Statusbar_AddMessage("Emulation paused", 100); /* make sure msg gets shown */ Statusbar_Update(sdlscrn, true); if (bGrabMouse && !bInFullScreen) /* Un-grab mouse pointer in windowed mode */ SDL_SetRelativeMouseMode(false); } return true; } /*-----------------------------------------------------------------------*/ /** * Start/continue emulation * * @return true if continued, false if was already running */ bool Main_UnPauseEmulation(void) { if ( bEmulationActive ) return false; Sound_BufferIndexNeedReset = true; Audio_EnableAudio(ConfigureParams.Sound.bEnableSound); bEmulationActive = true; /* Cause full screen update (to clear all) */ Screen_SetFullUpdate(); if (bGrabMouse) /* Grab mouse pointer again */ SDL_SetRelativeMouseMode(true); return true; } /*-----------------------------------------------------------------------*/ /** * Optionally ask user whether to quit and set bQuitProgram accordingly */ void Main_RequestQuit(int exitval) { if (ConfigureParams.Memory.bAutoSave) { bQuitProgram = true; MemorySnapShot_Capture(ConfigureParams.Memory.szAutoSaveFileName, false); } else if (ConfigureParams.Log.bConfirmQuit) { bQuitProgram = false; /* if set true, dialog exits */ bQuitProgram = DlgAlert_Query("All unsaved data will be lost.\nDo you really want to quit?"); } else { bQuitProgram = true; } if (bQuitProgram) { /* Assure that CPU core shuts down */ M68000_SetSpecial(SPCFLAG_BRK); } nQuitValue = exitval; } /** * Set exit value and enable quit flag */ void Main_SetQuitValue(int exitval) { bQuitProgram = true; M68000_SetSpecial(SPCFLAG_BRK); nQuitValue = exitval; } /*-----------------------------------------------------------------------*/ /** * Set how many VBLs Hatari should run, from the moment this function * is called and return zero. * * If zero value given instead, returns earlier set VBL count. */ uint32_t Main_SetRunVBLs(uint32_t vbls) { if (!vbls) return nRunVBLs; nRunVBLs = vbls; nVBLCount = 0; return 0; } /*-----------------------------------------------------------------------*/ /** * Set VBL wait slowdown factor/multiplayer * * Return NULL on success, error string on error */ const char* Main_SetVBLSlowdown(int factor) { if (factor < 1 || factor > 30) { return "invalid VBL slowdown factor, should be 1-30"; } nVBLSlowdown = factor; return NULL; } /*-----------------------------------------------------------------------*/ /** * This function waits on each emulated VBL to synchronize the real time * with the emulated ST. * Unfortunately SDL_Delay and other sleep functions like usleep or nanosleep * are very inaccurate on some systems like Linux 2.4 or macOS (they can only * wait for a multiple of 10ms due to the scheduler on these systems), so we * have to "busy wait" there to get an accurate timing. * All times are expressed as micro seconds, to avoid too much rounding error. */ void Main_WaitOnVbl(void) { int64_t CurrentTicks; static int64_t DestTicks = 0; int64_t FrameDuration_micro; int64_t nDelay; nVBLCount++; if (nRunVBLs && nVBLCount >= nRunVBLs) { /* show VBLs/s */ Main_PauseEmulation(true); exit(0); } // FrameDuration_micro = (int64_t) ( 1000000.0 / nScreenRefreshRate + 0.5 ); /* round to closest integer */ FrameDuration_micro = ClocksTimings_GetVBLDuration_micro ( ConfigureParams.System.nMachineType , nScreenRefreshRate ); FrameDuration_micro *= nVBLSlowdown; CurrentTicks = Time_GetTicks(); if (DestTicks == 0) /* on first call, init DestTicks */ { DestTicks = CurrentTicks + FrameDuration_micro; } DestTicks += pulse_swallowing_count; /* audio.c - Audio_CallBack() */ nDelay = DestTicks - CurrentTicks; /* Do not wait if we are in fast forward mode or if we are totally out of sync */ /* or if we are in benchmark mode */ if (ConfigureParams.System.bFastForward == true || nDelay < -4*FrameDuration_micro || nDelay > 50*FrameDuration_micro || BenchmarkMode ) { if ( ( ConfigureParams.System.bFastForward == true ) || ( BenchmarkMode == true ) ) { if (!nFirstMilliTick) nFirstMilliTick = Main_GetTicks(); } if (nFrameSkips < ConfigureParams.Screen.nFrameSkips) { nFrameSkips += 1; Log_Printf(LOG_DEBUG, "Increased frameskip to %d\n", nFrameSkips); } /* Only update DestTicks for next VBL */ DestTicks = CurrentTicks + FrameDuration_micro; #ifdef EMSCRIPTEN emscripten_sleep(0); #endif return; } /* If automatic frameskip is enabled and delay's more than twice * the effect of single frameskip, decrease frameskip */ if (nFrameSkips > 0 && ConfigureParams.Screen.nFrameSkips >= AUTO_FRAMESKIP_LIMIT && 2*nDelay > FrameDuration_micro/nFrameSkips) { nFrameSkips -= 1; Log_Printf(LOG_DEBUG, "Decreased frameskip to %d\n", nFrameSkips); } if (bAccurateDelays) { /* Accurate sleeping is possible -> use SDL_Delay to free the CPU */ if (nDelay > 1000) Time_Delay(nDelay - 1000); } else { /* No accurate SDL_Delay -> only wait if more than 5ms to go... */ if (nDelay > 5000) Time_Delay(nDelay<10000 ? nDelay-1000 : 9000); } /* Now busy-wait for the right tick: */ while (nDelay > 0) { CurrentTicks = Time_GetTicks(); nDelay = DestTicks - CurrentTicks; /* If the delay is still bigger than one frame, somebody * played tricks with the system clock and we have to abort */ if (nDelay > FrameDuration_micro) break; } //printf ( "tick %lld\n" , CurrentTicks ); /* Update DestTicks for next VBL */ DestTicks += FrameDuration_micro; } /*-----------------------------------------------------------------------*/ /** * Since SDL_Delay and friends are very inaccurate on some systems, we have * to check if we can rely on this delay function. */ static void Main_CheckForAccurateDelays(void) { int nStartTicks, nEndTicks; /* Force a task switch now, so we have a longer timeslice afterwards */ SDL_Delay(10); nStartTicks = SDL_GetTicks(); SDL_Delay(1); nEndTicks = SDL_GetTicks(); /* If the delay took longer than 10ms, we are on an inaccurate system! */ bAccurateDelays = ((nEndTicks - nStartTicks) < 9); if (bAccurateDelays) Log_Printf(LOG_DEBUG, "Host system has accurate delays. (%d)\n", nEndTicks - nStartTicks); else Log_Printf(LOG_WARN, "Host system does not have accurate delays. (%d)\n", nEndTicks - nStartTicks); } /* ----------------------------------------------------------------------- */ /** * Set mouse pointer to new x,y coordinates and set flag to ignore * the mouse event that is generated by SDL_WarpMouse(). * * Skip the request if: * - it's not position restore and mouse warping is disabled, or * - mouse warp disable due to mouse leaving Hatari window or focus loss * (i.e. user isn't interacting with it) */ void Main_WarpMouse(int x, int y, bool restore) { if (!(restore || ConfigureParams.Screen.bMouseWarp)) return; if (!bAllowMouseWarp) return; SDL_WarpMouseInWindow(sdlWindow, x, y); bIgnoreNextMouseMotion = true; } /* ----------------------------------------------------------------------- */ /** * Set mouse cursor visibility and return if it was visible before. */ bool Main_ShowCursor(bool show) { bool bOldVisibility; bOldVisibility = SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE; if (bOldVisibility != show) { if (show) { SDL_ShowCursor(SDL_ENABLE); } else { SDL_ShowCursor(SDL_DISABLE); } } return bOldVisibility; } /* ----------------------------------------------------------------------- */ /** * Handle mouse motion event. */ static void Main_HandleMouseMotion(int dx, int dy) { static int ax = 0, ay = 0; /* Ignore motion when position has changed right after a reset or TOS * (especially version 4.04) might get confused and play key clicks */ if (bIgnoreNextMouseMotion || nVBLs < 10) { bIgnoreNextMouseMotion = false; return; } /* In zoomed low res mode, we divide dx and dy by the zoom factor so that * the ST mouse cursor stays in sync with the host mouse. However, we have * to take care of lowest bit of dx and dy which will get lost when * dividing. So we store these bits in ax and ay and add them to dx and dy * the next time. */ if (nScreenZoomX != 1) { dx += ax; ax = dx % nScreenZoomX; dx /= nScreenZoomX; } if (nScreenZoomY != 1) { dy += ay; ay = dy % nScreenZoomY; dy /= nScreenZoomY; } if (!bInFullScreen) /* Consider window scaling? */ { static int wx, wy; int win_width, win_height, ndx, ndy; SDL_GetWindowSize(sdlWindow, &win_width, &win_height); if (sdlscrn->w != win_width) { ndx = dx * sdlscrn->w; dx = (ndx + wx) / win_width; wx = (ndx + wx) % win_width; } if (sdlscrn->h != win_height) { ndy = dy * sdlscrn->h; dy = (ndy + wy) / win_height; wy = (ndy + wy) % win_height; } } KeyboardProcessor.Mouse.dx += dx; KeyboardProcessor.Mouse.dy += dy; } /* ----------------------------------------------------------------------- */ /** * SDL message handler. * Here we process the SDL events (keyboard, mouse, ...) and map it to * Atari IKBD events. */ void Main_EventHandler(void) { bool bContinueProcessing; SDL_Event event; int events; int remotepause; static int mleave_x = -1, mleave_y = -1; do { bContinueProcessing = false; /* check remote process control */ remotepause = Control_CheckUpdates(); if ( bEmulationActive || remotepause ) { events = SDL_PollEvent(&event); } else { ShortCut_ActKey(); /* last (shortcut) event activated emulation? */ if ( bEmulationActive ) break; events = SDL_WaitEvent(&event); } if (!events) { /* no events -> if emulation is active or * user is quitting -> return from function. */ continue; } switch (event.type) { case SDL_QUIT: Main_RequestQuit(0); break; case SDL_KEYDOWN: if (event.key.repeat) { bContinueProcessing = true; break; } Keymap_KeyDown(&event.key.keysym); break; case SDL_KEYUP: Keymap_KeyUp(&event.key.keysym); break; case SDL_MOUSEMOTION: /* Read/Update internal mouse position */ Main_HandleMouseMotion(event.motion.xrel, event.motion.yrel); bContinueProcessing = true; break; case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_LEFT) { if (Keyboard.LButtonDblClk == 0) Keyboard.bLButtonDown |= BUTTON_MOUSE; /* Set button down flag */ } else if (event.button.button == SDL_BUTTON_RIGHT) { Keyboard.bRButtonDown |= BUTTON_MOUSE; } else if (event.button.button == SDL_BUTTON_MIDDLE) { /* Start double-click sequence in emulation time */ Keyboard.LButtonDblClk = 1; } break; case SDL_MOUSEBUTTONUP: if (event.button.button == SDL_BUTTON_LEFT) { Keyboard.bLButtonDown &= ~BUTTON_MOUSE; } else if (event.button.button == SDL_BUTTON_RIGHT) { Keyboard.bRButtonDown &= ~BUTTON_MOUSE; } break; case SDL_MOUSEWHEEL: /* Simulate cursor keys on mouse wheel events */ if (event.wheel.x > 0) { IKBD_PressSTKey(0x4d, true); IKBD_PressSTKey(0x4d, false); } else if (event.wheel.x < 0) { IKBD_PressSTKey(0x4b, true); IKBD_PressSTKey(0x4b, false); } if (event.wheel.y < 0) { IKBD_PressSTKey(0x50, true); IKBD_PressSTKey(0x50, false); } else if (event.wheel.y > 0) { IKBD_PressSTKey(0x48, true); IKBD_PressSTKey(0x48, false); } break; case SDL_WINDOWEVENT: Log_Printf(LOG_DEBUG, "SDL2 window event: 0x%x\n", event.window.event); switch(event.window.event) { case SDL_WINDOWEVENT_EXPOSED: if (!ConfigureParams.Screen.bUseSdlRenderer) { /* Hack: Redraw screen here when going into * fullscreen mode without SDL renderer */ sdlscrn = SDL_GetWindowSurface(sdlWindow); Screen_SetFullUpdate(); Statusbar_Init(sdlscrn); } /* fall through */ case SDL_WINDOWEVENT_RESTORED: /* Note: any changes here should most likely * be done also in sdlgui.c::SDLGui_DoDialog() */ Screen_UpdateRect(sdlscrn, 0, 0, 0, 0); break; case SDL_WINDOWEVENT_SIZE_CHANGED: /* internal & external window size changes */ Screen_SetTextureScale(sdlscrn->w, sdlscrn->h, event.window.data1, event.window.data2, false); Screen_UpdateRect(sdlscrn, 0, 0, 0, 0); break; /* mouse & keyboard focus */ case SDL_WINDOWEVENT_ENTER: if (mleave_x != -1) { int new_x, new_y; SDL_GetMouseState(&new_x, &new_y); Main_HandleMouseMotion(new_x - mleave_x, new_y - mleave_y); mleave_x = mleave_y = -1; } /* fall through */ case SDL_WINDOWEVENT_FOCUS_GAINED: bAllowMouseWarp = true; break; case SDL_WINDOWEVENT_LEAVE: SDL_GetMouseState(&mleave_x, &mleave_y); /* fall through */ case SDL_WINDOWEVENT_FOCUS_LOST: bAllowMouseWarp = false; break; } bContinueProcessing = true; break; default: /* don't let unknown events delay event processing */ bContinueProcessing = true; break; } } while (bContinueProcessing || !(bEmulationActive || bQuitProgram)); } /*-----------------------------------------------------------------------*/ /** * Set Hatari window title. Use NULL for default */ void Main_SetTitle(const char *title) { if (title) SDL_SetWindowTitle(sdlWindow, title); else SDL_SetWindowTitle(sdlWindow, PROG_NAME); } /*-----------------------------------------------------------------------*/ /** * Initialise emulation for some hardware components * It is required to init those parts before parsing the parameters * (for example, we should init FDC before inserting a disk and we * need to know valid joysticks before selecting default joystick IDs) */ static void Main_Init_HW(void) { Joy_Init(); FDC_Init(); STX_Init(); Video_InitTimings(); } /*-----------------------------------------------------------------------*/ /** * Initialise emulation */ static void Main_Init(void) { /* Open debug log file */ if (!Log_Init()) { Main_ErrorExit("Logging/tracing initialization failed", NULL, -1); } Log_Printf(LOG_INFO, PROG_NAME ", compiled on: " __DATE__ ", " __TIME__ "\n"); /* Init SDL's video subsystem. Note: Audio subsystem will be initialized later (failure not fatal). */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { Main_ErrorExit("Could not initialize the SDL library:", SDL_GetError(), -1); } if ( IPF_Init() != true ) { Main_ErrorExit("Could not initialize the IPF support", NULL, -1); } ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); Resolution_Init(); SDLGui_Init(); Printer_Init(); MFP_Init(MFP_Array); RS232_Init(); SCC_Init(); Midi_Init(); Videl_Init(); Screen_Init(); Main_SetTitle(NULL); STMemory_Init ( ConfigureParams.Memory.STRamSize_KB * 1024 ); ACIA_Init( ACIA_Array , MachineClocks.ACIA_Freq , MachineClocks.ACIA_Freq ); IKBD_Init(); /* After ACIA_Init */ DSP_Init(); Floppy_Init(); M68000_Init(); /* Init CPU emulation */ Audio_Init(); Keymap_Init(); /* Init HD emulation */ HDC_Init(); Ncr5380_Init(); Ide_Init(); GemDOS_Init(); if (ConfigureParams.HardDisk.bUseHardDiskDirectories) { /* uses variables set by HDC_Init/Ncr5380_Init/Ide_Init */ GemDOS_InitDrives(); } if (Reset_Cold()) /* Reset all systems, load TOS image */ { /* If loading of the TOS failed, we bring up the GUI to let the * user choose another TOS ROM file. */ Dialog_DoProperty(); } if (!bTosImageLoaded || bQuitProgram) { if (!bTosImageLoaded) { Main_ErrorExit("Failed to load TOS image", NULL, -2); } SDL_Quit(); exit(-2); } IoMem_Init(); NvRam_Init(); Sound_Init(); Rtc_Init(); /* done as last, needs CPU & DSP running... */ DebugUI_Init(); } /*-----------------------------------------------------------------------*/ /** * Un-Initialise emulation */ static void Main_UnInit(void) { Screen_ReturnFromFullScreen(); Floppy_UnInit(); HDC_UnInit(); Ncr5380_UnInit(); Midi_UnInit(); SCC_UnInit(); RS232_UnInit(); Printer_UnInit(); IoMem_UnInit(ConfigureParams.System.nMachineType); NvRam_UnInit(); GemDOS_UnInitDrives(); Ide_UnInit(); Joy_UnInit(); if (Sound_AreWeRecording()) Sound_EndRecording(); Audio_UnInit(); SDLGui_UnInit(); DSP_UnInit(); Screen_UnInit(); Exit680x0(); IPF_Exit(); /* SDL uninit: */ SDL_Quit(); /* Close debug log file */ DebugUI_UnInit(); Log_UnInit(); Paths_UnInit(); } /*-----------------------------------------------------------------------*/ /** * Load initial configuration files. The global config file is skipped in * test mode (i.e. if the HATARI_TEST environment variable has been set), * so that the test has always full control over the configuration settings. */ static void Main_LoadInitialConfig(void) { char *psGlobalConfig; if (getenv("HATARI_TEST")) psGlobalConfig = NULL; else psGlobalConfig = malloc(FILENAME_MAX); if (psGlobalConfig) { File_MakePathBuf(psGlobalConfig, FILENAME_MAX, CONFDIR, "hatari", "cfg"); /* Try to load the global configuration file */ Configuration_Load(psGlobalConfig); free(psGlobalConfig); } /* Now try the users configuration file */ Configuration_Load(NULL); if (ConfigureParams.Keyboard.nLanguage == TOS_LANG_UNKNOWN) ConfigureParams.Keyboard.nLanguage = TOS_DefaultLanguage(); } /*-----------------------------------------------------------------------*/ /** * Set TOS etc information and initial help message */ static void Main_StatusbarSetup(void) { struct { const int id; bool mod; char *name; } keys[] = { { SHORTCUT_OPTIONS, false, NULL }, { SHORTCUT_MOUSEGRAB, false, NULL } }; const char *name; bool named; SDL_Keycode key; int i; named = false; for (i = 0; i < ARRAY_SIZE(keys); i++) { key = ConfigureParams.Shortcut.withoutModifier[keys[i].id]; if (!key) { key = ConfigureParams.Shortcut.withModifier[keys[i].id]; if (!key) continue; keys[i].mod = true; } name = SDL_GetKeyName(key); if (!name) continue; keys[i].name = Str_ToUpper(strdup(name)); named = true; } if (named) { char message[60]; snprintf(message, sizeof(message), "Press %s%s for Options, %s%s for mouse grab toggle", keys[0].mod ? "AltGr+": "", keys[0].name, keys[1].mod ? "AltGr+": "", keys[1].name); for (i = 0; i < ARRAY_SIZE(keys); i++) { if (keys[i].name) free(keys[i].name); } Statusbar_AddMessage(message, 5000); } /* update information loaded by Main_Init() */ Statusbar_UpdateInfo(); } /** * Error exit wrapper, to make sure user sees the error messages * also on Windows. * * If message is given, Windows console is opened to show it, * otherwise it's assumed to be already open and relevant * messages shown before calling this. * * User input is waited on Windows, to make sure user sees * the message before console closes. * * Value overrides nQuitValue as process exit/return value. */ void Main_ErrorExit(const char *msg1, const char *msg2, int errval) { if (msg1) { #ifdef WIN32 Win_ForceCon(); #endif if (msg2) fprintf(stderr, "ERROR: %s\n\t%s\n", msg1, msg2); else fprintf(stderr, "ERROR: %s!\n", msg1); } SDL_Quit(); #ifdef WIN32 fputs("\n", stderr); (void)fgetc(stdin); #endif exit(errval); } /** * Main * * Note: 'argv' cannot be declared const, MinGW would then fail to link. */ int main(int argc, char *argv[]) { /* Generate random seed */ Hatari_srand(time(NULL)); /* Setup for string conversions */ Str_Init(); /* Logs default to stderr at start */ Log_Default(); /* Initialize directory strings */ Paths_Init(argv[0]); /* Init some HW components before parsing the configuration / parameters */ Main_Init_HW(); /* Set default configuration values */ Configuration_SetDefault(); /* Now load the values from the configuration file */ Main_LoadInitialConfig(); /* Check for any passed parameters */ if (!Opt_ParseParameters(argc, (const char * const *)argv)) { Control_RemoveFifo(); Main_ErrorExit(NULL, NULL, 1); } /* monitor type option might require "reset" -> true */ Configuration_Apply(true); #ifdef WIN32 Win_OpenCon(); #endif #if HAVE_SETENV /* Needed on maemo but useful also with normal X11 window managers for * window grouping when you have multiple Hatari SDL windows open */ setenv("SDL_VIDEO_X11_WMCLASS", "hatari", 1); #endif /* Init emulator system */ Main_Init(); /* Set initial Statusbar information */ Main_StatusbarSetup(); /* Check if SDL_Delay is accurate */ Main_CheckForAccurateDelays(); /* Immediately start AVI recording ? */ if ( AviRecordOnStartup ) Avi_StartRecording_WithConfig(); /* Run emulation */ Main_UnPauseEmulation(); M68000_Start(); /* Start emulation */ Control_RemoveFifo(); /* cleanly close the AVI file, if needed */ Avi_StopRecording_WithMsg(); /* Un-init emulation system */ Main_UnInit(); return nQuitValue; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/memorySnapShot.c000066400000000000000000000363001504763705000244660ustar00rootroot00000000000000/* Hatari - memorySnapShot.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Memory Snapshot This handles the saving/restoring of the emulator's state so any game or application can be saved and restored at any time. This is quite complicated as we need to store all STRam, all chip states, all emulation variables and then things get really complicated as we need to restore file handles and such like. To help keep things simple each file has one function which is used to save/restore all variables that are local to it. We use one function to reduce redundancy and the function 'MemorySnapShot_Store' decides if it should save or restore the data. */ const char MemorySnapShot_fileid[] = "Hatari memorySnapShot.c"; #include #include "main.h" #include "blitter.h" #include "configuration.h" #include "debugui.h" #include "dmaSnd.h" #include "fdc.h" #include "file.h" #include "floppy.h" #include "floppy_ipf.h" #include "floppy_stx.h" #include "gemdos.h" #include "acia.h" #include "ikbd.h" #include "cycles.h" #include "cycInt.h" #include "ioMem.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "mfp.h" #include "midi.h" #include "psg.h" #include "reset.h" #include "scc.h" #include "sound.h" #include "str.h" #include "stMemory.h" #include "scu_vme.h" #include "tos.h" #include "screen.h" #include "screenConvert.h" #include "video.h" #include "falcon/dsp.h" #include "falcon/crossbar.h" #include "falcon/videl.h" #include "statusbar.h" #include "hatari-glue.h" #define VERSION_STRING "2.6.1" /* Version number of compatible memory snapshots - Always 6 bytes (inc' NULL) */ #define SNAPSHOT_MAGIC 0xDeadBeef #if HAVE_LIBZ #define COMPRESS_MEMORYSNAPSHOT /* Compress snapshots to reduce disk space used */ #endif #ifdef COMPRESS_MEMORYSNAPSHOT /* Remove possible conflicting mkdir declaration from cpu/sysdeps.h */ #undef mkdir #include typedef gzFile MSS_File; #else typedef FILE* MSS_File; #endif static MSS_File CaptureFile; static bool bCaptureSave, bCaptureError; static char Temp_FileName[FILENAME_MAX]; static bool Temp_Confirm; /*-----------------------------------------------------------------------*/ /** * Open file. */ static MSS_File MemorySnapShot_fopen(const char *pszFileName, const char *pszMode) { #ifdef COMPRESS_MEMORYSNAPSHOT return gzopen(pszFileName, pszMode); #else return fopen(pszFileName, pszMode); #endif } /*-----------------------------------------------------------------------*/ /** * Close file. */ static void MemorySnapShot_fclose(MSS_File fhndl) { #ifdef COMPRESS_MEMORYSNAPSHOT gzclose(fhndl); #else fclose(fhndl); #endif } /*-----------------------------------------------------------------------*/ /** * Read from file. */ static int MemorySnapShot_fread(MSS_File fhndl, char *buf, int len) { #ifdef COMPRESS_MEMORYSNAPSHOT return gzread(fhndl, buf, len); #else return fread(buf, 1, len, fhndl); #endif } /*-----------------------------------------------------------------------*/ /** * Write data to file. */ static int MemorySnapShot_fwrite(MSS_File fhndl, const char *buf, int len) { #ifdef COMPRESS_MEMORYSNAPSHOT return gzwrite(fhndl, buf, len); #else return fwrite(buf, 1, len, fhndl); #endif } /*-----------------------------------------------------------------------*/ /** * Seek into file from current position */ static int MemorySnapShot_fseek(MSS_File fhndl, int pos) { #ifdef COMPRESS_MEMORYSNAPSHOT return (int)gzseek(fhndl, pos, SEEK_CUR); /* return -1 if error, new position >=0 if OK */ #else return fseek(fhndl, pos, SEEK_CUR); /* return -1 if error, 0 if OK */ #endif } /*-----------------------------------------------------------------------*/ /** * Open/Create snapshot file, and set flag so 'MemorySnapShot_Store' knows * how to handle data. */ static bool MemorySnapShot_OpenFile(const char *pszFileName, bool bSave, bool bConfirm) { char VersionString[] = VERSION_STRING; #define CORE_VERSION 1 uint8_t CpuCore; /* Set error */ bCaptureError = false; /* after opening file, set bCaptureSave to indicate whether * 'MemorySnapShot_Store' should load from or save to a file */ if (bSave) { if (bConfirm && !File_QueryOverwrite(pszFileName)) { /* info for debugger invocation */ Log_Printf(LOG_INFO, "Save canceled."); return false; } /* Save */ CaptureFile = MemorySnapShot_fopen(pszFileName, "wb"); if (!CaptureFile) { Log_Printf(LOG_WARN, "Save file open error: %s",strerror(errno)); bCaptureError = true; return false; } bCaptureSave = true; /* Store version string */ MemorySnapShot_Store(VersionString, sizeof(VersionString)); /* Store CPU core version */ CpuCore = CORE_VERSION; MemorySnapShot_Store(&CpuCore, sizeof(CpuCore)); } else { /* Restore */ CaptureFile = MemorySnapShot_fopen(pszFileName, "rb"); if (!CaptureFile) { Log_Printf(LOG_WARN, "File open error: %s", strerror(errno)); bCaptureError = true; return false; } bCaptureSave = false; /* Restore version string */ MemorySnapShot_Store(VersionString, sizeof(VersionString)); /* Does match current version? */ if (strcmp(VersionString, VERSION_STRING)) { /* No, inform user and error */ Log_AlertDlg(LOG_ERROR, "Unable to restore Hatari memory state.\n" "Given state file is compatible only with\n" "Hatari version %s", VersionString); bCaptureError = true; return false; } /* Check CPU core version */ MemorySnapShot_Store(&CpuCore, sizeof(CpuCore)); if (CpuCore != CORE_VERSION) { Log_AlertDlg(LOG_ERROR, "Unable to restore Hatari memory state.\n" "Given state file is for different Hatari\n" "CPU core version."); bCaptureError = true; return false; } } /* All OK */ return true; } /*-----------------------------------------------------------------------*/ /** * Close snapshot file. */ static void MemorySnapShot_CloseFile(void) { MemorySnapShot_fclose(CaptureFile); } /*-----------------------------------------------------------------------*/ /** * Skip Nb bytes when reading from/writing to file. */ void MemorySnapShot_Skip(int Nb) { int res; /* Check no file errors */ if (CaptureFile != NULL) { res = MemorySnapShot_fseek(CaptureFile, Nb); /* Did seek OK? */ if (res < 0) bCaptureError = true; } } /*-----------------------------------------------------------------------*/ /** * Save/Restore data to/from file. */ void MemorySnapShot_Store(void *pData, int Size) { long nBytes; /* Check no file errors */ if (CaptureFile != NULL) { /* Saving or Restoring? */ if (bCaptureSave) nBytes = MemorySnapShot_fwrite(CaptureFile, (char *)pData, Size); else nBytes = MemorySnapShot_fread(CaptureFile, (char *)pData, Size); /* Did save OK? */ if (nBytes != Size) bCaptureError = true; } } /*-----------------------------------------------------------------------*/ /** * Save 'snapshot' of memory/chips/emulation variables */ void MemorySnapShot_Capture(const char *pszFileName, bool bConfirm) { //fprintf ( stderr , "MemorySnapShot_Capture in\n" ); /* Make a temporary copy of the parameters for MemorySnapShot_Capture_Do() */ Str_Copy(Temp_FileName, pszFileName, FILENAME_MAX); Temp_Confirm = bConfirm; /* With WinUAE cpu core, capture is done from m68k_run_xxx() after the end of the current instruction */ UAE_Set_State_Save (); //fprintf ( stderr , "MemorySnapShot_Capture out\n" ); } /* * Same as MemorySnapShot_Capture, but snapshot is saved immediately * without restarting emulation (used in the debugger) */ void MemorySnapShot_Capture_Immediate(const char *pszFileName, bool bConfirm) { /* Make a temporary copy of the parameters for MemorySnapShot_Capture_Do() */ Str_Copy(Temp_FileName, pszFileName, FILENAME_MAX); Temp_Confirm = bConfirm; MemorySnapShot_Capture_Do (); } /* * Do the real saving (called from newcpu.c / m68k_go() */ void MemorySnapShot_Capture_Do(void) { uint32_t magic = SNAPSHOT_MAGIC; /* Set to 'saving' */ if (MemorySnapShot_OpenFile(Temp_FileName, true, Temp_Confirm)) { /* Capture each files details */ Configuration_MemorySnapShot_Capture(true); TOS_MemorySnapShot_Capture(true); STMemory_MemorySnapShot_Capture(true); Cycles_MemorySnapShot_Capture(true); /* Before fdc (for CyclesGlobalClockCounter) */ FDC_MemorySnapShot_Capture(true); Floppy_MemorySnapShot_Capture(true); IPF_MemorySnapShot_Capture(true); /* After fdc/floppy are saved */ STX_MemorySnapShot_Capture(true); /* After fdc/floppy are saved */ GemDOS_MemorySnapShot_Capture(true); ACIA_MemorySnapShot_Capture(true); IKBD_MemorySnapShot_Capture(true); MIDI_MemorySnapShot_Capture(true); CycInt_MemorySnapShot_Capture(true); M68000_MemorySnapShot_Capture(true); MFP_MemorySnapShot_Capture(true); PSG_MemorySnapShot_Capture(true); Sound_MemorySnapShot_Capture(true); Video_MemorySnapShot_Capture(true); Blitter_MemorySnapShot_Capture(true); DmaSnd_MemorySnapShot_Capture(true); Crossbar_MemorySnapShot_Capture(true); VIDEL_MemorySnapShot_Capture(true); DSP_MemorySnapShot_Capture(true); DebugUI_MemorySnapShot_Capture(Temp_FileName, true); IoMem_MemorySnapShot_Capture(true); ScreenConv_MemorySnapShot_Capture(true); SCC_MemorySnapShot_Capture(true); SCU_MemorySnapShot_Capture(true); /* end marker */ MemorySnapShot_Store(&magic, sizeof(magic)); /* And close */ MemorySnapShot_CloseFile(); } else { /* just canceled? */ if (!bCaptureError) return; } /* Did error */ if (bCaptureError) Log_AlertDlg(LOG_ERROR, "Unable to save memory state to file: %s", Temp_FileName); else if (Temp_Confirm) Log_AlertDlg(LOG_INFO, "Memory state file saved: %s", Temp_FileName); else Log_Printf(LOG_INFO, "Memory state file saved: %s", Temp_FileName); } /*-----------------------------------------------------------------------*/ /** * Restore 'snapshot' of memory/chips/emulation variables */ void MemorySnapShot_Restore(const char *pszFileName, bool bConfirm) { //fprintf ( stderr , "MemorySnapShot_Restore in\n" ); /* Make a temporary copy of the parameters for MemorySnapShot_Restore_Do() */ Str_Copy(Temp_FileName, pszFileName, FILENAME_MAX); Temp_Confirm = bConfirm; /* With WinUAE cpu core, restore is done from m68k_go() after the end of the current instruction */ UAE_Set_State_Restore (); UAE_Set_Quit_Reset ( false ); /* Ask for "quit" to start restoring state */ set_special(SPCFLAG_MODE_CHANGE); /* exit m68k_run_xxx() loop and check "quit" */ //fprintf ( stderr , "MemorySnapShot_Restore out\n" ); } /* * Do the real restoring (called from newcpu.c / m68k_go() */ void MemorySnapShot_Restore_Do(void) { uint32_t magic; //fprintf ( stderr , "MemorySnapShot_Restore_Do in\n" ); /* Set to 'restore' */ if (MemorySnapShot_OpenFile(Temp_FileName, false, Temp_Confirm)) { Configuration_MemorySnapShot_Capture(false); TOS_MemorySnapShot_Capture(false); /* FIXME [NP] : Reset_Cold calls TOS_InitImage which calls */ /* memory_init. memory_init allocs STRam and TTRam, but TTRam */ /* requires currprefs.address_space_24 which is not restored yet */ /* (it's from M68000_MemorySnapShot_Capture). To resolve this */ /* circular dependency, we init currprefs.address_space_24 here */ /* This should be split in different functions / order to avoid this loop */ currprefs.address_space_24 = ConfigureParams.System.bAddressSpace24; /* Reset emulator to get things running */ IoMem_UnInit(ConfigureParams.System.nMachineType); IoMem_Init(); Reset_Cold(); /* Capture each files details */ STMemory_MemorySnapShot_Capture(false); Cycles_MemorySnapShot_Capture(false); /* Before fdc (for CyclesGlobalClockCounter) */ FDC_MemorySnapShot_Capture(false); Floppy_MemorySnapShot_Capture(false); IPF_MemorySnapShot_Capture(false); /* After fdc/floppy are restored, as IPF depends on them */ STX_MemorySnapShot_Capture(false); /* After fdc/floppy are restored, as STX depends on them */ GemDOS_MemorySnapShot_Capture(false); ACIA_MemorySnapShot_Capture(false); IKBD_MemorySnapShot_Capture(false); /* After ACIA */ MIDI_MemorySnapShot_Capture(false); CycInt_MemorySnapShot_Capture(false); M68000_MemorySnapShot_Capture(false); MFP_MemorySnapShot_Capture(false); PSG_MemorySnapShot_Capture(false); Sound_MemorySnapShot_Capture(false); Video_MemorySnapShot_Capture(false); Blitter_MemorySnapShot_Capture(false); DmaSnd_MemorySnapShot_Capture(false); Crossbar_MemorySnapShot_Capture(false); VIDEL_MemorySnapShot_Capture(false); DSP_MemorySnapShot_Capture(false); DebugUI_MemorySnapShot_Capture(Temp_FileName, false); IoMem_MemorySnapShot_Capture(false); ScreenConv_MemorySnapShot_Capture(false); SCC_MemorySnapShot_Capture(false); SCU_MemorySnapShot_Capture(false); /* version string check catches release-to-release * state changes, bCaptureError catches too short * state file, this check a too long state file. */ MemorySnapShot_Store(&magic, sizeof(magic)); if (!bCaptureError && magic != SNAPSHOT_MAGIC) bCaptureError = true; /* And close */ MemorySnapShot_CloseFile(); /* changes may affect also info shown in statusbar */ Statusbar_UpdateInfo(); if (bCaptureError) { Log_AlertDlg(LOG_ERROR, "Full memory state restore failed!\nPlease reboot emulation."); return; } /* * Apply some specific changes after everything is restored */ if ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) { /* Restore CPU Freq and cache */ MegaSTE_CPU_Cache_Update ( IoMem_ReadByte(0xff8e21) ); } } //fprintf ( stderr , "MemorySnapShot_Restore_Do out\n" ); /* Did error? */ if (bCaptureError) Log_AlertDlg(LOG_ERROR, "Unable to restore memory state from file: %s", Temp_FileName); else if (Temp_Confirm) Log_AlertDlg(LOG_INFO, "Memory state file restored: %s", Temp_FileName); else Log_Printf(LOG_INFO, "Memory state file restored: %s", Temp_FileName); } /*-----------------------------------------------------------------------*/ /* * Save and restore functions required by the UAE CPU core... * ... don't use them in normal Hatari code! */ #include void save_u64(uae_u64 data) { MemorySnapShot_Store(&data, 8); } void save_u32(uae_u32 data) { MemorySnapShot_Store(&data, 4); //printf ("s32 %x\n", data); } void save_u16(uae_u16 data) { MemorySnapShot_Store(&data, 2); //printf ("s16 %x\n", data); } void save_u8(uae_u8 data) { MemorySnapShot_Store(&data, 1); //printf ("s8 %x\n", data); } void save_s8(uae_s8 data) { MemorySnapShot_Store(&data, 1); //printf ("s8s %x\n", data); } uae_u64 restore_u64(void) { uae_u64 data; bCaptureSave=false; /* (re)force bCaptureSave=false to prevent gcc11 warning */ MemorySnapShot_Store(&data, 8); return data; } uae_u32 restore_u32(void) { uae_u32 data; bCaptureSave=false; MemorySnapShot_Store(&data, 4); //printf ("r32 %x\n", data); return data; } uae_u16 restore_u16(void) { uae_u16 data; bCaptureSave=false; MemorySnapShot_Store(&data, 2); //printf ("r16 %x\n", data); return data; } uae_u8 restore_u8(void) { uae_u8 data; bCaptureSave=false; MemorySnapShot_Store(&data, 1); //printf ("r8 %x\n", data); return data; } uae_s8 restore_s8(void) { uae_s8 data; bCaptureSave=false; MemorySnapShot_Store(&data, 1); //printf ("r8s %x\n", data); return data; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/mfp.c000066400000000000000000004005601504763705000222630ustar00rootroot00000000000000/* Hatari - mfp.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. MFP - Multi Functional Peripheral. In emulation terms it's the 'chip from hell' - most differences between a real machine and an emulator are down to this chip. It seems very simple at first but the implementation is very difficult. The following code is the very accurate for an ST emulator as it is able to perform Spectrum 512 raster effects as well as simulate the quirks found in the chip. The easiest way to 'see' the MFP chip is to look at the diagram. It shows the main details of the chip's behaviour with regard to interrupts and pending/service bits. */ /* 2007/04/18 [NP] - Better values for MFPTimerToCPUCycleTable. */ /* - Don't restart the timers in MFP_EnableA_WriteByte and */ /* MFP_EnableB_WriteByte, this gives wrong results. */ /* 2007/05/05 [NP] - When a timer is looping (counter reaches 0), we must use */ /* PendingCyclesOver to restart it with Int_AddRelativeInterrupt. */ /* PendingCyclesOver is the value of PendingInterruptCount when */ /* the timer expired. */ /* - MFP_ReadTimer_AB/CD was wrong (returned the elapsed counter */ /* changes since start, instead of the remaining counter value). */ /* (ULM DSOTS Demos and Overscan Demos). */ /* 2007/09/25 [NP] Replace printf by calls to HATARI_TRACE. */ /* 2007/10/21 [NP] Use 'Int_AddRelativeInterruptWithOffset' when an MFP timer is */ /* looping. Gives better accuracy when using '4' as a divisor. */ /* (fix ULM DSOTS Demos and Overscan Demos). */ /* 2007/10/24 [NP] Handle the possibility to resume a timer after stopping it. */ /* After writing 0 to ctrl, writing a >0 in ctrl should continue */ /* the timer with the value that was stored in data reg when timer */ /* was stopped. The value is saved in MFP_Tx_MAINCOUNTER whenever */ /* 0 is written in ctrl reg (Froggies Over The Fence by STCNX). */ /* 2007/10/28 [NP] Function 'Int_ResumeStoppedInterrupt' to better handle the */ /* possibility to resume a timer that was stopped with ctrl=0 */ /* (ST CNX screen in Punish Your Machine). */ /* 2007/12/27 [NP] When adding a new MFP interrupt (ctrl != 0 ), we must take */ /* into account the number of cycles of the current instruction, as*/ /* well as the accumulated wait state cycles, else the int counter */ /* will be started between 8 - 20 cycles earlier, which can break */ /* some too strict code : the int counter must start after the */ /* current instruction is processed, not before. The write is */ /* considered effective 4 cycles before the end of the current */ /* instruction. */ /* (fix ULM Dark Side Of The Spoon and Decade Demo's Wow Scroll 2).*/ /* 2008/02/06 [NP] Handle "fast" timers as those started by the TOS for the RS232 */ /* baud rate generator. In that case, the timers could be too fast */ /* to be handled by the CPU, which means PendingCyclesOver can be */ /* >= INT_CONVERT_TO_INTERNAL ( TimerClockCycles , INT_MFP_CYCLE ) */ /* and this will give wrong results when the timer restarts if */ /* we call Int_AddRelativeInterruptWithOffset. We use a modulo to */ /* limit PendingCyclesOver to not more than the number of cycles */ /* of one int (which means we "skip" the ints that could not be */ /* processed). */ /* 2008/03/08 [NP] Add traces when writing to vector register fffa17. */ /* Use M68000_INT_MFP when calling M68000_Exception(). */ /* 2008/04/17 [NP] Handle the case where Timer B is in event count mode and the */ /* content of $fffa21 is updated by the end of line signal while a */ /* read instruction at addr $fffa21 occurs at the same time (before*/ /* calling MFP_TimerB_EventCount_Interrupt). */ /* In that case, we need to return MFP_TB_MAINCOUNTER - 1. */ /* (fix B.I.G. Demo Screen 1). */ /* FIXME : this should be handled by Cycles_GetCounterOnReadAccess */ /* but it's not correctly implemented at the moment. */ /* 2008/04/20 [NP] In the TRACE call in 'MFP_Exception', replace 'get_long' by */ /* 'STMemory_ReadLong' because 'get_long' produced a bus error */ /* if we were not already in supervisor mode when the mfp exception*/ /* occurred. This could cause bus error when restoring snapshot */ /* of a gemdos program for example if trace mode was activated. */ /* 2008/07/12 [NP] When stopping an active timer just when the internal data */ /* counter is going from 1 to 0, the internal data counter will be */ /* set to 0 (=256) instead of being reloaded with the original */ /* data value. In case no new value is written to the data reg, */ /* this means a write > 0 to the control reg will restart the timer*/ /* with a counter of 256 ! (fix timer saving routine used by */ /* ST Cnx in the Punish Your Machine and the Froggies Over The */ /* Fence (although this routine is in fact buggy)). */ /* NOTE : this was wrong, fixed on 2025/05/07 and saving routine */ /* is not buggy. */ /* 2008/09/13 [NP] Add some traces when stopping a timer and changing data reg. */ /* Don't apply timer D patch if timer D ctrl reg is 0. */ /* 2008/10/04 [NP] In MFP_TimerBData_ReadByte, test for overlap only when nHBL */ /* is between nStartHBL and nEndHBL (fix Wolfenstein 3D intro). */ /* In event count mode for timer A and B, set data reg to 255 when */ /* data reg was 0 (which in fact means 256). */ /* 2008/10/16 [NP] No need to set data reg to 255 when decrementing a data reg that*/ /* was 0, this is already what is implicitly done, because data */ /* reg for timer A/B is uint8_t (revert 2008/10/04 changes). */ /* 2008/12/11 [NP] In MFP_CheckPendingInterrupts(), returns true or false instead */ /* of void, depending on whether at least one MFP interrupt was */ /* allowed or not. */ /* 2009/03/28 [NP] Handle bit 3 of AER for timer B (fix Seven Gates Of Jambala). */ /* 2010/07/26 [NP] In MFP_StartTimer_AB, when ctrl reg is in pulse width mode, */ /* clear bit 3 to emulate it as in delay mode. This is not */ /* completely correct as we should also emulate GPIO 3/4, but it */ /* helps running some programs (fix the game Erik). */ /* 2013/02/24 [NP] - In MFP_CheckPendingInterrupts, don't check all the MFP ints, */ /* stop as soon as the highest interrupt is found (simultaneous */ /* interrupts could be processed during the same cycle and were */ /* stacked/executed in the reverse order, from lowest to highest */ /* priority, which was wrong). */ /* - Use MFP_ProcessIRQ to separate the MFP's IRQ signal handling */ /* and the exception processing at the CPU level. */ /* 2013/03/01 [NP] When MFP_IRQ goes from 0 to 1, the resulting signal is visible */ /* to the CPU only 4 cycles later (fix Audio Artistic Demo by */ /* Big Alec and the games Super Hang On, Super Monaco GP, Bolo). */ /* 2013/03/10 [NP] Improve the MFP_IRQ 4 cycle delay by taking into account the */ /* time at which the timer expired during the CPU instruction */ /* (fix Reset part in Decade Demo, High Fidelity Dreams by Aura). */ /* 2013/03/14 [NP] When writing to the MFP's registers, take the write cycles into */ /* account when updating MFP_IRQ_Time (properly fix Super Hang On).*/ /* 2013/04/11 [NP] Handle the IACK cycles, interrupts can change during the first */ /* 12 cycles of an MFP exception (fix Anomaly Demo Menu by MJJ Prod*/ /* and sample intro in the game The Final Conflict). */ /* 2013/04/21 [NP] Handle the case where several MFP interrupts happen during the */ /* same CPU instruction but at different sub-cycles. We must take */ /* into account only the oldest interrupts to choose the highest */ /* one (fix Fuzion CD Menus 77, 78, 84). */ /* 2015/02/27 [NP] Better support for GPIP/AER/DDR and triggering an interrupt */ /* when AER is changed (fix the MIDI programs Realtime and M */ /* by Eric Ameres, which toggle bit 0 in AER). */ /* 2015/04/08 [NP] When an interrupt happens on timers A/B/C/D, take into account */ /* PendingCyclesOver to determine if a 4 cycle delay should be */ /* added or not (depending on when it happened during the CPU */ /* instruction). */ /* 2022/01/07 [NP] Improve accuracy when reading Timer Data reg and don't use */ /* CycInt_ResumeStoppedInterrupt() anymore (fix ST CNX screen */ /* in Punish Your Machine when saving MFP registers by doing very */ /* fast start/stop on each MFP timer) */ /* 2022/01/27 [NP] Call MFP_UpdateTimers / CycInt_Process before accessing any MFP */ /* registers, to ensure MFP timers are updated in chronological */ /* order (fix the game Super Hang On, where 'bclr #0,$fffffa0f' */ /* to clear Timer B ISR sometimes happens at the same time that */ /* Timer C expires, which used the wrong ISR value and gave */ /* flickering raster colors) */ /* 2025/05/07 [NP] Fix wrong behaviour from 2008/07/12 : when timer is stopped and */ /* restarted when counter is going from 1 to 0, then the timer */ /* will be restarted with the latest timer data register, not with */ /* data=0 (=256). */ const char MFP_fileid[] = "Hatari mfp.c"; #include /* Needed for UINT64_MAX */ #include "main.h" #include "configuration.h" #include "dmaSnd.h" #include "crossbar.h" #include "fdc.h" #include "ikbd.h" #include "hatari-glue.h" #include "cycInt.h" #include "ioMem.h" #include "joy.h" #include "m68000.h" #include "cycles.h" #include "memorySnapShot.h" #include "mfp.h" #include "psg.h" #include "rs232.h" #include "sound.h" #include "stMemory.h" #include "tos.h" #include "vdi.h" #include "video.h" #include "ncr5380.h" #include "clocks_timings.h" #include "acia.h" #include "utils.h" #include "scc.h" /* MC68901 MultiFuncion Peripheral (MFP) References : - MC68901 datasheet by Motorola - Boards schematics for ST/STE/MegaST/MegaSTE/TT/Falcon Main MFP : 48 Pin version for STF/megaSTF/STe/megaSTE ----------------------------------------------------- ----------- R/W -| 1 48 |- CS(INV) RS1/A1 -| 2 47 |- DS(INV) RS2/A2 -| 3 46 |- DTACK(INV) RS3/A3 -| 4 45 |- IACK(INV) RS4/A4 -| 5 44 |- D7 RS5/A5 -| 6 43 |- D6 TC : connected to TDO -| 7 42 |- D5 SO : connected to Send on RS-232C -| 8 41 |- D4 SI : connected to Receive on RS-232C -| 9 40 |- D3 RC : connected to TDO -| 10 39 |- D2 VCC -| 11 38 |- D1 not connected -| 12 37 |- D0 TAO : not connected -| 13 36 |- GND TBO : not connected -| 14 35 |- CLK : connected to 4 MHz TCO : not connected -| 15 34 |- IEI(INV) : on TT connected to IEO on TT MFP, else connected to GND TDO : connected to TC and RC -| 16 33 |- IEO(INV) : not connected XTAL2 : not connected -| 17 32 |- IRQ(INV) XTAL1 : connected to 2.4576 MHz -| 18 31 |- RR(INV) : not connected TAI : ST connected to IO0, else DMA SOUND INT -| 19 30 |- TR(INV) : not connected TBI : connected to video DE -| 20 29 |- I7 : GPIP 7, see note below RESET(INV) -| 21 28 |- I6 : GPIP 6, see note below I0 : GPIP 0, see note below -| 22 27 |- I5 : GPIP 5, see note below I1 : GPIP 1, see note below -| 23 26 |- I4 : GPIP 4, see note below I2 : GPIP 2, see note below -| 24 25 |- I3 : GPIP 3, see note below ----------- Main MFP : 52 Pin version for TT/Falcon --------------------------------------- ----------- not connected -| 1 52 |- CS(INV) R/W -| 2 51 |- DS(INV) RS1/A1 -| 3 50 |- DTACK(INV RS2/A2 -| 4 49 |- IACK(INV RS3/A3 -| 5 48 |- D7 RS4/A4 -| 6 47 |- D6 RS5/A5 -| 7 46 |- D5 TC : connected to TDO -| 8 45 |- D4 SO : connected to Transmit on RS-232C -| 9 44 |- D3 SI : connected to Receive on RS-232C -| 10 43 |- D2 RC : connected to TDO -| 11 42 |- D1 VCC -| 12 41 |- D0 not connected -| 13 40 |- GND not connected -| 14 39 |- CLK : connected to 4 MHz TAO : not connected -| 15 38 |- IEI(INV) : on TT connected to IEO on TT MFP, else connected to GND TBO : not connected -| 16 37 |- IEO(INV) : not connected TCO : not connected -| 17 36 |- IRQ(INV) TDO : connected to TC and RC -| 18 35 |- RR(INV) : not connected XTAL2 : not connected -| 19 34 |- TR(INV) : not connected XTAL1 : connected to 2.4576 MHz -| 20 33 |- not connected not connected -| 21 32 |- I7 : GPIP 7, see note below TAI : ST connected to IO0, else DMA SOUND INT -| 22 31 |- I6 : GPIP 6, see note below TBI : connected to video DE -| 23 30 |- I5 : GPIP 5, see note below RESET(INV) -| 24 29 |- I4 : GPIP 4, see note below I0 : GPIP 0, see note below -| 25 28 |- I3 : GPIP 3, see note below I1 : GPIP 1, see note below -| 26 27 |- I2 : GPIP 2, see note below ----------- TT 2nd MFP : 52 Pin version --------------------------- ----------- not connected -| 1 52 |- CS(INV) R/W -| 2 51 |- DS(INV) RS1/A1 -| 3 50 |- DTACK(INV RS2/A2 -| 4 49 |- IACK(INV RS3/A3 -| 5 48 |- D7 RS4/A4 -| 6 47 |- D6 RS5/A5 -| 7 46 |- D5 TC : connected to TDO -| 8 45 |- D4 SO : connected to Transmit on Serial Port D -| 9 44 |- D3 SI : connected to Receive on Serial Port D -| 10 43 |- D2 RC : connected to TDO -| 11 42 |- D1 VCC -| 12 41 |- D0 not connected -| 13 40 |- GND not connected -| 14 39 |- CLK : connected to 4 MHz TAO : not connected -| 15 38 |- IEI(INV) : connected to GND TBO : not connected -| 16 37 |- IEO(INV) : connected to IEI on Main MFP TCO : connected to TCCLKX -| 17 36 |- IRQ(INV) : connected to IRQ on Main MFP TDO : connected to TC and RC -| 18 35 |- RR(INV) : not connected XTAL2 : not connected -| 19 34 |- TR(INV) : not connected XTAL1 : connected to 2.4576 MHz -| 20 33 |- not connected not connected -| 21 32 |- I7 : GPIP 7, see note below TAI : connected to GND ? -| 22 31 |- I6 : GPIP 6, see note below TBI : connected to video DE -| 23 30 |- I5 : GPIP 5, see note below RESET(INV) -| 24 29 |- I4 : GPIP 4, see note below I0 : GPIP 0, see note below -| 25 28 |- I3 : GPIP 3, see note below I1 : GPIP 1, see note below -| 26 27 |- I2 : GPIP 2, see note below ----------- MFP interrupt channel circuit: EdgeRegister EnableRegister MaskRegister SBit | | | | | | | | ------------------------ | | ------------------------ ---\ |---\ | | | o--\ | | AND---o----------------AND---| S InterruptInService | ---\ | AND---| S InterruptPending O |-------/ | |---/ | | XOR----------)--/ | R | | | ------------------------ Input -----/ | ------------------------ | | | | InterruptRequest | NOT OR | | | | | -------------------- --------------------------------------o--- PassVector */ /* Emulation Note : - MFP emulation doesn't run in parallel with the CPU emulation as it would take too much resources. Instead, MFP emulation is called each time a CPU instruction is completely processed. The drawback is that several MFP interrupts can happen during a single CPU instruction (especially for long ones like MOVEM or DIV). In that case, we should not choose the highest priority interrupt among all the interrupts, but we should keep only the interrupts that chronologically happened first during this CPU instruction (and ignore the other interrupts' requests for this CPU instruction). - When the MFP's main IRQ signal goes from 0 to 1, the signal is not immediately visible to the CPU, but only 4 cycles later. This 4 cycle delay should be taken into account, depending at what time the signal went to 1 in the corresponding CPU instruction (the 4 cycle delay can be "included" in the CPU instruction in some cases) - When an interrupt happens in the MFP, an exception will be started in the CPU. Then after 12 cycles an IACK sequence will be started by the CPU to request the interrupt vector from the MFP. During those 12 cycles, it is possible that a new higher priority MFP interrupt happens and in that case we must replace the MFP vector number that was initially computed at the start of the exception with the new one. This is also after the IACK sequence that in service / pending bits must be handled for this MFP's interrupt. - The TT uses 2 MFPs which are daisy chained using IEI and IEO signals (as described in the 68901's documentation) In that case, the TT's specific MFP (accessible between $FFFA81 and $FFFAAF) has the highest priority and the "normal" MFP (accessible between $FFFA01 and $FFFA2F) has the lowest priority - Each MFP has 8 GPIP bits used to connect signals from external devices/ports : Main MFP : 0 : parallel port, busy signal (0=not busy, 1=busy) 1 : rs232 port, data carrier detect (DCD) signal 2 : rs232 port, clear to send (CTS) signal 3 : STF/STE/TT : blitter, active low (0=IRQ set, 1=IRQ not set) Falcon : DSP, active low (0=HREQ set, 1=HREQ not set) 4 : ACIAs 6850 (ikbd and midi), active low (0=IRQ set for ACIA 1 and/or 2, 1=IRQ not set for neither ACIA 1 nor 2) 5 : FDC/HD, active low (0=IRQ set, 1=IRQ not set) STF/STE/megaSTE/TT : FDC/ACSI (0=IRQ set for FDC and/or ACSI, 1=IRQ not set for FDC nor ACSI) Falcon : FDC/IDE/SCSI (0=IRQ set for FDC and/or IDE and/or SCSI, 1=IRQ not set for FDC nor IDE nor SCSI) 6 : rs232 port, ring indicator (RI) signal 7 : monochrome monitor detect (0=monochrome, 1=color) and/or dma sound (0=idle, 1=play) STF : monochrome monitor detect (0=monochrome, 1=color) STE/TT : monochrome monitor detect XOR dma sound play Falcon : dma sound play/record (0=idle, 1=play/record) TT MFP : 0 : connected to external I/O pin 1 : connected to external I/O pin 2 : SCC DMA controller, active low (0=IRQ set, 1=IRQ not set) 3 : SCC serial port B, ring indicator (RI) signal 4 : Internal floppy drive pin 34, NOT disk change (DC) signal (0=inserted 1=ejected) 5 : SCSI DMA controller, active low (0=IRQ set, 1=IRQ not set) 6 : RTC MC146818A, active low (0=IRQ set, 1=IRQ not set) 7 : SCSI NCR5380, active *high* (1=IRQ set, 0=IRQ not set) */ /*-----------------------------------------------------------------------*/ #define TRACE_MFP (1ll<<55) MFP_STRUCT MFP_Array[ MFP_MAX_NB ]; MFP_STRUCT *pMFP_Main; MFP_STRUCT *pMFP_TT; #define PATCH_TIMER_TDDR_FAKE 0x64 /* TDDR value to slow down timer D */ static int PendingCyclesOver = 0; /* >= 0 value, used to "loop" a timer when data counter reaches 0 */ bool MFP_UpdateNeeded = false; /* When set to true, main CPU loop should call MFP_UpdateIRQ() */ #define MFP_IRQ_DELAY_TO_CPU 4 /* When MFP_IRQ is set, it takes 4 CPU cycles before it's visible to the CPU */ static const uint16_t MFPDiv[] = { 0, 4, 10, 16, 50, 64, 100, 200 }; /* Convert data/ctrl register to a number of mfp cycles */ #define MFP_REG_TO_CYCLES(data,ctrl) ( data * MFPDiv[ ctrl&0x7 ] ) /* Determine the data register corresponding to a number of mfp cycles/ctrl register */ /* (we round to the closest higher integer) */ #define MFP_CYCLE_TO_REG(cyc,ctrl) ( ( cyc + MFPDiv[ ctrl&0x7 ] - 1 ) / MFPDiv[ ctrl&0x7 ] ) //#define MFP_CYCLE_TO_REG(cyc,ctrl) ( cyc / MFPDiv[ ctrl&0x7 ] ) /* Interrupt number associated to each line of the GPIP */ static const int MFP_GPIP_LineToIntNumber[] = { MFP_INT_GPIP0 , MFP_INT_GPIP1 , MFP_INT_GPIP2 , MFP_INT_GPIP3, MFP_INT_GPIP4 , MFP_INT_GPIP5 , MFP_INT_GPIP6 , MFP_INT_GPIP7 }; /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static void MFP_Init_Pointers ( MFP_STRUCT *pAllMFP ); static void MFP_Reset ( MFP_STRUCT *pMFP ); static uint8_t MFP_ConvertIntNumber ( MFP_STRUCT *pMFP , int16_t Interrupt , uint8_t **pMFP_IER , uint8_t **pMFP_IPR , uint8_t **pMFP_ISR , uint8_t **pMFP_IMR ); static void MFP_UpdateTimers ( MFP_STRUCT *pMFP , uint64_t Clock ); static void MFP_Exception ( MFP_STRUCT *pMFP , int16_t Interrupt ); static bool MFP_ProcessIRQ ( MFP_STRUCT *pMFP ); static void MFP_UpdateIRQ ( MFP_STRUCT *pMFP , uint64_t Event_Time ); static bool MFP_InterruptRequest ( MFP_STRUCT *pMFP , int Int , uint8_t Bit , uint8_t IPRx , uint8_t IMRx , uint8_t PriorityMaskA , uint8_t PriorityMaskB ); static int MFP_CheckPendingInterrupts ( MFP_STRUCT *pMFP ); static void MFP_GPIP_Update_Interrupt ( MFP_STRUCT *pMFP , uint8_t GPIP_old , uint8_t GPIP_new , uint8_t AER_old , uint8_t AER_new , uint8_t DDR_old , uint8_t DDR_new ); static uint8_t MFP_Main_Compute_GPIP7 ( void ); static uint8_t MFP_Main_Compute_GPIP_LINE_ACIA ( void ); static void MFP_GPIP_ReadByte_Main ( MFP_STRUCT *pMFP ); static void MFP_GPIP_ReadByte_TT ( MFP_STRUCT *pMFP ); /*-----------------------------------------------------------------------*/ /** * Convert a number of CPU cycles running at CPU_Freq_Emul to a number of * MFP timer cycles running at MFP_Timer_Freq (XTAL) */ int MFP_ConvertCycle_CPU_MFP_TIMER ( int CPU_Cycles ) { int MFP_Cycles; MFP_Cycles = (int)( ( (uint64_t)CPU_Cycles * MachineClocks.MFP_Timer_Freq ) / MachineClocks.CPU_Freq_Emul ); return MFP_Cycles; } /*-----------------------------------------------------------------------*/ /** * Convert a number of MFP timer cycles running at MFP_Timer_Freq (XTAL) * to a number of CPU cycles running at CPU_Freq_Emul */ int MFP_ConvertCycle_MFP_TIMER_CPU ( int MFP_Cycles ) { int CPU_Cycles; CPU_Cycles = (int)( ( (uint64_t)MFP_Cycles * MachineClocks.CPU_Freq_Emul ) / MachineClocks.MFP_Timer_Freq ); return CPU_Cycles; } /*-----------------------------------------------------------------------*/ /** * Init the 2 MFPs ; the 2nd MFP is only used in TT mode * This is called only once, when the emulator starts. */ void MFP_Init ( MFP_STRUCT *pAllMFP ) { int i; LOG_TRACE ( TRACE_MFP , "mfp init\n" ); for ( i=0 ; iGPIP = 0; pMFP->AER = 0; pMFP->DDR = 0; pMFP->IERA = 0; pMFP->IERB = 0; pMFP->IPRA = 0; pMFP->IPRB = 0; pMFP->ISRA = 0; pMFP->ISRB = 0; pMFP->IMRA = 0; pMFP->IMRB = 0; pMFP->VR = 0; pMFP->TACR = 0; pMFP->TBCR = 0; pMFP->TCDCR = 0; pMFP->TADR = 0; pMFP->TBDR = 0; pMFP->TCDR = 0; pMFP->TDDR = 0; pMFP->TA_MAINCOUNTER = 0; pMFP->TB_MAINCOUNTER = 0; pMFP->TC_MAINCOUNTER = 0; pMFP->TD_MAINCOUNTER = 0; /* Clear counters */ // TODO drop those 4 variables, as they are not really used in MFP_ReadTimer_xx pMFP->TimerAClockCycles = 0; pMFP->TimerBClockCycles = 0; pMFP->TimerCClockCycles = 0; pMFP->TimerDClockCycles = 0; pMFP->PatchTimerD_Done = 0; /* Clear input on timers A and B */ pMFP->TAI = 0; pMFP->TBI = 0; /* Clear IRQ */ pMFP->Current_Interrupt = -1; pMFP->IRQ = 0; pMFP->IRQ_CPU = 0; pMFP->IRQ_Time = 0; pMFP->Pending_Time_Min = UINT64_MAX; for ( i=0 ; i<=MFP_INT_MAX ; i++ ) pMFP->Pending_Time[ i ] = UINT64_MAX; } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void MFP_MemorySnapShot_Capture ( bool bSave ) { MFP_STRUCT *pMFP; int i; int n; MemorySnapShot_Store(&MFP_UpdateNeeded, sizeof(MFP_UpdateNeeded)); /* Save/Restore each MFP */ for ( n=0 ; nGPIP), sizeof(pMFP->GPIP)); MemorySnapShot_Store(&(pMFP->AER), sizeof(pMFP->AER)); MemorySnapShot_Store(&(pMFP->DDR), sizeof(pMFP->DDR)); MemorySnapShot_Store(&(pMFP->IERA), sizeof(pMFP->IERA)); MemorySnapShot_Store(&(pMFP->IERB), sizeof(pMFP->IERB)); MemorySnapShot_Store(&(pMFP->IPRA), sizeof(pMFP->IPRA)); MemorySnapShot_Store(&(pMFP->IPRB), sizeof(pMFP->IPRB)); MemorySnapShot_Store(&(pMFP->ISRA), sizeof(pMFP->ISRA)); MemorySnapShot_Store(&(pMFP->ISRB), sizeof(pMFP->ISRB)); MemorySnapShot_Store(&(pMFP->IMRA), sizeof(pMFP->IMRA)); MemorySnapShot_Store(&(pMFP->IMRB), sizeof(pMFP->IMRB)); MemorySnapShot_Store(&(pMFP->VR), sizeof(pMFP->VR)); MemorySnapShot_Store(&(pMFP->TACR), sizeof(pMFP->TACR)); MemorySnapShot_Store(&(pMFP->TBCR), sizeof(pMFP->TBCR)); MemorySnapShot_Store(&(pMFP->TCDCR), sizeof(pMFP->TCDCR)); MemorySnapShot_Store(&(pMFP->TADR), sizeof(pMFP->TADR)); MemorySnapShot_Store(&(pMFP->TBDR), sizeof(pMFP->TBDR)); MemorySnapShot_Store(&(pMFP->TCDR), sizeof(pMFP->TCDR)); MemorySnapShot_Store(&(pMFP->TDDR), sizeof(pMFP->TDDR)); MemorySnapShot_Store(&(pMFP->TA_MAINCOUNTER), sizeof(pMFP->TA_MAINCOUNTER)); MemorySnapShot_Store(&(pMFP->TB_MAINCOUNTER), sizeof(pMFP->TB_MAINCOUNTER)); MemorySnapShot_Store(&(pMFP->TC_MAINCOUNTER), sizeof(pMFP->TC_MAINCOUNTER)); MemorySnapShot_Store(&(pMFP->TD_MAINCOUNTER), sizeof(pMFP->TD_MAINCOUNTER)); MemorySnapShot_Store(&(pMFP->TimerAClockCycles), sizeof(pMFP->TimerAClockCycles)); MemorySnapShot_Store(&(pMFP->TimerBClockCycles), sizeof(pMFP->TimerBClockCycles)); MemorySnapShot_Store(&(pMFP->TimerCClockCycles), sizeof(pMFP->TimerCClockCycles)); MemorySnapShot_Store(&(pMFP->TimerDClockCycles), sizeof(pMFP->TimerDClockCycles)); MemorySnapShot_Store(&(pMFP->PatchTimerD_Done), sizeof(pMFP->PatchTimerD_Done)); MemorySnapShot_Store(&(pMFP->PatchTimerD_TDDR_old), sizeof(pMFP->PatchTimerD_TDDR_old)); MemorySnapShot_Store(&(pMFP->Current_Interrupt), sizeof(pMFP->Current_Interrupt)); MemorySnapShot_Store(&(pMFP->IRQ), sizeof(pMFP->IRQ)); MemorySnapShot_Store(&(pMFP->IRQ_CPU), sizeof(pMFP->IRQ_CPU)); MemorySnapShot_Store(&(pMFP->IRQ_Time), sizeof(pMFP->IRQ_Time)); MemorySnapShot_Store(&(pMFP->Pending_Time_Min), sizeof(pMFP->Pending_Time_Min)); MemorySnapShot_Store(&PendingCyclesOver, sizeof(PendingCyclesOver)); for ( i=0 ; i<=MFP_INT_MAX ; i++ ) MemorySnapShot_Store(&(pMFP->Pending_Time[ i ]), sizeof(pMFP->Pending_Time[ i ])); } if ( !bSave ) /* If restoring */ MFP_Init_Pointers ( MFP_Array ); /* Restore pointers */ } /*-----------------------------------------------------------------------*/ /** * Given an MFP interrupt number, return a pointer to the corresponding * registers handling this interrupt, as well as the binary value * to set/clear these registers. * If an input pointer is NULL, we don't return the corresponding register. */ static uint8_t MFP_ConvertIntNumber ( MFP_STRUCT *pMFP , int16_t Interrupt , uint8_t **pMFP_IER , uint8_t **pMFP_IPR , uint8_t **pMFP_ISR , uint8_t **pMFP_IMR ) { uint8_t Bit; if ( Interrupt > 7 ) { Bit = 1 << ( Interrupt - 8 ); if ( pMFP_IER ) *pMFP_IER = &(pMFP->IERA); if ( pMFP_IPR ) *pMFP_IPR = &(pMFP->IPRA); if ( pMFP_ISR ) *pMFP_ISR = &(pMFP->ISRA); if ( pMFP_IMR ) *pMFP_IMR = &(pMFP->IMRA); } else { Bit = 1 << Interrupt; if ( pMFP_IER ) *pMFP_IER = &(pMFP->IERB); if ( pMFP_IPR ) *pMFP_IPR = &(pMFP->IPRB); if ( pMFP_ISR ) *pMFP_ISR = &(pMFP->ISRB); if ( pMFP_IMR ) *pMFP_IMR = &(pMFP->IMRB); } return Bit; } /*-----------------------------------------------------------------------*/ /** * Update the internal CycInt counters to check if some MFP timers expired. * This should be called before accessing the MFP registers for read or write * to ensure MFP events are processed in chronological order. * This should only be called when CPU runs in Cycle Exact mode, because * we need accurate cycles counting when calling CycInt functions during * the processing of an instruction. */ static void MFP_UpdateTimers ( MFP_STRUCT *pMFP , uint64_t Clock ) { //fprintf ( stderr , "mfp update timers clock=%"PRIu64"\n" , Clock ); if ( !CpuRunCycleExact ) return; CycInt_From_Opcode = true; /* TEMP for CYCLES_COUNTER_VIDEO, see cycInt.c */ CycInt_Process_Clock ( Clock ); if ( MFP_UpdateNeeded == true ) MFP_UpdateIRQ ( pMFP , Clock ); CycInt_From_Opcode = false; /* TEMP for CYCLES_COUNTER_VIDEO, see cycInt.c */ } /*-----------------------------------------------------------------------*/ /** * Call the MFP exception associated to the current MFP interrupt 0-15. * When the MFP sets its IRQ signal, it will put the interrupt vector number * on the data bus ; the 68000 will read it during the IACK cycle * and multiply it by 4 to get the address of the exception handler. * The upper 4 bits of the vector number are stored in the VR register 0xfffa17 * (default value is 0x40, which gives exceptions' handlers located at 0x100 in RAM) */ static void MFP_Exception ( MFP_STRUCT *pMFP , int16_t Interrupt ) { unsigned int VecNr; VecNr = ( pMFP->VR & 0xf0 ) + Interrupt; if (LOG_TRACE_LEVEL(TRACE_MFP_EXCEPTION)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s excep int=%d vec=0x%x new_pc=0x%x video_cyc=%d %d@%d\n" , pMFP->NameSuffix, Interrupt, VecNr * 4, STMemory_ReadLong ( VecNr * 4 ), FrameCycles, LineCycles, HblCounterVideo ); } M68000_Exception(EXCEPTION_NR_MFP_DSP, M68000_EXC_SRC_INT_MFP); } /*-----------------------------------------------------------------------*/ /** * Get the value of the MFP IRQ signal as seen from the CPU side. * When MFP_IRQ is changed in the MFP, the new value is visible on the * CPU side after MFP_IRQ_DELAY_TO_CPU. * MFP_IRQ_CPU holds the value seen by the CPU, it's updated with the value * of MFP_IRQ when MFP_IRQ_DELAY_TO_CPU cycles passed. * * When the machine is a TT, we combine the IRQ from the 2 MFPs */ uint8_t MFP_GetIRQ_CPU ( void ) { //fprintf ( stderr , "mfp get irq %d\n" , pMFP_Main->IRQ_CPU ); if ( !Config_IsMachineTT() ) /* Only 1 MFP */ return pMFP_Main->IRQ_CPU; else /* 2nd MFP is only in TT machine */ return pMFP_Main->IRQ_CPU | pMFP_TT->IRQ_CPU; } /*-----------------------------------------------------------------------*/ /** * A change in MFP_IRQ is visible to the CPU only after MFP_IRQ_DELAY_TO_CPU * cycles. This function will update MFP_IRQ_CPU if the delay has expired. * * When the machine is a TT, we update MFP_IRQ_CPU for pMFP_Main and pMFP_TT * * This function is called from the CPU emulation part when SPCFLAG_MFP is set. * * TODO : for now, we check the delay only when MFP_IRQ goes to 1, but this should be * handled too when MFP_IRQ goes to 0 (need to be measured on STF) */ void MFP_DelayIRQ ( void ) { MFP_STRUCT *pMFP; int Nb_MFP; int Nb_Unset; int i; Nb_MFP = 1; /* Only 1 MFP by default */ if ( Config_IsMachineTT() ) /* 2 MFPs for TT */ Nb_MFP = 2; /* Process all the MFPs */ Nb_Unset = 0; for ( i=0 ; iIRQ == 1 ) { if ( CyclesGlobalClockCounter - pMFP->IRQ_Time >= MFP_IRQ_DELAY_TO_CPU ) { pMFP->IRQ_CPU = pMFP->IRQ; Nb_Unset++; } } else /* MFP_IRQ == 0, no delay for now */ { pMFP->IRQ_CPU = pMFP->IRQ; Nb_Unset++; } } /* If we have as many "unset" as the number of MFP, we unset special MFP flag */ if ( Nb_Unset == Nb_MFP ) M68000_UnsetSpecial ( SPCFLAG_MFP ); } /*-----------------------------------------------------------------------*/ /** * Return the vector number associated to the current MFP interrupt. * MFP_ProcessIACK is called 12 cycles after the start of the 68000 exception. * We must call MFP_UpdateIRQ just before the IACK cycles to update * MFP_Current_Interrupt in case a higher MFP interrupt happened * or pending bit was set twice for the same interrupt during those 12 cycles (rare case) * * TT MFP has higher priority than main MFP, so IACK should be checked on TT MFP first if IRQ is set */ int MFP_ProcessIACK ( int OldVecNr ) { MFP_STRUCT *pMFP; uint8_t *pPendingReg; uint8_t *pInServiceReg; uint8_t Bit; int NewVecNr; /* If IRQ is set on TT MFP then we process IACK for TT MFP */ /* Else we process IACK for main MFP */ // TODO : If pMFP->IRQ = 0 after updating then return -1 and do a "spurious interrupt" in the cpu ? if ( Config_IsMachineTT() && pMFP_TT->IRQ ) pMFP = pMFP_TT; else pMFP = pMFP_Main; /* Check if MFP interrupt vector number changed before IACK */ MFP_UpdateIRQ ( pMFP , CyclesGlobalClockCounter ); NewVecNr = ( pMFP->VR & 0xf0 ) + pMFP->Current_Interrupt; /* Print traces if VecNr changed just before IACK */ if ( LOG_TRACE_LEVEL(TRACE_MFP_EXCEPTION) && ( OldVecNr != NewVecNr ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s iack change old_vec=0x%x new_vec=0x%x new_pc=0x%x video_cyc=%d %d@%d\n" , pMFP->NameSuffix, OldVecNr * 4, NewVecNr * 4, STMemory_ReadLong ( NewVecNr * 4 ) , FrameCycles, LineCycles, HblCounterVideo ); } Bit = MFP_ConvertIntNumber ( pMFP , pMFP->Current_Interrupt , NULL , &pPendingReg , &pInServiceReg , NULL ); *pPendingReg &= ~Bit; /* Clear pending bit */ /* Are we in 'auto' interrupt or 'manual' ? */ if ( pMFP->VR & 0x08 ) /* Software End-of-Interrupt (SEI) */ *pInServiceReg |= Bit; /* Set interrupt in service register */ else *pInServiceReg &= ~Bit; /* Clear interrupt in service register */ MFP_UpdateIRQ ( pMFP , CyclesGlobalClockCounter ); return NewVecNr; /* Vector number */ } /*-----------------------------------------------------------------------*/ /** * This function is called from the CPU emulation part when SPCFLAG_MFP is set. * If the MFP's IRQ signal is set, we check that SR allows a level 6 interrupt, * and if so, we call MFP_Exception. * If SR doesn't allow an MFP interrupt, MFP's pending requests will be * processed later when SR allows it. * * Important timing note : when the MFP's IRQ signal is set, it's visible to * the CPU only 4 cycles later. Depending if the signal happens during a CPU * instruction or just before processing a new instruction, this delay will * not always be necessary. * * Instead of using CycInt_AddRelativeInterrupt to simulate this 4 cycles delay, * we use MFP_IRQ_Time to delay the exception processing until 4 cycles have * passed. * * When the machine is a TT, both MFP are daisy chained using IEI and IEO signals * and the TT MFP has higher priority than the main MFP. So we must check TT MFP first. */ bool MFP_ProcessIRQ_All ( void ) { /* 2nd MFP is only in TT machine and has higher priority than main MFP */ if ( Config_IsMachineTT() && MFP_ProcessIRQ ( pMFP_TT ) ) return true; /* IRQ set on TT MFP, stop here */ /* 1st MFP is common to all machines */ return MFP_ProcessIRQ ( pMFP_Main ); } static bool MFP_ProcessIRQ ( MFP_STRUCT *pMFP ) { //fprintf ( stderr , "process irq=%d clock=%"PRIu64" irq_time=%"PRIu64" - ipr %x %x imr %x %x isr %x %x\n" , pMFP->IRQ , CyclesGlobalClockCounter , pMFP->IRQ_Time , pMFP->IPRA , pMFP->IPRB , pMFP->IMRA , pMFP->IMRB , pMFP->ISRA , pMFP->ISRB ); if ( pMFP->IRQ == 1 ) { if ( CyclesGlobalClockCounter - pMFP->IRQ_Time < MFP_IRQ_DELAY_TO_CPU ) /* Is it time to trigger the exception ? */ { return false; /* For now, return without calling an exception (and try again later) */ } if (regs.intmask < 6) { /* The exception is possible ; pending / in service bits will be handled in MFP_ProcessIACK() */ MFP_Exception ( pMFP , pMFP->Current_Interrupt ); return true; } } return false; } /*-----------------------------------------------------------------------*/ /** * Update the MFP IRQ signal for all the MFP */ void MFP_UpdateIRQ_All ( uint64_t Event_Time ) { /* 2nd MFP is only in TT machine */ if ( Config_IsMachineTT() ) MFP_UpdateIRQ ( pMFP_TT , Event_Time ); /* 1st MFP is common to all machines */ MFP_UpdateIRQ ( pMFP_Main , Event_Time ); } /*-----------------------------------------------------------------------*/ /** * Update the MFP IRQ signal when IERx, IPRx, ISRx or IMRx are modified. * We set the special flag SPCFLAG_MFP accordingly (to say if an MFP interrupt * is to be processed) so we only have one compare to call MFP_ProcessIRQ * during the CPU's decode instruction loop. * If MFP_IRQ goes from 0 to 1, we update MFP_IRQ_Time to correctly emulate * the 4 cycle delay before MFP_IRQ is visible to the CPU. * * When MFP_UpdateIRQ() is called after writing to an MFP's register, Event_Time * will be the time of the write cycle. * When MFP_UpdateIRQ is called from the main CPU loop after processing the * internal timers, Event_Time will be 0 and we must use MFP_Pending_Time[ NewInt ]. * This way, MFP_IRQ_Time should always be correct to check the delay in MFP_ProcessIRQ(). */ static void MFP_UpdateIRQ ( MFP_STRUCT *pMFP , uint64_t Event_Time ) { int NewInt = -1; //fprintf ( stderr , "updirq in irq=%d event_time=%"PRIu64" - ipr %x %x imr %x %x isr %x %x - clock=%"PRIu64"\n" , pMFP->IRQ , Event_Time , pMFP->IPRA , pMFP->IPRB , pMFP->IMRA , pMFP->IMRB , pMFP->ISRA , pMFP->ISRB , CyclesGlobalClockCounter ); if ( ( pMFP->IPRA & pMFP->IMRA ) | ( pMFP->IPRB & pMFP->IMRB ) ) { NewInt = MFP_CheckPendingInterrupts ( pMFP ); if ( NewInt >= 0 ) { if ( pMFP->IRQ == 0 ) /* MFP IRQ goes from 0 to 1 */ { if ( Event_Time != 0 ) pMFP->IRQ_Time = Event_Time; else pMFP->IRQ_Time = pMFP->Pending_Time[ NewInt ]; } pMFP->IRQ = 1; pMFP->Current_Interrupt = NewInt; } else pMFP->IRQ = 0; /* Pending interrupts are blocked by in-service interrupts */ } else { pMFP->IRQ = 0; } //fprintf ( stderr , "updirq out irq=%d irq_time=%"PRIu64" newint=%d - ipr %x %x imr %x %x isr %x %x - clock=%"PRIu64"\n" , pMFP->IRQ , pMFP->IRQ_Time , NewInt , pMFP->IPRA , pMFP->IPRB , pMFP->IMRA , pMFP->IMRB , pMFP->ISRA , pMFP->ISRB , CyclesGlobalClockCounter ); M68000_SetSpecial ( SPCFLAG_MFP ); /* CPU part should call MFP_Delay_IRQ() */ /* Update IRQ is done, reset Time_Min and UpdateNeeded */ pMFP->Pending_Time_Min = UINT64_MAX; MFP_UpdateNeeded = false; } /*-----------------------------------------------------------------------*/ /** * Test if interrupt 'Bit' is set in pending and mask register. * Also check that no higher priority interrupt is in service. * Depending on the interrupt, we check either IPRA/IMRA or IPRB/IMRB * @return true if the MFP interrupt request is allowed */ static bool MFP_InterruptRequest ( MFP_STRUCT *pMFP , int Int , uint8_t Bit , uint8_t IPRx , uint8_t IMRx , uint8_t PriorityMaskA , uint8_t PriorityMaskB ) { //fprintf ( stderr , "mfp int req %d %x %x %X %x %x %x %x\n" , Int , Bit , IPRx , IMRx , PriorityMaskA , PriorityMaskB , pMFP->ISRA , pMFP->ISRB ); if ( ( IPRx & IMRx & Bit ) /* Interrupt is pending and not masked */ && ( pMFP->Pending_Time[ Int ] <= pMFP->Pending_Time_Min ) )/* Process pending requests in chronological time */ { /* Are any higher priority interrupts in service ? */ if ( ( ( pMFP->ISRA & PriorityMaskA ) == 0 ) && ( ( pMFP->ISRB & PriorityMaskB ) == 0 ) ) return true; /* No higher int in service */ } return false; } /*-----------------------------------------------------------------------*/ /** * Check if any MFP interrupts can be serviced. * @return MFP interrupt number for the highest interrupt allowed, else return -1. */ static int MFP_CheckPendingInterrupts ( MFP_STRUCT *pMFP ) { if ( pMFP->IPRA & pMFP->IMRA ) /* Check we have non masked pending ints */ { if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP7 , MFP_GPIP7_BIT, pMFP->IPRA, pMFP->IMRA, 0x80, 0x00 ) ) /* Check MFP GPIP7 interrupt (bit 7) */ return MFP_INT_GPIP7; if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP6 , MFP_GPIP6_BIT, pMFP->IPRA, pMFP->IMRA, 0xc0, 0x00 ) ) /* Check MFP GPIP6 interrupt (bit 6) */ return MFP_INT_GPIP6; if ( MFP_InterruptRequest ( pMFP , MFP_INT_TIMER_A , MFP_TIMER_A_BIT, pMFP->IPRA, pMFP->IMRA, 0xe0, 0x00 ) ) /* Check Timer A (bit 5) */ return MFP_INT_TIMER_A; if ( MFP_InterruptRequest ( pMFP , MFP_INT_RCV_BUF_FULL , MFP_RCV_BUF_FULL_BIT, pMFP->IPRA, pMFP->IMRA, 0xf0, 0x00 ) ) /* Check Receive buffer full (bit 4) */ return MFP_INT_RCV_BUF_FULL; if ( MFP_InterruptRequest ( pMFP , MFP_INT_RCV_ERR , MFP_RCV_ERR_BIT, pMFP->IPRA, pMFP->IMRA, 0xf8, 0x00 ) ) /* Check Receive error (bit 3) */ return MFP_INT_RCV_ERR; if ( MFP_InterruptRequest ( pMFP , MFP_INT_TRN_BUF_EMPTY , MFP_TRN_BUF_EMPTY_BIT, pMFP->IPRA, pMFP->IMRA, 0xfc, 0x00 ) ) /* Check Transmit buffer empty (bit 2) */ return MFP_INT_TRN_BUF_EMPTY; if ( MFP_InterruptRequest ( pMFP , MFP_INT_TRN_ERR , MFP_TRN_ERR_BIT, pMFP->IPRA, pMFP->IMRA, 0xfe, 0x00 ) ) /* Check Transmit error empty (bit 1) */ return MFP_INT_TRN_ERR; if ( MFP_InterruptRequest ( pMFP , MFP_INT_TIMER_B , MFP_TIMER_B_BIT, pMFP->IPRA, pMFP->IMRA, 0xff, 0x00 ) ) /* Check Timer B (bit 0) */ return MFP_INT_TIMER_B; } if ( pMFP->IPRB & pMFP->IMRB ) /* Check we have non masked pending ints */ { if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP5 , MFP_GPIP5_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0x80 ) ) /* Check GPIP5 = FDC (bit 7) */ return MFP_INT_GPIP5; if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP4 , MFP_GPIP4_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xc0 ) ) /* Check GPIP4 = ACIA (Keyboard or MIDI) (bit 6) */ return MFP_INT_GPIP4; if ( MFP_InterruptRequest ( pMFP , MFP_INT_TIMER_C , MFP_TIMER_C_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xe0 ) ) /* Check Timer C (bit 5) */ return MFP_INT_TIMER_C; if ( MFP_InterruptRequest ( pMFP , MFP_INT_TIMER_D , MFP_TIMER_D_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xf0 ) ) /* Check Timer D (bit 4) */ return MFP_INT_TIMER_D; if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP3 , MFP_GPIP3_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xf8 ) ) /* Check GPIP3 = GPU/Blitter (bit 3) */ return MFP_INT_GPIP3; if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP2 , MFP_GPIP2_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xfc ) ) /* Check GPIP2 (bit 2) */ return MFP_INT_GPIP2; if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP1 , MFP_GPIP1_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xfe ) ) /* Check (Falcon) Centronics ACK / (ST) RS232 DCD (bit 1) */ return MFP_INT_GPIP1; if ( MFP_InterruptRequest ( pMFP , MFP_INT_GPIP0 , MFP_GPIP0_BIT, pMFP->IPRB, pMFP->IMRB, 0xff, 0xff ) ) /* Check Centronics BUSY (bit 0) */ return MFP_INT_GPIP0; } return -1; /* No pending interrupt */ } /*-----------------------------------------------------------------------*/ /** * If interrupt channel is active, set pending bit so it can be serviced * later. * As internal timers are processed after the current CPU instruction was * emulated, we use Interrupt_Delayed_Cycles to compute the precise time * at which the timer expired (it could be during the previous instruction). * This allows to correctly handle the 4 cycle MFP_IRQ delay in MFP_ProcessIRQ(). * * As we can have several inputs during one CPU instruction, not necessarily * sorted by Interrupt_Delayed_Cycles, we must call MFP_UpdateIRQ() only later * in the main CPU loop, when all inputs were received, to choose the oldest * input's event time. */ void MFP_InputOnChannel ( MFP_STRUCT *pMFP , int Interrupt , int Interrupt_Delayed_Cycles ) { uint8_t *pEnableReg; uint8_t *pPendingReg; uint8_t *pMaskReg; uint8_t Bit; //fprintf ( stderr , "mfp input %d delay %d clock %"PRIu64"\n" , Interrupt , Interrupt_Delayed_Cycles , CyclesGlobalClockCounter ); Bit = MFP_ConvertIntNumber ( pMFP , Interrupt , &pEnableReg , &pPendingReg , NULL , &pMaskReg ); /* Input has occurred on MFP channel, set interrupt pending to request service when able */ if ( *pEnableReg & Bit ) { /* Print traces if pending bits changed just before IACK */ if ( LOG_TRACE_LEVEL(TRACE_MFP_EXCEPTION) && ( CPU_IACK == true ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); if ( *pPendingReg & Bit ) LOG_TRACE_PRINT("mfp%s input, pending set again during iack for int=%d, skip one interrupt video_cyc=%d %d@%d\n" , pMFP->NameSuffix , Interrupt , FrameCycles, LineCycles, HblCounterVideo ); else LOG_TRACE_PRINT("mfp%s input, new pending set during iack for int=%d video_cyc=%d %d@%d\n" , pMFP->NameSuffix , Interrupt , FrameCycles, LineCycles, HblCounterVideo ); } /* Set pending bit and event's time */ *pPendingReg |= Bit; pMFP->Pending_Time[ Interrupt ] = CyclesGlobalClockCounter - Interrupt_Delayed_Cycles; /* Store the time of the most ancient non-masked pending=1 event */ if ( ( *pMaskReg & Bit ) && ( pMFP->Pending_Time[ Interrupt ] < pMFP->Pending_Time_Min ) ) pMFP->Pending_Time_Min = pMFP->Pending_Time[ Interrupt ]; } else *pPendingReg &= ~Bit; /* Clear bit */ MFP_UpdateNeeded = true; /* Tell main CPU loop to call MFP_UpdateIRQ() */ } /*-----------------------------------------------------------------------*/ /** * Update the interrupt status of the GPIP when the GPIP, AER or DDR * registers are changed. * Only lines defined as input in DDR can generate an interrupt. * Each input line is XORed with the corresponding AER bit to choose * if the interrupt should be triggered on 1->0 transition or 0->1. * * NOTE : In most case, only the input line will change, but because input line * and AER are XORed, this means that an interrupt can trigger too * if AER is changed ! ('M' and 'Realtime' are doing bset #0,$fffa03 * then bclr #0,$fffa03) */ static void MFP_GPIP_Update_Interrupt ( MFP_STRUCT *pMFP , uint8_t GPIP_old , uint8_t GPIP_new , uint8_t AER_old , uint8_t AER_new , uint8_t DDR_old , uint8_t DDR_new ) { uint8_t State_old; uint8_t State_new; int Bit; uint8_t BitMask; //fprintf ( stderr , "gpip upd gpip_old=%x gpip_new=%x aer_old=%x aer_new=%x ddr_old=%x ddr_new=%x\n" , GPIP_old, GPIP_new, AER_old, AER_new, DDR_old, DDR_new ); State_old = GPIP_old ^ AER_old; State_new = GPIP_new ^ AER_new; /* For each line, check if it's defined as input in DDR (0=input 1=output) */ /* and if the state is changing (0->1 or 1->0) */ for ( Bit=0 ; Bit<8 ; Bit++ ) { BitMask = 1<0 ; if AER=1, trigger on 0->1 */ /* -> so, we trigger if AER=GPIP_new */ if ( ( GPIP_new & BitMask ) == ( AER_new & BitMask ) ) { //fprintf ( stderr , "gpip int bit=%d %d->%d\n" , Bit , (State_old & BitMask)>>Bit , (State_new & BitMask)>>Bit ); MFP_InputOnChannel ( pMFP , MFP_GPIP_LineToIntNumber[ Bit ] , 0 ); } } } } /*-----------------------------------------------------------------------*/ /** * Change the state of one of the external lines connected to the GPIP. * Only lines configured as input in DDR can be changed. * If the new state is different from the previous one, we update GPIP and * we request an interrupt on the corresponding channel. */ void MFP_GPIP_Set_Line_Input ( MFP_STRUCT *pMFP , uint8_t LineNr , uint8_t Bit ) { uint8_t Mask; uint8_t GPIP_old; Mask = 1 << LineNr; //fprintf ( stderr , "gpip set0 mask=%x bit=%d ddr=%x gpip=%x\n", Mask, Bit, pMFP->DDR, pMFP->GPIP ); /* Special case when changing bit 7 of the main MFP : depending on the machine type, */ /* this can be a combination of several signals. So we override Bit with its new value */ if ( ( pMFP == pMFP_Main ) && ( LineNr == MFP_GPIP_LINE7 ) ) Bit = MFP_Main_Compute_GPIP7 (); /* Special case when changing bit 4 of the main MFP : this line is connected */ /* to the 2 ACIA's IRQ lines at the same time */ if ( ( pMFP == pMFP_Main ) && ( LineNr == MFP_GPIP_LINE_ACIA ) ) Bit = MFP_Main_Compute_GPIP_LINE_ACIA (); /* Check that corresponding line is defined as input in DDR (0=input 1=output) */ /* and that the bit is changing */ if ( ( ( pMFP->DDR & Mask ) == 0 ) && ( ( pMFP->GPIP & Mask ) != ( Bit << LineNr ) ) ) { GPIP_old = pMFP->GPIP; if ( Bit ) { pMFP->GPIP |= Mask; } else { pMFP->GPIP &= ~Mask; } /* Update possible interrupts after changing GPIP */ MFP_GPIP_Update_Interrupt ( pMFP , GPIP_old , pMFP->GPIP , pMFP->AER , pMFP->AER , pMFP->DDR , pMFP->DDR ); } //fprintf ( stderr , "gpip set gpip_old=%x gpip_new=%x\n" , GPIP_old , pMFP->GPIP ); } /*-----------------------------------------------------------------------*/ /** * Change input line for Timer A (TAI) and generate an interrupt when in event count mode * and counter reaches 1. * TAI is associated to AER GPIP4 */ void MFP_TimerA_Set_Line_Input ( MFP_STRUCT *pMFP , uint8_t Bit ) { uint8_t AER_bit; //fprintf ( stderr , "MFP_TimerA_Set_Line_Input bit=%d TAI=%d TACR=%d AER=%d\n" , Bit , pMFP->TAI, pMFP->TACR, ( pMFP->AER >> 4 ) & 1 ); if ( pMFP->TAI == Bit ) return; /* No change */ pMFP->TAI = Bit; /* Update TAI value */ if ( pMFP->TACR != 0x08 ) /* Not in event count mode */ return; /* Do nothing */ AER_bit = ( pMFP->AER >> 4 ) & 1; /* TAI is associated to AER GPIP4 */ if ( Bit != AER_bit ) /* See MFP_GPIP_Update_Interrupt : we detect a transition */ return; /* when AER=Bit */ if ( pMFP->TA_MAINCOUNTER == 1) /* Timer expired? If so, generate interrupt */ { pMFP->TA_MAINCOUNTER = pMFP->TADR; /* Reload timer from data register */ /* Acknowledge in MFP circuit, pass bit,enable,pending */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_A , 0 ); } else { pMFP->TA_MAINCOUNTER--; /* Decrement timer main counter */ /* As TA_MAINCOUNTER is uint8_t, when we decrement TA_MAINCOUNTER=0 */ /* we go to TA_MAINCOUNTER=255, which is the wanted behaviour because */ /* data reg = 0 means 256 in fact. So, the next 2 lines are redundant. */ /* if ( TA_MAINCOUNTER < 0 ) TA_MAINCOUNTER = 255; */ } } /*-----------------------------------------------------------------------*/ /** * Generate Timer A Interrupt when in Event Count mode * TODO : this should be replaced by using MFP_TimerA_Set_Line_Input * to take AER into account */ void MFP_TimerA_EventCount( MFP_STRUCT *pMFP ) { if ( pMFP->TACR != 0x08 ) /* Not in event count mode */ return; /* Do nothing */ if ( pMFP->TA_MAINCOUNTER == 1) /* Timer expired? If so, generate interrupt */ { pMFP->TA_MAINCOUNTER = pMFP->TADR; /* Reload timer from data register */ /* Acknowledge in MFP circuit, pass bit,enable,pending */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_A , 0 ); } else { pMFP->TA_MAINCOUNTER--; /* Decrement timer main counter */ /* As TA_MAINCOUNTER is uint8_t, when we decrement TA_MAINCOUNTER=0 */ /* we go to TA_MAINCOUNTER=255, which is the wanted behaviour because */ /* data reg = 0 means 256 in fact. So, the next 2 lines are redundant. */ /* if ( TA_MAINCOUNTER < 0 ) TA_MAINCOUNTER = 255; */ } } /*-----------------------------------------------------------------------*/ /** * Generate Timer B Interrupt when in Event Count mode */ void MFP_TimerB_EventCount ( MFP_STRUCT *pMFP , int Delayed_Cycles ) { if ( pMFP->TBCR != 0x08 ) /* Not in event count mode */ return; /* Do nothing */ /* Video DE signal is connected to Timer B on the main MFP and also on the TT MFP */ LOG_TRACE(TRACE_VIDEO_HBL , "mfp%s/video timer B new event count %d, delay=%d\n" , pMFP->NameSuffix , pMFP->TB_MAINCOUNTER-1 , Delayed_Cycles ); if ( pMFP->TB_MAINCOUNTER == 1 ) /* Timer expired? If so, generate interrupt */ { pMFP->TB_MAINCOUNTER = pMFP->TBDR; /* Reload timer from data register */ /* Acknowledge in MFP circuit, pass bit,enable,pending */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_B , Delayed_Cycles ); } else { pMFP->TB_MAINCOUNTER--; /* Decrement timer main counter */ /* As TB_MAINCOUNTER is uint8_t, when we decrement TB_MAINCOUNTER=0 */ /* we go to TB_MAINCOUNTER=255, which is the wanted behaviour because */ /* data reg = 0 means 256 in fact. So, the next 2 lines are redundant. */ /* if ( TB_MAINCOUNTER < 0 ) TB_MAINCOUNTER = 255; */ } } /*-----------------------------------------------------------------------*/ /** * Return the Timer C's frequency on the TT MFP * This is used as "TCCLK" input signal in the Atari TT to provide the RTxCB clock * on the SCC. * * Return the frequency in MHz or 0 if Timer C is disabled */ uint32_t MFP_TT_TimerC_Get_Freq ( void ) { uint32_t ClockCycles; ClockCycles = pMFP_TT->TimerCClockCycles; if ( ClockCycles == 0 ) return 0; else return MachineClocks.MFP_Timer_Freq / ClockCycles; } /*-----------------------------------------------------------------------*/ /** * Start Timer A or B - EventCount mode is done in HBL handler to time correctly */ static uint32_t MFP_StartTimer_AB ( MFP_STRUCT *pMFP , uint8_t TimerControl, uint16_t TimerData, interrupt_id Handler, bool bFirstTimer) { uint32_t TimerClockCycles = 0; /* When in pulse width mode, handle as in delay mode */ /* (this is not completely correct, as we should also handle GPIO 3/4 in pulse mode) */ if ( TimerControl > 8 ) { if (LOG_TRACE_LEVEL(TRACE_MFP_START)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s start AB handler=%d data=%d ctrl=%d timer_cyc=%d pending_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d pulse mode->delay mode\n", pMFP->NameSuffix , Handler, TimerData, TimerControl, TimerClockCycles, PendingCyclesOver, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } TimerControl &= 0x07; /* clear bit 3, pulse width mode -> delay mode */ } /* Is timer in delay mode (ctrl = 0-7) ? */ /* If we are in event-count mode (ctrl = 8) ignore this (done on HBL) */ if (TimerControl <= 7) { /* Find number of CPU cycles for when timer is due (include preset * and counter). As timer occurs very often we multiply by counter * to speed up emulator */ if (TimerData == 0) /* Data=0 is actually Data=256 */ TimerData = 256; TimerClockCycles = MFP_REG_TO_CYCLES ( TimerData, TimerControl ); /* [NP] FIXME : Temporary fix for Lethal Xcess calibration routine to remove top border : */ /* the routine expects that the delay is not always stable, there must be a small */ /* jitter due to the clock difference between CPU and MFP */ if ( ( M68000_GetPC() == 0x14d72 ) && ( STMemory_ReadLong ( 0x14d6c ) == 0x11faff75 ) ) { // fprintf ( stderr , "mfp add jitter %d\n" , TimerClockCycles ); TimerClockCycles += Hatari_rand()%5-2; /* add jitter for wod2 */ } if (LOG_TRACE_LEVEL(TRACE_MFP_START)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s start AB handler=%d data=%d ctrl=%d timer_cyc=%d pending_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d first=%s\n", pMFP->NameSuffix , Handler, TimerData, TimerControl, TimerClockCycles, PendingCyclesOver, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles, bFirstTimer?"true":"false"); } /* And add to our internal interrupt list, if timer cycles is zero * then timer is stopped */ CycInt_RemovePendingInterrupt(Handler); if (TimerClockCycles) { /* Start timer from now? If not continue timer using PendingCycleOver */ if (bFirstTimer) { CycInt_AddRelativeInterrupt(TimerClockCycles, INT_MFP_CYCLE, Handler); } else { int64_t TimerClockCyclesInternal = INT_CONVERT_TO_INTERNAL ( (int64_t)TimerClockCycles , INT_MFP_CYCLE ); /* In case we miss more than one int, we must correct the delay for the next one */ if ( (int64_t)PendingCyclesOver > TimerClockCyclesInternal ) PendingCyclesOver = PendingCyclesOver % TimerClockCyclesInternal; CycInt_AddRelativeInterruptWithOffset(TimerClockCycles, INT_MFP_CYCLE, Handler, -PendingCyclesOver); } } else /* Ctrl was 0 -> timer is stopped */ { /* do nothing, only print some traces */ if (LOG_TRACE_LEVEL(TRACE_MFP_START)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s stop AB handler=%d data=%d ctrl=%d timer_cyc=%d pending_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d first=%s\n", pMFP->NameSuffix , Handler, TimerData, TimerControl, TimerClockCycles, PendingCyclesOver, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles, bFirstTimer?"true":"false"); } } } else if (TimerControl == 8 ) /* event count mode */ { /* Make sure no outstanding interrupts in list if channel is disabled */ CycInt_RemovePendingInterrupt(Handler); if ( ( Handler == INTERRUPT_MFP_MAIN_TIMERB ) /* we're starting timer B event count mode */ || ( Handler == INTERRUPT_MFP_TT_TIMERB ) ) { /* Store start cycle for handling interrupt in video.c */ TimerBEventCountCycleStart = Video_GetCyclesSinceVbl_OnWriteAccess(); } if (LOG_TRACE_LEVEL(TRACE_MFP_START)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s start AB handler=%d data=%d ctrl=%d timer_cyc=%d pending_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d first=%s\n", pMFP->NameSuffix , Handler, TimerData, TimerControl, TimerClockCycles, PendingCyclesOver, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles, bFirstTimer?"true":"false"); } } return TimerClockCycles; } /*-----------------------------------------------------------------------*/ /** * Start Timer C or D */ static uint32_t MFP_StartTimer_CD ( MFP_STRUCT *pMFP , uint8_t TimerControl, uint16_t TimerData, interrupt_id Handler, bool bFirstTimer) { uint32_t TimerClockCycles = 0; /* Is timer in delay mode ? */ if ((TimerControl&0x7) != 0) { /* Find number of cycles for when timer is due (include preset and * counter). As timer occurs very often we multiply by counter to * speed up emulator */ if (TimerData == 0) /* Data=0 is actually Data=256 */ TimerData = 256; TimerClockCycles = MFP_REG_TO_CYCLES ( TimerData, TimerControl ); if ( LOG_TRACE_LEVEL( TRACE_MFP_START ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s start CD handler=%d data=%d ctrl=%d timer_cyc=%d pending_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d first=%s\n" , pMFP->NameSuffix , Handler, TimerData, TimerControl, TimerClockCycles, PendingCyclesOver, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles, bFirstTimer?"true":"false" ); } /* And add to our internal interrupt list, if timer cycles is zero * then timer is stopped */ CycInt_RemovePendingInterrupt(Handler); if (TimerClockCycles) { /* Start timer from now? If not continue timer using PendingCycleOver */ if (bFirstTimer) { CycInt_AddRelativeInterrupt(TimerClockCycles, INT_MFP_CYCLE, Handler); } else { int64_t TimerClockCyclesInternal = INT_CONVERT_TO_INTERNAL ( (int64_t)TimerClockCycles , INT_MFP_CYCLE ); /* In case we miss more than one int, we must correct the delay for the next one */ if ( (int64_t)PendingCyclesOver > TimerClockCyclesInternal ) PendingCyclesOver = PendingCyclesOver % TimerClockCyclesInternal; CycInt_AddRelativeInterruptWithOffset(TimerClockCycles, INT_MFP_CYCLE, Handler, -PendingCyclesOver); } } } else /* timer control is 0 */ { if ( LOG_TRACE_LEVEL( TRACE_MFP_START ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s stop CD handler=%d data=%d ctrl=%d timer_cyc=%d pending_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d first=%s\n" , pMFP->NameSuffix , Handler, TimerData, TimerControl, TimerClockCycles, PendingCyclesOver, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles, bFirstTimer?"true":"false" ); } /* Make sure no outstanding interrupts in list if channel is disabled */ CycInt_RemovePendingInterrupt(Handler); } /* If Timer C is changed on the MFP TT we must forward this to the TT SCC */ /* because Timer C output is connected to RTxCB input on the SCC */ if ( Handler == INTERRUPT_MFP_TT_TIMERC ) SCC_Update_TimerC_Clock (); return TimerClockCycles; } /*-----------------------------------------------------------------------*/ /** * Read Timer A or B - If in EventCount MainCounter already has correct value */ static uint8_t MFP_ReadTimer_AB ( MFP_STRUCT *pMFP , uint8_t TimerControl, uint8_t MainCounter, uint32_t TimerCycles, interrupt_id Handler, bool TimerIsStopping) { /* Find TimerAB count, if no interrupt or not in delay mode assume * in Event Count mode so already up-to-date as kept by HBL */ if (CycInt_InterruptActive(Handler) && (TimerControl > 0) && (TimerControl <= 7)) { /* Find cycles passed since last interrupt */ MainCounter = MFP_CYCLE_TO_REG ( CycInt_FindCyclesRemaining ( Handler, INT_MFP_CYCLE ), TimerControl ); //fprintf ( stderr , "mfp read AB count %d int_cyc=%d\n" , MainCounter , CycInt_FindCyclesRemaining ( Handler, INT_MFP_CYCLE ) ); } /* If the timer is stopped when the internal mfp data reg is already < 1 */ /* then the data reg will be the current TADR/TBDR next time the timer will be restarted */ /* if no write is made to the data reg before */ if ( TimerIsStopping ) { if ( CycInt_FindCyclesRemaining ( Handler, INT_MFP_CYCLE ) < MFP_REG_TO_CYCLES ( 1 , TimerControl ) ) { if ( ( Handler == INTERRUPT_MFP_MAIN_TIMERA ) || ( Handler == INTERRUPT_MFP_TT_TIMERA ) ) MainCounter = pMFP->TADR; else MainCounter = pMFP->TBDR; LOG_TRACE(TRACE_MFP_READ , "mfp%s read AB handler=%d stopping timer while data reg between 1 and 0 : forcing data to %d\n" , pMFP->NameSuffix , Handler , MainCounter ); } } if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read AB handler=%d data=%d ctrl=%d timer_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d\n" , pMFP->NameSuffix , Handler, MainCounter, TimerControl, TimerCycles, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } return MainCounter; } /*-----------------------------------------------------------------------*/ /** * Read Timer C or D */ static uint8_t MFP_ReadTimer_CD ( MFP_STRUCT *pMFP , uint8_t TimerControl, uint8_t TimerData, uint8_t MainCounter, uint32_t TimerCycles, interrupt_id Handler, bool TimerIsStopping) { /* Find TimerCD count. If timer is off, MainCounter already contains the latest value */ if (CycInt_InterruptActive(Handler)) { /* Find cycles passed since last interrupt */ MainCounter = MFP_CYCLE_TO_REG ( CycInt_FindCyclesRemaining ( Handler, INT_MFP_CYCLE ), TimerControl ); //fprintf ( stderr , "mfp read CD count %d int_cyc=%d\n" , MainCounter , CycInt_FindCyclesRemaining ( Handler, INT_MFP_CYCLE ) ); } /* If the timer is stopped when the internal mfp data reg is already < 1 */ /* then the data reg will be the current TCDR/TDDR next time the timer will be restarted */ /* if no write is made to the data reg before */ if ( TimerIsStopping ) { if ( CycInt_FindCyclesRemaining ( Handler, INT_MFP_CYCLE ) < MFP_REG_TO_CYCLES ( 1 , TimerControl ) ) { if ( ( Handler == INTERRUPT_MFP_MAIN_TIMERC ) || ( Handler == INTERRUPT_MFP_TT_TIMERC ) ) MainCounter = pMFP->TCDR; else MainCounter = pMFP->TDDR; LOG_TRACE(TRACE_MFP_READ , "mfp%s read CD handler=%d stopping timer while data reg between 1 and 0 : forcing data to %d\n" , pMFP->NameSuffix , Handler , MainCounter ); } } if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read CD handler=%d data=%d ctrl=%d timer_cyc=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d\n" , pMFP->NameSuffix , Handler, MainCounter, TimerControl, TimerCycles, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } return MainCounter; } /*-----------------------------------------------------------------------*/ /** * Start Timer A */ static void MFP_StartTimerA ( MFP_STRUCT *pMFP ) { pMFP->TimerAClockCycles = MFP_StartTimer_AB ( pMFP , pMFP->TACR , pMFP->TA_MAINCOUNTER , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERA : INTERRUPT_MFP_TT_TIMERA , true ); } /*-----------------------------------------------------------------------*/ /** * Read Timer A */ static void MFP_ReadTimerA ( MFP_STRUCT *pMFP , bool TimerIsStopping) { pMFP->TA_MAINCOUNTER = MFP_ReadTimer_AB ( pMFP , pMFP->TACR , pMFP->TA_MAINCOUNTER , pMFP->TimerAClockCycles , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERA : INTERRUPT_MFP_TT_TIMERA , TimerIsStopping); } /*-----------------------------------------------------------------------*/ /** * Start Timer B * (This does not start the EventCount mode time as this is taken care * of by the HBL) */ static void MFP_StartTimerB ( MFP_STRUCT *pMFP ) { pMFP->TimerBClockCycles = MFP_StartTimer_AB ( pMFP , pMFP->TBCR , pMFP->TB_MAINCOUNTER , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERB : INTERRUPT_MFP_TT_TIMERB , true ); } /*-----------------------------------------------------------------------*/ /** * Read Timer B */ static void MFP_ReadTimerB ( MFP_STRUCT *pMFP , bool TimerIsStopping ) { pMFP->TB_MAINCOUNTER = MFP_ReadTimer_AB ( pMFP , pMFP->TBCR , pMFP->TB_MAINCOUNTER , pMFP->TimerBClockCycles , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERB : INTERRUPT_MFP_TT_TIMERB , TimerIsStopping); } /*-----------------------------------------------------------------------*/ /** * Start Timer C */ static void MFP_StartTimerC ( MFP_STRUCT *pMFP ) { pMFP->TimerCClockCycles = MFP_StartTimer_CD ( pMFP , (pMFP->TCDCR>>4)&7 , pMFP->TC_MAINCOUNTER , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERC : INTERRUPT_MFP_TT_TIMERC , true ); } /*-----------------------------------------------------------------------*/ /** * Read Timer C */ static void MFP_ReadTimerC ( MFP_STRUCT *pMFP , bool TimerIsStopping ) { pMFP->TC_MAINCOUNTER = MFP_ReadTimer_CD ( pMFP , (pMFP->TCDCR>>4)&7 , pMFP->TCDR , pMFP->TC_MAINCOUNTER , pMFP->TimerCClockCycles , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERC : INTERRUPT_MFP_TT_TIMERC , TimerIsStopping); } /*-----------------------------------------------------------------------*/ /** * Start Timer D */ static void MFP_StartTimerD ( MFP_STRUCT *pMFP ) { pMFP->TimerDClockCycles = MFP_StartTimer_CD ( pMFP , pMFP->TCDCR&7 , pMFP->TD_MAINCOUNTER , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERD : INTERRUPT_MFP_TT_TIMERD , true ); } /*-----------------------------------------------------------------------*/ /** * Read Timer D */ static void MFP_ReadTimerD ( MFP_STRUCT *pMFP , bool TimerIsStopping ) { pMFP->TD_MAINCOUNTER = MFP_ReadTimer_CD ( pMFP , pMFP->TCDCR&7 , pMFP->TDDR , pMFP->TD_MAINCOUNTER , pMFP->TimerDClockCycles , pMFP == pMFP_Main ? INTERRUPT_MFP_MAIN_TIMERD : INTERRUPT_MFP_TT_TIMERD , TimerIsStopping); } /*-----------------------------------------------------------------------*/ /** * Handle Timer A Interrupt */ static void MFP_InterruptHandler_TimerA ( MFP_STRUCT *pMFP , interrupt_id Handler ) { /* Number of internal cycles we went over for this timer ( <= 0 ), * used when timer expires and needs to be restarted */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Acknowledge in MFP circuit, pass bit,enable,pending */ if ( ( pMFP->TACR & 0xf ) != 0 ) /* Is timer OK? */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_A , INT_CONVERT_FROM_INTERNAL ( PendingCyclesOver , INT_CPU_CYCLE ) ); /* Start next interrupt, if need one - from current cycle count */ pMFP->TimerAClockCycles = MFP_StartTimer_AB ( pMFP , pMFP->TACR , pMFP->TADR , Handler , false ); } void MFP_Main_InterruptHandler_TimerA ( void ) { MFP_InterruptHandler_TimerA ( pMFP_Main , INTERRUPT_MFP_MAIN_TIMERA ); } void MFP_TT_InterruptHandler_TimerA ( void ) { MFP_InterruptHandler_TimerA ( pMFP_TT , INTERRUPT_MFP_TT_TIMERA ); } /*-----------------------------------------------------------------------*/ /** * Handle Timer B Interrupt */ static void MFP_InterruptHandler_TimerB ( MFP_STRUCT *pMFP , interrupt_id Handler ) { /* Number of internal cycles we went over for this timer ( <= 0 ), * used when timer expires and needs to be restarted */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Acknowledge in MFP circuit, pass bit, enable, pending */ if ( ( pMFP->TBCR & 0xf ) != 0 ) /* Is timer OK? */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_B , INT_CONVERT_FROM_INTERNAL ( PendingCyclesOver , INT_CPU_CYCLE ) ); /* Start next interrupt, if need one - from current cycle count */ pMFP->TimerBClockCycles = MFP_StartTimer_AB ( pMFP , pMFP->TBCR , pMFP->TBDR , Handler , false ); } void MFP_Main_InterruptHandler_TimerB ( void ) { MFP_InterruptHandler_TimerB ( pMFP_Main , INTERRUPT_MFP_MAIN_TIMERB ); } void MFP_TT_InterruptHandler_TimerB ( void ) { MFP_InterruptHandler_TimerB ( pMFP_TT , INTERRUPT_MFP_TT_TIMERB ); } /*-----------------------------------------------------------------------*/ /** * Handle Timer C Interrupt */ static void MFP_InterruptHandler_TimerC ( MFP_STRUCT *pMFP , interrupt_id Handler ) { /* Number of internal cycles we went over for this timer ( <= 0 ), * used when timer expires and needs to be restarted */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Acknowledge in MFP circuit, pass bit, enable, pending */ if ( ( pMFP->TCDCR & 0x70 ) != 0 ) /* Is timer OK? */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_C , INT_CONVERT_FROM_INTERNAL ( PendingCyclesOver , INT_CPU_CYCLE ) ); /* Start next interrupt, if need one - from current cycle count */ pMFP->TimerCClockCycles = MFP_StartTimer_CD ( pMFP , (pMFP->TCDCR>>4)&7 , pMFP->TCDR , Handler , false ); } void MFP_Main_InterruptHandler_TimerC ( void ) { MFP_InterruptHandler_TimerC ( pMFP_Main , INTERRUPT_MFP_MAIN_TIMERC ); } void MFP_TT_InterruptHandler_TimerC ( void ) { MFP_InterruptHandler_TimerC ( pMFP_TT , INTERRUPT_MFP_TT_TIMERC ); } /*-----------------------------------------------------------------------*/ /** * Handle Timer D Interrupt */ static void MFP_InterruptHandler_TimerD ( MFP_STRUCT *pMFP , interrupt_id Handler ) { /* Number of internal cycles we went over for this timer ( <= 0 ), * used when timer expires and needs to be restarted */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Acknowledge in MFP circuit, pass bit, enable, pending */ if ( ( pMFP->TCDCR & 0x07 ) != 0 ) /* Is timer OK? */ MFP_InputOnChannel ( pMFP , MFP_INT_TIMER_D , INT_CONVERT_FROM_INTERNAL ( PendingCyclesOver , INT_CPU_CYCLE ) ); /* Start next interrupt, if need one - from current cycle count */ pMFP->TimerDClockCycles = MFP_StartTimer_CD ( pMFP , pMFP->TCDCR&7 , pMFP->TDDR , Handler , false ); } void MFP_Main_InterruptHandler_TimerD ( void ) { MFP_InterruptHandler_TimerD ( pMFP_Main , INTERRUPT_MFP_MAIN_TIMERD ); RS232_Update(); } void MFP_TT_InterruptHandler_TimerD ( void ) { MFP_InterruptHandler_TimerD ( pMFP_TT , INTERRUPT_MFP_TT_TIMERD ); } /*-----------------------------------------------------------------------*/ /** * Handle read from GPIP register (0xfffa01 or 0xfffa81) * We call a different function depending on the MFP (Main or TT) as all * the bits are specific */ void MFP_GPIP_ReadByte ( void ) { if ( IoAccessCurrentAddress == 0xfffa01 ) MFP_GPIP_ReadByte_Main ( pMFP_Main ); else MFP_GPIP_ReadByte_TT ( pMFP_TT ); } /*-----------------------------------------------------------------------*/ /** * Compute value of GPIP bit 7 for main MFP. * This bit can be a combination of the monochrome monitor signal and * the dma sound status (depending on the machine type) */ uint8_t MFP_Main_Compute_GPIP7 ( void ) { uint8_t Bit; if (Config_IsMachineFalcon()) { if (Crossbar_Get_SNDINT_Line()) Bit = 1; else Bit = 0; /* Sparrow / TOS 2.07 still use monitor detection via GPIP7 */ if (TosVersion == 0x207 && !Video_Get_MONO_Line()) Bit ^= 1; } else { if (!Video_Get_MONO_Line()) Bit = 1; /* Color monitor : set bit 7 */ else Bit = 0; /* Monochrome monitor : clear bit 7 */ /* In case of STE/TT, bit 7 is XORed with DMA sound XSINT signal */ if ( ( Config_IsMachineSTE() || Config_IsMachineTT() ) && DmaSnd_Get_XSINT_Line() ) Bit ^= 1; } return Bit; } /*-----------------------------------------------------------------------*/ /** * Compute value of GPIP bit 4 for main MFP. * This bit is a OR between the 2 ACIA's IRQ for IKBD and Midi * NOTE : signal is active low on ACIA, IRQ is set when line's value is 0 */ uint8_t MFP_Main_Compute_GPIP_LINE_ACIA ( void ) { uint8_t Bit; /* If any of the ACIA has IRQ set, set IRQ on GPIP 4 */ if ( ( pACIA_IKBD->Get_Line_IRQ ( pACIA_IKBD ) == 0 ) || ( pACIA_MIDI->Get_Line_IRQ ( pACIA_MIDI ) == 0 ) ) Bit = 0; /* IRQ ON */ /* If both ACIA have IRQ clear */ else Bit = 1; /* IRQ OFF */ return Bit; } /*-----------------------------------------------------------------------*/ /** * Handle read from main MFP GPIP pins register (0xfffa01). * * - Bit 0 is the BUSY signal of the printer port, it is SET if no printer * is connected or on BUSY. Therefore we should assume it to be 0 in Hatari * when a printer is emulated. * - Bit 1 is used for RS232: DCD (inverted, 0=DCD ON) * - Bit 2 is used for RS232: CTS (inverted, 0=CTS ON) * - Bit 3 is used by the blitter (busy/idle state) * - Bit 4 is used by the ACIAs (keyboard and midi) * - Bit 5 is used by the FDC / HDC * - Bit 6 is used for RS232: RI (inverted, O=RI ON) * - Bit 7 is monochrome monitor detection signal and/or dma sound. On STE and TT it is * also XORed with the DMA sound play bit. On Falcon it is only the DMA sound play bit * * When reading GPIP, output lines (DDR=1) should return the last value that was written, * only input lines (DDR=0) should be updated. */ void MFP_GPIP_ReadByte_Main ( MFP_STRUCT *pMFP ) { uint8_t gpip_new; uint8_t dcd,cts; M68000_WaitState(4); /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); /* Get value of DCD/CTS signals in case RS232 emulation is enabled */ RS232_Get_DCD_CTS ( &dcd , &cts ); gpip_new = pMFP->GPIP; /* Bit 7 */ if ( MFP_Main_Compute_GPIP7() ) gpip_new |= 0x80; /* set bit 7 */ else gpip_new &= ~0x80; /* clear bit 7 */ /* Bit 4 */ if ( MFP_Main_Compute_GPIP_LINE_ACIA() ) gpip_new |= 0x10; /* set bit 4 */ else gpip_new &= ~0x10; /* clear bit 4 */ /* Bit 2 : CTS (inverted, 0=ON) */ if ( !cts ) gpip_new |= 0x04; /* set bit 2 */ else gpip_new &= ~0x04; /* clear bit 2 */ /* Bit 1 : DCD (inverted, 0=ON) */ if ( !dcd ) gpip_new |= 0x02; /* set bit 1 */ else gpip_new &= ~0x02; /* clear bit 1 */ /* Bit 0 */ if (ConfigureParams.Printer.bEnablePrinting) { /* Signal that printer is not busy */ gpip_new &= ~1; } else { gpip_new |= 1; /* Printer BUSY bit is also used by parallel port joystick adapters as fire button */ if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED) { /* Fire pressed? */ if (Joy_GetStickData(JOYID_PARPORT1) & 0x80) gpip_new &= ~1; } } gpip_new &= ~pMFP->DDR; /* New input bits */ pMFP->GPIP = ( pMFP->GPIP & pMFP->DDR ) | gpip_new; /* Keep output bits unchanged and update input bits */ IoMem[IoAccessCurrentAddress] = pMFP->GPIP; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read gpip %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from TT MFP GPIP pins register (0xfffa81) * * * When reading GPIP, output lines (DDR=1) should return the last value that was written, * only input lines (DDR=0) should be updated. */ void MFP_GPIP_ReadByte_TT ( MFP_STRUCT *pMFP ) { uint8_t gpip_new; M68000_WaitState(4); /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); /* TODO : handle all bits, bit 7 is scsi, bit 4 is DC signal, other bits default to 1 for now */ gpip_new = pMFP->GPIP; gpip_new |= 0x6f; /* force bits 0-3 and 5-6 to 1 */ gpip_new &= ~pMFP->DDR; /* New input bits */ pMFP->GPIP = ( pMFP->GPIP & pMFP->DDR ) | gpip_new; /* Keep output bits unchanged and update input bits */ IoMem[IoAccessCurrentAddress] = pMFP->GPIP; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read gpip %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Active Edge Register AER (0xfffa03 or 0xfffa83) */ void MFP_ActiveEdge_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa03 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->AER; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read aer %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Data Direction Register DDR (0xfffa05 or 0xfffa85) */ void MFP_DataDirection_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa05 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->DDR; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read ddr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt Enable Register A IERA (0xfffa07 or 0xfffa87) */ void MFP_EnableA_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa07 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->IERA; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read iera %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt Enable Register B IERB (0xfffa09 or 0xfffa89) */ void MFP_EnableB_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa09 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->IERB; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read ierb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt Pending Register A IPRA (0xfffa0b or 0xfffa8b) */ void MFP_PendingA_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa0b ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->IPRA; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read ipra %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt Pending Register B IPRB (0xfffa0d or 0xfffa8d) */ void MFP_PendingB_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa0d ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->IPRB; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read iprb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt In Service Register A ISRA (0xfffa0f or 0xfffa8f) */ void MFP_InServiceA_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa0f ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->ISRA; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read isra %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt In Service Register B ISRB (0xfffa11 or 0xfffa91) */ void MFP_InServiceB_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa11 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->ISRB; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read isrb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt Mask Register A IMRA (0xfffa13 or 0xfffa93) */ void MFP_MaskA_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa13 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->IMRA; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read imra %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Interrupt Mask Register B IMRB (0xfffa15 or 0xfffa95) */ void MFP_MaskB_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa15 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->IMRB; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read imrb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from MFP Vector Register VR (0xfffa17 or 0xfffa97) */ void MFP_VectorReg_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa17 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->VR; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read vr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer A Control Register TACR (0xfffa19 or 0xfffa99) */ void MFP_TimerACtrl_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa19 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->TACR; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tacr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer B Control Register TBCR (0xfffa1b or 0Xfffa9b) */ void MFP_TimerBCtrl_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa1b ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->TBCR; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tbcr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer C/D Control Register TCDCR (0xfffa1d or 0xfffa9d) */ void MFP_TimerCDCtrl_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa1d ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); IoMem[IoAccessCurrentAddress] = pMFP->TCDCR; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tcdcr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer A Data Egister TADR (0xfffa1f or 0xfffa9f) */ void MFP_TimerAData_ReadByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa1f ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( pMFP->TACR != 8 ) /* Is event count? Need to update counter */ MFP_ReadTimerA ( pMFP , false ); /* Store result in 'TA_MAINCOUNTER' */ IoMem[IoAccessCurrentAddress] = pMFP->TA_MAINCOUNTER; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tadr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer B Data Register TBDR (0xfffa21 or 0xfffaa1) */ void MFP_TimerBData_ReadByte(void) { MFP_STRUCT *pMFP; uint8_t TB_count; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa21 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); /* Is it event count mode or not? */ if ( pMFP->TBCR != 8 ) { /* Not event count mode, so handle as normal timer * and store result in 'TB_MAINCOUNTER' */ MFP_ReadTimerB ( pMFP , false ); } else /* Video DE signal is connected to Timer B on both MFPs */ { if (bUseVDIRes) { /* HBLs are disabled in VDI mode, but TOS expects to read a 1 */ pMFP->TB_MAINCOUNTER = 1; } /* Special case when reading $fffa21, we need to test if the current read instruction */ /* overlaps the horizontal video position where $fffa21 is changed */ else { int FrameCycles, HblCounterVideo; int pos_start , pos_read; /* Cycle position of the start of the current instruction */ //pos_start = nFrameCycles % nCyclesPerLine; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &pos_start ); pos_start >>= nCpuFreqShift; /* Cycle position of the read for the current instruction (approximately, we consider */ /* the read happens after 4 cycles (due to MFP wait states in that case)) */ /* This is quite a hack, but hard to do without proper 68000 read cycle emulation */ if ( CurrentInstrCycles <= 8 ) /* move.b (a0),d0 / cmp.b (a0),d0 ... */ pos_read = pos_start + 4; /* wait state */ else /* cmp.b $fa21.w,d0 (BIG Demo) ... */ pos_read = pos_start + 8; /* more time needed to compute the effective address */ TB_count = pMFP->TB_MAINCOUNTER; /* default value */ /* If Timer B's change happens before the read cycle of the current instruction, we must return */ /* the current value - 1 (because MFP_TimerB_EventCount_Interrupt was not called yet) */ /* NOTE This is only needed when CpuRunCycleExact=false ; when CpuRunCycleExact=true, MFP_UpdateTimers() */ /* was already called above and MFP_TimerB_EventCount_Interrupt should have been called too if needed */ /* so TB_count should already be the correct value */ if ( !CpuRunCycleExact && (nHBL >= nStartHBL ) && ( nHBL < nEndHBL ) /* ensure display is ON and timer B can happen */ && ( LineTimerBPos > pos_start ) && ( LineTimerBPos < pos_read ) ) { LOG_TRACE(TRACE_MFP_READ , "mfp%s read tbdr overlaps pos_start=%d TB_pos=%d pos_read=%d nHBL=%d \n", pMFP->NameSuffix , pos_start, LineTimerBPos, pos_read , HblCounterVideo ); TB_count--; if ( TB_count == 0 ) /* going from 1 to 0 : timer restart, reload data reg */ TB_count = pMFP->TBDR; /* Going from 0 to -1 : data reg is in fact going from 256 to 255. As TB_count is uint8_t, */ /* this is already what we get when we decrement TB_count=0. So, the next 2 lines are redundant. */ /* else if ( TB_count < 0 ) TB_count = 255; */ } LOG_TRACE(TRACE_MFP_READ , "mfp%s read tbdr data=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d\n" , pMFP->NameSuffix , TB_count, FrameCycles, pos_start, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); IoMem[IoAccessCurrentAddress] = TB_count; return; } } IoMem[IoAccessCurrentAddress] = pMFP->TB_MAINCOUNTER; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tbdr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer C Data Register TCDR (0xfffa23 or 0xfffaa3) */ void MFP_TimerCData_ReadByte(void) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa23 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); MFP_ReadTimerC ( pMFP , false ); /* Store result in 'TC_MAINCOUNTER' */ IoMem[IoAccessCurrentAddress] = pMFP->TC_MAINCOUNTER; if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tcdr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle read from Timer D Data Register TDDR (0xfffa25 or 0xfffaa5) */ void MFP_TimerDData_ReadByte ( void ) { MFP_STRUCT *pMFP; uint32_t pc = M68000_GetPC(); M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa25 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before reading the register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); /* Special case for the main MFP when bPatchTimerD is used */ /* NOTE : in TT mode TOS also starts useless timer D on the TT MFP, so we should restore */ /* 0xfffa25/0xfffaa5 for Main MFP and TT MFP when bPatchTimerD is enabled */ if ( ConfigureParams.System.bPatchTimerD && pc >= TosAddress && pc <= TosAddress + TosSize ) { /* Trick the tos to believe TDDR was not changed */ IoMem[IoAccessCurrentAddress] = pMFP->PatchTimerD_TDDR_old; } else { MFP_ReadTimerD ( pMFP , false ); /* Store result in 'TD_MAINCOUNTER' */ IoMem[IoAccessCurrentAddress] = pMFP->TD_MAINCOUNTER; } if ( LOG_TRACE_LEVEL( TRACE_MFP_READ ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s read tddr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Handle write to GPIP register (0xfffa01 or 0xfffa81) * * Only line configured as output in DDR can be changed (0=input 1=output) * When reading GPIP, output lines should return the last value that was written, * only input lines should be updated. */ void MFP_GPIP_WriteByte ( void ) { MFP_STRUCT *pMFP; uint8_t GPIP_old; uint8_t GPIP_new; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa01 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write gpip %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } GPIP_old = pMFP->GPIP; GPIP_new = IoMem[IoAccessCurrentAddress] & pMFP->DDR; /* New output bits */ pMFP->GPIP = ( GPIP_old & ~pMFP->DDR ) | GPIP_new; /* Keep input bits unchanged and update output bits */ /* Update possible interrupts after changing GPIP */ MFP_GPIP_Update_Interrupt ( pMFP , GPIP_old , pMFP->GPIP , pMFP->AER , pMFP->AER , pMFP->DDR , pMFP->DDR ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Active Edge Register AER (0xfffa03 or 0xfffa83) * * Special case for bit 3 of main MFP (0xfffa03) : * Bit 3 of AER is linked to timer B in event count mode. * - If bit 3=0, timer B triggers on end of line when display goes off. * - If bit 3=1, timer B triggers on start of line when display goes on. */ void MFP_ActiveEdge_WriteByte ( void ) { MFP_STRUCT *pMFP; uint8_t AER_old; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa03 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write aer %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } AER_old = pMFP->AER; pMFP->AER = IoMem[IoAccessCurrentAddress]; /* Update possible interrupts after changing AER */ MFP_GPIP_Update_Interrupt ( pMFP , pMFP->GPIP , pMFP->GPIP , AER_old , pMFP->AER , pMFP->DDR , pMFP->DDR ); /* Special case when changing bit 3 in main MFP : */ /* Video DE signal is connected to Timer B on the main MFP */ /* We need to update the position of the timer B interrupt for 'event count' mode */ if ( IoAccessCurrentAddress == 0xfffa03 ) { if ( ( AER_old & ( 1 << 3 ) ) != ( pMFP->AER & ( 1 << 3 ) ) ) { int FrameCycles, HblCounterVideo, LineCycles; int LineTimerBPos_old = LineTimerBPos; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* 0 -> 1, timer B is now counting start of line events (cycle 56+28) */ if ( ( AER_old & ( 1 << 3 ) ) == 0 ) { LineTimerBPos = Video_TimerB_GetPos ( HblCounterVideo ); LOG_TRACE((TRACE_VIDEO_HBL | TRACE_MFP_WRITE), "mfp/video aer bit 3 0->1, timer B triggers on start of line," " old_pos=%d new_pos=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d\n", LineTimerBPos_old, LineTimerBPos, FrameCycles, LineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles); } /* 1 -> 0, timer B is now counting end of line events (cycle 376+28) */ else if ( ( AER_old & ( 1 << 3 ) ) != 0 ) { LineTimerBPos = Video_TimerB_GetPos ( HblCounterVideo ); LOG_TRACE((TRACE_VIDEO_HBL | TRACE_MFP_WRITE), "mfp/video aer bit 3 1->0, timer B triggers on end of line," " old_pos=%d new_pos=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d\n", LineTimerBPos_old, LineTimerBPos, FrameCycles, LineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles); } /* Timer B position changed, update the next interrupt */ if ( LineTimerBPos_old != LineTimerBPos ) Video_AddInterruptTimerB ( HblCounterVideo , LineCycles , LineTimerBPos ); } } } /*-----------------------------------------------------------------------*/ /** * Handle write to Data Direction Register DDR (0xfffa05 or 0xfffa85) */ void MFP_DataDirection_WriteByte ( void ) { MFP_STRUCT *pMFP; uint8_t DDR_old; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa05 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write ddr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } DDR_old = pMFP->DDR; pMFP->DDR = IoMem[IoAccessCurrentAddress]; /* Update possible interrupts after changing DDR */ MFP_GPIP_Update_Interrupt ( pMFP , pMFP->GPIP , pMFP->GPIP , pMFP->AER , pMFP->AER , DDR_old , pMFP->DDR ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt Enable Register A IERA (0xfffa07 or 0xfffa87) */ void MFP_EnableA_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa07 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write iera %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->IERA = IoMem[IoAccessCurrentAddress]; pMFP->IPRA &= pMFP->IERA; MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt Enable Register B IERB (0xfffa09 or 0xfffa89) */ void MFP_EnableB_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa09 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write ierb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->IERB = IoMem[IoAccessCurrentAddress]; pMFP->IPRB &= pMFP->IERB; MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt Pending Register A IPRA (0xfffa0b or 0xfffa8b) */ void MFP_PendingA_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa0b ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write ipra %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->IPRA &= IoMem[IoAccessCurrentAddress]; /* Cannot set pending bits - only clear via software */ MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt Pending Register B IPRB (0xfffa0d or 0xfffa8d) */ void MFP_PendingB_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa0d ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write iprb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->IPRB &= IoMem[IoAccessCurrentAddress]; /* Cannot set pending bits - only clear via software */ MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt In Service Register A ISRA (0xfffa0f or 0xfffa8f) */ void MFP_InServiceA_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa0f ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write isra %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->ISRA &= IoMem[IoAccessCurrentAddress]; /* Cannot set in-service bits - only clear via software */ MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt In Service Register B ISRB (0xfffa11 or 0xfffa91). */ void MFP_InServiceB_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa11 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write isrb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->ISRB &= IoMem[IoAccessCurrentAddress]; /* Cannot set in-service bits - only clear via software */ MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt Mask Register A IMRA (0xfffa13 or 0xfffa93) */ void MFP_MaskA_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa13 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write imra %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->IMRA = IoMem[IoAccessCurrentAddress]; MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to Interrupt Mask Register B IMRB (0xfffa15 or 0xfffa95) */ void MFP_MaskB_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa15 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write imrb %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->IMRB = IoMem[IoAccessCurrentAddress]; MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } /*-----------------------------------------------------------------------*/ /** * Handle write to MFP Vector Register (0xfffa17 or 0xfffa97) */ void MFP_VectorReg_WriteByte ( void ) { MFP_STRUCT *pMFP; uint8_t old_vr; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa17 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write vr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } old_vr = pMFP->VR; /* Copy for checking if set mode */ pMFP->VR = IoMem[IoAccessCurrentAddress]; if ((pMFP->VR^old_vr) & 0x08) /* Test change in end-of-interrupt mode */ { /* Mode did change but was it to automatic mode ? (ie bit is a zero) */ if (!(pMFP->VR & 0x08)) { /* We are now in automatic mode, so clear all in-service bits */ pMFP->ISRA = 0; pMFP->ISRB = 0; MFP_UpdateIRQ ( pMFP , Cycles_GetClockCounterOnWriteAccess() ); } } } /*-----------------------------------------------------------------------*/ /** * Handle write to Timer A Control Register TACR (0xfffa19 or 0xfffa99) */ void MFP_TimerACtrl_WriteByte ( void ) { MFP_STRUCT *pMFP; uint8_t new_tacr; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa19 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tacr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } new_tacr = IoMem[IoAccessCurrentAddress] & 0x0f; /* FIXME : ignore bit 4 (reset) ? */ if ( pMFP->TACR != new_tacr ) /* Timer control changed */ { /* If we stop a timer which was in delay mode, we need to store * the current value of the counter to be able to read it or to * continue from where we left if the timer is restarted later * without writing to the data register. */ if ((new_tacr == 0) && (pMFP->TACR >=1) && (pMFP->TACR <= 7)) MFP_ReadTimerA ( pMFP , true); /* Store result in 'TA_MAINCOUNTER' */ pMFP->TACR = new_tacr; /* set to new value before calling MFP_StartTimer */ MFP_StartTimerA ( pMFP ); /* start/stop timer depending on control reg */ } } /*-----------------------------------------------------------------------*/ /** * Handle write to Timer B Control Register TBCR (0xfffa1b or 0xfffa9b) */ void MFP_TimerBCtrl_WriteByte(void) { MFP_STRUCT *pMFP; uint8_t new_tbcr; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa1b ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tbcr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } new_tbcr = IoMem[IoAccessCurrentAddress] & 0x0f; /* FIXME : ignore bit 4 (reset) ? */ if ( pMFP->TBCR != new_tbcr ) /* Timer control changed */ { /* If we stop a timer which was in delay mode, we need to store * the current value of the counter to be able to read it or to * continue from where we left if the timer is restarted later * without writing to the data register. */ if ((new_tbcr == 0) && (pMFP->TBCR >=1) && (pMFP->TBCR <= 7)) MFP_ReadTimerB ( pMFP , true); /* Store result in 'TA_MAINCOUNTER' */ pMFP->TBCR = new_tbcr; /* set to new value before calling MFP_StartTimer */ MFP_StartTimerB ( pMFP ); /* start/stop timer depending on control reg */ } } /*-----------------------------------------------------------------------*/ /** * Handle write to timer C/D control register (0xfffa1d). */ void MFP_TimerCDCtrl_WriteByte(void) { MFP_STRUCT *pMFP; uint8_t new_tcdcr; uint8_t old_tcdcr; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa1d ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tcdcr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } new_tcdcr = IoMem[IoAccessCurrentAddress]; old_tcdcr = pMFP->TCDCR; //fprintf ( stderr , "write fa1d new %x old %x\n" , IoMem[IoAccessCurrentAddress] , pMFP->TCDCR ); if ((old_tcdcr & 0x70) != (new_tcdcr & 0x70)) /* Timer C control changed */ { /* If we stop a timer which was in delay mode, we need to store * the current value of the counter to be able to read it or to * continue from where we left if the timer is restarted later * without writing to the data register. */ if ((new_tcdcr & 0x70) == 0) MFP_ReadTimerC ( pMFP , true ); /* Store result in 'TC_MAINCOUNTER' */ pMFP->TCDCR = ( new_tcdcr & 0x70 ) | ( old_tcdcr & 0x07 ); /* we set TCCR and keep old TDCR in case we need to read it below */ MFP_StartTimerC ( pMFP ); /* start/stop timer depending on control reg */ } if ((old_tcdcr & 0x07) != (new_tcdcr & 0x07)) /* Timer D control changed */ { uint32_t pc = M68000_GetPC(); /* Special case for main MFP and TT MFP when bPatchTimerD is used */ if (ConfigureParams.System.bPatchTimerD && !pMFP->PatchTimerD_Done && pc >= TosAddress && pc <= TosAddress + TosSize) { /* Slow down Timer-D if set from TOS for the first time to gain * more desktop performance. * Obviously, we need to emulate all timers correctly but TOS sets * up Timer-D at a very high rate (every couple of instructions). * The interrupt isn't enabled but the emulator still needs to * process the interrupt table and this HALVES our frame rate!!! * Some games actually reference this timer but don't set it up * (eg Paradroid, Speedball I) so we simply intercept the Timer-D * setup code in TOS and fix the numbers with more 'laid-back' * values. This still keeps 100% compatibility */ if ( new_tcdcr & 0x07 ) /* apply patch only if timer D is being started */ { new_tcdcr = IoMem[IoAccessCurrentAddress] = (IoMem[IoAccessCurrentAddress] & 0xf0) | 7; pMFP->PatchTimerD_Done = 1; } } /* Special case for the main MFP when RS232 is enabled */ if ( IoAccessCurrentAddress == 0xfffa1d ) { /* Need to change baud rate of RS232 emulation? */ if (ConfigureParams.RS232.bEnableRS232) { RS232_SetBaudRateFromTimerD(); } } /* If we stop a timer which was in delay mode, we need to store * the current value of the counter to be able to read it or to * continue from where we left if the timer is restarted later * without writing to the data register. */ if ((new_tcdcr & 0x07) == 0) MFP_ReadTimerD ( pMFP , true ); /* Store result in 'TD_MAINCOUNTER' */ pMFP->TCDCR = new_tcdcr; /* set to new value before calling MFP_StartTimer */ MFP_StartTimerD ( pMFP ); /* start/stop timer depending on control reg */ } } /*-----------------------------------------------------------------------*/ /** * Handle write to Timer A Data Register TADR (0xfffa1f or 0xfffa9f) */ void MFP_TimerAData_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa1f ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tadr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->TADR = IoMem[IoAccessCurrentAddress]; /* Store into data register */ if ( pMFP->TACR == 0 ) /* Now check if timer is running - if so do not set */ { pMFP->TA_MAINCOUNTER = pMFP->TADR; /* Timer is off, store to main counter */ } } /*-----------------------------------------------------------------------*/ /** * Handle write to Timer B Data Register TBDR (0xfffa21 or 0xfffaa1) */ void MFP_TimerBData_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa21 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tbdr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->TBDR = IoMem[IoAccessCurrentAddress]; /* Store into data register */ if ( pMFP->TBCR == 0 ) /* Now check if timer is running - if so do not set */ { pMFP->TB_MAINCOUNTER = pMFP->TBDR; /* Timer is off, store to main counter */ } } /*-----------------------------------------------------------------------*/ /** * Handle write to Timer C Data Register TCDR (0xfffa23 or 0xfffaa3) */ void MFP_TimerCData_WriteByte ( void ) { MFP_STRUCT *pMFP; M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa23 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tcdr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } pMFP->TCDR = IoMem[IoAccessCurrentAddress]; /* Store into data register */ if ( (pMFP->TCDCR & 0x70) == 0 ) /* Now check if timer is running - if so do not set */ { pMFP->TC_MAINCOUNTER = pMFP->TCDR; /* Timer is off, store to main counter */ } } /*-----------------------------------------------------------------------*/ /** * Handle write to Timer D Data Register TDDR (0xfffa25 or 0xfffaa5) */ void MFP_TimerDData_WriteByte ( void ) { MFP_STRUCT *pMFP; uint32_t pc = M68000_GetPC(); M68000_WaitState(4); if ( IoAccessCurrentAddress == 0xfffa25 ) pMFP = pMFP_Main; else pMFP = pMFP_TT; /* Update timers' state before writing to register */ MFP_UpdateTimers ( pMFP , Cycles_GetClockCounterImmediate() ); if ( LOG_TRACE_LEVEL( TRACE_MFP_WRITE ) ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("mfp%s write tddr %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" , pMFP->NameSuffix , IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } /* Patch Timer D for better performance ? */ /* NOTE : in TT mode TOS also starts useless timer D on the TT MFP, so we should patch */ /* Main MFP and TT MFP when bPatchTimerD is enabled */ if ( ConfigureParams.System.bPatchTimerD && pc >= TosAddress && pc <= TosAddress + TosSize ) { pMFP->PatchTimerD_TDDR_old = IoMem[IoAccessCurrentAddress]; IoMem[IoAccessCurrentAddress] = PATCH_TIMER_TDDR_FAKE; /* Slow down the useless Timer D setup from the bios */ } if ( IoAccessCurrentAddress == 0xfffa25 ) { /* Need to change baud rate of RS232 emulation ? */ if ( ConfigureParams.RS232.bEnableRS232 && ( IoMem[0xfffa1d] & 0x07 ) ) { RS232_SetBaudRateFromTimerD(); } } pMFP->TDDR = IoMem[IoAccessCurrentAddress]; /* Store into data register */ if ( (pMFP->TCDCR & 0x07) == 0 ) /* Now check if timer is running - if so do not set */ { pMFP->TD_MAINCOUNTER = pMFP->TDDR; /* Timer is off, store to main counter */ } } static void MFP_Show(FILE *fp, MFP_STRUCT *mfp) { fprintf(fp, "General Purpose Pins: 0x%02x\n", mfp->GPIP); fprintf(fp, "Active Edge: 0x%02x\n", mfp->AER); fprintf(fp, "Data Direction: 0x%02x\n", mfp->DDR); fprintf(fp, "Interrupt A Enable: 0x%02x\n", mfp->IERA); fprintf(fp, "Interrupt B Enable: 0x%02x\n", mfp->IERB); fprintf(fp, "Interrupt A Pending: 0x%02x\n", mfp->IPRA); fprintf(fp, "Interrupt B Pending: 0x%02x\n", mfp->IPRB); fprintf(fp, "Interrupt A In-Service: 0x%02x\n", mfp->ISRA); fprintf(fp, "Interrupt B In-Service: 0x%02x\n", mfp->ISRB); fprintf(fp, "Interrupt A Mask: 0x%02x\n", mfp->IMRA); fprintf(fp, "Interrupt B Mask: 0x%02x\n", mfp->IMRB); fprintf(fp, "Vector: 0x%02x\n", mfp->VR); fprintf(fp, "Timer A Control: 0x%02x\n", mfp->TACR); fprintf(fp, "Timer B Control: 0x%02x\n", mfp->TBCR); fprintf(fp, "Timer C/D Control: 0x%02x\n", mfp->TCDCR); fprintf(fp, "Timer A Data: 0x%02x\n", mfp->TADR); fprintf(fp, "Timer B Data: 0x%02x\n", mfp->TBDR); fprintf(fp, "Timer C Data: 0x%02x\n", mfp->TCDR); fprintf(fp, "Timer D Data: 0x%02x\n", mfp->TDDR); fprintf(fp, "Synchronous Data: 0x%02x\n", mfp->SCR); fprintf(fp, "USART Control: 0x%02x\n", mfp->UCR); fprintf(fp, "Receiver Status: 0x%02x\n", mfp->RSR); fprintf(fp, "Transmitter Status: 0x%02x\n", mfp->TSR); fprintf(fp, "USART Data: 0x%02x\n", mfp->UDR); fprintf(fp, "IRQ signal: 0x%02x\n", mfp->IRQ); fprintf(fp, "Input signal on Timer A: 0x%02x\n", mfp->TAI); fprintf(fp, "Input signal on Timer B: 0x%02x\n", mfp->TBI); } void MFP_Info(FILE *fp, uint32_t dummy) { MFP_Show(fp, pMFP_Main); if (Config_IsMachineTT()) { fprintf(fp, "TT-MFP:\n"); MFP_Show(fp, pMFP_TT); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/midi.c000066400000000000000000000535411504763705000224260ustar00rootroot00000000000000/* Hatari - midi.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. MIDI communication. TODO: - Most bits in the ACIA's status + control registers are currently ignored. NOTE [NP] : In all accuracy, we should use a complete emulation of the acia serial line, as for the ikbd. But as the MIDI's baudrate is rather high and could require more resources to emulate at the bit level, we handle transfer 1 byte a time instead of sending each bit one after the other. This way, we only need a timer every 2560 cycles (instead of 256 cycles per bit). We handle a special case for the TX_EMPTY bit when reading SR : this bit should be set after TDR was copied into TSR, which is approximately when the next bit should be transferred (256 cycles) (fix the program 'Notator') */ const char Midi_fileid[] = "Hatari midi.c"; #include #include "main.h" #include "configuration.h" #include "ioMem.h" #include "m68000.h" #include "memorySnapShot.h" #include "mfp.h" #include "midi.h" #include "file.h" #include "acia.h" #include "video.h" #define ACIA_SR_INTERRUPT_REQUEST 0x80 #define ACIA_SR_TX_EMPTY 0x02 #define ACIA_SR_RX_FULL 0x01 /* Delay to send/receive 1 byte through MIDI (in cpu cycles at x1, x2 or x4 speed) * Serial line is set to 31250 bps, 1 start bit, 8 bits, 1 stop, no parity, which gives 256 cycles * per bit at 8 MHz, and 2560 cycles to transfer 10 bits */ #define MIDI_TRANSFER_BIT_CYCLE ( 256 << nCpuFreqShift ) #define MIDI_TRANSFER_BYTE_CYCLE (MIDI_TRANSFER_BIT_CYCLE * 10) static uint8_t MidiControlRegister; static uint8_t MidiStatusRegister; static uint8_t nRxDataByte; static uint64_t TDR_Write_Time; /* Time of the last write in TDR fffc06 */ static uint64_t TDR_Empty_Time; /* Time when TDR will be empty after a write to fffc06 (ie when TDR is transferred to TSR) */ static uint64_t TSR_Complete_Time; /* Time when TSR will be completely transferred */ /* ** Host MIDI I/O */ static bool Midi_Host_Open(void); static void Midi_Host_Close(void); static int Midi_Host_ReadByte(void); static bool Midi_Host_WriteByte(uint8_t byte); #ifndef HAVE_PORTMIDI static FILE *pMidiFhIn = NULL; /* File handle used for Midi input */ static FILE *pMidiFhOut = NULL; /* File handle used for Midi output */ #else #include "portmidi.h" #define INPUT_BUFFER_SIZE 1024 // PortMidi handles buffering static PmStream* midiIn = NULL; // current midi input port static PmStream* midiOut = NULL; // current midi output port static bool Midi_Host_SwitchPort(const char* portName, midi_dir_t dir); static int Midi_GetDataLength(uint8_t status); static int Midi_SplitEvent(PmEvent* midiEvent, uint8_t* msg); static PmEvent* Midi_BuildEvent(uint8_t byte); #endif /** * Initialization: Open MIDI device. */ void Midi_Init(void) { if (ConfigureParams.Midi.bEnableMidi) { if (!Midi_Host_Open()) { Log_AlertDlg(LOG_ERROR, "MIDI i/o open failed. MIDI support disabled."); ConfigureParams.Midi.bEnableMidi = false; } } } /** * Close MIDI device. */ void Midi_UnInit(void) { Midi_Host_Close(); CycInt_RemovePendingInterrupt(INTERRUPT_MIDI); } /** * Reset MIDI emulation. */ void Midi_Reset(void) { //fprintf ( stderr , "midi reset\n" ); MidiControlRegister = 0; MidiStatusRegister = ACIA_SR_TX_EMPTY; pACIA_MIDI->IRQ_Line = 1; /* IRQ cleared */ nRxDataByte = 1; TDR_Empty_Time = 0; TSR_Complete_Time = 0; /* Set timer */ CycInt_AddRelativeInterrupt ( MIDI_TRANSFER_BYTE_CYCLE , INT_CPU_CYCLE , INTERRUPT_MIDI ); } /** * Save/Restore snapshot of local variables */ void MIDI_MemorySnapShot_Capture(bool bSave) { MemorySnapShot_Store(&MidiControlRegister, sizeof(MidiControlRegister)); MemorySnapShot_Store(&MidiStatusRegister, sizeof(MidiStatusRegister)); MemorySnapShot_Store(&nRxDataByte, sizeof(nRxDataByte)); MemorySnapShot_Store(&TDR_Empty_Time, sizeof(TDR_Empty_Time)); MemorySnapShot_Store(&TSR_Complete_Time, sizeof(TSR_Complete_Time)); } /*-----------------------------------------------------------------------*/ /** * Check if the IRQ must be changed in SR. * When there's a change, we must change the IRQ line too. */ static void MIDI_UpdateIRQ ( void ) { uint8_t irq_bit_new; irq_bit_new = 0; if ( ( ( MidiControlRegister & 0x80 ) == 0x80 ) /* Check for RX causes of interrupt */ && ( MidiStatusRegister & ACIA_SR_RX_FULL ) ) irq_bit_new = ACIA_SR_INTERRUPT_REQUEST; if ( ( ( MidiControlRegister & 0x60) == 0x20 ) /* Check for TX causes of interrupt */ && ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) ) irq_bit_new = ACIA_SR_INTERRUPT_REQUEST; /* Update SR and IRQ line if a change happened */ if ( ( MidiStatusRegister & ACIA_SR_INTERRUPT_REQUEST ) != irq_bit_new ) { LOG_TRACE ( TRACE_MIDI_RAW, "midi update irq irq_new=%d VBL=%d HBL=%d\n" , irq_bit_new?1:0 , nVBLs , nHBL ); if ( irq_bit_new ) { /* Request interrupt by setting GPIP to low/0 */ pACIA_MIDI->IRQ_Line = 0; MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_LOW ); MidiStatusRegister |= ACIA_SR_INTERRUPT_REQUEST; } else { /* Clear interrupt request by setting GPIP to high/1 */ pACIA_MIDI->IRQ_Line = 1; MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_HIGH ); MidiStatusRegister &= ~ACIA_SR_INTERRUPT_REQUEST; } } } /** * Read MIDI status register ($FFFC04). */ void Midi_Control_ReadByte(void) { ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ /* Special case : if we wrote a byte into TDR, TX_EMPTY bit should be */ /* set approximately after the first bit was transferred using TSR */ if ( ( ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) == 0 ) && ( CyclesGlobalClockCounter > TDR_Empty_Time ) ) // OK avec 11 bits et 1 bit { MidiStatusRegister |= ACIA_SR_TX_EMPTY; /* Do we need to generate a transfer interrupt? */ MIDI_UpdateIRQ (); } //fprintf ( stderr , "midi read sr %x %lld %lld\n" , MidiStatusRegister , CyclesGlobalClockCounter , TDR_Write_Time ); IoMem[0xfffc04] = MidiStatusRegister; LOG_TRACE ( TRACE_MIDI_RAW, "midi read fffc04 sr=0x%02x VBL=%d HBL=%d\n" , MidiStatusRegister , nVBLs , nHBL ); } /** * Write to MIDI control register ($FFFC04). */ void Midi_Control_WriteByte(void) { ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ MidiControlRegister = IoMem[0xfffc04]; LOG_TRACE ( TRACE_MIDI_RAW, "midi write fffc04 cr=0x%02x VBL=%d HBL=%d\n" , MidiControlRegister , nVBLs , nHBL ); MIDI_UpdateIRQ (); } /** * Read MIDI data register ($FFFC06). */ void Midi_Data_ReadByte(void) { LOG_TRACE ( TRACE_MIDI_RAW, "midi read fffc06 rdr=0x%02x VBL=%d HBL=%d\n" , nRxDataByte , nVBLs , nHBL ); //fprintf ( stderr , "midi rx %x\n" , nRxDataByte); ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ IoMem[0xfffc06] = nRxDataByte; MidiStatusRegister &= ~ACIA_SR_RX_FULL; MIDI_UpdateIRQ (); } /** * Write to MIDI data register ($FFFC06). * We should determine precisely when TDR will be empty and when TSR will be transferred. * This is required to accurately emulate the TDRE bit in status register (fix the program 'Notator') */ void Midi_Data_WriteByte(void) { uint8_t nTxDataByte; ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */ nTxDataByte = IoMem[0xfffc06]; TDR_Write_Time = CyclesGlobalClockCounter; /* If TSR is already transferred, then TDR will be empty after 1 bit is transferred */ /* If TSR is not completely transferred, then TDR will be empty 1 bit after TSR is transferred */ if ( CyclesGlobalClockCounter >= TSR_Complete_Time ) { TDR_Empty_Time = CyclesGlobalClockCounter + MIDI_TRANSFER_BIT_CYCLE; TSR_Complete_Time = CyclesGlobalClockCounter + MIDI_TRANSFER_BYTE_CYCLE; } else { //fprintf ( stderr , "MIDI OVR %lld\n" , TSR_Complete_Time - CyclesGlobalClockCounter ); TDR_Empty_Time = TSR_Complete_Time + MIDI_TRANSFER_BIT_CYCLE; TSR_Complete_Time += MIDI_TRANSFER_BYTE_CYCLE; } LOG_TRACE ( TRACE_MIDI_RAW, "midi write fffc06 tdr=0x%02x VBL=%d HBL=%d\n" , nTxDataByte , nVBLs , nHBL ); //fprintf ( stderr , "midi tx %x sr=%x\n" , nTxDataByte , MidiStatusRegister ); MidiStatusRegister &= ~ACIA_SR_TX_EMPTY; MIDI_UpdateIRQ (); if (!ConfigureParams.Midi.bEnableMidi) return; if (Midi_Host_WriteByte(nTxDataByte)) { LOG_TRACE(TRACE_MIDI, "MIDI: write byte -> $%x\n", nTxDataByte); } else { LOG_TRACE(TRACE_MIDI, "MIDI: write error -> stop MIDI\n"); Midi_UnInit(); return; } } /** * Read and write MIDI interface data regularly */ void Midi_InterruptHandler_Update(void) { int nInChar; /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Special case : if we wrote a byte into TDR, TX_EMPTY bit should be */ /* set when reaching TDR_Empty_Time */ if ( ( ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) == 0 ) && ( CyclesGlobalClockCounter > TDR_Empty_Time ) ) { MidiStatusRegister |= ACIA_SR_TX_EMPTY; /* Do we need to generate a transfer interrupt? */ MIDI_UpdateIRQ (); /* Flush outgoing data (not necessary ?) */ // if (pMidiFhOut) // fflush(pMidiFhOut); } /* Read the bytes in, if we have any */ nInChar = Midi_Host_ReadByte(); if (nInChar != EOF) { LOG_TRACE(TRACE_MIDI, "MIDI: read byte -> $%x\n", nInChar); /* Copy into our internal queue */ nRxDataByte = nInChar; MidiStatusRegister |= ACIA_SR_RX_FULL; /* Do we need to generate a receive interrupt? */ MIDI_UpdateIRQ (); } /* Set timer */ CycInt_AddRelativeInterrupt ( MIDI_TRANSFER_BYTE_CYCLE , INT_CPU_CYCLE , INTERRUPT_MIDI ); } // ============================================================================ // Host MIDI I/O // ============================================================================ /** * open MIDI streams * return true for no errors */ static bool Midi_Host_Open(void) { #ifndef HAVE_PORTMIDI LOG_TRACE_VAR int ok; if (ConfigureParams.Midi.sMidiOutFileName[0]) { /* Open MIDI output file */ pMidiFhOut = File_Open(ConfigureParams.Midi.sMidiOutFileName, "wb"); if (!pMidiFhOut) return false; ok = setvbuf(pMidiFhOut, NULL, _IONBF, 0); /* No output buffering! */ LOG_TRACE(TRACE_MIDI, "MIDI: Opened file '%s' (%s) for output\n", ConfigureParams.Midi.sMidiOutFileName, ok == 0 ? "unbuffered" : "buffered"); } if (ConfigureParams.Midi.sMidiInFileName[0]) { /* Try to open MIDI input file */ pMidiFhIn = File_Open(ConfigureParams.Midi.sMidiInFileName, "rb"); if (!pMidiFhIn) return false; ok = setvbuf(pMidiFhIn, NULL, _IONBF, 0); /* No input buffering! */ LOG_TRACE(TRACE_MIDI, "MIDI: Opened file '%s' (%s) for input\n", ConfigureParams.Midi.sMidiInFileName, ok == 0 ? "unbuffered" : "buffered"); } #else int i, ports; if (Pm_Initialize() != pmNoError) { LOG_TRACE(TRACE_MIDI, "MIDI: PortMidi initialization failed\n"); return false; } // -- log available ports ports = Pm_CountDevices(); for (i = 0; i < ports; i++) { const PmDeviceInfo *info = Pm_GetDeviceInfo(i); if (!info) continue; LOG_TRACE(TRACE_MIDI, "MIDI: %s %d: '%s'\n", info->input ? "input " : "output", i, info->name); } // -- open input and output ports according to configuration // -- ignore errors to avoid MIDI being disabled if (ConfigureParams.Midi.sMidiInPortName[0]) Midi_Host_SwitchPort(ConfigureParams.Midi.sMidiInPortName, MIDI_FOR_INPUT); if (ConfigureParams.Midi.sMidiOutPortName[0]) Midi_Host_SwitchPort(ConfigureParams.Midi.sMidiOutPortName, MIDI_FOR_OUTPUT); #endif return true; } /* ---------------------------------------------------------------------------- */ /** * close MIDI streams */ static void Midi_Host_Close(void) { #ifndef HAVE_PORTMIDI pMidiFhIn = File_Close(pMidiFhIn); pMidiFhOut = File_Close(pMidiFhOut); #else if (midiIn) Pm_Close(midiIn); if (midiOut) Pm_Close(midiOut); midiIn = midiOut = NULL; // Can't terminate PM or free descriptor arrays as this gets // called by any write errors and GUI won't then work. // Pm_Terminate(); #endif } #ifdef HAVE_PORTMIDI /** * Returns port name if there's one matching the given port name * with given offset and direction. * * Offset interpretation: * 0: return matching device name, with prefix match as fallback * <0: return name of device before matching one * >0: return name of device after matching one * * As special case, for NULL/empty name with positive offset * (i.e. before any port has been selected for the first time), * name of the first port in that direction is returned. */ const char* Midi_Host_GetPortName(const char *name, midi_name_offset_t offset, midi_dir_t dir) { const PmDeviceInfo* info; const char *prev = NULL; const char *prefixmatch = NULL; bool prev_matched = false; int i, count, len; len = name ? strlen(name) : 0; // -- find port with given offset from named one count = Pm_CountDevices(); for (i = 0; i < count; i++) { info = Pm_GetDeviceInfo(i); if (!info) continue; if (dir == MIDI_FOR_INPUT && !info->input) continue; if (dir == MIDI_FOR_OUTPUT && info->input) continue; if (len == 0) { if (offset <= 0) return NULL; return info->name; } /* matches */ if (!strcmp(info->name, name)) { if (!offset) return name; if (offset < 0) return prev; prev_matched = true; continue; } if (prev_matched) return info->name; prev = info->name; if (!strncmp(info->name, name, len)) prefixmatch = info->name; } if (!offset && prefixmatch) return prefixmatch; return NULL; } /** * Closes current midi port (if any) and opens 'portName' if MIDI * enabled. If there is no exact match, last device where 'portName' * matches beginning of the device name, is used. Returns true for * success, false otherwise */ static bool Midi_Host_SwitchPort(const char* portName, midi_dir_t dir) { int i, prefixmatch, len, count; bool err; if (!ConfigureParams.Midi.bEnableMidi) return false; // -- no names if (strcasecmp("off", portName) == 0) return false; len = strlen(portName); prefixmatch = -1; // -- find PortMidi index for 'portName' count = Pm_CountDevices(); for (i = 0; i < count; i++) { const PmDeviceInfo* info = Pm_GetDeviceInfo(i); if (!info) continue; if (dir == MIDI_FOR_INPUT && !info->input) continue; if (dir == MIDI_FOR_OUTPUT && info->input) continue; if (!strcmp(info->name, portName)) break; if (!strncmp(info->name, portName, len)) prefixmatch = i; } if (i >= count && prefixmatch >= 0) i = prefixmatch; if (i >= count) { LOG_TRACE(TRACE_MIDI, "MIDI: no %s ports matching '%s'\n", dir == MIDI_FOR_INPUT ? "input" : "output", portName); return false; } // -- close current port in any case, then try open new one if (dir == MIDI_FOR_INPUT) { if (midiIn) { Pm_Close(midiIn); midiIn = NULL; } err = (Pm_OpenInput(&midiIn, i, NULL, INPUT_BUFFER_SIZE, NULL, NULL) == pmNoError); LOG_TRACE(TRACE_MIDI, "MIDI: input port %d '%s' open %s\n", i, portName, err ? "succeeded" : "failed"); return err; } else { if (midiOut) { Pm_Close(midiOut); midiOut = NULL; } err = (Pm_OpenOutput(&midiOut, i, NULL, 0, NULL, NULL, 0) == pmNoError); LOG_TRACE(TRACE_MIDI, "MIDI: output port %d '%s' open %s\n", i, portName, err ? "succeeded" : "failed"); return err; } return false; } /** * Log PortMidi error regardless of whether it's a host (backend) * or general PortMidi error (which need to be handled differently) */ static void Midi_LogError(PmError error) { const char *msg; char buf[PM_HOST_ERROR_MSG_LEN+1]; if (error == pmHostError) { Pm_GetHostErrorText(buf, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; msg = buf; } else { msg = Pm_GetErrorText(error); } Log_Printf(LOG_WARN, "MIDI: PortMidi write error %d: '%s'\n", error, msg); } #endif /** * returns byte from input stream, or EOF if it is empty */ static int Midi_Host_ReadByte(void) { #ifndef HAVE_PORTMIDI if (pMidiFhIn && File_InputAvailable(pMidiFhIn)) { /* man 3p: fgetc() returns EOF on all errors, but * sets errno only for other than end-of-file issues */ errno = 0; int ret = fgetc(pMidiFhIn); if (ret != EOF) return ret; if (errno && errno != EAGAIN) { LOG_TRACE(TRACE_MIDI, "MIDI: read error: %s\n", strerror(errno)); } /* affects only EOF indicator */ clearerr(pMidiFhIn); } return EOF; #else // TODO: should these be reset with Midi_Init()? static uint8_t msg[4]; static uint8_t ibyte = 0; static int bytesAvailable = 0; if (midiIn) { // -- we have not yet returned all bytes from previous event if (bytesAvailable > 0) { bytesAvailable--; return msg[ibyte++]; } // -- read new event (if any) else if (Pm_Poll(midiIn) == TRUE) { PmEvent midiEvent; if (Pm_Read(midiIn, &midiEvent, 1) != 1) return EOF; if ((bytesAvailable = Midi_SplitEvent(&midiEvent, msg)) > 0) { bytesAvailable--; ibyte = 1; return msg[0]; } } } // -- no more midi data return EOF; #endif } /** * writes 'byte' into output stream, returns true on success */ static bool Midi_Host_WriteByte(uint8_t byte) { #ifndef HAVE_PORTMIDI if (pMidiFhOut) { /* Write the character to the output file: */ int ret = fputc(byte, pMidiFhOut); return (ret != EOF); } #else if (midiOut) { PmEvent* midiEvent = Midi_BuildEvent(byte); if (midiEvent) { PmError error = Pm_Write(midiOut, midiEvent, 1); if (error == pmNoError || error == pmGotData) return true; Midi_LogError(error); return false; } return true; } #endif return false; } #ifdef HAVE_PORTMIDI // ============================================================================ // PortMidi utils // // PortMidi (as most native MIDI APIs) operate on complete MIDI messages // we need therefore handle running status, variable number of data bytes // and sysex correctly. // // ============================================================================ /** * return number of databytes that should accompany 'status' byte * four bytes for sysex is a special case to simplify Midi_BuildEvent() */ static int Midi_GetDataLength(uint8_t status) { static const uint8_t dataLength[] = { 2,2,2,2,1,2,2, 4,1,2,1,0,0,0,0 }; if (status >= 0xF8 || status == 0) return 0; if (status >= 0xF0) return dataLength[(status & 0x0F) + 7]; return dataLength[(status >> 4) - 8]; } /** * collect bytes until valid MIDI event has been formed / four bytes of sysex data has been gathered * returns PmEvent when done, or NULL if it needs still more data * see MIDI 1.0 Detailed Spec 4.2, pages A-1..A-2 for discussion on running status */ static PmEvent* Midi_BuildEvent(uint8_t byte) { static const uint8_t shifts[] = { 0,8,16,24 }; // TODO: should these be reset with Midi_Init()? static PmEvent midiEvent = { 0,0 }; static uint32_t midimsg; static uint8_t runningStatus = 0; static uint8_t bytesToWait = 0; static uint8_t bytesCollected = 0; static bool processingSysex = false; static bool expectStatus = true; // -- status byte if (byte & 0x80) { // -- realtime if (byte >= 0xF8) { midiEvent.message = Pm_Message(byte, 0, 0); return &midiEvent; } // -- sysex end if (byte == 0xF7) { midimsg |= ((uint32_t)0xF7) << shifts[bytesCollected]; midiEvent.message = midimsg; LOG_TRACE(TRACE_MIDI, "MIDI: SYX END event %X %X %X %X\n", (midimsg & 0x000000FF), (midimsg & 0x0000FF00) >> shifts[1], (midimsg & 0x00FF0000) >> shifts[2], (midimsg & 0xFF000000) >> shifts[3]); midimsg = bytesToWait = bytesCollected = 0; processingSysex = false; expectStatus = true; runningStatus = 0; return &midiEvent; } processingSysex = false; bytesCollected = 0; runningStatus = 0; // -- sysex start if (byte == 0xF0) { processingSysex = true; bytesCollected = 1; } else if (byte < 0xF0) { runningStatus = byte; } midimsg = byte; bytesToWait = Midi_GetDataLength(byte); expectStatus = false; return NULL; } // -- data byte if (processingSysex) { midimsg |= ((uint32_t)byte) << shifts[bytesCollected++]; } else { if (!expectStatus) { midimsg |= ((uint32_t)byte) << shifts[++bytesCollected]; } else if (runningStatus >= 0x80) { // reuse the previous status here. LOG_TRACE(TRACE_MIDI, "MIDI: running status %X byte %X\n", runningStatus, byte); bytesToWait = Midi_GetDataLength(runningStatus); midimsg = ((uint32_t)runningStatus); midimsg |= ((uint32_t)byte) << shifts[++bytesCollected]; expectStatus = false; } } if (bytesCollected >= bytesToWait && bytesCollected > 0) { midiEvent.message = midimsg; LOG_TRACE(TRACE_MIDI, "MIDI: event %X %X %X %X\n", (midimsg & 0x000000FF), (midimsg & 0x0000FF00) >> shifts[1], (midimsg & 0x00FF0000) >> shifts[2], (midimsg & 0xFF000000) >> shifts[3]); bytesToWait = processingSysex ? 4 : 0; midimsg = bytesCollected = 0; expectStatus = true; return &midiEvent; } return NULL; } /** * extracts raw bytes from 'midiEvent' into 'msg' * returns number of bytes available in 'msg' * * this method is required for sysex handling * native framework has already handled running status */ static int Midi_SplitEvent(PmEvent* midiEvent, uint8_t* msg) { // TODO: should be reset with Midi_Init()? static bool processingSysex = false; PmMessage midiMessage = midiEvent->message; int i, bytesAvailable = 0; msg[0] = Pm_MessageStatus(midiMessage); // -- sysex start or continuation if ((msg[0] == 0xF0) || (msg[0] < 0x80)) { if (msg[0] == 0xF0) processingSysex = true; if (processingSysex) { for (i = 0; i <= 3; i++) { msg[i] = midiMessage & 0xFF; bytesAvailable = i; if (msg[i] == 0xF7) { processingSysex = false; break; } midiMessage >>= 8; } } else bytesAvailable = -1; } // -- non-sysex else { if (msg[0] < 0xF8) // non-realtime { processingSysex = false; midiMessage >>= 8; bytesAvailable = Midi_GetDataLength(msg[0]); for (i = 1; i <= bytesAvailable; i++) { msg[i] = midiMessage & 0xFF; midiMessage >>= 8; } } } return bytesAvailable + 1; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/msa.c000066400000000000000000000276201504763705000222630ustar00rootroot00000000000000/* Hatari - msa.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. MSA Disk support */ const char MSA_fileid[] = "Hatari msa.c"; #include "main.h" #include "file.h" #include "floppy.h" #include "msa.h" #include "sysdeps.h" #include "maccess.h" #define SAVE_TO_MSA_IMAGES /* .MSA FILE FORMAT --================------------------------------------------------------------ For those interested, an MSA file is made up as follows: Header: Word ID marker, should be $0E0F Word Sectors per track Word Sides (0 or 1; add 1 to this to get correct number of sides) Word Starting track (0-based) Word Ending track (0-based) Individual tracks follow the header in alternating side order, e.g. a double sided disk is stored as: TRACK 0, SIDE 0 TRACK 0, SIDE 1 TRACK 1, SIDE 0 TRACK 1, SIDE 1 TRACK 2, SIDE 0 TRACK 2, SIDE 1 ...and so on. Track blocks are made up as follows: Word Data length Bytes Data If the data length is equal to 512 x the sectors per track value, it is an uncompressed track and you can merely copy the data to the appropriate track of the disk. However, if the data length value is less than 512 x the sectors per track value it is a compressed track. Compressed tracks use simple a Run Length Encoding (RLE) compression method. You can directly copy any data bytes until you find an $E5 byte. This signals a compressed run, and is made up as follows: Byte Marker - $E5 Byte Data byte Word Run length So, if MSA found six $AA bytes in a row it would encode it as: $E5AA0006 What happens if there's an actual $E5 byte on the disk? Well, logically enough, it is encoded as: $E5E50001 This is obviously bad news if a disk consists of lots of data like $E500E500E500E500... but if MSA makes a track bigger when attempting to compress it, it just stores the uncompressed version instead. MSA only compresses runs of at least 4 identical bytes (after all, it would be wasteful to store 4 bytes for a run of only 3 identical bytes!). There is one exception to this rule: if a run of 2 or 3 $E5 bytes is found, that is stored appropriately enough as a run. Again, it would be wasteful to store 4 bytes for every single $E5 byte. The hacked release of MSA that enables the user to turn off compression completely simply stops MSA from trying this compression and produces MSA images that are completely uncompressed. This is okay because it is possible for MSA to produce such an image anyway, and such images are therefore 100% compatible with normal MSA versions (and MSA-to-ST of course). */ typedef struct { uint16_t ID; /* Word : ID marker, should be $0E0F */ uint16_t SectorsPerTrack; /* Word : Sectors per track */ uint16_t Sides; /* Word : Sides (0 or 1; add 1 to this to get correct number of sides) */ uint16_t StartingTrack; /* Word : Starting track (0-based) */ uint16_t EndingTrack; /* Word : Ending track (0-based) */ } MSAHEADERSTRUCT; #define MSA_WORKSPACE_SIZE (1024*1024) /* Size of workspace to use when saving MSA files */ /*-----------------------------------------------------------------------*/ /** * Does filename end with a .MSA extension? If so, return true */ bool MSA_FileNameIsMSA(const char *pszFileName, bool bAllowGZ) { return(File_DoesFileExtensionMatch(pszFileName,".msa") || (bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".msa.gz"))); } /*-----------------------------------------------------------------------*/ /** * Uncompress .MSA data into a new buffer. */ uint8_t *MSA_UnCompress(uint8_t *pMSAFile, long *pImageSize, long nBytesLeft) { MSAHEADERSTRUCT *pMSAHeader; uint8_t *pMSAImageBuffer, *pImageBuffer; uint8_t Byte,Data; int i,Track,Side,DataLength,NumBytesUnCompressed,RunLength; uint8_t *pBuffer = NULL; *pImageSize = 0; pMSAHeader = (MSAHEADERSTRUCT *)pMSAFile; /* First swap 'header' words around to PC format - easier later on */ pMSAHeader->ID = be_swap16(pMSAHeader->ID); pMSAHeader->SectorsPerTrack = be_swap16(pMSAHeader->SectorsPerTrack); pMSAHeader->Sides = be_swap16(pMSAHeader->Sides); pMSAHeader->StartingTrack = be_swap16(pMSAHeader->StartingTrack); pMSAHeader->EndingTrack = be_swap16(pMSAHeader->EndingTrack); /* Is it really an '.msa' file? Check header */ if (pMSAHeader->ID != 0x0E0F || pMSAHeader->EndingTrack > 86 || pMSAHeader->StartingTrack > pMSAHeader->EndingTrack || pMSAHeader->SectorsPerTrack > 56|| pMSAHeader->Sides > 1 || nBytesLeft <= (long)sizeof(MSAHEADERSTRUCT)) { fprintf(stderr, "MSA image has a bad header!\n"); return NULL; } /* Create buffer */ pBuffer = malloc((pMSAHeader->EndingTrack - pMSAHeader->StartingTrack + 1) * pMSAHeader->SectorsPerTrack * (pMSAHeader->Sides + 1) * NUMBYTESPERSECTOR); if (!pBuffer) { perror("MSA_UnCompress"); return NULL; } /* Set pointers */ pImageBuffer = (uint8_t *)pBuffer; pMSAImageBuffer = pMSAFile + sizeof(MSAHEADERSTRUCT); nBytesLeft -= sizeof(MSAHEADERSTRUCT); /* Uncompress to memory as '.ST' disk image - NOTE: assumes 512 bytes * per sector (use NUMBYTESPERSECTOR define)!!! */ for (Track = pMSAHeader->StartingTrack; Track <= pMSAHeader->EndingTrack; Track++) { for (Side = 0; Side < (pMSAHeader->Sides+1); Side++) { int nBytesPerTrack = NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack; nBytesLeft -= sizeof(uint16_t); if (nBytesLeft < 0) goto out; /* Uncompress MSA Track, first check if is not compressed */ DataLength = do_get_mem_word(pMSAImageBuffer); pMSAImageBuffer += sizeof(uint16_t); if (DataLength == nBytesPerTrack) { nBytesLeft -= DataLength; if (nBytesLeft < 0) goto out; /* No compression on track, simply copy and continue */ memcpy(pImageBuffer, pMSAImageBuffer, nBytesPerTrack); pImageBuffer += nBytesPerTrack; pMSAImageBuffer += DataLength; continue; } /* Uncompress track */ NumBytesUnCompressed = 0; while (NumBytesUnCompressed < nBytesPerTrack) { if (--nBytesLeft < 0) goto out; Byte = *pMSAImageBuffer++; if (Byte != 0xE5) /* Compressed header? */ { *pImageBuffer++ = Byte; /* No, just copy byte */ NumBytesUnCompressed++; } else { nBytesLeft -= 3; if (nBytesLeft < 0) goto out; Data = *pMSAImageBuffer++; /* Byte to copy */ RunLength = do_get_mem_word(pMSAImageBuffer); /* For length */ /* Limit length to size of track, incorrect images may overflow */ if (RunLength+NumBytesUnCompressed > nBytesPerTrack) { fprintf(stderr, "MSA_UnCompress: Illegal run length -> corrupted disk image?\n"); RunLength = nBytesPerTrack - NumBytesUnCompressed; } pMSAImageBuffer += sizeof(uint16_t); for (i = 0; i < RunLength; i++) *pImageBuffer++ = Data; /* Copy byte */ NumBytesUnCompressed += RunLength; } } } } out: if (nBytesLeft < 0) { fprintf(stderr, "MSA error: Premature end of file!\n"); free(pBuffer); pBuffer = NULL; } else { /* Set size of loaded image */ *pImageSize = pImageBuffer-pBuffer; } /* Return pointer to buffer, NULL if failed */ return pBuffer; } /*-----------------------------------------------------------------------*/ /** * Uncompress .MSA file into memory, set number bytes of the disk image and * return a pointer to the buffer. */ uint8_t *MSA_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType) { uint8_t *pMsaFile; uint8_t *pDiskBuffer = NULL; long nFileSize; *pImageSize = 0; /* Read in file */ pMsaFile = File_Read(pszFileName, &nFileSize, NULL); if (pMsaFile) { /* Uncompress into disk buffer */ pDiskBuffer = MSA_UnCompress(pMsaFile, pImageSize, nFileSize); /* Free MSA file we loaded */ free(pMsaFile); } if ( ( pMsaFile == NULL ) || ( pDiskBuffer == NULL ) ) return NULL; *pImageType = FLOPPY_IMAGE_TYPE_MSA; /* Return pointer to buffer, NULL if failed */ return pDiskBuffer; } /*-----------------------------------------------------------------------*/ /** * Return number of bytes of the same byte in the passed buffer * If we return '0' this means no run (or end of buffer) */ static int MSA_FindRunOfBytes(uint8_t *pBuffer, int nBytesInBuffer) { uint8_t ScannedByte; int nTotalRun; bool bMarker; int i; /* Is this the marker? If so, this is at least a run of one. */ bMarker = (*pBuffer == 0xE5); /* Do we enough for a run? */ if (nBytesInBuffer < 2) { if (nBytesInBuffer == 1 && bMarker) return 1; else return 0; } /* OK, scan for run */ nTotalRun = 1; ScannedByte = *pBuffer++; for (i = 1; i < nBytesInBuffer; i++) { if (*pBuffer++ == ScannedByte) nTotalRun++; else break; } /* Was this enough of a run to make a difference? */ if (nTotalRun < 4 && !bMarker) nTotalRun = 0; /* Just store uncompressed */ return nTotalRun; } /*-----------------------------------------------------------------------*/ /** * Save compressed .MSA file from memory buffer. Returns true is all OK */ bool MSA_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize) { #ifdef SAVE_TO_MSA_IMAGES MSAHEADERSTRUCT *pMSAHeader; uint16_t *pMSADataLength; uint8_t *pMSAImageBuffer, *pMSABuffer, *pImageBuffer; uint16_t nSectorsPerTrack, nSides, nCompressedBytes, nBytesPerTrack; bool nRet; int nTracks,nBytesToGo,nBytesRun; int Track,Side; /* Allocate workspace for compressed image */ pMSAImageBuffer = (uint8_t *)malloc(MSA_WORKSPACE_SIZE); if (!pMSAImageBuffer) { perror("MSA_WriteDisk"); return false; } /* Store header */ pMSAHeader = (MSAHEADERSTRUCT *)pMSAImageBuffer; pMSAHeader->ID = be_swap16(0x0E0F); Floppy_FindDiskDetails(pBuffer,ImageSize, &nSectorsPerTrack, &nSides); pMSAHeader->SectorsPerTrack = be_swap16(nSectorsPerTrack); pMSAHeader->Sides = be_swap16(nSides - 1); pMSAHeader->StartingTrack = be_swap16(0); nTracks = ((ImageSize / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides; pMSAHeader->EndingTrack = be_swap16(nTracks - 1); /* Compress image */ pMSABuffer = pMSAImageBuffer + sizeof(MSAHEADERSTRUCT); for (Track = 0; Track < nTracks; Track++) { for (Side = 0; Side < nSides; Side++) { /* Get track data pointer */ nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack; pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track); /* Skip data length (fill in later) */ pMSADataLength = (uint16_t *)pMSABuffer; pMSABuffer += sizeof(uint16_t); /* Compress track */ nBytesToGo = nBytesPerTrack; nCompressedBytes = 0; while (nBytesToGo > 0) { nBytesRun = MSA_FindRunOfBytes(pImageBuffer,nBytesToGo); if (nBytesRun == 0) { /* Just copy byte */ *pMSABuffer++ = *pImageBuffer++; nCompressedBytes++; nBytesRun = 1; } else { /* Store run! */ *pMSABuffer++ = 0xE5; /* Marker */ *pMSABuffer++ = *pImageBuffer; /* Byte, and follow with 16-bit length */ do_put_mem_word(pMSABuffer, nBytesRun); pMSABuffer += sizeof(uint16_t); pImageBuffer += nBytesRun; nCompressedBytes += 4; } nBytesToGo -= nBytesRun; } /* Is compressed track smaller than the original? */ if (nCompressedBytes < nBytesPerTrack) { /* Yes, store size */ do_put_mem_word(pMSADataLength, nCompressedBytes); } else { /* No, just store uncompressed track */ do_put_mem_word(pMSADataLength, nBytesPerTrack); pMSABuffer = ((uint8_t *)pMSADataLength) + 2; pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track); memcpy(pMSABuffer,pImageBuffer, nBytesPerTrack); pMSABuffer += nBytesPerTrack; } } } /* And save to file! */ nRet = File_Save(pszFileName,pMSAImageBuffer, pMSABuffer-pMSAImageBuffer, false); /* Free workspace */ free(pMSAImageBuffer); return nRet; #else /*SAVE_TO_MSA_IMAGES*/ /* Oops, cannot save */ return false; #endif /*SAVE_TO_MSA_IMAGES*/ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ncr5380.c000066400000000000000000000656571504763705000226210ustar00rootroot00000000000000/* * Hatari - NCR 5380 SCSI controller emulation * * Based on scsi.ccp from WinUAE: * * Copyright 2007-2015 Toni Wilen * * Adaptions to Hatari: * * Copyright 2018 Thomas Huth * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ const char NCR5380_fileid[] = "Hatari ncr5380.c"; #include "main.h" #include "configuration.h" #include "cycles.h" #include "cycInt.h" #include "file.h" #include "fdc.h" #include "hdc.h" #include "hatari-glue.h" #include "ioMem.h" #include "log.h" #include "memorySnapShot.h" #include "mfp.h" #include "ncr5380.h" #include "stMemory.h" #include "newcpu.h" #include "tos.h" #define WITH_NCR5380 1 int nScsiPartitions; bool bScsiEmuOn; static SCSI_CTRLR ScsiBus; #define MAX_TOTAL_SCSI_DEVICES 8 #define RAW_SCSI_DEBUG 2 #define NCR5380_DEBUG 1 #define NCR5380_DEBUG_IRQ 0 #ifndef _T #define _T(x) x #endif // raw scsi #define SCSI_IO_BUSY 0x80 #define SCSI_IO_ATN 0x40 #define SCSI_IO_SEL 0x20 #define SCSI_IO_REQ 0x10 #define SCSI_IO_DIRECTION 0x01 #define SCSI_IO_COMMAND 0x02 #define SCSI_IO_MESSAGE 0x04 #define SCSI_SIGNAL_PHASE_FREE -1 #define SCSI_SIGNAL_PHASE_ARBIT -2 #define SCSI_SIGNAL_PHASE_SELECT_1 -3 #define SCSI_SIGNAL_PHASE_SELECT_2 -4 #define SCSI_SIGNAL_PHASE_DATA_OUT 0 #define SCSI_SIGNAL_PHASE_DATA_IN 1 #define SCSI_SIGNAL_PHASE_COMMAND 2 #define SCSI_SIGNAL_PHASE_STATUS 3 #define SCSI_SIGNAL_PHASE_MESSAGE_OUT 6 #define SCSI_SIGNAL_PHASE_MESSAGE_IN 7 #define SCSI_STATUS_GOOD 0x00 #define SCSI_STATUS_CHECK_CONDITION 0x02 #define SCSI_STATUS_CONDITION_MET 0x04 #define SCSI_STATUS_BUSY 0x08 #define SCSI_STATUS_INTERMEDIATE 0x10 #define SCSI_STATUS_ICM 0x14 /* intermediate condition met */ #define SCSI_STATUS_RESERVATION_CONFLICT 0x18 #define SCSI_STATUS_COMMAND_TERMINATED 0x22 #define SCSI_STATUS_QUEUE_FULL 0x28 #define SCSI_STATUS_ACA_ACTIVE 0x30 struct raw_scsi { int io; int bus_phase; bool atn; bool ack; uae_u8 data_write; uae_u8 status; bool databusoutput; int initiator_id, target_id; struct scsi_data *device[MAX_TOTAL_SCSI_DEVICES]; struct scsi_data *target; int msglun; }; struct soft_scsi { uae_u8 regs[9]; struct raw_scsi rscsi; bool irq; int dma_direction; bool dma_active; bool dma_started; bool dma_controller; bool dma_drq; int dmac_direction; int dmac_active; }; struct soft_scsi ncr_soft_scsi; static const uae_s16 outcmd[] = { 0x04, 0x0a, 0x0c, 0x11, 0x2a, 0xaa, 0x15, 0x55, 0x0f, -1 }; static const uae_s16 incmd[] = { 0x01, 0x03, 0x08, 0x0e, 0x12, 0x1a, 0x5a, 0x25, 0x28, 0x34, 0x37, 0x42, 0x43, 0xa8, 0x51, 0x52, 0xb9, 0xbd, 0xd8, 0xd9, 0xbe, -1 }; static const uae_s16 nonecmd[] = { 0x00, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x10, 0x16, 0x17, 0x19, 0x1b, 0x1d, 0x1e, 0x2b, 0x35, 0x45, 0x47, 0x48, 0x49, 0x4b, 0x4e, 0xa5, 0xa9, 0xba, 0xbc, 0xe0, 0xe3, 0xe4, -1 }; static const uae_s16 scsicmdsizes[] = { 6, 10, 10, 12, 16, 12, 10, 6 }; static uae_u8 ncr5380_bget(struct soft_scsi *scsi, int reg); void ncr5380_bput(struct soft_scsi *scsi, int reg, uae_u8 v); static void ncr5380_set_irq(struct soft_scsi *scsi); static int scsi_data_dir(struct scsi_data *sd) { int i; uae_u8 cmd; cmd = sd->cmd[0]; for (i = 0; outcmd[i] >= 0; i++) { if (cmd == outcmd[i]) { return 1; } } for (i = 0; incmd[i] >= 0; i++) { if (cmd == incmd[i]) { return -1; } } for (i = 0; nonecmd[i] >= 0; i++) { if (cmd == nonecmd[i]) { return 0; } } write_log (_T("SCSI command %02X, no direction specified!\n"), cmd); return 0; } static void scsi_start_transfer(struct scsi_data *sd) { // sd->offset = 0; ScsiBus.offset = 0; } static int scsi_send_data(struct scsi_data *sd, uae_u8 b) { if (ScsiBus.offset < 0) { write_log(_T("SCSI data offset is negative!\n")); return 0; } if (sd->direction == 1) { if (ScsiBus.offset >= ScsiBus.buffer_size) { write_log (_T("SCSI data buffer overflow!\n")); return 0; } ScsiBus.buffer[ScsiBus.offset++] = b; } else if (sd->direction == 2) { if (ScsiBus.offset >= 16) { write_log (_T("SCSI command buffer overflow!\n")); return 0; } sd->cmd[ScsiBus.offset++] = b; if (ScsiBus.offset == sd->cmd_len) return 1; } else { write_log (_T("scsi_send_data() without direction! (%02X)\n"), b); return 0; } if (ScsiBus.offset == ScsiBus.data_len) return 1; return 0; } static int scsi_receive_data(struct scsi_data *sd, uae_u8 *b, bool next) { if (!ScsiBus.data_len) { fprintf(stderr, "scsi_receive_data without length!\n"); return -1; } *b = ScsiBus.buffer[ScsiBus.offset]; // fprintf(stderr,"scsi_receive_data %i <-> %i (%i)\n", // ScsiBus.offset, ScsiBus.data_len, next); if (next) { ScsiBus.offset++; if (ScsiBus.offset == ScsiBus.data_len) return 1; // requested length got } return 0; } static void bus_free(struct raw_scsi *rs) { // fprintf(stderr, "BUS FREE!\n"); rs->bus_phase = SCSI_SIGNAL_PHASE_FREE; rs->io = 0; } static int getbit(uae_u8 v) { int i; for (i = 7; i >= 0; i--) { if ((1 << i) & v) return i; } return -1; } static int countbits(uae_u8 v) { int i, cnt = 0; for (i = 7; i >= 0; i--) { if ((1 << i) & v) cnt++; } return cnt; } static void raw_scsi_reset_bus(struct soft_scsi *scsi) { //struct raw_scsi *r = &scsi->rscsi; #if RAW_SCSI_DEBUG write_log(_T("SCSI BUS reset\n")); #endif #if 0 /* FIXME */ for (int i = 0; i < 8; i++) { scsi_emulate_reset_device(r->device[i]); } #endif } static void raw_scsi_set_databus(struct raw_scsi *rs, bool databusoutput) { rs->databusoutput = databusoutput; } static void raw_scsi_set_signal_phase(struct raw_scsi *rs, bool busy, bool select, bool atn) { // fprintf(stderr,"raw_scsi_set_signal_phase busy=%i sel=%i atn=%i phase=%i\n", // busy, select, atn, rs->bus_phase); switch (rs->bus_phase) { case SCSI_SIGNAL_PHASE_FREE: if (busy && !select && !rs->databusoutput) { if (countbits(rs->data_write) != 1) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: invalid arbitration scsi id mask! (%02x)\n"), rs->data_write); #endif return; } rs->bus_phase = SCSI_SIGNAL_PHASE_ARBIT; rs->initiator_id = getbit(rs->data_write); #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: arbitration initiator id %d (%02x)\n"), rs->initiator_id, rs->data_write); #endif } else if (!busy && select) { if (countbits(rs->data_write) > 2 || rs->data_write == 0) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: invalid scsi id selected mask (%02x)\n"), rs->data_write); #endif return; } rs->initiator_id = -1; rs->bus_phase = SCSI_SIGNAL_PHASE_SELECT_1; #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: selected scsi id mask (%02x)\n"), rs->data_write); #endif raw_scsi_set_signal_phase(rs, busy, select, atn); } break; case SCSI_SIGNAL_PHASE_ARBIT: rs->target_id = -1; rs->target = NULL; ScsiBus.target = -1; if (busy && select) { rs->bus_phase = SCSI_SIGNAL_PHASE_SELECT_1; } break; case SCSI_SIGNAL_PHASE_SELECT_1: rs->atn = atn; rs->msglun = -1; rs->target_id = -1; ScsiBus.target = -1; if (!busy) { int i; for (i = 0; i < 8; i++) { if (i == rs->initiator_id) continue; if ((rs->data_write & (1 << i)) && rs->device[i]) { rs->target_id = i; rs->target = rs->device[rs->target_id]; ScsiBus.target = i; #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: selected id %d\n"), rs->target_id); #endif rs->io |= SCSI_IO_BUSY; } } #if RAW_SCSI_DEBUG if (rs->target_id < 0) { for (i = 0; i < 8; i++) { if (i == rs->initiator_id) continue; if ((rs->data_write & (1 << i)) && !rs->device[i]) { write_log(_T("raw_scsi: selected non-existing id %d\n"), i); } } } #endif if (rs->target_id >= 0) { rs->bus_phase = SCSI_SIGNAL_PHASE_SELECT_2; } else { if (!select) { rs->bus_phase = SCSI_SIGNAL_PHASE_FREE; } } } break; case SCSI_SIGNAL_PHASE_SELECT_2: if (!select) { scsi_start_transfer(rs->target); rs->bus_phase = rs->atn ? SCSI_SIGNAL_PHASE_MESSAGE_OUT : SCSI_SIGNAL_PHASE_COMMAND; rs->io = SCSI_IO_BUSY | SCSI_IO_REQ; } break; } } static uae_u8 raw_scsi_get_signal_phase(struct raw_scsi *rs) { uae_u8 v = rs->io; if (rs->bus_phase >= 0) v |= rs->bus_phase; if (rs->ack) v &= ~SCSI_IO_REQ; return v; } static uae_u8 raw_scsi_get_data_2(struct raw_scsi *rs, bool next, bool nodebug) { struct scsi_data *sd = rs->target; uae_u8 v = 0; switch (rs->bus_phase) { case SCSI_SIGNAL_PHASE_FREE: v = 0; break; case SCSI_SIGNAL_PHASE_ARBIT: #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: arbitration\n")); #endif v = rs->data_write; break; case SCSI_SIGNAL_PHASE_DATA_IN: #if RAW_SCSI_DEBUG > 2 scsi_receive_data(sd, &v, false); write_log(_T("raw_scsi: read data byte %02x (%d/%d)\n"), v, ScsiBus.offset, ScsiBus.data_len); #endif if (scsi_receive_data(sd, &v, next)) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: data in finished, %d bytes: status phase\n"), ScsiBus.offset); #endif rs->bus_phase = SCSI_SIGNAL_PHASE_STATUS; } break; case SCSI_SIGNAL_PHASE_STATUS: #if RAW_SCSI_DEBUG if (!nodebug || next) write_log(_T("raw_scsi: status byte read %02x. Next=%d\n"), ScsiBus.status, next); #endif v = ScsiBus.status; // sd->status; if (next) { ScsiBus.status = 0; // sd->status = 0; rs->bus_phase = SCSI_SIGNAL_PHASE_MESSAGE_IN; } break; case SCSI_SIGNAL_PHASE_MESSAGE_IN: #if RAW_SCSI_DEBUG if (!nodebug || next) write_log(_T("raw_scsi: message byte read %02x. Next=%d\n"), ScsiBus.status, next); #endif v = ScsiBus.status; // sd->status; rs->status = v; if (next) { bus_free(rs); } break; default: #if RAW_SCSI_DEBUG write_log(_T("raw_scsi_get_data but bus phase is %d!\n"), rs->bus_phase); #endif break; } return v; } static uae_u8 raw_scsi_get_data(struct raw_scsi *rs, bool next) { return raw_scsi_get_data_2(rs, next, true); } static int getmsglen(uae_u8 *msgp, int len) { uae_u8 msg = msgp[0]; if (msg == 0 || (msg >= 0x02 && msg <= 0x1f) || msg >= 0x80) return 1; if (msg >= 0x20 && msg <= 0x2f) return 2; // extended message, at least 3 bytes if (len < 2) return 3; return msgp[1]; } static bool scsi_emulate_analyze (struct scsi_data *sd) { int cmd_len, data_len; data_len = ScsiBus.data_len; cmd_len = scsicmdsizes[sd->cmd[0] >> 5]; sd->cmd_len = cmd_len; switch (sd->cmd[0]) { case 0x04: // FORMAT UNIT // FmtData set? if (sd->cmd[1] & 0x10) { // int cl = (sd->cmd[1] & 8) != 0; // int dlf = sd->cmd[1] & 7; //data_len2 = 4; } else { sd->direction = 0; ScsiBus.data_len = 0; return true; } break; case 0x06: // FORMAT TRACK case 0x07: // FORMAT BAD TRACK sd->direction = 0; ScsiBus.data_len = 0; return true; case 0x0c: // INITIALIZE DRIVE CHARACTERICS (SASI) data_len = 8; break; case 0x08: // READ(6) break; case 0x11: // ASSIGN ALTERNATE TRACK (SASI) data_len = 4; break; case 0x28: // READ(10) break; case 0xa8: // READ(12) break; case 0x0f: // WRITE SECTOR BUFFER data_len = 512; //sd->blocksize; break; case 0x0a: // WRITE(6) data_len = (sd->cmd[4] == 0 ? 256 : sd->cmd[4]) * 512; //sd->blocksize; break; case 0x2a: // WRITE(10) data_len = ((sd->cmd[7] << 8) | (sd->cmd[8] << 0)) * 512; //sd->blocksize; break; /* case 0xaa: // WRITE(12) data_len = ((sd->cmd[6] << 24) | (sd->cmd[7] << 16) | (sd->cmd[8] << 8) | (sd->cmd[9] << 0)) * 512; //sd->blocksize; break; */ case 0x2f: // VERIFY if (sd->cmd[1] & 2) { ScsiBus.data_len = ((sd->cmd[7] << 8) | (sd->cmd[8] << 0)) * 512; // sd->blocksize; sd->direction = 1; } else { ScsiBus.data_len = 0; sd->direction = 0; } return true; } if (data_len < 0) { if (cmd_len == 6) { ScsiBus.data_len = sd->cmd[4]; } else { ScsiBus.data_len = (sd->cmd[7] << 8) | sd->cmd[8]; } } else { ScsiBus.data_len = data_len; } sd->direction = scsi_data_dir(sd); if (sd->direction > 0 && ScsiBus.data_len == 0) { sd->direction = 0; } return true; } static void scsi_emulate_cmd(struct scsi_data *sd) { int i; // fprintf(stderr,"scsi_emulate_cmd cmdlen=%i offset=%i\n", sd->cmd_len, ScsiBus.offset); ScsiBus.byteCount = 0; for (i = 0; i < sd->cmd_len; i++) { HDC_WriteCommandPacket(&ScsiBus, sd->cmd[i]); } } static void raw_scsi_write_data(struct raw_scsi *rs, uae_u8 data) { struct scsi_data *sd = rs->target; int len; switch (rs->bus_phase) { case SCSI_SIGNAL_PHASE_SELECT_1: case SCSI_SIGNAL_PHASE_FREE: break; case SCSI_SIGNAL_PHASE_COMMAND: sd->cmd[ScsiBus.offset++] = data; len = scsicmdsizes[sd->cmd[0] >> 5]; #if RAW_SCSI_DEBUG > 1 write_log(_T("raw_scsi: got command byte %02x (%d/%d)\n"), data, ScsiBus.offset, len); #endif if (ScsiBus.offset >= len) { if (rs->msglun >= 0) { sd->cmd[1] &= ~(0x80 | 0x40 | 0x20); sd->cmd[1] |= rs->msglun << 5; } scsi_emulate_analyze(rs->target); if (sd->direction > 0) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: data out %d bytes required\n"), ScsiBus.data_len); #endif scsi_emulate_cmd(sd); /* Hatari only */ scsi_start_transfer(sd); rs->bus_phase = SCSI_SIGNAL_PHASE_DATA_OUT; } else if (sd->direction <= 0) { scsi_emulate_cmd(sd); scsi_start_transfer(sd); if (!ScsiBus.status && ScsiBus.data_len > 0) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: data in %d bytes waiting\n"), ScsiBus.data_len); #endif rs->bus_phase = SCSI_SIGNAL_PHASE_DATA_IN; } else { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: no data, status = %d\n"), ScsiBus.status); #endif rs->bus_phase = SCSI_SIGNAL_PHASE_STATUS; } } } break; case SCSI_SIGNAL_PHASE_DATA_OUT: #if RAW_SCSI_DEBUG > 2 write_log(_T("raw_scsi: write data byte %02x (%d/%d)\n"), data, ScsiBus.offset, ScsiBus.data_len); #endif if (scsi_send_data(sd, data)) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi: data out finished, %d bytes\n"), ScsiBus.data_len); #endif if (ScsiBus.dmawrite_to_fh) { int r; r = fwrite(ScsiBus.buffer, 1, ScsiBus.data_len, ScsiBus.dmawrite_to_fh); if (r != ScsiBus.data_len) { Log_Printf(LOG_ERROR, "Could not write bytes to HD image (%d/%d).\n", r, ScsiBus.data_len); ScsiBus.status = HD_STATUS_ERROR; } ScsiBus.dmawrite_to_fh = NULL; } rs->bus_phase = SCSI_SIGNAL_PHASE_STATUS; } break; case SCSI_SIGNAL_PHASE_MESSAGE_OUT: sd->msgout[ScsiBus.offset++] = data; len = getmsglen(sd->msgout, ScsiBus.offset); #if RAW_SCSI_DEBUG write_log(_T("raw_scsi_put_data got message %02x (%d/%d)\n"), data, ScsiBus.offset, len); #endif if (ScsiBus.offset >= len) { #if RAW_SCSI_DEBUG write_log(_T("raw_scsi_put_data got message %02x (%d bytes)\n"), sd->msgout[0], len); #endif if ((sd->msgout[0] & (0x80 | 0x20)) == 0x80) rs->msglun = sd->msgout[0] & 7; scsi_start_transfer(sd); rs->bus_phase = SCSI_SIGNAL_PHASE_COMMAND; } break; default: #if RAW_SCSI_DEBUG write_log(_T("raw_scsi_put_data but bus phase is %d!\n"), rs->bus_phase); #endif break; } } static void raw_scsi_put_data(struct raw_scsi *rs, uae_u8 data, bool databusoutput) { rs->data_write = data; if (!databusoutput) return; raw_scsi_write_data(rs, data); } static void raw_scsi_set_ack(struct raw_scsi *rs, bool ack) { if (rs->ack != ack) { rs->ack = ack; if (!ack) return; if (rs->bus_phase < 0) return; if (!(rs->bus_phase & SCSI_IO_DIRECTION)) { if (rs->databusoutput) { raw_scsi_write_data(rs, rs->data_write); } } else { raw_scsi_get_data_2(rs, true, false); } } } static void Ncr5380_UpdateDmaAddrAndLen(uint32_t nDmaAddr, uint32_t nDataLen) { uint32_t nNewAddr = nDmaAddr + nDataLen; uint32_t nNewLen; if (Config_IsMachineFalcon()) { FDC_WriteDMAAddress(nNewAddr); } else { IoMem[0xff8701] = nNewAddr >> 24; IoMem[0xff8703] = nNewAddr >> 16; IoMem[0xff8705] = nNewAddr >> 8; IoMem[0xff8707] = nNewAddr; nNewLen = (uint32_t)IoMem[0xff8709] << 24 | IoMem[0xff870b] << 16 | IoMem[0xff870d] << 8 | IoMem[0xff870f]; assert(nDataLen <= nNewLen); nNewLen -= nDataLen; IoMem[0xff8709] = nNewLen >> 24; IoMem[0xff870b] = nNewLen >> 16; IoMem[0xff870d] = nNewLen >> 8; IoMem[0xff870f] = nNewLen; } } static void dma_check(struct soft_scsi *ncr) { int i, nDataLen; uint32_t nDmaAddr; // fprintf(stderr, "dma_check: dma_direction=%i data_len=%i/%i phase=%i %i active=%i \n", // ncr->dma_direction, ScsiBus.offset, ScsiBus.data_len, ncr->rscsi.bus_phase, ncr->regs[3] & 7, ncr->dma_active); /* Don't do anything if nothing to transfer */ if (ScsiBus.data_len - ScsiBus.offset == 0 || !ncr->dma_active || !ncr->dma_direction) return; if (Config_IsMachineFalcon()) { /* Is DMA really active? */ if ((FDC_DMA_GetMode() & 0xc0) != 0x00) return; nDmaAddr = FDC_GetDMAAddress(); nDataLen = FDC_DMA_GetSectorCount() * 512; } else { if ((IoMem[0xff8715] & 2) == 0) return; nDmaAddr = (uint32_t)IoMem[0xff8701] << 24 | IoMem[0xff8703] << 16 | IoMem[0xff8705] << 8 | IoMem[0xff8707]; nDataLen = (uint32_t)IoMem[0xff8709] << 24 | IoMem[0xff870b] << 16 | IoMem[0xff870d] << 8 | IoMem[0xff870f]; } if (nDataLen > ScsiBus.data_len - ScsiBus.offset) nDataLen = ScsiBus.data_len - ScsiBus.offset; if (ncr_soft_scsi.dma_direction < 0) { if (STMemory_CheckAreaType(nDmaAddr, nDataLen, ABFLAG_RAM | ABFLAG_ROM)) { for (i = 0; i < nDataLen; i++) { uint8_t val = ncr5380_bget(ncr, 8); STMemory_WriteByte(nDmaAddr + i, val); } ScsiBus.bDmaError = false; } else { ScsiBus.bDmaError = true; ScsiBus.status = HD_STATUS_ERROR; } if (Config_IsMachineFalcon()) { /* Note that the Falcon's DMA chip seems to report an * end address that is 16 bytes too high if the DATA IN * phase was interrupted by a different phase, but the * address is correct if there was no interruption. */ if (ScsiBus.offset < ScsiBus.data_len) Ncr5380_UpdateDmaAddrAndLen(nDmaAddr, nDataLen + 16); else Ncr5380_UpdateDmaAddrAndLen(nDmaAddr, nDataLen); } else { int nRemainingBytes = IoMem[0xff8707] & 3; Ncr5380_UpdateDmaAddrAndLen(nDmaAddr, nDataLen); for (i = 0; i < nRemainingBytes; i++) { /* For more precise emulation, we should not * pre-write the bytes to the STRam ... */ const uint32_t addr = nDmaAddr + nDataLen - nRemainingBytes; IoMem[0xff8710 + i] = STMemory_ReadByte(addr + i); } } } else if (ncr_soft_scsi.dma_direction > 0 && ScsiBus.dmawrite_to_fh) { /* write - if allowed */ if (STMemory_CheckAreaType(nDmaAddr, nDataLen, ABFLAG_RAM | ABFLAG_ROM)) { for (i = 0; i < nDataLen; i++) { uint8_t val = STMemory_ReadByte(nDmaAddr + i); ncr5380_bput(ncr, 8, val); } } else { Log_Printf(LOG_WARN, "SCSI DMA write uses invalid RAM range 0x%x+%i\n", nDmaAddr, nDataLen); ScsiBus.bDmaError = true; ScsiBus.status = HD_STATUS_ERROR; } Ncr5380_UpdateDmaAddrAndLen(nDmaAddr, nDataLen); } if (Config_IsMachineFalcon()) FDC_SetDMAStatus(ScsiBus.bDmaError); /* Set/Unset DMA error */ ncr5380_set_irq ( ncr ); if (ScsiBus.offset == ScsiBus.data_len) { ncr->dmac_active = 0; ncr->dma_active = 0; } } static void ncr5380_set_irq(struct soft_scsi *scsi) { if (scsi->irq) return; scsi->irq = true; #if 0 /* FIXME */ devices_rethink_all(ncr80_rethink); if (scsi->delayed_irq) x_do_cycles(2 * CYCLE_UNIT); #endif #if NCR5380_DEBUG_IRQ write_log(_T("IRQ\n")); #endif if (Config_IsMachineFalcon()) FDC_SetIRQ(FDC_IRQ_SOURCE_HDC); else if (Config_IsMachineTT()) MFP_GPIP_Set_Line_Input ( pMFP_TT , MFP_TT_GPIP_LINE_SCSI_NCR , MFP_GPIP_STATE_HIGH ); } static void ncr5380_databusoutput(struct soft_scsi *scsi) { bool databusoutput = (scsi->regs[1] & 1) != 0; struct raw_scsi *r = &scsi->rscsi; if (r->bus_phase >= 0 && (r->bus_phase & SCSI_IO_DIRECTION)) databusoutput = false; raw_scsi_set_databus(r, databusoutput); } static void ncr5380_check(struct soft_scsi *scsi) { ncr5380_databusoutput(scsi); } static void ncr5380_check_phase(struct soft_scsi *scsi) { if (!(scsi->regs[2] & 2)) return; if (scsi->regs[2] & 0x40) return; if (scsi->rscsi.bus_phase != (scsi->regs[3] & 7)) { if (scsi->dma_controller) { scsi->regs[5] |= 0x80; // end of dma scsi->regs[3] |= 0x80; // last byte sent } ncr5380_set_irq(scsi); } } static void ncr5380_reset(struct soft_scsi *scsi) { memset(scsi->regs, 0, sizeof scsi->regs); raw_scsi_reset_bus(scsi); scsi->regs[1] = 0x80; ncr5380_set_irq(scsi); } static uae_u8 ncr5380_bget(struct soft_scsi *scsi, int reg) { if (reg > 8) return 0; uae_u8 v = scsi->regs[reg]; struct raw_scsi *r = &scsi->rscsi; switch(reg) { case 1: break; case 4: { uae_u8 t = raw_scsi_get_signal_phase(r); v = 0; if (t & SCSI_IO_BUSY) v |= 1 << 6; if (t & SCSI_IO_REQ) v |= 1 << 5; if (t & SCSI_IO_SEL) v |= 1 << 1; if (r->bus_phase >= 0) v |= r->bus_phase << 2; if (scsi->regs[1] & 0x80) v |= 0x80; } break; case 5: { uae_u8 t = raw_scsi_get_signal_phase(r); v &= (0x80 | 0x40 | 0x20 | 0x04); if (t & SCSI_IO_ATN) v |= 1 << 1; if (r->bus_phase == (scsi->regs[3] & 7)) { v |= 1 << 3; } if (scsi->irq) { v |= 1 << 4; } if (scsi->dma_drq || (scsi->dma_active && !scsi->dma_controller && r->bus_phase == (scsi->regs[3] & 7))) { scsi->dma_drq = true; v |= 1 << 6; } if (scsi->regs[2] & 4) { // monitor busy if (r->bus_phase == SCSI_SIGNAL_PHASE_FREE) { // any loss of busy = Busy error // not just "unexpected" loss of busy v |= 1 << 2; scsi->dmac_active = false; } } } break; case 0: v = raw_scsi_get_data(r, false); break; case 6: v = raw_scsi_get_data(r, scsi->dma_active); ncr5380_check_phase(scsi); break; case 7: scsi->irq = false; if (Config_IsMachineFalcon()) FDC_ClearIRQ(); break; case 8: // fake dma port v = raw_scsi_get_data(r, true); ncr5380_check_phase(scsi); break; } ncr5380_check(scsi); return v; } void ncr5380_bput(struct soft_scsi *scsi, int reg, uae_u8 v) { if (reg > 8) return; bool dataoutput = (scsi->regs[1] & 1) != 0; struct raw_scsi *r = &scsi->rscsi; uae_u8 old = scsi->regs[reg]; scsi->regs[reg] = v; switch(reg) { case 0: { r->data_write = v; // assert data bus can be only active if direction is out // and bus phase matches if (r->databusoutput) { if (((scsi->regs[2] & 2) && scsi->dma_active) || r->bus_phase < 0) { raw_scsi_write_data(r, v); ncr5380_check_phase(scsi); } } } break; case 1: { scsi->regs[reg] &= ~((1 << 5) | (1 << 6)); scsi->regs[reg] |= old & ((1 << 5) | (1 << 6)); // AIP, LA if (!(v & 0x80)) { bool init = r->bus_phase < 0; ncr5380_databusoutput(scsi); if (init && !dataoutput && (v & 1) && (scsi->regs[2] & 1)) { r->bus_phase = SCSI_SIGNAL_PHASE_SELECT_1; } raw_scsi_set_signal_phase(r, (v & (1 << 3)) != 0, (v & (1 << 2)) != 0, (v & (1 << 1)) != 0); if (!(scsi->regs[2] & 2)) raw_scsi_set_ack(r, (v & (1 << 4)) != 0); } if (v & 0x80) { // RST ncr5380_reset(scsi); } } break; case 2: if ((v & 1) && !(old & 1)) { // Arbitrate r->databusoutput = false; raw_scsi_set_signal_phase(r, true, false, false); scsi->regs[1] |= 1 << 6; // AIP scsi->regs[1] &= ~(1 << 5); // LA } else if (!(v & 1) && (old & 1)) { scsi->regs[1] &= ~(1 << 6); } if (!(v & 2)) { // end of dma and dma request scsi->regs[5] &= ~(0x80 | 0x40); scsi->dma_direction = 0; scsi->dma_active = false; scsi->dma_drq = false; } break; case 5: scsi->regs[reg] = old; if (scsi->regs[2] & 2) { scsi->dma_direction = 1; scsi->dma_active = true; dma_check(scsi); } #if NCR5380_DEBUG write_log(_T("DMA send PC=%08x\n"), M68K_GETPC); #endif break; case 6: if (scsi->regs[2] & 2) { scsi->dma_direction = 1; scsi->dma_active = true; scsi->dma_started = true; dma_check(scsi); } #if NCR5380_DEBUG write_log(_T("DMA target recv PC=%08x\n"), M68K_GETPC); #endif break; case 7: if (scsi->regs[2] & 2) { scsi->dma_direction = -1; scsi->dma_active = true; scsi->dma_started = true; dma_check(scsi); } #if NCR5380_DEBUG write_log(_T("DMA initiator recv PC=%08x\n"), M68K_GETPC); #endif break; case 8: // fake dma port if (r->bus_phase == (scsi->regs[3] & 7)) { raw_scsi_put_data(r, v, true); } ncr5380_check_phase(scsi); break; } ncr5380_check(scsi); } /* ***** Hatari glue code below ***** */ /** * Open the disk image file, set partitions count. * Return true if there are any. */ bool Ncr5380_Init(void) { #if WITH_NCR5380 int i; nScsiPartitions = 0; bScsiEmuOn = false; memset(&ScsiBus, 0, sizeof(ScsiBus)); ScsiBus.typestr = "SCSI"; ScsiBus.buffer_size = 512; ScsiBus.buffer = malloc(ScsiBus.buffer_size); if (!ScsiBus.buffer) { perror("Ncr5380_Init"); return 0; } for (i = 0; i < MAX_SCSI_DEVS; i++) { if (!ConfigureParams.Scsi[i].bUseDevice) continue; if (HDC_InitDevice("SCSI", &ScsiBus.devs[i], &ConfigureParams.Scsi[i]) == 0) { nScsiPartitions += HDC_PartitionCount(ScsiBus.devs[i].image_file, TRACE_SCSI_CMD, NULL); bScsiEmuOn = true; } else ConfigureParams.Scsi[i].bUseDevice = false; } nNumDrives += nScsiPartitions; #endif return bScsiEmuOn; } /** * Ncr5380_UnInit - close image files and free resources * */ void Ncr5380_UnInit(void) { #if WITH_NCR5380 int i; for (i = 0; i < MAX_SCSI_DEVS; i++) { if (!ScsiBus.devs[i].enabled) continue; File_UnLock(ScsiBus.devs[i].image_file); fclose(ScsiBus.devs[i].image_file); ScsiBus.devs[i].image_file = NULL; ScsiBus.devs[i].enabled = false; } free(ScsiBus.buffer); ScsiBus.buffer = NULL; nNumDrives -= nScsiPartitions; nScsiPartitions = 0; bScsiEmuOn = false; #endif } /** * Emulate external reset "pin": Clear registers etc. */ void Ncr5380_Reset(void) { #if WITH_NCR5380 int i; ncr5380_reset(&ncr_soft_scsi); bus_free(&ncr_soft_scsi.rscsi); for (i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) { if (ScsiBus.devs[i].enabled) { ncr_soft_scsi.rscsi.device[i] = &ScsiBus.devs[i]; // fprintf(stderr, "SCSI #%i enabled\n", i); } else { ncr_soft_scsi.rscsi.device[i] = NULL; } } #endif } /** * Write a command byte to the NCR 5380 SCSI controller */ void Ncr5380_WriteByte(int addr, uint8_t byte) { #if WITH_NCR5380 ncr5380_bput(&ncr_soft_scsi, addr, byte); #endif } /** * Read a command byte from the NCR 5380 SCSI controller */ uint8_t Ncr5380_ReadByte(int addr) { #if WITH_NCR5380 return ncr5380_bget(&ncr_soft_scsi, addr); #else return 0; #endif } void Ncr5380_DmaTransfer_Falcon(void) { dma_check(&ncr_soft_scsi); } void Ncr5380_IoMemTT_WriteByte(void) { while (nIoMemAccessSize > 0) { if (IoAccessBaseAddress & 1) { int addr = IoAccessBaseAddress / 2 & 0x7; Ncr5380_WriteByte(addr, IoMem[IoAccessBaseAddress]); } IoAccessBaseAddress++; nIoMemAccessSize--; } } void Ncr5380_IoMemTT_ReadByte(void) { while (nIoMemAccessSize > 0) { if (IoAccessBaseAddress & 1) { int addr = IoAccessBaseAddress / 2 & 0x7; IoMem[IoAccessBaseAddress] = Ncr5380_ReadByte(addr); } IoAccessBaseAddress++; nIoMemAccessSize--; } } void Ncr5380_TT_DMA_Ctrl_WriteWord(void) { if (IoMem[0xff8715] & 2) dma_check(&ncr_soft_scsi); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/nf_scsidrv.c000066400000000000000000000335641504763705000236470ustar00rootroot00000000000000/* * Hatari - nf_scsidrv.c * * Copyright (C) 2015-2016, 2018, 2024 by Uwe Seimet * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * nf_scsidrv.c - Implementation of the host system part of a SCSI Driver * (Linux only), based on the Linux SG driver version 3. The corresponding * TOS binary and its source code can be downloaded from * http://hddriver.seimet.de/en/downloads.html, where you can also find * information on the open SCSI Driver standard. */ const char NfScsiDrv_fileid[] = "Hatari nf_scsidrv.c"; #if defined(__linux__) #include "config.h" #include #include #include #if HAVE_UDEV #include #endif #include #include #include #include "stMemory.h" #include "log.h" #include "gemdos_defines.h" #include "m68000.h" #include "nf_scsidrv.h" // The driver interface version, 1.02 #define INTERFACE_VERSION 0x0102 // Maximum is 20 characters // (newer TOS side SCSI driver versions will ignore the name) #define BUS_NAME "Linux Generic SCSI" // The SG driver supports cAllCmds #define BUS_FEATURES 0x02 // The transfer length may depend on the device, 65536 should always be safe #define BUS_TRANSFER_LEN 65536 // The maximum number of SCSI Driver handles, must be the same as in the stub #define SCSI_MAX_HANDLES 32 // The number of sense data bytes #define SENSE_LENGTH 18 typedef struct { int fd; int id_lo; int error; } HANDLE_META_DATA; static HANDLE_META_DATA handle_meta_data[SCSI_MAX_HANDLES]; // The current sense data static uint8_t sense_data[SENSE_LENGTH]; // The deferred sense data for 16 devices static uint8_t deferred_sense_data[16][SENSE_LENGTH]; static bool deferred_sense_data_valid[16]; #if HAVE_UDEV static struct udev *udev; static struct udev_monitor *mon; static int udev_mon_fd; static struct timeval tv; #endif static uint32_t read_stack_long(uint32_t *stack) { uint32_t value = STMemory_ReadLong(*stack); *stack += SIZE_LONG; return value; } static void *read_stack_pointer(uint32_t *stack) { uint32_t ptr = read_stack_long(stack); return ptr ? STMemory_STAddrToPointer(ptr) : 0; } static void write_long(uint32_t addr, uint32_t value) { STMemory_WriteLong(addr, value); } static void write_word(uint32_t addr, uint16_t value) { STMemory_WriteWord(addr, value); } // Sets the error status static void set_error(uint32_t handle, int errbit) { uint32_t i; for (i = 0; i < SCSI_MAX_HANDLES; i++) { if (handle != i && handle_meta_data[i].fd && handle_meta_data[i].id_lo == handle_meta_data[handle].id_lo) { handle_meta_data[i].error |= errbit; } } } // udev-based check for media change. When udev is active media change messages // are handled globally by udev, i.e. that media changes cannot directly be // detected by the SCSI Driver. The SCSI Driver has to query udev instead. static bool check_mchg_udev(void) { bool changed = false; #if HAVE_UDEV fd_set udevFds; int ret; FD_ZERO(&udevFds); FD_SET(udev_mon_fd, &udevFds); ret = select(udev_mon_fd + 1, &udevFds, 0, 0, &tv); if (ret > 0 && FD_ISSET(udev_mon_fd, &udevFds)) { struct udev_device *dev = udev_monitor_receive_device(mon); while (dev) { if (!changed) { const char *dev_type = udev_device_get_devtype(dev); const char *action = udev_device_get_action(dev); if (!strcmp("disk", dev_type) && !strcmp("change", action)) { LOG_TRACE(TRACE_SCSIDRV, ": %s has been changed", udev_device_get_devnode(dev)); // TODO Determine sg device name from block device name and // only report media change for the actually affected device changed = true; } } // Process all pending events dev = udev_monitor_receive_device(mon); } } #endif return changed; } // Checks whether a device exists by checking for the device file name static int check_device_file(uint32_t id) { char device_file[16]; sprintf(device_file, "/dev/sg%d", id); if (!access(device_file, R_OK | W_OK)) { LOG_TRACE(TRACE_SCSIDRV, ", device file %s is accessible", device_file); return 0; } else { LOG_TRACE(TRACE_SCSIDRV, ", device file %s is inaccessible", device_file); return -1; } } static int scsidrv_interface_version(uint32_t stack) { LOG_TRACE(TRACE_SCSIDRV, "scsidrv_interface_version: version=$%04x", INTERFACE_VERSION); return INTERFACE_VERSION; } static int scsidrv_interface_features(uint32_t stack) { uint32_t st_bus_name = STMemory_ReadLong(stack); char *busName = read_stack_pointer(&stack); uint32_t features = read_stack_long(&stack); uint32_t transferLen = read_stack_long(&stack); LOG_TRACE(TRACE_SCSIDRV, "scsidrv_interface_features: busName=%s, features=$%04x, transferLen=%d", BUS_NAME, BUS_FEATURES, BUS_TRANSFER_LEN); if ( !STMemory_CheckAreaType ( st_bus_name, 20, ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "scsidrv_interface_features: Invalid RAM range 0x%x+%i\n", st_bus_name, 20); return -1; } strncpy(busName, BUS_NAME, 20); M68000_Flush_Data_Cache(st_bus_name, 20); write_word(features, BUS_FEATURES); write_long(transferLen, BUS_TRANSFER_LEN); return 0; } // SCSI Driver: InquireBus() static int scsidrv_inquire_bus(uint32_t stack) { uint32_t id = read_stack_long(&stack); char device_file[16]; LOG_TRACE(TRACE_SCSIDRV, "scsidrv_inquire_bus: id=%d", id); sprintf(device_file, "/dev/sg%d", id); while (!access(device_file, F_OK)) { if (!check_device_file(id)) { return id; } sprintf(device_file, "/dev/sg%d", ++id); } return -1; } // SCSI Driver: Open() static int scsidrv_open(uint32_t stack) { char device_file[16]; uint32_t handle; uint32_t id; int fd; #if HAVE_UDEV if (!udev) { udev = udev_new(); if (!udev) { return -1; } mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "block", NULL); udev_monitor_enable_receiving(mon); udev_mon_fd = udev_monitor_get_fd(mon); tv.tv_sec = 0; tv.tv_usec = 0; } #endif handle = read_stack_long(&stack); id = read_stack_long(&stack); LOG_TRACE(TRACE_SCSIDRV, "scsidrv_open: handle=%d, id=%d", handle, id); if (handle >= SCSI_MAX_HANDLES || handle_meta_data[handle].fd || check_device_file(id)) { return GEMDOS_ENHNDL; } sprintf(device_file, "/dev/sg%d", id); fd = open(device_file, O_RDWR | O_NONBLOCK); if (fd < 0) { return fd; } handle_meta_data[handle].fd = fd; handle_meta_data[handle].id_lo = id; handle_meta_data[handle].error = 0; return 0; } // SCSI Driver: Close() static int scsidrv_close(uint32_t stack) { uint32_t handle = read_stack_long(&stack); LOG_TRACE(TRACE_SCSIDRV, "scsidrv_close: handle=%d", handle); if (handle >= SCSI_MAX_HANDLES || !handle_meta_data[handle].fd) { return GEMDOS_ENHNDL; } close(handle_meta_data[handle].fd); handle_meta_data[handle].fd = 0; return 0; } // SCSI Driver: In() and Out() static int scsidrv_inout(uint32_t stack) { uint32_t handle = read_stack_long(&stack); uint32_t dir = read_stack_long(&stack); unsigned char *cmd = read_stack_pointer(&stack); uint32_t cmd_len = read_stack_long(&stack); uint32_t st_buffer = STMemory_ReadLong(stack); unsigned char *buffer = read_stack_pointer(&stack); uint32_t transfer_len = read_stack_long(&stack); uint32_t st_sense_buffer = STMemory_ReadLong(stack); unsigned char *sense_buffer = read_stack_pointer(&stack); uint32_t timeout = read_stack_long(&stack); int status; if (LOG_TRACE_LEVEL(TRACE_SCSIDRV)) { LOG_TRACE_DIRECT_INIT(); LOG_TRACE_DIRECT( "scsidrv_inout: handle=%d, dir=%d, cmd_len=%d, buffer=%p,\n" " transfer_len=%d, sense_buffer=%p, timeout=%d,\n" " cmd=", handle, dir, cmd_len, buffer, transfer_len, sense_buffer, timeout); uint32_t i; for (i = 0; i < cmd_len; i++) { LOG_TRACE_DIRECT("%s$%02X", i?":":"", cmd[i]); } LOG_TRACE_DIRECT_FLUSH(); } // Writing is allowed with a RAM or ROM address, // reading requires a RAM address if ( !STMemory_CheckAreaType ( st_buffer, transfer_len, dir ? ABFLAG_RAM | ABFLAG_ROM : ABFLAG_RAM ) ) { Log_Printf(LOG_WARN, "scsidrv_inout: Invalid RAM range 0x%x+%i\n", st_buffer, transfer_len); return -1; } if (handle >= SCSI_MAX_HANDLES || !handle_meta_data[handle].fd) { return GEMDOS_ENHNDL; } if (sense_buffer) { memset(sense_buffer, 0, SENSE_LENGTH); } // There is no explicit LUN support, the SG driver maps each LUN to a device file if ((cmd[1] & 0b11100000) && cmd[0] != INQUIRY) { // REQUEST SENSE has a special handling for non-existing LUNs if (cmd[0] == REQUEST_SENSE) { memset(buffer, 0, SENSE_LENGTH); // LOGICAL UNIT NOT SUPPORTED buffer[0] = 0x70; buffer[2] = ILLEGAL_REQUEST; buffer[7] = 10; buffer[12] = 0x25; M68000_Flush_Data_Cache(st_buffer, SENSE_LENGTH); // When signalling an invalid LUN, for REQUEST SENSE the status must be GOOD return 0; } else if (sense_buffer) { // LOGICAL UNIT NOT SUPPORTED sense_buffer[0] = 0x70; sense_buffer[2] = ILLEGAL_REQUEST; sense_buffer[7] = 10; sense_buffer[12] = 0x25; M68000_Flush_Data_Cache(st_sense_buffer, SENSE_LENGTH); LOG_TRACE(TRACE_SCSIDRV, "\n Sense Key=$05, ASC=$25, ASCQ=$00"); } return 2; } const int id = handle_meta_data[handle].id_lo; // Return deferred sense data, if any if (cmd[0] == REQUEST_SENSE && deferred_sense_data_valid[id]) { memcpy(buffer, deferred_sense_data[id], SENSE_LENGTH); deferred_sense_data_valid[id] = false; M68000_Flush_Data_Cache(st_buffer, SENSE_LENGTH); LOG_TRACE(TRACE_SCSIDRV, "\n Deferred Sense Key=$%02X, ASC=$%02X, ASCQ=$%02X", buffer[2], buffer[12], buffer[13]); return 0; } deferred_sense_data_valid[id] = false; if (check_mchg_udev()) { // cErrMediach for all open handles uint32_t i; for (i = 0; i < SCSI_MAX_HANDLES; i++) { if (handle_meta_data[i].fd) { handle_meta_data[i].error |= 1; } } if (sense_buffer) { // NOT READY TO READY CHANGE sense_buffer[0] = 0x70; sense_buffer[2] = UNIT_ATTENTION; sense_buffer[7] = 10; sense_buffer[12] = 0x28; } status = 2; } else { struct sg_io_hdr io_hdr; memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); io_hdr.interface_id = 'S'; io_hdr.dxfer_direction = dir ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; if (!transfer_len) { io_hdr.dxfer_direction = SG_DXFER_NONE; } io_hdr.dxferp = buffer; io_hdr.dxfer_len = transfer_len; memset(sense_data, 0, SENSE_LENGTH); io_hdr.sbp = sense_data; io_hdr.mx_sb_len = SENSE_LENGTH; io_hdr.cmdp = cmd; io_hdr.cmd_len = cmd_len; io_hdr.timeout = timeout; status = ioctl(handle_meta_data[handle].fd, SG_IO, &io_hdr) < 0 ? -1 : io_hdr.status; if (status == -1) { Log_Printf(LOG_ERROR, "\nscsidrv_inout: Can't transfer %d byte(s)\n", transfer_len); } // Do not treat CONDITION MET as en error else if (status == 4) { status = 0; } // If the command was successful, use the sense key as status if (!status) { status = sense_data[2] & 0x0f; if (cmd[0] == INQUIRY && (cmd[1] & 0b11100000)) { // SCSI-2 section 8.2.5.1: Incorrect logical unit handling buffer[0] = 0x7f; } } } // If a sense buffer was passed, return the current sense data if (sense_buffer) { memcpy(sense_buffer, sense_data, SENSE_LENGTH); if (status) { LOG_TRACE(TRACE_SCSIDRV, "\n Sense Key=$%02X, ASC=$%02X, ASCQ=$%02X", sense_data[2], sense_data[12], sense_data[13]); } if (status == 2) { // Automatic media change and reset handling for // SCSI Driver version 1.0.1 if ((sense_data[2] & 0x0f) && !sense_data[13]) { if (sense_data[12] == 0x28) { // cErrMediach set_error(handle, 1); } else if (sense_data[12] == 0x29) { // cErrReset set_error(handle, 2); } } } } // Remember sense data for a subsequent REQUEST SENSE else { LOG_TRACE(TRACE_SCSIDRV, "\n Defer Sense Key=$%02X, ASC=$%02X, ASCQ=$%02X", sense_data[2], sense_data[12], sense_data[13]); memcpy(deferred_sense_data[id], sense_data, SENSE_LENGTH); deferred_sense_data_valid[id] = true; } M68000_Flush_Data_Cache(st_sense_buffer, SENSE_LENGTH); if (!dir) { M68000_Flush_All_Caches(st_buffer, transfer_len); } return status; } // SCSI Driver: Error() static int scsidrv_error(uint32_t stack) { uint32_t handle = read_stack_long(&stack); uint32_t rwflag = read_stack_long(&stack); uint32_t errnum = read_stack_long(&stack); int errbit; LOG_TRACE(TRACE_SCSIDRV, "scsidrv_error: handle=%d, rwflag=%d, errno=%d", handle, rwflag, errnum); if (handle >= SCSI_MAX_HANDLES || !handle_meta_data[handle].fd) { return GEMDOS_ENHNDL; } errbit = 1 << errnum; if (rwflag) { set_error(handle, errbit); return 0; } else { int status = handle_meta_data[handle].error & errbit; handle_meta_data[handle].error &= ~errbit; return status; } } // SCSI Driver: CheckDev() static int scsidrv_check_dev(uint32_t stack) { uint32_t id = read_stack_long(&stack); LOG_TRACE(TRACE_SCSIDRV, "scsidrv_check_dev: id=%d", id); return check_device_file(id); } static const struct { int (*cb)(uint32_t stack); } operations[] = { { scsidrv_interface_version }, { scsidrv_interface_features }, { scsidrv_inquire_bus }, { scsidrv_open }, { scsidrv_close }, { scsidrv_inout }, { scsidrv_error }, { scsidrv_check_dev } }; bool nf_scsidrv(uint32_t stack, uint32_t subid, uint32_t *retval) { if (subid >= ARRAY_SIZE(operations)) { *retval = -1; LOG_TRACE(TRACE_SCSIDRV, "ERROR: Invalid SCSI Driver operation %d requested\n", subid); } else { *retval = operations[subid].cb(stack); LOG_TRACE(TRACE_SCSIDRV, " -> %d\n", *retval); } return true; } void nf_scsidrv_reset(void) { int i; for (i = 0; i < SCSI_MAX_HANDLES; i++) { if (handle_meta_data[i].fd) { close(handle_meta_data[i].fd); handle_meta_data[i].fd = 0; } } } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/options.c000066400000000000000000002011351504763705000231710ustar00rootroot00000000000000/* Hatari - options.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Functions for showing and parsing all of Hatari's command line options. To add a new option: - Add option ID to the enum - Add the option information to HatariOptions[] - Add required actions for that ID to switch in Opt_ParseParameters() */ const char Options_fileid[] = "Hatari options.c"; #include #include #include #include #include #include "main.h" #include "version.h" #include "options.h" #include "configuration.h" #include "console.h" #include "control.h" #include "debugui.h" #include "file.h" #include "floppy.h" #include "fdc.h" #include "screen.h" #include "statusbar.h" #include "sound.h" #include "video.h" #include "vdi.h" #include "joy.h" #include "log.h" #include "inffile.h" #include "paths.h" #include "avi_record.h" #include "hatari-glue.h" #include "68kDisass.h" #include "xbios.h" #include "stMemory.h" #include "tos.h" #include "lilo.h" #include "screenSnapShot.h" #ifdef WIN32 #include "gui-win/opencon.h" #endif bool bLoadAutoSave; /* Load autosave memory snapshot at startup */ bool bLoadMemorySave; /* Load memory snapshot provided via option at startup */ bool AviRecordOnStartup; /* Start avi recording at startup */ bool BenchmarkMode; /* Start in benchmark mode (try to run at maximum emulation */ /* speed allowed by the CPU). Disable audio/video for best results */ static bool bBiosIntercept; typedef enum { CHECK_NONE, CHECK_FILE, CHECK_DIR } fs_check_t; /* List of supported options. */ enum { OPT_HEADER, /* options section header */ OPT_HELP, /* general options */ OPT_VERSION, OPT_CONFIRMQUIT, OPT_CONFIGFILE, OPT_KEYMAPFILE, OPT_COUNTRY_CODE, OPT_KBD_LAYOUT, OPT_LANGUAGE, OPT_FASTFORWARD, OPT_AUTOSTART, OPT_FF_KEY_REPEAT, OPT_MONO, /* common display options */ OPT_MONITOR, OPT_TOS_RESOLUTION, OPT_FULLSCREEN, OPT_WINDOW, OPT_GRAB, OPT_RESIZABLE, OPT_FRAMESKIPS, OPT_SLOWDOWN, OPT_MOUSE_WARP, OPT_STATUSBAR, OPT_DRIVE_LED, OPT_MAXWIDTH, OPT_MAXHEIGHT, OPT_ZOOM, OPT_DISABLE_VIDEO, OPT_BORDERS, /* ST/STE display options */ OPT_SPEC512, OPT_VIDEO_TIMING, OPT_RESOLUTION, /* TT/Falcon display options */ OPT_FORCE_MAX, OPT_ASPECT, OPT_VDI, /* VDI options */ OPT_VDI_PLANES, OPT_VDI_WIDTH, OPT_VDI_HEIGHT, OPT_SCREEN_CROP, /* screen capture options */ OPT_AVIRECORD, OPT_AVIRECORD_VCODEC, OPT_AVI_PNG_LEVEL, OPT_AVIRECORD_FPS, OPT_AVIRECORD_FILE, OPT_SCRSHOT_DIR, OPT_SCRSHOT_FORMAT, OPT_JOYSTICK, /* device options */ OPT_JOYSTICK0, OPT_JOYSTICK1, OPT_JOYSTICK2, OPT_JOYSTICK3, OPT_JOYSTICK4, OPT_JOYSTICK5, OPT_PRINTER, #ifdef HAVE_PORTMIDI OPT_MIDI, #else OPT_MIDI_IN, OPT_MIDI_OUT, #endif OPT_RS232_IN, OPT_RS232_OUT, OPT_SCCA_IN, OPT_SCCA_OUT, OPT_SCCA_LAN_IN, OPT_SCCA_LAN_OUT, OPT_SCCB_IN, OPT_SCCB_OUT, OPT_DRIVEA, /* floppy options */ OPT_DRIVEB, OPT_DRIVEA_HEADS, OPT_DRIVEB_HEADS, OPT_DISKA, OPT_DISKB, OPT_FASTFLOPPY, OPT_WRITEPROT_FLOPPY, OPT_HARDDRIVE, /* HD options */ OPT_WRITEPROT_HD, OPT_GEMDOS_CASE, OPT_GEMDOS_HOSTTIME, OPT_GEMDOS_CONVERT, OPT_GEMDOS_DRIVE, OPT_ACSIHDIMAGE, OPT_SCSIHDIMAGE, OPT_SCSIVERSION, OPT_IDEMASTERHDIMAGE, OPT_IDESLAVEHDIMAGE, OPT_IDEBYTESWAP, OPT_MEMSIZE, /* memory options */ OPT_TT_RAM, OPT_MEMSTATE, OPT_TOS, /* ROM options */ OPT_PATCHTOS, OPT_CARTRIDGE, OPT_CPULEVEL, /* CPU options */ OPT_CPUCLOCK, OPT_COMPATIBLE, OPT_CPU_DATA_CACHE, OPT_CPU_CYCLE_EXACT, OPT_CPU_ADDR24, OPT_FPU_TYPE, /* OPT_FPU_JIT_COMPAT, */ OPT_FPU_SOFTFLOAT, OPT_MMU, OPT_MACHINE, /* system options */ OPT_BLITTER, OPT_DSP, OPT_RTC_YEAR, OPT_TIMERD, OPT_FASTBOOT, OPT_MICROPHONE, /* sound options */ OPT_SOUND, OPT_SOUNDBUFFERSIZE, OPT_SOUNDSYNC, OPT_YM_MIXING, #ifdef WIN32 OPT_WINCON, /* debug options */ #endif OPT_DEBUG, OPT_EXCEPTIONS, OPT_LILO, OPT_BIOSINTERCEPT, OPT_CONOUT, OPT_DISASM, OPT_NATFEATS, OPT_TRACE, OPT_TRACEFILE, OPT_MSG_REPEAT, OPT_PARSE, OPT_SAVECONFIG, OPT_CONTROLSOCKET, OPT_CMDFIFO, OPT_LOGFILE, OPT_LOGLEVEL, OPT_ALERTLEVEL, OPT_RUNVBLS, OPT_BENCHMARK, OPT_ERROR, OPT_CONTINUE }; typedef struct { unsigned int id; /* option ID */ const char *chr; /* short option */ const char *str; /* long option */ const char *arg; /* type name for argument, if any */ const char *desc; /* option description */ } opt_t; /* it's easier to edit these if they are kept in the same order as the enums */ static const opt_t HatariOptions[] = { { OPT_HEADER, NULL, NULL, NULL, "General" }, { OPT_HELP, "-h", "--help", NULL, "Print this help text and exit" }, { OPT_VERSION, "-v", "--version", NULL, "Print version number and exit" }, { OPT_CONFIRMQUIT, NULL, "--confirm-quit", "", "Whether Hatari confirms quit" }, { OPT_CONFIGFILE, "-c", "--configfile", "", "Read (additional) configuration values from " }, { OPT_KEYMAPFILE, "-k", "--keymap", "", "Read (additional) keyboard mappings from " }, { OPT_COUNTRY_CODE, NULL, "--country", "", "Set country code for multi-code EmuTOS ROM" }, { OPT_KBD_LAYOUT, NULL, "--layout", "", "Set (TT/Falcon) NVRAM keyboard layout" }, { OPT_LANGUAGE, NULL, "--language", "", "Set (TT/Falcon) NVRAM language" }, { OPT_FASTFORWARD, NULL, "--fast-forward", "", "Help skipping stuff on fast machine" }, { OPT_AUTOSTART, NULL, "--auto", "", "Atari program autostarting with Atari path" }, { OPT_FF_KEY_REPEAT, NULL, "--fast-forward-key-repeat", "", "Use keyboard auto repeat in fast forward mode" }, { OPT_HEADER, NULL, NULL, NULL, "Common display" }, { OPT_MONO, "-m", "--mono", NULL, "Start in monochrome mode instead of color" }, { OPT_MONITOR, NULL, "--monitor", "", "Select monitor type (x = mono/rgb/vga/tv)" }, { OPT_TOS_RESOLUTION, NULL, "--tos-res", "", "TOS resolution (x = low/med/high/ttlow/ttmed)" }, { OPT_FULLSCREEN,"-f", "--fullscreen", NULL, "Start emulator in fullscreen mode" }, { OPT_WINDOW, "-w", "--window", NULL, "Start emulator in windowed mode" }, { OPT_GRAB, NULL, "--grab", NULL, "Grab mouse (also) in windowed mode" }, { OPT_RESIZABLE, NULL, "--resizable", "", "Allow window resizing" }, { OPT_FRAMESKIPS, NULL, "--frameskips", "", "Skip frames after each shown frame (0=off, >4=auto/max)" }, { OPT_SLOWDOWN, NULL, "--slowdown", "", "VBL wait time multiplier (1-30, default 1)" }, { OPT_MOUSE_WARP, NULL, "--mousewarp", "", "Center host mouse on reset & resolution changes" }, { OPT_STATUSBAR, NULL, "--statusbar", "", "Show statusbar (floppy leds etc)" }, { OPT_DRIVE_LED, NULL, "--drive-led", "", "Show overlay drive led when statusbar isn't shown" }, { OPT_MAXWIDTH, NULL, "--max-width", "", "Maximum Hatari screen width before scaling" }, { OPT_MAXHEIGHT, NULL, "--max-height", "", "Maximum Hatari screen height before scaling" }, { OPT_ZOOM, "-z", "--zoom", "", "Hatari screen/window scaling factor (1.0 - 8.0)" }, { OPT_DISABLE_VIDEO, NULL, "--disable-video", "", "Run emulation without displaying video (audio only)" }, { OPT_HEADER, NULL, NULL, NULL, "ST/STE specific display" }, { OPT_BORDERS, NULL, "--borders", "", "Show screen borders (for overscan demos etc)" }, { OPT_SPEC512, NULL, "--spec512", "", "Spec512 palette threshold (0 <= x <= 512, 0=disable)" }, { OPT_VIDEO_TIMING, NULL, "--video-timing", "", "Wakeup State for MMU/GLUE (x=ws1/ws2/ws3/ws4/random, default ws3)" }, { OPT_HEADER, NULL, NULL, NULL, "TT/Falcon specific display" }, { OPT_RESOLUTION, NULL, "--desktop", "", "Keep desktop resolution on fullscreen" }, { OPT_FORCE_MAX, NULL, "--force-max", "", "Resolution fixed to given max values" }, { OPT_ASPECT, NULL, "--aspect", "", "Monitor aspect ratio correction" }, { OPT_HEADER, NULL, NULL, NULL, "VDI" }, { OPT_VDI, NULL, "--vdi", "", "Whether to use VDI screen mode" }, { OPT_VDI_PLANES,NULL, "--vdi-planes", "", "VDI mode bit-depth (x = 1/2/4)" }, { OPT_VDI_WIDTH, NULL, "--vdi-width", "", "VDI mode width (320 < w <= 2048)" }, { OPT_VDI_HEIGHT, NULL, "--vdi-height", "", "VDI mode height (200 < h <= 1280)" }, { OPT_HEADER, NULL, NULL, NULL, "Screen capture" }, { OPT_SCREEN_CROP, NULL, "--crop", "", "Remove statusbar from screen capture" }, { OPT_AVIRECORD, NULL, "--avirecord", NULL, "Start AVI recording" }, { OPT_AVIRECORD_VCODEC, NULL, "--avi-vcodec", "", "Select AVI video codec (x = bmp/png)" }, { OPT_AVI_PNG_LEVEL, NULL, "--png-level", "", "Select AVI PNG compression level (x = 0-9)" }, { OPT_AVIRECORD_FPS, NULL, "--avi-fps", "", "Force AVI frame rate (x = 50/60/71/...)" }, { OPT_AVIRECORD_FILE, NULL, "--avi-file", "", "Use to record AVI" }, { OPT_SCRSHOT_DIR, NULL, "--screenshot-dir", "
", "Save screenshots in the directory " }, { OPT_SCRSHOT_FORMAT, NULL, "--screenshot-format", "", "Select file format (x = bmp/png/neo/ximg)" }, { OPT_HEADER, NULL, NULL, NULL, "Devices" }, { OPT_JOYSTICK, "-j", "--joystick", "", "Emulate joystick with cursor keys in given port (0-5)" }, /* these have to be exactly the same as I'm relying compiler giving * them the same same string pointer when strings are identical * (Opt_ShowHelpSection() skips successive options with same help * pointer). */ { OPT_JOYSTICK0, NULL, "--joy", "", "Set joystick type (none/keys/real) for given port" }, { OPT_JOYSTICK1, NULL, "--joy", "", "Set joystick type (none/keys/real) for given port" }, { OPT_JOYSTICK2, NULL, "--joy", "", "Set joystick type (none/keys/real) for given port" }, { OPT_JOYSTICK3, NULL, "--joy", "", "Set joystick type (none/keys/real) for given port" }, { OPT_JOYSTICK4, NULL, "--joy", "", "Set joystick type (none/keys/real) for given port" }, { OPT_JOYSTICK5, NULL, "--joy", "", "Set joystick type (none/keys/real) for given port" }, { OPT_PRINTER, NULL, "--printer", "", "Enable printer support and write data to " }, #ifdef HAVE_PORTMIDI { OPT_MIDI, NULL, "--midi", "", "Whether to use MIDI (with PortMidi devices)" }, #else { OPT_MIDI_IN, NULL, "--midi-in", "", "Enable MIDI and use as the input device" }, { OPT_MIDI_OUT, NULL, "--midi-out", "", "Enable MIDI and use as the output device" }, #endif { OPT_RS232_IN, NULL, "--rs232-in", "", "Enable serial port and use as the input device" }, { OPT_RS232_OUT, NULL, "--rs232-out", "", "Enable serial port and use as the output device" }, { OPT_SCCA_IN, NULL, "--scc-a-in", "", "Enable SCC channel A and use as the input" }, { OPT_SCCA_OUT, NULL, "--scc-a-out", "", "Enable SCC channel A and use as the output" }, { OPT_SCCA_LAN_IN, NULL, "--scc-a-lan-in", "", "Enable LAN on SCC channel A and use as the input" }, { OPT_SCCA_LAN_OUT, NULL, "--scc-a-lan-out", "", "Enable LAN on SCC channel A and use as the output" }, { OPT_SCCB_IN, NULL, "--scc-b-in", "", "Enable SCC channel B and use as the input" }, { OPT_SCCB_OUT, NULL, "--scc-b-out", "", "Enable SCC channel B and use as the output" }, { OPT_HEADER, NULL, NULL, NULL, "Floppy drive" }, { OPT_DRIVEA, NULL, "--drive-a", "", "Enable/disable drive A (default is on)" }, { OPT_DRIVEB, NULL, "--drive-b", "", "Enable/disable drive B (default is on)" }, { OPT_DRIVEA_HEADS, NULL, "--drive-a-heads", "", "Set number of heads for drive A (1=single sided, 2=double sided)" }, { OPT_DRIVEB_HEADS, NULL, "--drive-b-heads", "", "Set number of heads for drive B (1=single sided, 2=double sided)" }, { OPT_DISKA, NULL, "--disk-a", "", "Set disk image for floppy drive A" }, { OPT_DISKB, NULL, "--disk-b", "", "Set disk image for floppy drive B" }, { OPT_FASTFLOPPY, NULL, "--fastfdc", "", "Speed up floppy disk access emulation (can break some programs)" }, { OPT_WRITEPROT_FLOPPY, NULL, "--protect-floppy", "", "Write protect floppy image contents (on/off/auto)" }, { OPT_HEADER, NULL, NULL, NULL, "Hard drive" }, { OPT_HARDDRIVE, "-d", "--harddrive", "", "Emulate harddrive partition(s) with contents" }, { OPT_WRITEPROT_HD, NULL, "--protect-hd", "", "Write protect harddrive contents (on/off/auto)" }, { OPT_GEMDOS_CASE, NULL, "--gemdos-case", "", "Forcibly up/lowercase new GEMDOS dir/filenames (off/upper/lower)" }, { OPT_GEMDOS_HOSTTIME, NULL, "--gemdos-time", "", "Which timestamps to use for GEMDOS files (atari/host)" }, { OPT_GEMDOS_CONVERT, NULL, "--gemdos-conv", "", "Atari GEMDOS <-> host (UTF-8) file name conversion" }, { OPT_GEMDOS_DRIVE, NULL, "--gemdos-drive", "", "Assign GEMDOS HD to drive letter (C-Z, skip)" }, { OPT_ACSIHDIMAGE, NULL, "--acsi", "=", "Emulate an ACSI harddrive (0-7) with an image " }, { OPT_SCSIHDIMAGE, NULL, "--scsi", "=", "Emulate a SCSI harddrive (0-7) with an image " }, { OPT_SCSIVERSION, NULL, "--scsi-ver", "=", "Which SCSI version (1-2) to emulate for given drive ID" }, { OPT_IDEMASTERHDIMAGE, NULL, "--ide-master", "", "Emulate an IDE 0 (master) harddrive with an image " }, { OPT_IDESLAVEHDIMAGE, NULL, "--ide-slave", "", "Emulate an IDE 1 (slave) harddrive with an image " }, { OPT_IDEBYTESWAP, NULL, "--ide-swap", "=", "Set IDE (0/1) byte-swap option (off/on/auto)" }, { OPT_HEADER, NULL, NULL, NULL, "Memory" }, { OPT_MEMSIZE, "-s", "--memsize", "", "ST RAM size (x = size in MiB from 0 to 14, 0 = 512KiB ; else size in KiB)" }, { OPT_TT_RAM, NULL, "--ttram", "", "TT RAM size (x = size in MiB from 0 to 1024, in steps of 4)" }, { OPT_MEMSTATE, NULL, "--memstate", "", "Load memory snap-shot " }, { OPT_HEADER, NULL, NULL, NULL, "ROM" }, { OPT_TOS, "-t", "--tos", "", "Use TOS image " }, { OPT_PATCHTOS, NULL, "--patch-tos", "", "Apply TOS patches (experts only, leave it enabled!)" }, { OPT_CARTRIDGE, NULL, "--cartridge", "", "Use ROM cartridge image " }, { OPT_HEADER, NULL, NULL, NULL, "CPU/FPU/bus" }, { OPT_CPULEVEL, NULL, "--cpulevel", "", "Set the CPU type (x => 680x0) (EmuTOS/TOS 2.06 only!)" }, { OPT_CPUCLOCK, NULL, "--cpuclock", "", "Set the CPU clock (x = 8/16/32)" }, { OPT_COMPATIBLE, NULL, "--compatible", "", "Use (more compatible) prefetch mode for CPU" }, { OPT_CPU_DATA_CACHE, NULL, "--data-cache", "", "Emulate (>=030) CPU data cache" }, { OPT_CPU_CYCLE_EXACT, NULL, "--cpu-exact", "", "Use cycle exact CPU emulation" }, { OPT_CPU_ADDR24, NULL, "--addr24", "", "Use 24-bit instead of 32-bit addressing mode" }, { OPT_FPU_TYPE, NULL, "--fpu", "", "FPU type (x=none/68881/68882/internal)" }, /*{ OPT_FPU_JIT_COMPAT, NULL, "--fpu-compatible", "", "Use more compatible, but slower FPU JIT emulation" },*/ { OPT_FPU_SOFTFLOAT, NULL, "--fpu-softfloat", "", "Use full software FPU emulation" }, { OPT_MMU, NULL, "--mmu", "", "Use MMU emulation" }, { OPT_HEADER, NULL, NULL, NULL, "Misc system" }, { OPT_MACHINE, NULL, "--machine", "", "Select machine type (x = st/megast/ste/megaste/tt/falcon)" }, { OPT_BLITTER, NULL, "--blitter", "", "Use blitter emulation (ST only)" }, { OPT_DSP, NULL, "--dsp", "", "DSP emulation (x = none/dummy/emu, Falcon only)" }, { OPT_RTC_YEAR, NULL, "--rtc-year", "", "Set initial year for RTC (0, 1980 <= x < 2080)" }, { OPT_TIMERD, NULL, "--timer-d", "", "Patch Timer-D (about doubles ST emulation speed)" }, { OPT_FASTBOOT, NULL, "--fast-boot", "", "Patch TOS and memvalid system variables for faster boot" }, { OPT_HEADER, NULL, NULL, NULL, "Sound" }, { OPT_MICROPHONE, NULL, "--mic", "", "Enable/disable (Falcon only) microphone" }, { OPT_SOUND, NULL, "--sound", "", "Sound frequency (x=off/6000-50066, off=fastest)" }, { OPT_SOUNDBUFFERSIZE, NULL, "--sound-buffer-size", "", "Sound buffer size in ms (x=0/10-100, 0=SDL default)" }, { OPT_SOUNDSYNC, NULL, "--sound-sync", "", "Sound synchronized emulation (on|off, off=default)" }, { OPT_YM_MIXING, NULL, "--ym-mixing", "", "YM sound mixing method (x=linear/table/model)" }, { OPT_HEADER, NULL, NULL, NULL, "Debug" }, #ifdef WIN32 { OPT_WINCON, "-W", "--wincon", NULL, "Open console window (Windows only)" }, #endif { OPT_DEBUG, "-D", "--debug", NULL, "Toggle whether CPU exceptions invoke debugger" }, { OPT_EXCEPTIONS, NULL, "--debug-except", "", "Exceptions invoking debugger, see '--debug-except help'" }, { OPT_LILO, NULL, "--lilo", "", "Boot Linux (see manual page)" }, { OPT_BIOSINTERCEPT, NULL, "--bios-intercept", "", "Enable/disable XBIOS command parsing support" }, { OPT_CONOUT, NULL, "--conout", "", "Show console output (0-7, 2=VT-52 terminal)" }, { OPT_DISASM, NULL, "--disasm", "", "Set disassembly options (help/uae/ext/)" }, { OPT_NATFEATS, NULL, "--natfeats", "", "Whether Native Features support is enabled" }, { OPT_TRACE, NULL, "--trace", "", "Activate emulation tracing, see '--trace help'" }, { OPT_TRACEFILE, NULL, "--trace-file", "", "Save trace output to (default=stderr)" }, { OPT_MSG_REPEAT, NULL, "--msg-repeat", NULL, "Toggle log/trace message repeats (default=suppress)" }, { OPT_PARSE, NULL, "--parse", "", "Parse/execute debugger commands from " }, { OPT_SAVECONFIG, NULL, "--saveconfig", NULL, "Save current Hatari configuration and exit" }, #if HAVE_UNIX_DOMAIN_SOCKETS { OPT_CONTROLSOCKET, NULL, "--control-socket", "", "Hatari connects to given socket for commands" }, { OPT_CMDFIFO, NULL, "--cmd-fifo", "", "Hatari creates & reads commands from given fifo" }, #endif { OPT_LOGFILE, NULL, "--log-file", "", "Save log output to (default=stderr)" }, { OPT_LOGLEVEL, NULL, "--log-level", "", "Log output level (x=debug/todo/info/warn/error/fatal)" }, { OPT_ALERTLEVEL, NULL, "--alert-level", "", "Show dialog for log messages above given level" }, { OPT_RUNVBLS, NULL, "--run-vbls", "", "Exit after x VBLs" }, { OPT_BENCHMARK, NULL, "--benchmark", NULL, "Start in benchmark mode (use with --run-vbls)" }, { OPT_ERROR, NULL, NULL, NULL, NULL } }; /** * Show version string and license. */ static void Opt_ShowVersion(void) { #ifdef WIN32 /* Opt_ShowVersion() is called for all info exit paths, * so having this here should enable console for everything * relevant on Windows. */ Win_ForceCon(); #endif printf("\n" PROG_NAME " - the Atari ST, STE, TT and Falcon emulator.\n\n" "Hatari is free software licensed under the GNU General" " Public License.\n\n"); } /** * Calculate option + value len */ static unsigned int Opt_OptionLen(const opt_t *opt) { unsigned int len; len = strlen(opt->str); if (opt->arg) { len += strlen(opt->arg); len += 1; /* with arg, short options go to another line */ } else { if (opt->chr) { /* ' or -c' */ len += 6; } } return len; } /** * Show single option */ static void Opt_ShowOption(const opt_t *opt, unsigned int maxlen) { char buf[64]; if (!maxlen) { maxlen = Opt_OptionLen(opt); } assert(maxlen < sizeof(buf)); if (opt->arg) { sprintf(buf, "%s %s", opt->str, opt->arg); printf(" %-*s %s\n", maxlen, buf, opt->desc); if (opt->chr) { printf(" or %s %s\n", opt->chr, opt->arg); } } else { if (opt->chr) { sprintf(buf, "%s or %s", opt->str, opt->chr); printf(" %-*s %s\n", maxlen, buf, opt->desc); } else { printf(" %-*s %s\n", maxlen, opt->str, opt->desc); } } } /** * Show options for section starting from 'start_opt', * return next option after this section. */ static const opt_t *Opt_ShowHelpSection(const opt_t *start_opt) { const opt_t *opt, *last; unsigned int len, maxlen = 0; const char *previous = NULL; /* find longest option name and check option IDs */ for (opt = start_opt; opt->id != OPT_HEADER && opt->id != OPT_ERROR; opt++) { len = Opt_OptionLen(opt); if (len > maxlen) { maxlen = len; } } last = opt; /* output all options */ for (opt = start_opt; opt != last; opt++) { if (previous != opt->str) { Opt_ShowOption(opt, maxlen); } previous = opt->str; } return last; } /** * Show help text. */ static void Opt_ShowHelp(void) { const opt_t *opt = HatariOptions; Opt_ShowVersion(); printf("Usage:\n hatari [options] [directory|disk image|Atari program]\n"); while(opt->id != OPT_ERROR) { if (opt->id == OPT_HEADER) { assert(opt->desc); printf("\n%s options:\n", opt->desc); opt++; } opt = Opt_ShowHelpSection(opt); } printf("\nSpecial option values:\n" "\tDisable by using 'n', 'no', 'off', 'false', or '0'\n" "\tEnable by using 'y', 'yes', 'on', 'true' or '1'\n" "\tDevices accept also special 'stdout' and 'stderr' file names\n" "\t(if you use stdout for midi or printer, set log to stderr).\n" "\tSetting the file to 'none', disables given device or disk\n"); } /** * Show Hatari version and usage. * If 'error' given, show that error message. * If 'optid' != OPT_ERROR, tells for which option the error is, * otherwise 'value' is show as the option user gave. * Return false if error string was given, otherwise true */ bool Opt_ShowError(unsigned int optid, const char *value, const char *error) { const opt_t *opt; Opt_ShowVersion(); printf("Usage:\n hatari [options] [disk image name]\n\n" "Try option \"-h\" or \"--help\" to display more information.\n"); if (error) { if (optid == OPT_ERROR) { fprintf(stderr, "\nError: %s (%s)\n", error, value); } else { for (opt = HatariOptions; opt->id != OPT_ERROR; opt++) { if (optid == opt->id) break; } if (value != NULL) { fprintf(stderr, "\nError while parsing argument \"%s\" for option \"%s\":\n" " %s\n", value, opt->str, error); } else { fprintf(stderr, "\nError (%s): %s\n", opt->str, error); } fprintf(stderr, "\nOption usage:\n"); Opt_ShowOption(opt, 0); } return false; } return true; } /** * Return given value after constraining it within "min" and "max" values * and making it evenly divisible by "align" */ int Opt_ValueAlignMinMax(int value, int align, int min, int max) { if (value > max) { /* align down */ return (max/align)*align; } if (value < min) { /* align up */ min += align-1; return (min/align)*align; } return (value/align)*align; } /** * If 'conf' given, set it: * - true if given option 'arg' is y/yes/on/true/1 * - false if given option 'arg' is n/no/off/false/0 * Return false for any other value, otherwise true */ static bool Opt_Bool(const char *arg, int optid, bool *conf) { const char *enablers[] = { "y", "yes", "on", "true", "1", NULL }; const char *disablers[] = { "n", "no", "off", "false", "0", NULL }; const char **bool_str, *orig = arg; char *input, *str; input = strdup(arg); str = input; while (*str) { *str++ = tolower((unsigned char)*arg++); } for (bool_str = enablers; *bool_str; bool_str++) { if (strcmp(input, *bool_str) == 0) { free(input); if (conf) { *conf = true; } return true; } } for (bool_str = disablers; *bool_str; bool_str++) { if (strcmp(input, *bool_str) == 0) { free(input); if (conf) { *conf = false; } return true; } } free(input); return Opt_ShowError(optid, orig, "Not a value"); } /** * If 'conf' given, set it to parsed country code value * Return false for any other value, otherwise true */ static bool Opt_CountryCode(const char *arg, int optid, int *conf) { int val = TOS_ParseCountryCode(arg); if (val != TOS_LANG_UNKNOWN) { *conf = val; return true; } Opt_ShowError(optid, arg, "Invalid value"); TOS_ShowCountryCodes(); return false; } /** * Parse "=". If single digit "" and/or '=' missing, * assume drive ID 0, and interpret whole arg as "". * Return parsed "", and set "". */ static const char *Opt_DriveValue(const char *arg, int *drive) { if (strlen(arg) > 2 && isdigit((unsigned char)arg[0]) && arg[1] == '=') { *drive = arg[0] - '0'; return arg + 2; } *drive = 0; return arg; } /** * checks str argument against options of type "--option". * If match is found, returns ID for that, otherwise OPT_CONTINUE * and OPT_ERROR for errors. */ static int Opt_CheckBracketValue(const opt_t *opt, const char *str) { const char *bracket, *optstr; size_t offset; int digit, i; if (!opt->str) { return OPT_CONTINUE; } bracket = strchr(opt->str, '<'); if (!bracket) { return OPT_CONTINUE; } offset = bracket - opt->str; if (strncmp(opt->str, str, offset) != 0) { return OPT_CONTINUE; } digit = str[offset] - '0'; if (digit < 0 || digit > 9) { return OPT_CONTINUE; } if (str[offset+1]) { return OPT_CONTINUE; } optstr = opt->str; for (i = 0; opt->str == optstr; opt++, i++) { if (i == digit) { return opt->id; } } /* fprintf(stderr, "opt: %s (%d), str: %s (%d), digit: %d\n", opt->str, offset+1, str, strlen(str), digit); */ return OPT_ERROR; } /** * matches string under given index in the argv against all Hatari * short and long options. If match is found, returns ID for that, * otherwise shows help and returns OPT_ERROR. * * Checks also that if option is supposed to have argument, * whether there's one. */ static int Opt_WhichOption(int argc, const char * const argv[], int idx) { const opt_t *opt; const char *str = argv[idx]; int id; for (opt = HatariOptions; opt->id != OPT_ERROR; opt++) { /* exact option name matches? */ if (!((opt->str && !strcmp(str, opt->str)) || (opt->chr && !strcmp(str, opt->chr)))) { /* no, maybe name matches? */ id = Opt_CheckBracketValue(opt, str); if (id == OPT_CONTINUE) { continue; } if (id == OPT_ERROR) { break; } } /* matched, check args */ if (opt->arg) { if (idx+1 >= argc) { Opt_ShowError(opt->id, NULL, "Missing argument"); return OPT_ERROR; } /* early check for bools */ if (strcmp(opt->arg, "") == 0) { if (!Opt_Bool(argv[idx+1], opt->id, NULL)) { return OPT_ERROR; } } } return opt->id; } Opt_ShowError(OPT_ERROR, argv[idx], "Unrecognized option"); return OPT_ERROR; } /** * When 'check' is set, check whether given 'src' exists before copying * 'src' to 'dst'. Otherwise just copy option 'src' string to 'dst'. * If a pointer to (bool) 'option' is given, set that option to true. * - However, if src is "none", leave dst unmodified & set option to false. * ("none" is used to disable options related to file arguments) * Return false if there were errors, otherwise true */ static bool Opt_StrCpy(int optid, fs_check_t check, char *dst, const char *src, size_t dstlen, bool *option) { if (option) { *option = false; if(strcasecmp(src, "none") == 0) { return true; } } if (strlen(src) >= dstlen) { return Opt_ShowError(optid, src, "Path too long!"); } switch (check) { case CHECK_NONE: break; case CHECK_FILE: if (!File_Exists(src)) { return Opt_ShowError(optid, src, "Given file doesn't exist or permissions prevent access to it!"); } break; case CHECK_DIR: if (!File_DirExists(src)) { return Opt_ShowError(optid, src, "Given directory doesn't exist or permissions prevent access to it!"); } break; } if (option) { *option = true; } strcpy(dst, src); return true; } /** * Do final validation for the earlier + parsed options * * Return false if they fail validation. */ static bool Opt_ValidateOptions(void) { const char *err, *val; int opt_id; if ((opt_id = INF_ValidateAutoStart(&val, &err))) { return Opt_ShowError(opt_id, val, err); } return true; } /** * Return true if given path points to an Atari program, false otherwise. */ bool Opt_IsAtariProgram(const char *path) { bool ret = false; uint8_t test[2]; FILE *fp; if (File_Exists(path) && (fp = fopen(path, "rb"))) { /* file starts with GEMDOS magic? */ if (fread(test, 1, 2, fp) == 2 && test[0] == 0x60 && test[1] == 0x1A) { ret = true; } fclose(fp); } return ret; } /** * Handle last (non-option) argument. It can be a path or filename. * Filename can be a disk image or Atari program. * Return false if it's none of these. */ static bool Opt_HandleArgument(const char *path) { char *dir = NULL; /* Atari program? */ if (Opt_IsAtariProgram(path)) { const char *prgname = strrchr(path, PATHSEP); if (prgname) { dir = strdup(path); dir[prgname-path] = '\0'; prgname++; } else { dir = strdup(Paths_GetWorkingDir()); prgname = path; } Log_Printf(LOG_DEBUG, "ARG = autostart program: %s\n", prgname); /* after above, dir should point to valid dir, * then make sure that given program from that * dir will be started. */ if (bUseTos) { INF_SetAutoStart(prgname, OPT_AUTOSTART); } else { TOS_SetTestPrgName(path); } } if (dir) { path = dir; } /* GEMDOS HDD directory (as path arg, or dir for the Atari program)? */ if (File_DirExists(path)) { Log_Printf(LOG_DEBUG, "ARG = GEMDOS HD dir: %s\n", path); if (Opt_StrCpy(OPT_HARDDRIVE, CHECK_NONE, ConfigureParams.HardDisk.szHardDiskDirectories[0], path, sizeof(ConfigureParams.HardDisk.szHardDiskDirectories[0]), &ConfigureParams.HardDisk.bUseHardDiskDirectories) && ConfigureParams.HardDisk.bUseHardDiskDirectories) { ConfigureParams.HardDisk.bBootFromHardDisk = true; } bLoadAutoSave = false; if (dir) { free(dir); } return true; } /* something wrong if path to an existing prg has no valid dir */ assert(!dir); /* disk image? */ if (Floppy_SetDiskFileName(0, path, NULL)) { Log_Printf(LOG_DEBUG, "ARG = floppy image: %s\n", path); ConfigureParams.HardDisk.bBootFromHardDisk = false; bLoadAutoSave = false; return true; } return Opt_ShowError(OPT_ERROR, path, "Not a disk image, Atari program or directory"); } /** * parse all Hatari command line options and set Hatari state accordingly. * Returns true if everything was OK, false otherwise. */ bool Opt_ParseParameters(int argc, const char * const argv[]) { int ncpu, skips, planes, cpuclock, threshold, memsize; int dev, port, freq, temp, drive, year; const char *errstr, *str; int i, ok = true; float zoom; int val; bool valid; /* Defaults for loading initial memory snap-shots */ bLoadMemorySave = false; bLoadAutoSave = ConfigureParams.Memory.bAutoSave; for(i = 1; i < argc; i++) { /* last argument can be a non-option */ if (argv[i][0] != '-' && i+1 == argc) return Opt_HandleArgument(argv[i]) && Opt_ValidateOptions(); /* WhichOption() checks also that there is an argument, * so we don't need to check that below */ switch(Opt_WhichOption(argc, argv, i)) { /* general options */ case OPT_HELP: Opt_ShowHelp(); return false; case OPT_VERSION: Opt_ShowVersion(); return false; case OPT_CONFIRMQUIT: ok = Opt_Bool(argv[++i], OPT_CONFIRMQUIT, &ConfigureParams.Log.bConfirmQuit); break; case OPT_FASTFORWARD: ok = Opt_Bool(argv[++i], OPT_FASTFORWARD, &ConfigureParams.System.bFastForward); break; case OPT_AUTOSTART: if (!(ok = INF_SetAutoStart(argv[++i], OPT_AUTOSTART))) { return Opt_ShowError(OPT_AUTOSTART, argv[i], "Invalid drive and/or path specified for autostart program"); } break; case OPT_FF_KEY_REPEAT: ok = Opt_Bool(argv[++i], OPT_FF_KEY_REPEAT, &ConfigureParams.Keyboard.bFastForwardKeyRepeat); break; case OPT_CONFIGFILE: i += 1; /* true -> file needs to exist */ ok = Opt_StrCpy(OPT_CONFIGFILE, CHECK_FILE, sConfigFileName, argv[i], sizeof(sConfigFileName), NULL); if (ok) { Configuration_Load(NULL); bLoadAutoSave = ConfigureParams.Memory.bAutoSave; } break; /* common display options */ case OPT_MONO: ConfigureParams.Screen.nMonitorType = MONITOR_TYPE_MONO; bLoadAutoSave = false; break; case OPT_MONITOR: i += 1; if (strcasecmp(argv[i], "mono") == 0) { ConfigureParams.Screen.nMonitorType = MONITOR_TYPE_MONO; } else if (strcasecmp(argv[i], "rgb") == 0) { ConfigureParams.Screen.nMonitorType = MONITOR_TYPE_RGB; } else if (strcasecmp(argv[i], "vga") == 0) { ConfigureParams.Screen.nMonitorType = MONITOR_TYPE_VGA; } else if (strcasecmp(argv[i], "tv") == 0) { ConfigureParams.Screen.nMonitorType = MONITOR_TYPE_TV; } else { return Opt_ShowError(OPT_MONITOR, argv[i], "Unknown monitor type"); } bLoadAutoSave = false; break; case OPT_TOS_RESOLUTION: i += 1; if (!INF_SetResolution(argv[i], OPT_TOS_RESOLUTION)) { return Opt_ShowError(OPT_TOS_RESOLUTION, argv[i], "Invalid resolution"); } break; case OPT_FULLSCREEN: ConfigureParams.Screen.bFullScreen = true; break; case OPT_WINDOW: ConfigureParams.Screen.bFullScreen = false; break; case OPT_GRAB: bGrabMouse = true; break; case OPT_RESIZABLE: ok = Opt_Bool(argv[++i], OPT_RESIZABLE, &ConfigureParams.Screen.bResizable); break; case OPT_FRAMESKIPS: skips = atoi(argv[++i]); if (skips < 0) { return Opt_ShowError(OPT_FRAMESKIPS, argv[i], "Invalid frame skip value"); } else if (skips > 8) { Log_Printf(LOG_WARN, "Extravagant frame skip value %d!\n", skips); } ConfigureParams.Screen.nFrameSkips = skips; break; case OPT_SLOWDOWN: val = atoi(argv[++i]); errstr = Main_SetVBLSlowdown(val); if (errstr) { return Opt_ShowError(OPT_SLOWDOWN, argv[i], errstr); } Log_Printf(LOG_DEBUG, "Slow down host VBL wait by factor of %d.\n", val); break; case OPT_MOUSE_WARP: ok = Opt_Bool(argv[++i], OPT_MOUSE_WARP, &ConfigureParams.Screen.bMouseWarp); break; case OPT_STATUSBAR: ok = Opt_Bool(argv[++i], OPT_STATUSBAR, &ConfigureParams.Screen.bShowStatusbar); break; case OPT_DRIVE_LED: ok = Opt_Bool(argv[++i], OPT_DRIVE_LED, &ConfigureParams.Screen.bShowDriveLed); break; case OPT_DISABLE_VIDEO: ok = Opt_Bool(argv[++i], OPT_DISABLE_VIDEO, &ConfigureParams.Screen.DisableVideo); break; /* ST/STE display options */ case OPT_BORDERS: ok = Opt_Bool(argv[++i], OPT_BORDERS, &ConfigureParams.Screen.bAllowOverscan); break; case OPT_SPEC512: threshold = atoi(argv[++i]); if (threshold < 0 || threshold > 512) { return Opt_ShowError(OPT_SPEC512, argv[i], "Invalid palette writes per line threshold for Spec512"); } Log_Printf(LOG_DEBUG, "Spec512 threshold = %d palette writes per line.\n", threshold); ConfigureParams.Screen.nSpec512Threshold = threshold; break; case OPT_ZOOM: zoom = atof(argv[++i]); if (zoom < 1.0 || zoom > 8.0) { return Opt_ShowError(OPT_ZOOM, argv[i], "Invalid zoom value"); } ConfigureParams.Screen.nMaxWidth = NUM_VISIBLE_LINE_PIXELS; ConfigureParams.Screen.nMaxHeight = NUM_VISIBLE_LINES; /* double ST-low always so that resulting screen size * is approximately same size with same zoom factor * regardless of the machine or monitor type */ ConfigureParams.Screen.nMaxWidth *= 2; ConfigureParams.Screen.nMaxHeight *= 2; ConfigureParams.Screen.nZoomFactor = zoom; ConfigureParams.Screen.nMaxHeight += STATUSBAR_MAX_HEIGHT; break; case OPT_VIDEO_TIMING: i += 1; if (strcasecmp(argv[i], "random") == 0) ConfigureParams.System.VideoTimingMode = VIDEO_TIMING_MODE_RANDOM; else if (strcasecmp(argv[i], "ws1") == 0) ConfigureParams.System.VideoTimingMode = VIDEO_TIMING_MODE_WS1; else if (strcasecmp(argv[i], "ws2") == 0) ConfigureParams.System.VideoTimingMode = VIDEO_TIMING_MODE_WS2; else if (strcasecmp(argv[i], "ws3") == 0) ConfigureParams.System.VideoTimingMode = VIDEO_TIMING_MODE_WS3; else if (strcasecmp(argv[i], "ws4") == 0) ConfigureParams.System.VideoTimingMode = VIDEO_TIMING_MODE_WS4; else return Opt_ShowError(OPT_VIDEO_TIMING, argv[i], "Unknown video timing mode"); break; /* Falcon/TT display options */ case OPT_RESOLUTION: ok = Opt_Bool(argv[++i], OPT_RESOLUTION, &ConfigureParams.Screen.bKeepResolution); break; case OPT_MAXWIDTH: ConfigureParams.Screen.nMaxWidth = atoi(argv[++i]); break; case OPT_MAXHEIGHT: ConfigureParams.Screen.nMaxHeight = atoi(argv[++i]); break; case OPT_FORCE_MAX: ok = Opt_Bool(argv[++i], OPT_FORCE_MAX, &ConfigureParams.Screen.bForceMax); break; case OPT_ASPECT: ok = Opt_Bool(argv[++i], OPT_ASPECT, &ConfigureParams.Screen.bAspectCorrect); break; /* screen capture options */ case OPT_SCREEN_CROP: ok = Opt_Bool(argv[++i], OPT_SCREEN_CROP, &ConfigureParams.Screen.bCrop); break; case OPT_AVIRECORD: AviRecordOnStartup = true; break; case OPT_AVIRECORD_VCODEC: i += 1; if (strcasecmp(argv[i], "bmp") == 0) { ConfigureParams.Video.AviRecordVcodec = AVI_RECORD_VIDEO_CODEC_BMP; } else if (strcasecmp(argv[i], "png") == 0) { ConfigureParams.Video.AviRecordVcodec = AVI_RECORD_VIDEO_CODEC_PNG; } else { return Opt_ShowError(OPT_AVIRECORD_VCODEC, argv[i], "Unknown video codec"); } break; case OPT_AVI_PNG_LEVEL: i += 1; if (!Avi_SetCompressionLevel(argv[i])) return Opt_ShowError(OPT_AVI_PNG_LEVEL, argv[i], "Invalid compression level"); break; case OPT_AVIRECORD_FPS: val = atoi(argv[++i]); if (val < 0 || val > 100) { return Opt_ShowError(OPT_AVIRECORD_FPS, argv[i], "Invalid frame rate for avi recording"); } Log_Printf(LOG_DEBUG, "AVI recording FPS = %d.\n", val); ConfigureParams.Video.AviRecordFps = val; break; case OPT_AVIRECORD_FILE: i += 1; /* false -> file is created if it doesn't exist */ ok = Opt_StrCpy(OPT_AVIRECORD_FILE, CHECK_NONE, ConfigureParams.Video.AviRecordFile, argv[i], sizeof(ConfigureParams.Video.AviRecordFile), NULL); break; case OPT_SCRSHOT_DIR: i += 1; ok = Opt_StrCpy(OPT_SCRSHOT_DIR, CHECK_DIR, ConfigureParams.Screen.szScreenShotDir, argv[i], sizeof(ConfigureParams.Screen.szScreenShotDir), NULL); break; case OPT_SCRSHOT_FORMAT: i += 1; if (strcasecmp(argv[i], "bmp") == 0) { ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_BMP; } else if (strcasecmp(argv[i], "png") == 0) { ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_PNG; } else if (strcasecmp(argv[i], "neo") == 0) { ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_NEO; } else if (strcasecmp(argv[i], "ximg") == 0) { ConfigureParams.Screen.ScreenShotFormat = SCREEN_SNAPSHOT_XIMG; } else { return Opt_ShowError(OPT_SCRSHOT_FORMAT, argv[i], "Unknown screenshot format"); } break; /* VDI options */ case OPT_VDI: ok = Opt_Bool(argv[++i], OPT_VDI, &ConfigureParams.Screen.bUseExtVdiResolutions); if (ok) { bLoadAutoSave = false; } break; case OPT_VDI_PLANES: planes = atoi(argv[++i]); switch(planes) { case 1: ConfigureParams.Screen.nVdiColors = GEMCOLOR_2; break; case 2: ConfigureParams.Screen.nVdiColors = GEMCOLOR_4; break; case 4: ConfigureParams.Screen.nVdiColors = GEMCOLOR_16; break; default: return Opt_ShowError(OPT_VDI_PLANES, argv[i], "Unsupported VDI bit-depth"); } ConfigureParams.Screen.bUseExtVdiResolutions = true; bLoadAutoSave = false; break; case OPT_VDI_WIDTH: ConfigureParams.Screen.nVdiWidth = atoi(argv[++i]); ConfigureParams.Screen.bUseExtVdiResolutions = true; bLoadAutoSave = false; break; case OPT_VDI_HEIGHT: ConfigureParams.Screen.nVdiHeight = atoi(argv[++i]); ConfigureParams.Screen.bUseExtVdiResolutions = true; bLoadAutoSave = false; break; /* devices options */ case OPT_JOYSTICK: i++; if (strlen(argv[i]) != 1 || !Joy_SetCursorEmulation(argv[i][0] - '0')) { return Opt_ShowError(OPT_JOYSTICK, argv[i], "Invalid joystick port"); } break; case OPT_JOYSTICK0: case OPT_JOYSTICK1: case OPT_JOYSTICK2: case OPT_JOYSTICK3: case OPT_JOYSTICK4: case OPT_JOYSTICK5: port = argv[i][strlen(argv[i])-1] - '0'; if (port < 0 || port >= JOYSTICK_COUNT) { return Opt_ShowError(OPT_JOYSTICK0, argv[i], "Invalid joystick port"); } i += 1; if (strcasecmp(argv[i], "none") == 0 || strcasecmp(argv[i], "off") == 0) { ConfigureParams.Joysticks.Joy[port].nJoystickMode = JOYSTICK_DISABLED; } else if (strcasecmp(argv[i], "keys") == 0) { ConfigureParams.Joysticks.Joy[port].nJoystickMode = JOYSTICK_KEYBOARD; } else if (strcasecmp(argv[i], "real") == 0) { ConfigureParams.Joysticks.Joy[port].nJoystickMode = JOYSTICK_REALSTICK; } else { return Opt_ShowError(OPT_JOYSTICK0+port, argv[i], "Invalid joystick type"); } break; case OPT_PRINTER: i += 1; /* "none" can be used to disable printer */ ok = Opt_StrCpy(OPT_PRINTER, CHECK_NONE, ConfigureParams.Printer.szPrintToFileName, argv[i], sizeof(ConfigureParams.Printer.szPrintToFileName), &ConfigureParams.Printer.bEnablePrinting); break; #ifdef HAVE_PORTMIDI case OPT_MIDI: ok = Opt_Bool(argv[++i], OPT_MIDI, &ConfigureParams.Midi.bEnableMidi); break; #else case OPT_MIDI_IN: i += 1; ok = Opt_StrCpy(OPT_MIDI_IN, CHECK_FILE, ConfigureParams.Midi.sMidiInFileName, argv[i], sizeof(ConfigureParams.Midi.sMidiInFileName), &ConfigureParams.Midi.bEnableMidi); break; case OPT_MIDI_OUT: i += 1; ok = Opt_StrCpy(OPT_MIDI_OUT, CHECK_NONE, ConfigureParams.Midi.sMidiOutFileName, argv[i], sizeof(ConfigureParams.Midi.sMidiOutFileName), &ConfigureParams.Midi.bEnableMidi); break; #endif case OPT_RS232_IN: i += 1; ok = Opt_StrCpy(OPT_RS232_IN, CHECK_FILE, ConfigureParams.RS232.szInFileName, argv[i], sizeof(ConfigureParams.RS232.szInFileName), &ConfigureParams.RS232.bEnableRS232); break; case OPT_RS232_OUT: i += 1; ok = Opt_StrCpy(OPT_RS232_OUT, CHECK_NONE, ConfigureParams.RS232.szOutFileName, argv[i], sizeof(ConfigureParams.RS232.szOutFileName), &ConfigureParams.RS232.bEnableRS232); break; case OPT_SCCA_IN: i += 1; ok = Opt_StrCpy(OPT_SCCA_IN, CHECK_FILE, ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL], argv[i], sizeof(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL]), &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL]); break; case OPT_SCCA_OUT: i += 1; ok = Opt_StrCpy(OPT_SCCA_OUT, CHECK_NONE, ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL], argv[i], sizeof(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL]), &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL]); break; case OPT_SCCA_LAN_IN: i += 1; ok = Opt_StrCpy(OPT_SCCA_LAN_IN, CHECK_FILE, ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN], argv[i], sizeof(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN]), &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN]); break; case OPT_SCCA_LAN_OUT: i += 1; ok = Opt_StrCpy(OPT_SCCA_LAN_OUT, CHECK_NONE, ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN], argv[i], sizeof(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN]), &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN]); break; case OPT_SCCB_IN: i += 1; ok = Opt_StrCpy(OPT_SCCB_IN, CHECK_FILE, ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B], argv[i], sizeof(ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B]), &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B]); break; case OPT_SCCB_OUT: i += 1; ok = Opt_StrCpy(OPT_SCCB_OUT, CHECK_NONE, ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B], argv[i], sizeof(ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B]), &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B]); break; /* disk options */ case OPT_DRIVEA: ok = Opt_Bool(argv[++i], OPT_DRIVEA, &ConfigureParams.DiskImage.EnableDriveA); break; case OPT_DRIVEB: ok = Opt_Bool(argv[++i], OPT_DRIVEB, &ConfigureParams.DiskImage.EnableDriveB); break; case OPT_DRIVEA_HEADS: val = atoi(argv[++i]); if(val != 1 && val != 2) { return Opt_ShowError(OPT_DRIVEA_HEADS, argv[i], "Invalid number of heads"); } ConfigureParams.DiskImage.DriveA_NumberOfHeads = val; break; case OPT_DRIVEB_HEADS: val = atoi(argv[++i]); if(val != 1 && val != 2) { return Opt_ShowError(OPT_DRIVEB_HEADS, argv[i], "Invalid number of heads"); } ConfigureParams.DiskImage.DriveB_NumberOfHeads = val; break; case OPT_DISKA: i += 1; if (Floppy_SetDiskFileName(0, argv[i], NULL)) { ConfigureParams.HardDisk.bBootFromHardDisk = false; bLoadAutoSave = false; } else return Opt_ShowError(OPT_ERROR, argv[i], "Not a disk image"); break; case OPT_DISKB: i += 1; if (Floppy_SetDiskFileName(1, argv[i], NULL)) bLoadAutoSave = false; else return Opt_ShowError(OPT_ERROR, argv[i], "Not a disk image"); break; case OPT_FASTFLOPPY: ok = Opt_Bool(argv[++i], OPT_FASTFLOPPY, &ConfigureParams.DiskImage.FastFloppy); break; case OPT_WRITEPROT_FLOPPY: i += 1; if (strcasecmp(argv[i], "off") == 0) ConfigureParams.DiskImage.nWriteProtection = WRITEPROT_OFF; else if (strcasecmp(argv[i], "on") == 0) ConfigureParams.DiskImage.nWriteProtection = WRITEPROT_ON; else if (strcasecmp(argv[i], "auto") == 0) ConfigureParams.DiskImage.nWriteProtection = WRITEPROT_AUTO; else return Opt_ShowError(OPT_WRITEPROT_FLOPPY, argv[i], "Unknown option value"); break; case OPT_WRITEPROT_HD: i += 1; if (strcasecmp(argv[i], "off") == 0) ConfigureParams.HardDisk.nWriteProtection = WRITEPROT_OFF; else if (strcasecmp(argv[i], "on") == 0) ConfigureParams.HardDisk.nWriteProtection = WRITEPROT_ON; else if (strcasecmp(argv[i], "auto") == 0) ConfigureParams.HardDisk.nWriteProtection = WRITEPROT_AUTO; else return Opt_ShowError(OPT_WRITEPROT_HD, argv[i], "Unknown option value"); break; case OPT_GEMDOS_CASE: i += 1; if (strcasecmp(argv[i], "off") == 0) ConfigureParams.HardDisk.nGemdosCase = GEMDOS_NOP; else if (strcasecmp(argv[i], "upper") == 0) ConfigureParams.HardDisk.nGemdosCase = GEMDOS_UPPER; else if (strcasecmp(argv[i], "lower") == 0) ConfigureParams.HardDisk.nGemdosCase = GEMDOS_LOWER; else return Opt_ShowError(OPT_GEMDOS_CASE, argv[i], "Unknown option value"); break; case OPT_GEMDOS_HOSTTIME: i += 1; if (strcasecmp(argv[i], "atari") == 0) ConfigureParams.HardDisk.bGemdosHostTime = false; else if (strcasecmp(argv[i], "host") == 0) ConfigureParams.HardDisk.bGemdosHostTime = true; else return Opt_ShowError(OPT_GEMDOS_HOSTTIME, argv[i], "Unknown option value"); break; case OPT_GEMDOS_CONVERT: ok = Opt_Bool(argv[++i], OPT_GEMDOS_CONVERT, &ConfigureParams.HardDisk.bFilenameConversion); break; case OPT_GEMDOS_DRIVE: i += 1; if (strcasecmp(argv[i], "skip") == 0) { ConfigureParams.HardDisk.nGemdosDrive = DRIVE_SKIP; break; } else if (strlen(argv[i]) == 1) { drive = toupper(argv[i][0]); if (drive >= 'C' && drive <= 'Z') { drive = drive - 'C' + DRIVE_C; ConfigureParams.HardDisk.nGemdosDrive = drive; break; } } return Opt_ShowError(OPT_GEMDOS_DRIVE, argv[i], "Invalid "); case OPT_HARDDRIVE: i += 1; ok = Opt_StrCpy(OPT_HARDDRIVE, CHECK_DIR, ConfigureParams.HardDisk.szHardDiskDirectories[0], argv[i], sizeof(ConfigureParams.HardDisk.szHardDiskDirectories[0]), &ConfigureParams.HardDisk.bUseHardDiskDirectories); if (ok && ConfigureParams.HardDisk.bUseHardDiskDirectories && ConfigureParams.HardDisk.szHardDiskDirectories[0][0]) { ConfigureParams.HardDisk.bBootFromHardDisk = true; } else { ConfigureParams.HardDisk.bUseHardDiskDirectories = false; ConfigureParams.HardDisk.bBootFromHardDisk = false; } bLoadAutoSave = false; break; case OPT_ACSIHDIMAGE: i += 1; str = Opt_DriveValue(argv[i], &drive); if (drive < 0 || drive >= MAX_ACSI_DEVS) return Opt_ShowError(OPT_ACSIHDIMAGE, str, "Invalid ACSI drive , must be 0-7"); ok = Opt_StrCpy(OPT_ACSIHDIMAGE, CHECK_FILE, ConfigureParams.Acsi[drive].sDeviceFile, str, sizeof(ConfigureParams.Acsi[drive].sDeviceFile), &ConfigureParams.Acsi[drive].bUseDevice); if (ok) { bLoadAutoSave = false; } break; case OPT_SCSIHDIMAGE: i += 1; str = Opt_DriveValue(argv[i], &drive); if (drive < 0 || drive >= MAX_SCSI_DEVS) return Opt_ShowError(OPT_SCSIHDIMAGE, str, "Invalid SCSI drive , must be 0-7"); ok = Opt_StrCpy(OPT_SCSIHDIMAGE, CHECK_FILE, ConfigureParams.Scsi[drive].sDeviceFile, str, sizeof(ConfigureParams.Scsi[drive].sDeviceFile), &ConfigureParams.Scsi[drive].bUseDevice); if (ok) { bLoadAutoSave = false; } break; case OPT_SCSIVERSION: i += 1; str = Opt_DriveValue(argv[i], &drive); if (drive < 0 || drive >= MAX_SCSI_DEVS) return Opt_ShowError(OPT_SCSIVERSION, str, "Invalid SCSI drive , must be 0-7"); if (strcmp(str, "1") == 0) ConfigureParams.Scsi[drive].nScsiVersion = 1; else if (strcmp(str, "2") == 0) ConfigureParams.Scsi[drive].nScsiVersion = 2; else return Opt_ShowError(OPT_SCSIVERSION, argv[i], "Invalid SCSI version"); break; case OPT_IDEMASTERHDIMAGE: i += 1; ok = Opt_StrCpy(OPT_IDEMASTERHDIMAGE, CHECK_FILE, ConfigureParams.Ide[0].sDeviceFile, argv[i], sizeof(ConfigureParams.Ide[0].sDeviceFile), &ConfigureParams.Ide[0].bUseDevice); if (ok) { bLoadAutoSave = false; } break; case OPT_IDESLAVEHDIMAGE: i += 1; ok = Opt_StrCpy(OPT_IDESLAVEHDIMAGE, CHECK_FILE, ConfigureParams.Ide[1].sDeviceFile, argv[i], sizeof(ConfigureParams.Ide[1].sDeviceFile), &ConfigureParams.Ide[1].bUseDevice); if (ok) { bLoadAutoSave = false; } break; case OPT_IDEBYTESWAP: i += 1; str = Opt_DriveValue(argv[i], &drive); if (drive < 0 || drive > 1) return Opt_ShowError(OPT_IDEBYTESWAP, str, "Invalid IDE drive , must be 0/1"); if (strcasecmp(str, "off") == 0) ConfigureParams.Ide[drive].nByteSwap = BYTESWAP_OFF; else if (strcasecmp(str, "on") == 0) ConfigureParams.Ide[drive].nByteSwap = BYTESWAP_ON; else if (strcasecmp(str, "auto") == 0) ConfigureParams.Ide[drive].nByteSwap = BYTESWAP_AUTO; else return Opt_ShowError(OPT_IDEBYTESWAP, argv[i], "Invalid byte-swap setting"); break; /* Memory options */ case OPT_MEMSIZE: memsize = atoi(argv[++i]); memsize = STMemory_RAM_Validate_Size_KB ( memsize ); if (memsize < 0) { return Opt_ShowError(OPT_MEMSIZE, argv[i], "Invalid memory size"); } ConfigureParams.Memory.STRamSize_KB = memsize; bLoadAutoSave = false; break; case OPT_TT_RAM: memsize = atoi(argv[++i]); ConfigureParams.Memory.TTRamSize_KB = Opt_ValueAlignMinMax(memsize+3, 4, 0, 1024) * 1024; bLoadAutoSave = false; break; case OPT_TOS: i += 1; ok = Opt_StrCpy(OPT_TOS, CHECK_FILE, ConfigureParams.Rom.szTosImageFileName, argv[i], sizeof(ConfigureParams.Rom.szTosImageFileName), &bUseTos); if (ok || !bUseTos) { bLoadAutoSave = false; } break; case OPT_PATCHTOS: ok = Opt_Bool(argv[++i], OPT_PATCHTOS, &ConfigureParams.Rom.bPatchTos); break; case OPT_CARTRIDGE: i += 1; ok = Opt_StrCpy(OPT_CARTRIDGE, CHECK_FILE, ConfigureParams.Rom.szCartridgeImageFileName, argv[i], sizeof(ConfigureParams.Rom.szCartridgeImageFileName), NULL); if (ok) { bLoadAutoSave = false; } break; case OPT_MEMSTATE: i += 1; ok = Opt_StrCpy(OPT_MEMSTATE, CHECK_FILE, ConfigureParams.Memory.szMemoryCaptureFileName, argv[i], sizeof(ConfigureParams.Memory.szMemoryCaptureFileName), NULL); if (ok) { bLoadMemorySave = true; bLoadAutoSave = false; } break; /* CPU options */ case OPT_CPULEVEL: /* UAE core uses cpu_level variable */ ncpu = atoi(argv[++i]); if(ncpu < 0 || ncpu == 5 || ncpu > 6) { return Opt_ShowError(OPT_CPULEVEL, argv[i], "Invalid CPU level"); } if ( ncpu == 6 ) /* Special case for 68060, nCpuLevel should be 5 */ ncpu = 5; ConfigureParams.System.nCpuLevel = ncpu; bLoadAutoSave = false; break; case OPT_CPUCLOCK: cpuclock = atoi(argv[++i]); if(cpuclock != 8 && cpuclock != 16 && cpuclock != 32) { return Opt_ShowError(OPT_CPUCLOCK, argv[i], "Invalid CPU clock"); } Configuration_ChangeCpuFreq ( cpuclock ); bLoadAutoSave = false; break; case OPT_COMPATIBLE: ok = Opt_Bool(argv[++i], OPT_COMPATIBLE, &ConfigureParams.System.bCompatibleCpu); if (ok) { bLoadAutoSave = false; } break; case OPT_CPU_ADDR24: ok = Opt_Bool(argv[++i], OPT_CPU_ADDR24, &ConfigureParams.System.bAddressSpace24); bLoadAutoSave = false; break; case OPT_CPU_DATA_CACHE: ok = Opt_Bool(argv[++i], OPT_CPU_DATA_CACHE, &ConfigureParams.System.bCpuDataCache); bLoadAutoSave = false; break; case OPT_CPU_CYCLE_EXACT: ok = Opt_Bool(argv[++i], OPT_CPU_CYCLE_EXACT, &ConfigureParams.System.bCycleExactCpu); bLoadAutoSave = false; break; case OPT_FPU_TYPE: i += 1; if (strcasecmp(argv[i], "none") == 0 || strcasecmp(argv[i], "off") == 0) { ConfigureParams.System.n_FPUType = FPU_NONE; } else if (strcasecmp(argv[i], "68881") == 0) { ConfigureParams.System.n_FPUType = FPU_68881; } else if (strcasecmp(argv[i], "68882") == 0) { ConfigureParams.System.n_FPUType = FPU_68882; } else if (strcasecmp(argv[i], "internal") == 0) { ConfigureParams.System.n_FPUType = FPU_CPU; } else { return Opt_ShowError(OPT_FPU_TYPE, argv[i], "Unknown FPU type"); } bLoadAutoSave = false; break; /* case OPT_FPU_JIT_COMPAT: ok = Opt_Bool(argv[++i], OPT_FPU_COMPATIBLE, &ConfigureParams.System.bCompatibleFPU); break; */ case OPT_FPU_SOFTFLOAT: ok = Opt_Bool(argv[++i], OPT_FPU_SOFTFLOAT, &ConfigureParams.System.bSoftFloatFPU); break; case OPT_MMU: ok = Opt_Bool(argv[++i], OPT_MMU, &ConfigureParams.System.bMMU); bLoadAutoSave = false; break; /* system options */ case OPT_MACHINE: i += 1; if (strcasecmp(argv[i], "st") == 0) { ConfigureParams.System.nMachineType = MACHINE_ST; ConfigureParams.System.nCpuLevel = 0; Configuration_ChangeCpuFreq ( 8 ); } else if (strcasecmp(argv[i], "megast") == 0) { ConfigureParams.System.nMachineType = MACHINE_MEGA_ST; ConfigureParams.System.nCpuLevel = 0; Configuration_ChangeCpuFreq ( 8 ); } else if (strcasecmp(argv[i], "ste") == 0) { ConfigureParams.System.nMachineType = MACHINE_STE; ConfigureParams.System.nCpuLevel = 0; Configuration_ChangeCpuFreq ( 8 ); } else if (strcasecmp(argv[i], "megaste") == 0) { ConfigureParams.System.nMachineType = MACHINE_MEGA_STE; ConfigureParams.System.nCpuLevel = 0; Configuration_ChangeCpuFreq ( 8 ); } else if (strcasecmp(argv[i], "tt") == 0) { ConfigureParams.System.nMachineType = MACHINE_TT; ConfigureParams.System.nCpuLevel = 3; Configuration_ChangeCpuFreq ( 32 ); } else if (strcasecmp(argv[i], "falcon") == 0) { #if ENABLE_DSP_EMU ConfigureParams.System.nDSPType = DSP_TYPE_EMU; #endif ConfigureParams.System.nMachineType = MACHINE_FALCON; ConfigureParams.System.nCpuLevel = 3; Configuration_ChangeCpuFreq ( 16 ); } else { return Opt_ShowError(OPT_MACHINE, argv[i], "Unknown machine type"); } if (Config_IsMachineST() || Config_IsMachineSTE()) { ConfigureParams.System.bMMU = false; ConfigureParams.System.bAddressSpace24 = true; } if (Config_IsMachineTT()) { ConfigureParams.System.bCompatibleFPU = true; ConfigureParams.System.n_FPUType = FPU_68882; } else { ConfigureParams.System.n_FPUType = FPU_NONE; /* TODO: or leave it as-is? */ } bLoadAutoSave = false; break; case OPT_BLITTER: ok = Opt_Bool(argv[++i], OPT_BLITTER, &ConfigureParams.System.bBlitter); if (ok) { bLoadAutoSave = false; } break; case OPT_TIMERD: ok = Opt_Bool(argv[++i], OPT_TIMERD, &ConfigureParams.System.bPatchTimerD); break; case OPT_FASTBOOT: ok = Opt_Bool(argv[++i], OPT_FASTBOOT, &ConfigureParams.System.bFastBoot); break; case OPT_DSP: i += 1; if (strcasecmp(argv[i], "none") == 0 || strcasecmp(argv[i], "off") == 0) { ConfigureParams.System.nDSPType = DSP_TYPE_NONE; } else if (strcasecmp(argv[i], "dummy") == 0) { ConfigureParams.System.nDSPType = DSP_TYPE_DUMMY; } else if (strcasecmp(argv[i], "emu") == 0) { #if ENABLE_DSP_EMU ConfigureParams.System.nDSPType = DSP_TYPE_EMU; #else return Opt_ShowError(OPT_DSP, argv[i], "DSP type 'emu' support not compiled in"); #endif } else { return Opt_ShowError(OPT_DSP, argv[i], "Unknown DSP type"); } bLoadAutoSave = false; break; case OPT_RTC_YEAR: year = atoi(argv[++i]); if(year && (year < 1980 || year >= 2080)) { return Opt_ShowError(OPT_RTC_YEAR, argv[i], "Invalid RTC year"); } ConfigureParams.System.nRtcYear = year; break; /* sound options */ case OPT_YM_MIXING: i += 1; if (strcasecmp(argv[i], "linear") == 0) { ConfigureParams.Sound.YmVolumeMixing = YM_LINEAR_MIXING; } else if (strcasecmp(argv[i], "table") == 0) { ConfigureParams.Sound.YmVolumeMixing = YM_TABLE_MIXING; } else if (strcasecmp(argv[i], "model") == 0) { ConfigureParams.Sound.YmVolumeMixing = YM_MODEL_MIXING; } else { return Opt_ShowError(OPT_YM_MIXING, argv[i], "Unknown YM mixing method"); } break; case OPT_SOUND: i += 1; if (strcasecmp(argv[i], "off") == 0) { ConfigureParams.Sound.bEnableSound = false; } else { freq = atoi(argv[i]); if (freq < 6000 || freq > 50066) { return Opt_ShowError(OPT_SOUND, argv[i], "Unsupported sound frequency"); } ConfigureParams.Sound.nPlaybackFreq = freq; ConfigureParams.Sound.bEnableSound = true; } Log_Printf(LOG_DEBUG, "Sound %s, frequency = %d.\n", ConfigureParams.Sound.bEnableSound ? "ON" : "OFF", ConfigureParams.Sound.nPlaybackFreq); break; case OPT_SOUNDBUFFERSIZE: i += 1; temp = atoi(argv[i]); if ( temp == 0 ) /* use default setting for SDL */ ; else if (temp < 10 || temp > 100) { return Opt_ShowError(OPT_SOUNDBUFFERSIZE, argv[i], "Unsupported sound buffer size"); } Log_Printf(LOG_DEBUG, "SDL sound buffer size = %d ms.\n", temp); ConfigureParams.Sound.SdlAudioBufferSize = temp; break; case OPT_SOUNDSYNC: ok = Opt_Bool(argv[++i], OPT_SOUNDSYNC, &ConfigureParams.Sound.bEnableSoundSync); break; case OPT_MICROPHONE: ok = Opt_Bool(argv[++i], OPT_MICROPHONE, &ConfigureParams.Sound.bEnableMicrophone); break; case OPT_COUNTRY_CODE: ok = Opt_CountryCode(argv[++i], OPT_COUNTRY_CODE, &ConfigureParams.Keyboard.nCountryCode); break; case OPT_LANGUAGE: ok = Opt_CountryCode(argv[++i], OPT_LANGUAGE, &ConfigureParams.Keyboard.nLanguage); break; case OPT_KBD_LAYOUT: ok = Opt_CountryCode(argv[++i], OPT_KBD_LAYOUT, &ConfigureParams.Keyboard.nKbdLayout); break; case OPT_KEYMAPFILE: i += 1; ok = Opt_StrCpy(OPT_KEYMAPFILE, CHECK_FILE, ConfigureParams.Keyboard.szMappingFileName, argv[i], sizeof(ConfigureParams.Keyboard.szMappingFileName), &valid); if (ok && !valid) { ConfigureParams.Keyboard.szMappingFileName[0] = 0; } break; /* debug options */ #ifdef WIN32 case OPT_WINCON: ConfigureParams.Log.bConsoleWindow = true; break; #endif case OPT_DEBUG: if (ExceptionDebugMask) { ExceptionDebugMask = EXCEPT_NONE; Log_Printf(LOG_INFO, "Exception debugging disabled.\n"); } else { ExceptionDebugMask = ConfigureParams.Debugger.nExceptionDebugMask; Log_Printf(LOG_INFO, "Exception debugging enabled (0x%x).\n", ExceptionDebugMask); } break; case OPT_EXCEPTIONS: i += 1; /* sets ConfigureParams.Debugger.nExceptionDebugMask */ errstr = Log_SetExceptionDebugMask(argv[i]); if (errstr) { if (!errstr[0]) { /* silent parsing termination */ return false; } return Opt_ShowError(OPT_EXCEPTIONS, argv[i], errstr); } if (ExceptionDebugMask) { /* already enabled, change run-time config */ int oldmask = ExceptionDebugMask; ExceptionDebugMask = ConfigureParams.Debugger.nExceptionDebugMask; Log_Printf(LOG_INFO, "Exception debugging changed (0x%x -> 0x%x).\n", oldmask, ExceptionDebugMask); } break; case OPT_LILO: { size_t len; len = strlen(argv[++i]); len += strlen(ConfigureParams.Lilo.szCommandLine); if (argv[i][0] && len+2 < sizeof(ConfigureParams.Lilo.szCommandLine)) { strcat(ConfigureParams.Lilo.szCommandLine, " "); strcat(ConfigureParams.Lilo.szCommandLine, argv[i]); } else if (argv[i][0]) { return Opt_ShowError(OPT_LILO, argv[i], "kernel command line too long"); } bLoadAutoSave = false; bUseLilo = true; bUseTos = false; break; } case OPT_BIOSINTERCEPT: ok = Opt_Bool(argv[++i], OPT_BIOSINTERCEPT, &bBiosIntercept); Log_Printf(LOG_DEBUG, "XBIOS 11/20/255 Hatari versions %sabled: " "Dbmsg(), Scrdmp(), HatariControl().\n", bBiosIntercept ? "en" : "dis"); XBios_EnableCommands(bBiosIntercept); break; case OPT_CONOUT: i += 1; dev = atoi(argv[i]); if (!Console_SetDevice(dev)) { return Opt_ShowError(OPT_CONOUT, argv[i], "Invalid console device vector number"); } Log_Printf(LOG_DEBUG, "Xcounout device %d vector redirection enabled.\n", dev); break; case OPT_NATFEATS: ok = Opt_Bool(argv[++i], OPT_NATFEATS, &ConfigureParams.Log.bNatFeats); Log_Printf(LOG_DEBUG, "Native Features %s.\n", ConfigureParams.Log.bNatFeats ? "enabled" : "disabled"); break; case OPT_DISASM: i += 1; errstr = Disasm_ParseOption(argv[i]); if (errstr) { if (!errstr[0]) { /* silent parsing termination */ return false; } return Opt_ShowError(OPT_DISASM, argv[i], errstr); } break; case OPT_TRACE: i += 1; errstr = Log_SetTraceOptions(argv[i]); if (errstr) { if (!errstr[0]) { /* silent parsing termination */ return false; } return Opt_ShowError(OPT_TRACE, argv[i], errstr); } break; case OPT_TRACEFILE: i += 1; ok = Opt_StrCpy(OPT_TRACEFILE, CHECK_NONE, ConfigureParams.Log.sTraceFileName, argv[i], sizeof(ConfigureParams.Log.sTraceFileName), NULL); break; case OPT_MSG_REPEAT: Log_ToggleMsgRepeat(); break; case OPT_CONTROLSOCKET: i += 1; errstr = Control_SetSocket(argv[i]); if (errstr) { return Opt_ShowError(OPT_CONTROLSOCKET, argv[i], errstr); } break; case OPT_CMDFIFO: i += 1; errstr = Control_SetFifo(argv[i]); if (errstr) { return Opt_ShowError(OPT_CMDFIFO, argv[i], errstr); } break; case OPT_LOGFILE: i += 1; ok = Opt_StrCpy(OPT_LOGFILE, CHECK_NONE, ConfigureParams.Log.sLogFileName, argv[i], sizeof(ConfigureParams.Log.sLogFileName), NULL); break; case OPT_PARSE: i += 1; ok = DebugUI_AddParseFile(argv[i]); break; case OPT_SAVECONFIG: /* Hatari-UI needs Hatari config to start */ Configuration_Save(); exit(0); break; case OPT_LOGLEVEL: i += 1; ConfigureParams.Log.nTextLogLevel = Log_ParseOptions(argv[i]); if (ConfigureParams.Log.nTextLogLevel == LOG_NONE) { return Opt_ShowError(OPT_LOGLEVEL, argv[i], "Unknown log level!"); } Log_SetLevels(); break; case OPT_ALERTLEVEL: i += 1; ConfigureParams.Log.nAlertDlgLogLevel = Log_ParseOptions(argv[i]); if (ConfigureParams.Log.nAlertDlgLogLevel == LOG_NONE) { return Opt_ShowError(OPT_ALERTLEVEL, argv[i], "Unknown alert level!"); } Log_SetLevels(); break; case OPT_RUNVBLS: val = atoi(argv[++i]); Log_Printf(LOG_DEBUG, "Exit after %d VBLs.\n", val); Main_SetRunVBLs(val); break; case OPT_BENCHMARK: BenchmarkMode = true; break; case OPT_ERROR: /* unknown option or missing option parameter */ return false; default: return Opt_ShowError(OPT_ERROR, argv[i], "Internal Hatari error, unhandled option"); } if (!ok) { /* Opt_Bool() or Opt_StrCpy() failed */ return false; } } return Opt_ValidateOptions(); } /** * Readline match callback for option name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *Opt_MatchOption(const char *text, int state) { static int i, len; const char *name; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i < ARRAY_SIZE(HatariOptions)) { name = HatariOptions[i++].str; if (name && strncasecmp(name, text, len) == 0) return (strdup(name)); } return NULL; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/paths.c000066400000000000000000000206701504763705000226200ustar00rootroot00000000000000/* Hatari - paths.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Set up the various path strings. */ const char Paths_fileid[] = "Hatari paths.c"; #include #include #include #include "main.h" #include "configuration.h" #include "file.h" #include "paths.h" #include "str.h" #if defined(WIN32) && !defined(mkdir) #define mkdir(name,mode) mkdir(name) #endif /* WIN32 */ #if defined(__APPLE__) #define HATARI_HOME_DIR "Library/Application Support/Hatari" #elif defined(WIN32) #define HATARI_HOME_DIR "AppData\\Local\\Hatari" #else #define HATARI_HOME_DIR ".config/hatari" #endif static char *sWorkingDir; /* Working directory */ static char *sDataDir; /* Directory where data files of Hatari can be found */ static char *sUserHomeDir; /* User's home directory ($HOME) */ static char *sHatariHomeDir; /* Hatari's home directory ($HOME/.hatari/) */ static char *sScreenShotDir; /* Default directory to use for screenshots */ /** * Return pointer to current working directory string */ const char *Paths_GetWorkingDir(void) { return sWorkingDir; } /** * Return pointer to data directory string */ const char *Paths_GetDataDir(void) { return sDataDir; } /** * Return pointer to user's home directory string */ const char *Paths_GetUserHome(void) { return sUserHomeDir; } /** * Return pointer to Hatari's home directory string */ const char *Paths_GetHatariHome(void) { return sHatariHomeDir; } /** * Return pointer to screenshot directory string */ const char *Paths_GetScreenShotDir(void) { if (!ConfigureParams.Screen.szScreenShotDir[0]) return sScreenShotDir; /* user override */ return ConfigureParams.Screen.szScreenShotDir; } /** * Explore the PATH environment variable to see where our executable is * installed. */ static void Paths_GetExecDirFromPATH(const char *argv0, char *pExecDir, int nMaxLen) { char *pPathEnv; char *pAct; char *pTmpName; const char *pToken; /* Get the PATH environment string */ pPathEnv = getenv("PATH"); if (!pPathEnv) return; /* Duplicate the string because strtok destroys it later */ pPathEnv = strdup(pPathEnv); if (!pPathEnv) return; pTmpName = malloc(FILENAME_MAX); if (!pTmpName) { perror("Paths_GetExecDirFromPATH"); free(pPathEnv); return; } /* If there is a semicolon in the PATH, we assume it is the PATH * separator token (like on Windows), otherwise we use a colon. */ if (strchr((pPathEnv), ';')) pToken = ";"; else pToken = ":"; pAct = strtok (pPathEnv, pToken); while (pAct) { snprintf(pTmpName, FILENAME_MAX, "%s%c%s", pAct, PATHSEP, argv0); if (File_Exists(pTmpName)) { /* Found the executable - so use the corresponding path: */ Str_Copy(pExecDir, pAct, nMaxLen); break; } pAct = strtok (NULL, pToken); } free(pPathEnv); free(pTmpName); } /** * Locate the directory where the hatari executable resides */ static char *Paths_InitExecDir(const char *argv0) { char *psExecDir; /* Path string where the hatari executable can be found */ /* Allocate memory for storing the path string of the executable */ psExecDir = malloc(FILENAME_MAX); if (!psExecDir) { fprintf(stderr, "Out of memory (Paths_Init)\n"); exit(-1); } /* Determine the bindir... * Start with empty string, then try to use OS specific functions, * and finally analyze the PATH variable if it has not been found yet. */ psExecDir[0] = '\0'; #if defined(__linux__) { int i; /* On Linux, we can analyze the symlink /proc/self/exe */ i = readlink("/proc/self/exe", psExecDir, FILENAME_MAX-1); if (i > 0) { char *p; psExecDir[i] = '\0'; p = strrchr(psExecDir, '/'); /* Search last slash */ if (p) *p = 0; /* Strip file name from path */ } } //#elif defined(WIN32) // /* On Windows we can use GetModuleFileName for getting the exe path */ // GetModuleFileName(NULL, psExecDir, FILENAME_MAX); #endif /* If we do not have the execdir yet, analyze argv[0] and the PATH: */ if (psExecDir[0] == 0) { if (strchr(argv0, PATHSEP) == NULL) { /* No separator in argv[0], we have to explore PATH... */ Paths_GetExecDirFromPATH(argv0, psExecDir, FILENAME_MAX); } else { /* There was a path separator in argv[0], so let's assume a * relative or absolute path to the current directory in argv[0] */ char *p; Str_Copy(psExecDir, argv0, FILENAME_MAX); p = strrchr(psExecDir, PATHSEP); /* Search last slash */ if (p) *p = 0; /* Strip file name from path */ } } return psExecDir; } /** * Initialize the users home directory string * and Hatari's home directory (~/.config/hatari) */ static void Paths_InitHomeDirs(void) { char *psHome; psHome = getenv("HOME"); if (psHome) { sUserHomeDir = Str_Dup(psHome); } #if defined(WIN32) else { char *psDrive; int len = 0; /* Windows home path? */ psDrive = getenv("HOMEDRIVE"); if (psDrive) len = strlen(psDrive); psHome = getenv("HOMEPATH"); if (psHome) len += strlen(psHome); if (len > 0) { sUserHomeDir = Str_Alloc(len); if (psDrive) strcpy(sUserHomeDir, psDrive); if (psHome) strcat(sUserHomeDir, psHome); } } #endif if (!sUserHomeDir) { /* $HOME not set, so let's use current working dir as home */ sUserHomeDir = Str_Dup(sWorkingDir); sHatariHomeDir = Str_Dup(sWorkingDir); return; } sHatariHomeDir = Str_Alloc(strlen(sUserHomeDir) + 1 + strlen(HATARI_HOME_DIR)); /* Try to use a private hatari directory in the users home directory */ sprintf(sHatariHomeDir, "%s%c%s", sUserHomeDir, PATHSEP, HATARI_HOME_DIR); if (File_DirExists(sHatariHomeDir)) { return; } /* Try legacy location ~/.hatari */ sprintf(sHatariHomeDir, "%s%c.hatari", sUserHomeDir, PATHSEP); if (File_DirExists(sHatariHomeDir)) { return; } /* Hatari home directory does not exists yet... * ... so let's try to create it: */ #if !defined(__APPLE__) && !defined(WIN32) sprintf(sHatariHomeDir, "%s%c.config", sUserHomeDir, PATHSEP); if (!File_DirExists(sHatariHomeDir)) { /* ~/.config does not exist yet, create it first */ if (mkdir(sHatariHomeDir, 0700) != 0) { perror("Failed to create ~/.config directory"); } } #endif sprintf(sHatariHomeDir, "%s%c%s", sUserHomeDir, PATHSEP, HATARI_HOME_DIR); if (mkdir(sHatariHomeDir, 0750) != 0) { /* Failed to create, so use user's home dir instead */ strcpy(sHatariHomeDir, sUserHomeDir); } } /** * Initialize directory names * * The datadir will be initialized relative to the bindir (where the executable * has been installed to). This means a lot of additional effort since we first * have to find out where the executable is. But thanks to this effort, we get * a relocatable package (we don't have any absolute path names in the program)! */ void Paths_Init(const char *argv0) { char *psExecDir; /* Path string where the hatari executable can be found */ /* Init working directory string */ sWorkingDir = malloc(FILENAME_MAX); if (!sWorkingDir || getcwd(sWorkingDir, FILENAME_MAX) == NULL) { /* This should never happen... just in case... */ sWorkingDir = Str_Dup("."); } /* Init the user's home directory string */ Paths_InitHomeDirs(); /* Get default screenshot directory string */ #if !defined(__APPLE__) /* TODO: use ~/Pictures/Screenshots/ instead, if it exists? */ sScreenShotDir = Str_Dup(sWorkingDir); #else sScreenShotDir = Paths_GetMacScreenShotDir(); if (!sScreenShotDir) { /* Failsafe, but should not be able to happen */ sScreenShotDir = Str_Dup(sWorkingDir); } #endif /* Get the directory where the executable resides */ psExecDir = Paths_InitExecDir(argv0); /* Now create the datadir path name from the bindir path name: */ sDataDir = Str_Alloc(FILENAME_MAX); if (psExecDir && strlen(psExecDir) > 0) { sprintf(sDataDir, "%s%c%s", psExecDir, PATHSEP, BIN2DATADIR); } else { /* bindir could not be determined, let's assume datadir is relative * to current working directory... */ strcpy(sDataDir, BIN2DATADIR); } /* And finally make a proper absolute path out of datadir: */ File_MakeAbsoluteName(sDataDir); free(psExecDir); /* fprintf(stderr, " WorkingDir = %s\n DataDir = %s\n UserHomeDir = %s\n HatariHomeDir = %s\n ScrenShotDir = %s\n", sWorkingDir, sDataDir, sUserHomeDir, sHatariHomeDir, sScreenShotDir); */ } void Paths_UnInit(void) { Str_Free(sWorkingDir); Str_Free(sDataDir); Str_Free(sUserHomeDir); Str_Free(sHatariHomeDir); Str_Free(sScreenShotDir); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/printer.c000066400000000000000000000070141504763705000231610ustar00rootroot00000000000000/* Hatari - printer.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Printer communication. When bytes are sent from the ST they are sent to these functions via 'Printer_TransferByteTo()'. This will then open a file and direct the output to this. These bytes are buffered up (to improve speed) and this also allow us to detect when the stream goes into idle - at which point we close the file/printer. */ const char Printer_fileid[] = "Hatari printer.c"; #include "main.h" #include "configuration.h" #include "file.h" #include "paths.h" #include "printer.h" #include "log.h" #define PRINTER_DEBUG 0 #if PRINTER_DEBUG #define Dprintf(a) printf a #else #define Dprintf(a) #endif /* After ~4 seconds (4*50 VBLs), flush & close printer */ #define PRINTER_IDLE_CLOSE (4*50) static int nIdleCount; static int bUnflushed; static FILE *pPrinterHandle; /*-----------------------------------------------------------------------*/ /** * Initialise Printer */ void Printer_Init(void) { char *separator; Dprintf((stderr, "Printer_Init()\n")); /* disabled from config/command line? */ if (!ConfigureParams.Printer.bEnablePrinting || !ConfigureParams.Printer.szPrintToFileName[0]) return; /* printer name without path? */ separator = strrchr(ConfigureParams.Printer.szPrintToFileName, PATHSEP); if (!separator) return; *separator = '\0'; if (!File_DirExists(ConfigureParams.Printer.szPrintToFileName)) { Log_AlertDlg(LOG_ERROR, "Printer output file directory inaccessible. Printing disabled."); ConfigureParams.Printer.bEnablePrinting = false; } *separator = PATHSEP; Dprintf((stderr, "Filename for printing: %s \n", ConfigureParams.Printer.szPrintToFileName)); } /*-----------------------------------------------------------------------*/ /** * Uninitialise Printer */ void Printer_UnInit(void) { Dprintf((stderr, "Printer_UnInit()\n")); /* Close any open files */ pPrinterHandle = File_Close(pPrinterHandle); bUnflushed = false; nIdleCount = 0; } /*-----------------------------------------------------------------------*/ /** * Pass byte from emulator to printer. Opens the printer file appending * if it isn't already open. Returns false if connection to "printer" * failed */ bool Printer_TransferByteTo(uint8_t Byte) { /* Do we want to output to a printer/file? */ if (!ConfigureParams.Printer.bEnablePrinting) return false; /* Failed if printing disabled */ /* Have we made a connection to our printer/file? */ if (!pPrinterHandle) { /* open printer file... */ pPrinterHandle = File_Open(ConfigureParams.Printer.szPrintToFileName, "a+b"); if (!pPrinterHandle) { Log_AlertDlg(LOG_ERROR, "Printer output file open failed. Printing disabled."); ConfigureParams.Printer.bEnablePrinting = false; return false; } } if (fputc(Byte, pPrinterHandle) != Byte) { Log_Printf(LOG_ERROR, "Printer_TransferByteTo() writing failed!\n"); return false; } bUnflushed = true; return true; } /*-----------------------------------------------------------------------*/ /** * Empty printer buffer, and if remains idle for set time close connection * (ie close file, stop printer) */ void Printer_CheckIdleStatus(void) { /* Is anything waiting for printer? */ if (bUnflushed) { fflush(pPrinterHandle); bUnflushed = false; nIdleCount = 0; } else { nIdleCount++; /* Has printer been idle? */ if (nIdleCount >= PRINTER_IDLE_CLOSE) { /* Close printer output */ Printer_UnInit(); } } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/psg.c000066400000000000000000000630541504763705000222750ustar00rootroot00000000000000/* Hatari - psg.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Programmable Sound Generator (YM-2149) - PSG Also used for the printer (centronics) port emulation (PSG Port B, Register 15) */ /* 2007/04/14 [NP] First approximation to get cycle accurate accesses to ff8800/02 */ /* by cumulating wait state of 1 cycle and rounding the final */ /* result to 4. */ /* 2007/04/29 [NP] Functions PSG_Void_WriteByte and PSG_Void_ReadByte to handle */ /* accesses to $ff8801/03. These addresses have no effect, but */ /* they must give a 1 cycle wait state (e.g. move.l d0,ff8800). */ /* 2007/09/29 [NP] Replace printf by calls to HATARI_TRACE. */ /* 2007/10/23 [NP] In PSG_Void_WriteByte, add a wait state only if no wait state */ /* were added so far (hack, but gives good result). */ /* 2007/11/18 [NP] In PSG_DataRegister_WriteByte, set unused bit to 0, in case */ /* the data reg is read later (fix Mindbomb Demo / BBC). */ /* 2008/04/20 [NP] In PSG_DataRegister_WriteByte, set unused bit to 0 for register */ /* 6 too (noise period). */ /* 2008/07/27 [NP] Better separation between accesses to the YM hardware registers */ /* and the sound rendering routines. Use Sound_WriteReg() to pass */ /* all writes to the sound rendering functions. This allows to */ /* have sound.c independent of psg.c (to ease replacement of */ /* sound.c by another rendering method). */ /* 2008/08/11 [NP] Set drive leds. */ /* 2008/10/16 [NP] When writing to $ff8800, register select should not be masked */ /* with 0xf, it's a real 8 bits register where all bits are */ /* significant. This means only value <16 should be considered as */ /* valid register selection. When reg select is >= 16, all writes */ /* and reads in $ff8802 should be ignored. */ /* (fix European Demo Intro, which sets addr reg to 0x10 when */ /* sample playback is disabled). */ /* 2008/12/21 [NP] After testing different cases on a real STF, rewrite registers */ /* handling. As only pins BC1 and BDIR are used in an Atari to */ /* address the YM2149, this means only 1 bit is necessary to access*/ /* select/data registers. Other variations of the $ff88xx addresses*/ /* will point to either $ff8800 or $ff8802. Only bit 1 of $ff88xx */ /* is useful to know which register is accessed in the YM2149. */ /* So, it's possible to access the YM2149 with $ff8801 and $ff8803 */ /* but under conditions : the write to a shadow address (bit 0=1) */ /* can't be made by an instruction that writes to the same address */ /* with bit 0=0 at the same time (.W or .L access). */ /* In that case, only the address with bit 0=0 is taken into */ /* account. This means a write to $ff8801/03 will succeed only if */ /* the access size is .B (byte) or the opcode is a movep (because */ /* in that case we won't access the same register with 2 different */ /* addresses) (fix the game X-Out, which uses movep.w to write to */ /* $ff8801/03). */ /* Refactorize some code for cleaner handling of these accesses. */ /* Only reads to $ff8800 will return a data, reads to $ff8801/02/03*/ /* always return 0xff (tested on STF). */ /* When PSGRegisterSelect > 15, reads to $ff8800 also return 0xff. */ /* 2009/01/24 [NP] Remove redundant test, as movep implies SIZE_BYTE access. */ /* 2011/10/30 [NP] There's a special case when reading a register from $ff8800 : */ /* if the register number was not changed since the last write, */ /* then we must return the value that was written to $ff8802 */ /* without masking the unused bit (fix the game Murders In Venice, */ /* which expects to read $10 from reg 3). */ /* 2015/10/15 [NP] Better handling of the wait states when accessing YM2149 regs. */ /* Replace M68000_WaitState(1) by PSG_WaitState() which adds */ /* 4 cycles every 4th access. Previous method worked because all */ /* cycles were rounded to 4, but it was not how real HW works and */ /* would not work in cycle exact mode where cycles are not rounded.*/ /* YM2149 Software-Controlled Sound Generator / Programmable Sound Generator References : - YM2149 datasheet by Yamaha (1987) ----------- VSS/GND -| 1 40 |- VCC NC -| 2 39 |- TEST1 : not connected ANALOG CHANNEL B -| 3 38 |- ANALOG CHANNEL C ANALOG CHANNEL A -| 4 37 |- DA0 NC -| 5 36 |- DA1 IOB7 : connected to parallel port D7 -| 6 35 |- DA2 IOB6 : connected to parallel port D6 -| 7 34 |- DA3 IOB5 : connected to parallel port D5 -| 8 33 |- DA4 IOB4 : connected to parallel port D4 -| 9 32 |- DA5 IOB3 : connected to parallel port D3 -| 10 31 |- DA6 IOB2 : connected to parallel port D2 -| 11 30 |- DA7 IOB1 : connected to parallel port D1 -| 12 29 |- BC1 IOB0 : connected to parallel port D0 -| 13 28 |- BC2 : connected to VCC IOA7 : not connected -| 14 27 |- BDIR IOA6 : connected to GPO -| 15 26 |- SEL(INV) : not connected IOA5 : connected to parallel port STROBE -| 16 25 |- A8 : connected to VCC IOA4 : connected to RS232C port DTR -| 17 24 |- A9(INV) : connected to VSS/GND IOA3 : connected to RS232C port RTS -| 18 23 |- RESET(INV) IOA2 : connected to floppy drive 1 'select' -| 19 22 |- CLOCK : connected to 2 MHz IOA1 : connected to floppy drive 0 'select' -| 20 21 |- IOA0 : connected to floppy drives 'side select' ----------- Registers : 0xff8800.b Set address register (write) / Read content of selected data register (read) 0xff8802.b Write into selected data register (write) / No action, return 0xFF (read) Note that under certain conditions 0xff8801 can be accessed as a shadow version of 0xff8800. Similarly, 0xff8803 can be used instead of 0xff8802. Also, only bits 0 and 1 of addresses 0xff88xx are used to access the YM2149, which means the region 0xff8804 - 0xff88ff can be used to access 0xff8800 - 0xff8803 (with a special case for Falcon when not in ST compatible mode) */ /* Emulating wait states when accessing $ff8800/01/02/03 with different 'move' variants */ /* is a complex task. So far, adding 1 cycle wait state to each access and rounding the */ /* final number to 4 gave some good results, but this is certainly not the way it's */ /* working for real in the ST. */ /* Also in Hatari it only works when the cpu rounds all cycles to the next multiple */ /* of 4, but it will not work when running in cycle exact mode. This means we must */ /* add 4 cycles at a time, but not on every register access, see below. */ /* */ /* The following examples show some verified wait states for different accesses : */ /* lea $ffff8800,a1 */ /* lea $ffff8802,a2 */ /* lea $ffff8801,a3 */ /* */ /* movep.w d1,(a1) ; 20 16+4 (ventura loader) */ /* movep.l d1,(a1) ; 28 24+4 (ventura loader, ulm loader) */ /* */ /* movep.l d6,0(a5) ; 28 24+4 (SNY I, TCB) */ /* movep.w d5,0(a5) ; 20 16+4 (SNY I, TCB) */ /* */ /* move.b d1,(a1) ; 12 8+4 */ /* move.b d1,(a2) ; 12 8+4 */ /* move.b d1,(a3) ; 12 8+4 (crickey ulm hidden) */ /* */ /* move.w d1,(a1) ; 12 8+4 */ /* move.w d1,(a2) ; 12 8+4 */ /* move.l d1,(a1) ; 16 12+4 (ulm loader) */ /* */ /* movem.l d1,(a1) ; 20 16+4 */ /* movem.l d1-d2,(a1) ; 28 24+4 */ /* movem.l d1-d3,(a1) ; 40 32+4+4 */ /* movem.l d1-d4,(a1) ; 48 40+4+4 */ /* movem.l d1-d5,(a1) ; 60 48+4+4+4 */ /* movem.l d1-d6,(a1) ; 68 56+4+4+4 */ /* movem.l d1-d7,(a1) ; 80 64+4+4+4+4 */ /* movem.l d0-d7,(a1) ; 88 72+4+4+4+4 */ /* */ /* movep.w d0,(a3) (X-Out) */ /* */ /* clr.b (a1) ; 20 12+8 (4 for read + 4 for write) */ /* tas (a1) ; 16 (no waitstate ?) */ /* */ /* */ /* This gives the following "model" : */ /* - each instruction accessing a valid YM2149 register gets an initial 4 cycle */ /* wait state for the 1st access (whether it accesses just 1 reg (eg move.b) */ /* or up to 4 regs (movep.l)). */ /* - susbequent accesses made by the same instruction don't add more wait state */ /* (except if the instruction is a MOVEM). */ /* - MOVEM can access more than 4 regs (up to 15) : in that case we add 4 extra */ /* cycles each time we access a 4th register (eg : regs 4,8,12, ...) */ /* - accesses to $ff8801 or $ff8803 are considered "valid" only if we don't access */ /* the corresponding "non shadow" addresses $ff8800/02 at the same time (ie with */ /* the same instruction). */ /* This means only .B size (move.b for example) or movep opcode will work. */ /* If the access is valid, add 4 cycle wait state when necessary, else ignore */ /* the write and don't add any cycle. */ const char PSG_fileid[] = "Hatari psg.c"; #include "main.h" #include "configuration.h" #include "ioMem.h" #include "joy.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "sound.h" #include "printer.h" /* because Printer I/O goes through PSG Register 15 */ #include "psg.h" #if ENABLE_DSP_EMU #include "falcon/dsp.h" #endif #include "video.h" #include "statusbar.h" #include "mfp.h" #include "fdc.h" #include "scc.h" static uint8_t PSGRegisterSelect; /* Write to 0xff8800 sets the register number used in read/write accesses */ static uint8_t PSGRegisterReadData; /* Value returned when reading from 0xff8800 */ uint8_t PSGRegisters[MAX_PSG_REGISTERS]; /* Registers in PSG, see PSG_REG_xxxx */ static unsigned int LastStrobe=0; /* Falling edge of Strobe used for printer */ /*-----------------------------------------------------------------------*/ /** * Reset variables used in PSG */ void PSG_Reset(void) { int i; if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym reset video_cyc=%d %d@%d pc=%x instr_cycle %d\n", FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } PSGRegisterSelect = 0; PSGRegisterReadData = 0; memset(PSGRegisters, 0, sizeof(PSGRegisters)); PSGRegisters[PSG_REG_IO_PORTA] = 0xff; /* no drive selected + side 0 after a reset */ /* Update sound's emulation registers */ for (i = 0; i < NUM_PSG_SOUND_REGISTERS; i++) Sound_WriteReg ( i , 0 ); LastStrobe=0; } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void PSG_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&PSGRegisterSelect, sizeof(PSGRegisterSelect)); MemorySnapShot_Store(&PSGRegisterReadData, sizeof(PSGRegisterReadData)); MemorySnapShot_Store(PSGRegisters, sizeof(PSGRegisters)); MemorySnapShot_Store(&LastStrobe, sizeof(LastStrobe)); } /*-----------------------------------------------------------------------*/ /** * Write byte to the YM address register (usually 0xff8800). This is used * as a selector for when we read/write the YM data register (0xff8802). */ void PSG_Set_SelectRegister(uint8_t val) { /* Store register used to read/write in $ff8802. This register */ /* is 8 bits on the YM2149, this means it should not be masked */ /* with 0xf. Instead, we keep the 8 bits, but we must ignore */ /* read/write to ff8802 when PSGRegisterSelect >= 16 */ PSGRegisterSelect = val; /* When address register is changed, a read from $ff8800 should */ /* return the masked value of the register. We set the value here */ /* to be returned in case PSG_Get_DataRegister is called */ PSGRegisterReadData = PSGRegisters[PSGRegisterSelect]; if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write reg=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", PSGRegisterSelect, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Read byte from 0xff8800, return PSG data */ uint8_t PSG_Get_DataRegister(void) { /* Is a valid PSG register currently selected ? */ if ( PSGRegisterSelect >= MAX_PSG_REGISTERS ) return 0xff; /* not valid, return 0xff */ if (PSGRegisterSelect == PSG_REG_IO_PORTA) { /* Second parallel port joystick uses centronics strobe bit as fire button: */ if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED) { if (Joy_GetStickData(JOYID_PARPORT2) & 0x80) PSGRegisters[PSG_REG_IO_PORTA] &= ~32; else PSGRegisters[PSG_REG_IO_PORTA] |= 32; } } else if (PSGRegisterSelect == PSG_REG_IO_PORTB) { /* PSG register 15 is parallel port data register - used by parallel port joysticks: */ if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED) { PSGRegisters[PSG_REG_IO_PORTB] &= 0x0f; PSGRegisters[PSG_REG_IO_PORTB] |= ~Joy_GetStickData(JOYID_PARPORT1) << 4; } if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED) { PSGRegisters[PSG_REG_IO_PORTB] &= 0xf0; PSGRegisters[PSG_REG_IO_PORTB] |= ~Joy_GetStickData(JOYID_PARPORT2) & 0x0f; } } /* Read data last selected by register */ return PSGRegisterReadData; } /*-----------------------------------------------------------------------*/ /** * Write byte to YM's register (0xff8802), store according to PSG select register (0xff8800) */ void PSG_Set_DataRegister(uint8_t val) { uint8_t val_old; if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write data reg=0x%x val=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", PSGRegisterSelect, val, FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* Is a valid PSG register currently selected ? */ if ( PSGRegisterSelect >= MAX_PSG_REGISTERS ) return; /* not valid, ignore write and do nothing */ /* Create samples up until this point with current values */ Sound_Update ( Cycles_GetClockCounterOnWriteAccess() ); /* When a read is made from $ff8800 without changing PSGRegisterSelect, we should return */ /* the non masked value. */ PSGRegisterReadData = val; /* store non masked value for PSG_Get_DataRegister */ /* Read previous content */ val_old = PSGRegisters[PSGRegisterSelect]; /* Copy value to PSGRegisters[] */ PSGRegisters[PSGRegisterSelect] = val; /* Clear unused bits for some regs */ if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_COARSE ) || ( PSGRegisterSelect == PSG_REG_ENV_SHAPE ) ) PSGRegisters[PSGRegisterSelect] &= 0x0f; /* only keep bits 0 - 3 */ else if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_AMP ) || ( PSGRegisterSelect == PSG_REG_NOISE_GENERATOR ) ) PSGRegisters[PSGRegisterSelect] &= 0x1f; /* only keep bits 0 - 4 */ if ( PSGRegisterSelect < NUM_PSG_SOUND_REGISTERS ) { /* Copy sound related registers 0..13 to the sound module's internal buffer */ Sound_WriteReg ( PSGRegisterSelect , PSGRegisters[PSGRegisterSelect] ); } else if ( PSGRegisterSelect == PSG_REG_IO_PORTA ) { /* * FIXME: This is only a prelimary dirty hack! * Port B (Printer port) - writing here needs to be dispatched to the printer * STROBE (Port A bit5) does a short LOW and back to HIGH when the char is valid * To print you need to write the character byte to IOB and you need to toggle STROBE * (like EmuTOS does). */ /* Printer dispatching only when printing is activated */ if (ConfigureParams.Printer.bEnablePrinting) { /* Bit 5 - Centronics strobe? If STROBE is low and the LastStrobe was high, then print/transfer to the emulated Centronics port. */ if (LastStrobe && ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<5)) == 0 )) { /* Seems like we want to print something... */ Printer_TransferByteTo(PSGRegisters[PSG_REG_IO_PORTB]); /* Initiate a possible GPIP0 Printer BUSY interrupt */ MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE0 , MFP_GPIP_STATE_LOW ); /* Initiate a possible GPIP1 Falcon ACK interrupt */ if (Config_IsMachineFalcon()) MFP_GPIP_Set_Line_Input ( pMFP_Main , MFP_GPIP_LINE1 , MFP_GPIP_STATE_LOW ); } } LastStrobe = PSGRegisters[PSG_REG_IO_PORTA]&(1<<5); /* Bit 0-2 : side and drive select */ if ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<1)) == 0 ) { /* floppy drive A is ON */ Statusbar_SetFloppyLed(DRIVE_LED_A, LED_STATE_ON); } else { Statusbar_SetFloppyLed(DRIVE_LED_A, LED_STATE_OFF); } if ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<2)) == 0 ) { /* floppy drive B is ON */ Statusbar_SetFloppyLed(DRIVE_LED_B, LED_STATE_ON); } else { Statusbar_SetFloppyLed(DRIVE_LED_B, LED_STATE_OFF); } /* Report a possible drive/side change */ FDC_SetDriveSide ( val_old & 7 , PSGRegisters[PSG_REG_IO_PORTA] & 7 ); /* Handle MegaSTE / TT specific bit 7 in PORTA of the PSG */ if (Config_IsMachineMegaSTE() || Config_IsMachineTT()) { SCC_Check_Lan_IsEnabled (); } /* Handle Falcon specific bits in PORTA of the PSG */ if (Config_IsMachineFalcon()) { /* Bit 3 - centronics port SELIN line (pin 17) */ /* if (PSGRegisters[PSG_REG_IO_PORTA] & (1 << 3)) { // not emulated yet } */ /* Bit 4 - DSP reset? */ if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<4)) { Log_Printf(LOG_DEBUG, "Calling DSP_Reset?\n"); #if ENABLE_DSP_EMU if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU) { DSP_Reset(); } #endif } /* Bit 6 - Internal Speaker control */ if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<6)) { /*Log_Printf(LOG_DEBUG, "Falcon: Internal Speaker state\n");*/ /* FIXME: add code to handle? (if we want to emulate the speaker at all? */ } /* Bit 7 - Reset IDE? */ if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<7)) { Log_Printf(LOG_DEBUG, "Falcon: Reset IDE subsystem\n"); /* FIXME: add code to handle IDE reset */ } } } } /*-----------------------------------------------------------------------*/ /** * Handle wait state when accessing YM2149 registers * - each instruction accessing YM2149 gets an initial 4 cycle wait state * for the 1st access (whether it accesses just 1 reg (eg move.b) or up to 4 regs (movep.l)) * - special case for movem which can access more than 4 regs (up to 15) : * we add 4 extra cycles each time we access a 4th reg (eg : regs 4,8,12, ...) * * See top of this file for several examples measured on real STF */ static void PSG_WaitState(void) { #if 0 M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */ #else static uint64_t PSG_InstrPrevClock; static int NbrAccesses; if ( PSG_InstrPrevClock != CyclesGlobalClockCounter ) /* New instruction accessing YM2149 : add 4 cycles */ { M68000_WaitState ( 4 ); PSG_InstrPrevClock = CyclesGlobalClockCounter; NbrAccesses = 0; } else /* Same instruction doing several accesses : only movem can add more cycles */ { if ( ( OpcodeFamily == i_MVMEL ) || ( OpcodeFamily == i_MVMLE ) ) { NbrAccesses += 1; if ( NbrAccesses % 4 == 0 ) /* Add 4 extra cycles every 4th access */ M68000_WaitState ( 4 ); } } #endif } /*-----------------------------------------------------------------------*/ /** * Read byte from 0xff8800. Return current content of data register */ void PSG_ff8800_ReadByte(void) { PSG_WaitState(); IoMem[IoAccessCurrentAddress] = PSG_Get_DataRegister(); if (LOG_TRACE_LEVEL(TRACE_PSG_READ)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym read data %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Read byte from 0xff8801/02/03. Return 0xff. */ void PSG_ff880x_ReadByte(void) { PSG_WaitState(); IoMem[IoAccessCurrentAddress] = 0xff; if (LOG_TRACE_LEVEL(TRACE_PSG_READ)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym read void %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } /*-----------------------------------------------------------------------*/ /** * Write byte to 0xff8800. Set content of YM's address register. */ void PSG_ff8800_WriteByte(void) { PSG_WaitState(); if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } PSG_Set_SelectRegister ( IoMem[IoAccessCurrentAddress] ); } /*-----------------------------------------------------------------------*/ /** * Write byte to 0xff8801. Set content of YM's address register under conditions. * Address 0xff8801 is a shadow version of 0xff8800, so both addresses can't be written * at the same time by the same instruction. This means only a .B access or * a movep will have a valid effect, other accesses are ignored. */ void PSG_ff8801_WriteByte(void) { if ( nIoMemAccessSize == SIZE_BYTE ) /* byte access or movep */ { PSG_WaitState(); if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } PSG_Set_SelectRegister ( IoMem[IoAccessCurrentAddress] ); } else { /* do nothing, just a trace if needed */ if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write ignored %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } } /*-----------------------------------------------------------------------*/ /** * Write byte to 0xff8802. Set content of YM's data register. */ void PSG_ff8802_WriteByte(void) { PSG_WaitState(); if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } PSG_Set_DataRegister ( IoMem[IoAccessCurrentAddress] ); } /*-----------------------------------------------------------------------*/ /** * Write byte to 0xff8803. Set content of YM's data register under conditions. * Address 0xff8803 is a shadow version of 0xff8802, so both addresses can't be written * at the same time by the same instruction. This means only a .B access or * a movep will have a valid effect, other accesses are ignored. */ void PSG_ff8803_WriteByte(void) { if ( nIoMemAccessSize == SIZE_BYTE ) /* byte access or movep */ { PSG_WaitState(); if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } PSG_Set_DataRegister ( IoMem[IoAccessCurrentAddress] ); } else { /* do nothing, just a trace if needed */ if (LOG_TRACE_LEVEL(TRACE_PSG_WRITE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("ym write ignored %x=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", IoAccessCurrentAddress, IoMem[IoAccessCurrentAddress], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } } } /* ------------------------------------------------------------------ * YM-2149 register content dump (for debugger info command) */ void PSG_Info(FILE *fp, uint32_t dummy) { int i; for(i = 0; i < ARRAY_SIZE(PSGRegisters); i++) { fprintf(fp, "Reg $%02X : $%02X\n", i, PSGRegisters[i]); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/reset.c000066400000000000000000000073011504763705000226170ustar00rootroot00000000000000/* Hatari This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Reset emulation state. */ const char Reset_fileid[] = "Hatari reset.c"; #include "main.h" #include "configuration.h" #include "cart.h" #include "dmaSnd.h" #include "crossbar.h" #include "fdc.h" #include "floppy.h" #include "gemdos.h" #include "hdc.h" #include "acia.h" #include "ikbd.h" #include "ioMem.h" #include "cycles.h" #include "cycInt.h" #include "m68000.h" #include "mfp.h" #include "midi.h" #include "ncr5380.h" #include "blitter.h" #include "psg.h" #include "reset.h" #include "scc.h" #include "screen.h" #include "scu_vme.h" #include "sound.h" #include "stMemory.h" #include "tos.h" #include "vdi.h" #include "nvram.h" #include "video.h" #include "falcon/videl.h" #include "falcon/dsp.h" #include "debugcpu.h" #include "debugdsp.h" /*-----------------------------------------------------------------------*/ /** * Reset ST emulator states, chips, interrupts and registers. * Return zero or negative TOS image load error code. */ static int Reset_ST(bool bCold) { /* Ensure MMU has default values before calling memory_init() later */ STMemory_Reset ( bCold ); if (bCold) { int ret; IoMem_Reset(); Floppy_GetBootDrive(); /* Find which device to boot from (A: or C:) */ ret = TOS_InitImage(); /* Load TOS and copy it into ROM memory */ if (ret) return ret; /* If we can not load a TOS image, return now! */ Cart_ResetImage(); /* Load cartridge program into ROM memory. */ /* Video timings can change only on cold boot (wakeup states) */ Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); } CycInt_Reset(); /* Reset interrupts */ MFP_Reset_All(); /* Setup MFPs */ Video_Reset(); /* Reset video */ VDI_Reset(); /* Reset internal VDI variables */ NvRam_Reset(); /* reset NvRAM (video) settings */ GemDOS_Reset(); /* Reset GEMDOS emulation */ if (bCold) { FDC_Reset( bCold ); /* Reset FDC */ } Floppy_Reset(); /* Reset Floppy */ if (Config_IsMachineFalcon() || Config_IsMachineTT()) { Ncr5380_Reset(); } if (Config_IsMachineTT() || Config_IsMachineMegaSTE()) { SCU_Reset( bCold ); } if (Config_IsMachineFalcon()) { DSP_Reset(); /* Reset the DSP */ Crossbar_Reset(bCold); /* Reset Crossbar sound */ } else DmaSnd_Reset(bCold); /* Reset DMA sound */ if (Config_IsMachineMegaSTE()) MegaSTE_CPU_Cache_Reset(); Blitter_Reset(); /* Reset Blitter */ PSG_Reset(); /* Reset PSG */ Sound_Reset(); /* Reset Sound */ ACIA_Reset( ACIA_Array ); /* ACIA */ IKBD_Reset(bCold); /* Keyboard (after ACIA) */ SCC_Reset(); if (Config_IsMachineFalcon() && !bUseVDIRes) VIDEL_reset(); else Screen_Reset(); /* Reset screen */ M68000_Reset(bCold); /* Reset CPU */ DebugCpu_SetDebugging(); /* Re-set debugging flag if needed */ DebugDsp_SetDebugging(); Midi_Reset(); /* Start HBL, Timer B and VBL interrupts with a 0 cycle delay */ Video_StartInterrupts( 0 ); return 0; } /*-----------------------------------------------------------------------*/ /** * Cold reset ST (reset memory, all registers and reboot) */ int Reset_Cold(void) { /* Set mouse pointer to the middle of the screen */ Main_WarpMouse(sdlscrn->w/2, sdlscrn->h/2, false); return Reset_ST(true); } /*-----------------------------------------------------------------------*/ /** * Warm reset ST (reset registers, leave in same state and reboot) */ int Reset_Warm(void) { return Reset_ST(false); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/resolution.c000066400000000000000000000051761504763705000237100ustar00rootroot00000000000000/* Hatari - resolution.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. SDL resolution limitation and selection routines. */ const char Resolution_fileid[] = "Hatari resolution.c"; #include #include "main.h" #include "configuration.h" #include "log.h" #include "resolution.h" #include "statusbar.h" #include "screen.h" #define DEBUG 0 #if DEBUG # define DEBUGPRINT(x) printf x #else # define DEBUGPRINT(x) #endif static int DesktopWidth, DesktopHeight; /** * Initializes resolution settings (gets current desktop * resolution, sets max Falcon/TT Videl zooming resolution). */ void Resolution_Init(void) { SDL_DisplayMode dm; if (SDL_GetDesktopDisplayMode(0, &dm) == 0) { DesktopWidth = dm.w; DesktopHeight = dm.h; } else { Log_Printf(LOG_ERROR, "SDL_GetDesktopDisplayMode failed: %s", SDL_GetError()); DesktopWidth = 2*NUM_VISIBLE_LINE_PIXELS; DesktopHeight = 2*NUM_VISIBLE_LINES+STATUSBAR_MAX_HEIGHT; } /* if user hasn't set own max zoom size, use desktop size */ if (!(ConfigureParams.Screen.nMaxWidth && ConfigureParams.Screen.nMaxHeight)) { ConfigureParams.Screen.nMaxWidth = DesktopWidth; ConfigureParams.Screen.nMaxHeight = DesktopHeight; } DEBUGPRINT(("Desktop resolution: %dx%d\n",DesktopWidth, DesktopHeight)); Log_Printf(LOG_DEBUG, "Configured max Hatari resolution = %dx%d, optimal for ST = %dx%d(+%d)\n", ConfigureParams.Screen.nMaxWidth, ConfigureParams.Screen.nMaxHeight, 2*NUM_VISIBLE_LINE_PIXELS, 2*NUM_VISIBLE_LINES, STATUSBAR_MAX_HEIGHT); } /** * Get current desktop resolution */ void Resolution_GetDesktopSize(int *width, int *height) { DEBUGPRINT(("resolution: limit to desktop size\n")); *width = DesktopWidth; *height = DesktopHeight; } /** * Get max resolution */ static void Resolution_GetMaxSize(int *width, int *height) { DEBUGPRINT(("resolution: use specified max size\n")); *width = ConfigureParams.Screen.nMaxWidth; *height = ConfigureParams.Screen.nMaxHeight; } /** * Set given width & height arguments to maximum size allowed in the * configuration. */ void Resolution_GetLimits(int *width, int *height, bool keep) { *width = *height = 0; if (bInFullScreen) { /* resolution change not allowed? */ if (keep) { Resolution_GetDesktopSize(width, height); } } if (ConfigureParams.Screen.bForceMax) { /* force given max size */ Resolution_GetMaxSize(width, height); return; } if (!(*width && *height) || (ConfigureParams.Screen.nMaxWidth < *width && ConfigureParams.Screen.nMaxHeight < *height)) { Resolution_GetMaxSize(width, height); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/rs232.c000066400000000000000000000407051504763705000223550ustar00rootroot00000000000000/* Hatari - rs232.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. RS-232 Communications This is similar to the printing functions, we open a direct file (e.g. /dev/ttyS0) and send bytes over it. Using such method mimics the ST exactly, and even allows us to connect to an actual ST! */ const char RS232_fileid[] = "Hatari rs232.c"; #include #include "main.h" #include "configuration.h" #include "file.h" #include "ioMem.h" #include "m68000.h" #include "mfp.h" #include "rs232.h" /* AmigaOS has termios.h, but no tcsetattr() and friends - d'oh! */ #if !defined(HAVE_TCSETATTR) #undef HAVE_TERMIOS_H #endif #if HAVE_TERMIOS_H # include # include #endif #if HAVE_SYS_IOCTL_H # include #endif #define RS232_DEBUG 0 #if RS232_DEBUG #define Dprintf(a) printf a #else #define Dprintf(a) #endif struct RS232_s { FILE *ReadFile; /* Handle to file for reading */ int Read_fd; /* Corresponding file descriptor */ bool Read_fd_IsATTY; /* true if fd is a tty */ FILE *WriteFile; /* Same for writing */ int Write_fd; bool Write_fd_IsATTY; bool ByteReceived; uint8_t RxByte; }; static struct RS232_s RS232_MFP; #if HAVE_TERMIOS_H #if !HAVE_CFMAKERAW static inline void cfmakeraw(struct termios *termios_p) { termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); termios_p->c_oflag &= ~OPOST; termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); termios_p->c_cflag &= ~(CSIZE|PARENB); termios_p->c_cflag |= CS8; } #endif /*-----------------------------------------------------------------------*/ /** * Set serial line parameters to "raw" mode. */ static bool RS232_SetRawMode ( int fd , bool IsATTY ) { struct termios termmode; memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */ if ( IsATTY ) { if (tcgetattr(fd, &termmode) != 0) return false; /* Set "raw" mode: */ termmode.c_cc[VMIN] = 1; termmode.c_cc[VTIME] = 0; cfmakeraw(&termmode); if (tcsetattr(fd, TCSADRAIN, &termmode) != 0) return false; } return true; } /*-----------------------------------------------------------------------*/ /** * Set hardware configuration of RS-232: * - Bits per character * - Parity * - Start/stop bits */ static bool RS232_SetBitsConfig ( int fd, bool IsATTY, int nCharSize, int nStopBits, bool bUseParity, bool bEvenParity ) { struct termios termmode; memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */ if ( IsATTY ) { if (tcgetattr(fd, &termmode) != 0) { Dprintf(("RS232_SetBitsConfig: tcgetattr failed.\n")); return false; } /* Set the character size: */ termmode.c_cflag &= ~CSIZE; switch (nCharSize) { case 8: termmode.c_cflag |= CS8; break; case 7: termmode.c_cflag |= CS7; break; case 6: termmode.c_cflag |= CS6; break; case 5: termmode.c_cflag |= CS5; break; } /* Set stop bits: */ if (nStopBits >= 2) termmode.c_oflag |= CSTOPB; else termmode.c_oflag &= ~CSTOPB; /* Parity bit: */ if (bUseParity) termmode.c_cflag |= PARENB; else termmode.c_cflag &= ~PARENB; if (bEvenParity) termmode.c_cflag &= ~PARODD; else termmode.c_cflag |= PARODD; /* Now store the configuration: */ if (tcsetattr(fd, TCSADRAIN, &termmode) != 0) { Dprintf(("RS232_SetBitsConfig: tcsetattr failed.\n")); return false; } } return true; } #endif /* HAVE_TERMIOS_H */ /*-----------------------------------------------------------------------*/ /** * Open file on COM port. */ static bool RS232_OpenCOMPort(void) { bool ok = true; RS232_MFP.ByteReceived = false; if (!RS232_MFP.WriteFile && ConfigureParams.RS232.szOutFileName[0]) { /* Create our serial file for output */ RS232_MFP.WriteFile = File_Open(ConfigureParams.RS232.szOutFileName, "wb"); if ( RS232_MFP.WriteFile ) { setvbuf(RS232_MFP.WriteFile, NULL, _IONBF, 0); RS232_MFP.Write_fd = fileno ( RS232_MFP.WriteFile ); if ( isatty ( RS232_MFP.Write_fd ) ) RS232_MFP.Write_fd_IsATTY = true; else RS232_MFP.Write_fd_IsATTY = false; #if HAVE_TERMIOS_H /* First set the output parameters to "raw" mode */ if (!RS232_SetRawMode ( RS232_MFP.Write_fd , RS232_MFP.Write_fd_IsATTY )) { Log_Printf(LOG_WARN, "Can't set raw mode for %s\n", ConfigureParams.RS232.szOutFileName); } #endif Dprintf(("Successfully opened RS232 output file.\n")); } else { RS232_MFP.Write_fd = -1; Log_Printf(LOG_WARN, "RS232: Failed to open output file %s\n", ConfigureParams.RS232.szOutFileName); ok = false; } } if (!RS232_MFP.ReadFile && ConfigureParams.RS232.szInFileName[0]) { /* Create our serial file for output */ RS232_MFP.ReadFile = File_Open(ConfigureParams.RS232.szInFileName, "rb"); if ( RS232_MFP.ReadFile ) { setvbuf(RS232_MFP.ReadFile, NULL, _IONBF, 0); RS232_MFP.Read_fd = fileno ( RS232_MFP.ReadFile ); if ( isatty ( RS232_MFP.Read_fd ) ) RS232_MFP.Read_fd_IsATTY = true; else RS232_MFP.Read_fd_IsATTY = false; #if HAVE_TERMIOS_H /* Now set the input parameters to "raw" mode */ if (!RS232_SetRawMode ( RS232_MFP.Read_fd, RS232_MFP.Read_fd_IsATTY )) { Log_Printf(LOG_WARN, "Can't set raw mode for %s\n", ConfigureParams.RS232.szInFileName); } #endif Dprintf(("Successfully opened RS232 input file.\n")); } else { RS232_MFP.Read_fd = -1; Log_Printf(LOG_WARN, "RS232: Failed to open input file %s\n", ConfigureParams.RS232.szInFileName); ok = false; } } return ok; } /*-----------------------------------------------------------------------*/ /** * Close file on COM port */ static void RS232_CloseCOMPort(void) { /* Write side needs to be closed first. Otherwise Hatari * instances at both ends of a "RS-232" FIFO file would freeze * when Hatari exists or RS-232 configuration is changed * (with this, only one of them freezes until other * end of a FIFO also closes the "device" file(s)). */ if ( RS232_MFP.WriteFile ) { File_Close ( RS232_MFP.WriteFile ); RS232_MFP.WriteFile = NULL; } if ( RS232_MFP.ReadFile ) { File_Close( RS232_MFP.ReadFile ); RS232_MFP.ReadFile = NULL; } Dprintf(("Closed RS232 files.\n")); } /*-----------------------------------------------------------------------*/ void RS232_Update(void) { if (!RS232_MFP.ByteReceived && RS232_MFP.ReadFile && File_InputAvailable(RS232_MFP.ReadFile)) { int ch = fgetc(RS232_MFP.ReadFile); if (ch != EOF) { RS232_MFP.RxByte = ch; RS232_MFP.ByteReceived = true; MFP_InputOnChannel(pMFP_Main, MFP_INT_RCV_BUF_FULL, 0); } else { RS232_MFP.RxByte = 0xff; } } } /*-----------------------------------------------------------------------*/ /** * Initialize RS-232 (open and configure device handles if enabled). */ void RS232_Init(void) { if (ConfigureParams.RS232.bEnableRS232) { if (!RS232_OpenCOMPort()) { RS232_CloseCOMPort(); Log_AlertDlg(LOG_ERROR, "RS232 input or output file open failed. RS232 support disabled."); ConfigureParams.RS232.bEnableRS232 = false; return; } } } /*-----------------------------------------------------------------------*/ /** * Close RS-232 connection and stop checking for incoming data. */ void RS232_UnInit(void) { RS232_CloseCOMPort(); } /*-----------------------------------------------------------------------*/ /** * Set hardware configuration of RS-232 according to the USART control register. * * ucr: USART Control Register * Bit 0: unused * Bit 1: 0-Odd Parity, 1-Even Parity * Bit 2: 0-No Parity, 1-Parity * Bits 3,4: Start/Stop bits * 0 0 : 0-Start, 0-Stop Synchronous * 0 1 : 0-Start, 1-Stop Asynchronous * 1 0 : 1-Start, 1.5-Stop Asynchronous * 1 1 : 1-Start, 2-Stop Asynchronous * Bits 5,6: 'WordLength' * 0 0 : 8 Bits * 0 1 : 7 Bits * 1 0 : 6 Bits * 1 1 : 5 Bits * Bit 7: Frequency from TC and RC */ static void RS232_HandleUCR(int16_t ucr) { #if HAVE_TERMIOS_H int nCharSize; /* Bits per character: 5, 6, 7 or 8 */ int nStopBits; /* Stop bits: 0=0 bits, 1=1 bit, 2=1.5 bits, 3=2 bits */ nCharSize = 8 - ((ucr >> 5) & 3); nStopBits = (ucr >> 3) & 3; Dprintf(("RS232_HandleUCR(%i) : character size=%i , stop bits=%i\n", ucr, nCharSize, nStopBits)); if ( RS232_MFP.WriteFile ) { if ( !RS232_SetBitsConfig ( RS232_MFP.Write_fd, RS232_MFP.Write_fd_IsATTY, nCharSize, nStopBits, ucr&4, ucr&2 ) ) Log_Printf(LOG_WARN, "RS232_HandleUCR: failed to set bits configuration for %s\n", ConfigureParams.RS232.szOutFileName); } if ( RS232_MFP.ReadFile ) { if ( !RS232_SetBitsConfig ( RS232_MFP.Read_fd, RS232_MFP.Read_fd_IsATTY, nCharSize, nStopBits, ucr&4, ucr&2 ) ) Log_Printf(LOG_WARN, "RS232_HandleUCR: failed to set bits configuration for %s\n", ConfigureParams.RS232.szInFileName); } #endif /* HAVE_TERMIOS_H */ } /*-----------------------------------------------------------------------*/ /** * Set baud rate configuration of RS-232. */ static bool RS232_SetBaudRate(int nBaud) { #if HAVE_TERMIOS_H int i; speed_t baudtype; struct termios termmode; static const int baudtable[][2] = { { 50, B50 }, { 75, B75 }, { 110, B110 }, { 134, B134 }, { 150, B150 }, { 200, B200 }, { 300, B300 }, { 600, B600 }, { 1200, B1200 }, { 1800, B1800 }, { 2400, B2400 }, { 4800, B4800 }, { 9600, B9600 }, { 19200, B19200 }, { 38400, B38400 }, { 57600, B57600 }, { 115200, B115200 }, #ifdef B230400 /* B230400 is not defined on all systems */ { 230400, B230400 }, #endif { -1, -1 } }; Dprintf(("RS232_SetBaudRate(%i)\n", nBaud)); /* Convert baud number to baud termios constant: */ baudtype = -1; for (i = 0; baudtable[i][0] != -1; i++) { if (baudtable[i][0] == nBaud) { baudtype = baudtable[i][1]; break; } } if (baudtype == (speed_t)-1) { Dprintf(("RS232_SetBaudRate: Unsupported baud rate %i.\n", nBaud)); return false; } /* Set output speed: */ if ( RS232_MFP.WriteFile ) { memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */ if ( RS232_MFP.Write_fd_IsATTY ) { if (tcgetattr ( RS232_MFP.Write_fd, &termmode ) != 0) return false; cfsetospeed(&termmode, baudtype); if (tcsetattr( RS232_MFP.Write_fd, TCSADRAIN, &termmode ) != 0) return false; } } /* Set input speed: */ if ( RS232_MFP.ReadFile ) { memset (&termmode, 0, sizeof(termmode)); /* Init with zeroes */ if ( RS232_MFP.Read_fd_IsATTY ) { if (tcgetattr ( RS232_MFP.Read_fd, &termmode ) != 0) return false; cfsetispeed(&termmode, baudtype); if (tcsetattr ( RS232_MFP.Read_fd, TCSADRAIN, &termmode ) != 0) return false; } } #endif /* HAVE_TERMIOS_H */ return true; } /*-----------------------------------------------------------------------*/ /** * Set baud rate configuration of RS-232 according to the Timer-D hardware * registers. */ void RS232_SetBaudRateFromTimerD(void) { int nTimerD_CR, nTimerD_DR, nBaudRate; nTimerD_CR = IoMem[0xfffa1d] & 0x07; nTimerD_DR = IoMem[0xfffa25]; if (!nTimerD_CR) return; if ( nTimerD_DR == 0 ) nTimerD_DR = 256; /* In MFP, a data register=0 is in fact 256 */ /* Calculate baud rate: (MFP/Timer-D is supplied with 2.4576 MHz) */ nBaudRate = 2457600 / nTimerD_DR / 2; /*if (IoMem[0xfffa29] & 0x80)*/ /* We only support the by-16 prescaler */ nBaudRate /= 16; switch (nTimerD_CR) { case 1: nBaudRate /= 4; break; case 2: nBaudRate /= 10; break; case 3: nBaudRate /= 16; break; case 4: nBaudRate /= 50; break; case 5: nBaudRate /= 64; break; case 6: nBaudRate /= 100; break; case 7: nBaudRate /= 200; break; } /* Adjust some ugly baud rates from TOS to more reasonable values: */ switch (nBaudRate) { case 80: nBaudRate = 75; break; case 109: nBaudRate = 110; break; case 120: nBaudRate = 110; break; case 1745: nBaudRate = 1800; break; case 1920: nBaudRate = 1800; break; } RS232_SetBaudRate(nBaudRate); } /*----------------------------------------------------------------------- */ /** * Get value of DCD and CTS input signals, as returned by the underlying OS */ void RS232_Get_DCD_CTS ( uint8_t *pDCD , uint8_t *pCTS ) { /* Set default value for DCD and CTS in case ioctl() fails or RS232 emulation is not enabled */ *pDCD = 1; *pCTS = 1; if (!ConfigureParams.RS232.bEnableRS232) return; #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCMGET) int status = 0; if ( ( RS232_MFP.Read_fd >= 0 ) && RS232_MFP.Read_fd_IsATTY ) { if ( ioctl ( RS232_MFP.Read_fd , TIOCMGET , &status ) < 0 ) { Log_Printf(LOG_DEBUG, "RS232_Get_DCD_CTS: Can't get status for DCD/CTS errno=%d\n" , errno); } else { if ( status & TIOCM_CAR ) *pDCD = 1; else *pDCD = 0; if ( status & TIOCM_CTS ) *pCTS = 1; else *pCTS = 0; } } //fprintf ( stderr , "RS232_Get_DCD_CTS dcd=%d cts=%d\n" , *pDCD, *pCTS ); #endif } /*----------------------------------------------------------------------- */ /** * Pass bytes from emulator to RS-232 */ static bool RS232_TransferBytesTo(uint8_t *pBytes, int nBytes) { /* Make sure there's a RS-232 connection if it's enabled */ if (ConfigureParams.RS232.bEnableRS232) RS232_OpenCOMPort(); /* Have we connected to the RS232 ? */ if ( RS232_MFP.WriteFile ) { /* Send bytes directly to the serial file */ if (fwrite(pBytes, 1, nBytes, RS232_MFP.WriteFile)) { Dprintf(("RS232: Sent %i bytes ($%x ...)\n", nBytes, *pBytes)); MFP_InputOnChannel ( pMFP_Main , MFP_INT_TRN_BUF_EMPTY , 0 ); return true; /* OK */ } } return false; /* Failed */ } /*-----------------------------------------------------------------------*/ /** * Read from the Synchronous Character Register. */ void RS232_SCR_ReadByte(void) { M68000_WaitState(4); /* nothing */ } /*-----------------------------------------------------------------------*/ /** * Write to the Synchronous Character Register. */ void RS232_SCR_WriteByte(void) { M68000_WaitState(4); /*Dprintf(("RS232: Write to SCR: $%x\n", (int)IoMem[0xfffa27]));*/ } /*-----------------------------------------------------------------------*/ /** * Read from the USART Control Register. */ void RS232_UCR_ReadByte(void) { M68000_WaitState(4); Dprintf(("RS232: Read from UCR: $%x\n", (int)IoMem[0xfffa29])); } /*-----------------------------------------------------------------------*/ /** * Write to the USART Control Register. */ void RS232_UCR_WriteByte(void) { M68000_WaitState(4); Dprintf(("RS232: Write to UCR: $%x\n", (int)IoMem[0xfffa29])); RS232_HandleUCR(IoMem[0xfffa29]); } /*-----------------------------------------------------------------------*/ /** * Read from the Receiver Status Register. */ void RS232_RSR_ReadByte(void) { M68000_WaitState(4); if ( RS232_MFP.ByteReceived ) IoMem[0xfffa2b] |= 0x80; /* Buffer full */ else IoMem[0xfffa2b] &= ~0x80; /* Buffer not full */ Dprintf(("RS232: Read from RSR: $%x\n", (int)IoMem[0xfffa2b])); } /*-----------------------------------------------------------------------*/ /** * Write to the Receiver Status Register. */ void RS232_RSR_WriteByte(void) { M68000_WaitState(4); Dprintf(("RS232: Write to RSR: $%x\n", (int)IoMem[0xfffa2b])); } /*-----------------------------------------------------------------------*/ /** * Read from the Transmitter Status Register. * When RS232 emulation is not enabled, we still return 0x80 to allow * some games to work when they don't require send/receive on the RS232 port * (eg : 'Treasure Trap', 'The Deep' write some debug information to RS232) */ void RS232_TSR_ReadByte(void) { M68000_WaitState(4); IoMem[0xfffa2d] |= 0x80; /* Buffer empty */ Dprintf(("RS232: Read from TSR: $%x\n", (int)IoMem[0xfffa2d])); } /*-----------------------------------------------------------------------*/ /** * Write to the Transmitter Status Register. */ void RS232_TSR_WriteByte(void) { M68000_WaitState(4); Dprintf(("RS232: Write to TSR: $%x\n", (int)IoMem[0xfffa2d])); } /*-----------------------------------------------------------------------*/ /** * Read from the USART Data Register. */ void RS232_UDR_ReadByte(void) { M68000_WaitState(4); IoMem[0xfffa2f] = RS232_MFP.RxByte; RS232_MFP.ByteReceived = false; Dprintf(("RS232: Read from UDR: $%x\n", (int)IoMem[0xfffa2f])); } /*-----------------------------------------------------------------------*/ /** * Write to the USART Data Register. */ void RS232_UDR_WriteByte(void) { uint8_t OutByte; M68000_WaitState(4); OutByte = IoMem[0xfffa2f]; RS232_TransferBytesTo(&OutByte, 1); Dprintf(("RS232: Write to UDR: $%x\n", (int)IoMem[0xfffa2f])); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/rtc.c000066400000000000000000000155041504763705000222710ustar00rootroot00000000000000/* Hatari - rtc.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Mega-ST / Mega-STE real time clock. There is probably a more efficient way to do it, such as using directly a timer in ram instead of calling localtime for each function. For now it will show that it works, at least... In fact these mappings seems to force the gem to ask the IKBD for the real time (seconds units). See ikbd.c for the time returned by the IKBD. NOTE : we only emulate the case where BANK=0 in MODE register, as TOS doesn't use the BANK=1 setting for alarm */ /* RP5C15 Real Time Clock References : - RP/RF/RJ5C15 datasheet by Ricoh (EK-086-9908, June 1995) ----------- CS(INV) -| 1 18 |- VCC CS -| 2 17 |- OSCOUT CLKOUT : connected to TPI -| 3 16 |- OSCIN A0 -| 4 15 |- ALARM(INV) : not connected A1 -| 5 14 |- D3 A2 -| 6 13 |- D2 A3 -| 7 12 |- D1 RD(INV) -| 8 11 |- D0 GND -| 9 10 |- WR(INV) ----------- Registers (when BANK=0) : 0xfffc21.b Seconds, units 0xfffc23.b Seconds, tens 0xfffc25.b Minutes, units 0xfffc27.b Minutes, tens 0xfffc29.b Hours, units 0xfffc2b.b Hours, tens 0xfffc2d.b Weekday 0xfffc2f.b Day, units 0xfffc31.b Day, tens 0xfffc33.b Month, units 0xfffc35.b Month, tens 0xfffc37.b Year, units 0xfffc39.b Year, tens 0xfffc3b.b Mode register 0xfffc3d.b Test register 0xfffc3f.b Reset register */ const char Rtc_fileid[] = "Hatari rtc.c"; #include #include "main.h" #include "configuration.h" #include "ioMem.h" #include "rtc.h" static bool rtc_bank; /* RTC bank select (0=normal, 1=configuration(?)) */ static int8_t fake_am, fake_amz; static int year_offset; /*-----------------------------------------------------------------------*/ /** * Helper to return pointer to system time struct * (statically allocated by libc) */ static struct tm* get_localtime(void) { /* Get system time */ time_t nTimeTicks = time(NULL); return localtime(&nTimeTicks); } /*-----------------------------------------------------------------------*/ /** * tm->tm_year starts from 1900, GEMDOS year from 1980 * Set suitable tm->tm_year offset for GEMDOS */ void Rtc_Init(void) { year_offset = 80; if (!ConfigureParams.System.nRtcYear) return; year_offset += 1900 + get_localtime()->tm_year - ConfigureParams.System.nRtcYear; } /*-----------------------------------------------------------------------*/ /** * Read seconds units. */ void Rtc_SecondsUnits_ReadByte(void) { IoMem[0xfffc21] = get_localtime()->tm_sec % 10; } /*-----------------------------------------------------------------------*/ /** * Read seconds tens. */ void Rtc_SecondsTens_ReadByte(void) { IoMem[0xfffc23] = get_localtime()->tm_sec / 10; } /*-----------------------------------------------------------------------*/ /** * Read minutes units. */ void Rtc_MinutesUnits_ReadByte(void) { if (rtc_bank) { IoMem[0xfffc25] = fake_am; } else { IoMem[0xfffc25] = get_localtime()->tm_min % 10; } } /*-----------------------------------------------------------------------*/ /** * Write minutes units. */ void Rtc_MinutesUnits_WriteByte(void) { /* TOS 1.0x uses this... */ if (rtc_bank) fake_am = ((IoMem[0xfffc25] & 0x0f) | 0xf0); /* else ignore */ } /*-----------------------------------------------------------------------*/ /** * Read minutes tens. */ void Rtc_MinutesTens_ReadByte(void) { if (rtc_bank) { IoMem[0xfffc27] = fake_amz; } else { IoMem[0xfffc27] = get_localtime()->tm_min / 10; } } /*-----------------------------------------------------------------------*/ /** * Write minutes tens. */ void Rtc_MinutesTens_WriteByte(void) { /* TOS 1.0x uses this... */ if (rtc_bank) fake_amz = ((IoMem[0xfffc27] & 0x0f) | 0xf0); /* else ignore */ } /*-----------------------------------------------------------------------*/ /** * Read hours units. */ void Rtc_HoursUnits_ReadByte(void) { IoMem[0xfffc29] = get_localtime()->tm_hour % 10; } /*-----------------------------------------------------------------------*/ /** * Read hours tens. */ void Rtc_HoursTens_ReadByte(void) { IoMem[0xfffc2b] = get_localtime()->tm_hour / 10; } /*-----------------------------------------------------------------------*/ /** * Read weekday. */ void Rtc_Weekday_ReadByte(void) { IoMem[0xfffc2d] = get_localtime()->tm_wday; } /*-----------------------------------------------------------------------*/ /** * Read day units. */ void Rtc_DayUnits_ReadByte(void) { IoMem[0xfffc2f] = get_localtime()->tm_mday % 10; } /*-----------------------------------------------------------------------*/ /** * Read day tens. */ void Rtc_DayTens_ReadByte(void) { IoMem[0xfffc31] = get_localtime()->tm_mday / 10; } /*-----------------------------------------------------------------------*/ /** * Read month units. */ void Rtc_MonthUnits_ReadByte(void) { IoMem[0xfffc33] = (get_localtime()->tm_mon + 1) % 10; } /*-----------------------------------------------------------------------*/ /** * Read month tens. */ void Rtc_MonthTens_ReadByte(void) { IoMem[0xfffc35] = (get_localtime()->tm_mon + 1) / 10; } /*-----------------------------------------------------------------------*/ /** * Read year units. */ void Rtc_YearUnits_ReadByte(void) { IoMem[0xfffc37] = (get_localtime()->tm_year - year_offset) % 10; } /*-----------------------------------------------------------------------*/ /** * Read year tens. */ void Rtc_YearTens_ReadByte(void) { IoMem[0xfffc39] = (get_localtime()->tm_year - year_offset) / 10; } /*-----------------------------------------------------------------------*/ /** * Read clock mod. */ void Rtc_ClockMod_ReadByte(void) { IoMem[0xfffc3b] = ((IoMem[0xfffc3b] & 0x0f) | 0xf0); } /*-----------------------------------------------------------------------*/ /** * Write clock mod. */ void Rtc_ClockMod_WriteByte(void) { rtc_bank = IoMem[0xfffc3b] & 1; } /*-----------------------------------------------------------------------*/ /** * Show RTC register values */ void Rtc_Info(FILE *fp, uint32_t dummy) { fprintf(fp, "Mode: 0x%02x\n", IoMem[0xfffc2d]); fprintf(fp, "Weekday: %d\n", IoMem[0xfffc2d]); fprintf(fp, "Time: XX%d%d-%d%d-%d%d %d%d:%d%d:%d%d\n", IoMem[0xfffc39], IoMem[0xfffc37], IoMem[0xfffc35], IoMem[0xfffc33], IoMem[0xfffc31], IoMem[0xfffc2f], IoMem[0xfffc2b], IoMem[0xfffc29], IoMem[0xfffc27], IoMem[0xfffc25], IoMem[0xfffc23], IoMem[0xfffc21]); fprintf(fp, "NOTE: register values are valid/updated only on Atari side reads!\n"); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/scandir.c000066400000000000000000000137311504763705000231240ustar00rootroot00000000000000/* Hatari - scandir.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. scandir function for BEOS, SunOS etc.. */ const char ScanDir_fileid[] = "Hatari scandir.c"; #include #include #include #include #include #include #include #include "scandir.h" #include "log.h" /*----------------------------------------------------------------------- * Here come alphasort and scandir for POSIX-like OSes *-----------------------------------------------------------------------*/ #if !defined(WIN32) /** * Alphabetic order comparison routine. */ #if !HAVE_ALPHASORT int alphasort(const struct dirent **d1, const struct dirent **d2) { return strcmp((*d1)->d_name, (*d2)->d_name); } #endif #if !HAVE_SCANDIR #undef DIRSIZ #define DIRSIZ(dp) \ ((sizeof(struct dirent) - sizeof(dp)->d_name) + \ (((dp)->d_reclen + 1 + 3) &~ 3)) #if (defined(__sun) && defined(__SVR4)) # define dirfd(d) ((d)->dd_fd) #elif defined(__BEOS__) # define dirfd(d) ((d)->fd) #endif /** * Scan a directory for all its entries * Return -1 on error, number of entries on success */ int scandir(const char *dirname, struct dirent ***namelist, int (*sdfilter)(const struct dirent *), int (*dcomp)(const struct dirent **, const struct dirent **)) { struct dirent *d, *p = NULL, **names = NULL; struct stat stb; size_t nitems = 0; size_t arraysz; DIR *dirp; if ((dirp = opendir(dirname)) == NULL) goto error_out; if (fstat(dirfd(dirp), &stb) < 0) goto error_out; /* * estimate the array size by taking the size of the directory file * and dividing it by a multiple of the minimum size entry. */ arraysz = (stb.st_size / 24); names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *)); if (names == NULL) goto error_out; while ((d = readdir(dirp)) != NULL) { if (sdfilter != NULL && !(*sdfilter)(d)) continue; /* just selected names */ /* * Make a minimum size copy of the data */ p = (struct dirent *)malloc(DIRSIZ(d)); if (p == NULL) goto error_out; p->d_ino = d->d_ino; p->d_reclen = d->d_reclen; /*p->d_namlen = d->d_namlen;*/ memcpy(p->d_name, d->d_name, p->d_reclen + 1); /* * Check to make sure the array has space left and * realloc the maximum size. */ if ((nitems+1) >= arraysz) { struct dirent **tmp; if (fstat(dirfd(dirp), &stb) < 0) goto error_out; /* just might have grown */ arraysz = stb.st_size / 12; tmp = (struct dirent **)realloc((char *)names, arraysz * sizeof(struct dirent *)); if (tmp == NULL) goto error_out; names = tmp; } names[nitems++] = p; p = NULL; } closedir(dirp); if (nitems && dcomp != NULL) qsort(names, nitems, sizeof(struct dirent *), (int (*)(const void *, const void *))dcomp); *namelist = names; return nitems; error_out: if (names) { int i; for (i = 0; i < nitems; i++) free(names[i]); free(names); } if (dirp) closedir(dirp); return -1; } #endif /* !HAVE_SCANDIR */ #endif /* !WIN32 */ /*----------------------------------------------------------------------- * Here come alphasort and scandir for Windows *-----------------------------------------------------------------------*/ #if defined(WIN32) && !defined(DIRENT_H) #include #include /*-----------------------------------------------------------------------*/ /** * Alphabetic order comparison routine. */ int alphasort(const struct dirent **d1, const struct dirent **d2) { return stricmp((*d1)->d_name, (*d2)->d_name); } /*-----------------------------------------------------------------------*/ /** * Scan a directory for all its entries */ int scandir(const char *dirname, struct dirent ***namelist, int (*sdfilter)(const struct dirent *), int (*dcomp)(const struct dirent **, const struct dirent **)) { int len; char *findIn, *d; WIN32_FIND_DATA find; HANDLE h; int nDir = 0, NDir = 0; struct dirent **dir = 0, *selectDir; unsigned long ret; len = strlen(dirname); findIn = (char *)malloc(len+5); if (!findIn) return -1; strcpy(findIn, dirname); Log_Printf(LOG_DEBUG, "scandir : findIn origin='%s'\n", findIn); for (d = findIn; *d; d++) if (*d=='/') *d='\\'; if ((len==0)) { strcpy(findIn, ".\\*"); } if ((len==1)&& (d[-1]=='.')) { strcpy(findIn, ".\\*"); } if ((len>0) && (d[-1]=='\\')) { *d++ = '*'; *d = 0; } if ((len>1) && (d[-1]=='.') && (d[-2]=='\\')) { d[-1] = '*'; } if ((len>1) && !(d[-2]=='\\' && d[-1]=='*') ) { *d++ = '\\'; *d++ = '*'; *d = 0; } Log_Printf(LOG_DEBUG, "scandir : findIn processed='%s'\n", findIn); h = FindFirstFile(findIn, &find); if (h == INVALID_HANDLE_VALUE) { Log_Printf(LOG_DEBUG, "scandir : FindFirstFile error\n"); ret = GetLastError(); if (ret != ERROR_NO_MORE_FILES) { // TODO: return some error code } *namelist = dir; return nDir; } do { selectDir=(struct dirent*)malloc(sizeof(struct dirent)+lstrlen(find.cFileName)+1); strcpy(selectDir->d_name, find.cFileName); //Log_Printf(LOG_DEBUG, "scandir : findFile='%s'\n", selectDir->d_name); if (!sdfilter || (*sdfilter)(selectDir)) { if (nDir==NDir) { struct dirent **tempDir = (struct dirent **)calloc(sizeof(struct dirent*), NDir+33); if (NDir) memcpy(tempDir, dir, sizeof(struct dirent*)*NDir); free(dir); dir = tempDir; NDir += 32; } dir[nDir] = selectDir; nDir++; dir[nDir] = 0; } else { free(selectDir); } ret = FindNextFile(h, &find); } while (ret); ret = GetLastError(); if (ret != ERROR_NO_MORE_FILES) { // TODO: return some error code Log_Printf(LOG_DEBUG, "scandir: last error = %ld\n", ret); } FindClose(h); free(findIn); if (dcomp) qsort(dir, nDir, sizeof(*dir), (int (*)(const void *, const void *))dcomp); *namelist = dir; return nDir; } #endif /* WIN32 */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/scc.c000066400000000000000000002401201504763705000222430ustar00rootroot00000000000000/* * scc.c - SCC 85C30 emulation code * * Adaptions to Hatari: * * Copyright 2023-2025 Nicolas Pomarède, major rewrite of most of the code * * Copyright 2018 Thomas Huth * * Original code taken from Aranym: * * Copyright (c) 2001-2004 Petr Stehlik of ARAnyM dev team * 2010 Jean Conter * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* The SCC is available in the Mega STE, the Falcon and the TT Depending on the machine, the SCC can have several clock sources, which allows to get closer to the requested baud rate by choosing the most appropriate base clock freq. Mega STE : SCC port A : 1 RS422 LAN port (MiniDIN, 8 pins) "Lan" or 1 internal 10 pins cable connected to RS232C serial port in VME slot (DP-9P, 9 pins) "Serial 2" SCC port B : 1 RS232C serial port (DP-9P, 9 pins) "Modem 2" - PCLK : connected to CLK8, 8021247 Hz for PAL - RTxCA and RTxCB : connected to PCLK4, dedicated OSC running at 3.672 MHz - TRxCA : connected to LCLK : SYNCI signal on pin 2 of the LAN connector or pin 6 of Serial port A - TRxCB : connected to BCLK, dedicated OSC running at 2.4576 MHz for the MFP's XTAL1 TT : SCC port A : 1 RS422 LAN port (MiniDIN, 8 pins) "Lan" or 1 internal 10 pins cable connected to RS232C serial port in VME slot (DP-9P, 9 pins) "Serial 2" SCC port B : 1 RS232C serial port (DP-9P, 9 pins) "Modem 2" - PCLK : connected to CLK8, 8021247 Hz for PAL - RTxCA : connected to PCLK4, dedicated OSC running at 3.672 MHz - TRxCA : connected to LCLK : SYNCI signal on pin 2 of the LAN connector or pin 6 of Serial port A - RTxCB : connected to TCCLK on the TT-MFP (Timer C output) - TRxCB : connected to BCLK, dedicated OSC running at 2.4576 MHz for the 2 MFPs' XTAL1 Falcon : SCC port A : 1 RS422 LAN port (MiniDIN, 8 pins) "Lan" SCC port B : 1 RS232C serial port B (DP-9P, 9 pins) "Modem" - PCLK : connected to CLK8, 8021247 Hz for PAL - RTxCA and RTxCB : connected to PCLK4, dedicated OSC running at 3.672 MHz - TRxCA : connected to SYNCA on the SCC - TRxCB : connected to BCLKA, dedicated OSC running at 2.4576 MHz for the MFP's XTAL1 Note on other serial ports : MegaSTE and TT : - "Modem 1" is a RS232C serial port (DP-9P, 9 pins) connected to the main MFP TT : - "Serial 1" is a 3-wire serial port in VME slot (DP-9P, 9 pins) connected to the TT MFP */ #include "main.h" #if HAVE_TERMIOS_H # include # include #endif #if HAVE_SYS_IOCTL_H # include #endif #include #include #include #include #include "configuration.h" #include "ioMem.h" #include "log.h" #include "memorySnapShot.h" #include "scc.h" #include "clocks_timings.h" #include "m68000.h" #include "cycles.h" #include "cycInt.h" #include "video.h" #include "psg.h" #include "mfp.h" #ifndef O_NONBLOCK # ifdef O_NDELAY # define O_NONBLOCK O_NDELAY # else # define O_NONBLOCK 0 # endif #endif #define SCC_CLOCK_PCLK MachineClocks.SCC_Freq /* 8021247 Hz */ #define SCC_CLOCK_PCLK4 3672000 /* Dedicated OSC */ #define SCC_CLOCK_BCLK MachineClocks.MFP_Timer_Freq /* Connected to the MFP's XTAL clock 2.4576 MHz */ #define SCC_BAUDRATE_SOURCE_CLOCK_RTXC 0 #define SCC_BAUDRATE_SOURCE_CLOCK_TRXC 1 #define SCC_BAUDRATE_SOURCE_CLOCK_BRG 2 #define SCC_BAUDRATE_SOURCE_CLOCK_DPLL 3 #define SCC_BAUDRATE_SOURCE_CLOCK_PCLK 1 #define SCC_BAUDRATE_SOURCE_CLOCK_PCLK4 2 #define SCC_BAUDRATE_SOURCE_CLOCK_BCLK 3 #define SCC_BAUDRATE_SOURCE_CLOCK_TCCLK 4 /* CRC Reset codes 6-7 of WR0 */ #define SCC_WR0_COMMAND_CRC_NULL 0x00 /* Null command */ #define SCC_WR0_COMMAND_CRC_RESET_RX 0x01 /* Reset Receive CRC Checker */ #define SCC_WR0_COMMAND_CRC_RESET_TX 0x02 /* Reset Transmit CRC Generator */ #define SCC_WR0_COMMAND_CRC_RESET_TX_UNDERRUN 0x03 /* Reset Transmit Underrun/EOM Latch */ /* Commands for the bits 3-5 of WR0 */ #define SCC_WR0_COMMAND_NULL 0x00 /* Null Command */ #define SCC_WR0_COMMAND_POINT_HIGH 0x01 /* Point High */ #define SCC_WR0_COMMAND_RESET_EXT_STATUS_INT 0x02 /* Reset Ext/Status Int */ #define SCC_WR0_COMMAND_SEND_ABORT 0x03 /* Send Abort */ #define SCC_WR0_COMMAND_INT_NEXT_RX 0x04 /* Enable Interrupt on Next Rx Char */ #define SCC_WR0_COMMAND_RESET_TX_IP 0x05 /* Reset Tx Interrupt pending */ #define SCC_WR0_COMMAND_ERROR_RESET 0x06 /* Error Reset */ #define SCC_WR0_COMMAND_RESET_HIGHEST_IUS 0x07 /* Reset Highest IUS */ /* RX Int mode for the bits 3-4 of WR1 */ #define SCC_WR1_RX_MODE_INT_OFF 0x00 #define SCC_WR1_RX_MODE_INT_FIRST_CHAR_SPECIAL 0x01 #define SCC_WR1_RX_MODE_INT_ALL_CHAR_SPECIAL 0x02 #define SCC_WR1_RX_MODE_INT_SPECIAL 0x03 #define SCC_WR1_BIT_EXT_INT_ENABLE 0x01 /* Ext Int Enable */ #define SCC_WR1_BIT_TX_INT_ENABLE 0x02 /* Tx Int Enable */ #define SCC_WR1_BIT_PARITY_SPECIAL_COND 0x04 /* Parity is Special Condition */ #define SCC_WR3_BIT_RX_ENABLE 0x01 /* RX Enable */ #define SCC_WR5_BIT_RX_BITS_CHAR_BIT0 0x40 /* Bit 0 for SCC_WR5_RX_n_BITS */ #define SCC_WR5_BIT_RX_BITS_CHAR_BIT1 0x80 /* Bit 1 for SCC_WR5_RX_n_BITS */ /* RX Bits/char for the bits 6-7 */ #define SCC_WR3_RX_5_BITS 0x00 /* RX 5 bits/char or less */ #define SCC_WR3_RX_6_BITS 0x02 /* RX 6 bits/char or less */ #define SCC_WR3_RX_7_BITS 0x01 /* RX 7 bits/char or less */ #define SCC_WR3_RX_8_BITS 0x03 /* RX 8 bits/char or less */ #define SCC_WR4_BIT_PARITY_ENABLE 0x01 /* Parity Enable */ #define SCC_WR4_BIT_PARITY_IS_EVEN 0x02 /* Even Parity */ /* Parity mode for the bits 2-3 */ #define SCC_WR4_STOP_SYNC 0x00 /* Synchronous mode */ #define SCC_WR4_STOP_1_BIT 0x01 /* 1 Stop bit */ #define SCC_WR4_STOP_15_BIT 0x02 /* 1.5 Stop bit */ #define SCC_WR4_STOP_2_BIT 0x03 /* 2 Stop bits */ #define SCC_WR5_BIT_TX_CRC_ENABLE 0x01 /* TX CRC Enable */ #define SCC_WR5_BIT_RTS 0x02 /* RTS */ #define SCC_WR5_BIT_SDLC_CRC 0x04 /* SDLC/CRC-16 */ #define SCC_WR5_BIT_TX_ENABLE 0x08 /* Tx Enable */ #define SCC_WR5_BIT_SEND_BREAK 0x10 /* Send Break */ #define SCC_WR5_BIT_TX_BITS_CHAR_BIT0 0x20 /* Bit 0 for SCC_WR5_TX_n_BITS */ #define SCC_WR5_BIT_TX_BITS_CHAR_BIT1 0x40 /* Bit 1 for SCC_WR5_TX_n_BITS */ #define SCC_WR5_BIT_DTR 0x80 /* DTR */ /* TX Bits/char for the bits 5-6 */ #define SCC_WR5_TX_5_BITS 0x00 /* TX 5 bits/char or less */ #define SCC_WR5_TX_6_BITS 0x02 /* TX 6 bits/char or less */ #define SCC_WR5_TX_7_BITS 0x01 /* TX 7 bits/char or less */ #define SCC_WR5_TX_8_BITS 0x03 /* TX 8 bits/char or less */ #define SCC_WR9_BIT_VIS 0x01 /* Vector Includes Status */ #define SCC_WR9_BIT_NV 0x02 /* No Vector */ #define SCC_WR9_BIT_DISABLE_LOWER_CHAIN 0x04 /* Disable Lower Chain */ #define SCC_WR9_BIT_MIE 0x08 /* Master Interrupt Enable */ #define SCC_WR9_BIT_STATUS_HIGH_LOW 0x10 /* Status High / Low */ #define SCC_WR9_BIT_SOFT_INTACK 0x20 /* Software INTACK Enable */ /* Commands for the bits 7-8 of WR9 */ #define SCC_WR9_COMMAND_RESET_NULL 0x00 /* Null Command */ #define SCC_WR9_COMMAND_RESET_B 0x01 /* Channel B Reset */ #define SCC_WR9_COMMAND_RESET_A 0x02 /* Channel A Reset */ #define SCC_WR9_COMMAND_RESET_FORCE_HW 0x03 /* Force Hardware Reset */ #define SCC_WR15_BIT_WR7_PRIME 0x01 /* Point to Write Register WR7 Prime */ #define SCC_WR15_BIT_ZERO_COUNT_INT_ENABLE 0x02 /* Zero Count Interrupt Enable */ #define SCC_WR15_BIT_STATUS_FIFO_ENABLE 0x04 /* Status FIFO Enable */ #define SCC_WR15_BIT_DCD_INT_ENABLE 0x08 /* DCD Int Enable */ #define SCC_WR15_BIT_SYNC_HUNT_INT_ENABLE 0x10 /* SYNC/Hunt Int Enable */ #define SCC_WR15_BIT_CTS_INT_ENABLE 0x20 /* CTS Int Enable */ #define SCC_WR15_BIT_TX_UNDERRUN_EOM_INT_ENABLE 0x40 /* Transmit Underrun/EOM Int Enable */ #define SCC_WR15_BIT_BREAK_ABORT_INT_ENABLE 0x80 /* Break/Abort Int Enable */ #define SCC_RR0_BIT_RX_CHAR_AVAILABLE 0x01 #define SCC_RR0_BIT_ZERO_COUNT 0x02 #define SCC_RR0_BIT_TX_BUFFER_EMPTY 0x04 #define SCC_RR0_BIT_DCD 0x08 #define SCC_RR0_BIT_SYNC_HUNT 0x10 #define SCC_RR0_BIT_CTS 0x20 #define SCC_RR0_BIT_TX_UNDERRUN_EOM 0x40 #define SCC_RR0_BIT_BREAK_ABORT 0x80 #define SCC_RR1_BIT_ALL_SENT 0x01 #define SCC_RR1_BIT_RES_CODE_2 0x02 #define SCC_RR1_BIT_RES_CODE_1 0x04 #define SCC_RR1_BIT_RES_CODE_0 0x08 #define SCC_RR1_BIT_PARITY_ERROR 0x10 #define SCC_RR1_BIT_RX_OVERRUN_ERROR 0x20 #define SCC_RR1_BIT_CRC_FRAMING_ERROR 0x40 #define SCC_RR1_BIT_EOF_SDLC 0x80 #define SCC_RR3_BIT_EXT_STATUS_IP_B 0x01 #define SCC_RR3_BIT_TX_IP_B 0x02 #define SCC_RR3_BIT_RX_IP_B 0x04 #define SCC_RR3_BIT_EXT_STATUS_IP_A 0x08 #define SCC_RR3_BIT_TX_IP_A 0x10 #define SCC_RR3_BIT_RX_IP_A 0x20 struct SCC_Channel { /* NOTE : WR2 and WR9 are common to both channels, we store their content in channel A */ /* RR2A stores the vector, RR2B stores the vector + status bits */ /* RR3 is only in channel A, RR3B returns 0 */ uint8_t WR[16]; /* 0-15 are for WR0-WR15 */ uint8_t WR7p; /* special case for WR7' */ uint8_t RR[16]; /* 0-15 are for RR0-RR15 */ int BaudRate_BRG; int BaudRate_TX; /* TODO : tx and rx baud rate can be different */ int BaudRate_RX; bool RR0_IsLatched; uint8_t RR0_No_Latch; /* "real time" values of all bits, before being latched if necessary */ bool TX_Buffer_Written; /* True if a write to data reg was made, needed for TBE int */ uint8_t TX_bits; /* TX Bits/char (5 or less, 6, 7, 8) */ uint8_t RX_bits; /* RX Bits/char (5 or less, 6, 7, 8) */ uint8_t Parity_bits; /* 0 or 1 bit */ float Stop_bits; /* Stop bit can be 0 bit (sync), 1 bit, 2 bits or 1.5 bit */ uint8_t TSR; /* Transfer Shift Register */ bool TSR_Full; /* True if data reg was copied to TSR, false when all bits of TSR have been sent */ uint32_t IntSources; /* Interrupt sources : 0=clear 1=set */ /* Channel A can have serial and lan filehandles, channel B will have only serial filehandle */ int ReadHandle_Serial , WriteHandle_Serial; /* For channels A and B on all machines */ bool FileHandle_Serial_IsATTY; int ReadHandle_Lan , WriteHandle_Lan; /* Only used on channel A for MegaSTE/TT */ bool FileHandle_Lan_IsATTY; /* Current values for filehandles */ int ReadHandle , WriteHandle; bool FileHandle_IsATTY; }; typedef struct { struct SCC_Channel Chn[2]; /* 0 is for channel A, 1 is for channel B */ uint8_t IRQ_Line; /* 0=IRQ set 1=IRQ cleared */ uint8_t IUS; /* Interrupt Under Service (same bits as RR3 bits 0-5) */ int Active_Reg; } SCC_STRUCT; static SCC_STRUCT SCC; static int SCC_ClockMode[] = { 1 , 16 , 32 , 64 }; /* Clock multiplier from WR4 bits 6-7 */ static int SCC_Standard_Baudrate[] = { 50 , 75, 110, 134, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400 }; /* Possible sources of interrupt for each channel */ #define SCC_INT_SOURCE_RX_CHAR_AVAILABLE (1<<0) #define SCC_INT_SOURCE_RX_OVERRUN (1<<1) #define SCC_INT_SOURCE_RX_FRAMING_ERROR (1<<2) #define SCC_INT_SOURCE_RX_EOF_SDLC (1<<3) #define SCC_INT_SOURCE_RX_PARITY_ERROR (1<<4) #define SCC_INT_SOURCE_TX_BUFFER_EMPTY (1<<5) #define SCC_INT_SOURCE_EXT_ZERO_COUNT (1<<6) #define SCC_INT_SOURCE_EXT_DCD (1<<7) #define SCC_INT_SOURCE_EXT_SYNC_HUNT (1<<8) #define SCC_INT_SOURCE_EXT_CTS (1<<9) #define SCC_INT_SOURCE_EXT_TX_UNDERRUN (1<<10) #define SCC_INT_SOURCE_EXT_BREAK_ABORT (1<<11) /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static void SCC_ResetChannel ( int Channel , bool HW_Reset ); static void SCC_ResetFull ( bool HW_Reset ); static bool SCC_Serial_Read_Byte ( int Channel , uint8_t *pValue ); static void SCC_Serial_Write_Byte ( int Channel, uint8_t value ); #if HAVE_TERMIOS_H static void SCC_Serial_Set_BaudAttr ( int handle, speed_t new_speed ); #endif static void SCC_Serial_Set_BaudRate ( int Channel, int value ); static uint16_t SCC_Serial_Get_CTS ( int Channel ); static uint16_t SCC_Serial_Get_DCD ( int Channel ); static int SCC_Get_Standard_BaudRate ( int BaudRate ); static int SCC_Get_RTxC_Freq ( int chn ); static int SCC_Get_TRxC_Freq ( int chn ); static int SCC_Compute_BaudRate ( int chn , bool *pStartBRG , uint32_t *pBaudRate_BRG ); static void SCC_Update_BaudRate ( int Channel ); static uint8_t SCC_Get_Vector_Status ( void ); static void SCC_Update_RR0 ( int Channel ); static void SCC_Update_RR0_Clear ( int Channel , int bits ); static void SCC_Update_RR0_Set ( int Channel , int bits ); static void SCC_Update_RR0_Latch_Off ( int Channel ); static void SCC_Update_RR2 ( void ); static void SCC_Update_RR3_Bit ( bool Set , uint8_t Bit ); static void SCC_Update_RR3 ( int Channel ); static void SCC_Copy_TDR_TSR ( int Channel , uint8_t TDR ); static void SCC_Process_TX ( int Channel ); static void SCC_Process_RX ( int Channel ); static void SCC_Start_InterruptHandler_BRG ( int Channel , int InternalCycleOffset ); static void SCC_Stop_InterruptHandler_BRG ( int Channel ); static void SCC_InterruptHandler_BRG ( int Channel ); static void SCC_Start_InterruptHandler_TX_RX ( int Channel , bool is_tx , int InternalCycleOffset ); static void SCC_Stop_InterruptHandler_TX_RX ( int Channel , bool is_tx ); static void SCC_Restart_InterruptHandler_TX_RX ( int Channel , bool is_tx ); static void SCC_InterruptHandler_TX_RX ( int Channel ); static void SCC_InterruptHandler_RX ( int Channel ); static void SCC_Set_Line_IRQ ( int bit ); static void SCC_Update_IRQ ( void ); static void SCC_IntSources_Change ( int Channel , uint32_t Source , bool Set ); static void SCC_IntSources_Set ( int Channel , uint32_t Source ); static void SCC_IntSources_Clear ( int Channel , uint32_t Source ); static void SCC_IntSources_Clear_NoUpdate ( int Channel , uint32_t Source ); static int SCC_Do_IACK ( bool Soft ); static void SCC_Soft_IACK ( void ); /* * Return true if the current machine has a built-in SCC chip. * Else return false. */ bool SCC_IsAvailable(CNF_PARAMS *cnf) { return ConfigureParams.System.nMachineType == MACHINE_MEGA_STE || ConfigureParams.System.nMachineType == MACHINE_TT || ConfigureParams.System.nMachineType == MACHINE_FALCON; } /* * Return true if the LAN port is connected to SCC Channel A * Return false if SCC Channel A is connected to the serial port * The LAN port is only available on MegaSTE and TT and can be enabled * using bit 7 in reg 14 of the YM2149 (0=Lan, 1=Serial) */ void SCC_Check_Lan_IsEnabled ( void ) { if ( Config_IsMachineFalcon() /* Falcon doesn't have a LAN port on SCC A */ || ( PSGRegisters[ PSG_REG_IO_PORTA ] & 0x80 ) ) /* Bit7 = 1 : Serial port */ { SCC.Chn[0].ReadHandle = SCC.Chn[0].ReadHandle_Serial; SCC.Chn[0].WriteHandle = SCC.Chn[0].WriteHandle_Serial; SCC.Chn[0].FileHandle_IsATTY = SCC.Chn[0].FileHandle_Serial_IsATTY; } else /* Bit7 = 0 : LAN port */ { SCC.Chn[0].ReadHandle = SCC.Chn[0].ReadHandle_Lan; SCC.Chn[0].WriteHandle = SCC.Chn[0].WriteHandle_Lan; SCC.Chn[0].FileHandle_IsATTY = SCC.Chn[0].FileHandle_Lan_IsATTY; } } /* * This function is called from mfp.c whenever Timer C frequency (TCCLK) is changed on the TT MFP * TCCLK can be used as the main clock source for the RTxCB clock on channel B * * If RTxCB is enabled, we must update the SCC's baudrate */ void SCC_Update_TimerC_Clock ( void ) { if ( Config_IsMachineTT() ) { LOG_TRACE(TRACE_SCC, "scc update tcclk from tt mfp\n" ); SCC_Update_BaudRate ( 1 ); /* Update channel B baudrate if needed */ } } static void SCC_Init_Channel ( int Channel , bool *pConfEnableScc , char *InFileName , char *OutFileName , int *pReadHandle , int *pWriteHandle , bool *pIsATTY ) { *pReadHandle = *pWriteHandle = -1; *pIsATTY = false; if (!*pConfEnableScc || !SCC_IsAvailable(&ConfigureParams)) return; if ( InFileName[0] && strcmp ( InFileName , OutFileName ) == 0 ) { #if HAVE_TERMIOS_H *pReadHandle = open ( InFileName , O_RDWR | O_NONBLOCK); if ( *pReadHandle >= 0 ) { if ( isatty(*pReadHandle) ) { *pWriteHandle = *pReadHandle; *pIsATTY = true; } else { Log_Printf(LOG_ERROR, "SCC_Init: Setting SCC %c input and output " "to the same file only works with tty devices.\n" , Channel+'A'); close ( *pReadHandle ); *pReadHandle = -1; } } else { Log_Printf(LOG_ERROR, "SCC_Init: Can not open device '%s'\n", InFileName); } #else Log_Printf(LOG_ERROR, "SCC_Init: Setting SCC %c input and output " "to the same file is not supported on this system.\n" , Channel+'A'); #endif } else { if ( InFileName[0] ) { *pReadHandle = open ( InFileName , O_RDONLY | O_NONBLOCK); if ( *pReadHandle < 0) { Log_Printf(LOG_ERROR, "SCC_Init: Can not open input file '%s'\n", InFileName); } } if ( OutFileName[0] ) { *pWriteHandle = open ( OutFileName , O_CREAT | O_WRONLY | O_NONBLOCK, S_IRUSR | S_IWUSR); if ( *pWriteHandle < 0 ) { Log_Printf(LOG_ERROR, "SCC_Init: Can not open output file '%s'\n", OutFileName); } } } if ( *pReadHandle == -1 && *pWriteHandle == -1) { *pConfEnableScc = false; } } void SCC_Init ( void ) { SCC_Reset(); /* Init filehandles for channel A Serial */ SCC_Init_Channel ( 0 , &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_SERIAL] , ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_SERIAL] , ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_SERIAL] , &SCC.Chn[0].ReadHandle_Serial , &SCC.Chn[0].WriteHandle_Serial , &SCC.Chn[0].FileHandle_Serial_IsATTY ); /* Init filehandles for channel A LAN */ SCC_Init_Channel ( 0 , &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_A_LAN] , ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_A_LAN] , ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_A_LAN] , &SCC.Chn[0].ReadHandle_Lan , &SCC.Chn[0].WriteHandle_Lan , &SCC.Chn[0].FileHandle_Lan_IsATTY ); /* Connect Channel A to Serial or LAN port depending on the machine */ SCC_Check_Lan_IsEnabled(); /* Init filehandles for channel B Serial */ SCC_Init_Channel ( 1 , &ConfigureParams.RS232.EnableScc[CNF_SCC_CHANNELS_B] , ConfigureParams.RS232.SccInFileName[CNF_SCC_CHANNELS_B] , ConfigureParams.RS232.SccOutFileName[CNF_SCC_CHANNELS_B] , &SCC.Chn[1].ReadHandle_Serial , &SCC.Chn[1].WriteHandle_Serial , &SCC.Chn[1].FileHandle_Serial_IsATTY ); /* Channel B has only serial filehandle */ SCC.Chn[1].ReadHandle = SCC.Chn[1].ReadHandle_Serial; SCC.Chn[1].WriteHandle = SCC.Chn[1].WriteHandle_Serial; SCC.Chn[1].FileHandle_IsATTY = SCC.Chn[1].FileHandle_Serial_IsATTY; SCC.Chn[1].ReadHandle_Lan = SCC.Chn[1].WriteHandle_Lan = -1; } void SCC_UnInit(void) { int Channel; for ( Channel = 0; Channel < 2; Channel++ ) { if ( SCC.Chn[Channel].ReadHandle_Serial >= 0 ) close(SCC.Chn[Channel].ReadHandle_Serial); if ( ( SCC.Chn[Channel].WriteHandle_Serial != SCC.Chn[Channel].ReadHandle_Serial ) && ( SCC.Chn[Channel].WriteHandle_Serial >= 0 ) ) close(SCC.Chn[Channel].WriteHandle_Serial); if ( SCC.Chn[Channel].ReadHandle_Lan >= 0 ) close(SCC.Chn[Channel].ReadHandle_Lan); if ( ( SCC.Chn[Channel].WriteHandle_Lan != SCC.Chn[Channel].ReadHandle_Lan ) && ( SCC.Chn[Channel].WriteHandle_Lan >= 0 ) ) close(SCC.Chn[Channel].WriteHandle_Lan); SCC.Chn[Channel].ReadHandle_Serial = SCC.Chn[Channel].WriteHandle_Serial = -1; SCC.Chn[Channel].ReadHandle_Lan = SCC.Chn[Channel].WriteHandle_Lan = -1; SCC.Chn[Channel].ReadHandle = SCC.Chn[Channel].WriteHandle = -1; } } void SCC_MemorySnapShot_Capture(bool bSave) { int c; for (c = 0; c < 2; c++) { MemorySnapShot_Store(SCC.Chn[c].WR, sizeof(SCC.Chn[c].WR)); MemorySnapShot_Store(&SCC.Chn[c].WR7p, sizeof(SCC.Chn[c].WR7p)); MemorySnapShot_Store(SCC.Chn[c].RR, sizeof(SCC.Chn[c].RR)); MemorySnapShot_Store(&SCC.Chn[c].BaudRate_BRG, sizeof(SCC.Chn[c].BaudRate_BRG)); MemorySnapShot_Store(&SCC.Chn[c].BaudRate_TX, sizeof(SCC.Chn[c].BaudRate_TX)); MemorySnapShot_Store(&SCC.Chn[c].BaudRate_RX, sizeof(SCC.Chn[c].BaudRate_RX)); MemorySnapShot_Store(&SCC.Chn[c].RR0_IsLatched, sizeof(SCC.Chn[c].RR0_IsLatched)); MemorySnapShot_Store(&SCC.Chn[c].RR0_No_Latch, sizeof(SCC.Chn[c].RR0_No_Latch)); MemorySnapShot_Store(&SCC.Chn[c].TX_Buffer_Written, sizeof(SCC.Chn[c].TX_Buffer_Written)); MemorySnapShot_Store(&SCC.Chn[c].TX_bits, sizeof(SCC.Chn[c].TX_bits)); MemorySnapShot_Store(&SCC.Chn[c].RX_bits, sizeof(SCC.Chn[c].RX_bits)); MemorySnapShot_Store(&SCC.Chn[c].Parity_bits, sizeof(SCC.Chn[c].Parity_bits)); MemorySnapShot_Store(&SCC.Chn[c].Stop_bits, sizeof(SCC.Chn[c].Stop_bits)); MemorySnapShot_Store(&SCC.Chn[c].TSR, sizeof(SCC.Chn[c].TSR)); MemorySnapShot_Store(&SCC.Chn[c].TSR_Full, sizeof(SCC.Chn[c].TSR_Full)); MemorySnapShot_Store(&SCC.Chn[c].IntSources, sizeof(SCC.Chn[c].IntSources)); } MemorySnapShot_Store(&SCC.IRQ_Line, sizeof(SCC.IRQ_Line)); MemorySnapShot_Store(&SCC.IUS, sizeof(SCC.IUS)); MemorySnapShot_Store(&SCC.Active_Reg, sizeof(SCC.Active_Reg)); } static void SCC_ResetChannel ( int Channel , bool HW_Reset ) { SCC.Chn[Channel].WR[0] = 0x00; SCC.Active_Reg = 0; SCC.Chn[Channel].WR[1] &= 0x24; /* keep bits 2 and 5, clear others */ SCC.Chn[Channel].WR[3] &= 0xfe; /* keep bits 1 to 7, clear bit 0 */ SCC.Chn[Channel].WR[4] |= 0x04; /* set bit 2, keep others */ SCC.Chn[Channel].WR[5] &= 0x61; /* keep bits 0,5 and 6, clear others */ SCC.Chn[Channel].WR[15] = 0xf8; SCC.Chn[Channel].WR7p = 0x20; /* WR7' set bit5, clear others */ if ( HW_Reset ) { /* WR9 is common to channel A and B, we store it in channel A */ SCC.Chn[0].WR[9] &= 0x03; /* keep bits 0 and 1, clear others */ SCC.Chn[0].WR[9] |= 0xC0; /* set bits 7 and 8 */ SCC.IUS = 0x00; /* clearing MIE also clear IUS */ SCC.Chn[Channel].WR[10] = 0x00; SCC.Chn[Channel].WR[11] = 0x08; SCC.Chn[Channel].WR[14] &= 0xC0; /* keep bits 7 and 8, clear others */ SCC.Chn[Channel].WR[14] |= 0x30; /* set bits 4 and 5 */ } else { /* WR9 is common to channel A and B, we store it in channel A */ SCC.Chn[0].WR[9] &= 0xdf; /* clear bit 6, keep others */ SCC.Chn[Channel].WR[10] &= 0x60; /* keep bits 5 and 6, clear others */ SCC.Chn[Channel].WR[14] &= 0xC3; /* keep bits 0,1,7 and 8, clear others */ SCC.Chn[Channel].WR[14] |= 0x20; /* set bit 5 */ } SCC.Chn[Channel].RR[0] &= 0xb8; /* keep bits 3,4 and 5, clear others */ SCC.Chn[Channel].RR[0] |= 0x44; /* set bits 2 and 6 */ SCC.Chn[Channel].RR0_No_Latch = SCC.Chn[Channel].RR[0]; SCC.Chn[Channel].RR0_IsLatched = false; /* no pending int sources */ SCC.Chn[Channel].RR[1] &= 0x01; /* keep bits 0, clear others */ SCC.Chn[Channel].RR[1] |= 0x06; /* set bits 1 and 2 */ SCC.Chn[Channel].RR[3] = 0x00; SCC.Chn[Channel].RR[10] &= 0x40; /* keep bits 6, clear others */ SCC.Chn[Channel].TX_Buffer_Written = false; /* no write made to TB for now */ SCC.Chn[Channel].TSR_Full = false; /* TSR is empty */ } /* On real hardware HW_Reset would be true when /RD and /WR are low at the same time (not supported in Mega STE / TT / Falcon) * - For our emulation, we also do HW_Reset=true when resetting the emulated machine * - When writing 0xC0 to WR9 a full reset will be done with HW_Reset=true */ static void SCC_ResetFull ( bool HW_Reset ) { uint8_t wr9_old; wr9_old = SCC.Chn[0].WR[9]; /* Save WR9 before reset */ SCC_ResetChannel ( 0 , true ); SCC_ResetChannel ( 1 , true ); if ( !HW_Reset ) /* Reset through WR9, some bits are kept */ { /* Restore bits 2,3,4 after software full reset */ SCC.Chn[0].WR[9] &= ~0x1c; SCC.Chn[0].WR[9] |= ( wr9_old & 0x1c ); } SCC.Chn[0].IntSources = 0; SCC.Chn[1].IntSources = 0; SCC_Set_Line_IRQ ( SCC_IRQ_OFF ); /* IRQ line goes high */ } void SCC_Reset(void) { memset(SCC.Chn[0].WR, 0, sizeof(SCC.Chn[0].WR)); memset(SCC.Chn[0].RR, 0, sizeof(SCC.Chn[0].RR)); memset(SCC.Chn[1].WR, 0, sizeof(SCC.Chn[1].WR)); memset(SCC.Chn[1].RR, 0, sizeof(SCC.Chn[1].RR)); SCC_ResetFull ( true ); } static bool SCC_Serial_Read_Byte ( int Channel , uint8_t *pValue ) { int nb; if ( SCC.Chn[Channel].ReadHandle >= 0 ) { nb = read ( SCC.Chn[Channel].ReadHandle , pValue , 1 ); if (nb < 0) { if (errno == EAGAIN || errno == EINTR) /* nothing yet, retry later */ return false; Log_Printf(LOG_WARN, "scc serial read byte channel %c : read failed, errno=%d\n", 'A'+Channel, errno); } else if ( nb == 0 ) { LOG_TRACE(TRACE_SCC, "scc serial read byte channel %c : no byte\n", 'A'+Channel); } else { LOG_TRACE(TRACE_SCC, "scc serial read byte channel %c rx=$%02x\n", 'A'+Channel, *pValue); } return ( nb > 0 ); } return false; } static void SCC_Serial_Write_Byte ( int Channel, uint8_t value ) { int nb; LOG_TRACE(TRACE_SCC, "scc serial write byte channel=%c value=$%02x\n", 'A'+Channel, value); if ( SCC.Chn[Channel].WriteHandle >= 0 ) { do { nb = write ( SCC.Chn[Channel].WriteHandle , &value , 1 ); } while (nb < 0 && (errno == EAGAIN || errno == EINTR)); } } #if HAVE_TERMIOS_H static void SCC_Serial_Set_BaudAttr ( int handle, speed_t new_speed ) { struct termios options; if (handle < 0) return; memset(&options, 0, sizeof(options)); if (tcgetattr(handle, &options) < 0) { LOG_TRACE(TRACE_SCC, "scc tcgetattr() failed\n"); return; } cfsetispeed(&options, new_speed); cfsetospeed(&options, new_speed); options.c_cflag |= (CLOCAL | CREAD); options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input options.c_iflag &= ~(ICRNL); // CR is not CR+LF tcsetattr(handle, TCSANOW, &options); } #endif /* summary of baud rates: Rsconf Falcon Falcon(+HSMODEM) Hatari Hatari(+HSMODEM) 0 19200 19200 19200 19200 1 9600 9600 9600 9600 2 4800 4800 4800 4800 3 3600 3600 57600 57600 4 2400 2400 2400 2400 5 2000 2000 38400 38400 6 1800 1800 1800 1800 7 1200 1200 1200 1200 8 600 600 600 600 9 300 300 300 300 10 200 230400 200 230400 11 150 115200 150 115200 12 134 57600 134 57600 13 110 38400 110 38400 14 75 153600 75 75 15 50 76800 50 50 */ static void SCC_Serial_Set_BaudRate ( int Channel, int value ) { #if HAVE_TERMIOS_H speed_t new_speed = B0; LOG_TRACE(TRACE_SCC, "scc serial set baud channel=%c value=$%02x\n", 'A'+Channel, value); switch (value) { #ifdef B230400 /* B230400 is not defined on all systems */ case 230400: new_speed = B230400; break; #endif case 115200: new_speed = B115200; break; case 57600: new_speed = B57600; break; case 38400: new_speed = B38400; break; case 19200: new_speed = B19200; break; case 9600: new_speed = B9600; break; case 4800: new_speed = B4800; break; case 2400: new_speed = B2400; break; case 1800: new_speed = B1800; break; case 1200: new_speed = B1200; break; case 600: new_speed = B600; break; case 300: new_speed = B300; break; case 200: new_speed = B200; break; case 150: new_speed = B150; break; case 134: new_speed = B134; break; case 110: new_speed = B110; break; case 75: new_speed = B75; break; case 50: new_speed = B50; break; default: Log_Printf(LOG_DEBUG, "SCC: unsupported baud rate %i\n", value); break; } if (new_speed == B0) return; SCC_Serial_Set_BaudAttr ( SCC.Chn[Channel].ReadHandle, new_speed ); if ( SCC.Chn[Channel].ReadHandle != SCC.Chn[Channel].WriteHandle ) SCC_Serial_Set_BaudAttr ( SCC.Chn[Channel].WriteHandle , new_speed ); #endif } static uint16_t SCC_Serial_Get_CTS ( int Channel ) { int cts = 1; LOG_TRACE(TRACE_SCC, "scc get status for CTS %d\n" , Channel); #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCMGET) int status = 0; if ( SCC.Chn[Channel].WriteHandle >= 0 && SCC.Chn[Channel].FileHandle_IsATTY ) { if ( ioctl ( SCC.Chn[Channel].WriteHandle , TIOCMGET , &status ) < 0 ) { Log_Printf(LOG_DEBUG, "SCC: Can't get status for CTS errno=%d\n", errno); } else { if ( status & TIOCM_CTS ) cts = 1; else cts = 0; } } #endif return cts; } static uint16_t SCC_Serial_Get_DCD ( int Channel ) { int dcd = 1; LOG_TRACE(TRACE_SCC, "scc get status for DCD %d\n" , Channel); #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCMGET) int status = 0; if ( SCC.Chn[Channel].WriteHandle >= 0 && SCC.Chn[Channel].FileHandle_IsATTY ) { if ( ioctl ( SCC.Chn[Channel].WriteHandle , TIOCMGET , &status ) < 0 ) { Log_Printf(LOG_DEBUG, "SCC: Can't get status for DCD errno=%d\n" , errno); } else { if ( status & TIOCM_CAR ) dcd = 1; else dcd = 0; } } #endif return dcd; } /* Send BREAK. If value!=0, set break on. If value=0, set break off */ static void SCC_serial_Set_BRK(int chn, uint8_t value) { #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCSBRK) int cmd = 0; if ( SCC.Chn[chn].WriteHandle >= 0 && SCC.Chn[chn].FileHandle_IsATTY ) { if ( value ) cmd = TIOCSBRK; /* set break */ else cmd = TIOCCBRK; /* clear break */ if ( ioctl ( SCC.Chn[chn].WriteHandle , cmd) < 0) { Log_Printf(LOG_DEBUG, "SCC: Can't set BRK=%s errno=%d\n" , value?"ON":"OFF" , errno ); } } #endif } static void SCC_serial_setRTS(int chn, uint8_t value) { #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCMGET) int status = 0; if ( SCC.Chn[chn].WriteHandle >= 0 && SCC.Chn[chn].FileHandle_IsATTY) { if ( ioctl ( SCC.Chn[chn].WriteHandle , TIOCMGET , &status ) < 0 ) { Log_Printf(LOG_DEBUG, "SCC: Can't get status for RTS\n"); } if (value) status |= TIOCM_RTS; else status &= ~TIOCM_RTS; ioctl ( SCC.Chn[chn].WriteHandle , TIOCMSET , &status ); } #endif } static void SCC_serial_setDTR(int chn, uint8_t value) { #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCMGET) int status = 0; if ( SCC.Chn[chn].WriteHandle >= 0 && SCC.Chn[chn].FileHandle_IsATTY ) { if ( ioctl ( SCC.Chn[chn].WriteHandle , TIOCMGET , &status ) < 0 ) { Log_Printf(LOG_DEBUG, "SCC: Can't get status for DTR\n"); } if (value) status |= TIOCM_DTR; else status &= ~TIOCM_DTR; ioctl ( SCC.Chn[chn].WriteHandle , TIOCMSET , &status ); } #endif } /* * Depending on the selected clock mode the baud rate might not match * exactly the standard baud rates. For example with a 8 MHz clock and * time constant=24 with x16 multiplier, we get an effective baud rate * of 9641, instead of the standard 9600. * To handle this we use a 1% margin to check if the computed baud rate * match one of the standard baud rates. If so, we will use the standard * baud rate to configure the serial port. */ static int SCC_Get_Standard_BaudRate ( int BaudRate ) { float margin , low , high; int i; for ( i=0 ; i<(int)ARRAY_SIZE(SCC_Standard_Baudrate) ; i++ ) { margin = SCC_Standard_Baudrate[ i ] * 0.01; /* 1% */ if ( margin < 4 ) margin = 4; /* increase margin for small bitrates < 600 */ low = SCC_Standard_Baudrate[ i ] - margin; high = SCC_Standard_Baudrate[ i ] + margin; //fprintf ( stderr , "check %d %d %f %f\n" , i , BaudRate , low , high ); if ( ( low <= BaudRate ) && ( BaudRate <= high ) ) return SCC_Standard_Baudrate[ i ]; } return -1; } /* * Get the frequency in Hz for RTxCA and RTxCB depending on the machine type * - RTxCA is connected to PCLK4 on all machines * - RTxCB is also connected to PCLK4 on MegaSTE and Falcon * On TT it's connected to TTCLK (Timer C output on the TT-MFP) */ static int SCC_Get_RTxC_Freq ( int chn ) { int ClockFreq; if ( Config_IsMachineMegaSTE() || Config_IsMachineFalcon() ) ClockFreq = SCC_CLOCK_PCLK4; else /* TT */ { if ( chn == 0 ) ClockFreq = SCC_CLOCK_PCLK4; else { /* Clock is connected to timer C output on TT-MFP */ ClockFreq = MFP_TT_TimerC_Get_Freq(); } } return ClockFreq; } /* * Get the frequency in Hz for TRxCA and TRxCB depending on the machine type * - TRxCB is connected to BCLK on all machines (2.4576 MHz on the MFP's XTAL) * - TRxCA is connected to LCLK on MegaSTE and TT * On Falcon it's connected to SYNCA on the SCC */ static int SCC_Get_TRxC_Freq ( int chn ) { int ClockFreq; if ( chn == 1 ) ClockFreq = SCC_CLOCK_BCLK; else { if ( Config_IsMachineMegaSTE() || Config_IsMachineTT() ) /* TODO : clock is connected to LCLK */ ClockFreq = SCC_CLOCK_BCLK; /* TODO : use LCLK */ else /* TODO : clock is connected to SYNCA */ ClockFreq = SCC_CLOCK_BCLK; /* TODO : use SYNCA */ } return ClockFreq; } /* * Return the generated baud rate depending on the value of WR4, WR11, WR12, WR13 and WR14 * * The baud rate can use RtxC or TRxC clocks (which depend on the machine type) with * an additional clock multipier. * Or the baud rate can use the baud rate generator and its time constant. * * The SCC doc gives the formula to compute time constant from a baud rate in the BRG : * TimeConstant = ( ClockFreq / ( 2 * BaudRate * ClockMult ) ) - 2 * * when we know the time constant in the BRG, we can compute the baud rate for the BRG : * BaudRate = ClockFreq / ( 2 * ( TimeConstant + 2 ) * ClockMult ) */ static int SCC_Compute_BaudRate ( int chn , bool *pStartBRG , uint32_t *pBaudRate_BRG ) { int TimeConstant; int ClockFreq_BRG = 0; int ClockFreq = 0; int ClockMult; int TransmitClock , ReceiveClock; int BaudRate; LOG_TRACE_VAR const char *ClockName; /* WR4 gives Clock Mode Multiplier */ if ( ( SCC.Chn[chn].WR[4] & 0x0c ) == 0 ) /* bits 2-3 = 0, sync modes enabled, force x1 clock */ ClockMult = 1; else ClockMult = SCC_ClockMode[ SCC.Chn[chn].WR[4] >> 6 ]; /* use bits 6-7 to get multiplier */ /* WR12 and WR13 give Low/High values of the 16 bit time constant for the BRG */ TimeConstant = ( SCC.Chn[chn].WR[13]<<8 ) + SCC.Chn[chn].WR[12]; /* WR14 gives the clock source for the baud rate generator + enable the BRG */ /* NOTE : it's possible to start the BRG even if we use a different clock mode later */ /* for the baud rate in WR11 */ if ( ( SCC.Chn[chn].WR[14] & 1 ) == 0 ) /* BRG is disabled */ *pStartBRG = false; else { *pStartBRG = true; if ( SCC.Chn[chn].WR[14] & 2 ) /* source is PCLK */ ClockFreq_BRG = SCC_CLOCK_PCLK; else /* source is RTxC */ { ClockFreq_BRG = SCC_Get_RTxC_Freq ( chn ); if ( ClockFreq_BRG == 0 ) /* If SCC_Get_RTxC_Freq() returns 0 then we consider serial interface is OFF */ { LOG_TRACE(TRACE_SCC, "scc compute baud rate chn=%d, rtxc freq is 0 : disable serial\n" , chn ); *pStartBRG = false; return -1; } } *pBaudRate_BRG = round ( (float)ClockFreq_BRG / ( 2 * ClockMult * ( TimeConstant + 2 ) ) ); if ( *pBaudRate_BRG == 0 ) /* if we rounded to O, we use 1 instead */ *pBaudRate_BRG = 1; LOG_TRACE(TRACE_SCC, "scc compute baud rate start BRG clock_freq=%d chn=%d mult=%d tc=%d br=%d\n" , ClockFreq_BRG , chn , ClockMult , TimeConstant , *pBaudRate_BRG ); } /* WR11 clock mode */ /* In the case of our emulation we only support when "Receive Clock" mode is the same as "Transmit Clock" */ TransmitClock = ( SCC.Chn[chn].WR[11] >> 3 ) & 3; ReceiveClock = ( SCC.Chn[chn].WR[11] >> 5 ) & 3; if ( TransmitClock != ReceiveClock ) { LOG_TRACE(TRACE_SCC, "scc compute baud rate %c, unsupported clock mode in WR11, transmit=%d != receive=%d\n" , 'A'+chn , TransmitClock , ReceiveClock ); return -1; } /* Compute the tx/rx baud rate depending on the clock mode in WR11 */ if ( TransmitClock == SCC_BAUDRATE_SOURCE_CLOCK_BRG ) /* source is BRG */ { if ( !*pStartBRG ) { LOG_TRACE(TRACE_SCC, "scc compute baud rate %c, clock mode set to BRG but BRG not enabled\n" , 'A'+chn ); return -1; } ClockName = "BRG"; BaudRate = *pBaudRate_BRG; } else { if ( TransmitClock == SCC_BAUDRATE_SOURCE_CLOCK_RTXC ) /* source is RTxC */ { ClockName = "RTxC"; ClockFreq = SCC_Get_RTxC_Freq ( chn ); } else if ( TransmitClock == SCC_BAUDRATE_SOURCE_CLOCK_TRXC ) /* source is TRxC */ { ClockName = "TRxC"; ClockFreq = SCC_Get_TRxC_Freq ( chn ); } else /* source is DPLL, not supported */ { ClockName = "DPLL"; LOG_TRACE(TRACE_SCC, "scc compute baud rate %c, unsupported clock mode dpll in WR11\n" , 'A'+chn ); return -1; } if ( ClockFreq == 0 ) /* this can happen when using RTxC=TCCLK */ { LOG_TRACE(TRACE_SCC, "scc compute baud rate clock_source=%s clock_freq=%d chn=%d, clock is stopped\n" , ClockName , ClockFreq , chn ); return -1; } BaudRate = round ( (float)ClockFreq / ClockMult ); } LOG_TRACE(TRACE_SCC, "scc compute baud rate clock_source=%s clock_freq=%d chn=%d clock_mode=%d mult=%d tc=%d br=%d\n" , ClockName , TransmitClock==SCC_BAUDRATE_SOURCE_CLOCK_BRG?ClockFreq_BRG:ClockFreq , chn , TransmitClock , ClockMult , TimeConstant , BaudRate ); return BaudRate; } /* * This function groups all the actions when the corresponding WRx are modified * to change the baud rate on a channel : * - compute new baud rate * - start BRG timer if needed * - check if baud rate is a standard one and configure host serial port */ static void SCC_Update_BaudRate ( int Channel ) { bool StartBRG; uint32_t BaudRate_BRG; int BaudRate; int BaudRate_Standard; bool Serial_ON; BaudRate = SCC_Compute_BaudRate ( Channel , &StartBRG , &BaudRate_BRG ); if ( StartBRG ) { SCC.Chn[Channel].BaudRate_BRG = BaudRate_BRG; SCC_Start_InterruptHandler_BRG ( Channel , 0 ); } else { SCC_Stop_InterruptHandler_BRG ( Channel ); } SCC.Chn[Channel].BaudRate_TX = BaudRate; SCC.Chn[Channel].BaudRate_RX = BaudRate; if ( BaudRate == -1 ) { Serial_ON = false; SCC_Stop_InterruptHandler_TX_RX ( Channel , true ); /* TX */ SCC_Stop_InterruptHandler_TX_RX ( Channel , false ); /* RX */ } else { BaudRate_Standard = SCC_Get_Standard_BaudRate ( BaudRate ); if ( BaudRate_Standard > 0 ) Serial_ON = true; else Serial_ON = false; /* If baudrate is the same for TX and RX we start only one timer TX for both */ /* Else we start a timer for TX and a timer for RX */ SCC_Start_InterruptHandler_TX_RX ( Channel , true , 0 ); /* start TX */ if ( SCC.Chn[Channel].BaudRate_TX != SCC.Chn[Channel].BaudRate_RX ) SCC_Start_InterruptHandler_TX_RX ( Channel , false , 0 ); /* start RX */ else SCC_Stop_InterruptHandler_TX_RX ( Channel , false ); /* stop RX */ } if ( Serial_ON ) { //fprintf(stderr , "update br serial_on %d->%d\n" , BaudRate , BaudRate_Standard ); SCC_Serial_Set_BaudRate ( Channel , BaudRate_Standard ); } else { /* TODO : stop serial ; use baudrate = B0 ? */ } } /* * Return Status Information bits, as included in RR2B / Vector register * This status contains 3 bits to indicate the current highest IP * - This status is always included in the vector when reading RR2B * - During an INTACK this status will only be included in the vector if VIS bit is set in WR9 * If no interrupt are pending, return "Ch B Special Receive Condition" * * Note that depending on Status High/Low bit in WR9 these 3 bits will be in a different order */ static uint8_t SCC_Get_Vector_Status ( void ) { uint8_t status; uint8_t special_cond_mask; /* Check pending interrupts from highest to lowest ; if no IP, return Ch B Special Receive Condition */ if ( SCC.Chn[0].RR[3] & SCC_RR3_BIT_RX_IP_A ) { special_cond_mask = SCC_RR1_BIT_RX_OVERRUN_ERROR | SCC_RR1_BIT_CRC_FRAMING_ERROR | SCC_RR1_BIT_EOF_SDLC; if ( SCC.Chn[0].WR[1] & SCC_WR1_BIT_PARITY_SPECIAL_COND ) special_cond_mask |= SCC_RR1_BIT_PARITY_ERROR; if ( SCC.Chn[0].RR[0] & special_cond_mask ) status = 7; /* Ch. A Special Receive Condition */ else status = 6; /* Ch. A Receive Char Available */ } else if ( SCC.Chn[0].RR[3] & SCC_RR3_BIT_TX_IP_A ) status = 4; else if ( SCC.Chn[0].RR[3] & SCC_RR3_BIT_EXT_STATUS_IP_A ) status = 5; else if ( SCC.Chn[0].RR[3] & SCC_RR3_BIT_RX_IP_B ) { special_cond_mask = SCC_RR1_BIT_RX_OVERRUN_ERROR | SCC_RR1_BIT_CRC_FRAMING_ERROR | SCC_RR1_BIT_EOF_SDLC; if ( SCC.Chn[1].WR[1] & SCC_WR1_BIT_PARITY_SPECIAL_COND ) special_cond_mask |= SCC_RR1_BIT_PARITY_ERROR; if ( SCC.Chn[1].RR[0] & special_cond_mask ) status = 3; /* Ch. B Special Receive Condition */ else status = 2; /* Ch. B Receive Char Available */ } else if ( SCC.Chn[0].RR[3] & SCC_RR3_BIT_TX_IP_B ) status = 0; else if ( SCC.Chn[0].RR[3] & SCC_RR3_BIT_EXT_STATUS_IP_B ) status = 1; else status = 3; /* No IP : return Ch. B Special Receive Condition */ return status; } /* * Update the content of RRO (TX/RX status and External status) * As a result of a pending interrupt, some bits in RR0 for external status can be latched. * - Bit should not be updated if corresponding IE bit in WR15 is set (bit is latched) * - If corresponding bit in WR15 is clear then RR0 will reflect the current status (bit is not latched) * Latch will be reset when writing command RESET_EXT_STATUS_INT in WR0 */ static void SCC_Update_RR0 ( int Channel ) { uint8_t RR0_New; uint8_t RR0_Old; bool Update_CTS= false; bool Update_DCD= false; bool Set_RR3; //fprintf ( stderr , "update rr0 %c in=$%02x wr15=$%02x pc=%x\n" , 'A'+Channel , SCC.Chn[Channel].RR[0] , SCC.Chn[Channel].WR[15] , M68000_GetPC() ); if ( !SCC.Chn[ Channel ].RR0_IsLatched ) { /* Use all "non latched" bits for RR0 */ RR0_New = SCC.Chn[ Channel ].RR0_No_Latch; /* Update CTS and DCD with their current line value */ Update_CTS = true; Update_DCD = true; } else { /* Bits 0 and 2 are not latched */ RR0_New = SCC.Chn[ Channel ].RR0_No_Latch & ( SCC_RR0_BIT_RX_CHAR_AVAILABLE | SCC_RR0_BIT_TX_BUFFER_EMPTY ); /* Bit 1 Zero count is special, it's never latched (although it can activate the latches) */ RR0_New |= SCC.Chn[ Channel ].RR0_No_Latch & SCC_RR0_BIT_ZERO_COUNT; /* Bit 3 : DCD */ if ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_DCD_INT_ENABLE ) RR0_New |= SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_DCD; else Update_DCD = true; /* Bit 4 : sync hunt */ if ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_SYNC_HUNT_INT_ENABLE ) RR0_New |= SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_SYNC_HUNT; else RR0_New |= SCC.Chn[ Channel ].RR0_No_Latch & SCC_RR0_BIT_SYNC_HUNT; /* Bit 5 : CTS */ if ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_CTS_INT_ENABLE ) RR0_New |= SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_CTS; else Update_CTS = true; /* Bit 6 : tx underrun */ if ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_TX_UNDERRUN_EOM_INT_ENABLE ) RR0_New |= SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_TX_UNDERRUN_EOM; else RR0_New |= SCC.Chn[ Channel ].RR0_No_Latch & SCC_RR0_BIT_TX_UNDERRUN_EOM; /* Bit 7 : break/abort */ if ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_BREAK_ABORT_INT_ENABLE ) RR0_New |= SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_BREAK_ABORT; else RR0_New |= SCC.Chn[ Channel ].RR0_No_Latch & SCC_RR0_BIT_BREAK_ABORT; } if ( Update_CTS ) { RR0_New &= ~SCC_RR0_BIT_CTS; if ( SCC_Serial_Get_CTS ( Channel ) ) RR0_New |= SCC_RR0_BIT_CTS; } if ( Update_DCD ) { RR0_New &= ~SCC_RR0_BIT_DCD; if ( SCC_Serial_Get_DCD ( Channel ) ) RR0_New |= SCC_RR0_BIT_DCD; } RR0_Old = SCC.Chn[ Channel ].RR[0]; SCC.Chn[ Channel ].RR[0] = RR0_New; /* Changes in RR0 can set RR3 Ext IP bit */ /* This can be either for any transition, or only 0 to 1 transition */ /* IP bits can be reset using "reset command" in WR0 */ if ( SCC.Chn[Channel].WR[1] & SCC_WR1_BIT_EXT_INT_ENABLE ) { Set_RR3 = false; /* Bit 1 ZC : 0->1 */ if ( ( ( ( RR0_Old & SCC_RR0_BIT_ZERO_COUNT ) == 0 ) && ( ( RR0_New & SCC_RR0_BIT_ZERO_COUNT ) != 0 ) ) && ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_ZERO_COUNT_INT_ENABLE ) ) Set_RR3 = true; /* Bit 3 DCD : 0->1 or 1->0 */ else if ( ( ( RR0_Old & SCC_RR0_BIT_DCD ) != ( RR0_New & SCC_RR0_BIT_DCD ) ) && ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_DCD_INT_ENABLE ) ) Set_RR3 = true; /* Bit 4 SYNC : 0->1 or 1->0 (in asynchronous mode) */ else if ( ( ( RR0_Old & SCC_RR0_BIT_SYNC_HUNT ) != ( RR0_New & SCC_RR0_BIT_SYNC_HUNT ) ) && ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_SYNC_HUNT_INT_ENABLE ) ) Set_RR3 = true; /* Bit 5 CTS : 0->1 or 1->0 */ else if ( ( ( RR0_Old & SCC_RR0_BIT_CTS ) != ( RR0_New & SCC_RR0_BIT_CTS ) ) && ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_CTS_INT_ENABLE ) ) Set_RR3 = true; /* Bit 6 TX underrun : 0->1 */ else if ( ( ( ( RR0_Old & SCC_RR0_BIT_TX_UNDERRUN_EOM ) == 0 ) && ( ( RR0_New & SCC_RR0_BIT_TX_UNDERRUN_EOM ) != 0 ) ) && ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_TX_UNDERRUN_EOM_INT_ENABLE ) ) Set_RR3 = true; /* Bit 6 Break/Abord : 0->1 or 1->0 */ else if ( ( ( RR0_Old & SCC_RR0_BIT_BREAK_ABORT ) != ( RR0_New & SCC_RR0_BIT_BREAK_ABORT ) ) && ( SCC.Chn[ Channel ].WR[15] & SCC_WR15_BIT_BREAK_ABORT_INT_ENABLE ) ) Set_RR3 = true; if ( Set_RR3 ) { SCC.Chn[ Channel ].RR0_IsLatched = true; /* Latch bits in RR0 */ if ( Channel ) SCC_Update_RR3_Bit ( 1 , SCC_RR3_BIT_EXT_STATUS_IP_B ); else SCC_Update_RR3_Bit ( 1 , SCC_RR3_BIT_EXT_STATUS_IP_A ); } } //fprintf ( stderr , "update rr0 %c out=$%02x wr15=$%02x pc=%x\n" , 'A'+Channel , SCC.Chn[Channel].RR[0] , SCC.Chn[Channel].WR[15] , M68000_GetPC() ); } static void SCC_Update_RR0_Clear ( int Channel , int bits ) { SCC.Chn[ Channel ].RR0_No_Latch &= ~bits; } static void SCC_Update_RR0_Set ( int Channel , int bits ) { SCC.Chn[ Channel ].RR0_No_Latch |= bits; } static void SCC_Update_RR0_Latch_Off ( int Channel ) { SCC.Chn[ Channel ].RR0_IsLatched = false; SCC_Update_RR0 ( Channel ); } /* * Update the content of RR2A and RR2B. * This is used when reading RR2A/RR2B or to send the vector during IACK */ static void SCC_Update_RR2 ( void ) { uint8_t Vector; uint8_t status; Vector = SCC.Chn[0].WR[2]; /* RR2A is WR2 */ SCC.Chn[0].RR[2] = Vector; /* RR2B is WR2 + status bits */ /* RR2B always include status bit even if SCC_WR9_BIT_VIS is not set */ status = SCC_Get_Vector_Status (); if ( SCC.Chn[0].WR[9] & SCC_WR9_BIT_STATUS_HIGH_LOW ) /* modify high bits */ { status = ( ( status & 1 ) << 2 ) + ( status & 2 ) + ( ( status & 4 ) >> 2 ); /* bits 2,1,0 become bits 0,1,2 */ Vector &= 0x8f; /* clear bits 4,5,6 */ Vector |= ( status << 4 ); /* insert status in bits 4,5,6 */ } else { Vector &= 0xf1; /* clear bits 1,2,3 */ Vector |= ( status << 1 ); /* insert status in bits 1,2,3 */ } SCC.Chn[1].RR[2] = Vector; } /* * Update the content of RR3A depending on the 3 interrupts sources (RX, TX, Ext) * and their corresponding IE bit */ static void SCC_Update_RR3_Bit ( bool Set , uint8_t Bit ) { if ( Set ) SCC.Chn[0].RR[3] |= Bit; else SCC.Chn[0].RR[3] &= ~Bit; } static void SCC_Update_RR3 ( int Channel ) { uint8_t Set; uint8_t RX_Mode; bool Int_On_RX; bool Int_On_Special; /* RR3 depends on some RR0 bits, so update RR0 first */ SCC_Update_RR0 ( Channel ); //printf ( stderr , "update rr3 %c in=$%02x rr0=$%02x wr15=$%02x ius=$%02x pc=%x\n" , 'A'+Channel , SCC.Chn[0].RR[3] , SCC.Chn[Channel].RR[0] , SCC.Chn[Channel].WR[15] , SCC.IUS , M68000_GetPC() ); /* * Update RR3 RX bits */ RX_Mode = ( SCC.Chn[ Channel ].WR[1] >> 3 ) & 0x03; Int_On_RX = false; Int_On_Special = false; if ( RX_Mode != SCC_WR1_RX_MODE_INT_OFF ) Int_On_Special = true; if ( ( RX_Mode == SCC_WR1_RX_MODE_INT_FIRST_CHAR_SPECIAL ) || ( RX_Mode == SCC_WR1_RX_MODE_INT_ALL_CHAR_SPECIAL ) ) Int_On_RX = true; if ( ( Int_On_RX && ( SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_RX_CHAR_AVAILABLE ) ) || ( Int_On_Special && ( ( SCC.Chn[ Channel ].RR[1] & SCC_RR1_BIT_RX_OVERRUN_ERROR ) || ( SCC.Chn[ Channel ].RR[1] & SCC_RR1_BIT_CRC_FRAMING_ERROR ) || ( SCC.Chn[ Channel ].RR[1] & SCC_RR1_BIT_EOF_SDLC ) || ( ( SCC.Chn[ Channel ].RR[1] & SCC_RR1_BIT_PARITY_ERROR ) && ( SCC.Chn[ Channel ].WR[1] & SCC_WR1_BIT_PARITY_SPECIAL_COND ) ) ) ) ) Set = 1; else Set = 0; if ( Channel ) SCC_Update_RR3_Bit ( Set , SCC_RR3_BIT_RX_IP_B ); else SCC_Update_RR3_Bit ( Set , SCC_RR3_BIT_RX_IP_A ); /* * Update RR3 TX bits (only if Tx buffer is empty but has previously been written to) */ if ( ( SCC.Chn[ Channel ].RR[0] & SCC_RR0_BIT_TX_BUFFER_EMPTY ) && ( SCC.Chn[ Channel ].WR[1] & SCC_WR1_BIT_TX_INT_ENABLE ) && ( SCC.Chn[ Channel ].TX_Buffer_Written ) ) Set = 1; else Set = 0; if ( Channel ) SCC_Update_RR3_Bit ( Set , SCC_RR3_BIT_TX_IP_B ); else SCC_Update_RR3_Bit ( Set , SCC_RR3_BIT_TX_IP_A ); //fprintf ( stderr , "update rr3 %c out=$%02x rr0=$%02x wr15=$%02x ius=$%02x pc=%x\n" , 'A'+Channel , SCC.Chn[0].RR[3] , SCC.Chn[Channel].RR[0] , SCC.Chn[Channel].WR[15] , SCC.IUS , M68000_GetPC() ); } /* * Read from data register * This function is called either when reading from RR8 or when setting D//C signal to high * to read directly from the data register */ static uint8_t SCC_ReadDataReg(int chn) { /* NOTE : when reading data reg we consider a 1 byte FIFO instead of the real 3 bytes FIFO */ /* to simplify processing (see SCC_Process_RX) */ /* So we clear SCC_RR0_BIT_RX_CHAR_AVAILABLE immediately after reading, but it should be cleared */ /* when the 3 bytes FIFO is completely empty */ SCC_Update_RR0_Clear ( chn , SCC_RR0_BIT_RX_CHAR_AVAILABLE ); SCC_IntSources_Clear ( chn , SCC_INT_SOURCE_RX_CHAR_AVAILABLE ); return SCC.Chn[chn].RR[8]; } static uint8_t SCC_ReadControl(int chn) { uint8_t value = 0; uint8_t active_reg; active_reg = SCC.Active_Reg; switch ( active_reg ) { case 0: // RR0 case 4: // also returns RR0 SCC_Update_RR0 ( chn ); value = SCC.Chn[chn].RR[0]; LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d tx/rx buffer status value=$%02x\n" , 'A'+chn , active_reg , value ); break; case 1: // RR1 case 5: // also returns RR1 value = SCC.Chn[chn].RR[1]; LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d special cond status value=$%02x\n" , 'A'+chn , active_reg , value ); break; case 2: /* Return interrupt vector and perform INTACK when in software mode */ SCC_Update_RR2 (); if ( SCC.Chn[0].WR[9] & SCC_WR9_BIT_SOFT_INTACK ) { SCC_Soft_IACK (); } value = SCC.Chn[chn].RR[2]; /* RR2A or RR2B with status bits */ LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d int vector value=$%02x\n" , 'A'+chn , active_reg , value ); break; case 3: value = chn ? 0 : SCC.Chn[0].RR[3]; /* access on A channel only, return 0 on channel B */ LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d interrupt pending value=$%02x\n" , 'A'+chn , active_reg , value ); break; // RR4 : See RR0 // RR5 : See RR1 // RR6/RR7 : Low/High bytes of the frame byte count if WR15 bit 2 is set. Else, return RR2/RR3 case 6: case 7: if ( SCC.Chn[0].WR[15] & SCC_WR15_BIT_STATUS_FIFO_ENABLE ) { value = SCC.Chn[chn].RR[active_reg]; /* for SDLC, not used in Hatari */ LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d status fifo lsb/msb value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); } else { value = SCC.Chn[0].RR[active_reg-4]; /* return RR2 or RR3 */ LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d returns RR%d value=$%02x\n" , 'A'+chn , SCC.Active_Reg , active_reg-4 , value ); } break; case 8: // DATA reg value = SCC_ReadDataReg ( chn ); LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d data reg value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); break; // RR9 : See RR13 case 10: // Misc Status Bits case 14: // also returns RR10 value = SCC.Chn[chn].RR[10]; /* for DPLL/SDLC, not used in Hatari */ LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d dpll/sdlc status value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); break; // RR11 : See RR15 case 12: // BRG LSB value = SCC.Chn[chn].WR[active_reg]; LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d baud rate time constant low value=$%02x\n" , 'A'+chn , active_reg , value ); break; case 13: // BRG MSB case 9: // also returns RR13 value = SCC.Chn[chn].WR[active_reg]; LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d baud rate time constant high value=$%02x\n" , 'A'+chn , active_reg , value ); break; // RR14 : See RR10 case 15: // EXT/STATUS IT Ctrl case 11: // also returns RR15 value = SCC.Chn[chn].WR[15] &= 0xFA; // mask out D2 and D0 LOG_TRACE(TRACE_SCC, "scc read channel=%c RR%d ext status IE value=$%02x\n" , 'A'+chn , active_reg , value ); break; default: /* should not happen */ Log_Printf(LOG_DEBUG, "SCC: unprocessed read, address=$%x\n", active_reg); value = 0; break; } LOG_TRACE(TRACE_SCC, "scc read RR%d value=$%02x\n" , active_reg , value ); return value; } static uint8_t SCC_handleRead(uint32_t addr) { uint8_t value; int Channel; Channel = ( addr >> 2 ) & 1; /* bit 2 : 0 = channel A, 1 = channel B */ LOG_TRACE(TRACE_SCC, "scc read addr=%x channel=%c VBL=%d HBL=%d pc=%x\n" , addr , 'A'+Channel , nVBLs , nHBL , M68000_GetPC() ); if ( addr & 2 ) /* bit 1 */ value = SCC_ReadDataReg(Channel); else value = SCC_ReadControl(Channel); SCC.Active_Reg = 0; /* Next access default to RR0 or WR0 */ return value; } /* * Write to data register * This function is called either when writing to WR8 or when setting D//C signal to high * to write directly to the data register */ static void SCC_WriteDataReg(int chn, uint8_t value) { SCC.Chn[chn].WR[8] = value; //fprintf(stderr , "scc write_data_reg1 rr0_nolatch=%x rr0=%x written=%d rr1=%x\n" , SCC.Chn[chn].RR0_No_Latch , SCC.Chn[chn].RR[0] , SCC.Chn[ chn ].TX_Buffer_Written , SCC.Chn[chn].RR[1] ); /* According to the SCC doc, transmit buffer will be copied to the */ /* transmit shift register TSR after the last bit is shifted out, in ~3 PCLKs */ /* This means that if transmitter is disabled we can consider TDR is copied */ /* almost immediately to TSR when writing to WR8 */ /* Else TDR will be copied during SCC_Process_TX when current transfer is completed */ /* If this is the first write to WR8 (TX_Buffer_Written==false) we should also consider */ /* that TDR is copied almost immediately to TSR */ if ( ( ( SCC.Chn[chn].WR[5] & SCC_WR5_BIT_TX_ENABLE ) == 0 ) || ( SCC.Chn[chn].TSR_Full == false ) ) { SCC_Copy_TDR_TSR ( chn , SCC.Chn[chn].WR[8] ); } else { /* Clear TX Buffer Empty bit and reset TX Interrupt Pending for this channel */ SCC_Update_RR0_Clear ( chn , SCC_RR0_BIT_TX_BUFFER_EMPTY ); SCC_IntSources_Clear ( chn , SCC_INT_SOURCE_TX_BUFFER_EMPTY ); } SCC.Chn[chn].TX_Buffer_Written = true; /* Allow TBE int later if enabled */ //fprintf(stderr , "scc write_data_reg2 rr0_nolatch=%x rr0=%x written=%d rr1=%x\n" , SCC.Chn[chn].RR0_No_Latch , SCC.Chn[chn].RR[0] , SCC.Chn[ chn ].TX_Buffer_Written , SCC.Chn[chn].RR[1] ); } static void SCC_WriteControl(int chn, uint8_t value) { int i; uint8_t command; uint8_t bits; if ( SCC.Active_Reg == 0 ) { // TODO for later [NP] : according to doc, it should be possible to set register nbr // and execute a command at the same time /* Bits 0-2 : register nbr */ /* Bits 3-5 : command */ /* Bits 6-7 : CRC reset codes */ if (value <= 15) { SCC.Active_Reg = value & 0x0f; LOG_TRACE(TRACE_SCC, "scc set active reg=R%d\n" , SCC.Active_Reg ); } else { command = ( value >> 3 ) & 7; if ( command == SCC_WR0_COMMAND_NULL ) {} /* This will select registers 0-7 */ else if ( command == SCC_WR0_COMMAND_POINT_HIGH ) {} /* This will select registers 8-15 */ else if ( command == SCC_WR0_COMMAND_RESET_EXT_STATUS_INT ) { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d value=$%02x command=reset ext/status int RR3=$%02x IUS=$%02x\n" , 'A'+chn , SCC.Active_Reg , value , SCC.Chn[0].RR[3] , SCC.IUS ); /* Remove latches on RR0 and allow interrupt to happen again */ if ( chn ) SCC_Update_RR3_Bit ( 0 , SCC_RR3_BIT_EXT_STATUS_IP_B ); else SCC_Update_RR3_Bit ( 0 , SCC_RR3_BIT_EXT_STATUS_IP_A ); SCC_Update_RR0_Latch_Off ( chn ); SCC_Update_RR3 ( chn ); SCC_Update_IRQ (); } else if ( command == SCC_WR0_COMMAND_SEND_ABORT ) {} /* Not emulated */ else if ( command == SCC_WR0_COMMAND_INT_NEXT_RX ) { } else if ( command == SCC_WR0_COMMAND_RESET_TX_IP ) { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d value=$%02x command=reset tx ip RR3=$%02x IUS=$%02x\n" , 'A'+chn , SCC.Active_Reg , value , SCC.Chn[0].RR[3] , SCC.IUS ); SCC.Chn[chn].TX_Buffer_Written = false; if ( chn ) SCC.Chn[0].RR[3] &= ~SCC_RR3_BIT_TX_IP_B; else SCC.Chn[0].RR[3] &= ~SCC_RR3_BIT_TX_IP_A; SCC_Update_IRQ (); } else if ( command == SCC_WR0_COMMAND_ERROR_RESET ) { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d value=$%02x command=reset error RR1=$%02x RR3=$%02x IUS=$%02x\n" , 'A'+chn , SCC.Active_Reg , value , SCC.Chn[chn].RR[1] , SCC.Chn[0].RR[3] , SCC.IUS ); /* Reset error bits in RR1 */ SCC.Chn[chn].RR[1] &= ~( SCC_RR1_BIT_PARITY_ERROR | SCC_RR1_BIT_RX_OVERRUN_ERROR | SCC_RR1_BIT_CRC_FRAMING_ERROR ); SCC_IntSources_Clear ( chn , SCC_INT_SOURCE_RX_PARITY_ERROR | SCC_INT_SOURCE_RX_OVERRUN | SCC_INT_SOURCE_RX_FRAMING_ERROR ); } else if ( command == SCC_WR0_COMMAND_RESET_HIGHEST_IUS ) { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d value=$%02x command=reset highest ius RR3=$%02x IUS=$%02x\n" , 'A'+chn , SCC.Active_Reg , value , SCC.Chn[0].RR[3] , SCC.IUS ); for ( i=5 ; i>=0 ; i-- ) if ( SCC.IUS & ( 1 << i ) ) { SCC.IUS &= ~( 1 << i ); break; } SCC_Update_IRQ (); } } return; } LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); /* Special case for WR7' (Active_Reg=7 and WR15 bit0=1) */ if ( ( SCC.Active_Reg == 7 ) && ( SCC.Chn[chn].WR[15] & 1 ) ) SCC.Chn[chn].WR7p = value; else SCC.Chn[chn].WR[SCC.Active_Reg] = value; if (SCC.Active_Reg == 1) // Tx/Rx interrupt enable { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set tx/rx int value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); /* Update RR3 depending on WR1 then update IRQ state */ SCC_Update_RR3 ( chn ); SCC_Update_IRQ (); } else if (SCC.Active_Reg == 2) /* Interrupt Vector */ { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set int vector value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC.Chn[0].WR[2] = value; /* WR2 is common to channels A and B, store it in channel A */ } else if (SCC.Active_Reg == 3) /* Receive parameter and control */ { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set rx parameter and control value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); /* Bit 0 : RX Enable */ // -> see SCC_Process_RX /* Bit 6-7 : RX Bits/char */ bits = ( value >> 6 ) & 3; if ( bits == SCC_WR3_RX_5_BITS ) SCC.Chn[chn].RX_bits = 5; else if ( bits == SCC_WR3_RX_6_BITS ) SCC.Chn[chn].RX_bits = 6; else if ( bits == SCC_WR3_RX_7_BITS ) SCC.Chn[chn].RX_bits = 7; else if ( bits == SCC_WR3_RX_8_BITS ) SCC.Chn[chn].RX_bits = 8; } else if (SCC.Active_Reg == 4) /* Tx/Rx misc parameters and modes */ { uint8_t stop_bits; LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set tx/rx stop/parity value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); /* Bit 0 : Parity Enable */ SCC.Chn[chn].Parity_bits = ( value & SCC_WR4_BIT_PARITY_ENABLE ? 1 : 0 ); /* Bit 2-3 : Parity Mode */ stop_bits = ( value >> 2 ) & 3; if ( stop_bits == SCC_WR4_STOP_SYNC ) SCC.Chn[chn].Stop_bits = 0; else if ( stop_bits == SCC_WR4_STOP_1_BIT ) SCC.Chn[chn].Stop_bits = 1; else if ( stop_bits == SCC_WR4_STOP_15_BIT ) SCC.Chn[chn].Stop_bits = 1.5; else if ( stop_bits == SCC_WR4_STOP_2_BIT ) SCC.Chn[chn].Stop_bits = 2; if ( stop_bits != SCC_WR4_STOP_SYNC ) /* asynchronous mode */ SCC_Update_RR0_Set ( chn , SCC_RR0_BIT_TX_UNDERRUN_EOM ); SCC_Update_BaudRate ( chn ); } else if (SCC.Active_Reg == 5) /* Transmit parameter and control */ { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set tx parameter and control value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); /* Bit 2 : set RTS */ SCC_serial_setRTS(chn, value & SCC_WR5_BIT_RTS); /* Bit 3 : TX Enable */ // -> see SCC_Process_TX /* Bit 4 : Send Break */ SCC_serial_Set_BRK(chn, value & SCC_WR5_BIT_SEND_BREAK); /* Bit 5-6 : TX Bits/char */ bits = ( value >> 6 ) & 3; if ( bits == SCC_WR5_TX_5_BITS ) SCC.Chn[chn].TX_bits = 5; else if ( bits == SCC_WR5_TX_6_BITS ) SCC.Chn[chn].TX_bits = 6; else if ( bits == SCC_WR5_TX_7_BITS ) SCC.Chn[chn].TX_bits = 7; else if ( bits == SCC_WR5_TX_8_BITS ) SCC.Chn[chn].TX_bits = 8; /* Bit 7 : set DTR */ SCC_serial_setDTR(chn, value & SCC_WR5_BIT_DTR); } else if (SCC.Active_Reg == 6) // Sync characters high or SDLC Address Field { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set sync hi/sdlc addr value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); } else if (SCC.Active_Reg == 7) // Sync characters low or SDLC flag or WR7' { if ( SCC.Chn[chn].WR[15] & 1 ) { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set WR7' value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); } else { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set sync low/sdlc flag value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); } } else if (SCC.Active_Reg == 8) { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set transmit buffer value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC_WriteDataReg ( chn , value ); } else if (SCC.Active_Reg == 9) /* Master interrupt control (common for both channels) */ { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set master control value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC.Chn[0].WR[9] = value; /* WR9 is common to channels A and B, store it in channel A */ /* Bit 0 : VIS, Vector Includes Status */ /* Bit 1 : NV, No Vector during INTACK */ /* Bit 2 : Disable Lower Chain (not used in Hatari, there's only 1 SCC) */ /* Bit 3 : Master Interrupt Enable */ if ( ( value & SCC_WR9_BIT_MIE ) == 0 ) { /* Clearing MIE reset IUS and IRQ */ SCC.IUS = 0; } /* Bit 4 : Status High / Low */ /* Bit 5 : Software INTACK Enable */ /* Bits 6-7 : reset command */ command = ( value >> 6 ) & 3; if ( command == SCC_WR9_COMMAND_RESET_FORCE_HW ) { SCC_ResetFull ( false ); /* Force Hardware Reset */ } else if ( command == SCC_WR9_COMMAND_RESET_A ) { SCC_ResetChannel ( 0 , false ); /* Channel A */ } else if ( command == SCC_WR9_COMMAND_RESET_B ) { SCC_ResetChannel ( 1 , false ); /* Channel B */ } SCC_Update_IRQ (); /* Update IRQ depending on MIE bit */ } else if (SCC.Active_Reg == 10) // Tx/Rx misc control bits { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set tx/rx control bits value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); } else if (SCC.Active_Reg == 11) // Clock Mode Control { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set clock mode control value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC_Update_BaudRate ( chn ); } else if (SCC.Active_Reg == 12) // Lower byte of baud rate { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set baud rate time constant low value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC_Update_BaudRate ( chn ); } else if (SCC.Active_Reg == 13) // set baud rate according to WR13 and WR12 { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set baud rate time constant high value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC_Update_BaudRate ( chn ); } else if (SCC.Active_Reg == 14) // Misc Control bits { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set misc control bits value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); SCC_Update_BaudRate ( chn ); } else if (SCC.Active_Reg == 15) // external status int control { LOG_TRACE(TRACE_SCC, "scc write channel=%c WR%d set ext status int control value=$%02x\n" , 'A'+chn , SCC.Active_Reg , value ); /* Bit 0 : Point to Write Register WR7 Prime */ /* Bit 1 : Zero Count Interrupt Enable ; if 0 then zero count bit must be cleared in RR0 */ if ( ( value & SCC_WR15_BIT_ZERO_COUNT_INT_ENABLE ) == 0 ) SCC_Update_RR0_Clear ( chn , SCC_RR0_BIT_ZERO_COUNT ); /* Clear Zero Count bit */ /* Bit 2 : Status FIFO Enable */ /* Bit 3 : DCD Int Enable */ /* Bit 4 : SYNC/Hunt Int Enable */ /* Bit 5 : CTS Int Enable */ /* Bit 6 : Transmit Underrun/EOM Int Enable */ /* Bit 7 : Break/Abort Int Enable */ /* Update RR3 depending on WR15 then update IRQ state */ SCC_Update_RR3 ( chn ); SCC_Update_IRQ (); } SCC.Active_Reg = 0; /* next access for RR0 or WR0 */ } static void SCC_handleWrite(uint32_t addr, uint8_t value) { int Channel; Channel = ( addr >> 2 ) & 1; /* bit 2 : 0 = channel A, 1 = channel B */ LOG_TRACE(TRACE_SCC, "scc write addr=%x channel=%c value=$%02x VBL=%d HBL=%d pc=%x\n" , addr , 'A'+Channel , value , nVBLs , nHBL , M68000_GetPC() ); if ( addr & 2 ) /* bit 1 */ SCC_WriteDataReg ( Channel, value ); else SCC_WriteControl ( Channel, value ); } /* * Copy the content of the Transmit Data Register TDR to the Transmit Shift Register TSR * and set TX Buffer Empty (TBE) bit * According to the SCC doc, transmit buffer will be copied to the * transmit shift register TSR after the last bit is shifted out, in ~3 PCLKs * and 'all sent' bit will be cleared in RR1 */ static void SCC_Copy_TDR_TSR ( int Channel , uint8_t TDR ) { SCC.Chn[Channel].TSR = TDR; SCC.Chn[Channel].TSR_Full = true; /* Clear 'All Sent' bit in RR1 */ SCC.Chn[Channel].RR[1] &= ~SCC_RR1_BIT_ALL_SENT; /* new TSR to send */ /* Set 'TX buffer empty' */ SCC_Update_RR0_Set ( Channel , SCC_RR0_BIT_TX_BUFFER_EMPTY ); SCC_IntSources_Set ( Channel , SCC_INT_SOURCE_TX_BUFFER_EMPTY ); } /* Order of operations : * - check there's no underrun * - send current value of TSR to the emulator's underlying OS * - set 'all sent' bit in RR1 * - load new value into TSR by copying TDR / WR8 if transmit buffer is not empty (+clear 'all sent' in RR1) * In case of underrun (tx buffer is empty and TSR is empty) then nothing is sent, * the transmitter will remain in its latest 'stop bit' state, until a new byte is written in WR8 */ static void SCC_Process_TX ( int Channel ) { //fprintf(stderr , "scc process1 tx rr0_nolatch=%x rr0=%x latched=%d rr1=%x\n" , SCC.Chn[Channel].RR0_No_Latch , SCC.Chn[Channel].RR[0] , SCC.Chn[ Channel ].RR0_IsLatched , SCC.Chn[Channel].RR[1] ); /* If no new byte was written to the tx buffer and TSR is empty then we have an underrun */ /* Don't do anything in that case, TxD pin will remain in its latest 'stop bit' state */ if ( ( SCC.Chn[Channel].RR[0] & SCC_RR0_BIT_TX_BUFFER_EMPTY ) && ( SCC.Chn[Channel].TSR_Full == false ) ) return; if ( ( SCC.Chn[Channel].TSR_Full && ( SCC.Chn[Channel].WR[5] & SCC_WR5_BIT_TX_ENABLE ) ) ) { /* Send byte to emulated serial device / file descriptor */ SCC_Serial_Write_Byte ( Channel, SCC.Chn[Channel].TSR ); /* All bits of TSR have been sent, TSR is empty */ SCC.Chn[Channel].TSR_Full = false; SCC.Chn[Channel].RR[1] |= SCC_RR1_BIT_ALL_SENT; /* Set 'All Sent' bit in RR1 */ } /* Prepare TSR for the next call if TX buffer is not empty */ if ( ( SCC.Chn[Channel].RR[0] & SCC_RR0_BIT_TX_BUFFER_EMPTY ) == 0 ) SCC_Copy_TDR_TSR ( Channel , SCC.Chn[Channel].WR[8] ); //fprintf(stderr , "scc process2 tx rr0_nolatch=%x rr0=%x latched=%d rr1=%x\n" , SCC.Chn[Channel].RR0_No_Latch , SCC.Chn[Channel].RR[0] , SCC.Chn[ Channel ].RR0_IsLatched , SCC.Chn[Channel].RR[1] ); } static void SCC_Process_RX ( int Channel ) { uint8_t rx_byte; if ( SCC.Chn[Channel].WR[3] & SCC_WR3_BIT_RX_ENABLE ) { /* Receive byte from emulated serial device / file descriptor */ if ( SCC_Serial_Read_Byte ( Channel , &rx_byte ) ) { SCC.Chn[Channel].RR[8] = rx_byte; if ( SCC.Chn[Channel].RR[0] & SCC_RR0_BIT_RX_CHAR_AVAILABLE ) { /* NOTE : The SCC has a 3 bytes deep FIFO, so we should have an overrun */ /* when we receive a new byte and all 3 bytes were not read so far */ /* In Hatari's case we simplify this condition by using a 1 byte FIFO, */ /* so any new char received when latest char was not read will set */ /* the overrun bit (this can be improved later if needed) */ SCC.Chn[Channel].RR[1] |= SCC_RR1_BIT_RX_OVERRUN_ERROR; SCC_IntSources_Set ( Channel , SCC_INT_SOURCE_RX_OVERRUN ); } else { SCC_Update_RR0_Set ( Channel , SCC_RR0_BIT_RX_CHAR_AVAILABLE ); SCC_IntSources_Set ( Channel , SCC_INT_SOURCE_RX_CHAR_AVAILABLE ); } } } } /* * Start the internal interrupt handler for SCC A or B when the baud rate generator is enabled */ static void SCC_Start_InterruptHandler_BRG ( int Channel , int InternalCycleOffset ) { int IntHandler; int Cycles; if ( Channel == 0 ) IntHandler = INTERRUPT_SCC_BRG_A; else IntHandler = INTERRUPT_SCC_BRG_B; Cycles = MachineClocks.CPU_Freq / SCC.Chn[Channel].BaudRate_BRG; /* Convert baud rate in CPU cycles */ LOG_TRACE ( TRACE_SCC, "scc start interrupt handler brg channel=%c baudrate=%d cpu_cycles=%d VBL=%d HBL=%d\n" , 'A'+Channel , SCC.Chn[Channel].BaudRate_BRG , Cycles , nVBLs , nHBL ); CycInt_AddRelativeInterruptWithOffset ( Cycles, INT_CPU_CYCLE, IntHandler, InternalCycleOffset ); } /* * Stop the internal interrupt handler for SCC A or B when the baud rate generator is disabled */ static void SCC_Stop_InterruptHandler_BRG ( int Channel ) { int IntHandler; if ( Channel == 0 ) IntHandler = INTERRUPT_SCC_BRG_A; else IntHandler = INTERRUPT_SCC_BRG_B; CycInt_RemovePendingInterrupt ( IntHandler ); } /* * Interrupt called each time the baud rate generator's counter reaches 0 * We continuously restart the interrupt, taking into account PendingCyclesOver. */ static void SCC_InterruptHandler_BRG ( int Channel ) { int PendingCyclesOver; /* Number of internal cycles we went over for this timer ( <= 0 ) */ /* Used to restart the next timer and keep a constant baud rate */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ LOG_TRACE ( TRACE_SCC, "scc interrupt handler brg channel=%c pending_cyc=%d VBL=%d HBL=%d\n" , 'A'+Channel , PendingCyclesOver , nVBLs , nHBL ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); SCC_Start_InterruptHandler_BRG ( Channel , -PendingCyclesOver ); /* Compensate for a != 0 value of PendingCyclesOver */ /* BRG counter reached 0, check if corresponding interrupt pending bit must be set in RR3 */ /* NOTE : we should set bit ZERO_COUNT in RR0 here, but we don't support resetting it later */ /* as this would require to emulate BRG on every count, which would slow down emulation too much */ /* Instead, we set ZC bit, update irq, then clear ZC bit just after, which should give */ /* a close enough result as real HW */ SCC_Update_RR0_Set ( Channel , SCC_RR0_BIT_ZERO_COUNT ); SCC_IntSources_Set ( Channel , SCC_INT_SOURCE_EXT_ZERO_COUNT ); SCC_Update_RR0_Clear ( Channel , SCC_RR0_BIT_ZERO_COUNT ); SCC_IntSources_Clear_NoUpdate ( Channel , SCC_INT_SOURCE_EXT_ZERO_COUNT ); } void SCC_InterruptHandler_BRG_A ( void ) { SCC_InterruptHandler_BRG ( 0 ); } void SCC_InterruptHandler_BRG_B ( void ) { SCC_InterruptHandler_BRG ( 1 ); } /* * Start the TX or RX internal cycint timer * NOTE : instead of having a cycint interrupt on every bit, we trigger the cycint interrupt * only when 1 char has been sent/received, taking into account all start/parity/stop bits * Although not fully accurate this will give a good timing to emulate when RX buffer is full * or when TX buffer is empty (and set the corresponding status bits in RRx) and this will * lower the cpu usage on the host running the emulation (as baudrate can be rather high with the SCC) */ static void SCC_Start_InterruptHandler_TX_RX ( int Channel , bool is_tx , int InternalCycleOffset ) { int IntHandler; int Cycles; int BaudRate; float Char_Total_Bits; /* Total number of bits for 1 char, including start/stop/parity bits */ if ( is_tx ) { BaudRate = SCC.Chn[Channel].BaudRate_TX; Char_Total_Bits = SCC.Chn[Channel].TX_bits; if ( Channel == 0 ) IntHandler = INTERRUPT_SCC_TX_RX_A; else IntHandler = INTERRUPT_SCC_TX_RX_B; } else { BaudRate = SCC.Chn[Channel].BaudRate_RX; Char_Total_Bits = SCC.Chn[Channel].RX_bits; if ( Channel == 0 ) IntHandler = INTERRUPT_SCC_RX_A; else IntHandler = INTERRUPT_SCC_RX_B; } /* Number of cycles to send/receive 1 bit */ Cycles = MachineClocks.CPU_Freq / BaudRate; /* Convert baud rate in CPU cycles */ /* Take start bit (=1), parity bit, stop bits and data bits into account */ /* to get the total number of bits to send/receive 1 char */ Char_Total_Bits += 1 + SCC.Chn[Channel].Parity_bits + SCC.Chn[Channel].Stop_bits; /* Get the total number of cycles to send/receive all the needed bits for 1 char */ Cycles = Cycles * Char_Total_Bits; LOG_TRACE ( TRACE_SCC, "scc start interrupt handler %s channel=%c total_bits=%.1f baudrate=%d cpu_cycles=%d VBL=%d HBL=%d\n" , is_tx?"tx":"rx" , 'A'+Channel , Char_Total_Bits , SCC.Chn[Channel].BaudRate_BRG , Cycles , nVBLs , nHBL ); CycInt_AddRelativeInterruptWithOffset ( Cycles, INT_CPU_CYCLE, IntHandler, InternalCycleOffset ); } /* * Stop the internal interrupt handler for SCC A or B */ static void SCC_Stop_InterruptHandler_TX_RX ( int Channel , bool is_tx ) { int IntHandler; if ( is_tx ) { if ( Channel == 0 ) IntHandler = INTERRUPT_SCC_TX_RX_A; else IntHandler = INTERRUPT_SCC_TX_RX_B; } else { if ( Channel == 0 ) IntHandler = INTERRUPT_SCC_RX_A; else IntHandler = INTERRUPT_SCC_RX_B; } LOG_TRACE ( TRACE_SCC, "scc stop interrupt handler %s channel=%c VBL=%d HBL=%d\n" , is_tx?"tx":"rx" , 'A'+Channel , nVBLs , nHBL ); CycInt_RemovePendingInterrupt ( IntHandler ); } static void SCC_Restart_InterruptHandler_TX_RX ( int Channel , bool is_tx ) { int PendingCyclesOver; /* Number of internal cycles we went over for this timer ( <= 0 ) */ /* Used to restart the next timer and keep a constant baud rate */ PendingCyclesOver = -PendingInterruptCount; /* >= 0 */ LOG_TRACE ( TRACE_SCC, "scc interrupt handler %s channel=%c pending_cyc=%d VBL=%d HBL=%d\n" , is_tx?"tx":"rx" , 'A'+Channel , PendingCyclesOver , nVBLs , nHBL ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); SCC_Start_InterruptHandler_TX_RX ( Channel , is_tx , -PendingCyclesOver ); /* Compensate for a != 0 value of PendingCyclesOver */ } static void SCC_InterruptHandler_TX_RX ( int Channel ) { SCC_Restart_InterruptHandler_TX_RX ( Channel , true ); SCC_Process_TX ( Channel ); /* If TX and RX use the same baudrate, we process RX here too */ /* (avoid using an additional cycint timer for RX) */ if ( SCC.Chn[Channel].BaudRate_TX == SCC.Chn[Channel].BaudRate_RX ) SCC_Process_RX ( Channel ); } void SCC_InterruptHandler_TX_RX_A ( void ) { SCC_InterruptHandler_TX_RX ( 0 ); } void SCC_InterruptHandler_TX_RX_B ( void ) { SCC_InterruptHandler_TX_RX ( 1 ); } static void SCC_InterruptHandler_RX ( int Channel ) { SCC_Restart_InterruptHandler_TX_RX ( Channel , false ); SCC_Process_RX ( Channel ); } void SCC_InterruptHandler_RX_A ( void ) { SCC_InterruptHandler_RX ( 0 ); } void SCC_InterruptHandler_RX_B ( void ) { SCC_InterruptHandler_RX ( 1 ); } /*-----------------------------------------------------------------------*/ /** * Set or reset the SCC's IRQ signal. * IRQ signal is inverted (0/low sets irq, 1/high clears irq) * On Falcon, SCC's INT pin is connected to COMBEL EINT5 * On MegaSTE and TT, SCC's INT pin is connected to TTSCU XSCCIRQ/SIR5 */ static void SCC_Set_Line_IRQ ( int bit ) { LOG_TRACE ( TRACE_SCC, "scc set irq line val=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL ); SCC.IRQ_Line = bit; M68000_Update_intlev (); } /*-----------------------------------------------------------------------*/ /** * Return the value of the SCC's IRQ signal. * IRQ signal is inverted (0/low sets irq, 1/high clears irq) */ int SCC_Get_Line_IRQ ( void ) { return SCC.IRQ_Line; } /* * Check if Master Interrupt is enabled and if any IP bits are set in RR3A (and not lower than IUS * Update main IRQ line accordingly */ static void SCC_Update_IRQ ( void ) { int IRQ_new; int i; //fprintf ( stderr , "scc update irq wr9=$%02x ius=$%02x rr3=$%02x irq_in=%d pc=%x\n" , SCC.Chn[0].WR[9] , SCC.IUS , SCC.Chn[0].RR[3] , SCC.IRQ_Line , M68000_GetPC() ); if ( SCC.Chn[0].WR[9] & SCC_WR9_BIT_MIE ) /* Master Interrupt enabled */ { /* Check if there's an IP bit set and not lower than IUS */ IRQ_new = SCC_IRQ_OFF; for ( i=5 ; i>=0 ; i-- ) /* Test from higher to lower IP */ { if ( SCC.IUS & ( 1 << i ) ) /* IUS bit set */ { IRQ_new = SCC_IRQ_OFF; break; } else if ( SCC.Chn[0].RR[3] & ( 1 << i ) ) /* IP bit set and IUS bit not set */ { IRQ_new = SCC_IRQ_ON; break; } } } else IRQ_new = SCC_IRQ_OFF; //fprintf ( stderr , "scc update irq wr9=$%02x ius=$%02x rr3=$%02x irq_out=%d pc=%x\n" , SCC.Chn[0].WR[9] , SCC.IUS , SCC.Chn[0].RR[3] , IRQ_new , M68000_GetPC() ); /* Update IRQ line if needed */ if ( IRQ_new != SCC.IRQ_Line ) SCC_Set_Line_IRQ ( IRQ_new ); } /* * Set/Clear some interrupt sources for channel A or B * Update RR3A and set IRQ Low/High depending on the result */ static void SCC_IntSources_Change ( int Channel , uint32_t Sources , bool Set ) { //fprintf ( stderr, "scc int source %d old=%x new=%x %d pc=%x\n" , Channel , SCC.Chn[ Channel ].IntSources , Sources , Set , M68000_GetPC() ); if ( Set ) { /* Don't do anything if all bits from Sources are already set */ if ( ( SCC.Chn[ Channel ].IntSources & Sources ) == Sources ) return; /* No change */ } else { /* Don't do anything if all bits from Sources are already cleared */ if ( ( SCC.Chn[ Channel ].IntSources & Sources ) == 0 ) return; /* No change */ } SCC_Update_RR3 ( Channel ); if ( Set ) SCC.Chn[ Channel ].IntSources |= Sources; else SCC.Chn[ Channel ].IntSources &= ~Sources; /* Set IRQ Low/high depending on RR3A */ SCC_Update_IRQ (); } /* * Shortcut function to set an interrupt source */ static void SCC_IntSources_Set ( int Channel , uint32_t Sources ) { SCC_IntSources_Change ( Channel , Sources , true ); } /* * Shortcut function to clear an interrupt source */ static void SCC_IntSources_Clear ( int Channel , uint32_t Sources ) { SCC_IntSources_Change ( Channel , Sources , false ); } /* * Just clear bits in interrupts sources, don't update RR3 and IRQ */ static void SCC_IntSources_Clear_NoUpdate ( int Channel , uint32_t Sources ) { SCC.Chn[ Channel ].IntSources &= ~Sources; } /* * Do the IACK sequence (common to the software IACK or the hardware IACK) : * - clear IRQ * - set IUS bit for the pending interrupt * - return an interrupt vector (only used for hardware IACK) */ static int SCC_Do_IACK ( bool Soft ) { int Vector; int i; SCC_Set_Line_IRQ ( SCC_IRQ_OFF ); /* Set IUS bit corresponding to highest IP */ for ( i=5 ; i>=0 ; i-- ) /* Test from higher to lower IP */ { if ( SCC.Chn[0].RR[3] & ( 1 << i ) ) { SCC.IUS |= ( 1 << i ); break; } } SCC_Update_RR2 (); if ( SCC.Chn[0].WR[9] & SCC_WR9_BIT_VIS ) Vector = SCC.Chn[1].RR[2]; /* RR2B including status bits */ else Vector = SCC.Chn[0].RR[2]; /* RR2B without status bits */ return Vector; } /* * Called when software IACK is enabled and reading from RR2 */ static void SCC_Soft_IACK ( void ) { SCC_Do_IACK ( true ); } /* * Called by the CPU when processing interrupts (see iack_cycle() in newcpu.c) */ int SCC_Process_IACK ( void ) { int Vector; Vector = SCC_Do_IACK ( false ); if ( SCC.Chn[0].WR[9] & SCC_WR9_BIT_NV ) return -1; /* IACK is disabled, no vector */ //fprintf ( stderr , "scc iack %d\n" , Vector ); return Vector; } void SCC_IoMem_ReadByte(void) { int i; for (i = 0; i < nIoMemAccessSize; i++) { uint32_t addr = IoAccessBaseAddress + i; if (addr & 1) IoMem[addr] = SCC_handleRead(addr); else IoMem[addr] = 0xff; } } void SCC_IoMem_WriteByte(void) { int i; for (i = 0; i < nIoMemAccessSize; i++) { uint32_t addr = IoAccessBaseAddress + i; if (addr & 1) SCC_handleWrite(addr, IoMem[addr]); } } void SCC_Info(FILE *fp, uint32_t dummy) { unsigned int i, reg; fprintf(fp, "SCC common:\n"); fprintf(fp, "- IRQ_Line: %d (%s)\n", SCC.IRQ_Line , SCC.IRQ_Line==SCC_IRQ_ON ? "ON" : "OFF" ); fprintf(fp, "- IUS: %02x\n", SCC.IUS); fprintf(fp, "- Active register: %d\n", SCC.Active_Reg); for (i = 0; i < 2; i++) { fprintf(fp, "\nSCC %c:\n", 'A' + i); fprintf(fp, "- Write Registers:\n"); for (reg = 0; reg < ARRAY_SIZE(SCC.Chn[i].WR); reg++) fprintf(fp, " %02x", SCC.Chn[i].WR[reg]); fprintf(fp, " WR7'=%02x\n" , SCC.Chn[i].WR7p ); fprintf(fp, "- Read Registers:\n"); for (reg = 0; reg < ARRAY_SIZE(SCC.Chn[i].RR); reg++) fprintf(fp, " %02x", SCC.Chn[i].RR[reg]); fprintf(fp, "\n"); fprintf(fp, "- Device's file is %s TTY\n", SCC.Chn[i].FileHandle_IsATTY ? "a" : "not a"); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/screen.c000066400000000000000000001241171504763705000227610ustar00rootroot00000000000000/* Hatari - screen.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This code converts a 1/2/4 plane ST format screen to either 8, 16 or 32-bit PC format. An awful lot of processing is needed to do this conversion - we cannot simply change palettes on interrupts as it is possible with DOS. The main code processes the palette/resolution mask tables to find exactly which lines need to updating and the conversion routines themselves only update 16-pixel blocks which differ from the previous frame - this gives a large performance increase. Each conversion routine can convert any part of the source ST screen (which includes the overscan border, usually set to colour zero) so they can be used for both window and full-screen mode. Note that in Hi-Resolution we have no overscan and just two colors so we can optimise things further. In color mode it seems possible to display 47 lines in the bottom border with a second 60/50 Hz switch, but most programs consider there are 45 visible lines in the bottom border only, which gives a total of 274 lines for a screen. So not displaying the last two lines fixes garbage that could appear in the last two lines when displaying 47 lines (Digiworld 2 by ICE, Tyranny by DHS). */ const char Screen_fileid[] = "Hatari screen.c"; #include #include #include #include "main.h" #include "configuration.h" #include "avi_record.h" #include "file.h" #include "log.h" #include "paths.h" #include "options.h" #include "screen.h" #include "screenConvert.h" #include "control.h" #include "convert/routines.h" #include "resolution.h" #include "spec512.h" #include "statusbar.h" #include "vdi.h" #include "video.h" #include "falcon/videl.h" #define DEBUG 0 #if DEBUG # define DEBUGPRINT(x) printf x #else # define DEBUGPRINT(x) #endif /* extern for several purposes */ SDL_Surface *sdlscrn = NULL; /* The SDL screen surface */ int nScreenZoomX, nScreenZoomY; /* Zooming factors, used for scaling mouse motions */ int nBorderPixelsLeft, nBorderPixelsRight; /* Pixels in left and right border */ static int nBorderPixelsTop, nBorderPixelsBottom; /* Lines in top and bottom border */ /* extern for shortcuts etc. */ bool bGrabMouse = false; /* Grab the mouse cursor in the window */ bool bInFullScreen = false; /* true if in full screen */ /* extern for spec512.c */ int STScreenLeftSkipBytes; int STScreenStartHorizLine; /* Start lines to be converted */ Uint32 STRGBPalette[16]; /* Palette buffer used in conversion routines */ Uint32 ST2RGB[4096]; /* Table to convert ST 0x777 / STe 0xfff palette to PC format RGB551 (2 pixels each entry) */ /* extern for video.c */ Uint8 *pSTScreen; FRAMEBUFFER *pFrameBuffer; /* Pointer into current 'FrameBuffer' */ /* extern for screen snapshot palettes */ Uint32* ConvertPalette = STRGBPalette; int ConvertPaletteSize = 0; uint16_t HBLPalettes[HBL_PALETTE_LINES]; /* 1x16 colour palette per screen line, +1 line just in case write after line 200 */ uint16_t *pHBLPalettes; /* Pointer to current palette lists, one per HBL */ uint32_t HBLPaletteMasks[HBL_PALETTE_MASKS]; /* Bit mask of palette colours changes, top bit set is resolution change */ uint32_t *pHBLPaletteMasks; static FRAMEBUFFER FrameBuffer; /* Store frame buffer details to tell how to update */ static Uint8 *pSTScreenCopy; /* Keep track of current and previous ST screen data */ static Uint8 *pPCScreenDest; /* Destination PC buffer */ static int STScreenEndHorizLine; /* End lines to be converted */ static int PCScreenBytesPerLine; static int STScreenWidthBytes; static int PCScreenOffsetX; /* how many pixels to skip from left when drawing */ static int PCScreenOffsetY; /* how many pixels to skip from top when drawing */ static SDL_Rect STScreenRect; /* screen size without statusbar */ int STScreenLineOffset[NUM_VISIBLE_LINES]; /* Offsets for ST screen lines eg, 0,160,320... */ static Uint16 HBLPalette[16], PrevHBLPalette[16]; /* Current palette for line, also copy of first line */ static void (*ScreenDrawFunctionsNormal[3])(void); /* Screen draw functions */ static bool bScreenContentsChanged; /* true if buffer changed and requires blitting */ static bool bScrDoubleY; /* true if double on Y */ static int ScrUpdateFlag; /* Bit mask of how to update screen */ static bool bRGBTableInSync; /* Is RGB table up to date? */ /* These are used for the generic screen conversion functions */ static int genconv_width_req, genconv_height_req; static bool Screen_DrawFrame(bool bForceFlip); SDL_Window *sdlWindow; static SDL_Renderer *sdlRenderer; static SDL_Texture *sdlTexture; static bool bUseSdlRenderer; /* true when using SDL2 renderer */ static bool bIsSoftwareRenderer; void Screen_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects) { if (bUseSdlRenderer) { SDL_UpdateTexture(sdlTexture, NULL, screen->pixels, screen->pitch); /* Need to clear the renderer context for certain accelerated cards */ if (!bIsSoftwareRenderer) SDL_RenderClear(sdlRenderer); SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL); SDL_RenderPresent(sdlRenderer); } else { SDL_UpdateWindowSurfaceRects(sdlWindow, rects, numrects); } } void Screen_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h) { SDL_Rect rect; if (w == 0 && h == 0) { x = y = 0; w = screen->w; h = screen->h; } rect.x = x; rect.y = y; rect.w = w; rect.h = h; Screen_UpdateRects(screen, 1, &rect); } /** * Create ST 0x777 / STe 0xfff color format to 16 or 32 bits per pixel * conversion table. Called each time when changed resolution or to/from * fullscreen mode. */ static void Screen_SetupRGBTable(void) { Uint16 STColor; Uint32 RGBColor; int r, g, b; int rr, gg, bb; /* Do Red, Green and Blue for all 16*16*16 = 4096 STe colors */ for (r = 0; r < 16; r++) { for (g = 0; g < 16; g++) { for (b = 0; b < 16; b++) { /* STe 0xfff format */ STColor = (r<<8) | (g<<4) | (b); rr = ((r & 0x7) << 1) | ((r & 0x8) >> 3); rr |= rr << 4; gg = ((g & 0x7) << 1) | ((g & 0x8) >> 3); gg |= gg << 4; bb = ((b & 0x7) << 1) | ((b & 0x8) >> 3); bb |= bb << 4; RGBColor = SDL_MapRGB(sdlscrn->format, rr, gg, bb); if (sdlscrn->format->BitsPerPixel <= 16) { /* As longs, for speed (write two pixels at once) */ ST2RGB[STColor] = (RGBColor<<16) | RGBColor; } else { ST2RGB[STColor] = RGBColor; } } } } } /** * Convert 640x400 monochrome screen */ static void Screen_ConvertHighRes(void) { int linewidth = 640 / 16; Screen_GenConvert(VideoBase, pSTScreen, 640, 400, 1, linewidth, 0, 0, 0, 0, 0); bScreenContentsChanged = true; } /** * Set screen draw functions. */ static void Screen_SetDrawFunctions(int nBitCount, bool bDoubleLowRes) { if (bDoubleLowRes) ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_640x32Bit; else ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_320x32Bit; ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x32Bit; } /*-----------------------------------------------------------------------*/ /** * Set amount of border pixels */ static void Screen_SetBorderPixels(int leftX, int leftY) { /* All screen widths need to be aligned to 16-bits */ nBorderPixelsLeft = Opt_ValueAlignMinMax(leftX/2, 16, 0, 48); nBorderPixelsRight = nBorderPixelsLeft; /* assertain assumption of code below */ assert(OVERSCAN_TOP < MAX_OVERSCAN_BOTTOM); if (leftY > 2*OVERSCAN_TOP) { nBorderPixelsTop = OVERSCAN_TOP; if (leftY >= OVERSCAN_TOP + MAX_OVERSCAN_BOTTOM) nBorderPixelsBottom = MAX_OVERSCAN_BOTTOM; else nBorderPixelsBottom = leftY - OVERSCAN_TOP; } else { if (leftY > 0) nBorderPixelsTop = nBorderPixelsBottom = leftY/2; else nBorderPixelsTop = nBorderPixelsBottom = 0; } } /*-----------------------------------------------------------------------*/ /** * store Y offset for each horizontal line in our source ST screen for * reference in the convert functions. */ static void Screen_SetSTScreenOffsets(void) { int i; /* Store offset to each horizontal line, uses * nBorderPixels* variables. */ for (i = 0; i < NUM_VISIBLE_LINES; i++) { STScreenLineOffset[i] = i * SCREENBYTES_LINE; } } /** * Return true if Falcon/TT/VDI generic screen convert functions * need to be used instead of the ST/STE functions. */ bool Screen_UseGenConvScreen(void) { return Config_IsMachineFalcon() || Config_IsMachineTT() || bUseHighRes || bUseVDIRes; } static void Screen_FreeSDL2Resources(void) { if (sdlTexture) { SDL_DestroyTexture(sdlTexture); sdlTexture = NULL; } if (sdlscrn) { if (bUseSdlRenderer) SDL_FreeSurface(sdlscrn); sdlscrn = NULL; } if (sdlRenderer) { SDL_DestroyRenderer(sdlRenderer); sdlRenderer = NULL; } } /* * Create window backing texture when needed, with suitable scaling * quality. * * Window size is affected by ZoomFactor setting and window resizes * done by the user, and constrained by maximum window size setting * and desktop size. * * Calculate scale factor for the given resulting window size, compared * to the size of the SDL frame buffer rendered by Hatari, and based on * that, set the render scaling quality hint to: * - (sharp) nearest pixel sampling for integer zoom factors * - (smoothing/blurring) linear filtering otherwise * * If hint value changes from earlier one (or force flag is used), * window texture needs to be re-created to apply the scaling quality * change. */ void Screen_SetTextureScale(int width, int height, int win_width, int win_height, bool bForce) { static char prev_quality; float scale_w, scale_h, scale; char quality; int pfmt; if (!(bUseSdlRenderer && sdlRenderer)) return; scale_w = (float)win_width / width; scale_h = (float)win_height / height; if (bInFullScreen) /* SDL letterboxes fullscreen so it's enough for * closest dimension to window size being evenly * divisible. */ scale = fminf(scale_w, scale_h); else /* For windowed mode (= no letterboxing), both * dimensions (here, their avg) need to be evenly * divisible for nearest neighbor scaling to look good. */ scale = (scale_w + scale_h) / 2.0; if (scale == floorf(scale)) quality = '0'; // nearest pixel else quality = '1'; // linear filtering DEBUGPRINT(("%dx%d / %dx%d -> scale = %g, Render Scale Quality = %c\n", win_width, win_height, width, height, scale, quality)); if (bForce || quality != prev_quality) { char hint[2] = { quality, 0 }; prev_quality = quality; /* hint needs to be there before texture */ SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY, hint, SDL_HINT_OVERRIDE); if (sdlTexture) { SDL_DestroyTexture(sdlTexture); sdlTexture = NULL; } if (sdlscrn->format->BitsPerPixel == 16) pfmt = SDL_PIXELFORMAT_RGB565; else pfmt = SDL_PIXELFORMAT_RGB888; sdlTexture = SDL_CreateTexture(sdlRenderer, pfmt, SDL_TEXTUREACCESS_STREAMING, width, height); if (!sdlTexture) { fprintf(stderr, "%dx%d@%d texture\n", width, height, sdlscrn->format->BitsPerPixel); Main_ErrorExit("Failed to create texture:", SDL_GetError(), -3); } } } /** * Change the SDL video mode. * @return true if mode has been changed, false if change was not necessary */ static bool Screen_SetSDLVideoSize(int width, int height, bool bForceChange) { Uint32 sdlVideoFlags; char *psSdlVideoDriver; bool bUseDummyMode; static bool bPrevUseVsync = false; static bool bPrevInFullScreen; int win_width, win_height; float scale = 1.0; /* Check if we really have to change the video mode: */ if (sdlscrn != NULL && sdlscrn->w == width && sdlscrn->h == height && !bForceChange) return false; psSdlVideoDriver = SDL_getenv("SDL_VIDEODRIVER"); bUseDummyMode = psSdlVideoDriver && !strcmp(psSdlVideoDriver, "dummy"); if (bInFullScreen) { /* unhide the Hatari WM window for fullscreen */ Control_ReparentWindow(width, height, bInFullScreen); } bUseSdlRenderer = ConfigureParams.Screen.bUseSdlRenderer && !bUseDummyMode; /* SDL Video attributes: */ win_width = width; win_height = height; if (bUseSdlRenderer) { scale = ConfigureParams.Screen.nZoomFactor; win_width *= scale; win_height *= scale; } if (bInFullScreen) { sdlVideoFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_INPUT_GRABBED; if (ConfigureParams.Screen.bKeepResolution) sdlVideoFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; else sdlVideoFlags |= SDL_WINDOW_FULLSCREEN; } else { int deskw, deskh; if (getenv("PARENT_WIN_ID") != NULL) /* Embedded window? */ sdlVideoFlags = SDL_WINDOW_BORDERLESS|SDL_WINDOW_HIDDEN; else if (ConfigureParams.Screen.bResizable && bUseSdlRenderer) sdlVideoFlags = SDL_WINDOW_RESIZABLE; else sdlVideoFlags = 0; /* Make sure that window is not bigger than current desktop */ if (bUseSdlRenderer) { Resolution_GetDesktopSize(&deskw, &deskh); if (win_width > deskw) win_width = deskw; if (win_height > deskh) win_height = deskh; } } Screen_FreeSDL2Resources(); if (sdlWindow && ((bInFullScreen && !ConfigureParams.Screen.bKeepResolution) || (bPrevInFullScreen != bInFullScreen) || bForceChange )) { SDL_DestroyWindow(sdlWindow); sdlWindow = NULL; } bPrevInFullScreen = bInFullScreen; if (bPrevUseVsync != ConfigureParams.Screen.bUseVsync) { char hint[2] = { '0' + ConfigureParams.Screen.bUseVsync, 0 }; SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, hint, SDL_HINT_OVERRIDE); bPrevUseVsync = ConfigureParams.Screen.bUseVsync; } /* Disable closing Hatari with alt+F4 under Windows as alt+F4 can be used by some emulated programs */ SDL_SetHintWithPriority(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1", SDL_HINT_OVERRIDE); /* Set new video mode */ DEBUGPRINT(("SDL screen request: %d x %d (%s) -> window: %d x %d\n", width, height, (bInFullScreen ? "fullscreen" : "windowed"), win_width, win_height)); if (sdlWindow) { if ((SDL_GetWindowFlags(sdlWindow) & SDL_WINDOW_MAXIMIZED) == 0) SDL_SetWindowSize(sdlWindow, win_width, win_height); } else { sdlWindow = SDL_CreateWindow("Hatari", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, win_width, win_height, sdlVideoFlags); if (!sdlWindow) { fprintf(stderr, "%dx%d window\n", win_width, win_height); Main_ErrorExit("Failed to create window:", SDL_GetError(), -1); } } if (bUseSdlRenderer) { int rm, bm, gm; SDL_RendererInfo sRenderInfo = { 0 }; sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, 0); if (!sdlRenderer) { fprintf(stderr, "%dx%d renderer\n", win_width, win_height); Main_ErrorExit("Failed to create renderer:", SDL_GetError(), 1); } if (bInFullScreen) SDL_RenderSetLogicalSize(sdlRenderer, width, height); else SDL_RenderSetScale(sdlRenderer, scale, scale); /* Force to black to stop side bar artifacts on 16:9 monitors. */ SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255); SDL_RenderClear(sdlRenderer); SDL_RenderPresent(sdlRenderer); SDL_GetRendererInfo(sdlRenderer, &sRenderInfo); bIsSoftwareRenderer = sRenderInfo.flags & SDL_RENDERER_SOFTWARE; rm = 0x00FF0000; gm = 0x0000FF00; bm = 0x000000FF; sdlscrn = SDL_CreateRGBSurface(0, width, height, 32, rm, gm, bm, 0); Screen_SetTextureScale(width, height, win_width, win_height, true); } else { sdlscrn = SDL_GetWindowSurface(sdlWindow); bIsSoftwareRenderer = true; } /* Exit if we can not open a screen */ if (!sdlscrn) { Main_ErrorExit("Could not set video mode:", SDL_GetError(), -2); } DEBUGPRINT(("SDL screen granted: %d x %d @ %d\n", sdlscrn->w, sdlscrn->h, sdlscrn->format->BitsPerPixel)); if (!bInFullScreen) { /* re-embed the new Hatari SDL window */ Control_ReparentWindow(width, height, bInFullScreen); } Avi_SetSurface(sdlscrn); bRGBTableInSync = false; return true; } /** * Initialize ST/STE screen resolution. */ static void Screen_SetSTResolution(bool bForceChange) { int Width, Height, nZoom, SBarHeight, maxW, maxH; bool bDoubleLowRes = false; nBorderPixelsTop = nBorderPixelsBottom = 0; nBorderPixelsLeft = nBorderPixelsRight = 0; nScreenZoomX = 1; nScreenZoomY = 1; if (STRes == ST_LOW_RES) { Width = 320; Height = 200; nZoom = 1; } else /* else use 640x400, also for med-rez */ { Width = 640; Height = 400; nZoom = 2; } /* Statusbar height for doubled screen size */ SBarHeight = Statusbar_GetHeightForSize(640, 400); Resolution_GetLimits(&maxW, &maxH, ConfigureParams.Screen.bKeepResolution); /* Zoom if necessary, factors used for scaling mouse motions */ if (STRes == ST_LOW_RES && 2*Width <= maxW && 2*Height+SBarHeight <= maxH) { nZoom = 2; Width *= 2; Height *= 2; nScreenZoomX = 2; nScreenZoomY = 2; bDoubleLowRes = true; } else if (STRes == ST_MEDIUM_RES) { /* med-rez conversion functions want always * to double vertically, they don't support * skipping that (only leaving doubled lines * black for the TV mode). */ nScreenZoomX = 1; nScreenZoomY = 2; } /* Adjust width/height for overscan borders, if mono or VDI we have no overscan */ if (ConfigureParams.Screen.bAllowOverscan && !bUseHighRes) { int leftX = maxW - Width; int leftY = maxH - (Height + Statusbar_GetHeightForSize(Width, Height)); Screen_SetBorderPixels(leftX/nZoom, leftY/nZoom); DEBUGPRINT(("resolution limit:\n\t%d x %d\nlimited resolution:\n\t", maxW, maxH)); DEBUGPRINT(("%d * (%d + %d + %d) x (%d + %d + %d)\n", nZoom, nBorderPixelsLeft, Width/nZoom, nBorderPixelsRight, nBorderPixelsTop, Height/nZoom, nBorderPixelsBottom)); Width += (nBorderPixelsRight + nBorderPixelsLeft)*nZoom; Height += (nBorderPixelsTop + nBorderPixelsBottom)*nZoom; DEBUGPRINT(("\t= %d x %d (+ statusbar)\n", Width, Height)); } Screen_SetSTScreenOffsets(); Height += Statusbar_SetHeight(Width, Height); PCScreenOffsetX = PCScreenOffsetY = 0; if (Screen_SetSDLVideoSize(Width, Height, bForceChange)) { Statusbar_Init(sdlscrn); /* screen area without the statusbar */ STScreenRect.x = 0; STScreenRect.y = 0; STScreenRect.w = sdlscrn->w; STScreenRect.h = sdlscrn->h - Statusbar_GetHeight(); } if (!bRGBTableInSync) { Screen_SetupRGBTable(); /* Create color conversion table */ bRGBTableInSync = true; } /* Set drawing functions */ Screen_SetDrawFunctions(sdlscrn->format->BitsPerPixel, bDoubleLowRes); Screen_SetFullUpdate(); /* Cause full update of screen */ } /** * Change resolution, according to the machine and display type * that we're currently emulating. */ static void Screen_ChangeResolution(bool bForceChange) { if (bUseVDIRes) { Screen_SetGenConvSize(VDIWidth, VDIHeight, bForceChange); } else if (Config_IsMachineFalcon()) { Videl_ScreenModeChanged(bForceChange); } else if (Config_IsMachineTT()) { int width, height, bpp; Video_GetTTRes(&width, &height, &bpp); Screen_SetGenConvSize(width, height, bForceChange); } else if (bUseHighRes) { Screen_SetGenConvSize(640, 400, bForceChange); } else { Screen_SetSTResolution(bForceChange); } SDL_SetRelativeMouseMode(bInFullScreen || bGrabMouse); } /** * Change the resolution - but only if it was already initialized before */ void Screen_ModeChanged(bool bForceChange) { if (sdlscrn) /* Do it only if we're already up and running */ { Screen_ChangeResolution(bForceChange); } } /*-----------------------------------------------------------------------*/ /** * Init Screen bitmap and buffers/tables needed for ST to PC screen conversion */ void Screen_Init(void) { SDL_Surface *pIconSurf; char sIconFileName[FILENAME_MAX]; /* Clear frame buffer structures and set current pointer */ memset(&FrameBuffer, 0, sizeof(FRAMEBUFFER)); /* Allocate screen check workspace. */ FrameBuffer.pSTScreen = malloc(MAX_VDI_BYTES); FrameBuffer.pSTScreenCopy = malloc(MAX_VDI_BYTES); if (!FrameBuffer.pSTScreen || !FrameBuffer.pSTScreenCopy) { Main_ErrorExit("Failed to allocate frame buffer memory", NULL, -1); } pFrameBuffer = &FrameBuffer; /* TODO: Replace pFrameBuffer with FrameBuffer everywhere */ /* Set initial window resolution */ bInFullScreen = ConfigureParams.Screen.bFullScreen; Screen_ChangeResolution(false); ScreenDrawFunctionsNormal[ST_HIGH_RES] = Screen_ConvertHighRes; Video_SetScreenRasters(); /* Set rasters ready for first screen */ /* Load and set icon */ File_MakePathBuf(sIconFileName, sizeof(sIconFileName), Paths_GetDataDir(), "hatari-icon", "bmp"); pIconSurf = SDL_LoadBMP(sIconFileName); if (pIconSurf) { SDL_SetColorKey(pIconSurf, SDL_TRUE, SDL_MapRGB(pIconSurf->format, 255, 255, 255)); SDL_SetWindowIcon(sdlWindow, pIconSurf); SDL_FreeSurface(pIconSurf); } /* Configure some SDL stuff: */ Main_ShowCursor(false); } /*-----------------------------------------------------------------------*/ /** * Free screen bitmap and allocated resources */ void Screen_UnInit(void) { /* Free memory used for copies */ free(FrameBuffer.pSTScreen); free(FrameBuffer.pSTScreenCopy); FrameBuffer.pSTScreen = NULL; FrameBuffer.pSTScreenCopy = NULL; Screen_FreeSDL2Resources(); if (sdlWindow) { SDL_DestroyWindow(sdlWindow); sdlWindow = NULL; } } /*-----------------------------------------------------------------------*/ /** * Reset screen */ void Screen_Reset(void) { /* On re-boot, always correct ST resolution for monitor, eg Colour/Mono */ if (bUseVDIRes) { STRes = VDIRes; } else { if (bUseHighRes) { STRes = ST_HIGH_RES; TTRes = TT_HIGH_RES; } else { STRes = ST_LOW_RES; TTRes = TT_MEDIUM_RES; } } /* Cause full update */ Screen_ModeChanged(false); } /*-----------------------------------------------------------------------*/ /** * Set flags so screen will be TOTALLY re-drawn (clears whole of full-screen) * next time around */ void Screen_SetFullUpdate(void) { /* Update frame buffers */ FrameBuffer.bFullUpdate = true; } /*-----------------------------------------------------------------------*/ /** * Clear Window display memory */ static void Screen_ClearScreen(void) { SDL_FillRect(sdlscrn, &STScreenRect, SDL_MapRGB(sdlscrn->format, 0, 0, 0)); } /*-----------------------------------------------------------------------*/ /** * Force screen redraw. Does the right thing regardless of whether * we're in ST/STe, Falcon or TT mode. Needed when switching modes * while emulation is paused. */ static void Screen_Refresh(void) { if (bUseVDIRes) { Screen_GenDraw(VideoBase, VDIWidth, VDIHeight, VDIPlanes, VDIWidth * VDIPlanes / 16, 0, 0, 0, 0); } else if (Config_IsMachineFalcon()) { VIDEL_renderScreen(); } else if (Config_IsMachineTT()) { Video_RenderTTScreen(); } else { Screen_DrawFrame(true); } } /*-----------------------------------------------------------------------*/ /** * Enter Full screen mode */ void Screen_EnterFullScreen(void) { bool bWasRunning; if (!bInFullScreen) { /* Hold things... */ bWasRunning = Main_PauseEmulation(false); bInFullScreen = true; if (Screen_UseGenConvScreen()) { Screen_SetGenConvSize(genconv_width_req, genconv_height_req, true); /* force screen redraw */ Screen_GenConvUpdate(NULL, true); } else { Screen_SetSTResolution(true); Screen_ClearScreen(); /* Black out screen bitmap as will be invalid when return */ } if (!ConfigureParams.Screen.bKeepResolution) { /* Give monitor time to change to new resolution */ SDL_Delay(20); } if (bWasRunning) { /* And off we go... */ Main_UnPauseEmulation(); } else { Screen_Refresh(); } SDL_SetRelativeMouseMode(true); /* Grab mouse pointer in fullscreen */ } } /*-----------------------------------------------------------------------*/ /** * Return from Full screen mode back to a window */ void Screen_ReturnFromFullScreen(void) { bool bWasRunning; if (bInFullScreen) { /* Hold things... */ bWasRunning = Main_PauseEmulation(false); bInFullScreen = false; if (Screen_UseGenConvScreen()) { Screen_SetGenConvSize(genconv_width_req, genconv_height_req, true); /* force screen redraw */ Screen_GenConvUpdate(NULL, true); } else { Screen_SetSTResolution(true); } if (!ConfigureParams.Screen.bKeepResolution) { /* Give monitor time to switch resolution */ SDL_Delay(20); } if (bWasRunning) { /* And off we go... */ Main_UnPauseEmulation(); } else { Screen_Refresh(); } if (!bGrabMouse) { /* Un-grab mouse pointer in windowed mode */ SDL_SetRelativeMouseMode(false); } } } /*-----------------------------------------------------------------------*/ /** * Have we changed between low/med/high res? */ static void Screen_DidResolutionChange(int new_res) { if (new_res != STRes) { STRes = new_res; Screen_ModeChanged(false); } else { /* Did change overscan mode? Causes full update */ if (pFrameBuffer->VerticalOverscanCopy != VerticalOverscan) pFrameBuffer->bFullUpdate = true; } } /** * Compare current resolution on line with previous, and set 'UpdateLine' accordingly * Return if swap between low/medium resolution */ static bool Screen_CompareResolution(int y, int *pUpdateLine, int oldres) { /* Check if wrote to resolution register */ if (HBLPaletteMasks[y]&PALETTEMASK_RESOLUTION) /* See 'Intercept_ShifterMode_WriteByte' */ { int newres = (HBLPaletteMasks[y]>>16)&ST_MEDIUM_RES_BIT; /* Did resolution change? */ if (newres != (int)((pFrameBuffer->HBLPaletteMasks[y]>>16)&ST_MEDIUM_RES_BIT)) *pUpdateLine |= PALETTEMASK_UPDATERES; else *pUpdateLine &= ~PALETTEMASK_UPDATERES; /* Have used any low/medium res mix? */ return (newres != (oldres&ST_MEDIUM_RES_BIT)); } return false; } /*-----------------------------------------------------------------------*/ /** * Check to see if palette changes cause screen update and keep 'HBLPalette[]' up-to-date */ static void Screen_ComparePalette(int y, int *pUpdateLine) { bool bPaletteChanged = false; int i; /* Did write to palette in this or previous frame? */ if (((HBLPaletteMasks[y]|pFrameBuffer->HBLPaletteMasks[y])&PALETTEMASK_PALETTE)!=0) { /* Check and update ones which changed */ for (i = 0; i < 16; i++) { if (HBLPaletteMasks[y]&(1<HBLPalettes[(y*16)+i]) bPaletteChanged = true; } if (bPaletteChanged) *pUpdateLine |= PALETTEMASK_UPDATEPAL; else *pUpdateLine &= ~PALETTEMASK_UPDATEPAL; } } /*-----------------------------------------------------------------------*/ /** * Check for differences in Palette and Resolution from Mask table and update * and store off which lines need updating and create full-screen palette. * (It is very important for these routines to check for colour changes with * the previous screen so only the very minimum parts are updated). * Return new STRes value. */ static int Screen_ComparePaletteMask(int res) { bool bLowMedMix = false; int LineUpdate = 0; int y; /* Set for monochrome? */ if (bUseHighRes) { VerticalOverscan = V_OVERSCAN_NONE; /* Just copy mono colors */ if (HBLPalettes[0] & 0x777) { HBLPalettes[0] = 0x777; HBLPalettes[1] = 0x000; } else { HBLPalettes[0] = 0x000; HBLPalettes[1] = 0x777; } /* Colors changed? */ if (HBLPalettes[0] != PrevHBLPalette[0]) pFrameBuffer->bFullUpdate = true; /* Set bit to flag 'full update' */ if (pFrameBuffer->bFullUpdate) ScrUpdateFlag = PALETTEMASK_UPDATEFULL; else ScrUpdateFlag = 0x00000000; /* Force to standard hi-resolution screen, without overscan */ res = ST_HIGH_RES; } else /* Full colour */ { /* Get resolution */ //res = (HBLPaletteMasks[0]>>16)&ST_RES_MASK; /* [NP] keep only low/med bit (could be hires in case of overscan on the 1st line) */ res = (HBLPaletteMasks[0]>>16)&ST_MEDIUM_RES_BIT; /* Do all lines - first is tagged as full-update */ for (y = 0; y < NUM_VISIBLE_LINES; y++) { /* Find any resolution/palette change and update palette/mask buffer */ /* ( LineUpdate has top two bits set to say if line needs updating due to palette or resolution change ) */ bLowMedMix |= Screen_CompareResolution(y, &LineUpdate, res); Screen_ComparePalette(y,&LineUpdate); HBLPaletteMasks[y] = (HBLPaletteMasks[y]&(~PALETTEMASK_UPDATEMASK)) | LineUpdate; /* Copy palette and mask for next frame */ memcpy(&pFrameBuffer->HBLPalettes[y*16],HBLPalette,sizeof(short int)*16); pFrameBuffer->HBLPaletteMasks[y] = HBLPaletteMasks[y]; } /* Did mix/have medium resolution? */ if (bLowMedMix || (res & ST_MEDIUM_RES_BIT)) res = ST_MEDIUM_RES; } /* Copy old palette for compare */ memcpy(PrevHBLPalette, HBLPalettes, sizeof(Uint16)*16); return res; } /*-----------------------------------------------------------------------*/ /** * Update Palette Mask to show 'full-update' required. This is usually done after a resolution change * or when going between a Window and full-screen display */ static void Screen_SetFullUpdateMask(void) { int y; for (y = 0; y < NUM_VISIBLE_LINES; y++) HBLPaletteMasks[y] |= PALETTEMASK_UPDATEFULL; } /*-----------------------------------------------------------------------*/ /** * Set details for ST screen conversion. */ static void Screen_SetConvertDetails(void) { pSTScreen = pFrameBuffer->pSTScreen; /* Source in ST memory */ pSTScreenCopy = pFrameBuffer->pSTScreenCopy; /* Previous ST screen */ pPCScreenDest = sdlscrn->pixels; /* Destination PC screen */ PCScreenBytesPerLine = sdlscrn->pitch; /* Bytes per line */ /* Center to available framebuffer */ pPCScreenDest += PCScreenOffsetY * PCScreenBytesPerLine + PCScreenOffsetX * (sdlscrn->format->BitsPerPixel/8); pHBLPalettes = pFrameBuffer->HBLPalettes; /* HBL palettes pointer */ /* Not in TV-Mode? Then double up on Y: */ bScrDoubleY = !(ConfigureParams.Screen.nMonitorType == MONITOR_TYPE_TV); if (ConfigureParams.Screen.bAllowOverscan) /* Use borders? */ { /* Always draw to WHOLE screen including ALL borders */ STScreenLeftSkipBytes = 0; /* Number of bytes to skip on ST screen for left (border) */ if (bUseHighRes) { pFrameBuffer->VerticalOverscanCopy = VerticalOverscan = V_OVERSCAN_NONE; STScreenStartHorizLine = 0; STScreenEndHorizLine = 400; } else { STScreenWidthBytes = SCREENBYTES_LINE; /* Number of horizontal bytes in our ST screen */ STScreenStartHorizLine = OVERSCAN_TOP - nBorderPixelsTop; STScreenEndHorizLine = OVERSCAN_TOP + 200 + nBorderPixelsBottom; } } else { /* Only draw main area and centre on Y */ STScreenLeftSkipBytes = SCREENBYTES_LEFT; STScreenWidthBytes = SCREENBYTES_MIDDLE; STScreenStartHorizLine = OVERSCAN_TOP; STScreenEndHorizLine = OVERSCAN_TOP + (bUseHighRes ? 400 : 200); } } /*-----------------------------------------------------------------------*/ /** * Lock full-screen for drawing */ bool Screen_Lock(void) { if (SDL_MUSTLOCK(sdlscrn)) { if (SDL_LockSurface(sdlscrn)) { Screen_ReturnFromFullScreen(); /* All OK? If not need to jump back to a window */ return false; } } return true; } /*-----------------------------------------------------------------------*/ /** * UnLock full-screen */ void Screen_UnLock(void) { if ( SDL_MUSTLOCK(sdlscrn) ) SDL_UnlockSurface(sdlscrn); } /*-----------------------------------------------------------------------*/ /** * Blit our converted ST screen to window/full-screen */ static void Screen_Blit(SDL_Rect *sbar_rect) { unsigned char *pTmpScreen; int count = 1; SDL_Rect rects[2]; rects[0] = STScreenRect; if (sbar_rect) { rects[1] = *sbar_rect; count = 2; } Screen_UpdateRects(sdlscrn, count, rects); /* Swap copy/raster buffers in screen. */ pTmpScreen = pFrameBuffer->pSTScreenCopy; pFrameBuffer->pSTScreenCopy = pFrameBuffer->pSTScreen; pFrameBuffer->pSTScreen = pTmpScreen; } /*-----------------------------------------------------------------------*/ /** * Draw ST screen to window/full-screen framebuffer * @param bForceFlip Force screen update, even if contents did not change * @return true if screen contents changed */ static bool Screen_DrawFrame(bool bForceFlip) { int new_res; void (*pDrawFunction)(void); static bool bPrevFrameWasSpec512 = false; SDL_Rect *sbar_rect; assert(!bUseVDIRes); /* Scan palette/resolution masks for each line and build up palette/difference tables */ new_res = Screen_ComparePaletteMask(STRes); /* Did we change resolution this frame - allocate new screen if did so */ Screen_DidResolutionChange(new_res); /* Is need full-update, tag as such */ if (pFrameBuffer->bFullUpdate) Screen_SetFullUpdateMask(); /* restore area potentially left under overlay led * and saved by Statusbar_OverlayBackup() */ Statusbar_OverlayRestore(sdlscrn); /* Lock screen for direct screen surface format writes */ if (ConfigureParams.Screen.DisableVideo || !Screen_Lock()) { return false; } bScreenContentsChanged = false; /* Did change (ie needs blit?) */ /* Set details */ Screen_SetConvertDetails(); /* Clear screen on full update to clear out borders and also interleaved lines */ if (pFrameBuffer->bFullUpdate) Screen_ClearScreen(); /* Call drawing for full-screen */ pDrawFunction = ScreenDrawFunctionsNormal[STRes]; /* Check if is Spec512 image */ if (Spec512_IsImage()) { bPrevFrameWasSpec512 = true; /* What mode were we in? Keep to 320xH or 640xH */ if (pDrawFunction==ConvertLowRes_320x32Bit) pDrawFunction = ConvertLowRes_320x32Bit_Spec; else if (pDrawFunction==ConvertLowRes_640x32Bit) pDrawFunction = ConvertLowRes_640x32Bit_Spec; else if (pDrawFunction==ConvertMediumRes_640x32Bit) pDrawFunction = ConvertMediumRes_640x32Bit_Spec; } else if (bPrevFrameWasSpec512) { /* If we switch back from Spec512 mode to normal * screen rendering, we have to make sure to do * a full update of the screen. */ Screen_SetFullUpdateMask(); bPrevFrameWasSpec512 = false; } /* Store palette for screenshots * pDrawFunction may override this if it calls Screen_GenConvert */ ConvertPalette = STRGBPalette; ConvertPaletteSize = (STRes == ST_MEDIUM_RES) ? 4 : 16; if (pDrawFunction) CALL_VAR(pDrawFunction); /* Unlock screen */ Screen_UnLock(); /* draw overlay led(s) or statusbar after unlock */ Statusbar_OverlayBackup(sdlscrn); sbar_rect = Statusbar_Update(sdlscrn, false); /* Clear flags, remember type of overscan as if change need screen full update */ pFrameBuffer->bFullUpdate = false; pFrameBuffer->VerticalOverscanCopy = VerticalOverscan; /* And show to user */ if (bScreenContentsChanged || bForceFlip || sbar_rect) { Screen_Blit(sbar_rect); } return bScreenContentsChanged; } /*-----------------------------------------------------------------------*/ /** * Draw ST screen to window/full-screen */ bool Screen_Draw(void) { if (bQuitProgram) { return false; } /* And draw (if screen contents changed) */ return Screen_DrawFrame(false); } /** * This is used to set the size of the SDL screen * when we're using the generic conversion functions. */ void Screen_SetGenConvSize(int width, int height, bool bForceChange) { const bool keep = ConfigureParams.Screen.bKeepResolution; int screenwidth, screenheight, maxw, maxh; int scalex, scaley, sbarheight; /* constrain size request to user's desktop size */ Resolution_GetLimits(&maxw, &maxh, keep); nScreenZoomX = nScreenZoomY = 1; if (ConfigureParams.Screen.bAspectCorrect) { /* Falcon (and TT) pixel scaling factors seem to 2^x * (quarter/half pixel, interlace/double line), so * do aspect correction as 2's exponent. */ while (nScreenZoomX*width < height && 2*nScreenZoomX*width < maxw) { nScreenZoomX *= 2; } while (2*nScreenZoomY*height < width && 2*nScreenZoomY*height < maxh) { nScreenZoomY *= 2; } if (nScreenZoomX*nScreenZoomY > 2) { Log_Printf(LOG_INFO, "Strange screen size %dx%d -> aspect corrected by %dx%d!\n", width, height, nScreenZoomX, nScreenZoomY); } } /* then select scale as close to target size as possible * without having larger size than it */ scalex = maxw/(nScreenZoomX*width); scaley = maxh/(nScreenZoomY*height); if (scalex > 1 && scaley > 1) { /* keep aspect ratio */ if (scalex < scaley) { nScreenZoomX *= scalex; nScreenZoomY *= scalex; } else { nScreenZoomX *= scaley; nScreenZoomY *= scaley; } } genconv_width_req = width; genconv_height_req = height; width *= nScreenZoomX; height *= nScreenZoomY; /* get statusbar size for this screen size */ sbarheight = Statusbar_GetHeightForSize(width, height); screenheight = height + sbarheight; screenwidth = width; /* re-calculate statusbar height for this resolution */ sbarheight = Statusbar_SetHeight(screenwidth, screenheight-sbarheight); /* screen area without the statusbar */ STScreenRect.x = STScreenRect.y = 0; STScreenRect.w = screenwidth; STScreenRect.h = screenheight - sbarheight; if (!Screen_SetSDLVideoSize(screenwidth, screenheight, bForceChange)) { /* same host screen size despite Atari resolution change, * -> no time consuming host video mode change needed */ if (screenwidth > width || screenheight > height+sbarheight) { /* Atari screen smaller than host -> clear screen */ Screen_ClearScreen(); /* re-calculate variables in case height + statusbar height * don't anymore match SDL surface size (there's an assert * for that) */ Statusbar_Init(sdlscrn); } return; } // In case surface format changed, remap the native palette Screen_RemapPalette(); // redraw statusbar Statusbar_Init(sdlscrn); DEBUGPRINT(("Surface Pitch = %d, width = %d, height = %d\n", sdlscrn->pitch, sdlscrn->w, sdlscrn->h)); DEBUGPRINT(("Must Lock? %s\n", SDL_MUSTLOCK(sdlscrn) ? "YES" : "NO")); DEBUGPRINT(("Pixel format:bitspp=%d, tmasks r=%04x g=%04x b=%04x" ", tshifts r=%d g=%d b=%d" ", tlosses r=%d g=%d b=%d\n", sdlscrn->format->BitsPerPixel, sdlscrn->format->Rmask, sdlscrn->format->Gmask, sdlscrn->format->Bmask, sdlscrn->format->Rshift, sdlscrn->format->Gshift, sdlscrn->format->Bshift, sdlscrn->format->Rloss, sdlscrn->format->Gloss, sdlscrn->format->Bloss)); Main_WarpMouse(sdlscrn->w/2,sdlscrn->h/2, false); } void Screen_GenConvUpdate(SDL_Rect *extra, bool forced) { SDL_Rect rects[2]; int count = 1; /* Don't update anything on screen if video output is disabled */ if ( ConfigureParams.Screen.DisableVideo ) return; rects[0] = STScreenRect; if (extra) { rects[1] = *extra; count = 2; } Screen_UpdateRects(sdlscrn, count, rects); } Uint32 Screen_GetGenConvWidth(void) { return STScreenRect.w; } Uint32 Screen_GetGenConvHeight(void) { return STScreenRect.h; } /* -------------- screen conversion routines -------------------------------- Screen conversion routines. We have a number of routines to convert ST screen to PC format. We split these into Low, Medium and High each with 8/16-bit versions. To gain extra speed, as almost half of the processing time can be spent in these routines, we check for any changes from the previously displayed frame. AdjustLinePaletteRemap() sets a flag to tell the routines if we need to totally update a line (ie full update, or palette/res change) or if we just can do a difference check. We convert each screen 16 pixels at a time by use of a couple of look-up tables. These tables convert from 2-plane format to bbp and then we can add two of these together to get 4-planes. This keeps the tables small and thus improves speed. We then look these bbp values up as an RGB/Index value to copy to the screen. */ /*-----------------------------------------------------------------------*/ /** * Update the STRGBPalette[] array with current colours for this raster line. * * Return 'ScrUpdateFlag', 0x80000000=Full update, 0x40000000=Update * as palette changed */ static int AdjustLinePaletteRemap(int y) { #if SDL_BYTEORDER == SDL_BIG_ENDIAN static const int endiantable[16] = {0,2,1,3,8,10,9,11,4,6,5,7,12,14,13,15}; #endif Uint16 *actHBLPal; int i; /* Copy palette and convert to RGB in display format */ actHBLPal = pHBLPalettes + (y<<4); /* offset in palette */ for (i=0; i<16; i++) { #if SDL_BYTEORDER == SDL_BIG_ENDIAN STRGBPalette[endiantable[i]] = ST2RGB[*actHBLPal++]; #else STRGBPalette[i] = ST2RGB[*actHBLPal++]; #endif } ScrUpdateFlag = HBLPaletteMasks[y]; return ScrUpdateFlag; } /*-----------------------------------------------------------------------*/ /** * Run updates to palette(STRGBPalette[]) until get to screen line * we are to convert from */ static void Convert_StartFrame(void) { int y = 0; /* Get #lines before conversion starts */ int lines = STScreenStartHorizLine; while (lines--) AdjustLinePaletteRemap(y++); /* Update palette */ } /*-----------------------------------------------------------------------*/ /** * Copy given line (address) of given length, to line below it * as-is, or halve its intensity, depending on bScrDoubleY. * * Source line is already in host format, so we don't need to * care about endianness. * * Return address to next line after the copy */ static Uint32* Double_ScreenLine32(Uint32 *line, int size) { SDL_PixelFormat *fmt; int fmt_size = size/4; Uint32 *next; Uint32 mask; next = line + fmt_size; /* copy as-is */ if (bScrDoubleY) { memcpy(next, line, size); return next + fmt_size; } /* TV-mode -- halve the intensity while copying */ fmt = sdlscrn->format; mask = ((fmt->Rmask >> 1) & fmt->Rmask) | ((fmt->Gmask >> 1) & fmt->Gmask) | ((fmt->Bmask >> 1) & fmt->Bmask); do { *next++ = (*line++ >> 1) & mask; } while (--fmt_size); return next; } /* lookup tables and conversion macros */ #include "convert/macros.h" /* Conversion routines */ #include "convert/low320x32.c" /* LowRes To 320xH x 32-bit color */ #include "convert/low640x32.c" /* LowRes To 640xH x 32-bit color */ #include "convert/med640x32.c" /* MediumRes To 640xH x 32-bit color */ #include "convert/low320x32_spec.c" /* LowRes Spectrum 512 To 320xH x 32-bit color */ #include "convert/low640x32_spec.c" /* LowRes Spectrum 512 To 640xH x 32-bit color */ #include "convert/med640x32_spec.c" /* MediumRes Spectrum 512 To 640xH x 32-bit color */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/screenConvert.c000066400000000000000000000523541504763705000243250ustar00rootroot00000000000000/* Hatari - screenConvert.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #include #include "main.h" #include "configuration.h" #include "log.h" #include "ioMem.h" #include "memorySnapShot.h" #include "screen.h" #include "screenConvert.h" #include "statusbar.h" #include "stMemory.h" #include "video.h" struct screen_zoom_s { Uint16 zoomwidth; Uint16 prev_scrwidth; Uint16 zoomheight; Uint16 prev_scrheight; int *zoomxtable; int *zoomytable; }; static struct screen_zoom_s screen_zoom; static bool bTTSampleHold = false; /* TT special video mode */ static int nSampleHoldIdx; static uint32_t nScreenBaseAddr; /* address of screen in STRam */ int ConvertW = 0; int ConvertH = 0; int ConvertBPP = 1; int ConvertNextLine = 0; /* TOS palette (bpp < 16) to SDL color mapping */ static struct { SDL_Color standard[256]; Uint32 native[256]; } palette; void Screen_SetPaletteColor(Uint8 idx, Uint8 red, Uint8 green, Uint8 blue) { // set the SDL standard RGB palette settings palette.standard[idx].r = red; palette.standard[idx].g = green; palette.standard[idx].b = blue; // convert the color to native palette.native[idx] = SDL_MapRGB(sdlscrn->format, red, green, blue); } SDL_Color Screen_GetPaletteColor(Uint8 idx) { return palette.standard[idx]; } void Screen_RemapPalette(void) { int i; Uint32 *native = palette.native; SDL_Color *standard = palette.standard; SDL_PixelFormat *fmt = sdlscrn->format; for(i = 0; i < 256; i++, native++, standard++) { *native = SDL_MapRGB(fmt, standard->r, standard->g, standard->b); } } void ScreenConv_MemorySnapShot_Capture(bool bSave) { MemorySnapShot_Store(palette.standard, sizeof(palette.standard)); if (!bSave) Screen_RemapPalette(); } static void Screen_memset_uint32(Uint32 *addr, Uint32 color, int count) { while (count-- > 0) { *addr++ = color; } } static inline Uint32 idx2pal(Uint8 idx) { if (unlikely(bTTSampleHold)) { if (idx == 0) return palette.native[nSampleHoldIdx]; nSampleHoldIdx = idx; } return palette.native[idx]; } /** * Performs conversion from the TOS's bitplane word order (big endian) data * into the native 32-bit chunky pixels. */ static void Screen_BitplaneToChunky32(Uint16 *atariBitplaneData, Uint16 bpp, Uint32 *hvram) { Uint32 a, b, c, d, x; if (bpp >= 4) { d = *(Uint32 *)&atariBitplaneData[0]; c = *(Uint32 *)&atariBitplaneData[2]; if (bpp == 4) { a = b = 0; } else { b = *(Uint32 *)&atariBitplaneData[4]; a = *(Uint32 *)&atariBitplaneData[6]; } x = a; a = (a & 0xf0f0f0f0) | ((c & 0xf0f0f0f0) >> 4); c = ((x & 0x0f0f0f0f) << 4) | (c & 0x0f0f0f0f); } else { a = b = c = 0; if (bpp == 2) { d = *(Uint32 *)&atariBitplaneData[0]; } else { #if SDL_BYTEORDER == SDL_BIG_ENDIAN d = atariBitplaneData[0]<<16; #else d = atariBitplaneData[0]; #endif } } x = b; b = (b & 0xf0f0f0f0) | ((d & 0xf0f0f0f0) >> 4); d = ((x & 0x0f0f0f0f) << 4) | (d & 0x0f0f0f0f); x = a; a = (a & 0xcccccccc) | ((b & 0xcccccccc) >> 2); b = ((x & 0x33333333) << 2) | (b & 0x33333333); x = c; c = (c & 0xcccccccc) | ((d & 0xcccccccc) >> 2); d = ((x & 0x33333333) << 2) | (d & 0x33333333); #if SDL_BYTEORDER == SDL_BIG_ENDIAN a = (a & 0x5555aaaa) | ((a & 0x00005555) << 17) | ((a & 0xaaaa0000) >> 17); b = (b & 0x5555aaaa) | ((b & 0x00005555) << 17) | ((b & 0xaaaa0000) >> 17); c = (c & 0x5555aaaa) | ((c & 0x00005555) << 17) | ((c & 0xaaaa0000) >> 17); d = (d & 0x5555aaaa) | ((d & 0x00005555) << 17) | ((d & 0xaaaa0000) >> 17); *hvram++ = idx2pal(a >> 8); *hvram++ = idx2pal(a >> 24); *hvram++ = idx2pal(b >> 8); *hvram++ = idx2pal(b >> 24); *hvram++ = idx2pal(c >> 8); *hvram++ = idx2pal(c >> 24); *hvram++ = idx2pal(d >> 8); *hvram++ = idx2pal(d >> 24); *hvram++ = idx2pal(a); *hvram++ = idx2pal(a >> 16); *hvram++ = idx2pal(b); *hvram++ = idx2pal(b >> 16); *hvram++ = idx2pal(c); *hvram++ = idx2pal(c >> 16); *hvram++ = idx2pal(d); *hvram++ = idx2pal(d >> 16); #else a = (a & 0xaaaa5555) | ((a & 0x0000aaaa) << 15) | ((a & 0x55550000) >> 15); b = (b & 0xaaaa5555) | ((b & 0x0000aaaa) << 15) | ((b & 0x55550000) >> 15); c = (c & 0xaaaa5555) | ((c & 0x0000aaaa) << 15) | ((c & 0x55550000) >> 15); d = (d & 0xaaaa5555) | ((d & 0x0000aaaa) << 15) | ((d & 0x55550000) >> 15); *hvram++ = idx2pal(a >> 16); *hvram++ = idx2pal(a); *hvram++ = idx2pal(b >> 16); *hvram++ = idx2pal(b); *hvram++ = idx2pal(c >> 16); *hvram++ = idx2pal(c); *hvram++ = idx2pal(d >> 16); *hvram++ = idx2pal(d); *hvram++ = idx2pal(a >> 24); *hvram++ = idx2pal(a >> 8); *hvram++ = idx2pal(b >> 24); *hvram++ = idx2pal(b >> 8); *hvram++ = idx2pal(c >> 24); *hvram++ = idx2pal(c >> 8); *hvram++ = idx2pal(d >> 24); *hvram++ = idx2pal(d >> 8); #endif } static inline Uint32 *ScreenConv_BitplaneLineTo32bpp(Uint16 *fvram_column, Uint32 *hvram_column, int vw, int vbpp, int hscrolloffset) { Uint32 hvram_buf[16]; int i; /* First 16 pixels */ Screen_BitplaneToChunky32(fvram_column, vbpp, hvram_buf); for (i = hscrolloffset; i < 16; i++) { *hvram_column++ = hvram_buf[i]; } fvram_column += vbpp; /* Now the main part of the line */ for (i = 1; i < (vw + 15) >> 4; i++) { Screen_BitplaneToChunky32(fvram_column, vbpp, hvram_column); hvram_column += 16; fvram_column += vbpp; } /* Last pixels of the line for fine scrolling */ if (hscrolloffset) { Screen_BitplaneToChunky32(fvram_column, vbpp, hvram_buf); for (i = 0; i < hscrolloffset; i++) { *hvram_column++ = hvram_buf[i]; } } return hvram_column; } static void ScreenConv_BitplaneTo32bppNoZoom(Uint16 *fvram_line, Uint8 *hvram, int scrwidth, int scrheight, int vw, int vh, int vbpp, int nextline, int hscrolloffset, int leftBorder, int rightBorder, int upperBorder, int lowBorder) { Uint32 *hvram_line = (Uint32 *)hvram; uint32_t nLineEndAddr = nScreenBaseAddr + nextline * 2; int pitch = sdlscrn->pitch >> 2; int h; /* Render the upper border */ for (h = 0; h < upperBorder; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } /* Render the graphical area */ for (h = 0; h < vh; h++) { Uint32 *hvram_column = hvram_line; if (nLineEndAddr > STRamEnd) { Screen_memset_uint32(hvram_line, palette.native[0], pitch); hvram_line += pitch; continue; } nSampleHoldIdx = 0; /* Left border first */ Screen_memset_uint32(hvram_column, palette.native[0], leftBorder); hvram_column += leftBorder; hvram_column = ScreenConv_BitplaneLineTo32bpp(fvram_line, hvram_column, vw, vbpp, hscrolloffset); /* Right border */ Screen_memset_uint32(hvram_column, palette.native[0], rightBorder); nLineEndAddr += nextline * 2; fvram_line += nextline; hvram_line += pitch; } /* Render the lower border */ for (h = 0; h < lowBorder; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } } static void ScreenConv_HiColorTo32bppNoZoom(Uint16 *fvram_line, Uint8 *hvram, int scrwidth, int scrheight, int vw, int vh, int vbpp, int nextline, int leftBorder, int rightBorder, int upperBorder, int lowBorder) { Uint32 *hvram_line = (Uint32 *)hvram; uint32_t nLineEndAddr = nScreenBaseAddr + nextline * 2; int pitch = sdlscrn->pitch >> 2; int h, w; /* Render the upper border */ for (h = 0; h < upperBorder; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } /* Render the graphical area */ for (h = 0; h < vh; h++) { Uint16 *fvram_column = fvram_line; Uint32 *hvram_column = hvram_line; if (nLineEndAddr > STRamEnd) { Screen_memset_uint32(hvram_line, palette.native[0], pitch); hvram_line += pitch; continue; } /* Left border first */ Screen_memset_uint32(hvram_column, palette.native[0], leftBorder); hvram_column += leftBorder; /* Graphical area */ for (w = 0; w < vw; w++) { Uint16 srcword = SDL_SwapBE16(*fvram_column++); Uint8 r = ((srcword >> 8) & 0xf8) | (srcword >> 13); Uint8 g = ((srcword >> 3) & 0xfc) | ((srcword >> 9) & 0x3); Uint8 b = (srcword << 3) | ((srcword >> 2) & 0x07); *hvram_column ++ = SDL_MapRGB(sdlscrn->format, r, g, b); } /* Right border */ Screen_memset_uint32(hvram_column, palette.native[0], rightBorder); nLineEndAddr += nextline * 2; fvram_line += nextline; hvram_line += pitch; } /* Render the bottom border */ for (h = 0; h < lowBorder; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } } static void Screen_ConvertWithoutZoom(Uint16 *fvram, int vw, int vh, int vbpp, int nextline, int hscrolloffset, int leftBorder, int rightBorder, int upperBorder, int lowerBorder) { Uint8 *hvram = sdlscrn->pixels; Uint16 lowBorderSize, rightBorderSize; int scrwidth, scrheight; unsigned int nBytesPerPixel = sdlscrn->format->BytesPerPixel; int vw_clip, vh_clip; /* Horizontal scroll register set? */ if (hscrolloffset) { /* Yes, so we need to adjust offset to next line: */ nextline += vbpp; ConvertNextLine = nextline * 2; } /* The sample-hold feature exists only on the TT */ bTTSampleHold = (TTSpecialVideoMode & 0x80) != 0; /* Clip to SDL_Surface dimensions */ scrwidth = Screen_GetGenConvWidth(); scrheight = Screen_GetGenConvHeight(); vw_clip = vw + rightBorder + leftBorder; vh_clip = vh + upperBorder + lowerBorder; if (vw_clip > scrwidth) vw_clip = scrwidth; if (vh_clip > scrheight) vh_clip = scrheight; /* If there's not enough space to display the left border, just return */ if (vw_clip < leftBorder) return; /* If there's not enough space for the left border + the graphic area, we clip */ if (vw_clip < vw + leftBorder) { vw = vw_clip - leftBorder; rightBorderSize = 0; } /* if there's not enough space for the left border + the graphic area + the right border, we clip the border */ else if (vw_clip < vw + leftBorder + rightBorder) rightBorderSize = vw_clip - leftBorder - vw; else rightBorderSize = rightBorder; /* If there's not enough space to display the upper border, just return */ if (vh_clip < upperBorder) return; /* If there's not enough space for the upper border + the graphic area, we clip */ if (vh_clip < vh + upperBorder) { vh = vh_clip - upperBorder; lowBorderSize = 0; } /* if there's not enough space for the upper border + the graphic area + the lower border, we clip the border */ else if (vh_clip < vh + upperBorder + lowerBorder) lowBorderSize = vh_clip - upperBorder - vh; else lowBorderSize = lowerBorder; /* Center screen */ hvram += ((scrheight-vh_clip)>>1) * sdlscrn->pitch; hvram += ((scrwidth-vw_clip)>>1) * nBytesPerPixel; scrwidth = leftBorder + vw + rightBorder; /* render the graphic area */ if (vbpp < 16) { /* Bitplanes modes */ ScreenConv_BitplaneTo32bppNoZoom(fvram, hvram, scrwidth, scrheight, vw, vh, vbpp, nextline, hscrolloffset, leftBorder, rightBorderSize, upperBorder, lowBorderSize); } else { /* Falcon TC (High Color) */ ScreenConv_HiColorTo32bppNoZoom(fvram, hvram, scrwidth, scrheight, vw, vh, vbpp, nextline, leftBorder, rightBorderSize, upperBorder, lowBorderSize); } } static void ScreenConv_BitplaneTo32bppZoomed(Uint16 *fvram, Uint8 *hvram, int scrwidth, int scrheight, int vw, int vh, int vbpp, int nextline, int hscrolloffset, int leftBorder, int rightBorder, int upperBorder, int lowerBorder, int coefx, int coefy) { /* One complete 16-pixel aligned planar 2 chunky line */ Uint32 *p2cline = malloc(sizeof(Uint32) * ((vw+15) & ~15)); Uint32 *hvram_line = (Uint32 *)hvram; Uint32 *hvram_column = p2cline; Uint16 *fvram_line; uint32_t nLineEndAddr = nScreenBaseAddr + nextline * 2; unsigned int nBytesPerPixel = sdlscrn->format->BytesPerPixel; int pitch = sdlscrn->pitch >> 2; int cursrcline = -1; int scrIdx = 0; int w, h; /* Render the upper border */ for (h = 0; h < upperBorder * coefy; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } /* Render the graphical area */ for (h = 0; h < scrheight; h++) { fvram_line = fvram + (screen_zoom.zoomytable[scrIdx] * nextline); scrIdx ++; nSampleHoldIdx = 0; /* Recopy the same line ? */ if (screen_zoom.zoomytable[h] == cursrcline) { memcpy(hvram_line, hvram_line - pitch, scrwidth * nBytesPerPixel); } else if (nLineEndAddr > STRamEnd) { Screen_memset_uint32(hvram_line, palette.native[0], pitch); } else { ScreenConv_BitplaneLineTo32bpp(fvram_line, p2cline, vw, vbpp, hscrolloffset); hvram_column = hvram_line; /* Display the Left border */ Screen_memset_uint32(hvram_column, palette.native[0], leftBorder * coefx); hvram_column += leftBorder * coefx; /* Display the Graphical area */ for (w = 0; w < vw * coefx; w++) { hvram_column[w] = p2cline[screen_zoom.zoomxtable[w]]; } hvram_column += vw * coefx; /* Display the Right border */ Screen_memset_uint32(hvram_column, palette.native[0], rightBorder * coefx); nLineEndAddr += nextline * 2; } hvram_line += pitch; cursrcline = screen_zoom.zoomytable[h]; } /* Render the lower border */ for (h = 0; h < lowerBorder * coefy; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } free(p2cline); } static void ScreenConv_HiColorTo32bppZoomed(Uint16 *fvram, Uint8 *hvram, int scrwidth, int scrheight, int vw, int vh, int vbpp, int nextline, int leftBorder, int rightBorder, int upperBorder, int lowerBorder, int coefx, int coefy) { Uint32 *hvram_line = (Uint32 *)hvram; Uint32 *hvram_column = hvram_line; Uint16 *fvram_line; uint32_t nLineEndAddr = nScreenBaseAddr + nextline * 2; unsigned int nBytesPerPixel = sdlscrn->format->BytesPerPixel; int pitch = sdlscrn->pitch >> 2; int cursrcline = -1; int scrIdx = 0; int w, h; /* Render the upper border */ for (h = 0; h < upperBorder * coefy; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } /* Render the graphical area */ for (h = 0; h < scrheight; h++) { Uint16 *fvram_column; fvram_line = fvram + (screen_zoom.zoomytable[scrIdx] * nextline); scrIdx ++; fvram_column = fvram_line; /* Recopy the same line ? */ if (screen_zoom.zoomytable[h] == cursrcline) { memcpy(hvram_line, hvram_line - pitch, scrwidth * nBytesPerPixel); } else if (nLineEndAddr > STRamEnd) { Screen_memset_uint32(hvram_line, palette.native[0], pitch); } else { hvram_column = hvram_line; /* Display the Left border */ Screen_memset_uint32(hvram_column, palette.native[0], leftBorder * coefx); hvram_column += leftBorder * coefx; /* Display the Graphical area */ for (w = 0; w < vw * coefx; w++) { Uint16 srcword; Uint8 r, g, b; srcword = SDL_SwapBE16(fvram_column[screen_zoom.zoomxtable[w]]); r = ((srcword >> 8) & 0xf8) | (srcword >> 13); g = ((srcword >> 3) & 0xfc) | ((srcword >> 9) & 0x3); b = (srcword << 3) | ((srcword >> 2) & 0x07); *hvram_column ++ = SDL_MapRGB(sdlscrn->format, r, g, b); } /* Display the Right border */ Screen_memset_uint32(hvram_column, palette.native[0], rightBorder * coefx); nLineEndAddr += nextline * 2; } hvram_line += pitch; cursrcline = screen_zoom.zoomytable[h]; } /* Render the lower border */ for (h = 0; h < lowerBorder * coefy; h++) { Screen_memset_uint32(hvram_line, palette.native[0], scrwidth); hvram_line += pitch; } } static void Screen_ConvertWithZoom(Uint16 *fvram, int vw, int vh, int vbpp, int nextline, int hscrolloffset, int leftBorder, int rightBorder, int upperBorder, int lowerBorder) { int coefx = 1; int coefy = 1; int scrpitch, scrwidth, scrheight, scrbpp; Uint8 *hvram; int vw_b, vh_b; int i; /* The sample-hold feature exists only on the TT */ bTTSampleHold = (TTSpecialVideoMode & 0x80) != 0; vw_b = vw + leftBorder + rightBorder; vh_b = vh + upperBorder + lowerBorder; /* Host screen infos */ scrpitch = sdlscrn->pitch; scrwidth = Screen_GetGenConvWidth(); scrheight = Screen_GetGenConvHeight(); scrbpp = sdlscrn->format->BytesPerPixel; hvram = sdlscrn->pixels; /* Horizontal scroll register set? */ if (hscrolloffset) { /* Yes, so we need to adjust offset to next line: */ nextline += vbpp; ConvertNextLine = nextline * 2; } /* Integer zoom coef ? */ if (scrwidth >= vw_b && scrheight >= vh_b) { coefx = scrwidth / vw_b; coefy = scrheight / vh_b; scrwidth = vw_b * coefx; scrheight = vh_b * coefy; /* Center screen */ hvram += ((Screen_GetGenConvHeight()-scrheight)>>1)*scrpitch; hvram += ((Screen_GetGenConvWidth()-scrwidth)>>1)*scrbpp; } /* New zoom ? */ if (screen_zoom.zoomwidth != vw_b || scrwidth != screen_zoom.prev_scrwidth) { if (screen_zoom.zoomxtable) { free(screen_zoom.zoomxtable); } screen_zoom.zoomxtable = malloc(sizeof(int)*scrwidth); for (i=0; i 256) ConvertPaletteSize = 256; if (nScreenZoomX * nScreenZoomY != 1) { Screen_ConvertWithZoom(fvram, vw, vh, vbpp, nextline, hscroll, leftBorderSize, rightBorderSize, upperBorderSize, lowerBorderSize); } else { Screen_ConvertWithoutZoom(fvram, vw, vh, vbpp, nextline, hscroll, leftBorderSize, rightBorderSize, upperBorderSize, lowerBorderSize); } } bool Screen_GenDraw(uint32_t vaddr, int vw, int vh, int vbpp, int nextline, int leftBorder, int rightBorder, int upperBorder, int lowerBorder) { int hscrolloffset; if (ConfigureParams.Screen.DisableVideo || !Screen_Lock()) return false; if (Config_IsMachineST()) hscrolloffset = 0; else hscrolloffset = IoMem_ReadByte(0xff8265) & 0x0f; Screen_GenConvert(vaddr, &STRam[vaddr], vw, vh, vbpp, nextline, hscrolloffset, leftBorder, rightBorder, upperBorder, lowerBorder); Screen_UnLock(); Screen_GenConvUpdate(Statusbar_Update(sdlscrn, false), false); return true; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/screenSnapShot.c000066400000000000000000000412471504763705000244430ustar00rootroot00000000000000/* Hatari - screenSnapShot.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Screen Snapshots. */ const char ScreenSnapShot_fileid[] = "Hatari screenSnapShot.c"; #include #include #include #include "main.h" #include "configuration.h" #include "file.h" #include "log.h" #include "paths.h" #include "screen.h" #include "screenConvert.h" #include "screenSnapShot.h" #include "statusbar.h" #include "vdi.h" #include "video.h" #include "stMemory.h" /* after above that bring in config.h */ #if HAVE_LIBPNG # include # include # include "pixel_convert.h" /* inline functions */ #endif static int nScreenShots = 0; /* Number of screen shots saved */ /*-----------------------------------------------------------------------*/ /** * Scan working directory to get the screenshot number */ static void ScreenSnapShot_GetNum(void) { char dummy[5]; int i, num; DIR *workingdir = opendir(Paths_GetScreenShotDir()); struct dirent *file; nScreenShots = 0; if (workingdir == NULL) return; file = readdir(workingdir); while (file != NULL) { if ( strncmp("grab", file->d_name, 4) == 0 ) { /* copy next 4 numbers */ for (i = 0; i < 4; i++) { if (file->d_name[4+i] >= '0' && file->d_name[4+i] <= '9') dummy[i] = file->d_name[4+i]; else break; } dummy[i] = '\0'; /* null terminate */ num = atoi(dummy); if (num > nScreenShots) nScreenShots = num; } /* next file.. */ file = readdir(workingdir); } closedir(workingdir); } #if HAVE_LIBPNG /** * Save given SDL surface as PNG. * Return PNG file size for success, -1 for fail */ static int ScreenSnapShot_SavePNG(const char *filename) { FILE *fp = NULL; int ret, bottom; fp = fopen(filename, "wb"); if (!fp) return -1; if (ConfigureParams.Screen.bCrop) bottom = Statusbar_GetHeight(); else bottom = 0; /* default compression/filter and configured cropping */ ret = ScreenSnapShot_SavePNG_ToFile(sdlscrn, 0, 0, fp, -1, -1, 0, 0, 0, bottom); fclose (fp); return ret; /* >0 if OK, -1 if error */ } /** * Save given SDL surface as PNG in an already opened FILE, eventually cropping some borders. * Return png file size > 0 for success. * This function is also used by avi_record.c to save individual frames as png images. */ int ScreenSnapShot_SavePNG_ToFile(SDL_Surface *surface, int dw, int dh, FILE *fp, int png_compression_level, int png_filter, int CropLeft , int CropRight , int CropTop , int CropBottom ) { bool do_lock; int y, ret; int sw = surface->w - CropLeft - CropRight; int sh = surface->h - CropTop - CropBottom; Uint8 *src_ptr; Uint8 *rowbuf; png_infop info_ptr = NULL; png_structp png_ptr; png_text pngtext; char key[] = "Title"; char text[] = "Hatari screenshot"; off_t start; bool do_palette = true; png_color png_pal[256]; Uint8 palbuf[3]; assert(surface->format->BytesPerPixel == 4); if (!dw) dw = sw; if (!dh) dh = sh; rowbuf = alloca(3 * dw); /* Use current ST palette if all colours in the image belong to it, otherwise RGB */ do_lock = SDL_MUSTLOCK(surface); if (do_lock) SDL_LockSurface(surface); for (y = 0; y < dh; y++) { src_ptr = (Uint8 *)surface->pixels + (CropTop + (y * sh + dh/2) / dh) * surface->pitch + CropLeft * surface->format->BytesPerPixel; if (!PixelConvert_32to8Bits(rowbuf, (Uint32*)src_ptr, dw, surface)) do_palette = false; } if (do_lock) SDL_UnlockSurface(surface); /* Create and initialize the png_struct with error handler functions. */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { return -1; } /* Allocate/initialize the image information data. */ info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { ret = -1; goto png_cleanup; } /* libpng ugliness: Set error handling when not supplying own * error handling functions in the png_create_write_struct() call. */ if (setjmp(png_jmpbuf(png_ptr))) { ret = -1; goto png_cleanup; } /* store current pos in fp (could be != 0 for avi recording) */ start = ftello ( fp ); /* initialize the png structure */ png_init_io(png_ptr, fp); /* image data properties */ png_set_IHDR(png_ptr, info_ptr, dw, dh, 8, do_palette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); if ( png_compression_level >= 0 ) png_set_compression_level ( png_ptr , png_compression_level ); if ( png_filter >= 0 ) png_set_filter ( png_ptr , 0 , png_filter ); /* image info */ pngtext.key = key; pngtext.text = text; pngtext.compression = PNG_TEXT_COMPRESSION_NONE; #ifdef PNG_iTXt_SUPPORTED pngtext.lang = NULL; #endif png_set_text(png_ptr, info_ptr, &pngtext, 1); if (do_palette) { /* Generate palette for PNG */ for (y = 0; y < ConvertPaletteSize; y++) { PixelConvert_32to24Bits(palbuf, (Uint32*)(ConvertPalette+y), 1, surface); png_pal[y].red = palbuf[0]; png_pal[y].green = palbuf[1]; png_pal[y].blue = palbuf[2]; } png_set_PLTE(png_ptr, info_ptr, png_pal, ConvertPaletteSize); } /* write the file header information */ png_write_info(png_ptr, info_ptr); /* write surface data rows one at a time (after cropping if necessary) */ do_lock = SDL_MUSTLOCK(surface); for (y = 0; y < dh; y++) { /* need to lock the surface while accessing it directly */ if (do_lock) SDL_LockSurface(surface); src_ptr = (Uint8 *)surface->pixels + (CropTop + (y * sh + dh/2) / dh) * surface->pitch + CropLeft * surface->format->BytesPerPixel; if (!do_palette) { /* unpack 32-bit RGBA pixels */ PixelConvert_32to24Bits(rowbuf, (Uint32*)src_ptr, dw, surface); } else { /* Reindex back to ST palette * Note that this cannot disambiguate indices if the palette has duplicate colors */ PixelConvert_32to8Bits(rowbuf, (Uint32*)src_ptr, dw, surface); } /* and unlock surface before syscalls */ if (do_lock) SDL_UnlockSurface(surface); png_write_row(png_ptr, rowbuf); } /* write the additional chunks to the PNG file */ png_write_end(png_ptr, info_ptr); ret = (int)( ftello ( fp ) - start ); /* size of the png image */ png_cleanup: if (png_ptr) /* handles info_ptr being NULL */ png_destroy_write_struct(&png_ptr, &info_ptr); return ret; } #endif /** * Determine the internally used screen dimensions, bits per pixel, and whether GenConv is used. */ static void ScreenSnapShot_GetInternalFormat(bool *genconv, int *sw, int *sh, int *bpp, uint32_t* line_size) { *genconv = Config_IsMachineFalcon() || Config_IsMachineTT() || bUseVDIRes; /* genconv here is almost the same as Screen_UseGenConvScreen, but omits bUseHighRes, * which is a hybrid GenConvert that also fills pFrameBuffer. */ *sw = (STRes == ST_LOW_RES) ? 320 : 640; *sh = (STRes == ST_HIGH_RES) ? 400 : 200; *bpp = 4; if (STRes == ST_MEDIUM_RES) *bpp = 2; if (STRes == ST_HIGH_RES) *bpp = 1; if (*genconv) { /* Assume resolution based on GenConvert. */ *bpp = ConvertBPP; *sw = ConvertW; *sh = ConvertH; } *line_size = (uint32_t)(*bpp * ((*sw + 15) & ~15)) / 8; /* size of line data in bytes, rounded up to 16 pixels */ } /** * Save direct video memory dump to NEO file * return 1 for success, -1 for fail */ static int ScreenSnapShot_SaveNEO(const char *filename) { FILE *fp = NULL; int i, res, sw, sh, bpp, offset; bool genconv; SDL_Color col; uint32_t video_base, line_size; uint16_t header[64]; ScreenSnapShot_GetInternalFormat(&genconv, &sw, &sh, &bpp, &line_size); /* Convert BPP to ST resolution number */ if (bpp == 4) res = 0; else if (bpp == 2) res = 1; else if (bpp == 1) res = 2; else /* Preventing NEO screenshots with unexpected BPP or dimensions. */ { /* The NEO header contains only 16 palette entries, so 8bpp would need extra palette information, * and 16bpp true color mode is not supported by existing NEO tools. */ Log_AlertDlg(LOG_ERROR, "The .NEO screenshot format does not support the color depth of the current video mode."); return -1; } if ((res == 0 && sw != 320) || (res < 2 && sh != 200) || (res > 0 && sw != 640) || (res == 2 && sh != 400)) { /* The NEO header contains dimension info, and any width that is a multiple of 16 pixels should be theoretically valid, * but existing NEO tools mostly ignore the dimension fields. */ Log_AlertDlg(LOG_ERROR,"The current video mode has non-standard resolution dimensions, unable to save in .NEO screenshot format"); return -1; } fp = fopen(filename, "wb"); if (!fp) return -1; memset(header, 0, sizeof(header)); header[0] = be_swap16(0); /* Flags field (always 0). */ header[1] = be_swap16(res); /* NEO resolution word is the primary indicator of BPP. */ /* ST Low/Medium resolution stores a palette for each line. Using the centre line's palette. */ if (!genconv && res != 2 && pFrameBuffer) { for (i=0; i<16; i++) header[2+i] = be_swap16(pFrameBuffer->HBLPalettes[i+((OVERSCAN_TOP+sh/2)<<4)]); } else /* High resolution or other GenConvert: use stored GenConvert RGB palette. */ { for (i=0; i<16; i++) { col = Screen_GetPaletteColor(i); header[2+i] = be_swap16( ((col.r >> 5) << 8) | ((col.g >> 5) << 4) | ((col.b >> 5) << 0)); } /* Note that this 24-bit palette is being approximated as a 9-bit ST color palette, * and 256 colors needed for 8bpp cannot be expressed in this header. */ } /* Use filename field to indicate Hatari source and format. */ memcpy(header + 18, "HATARI 0BPP", 12); ((uint8_t*)(header+18))[8] = '0' + bpp; header[29] = be_swap16(sw); /* screen width */ header[30] = be_swap16(sh); /* screen height */ fwrite(header, 1, 128, fp); /* ST modes fill pFrameBuffer->pSTScreen from each scanline, during Video_EndHBL. */ if (!genconv && pFrameBuffer && pFrameBuffer->pSTScreen) { for (i = 0; i < sh; i++) { offset = (res == 2) ? (SCREENBYTES_MONOLINE * i) : (STScreenLineOffset[i+OVERSCAN_TOP] + SCREENBYTES_LEFT); fwrite(pFrameBuffer->pSTScreen + offset, 1, line_size, fp); } } else /* TT/Falcon bypass Video_EndHBL, so pFrameBuffer is unused. * As a fallback we just copy the video data from ST RAM. */ { video_base = Video_GetScreenBaseAddr(); for (i = 0; i < sh; i++) { if ((video_base + line_size) <= STRamEnd) { fwrite(STRam + video_base, 1, line_size, fp); video_base += ConvertNextLine; } else { fclose(fp); return -1; } } } fclose (fp); return 1; /* >0 if OK, -1 if error */ } /** * Save direct video memory dump to XIMG file * return 1 for success, -1 for fail */ static int ScreenSnapShot_SaveXIMG(const char *filename) { FILE *fp = NULL; int i, j, k, sw, sh, bpp, offset; bool genconv; SDL_Color col; uint16_t colst, colr, colg, colb; uint32_t video_base, line_size; uint16_t header_size; uint8_t *scanline; uint16_t header[11]; ScreenSnapShot_GetInternalFormat(&genconv, &sw, &sh, &bpp, &line_size); if (bpp > 8 && bpp != 16) { /* bpp = 24 is a possible format for XIMG but Hatari's screenConvert only supports 16-bit true color. */ Log_AlertDlg(LOG_ERROR,"XIMG screenshot only supports up to 8-bit palette, or 16-bit true color."); return -1; } fp = fopen(filename, "wb"); if (!fp) return -1; /* XIMG header */ header_size = (8 + 3) * 2; /* IMG + XIMG */ if (bpp <= 8) /* palette */ header_size += (3 * 2) * (1 << bpp); memset(header, 0, sizeof(header)); header[0] = be_swap16(1); /* version */ header[1] = be_swap16(header_size/2); header[2] = be_swap16(bpp); /* bitplanes */ header[3] = be_swap16(2); /* pattern length (unused) */ header[4] = be_swap16(0x55); /* pixel width (microns) */ header[5] = be_swap16(0x55); /* pixel height (microns) */ header[6] = be_swap16(sw); /* screen width */ header[7] = be_swap16(sh); /* screen height */ memcpy(header+8,"XIMG",4); header[10] = be_swap16(0); /* XIMG RGB palette format */ fwrite(header, 1, (8 + 3) * 2, fp); /* XIMG RGB format, word triples each 0-1000 */ if (bpp <= 8) { for (i=0; i<(1<HBLPalettes[i+((OVERSCAN_TOP+sh/2)<<4)]; colr = (uint16_t)((((colst >> 8) & 7) * 1000) / 7); colg = (uint16_t)((((colst >> 4) & 7) * 1000) / 7); colb = (uint16_t)((((colst >> 0) & 7) * 1000) / 7); } else /* High resolution or GenConvert palette */ { col = Screen_GetPaletteColor(i); colr = (uint16_t)((col.r * 1000) / 255); colg = (uint16_t)((col.g * 1000) / 255); colb = (uint16_t)((col.b * 1000) / 255); } header[0] = be_swap16(colr); header[1] = be_swap16(colg); header[2] = be_swap16(colb); fwrite(header, 1, 3 * 2, fp); } } /* Image data, no compression is attempted */ for (i = 0; i < sh; i++) { /* Find line of scanline data */ if (!genconv && pFrameBuffer && pFrameBuffer->pSTScreen) { scanline = pFrameBuffer->pSTScreen + ( (sh >= 300) ? (SCREENBYTES_MONOLINE * i) : (STScreenLineOffset[i+OVERSCAN_TOP] + SCREENBYTES_LEFT) ); } else { video_base = Video_GetScreenBaseAddr() + (i * ConvertNextLine); if ((video_base + line_size) <= STRamEnd) { scanline = STRam + video_base; } else { fclose(fp); return -1; } } if (bpp <= 8) { /* de-interleave scanline into XIMG format */ for (j=0; j 0) /* break into <= 254 byte packets */ { k = (j > 254) ? 254 : j; /* bytes in packet */ fputc(0x80,fp); fputc(k,fp); fwrite(scanline,1,k,fp); j -= k; scanline += k; } } else { fclose(fp); return -1; } } fclose (fp); return 1; /* >0 if OK, -1 if error */ } /*-----------------------------------------------------------------------*/ /** * Wrapper for SDL BPM save function * return 1 for success, -1 for fail */ static int ScreenSnapShot_SaveBMP(const char *filename) { if(SDL_SaveBMP_RW(sdlscrn, SDL_RWFromFile(filename, "wb"), 1) < 0) { Log_Printf(LOG_WARN, "SDL_SaveBMP_RW failed: %s", SDL_GetError()); return -1; } return 1; } /*-----------------------------------------------------------------------*/ /** * Save screen shot file with filename like 'grab0000.[png|bmp]', * 'grab0001.[png|bmp]', etc... Whether screen shots are saved as BMP * or PNG depends on Hatari configuration. */ void ScreenSnapShot_SaveScreen(void) { char *szFileName = malloc(FILENAME_MAX); const char *ext, *name, *path; int (*savefn)(const char *); if (!szFileName) return; /* Create our filename */ path = Paths_GetScreenShotDir(); ScreenSnapShot_GetNum(); nScreenShots++; switch(ConfigureParams.Screen.ScreenShotFormat) { #if HAVE_LIBPNG case SCREEN_SNAPSHOT_PNG: savefn = ScreenSnapShot_SavePNG; name = "PNG"; ext = "png"; break; #endif case SCREEN_SNAPSHOT_NEO: savefn = ScreenSnapShot_SaveNEO; name = "NEO"; ext = "neo"; break; case SCREEN_SNAPSHOT_XIMG: savefn = ScreenSnapShot_SaveXIMG; name = "XIMG"; ext = "ximg"; break; case SCREEN_SNAPSHOT_BMP: default: savefn = ScreenSnapShot_SaveBMP; name = "BMP"; ext = "bmp"; break; } sprintf(szFileName, "%s/grab%4.4d.%s", path, nScreenShots, ext); if (savefn(szFileName) > 0) { /* use LOG_WARN also for success as users want to see the path */ Log_Printf(LOG_WARN, "%s screen dump saved to: %s", name, szFileName); } else { Log_Printf(LOG_WARN, "Failed to save %s screen dump to: %s!", name, szFileName); } free(szFileName); } /** * Save screen shot to given file. */ void ScreenSnapShot_SaveToFile(const char *szFileName) { int ret; if (!szFileName) { fprintf(stderr, "ERROR: no screen dump file name specified\n"); return; } #if HAVE_LIBPNG if (File_DoesFileExtensionMatch(szFileName, ".png")) { ret = ScreenSnapShot_SavePNG(szFileName); } else #endif if (File_DoesFileExtensionMatch(szFileName, ".bmp")) { ret = ScreenSnapShot_SaveBMP(szFileName); } else if (File_DoesFileExtensionMatch(szFileName, ".neo")) { ret = ScreenSnapShot_SaveNEO(szFileName); } else if (File_DoesFileExtensionMatch(szFileName, ".ximg") || File_DoesFileExtensionMatch(szFileName, ".img")) { ret = ScreenSnapShot_SaveXIMG(szFileName); } else { fprintf(stderr, "ERROR: unknown screen dump file name extension: %s\n", szFileName); return; } fprintf(stderr, "Screen dump to '%s' %s\n", szFileName, ret > 0 ? "succeeded" : "failed"); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/scu_vme.c000066400000000000000000000263011504763705000231370ustar00rootroot00000000000000/* Hatari - scu_vme.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. SCU (System Control Unit) interrupt handling, used only in MegaSTE and TT References : - Atari TT030 Hardware reference Manual - June 1990 - Atari Profibuch ST-STE-TT, chapter 9 (german edition) - 1991 TODO: Non-cacheable TT VME card address mapping (word based data transfer): - FE000000-FEFEFFFF VMEbus A24:D16 - FEFF0000-FEFFFFFF VMEbus A16:D16 TODO: more limited MegaSTE VME card address mapping: - 00A00000-00DEFFFF VMEbus A24:D16 - 00DF0000-00DFFFFF VMEbus A16:D16 SCU IRQ info from TT HW reference: - SCU generated IRQ1 is detected only by the MPU not the VMEbus - SCU generated IRQ1 and IRQ3 are hardwired to the corresponding priorities and are always auto vectored - only interrupts 5 and 6 have external IACK pins and are capable of generating vectored interrupts on the motherboard (and also cause VME IRQ5 and IRQ6 respectively) - VMEbus SYSFAIL generates a system (motherboard) IRQ7 to the MPU, but does not not generate an IRQ7 to the VMEbus. The only other source of an IRQ7 is a VMEbus card TODO : - SCU generated IRQ3 is ignored because it should send a level 3 IRQ on the VME bus, but we don't emulate any VME board at the moment June 2024 : Nicolas Pomarde, add all the required SCU logic to handle interrupts on MegaSTE / TT (code was "empty" before, only displaying traces) */ const char vme_fileid[] = "Hatari scu_vme.c"; #include "main.h" #include "configuration.h" #include "ioMem.h" #include "log.h" #include "scu_vme.h" #include "m68000.h" #include "hatari-glue.h" #include "memorySnapShot.h" /* Possible interrupt level for SysIntMask at $FF8E01 */ #define SCU_SYS_INT_LEVEL_VME_SYSFAIL 7 #define SCU_SYS_INT_LEVEL_MFP 6 #define SCU_SYS_INT_LEVEL_SCC 5 #define SCU_SYS_INT_LEVEL_VSYNC 4 #define SCU_SYS_INT_LEVEL_UNUSED_3 3 #define SCU_SYS_INT_LEVEL_HSYNC 2 #define SCU_SYS_INT_LEVEL_SOFT_INT 1 #define SCU_SYS_INT_LEVEL_UNUSED_0 0 typedef struct { uint8_t SysIntMask; /* FF8E01 */ uint8_t SysIntState; /* FF8E03 */ uint8_t SysInterrupter; /* FF8E05 */ uint8_t VmeIntMask; /* FF8E0D */ uint8_t VmeIntState; /* FF8E0F */ uint8_t VmeInterrupter; /* FF8E07 */ uint8_t GPR1; /* FF8E09 */ uint8_t GPR2; /* FF8E0B */ } SCU_REGS; static SCU_REGS SCU; /** * SCU trace logging for read / write accesses */ static void SCU_TraceRead ( const char *info ) { LOG_TRACE_VAR int addr = IoAccessCurrentAddress; LOG_TRACE(TRACE_SCU, "scu read %s %x=0x%x pc=%x\n", info , addr, IoMem[addr], M68000_GetPC()); } static void SCU_TraceWrite ( const char *info ) { LOG_TRACE_VAR int addr = IoAccessCurrentAddress; LOG_TRACE(TRACE_SCU, "scu write %s %x=0x%x pc=%x\n", info , addr, IoMem[addr], M68000_GetPC()); } /** * Return 'true' if SCU is enabled (MegaSTE or TT), else 'false' */ bool SCU_IsEnabled ( void ) { return ( Config_IsMachineTT() || Config_IsMachineMegaSTE() ); } /** * Reset SCU/VME registers and interrupts */ void SCU_Reset ( bool bCold ) { if ( !SCU_IsEnabled() ) return; /* All the SCU regs are cleared on reset */ SCU.SysIntMask = 0x00; /* TOS will set 0x14 : hsync and vsync */ SCU.SysIntState = 0x00; SCU.SysInterrupter = 0x00; SCU.VmeIntMask = 0x00; /* TOS will set 0x60 : MFP and SCC */ SCU.VmeIntState = 0x00; SCU.VmeInterrupter = 0x00; /* GPR1 and GPR2 are cleared only on cold boot, they keep their content on warm boot */ if ( bCold ) { SCU.GPR1 = 0x00; SCU.GPR2 = 0x00; } /* TODO: ...but TOS v2 / v3 crash on MegaSTE / TT * unless gen reg 1 has this value, why? */ SCU.GPR1 = 0x01; /* Update CPU interrupts (=clear all) */ SCU_UpdatePendingInts_CPU(); } /** * Functions used to process the IRQ that should go to the CPU and change IPL * * On MegaSTE / TT all IRQ are connected to the SCU, which uses 2 masks to forward or not * the IRQ to the CPU through the IPL signal (on STF/STE/Falcon, changes in IRQ will update IPL directly) * * - SysIntMask is used to mask IRQ level 1 - 7 coming from the motherboard (hsync, vsync, ...) * - VmeIntMask is used to mask IRQ level 1 - 7 coming from the VME Bus * * NOTE : MFP (level 6) and SCC (level 5) are in fact hardwired to the VME bus, even if MFP and SCC * are on the motherboard. So, instead of using SysIntMask to control level 5 and 6 IRQ, we must * use VmeIntMask bits 5 and 6. * This is why on boot TOS will set : * - SysIntMask = 0x14 to enable hsync and vsync IRQ * - VmeIntMask = 0x60 to enable MFP and SCC IRQ */ void SCU_UpdatePendingInts_CPU ( void ) { pendingInterrupts = ( ( SCU.SysIntState & SCU.SysIntMask ) & 0x9f ) /* keep bits 0-7, except 5 and 6 */ | ( ( SCU.VmeIntState & SCU.VmeIntMask ) & 0x60 ); /* keep only bits 5 and 6 */ //fprintf ( stderr , "scu update int state=%x mask=%x vme state=%x mask=%x : out=%x\n" , SCU.SysIntState, SCU.SysIntMask, SCU.VmeIntState , SCU.VmeIntMask , pendingInterrupts ); } void SCU_SetIRQ_CPU ( int IntNr ) { if ( ( IntNr == 6 ) || ( IntNr == 5 ) ) /* MFP level 6 and SCC level 5 */ SCU.VmeIntState |= ( 1 << IntNr ); else SCU.SysIntState |= ( 1 << IntNr ); SCU_UpdatePendingInts_CPU(); } void SCU_ClearIRQ_CPU ( int IntNr ) { if ( ( IntNr == 6 ) || ( IntNr == 5 ) ) /* MFP level 6 and SCC level 5 */ SCU.VmeIntState &= ~( 1 << IntNr ); else SCU.SysIntState &= ~( 1 << IntNr ); SCU_UpdatePendingInts_CPU(); } /** * 0xff8e01 - system interrupt mask * * Bits 1-7 -> IRQ 1-7, Bit 0 unused * * IRQ5 & IRQ6 can be serviced either by 68030 or VMEbus master, * so they cannot be masked independently by VME & system masks, * they will be masked using VmeIntMask and not SysIntMask. */ void SCU_SysIntMask_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.SysIntMask; SCU_TraceRead ( "sys_int mask" ); /* Accessing sys int mask resets all pending interrupt requests */ SCU.SysIntState = 0; M68000_Update_intlev (); } void SCU_SysIntMask_WriteByte ( void ) { SCU_TraceWrite ( "sys_int mask" ); SCU.SysIntMask = IoMem[IoAccessCurrentAddress]; //SCU.SysIntMask = 0; /* Accessing sys int mask resets all pending interrupt requests */ SCU.SysIntState = 0; M68000_Update_intlev (); } /** * 0xff8e03 - system interrupt status (pending bits before they are masked with SysIntMask above) */ void SCU_SysIntState_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.SysIntState; SCU_TraceRead ( "sys_int state" ); } void SCU_SysIntState_WriteByte ( void ) { SCU_TraceWrite ( "sys_int state (read only)" ); } /** * 0xff8e05 - SCU system interrupter * * If bit 0 is set, a level 1 interrupt request to the CPU is triggered (if level 1 int is enabled in SysIntMask) * If bit 0 is clear, the level 1 interrupt request is removed * Other bits are not used */ void SCU_SysInterrupter_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.SysInterrupter; SCU_TraceRead ( "sys interrupter" ); } void SCU_SysInterrupter_WriteByte ( void ) { SCU.SysInterrupter = IoMem[IoAccessCurrentAddress]; if ( SCU.SysInterrupter & 0x1 ) { SCU_TraceWrite ( "sys interrupter, set IRQ1" ); SCU.SysIntState |= SCU_SYS_INT_LEVEL_SOFT_INT; } else { SCU_TraceWrite ( "sys interrupter, clear IRQ1" ); SCU.SysIntState &= ~SCU_SYS_INT_LEVEL_SOFT_INT; } /* Update CPU's intlev depending on IRQ1 status */ M68000_Update_intlev (); } /** * 0xff8e07 - SCU VME interrupter * * Bit 0 controls VME IRQ3 setting/clearing * * NOTE : not implemented at the moment as Hatari doesn't emulate the VME bus itself */ void SCU_VmeInterrupter_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.SysInterrupter; SCU_TraceRead ( "vme interrupter" ); } void SCU_VmeInterrupter_WriteByte ( void ) { if (IoMem[0xff8e07] & 0x1) { SCU_TraceWrite ( "vme interrupter, set IRQ3 (ignored)" ); /* TODO: generate VMEbus level 3 interrupt (IRQ3), * interrupt CPU immediately unless masked off * * System responds to interrupt acknowledge cycle * with the status ID of 0xFF * * Status word supplied by the card during acknowledge * cycle is used as 030 interrupt vector. */ } else { SCU_TraceWrite ( "vme interrupter, clear IRQ3 (ignored)" ); /* TODO: clear VMEbus IRQ3 */ } } /** * 0xff8e09 - SCU general purpose reg 1 */ void SCU_GPR1_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.GPR1; SCU_TraceRead ( "gpr1" ); } void SCU_GPR1_WriteByte ( void ) { SCU_TraceWrite ( "gpr1" ); SCU.GPR1 = IoMem[IoAccessCurrentAddress]; } /** * 0xff8e0b - SCU general purpose reg 2 */ void SCU_GPR2_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.GPR2; SCU_TraceRead ( "gpr2" ); } void SCU_GPR2_WriteByte ( void ) { SCU_TraceWrite ( "gpr2" ); SCU.GPR2 = IoMem[IoAccessCurrentAddress]; } /** * 0xff8e0d - masks interrupts generated by VMEbus sources * * Bits 1-7 -> IRQ 1-7, Bit 0 unused */ void SCU_VmeIntMask_Readyte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.VmeIntMask; SCU_TraceRead ( "vme_int mask" ); /* Accessing vme int mask resets all pending interrupt requests */ SCU.VmeIntState = 0; M68000_Update_intlev (); } void SCU_VmeIntMask_WriteByte ( void ) { SCU_TraceWrite ( "vme_int mask" ); SCU.VmeIntMask = IoMem[IoAccessCurrentAddress]; /* Accessing vme int mask resets all pending interrupt requests */ SCU.VmeIntState = 0; M68000_Update_intlev (); } /** * 0xff8e0f - VME interrupt status (pending bits before they are masked with VmeIntMask above) */ void SCU_VmeIntState_ReadByte ( void ) { IoMem[IoAccessCurrentAddress] = SCU.VmeIntState; SCU_TraceRead ( "vme_int state" ); } void SCU_VmeIntState_WriteByte ( void ) { SCU_TraceWrite ( "vme_int state (read only)" ); } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of SCU/VME variables. */ void SCU_MemorySnapShot_Capture ( bool bSave ) { /* Save/Restore details */ MemorySnapShot_Store(&SCU.SysIntMask, sizeof(SCU.SysIntMask)); MemorySnapShot_Store(&SCU.SysIntState, sizeof(SCU.SysIntState)); MemorySnapShot_Store(&SCU.SysInterrupter, sizeof(SCU.SysInterrupter)); MemorySnapShot_Store(&SCU.VmeIntMask, sizeof(SCU.VmeIntMask)); MemorySnapShot_Store(&SCU.VmeIntState, sizeof(SCU.VmeIntState)); MemorySnapShot_Store(&SCU.VmeInterrupter, sizeof(SCU.VmeInterrupter)); MemorySnapShot_Store(&SCU.GPR1, sizeof(SCU.GPR1)); MemorySnapShot_Store(&SCU.GPR2, sizeof(SCU.GPR2)); } /** * Show SCU/VME register values */ void SCU_Info ( FILE *fp, uint32_t arg ) { if (!(Config_IsMachineTT() || Config_IsMachineMegaSTE())) { fprintf(fp, "No MegaSTE/TT -> no SCU/VME\n\n"); return; } fprintf(fp, "$FF8E01.b : system interrupt mask : 0x%02x\n", SCU.SysIntMask); fprintf(fp, "$FF8E03.b : system interrupt state : 0x%02x (RO)\n", SCU.SysIntState); fprintf(fp, "$FF8E05.b : system interrupter : 0x%02x\n", SCU.SysInterrupter); fprintf(fp, "$FF8E07.b : VME interrupter : 0x%02x\n", SCU.VmeInterrupter); fprintf(fp, "$FF8E09.b : general register 1 : 0x%02x\n", SCU.GPR1); fprintf(fp, "$FF8E0B.b : general register 2 : 0x%02x\n", SCU.GPR2); fprintf(fp, "$FF8E0D.b : VME interrupt mask : 0x%02x\n", SCU.VmeIntMask); fprintf(fp, "$FF8E0F.b : VME interrupt state : 0x%02x (RO)\n", SCU.VmeIntState); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/shortcut.c000066400000000000000000000252371504763705000233600ustar00rootroot00000000000000/* Hatari - shortcut.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Shortcut keys */ const char ShortCut_fileid[] = "Hatari shortcut.c"; #include #include "main.h" #include "dialog.h" #include "audio.h" #include "file.h" #include "floppy.h" #include "joy.h" #include "keymap.h" #include "memorySnapShot.h" #include "reset.h" #include "screen.h" #include "screenSnapShot.h" #include "configuration.h" #include "shortcut.h" #include "debugui.h" #include "sound.h" #include "sdlgui.h" #include "avi_record.h" #include "statusbar.h" static SHORTCUTKEYIDX ShortCutKey = SHORTCUT_NONE; /* current shortcut key */ /*-----------------------------------------------------------------------*/ /** * Shortcut to toggle full-screen */ static void ShortCut_FullScreen(void) { static Uint32 last_ticks = 0; Uint32 cur_ticks; /* SDL2 sometimes reports multiple key up and down events when toggling * fullscreen mode, even though the key has not been released in between * (likely because the SDL window focus is lost when switching mode). * To avoid that we're going back and forth between fullscreen mode and * windowed mode in this case, we have to ignore full screen shortcut * events that happen too often. */ cur_ticks = SDL_GetTicks(); if (cur_ticks - last_ticks < 200) return; last_ticks = cur_ticks; if (!bInFullScreen) { Screen_EnterFullScreen(); } else { Screen_ReturnFromFullScreen(); } } /*-----------------------------------------------------------------------*/ /** * Shortcut to toggle borders */ static void ShortCut_Borders(void) { ConfigureParams.Screen.bAllowOverscan = !ConfigureParams.Screen.bAllowOverscan; Screen_ModeChanged(false); /* false: re-create window only if size changed */ } /*-----------------------------------------------------------------------*/ /** * Shortcut to toggle mouse grabbing mode */ static void ShortCut_MouseGrab(void) { bGrabMouse = !bGrabMouse; /* Toggle flag */ /* If we are in windowed mode, toggle the mouse cursor mode now: */ if (!bInFullScreen) { SDL_SetRelativeMouseMode(bGrabMouse); } } /*-----------------------------------------------------------------------*/ /** * Shortcut to toggle YM/WAV sound recording */ static void ShortCut_RecordSound(void) { /* Is working? */ if (bSoundWorking) { /* Are we currently recording? If so stop */ if (Sound_AreWeRecording()) { /* Stop, and save */ Sound_EndRecording(); } else { /* Begin recording */ Sound_BeginRecording(ConfigureParams.Sound.szYMCaptureFileName); } } } /*-----------------------------------------------------------------------*/ /** * Shortcut to toggle screen animation recording */ static void ShortCut_RecordAnimation(void) { /* Are we currently recording? If so stop */ if (Avi_AreWeRecording()) { /* Stop */ Avi_StopRecording_WithMsg(); } else { /* Start recording */ Avi_StartRecording_WithConfig(); } } /*-----------------------------------------------------------------------*/ /** * Shortcut to sound on/off */ static void ShortCut_SoundOnOff(void) { /* Toggle sound on/off */ ConfigureParams.Sound.bEnableSound ^= true; /* And start/stop if need to */ if (!ConfigureParams.Sound.bEnableSound) { if (Sound_AreWeRecording()) Sound_EndRecording(); Audio_UnInit(); } else { Audio_Init(); } } /*-----------------------------------------------------------------------*/ /** * Shortcut to fast forward */ static void ShortCut_FastForward(void) { /* If already on max speed, switch back to normal */ if (ConfigureParams.System.bFastForward == true) { /* Restore */ ConfigureParams.System.bFastForward = false; /* Reset the sound emulation variables: */ Sound_BufferIndexNeedReset = true; } else { /* Set maximum speed */ ConfigureParams.System.bFastForward = true; } } /** * Shortcut to 'Boss' key, ie minimize Window and switch to another application */ static void ShortCut_BossKey(void) { /* If we are in full-screen, then return to a window */ Screen_ReturnFromFullScreen(); if (bGrabMouse) { SDL_SetRelativeMouseMode(false); bGrabMouse = false; } Main_PauseEmulation(true); /* Minimize Window and give up processing to next one! */ SDL_MinimizeWindow(sdlWindow); } /** * Shortcut to debug interface */ static void ShortCut_Debug(void) { int running; /* Call the debugger */ running = Main_PauseEmulation(true); DebugUI(REASON_USER); if (running) Main_UnPauseEmulation(); } /** * Shortcut to pausing */ static void ShortCut_Pause(void) { if (!Main_UnPauseEmulation()) Main_PauseEmulation(true); } /** * Shortcut to load a disk image */ static void ShortCut_InsertDisk(int drive) { char *selname, *zip_path = NULL; const char *tmpname; char FileNameB[ FILENAME_MAX ]; bool bOldMouseMode = SDL_GetRelativeMouseMode(); if (SDLGui_SetScreen(sdlscrn)) return; /* Save current names for drive 1 before checking autoinsert */ strcpy ( FileNameB , ConfigureParams.DiskImage.szDiskFileName[ 1 ] ); if (ConfigureParams.DiskImage.szDiskFileName[drive][0]) tmpname = ConfigureParams.DiskImage.szDiskFileName[drive]; else tmpname = ConfigureParams.DiskImage.szDiskImageDirectory; Main_PauseEmulation(true); SDL_SetRelativeMouseMode(SDL_FALSE); selname = SDLGui_FileSelect("Floppy image:", tmpname, &zip_path, false); SDL_SetRelativeMouseMode(bOldMouseMode); if (selname) { if (File_Exists(selname)) Floppy_SetDiskFileName(drive, selname, zip_path); else Floppy_SetDiskFileNameNone(drive); free(zip_path); free(selname); Floppy_InsertDiskIntoDrive(0); /* Check if inserting into drive 0 also changed drive 1 with autoinsert */ if ( ( strcmp ( FileNameB , ConfigureParams.DiskImage.szDiskFileName[ 1 ] ) != 0 ) || ( strcmp ( FileNameB , ConfigureParams.DiskImage.szDiskZipPath[ 1 ] ) != 0 ) ) Floppy_InsertDiskIntoDrive(1); } Main_UnPauseEmulation(); } /*-----------------------------------------------------------------------*/ /** * Check to see if pressed any shortcut keys, and call handling function */ void ShortCut_ActKey(void) { if (ShortCutKey == SHORTCUT_NONE) return; switch (ShortCutKey) { case SHORTCUT_OPTIONS: Dialog_DoProperty(); /* Show options dialog */ break; case SHORTCUT_FULLSCREEN: ShortCut_FullScreen(); /* Switch between fullscreen/windowed mode */ break; case SHORTCUT_BORDERS: ShortCut_Borders(); /* Toggle Atari borders */ break; case SHORTCUT_MOUSEGRAB: ShortCut_MouseGrab(); /* Toggle mouse grab */ break; case SHORTCUT_COLDRESET: Main_UnPauseEmulation(); Reset_Cold(); /* Reset emulator with 'cold' (clear all) */ Statusbar_UpdateInfo(); /* Some infos can change after 'reset' */ break; case SHORTCUT_WARMRESET: Main_UnPauseEmulation(); Reset_Warm(); /* Emulator 'warm' reset */ Statusbar_UpdateInfo(); /* Some infos can change after 'reset' */ break; case SHORTCUT_SCREENSHOT: ScreenSnapShot_SaveScreen(); /* Grab screenshot */ break; case SHORTCUT_BOSSKEY: ShortCut_BossKey(); /* Boss key */ break; case SHORTCUT_CURSOREMU: /* Toggle joystick emu on/off */ Joy_ToggleCursorEmulation(); break; case SHORTCUT_FASTFORWARD: ShortCut_FastForward(); /* Toggle Min/Max speed */ break; case SHORTCUT_RECANIM: ShortCut_RecordAnimation(); /* Record animation */ break; case SHORTCUT_RECSOUND: ShortCut_RecordSound(); /* Toggle sound recording */ break; case SHORTCUT_SOUND: ShortCut_SoundOnOff(); /* Enable/disable sound */ break; case SHORTCUT_DEBUG: ShortCut_Debug(); /* Invoke the Debug UI */ break; case SHORTCUT_PAUSE: ShortCut_Pause(); /* Invoke Pause */ break; case SHORTCUT_JOY_0: Joy_SwitchMode(0); break; case SHORTCUT_JOY_1: Joy_SwitchMode(1); break; case SHORTCUT_PAD_A: Joy_SwitchMode(2); break; case SHORTCUT_PAD_B: Joy_SwitchMode(3); break; case SHORTCUT_QUIT: Main_RequestQuit(0); break; case SHORTCUT_LOADMEM: MemorySnapShot_Restore(ConfigureParams.Memory.szMemoryCaptureFileName, true); break; case SHORTCUT_SAVEMEM: MemorySnapShot_Capture(ConfigureParams.Memory.szMemoryCaptureFileName, true); break; case SHORTCUT_INSERTDISKA: ShortCut_InsertDisk(0); break; case SHORTCUT_KEYS: case SHORTCUT_NONE: /* ERROR: cannot happen, just make compiler happy */ break; } ShortCutKey = SHORTCUT_NONE; } /*-----------------------------------------------------------------------*/ /** * Invoke shortcut identified by name. This supports only keys for * functionality that cannot be invoked with command line options * or otherwise for remote GUIs etc. */ bool Shortcut_Invoke(const char *shortcut) { struct { SHORTCUTKEYIDX id; const char *name; } shortcuts[] = { { SHORTCUT_MOUSEGRAB, "mousegrab" }, { SHORTCUT_COLDRESET, "coldreset" }, { SHORTCUT_WARMRESET, "warmreset" }, { SHORTCUT_SCREENSHOT, "screenshot" }, { SHORTCUT_BOSSKEY, "bosskey" }, { SHORTCUT_RECANIM, "recanim" }, { SHORTCUT_RECSOUND, "recsound" }, { SHORTCUT_SAVEMEM, "savemem" }, { SHORTCUT_QUIT, "quit" }, { SHORTCUT_NONE, NULL } }; int i; if (ShortCutKey != SHORTCUT_NONE) { fprintf(stderr, "WARNING: Shortcut invocation failed, shortcut already active\n"); return false; } for (i = 0; shortcuts[i].name; i++) { if (strcmp(shortcut, shortcuts[i].name) == 0) { ShortCutKey = shortcuts[i].id; ShortCut_ActKey(); ShortCutKey = SHORTCUT_NONE; return true; } } fprintf(stderr, "WARNING: unknown shortcut '%s'\n\n", shortcut); fprintf(stderr, "Hatari shortcuts are:\n"); for (i = 0; shortcuts[i].name; i++) { fprintf(stderr, "- %s\n", shortcuts[i].name); } return false; } /*-----------------------------------------------------------------------*/ /** * Check whether given key was any of the ones in given shortcut array. * Return corresponding array index or SHORTCUT_NONE for no match */ static SHORTCUTKEYIDX ShortCut_CheckKey(int symkey, int *keys) { SHORTCUTKEYIDX key; for (key = SHORTCUT_OPTIONS; key < SHORTCUT_KEYS; key++) { if (symkey == keys[key]) return key; } return SHORTCUT_NONE; } /*-----------------------------------------------------------------------*/ /** * Check which Shortcut key is pressed/released. * If press is set, store the key array index. * Return true if key combo matched to a shortcut */ bool ShortCut_CheckKeys(int modkey, int symkey, bool press) { SHORTCUTKEYIDX key; if (symkey == SDLK_UNKNOWN) return false; if (modkey & (KMOD_RALT|KMOD_LGUI|KMOD_RGUI|KMOD_MODE)) key = ShortCut_CheckKey(symkey, ConfigureParams.Shortcut.withModifier); else key = ShortCut_CheckKey(symkey, ConfigureParams.Shortcut.withoutModifier); if (key == SHORTCUT_NONE) return false; if (press) ShortCutKey = key; return true; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/sound.c000066400000000000000000002064271504763705000226370ustar00rootroot00000000000000/* Hatari - sound.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. This is where we emulate the YM2149. To obtain cycle-accurate timing we store the current cycle time and this is incremented during each instruction. When a write occurs in the PSG registers we take the difference in time and generate this many samples using the previous register data. Now we begin again from this point. To make sure we always have 1/50th of samples we update the buffer generation every 1/50th second, just in case no write took place on the PSG. NOTE: If the emulator runs slower than 50fps it cannot update the buffers, but the sound thread still needs some data to play to prevent a 'pop'. The ONLY feasible solution is to play the same buffer again. I have tried all kinds of methods to play the sound 'slower', but this produces un-even timing in the sound and it simply doesn't work. If the emulator cannot keep the speed, users will have to turn off the sound - that's it. The new version of the sound core uses/used some code/ideas from the following GPL projects : - tone and noise steps computations are from StSound 1.2 by Arnaud Carré (Leonard/Oxygene) (not used since Hatari 1.1.0) - 5 bits volume table and 16*16*16 combinations of all volume are from Sc68 by Benjamin Gerard - 4 bits to 5 bits volume interpolation from 16*16*16 to 32*32*32 from YM blep synthesis by Antti Lankila Special case for per==0 : as measured on a real STF, when tone/noise/env's per==0, we get the same sound output as when per==1. NOTE [NP] : As of June 2017, Hatari uses a completely new/rewritten cycle exact YM2149 emulation. Instead of updating YM2149's state on every host call (for example at 44.1 kHz), YM2149's state is now updated at 250 kHz (which is the base frequency used by real YM2149 to handle its various counters) and then downsampled to the host frequency. This is required to perfectly emulate transitions when the periods for tone/noise/envelope are changed and whether a new phase should be started or the current phase should be extended. Using a custom program on a real STF, it's possible to change YM2149 registers at very precise cycles during various sound phases and to record the STF sound output as a wav file on a modern PC. The wav file can then be studied (using Audacity for example) to precisely see how and when a change in a register affects the sound output. Based on these measures I made, the following behaviours of the YM2149 were confirmed : - unlike what it written in Yamaha documentation, each period counter doesn't count from 'period' down to 0, but count up from 0 until the counter reaches 'period'. The counter is incremented at 250 kHz, with the following pseudo code : tone_counter = tone_counter+1 if ( tone_counter >= tone_period ) tone_counter = 0 invert tone output This means that when a new value is written in tone period A for example, the current phase will be either extended (if new_period_A > tone_counter_A) or a new phase will be started immediately (if new_period_A < tone_counter_A). - noise counter is incremented twice slower than tone counter ; for example with period=31 a tone phase will remain up or down for ~0.0001 sec, but a noise phase will remain up or down for ~0.0002 sec. This is equivalent to incrementing noise counter at 125 kHz instead of 250 kHz like tone counter. - it is known that when writing to the env wave register (reg 13) the current envelope is restarted from the start (this is often used in so called sync-buzzer effects). But the measures on the resulting wav file of the YM2149 also show that the current envelope phase is restarted at the same time that envelope wave register is written to. - The various counters for tone/noise/env all give the same result when period=1 and when period=0. For noise and envelope, this can be seen when sampling the sound output as above For tone, this was also measured using a logic analyser (to sample at much higher rate than 250 kHz) - 2 voices playing tones at the same frequency and at the same volume can cancel each other partially or completely, giving no sound output at all. This happens when the phase for one voice is "up" while the phase for the other voice is "down", then on the next phase inversion, 1st voice will be "down" and 2nd voice will be "up". In such cases, the sum of these 2 tone waveforms will give a constant output signal, producing no sound at all. If both voices are not fully in opposite phase, the resulting sound will vary depending on how much phases are in common and how much they "cancel" each other */ /* 2008/05/05 [NP] Fix case where period is 0 for noise, sound or envelope. */ /* In that case, a real ST sounds as if period was in fact 1. */ /* (fix buggy sound replay in ESwat that set volume<0 and trigger */ /* a badly initialised envelope with envper=0). */ /* 2008/07/27 [NP] Better separation between accesses to the YM hardware registers */ /* and the sound rendering routines. Use Sound_WriteReg() to pass */ /* all writes to the sound rendering functions. This allows to */ /* have sound.c independent of psg.c (to ease replacement of */ /* sound.c by another rendering method). */ /* 2008/08/02 [NP] Initial convert of Ym2149Ex.cpp from C++ to C. */ /* Remove unused part of the code (StSound specific). */ /* 2008/08/09 [NP] Complete integration of StSound routines into sound.c */ /* Set EnvPer=3 if EnvPer<3 (ESwat buggy replay). */ /* 2008/08/13 [NP] StSound was generating samples in the range 0-32767, instead */ /* of really signed samples between -32768 and 32767, which could */ /* give incorrect results in many case. */ /* 2008/09/06 [NP] Use sc68 volumes table for a more accurate mixing of the voices */ /* All volumes are converted to 5 bits and the table contains */ /* 32*32*32 values. Samples are signed and centered to get the */ /* biggest amplitude possible. */ /* Faster mixing routines for tone+volume+envelope (don't use */ /* StSound's version anymore, it gave problem with some GCC). */ /* 2008/09/17 [NP] Add ym_normalise_5bit_table to normalise the 32*32*32 table and */ /* to optionally center 16 bit signed sample. */ /* Possibility to mix volumes using a table measured on ST or a */ /* linear mean of the 3 channels' volume. */ /* Default mixing set to YM_LINEAR_MIXING. */ /* 2008/10/14 [NP] Full support for 5 bits volumes : envelopes are generated with */ /* 32 volumes per pattern as on a real YM-2149. Fixed volumes */ /* on 4 bits are converted to their 5 bits equivalent. This should */ /* give the maximum accuracy possible when computing volumes. */ /* New version of Ym2149_EnvStepCompute to handle 5 bits volumes. */ /* Function YM2149_EnvBuild to compute the 96 volumes that define */ /* a single envelope (32 initial volumes, then 64 repeated values).*/ /* 2008/10/26 [NP] Correctly save/restore all necessary variables in */ /* Sound_MemorySnapShot_Capture. */ /* 2008/11/23 [NP] Clean source, remove old sound core. */ /* 2011/11/03 [DS] Stereo DC filtering which accounts for DMA sound. */ /* 2017/06/xx [NP] New cycle exact emulation method, all counters are incremented */ /* using a simulated freq of 250 kHz. Some undocumented cases */ /* where also measured on real STF to improve accuracy. */ /* 2019/09/09 [NP] Add YM2149_Next_Resample_Weighted_Average_N for better */ /* downsampling of the internal 250 kHz sound buffer. */ /* 2021/07/23 [NP] Default to 250 kHz cycle accurate emulation and remove older */ /* rendering and associated functions/variables. */ const char Sound_fileid[] = "Hatari sound.c"; #include "main.h" #include "audio.h" #include "cycles.h" #include "m68000.h" #include "configuration.h" #include "dmaSnd.h" #include "crossbar.h" #include "file.h" #include "cycInt.h" #include "log.h" #include "memorySnapShot.h" #include "psg.h" #include "sound.h" #include "screen.h" #include "video.h" #include "wavFormat.h" #include "ymFormat.h" #include "avi_record.h" #include "clocks_timings.h" /*--------------------------------------------------------------*/ /* Definition of the possible envelopes shapes (using 5 bits) */ /*--------------------------------------------------------------*/ #define ENV_GODOWN 0 /* 31 -> 0 */ #define ENV_GOUP 1 /* 0 -> 31 */ #define ENV_DOWN 2 /* 0 -> 0 */ #define ENV_UP 3 /* 31 -> 31 */ /* To generate an envelope, we first use block 0, then we repeat blocks 1 and 2 */ static const int YmEnvDef[ 16 ][ 3 ] = { { ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 0 \___ */ { ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 1 \___ */ { ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 2 \___ */ { ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 3 \___ */ { ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 4 /___ */ { ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 5 /___ */ { ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 6 /___ */ { ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* 7 /___ */ { ENV_GODOWN, ENV_GODOWN, ENV_GODOWN } , /* 8 \\\\ */ { ENV_GODOWN, ENV_DOWN, ENV_DOWN } , /* 9 \___ */ { ENV_GODOWN, ENV_GOUP, ENV_GODOWN } , /* A \/\/ */ { ENV_GODOWN, ENV_UP, ENV_UP } , /* B \--- */ { ENV_GOUP, ENV_GOUP, ENV_GOUP } , /* C //// */ { ENV_GOUP, ENV_UP, ENV_UP } , /* D /--- */ { ENV_GOUP, ENV_GODOWN, ENV_GOUP } , /* E /\/\ */ { ENV_GOUP, ENV_DOWN, ENV_DOWN } , /* F /___ */ }; /* Buffer to store the 16 envelopes built from YmEnvDef */ static ymu16 YmEnvWaves[ 16 ][ 32 * 3 ]; /* 16 envelopes with 3 blocks of 32 volumes */ /*--------------------------------------------------------------*/ /* Definition of the volumes tables (using 5 bits) and of the */ /* mixing parameters for the 3 voices. */ /*--------------------------------------------------------------*/ /* Table of unsigned 5 bit D/A output level for 1 channel as measured on a real ST (expanded from 4 bits to 5 bits) */ /* Vol 0 should be 310 when measuread as a voltage, but we set it to 0 in order to have a volume=0 matching */ /* the 0 level of a 16 bits unsigned sample (no sound output) */ static const ymu16 ymout1c5bit[ 32 ] = { 0 /*310*/, 369, 438, 521, 619, 735, 874, 1039, 1234, 1467, 1744, 2072, 2463, 2927, 3479, 4135, 4914, 5841, 6942, 8250, 9806,11654,13851,16462, 19565,23253,27636,32845,39037,46395,55141,65535 }; /* Convert a constant 4 bits volume to the internal 5 bits value : */ /* volume5=volume4*2+1, except for volumes 0 and 1 which remain 0 and 1, */ /* in order to map [0,15] into [0,31] (O must remain 0, and 15 must give 31) */ static const ymu16 YmVolume4to5[ 16 ] = { 0,1,5,7,9,11,13,15,17,19,21,23,25,27,29,31 }; /* Table of unsigned 4 bit D/A output level for 3 channels as measured on a real ST */ static ymu16 volumetable_original[16][16][16] = #include "ym2149_fixed_vol.h" /* Corresponding table interpolated to 5 bit D/A output level (16 bits unsigned) */ static ymu16 ymout5_u16[32][32][32]; /* Same table, after conversion to signed results (same pointer, with different type) */ static yms16 *ymout5 = (yms16 *)ymout5_u16; /*--------------------------------------------------------------*/ /* Other constants / macros */ /*--------------------------------------------------------------*/ /* Number of generated samples per frame (eg. 44Khz=882) */ #define SAMPLES_PER_FRAME (nAudioFrequency/nScreenRefreshRate) /* Current sound replay freq (usually 44100 Hz) */ #define YM_REPLAY_FREQ nAudioFrequency /* YM-2149 clock on all Atari models is 2 MHz (CPU freq / 4) */ /* Period counters for tone/noise/env are based on YM clock / 8 = 250 kHz */ #define YM_ATARI_CLOCK (MachineClocks.YM_Freq) #define YM_ATARI_CLOCK_COUNTER (YM_ATARI_CLOCK / 8) /* Merge/read the 3 volumes in a single integer (5 bits per volume) */ #define YM_MERGE_VOICE(C,B,A) ( (C)<<10 | (B)<<5 | A ) #define YM_MASK_1VOICE 0x1f #define YM_MASK_A 0x1f #define YM_MASK_B (0x1f<<5) #define YM_MASK_C (0x1f<<10) /* Constants for YM2149_Normalise_5bit_Table */ #define YM_OUTPUT_LEVEL 0x7fff /* amplitude of the final signal (0..65535 if centered, 0..32767 if not) */ #define YM_OUTPUT_CENTERED false /*--------------------------------------------------------------*/ /* Variables for the YM2149 emulator (need to be saved and */ /* restored in memory snapshots) */ /*--------------------------------------------------------------*/ /* Uncomment next line to write raw 250 kHz samples to a file 'hatari_250.wav' */ //#define YM_250_DEBUG /* For our internal computations to convert down/up square wave signals into 0-31 volume, */ /* we consider that 'up' is 31 and 'down' is 0 */ #define YM_SQUARE_UP 0x1f #define YM_SQUARE_DOWN 0x00 static ymu16 ToneA_per , ToneA_count , ToneA_val; static ymu16 ToneB_per , ToneB_count , ToneB_val; static ymu16 ToneC_per , ToneC_count , ToneC_val; static ymu16 Noise_per , Noise_count , Noise_val; static ymu16 Env_per , Env_count; static ymu32 Env_pos; static int Env_shape; static ymu32 mixerTA , mixerTB , mixerTC; static ymu32 mixerNA , mixerNB , mixerNC; static ymu32 RndRack; /* current random seed */ static ymu16 EnvMask3Voices = 0; /* mask is 0x1f for voices having an active envelope */ static ymu16 Vol3Voices = 0; /* volume 0-0x1f for voices having a constant volume */ /* volume is set to 0 if voice has an envelope in EnvMask3Voices */ /* Global variables that can be changed/read from other parts of Hatari */ uint8_t SoundRegs[ 14 ]; int YmVolumeMixing = YM_TABLE_MIXING; int YM2149_LPF_Filter = YM2149_LPF_FILTER_PWM; // int YM2149_LPF_Filter = YM2149_LPF_FILTER_NONE; /* For debug */ int YM2149_HPF_Filter = YM2149_HPF_FILTER_IIR; // int YM2149_HPF_Filter = YM2149_HPF_FILTER_NONE; /* For debug */ //int YM2149_Resample_Method = YM2149_RESAMPLE_METHOD_NEAREST; //int YM2149_Resample_Method = YM2149_RESAMPLE_METHOD_WEIGHTED_AVERAGE_2; int YM2149_Resample_Method = YM2149_RESAMPLE_METHOD_WEIGHTED_AVERAGE_N; static double pos_fract_nearest; /* For YM2149_Next_Resample_Nearest */ static double pos_fract_weighted_2; /* For YM2149_Next_Resample_Weighted_Average_2 */ static uint32_t pos_fract_weighted_n; /* YM2149_Next_Resample_Weighted_Average_N */ bool bEnvelopeFreqFlag; /* Cleared each frame for YM saving */ int16_t AudioMixBuffer[AUDIOMIXBUFFER_SIZE][2]; /* Ring buffer to store mixed audio output (YM2149, DMA sound, ...) */ int AudioMixBuffer_pos_write; /* Current writing position into above buffer */ int AudioMixBuffer_pos_read; /* Current reading position into above buffer */ int nGeneratedSamples; /* Generated samples since audio buffer update */ static int AudioMixBuffer_pos_write_avi; /* Current working index to save an AVI audio frame */ bool Sound_BufferIndexNeedReset = false; #define YM_BUFFER_250_SIZE 32768 /* Size to store YM samples generated at 250 kHz (must be a power of 2) */ /* As we fill YM_Buffer_250[] at least once per VBL (min freq = 50 Hz) */ /* we can have 5000 YM samples per VBL. We use a slightly larger buffer */ /* to have some kind of double buffering */ #define YM_BUFFER_250_SIZE_MASK ( YM_BUFFER_250_SIZE - 1 ) /* To limit index values inside the ring buffer */ ymsample YM_Buffer_250[ YM_BUFFER_250_SIZE ]; /* Ring buffer to store YM samples */ static int YM_Buffer_250_pos_write; /* Current writing position into above buffer */ static int YM_Buffer_250_pos_read; /* Current reading position into above buffer */ static uint64_t YM2149_Clock_250; /* 250 kHz counter */ static uint64_t YM2149_Clock_250_CpuClock; /* Corresponding value of CyclesGlobalClockCounter at the time YM2149_Clock_250 was updated */ static ymu16 YM2149_Freq_div_2 = 0; /* Used for noise's generator which uses half the main freq (125 KHz) */ /* Some variables used for stats / debug */ #define SOUND_STATS_SIZE 60 static int Sound_Stats_Array[ SOUND_STATS_SIZE ]; static int Sound_Stats_Index = 0; static int Sound_Stats_SamplePerVBL; static CLOCKS_CYCLES_STRUCT YM2149_ConvertCycles_250; /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static ymsample LowPassFilter (ymsample x0); static ymsample PWMaliasFilter (ymsample x0); static void interpolate_volumetable (ymu16 volumetable[32][32][32]); static void YM2149_BuildModelVolumeTable(ymu16 volumetable[32][32][32]); static void YM2149_BuildLinearVolumeTable(ymu16 volumetable[32][32][32]); static void YM2149_Normalise_5bit_Table(ymu16 *in_5bit , yms16 *out_5bit, unsigned int Level, bool DoCenter); static void YM2149_EnvBuild (void); static void Ym2149_BuildVolumeTable (void); static void YM2149_UpdateClock_250 ( uint64_t CpuClock ); static void Ym2149_Init (void); static void Ym2149_Reset (void); static ymu32 YM2149_RndCompute (void); static ymu16 YM2149_TonePer (ymu8 rHigh , ymu8 rLow); static ymu16 YM2149_NoisePer (ymu8 rNoise); static ymu16 YM2149_EnvPer (ymu8 rHigh , ymu8 rLow); static void YM2149_Run ( uint64_t CPU_Clock ); static int Sound_GenerateSamples ( uint64_t CPU_Clock); static void YM2149_DoSamples_250 ( int SamplesToGenerate_250 ); #ifdef YM_250_DEBUG static void YM2149_DoSamples_250_Debug ( int SamplesToGenerate , int pos ); #endif /*--------------------------------------------------------------*/ /* DC Adjuster */ /*--------------------------------------------------------------*/ /** * 6dB/octave first order HPF fc = (1.0-0.998)*44100/(2.0*pi) * Z pole = 0.99804 --> FS = 44100 Hz : fc=13.7 Hz (11 Hz meas) * a = (int32_t)(32768.0*(1.0 - pole)) : a = 64 !!! * Input range: -32768 to 32767 Maximum step: +65536 or -65472 */ ymsample Subsonic_IIR_HPF_Left(ymsample x0) { static yms32 x1 = 0, y1 = 0, y0 = 0; if ( YM2149_HPF_Filter == YM2149_HPF_FILTER_NONE ) return x0; y1 += ((x0 - x1) * 32768) - (y0 * 64); y0 = y1 / 32768; x1 = x0; return y0; } ymsample Subsonic_IIR_HPF_Right(ymsample x0) { static yms32 x1 = 0, y1 = 0, y0 = 0; if ( YM2149_HPF_Filter == YM2149_HPF_FILTER_NONE ) return x0; y1 += ((x0 - x1) * 32768) - (y0 * 64); y0 = y1 / 32768; x1 = x0; return y0; } /*--------------------------------------------------------------*/ /* Low Pass Filter routines. */ /*--------------------------------------------------------------*/ /** * Get coefficients for different Fs (C10 is in ST only): * Wc = 2*M_PI*4895.1; * Fs = 44100; * warp = Wc/tanf((Wc/2)/Fs); * b = Wc/(warp+Wc); * a = (Wc-warp)/(warp+Wc); * * #define B_z (yms32)( 0.2667*(1<<15)) * #define A_z (yms32)(-0.4667*(1<<15)) * * y0 = (B_z*(x0 + x1) - A_z*y0) >> 15; * x1 = x0; * * The Lowpass Filter formed by C10 = 0.1 uF * and * R8=1k // 1k*(65119-46602)/65119 // R9=10k // R10=5.1k // * (R12=470)*(100=Q1_HFE) = 206.865 ohms when YM2149 is High * and * R8=1k // R9=10k // R10=5.1k // (R12=470)*(100=Q1_HFE) * = 759.1 ohms when YM2149 is Low * High corner is 1/(2*pi*(0.1*10e-6)*206.865) fc = 7693.7 Hz * Low corner is 1/(2*pi*(0.1*10e-6)*795.1) fc = 2096.6 Hz * Notes: * - using STF reference designators R8 R9 R10 C10 (from dec 1986 schematics) * - using corresponding numbers from psgstrep and psgquart * - 65119 is the largest value in Paulo's psgstrep table * - 46602 is the largest value in Paulo's psgquart table * - this low pass filter uses the highest cutoff frequency * on the STf (a slightly lower frequency is reasonable). * * A first order lowpass filter with a high cutoff frequency * is used when the YM2149 pulls high, and a lowpass filter * with a low cutoff frequency is used when R8 pulls low. */ static ymsample LowPassFilter(ymsample x0) { static yms32 y0 = 0, x1 = 0; if (x0 >= y0) /* YM Pull up: fc = 7586.1 Hz (44.1 KHz), fc = 8257.0 Hz (48 KHz) */ y0 = (3*(x0 + x1) + (y0<<1)) >> 3; else /* R8 Pull down: fc = 1992.0 Hz (44.1 KHz), fc = 2168.0 Hz (48 KHz) */ y0 = ((x0 + x1) + (6*y0)) >> 3; x1 = x0; return y0; } /** * This piecewise selective filter works by filtering the falling * edge of a sampled pulse-wave differently from the rising edge. * * Piecewise selective filtering is effective because harmonics on * one part of a wave partially define harmonics on other portions. * * Piecewise selective filtering can efficiently reduce aliasing * with minimal harmonic removal. * * I disclose this information into the public domain so that it * cannot be patented. May 23 2012 David Savinkoff. */ static ymsample PWMaliasFilter(ymsample x0) { static yms32 y0 = 0, x1 = 0; if (x0 >= y0) /* YM Pull up */ y0 = x0; else /* R8 Pull down */ y0 = (3*(x0 + x1) + (y0<<1)) >> 3; x1 = x0; return y0; } /*--------------------------------------------------------------*/ /* Build the volume conversion table used to simulate the */ /* behaviour of DAC used with the YM2149 in the atari ST. */ /* The final 32*32*32 table is built using a 16*16*16 table */ /* of all possible fixed volume combinations on a ST. */ /*--------------------------------------------------------------*/ static void interpolate_volumetable(ymu16 volumetable[32][32][32]) { int i, j, k; for (i = 1; i < 32; i += 2) { /* Copy 16 Panels to make a Block */ for (j = 1; j < 32; j += 2) { /* Copy 16 Rows to make a Panel */ for (k = 1; k < 32; k += 2) { /* Copy 16 Elements to make a Row */ volumetable[i][j][k] = volumetable_original[(i-1)/2][(j-1)/2][(k-1)/2]; } volumetable[i][j][0] = volumetable[i][j][1]; /* Move 0th Element */ volumetable[i][j][1] = volumetable[i][j][3]; /* Move 1st Element */ /* Interpolate 3rd Element */ volumetable[i][j][3] = (ymu16)(0.5 + sqrt((double)volumetable[i][j][1] * volumetable[i][j][5])); for (k = 2; k < 32; k += 2) /* Interpolate Even Elements */ volumetable[i][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[i][j][k-1] * volumetable[i][j][k+1])); } for (k = 0; k < 32; k++) { volumetable[i][0][k] = volumetable[i][1][k]; /* Move 0th Row */ volumetable[i][1][k] = volumetable[i][3][k]; /* Move 1st Row */ /* Interpolate 3rd Row */ volumetable[i][3][k] = (ymu16)(0.5 + sqrt((double)volumetable[i][1][k] * volumetable[i][5][k])); } for (j = 2; j < 32; j += 2) /* Interpolate Even Rows */ for (k = 0; k < 32; k++) volumetable[i][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[i][j-1][k] * volumetable[i][j+1][k])); } for (j = 0; j < 32; j++) for (k = 0; k < 32; k++) { volumetable[0][j][k] = volumetable[1][j][k]; /* Move 0th Panel */ volumetable[1][j][k] = volumetable[3][j][k]; /* Move 1st Panel */ /* Interpolate 3rd Panel */ volumetable[3][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[1][j][k] * volumetable[5][j][k])); } for (i = 2; i < 32; i += 2) /* Interpolate Even Panels */ for (j = 0; j < 32; j++) /* Interpolate Even Panels */ for (k = 0; k < 32; k++) volumetable[i][j][k] = (ymu16)(0.5 + sqrt((double)volumetable[i-1][j][k] * volumetable[i+1][j][k])); } /*-----------------------------------------------------------------------*/ /** * Build a linear version of the conversion table. * We use the mean of the 3 volumes converted to 16 bit values * (each value of ymout1c5bit is in [0,65535]) */ static void YM2149_BuildLinearVolumeTable(ymu16 volumetable[32][32][32]) { int i, j, k; for (i = 0; i < 32; i++) for (j = 0; j < 32; j++) for (k = 0; k < 32; k++) volumetable[i][j][k] = (ymu16)( ((ymu32)ymout1c5bit[i] + ymout1c5bit[j] + ymout1c5bit[k]) / 3); } /*-----------------------------------------------------------------------*/ /** * Build a circuit analysed version of the conversion table. * David Savinkoff designed this algorithm by analysing data * measured by Paulo Simoes and Benjamin Gerard. * The numbers are arrived at by assuming a current steering * resistor ladder network and using the voltage divider rule. * * If one looks at the ST schematic of the YM2149, one sees * three sound pins tied together and attached to a 1000 ohm * resistor (1k) that has the other end grounded. * The 1k resistor is also in parallel with a 0.1 microfarad * capacitor (on the Atari ST, not STE or others). The voltage * developed across the 1K resistor is the output voltage which * I call Vout. * * The output of the YM2149 is modelled well as pullup resistors. * Thus, the three sound pins are seen as three parallel * computer-controlled, adjustable pull-up resistors. * To emulate the output of the YM2149, one must determine the * resistance values of the YM2149 relative to the 1k resistor, * which is done by the 'math model'. * * The AC + DC math model is: * * (MaxVol*WARP) / (1.0 + 1.0/(conductance_[i]+conductance_[j]+conductance_[k])) * or * (MaxVol*WARP) / (1.0 + 1.0/( 1/Ra +1/Rb +1/Rc )) , Ra = channel A resistance * * Note that the first 1.0 in the formula represents the * normalized 1k resistor (1.0 * 1000 ohms = 1k). * * The YM2149 DC component model represents the output voltage * filtered of high frequency AC component, but DC component * remains. * The YM2149 DC component mode treats the voltage exactly as if * it were low pass filtered. This is more than what is required * to make 'quartet mode sound'. Simplicity leads to Generality! * * The DC component model model is: * * (MaxVol*WARP) / (2.0 + 1.0/( 1/Ra + 1/Rb + 1/Rc)) * or * (MaxVol*WARP*0.5) / (1.0 + 0.5/( 1/Ra + 1/Rb + 1/Rc)) * * Note that the 1.0 represents the normalized 1k resistor. * 0.5 represents 50% duty cycle for the parallel resistors * being summed (this effectively doubles the pull-up resistance). */ static void YM2149_BuildModelVolumeTable(ymu16 volumetable[32][32][32]) { #define MaxVol 65535.0 /* Normal Mode Maximum value in table */ #define FOURTH2 1.19 /* Fourth root of two from YM2149 */ #define WARP 1.666666666666666667 /* measured as 1.65932 from 46602 */ double conductance; double conductance_[32]; int i, j, k; /** * YM2149 and R8=1k follows (2^-1/4)^(n-31) better when 2 voices are * summed (A+B or B+C or C+A) rather than individually (A or B or C): * conductance = 2.0/3.0/(1.0-1.0/WARP)-2.0/3.0; * When taken into consideration with three voices. * * Note that the YM2149 does not use laser trimmed resistances, thus * has offsets that are added and/or multiplied with (2^-1/4)^(n-31). */ conductance = 2.0/3.0/(1.0-1.0/WARP)-2.0/3.0; /* conductance = 1.0 */ /** * Because the YM current output (voltage source with series resistances) * is connected to a grounded resistor to develop the output voltage * (instead of a current to voltage converter), the output transfer * function is not linear. Thus: * 2.0*conductance_[n] = 1.0/(1.0-1.0/FOURTH2/(1.0/conductance + 1.0))-1.0; */ for (i = 31; i >= 1; i--) { conductance_[i] = conductance/2.0; conductance = 1.0/(1.0-1.0/FOURTH2/(1.0/conductance + 1.0))-1.0; } conductance_[0] = 1.0e-8; /* Avoid divide by zero */ /** * YM2149 AC + DC components model: * (Note that Maxvol is 65119 in Simoes' table, 65535 in Gerard's) * * Sum the conductances as a function of a voltage divider: * Vout=Vin*Rout/(Rout+Rin) */ for (i = 0; i < 32; i++) for (j = 0; j < 32; j++) for (k = 0; k < 32; k++) { volumetable[i][j][k] = (ymu16)(0.5+(MaxVol*WARP)/(1.0 + 1.0/(conductance_[i]+conductance_[j]+conductance_[k]))); } /** * YM2149 DC component model: * R8=1k (pulldown) + YM//1K (pullup) with YM 50% duty PWM * (Note that MaxVol is 46602 in Paulo Simoes Quartet mode table) * * for (i = 0; i < 32; i++) * for (j = 0; j < 32; j++) * for (k = 0; k < 32; k++) * { * volumetable[i][j][k] = (ymu16)(0.5+(MaxVol*WARP)/(1.0 + * 2.0/(conductance_[i]+conductance_[j]+conductance_[k]))); * } */ } /*-----------------------------------------------------------------------*/ /** * Normalise and optionally center the volume table used to * convert the 3 volumes to a final signed 16 bit sample. * This allows to adapt the amplitude/volume of the samples and * to convert unsigned values to signed values. * - in_5bit contains 32*32*32 unsigned values in the range * [0,65535]. * - out_5bit will contain signed values * Possible values are : * Level=65535 and DoCenter=TRUE -> [-32768,32767] * Level=32767 and DoCenter=false -> [0,32767] * Level=16383 and DoCenter=false -> [0,16383] (to avoid overflow with DMA sound on STe) */ static void YM2149_Normalise_5bit_Table(ymu16 *in_5bit , yms16 *out_5bit, unsigned int Level, bool DoCenter) { if ( Level ) { int h; int Max = in_5bit[0x7fff]; int Center = (Level+1)>>1; //fprintf ( stderr , "level %d max %d center %d\n" , Level, Max, Center ); /* Change the amplitude of the signal to 'level' : [0,max] -> [0,level] */ /* Then optionally center the signal around Level/2 */ /* This means we go from sthg like [0,65535] to [-32768, 32767] if Level=65535 and DoCenter=TRUE */ for (h=0; h<32*32*32; h++) { int tmp = in_5bit[h], res; res = tmp * Level / Max; if ( DoCenter ) res -= Center; out_5bit[h] = res; //fprintf ( stderr , "h %d in %d out %d\n" , h , tmp , res ); } } } /*-----------------------------------------------------------------------*/ /** * Precompute all 16 possible envelopes. * Each envelope is made of 3 blocks of 32 volumes. */ static void YM2149_EnvBuild ( void ) { int env; int block; int vol=0 , inc=0; int i; for ( env=0 ; env<16 ; env++ ) /* 16 possible envelopes */ for ( block=0 ; block<3 ; block++ ) /* 3 blocks to define an envelope */ { switch ( YmEnvDef[ env ][ block ] ) { case ENV_GODOWN : vol=31 ; inc=-1 ; break; case ENV_GOUP : vol=0 ; inc=1 ; break; case ENV_DOWN : vol=0 ; inc=0 ; break; case ENV_UP : vol=31 ; inc=0 ; break; } for ( i=0 ; i<32 ; i++ ) /* 32 volumes per block */ { YmEnvWaves[ env ][ block*32 + i ] = YM_MERGE_VOICE ( vol , vol , vol ); vol += inc; } } } /*-----------------------------------------------------------------------*/ /** * Depending on the YM mixing method, build the table used to convert * the 3 YM volumes into a single sample. */ static void Ym2149_BuildVolumeTable(void) { /* Depending on the volume mixing method, we use a table based on real measures */ /* or a table based on a linear volume mixing. */ if ( YmVolumeMixing == YM_MODEL_MIXING ) YM2149_BuildModelVolumeTable(ymout5_u16); /* create 32*32*32 circuit analysed model of the volume table */ else if ( YmVolumeMixing == YM_TABLE_MIXING ) interpolate_volumetable(ymout5_u16); /* expand the 16*16*16 values in volumetable_original to 32*32*32 */ else YM2149_BuildLinearVolumeTable(ymout5_u16); /* combine the 32 possible volumes */ /* Normalise/center the values (convert from u16 to s16) */ /* On STE/TT, we use YM_OUTPUT_LEVEL>>1 to avoid overflow with DMA sound */ if (Config_IsMachineSTE() || Config_IsMachineTT()) YM2149_Normalise_5bit_Table ( ymout5_u16[0][0] , ymout5 , (YM_OUTPUT_LEVEL>>1) , YM_OUTPUT_CENTERED ); else YM2149_Normalise_5bit_Table ( ymout5_u16[0][0] , ymout5 , YM_OUTPUT_LEVEL , YM_OUTPUT_CENTERED ); } /*-----------------------------------------------------------------------*/ /** * Convert a CPU clock value (as in CyclesGlobalClockCounter) * into a 250 kHz YM2149 clock. * * NOTE : we should not use this simple method : * Clock_250 = CpuClock / ( 32 << nCpuFreqShift ) * because it won't work if nCpuFreqShift is changed on the fly (when the * CPU goes from 8 MHz to 16 MHz in the case of the MegaSTE for example) * * To get the correct 250 kHZ clock, we must compute how many CpuClock units * elapsed since the previous call and convert this increment into * an increment for the 250 kHz clock * After each call the remainder will be saved to be used on the next call */ #if 0 /* integer version : use it when YM2149's clock is the same as CPU's clock (eg STF) */ static void YM2149_UpdateClock_250_int ( uint64_t CpuClock ) { uint64_t CpuClockDiff; uint64_t YM_Div; uint64_t YM_Inc; /* We divide CpuClockDiff by YM_Div to get a 250 Hz YM clock increment (YM_Div=32 for an STF with a 8 MHz CPU) */ YM_Div = 32 << nCpuFreqShift; //fprintf ( stderr , "ym_div %lu %f\n" , YM_Div , ((double)MachineClocks.CPU_Freq_Emul) / YM_ATARI_CLOCK_COUNTER ); /* We update YM2149_Clock_250 only if enough CpuClock units elapsed (at least YM_Div) */ CpuClockDiff = CpuClock - YM2149_Clock_250_CpuClock; if ( CpuClockDiff >= YM_Div ) { YM_Inc = CpuClockDiff / YM_Div; /* truncate to lower integer */ //fprintf ( stderr , "update_250 in div=%lu clock_cpu=%lu cpu_diff=%lu inc=%lu clock_250_in=%lu\n" , YM_Div, CpuClock, CpuClockDiff, YM_Inc, YM2149_Clock_250 ); YM2149_Clock_250 += YM_Inc; YM2149_Clock_250_CpuClock = CpuClock - CpuClockDiff % YM_Div; //fprintf ( stderr , "update_250 out div=%lu clock_cpu=%lu cpu_diff=%lu inc=%lu clock_250_in=%lu\n" , YM_Div, CpuClock, CpuClockDiff, YM_Inc, YM2149_Clock_250 ); } //fprintf ( stderr , "update_250 clock_cpu=%ld -> ym_inc=%ld clock_250=%ld clock_250_cpu_clock=%ld\n" , CpuClock , YM_Inc , YM2149_Clock_250 , YM2149_Clock_250_CpuClock ); } /* floating point version : use it when YM2149's clock is different from CPU's clock (eg STE) */ static void YM2149_UpdateClock_250_float ( uint64_t CpuClock ) { uint64_t CpuClockDiff; double YM_Div; uint64_t YM_Inc; /* We divide CpuClockDiff by YM_Div to get a 250 Hz YM clock increment (YM_Div=32.0425 for an STE with a 8 MHz CPU) */ YM_Div = ((double)MachineClocks.CPU_Freq_Emul) / YM_ATARI_CLOCK_COUNTER; //fprintf ( stderr , "ym_div %f\n" , YM_Div ); /* We update YM2149_Clock_250 only if enough CpuClock units elapsed (at least YM_Div) */ CpuClockDiff = CpuClock - YM2149_Clock_250_CpuClock; if ( CpuClockDiff >= YM_Div ) { YM_Inc = CpuClockDiff / YM_Div; /* will truncate to lower integer when casting to uint64_t */ //fprintf ( stderr , "update_250 in div=%f clock_cpu=%lu cpu_diff=%lu inc=%lu clock_250_in=%lu\n" , YM_Div, CpuClock, CpuClockDiff, YM_Inc, YM2149_Clock_250 ); YM2149_Clock_250 += YM_Inc; YM2149_Clock_250_CpuClock = CpuClock - round ( fmod ( CpuClockDiff , YM_Div ) ); //fprintf ( stderr , "update_250 out div=%f clock_cpu=%lu cpu_diff=%lu inc=%lu clock_250_in=%lu\n" , YM_Div, CpuClock, CpuClockDiff, YM_Inc, YM2149_Clock_250 ); } //fprintf ( stderr , "update_250 clock_cpu=%ld -> ym_inc=%ld clock_250=%ld clock_250_cpu_clock=%ld\n" , CpuClock , YM_Inc , YM2149_Clock_250 , YM2149_Clock_250_CpuClock ); } #endif static void YM2149_UpdateClock_250_int_new ( uint64_t CpuClock ) { uint64_t CpuClockDiff; CpuClockDiff = CpuClock - YM2149_Clock_250_CpuClock; ClocksTimings_ConvertCycles ( CpuClockDiff , MachineClocks.CPU_Freq_Emul , &YM2149_ConvertCycles_250 , YM_ATARI_CLOCK_COUNTER ); YM2149_Clock_250 += YM2149_ConvertCycles_250.Cycles; YM2149_Clock_250_CpuClock = CpuClock; //fprintf ( stderr , "update_250_new out clock_cpu=%lu cpu_diff=%lu inc=%lu rem=%lu clock_250_in=%lu\n" , CpuClock, CpuClockDiff, YM2149_ConvertCycles_250.Cycles, YM2149_ConvertCycles_250.Remainder , YM2149_Clock_250 ); //fprintf ( stderr , "update_250 clock_cpu=%ld -> ym_inc=%ld clock_250=%ld clock_250_cpu_clock=%ld\n" , CpuClock , YM2149_ConvertCycles_250.Cycles , YM2149_Clock_250 , YM2149_Clock_250_CpuClock ); } /* * In case of STF/MegaST, we use the 'integer' version that should give less rounding * than the 'floating point' version. It should slightly faster too. * For other machines, we use the 'floating point' version because CPU and YM/DMA Audio don't * share the same clock. * * In the end, 'integer' and 'floating point' versions will sound the same because * floating point precision should be good enough to avoid rounding errors. */ static void YM2149_UpdateClock_250 ( uint64_t CpuClock ) { if ( ConfigureParams.System.nMachineType == MACHINE_ST || ConfigureParams.System.nMachineType == MACHINE_MEGA_ST ) { // YM2149_UpdateClock_250_int ( CpuClock ); YM2149_UpdateClock_250_int_new ( CpuClock ); } else // YM2149_UpdateClock_250_float ( CpuClock ); YM2149_UpdateClock_250_int_new ( CpuClock ); } /*-----------------------------------------------------------------------*/ /** * Init some internal tables for faster results (env, volume) * and reset the internal states. */ static void Ym2149_Init(void) { /* Build the 16 envelope shapes */ YM2149_EnvBuild(); /* Build the volume conversion table */ Ym2149_BuildVolumeTable(); /* Reset YM2149 internal states */ Ym2149_Reset(); /* Reset 250 Hz clock */ YM2149_Clock_250 = 0; YM2149_Clock_250_CpuClock = CyclesGlobalClockCounter; /* Clear internal YM audio buffer at 250 kHz */ memset ( YM_Buffer_250 , 0 , sizeof(YM_Buffer_250) ); YM_Buffer_250_pos_write = 0; YM_Buffer_250_pos_read = 0; } /*-----------------------------------------------------------------------*/ /** * Reset all ym registers as well as the internal variables */ static void Ym2149_Reset(void) { int i; for ( i=0 ; i<14 ; i++ ) Sound_WriteReg ( i , 0 ); Sound_WriteReg ( 7 , 0xff ); /* Reset internal variables and counters */ ToneA_per = ToneA_count = 0; ToneB_per = ToneB_count = 0; ToneC_per = ToneC_count = 0; Noise_per = Noise_count = 0; Env_per = Env_count = 0; Env_shape = Env_pos = 0; ToneA_val = ToneB_val = ToneC_val = Noise_val = YM_SQUARE_DOWN; RndRack = 1; } /*-----------------------------------------------------------------------*/ /** * Returns a pseudo random value, used to generate white noise. * As measured by David Savinkoff, the YM2149 uses a 17 stage LSFR with * 2 taps (17,14) */ static ymu32 YM2149_RndCompute(void) { /* 17 stage, 2 taps (17, 14) LFSR */ if (RndRack & 1) { RndRack = RndRack>>1 ^ 0x12000; /* bits 17 and 14 are ones */ return 0xffff; } else { RndRack >>= 1; return 0; } } static ymu16 YM2149_TonePer(ymu8 rHigh , ymu8 rLow) { ymu16 per; per = ( ( rHigh & 0x0f ) << 8 ) + rLow; return per; } static ymu16 YM2149_NoisePer(ymu8 rNoise) { ymu16 per; per = rNoise & 0x1f; return per; } static ymu16 YM2149_EnvPer(ymu8 rHigh , ymu8 rLow) { ymu16 per; per = ( rHigh << 8 ) + rLow; return per; } /*-----------------------------------------------------------------------*/ /** * Main function : compute the value of the next sample. * Mixes all 3 voices with tone+noise+env and apply low pass * filter if needed. * For maximum accuracy, this function emulates all single cycles at 250 kHz * As output we get a "raw" 250 kHz signal that will be later downsampled * to the chosen output frequency (eg 44.1 kHz) * Creating a complete 250 kHz signal allow to emulate effects that require * precise cycle accuracy (such as "syncsquare" used in maxYMiser v1.53) */ static void YM2149_DoSamples_250 ( int SamplesToGenerate_250 ) { ymsample sample; ymu32 bt; ymu16 Env3Voices; /* 0x00CCBBAA */ ymu16 Tone3Voices; /* 0x00CCBBAA */ int pos; int n; //fprintf ( stderr , "ym2149_dosamples_250 in nb=%d ym_pos_wr=%d\n",SamplesToGenerate_250 , YM_Buffer_250_pos_write ); /* We write new samples at position YM_Buffer_250_pos_write while we read them at the same time */ /* at position YM_Buffer_250_pos_read (to create the output at YM_REPLAY_FREQ) */ /* This means we must ensure YM_Buffer_250[] is large enough to avoid overwriting data */ /* that are not read yet */ pos = YM_Buffer_250_pos_write; /* Emulate as many internal YM cycles as needed to generate samples */ for ( n=0 ; n= Noise_per ) { Noise_count = 0; Noise_val = YM2149_RndCompute();/* 0 or 0xffff */ } //fprintf ( stderr , "ym2149_dosamples_250 max=%d n=%d ToneA_count=%d ToneA_per=%d val=%x pos=%d\n",SamplesToGenerate_250,n,ToneA_count,ToneA_per,ToneA_val,pos ); /* Other counters are increased on every call, at 250 KHz */ ToneA_count++; if ( ToneA_count >= ToneA_per ) { //fprintf ( stderr , "ym2149_dosamples_250 max=%d n=%d ToneA_count=%d ToneA_per=%d val=%x pos=%d toggle\n",SamplesToGenerate_250,n,ToneA_count,ToneA_per,ToneA_val,pos ); ToneA_count = 0; ToneA_val ^= YM_SQUARE_UP; /* 0 or 0x1f */ } ToneB_count++; if ( ToneB_count >= ToneB_per ) { ToneB_count = 0; ToneB_val ^= YM_SQUARE_UP; /* 0 or 0x1f */ } ToneC_count++; if ( ToneC_count >= ToneC_per ) { ToneC_count = 0; ToneC_val ^= YM_SQUARE_UP; /* 0 or 0x1f */ } Env_count += 1; if ( Env_count >= Env_per ) { Env_count = 0; Env_pos += 1; if ( Env_pos >= 3*32 ) /* blocks 0, 1 and 2 were used (Env_pos 0 to 95) */ Env_pos -= 2*32; /* replay/loop blocks 1 and 2 (Env_pos 32 to 95) */ } /* Build 'sample' value with the values of tone/noise/volume/env */ /* Get the 5 bits volume corresponding to the current envelope's position */ Env3Voices = YmEnvWaves[ Env_shape ][ Env_pos ]; Env3Voices &= EnvMask3Voices; /* only keep volumes for voices using envelope */ /* Tone3Voices will contain the output state of each voice : 0 or 0x1f */ bt = (ToneA_val | mixerTA) & (Noise_val | mixerNA); /* 0 or 0xffff */ Tone3Voices = bt & YM_MASK_1VOICE; /* 0 or 0x1f */ bt = (ToneB_val | mixerTB) & (Noise_val | mixerNB); Tone3Voices |= ( bt & YM_MASK_1VOICE ) << 5; bt = (ToneC_val | mixerTC) & (Noise_val | mixerNC); Tone3Voices |= ( bt & YM_MASK_1VOICE ) << 10; /* Combine fixed volumes and envelope volumes and keep the resulting */ /* volumes depending on the output state of each voice (0 or 0x1f) */ Tone3Voices &= ( Env3Voices | Vol3Voices ); sample = ymout5[ Tone3Voices ]; /* 16 bits signed value */ /* Apply low pass filter ? */ if ( YM2149_LPF_Filter == YM2149_LPF_FILTER_LPF_STF ) sample = LowPassFilter ( sample ); else if ( YM2149_LPF_Filter == YM2149_LPF_FILTER_PWM ) sample = PWMaliasFilter ( sample ); /* Store sample */ YM_Buffer_250[ pos ] = sample; pos = ( pos + 1 ) & YM_BUFFER_250_SIZE_MASK; } #ifdef YM_250_DEBUG /* write raw 250 kHz samples into a wav file */ YM2149_DoSamples_250_Debug ( SamplesToGenerate_250 , YM_Buffer_250_pos_write ); #endif YM_Buffer_250_pos_write = pos; //fprintf ( stderr , "ym2149_dosamples_250 out nb=%d ym_pos_wr=%d\n",SamplesToGenerate_250 , YM_Buffer_250_pos_write ); } #ifdef YM_250_DEBUG /*-----------------------------------------------------------------------*/ /** * Write raw 250 kHz samples into a wav sound file as "mono + signed 16 bit PCM + little endian" * This is used to compare sound before downsampling at native output freq (eg 44.1 kHz) * and to measure the quality of the downsampling method */ static void YM2149_DoSamples_250_Debug ( int SamplesToGenerate , int pos ) { static uint8_t WavHeader[] = { /* RIFF chunk */ 'R', 'I', 'F', 'F', /* "RIFF" (ASCII Characters) */ 0, 0, 0, 0, /* Total Length Of Package To Follow (patched when file is closed) */ 'W', 'A', 'V', 'E', /* "WAVE" (ASCII Characters) */ /* Format chunk */ 'f', 'm', 't', ' ', /* "fmt_" (ASCII Characters) */ 0x10, 0, 0, 0, /* Length Of FORMAT Chunk (always 0x10) */ 0x01, 0, /* Always 0x01 */ 0x02, 0, /* Number of channels (2 for stereo) */ 0, 0, 0, 0, /* Sample rate (patched when file header is written) */ 0, 0, 0, 0, /* Bytes per second (patched when file header is written) */ 0x04, 0, /* Bytes per sample (4 = 16 bit stereo) */ 0x10, 0, /* Bits per sample (16 bit) */ /* Data chunk */ 'd', 'a', 't', 'a', 0, 0, 0, 0, /* Length of data to follow (will be patched when file is closed) */ }; FILE *file_ptr; int val; ymsample sample; int n; static int wav_size; if ( File_Exists ( "hatari_250.wav" ) ) { file_ptr = fopen( "hatari_250.wav", "rb+"); fseek ( file_ptr , 0 , SEEK_END ); } else { file_ptr = fopen( "hatari_250.wav", "wb"); /* Patch mono, 2 bytes per sample */ WavHeader[22] = (uint8_t)0x01; WavHeader[32] = (uint8_t)0x02; /* Patch sample frequency in header structure */ val = 250000; WavHeader[24] = (uint8_t)val; WavHeader[25] = (uint8_t)(val >> 8); WavHeader[26] = (uint8_t)(val >> 16); WavHeader[27] = (uint8_t)(val >> 24); /* Patch bytes per second in header structure */ val = 250000 * 2; WavHeader[28] = (uint8_t)val; WavHeader[29] = (uint8_t)(val >> 8); WavHeader[30] = (uint8_t)(val >> 16); WavHeader[31] = (uint8_t)(val >> 24); fwrite ( &WavHeader, sizeof(WavHeader), 1, file_ptr ); } for ( n=0 ; n> 8); WavHeader[6] = (uint8_t)(val >> 16); WavHeader[7] = (uint8_t)(val >> 24); val = wav_size; /* data size */ WavHeader[40] = (uint8_t)val; WavHeader[41] = (uint8_t)(val >> 8); WavHeader[42] = (uint8_t)(val >> 16); WavHeader[43] = (uint8_t)(val >> 24); rewind ( file_ptr ); fwrite ( &WavHeader, sizeof(WavHeader), 1, file_ptr ); fclose ( file_ptr ); } #endif /*-----------------------------------------------------------------------*/ /** * Run internal YM2149 emulation, producing as much samples as needed * for this time range. * We compute how many CPU cycles passed since the previous call to YM2149_Run * (using CyclesGlobalClockCounter) and we convert this into a number * of internal YM2149 updates at 250 kHz. * When the CPU runs at 8 MHz, the YM2149 runs at 1/4 of this freq (2 MHz), * so it takes 32 CPU cycles to do 1 internal YM2149 update at 250 kHz. * (when cpu runs at higher freq, we must take nCpuFreqShift into account) * * On each call, we consider samples were already generated up to (and including) counter value * YM2149_Clock_250_prev. We must generate as many samples to reach (and include) YM2149_Clock_250. */ static void YM2149_Run ( uint64_t CPU_Clock ) { uint64_t YM2149_Clock_250_prev; int YM2149_Nb_Updates_250; YM2149_Clock_250_prev = YM2149_Clock_250; YM2149_UpdateClock_250 ( CPU_Clock ); YM2149_Nb_Updates_250 = YM2149_Clock_250 - YM2149_Clock_250_prev; if ( YM2149_Nb_Updates_250 > 0 ) { YM2149_DoSamples_250 ( YM2149_Nb_Updates_250 ); } } /*-----------------------------------------------------------------------*/ /** * Downsample the YM2149 samples data from 250 KHz to YM_REPLAY_FREQ and * return the next sample to output. * * This method will choose the nearest sample from the input buffer * which can be not precise enough when frequencies from the input * buffer are much higher than YM_REPLAY_FREQ (see Nyquist rate) * * advantage : fast method * disadvantage : more aliasing when high frequency notes are played */ static ymsample YM2149_Next_Resample_Nearest ( void ) { ymsample sample; /* Get the nearest sample at pos_read or pos_read+1 */ if ( pos_fract_nearest < 0.5 ) sample = YM_Buffer_250[ YM_Buffer_250_pos_read ]; else sample = YM_Buffer_250[ ( YM_Buffer_250_pos_read + 1 ) & YM_BUFFER_250_SIZE_MASK ]; /* Increase fractional pos and integer pos */ pos_fract_nearest += ( (double)YM_ATARI_CLOCK_COUNTER ) / YM_REPLAY_FREQ; YM_Buffer_250_pos_read = ( YM_Buffer_250_pos_read + (int)pos_fract_nearest ) & YM_BUFFER_250_SIZE_MASK; pos_fract_nearest -= (int)pos_fract_nearest; /* 0 <= pos_fract_nearest < 1 */ return sample; } /*-----------------------------------------------------------------------*/ /** * Downsample the YM2149 samples data from 250 KHz to YM_REPLAY_FREQ and * return the next sample to output. * * This method will do a weighted average between the 2 input samples * surrounding the theoretical position of the sample we want to generate * * It's a little slower than 'Resample_Nearest' but more accurate */ static ymsample YM2149_Next_Resample_Weighted_Average_2 ( void ) { ymsample sample_before , sample_after; ymsample sample; /* Get the 2 samples that surround pos_read and do a weighted average */ sample_before = YM_Buffer_250[ YM_Buffer_250_pos_read ]; sample_after = YM_Buffer_250[ ( YM_Buffer_250_pos_read + 1 ) & YM_BUFFER_250_SIZE_MASK ]; sample = round ( ( 1.0 - pos_fract_weighted_2 ) * sample_before + pos_fract_weighted_2 * sample_after ); //fprintf ( stderr , "b=%04x a=%04x frac=%f -> res=%04x\n" , sample_before , sample_after , pos_fract_weighted_2 , sample ); /* Increase fractional pos and integer pos */ pos_fract_weighted_2 += ( (double)YM_ATARI_CLOCK_COUNTER ) / YM_REPLAY_FREQ; YM_Buffer_250_pos_read = ( YM_Buffer_250_pos_read + (int)pos_fract_weighted_2 ) & YM_BUFFER_250_SIZE_MASK; pos_fract_weighted_2 -= (int)pos_fract_weighted_2; /* 0 <= pos_fract < 1 */ return sample; } /*-----------------------------------------------------------------------*/ /** * Downsample the YM2149 samples data from 250 KHz to YM_REPLAY_FREQ and * return the next sample to output. * * This method will do a weighted average of all the sample from the input * buffer that surround an output sample (for example 250 KHz / 44.1 KHz would * do a weighted average on ~5.66 input samples) * It is based on the resample function used in MAME which computes * the average energy on an interval by summing samples (see src/emu/sound.c in MAME) * * It's slower than 'Weighted_Average_2' but it's more accurate as it uses all the * samples from the input buffer and works better when the input signal has some very * high frequencies (eg when YM voice uses period 0 to 6) * * For better accuracy without using floating point, fractional values are multiplied * by 0x10000 and stored using 32 or 64 bits : upper bits are the integer part and * lower 16 bits are the decimal part. */ static ymsample YM2149_Next_Resample_Weighted_Average_N ( void ) { uint32_t interval_fract; int64_t total; ymsample sample; interval_fract = ( YM_ATARI_CLOCK_COUNTER * 0x10000LL ) / YM_REPLAY_FREQ; /* 'LL' ensure the div is made on 64 bits */ total = 0; //fprintf ( stderr , "next 1 clock=%d freq=%d interval=%x %d\n" , YM_ATARI_CLOCK_COUNTER , YM_REPLAY_FREQ , interval_fract , YM_Buffer_250_pos_read ); if ( pos_fract_weighted_n ) /* start position : 0xffff <= pos_fract_weighted_n <= 0 */ { total += ((int64_t)YM_Buffer_250[ YM_Buffer_250_pos_read ]) * ( 0x10000 - pos_fract_weighted_n ); YM_Buffer_250_pos_read = ( YM_Buffer_250_pos_read + 1 ) & YM_BUFFER_250_SIZE_MASK; pos_fract_weighted_n -= 0x10000; /* next sample */ } pos_fract_weighted_n += interval_fract; /* end position */ while ( pos_fract_weighted_n & 0xffff0000 ) /* check integer part */ { total += ((int64_t)YM_Buffer_250[ YM_Buffer_250_pos_read ]) * 0x10000; YM_Buffer_250_pos_read = ( YM_Buffer_250_pos_read + 1 ) & YM_BUFFER_250_SIZE_MASK; pos_fract_weighted_n -= 0x10000; /* next sample */ } if ( pos_fract_weighted_n ) /* partial end sample if 0xffff <= pos_fract_weighted_n < 0 */ { total += ((int64_t)YM_Buffer_250[ YM_Buffer_250_pos_read ]) * pos_fract_weighted_n; } //fprintf ( stderr , "next 2 %d\n" , YM_Buffer_250_pos_read ); sample = total / interval_fract; return sample; } static ymsample YM2149_NextSample_250 ( void ) { if ( YM2149_Resample_Method == YM2149_RESAMPLE_METHOD_WEIGHTED_AVERAGE_2 ) return YM2149_Next_Resample_Weighted_Average_2 (); else if ( YM2149_Resample_Method == YM2149_RESAMPLE_METHOD_NEAREST ) return YM2149_Next_Resample_Nearest (); else if ( YM2149_Resample_Method == YM2149_RESAMPLE_METHOD_WEIGHTED_AVERAGE_N ) return YM2149_Next_Resample_Weighted_Average_N (); else return 0; } /*-----------------------------------------------------------------------*/ /** * Update internal variables (steps, volume masks, ...) each * time an YM register is changed. */ #define BIT_SHIFT 24 void Sound_WriteReg(int reg, uint8_t data) { switch (reg) { case 0: SoundRegs[0] = data; ToneA_per = YM2149_TonePer ( SoundRegs[1] , SoundRegs[0] ); break; case 1: SoundRegs[1] = data & 0x0f; ToneA_per = YM2149_TonePer ( SoundRegs[1] , SoundRegs[0] ); break; case 2: SoundRegs[2] = data; ToneB_per = YM2149_TonePer ( SoundRegs[3] , SoundRegs[2] ); break; case 3: SoundRegs[3] = data & 0x0f; ToneB_per = YM2149_TonePer ( SoundRegs[3] , SoundRegs[2] ); break; case 4: SoundRegs[4] = data; ToneC_per = YM2149_TonePer ( SoundRegs[5] , SoundRegs[4] ); break; case 5: SoundRegs[5] = data & 0x0f; ToneC_per = YM2149_TonePer ( SoundRegs[5] , SoundRegs[4] ); break; case 6: SoundRegs[6] = data & 0x1f; Noise_per = YM2149_NoisePer ( SoundRegs[6] ); break; case 7: SoundRegs[7] = data & 0x3f; /* ignore bits 6 and 7 */ mixerTA = (data&(1<<0)) ? 0xffff : 0; mixerTB = (data&(1<<1)) ? 0xffff : 0; mixerTC = (data&(1<<2)) ? 0xffff : 0; mixerNA = (data&(1<<3)) ? 0xffff : 0; mixerNB = (data&(1<<4)) ? 0xffff : 0; mixerNC = (data&(1<<5)) ? 0xffff : 0; break; case 8: SoundRegs[8] = data & 0x1f; if ( data & 0x10 ) { EnvMask3Voices |= YM_MASK_A; /* env ON */ Vol3Voices &= ~YM_MASK_A; /* fixed vol OFF */ } else { EnvMask3Voices &= ~YM_MASK_A; /* env OFF */ Vol3Voices &= ~YM_MASK_A; /* clear previous vol */ Vol3Voices |= YmVolume4to5[ SoundRegs[8] ]; /* fixed vol ON */ } break; case 9: SoundRegs[9] = data & 0x1f; if ( data & 0x10 ) { EnvMask3Voices |= YM_MASK_B; /* env ON */ Vol3Voices &= ~YM_MASK_B; /* fixed vol OFF */ } else { EnvMask3Voices &= ~YM_MASK_B; /* env OFF */ Vol3Voices &= ~YM_MASK_B; /* clear previous vol */ Vol3Voices |= ( YmVolume4to5[ SoundRegs[9] ] ) << 5; /* fixed vol ON */ } break; case 10: SoundRegs[10] = data & 0x1f; if ( data & 0x10 ) { EnvMask3Voices |= YM_MASK_C; /* env ON */ Vol3Voices &= ~YM_MASK_C; /* fixed vol OFF */ } else { EnvMask3Voices &= ~YM_MASK_C; /* env OFF */ Vol3Voices &= ~YM_MASK_C; /* clear previous vol */ Vol3Voices |= ( YmVolume4to5[ SoundRegs[10] ] ) << 10; /* fixed vol ON */ } break; case 11: SoundRegs[11] = data; Env_per = YM2149_EnvPer ( SoundRegs[12] , SoundRegs[11] ); break; case 12: SoundRegs[12] = data; Env_per = YM2149_EnvPer ( SoundRegs[12] , SoundRegs[11] ); break; case 13: SoundRegs[13] = data & 0xf; Env_pos = 0; /* when writing to Env_shape, we must reset the Env_pos */ Env_count = 0; /* this also starts a new phase */ Env_shape = SoundRegs[13]; bEnvelopeFreqFlag = true; /* used for YmFormat saving */ break; } } /*-----------------------------------------------------------------------*/ /** * Init random generator, sound tables and envelopes * (called only once when Hatari starts) */ void Sound_Init(void) { /* Build volume/env tables, ... */ Ym2149_Init(); Sound_Reset(); } /*-----------------------------------------------------------------------*/ /** * Reset the sound emulation (called from Reset_ST() in reset.c) */ void Sound_Reset(void) { /* Lock audio system before accessing variables which are used by the * callback function, too! */ Audio_Lock(); /* Clear sound mixing buffer: */ memset(AudioMixBuffer, 0, sizeof(AudioMixBuffer)); /* Clear buffer index and register '13' flags */ bEnvelopeFreqFlag = false; AudioMixBuffer_pos_read = 0; /* We do not start with 0 here to fake some initial samples: */ nGeneratedSamples = SoundBufferSize + SAMPLES_PER_FRAME; AudioMixBuffer_pos_write = nGeneratedSamples & AUDIOMIXBUFFER_SIZE_MASK; AudioMixBuffer_pos_write_avi = AudioMixBuffer_pos_write; //fprintf ( stderr , "Sound_Reset SoundBufferSize %d SAMPLES_PER_FRAME %d nGeneratedSamples %d , AudioMixBuffer_pos_write %d\n" , // SoundBufferSize , SAMPLES_PER_FRAME, nGeneratedSamples , AudioMixBuffer_pos_write ); Ym2149_Reset(); Audio_Unlock(); } /*-----------------------------------------------------------------------*/ /** * Reset the sound buffer index variables. * Very important : this function should only be called by setting * Sound_BufferIndexNeedReset=true */ void Sound_ResetBufferIndex(void) { Audio_Lock(); nGeneratedSamples = SoundBufferSize + SAMPLES_PER_FRAME; AudioMixBuffer_pos_write = (AudioMixBuffer_pos_read + nGeneratedSamples) & AUDIOMIXBUFFER_SIZE_MASK; AudioMixBuffer_pos_write_avi = AudioMixBuffer_pos_write; //fprintf ( stderr , "Sound_ResetBufferIndex SoundBufferSize %d SAMPLES_PER_FRAME %d nGeneratedSamples %d , AudioMixBuffer_pos_write %d\n" , // SoundBufferSize , SAMPLES_PER_FRAME, nGeneratedSamples , AudioMixBuffer_pos_write ); Audio_Unlock(); } /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void Sound_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&ToneA_per, sizeof(ToneA_per)); MemorySnapShot_Store(&ToneA_count, sizeof(ToneA_count)); MemorySnapShot_Store(&ToneA_val, sizeof(ToneA_val)); MemorySnapShot_Store(&ToneB_per, sizeof(ToneB_per)); MemorySnapShot_Store(&ToneB_count, sizeof(ToneB_count)); MemorySnapShot_Store(&ToneB_val, sizeof(ToneB_val)); MemorySnapShot_Store(&ToneC_per, sizeof(ToneC_per)); MemorySnapShot_Store(&ToneC_count, sizeof(ToneC_count)); MemorySnapShot_Store(&ToneC_val, sizeof(ToneC_val)); MemorySnapShot_Store(&Noise_per, sizeof(Noise_per)); MemorySnapShot_Store(&Noise_count, sizeof(Noise_count)); MemorySnapShot_Store(&Noise_val, sizeof(Noise_val)); MemorySnapShot_Store(&Env_per, sizeof(Env_per)); MemorySnapShot_Store(&Env_count, sizeof(Env_count)); MemorySnapShot_Store(&Env_pos, sizeof(Env_pos)); MemorySnapShot_Store(&Env_shape, sizeof(Env_shape)); MemorySnapShot_Store(&mixerTA, sizeof(mixerTA)); MemorySnapShot_Store(&mixerTB, sizeof(mixerTB)); MemorySnapShot_Store(&mixerTC, sizeof(mixerTC)); MemorySnapShot_Store(&mixerNA, sizeof(mixerNA)); MemorySnapShot_Store(&mixerNB, sizeof(mixerNB)); MemorySnapShot_Store(&mixerNC, sizeof(mixerNC)); MemorySnapShot_Store(&RndRack, sizeof(RndRack)); MemorySnapShot_Store(&EnvMask3Voices, sizeof(EnvMask3Voices)); MemorySnapShot_Store(&Vol3Voices, sizeof(Vol3Voices)); MemorySnapShot_Store(SoundRegs, sizeof(SoundRegs)); MemorySnapShot_Store(&YM2149_Clock_250, sizeof(YM2149_Clock_250)); MemorySnapShot_Store(&YM2149_Clock_250_CpuClock, sizeof(YM2149_Clock_250_CpuClock)); MemorySnapShot_Store(&YM2149_Freq_div_2, sizeof(YM2149_Freq_div_2)); MemorySnapShot_Store(&YmVolumeMixing, sizeof(YmVolumeMixing)); MemorySnapShot_Store(&YM_Buffer_250, sizeof(YM_Buffer_250)); MemorySnapShot_Store(&YM_Buffer_250_pos_write, sizeof(YM_Buffer_250_pos_write)); MemorySnapShot_Store(&YM_Buffer_250_pos_read, sizeof(YM_Buffer_250_pos_read)); MemorySnapShot_Store(&YM2149_ConvertCycles_250, sizeof(YM2149_ConvertCycles_250)); MemorySnapShot_Store(&pos_fract_nearest, sizeof(pos_fract_nearest)); MemorySnapShot_Store(&pos_fract_weighted_2, sizeof(pos_fract_weighted_2)); MemorySnapShot_Store(&pos_fract_weighted_n, sizeof(pos_fract_weighted_n)); } /*-----------------------------------------------------------------------*/ /** * Store how many samples were generated during one VBL */ static void Sound_Stats_Add ( int Samples_Nbr ) { Sound_Stats_Array[ Sound_Stats_Index++ ] = Samples_Nbr; if ( Sound_Stats_Index == SOUND_STATS_SIZE ) Sound_Stats_Index = 0; } /*-----------------------------------------------------------------------*/ /** * Use all the numbers of samples per vbl to show an estimate of the * final number of generated samples during 1 second. This value should * stay as close as possible over time to the chosen audio frequency (eg 44100 Hz). * If not, it means the accuracy should be improved when generating YM samples */ void Sound_Stats_Show ( void ) { int i; double sum; double vbl_per_sec; double freq_gen; double freq_diff; static double diff_min=0, diff_max=0; sum = 0; for ( i=0 ; i -40 ) && ( freq_diff < diff_min ) ) diff_min = freq_diff; if ( ( freq_diff > 0 ) && ( freq_diff < 40 ) && ( freq_diff > diff_max ) ) diff_max = freq_diff; fprintf ( stderr , "Sound_Stats_Show vbl_per_sec=%.4f freq_gen=%.4f freq_diff=%.4f (min=%.4f max=%.4f)\n" , vbl_per_sec , freq_gen , freq_diff , diff_min , diff_max ); } /*-----------------------------------------------------------------------*/ /** * Generate output samples for all channels (YM2149, DMA or crossbar) during this time-frame */ static int Sound_GenerateSamples(uint64_t CPU_Clock) { int idx; int ym_margin; int Sample_Nbr; //fprintf ( stderr , "sound_gen in ym_pos_rd=%d ym_pos_wr=%d clock=%ld\n" , YM_Buffer_250_pos_read , YM_Buffer_250_pos_write , CPU_Clock ); /* Run YM2149 emulation at 250 kHz to reach CPU_Clock counter value */ /* This fills YM_Buffer_250[] and update YM_Buffer_250_pos_write */ YM2149_Run ( CPU_Clock ); ym_margin = ceil ( ((double)YM_ATARI_CLOCK_COUNTER) / nAudioFrequency ) + 2; //fprintf ( stderr , "sound_gen margin=%d read_max=%d\n" , ym_margin , ( YM_Buffer_250_pos_write - ym_margin ) & YM_BUFFER_250_SIZE_MASK ); Sample_Nbr = 0; idx = AudioMixBuffer_pos_write & AUDIOMIXBUFFER_SIZE_MASK; if (Config_IsMachineFalcon()) { while ( ( ( YM_Buffer_250_pos_write - YM_Buffer_250_pos_read ) & YM_BUFFER_250_SIZE_MASK ) >= ym_margin ) { AudioMixBuffer[idx][0] = AudioMixBuffer[idx][1] = Subsonic_IIR_HPF_Left( YM2149_NextSample_250() ); idx = ( idx+1 ) & AUDIOMIXBUFFER_SIZE_MASK; Sample_Nbr++; } /* If Falcon emulation, crossbar does the job */ if ( Sample_Nbr > 0 ) Crossbar_GenerateSamples(AudioMixBuffer_pos_write, Sample_Nbr); } else if (!Config_IsMachineST()) { while ( ( ( YM_Buffer_250_pos_write - YM_Buffer_250_pos_read ) & YM_BUFFER_250_SIZE_MASK ) >= ym_margin ) { AudioMixBuffer[idx][0] = AudioMixBuffer[idx][1] = YM2149_NextSample_250(); idx = ( idx+1 ) & AUDIOMIXBUFFER_SIZE_MASK; Sample_Nbr++; } /* If Ste or TT emulation, DmaSnd does mixing and filtering */ if ( Sample_Nbr > 0 ) DmaSnd_GenerateSamples(AudioMixBuffer_pos_write, Sample_Nbr); } else { while ( ( ( YM_Buffer_250_pos_write - YM_Buffer_250_pos_read ) & YM_BUFFER_250_SIZE_MASK ) >= ym_margin ) { AudioMixBuffer[idx][0] = AudioMixBuffer[idx][1] = Subsonic_IIR_HPF_Left( YM2149_NextSample_250() ); idx = ( idx+1 ) & AUDIOMIXBUFFER_SIZE_MASK; Sample_Nbr++; } } AudioMixBuffer_pos_write = (AudioMixBuffer_pos_write + Sample_Nbr) & AUDIOMIXBUFFER_SIZE_MASK; nGeneratedSamples += Sample_Nbr; //fprintf ( stderr , "sound_gen out nb=%d ym_pos_rd=%d ym_pos_wr=%d clock=%ld\n" , Sample_Nbr , YM_Buffer_250_pos_read , YM_Buffer_250_pos_write , CPU_Clock ); return Sample_Nbr; } /*-----------------------------------------------------------------------*/ /** * This is called to built samples up until this clock cycle * Sound_Update() can be called several times during a VBL */ void Sound_Update(uint64_t CPU_Clock) { int pos_write_prev = AudioMixBuffer_pos_write; int Samples_Nbr; int nGeneratedSamples_before; /* Make sure that we don't interfere with the audio callback function */ Audio_Lock(); /* Generate samples */ nGeneratedSamples_before = nGeneratedSamples; Samples_Nbr = Sound_GenerateSamples ( CPU_Clock ); Sound_Stats_SamplePerVBL += Samples_Nbr; //fprintf ( stderr , "sound update vbl=%d hbl=%d nbr=%d\n" , nVBLs , nHBL, Samples_Nbr ); /* Check we don't fill the sound's ring buffer before it's played by Audio_Callback() */ /* This should never happen, except if the system suffers major slowdown due to other */ /* processes or if we run in fast forward mode. */ /* In the case of slowdown, we set Sound_BufferIndexNeedReset to "resync" the working */ /* buffer's index AudioMixBuffer_pos_write with the system buffer's index */ /* AudioMixBuffer_pos_read. */ /* In the case of fast forward, we do nothing here, Sound_BufferIndexNeedReset will be */ /* set when the user exits fast forward mode. */ if ( ( Samples_Nbr > AUDIOMIXBUFFER_SIZE - nGeneratedSamples_before ) && ( ConfigureParams.System.bFastForward == false ) && ( ConfigureParams.Sound.bEnableSound == true ) ) { static int logcnt = 0; if (logcnt++ < 50) { Log_Printf(LOG_WARN, "Your system is too slow, " "some sound samples were not correctly emulated\n"); } Sound_BufferIndexNeedReset = true; } /* Allow audio callback function to occur again */ Audio_Unlock(); /* Save to WAV file, if open */ if (bRecordingWav) WAVFormat_Update(AudioMixBuffer, pos_write_prev, Samples_Nbr); } /*-----------------------------------------------------------------------*/ /** * On the end of each VBL, complete audio buffer up to the current value of CyclesGlobalClockCounter * As Sound_Update() could be called several times during the VBL, the audio * buffer might be already partially filled. * This function should be called from the VBL's handler (in video.c) */ void Sound_Update_VBL(void) { Sound_Update ( CyclesGlobalClockCounter ); /* generate as many samples as needed to fill this VBL */ //fprintf ( stderr , "sound_update_vbl vbl=%d nbr=%d\n" , nVBLs, Sound_Stats_SamplePerVBL ); /* Update some stats */ Sound_Stats_Add ( Sound_Stats_SamplePerVBL ); // Sound_Stats_Show (); /* Reset sound buffer if needed (after pause, fast forward, slow system, ...) */ if ( Sound_BufferIndexNeedReset ) { Sound_ResetBufferIndex (); Sound_BufferIndexNeedReset = false; } /* Record AVI audio frame is necessary */ if ( Avi_AreWeRecording() ) { int Len; Len = AudioMixBuffer_pos_write - AudioMixBuffer_pos_write_avi; /* number of generated samples for this frame */ if ( Len < 0 ) Len += AUDIOMIXBUFFER_SIZE; /* end of ring buffer was reached */ Avi_RecordAudioStream ( AudioMixBuffer , AudioMixBuffer_pos_write_avi , Len ); } AudioMixBuffer_pos_write_avi = AudioMixBuffer_pos_write; /* save new position for next AVI audio frame */ Sound_Stats_SamplePerVBL = 0; /* Clear write to register '13', used for YM file saving */ bEnvelopeFreqFlag = false; } /*-----------------------------------------------------------------------*/ /** * Start recording sound, as .YM or .WAV output */ bool Sound_BeginRecording(char *pszCaptureFileName) { bool bRet; if (!pszCaptureFileName || strlen(pszCaptureFileName) <= 3) { Log_Printf(LOG_ERROR, "Illegal sound recording file name!\n"); return false; } /* Did specify .YM or .WAV? If neither report error */ if (File_DoesFileExtensionMatch(pszCaptureFileName,".ym")) bRet = YMFormat_BeginRecording(pszCaptureFileName); else if (File_DoesFileExtensionMatch(pszCaptureFileName,".wav")) bRet = WAVFormat_OpenFile(pszCaptureFileName); else { Log_AlertDlg(LOG_ERROR, "Unknown Sound Recording format.\n" "Please specify a .YM or .WAV output file."); bRet = false; } return bRet; } /*-----------------------------------------------------------------------*/ /** * End sound recording */ void Sound_EndRecording(void) { /* Stop sound recording and close files */ if (bRecordingYM) YMFormat_EndRecording(); if (bRecordingWav) WAVFormat_CloseFile(); } /*-----------------------------------------------------------------------*/ /** * Are we recording sound data? */ bool Sound_AreWeRecording(void) { return (bRecordingYM || bRecordingWav); } /*-----------------------------------------------------------------------*/ /** * Rebuild volume conversion table */ void Sound_SetYmVolumeMixing(void) { /* Build the volume conversion table */ Ym2149_BuildVolumeTable(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/spec512.c000066400000000000000000000322241504763705000226610ustar00rootroot00000000000000/* Hatari - spec512.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Handle storing of writes to ST palette using clock-cycle counts. We can use this to accurately any form of Spectrum512 style images - even down to the way the screen colours change on decompression routines in menus! As the 68000 has a 4-clock cycle increment we can only change palette every 4 cycles. This means that on one scanline (512 cycles in 50Hz) we have just 512/4=128 places where palette writes can take place. We keep track of this in a table (storing on each scanline and color writes and the cycles on the scanline where they happen). When we draw the screen we simply keep a cycle-count on the line and check this with our table and update the 16-color palette with each change. As the table is already ordered this makes things very simple. Speed is a problem, though, as the palette can change once every 4 pixels - that's a lot of processing. */ /* 2008/01/12 [NP] In Spec512_StoreCyclePalette, don't use Cycles_GetCounterOnWriteAccess */ /* as it doesn't support movem for example. Use Cycles_GetCounter with */ /* an average correction of 8 cycles (this should be fixed). */ /* In Spec512_StartScanLine, better handling of SCREENBYTES_LEFT when */ /* border are present. Better alignment of pixel and color when left */ /* border is removed (Rotofull in Nostalgia Demo, When Colors Are Going */ /* Bang Bang by DF in Punish Your Machine). */ /* 2008/01/13 [NP] In Spec512_StoreCyclePalette, if a movem was used to access several */ /* color regs just before the end of a line, the value for nHorPos was not */ /* checked and could take values >= 512, which means some colors in the */ /* palette were overwritten next time colors were stored on the next line */ /* and the palette was not set correctly, especially in the bottom of the */ /* screen (Decade Demo Main Menu, Punish Your Machine Main Menu, ULM */ /* Hidden Screen in Oh Crickey...). */ /* 2008/01/13 [NP] One line should contain 512/4 + 1 colors slots, not 512/4. Else, if */ /* a program manages to change colors every 4 cycles, the '-1' terminator */ /* added by Spec512_StartFrame will in fact be written on cycle 0 in the */ /* next line (this is theoretical, practically no program changes the */ /* color 128 times per line). */ /* Use nCyclesPerLine instead of 512 to check if nHorPos is too big and if */ /* the colors must be stored on the next line when freq is 50 or 60Hz */ /* (readme.prg by TEX (in 1987)) */ /* 2008/01/24 [NP] In Spec512_StartScanLine, use different values for LineStartCycle when */ /* running in 50 Hz or 60 Hz (TEX Spectrum Slideshow in 60 Hz). */ /* 2008/12/14 [NP] In Spec512_StoreCyclePalette, instead of approximating write position */ /* by Cycles_GetCounter+8, we use different cases for movem, .l access and */ /* .w access (similar to cycles.c). This gives correct results when using */ /* "move.w d0,(a0) or move.w (a0)+,(a1)+" for example, which were shifted */ /* 8 or 4 pixels too late. Calibration was made using a custom program to */ /* compare the results with a real STF in different cases (fix Froggies */ /* Over The Fence Main Menu). */ /* 2008/12/21 [NP] Use BusMode to adjust Cycles_GetCounterOnReadAccess and */ /* Cycles_GetCounterOnWriteAccess depending on who is owning the */ /* bus (cpu, blitter). */ /* 2009/07/28 [NP] Use different timings for movem.l and movem.w */ /* 2014/01/02 [NP] In Spec512_StoreCyclePalette, write occurs during the last cycles for */ /* i_ADD and i_SUB (fix 'add d1,(a0)' in '4-pixel plasma' by TOS Crew). */ /* 2015/09/25 [NP] In Spec512_StoreCyclePalette, fix 'move.l' and 'movem' when used with */ /* the new WinUAE CPU core (move.l accesses for IO registers are in fact */ /* not possible on a 68000 STF, this is a bug in old UAE CPU core. */ /* A 'move.l' does 2 word accesses because STF bus is 16 bits) */ /* 2015/10/02 [NP] In Spec512_StoreCyclePalette, move the code to handle specific cycles */ /* for some opcodes into cycles.c and use Cycles_GetCounterOnWriteAccess() */ /* instead for all cases. */ const char Spec512_fileid[] = "Hatari spec512.c"; #include #include "main.h" #include "configuration.h" #include "cycles.h" #include "cycInt.h" #include "m68000.h" #include "ioMem.h" #include "screen.h" #include "spec512.h" #include "video.h" /* As 68000 clock multiple of 4 this mean we can only write to the palette this many time per scanline */ #define MAX_CYCLEPALETTES_PERLINE ((512/4) + 1) /* +1 for the '-1' added as a line's terminator */ /* Store writes to palette by cycles per scan line, colour and index in ST */ typedef struct { int LineCycles; /* Number of cycles into line (MUST be div by 4) */ Uint16 Colour; /* ST Colour value */ Uint16 Index; /* Index into ST palette (0...15) */ } CYCLEPALETTE; /* 314k; 1024-bytes per line */ static CYCLEPALETTE CyclePalettes[(MAX_SCANLINES_PER_FRAME+1)*MAX_CYCLEPALETTES_PERLINE]; static CYCLEPALETTE *pCyclePalette; static int nCyclePalettes[(MAX_SCANLINES_PER_FRAME+1)]; /* Number of entries in above table for each scanline */ static int nPalettesAccesses; /* Number of times accessed palette registers */ static Uint16 CycleColour; static int CycleColourIndex; static int nScanLine, ScanLineCycleCount; static bool bIsSpec512Display; #if SDL_BYTEORDER == SDL_BIG_ENDIAN static const int STRGBPalEndianTable[16] = { 0,2,1,3,8,10,9,11,4,6,5,7,12,14,13,15 }; #endif /*-----------------------------------------------------------------------*/ /** * Return true if this frame is a Spectrum 512 style image (can be low/med * res mix). */ bool Spec512_IsImage(void) { /* Spec512 mode was triggered in low or med res ? */ if (bIsSpec512Display) return true; return false; } /*-----------------------------------------------------------------------*/ /** * We store every palette access in a table to perform Spectrum 512 color * effects. This is cleared on each VBL. */ void Spec512_StartVBL(void) { /* Clear number of cycle palettes on each frame */ memset(nCyclePalettes, 0x0, sizeof(nCyclePalettes)); /* Clear number of times accessed on entry in palette (used to check if * it is true Spectrum 512 image) */ nPalettesAccesses = 0; /* Set as not Spectrum 512 displayed image */ bIsSpec512Display = false; } /*-----------------------------------------------------------------------*/ /** * Store color into table 'CyclePalettes[]' for screen conversion according * to cycles into frame. */ void Spec512_StoreCyclePalette(Uint16 col, Uint32 addr) { CYCLEPALETTE *pTmpCyclePalette; int FrameCycles, ScanLine, nHorPos; int CycleEnd; if (!ConfigureParams.Screen.nSpec512Threshold) return; CycleColour = col; CycleColourIndex = (addr-0xff8240)>>1; /* Find number of cycles into frame */ FrameCycles = Video_GetCyclesSinceVbl_OnWriteAccess(); /* Find scan line we are currently on and get index into cycle-palette table */ Video_ConvertPosition ( FrameCycles , &ScanLine , &nHorPos ); CycleEnd = nCyclesPerLine; if ( nCpuFreqShift ) /* if cpu freq is 16 or 32 MHz */ { /* Convert cycle position to 8 MHz equivalent and round to 4 cycles */ nHorPos >>= nCpuFreqShift; nHorPos &= ~3; CycleEnd >>= nCpuFreqShift; } if (ScanLine > MAX_SCANLINES_PER_FRAME) return; pTmpCyclePalette = &CyclePalettes[ (ScanLine*MAX_CYCLEPALETTES_PERLINE) + nCyclePalettes[ScanLine] ]; /* Do we have a previous entry at the same cycles? If so, 68000 have used a 'move.l' instruction so stagger writes */ if (nCyclePalettes[ScanLine] > 0) { /* In case the ST uses a move.l or a movem.l to update colors, we need * to add at least 4 cycles between each color: */ if ((pTmpCyclePalette-1)->LineCycles >= nHorPos) nHorPos = (pTmpCyclePalette-1)->LineCycles + 4; if ( nHorPos >= CycleEnd ) /* end of line reached, continue on the next line */ { ScanLine++; pTmpCyclePalette = &CyclePalettes[ (ScanLine*MAX_CYCLEPALETTES_PERLINE) + nCyclePalettes[ScanLine] ]; nHorPos = nCyclePalettes[ScanLine] * 4; /* 4 cycles per access */ } } /* Store palette access */ pTmpCyclePalette->LineCycles = nHorPos; /* Cycles into scanline */ pTmpCyclePalette->Colour = CycleColour; /* Store ST/STe color RGB */ pTmpCyclePalette->Index = CycleColourIndex; /* And index (0...15) */ if ( 1 && LOG_TRACE_LEVEL(TRACE_VIDEO_COLOR)) { int nFrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &nFrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE_PRINT("spec store col line %d cyc=%d col=%03x idx=%d video_cyc=%d %d@%d pc=%x instr_cyc=%d\n", ScanLine, nHorPos, CycleColour, CycleColourIndex, nFrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles); } /* Increment count (this can never overflow as you cannot write to the palette more than 'MAX_CYCLEPALETTES_PERLINE-1' times per scanline) */ nCyclePalettes[ScanLine]++; /* Check if program wrote to palette registers multiple times on a frame. */ /* If so it must be using a spec512 image or some kind of color cycling. */ nPalettesAccesses++; if (nPalettesAccesses >= ConfigureParams.Screen.nSpec512Threshold) { bIsSpec512Display = true; } } /*-----------------------------------------------------------------------*/ /** * Begin palette calculation for Spectrum 512 style images, */ void Spec512_StartFrame(void) { int i; /* Set so screen gets full-update when returns from Spectrum 512 display */ Screen_SetFullUpdate(); /* Set terminators on each line, so when scan during conversion we know when to stop */ for (i = 0; i < (nScanlinesPerFrame+1); i++) { pCyclePalette = &CyclePalettes[ (i*MAX_CYCLEPALETTES_PERLINE) + nCyclePalettes[i] ]; pCyclePalette->LineCycles = -1; /* Term */ } /* Copy first line palette, kept in 'HBLPalettes' and store to 'STRGBPalette' */ for (i = 0; i < 16; i++) { #if SDL_BYTEORDER == SDL_BIG_ENDIAN STRGBPalette[STRGBPalEndianTable[i]] = ST2RGB[pHBLPalettes[i]]; #else STRGBPalette[i] = ST2RGB[pHBLPalettes[i]]; #endif } /* Ready for first call to 'Spec512_ScanLine' */ nScanLine = 0; if (VerticalOverscan & V_OVERSCAN_NO_TOP) nScanLine += OVERSCAN_TOP; /* Skip to first line(where start to draw screen from) */ for (i = 0; i < (STScreenStartHorizLine+(nStartHBL-OVERSCAN_TOP)); i++) Spec512_ScanWholeLine(); } /*-----------------------------------------------------------------------*/ /** * Scan whole line and build up palette - need to do this so when get to screen line we have * the correct 16 colours set */ void Spec512_ScanWholeLine(void) { /* Store pointer to line of palette cycle writes */ pCyclePalette = &CyclePalettes[nScanLine*MAX_CYCLEPALETTES_PERLINE]; /* Ready for next scan line */ nScanLine++; /* Update palette entries until we reach start of displayed screen */ ScanLineCycleCount = 0; Spec512_EndScanLine(); /* Read whole line of palettes and update 'STRGBPalette' */ } /*-----------------------------------------------------------------------*/ /** * Build up palette for this scan line and store in 'ScanLinePalettes' */ void Spec512_StartScanLine(void) { int i; int LineStartCycle; /* Store pointer to line of palette cycle writes */ pCyclePalette = &CyclePalettes[nScanLine*MAX_CYCLEPALETTES_PERLINE]; /* Ready for next scan line */ nScanLine++; if ( nScanlinesPerFrame == SCANLINES_PER_FRAME_50HZ ) LineStartCycle = LINE_START_CYCLE_50; /* The screen was 50 Hz */ else LineStartCycle = LINE_START_CYCLE_60; /* The screen was 60 Hz */ /* Update palette entries until we reach start of displayed screen */ ScanLineCycleCount = 0; for (i=0; i<((LineStartCycle-SCREENBYTES_LEFT*2)/4 + 7); i++) /* [NP] '7' is required to align pixels and colors */ Spec512_UpdatePaletteSpan(); /* Update palette for this 4-cycle period */ /* And skip for left border is not using overscan display to user */ for (i=0; i<(STScreenLeftSkipBytes/2); i++) /* Eg, 16 bytes = 32 pixels or 8 palette periods */ Spec512_UpdatePaletteSpan(); } /*-----------------------------------------------------------------------*/ /** * Run to end of scan line looking up palettes so 'STRGBPalette' is up-to-date */ void Spec512_EndScanLine(void) { int CycleEnd = nCyclesPerLine; CycleEnd >>= nCpuFreqShift; /* Convert cycle position to 8 MHz equivalent */ /* Continue to reads palette until complete so have correct version for next line */ while (ScanLineCycleCount < CycleEnd) Spec512_UpdatePaletteSpan(); } /*-----------------------------------------------------------------------*/ /** * Update palette for 4-pixels span, storing to 'STRGBPalette' */ void Spec512_UpdatePaletteSpan(void) { if (pCyclePalette->LineCycles == ScanLineCycleCount) { /* Need to update palette with new entry */ #if SDL_BYTEORDER == SDL_BIG_ENDIAN STRGBPalette[STRGBPalEndianTable[pCyclePalette->Index]] = ST2RGB[pCyclePalette->Colour]; #else STRGBPalette[pCyclePalette->Index] = ST2RGB[pCyclePalette->Colour]; //fprintf ( stderr , "upd spec cyc %d %x %x\n" , ScanLineCycleCount , pCyclePalette->Index , pCyclePalette->Colour ); #endif pCyclePalette += 1; } ScanLineCycleCount += 4; /* Next 4 cycles */ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/st.c000066400000000000000000000046161504763705000221310ustar00rootroot00000000000000/* Hatari - st.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. ST disk image support. The file format of the .ST image files is simplicity itself. They are just straight images of the disk in question, with sectors stored in the expected logical order. So, on a sector basis the images run from sector 0 (bootsector) to however many sectors are on the disk. On a track basis the layout is the same as for MSA files but obviously the data is raw, no track header or compression or anything like that. TRACK 0, SIDE 0 TRACK 0, SIDE 1 TRACK 1, SIDE 0 TRACK 1, SIDE 1 TRACK 2, SIDE 0 TRACK 2, SIDE 1 */ const char ST_fileid[] = "Hatari st.c"; #include "main.h" #include "file.h" #include "floppy.h" #include "st.h" #define SAVE_TO_ST_IMAGES #if defined(__riscos) /* The following two lines are required on RISC OS for preventing it from * interfering with the .ST image files: */ #include int __feature_imagefs_is_file = 1; #endif /*-----------------------------------------------------------------------*/ /** * Does filename end with a .ST extension? If so, return true. */ bool ST_FileNameIsST(const char *pszFileName, bool bAllowGZ) { return(File_DoesFileExtensionMatch(pszFileName,".st") || (bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".st.gz"))); } /*-----------------------------------------------------------------------*/ /** * Load .ST file into memory, set number of bytes loaded and return a pointer * to the buffer. */ uint8_t *ST_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType) { uint8_t *pStFile; *pImageSize = 0; /* Just load directly a buffer, and set ImageSize accordingly */ pStFile = File_Read(pszFileName, pImageSize, NULL); if (!pStFile) { *pImageSize = 0; return NULL; } *pImageType = FLOPPY_IMAGE_TYPE_ST; return pStFile; } /*-----------------------------------------------------------------------*/ /** * Save .ST file from memory buffer. Returns true is all OK. */ bool ST_WriteDisk(int Drive, const char *pszFileName, uint8_t *pBuffer, int ImageSize) { #ifdef SAVE_TO_ST_IMAGES /* Just save buffer directly to file */ return File_Save(pszFileName, pBuffer, ImageSize, false); #else /*SAVE_TO_ST_IMAGES*/ /* Oops, cannot save */ return false; #endif /*SAVE_TO_ST_IMAGES*/ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/stMemory.c000066400000000000000000001310511504763705000233140ustar00rootroot00000000000000/* Hatari - stMemory.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. ST Memory access functions. */ const char STMemory_fileid[] = "Hatari stMemory.c"; #include "stMemory.h" #include "configuration.h" #include "floppy.h" #include "gemdos.h" #include "ioMem.h" #include "log.h" #include "memory.h" #include "memorySnapShot.h" #include "tos.h" #include "vdi.h" #include "m68000.h" #include "video.h" /* STRam points to our malloc'ed buffer with the ST RAM. * Note that the ROM and IO memory will be handled separately. */ uint8_t *STRam; uint32_t STRamEnd; /* End of ST Ram, above this address is no-mans-land and ROM/IO memory */ uint32_t RAM_Bank0_Size; /* Physical RAM on board in bank0 (in bytes) : 128, 512 or 2048 KB */ uint32_t RAM_Bank1_Size; /* Physical RAM on board in bank1 (in bytes) : 128, 512 or 2048 KB */ uint32_t MMU_Bank0_Size; /* Logical MMU RAM size for bank0 (in bytes) : 128, 512 or 2048 KB */ uint32_t MMU_Bank1_Size; /* Logical MMU RAM size for bank1 (in bytes) : 128, 512 or 2048 KB */ uint8_t MMU_Conf_Expected; /* Expected value for $FF8001 corresponding to ST RAM size if <= 4MB */ static void STMemory_MMU_ConfToBank ( uint8_t MMU_conf , uint32_t *pBank0 , uint32_t *pBank1 ); static int STMemory_MMU_Size ( uint8_t MMU_conf ); static int STMemory_MMU_Size_TT ( uint8_t MMU_conf ); static uint32_t STMemory_MMU_Translate_Addr_STF ( uint32_t addr_logical , int RAM_Bank_Size , int MMU_Bank_Size ); static uint32_t STMemory_MMU_Translate_Addr_STE ( uint32_t addr_logical , int RAM_Bank_Size , int MMU_Bank_Size ); #define DMA_READ_WORD_BUS_ERR 0x0000 /* This value is returned when reading a word using DMA (blitter, sound) */ /* in a region that would cause a bus error */ /* [NP] FIXME : for now we return a constant, but it should depend on the bus activity */ #define DMA_READ_BYTE_BUS_ERR 0x00 /** * Set default value for MMU bank size and RAM bank size * NOTE : IoMem will not be allocated yet on the first call so we default to 0. * TODO [NP] : don't call STMemory_MMU_ConfToBank from here ? Better ensure STMemory_Reset() * is called early enough. */ void STMemory_Init ( int RAM_Size_Byte ) { uint8_t val; /* Set default MMU bank size values */ if ( IOmemory == NULL ) val = 0x0; else val = IoMem[ 0xff8001 ]; // fprintf ( stderr , "STMemory_Init %d %x\n" , RAM_Size_Byte , val ); STMemory_MMU_ConfToBank ( val, &MMU_Bank0_Size, &MMU_Bank1_Size ); if ( RAM_Size_Byte <= 0x400000 ) { if ( STMemory_RAM_SetBankSize ( RAM_Size_Byte , &RAM_Bank0_Size , &RAM_Bank1_Size , &MMU_Conf_Expected ) == false ) { Log_Printf(LOG_ERROR, "invalid RAM size %d KB for MMU banks\n", RAM_Size_Byte ); } } } /* * Reset the internal MMU/MCU used to configure address decoding for the RAM banks * 0xFF8001 is set to 0 on cold reset but keep its value on warm reset * This should be called early during the whole reset process to ensure MMU_Bank0_Size * and MMU_Bank1_Size have a consistent value (ie != 0) before calling memory_init() * (MMU_BankX_Size can be 0 in case Hatari was started with > 4 MB RAM, which is not standard * for STF/STE) * NOTE: as with STMemory_Init(), IoMem will not be allocated yet on the first call */ void STMemory_Reset ( bool bCold ) { if ( bCold ) { // fprintf ( stderr , "STMemory_Reset\n" ); if ( IOmemory != NULL ) IoMem[ 0xff8001 ] = 0x0; STMemory_MMU_ConfToBank ( 0 , &MMU_Bank0_Size , &MMU_Bank1_Size ); } } /** * Clear section of ST's memory space. * @param addr Destination Atari RAM address * @param len Number of bytes to clear * * @return true if whole clear was safe / valid. */ bool STMemory_SafeClear(uint32_t addr, unsigned int len) { uint32_t end; if (STMemory_CheckAreaType(addr, len, ABFLAG_RAM)) { if (addr + len < 0x1000000) { memset(&STRam[addr], 0, len); } else { assert(TTmemory && addr + len <= TTmem_size + 0x1000000); memset(&TTmemory[addr - 0x1000000], 0, len); } /* We modify the memory, so we flush the instr/data caches if needed */ M68000_Flush_All_Caches ( addr , len ); return true; } Log_Printf(LOG_WARN, "Invalid RAM clear range 0x%x+%i!\n", addr, len); for (end = addr + len; addr < end; addr++) { if (STMemory_CheckAreaType(addr, 1, ABFLAG_RAM)) { put_byte(addr, 0); /* We modify the memory, so we flush the instr/data caches if needed */ M68000_Flush_All_Caches ( addr , 1 ); } } return false; } /** * Copy given memory area safely to Atari RAM. * If the memory area isn't fully within RAM, only the valid parts are written. * Useful for all kinds of IO operations. * * addr - destination Atari RAM address * src - source Hatari memory address * len - number of bytes to copy * name - name / description if this memory copy for error messages * * Return true if whole copy was safe / valid. */ bool STMemory_SafeCopy(uint32_t addr, uint8_t *src, unsigned int len, const char *name) { uint32_t end; if ( STMemory_CheckAreaType ( addr, len, ABFLAG_RAM ) ) { if (addr + len < 0x1000000) { memcpy(&STRam[addr], src, len); } else { assert(TTmemory && addr + len <= TTmem_size + 0x1000000); memcpy(&TTmemory[addr - 0x1000000], src, len); } /* We modify the memory, so we flush the instr/data caches if needed */ M68000_Flush_All_Caches ( addr , len ); return true; } Log_Printf(LOG_WARN, "Invalid '%s' RAM range 0x%x+%i!\n", name, addr, len); for (end = addr + len; addr < end; addr++) { if ( STMemory_CheckAreaType ( addr, 1, ABFLAG_RAM ) ) { put_byte(addr, *src++); /* We modify the memory, so we flush the instr/data caches if needed */ M68000_Flush_All_Caches ( addr , 1 ); } } return false; } /** * Save/Restore snapshot of RAM / ROM variables * ('MemorySnapShot_Store' handles type) */ void STMemory_MemorySnapShot_Capture(bool bSave) { MemorySnapShot_Store(&STRamEnd, sizeof(STRamEnd)); /* After restoring RAM/MMU bank sizes we must call memory_map_Standard_RAM() */ MemorySnapShot_Store(&RAM_Bank0_Size, sizeof(RAM_Bank0_Size)); MemorySnapShot_Store(&RAM_Bank1_Size, sizeof(RAM_Bank1_Size)); MemorySnapShot_Store(&MMU_Bank0_Size, sizeof(MMU_Bank0_Size)); MemorySnapShot_Store(&MMU_Bank1_Size, sizeof(MMU_Bank1_Size)); MemorySnapShot_Store(&MMU_Conf_Expected, sizeof(MMU_Conf_Expected)); /* Only save/restore area of memory machine is set to, eg 1Mb */ MemorySnapShot_Store(STRam, STRamEnd); /* And Cart/TOS/Hardware area */ MemorySnapShot_Store(&RomMem[0xE00000], 0x200000); /* Save/restore content of TT RAM if TTRamSize_KB != 0 */ if ( ConfigureParams.Memory.TTRamSize_KB > 0 ) MemorySnapShot_Store ( TTmemory , ConfigureParams.Memory.TTRamSize_KB*1024 ); if ( !bSave ) memory_map_Standard_RAM ( MMU_Bank0_Size , MMU_Bank1_Size ); } /** * Set default memory configuration, connected floppies, memory size and * clear the ST-RAM area. * As TOS checks hardware for memory size + connected devices on boot-up * we set these values ourselves and fill in the magic numbers so TOS * skips these tests. */ void STMemory_SetDefaultConfig(void) { int i; int screensize, limit; int memtop, phystop; uint8_t MMU_Conf_Force; uint8_t nFalcSysCntrl; if (bRamTosImage) { /* Clear ST-RAM, excluding the RAM TOS image */ STMemory_SafeClear(0x00000000, TosAddress); STMemory_SafeClear(TosAddress + TosSize, STRamEnd - TosAddress - TosSize); } else { /* Clear whole ST-RAM */ STMemory_SafeClear(0x00000000, STRamEnd); } /* Mirror ROM boot vectors */ STMemory_WriteLong(0x00, STMemory_ReadLong(TosAddress)); STMemory_WriteLong(0x04, STMemory_ReadLong(TosAddress+4)); /* Fill in magic numbers to bypass TOS' memory tests for faster boot or * if VDI resolution is enabled or if more than 4 MB of ram are used * or if TT RAM added in Falcon mode. * (for highest compatibility, those tests should not be bypassed in * the common STF/STE cases as some programs like "Yolanda" rely on * the RAM content after those tests) */ if ( ConfigureParams.System.bFastBoot || bUseVDIRes || ( ConfigureParams.Memory.STRamSize_KB > (4*1024) && !bIsEmuTOS ) || ( Config_IsMachineTT() && ConfigureParams.System.bAddressSpace24 && !bIsEmuTOS ) || ( Config_IsMachineFalcon() && TTmemory && !bIsEmuTOS) ) { /* Write magic values to sysvars to signal valid config */ STMemory_WriteLong(0x420, 0x752019f3); /* memvalid */ STMemory_WriteLong(0x43a, 0x237698aa); /* memval2 */ STMemory_WriteLong(0x51a, 0x5555aaaa); /* memval3 */ /* If ST RAM detection is bypassed, we must also force TT RAM config if enabled */ if ( TTmemory ) STMemory_WriteLong ( 0x5a4 , 0x01000000 + TTmem_size ); /* ramtop */ else STMemory_WriteLong ( 0x5a4 , 0 ); /* ramtop */ STMemory_WriteLong ( 0x5a8 , 0x1357bd13 ); /* ramvalid */ /* On Falcon, set bit6=1 at $ff8007 to simulate a warm start */ /* (else memory detection is not skipped after a cold start/reset) */ if (Config_IsMachineFalcon()) STMemory_WriteByte ( 0xff8007, IoMem_ReadByte(0xff8007) | 0x40 ); /* On TT, set bit0=1 at $ff8e09 to simulate a warm start */ /* (else memory detection is not skipped after a cold start/reset) */ if (Config_IsMachineTT()) STMemory_WriteByte ( 0xff8e09, IoMem_ReadByte(0xff8e09) | 0x01 ); /* TOS 3.0x and 4.0x check _hz200 and always do a memory test * if the machine runs less than 80 seconds */ if (!bIsEmuTOS && TosVersion >= 0x300) STMemory_WriteLong(0x4ba, 80 * 200); } /* VDI screen size. Needs to leave extra space for 16x16 area * between end of screen & RAM end, or <= v2.x TOS versions * crash when mouse moves to bottom right corner of screen. */ screensize = VDIWidth * VDIHeight / 8 * VDIPlanes + 16*16*VDIPlanes/8; /* Use 32 kiB in normal screen mode or when the screen size * is smaller than 32 kiB */ if (!bUseVDIRes || screensize < 0x8000) screensize = 0x8000; /* mem top - upper end of user memory (right before the screen memory) * memtop / phystop must be divisible by 512 or TOS crashes */ memtop = (STRamEnd - screensize) & 0xfffffe00; /* phys top - 32k gap causes least issues with apps & TOS * as that's the largest _common_ screen size. EmuTOS behavior * depends on machine type. * * TODO: what to do about _native_ TT & Videl resolutions * which size is >32k? Should memtop be adapted also for * those? */ switch (ConfigureParams.System.nMachineType) { case MACHINE_FALCON: /* TOS v4 doesn't work with VDI mode (yet), and * EmuTOS works with correct gap, so use that */ phystop = STRamEnd; break; case MACHINE_TT: /* For correct TOS v3 memory detection, phystop should be * at the end of memory, not at memtop + 32k. * * However: * - TOS v3 crashes/hangs if phystop-memtop gap is larger * than largest real HW screen size (150k) * - NVDI hangs if gap is larger than 32k in any other than * monochrome mode */ if (VDIPlanes == 1) limit = 1280*960/8; else limit = 0x8000; if (screensize > limit) { phystop = memtop + limit; Log_Printf(LOG_WARN, "too large VDI mode for TOS v3 memory detection to work correctly!\n"); } else phystop = STRamEnd; break; default: phystop = memtop + 0x8000; } STMemory_WriteLong(0x436, memtop); STMemory_WriteLong(0x42e, phystop); if (bUseVDIRes) { Log_Printf(LOG_DEBUG, "VDI mode memtop: 0x%x, phystop: 0x%x (screensize: %d kB, memtop->phystop: %d kB)\n", memtop, phystop, (screensize+511) / 1024, (phystop-memtop+511) / 1024); } /* If possible we don't override memory detection, TOS will do it * (in that case MMU/MCU can be correctly emulated, and we do nothing * and let TOS do its own memory tests using $FF8001) */ if (!(Config_IsMachineST() || Config_IsMachineSTE()) || ConfigureParams.System.bFastBoot || bUseVDIRes || ConfigureParams.Memory.STRamSize_KB > 4*1024) { /* Set memory controller byte according to different memory sizes */ /* Setting per bank : %00=128k %01=512k %10=2Mb %11=reserved. - e.g. %1010 means 4Mb */ if (ConfigureParams.Memory.STRamSize_KB <= 4*1024) MMU_Conf_Force = MMU_Conf_Expected; else MMU_Conf_Force = 0x0f; STMemory_WriteByte(0x424, MMU_Conf_Force); IoMem_WriteByte(0xff8001, MMU_Conf_Force); } if (Config_IsMachineFalcon()) { /* Set the Falcon memory and monitor configuration register: $ffff8006.b [R] 76543210 Monitor-memory |||||||| |||||||+- RAM Wait Status ||||||| 0 = 1 Wait (default) ||||||| 1 = 0 Wait ||||||+-- Video Bus size ??? |||||| 0 = 16 Bit |||||| 1 = 32 Bit (default) ||||++--- ROM Wait Status |||| 00 = Reserved |||| 01 = 2 Wait (default) |||| 10 = 1 Wait |||| 11 = 0 Wait ||++----- Falcon Memory || 00 = 1 MB || 01 = 4 MB || 10 = 14 MB || 11 = no boot ! ++------- Monitor-Typ 00 - Monochrome (SM124) 01 - Color (SC1224) 10 - VGA Color 11 - Television Bit 1 seems not to be well documented. It's used by TOS at bootup to compute the memory size. After some tests, I get the following RAM values (Bits 5, 4, 1 are involved) : 00 = 512 Ko 20 = 8192 Ko 02 = 1024 Ko 22 = 14366 Ko 10 = 2048 Ko 30 = Illegal 12 = 4096 Ko 32 = Illegal I use these values for Hatari's emulation. I also set the bit 3 and 2 at value 01 are mentioned in the register description. */ if (ConfigureParams.Memory.STRamSize_KB == 14*1024) /* 14 Meg */ nFalcSysCntrl = 0x26; else if (ConfigureParams.Memory.STRamSize_KB == 8*1024) /* 8 Meg */ nFalcSysCntrl = 0x24; else if (ConfigureParams.Memory.STRamSize_KB == 4*1024) /* 4 Meg */ nFalcSysCntrl = 0x16; else if (ConfigureParams.Memory.STRamSize_KB == 2*1024) /* 2 Meg */ nFalcSysCntrl = 0x14; else if (ConfigureParams.Memory.STRamSize_KB == 1024) /* 1 Meg */ nFalcSysCntrl = 0x06; else nFalcSysCntrl = 0x04; /* 512 Ko */ switch(ConfigureParams.Screen.nMonitorType) { case MONITOR_TYPE_TV: nFalcSysCntrl |= FALCON_MONITOR_TV; break; case MONITOR_TYPE_VGA: nFalcSysCntrl |= FALCON_MONITOR_VGA; break; case MONITOR_TYPE_RGB: nFalcSysCntrl |= FALCON_MONITOR_RGB; break; case MONITOR_TYPE_MONO: nFalcSysCntrl |= FALCON_MONITOR_MONO; break; } STMemory_WriteByte(0xff8006, nFalcSysCntrl); } /* Set TOS floppies */ STMemory_WriteWord(0x446, nBootDrive); /* Boot up on A(0) or C(2) */ /* Create connected drives mask (only for harddrives, don't change floppy drive detected by TOS) */ ConnectedDriveMask = STMemory_ReadLong(0x4c2); // Get initial drive mask (see what TOS thinks) if (GEMDOS_EMU_ON) { for (i = 0; i < MAX_HARDDRIVES; i++) { if (emudrives[i] != NULL) // Is this GEMDOS drive enabled? ConnectedDriveMask |= (1 << emudrives[i]->drive_number); } } /* Set connected drives system variable. * NOTE: some TOS images overwrite this value, see 'OpCode_SysInit', too */ STMemory_WriteLong(0x4c2, ConnectedDriveMask); } /** * Called after machine type is fixed, to correct ST-RAM amount to machine * specific value, when machine doesn't support all values accepted by Hatari. * * Returns resulting STRamEnd value. */ int STMemory_CorrectSTRamSize(void) { int STRamSize_KB = ConfigureParams.Memory.STRamSize_KB; if (Config_IsMachineFalcon()) { /* Falcon ST RAM values need to match to ones used * in STMemory_SetDefaultConfig() above. */ if (STRamSize_KB > 8*1024) STRamSize_KB = 14*1024; else if (STRamSize_KB > 4*1024) STRamSize_KB = 8*1024; else if (STRamSize_KB > 2*1024) STRamSize_KB = 4*1024; else if (STRamSize_KB > 1024) STRamSize_KB = 2*1024; else if (STRamSize_KB > 512) STRamSize_KB = 1024; else STRamSize_KB = 512; if (STRamSize_KB != ConfigureParams.Memory.STRamSize_KB) { Log_Printf(LOG_WARN, "unsupported Falcon ST-RAM amount %d, changing to %d KB\n", ConfigureParams.Memory.STRamSize_KB, STRamSize_KB); ConfigureParams.Memory.STRamSize_KB = STRamSize_KB; } } else if (Config_IsMachineMegaSTE() || Config_IsMachineTT()) { if (STRamSize_KB > 10*1024) { Log_Printf(LOG_INFO, "max ST-RAM on real MegaSTE/TT would be 10MB due to VME, not %dMB\n", STRamSize_KB/1024); } } return STRamSize_KB * 1024; } /** * Check that the region of 'size' starting at 'addr' is entirely inside * a memory bank of the same memory type */ bool STMemory_CheckAreaType ( uint32_t addr , int size , int mem_type ) { addrbank *pBank; pBank = &get_mem_bank ( addr ); if ( ( pBank->flags & mem_type ) == 0 ) { Log_Printf(LOG_DEBUG, "pBank flags mismatch: 0x%x & 0x%x (RAM = 0x%x)\n", pBank->flags, mem_type, ABFLAG_RAM); return false; } return pBank->check ( addr , size ); } /** * Check if an address access would cause a bus error (read or write) * This is used for blitter and other DMA chips that should not cause * a bus error when accessing directly such regions (on the contrary of the CPU) * Bus error can come from : * - an access to a bus error region * - an access to a part of the IO region that cause a bus error * Returns true if address would give a bus error */ bool STMemory_CheckAddrBusError ( uint32_t addr ) { /* Check if it's a whole "bus error" region */ if ( memory_region_bus_error ( addr ) ) return true; /* In case of IO region, bus error can happen at various addresses, depending on the machine type */ if ( memory_region_iomem ( addr ) && IoMem_CheckBusError ( addr ) ) return true; /* TODO : in case of the Falcon, we should also check IDE region 0xF0xxxx for possible bus error */ return false; } /** * Convert an address in the ST memory space to a direct pointer * in the host memory. * * NOTE : Using this function to get a direct pointer to the memory should * only be used after doing a call to valid_address or STMemory_CheckAreaType * to ensure we don't try to access a non existing memory region. * Basically, this function should be used only for addr in RAM or in ROM */ void *STMemory_STAddrToPointer ( uint32_t addr ) { uint8_t *p; if ( ConfigureParams.System.bAddressSpace24 == true ) addr &= 0x00ffffff; /* Only keep the 24 lowest bits */ p = get_real_address ( addr ); return (void *)p; } /** * Get the host memory pointer of a NUL-terminated string in the ST memory, * or NULL if the whole string memory is not accessible or if the string * is too big (i.e. rather garbage than a real string). */ char *STMemory_GetStringPointer(uint32_t addr) { int idx = 0; do { if (!STMemory_CheckAreaType(addr + idx, 1, ABFLAG_RAM | ABFLAG_ROM)) { return NULL; } if (*(char *)STMemory_STAddrToPointer(addr + idx) == '\0') { return STMemory_STAddrToPointer(addr); } } while (idx++ < 0x10000); return NULL; } /** * Those functions are directly accessing the memory of the corresponding * bank, without calling its dedicated access handlers (they won't generate * bus errors or address errors or update IO values) * They are only used for internal work of the emulation, such as debugger, * log to print the content of memory, intercepting gemdos/bios calls, ... * * These functions are not used by the CPU emulation itself, see memory.c * for the functions that emulate real memory accesses. */ /** * Write long/word/byte into memory. * NOTE - value will be converted to 68000 endian */ void STMemory_Write ( uint32_t addr , uint32_t val , int size ) { addrbank *pBank; uint8_t *p; //printf ( "mem direct write %x %x %d\n" , addr , val , size ); pBank = &get_mem_bank ( addr ); if ( pBank->baseaddr == NULL ) return; /* No real memory, do nothing */ addr -= pBank->start & pBank->mask; addr &= pBank->mask; p = pBank->baseaddr + addr; /* We modify the memory, so we flush the instr/data caches if needed */ M68000_Flush_All_Caches ( addr , size ); if ( size == 4 ) do_put_mem_long ( p , val ); else if ( size == 2 ) do_put_mem_word ( p , (uint16_t)val ); else *p = (uint8_t)val; } void STMemory_WriteLong ( uint32_t addr , uint32_t val ) { STMemory_Write ( addr , val , 4 ); } void STMemory_WriteWord ( uint32_t addr , uint16_t val ) { STMemory_Write ( addr , (uint32_t)val , 2 ); } void STMemory_WriteByte ( uint32_t addr , uint8_t val ) { STMemory_Write ( addr , (uint32_t)val , 1 ); } /** * Read long/word/byte from memory. * NOTE - value will be converted to 68000 endian */ uint32_t STMemory_Read ( uint32_t addr , int size ) { addrbank *pBank; uint8_t *p; //printf ( "mem direct read %x %d\n" , addr , size ); pBank = &get_mem_bank ( addr ); if ( pBank->baseaddr == NULL ) return 0; /* No real memory, return 0 */ addr -= pBank->start & pBank->mask; addr &= pBank->mask; p = pBank->baseaddr + addr; if ( size == 4 ) return do_get_mem_long ( p ); else if ( size == 2 ) return (uint32_t)do_get_mem_word ( p ); else return (uint32_t)*p; } uint32_t STMemory_ReadLong ( uint32_t addr ) { return (uint32_t) STMemory_Read ( addr , 4 ); } uint16_t STMemory_ReadWord ( uint32_t addr ) { return (uint16_t)STMemory_Read ( addr , 2 ); } uint8_t STMemory_ReadByte ( uint32_t addr ) { return (uint8_t)STMemory_Read ( addr , 1 ); } /** * Access memory when using DMA * Contrary to the CPU, when DMA is used there should be no bus error */ uint16_t STMemory_DMA_ReadWord ( uint32_t addr ) { uint16_t value; /* When reading from a bus error region, just return a constant */ if ( STMemory_CheckAddrBusError ( addr ) ) value = DMA_READ_WORD_BUS_ERR; else value = (uint16_t)get_word ( addr ); //fprintf ( stderr , "readw %x %x %x\n" , addr , value , STMemory_CheckAddrBusError(addr) ); return value; } void STMemory_DMA_WriteWord ( uint32_t addr , uint16_t value ) { /* Call put_word only if the address doesn't point to a bus error region */ /* (also see SysMem_wput for addr < 0x8) */ if ( STMemory_CheckAddrBusError ( addr ) == false ) put_word ( addr , (uint32_t)(value) ); //fprintf ( stderr , "writew %x %x %x\n" , addr , value , STMemory_CheckAddrBusError(addr) ); } uint8_t STMemory_DMA_ReadByte ( uint32_t addr ) { uint8_t value; /* When reading from a bus error region, just return a constant */ if ( STMemory_CheckAddrBusError ( addr ) ) value = DMA_READ_BYTE_BUS_ERR; else value = (uint8_t)get_byte ( addr ); //fprintf ( stderr , "readb %x %x %x\n" , addr , value , STMemory_CheckAddrBusError(addr) ); return value; } void STMemory_DMA_WriteByte ( uint32_t addr , uint8_t value ) { /* Call put_word only if the address doesn't point to a bus error region */ /* (also see SysMem_wput for addr < 0x8) */ if ( STMemory_CheckAddrBusError ( addr ) == false ) put_byte ( addr , (uint32_t)(value) ); //fprintf ( stderr , "writeb %x %x %x\n" , addr , value , STMemory_CheckAddrBusError(addr) ); } /* Description of the MMU used in STF/STE to address RAM : ------------------------------------------------------- Atari's computer used their own custom MMU to map logical addresses to physical RAM or to hardware registers. The CAS/RAS mappings are based on Christian Zietz research to reverse the MMU's inner work, as well as by using some custom programs on ST to change MMU configs and see how RAM content is modified when the shifter displays it on screen. When addressing RAM, the MMU will convert a logical address into the corresponding RAS0/CAS0L/CAS0H or RAS1/CAS1L/CAS1H (using the MAD0-MAD9 signals), which will select the RAM chips needed to store the data. Data are handled as 16 bits. The mapping between a logical address and a physical bank/memory chips depends on the ST model. STF : A bank is made of 16 chips of 1 bit memory. The MMU can use chips of 64 kbits, 256 kbits or 1024 kbits, which gives a bank size of 128 KB, 512 KB or 2048 KB (for example 16 chips of 41256 RAM will give 512 KB) Over the years, several revisions of the MMU were made : - C025912-20 : maker unknown, found in very first STs, banks 0 and 1 can be different - C025912-38 : made by Ricoh, found in most STFs, banks 0 and 1 can be different - C100109-001 : made by IMP, found in more recent STFs ; although different values can be set for banks 0 and 1, bank 0 setting will always apply to the 2 banks (so, 2.5 MB config is not possible) STE : Each bank is made of 2 chips of SIMM RAM using 8 bit memory (instead of 1 bit on STF). The MMU was integrated into a bigger chip, the GST/MCU. As for the STF's IMP MMU, the MCU will only use bank 0 setting for both banks - C300589-001 : STE - C302183-001 : Mega STE Regarding physical RAM on STF/STE, bank 1 can be empty or not, but bank 0 must always be filled (due to the way TOS checks for available RAM and size, memory detection would give wrong results if bank 0 was empty and bank 1 was filled, as bank 0 would be considered as 128 KB in such cases) TT : The TT had several possibilities for memory extensions : - on board "slow" dual purpose (system/shifter) memory : 16 chips of 4 bit memory using 256 kbits or 1024 kbits modules Most (all ?) TT were shipped with 2 MB of on board RAM (ie 256 kbits chips). Using 1024 kbits chips, it's possible to get 8 MB of RAM - daughterboard "slow" dual purpose memory : similar to on board RAM, you get 2 MB or 8 MB - CA400313-xxx : 2 MB board by Atari - CA401059-xxx : 2 or 8 MB board by Atari - extension board using the VME BUS ; such RAM can't be used for shifter and it's slower than fast RAM - fast RAM : up to 512 MB of "fast" single purpose RAM could be added. It can't be used for shifter, but it can be used with TT DMA specific chips. As this RAM is not shared with the shifter, it's much faster (there's no bus cycle penalty every 250 ns as with dual purpose memory) As tested by some people, if the TT has 8 MB on board and 8 MB on the daughterboard of "slow" dual purpose RAM, then the resulting memory will be limited to 10 MB (addr 0x000000 to 0xA00000) and not to 14 or 16 MB, the rest is reserved for cartridge, VME, ROM, IO regs MMU configuration at $FF8001 : This register is used to specify the memory bank sizes used by the MMU to translate logical addresses into physical ones. Under normal operations, it should match the size of the physical RAM. STF/STE : bits 2-3 = size of bank 0 bits 0-1 = size of bank 1 bank size : 00 = 128 KB 01=512 KB 10=2048 KB 11=reserved TT : only bit 1 is used (there's only 1 bank) bank size : 0 = 2 MB (uses 256 kbits chips) 1 = 8 MB (uses 1024 kits chips) */ static void STMemory_MMU_ConfToBank ( uint8_t MMU_conf , uint32_t *pBank0 , uint32_t *pBank1 ) { //fprintf(stderr , "STMemory_MMU_ConfToBank %d %d %d\n" , MMU_conf, *pBank0, *pBank1 ); if ( Config_IsMachineTT() ) { *pBank0 = STMemory_MMU_Size_TT ( ( MMU_conf >> 1 ) & 1 ); *pBank1 = 0; } else { *pBank0 = STMemory_MMU_Size ( ( MMU_conf >> 2 ) & 3 ); /* - STF with non-IMP MMU can have 2 different size of banks */ /* - STF with IMP MMU and STE use bank0 value for the 2 banks (ie bank1=bank0 in all cases) */ if ( Config_IsMachineST() ) *pBank1 = STMemory_MMU_Size ( MMU_conf & 3 ); else *pBank1 = MMU_Bank0_Size; } //fprintf(stderr , "STMemory_MMU_ConfToBank2 %d %d %d\n" , MMU_conf, *pBank0, *pBank1 ); } /** * Return the number of bytes for a given MMU bank configuration on STF/STE * Possible values are 00, 01 or 10 */ static int STMemory_MMU_Size ( uint8_t MMU_conf ) { if ( MMU_conf == 0 ) return MEM_BANK_SIZE_128; else if ( MMU_conf == 1 ) return MEM_BANK_SIZE_512; else if ( MMU_conf == 2 ) return MEM_BANK_SIZE_2048; else return 0; /* invalid */ } /** * Return the number of bytes for a given MMU bank configuration on TT * Possible values are 0 or 1 */ static int STMemory_MMU_Size_TT ( uint8_t MMU_conf ) { if ( MMU_conf == 0 ) return MEM_BANK_SIZE_2048; else return MEM_BANK_SIZE_8192; } /** * Read the MMU banks configuration at $FF8001 */ void STMemory_MMU_Config_ReadByte ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE(TRACE_MEM, "mmu read memory config ff8001 val=0x%02x mmu_bank0=%d KB mmu_bank1=%d KB VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoMem[ 0xff8001 ] , MMU_Bank0_Size/1024 , MMU_Bank1_Size/1024 , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } /** * Write to the MMU banks configuration at $FF8001 * When value is changed, we remap the RAM bank into our STRam[] buffer * and enable addresses translation if necessary */ void STMemory_MMU_Config_WriteByte ( void ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); STMemory_MMU_ConfToBank ( IoMem[ 0xff8001 ] , &MMU_Bank0_Size , &MMU_Bank1_Size ); memory_map_Standard_RAM ( MMU_Bank0_Size , MMU_Bank1_Size ); LOG_TRACE(TRACE_MEM, "mmu write memory config ff8001 val=0x%02x mmu_bank0=%d KB mmu_bank1=%d KB VBL=%d video_cyc=%d %d@%d pc=%x\n" , IoMem[ 0xff8001 ] , MMU_Bank0_Size/1024 , MMU_Bank1_Size/1024 , nVBLs , FrameCycles, LineCycles, HblCounterVideo , M68000_GetPC() ); } /** * Check if "TotalMem" bytes is a valid value for the ST RAM size * and return the corresponding number of KB. * TotalMem can be expressed in MB if <= 14, else in KB * We list the most usual sizes, some more could be added if needed * Some values are not standard for all machines and will also require * to patch TOS to bypass RAM detection. * * If TotalMem is not a valid ST RAM size, return -1 */ int STMemory_RAM_Validate_Size_KB ( int TotalMem ) { /* Old format where ST RAM size was in MB between 0 and 14 */ if ( TotalMem == 0 ) return 512; if ( TotalMem <= 14 ) TotalMem *= 1024; /* New format where ST RAM size is in KB * * These memory amounts are accepted for all machine types, but in * case of Falcon, rounded up later in STMemory_SetDefaultConfig(), * to amounts Falcon mem config reg actually supports * * Note: Hatari emulates ST with Ricoch chipset, and MegaST with * IMP one, see ioMem.c::IoMem_FixVoidAccessFor*ST() */ switch (TotalMem) { /* all ST/STE MMU chipsets */ case 128: case 256: /* other than IMP ST/STE MMU chipset (mixed banks) */ case 640: case 2176: case 2560: /* all machines */ case 512: case 1024: case 2048: /* max on original (Mega)ST(e) machines */ case 4096: case 8*1024: /* max on real TT, and HW modified MegaSTE (due to VME) */ case 10*1024: /* max on Falcon, and HW modified ST/MegaST/STE */ case 14*1024: return TotalMem; } return -1; } /** * For TotalMem <= 4MB, set the corresponding size in bytes for RAM bank 0 and RAM bank 1. * Also set the corresponding MMU value to expect at $FF8001 * Return true if TotalMem is a valid ST RAM size for the MMU, else false */ bool STMemory_RAM_SetBankSize ( int TotalMem , uint32_t *pBank0_Size , uint32_t *pBank1_Size , uint8_t *pMMU_Conf ) { int TotalMem_KB = TotalMem / 1024; /* Check some possible RAM size configurations in KB */ if ( TotalMem_KB == 128 ) { *pBank0_Size = 128; *pBank1_Size = 0; *pMMU_Conf = (0<<2) + 0; } /* 0x0 : 128 + 0 */ else if ( TotalMem_KB == 256 ) { *pBank0_Size = 128; *pBank1_Size = 128; *pMMU_Conf = (0<<2) + 0; } /* 0x0 : 128 + 128 */ else if ( TotalMem_KB == 512 ) { *pBank0_Size = 512; *pBank1_Size = 0; *pMMU_Conf = (1<<2) + 0; } /* 0x4 : 512 + 0 */ else if ( TotalMem_KB == 640 ) { *pBank0_Size = 512; *pBank1_Size = 128; *pMMU_Conf = (1<<2) + 0; } /* 0x4 : 512 + 128 */ else if ( TotalMem_KB == 1024 ) { *pBank0_Size = 512; *pBank1_Size = 512; *pMMU_Conf = (1<<2) + 1; } /* 0x5 : 512 + 512 */ else if ( TotalMem_KB == 2048 ) { *pBank0_Size = 2048; *pBank1_Size = 0; *pMMU_Conf = (2<<2) + 0; } /* 0x8 : 2048 + 0 */ else if ( TotalMem_KB == 2176 ) { *pBank0_Size = 2048; *pBank1_Size = 128; *pMMU_Conf = (2<<2) + 0; } /* 0x8 : 2048 + 128 */ else if ( TotalMem_KB == 2560 ) { *pBank0_Size = 2048; *pBank1_Size = 512; *pMMU_Conf = (2<<2) + 1; } /* 0x9 : 2048 + 512 */ else if ( TotalMem_KB == 4096 ) { *pBank0_Size = 2048; *pBank1_Size = 2048; *pMMU_Conf = (2<<2) + 2; } /* 0xA : 2048 + 2048 */ else { Log_Printf(LOG_ERROR, "Invalid RAM size %d KB for MMU banks\n", TotalMem_KB); return false; } Log_Printf(LOG_DEBUG, "STMemory_RAM_SetBankSize total=%d KB bank0=%d KB bank1=%d KB MMU=%x\n", TotalMem_KB, *pBank0_Size, *pBank1_Size, *pMMU_Conf); *pBank0_Size *= 1024; *pBank1_Size *= 1024; return true; } /** * STF : translate a logical address (as used by the CPU, DMA or the shifter) into a physical inside * the corresponding RAM bank using the RAS/CAS signal. * The STF MMU maps a 21 bit address (bits A20 .. A0) as follow : * - A0 : used to select low/high byte of a 16 bit word * - A1 ... A10 -> RAS0 ... RAS9 * - CASx : * - if MMU set to 2 MB, then A11 ... A20 -> CAS0 ... CAS9 * - if MMU set to 512 KB, then A10 ... A18 -> CAS0 ... CAS8 * - if MMU set to 128 KB, then A9 ... A16 -> CAS0 ... CAS7 * * [NP] As seen on a real STF (and confirmed by analyzing the STF's MMU), there's a special case * when bank0 is set to 128 KB and bank 1 is set to 2048 KB : the region between $40000 and $80000 will * not be mapped to any RAM at all, but will point to a "void" region ; this looks like a bug in the MMU's logic, * maybe not handled by Atari because this bank combination is unlikely to be used in real machines. */ static uint32_t STMemory_MMU_Translate_Addr_STF ( uint32_t addr_logical , int RAM_Bank_Size , int MMU_Bank_Size ) { uint32_t addr; if ( RAM_Bank_Size == MEM_BANK_SIZE_2048 ) { /* RAM modules use lines MAD0-MAD9, C9/C8/R9/R8 exist : 21 bits per address in bank */ if ( MMU_Bank_Size == MEM_BANK_SIZE_2048 ) { /* 21 bit address is mapped to 21 bits : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* C C C C C C C C C C R R R R R R R R R R X */ /* 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 X */ addr = addr_logical; } else if ( MMU_Bank_Size == MEM_BANK_SIZE_512 ) { /* 21 bit address is mapped to 19 bits (C9/R9 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . C C C C C C C C C R R R R R R R R R X */ /* . . 8 7 6 5 4 3 2 1 0 8 7 6 5 4 3 2 1 0 X */ addr = ( ( addr_logical & 0xffc00 ) << 1 ) | ( addr_logical & 0x7ff ); /* add C9=A19 and R9=A10 */ } else /* if ( MMU_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* 21 bit address is mapped to 17 bits (C9/C8/R9/R8 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . . . C C C C C C C C R R R R R R R R X */ /* . . . . 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 X */ addr = ( ( addr_logical & 0x7fe00 ) << 2 ) | ( addr_logical & 0x7ff ); /* add C9=A18 C8=A17 and R9=A10 R8=A9 */ } } else if ( RAM_Bank_Size == MEM_BANK_SIZE_512 ) { /* RAM modules use lines MAD0-MAD8, C9/R9 don't exist, C8/R8 exist : 19 bits per address in bank */ if ( MMU_Bank_Size == MEM_BANK_SIZE_2048 ) { /* 21 bit address is mapped to 21 bits : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* C C C C C C C C C C R R R R R R R R R R X */ /* 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 X */ addr = ( ( addr_logical & 0xff800 ) >> 1 ) | ( addr_logical & 0x3ff ); /* remove C9/R9 */ } else if ( MMU_Bank_Size == MEM_BANK_SIZE_512 ) { /* 21 bit address is mapped to 19 bits (C9/R9 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . C C C C C C C C C R R R R R R R R R X */ /* . . 8 7 6 5 4 3 2 1 0 8 7 6 5 4 3 2 1 0 X */ addr = addr_logical; } else /* if ( MMU_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* 21 bit address is mapped to 17 bits (C9/C8/R9/R8 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . . . C C C C C C C C R R R R R R R R X */ /* . . . . 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 X */ addr = ( ( addr_logical & 0x3fe00 ) << 1 ) | ( addr_logical & 0x3ff ); /* add C8=A17 and R8=A9 */ } } else /* ( RAM_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* RAM modules use lines MAD0-MAD7, C9/C8/R9/R8 don't exist : 17 bits per address in bank */ if ( MMU_Bank_Size == MEM_BANK_SIZE_2048 ) { /* 21 bit address is mapped to 21 bits : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* C C C C C C C C C C R R R R R R R R R R X */ /* 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 X */ addr = ( ( addr_logical & 0x7f800 ) >> 2 ) | ( addr_logical & 0x1ff ); /* remove C9/C8/R9/R8 */ } else if ( MMU_Bank_Size == MEM_BANK_SIZE_512 ) { /* 21 bit address is mapped to 19 bits (C9/R9 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . C C C C C C C C C R R R R R R R R R X */ /* . . 8 7 6 5 4 3 2 1 0 8 7 6 5 4 3 2 1 0 X */ addr = ( ( addr_logical & 0x3fc00 ) >> 1 ) | ( addr_logical & 0x1ff ); /* remove C8/R8 */ } else /* if ( MMU_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* 21 bit address is mapped to 17 bits (C9/C8/R9/R8 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . . . C C C C C C C C R R R R R R R R X */ /* . . . . 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 X */ addr = addr_logical; } } addr &= ( RAM_Bank_Size - 1 ); /* Keep address inside RAM bank size */ return addr; } /** * STE : translate a logical address (as used by the CPU, DMA or the shifter) into a physical inside * the corresponding RAM bank using the RAS/CAS signal. * The STE MMU maps a 21 bit address (bits A20 .. A0) as follow : * - A0 : used to select low/high byte of a 16 bit word * - A1 ... A20 -> RAS0 CAS0 RAS1 CAS1 ... RAS9 CAS9 * * Note : the following code uses 9 cases for readability and to compare with STF, but it could be * largely reduced as many cases are common. */ static uint32_t STMemory_MMU_Translate_Addr_STE ( uint32_t addr_logical , int RAM_Bank_Size , int MMU_Bank_Size ) { uint32_t addr; if ( RAM_Bank_Size == MEM_BANK_SIZE_2048 ) { /* RAM modules use lines MAD0-MAD9, C9/C8/R9/R8 exist : 21 bits per address in bank */ if ( MMU_Bank_Size == MEM_BANK_SIZE_2048 ) { /* 21 bit address is mapped to 21 bits : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* C R C R C R C R C R C R C R C R C R C R X */ /* 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = addr_logical; } else if ( MMU_Bank_Size == MEM_BANK_SIZE_512 ) { /* 21 bit address is mapped to 19 bits (C9/R9 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . C R C R C R C R C R C R C R C R C R X */ /* . . 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = ( addr_logical & 0x1fffff ); /* add C9=A20 and R9=A19 */ } else /* if ( MMU_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* 21 bit address is mapped to 17 bits (C9/C8/R9/R8 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . . . C R C R C R C R C R C R C R C R X */ /* . . . . 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = ( addr_logical & 0x1fffff ); /* add C9=A20 C8=A18 and R9=A19 R8=A17 */ } } else if ( RAM_Bank_Size == MEM_BANK_SIZE_512 ) { /* RAM modules use lines MAD0-MAD8, C9/R9 don't exist, C8/R8 exist : 19 bits per address in bank */ if ( MMU_Bank_Size == MEM_BANK_SIZE_2048 ) { /* 21 bit address is mapped to 21 bits : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* C R C R C R C R C R C R C R C R C R C R X */ /* 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = ( addr_logical & 0x7ffff ); /* remove C9/R9 */ } else if ( MMU_Bank_Size == MEM_BANK_SIZE_512 ) { /* 21 bit address is mapped to 19 bits (C9/R9 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . C R C R C R C R C R C R C R C R C R X */ /* . . 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = addr_logical; } else /* if ( MMU_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* 21 bit address is mapped to 17 bits (C9/C8/R9/R8 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . . . C R C R C R C R C R C R C R C R X */ /* . . . . 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = ( addr_logical & 0x7ffff ); /* add C8=A18 and R8=A17 */ } } else /* ( RAM_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* RAM modules use lines MAD0-MAD7, C9/C8/R9/R8 don't exist : 17 bits per address in bank */ if ( MMU_Bank_Size == MEM_BANK_SIZE_2048 ) { /* 21 bit address is mapped to 21 bits : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* C R C R C R C R C R C R C R C R C R C R X */ /* 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = ( addr_logical & 0x1ffff ); /* remove C9/C8/R9/R8 */ } else if ( MMU_Bank_Size == MEM_BANK_SIZE_512 ) { /* 21 bit address is mapped to 19 bits (C9/R9 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . C R C R C R C R C R C R C R C R C R X */ /* . . 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = ( addr_logical & 0x1ffff ); /* remove C8/R8 */ } else /* if ( MMU_Bank_Size == MEM_BANK_SIZE_128 ) */ { /* 21 bit address is mapped to 17 bits (C9/C8/R9/R8 are not used) : */ /* a20 a19 a18 a17 a16 a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0 */ /* . . . . C R C R C R C R C R C R C R C R X */ /* . . . . 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 X */ addr = addr_logical; } } addr &= ( RAM_Bank_Size - 1 ); /* Keep address inside RAM bank size */ return addr; } /** * Translate a logical address into a physical address inside the STRam[] buffer * by taking into account the size of the 2 MMU banks and the machine type (STF or STE) */ uint32_t STMemory_MMU_Translate_Addr ( uint32_t addr_logical ) { uint32_t addr; uint32_t addr_physical; uint32_t Bank_Start_physical; int RAM_Bank_Size , MMU_Bank_Size; /* MMU only translates RAM addr < 4 MB */ /* If logical address is beyond total MMU size and < 4MB, then we don't translate either */ /* Useless check below : memory_map_Standard_RAM() ensures addr_logical is always < MMU_Bank0_Size + MMU_Bank1_Size when MMU is enabled */ // if ( addr_logical >= MMU_Bank0_Size + MMU_Bank1_Size ) // return addr_logical; addr = addr_logical; if ( addr < MMU_Bank0_Size ) /* Accessing bank0 */ { Bank_Start_physical = 0; /* Bank0's start relative to STRam[] */ RAM_Bank_Size = RAM_Bank0_Size; /* Physical size for bank0 */ MMU_Bank_Size = MMU_Bank0_Size; /* Logical size for bank0 */ } else /* Accessing bank1 */ { Bank_Start_physical = RAM_Bank0_Size; /* Bank1's start relative to STRam[] */ RAM_Bank_Size = RAM_Bank1_Size; /* Physical size for bank1 */ MMU_Bank_Size = MMU_Bank1_Size; /* Logical size for bank1 */ } if ( Config_IsMachineST() ) /* For STF / Mega STF */ addr_physical = STMemory_MMU_Translate_Addr_STF ( addr , RAM_Bank_Size , MMU_Bank_Size ); else /* For STE / Mega STE */ addr_physical = STMemory_MMU_Translate_Addr_STE ( addr , RAM_Bank_Size , MMU_Bank_Size ); addr_physical += Bank_Start_physical; //fprintf ( stderr , "mmu translate %x -> %x pc=%x\n" , addr_logical , addr_physical , M68000_GetPC() ); return addr_physical; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/statusbar.c000066400000000000000000000657121504763705000235170ustar00rootroot00000000000000/* Hatari - statusbar.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Code to draw statusbar area, floppy leds etc. Use like this: - Before screen surface is (re-)created Statusbar_SetHeight() has to be called with the new screen height. Add the returned value to screen height (zero means no statusbar). After this, Statusbar_GetHeight() can be used to retrieve the statusbar size - After screen surface is (re-)created, call Statusbar_Init() to re-initialize / re-draw the statusbar - Call Statusbar_SetFloppyLed() to set floppy drive led ON/OFF, or call Statusbar_EnableHDLed() to enabled HD led for a while - Whenever screen is redrawn, call Statusbar_Update() to update statusbar contents and find out whether and what screen area needs to be updated (outside of screen locking) - If screen redraws can be partial, Statusbar_OverlayRestore() needs to be called before locking the screen for drawing and Statusbar_OverlayBackup() needs to be called after screen unlocking, but before calling Statusbar_Update(). These are needed for hiding the overlay drive led (= restoring the area that was below them before LED was shown) when drive leds are turned OFF. - If other information shown by Statusbar (TOS version etc) changes, call Statusbar_UpdateInfo() TODO: - re-calculate colors on each update to make sure they're correct in Falcon & TT 8-bit palette modes? - call Statusbar_AddMessage() from log.c? */ const char Statusbar_fileid[] = "Hatari statusbar.c"; #include #include "main.h" #include "configuration.h" #include "screenSnapShot.h" #include "sdlgui.h" #include "statusbar.h" #include "tos.h" #include "screen.h" #include "video.h" #include "wavFormat.h" #include "ymFormat.h" #include "avi_record.h" #include "vdi.h" #include "fdc.h" #include "stMemory.h" #include "blitter.h" #include "str.h" #include "lilo.h" #define DEBUG 0 #if DEBUG # include # define DEBUGPRINT(x) printf x #else # define DEBUGPRINT(x) #endif /* space needed for FDC information */ #define FDC_MSG_MAX_LEN 20 #define STATUSBAR_LINES 2 #define MAX_DRIVE_LEDS (DRIVE_LED_HD + 1) /* whole statusbar area, for full updates */ static SDL_Rect FullRect; /* whether drive leds should be ON and their previous shown state */ static struct { drive_led_t state; drive_led_t oldstate; Uint32 expire; /* when to disable led, valid only if >0 && state=TRUE */ int offset; /* led x-pos on screen */ } Led[MAX_DRIVE_LEDS]; /* drive leds size & y-pos */ static SDL_Rect LedRect; /* overlay led size & pos */ static SDL_Rect OverlayLedRect; /* screen contents left under overlay led */ static SDL_Surface *OverlayUnderside; static enum { OVERLAY_NONE, OVERLAY_DRAWN, OVERLAY_RESTORED } nOverlayState; static SDL_Rect RecLedRect; static bool bOldRecording; static SDL_Rect BltLedRect; static int BltLed_lines_on; /* led colors */ static Uint32 LedColor[ MAX_LED_STATE ]; static Uint32 BltColorOn, BltColorOff; static Uint32 RecColorOn, RecColorOff; static Uint32 GrayBg, LedColorBg; /* needs to be enough for all messages, but <= MessageRect width / font width */ #define MAX_MESSAGE_LEN 63 typedef struct msg_item { struct msg_item *next; char msg[MAX_MESSAGE_LEN+1]; Uint32 timeout; /* msecs, zero=no timeout */ Uint32 expire; /* when to expire message */ bool shown; } msg_item_t; static msg_item_t DefaultMessage; static msg_item_t *MessageList = &DefaultMessage; static SDL_Rect MessageRect; /* rect for both frame skip value and fast forward indicator */ static SDL_Rect FrameSkipsRect; static int nOldFrameSkips; static int bOldFastForward; static SDL_Rect FDCTextRect; static SDL_Rect JoysticksTextRect; /* screen height above statusbar and height of statusbar below screen */ static int ScreenHeight; static int StatusbarHeight; /*-----------------------------------------------------------------------*/ /** * Return statusbar height for given width and height */ int Statusbar_GetHeightForSize(int width, int height) { int h = 0; /* Must arrive at same conclusion about font size as SDLGui_SetScreen(), * and max size returned by this must correspond to STATUSBAR_MAX_HEIGHT */ if (ConfigureParams.Screen.bShowStatusbar) { /* smaller SDL GUI font height = 8, larger = 16 */ h = 8; if (width >= 640 && height >= (400-2*h)) { h *= 2; } h += 1+1; h *= STATUSBAR_LINES; } DEBUGPRINT(("Statusbar_GetHeightForSize(%d, %d) -> %d\n", width, height, h)); return h; } /*-----------------------------------------------------------------------*/ /** * Set screen height used for statusbar height calculation. * * Return height of statusbar that should be added to the screen * height when screen is (re-)created, or zero if statusbar will * not be shown */ int Statusbar_SetHeight(int width, int height) { #if DEBUG /* find out from where the set height is called */ void *addr[8]; int count = backtrace(addr, sizeof(addr)/sizeof(*addr)); backtrace_symbols_fd(addr, count, fileno(stderr)); #endif ScreenHeight = height; StatusbarHeight = Statusbar_GetHeightForSize(width, height); DEBUGPRINT(("Statusbar_SetHeight(%d, %d) -> %d\n", width, height, StatusbarHeight)); return StatusbarHeight; } /*-----------------------------------------------------------------------*/ /** * Return height of statusbar set with Statusbar_SetHeight() */ int Statusbar_GetHeight(void) { return StatusbarHeight; } /*-----------------------------------------------------------------------*/ /** * Enable HD drive led, it will be automatically disabled after a while. */ void Statusbar_EnableHDLed(drive_led_t state) { /* leds are shown for 1/2 sec after enabling */ Led[DRIVE_LED_HD].expire = SDL_GetTicks() + 1000/2; Led[DRIVE_LED_HD].state = state; } /*-----------------------------------------------------------------------*/ /** * Set given floppy drive led state, anything enabling led with this * needs also to take care of disabling it. */ void Statusbar_SetFloppyLed(drive_index_t drive, drive_led_t state) { assert(drive == DRIVE_LED_A || drive == DRIVE_LED_B); Led[drive].state = state; } /*-----------------------------------------------------------------------*/ /** * Compute the number of lines to fill with color BltColorOn, depending * on the blitter's activity per VBL * return a value between 0 and max_h (included) */ static int Statusbar_BlitterGetLinesOn ( int max_h ) { double res; res = ( max_h * Blitter_StatsGetRate() ) / 100.0; return ceil(res); } /*-----------------------------------------------------------------------*/ /** * Return a text corresponding to the status of each emulated joystick * - : off J : real joystick K : keyboard emulation */ static void Statusbar_JoysticksGetText ( char *buf ) { int i; for (i = 0; i < JOYSTICK_COUNT; i++) { switch (ConfigureParams.Joysticks.Joy[i].nJoystickMode) { case JOYSTICK_DISABLED: *buf++ = '-'; break; case JOYSTICK_REALSTICK: *buf++ = 'J'; break; case JOYSTICK_KEYBOARD: *buf++ = 'K'; break; } } *buf = '\0'; } /*-----------------------------------------------------------------------*/ /** * Set overlay led size/pos on given screen to internal Rect * and free previous resources. */ static void Statusbar_OverlayInit(const SDL_Surface *surf) { int h; /* led size/pos needs to be re-calculated in case screen changed */ h = surf->h / 50; OverlayLedRect.w = 2*h; OverlayLedRect.h = h; OverlayLedRect.x = surf->w - 5*h/2; OverlayLedRect.y = h/2; /* free previous restore surface if it's incompatible */ if (OverlayUnderside && ( OverlayUnderside->w != OverlayLedRect.w || OverlayUnderside->h != OverlayLedRect.h || OverlayUnderside->format->BitsPerPixel != surf->format->BitsPerPixel)) { SDL_FreeSurface(OverlayUnderside); OverlayUnderside = NULL; } nOverlayState = OVERLAY_NONE; } /*-----------------------------------------------------------------------*/ /** * (re-)initialize statusbar internal variables for given screen surface * (sizes&colors may need to be re-calculated for the new SDL surface) * and draw the statusbar background. */ void Statusbar_Init(SDL_Surface *surf) { msg_item_t *item; SDL_Rect ledbox; int i, fontw, fonth, lineh, xoffset, yoffset; const char *text[MAX_DRIVE_LEDS] = { "A:", "B:", "HD:" }; char FdcText[FDC_MSG_MAX_LEN]; int FdcTextLen; char JoysticksText[JOYSTICK_COUNT+1]; DEBUGPRINT(("Statusbar_Init()\n")); assert(surf); /* dark green and light green for leds themselves */ LedColor[ LED_STATE_OFF ] = SDL_MapRGB(surf->format, 0x00, 0x40, 0x00); LedColor[ LED_STATE_ON ] = SDL_MapRGB(surf->format, 0x00, 0xc0, 0x00); LedColor[ LED_STATE_ON_BUSY ] = SDL_MapRGB(surf->format, 0x00, 0xe0, 0x00); LedColorBg = SDL_MapRGB(surf->format, 0x00, 0x00, 0x00); BltColorOff = SDL_MapRGB(surf->format, 0x40, 0x00, 0x00); BltColorOn = SDL_MapRGB(surf->format, 0xe0, 0x00, 0x00); RecColorOff = SDL_MapRGB(surf->format, 0x40, 0x00, 0x00); RecColorOn = SDL_MapRGB(surf->format, 0xe0, 0x00, 0x00); GrayBg = SDL_MapRGB(surf->format, 0xc0, 0xc0, 0xc0); /* disable leds */ for (i = 0; i < MAX_DRIVE_LEDS; i++) { Led[i].state = Led[i].oldstate = LED_STATE_OFF; Led[i].expire = 0; } Statusbar_OverlayInit(surf); /* disable statusbar if it doesn't fit to video mode */ if (surf->h < ScreenHeight + StatusbarHeight) { StatusbarHeight = 0; } if (!StatusbarHeight) { DEBUGPRINT(("Doesn't fit <- Statusbar_Init()\n")); return; } /* prepare fonts */ SDLGui_Init(); SDLGui_SetScreen(surf); SDLGui_GetFontSize(&fontw, &fonth); /* video mode didn't match, need to recalculate sizes */ lineh = 1+fonth+1; if (surf->h > ScreenHeight + StatusbarHeight) { StatusbarHeight = STATUSBAR_LINES*lineh; /* actually statusbar vertical offset */ ScreenHeight = surf->h - StatusbarHeight; } else { assert(STATUSBAR_LINES*lineh <= StatusbarHeight); } /* draw statusbar background gray so that text shows */ FullRect.x = 0; FullRect.y = surf->h - StatusbarHeight; FullRect.w = surf->w; FullRect.h = StatusbarHeight; SDL_FillRect(surf, &FullRect, GrayBg); /* initialize messages (first row) */ MessageRect.x = fontw; MessageRect.y = ScreenHeight + lineh/2 - fonth/2; MessageRect.w = surf->w - fontw; MessageRect.h = fonth; for (item = MessageList; item; item = item->next) { item->shown = false; } /* indicator leds size (second row) */ LedRect.w = fonth/2; LedRect.h = fonth - 4; LedRect.y = ScreenHeight + lineh + lineh/2 - LedRect.h/2; /* black box for the leds */ ledbox = LedRect; ledbox.y -= 1; ledbox.w += 2; ledbox.h += 2; xoffset = fontw; yoffset = ScreenHeight + lineh + lineh/2 - fonth/2; /* draw led texts and boxes + calculate box offsets */ for (i = 0; i < MAX_DRIVE_LEDS; i++) { SDLGui_Text(xoffset, yoffset, text[i]); xoffset += strlen(text[i]) * fontw; xoffset += fontw/2; ledbox.x = xoffset - 1; SDL_FillRect(surf, &ledbox, LedColorBg); LedRect.x = xoffset; SDL_FillRect(surf, &LedRect, LedColor[ LED_STATE_OFF ]); Led[i].offset = xoffset; xoffset += LedRect.w + fontw; } /* print FDC's info */ FDCTextRect.x = xoffset; FDCTextRect.y = yoffset; FdcTextLen = FDC_Get_Statusbar_Text(FdcText, sizeof(FdcText)); SDLGui_Text(FDCTextRect.x, FDCTextRect.y, FdcText); FDCTextRect.w = FdcTextLen * fontw + fontw/2; FDCTextRect.h = fonth; xoffset += FDCTextRect.w; /* print joysticks' info */ xoffset += 2*fontw; JoysticksTextRect.x = xoffset; JoysticksTextRect.y = yoffset; Statusbar_JoysticksGetText(JoysticksText); SDLGui_Text(JoysticksTextRect.x, JoysticksTextRect.y, JoysticksText); JoysticksTextRect.w = JOYSTICK_COUNT * fontw + fontw/2; JoysticksTextRect.h = fonth; // xoffset += JoysticksTextRect.w; /* draw frameskip on the right */ FrameSkipsRect.x = surf->w - 21*fontw; FrameSkipsRect.y = yoffset; SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, "FS:"); FrameSkipsRect.x += 3 * fontw + fontw/2; FrameSkipsRect.w = 4 * fontw; FrameSkipsRect.h = fonth; if(ConfigureParams.System.bFastForward) { SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, "0 >>"); } else { SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, "0"); } nOldFrameSkips = 0; /* draw blitter led box on the right */ BltLedRect = LedRect; BltLedRect.x = surf->w - 7*fontw - BltLedRect.w; ledbox.x = BltLedRect.x - 1; SDLGui_Text(ledbox.x - 4*fontw - fontw/2, yoffset, "BLT:"); SDL_FillRect(surf, &ledbox, LedColorBg); SDL_FillRect(surf, &BltLedRect, BltColorOff); BltLed_lines_on = 0; /* draw recording led box on the right */ RecLedRect = LedRect; RecLedRect.x = surf->w - fontw - RecLedRect.w; ledbox.x = RecLedRect.x - 1; SDLGui_Text(ledbox.x - 4*fontw - fontw/2, yoffset, "REC:"); SDL_FillRect(surf, &ledbox, LedColorBg); SDL_FillRect(surf, &RecLedRect, RecColorOff); bOldRecording = false; /* and blit statusbar on screen */ Screen_UpdateRects(surf, 1, &FullRect); DEBUGPRINT(("Drawn <- Statusbar_Init()\n")); } /*-----------------------------------------------------------------------*/ /** * Queue new statusbar message 'msg' to be shown for 'msecs' milliseconds */ void Statusbar_AddMessage(const char *msg, Uint32 msecs) { msg_item_t *item; if (!ConfigureParams.Screen.bShowStatusbar) { /* no sense in queuing messages that aren't shown */ return; } item = calloc(1, sizeof(msg_item_t)); assert(item); item->next = MessageList; MessageList = item; Str_Copy(item->msg, msg, sizeof(item->msg)); DEBUGPRINT(("Add message: '%s'\n", item->msg)); if (msecs) { item->timeout = msecs; } else { /* show items by default for 2.5 secs */ item->timeout = 2500; } item->shown = false; } /*-----------------------------------------------------------------------*/ /** * Write given 'more' string to 'buffer' and return new end of 'buffer' */ static char *Statusbar_AddString(char *buffer, const char *more) { if (!more) return buffer; while (*more) *buffer++ = *more++; return buffer; } /*-----------------------------------------------------------------------*/ /** * Retrieve/update default statusbar information */ void Statusbar_UpdateInfo(void) { int size; char buffer[200]; /* large enough for any message */ char *end; end = buffer; /* CPU MHz */ if (ConfigureParams.System.nCpuFreq > 9) { *end++ = '0' + ConfigureParams.System.nCpuFreq / 10; } *end++ = '0' + ConfigureParams.System.nCpuFreq % 10; end = Statusbar_AddString(end, "MHz"); /* CPU type */ if(ConfigureParams.System.nCpuLevel > 0) { *end++ = '/'; *end++ = '0'; if ( ConfigureParams.System.nCpuLevel == 5 ) /* Special case : 68060 has nCpuLevel=5 */ *end++ = '0' + 6; else *end++ = '0' + ConfigureParams.System.nCpuLevel % 10; *end++ = '0'; } const char *mode = NULL; bool cache = ConfigureParams.System.bCpuDataCache && ConfigureParams.System.nCpuLevel > 2; /* Prefetch / cycle exact mode and data cache? */ if ( ConfigureParams.System.bCycleExactCpu ) mode = cache ? "(CED)" : "(CE)"; else if ( ConfigureParams.System.bCompatibleCpu ) mode = cache ? "(PFD)" : "(PF)"; else if (cache) mode = "(D)"; end = Statusbar_AddString(end, mode); /* additional WinUAE CPU/FPU info */ *end++ = '/'; switch (ConfigureParams.System.n_FPUType) { case FPU_68881: end = Statusbar_AddString(end, "68881"); break; case FPU_68882: end = Statusbar_AddString(end, "68882"); break; case FPU_CPU: end = ( ConfigureParams.System.nCpuLevel == 5 ? Statusbar_AddString(end, "060") : Statusbar_AddString(end, "040") ); break; default: *end++ = '-'; } if (ConfigureParams.System.bSoftFloatFPU && ConfigureParams.System.n_FPUType != FPU_NONE) { end = Statusbar_AddString(end, "(SF)"); } if (ConfigureParams.System.bMMU) { end = Statusbar_AddString(end, "/MMU"); } /* amount of memory in MB */ *end++ = ' '; size = ConfigureParams.Memory.STRamSize_KB; end += sprintf(end, "%d", size / 1024); if ( size % 1024 == 256 ) end += sprintf(end, ".25"); else if ( size % 1024 == 512 ) end += sprintf(end, ".5"); if (TTmemory && ConfigureParams.Memory.TTRamSize_KB) { end += sprintf(end, "/%i", ConfigureParams.Memory.TTRamSize_KB/1024); } end = Statusbar_AddString(end, "MB "); /* machine type */ switch (ConfigureParams.System.nMachineType) { case MACHINE_ST: end = Statusbar_AddString(end, "ST("); end = Statusbar_AddString(end, Video_GetTimings_Name()); *end++ = ')'; break; case MACHINE_MEGA_ST: end = Statusbar_AddString(end, "MegaST"); break; case MACHINE_STE: end = Statusbar_AddString(end, "STE"); break; case MACHINE_MEGA_STE: end = Statusbar_AddString(end, "MegaSTE"); break; case MACHINE_TT: end = Statusbar_AddString(end, "TT"); break; case MACHINE_FALCON: end = Statusbar_AddString(end, "Falcon"); break; default: end = Statusbar_AddString(end, "???"); } /* TOS type/version */ end = Statusbar_AddString(end, ", "); if (bIsEmuTOS) { if (EmuTosVersion > 0) { char str[20]; snprintf(str, sizeof(str), "EmuTOS %d.%d.%d", EmuTosVersion >> 24, (EmuTosVersion >> 16) & 0xff, (EmuTosVersion >> 8) & 0xff); end = Statusbar_AddString(end, str); if (EmuTosVersion & 0xff) end = Statusbar_AddString(end, "+"); } else { end = Statusbar_AddString(end, "EmuTOS"); } } else if (bUseLilo) { end = Statusbar_AddString(end, "Linux"); } else { end = Statusbar_AddString(end, "TOS "); *end++ = '0' + ((TosVersion & 0xf00) >> 8); *end++ = '.'; *end++ = '0' + ((TosVersion & 0xf0) >> 4); *end++ = '0' + (TosVersion & 0xf); } /* monitor type */ end = Statusbar_AddString(end, ", "); if (bUseVDIRes) { end = Statusbar_AddString(end, "VDI"); } else { switch (ConfigureParams.Screen.nMonitorType) { case MONITOR_TYPE_MONO: end = Statusbar_AddString(end, "MONO"); break; case MONITOR_TYPE_RGB: end = Statusbar_AddString(end, "RGB"); break; case MONITOR_TYPE_VGA: /* There were no VGA monitors for the ST/STE */ if (Config_IsMachineST() || Config_IsMachineSTE()) end = Statusbar_AddString(end, "RGB"); else end = Statusbar_AddString(end, "VGA"); break; case MONITOR_TYPE_TV: end = Statusbar_AddString(end, "TV"); break; default: *end++ = '?'; } end += sprintf(end, " %d Hz" , nScreenRefreshRate); } *end = '\0'; Str_Copy(DefaultMessage.msg, buffer, MAX_MESSAGE_LEN); DEBUGPRINT(("Set default message: '%s'\n", DefaultMessage.msg)); /* make sure default message gets (re-)drawn when next checked */ DefaultMessage.shown = false; } /*-----------------------------------------------------------------------*/ /** * Draw 'msg' centered to the message area */ static SDL_Rect* Statusbar_DrawMessage(SDL_Surface *surf, const char *msg) { int fontw, fonth, offset; SDL_FillRect(surf, &MessageRect, GrayBg); if (*msg) { SDLGui_GetFontSize(&fontw, &fonth); offset = (MessageRect.w - strlen(msg) * fontw) / 2; SDLGui_Text(MessageRect.x + offset, MessageRect.y, msg); } DEBUGPRINT(("Draw message: '%s'\n", msg)); return &MessageRect; } /*-----------------------------------------------------------------------*/ /** * If message's not shown, show it. If message's timed out, * remove it and show next one. * * Return updated area, or NULL if nothing drawn */ static SDL_Rect* Statusbar_ShowMessage(SDL_Surface *surf, Uint32 ticks) { msg_item_t *next; if (MessageList->shown) { if (!MessageList->expire) { /* last/default message newer expires */ return NULL; } if (MessageList->expire > ticks) { /* not timed out yet */ return NULL; } assert(MessageList->next); /* last message shouldn't end here */ next = MessageList->next; free(MessageList); /* show next */ MessageList = next; } /* not shown yet, show */ MessageList->shown = true; if (MessageList->timeout && !MessageList->expire) { MessageList->expire = ticks + MessageList->timeout; } return Statusbar_DrawMessage(surf, MessageList->msg); } /*-----------------------------------------------------------------------*/ /** * Save the area that will be left under overlay led */ void Statusbar_OverlayBackup(SDL_Surface *surf) { if ((StatusbarHeight && ConfigureParams.Screen.bShowStatusbar) || !ConfigureParams.Screen.bShowDriveLed) { /* overlay not used with statusbar */ return; } assert(surf); if (!OverlayUnderside) { SDL_Surface *bak; SDL_PixelFormat *fmt = surf->format; bak = SDL_CreateRGBSurface(surf->flags, OverlayLedRect.w, OverlayLedRect.h, fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask); assert(bak); OverlayUnderside = bak; } SDL_BlitSurface(surf, &OverlayLedRect, OverlayUnderside, NULL); } /*-----------------------------------------------------------------------*/ /** * Restore the area left under overlay led * * State machine for overlay led handling will return from * Statusbar_Update() call the area that is restored (if any) */ void Statusbar_OverlayRestore(SDL_Surface *surf) { if ((StatusbarHeight && ConfigureParams.Screen.bShowStatusbar) || !ConfigureParams.Screen.bShowDriveLed) { /* overlay not used with statusbar */ return; } if (nOverlayState == OVERLAY_DRAWN && OverlayUnderside) { assert(surf); SDL_BlitSurface(OverlayUnderside, NULL, surf, &OverlayLedRect); /* this will make the draw function to update this the screen */ nOverlayState = OVERLAY_RESTORED; } } /*-----------------------------------------------------------------------*/ /** * Draw overlay led */ static void Statusbar_OverlayDrawLed(SDL_Surface *surf, Uint32 color) { SDL_Rect rect; if (nOverlayState == OVERLAY_DRAWN) { /* some led already drawn */ return; } nOverlayState = OVERLAY_DRAWN; /* enabled led with border */ rect = OverlayLedRect; rect.x += 1; rect.y += 1; rect.w -= 2; rect.h -= 2; SDL_FillRect(surf, &OverlayLedRect, LedColorBg); SDL_FillRect(surf, &rect, color); } /*-----------------------------------------------------------------------*/ /** * Draw overlay led onto screen surface if any drives are enabled. * * Return updated area, or NULL if nothing drawn */ static SDL_Rect* Statusbar_OverlayDraw(SDL_Surface *surf) { Uint32 currentticks = SDL_GetTicks(); int i; if (bRecordingYM || bRecordingWav || Avi_AreWeRecording()) { Statusbar_OverlayDrawLed(surf, RecColorOn); } for (i = 0; i < MAX_DRIVE_LEDS; i++) { if (Led[i].state) { if (Led[i].expire && Led[i].expire < currentticks) { Led[i].state = LED_STATE_OFF; continue; } Statusbar_OverlayDrawLed(surf, LedColor[ Led[i].state ]); break; } } /* possible state transitions: * NONE -> DRAWN -> RESTORED -> DRAWN -> RESTORED -> NONE * Other than NONE state needs to be updated on screen */ switch (nOverlayState) { case OVERLAY_RESTORED: nOverlayState = OVERLAY_NONE; /* fall through */ case OVERLAY_DRAWN: DEBUGPRINT(("Overlay LED = %s\n", nOverlayState==OVERLAY_DRAWN?"ON":"OFF")); return &OverlayLedRect; case OVERLAY_NONE: break; } return NULL; } /*-----------------------------------------------------------------------*/ /** * Update statusbar information (leds etc) if/when needed. * * May not be called when screen is locked (SDL limitation). * * Return updated area, or NULL if nothing is drawn. */ SDL_Rect* Statusbar_Update(SDL_Surface *surf, bool do_update) { static char FdcOld[FDC_MSG_MAX_LEN] = ""; char FdcNew[FDC_MSG_MAX_LEN]; static char JoysticksOld[JOYSTICK_COUNT+1] = ""; char JoysticksNew[JOYSTICK_COUNT+1]; Uint32 color, currentticks; static SDL_Rect rect; SDL_Rect *last_rect; int i, updates; int BltLed_lines_on_new; /* Don't update anything on screen if video output is disabled */ if ( ConfigureParams.Screen.DisableVideo ) return NULL; assert(surf); if (!(StatusbarHeight && ConfigureParams.Screen.bShowStatusbar)) { last_rect = NULL; /* not enabled (anymore), show overlay led instead? */ if (ConfigureParams.Screen.bShowDriveLed) { last_rect = Statusbar_OverlayDraw(surf); if (do_update && last_rect) { Screen_UpdateRects(surf, 1, last_rect); last_rect = NULL; } } return last_rect; } /* Statusbar_Init() not called before this? */ #if DEBUG if (surf->h != ScreenHeight + StatusbarHeight) { printf("DEBUG: %d != %d + %d\n", surf->h, ScreenHeight, StatusbarHeight); } #endif assert(surf->h == ScreenHeight + StatusbarHeight); currentticks = SDL_GetTicks(); last_rect = Statusbar_ShowMessage(surf, currentticks); updates = last_rect ? 1 : 0; rect = LedRect; for (i = 0; i < MAX_DRIVE_LEDS; i++) { if (Led[i].expire && Led[i].expire < currentticks) { Led[i].state = LED_STATE_OFF; } if (Led[i].state == Led[i].oldstate) { continue; } Led[i].oldstate = Led[i].state; color = LedColor[ Led[i].state ]; rect.x = Led[i].offset; SDL_FillRect(surf, &rect, color); DEBUGPRINT(("LED[%d] = %d\n", i, Led[i].state)); last_rect = ▭ updates++; } FDC_Get_Statusbar_Text(FdcNew, sizeof(FdcNew)); if (strcmp(FdcNew, FdcOld)) { strcpy(FdcOld, FdcNew); SDL_FillRect(surf, &FDCTextRect, GrayBg); SDLGui_Text(FDCTextRect.x, FDCTextRect.y, FdcNew); last_rect = &FDCTextRect; updates++; } /* joysticks' type */ Statusbar_JoysticksGetText(JoysticksNew); if (strcmp(JoysticksNew, JoysticksOld)) { strcpy(JoysticksOld, JoysticksNew); SDL_FillRect(surf, &JoysticksTextRect, GrayBg); SDLGui_Text(JoysticksTextRect.x, JoysticksTextRect.y, JoysticksNew); last_rect = &JoysticksTextRect; updates++; } if (nOldFrameSkips != nFrameSkips || bOldFastForward != ConfigureParams.System.bFastForward) { char fscount[5]; int end = 2; nOldFrameSkips = nFrameSkips; bOldFastForward = ConfigureParams.System.bFastForward; if (nFrameSkips < 10) fscount[0] = '0' + nFrameSkips; else fscount[0] = 'X'; fscount[1] = ' '; if(ConfigureParams.System.bFastForward) { fscount[2] = '>'; fscount[3] = '>'; end = 4; } fscount[end] = '\0'; SDL_FillRect(surf, &FrameSkipsRect, GrayBg); SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, fscount); DEBUGPRINT(("FS = %s\n", fscount)); last_rect = &FrameSkipsRect; updates++; } /* Blitter : draw 'BltLed_lines_on_new' lines with color BltColorOn */ /* and the rest with color BltColorOff. This gives some kind of vu-meter */ BltLed_lines_on_new = Statusbar_BlitterGetLinesOn( BltLedRect.h ); if (BltLed_lines_on_new != BltLed_lines_on) { BltLed_lines_on = BltLed_lines_on_new; if ( BltLed_lines_on > 0 ) { rect = BltLedRect; rect.y += rect.h - BltLed_lines_on; rect.h = BltLed_lines_on; SDL_FillRect(surf, &rect, BltColorOn); last_rect = ▭ updates++; } if ( BltLed_lines_on < BltLedRect.h ) { rect = BltLedRect; rect.h -= BltLed_lines_on; SDL_FillRect(surf, &rect, BltColorOff); last_rect = ▭ updates++; } } if ((bRecordingYM || bRecordingWav || Avi_AreWeRecording()) != bOldRecording) { bOldRecording = !bOldRecording; if (bOldRecording) { color = RecColorOn; } else { color = RecColorOff; } SDL_FillRect(surf, &RecLedRect, color); DEBUGPRINT(("REC = ON\n")); last_rect = &RecLedRect; updates++; } if (updates > 1) { /* multiple items updated -> update whole statusbar */ last_rect = &FullRect; } if (do_update && last_rect) { Screen_UpdateRects(surf, 1, last_rect); last_rect = NULL; } return last_rect; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/str.c000066400000000000000000000377471504763705000223260ustar00rootroot00000000000000/* Hatari - str.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. String functions. */ const char Str_fileid[] = "Hatari str.c"; #include #include #include #include #include #include #include #include "main.h" #include "configuration.h" #include "str.h" /** * Remove whitespace from beginning and end of a string. * Returns the trimmed string (string content is moved * so that it still starts from the same address) */ char *Str_Trim(char *buffer) { int i, linelen; if (buffer == NULL) return NULL; linelen = strlen(buffer); for (i = 0; i < linelen; i++) { if (!isspace((unsigned char)buffer[i])) break; } if (i > 0 && i < linelen) { linelen -= i; memmove(buffer, buffer + i, linelen); } for (i = linelen; i > 0; i--) { if (!isspace((unsigned char)buffer[i-1])) break; } buffer[i] = '\0'; return buffer; } /** * Convert a string to uppercase in place. */ char *Str_ToUpper(char *pString) { char *str = pString; while (*str) { *str = toupper((unsigned char)*str); str++; } return pString; } /** * Convert string to lowercase in place. */ char *Str_ToLower(char *pString) { char *str = pString; while (*str) { *str = tolower((unsigned char)*str); str++; } return pString; } /** * Allocate memory for a string and check for out-of memory (and exit the * program in that case, since there is likely nothing we can do if we even * can not allocate small strings anymore). * * @param len Length of the string (without the trailing NUL character) */ char *Str_Alloc(int len) { char *newstr = malloc(len + 1); if (!newstr) { perror("string allocation failed"); exit(1); } newstr[0] = newstr[len] = 0; return newstr; } /** * This function is like strdup, but also checks for out-of memory and exits * the program in that case (there is likely nothing we can do if we even can * not allocate small strings anymore). */ char *Str_Dup(const char *str) { char *newstr; if (!str) return NULL; newstr = strdup(str); if (!newstr) { perror("string duplication failed"); exit(1); } return newstr; } /** * Copy string from pSrc to pDest, taking the destination buffer size * into account. * This function is similar to strscpy() in the Linux-kernel, it * is a replacement for strlcpy() (which cannot be used on untrusted * source strings since it tries to find out its length). Our * function here returns -E2BIG instead if the string does not * fit the destination buffer. */ long Str_Copy(char *pDest, const char *pSrc, long nBufLen) { long nCount = 0; if (nBufLen == 0) return -E2BIG; while (nBufLen) { char c; c = pSrc[nCount]; pDest[nCount] = c; if (!c) return nCount; nCount++; nBufLen--; } /* Hit buffer length without finding a NUL; force NUL-termination. */ if (nCount > 0) pDest[nCount - 1] = '\0'; return -E2BIG; } #if 0 /** * truncate string at first unprintable char (e.g. newline). */ char *Str_Trunc(char *pString) { int i = 0; char *str = pString; while (str[i] != '\0') { if (!isprint((unsigned)str[i])) { str[i] = '\0'; break; } i++; } return pString; } #endif #if 0 /** * check if string is valid hex number. */ bool Str_IsHex(const char *str) { int i = 0; while (str[i] != '\0' && str[i] != ' ') { if (!isxdigit((unsigned)str[i])) return false; i++; } return true; } #endif /** * Convert "\e", "\n", "\t", "\\" backslash escapes in given string to * corresponding byte values, anything else as left as-is. */ void Str_UnEscape(char *s1) { char *s2 = s1; for (; *s1; s1++) { if (*s1 != '\\') { *s2++ = *s1; continue; } s1++; switch(*s1) { case 'e': *s2++ = '\e'; break; case 'n': *s2++ = '\n'; break; case 't': *s2++ = '\t'; break; case '\\': *s2++ = '\\'; break; default: s1--; *s2++ = '\\'; } } assert(s2 < s1); *s2 = '\0'; } /** * Convert potentially too long host filenames to 8.3 TOS filenames * by first converting host encoding to Atari one, truncating extension * and part before it, replacing invalid GEMDOS file name characters * with INVALID_CHAR + upcasing the result. * * Matching them from the host file system should first try exact * case-insensitive match, and then with a pattern that takes into * account the conversion done in here. */ void Str_Filename_Host2Atari(const char *source, char *dst) { char *dot, *tmp, *src; int len; src = strdup(source); /* dup so that it can be modified */ /* convert host string encoding to AtariST character set */ if (ConfigureParams.HardDisk.bFilenameConversion) Str_HostToAtari(source, src, INVALID_CHAR); len = strlen(src); /* does filename have an extension? */ dot = strrchr(src, '.'); if (dot) { /* limit extension to 3 chars */ if (src + len - dot > 3) dot[4] = '\0'; /* if there are extra dots, convert them */ for (tmp = src; tmp < dot; tmp++) if (*tmp == '.') *tmp = INVALID_CHAR; /* limit part before extension to 8 chars */ if (dot - src > 8) memmove(src + 8, dot, strlen(dot) + 1); } else if (len > 8) src[8] = '\0'; strcpy(dst, src); free(src); /* upcase and replace rest of invalid characters */ for (tmp = dst; *tmp; tmp++) { /* invalid characters above 0x80 have already been replaced */ if (((unsigned char)*tmp) < 32 || *tmp == 127) *tmp = INVALID_CHAR; else { switch (*tmp) { case '*': case '/': case ':': case '?': case '\\': case '{': case '}': *tmp = INVALID_CHAR; break; default: if (((unsigned char)*tmp) < 128) *tmp = toupper((unsigned char)*tmp); } } } } /* ---------------------------------------------------------------------- */ /* Implementation of character set conversions */ /* Maps AtariST characters 0x80..0xFF to unicode code points * see http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/ATARIST.TXT */ static int mapAtariToUnicode[128] = { 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x00DF, 0x0192, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 0x00E3, 0x00F5, 0x00D8, 0x00F8, 0x0153, 0x0152, 0x00C0, 0x00C3, 0x00D5, 0x00A8, 0x00B4, 0x2020, 0x00B6, 0x00A9, 0x00AE, 0x2122, 0x0133, 0x0132, 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05DF, 0x05DA, 0x05DD, 0x05E3, 0x05E5, 0x00A7, 0x2227, 0x221E, 0x03B1, 0x03B2, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x222E, 0x03C6, 0x2208, 0x2229, 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x00B3, 0x00AF }; /* Hashtable which maps unicode code points to AtariST characters 0x80..0xFF. * The last 9 bits of the unicode code point provide a hash function * without collisions. */ static char mapUnicodeToAtari[512]; /** * Initializations needed for string conversions */ void Str_Init(void) { int i; for (i = 0; i < 128; i++) { mapUnicodeToAtari[mapAtariToUnicode[i] & 511] = i; } #if defined(WIN32) || defined(USE_LOCALE_CHARSET) /* Change libc from default "C" locale to one * specified by the program environment. Needed * only for Windows, as Unix based OSes (are * assumed to) use UTF-8 based locales nowadays. */ setlocale(LC_ALL, ""); #endif } #if !(defined(WIN32) || defined(USE_LOCALE_CHARSET)) /** * Convert a 0-terminated string in the AtariST character set to a 0-terminated * UTF-8 encoded string. destLen is the number of available bytes in dest[]. * A single character of the AtariST charset can consume up to 3 bytes in UTF-8. * Returns true if any chars were mapped to UTF-8. */ static bool Str_AtariToUtf8(const char *source, char *dest, int destLen) { bool mapped = false; int c; while (*source) { c = *source++ & 255; if (c >= 128) { c = mapAtariToUnicode[c & 127]; } if (c < 128 && destLen > 1) { *dest++ = c; /* 0xxxxxxx */ destLen--; } else if (c < 2048 && destLen > 2) { *dest++ = (c >> 6) | 192; /* 110xxxxx */ *dest++ = (c & 63) | 128; /* 10xxxxxx */ destLen -= 2; } else if (destLen > 3) { *dest++ = (c >> 12) | 224; /* 1110xxxx */ *dest++ = ((c >> 6) & 63) | 128; /* 10xxxxxx */ *dest++ = (c & 63) | 128; /* 10xxxxxx */ destLen -= 3; } mapped = true; } *dest = 0; return mapped; } /** * Convert a 0-terminated utf-8 encoded string to a 0-terminated string * in the AtariST character set. * when char has no mapping, replacementChar is used, and false returned. */ static bool Str_Utf8ToAtari(const char *source, char *dest, char replacementChar) { int c, c2, c3, i; bool mapped = (*source != 0); while (*source) { c = *source++ & 255; if (c < 128) /* single-byte utf-8 code (0xxxxxxx) */ { *dest++ = c; } else if (c < 192) /* invalid utf-8 encoding (10xxxxxx) */ { *dest++ = replacementChar; mapped = false; } else /* multi-byte utf-8 code */ { if (c < 224) /* 110xxxxx, 10xxxxxx */ { c2 = *source++; c = ((c & 31) << 6) | (c2 & 63); } else if (c < 240) /* 1110xxxx, 10xxxxxx, 10xxxxxx */ { c2 = *source++; c3 = *source++; c = ((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63); } /* find AtariST character code for unicode code point c */ i = mapUnicodeToAtari[c & 511]; if (mapAtariToUnicode[i] == c) { *dest++ = i + 128; } else { *dest++ = replacementChar; mapped = false; } } } *dest = 0; return mapped; } #else /* WIN32 || USE_LOCALE_CHARSET */ /** * Convert a string from the AtariST character set into the host representation as * defined by the current locale. If characters do not exist in character set * of the host as defined by the locale, they're replaced by replacementChar, * and false returned. */ static bool Str_AtariToLocal(const char *source, char *dest, int destLen, char replacementChar) { int c, i; bool mapped = (*source != 0); while (*source && destLen > (int)MB_CUR_MAX) { c = *source++ & 255; if (c >= 128) c = mapAtariToUnicode[c & 127]; /* convert the unicode code point c to a character in the current locale */ i = wctomb(dest, c); if (i < 0) { *dest = replacementChar; mapped = false; i = 1; } dest += i; destLen -= i; } *dest = 0; return mapped; } /** * Convert a string from the character set defined by current host locale into the * AtariST character set. Characters which do not exist in the AtariST character set * will be replaced by replacementChar, and false returned. */ static bool Str_LocalToAtari(const char *source, char *dest, char replacementChar) { int i; wchar_t c; bool mapped = (*source != 0); while (*source) { /* convert a character from the current locale into an unicode code point */ i = mbtowc(&c, source, 4); if (i < 0) { c = replacementChar; mapped = false; i = 1; } source += i; if (c >= 128) { /* find AtariST character code for unicode code point c */ i = mapUnicodeToAtari[c & 511]; if (mapAtariToUnicode[i] == c) { c = i + 128; } else { c = replacementChar; mapped = false; } } *dest++ = c; } *dest = 0; return mapped; } #endif /** * Convert given host 'source' file name to 'destLen' sized 'dest', * in Atari encoding, if GEMDOS HD filename conversion is enabled. */ void Str_Filename_Atari2Host(const char *source, char *dest, int destLen, char replacementChar) { if (!ConfigureParams.HardDisk.bFilenameConversion) { Str_Copy(dest, source, destLen); return; } Str_AtariToHost(source, dest, destLen, replacementChar); } bool Str_AtariToHost(const char *source, char *dest, int destLen, char replacementChar) { #if defined(WIN32) || defined(USE_LOCALE_CHARSET) return Str_AtariToLocal(source, dest, destLen, replacementChar); #else return Str_AtariToUtf8(source, dest, destLen); #endif } bool Str_HostToAtari(const char *source, char *dest, char replacementChar) { #if defined(WIN32) || defined(USE_LOCALE_CHARSET) return Str_LocalToAtari(source, dest, replacementChar); #else return Str_Utf8ToAtari(source, dest, replacementChar); #endif } /* This table is needed to convert the UTF-8 representation of paths with * diacritical marks from the decomposed form (as returned by OSX) into the * precomposed form. Combining unicode characters are 0x0300..0x036F. * This table contains only those characters which are part of the AtariST * character set. */ static int mapDecomposedPrecomposed[] = { 'A', 0x0300, 0xC0, 'A', 0x0301, 0xC1, 'A', 0x0302, 0xC2, 'A', 0x0303, 0xC3, 'A', 0x0308, 0xC4, 'A', 0x030A, 0xC5, 'C', 0x0327, 0xC7, 'E', 0x0300, 0xC8, 'E', 0x0301, 0xC9, 'E', 0x0302, 0xCA, 'E', 0x0308, 0xCB, 'I', 0x0300, 0xCC, 'I', 0x0301, 0xCD, 'I', 0x0302, 0xCE, 'I', 0x0308, 0xCF, 'N', 0x0303, 0xD1, 'O', 0x0300, 0xD2, 'O', 0x0301, 0xD3, 'O', 0x0302, 0xD4, 'O', 0x0303, 0xD5, 'O', 0x0308, 0xD6, 'U', 0x0300, 0xD9, 'U', 0x0301, 0xDA, 'U', 0x0302, 0xDB, 'U', 0x0308, 0xDC, 'Y', 0x0301, 0xDD, 'a', 0x0300, 0xE0, 'a', 0x0301, 0xE1, 'a', 0x0302, 0xE2, 'a', 0x0303, 0xE3, 'a', 0x0308, 0xE4, 'a', 0x030A, 0xE5, 'c', 0x0327, 0xE7, 'e', 0x0300, 0xE8, 'e', 0x0301, 0xE9, 'e', 0x0302, 0xEA, 'e', 0x0308, 0xEB, 'i', 0x0300, 0xEC, 'i', 0x0301, 0xED, 'i', 0x0302, 0xEE, 'i', 0x0308, 0xEF, 'n', 0x0303, 0xF1, 'o', 0x0300, 0xF2, 'o', 0x0301, 0xF3, 'o', 0x0302, 0xF4, 'o', 0x0303, 0xF5, 'o', 0x0308, 0xF6, 'u', 0x0300, 0xF9, 'u', 0x0301, 0xFA, 'u', 0x0302, 0xFB, 'u', 0x0308, 0xFC, 'y', 0x0301, 0xFD, 'y', 0x0308, 0xFF, 0 }; /** * Convert decomposed unicode characters (sequence of a letter * and a combining character) in an UTF-8 encoded string into * the precomposed UTF-8 encoded form. Only characters which * exist in the AtariST character set are converted. * This is needed for OSX which returns filesystem paths in the * decomposed form (NFD). */ void Str_DecomposedToPrecomposedUtf8(const char *source, char *dest) { int c, c1, i; while (*source) { c = *source++ & 255; /* do we have a combining character behind the current character */ if ((source[0] & 0xFC) == 0xCC) /* 0x03XX is in UTF-8: 110011xx 10xxxxxx */ { c1 = ((source[0] & 31) << 6) | (source[1] & 63); for (i = 0; mapDecomposedPrecomposed[i]; i += 3) { if (mapDecomposedPrecomposed[i] == c && mapDecomposedPrecomposed[i + 1] == c1) { c = mapDecomposedPrecomposed[i + 2]; /* precomposed unicode code point */ *dest++ = 0xC0 | (c >> 6); /* UTF-8 first byte: 110xxxxx */ c = 0x80 + (c & 63); /* UTF-8 second byte: 10xxxxxx */ source += 2; break; } } } *dest++ = c; } *dest = 0; } /* ---------------------------------------------------------------------- */ /** * Print an Hex/Ascii dump of Len bytes located at *p * Each line consists of Width bytes, printed as an hexa value and as a char * (non printable chars are replaced by a '.') * The Suffix string is added at the beginning of each line. */ void Str_Dump_Hex_Ascii ( char *p , int Len , int Width , const char *Suffix , FILE *pFile ) { int nb; char buf_hex[ 200*3 ]; /* max for 200 bytes per line */ char buf_ascii[ 200 ]; char *p_h; char *p_a; unsigned char c; int offset; nb = 0; offset = 0; p_h = buf_hex; p_a = buf_ascii; while ( Len > 0 ) { c = *p++; sprintf ( p_h , "%2.2x " , c ); if ( ( c < 0x20 ) || ( c >= 0x7f ) ) c = '.'; sprintf ( p_a , "%c" , c ); p_h += 3; p_a += 1; Len--; nb++; if ( ( nb % Width == 0 ) || ( Len == 0 ) ) { fprintf ( pFile , "%s%6.6x: %-*s %-*s\n" , Suffix , offset , Width*3 , buf_hex , Width , buf_ascii ); offset = nb; p_h = buf_hex; p_a = buf_ascii; } } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/tos.c000066400000000000000000001731031504763705000223060ustar00rootroot00000000000000/* Hatari - tos.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Load TOS image file into ST memory, fix/setup for emulator. The Atari ST TOS needs to be patched to help with emulation. Eg, it references the MMU chip to set memory size. This is patched to the sizes we need without the complicated emulation of hardware which is not needed (as yet). We also patch DMA devices and Hard Drives. NOTE: TOS versions 1.06 and 1.62 were not designed for use on a real STfm. These were for the STe machine ONLY. They access the DMA/Microwire addresses on boot-up which (correctly) cause a bus-error on Hatari as they would in a real STfm. If a user tries to select any of these images we bring up an error. */ const char TOS_fileid[] = "Hatari tos.c"; #include "main.h" #include "configuration.h" #include "file.h" #include "gemdos.h" #include "hdc.h" #include "ide.h" #include "ioMem.h" #include "log.h" #include "m68000.h" #include "memorySnapShot.h" #include "nvram.h" #include "stMemory.h" #include "str.h" #include "symbols.h" #include "tos.h" #include "lilo.h" #include "vdi.h" #include "falcon/dsp.h" #include "clocks_timings.h" #include "video.h" #include "keymap.h" #include "faketosData.c" #define TEST_PRG_BASEPAGE 0x1000 #define TEST_PRG_START (TEST_PRG_BASEPAGE + 0x100) bool bIsEmuTOS; uint32_t EmuTosVersion; uint16_t TosVersion; /* eg. 0x0100, 0x0102 */ uint32_t TosAddress, TosSize; /* Address in ST memory and size of TOS image */ bool bTosImageLoaded = false; /* Successfully loaded a TOS image? */ bool bRamTosImage; /* true if we loaded a RAM TOS image */ bool bUseTos = true; /* false if we run in TOS-less test mode */ const char *psTestPrg; /* Points to the name of the PRG that should be used for testing */ unsigned int ConnectedDriveMask = 0x00; /* Bit mask of connected drives, eg 0x7 is A,B,C */ int nNumDrives = 2; /* Number of drives, default is 2 for A: and B: - Strictly, this is the highest mapped drive letter, in-between drives may not be allocated */ /* Possible TOS file extensions to scan for */ static const char * const pszTosNameExts[] = { ".img", ".rom", ".tos", NULL }; /* Flags that define if a TOS patch should be applied */ enum { TP_ALWAYS, /* Patch should always be applied */ TP_HDIMAGE_OFF, /* Apply patch only if HD emulation is off */ TP_ANTI_STE, /* Apply patch only if running on plain ST */ TP_ANTI_PMMU, /* Apply patch only if no PMMU is available */ TP_FIX_040, /* Apply patch only if CPU is 68040 */ TP_FIX_060, /* Apply patch only if CPU is 68060 */ TP_VDIRES, /* Apply patch only if VDI is used */ }; /* This structure is used for patching the TOS ROMs */ typedef struct { uint16_t Version; /* TOS version number */ int16_t Country; /* TOS country code: -1 if it does not matter, 0=US, 1=Germany, 2=France, etc. */ const char *pszName; /* Name of the patch */ int Flags; /* When should the patch be applied? (see enum above) */ uint32_t Address; /* Where the patch should be applied */ uint32_t OldData; /* Expected first 4 old bytes */ uint32_t Size; /* Length of the patch */ const void *pNewData; /* Pointer to the new bytes */ } TOS_PATCH; static const char pszDmaBoot[] = "boot from DMA bus"; static const char pszMouse[] = "big VDI resolutions mouse driver"; static const char pszRomCheck[] = "ROM checksum"; static const char pszNoSteHw[] = "disable STE hardware access"; static const char pszNoPmmu[] = "disable PMMU access"; static const char pszFix060[] = "replace code for 68060"; static const char pszFix040[] = "replace code for 68040"; static const char pszFalconExtraRAM[] = "enable extra TT RAM on Falcon"; static const char pszAtariLogo[] = "draw Atari Logo"; static const char pszSTbook[] = "disable MCU access on ST-Book"; static const char pszNoSparrowHw[] = "disable Sparrow hardware access"; //static uint8_t pRtsOpcode[] = { 0x4E, 0x75 }; /* 0x4E75 = RTS */ static const uint8_t pNopOpcodes[] = { 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71, 0x4E, 0x71 }; /* 0x4E71 = NOP */ static const uint8_t pMouseOpcode[] = { 0xD3, 0xC1 }; /* "ADDA.L D1,A1" (instead of "ADDA.W D1,A1") */ static const uint8_t pRomCheckOpcode206[] = { 0x60, 0x00, 0x00, 0x98 }; /* BRA $e00894 */ static const uint8_t pRomCheckOpcode207[] = { 0x60, 0x00, 0x00, 0x98 }; /* BRA $e00892 */ static const uint8_t pRomCheckOpcode306[] = { 0x60, 0x00, 0x00, 0xB0 }; /* BRA $e00886 */ static const uint8_t pRomCheckOpcode404[] = { 0x60, 0x00, 0x00, 0x94 }; /* BRA $e00746 */ static const uint8_t pBraOpcode[] = { 0x60 }; /* 0x60XX = BRA */ /* * Routine for drawing the Atari logo. * When this function is called, A0 contains a pointer to a 96x86x1 image. * We cannot use the vdi yet (the screen workstation has not yet been opened), * but we can take into account extended VDI modes. */ static const uint8_t pAtariLogo[] = { 0x3e, 0x3c, 0x00, 0x01, /* move.w #planes, d7; number will be patched below */ 0x2c, 0x3c, 0, 0, 1, 64, /* move.l #linewidth, d6; number will be patched below */ 0x22, 0x78, 0x04, 0x4e, /* movea.l (_v_bas_ad).w,a1 */ 0xd3, 0xc6, /* adda.l d6,a1 ; start drawing at 5th line */ 0xd3, 0xc6, /* adda.l d6,a1 */ 0xd3, 0xc6, /* adda.l d6,a1 */ 0xd3, 0xc6, /* adda.l d6,a1 */ 0xd3, 0xc6, /* adda.l d6,a1 */ 0x30, 0x3c, 0x00, 0x55, /* move.w #$0055,d0 ; 86 lines of data */ /* logocol1: */ 0x72, 0x05, /* moveq.l #5,d1 ; 6 words of data per line */ 0x24, 0x49, /* movea.l a1,a2 */ /* logocol2: */ 0x34, 0x18, /* move.w (a0)+,d2 */ 0x36, 0x07, /* move.w d7,d3 */ 0x53, 0x43, /* subq.w #1,d3 */ /* logocol3: */ 0x34, 0xc2, /* move.w d2,(a2)+ */ 0x51, 0xcb, 0xff, 0xfc, /* dbf d3,logocol3 */ 0x51, 0xc9, 0xff, 0xf2, /* dbf d1,logocol2 */ 0xd3, 0xc6, /* adda.l d6,a1 */ 0x51, 0xc8, 0xff, 0xe8, /* dbf d0,logocol1 */ 0x4e, 0x71, /* nops to end of original routine */ 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71 }; static const uint8_t p060movep1[] = { /* replace MOVEP */ 0x70, 0x0c, /* moveq #12,d0 */ 0x42, 0x30, 0x08, 0x00, /* loop: clr.b 0,(d0,a0) */ 0x55, 0x40, /* subq #2,d0 */ 0x4a, 0x40, /* tst.w d0 */ 0x66, 0xf6, /* bne.s loop */ }; static const uint8_t p060movep2[] = { /* replace MOVEP */ 0x41, 0xf8, 0xfa, 0x26, /* lea 0xfffffa26.w,a0 */ 0x20, 0xfc, 0x00, 0x00, 0x00, 0x88, /* move.l #$00000088,(a0)+ */ 0x20, 0xbc, 0x00, 0x01, 0x00, 0x05, /* move.l #$00010005,(a0) */ 0x4a, 0x38, 0x0a, 0x87 /* tst.b $a87.w */ }; static const uint8_t p060movep3_1[] = { /* replace MOVEP */ 0x4e, 0xb9, 0x00, 0xe7, 0xf0, 0x00, /* jsr $e7f000 */ 0x4e, 0x71 /* nop */ }; static const uint8_t p060movep3_2[] = { /* replace MOVEP $28(a2),d7 */ 0x00, 0x7c, 0x07, 0x00, /* ori #$700,sr */ 0x1e, 0x2a, 0x00, 0x28, /* move.b $28(a2),d7 */ 0xe1, 0x4f, /* lsl.w #8,d7 */ 0x1e, 0x2a, 0x00, 0x2a, /* move.b $2a(a2),d7 */ 0x48, 0x47, /* swap d7 */ 0x1e, 0x2a, 0x00, 0x2c, /* move.b $2c(a2),d7 */ 0xe1, 0x4f, /* lsl.w #8,d7 */ 0x1e, 0x2a, 0x00, 0x2e, /* move.b $2e(a2),d7 */ 0x4e, 0x75 /* rts */ }; static const uint8_t pFalconExtraRAM_1[] = { 0x4e, 0xb9, 0x00, 0xe7, 0xf1, 0x00 /* jsr $e7f100 */ }; static const uint8_t pFalconExtraRAM_2[] = { /* If we use fast RAM in Falcon mode, Hatari patched $05a4 already, so * we can use this value to check whether we have to do work here: */ 0x20, 0x38, 0x05, 0xa4, /* move.l $05a4.w,d0 */ 0x67, 0x58, /* beq.s extra_ram_end */ /* call maddalt() to declare the extra RAM */ 0x04, 0x80, 0x01, 0x00, 0x00, 0x00, /* subi.l #$1000000,d0 */ 0x2f, 0x00, /* move.l d0,-(sp) */ 0x2f, 0x3c, 0x01, 0x00, 0x00, 0x00, /* move.l #$1000000,-(sp) */ 0x3f, 0x3c, 0x00, 0x14, /* move.w #$14,-(sp) */ 0x4e, 0x41, /* trap #1 */ 0x4f, 0xef, 0x00, 0x0a, /* lea $a(sp),sp */ /* call Mxalloc() to get a buffer in ST RAM for the _FRB cookie: */ 0x42, 0x67, /* clr.w -(a7) */ 0x2f, 0x3c, 0x00, 0x01, 0x02, 0x00, /* move.l #65536+512,-(sp) */ 0x3f, 0x3c, 0x00, 0x44, /* move.w #$44,-(sp) */ 0x4e, 0x41, /* trap #1 ; Mxalloc */ 0x50, 0x8f, /* addq.l #8,sp */ 0x4a, 0x80, /* tst.l d0 */ 0x67, 0x2c, /* beq.s extra_ram_end */ /* Align the buffer to a 512 byte boundary: */ 0xd0, 0xbc, 0x00, 0x00, 0x01, 0xff, /* add.l #511,d0 */ 0xc0, 0xbc, 0xff, 0xff, 0xfe, 0x00, /* and.l #$fffffe00,d0 */ /* Get cookie jar pointer and search for the end. Since we run this * with TOS 4.x only, we can assume that the jar is available and * that there is at least one entry in the jar already */ 0x20, 0x78, 0x05, 0xa0, /* move.l $5a0.s,a0 */ /* jar_loop: */ 0x0c, 0x98, 0x5f, 0x46, 0x52, 0x42, /* cmp.l #'_FRB',(a0)+ */ 0x67, 0x14, /* beq.s extra_ram_end */ 0x58, 0x88, /* addaq.l #4,a0 */ 0x4a, 0x90, /* tst.l (a0) */ 0x66, 0xf2, /* bne.s cj_loop */ /* We reached the end of the jar, so install our _FRB cookie here: */ 0x20, 0xfc, 0x5f, 0x46, 0x52, 0x42, /* move.l #'_FRB',(a0)+ */ 0x21, 0x50, 0x00, 0x08, /* move.l (a0),8(a0) */ 0x20, 0xc0, /* move.l d0,(a0)+ */ 0x42, 0x98, /* clr. (a0)+ ; jar end */ /* extra_ram_end: ; The code we replaced at address $E0096E */ 0x70, 0x03, /* moveq #3,d0 */ 0x4e, 0xf9, 0x00, 0xe0, 0x0b, 0xd2 /* jmp $e00bd2 */ }; /* patch for cpu type detection */ static unsigned char const pCputype040[58] = { 0x20, 0xfc, 0x00, 0x00, 0x00, 0x28, /* move.l #40,(a0)+ */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71 }; static unsigned char const pCputype060[58] = { 0x20, 0xfc, 0x00, 0x00, 0x00, 0x3c, /* move.l #60,(a0)+ */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71 }; static unsigned char const pCacheflush[10] = { 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, /* nops */ 0xf4, 0xf8, /* cpusha bc */ 0x4e, 0x71 /* nop */ }; static unsigned char const pCacheflush2[14] = { 0x4e, 0x71, /* nop */ 0xf4, 0xf8, /* cpusha bc */ 0x4e, 0x71, 0x4e, 0x71, 0x4e, 0x71, /* nops */ 0x4e, 0x71, 0x4e, 0x71 /* nops */ }; static unsigned char const pCacheflush3[12] = { 0x4e, 0x71, /* nop */ 0xf4, 0xf8, /* cpusha bc */ 0x4e, 0x71, 0x4e, 0x71, /* nops */ 0x4e, 0x71, 0x4e, 0x71 /* nops */ }; static unsigned char const pCacheflush4[12] = { 0x4e, 0x71, /* nop */ 0xf4, 0x98, /* cinva ic */ 0x4e, 0x71, 0x4e, 0x71, /* nops */ 0x4e, 0x71, 0x4e, 0x71 /* nops */ }; static unsigned char const pColdboot1[10] = { 0x4d, 0xfa, 0x00, 0x08, /* lea.l $00E00064(pc),a6 */ 0x4e, 0xf9, 0x00, 0xe7, 0xb0, 0x00 /* jmp $00E7B000 */ }; static unsigned char const pColdboot2[6] = { 0x4e, 0xf9, 0x00, 0xe7, 0xb0, 0xf4 /* jmp $00E7B0F4 */ }; static unsigned char const pColdboot3[] = { 0x40, 0xc7, /* move.w sr,d7 */ 0x00, 0x7c, 0x07, 0x00, /* ori.w #$0700,sr */ 0x4e, 0x7a, 0x10, 0x06, /* movec dtt0,d1 */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x02, /* movec d0,cacr */ 0x4e, 0x71, /* nop */ 0xf4, 0xd8, /* cinva bc */ 0x4e, 0x71, /* nop */ 0x20, 0x3c, 0x00, 0xff, 0xe0, 0x40, /* move.l #$00FFE040,d0 */ 0x4e, 0x7b, 0x00, 0x06, /* movec d0,dtt0 */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ 0x7c, 0x00, /* moveq.l #0,d6 */ 0x4e, 0x7a, 0x08, 0x07, /* movec srp,d0 */ 0x4a, 0x80, /* tst.l d0 */ 0x67, 0x32, /* beq.s $00E7B062 */ 0xb0, 0xbc, 0x05, 0x00, 0x00, 0x00, /* cmp.l #$05000000,d0 */ 0x64, 0x2a, /* bcc.s $00E7B062 */ 0xb0, 0xbc, 0x00, 0xe0, 0x00, 0x00, /* cmp.l #$00E00000,d0 */ 0x65, 0x08, /* bcs.s $00E7B048 */ 0xb0, 0xbc, 0x01, 0x00, 0x00, 0x00, /* cmp.l #$01000000,d0 */ 0x65, 0x1a, /* bcs.s $00E7B062 */ /* e7b048 : */ 0x20, 0x40, /* movea.l d0,a0 */ 0x0c, 0xa8, 0x54, 0x52, 0x45, 0x45, 0xff, 0xe8, /* cmpi.l #$54524545,-24(a0) */ 0x66, 0x00, 0x00, 0x0e, /* bne.w $00E7B062 */ 0x0c, 0xa8, 0x4b, 0x45, 0x45, 0x50, 0xff, 0xec, /* cmpi.l #$4B454550,-20(a0) */ 0x66, 0x02, /* bne.s $00E7B062 */ 0x7c, 0x01, /* moveq.l #1,d6 */ /* e7b062 : */ 0x4e, 0x71, /* nop */ 0x4e, 0x7b, 0x10, 0x06, /* movec d1,dtt0 */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ 0x4a, 0x86, /* tst.l d6 */ 0x67, 0x2e, /* beq.s $00E7B0A0 */ 0x20, 0x08, /* move.l a0,d0 */ 0x4e, 0x7b, 0x08, 0x07, /* movec d0,srp */ 0x4e, 0x7b, 0x08, 0x06, /* movec d0,urp */ 0x20, 0x3c, 0x00, 0x00, 0xc0, 0x00, /* move.l #$0000C000,d0 */ 0x4e, 0x7b, 0x00, 0x03, /* movec d0,tc */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x04, /* movec d0,itt0 */ 0x4e, 0x7b, 0x00, 0x05, /* movec d0,itt1 */ 0x4e, 0x7b, 0x00, 0x06, /* movec d0,dtt0 */ 0x4e, 0x7b, 0x00, 0x07, /* movec d0,dtt1 */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ 0x60, 0x32, /* bra.s $00E7B0D2 */ /* e7b0a0 : */ 0x20, 0x3c, 0x00, 0xff, 0xe0, 0x00, /* move.l #$00FFE000,d0 */ 0x4e, 0x7b, 0x00, 0x04, /* movec d0,itt0 */ 0x20, 0x3c, 0x00, 0xff, 0xe0, 0x40, /* move.l #$00FFE040,d0 */ 0x4e, 0x7b, 0x00, 0x06, /* movec d0,dtt0 */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x05, /* movec d0,itt1 */ 0x4e, 0x7b, 0x00, 0x07, /* movec d0,dtt1 */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x03, /* movec d0,tc */ 0x4e, 0x7b, 0x08, 0x07, /* movec d0,srp */ 0x4e, 0x7b, 0x08, 0x06, /* movec d0,urp */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ /* e7b0d2 : */ 0x20, 0x3c, 0x00, 0x00, 0x80, 0x00, /* move.l #$00008000,d0 */ 0x4e, 0x7b, 0x00, 0x02, /* movec d0,cacr */ 0x46, 0xc7, /* move.w d7,sr */ 0x4e, 0xd6, /* jmp (a6) */ /* e7b0e0 : */ 0x08, 0xb8, 0x00, 0x05, 0x82, 0x66, /* bclr #5,($FFFF8266).w */ 0x08, 0xb8, 0x00, 0x06, 0x82, 0x66, /* bclr #6,($FFFF8266).w */ 0x08, 0xb8, 0x00, 0x00, 0x82, 0x0a, /* bclr #0,($FFFF820A).w */ 0x4e, 0xd0, /* jmp (a0) */ /* e7b0f4 : */ 0x00, 0x7c, 0x07, 0x00, /* ori.w #$0700,sr */ 0x72, 0x00, /* moveq.l #0,d1 */ 0x41, 0xf8, 0x98, 0x00, /* lea.l ($FFFF9800).w,a0 */ 0x30, 0x3c, 0x00, 0xff, /* move.w #$00FF,d0 */ 0x20, 0xc1, /* move.l d1,(a0)+ */ 0x51, 0xc8, 0xff, 0xfc, /* dbf d0,$00E7B102 */ 0x41, 0xf8, 0x82, 0x40, /* lea.l ($FFFF8240).w,a0 */ 0x70, 0x07, /* moveq.l #7,d0 */ 0x20, 0xc1, /* move.l d1,(a0)+ */ 0x51, 0xc8, 0xff, 0xfc, /* dbf d0,$00E7B10E */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x02, /* movec d0,cacr */ 0x4e, 0x71, /* nop */ 0x4e, 0x71, /* nop */ 0xf4, 0xd8, /* cinva bc */ 0x4e, 0x71, /* nop */ 0x41, 0xf8, 0x00, 0x08, /* lea.l ($00000008).w,a0 */ 0x20, 0x3c, 0x00, 0x00, 0x06, 0x00, /* move.l #$00000600,d0 */ 0x90, 0x88, /* sub.l a0,d0 */ 0xe4, 0x88, /* lsr.l #2,d0 */ 0x42, 0x81, /* clr.l d1 */ /* e7b132 : */ 0x20, 0xc1, /* move.l d1,(a0)+ */ 0x53, 0x80, /* subq.l #1,d0 */ 0x66, 0xfa, /* bne.s $00E7B132 */ 0x4e, 0x71, /* nop */ 0xf5, 0x18, /* pflusha */ 0x4e, 0x71, /* nop */ 0x20, 0x3c, 0x00, 0xff, 0xe0, 0x00, /* move.l #$00FFE000,d0 */ 0x4e, 0x7b, 0x00, 0x04, /* movec d0,itt0 */ 0x20, 0x3c, 0x00, 0xff, 0xe0, 0x40, /* move.l #$00FFE040,d0 */ 0x4e, 0x7b, 0x00, 0x06, /* movec d0,dtt0 */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x05, /* movec d0,itt1 */ 0x4e, 0x7b, 0x00, 0x07, /* movec d0,dtt1 */ 0x4e, 0x71, /* nop */ 0x70, 0x00, /* moveq.l #0,d0 */ 0x4e, 0x7b, 0x00, 0x03, /* movec d0,tc */ 0x4e, 0x71, /* nop */ 0x4e, 0x7b, 0x08, 0x07, /* movec d0,srp */ 0x4e, 0x7b, 0x08, 0x06, /* movec d0,urp */ 0x4e, 0x71, /* nop */ 0x4e, 0xf9, 0x00, 0xe0, 0x00, 0x30, /* jmp $00E00030 */ /* e7b176 : */ 0x4e, 0x7a, 0x00, 0x02, /* movec cacr,d0 */ 0x08, 0x80, 0x00, 0x0f, /* bclr #15,d0 */ 0x67, 0x0a, /* beq.s $00E7B18A */ 0x4e, 0x7b, 0x00, 0x02, /* movec d0,cacr */ 0x4e, 0x71, /* nop */ 0xf4, 0x98, /* cinva ic */ 0x4e, 0x71, /* nop */ 0x4e, 0xf9, 0xde, 0xad, 0xfa, 0xce, /* jmp $DEADFACE */ /* e7b190 : */ 0x20, 0x02, 0x10, 0x21, /* dc.l 0x20021021 */ 0x00, 0xe7, 0xb1, 0x9c, /* dc.l 0x00e7b19c */ 0x00, 0xe7, 0xb1, 0xa0, /* dc.l 0x00e7b1a0 */ /* e7b19c : */ 0x73, 0x00, /* dc.w 0x7300 (nf_id) */ 0x4e, 0x75, /* rts */ /* e7b1a0 : */ 0x73, 0x01, /* dc.w 0x7301 (nf_call) */ 0x4e, 0x75 /* rts */ }; /* The patches for the TOS: */ static const TOS_PATCH TosPatches[] = { { 0x100, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xFC03D6, 0x610000D0, 4, pNopOpcodes }, /* BSR $FC04A8 */ { 0x102, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xFC0472, 0x610000E4, 4, pNopOpcodes }, /* BSR.W $FC0558 */ { 0x102, 0, pszMouse, TP_ALWAYS, 0xFD0030, 0xD2C147F9, 2, pMouseOpcode }, { 0x102, 1, pszMouse, TP_ALWAYS, 0xFD008A, 0xD2C147F9, 2, pMouseOpcode }, { 0x102, 2, pszMouse, TP_ALWAYS, 0xFD00A8, 0xD2C147F9, 2, pMouseOpcode }, { 0x102, 3, pszMouse, TP_ALWAYS, 0xFD0030, 0xD2C147F9, 2, pMouseOpcode }, { 0x102, 6, pszMouse, TP_ALWAYS, 0xFCFEF0, 0xD2C147F9, 2, pMouseOpcode }, { 0x102, 8, pszMouse, TP_ALWAYS, 0xFCFEFE, 0xD2C147F9, 2, pMouseOpcode }, { 0x104, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xFC0466, 0x610000E4, 4, pNopOpcodes }, /* BSR.W $FC054C */ { 0x106, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xE00576, 0x610000E4, 4, pNopOpcodes }, /* BSR.W $E0065C */ { 0x162, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xE00576, 0x610000E4, 4, pNopOpcodes }, /* BSR.W $E0065C */ { 0x205, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xE006AE, 0x610000E4, 4, pNopOpcodes }, /* BSR.W $E00794 */ /* An unpatched TOS 2.05 only works on STEs, so apply some anti-STE patches... */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00096, 0x42788900, 4, pNopOpcodes }, /* CLR.W $FFFF8900 */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE0009E, 0x31D88924, 4, pNopOpcodes }, /* MOVE.W (A0)+,$FFFF8924 */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE000A6, 0x09D10AA9, 28, pNopOpcodes }, { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE003A0, 0x30389200, 4, pNopOpcodes }, /* MOVE.W $ffff9200,D0 */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE004EA, 0x61000CBC, 4, pNopOpcodes }, { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00508, 0x61000C9E, 4, pNopOpcodes }, { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE007A0, 0x631E2F3C, 1, pBraOpcode }, { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00928, 0x10388901, 4, pNopOpcodes }, /* MOVE.B $FFFF8901,D0 */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00944, 0xB0388901, 4, pNopOpcodes }, /* CMP.B $FFFF8901,D0 */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00950, 0x67024601, 1, pBraOpcode }, { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00968, 0x61000722, 4, pNopOpcodes }, { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00CF2, 0x1038820D, 4, pNopOpcodes }, /* MOVE.B $FFFF820D,D0 */ { 0x205, -1, pszNoSteHw, TP_ANTI_STE, 0xE00E00, 0x1038820D, 4, pNopOpcodes }, /* MOVE.B $FFFF820D,D0 */ { 0x205, 0, pszNoSteHw, TP_ANTI_STE, 0xE03038, 0x31C0860E, 4, pNopOpcodes }, { 0x205, 0, pszNoSteHw, TP_ANTI_STE, 0xE034A8, 0x31C0860E, 4, pNopOpcodes }, { 0x205, 0, pszNoSteHw, TP_ANTI_STE, 0xE034F6, 0x31E90002, 6, pNopOpcodes }, /* E007FA MOVE.L #$1FFFE,D7 Run checksums on 2xROMs (skip) */ /* Checksum is total of TOS ROM image, but get incorrect results */ /* as we've changed bytes in the ROM! So, just skip anyway! */ { 0x206, -1, pszRomCheck, TP_ALWAYS, 0xE007FA, 0x2E3C0001, 4, pRomCheckOpcode206 }, { 0x206, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xE00898, 0x610000E0, 4, pNopOpcodes }, /* BSR.W $E0097A */ { 0x206, -1, pszAtariLogo, TP_VDIRES, 0xE0076C, 0x1038044c, sizeof( pAtariLogo ), pAtariLogo }, { 0x207, -1, pszNoSparrowHw, TP_ALWAYS, 0xE02D90, 0x08F80005, 6, pNopOpcodes }, /* BSET #5,$ffff8e0d.w */ { 0x207, -1, pszRomCheck, TP_ALWAYS, 0xE007F8, 0x2E3C0001, 4, pRomCheckOpcode207 }, { 0x207, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xE008DC, 0x610000E0, 4, pNopOpcodes }, /* BSR.W $E009BE */ { 0x207, -1, pszAtariLogo, TP_VDIRES, 0xE0076A, 0x1038044c, sizeof(pAtariLogo), pAtariLogo }, { 0x208, -1, pszDmaBoot, TP_HDIMAGE_OFF, 0xE00806, 0x610000E8, 4, pNopOpcodes }, /* BSR.W $E008F0 */ { 0x208, -1, pszAtariLogo, TP_VDIRES, 0xE006B4, 0x1038044c, sizeof( pAtariLogo ), pAtariLogo }, { 0x208, -1, pszSTbook, TP_ALWAYS, 0xE00066, 0x303900d0, 18, pNopOpcodes }, { 0x208, -1, pszSTbook, TP_ALWAYS, 0xE000D6, 0x4a7900d0, 6, pNopOpcodes }, { 0x208, -1, pszSTbook, TP_ALWAYS, 0xE009FE, 0x303900d0, 14, pNopOpcodes }, { 0x306, -1, pszRomCheck, TP_ALWAYS, 0xE007D4, 0x2E3C0001, 4, pRomCheckOpcode306 }, { 0x306, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE00068, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x306, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE01702, 0xF0394C00, 32, pNopOpcodes }, /* pmove : CRP=80000002 00000700 TC=80f04445 TT0=017e8107 TT1=807e8507 -> */ { 0x306, -1, pszFix060, TP_FIX_060, 0xe024dc, 0x01C80000, 12, p060movep1 }, { 0x306, -1, pszFix060, TP_FIX_060, 0xe024fa, 0x01C80000, 12, p060movep1 }, { 0x306, -1, pszAtariLogo, TP_VDIRES, 0xE00754, 0x1038044c, sizeof( pAtariLogo ), pAtariLogo }, { 0x400, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE00064, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x400, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE0148A, 0xF0394C00, 32, pNopOpcodes }, { 0x400, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE03948, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x400, -1, pszRomCheck, TP_ALWAYS, 0xE00686, 0x2E3C0007, 4, pRomCheckOpcode404 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE003BE, 0x7200347C, sizeof(pCputype040), pCputype040 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE00614, 0x4E7B0002, 4, pNopOpcodes }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE0072C, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE00836, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE01894, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE01916, 0x4E7B0002, 4, pNopOpcodes }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE00054, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE03934, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE098AC, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE23636, 0x4E7B7002, 4, pNopOpcodes }, { 0x400, -1, pszFix040, TP_FIX_040, 0xE41634, 0x4E7B7002, 4, pNopOpcodes }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE003BE, 0x7200347C, sizeof(pCputype060), pCputype060 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE00614, 0x4E7B0002, 4, pNopOpcodes }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE0072C, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE00836, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE01894, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE01916, 0x4E7B0002, 4, pNopOpcodes }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE00054, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE03934, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE098AC, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE23636, 0x4E7B7002, 4, pNopOpcodes }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE41634, 0x4E7B7002, 4, pNopOpcodes }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE0258A, 0x01C80000, 12, p060movep1 }, { 0x400, -1, pszFix060, TP_FIX_060, 0xE025DA, 0x41F8FA01, 20, p060movep2 }, { 0x401, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE0006A, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x401, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE014A8, 0xF0394C00, 32, pNopOpcodes }, { 0x401, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE03946, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x401, -1, pszRomCheck, TP_ALWAYS, 0xE006A6, 0x2E3C0007, 4, pRomCheckOpcode404 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE003C4, 0x7200347C, sizeof(pCputype040), pCputype040 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE00634, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE0074C, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE00856, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE01892, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE01914, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE0005A, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE03932, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE098A2, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE11B28, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE11BB0, 0x4E7B7002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE11CAC, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12512, 0x4E7B5002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12888, 0x4E7B6002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE128D4, 0x4E7B6002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12938, 0x4E7B6002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12B50, 0x4E7B5002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12BD0, 0x4E7B5002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12C48, 0x4E7B2002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12CC6, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12D2E, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE12DF0, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE17AA6, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE17B3E, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE23840, 0x4E7B7002, 4, pNopOpcodes }, { 0x401, -1, pszFix040, TP_FIX_040, 0xE42670, 0x4E7B7002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE003C4, 0x7200347C, sizeof(pCputype060), pCputype060 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE00634, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE0074C, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE00856, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE01892, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE01914, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE0005A, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE03932, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE098A2, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE11B28, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE11BB0, 0x4E7B7002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE11CAC, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12512, 0x4E7B5002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12888, 0x4E7B6002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE128D4, 0x4E7B6002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12938, 0x4E7B6002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12B50, 0x4E7B5002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12BD0, 0x4E7B5002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12C48, 0x4E7B2002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12CC6, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12D2E, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE12DF0, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE17AA6, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE17B3E, 0x4E7B0002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE23840, 0x4E7B7002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE42670, 0x4E7B7002, 4, pNopOpcodes }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE02588, 0x01C80000, 12, p060movep1 }, { 0x401, -1, pszFix060, TP_FIX_060, 0xE025D8, 0x41F8FA01, 20, p060movep2 }, { 0x402, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE0006A, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x402, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE014A8, 0xF0394C00, 32, pNopOpcodes }, { 0x402, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE03946, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x402, -1, pszRomCheck, TP_ALWAYS, 0xE006A6, 0x2E3C0007, 4, pRomCheckOpcode404 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE003C4, 0x7200347C, sizeof(pCputype040), pCputype040 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE00634, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE0074C, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE00856, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE01892, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE01914, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE0005A, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE03932, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE098AC, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE11B76, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE11BFE, 0x4E7B7002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE11CFA, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12560, 0x4E7B5002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE128D6, 0x4E7B6002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12922, 0x4E7B6002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12986, 0x4E7B6002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12B9E, 0x4E7B5002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12C1E, 0x4E7B5002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12C96, 0x4E7B2002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12D14, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12D7C, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE12E3E, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE17AF4, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE17B8C, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE25078, 0x4E7B7002, 4, pNopOpcodes }, { 0x402, -1, pszFix040, TP_FIX_040, 0xE444B0, 0x4E7B7002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE003C4, 0x7200347C, sizeof(pCputype060), pCputype060 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE00634, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE0074C, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE00856, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE01892, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE01914, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE0005A, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE03932, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE098AC, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE11B76, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE11BFE, 0x4E7B7002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE11CFA, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12560, 0x4E7B5002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE128D6, 0x4E7B6002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12986, 0x4E7B6002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12922, 0x4E7B6002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12B9E, 0x4E7B5002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12C1E, 0x4E7B5002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12C96, 0x4E7B2002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12D14, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12D7C, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE12E3E, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE17AF4, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE17B8C, 0x4E7B0002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE25078, 0x4E7B7002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE444B0, 0x4E7B7002, 4, pNopOpcodes }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE02588, 0x01C80000, 12, p060movep1 }, { 0x402, -1, pszFix060, TP_FIX_060, 0xE025D8, 0x41F8FA01, 20, p060movep2 }, { 0x404, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE0006A, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x404, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE014E6, 0xF0394C00, 32, pNopOpcodes }, /* pmove : CRP=80000002 00000700 TC=80f04445 TT0=017e8107 TT1=807e8507 -> */ { 0x404, -1, pszNoPmmu, TP_ANTI_PMMU, 0xE039A0, 0xF0394000, 24, pNopOpcodes }, /* pmove : TC=0 TT0=0 TT1=0 -> disable MMU */ { 0x404, -1, pszRomCheck, TP_ALWAYS, 0xE006B0, 0x2E3C0007, 4, pRomCheckOpcode404 }, { 0x404, -1, pszDmaBoot, TP_ALWAYS, 0xE01C9E, 0x62FC31FC, 2, pNopOpcodes }, /* Just a delay */ { 0x404, -1, pszDmaBoot, TP_ALWAYS, 0xE01CB2, 0x62FC31FC, 2, pNopOpcodes }, /* Just a delay */ { 0x404, -1, pszFix040, TP_FIX_040, 0xE003C4, 0x7200347C, sizeof(pCputype040), pCputype040 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE0063E, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE00756, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE00860, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE018D0, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE01952, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE0005A, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE0398C, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE0990C, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE11BD6, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE11C5E, 0x4E7B7002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE11D5A, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE125C0, 0x4E7B5002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12936, 0x4E7B6002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12982, 0x4E7B6002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE129E6, 0x4E7B6002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12BFE, 0x4E7B5002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12C7E, 0x4E7B5002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12CF6, 0x4E7B2002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12D74, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12DDC, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE12E9E, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE17B54, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE17BEC, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE250D8, 0x4E7B7002, 4, pNopOpcodes }, { 0x404, -1, pszFix040, TP_FIX_040, 0xE44510, 0x4E7B7002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE003C4, 0x7200347C, sizeof(pCputype060), pCputype060 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE0063E, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE00756, 0x203C0000, sizeof(pCacheflush), pCacheflush }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE00860, 0x4E7A0002, sizeof(pCacheflush2), pCacheflush2 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE018D0, 0x4E7A1002, sizeof(pCacheflush3), pCacheflush3 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE01952, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE0005A, 0x203c0000, sizeof(pColdboot1), pColdboot1 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE0398C, 0x46FC2700, sizeof(pColdboot2), pColdboot2 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE7B000, 0xFFFFFFFF, sizeof(pColdboot3), pColdboot3 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE0990C, 0x4E7A2002, sizeof(pCacheflush4), pCacheflush4 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE11BD6, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE11C5E, 0x4E7B7002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE11D5A, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE125C0, 0x4E7B5002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12936, 0x4E7B6002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12982, 0x4E7B6002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE129E6, 0x4E7B6002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12BFE, 0x4E7B5002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12C7E, 0x4E7B5002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12CF6, 0x4E7B2002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12D74, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12DDC, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE12E9E, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE17B54, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE17BEC, 0x4E7B0002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE250D8, 0x4E7B7002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE44510, 0x4E7B7002, 4, pNopOpcodes }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE025E2, 0x01C80000, 12, p060movep1 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE02632, 0x41F8FA01, 20, p060movep2 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE02B1E, 0x007c0700, 8, p060movep3_1 }, { 0x404, -1, pszFix060, TP_FIX_060, 0xE7F000, 0xFFFFFFFF, sizeof( p060movep3_2 ), p060movep3_2 }, { 0x404, -1, pszFalconExtraRAM, TP_ALWAYS, 0xE0096E, 0x70036100, 6, pFalconExtraRAM_1 }, { 0x404, -1, pszFalconExtraRAM, TP_ALWAYS, 0xE7F100, 0xFFFFFFFF, sizeof( pFalconExtraRAM_2 ), pFalconExtraRAM_2 }, { 0x492, -1, pszNoPmmu, TP_ANTI_PMMU, 0x00F946, 0xF0394000, 24, pNopOpcodes }, { 0x492, -1, pszNoPmmu, TP_ANTI_PMMU, 0x01097A, 0xF0394C00, 32, pNopOpcodes }, { 0x492, -1, pszNoPmmu, TP_ANTI_PMMU, 0x012E04, 0xF0394000, 24, pNopOpcodes }, { 0, 0, NULL, 0, 0, 0, 0, NULL } }; /*-----------------------------------------------------------------------*/ /** * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type) */ void TOS_MemorySnapShot_Capture(bool bSave) { /* Save/Restore details */ MemorySnapShot_Store(&TosVersion, sizeof(TosVersion)); MemorySnapShot_Store(&TosAddress, sizeof(TosAddress)); MemorySnapShot_Store(&TosSize, sizeof(TosSize)); MemorySnapShot_Store(&ConnectedDriveMask, sizeof(ConnectedDriveMask)); MemorySnapShot_Store(&nNumDrives, sizeof(nNumDrives)); } /*-----------------------------------------------------------------------*/ /** * Patch TOS to skip some TOS setup code which we don't support/need. * * So, how do we find these addresses when we have no commented source code? * - For the "Boot from DMA bus" patch: * Scan at start of rom for tst.w $482, boot call will be just above it. * * Set logpatch_addr if patch for that is needed. */ static void TOS_FixRom(uint32_t *logopatch_addr) { int nGoodPatches, nBadPatches; short TosCountry; const TOS_PATCH *pPatch; /* We can't patch RAM TOS images (yet) */ if (bRamTosImage && TosVersion != 0x0492) { Log_Printf(LOG_DEBUG, "Detected RAM TOS image, skipping TOS patches.\n"); return; } nGoodPatches = nBadPatches = 0; TosCountry = STMemory_ReadWord(TosAddress+28)>>1; /* TOS country code */ pPatch = TosPatches; /* Apply TOS patches: */ while (pPatch->Version) { /* Only apply patches that suit to the actual TOS version: */ if (pPatch->Version == TosVersion && (pPatch->Country == TosCountry || pPatch->Country == -1)) { bool use_mmu = ConfigureParams.System.bMMU && ConfigureParams.System.nCpuLevel == 3; /* Only apply the patch if it is really needed: */ if (pPatch->Flags == TP_ALWAYS || (pPatch->Flags == TP_HDIMAGE_OFF && !bAcsiEmuOn && !ConfigureParams.Ide[0].bUseDevice && ConfigureParams.System.bFastBoot) || (pPatch->Flags == TP_ANTI_STE && Config_IsMachineST()) || (pPatch->Flags == TP_ANTI_PMMU && !use_mmu) || (pPatch->Flags == TP_VDIRES && bUseVDIRes) || (pPatch->Flags == TP_FIX_060 && ConfigureParams.System.nCpuLevel > 4) || (pPatch->Flags == TP_FIX_040 && ConfigureParams.System.nCpuLevel == 4) ) { /* Make sure that we really patch the right place by comparing data: */ if(STMemory_ReadLong(pPatch->Address) == pPatch->OldData) { /* Now we can really apply the patch! */ Log_Printf(LOG_DEBUG, "Applying TOS patch '%s'.\n", pPatch->pszName); memcpy(&RomMem[pPatch->Address], pPatch->pNewData, pPatch->Size); nGoodPatches += 1; if (strcmp(pPatch->pszName, pszAtariLogo) == 0) *logopatch_addr = pPatch->Address; } else { Log_Printf(LOG_DEBUG, "Failed to apply TOS patch '%s' at %x (expected %x, found %x).\n", pPatch->pszName, pPatch->Address, pPatch->OldData, STMemory_ReadLong(pPatch->Address)); nBadPatches += 1; } } else { Log_Printf(LOG_DEBUG, "Skipped patch '%s'.\n", pPatch->pszName); } } pPatch += 1; } Log_Printf(LOG_DEBUG, "Applied %i TOS patches, %i patches failed.\n", nGoodPatches, nBadPatches); } /*-----------------------------------------------------------------------*/ /** * Assert that TOS version matches the machine type and change the system * configuration if necessary. * For example TOSes 1.06 and 1.62 are for the STE ONLY and so don't run * on a real ST, TOS 3.0x is TT only and TOS 4.x is Falcon only. * These TOS version access illegal memory addresses on machine they were * not designed for and so cause the OS to lock up. So, if user selects one * of these, switch to the appropriate machine type. */ static void TOS_CheckSysConfig(void) { int oldCpuLevel = ConfigureParams.System.nCpuLevel; MACHINETYPE oldMachineType = ConfigureParams.System.nMachineType; FPUTYPE oldFpuType = ConfigureParams.System.n_FPUType; if (((TosVersion == 0x0106 || TosVersion == 0x0162) && ConfigureParams.System.nMachineType != MACHINE_STE) || (TosVersion == 0x0162 && ConfigureParams.System.nCpuLevel != 0)) { Log_AlertDlg(LOG_ERROR, "TOS versions 1.06 and 1.62 are for Atari STE only.\n" " ==> Switching to STE mode now.\n"); IoMem_UnInit(ConfigureParams.System.nMachineType); ConfigureParams.System.nMachineType = MACHINE_STE; ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); IoMem_Init(); Configuration_ChangeCpuFreq ( 8 ); ConfigureParams.System.nCpuLevel = 0; } else if ((TosVersion & 0x0f00) == 0x0300 && !Config_IsMachineTT()) { Log_AlertDlg(LOG_ERROR, "TOS versions 3.0x are for Atari TT only.\n" " ==> Switching to TT mode now.\n"); IoMem_UnInit(ConfigureParams.System.nMachineType); ConfigureParams.System.nMachineType = MACHINE_TT; ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); IoMem_Init(); Configuration_ChangeCpuFreq ( 32 ); ConfigureParams.System.nCpuLevel = 3; } else if (((TosVersion & 0x0f00) == 0x0400 || TosVersion == 0x0207) && !Config_IsMachineFalcon()) { Log_AlertDlg(LOG_ERROR, "TOS version %x.%02x is for Atari Falcon only.\n" " ==> Switching to Falcon mode now.\n", TosVersion >> 8, TosVersion & 0xff); Ide_UnInit(); IoMem_UnInit(ConfigureParams.System.nMachineType); ConfigureParams.System.nMachineType = MACHINE_FALCON; ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); #if ENABLE_DSP_EMU ConfigureParams.System.nDSPType = DSP_TYPE_EMU; DSP_Enable(); #endif IoMem_Init(); Ide_Init(); Configuration_ChangeCpuFreq ( 16 ); ConfigureParams.System.nCpuLevel = 3; } else if (TosVersion <= 0x0104 && (ConfigureParams.System.nCpuLevel > 0 || !Config_IsMachineST())) { Log_AlertDlg(LOG_ERROR, "TOS versions <= 1.4 work only in\n" "ST mode and with a 68000 CPU.\n" " ==> Switching to ST mode with 68000 now.\n"); IoMem_UnInit(ConfigureParams.System.nMachineType); ConfigureParams.System.nMachineType = MACHINE_ST; ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); IoMem_Init(); Configuration_ChangeCpuFreq ( 8 ); ConfigureParams.System.nCpuLevel = 0; } else if (TosVersion < 0x0300 && (Config_IsMachineTT() || (Config_IsMachineFalcon() && TosVersion != 0x0207))) { Log_AlertDlg(LOG_ERROR, "This TOS version does not work in TT/Falcon mode.\n" " ==> Switching to STE mode now.\n"); IoMem_UnInit(ConfigureParams.System.nMachineType); ConfigureParams.System.nMachineType = MACHINE_STE; ClocksTimings_InitMachine ( ConfigureParams.System.nMachineType ); Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); IoMem_Init(); Configuration_ChangeCpuFreq ( 8 ); ConfigureParams.System.nCpuLevel = 0; } else if ((TosVersion & 0x0f00) == 0x0400 && ConfigureParams.System.nCpuLevel < 2) { Log_AlertDlg(LOG_ERROR, "TOS versions 4.x require a CPU >= 68020.\n" " ==> Switching to 68020 mode now.\n"); ConfigureParams.System.nCpuLevel = 2; } else if ((TosVersion & 0x0f00) == 0x0300 && (ConfigureParams.System.nCpuLevel < 2 || ConfigureParams.System.n_FPUType == FPU_NONE)) { Log_AlertDlg(LOG_ERROR, "TOS versions 3.0x require a CPU >= 68020 with FPU.\n" " ==> Switching to 68030 mode with FPU now.\n"); ConfigureParams.System.nCpuLevel = 3; ConfigureParams.System.n_FPUType = FPU_68882; } /* TOS version triggered changes? */ if (ConfigureParams.System.nMachineType != oldMachineType) { if (Config_IsMachineTT()) { ConfigureParams.System.bCompatibleFPU = true; ConfigureParams.System.n_FPUType = FPU_68882; } else { ConfigureParams.System.n_FPUType = FPU_NONE; /* TODO: or leave it as-is? */ } if (TosVersion < 0x200) { ConfigureParams.System.bAddressSpace24 = true; ConfigureParams.System.bMMU = false; } M68000_CheckCpuSettings(); /* Ensure MMU has default values when changing machine's type before calling memory_init() later */ STMemory_Reset ( true ); } else if (ConfigureParams.System.nCpuLevel != oldCpuLevel || ConfigureParams.System.n_FPUType != oldFpuType) { M68000_CheckCpuSettings(); } if (TosVersion < 0x0104 && ConfigureParams.HardDisk.bUseHardDiskDirectories) { Log_AlertDlg(LOG_ERROR, "Please use at least TOS v1.04 for the HD directory emulation " "(all required GEMDOS functionality isn't completely emulated for this TOS version)."); } if (Config_IsMachineFalcon() && bUseVDIRes && !bIsEmuTOS) { Log_AlertDlg(LOG_ERROR, "Please use 512k EmuTOS instead of TOS v4 " "for proper extended VDI resolutions support on Falcon."); } } /** * Load TOS Rom image file and do some basic sanity checks. * Returns pointer to allocated memory with TOS data, or NULL for error. */ static uint8_t *TOS_LoadImage(void) { uint8_t *pTosFile = NULL; long nFileSize; /* Load TOS image into memory so that we can check its version */ TosVersion = 0; pTosFile = File_Read(ConfigureParams.Rom.szTosImageFileName, &nFileSize, pszTosNameExts); if (!pTosFile || nFileSize < 0x40) { Log_AlertDlg(LOG_FATAL, "Can not load TOS file:\n'%s'", ConfigureParams.Rom.szTosImageFileName); free(pTosFile); return NULL; } TosSize = nFileSize; /* Check for RAM TOS images first: */ if (be_swap32(*(uint32_t *)pTosFile) == 0x46FC2700) { int nRamTosLoaderSize; Log_Printf(LOG_WARN, "Detected a RAM TOS - this will probably not work very well!\n"); /* RAM TOS images have a 256 bytes loader function before the real image * starts (34 bytes for TOS 4.92). Since we directly copy the image to the right * location later, we simply skip this additional header here: */ if (be_swap32(*(uint32_t *)(pTosFile+34)) == 0x602E0492) nRamTosLoaderSize = 0x22; else nRamTosLoaderSize = 0x100; TosSize -= nRamTosLoaderSize; memmove(pTosFile, pTosFile + nRamTosLoaderSize, TosSize); bRamTosImage = true; } else { bRamTosImage = false; } /* Check for EmuTOS ... (0x45544F53 = 'ETOS') */ bIsEmuTOS = (be_swap32(*(uint32_t *)&pTosFile[0x2c]) == 0x45544F53); if (bIsEmuTOS) { /* The magic value 'OSXH' indicates an extended header */ if (be_swap32(*(uint32_t *)&pTosFile[0x34]) == 0x4F535848) EmuTosVersion = be_swap32(*(uint32_t *)&pTosFile[0x3c]); else EmuTosVersion = 0; /* Older than 1.0 */ } /* Now, look at start of image to find Version number and address */ TosVersion = be_swap16(*(uint16_t *)&pTosFile[2]); TosAddress = be_swap32(*(uint32_t *)&pTosFile[8]); if (TosVersion == 0x206 && be_swap16(*(uint16_t *)&pTosFile[30]) == 0x186A) TosVersion = 0x208; /* Check for reasonable TOS version: */ if (TosVersion == 0x000 && TosSize == 16384) { /* TOS 0.00 was a very early boot loader ROM which could only * execute a boot sector from floppy disk, which was used in * the very early STs before a full TOS was available in ROM. * It's not very useful nowadays, but we support it here, too, * just for fun. */ TosAddress = 0xfc0000; } else if (TosVersion < 0x100 || TosVersion >= 0x700 || TosSize > 1024*1024L || (TosAddress == 0xfc0000 && TosSize > 224*1024L) || (bRamTosImage && TosAddress + TosSize > STRamEnd) || (!bRamTosImage && TosAddress != 0xe00000 && TosAddress != 0xfc0000)) { Log_AlertDlg(LOG_FATAL, "Your TOS image seems not to be a valid TOS ROM file!\n" "(TOS version %x.%02x, address $%x)", TosVersion >> 8, TosVersion & 0xff, TosAddress); free(pTosFile); return NULL; } /* Assert that machine type matches the TOS version. * 512k EmuTOS declares itself as TOS 2.x, but it can handle * all machine types, so it can & needs to be skipped here */ if (!bIsEmuTOS || TosSize < 512 * 1024) TOS_CheckSysConfig(); /* 32-bit addressing is supported only by CPU >= 68020, TOS v3, TOS v4 and EmuTOS */ if (ConfigureParams.System.nCpuLevel < 2 || (TosVersion < 0x0300 && !bIsEmuTOS)) { ConfigureParams.System.bAddressSpace24 = true; M68000_CheckCpuSettings(); } else if (ConfigureParams.Memory.TTRamSize_KB) { switch (ConfigureParams.System.nMachineType) { case MACHINE_TT: if (ConfigureParams.System.bAddressSpace24) { /* Print a message and force 32 bit addressing (keeping 24 bit with TT RAM would crash TOS) */ Log_AlertDlg(LOG_ERROR, "Automatically enabling 32-bit addressing\nto support TT-RAM. This can cause issues\nin some programs!\n"); ConfigureParams.System.bAddressSpace24 = false; M68000_CheckCpuSettings(); } break; case MACHINE_FALCON: if (ConfigureParams.System.bAddressSpace24) { /* Print a message, but don't force 32 bit addressing as 24 bit addressing is also possible under Falcon */ /* So, if Falcon is in 24 bit mode, we just don't add TT RAM */ Log_AlertDlg(LOG_ERROR, "You need to disable 24-bit addressing to use TT-RAM in Falcon mode.\n"); } break; default: break; } } /* try to load (Emu)TOS symbols from .sym */ Symbols_LoadTOS(ConfigureParams.Rom.szTosImageFileName, TosAddress+TosSize); return pTosFile; } /** * Set the name of the program that should be tested (without TOS) */ void TOS_SetTestPrgName(const char *testprg) { psTestPrg = testprg; } /** * Create a fake TOS ROM that just jumps to test code in memory */ static uint8_t *TOS_FakeRomForTesting(void) { uint8_t *pFakeTosMem; /* We don't have a proper memory detection code in above init code, * so we have to disable the MMU emulation in this TOS-less mode */ ConfigureParams.System.bFastBoot = true; TosVersion = 0; TosAddress = 0xe00000; TosSize = sizeof(FakeTos_data); pFakeTosMem = malloc(TosSize); if (!pFakeTosMem) return NULL; memcpy(pFakeTosMem, FakeTos_data, TosSize); return pFakeTosMem; } /** * Load TOS Rom image file into ST memory space and fix image so it can be * emulated correctly. Pre TOS 1.06 are loaded at 0xFC0000 and later ones * at 0xE00000. * * Return zero if all OK, non-zero value for error. */ int TOS_InitImage(void) { uint8_t *pTosFile = NULL; uint32_t logopatch_addr = 0; uint16_t osconf, countrycode; bTosImageLoaded = false; /* Calculate initial end of RAM */ STRamEnd = ConfigureParams.Memory.STRamSize_KB * 1024; if (bUseTos) { pTosFile = TOS_LoadImage(); if (!pTosFile) return -1; } else { pTosFile = TOS_FakeRomForTesting(); if (!pTosFile) return -1; } /* After TOS is loaded, and machine configuration adapted * accordingly, memory amount can be corrected to a valid * machine specific value */ STRamEnd = STMemory_CorrectSTRamSize(); /* (Re-)Initialize the memory banks: */ memory_uninit(); memory_init(STRamEnd, ConfigureParams.Memory.TTRamSize_KB*1024, TosAddress); /* Clear Upper memory (ROM and IO memory) */ memset(&RomMem[0xe00000], 0, 0x200000); /* Copy loaded image into memory */ if (bRamTosImage) memcpy(&STRam[TosAddress], pTosFile, TosSize); else memcpy(&RomMem[TosAddress], pTosFile, TosSize); free(pTosFile); pTosFile = NULL; Log_Printf(LOG_DEBUG, "Loaded TOS version %i.%c%c, starting at $%x, " "country code = %i, %s\n", TosVersion>>8, '0'+((TosVersion>>4)&0x0f), '0'+(TosVersion&0x0f), TosAddress, STMemory_ReadWord(TosAddress+28)>>1, (STMemory_ReadWord(TosAddress+28)&1)?"PAL":"NTSC"); /* Are we allowed VDI under this TOS? */ if (bUseVDIRes) { if (TosVersion == 0x0100) { /* Warn user */ Log_AlertDlg(LOG_ERROR, "To use extended VDI resolutions, you must select a TOS >= 1.02."); /* And select non VDI */ bUseVDIRes = ConfigureParams.Screen.bUseExtVdiResolutions = false; } else { /* needs to be called after TosVersion is set, but * before STMemory_SetDefaultConfig() is called */ VDI_SetResolution(ConfigureParams.Screen.nVdiColors, ConfigureParams.Screen.nVdiWidth, ConfigureParams.Screen.nVdiHeight); } } /* Fix TOS image, modify code for emulation */ if (ConfigureParams.Rom.bPatchTos && !bIsEmuTOS && bUseTos) { TOS_FixRom(&logopatch_addr); } else { Log_Printf(LOG_DEBUG, "Skipped TOS patches.\n"); } /* whether to override EmuTOS country code */ osconf = STMemory_ReadWord(TosAddress+0x1C); countrycode = osconf >> 1; if (bIsEmuTOS && countrycode == TOS_LANG_ALL && !NvRam_Present() && ConfigureParams.Keyboard.nCountryCode != TOS_LANG_UNKNOWN) { countrycode = ConfigureParams.Keyboard.nCountryCode; /* low bit: us -> NTSC (0), any other -> PAL (1) */ osconf = (countrycode << 1) | (countrycode?1:0); STMemory_WriteWord(TosAddress+0x1C, osconf); Log_Printf(LOG_WARN, "=> EmuTOS country code: %d (%s), %s\n", countrycode, TOS_LanguageName(countrycode), (osconf & 1) ? "PAL" : "NTSC"); } Keymap_SetCountry(countrycode); /* * patch some values into the "Draw logo" patch. * Needs to be called after final VDI resolution has been determined. */ if (logopatch_addr != 0) { STMemory_WriteWord(logopatch_addr + 2, VDIPlanes); STMemory_WriteLong(logopatch_addr + 6, VDIWidth * VDIPlanes / 8); } /* Set rest of machine memory configuration, connected devices, etc. */ STMemory_SetDefaultConfig(); if (bUseLilo) { TosSize = 0; /* load linux */ if (!lilo_init()) return -1; } else if (!bUseTos) { /* Load test program (has to be done after memory has been cleared) */ if (psTestPrg) { Log_Printf(LOG_DEBUG, "Loading '%s' to 0x%x.\n", psTestPrg, TEST_PRG_START); if (GemDOS_LoadAndReloc(psTestPrg, TEST_PRG_BASEPAGE, true)) { Main_ErrorExit("Failed to load:", psTestPrg, 1); } } else { /* Jump to same address again */ STMemory_WriteLong(TEST_PRG_START, 0x4ef80000 | TEST_PRG_START); } } bTosImageLoaded = true; return 0; } static const struct { uint8_t value; const char *code; const char *name; } countries[] = { { TOS_LANG_US, "us", "USA" }, { TOS_LANG_DE, "de", "Germany" }, { TOS_LANG_FR, "fr", "France" }, { TOS_LANG_UK, "uk", "United Kingdom" }, { TOS_LANG_ES, "es", "Spain" }, { TOS_LANG_IT, "it", "Italy" }, { TOS_LANG_SE, "se", "Sweden" }, { TOS_LANG_CH_FR, "ch_fr", "Switzerland (French)" }, { TOS_LANG_CH_DE, "ch_de", "Switzerland (German)" }, { TOS_LANG_TR, "tr", "Turkey" }, { TOS_LANG_FI, "fi", "Finland" }, { TOS_LANG_NO, "no", "Norway" }, { TOS_LANG_DK, "dk", "Denmark" }, { TOS_LANG_SA, "sa", "Saudi Arabia" }, { TOS_LANG_NL, "nl", "Holland" }, { TOS_LANG_CS, "cs", "Czech Republic" }, { TOS_LANG_HU, "hu", "Hungary" }, { TOS_LANG_PL, "pl", "Poland" }, { TOS_LANG_RU, "ru", "Russia" }, { TOS_LANG_GR, "gr", "Greece" }, { TOS_LANG_RO, "ro", "Romania" }, { TOS_LANG_CA, "ca", "Catalan" }, }; /** * TOS_ValidCountryCode: returns parsed country code if * it's recognized, otherwise TOS_LANG_UNKNOWN is returned. */ int TOS_ParseCountryCode(const char *code) { for (int i = 0; i < ARRAY_SIZE(countries); i++) { if (strcmp(code, countries[i].code) == 0) { return countries[i].value; } } return TOS_LANG_UNKNOWN; } void TOS_ShowCountryCodes(void) { fprintf(stderr, "\nTOS v4 supports:\n"); for (int i = 0; i < ARRAY_SIZE(countries); i++) { if (countries[i].value == TOS_LANG_CH_FR) fprintf(stderr, "\nEmuTOS 1024k supports also:\n"); if (countries[i].value == TOS_LANG_RO) fprintf(stderr, "\nEmuTOS 1024k >1.2.1 also:\n"); if (countries[i].value == TOS_LANG_CA) fprintf(stderr, "\nEmuTOS 1024k >1.3.0 also:\n"); fprintf(stderr, "- %s : %s\n", countries[i].code, countries[i].name); } } /** * TOS_DefaultLanguage: return TOS country code matching LANG * environment variable. Supports LANG formats: "uk", "en_UK.*" */ int TOS_DefaultLanguage(void) { int len; const char *lang = getenv("LANG"); if (!lang) return TOS_LANG_UNKNOWN; len = strlen(lang); if (len == 2) return TOS_ParseCountryCode(lang); if (len >= 5 && lang[2] == '_') { char cc[3]; cc[0] = tolower(lang[3]); cc[1] = tolower(lang[4]); cc[2] = '\0'; return TOS_ParseCountryCode(cc); } return TOS_LANG_UNKNOWN; } /** * TOS_LanguageName: return name for given country code */ const char *TOS_LanguageName(int code) { for (int i = 0; i < ARRAY_SIZE(countries); i++) { if (code == countries[i].value) { return countries[i].name; } } return "Unknown"; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/unzip.c000066400000000000000000000727531504763705000226570ustar00rootroot00000000000000/* Hatari - unzip.c Support for *.zip files, uses zlib. This file is originally from the minizip code by Gilles Vollant. */ /* unzip.c -- IO on .zip files using zlib Version 0.15 beta, Mar 19th, 1998, Read unzip.h for more info */ const char Unzip_fileid[] = "Hatari unzip.c"; #include #include #include #include #include #include #include #if HAVE_STRINGS_H #include #endif #include "unzip.h" #ifndef local # define local static #endif /* compile with -Dlocal if your debugger can't find static symbols */ #if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ !defined(CASESENSITIVITYDEFAULT_NO) #define CASESENSITIVITYDEFAULT_NO #endif #ifndef UNZ_BUFSIZE #define UNZ_BUFSIZE (16384) #endif #ifndef UNZ_MAXFILENAMEINZIP #define UNZ_MAXFILENAMEINZIP (256) #endif #define SIZECENTRALDIRITEM (0x2e) #define SIZEZIPLOCALHEADER (0x1e) /* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif static const char unz_copyright[] = " unzip 0.15 Copyright 1998 Gilles Vollant"; /* unz_file_info_interntal contain internal info about a file in zipfile*/ typedef struct unz_file_info_internal_s { uLong offset_curfile;/* relative offset of local header 4 bytes */ } unz_file_info_internal; /* file_in_zip_read_info_s contain internal information about a file in zipfile, when reading and decompress it */ typedef struct { char *read_buffer; /* internal buffer for compressed data */ z_stream stream; /* zLib stream structure for inflate */ uLong pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ uLong stream_initialised; /* flag set if stream structure is initialised*/ uLong offset_local_extrafield;/* offset of the local extra field */ uInt size_local_extrafield;/* size of the local extra field */ uLong pos_local_extrafield; /* position in the local extra field in read*/ uLong crc32; /* crc32 of all data uncompressed */ uLong crc32_wait; /* crc32 we must obtain after decompress all */ uLong rest_read_compressed; /* number of byte to be decompressed */ uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ FILE* file; /* io structure of the zipfile */ uLong compression_method; /* compression method (0==store) */ uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ } file_in_zip_read_info_s; /* unz_s contain internal information about the zipfile */ typedef struct { FILE* file; /* io structure of the zipfile */ unz_global_info gi; /* public global information */ uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ uLong num_file; /* number of the current file in the zipfile*/ uLong pos_in_central_dir; /* pos of the current file in the central dir*/ uLong current_file_ok; /* flag about the usability of the current file*/ uLong central_pos; /* position of the beginning of the central dir*/ uLong size_central_dir; /* size of the central directory */ uLong offset_central_dir; /* offset of start of central directory with respect to the starting disk number */ unz_file_info cur_file_info; /* public info about the current file in zip*/ unz_file_info_internal cur_file_info_internal; /* private info about it*/ file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current file if we are decompressing it */ } unz_s; /* ===========================================================================*/ /** * Read a byte from a gz_stream; update next_in and avail_in. Return EOF * for end of file. * IN assertion: the stream s has been successfully opened for reading. */ local int unzlocal_getByte(FILE *fin, int *pi) { unsigned char c; int err = fread(&c, 1, 1, fin); if (err == 1) { *pi = (int)c; return UNZ_OK; } else { if (ferror(fin)) return UNZ_ERRNO; else return UNZ_EOF; } } /* ===========================================================================*/ /** * Reads a short in LSB order from the given gz_stream. */ local int unzlocal_getShort (FILE* fin, uLong *pX) { uLong x ; int i = 0; int err; err = unzlocal_getByte(fin,&i); x = (uLong)i; if (err == UNZ_OK) err = unzlocal_getByte(fin,&i); x += ((uLong)i)<<8; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } /** * Reads a long in LSB order from the given gz_stream. */ local int unzlocal_getLong (FILE* fin, uLong *pX) { uLong x ; int i = 0; int err; err = unzlocal_getByte(fin,&i); x = (uLong)i; if (err == UNZ_OK) err = unzlocal_getByte(fin,&i); x += ((uLong)i)<<8; if (err==UNZ_OK) err = unzlocal_getByte(fin,&i); x += ((uLong)i)<<16; if (err==UNZ_OK) err = unzlocal_getByte(fin,&i); x += ((uLong)i)<<24; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } #ifdef CASESENSITIVITYDEFAULT_NO #define CASESENSITIVITYDEFAULTVALUE 2 #else #define CASESENSITIVITYDEFAULTVALUE 1 #endif /** * Compare two filename (fileName1,fileName2). * If iCaseSenisivity = 1, comparison is case sensitivity (like strcmp) * If iCaseSenisivity = 2, comparison is not case sensitivity (like strcmpi * or strcasecmp) * If iCaseSenisivity = 0, case sensitivity is default of your operating system * (like 1 on Unix, 2 on Windows) * */ int ZEXPORT unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity) { if (iCaseSensitivity==0) iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; if (iCaseSensitivity==1) return strcmp(fileName1, fileName2); return strcasecmp(fileName1, fileName2); } #define BUFREADCOMMENT (0x400) /** * Locate the Central directory of a zipfile (at the end, just before * the global comment) */ local uLong unzlocal_SearchCentralDir(FILE *fin) { unsigned char* buf; uLong uSizeFile; uLong uBackRead; uLong uMaxBack=0xffff; /* maximum size of global comment */ uLong uPosFound=0; if (fseek(fin,0,SEEK_END) != 0) return 0; uSizeFile = ftell( fin ); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = malloc(BUFREADCOMMENT + 4); if (!buf) return 0; uBackRead = 4; while (uBackReaduMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); if (fseek(fin,uReadPos,SEEK_SET)!=0) break; if (fread(buf,(uInt)uReadSize,1,fin)!=1) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } free(buf); return uPosFound; } /** * Open a Zip file. path contain the full pathname (by example, * on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer * "zlib/zlib109.zip". * If the zipfile cannot be opened (file don't exist or in not valid), the * return value is NULL. * Else, the return value is a unzFile Handle, usable with other function * of this unzip package. */ unzFile ZEXPORT unzOpen (const char *path) { unz_s us = { 0 }; unz_s *s; uLong central_pos,uL; FILE * fin ; uLong number_disk; /* number of the current dist, used for spanning ZIP, unsupported, always 0*/ uLong number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP, unsupported, always 0*/ uLong number_entry_CD; /* total number of entries in the central dir (same than number_entry on nospan) */ int err=UNZ_OK; if (unz_copyright[0]!=' ') return NULL; fin=fopen(path,"rb"); if (fin==NULL) return NULL; central_pos = unzlocal_SearchCentralDir(fin); if (central_pos==0) err=UNZ_ERRNO; if (fseek(fin,central_pos,SEEK_SET)!=0) err=UNZ_ERRNO; /* the signature, already checked */ if (unzlocal_getLong(fin,&uL)!=UNZ_OK) err=UNZ_ERRNO; /* number of this disk */ if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) err=UNZ_ERRNO; /* number of the disk with the start of the central directory */ if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central dir on this disk */ if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central dir */ if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) err=UNZ_ERRNO; if ((number_entry_CD!=us.gi.number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=UNZ_BADZIPFILE; /* size of the central directory */ if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) err=UNZ_ERRNO; /* offset of start of central directory with respect to the starting disk number */ if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) err=UNZ_ERRNO; /* zipfile comment length */ if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) err=UNZ_ERRNO; if ((central_pospfile_in_zip_read!=NULL) unzCloseCurrentFile(file); fclose(s->file); free(s); return UNZ_OK; } /** * Write info about the ZipFile in the *pglobal_info structure. * No preparation of the structure is needed * return UNZ_OK if there is no problem. */ int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info) { unz_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; *pglobal_info=s->gi; return UNZ_OK; } /** * Translate date/time from Dos format to tm_unz (readable more easily) */ local void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) { uLong uDate; uDate = (uLong)(ulDosDate>>16); ptm->tm_mday = (uInt)(uDate&0x1f) ; ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; } /** * Get Info about the current file in the zipfile, with internal only info */ local int unzlocal_GetCurrentFileInfoInternal (unzFile file, unz_file_info *pfile_info, unz_file_info_internal *pfile_info_internal, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize) { unz_s* s; unz_file_info file_info; unz_file_info_internal file_info_internal; int err=UNZ_OK; uLong uMagic; long lSeek=0; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; if (fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) err=UNZ_ERRNO; /* we check the magic */ if (err==UNZ_OK) { if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) err=UNZ_ERRNO; else if (uMagic!=0x02014b50) err=UNZ_BADZIPFILE; } if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) err=UNZ_ERRNO; unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) err=UNZ_ERRNO; lSeek+=file_info.size_filename; if ((err==UNZ_OK) && (szFileName!=NULL)) { uLong uSizeRead ; if (file_info.size_filename0) && (fileNameBufferSize>0)) if (fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) err=UNZ_ERRNO; lSeek -= uSizeRead; } if ((err==UNZ_OK) && (extraField!=NULL)) { uLong uSizeRead ; if (file_info.size_file_extrafile,lSeek,SEEK_CUR)==0) { lSeek=0; } else { err=UNZ_ERRNO; } } if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) if (fread(extraField,(uInt)uSizeRead,1,s->file)!=1) err=UNZ_ERRNO; lSeek += file_info.size_file_extra - uSizeRead; } else lSeek+=file_info.size_file_extra; if ((err==UNZ_OK) && (szComment!=NULL)) { uLong uSizeRead ; if (file_info.size_file_commentfile,lSeek,SEEK_CUR)==0) { lSeek=0; } else { err=UNZ_ERRNO; } } if ((file_info.size_file_comment>0) && (commentBufferSize>0)) if (fread(szComment,(uInt)uSizeRead,1,s->file)!=1) err=UNZ_ERRNO; lSeek+=file_info.size_file_comment - uSizeRead; } else lSeek+=file_info.size_file_comment; if ((err==UNZ_OK) && (pfile_info!=NULL)) *pfile_info=file_info; if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) *pfile_info_internal=file_info_internal; return err; } /** * Write info about the ZipFile in the *pglobal_info structure. * No preparation of the structure is needed * return UNZ_OK if there is no problem. */ int ZEXPORT unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize) { return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, szFileName,fileNameBufferSize, extraField,extraFieldBufferSize, szComment,commentBufferSize); } /** * Set the current file of the zipfile to the first file. * return UNZ_OK if there is no problem */ int ZEXPORT unzGoToFirstFile (unzFile file) { int err=UNZ_OK; unz_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; s->pos_in_central_dir=s->offset_central_dir; s->num_file=0; err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } /** * Set the current file of the zipfile to the next file. * return UNZ_OK if there is no problem * return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. */ int ZEXPORT unzGoToNextFile (unzFile file) { unz_s* s; int err; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; if (s->num_file+1==s->gi.number_entry) return UNZ_END_OF_LIST_OF_FILE; s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; s->num_file++; err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } /** * Try locate the file szFileName in the zipfile. * For the iCaseSensitivity signification, see unzipStringFileNameCompare * * return value : * UNZ_OK if the file is found. It becomes the current file. * UNZ_END_OF_LIST_OF_FILE if the file is not found */ int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) { unz_s* s; int err; uLong num_fileSaved; uLong pos_in_central_dirSaved; if (file==NULL) return UNZ_PARAMERROR; if (strlen(szFileName) >= UNZ_MAXFILENAMEINZIP) return UNZ_PARAMERROR; s=(unz_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; num_fileSaved = s->num_file; pos_in_central_dirSaved = s->pos_in_central_dir; err = unzGoToFirstFile(file); while (err == UNZ_OK) { char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; unzGetCurrentFileInfo(file,NULL, szCurrentFileName,sizeof(szCurrentFileName)-1, NULL,0,NULL,0); if (unzStringFileNameCompare(szCurrentFileName, szFileName,iCaseSensitivity)==0) return UNZ_OK; err = unzGoToNextFile(file); } s->num_file = num_fileSaved ; s->pos_in_central_dir = pos_in_central_dirSaved ; return err; } /** * Read the local header of the current zipfile * Check the coherency of the local header and info in the end of central * directory about this file * store in *piSizeVar the size of extra info in local header * (filename and size of extra field data) */ local int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, uLong *poffset_local_extrafield, uInt *psize_local_extrafield) { uLong uMagic,uData,uFlags; uLong size_filename; uLong size_extra_field; int err=UNZ_OK; *piSizeVar = 0; *poffset_local_extrafield = 0; *psize_local_extrafield = 0; if (fseek(s->file,s->cur_file_info_internal.offset_curfile + s->byte_before_the_zipfile,SEEK_SET)!=0) return UNZ_ERRNO; if (err==UNZ_OK) { if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) err=UNZ_ERRNO; else if (uMagic!=0x04034b50) err=UNZ_BADZIPFILE; } if (unzlocal_getShort(s->file,&uData) != UNZ_OK) err=UNZ_ERRNO; /* else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) err=UNZ_BADZIPFILE; */ if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) err=UNZ_ERRNO; if (unzlocal_getShort(s->file,&uData) != UNZ_OK) err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) err=UNZ_BADZIPFILE; if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && (s->cur_file_info.compression_method!=Z_DEFLATED)) err=UNZ_BADZIPFILE; if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ err=UNZ_ERRNO; if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) err=UNZ_ERRNO; else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) err=UNZ_BADZIPFILE; *piSizeVar += (uInt)size_filename; if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) err=UNZ_ERRNO; *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + size_filename; *psize_local_extrafield = (uInt)size_extra_field; *piSizeVar += (uInt)size_extra_field; return err; } /** * Open for reading data the current file in the zipfile. * If there is no error and the file is opened, the return value is UNZ_OK. */ int ZEXPORT unzOpenCurrentFile (unzFile file) { int err=UNZ_OK; int Store; uInt iSizeVar; unz_s* s; file_in_zip_read_info_s* pfile_in_zip_read_info; uLong offset_local_extrafield; /* offset of the local extra field */ uInt size_local_extrafield; /* size of the local extra field */ if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; if (!s->current_file_ok) return UNZ_PARAMERROR; if (s->pfile_in_zip_read != NULL) unzCloseCurrentFile(file); if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) return UNZ_BADZIPFILE; pfile_in_zip_read_info = malloc(sizeof(file_in_zip_read_info_s)); if (!pfile_in_zip_read_info) return UNZ_INTERNALERROR; pfile_in_zip_read_info->read_buffer = malloc(UNZ_BUFSIZE); pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; pfile_in_zip_read_info->pos_local_extrafield=0; if (pfile_in_zip_read_info->read_buffer==NULL) { free(pfile_in_zip_read_info); return UNZ_INTERNALERROR; } pfile_in_zip_read_info->stream_initialised=0; if ((s->cur_file_info.compression_method!=0) && (s->cur_file_info.compression_method!=Z_DEFLATED)) err=UNZ_BADZIPFILE; Store = s->cur_file_info.compression_method==0; pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; pfile_in_zip_read_info->crc32=0; pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; pfile_in_zip_read_info->file=s->file; pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; pfile_in_zip_read_info->stream.total_out = 0; if (!Store) { pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; pfile_in_zip_read_info->stream.zfree = (free_func)0; pfile_in_zip_read_info->stream.opaque = (voidpf)0; err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); if (err == Z_OK) pfile_in_zip_read_info->stream_initialised=1; /* windowBits is passed < 0 to tell that there is no zlib header. * Note that in this case inflate *requires* an extra "dummy" byte * after the compressed stream in order to complete decompression and * return Z_STREAM_END. * In unzip, i don't wait absolutely Z_STREAM_END because I known the * size of both compressed and uncompressed data */ } pfile_in_zip_read_info->rest_read_compressed = s->cur_file_info.compressed_size ; pfile_in_zip_read_info->rest_read_uncompressed = s->cur_file_info.uncompressed_size ; pfile_in_zip_read_info->pos_in_zipfile = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + iSizeVar; pfile_in_zip_read_info->stream.avail_in = (uInt)0; s->pfile_in_zip_read = pfile_in_zip_read_info; return UNZ_OK; } /** * Read bytes from the current file. * buf contain buffer where data must be copied * len the size of buf. * * return the number of byte copied if some bytes are copied * return 0 if the end of file was reached * return <0 with error code if there is an error * (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len) { int err=UNZ_OK; uInt iRead = 0; unz_s* s; file_in_zip_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->read_buffer == NULL) return UNZ_END_OF_LIST_OF_FILE; if (len==0) return 0; pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; pfile_in_zip_read_info->stream.avail_out = (uInt)len; if (len>pfile_in_zip_read_info->rest_read_uncompressed) pfile_in_zip_read_info->stream.avail_out = (uInt)pfile_in_zip_read_info->rest_read_uncompressed; while (pfile_in_zip_read_info->stream.avail_out>0) { if ((pfile_in_zip_read_info->stream.avail_in==0) && (pfile_in_zip_read_info->rest_read_compressed>0)) { uInt uReadThis = UNZ_BUFSIZE; if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; if (uReadThis == 0) return UNZ_EOF; if (fseek(pfile_in_zip_read_info->file, pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) return UNZ_ERRNO; if (fread(pfile_in_zip_read_info->read_buffer, uReadThis, 1, pfile_in_zip_read_info->file)!=1) return UNZ_ERRNO; pfile_in_zip_read_info->pos_in_zipfile += uReadThis; pfile_in_zip_read_info->rest_read_compressed-=uReadThis; pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->read_buffer; pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; } if (pfile_in_zip_read_info->compression_method==0) { uInt uDoCopy,i ; if (pfile_in_zip_read_info->stream.avail_out < pfile_in_zip_read_info->stream.avail_in) uDoCopy = pfile_in_zip_read_info->stream.avail_out ; else uDoCopy = pfile_in_zip_read_info->stream.avail_in ; for (i=0;istream.next_out+i) = *(pfile_in_zip_read_info->stream.next_in+i); pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, pfile_in_zip_read_info->stream.next_out, uDoCopy); pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; pfile_in_zip_read_info->stream.avail_in -= uDoCopy; pfile_in_zip_read_info->stream.avail_out -= uDoCopy; pfile_in_zip_read_info->stream.next_out += uDoCopy; pfile_in_zip_read_info->stream.next_in += uDoCopy; pfile_in_zip_read_info->stream.total_out += uDoCopy; iRead += uDoCopy; } else { uLong uTotalOutBefore,uTotalOutAfter; const Bytef *bufBefore; uLong uOutThis; int flush=Z_SYNC_FLUSH; uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; bufBefore = pfile_in_zip_read_info->stream.next_out; /* if ((pfile_in_zip_read_info->rest_read_uncompressed == pfile_in_zip_read_info->stream.avail_out) && (pfile_in_zip_read_info->rest_read_compressed == 0)) flush = Z_FINISH; */ err=inflate(&pfile_in_zip_read_info->stream,flush); uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; uOutThis = uTotalOutAfter-uTotalOutBefore; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); if (err==Z_STREAM_END) return (iRead==0) ? UNZ_EOF : iRead; if (err != Z_OK) break; } } if (err==Z_OK) return iRead; return err; } /** * Give the current position in uncompressed data */ #if 0 z_off_t ZEXPORT unztell (unzFile file) { unz_s* s; file_in_zip_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; return (z_off_t)pfile_in_zip_read_info->stream.total_out; } #endif /** * return 1 if the end of file was reached, 0 elsewhere */ #if 0 int ZEXPORT unzeof (unzFile file) { unz_s* s; file_in_zip_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->rest_read_uncompressed == 0) return 1; else return 0; } #endif /** * Close the file in zip opened with unzipOpenCurrentFile * Return UNZ_CRCERROR if all the file was read but the CRC is not good */ int ZEXPORT unzCloseCurrentFile (unzFile file) { int err=UNZ_OK; unz_s* s; file_in_zip_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->rest_read_uncompressed == 0) { if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) err=UNZ_CRCERROR; } free(pfile_in_zip_read_info->read_buffer); pfile_in_zip_read_info->read_buffer = NULL; if (pfile_in_zip_read_info->stream_initialised) inflateEnd(&pfile_in_zip_read_info->stream); pfile_in_zip_read_info->stream_initialised = 0; free(pfile_in_zip_read_info); s->pfile_in_zip_read=NULL; return err; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/utils.c000066400000000000000000000063541504763705000226440ustar00rootroot00000000000000/* * Hatari - utils.c * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Utils functions : * - CRC32 * * This file contains various utility functions used by different parts of Hatari. */ const char Utils_fileid[] = "Hatari utils.c"; #include "utils.h" /************************************************************************/ /* Functions used to compute the CRC32 of a stream of bytes. */ /* These functions require a pointer to an unsigned int (uint32_t) to */ /* store the resulting CRC. */ /* crc32_reset : call this once to reset the CRC, before adding */ /* some bytes. */ /* crc32_add_byte : update the current CRC with a new byte. */ /************************************************************************/ /*--------------------------------------------------------------*/ /* Reset the crc32 value. This should be done before calling */ /* crc32_add_byte(). */ /*--------------------------------------------------------------*/ void crc32_reset ( uint32_t *crc ) { *crc = 0xffffffff; } /*--------------------------------------------------------------*/ /* Update the current value of crc with a new byte. */ /* Call crc32_reset() first to init the crc value. */ /*--------------------------------------------------------------*/ void crc32_add_byte ( uint32_t *crc , uint8_t c ) { int bit; for ( bit=0 ; bit<8; bit++ ) { if ( ( c & 0x80 ) ^ ( *crc & 0x80000000 ) ) *crc = ( *crc << 1 ) ^ CRC32_POLY; else *crc = *crc << 1; c <<= 1; } } /************************************************************************/ /* Functions used to compute the CRC16 of a stream of bytes. */ /* These functions require a pointer to an unsigned int (uint16_t) to */ /* store the resulting CRC. */ /* crc16_reset : call this once to reset the CRC, before adding */ /* some bytes. */ /* crc16_add_byte : update the current CRC with a new byte. */ /************************************************************************/ /*--------------------------------------------------------------*/ /* Reset the crc16 value. This should be done before calling */ /* crc16_add_byte(). */ /*--------------------------------------------------------------*/ void crc16_reset ( uint16_t *crc ) { *crc = 0xffff; } /*--------------------------------------------------------------*/ /* Update the current value of crc with a new byte. */ /* Call crc16_reset() first to init the crc value. */ /*--------------------------------------------------------------*/ void crc16_add_byte ( uint16_t *crc , uint8_t c ) { int bit; *crc ^= ( c << 8 ); for ( bit=0 ; bit<8; bit++ ) { if ( *crc & 0x8000 ) *crc = ( *crc << 1 ) ^ CRC16_POLY; else *crc = *crc << 1; c <<= 1; } } /************************************************************************/ /* Functions used to compute random numbers */ /* */ /* These can be simple wrappers around the OS calls or some customised */ /* routines. */ /************************************************************************/ void Hatari_srand ( unsigned int seed ) { srand(seed); } int Hatari_rand ( void ) { return rand(); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/vdi.c000066400000000000000000000770721504763705000222730ustar00rootroot00000000000000/* Hatari - vdi.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. VDI (Virtual Device Interface) (Trap #2) To get higher resolutions on the Desktop, we intercept the VDI/Line-A calls and set elements in their structures to the higher width/height/cel/planes. We need to intercept the initial Line-A call (which we force into the TOS on boot-up) and also the init calls to the VDI. */ const char VDI_fileid[] = "Hatari vdi.c"; #include "main.h" #include "configuration.h" #include "file.h" #include "gemdos.h" #include "inffile.h" #include "m68000.h" #include "options.h" #include "screen.h" #include "stMemory.h" #include "tos.h" #include "vars.h" #include "vdi.h" #include "video.h" /* #undef ENABLE_TRACING */ #define DEBUG 0 uint32_t VDI_OldPC; /* When call Trap#2, store off PC */ bool bVdiAesIntercept = false; /* Set to true to trace VDI & AES calls */ bool bUseVDIRes = false; /* Set to true (if want VDI), or false (ie for games) */ /* defaults */ int VDIRes = ST_LOW_RES; /* used in screen.c */ int VDIWidth = 640; /* 640x480, 800x600 or 1024x768 */ int VDIHeight = 480; int VDIPlanes = 4; static uint32_t LineABase; /* Line-A structure */ static uint32_t FontBase; /* Font base, used for 16-pixel high font */ /* Last VDI opcode, vectors & their contents (for "info vdi") */ static struct { uint32_t Control; uint32_t Intin; uint32_t Ptsin; uint32_t Intout; uint32_t Ptsout; /* TODO: add arrays for storing above vector contents */ uint16_t OpCode; } VDI; /* Last AES opcode, vectors & their contents (for "info aes") */ static struct { uint32_t Control; uint32_t Global; uint32_t Intin; uint32_t Intout; uint32_t Addrin; uint32_t Addrout; /* TODO: add arrays for storing above vector contents */ uint16_t OpCode; } AES; /*-----------------------------------------------------------------------*/ /** * Called to reset VDI variables on reset. */ void VDI_Reset(void) { /* no VDI calls in progress */ VDI_OldPC = 0; } /*-----------------------------------------------------------------------*/ /** * Limit width and height to VDI screen size in bytes, retaining their ratio. * Return true if limiting was done. */ bool VDI_ByteLimit(int *width, int *height, int planes) { double ratio; int size; size = (*width)*(*height)*planes/8; if (size <= MAX_VDI_BYTES) return false; ratio = sqrt(MAX_VDI_BYTES) / sqrt(size); *width = (*width) * ratio; *height = (*height) * ratio; if (*width < MIN_VDI_WIDTH || *height < MIN_VDI_HEIGHT) { *width = MIN_VDI_WIDTH; *height = MIN_VDI_HEIGHT; Log_Printf(LOG_WARN, "Bad VDI screen ratio / too small size -> use smallest valid size.\n"); } else Log_Printf(LOG_WARN, "VDI screen size limited to <= %dKB\n", MAX_VDI_BYTES/1024); return true; } /*-----------------------------------------------------------------------*/ /** * Set Width/Height/BitDepth according to passed GEMCOLOR_2/4/16. * Align size when necessary. */ void VDI_SetResolution(int GEMColor, int WidthRequest, int HeightRequest) { int w = WidthRequest; int h = HeightRequest; /* Color depth */ switch (GEMColor) { case GEMCOLOR_2: VDIRes = ST_HIGH_RES; VDIPlanes = 1; break; case GEMCOLOR_4: VDIRes = ST_MEDIUM_RES; VDIPlanes = 2; break; case GEMCOLOR_16: VDIRes = ST_LOW_RES; VDIPlanes = 4; break; default: Main_ErrorExit("Invalid VDI planes mode request (not 2/4/16)", NULL, 1); } #if DEBUG printf("%s v0x%04x, RAM=%dkB\n", bIsEmuTOS ? "EmuTOS" : "TOS", TosVersion, ConfigureParams.Memory.STRamSize_KB); #endif /* Make sure VDI screen size is acceptable and aligned to TOS requirements */ w = Opt_ValueAlignMinMax(w, VDI_ALIGN_WIDTH, MIN_VDI_WIDTH, MAX_VDI_WIDTH); h = Opt_ValueAlignMinMax(h, VDI_ALIGN_HEIGHT, MIN_VDI_HEIGHT, MAX_VDI_HEIGHT); /* screen size in bytes needs to be below limit */ if (VDI_ByteLimit(&w, &h, VDIPlanes)) { /* align again */ w = Opt_ValueAlignMinMax(w, VDI_ALIGN_WIDTH, MIN_VDI_WIDTH, MAX_VDI_WIDTH); h = Opt_ValueAlignMinMax(h, VDI_ALIGN_HEIGHT, MIN_VDI_HEIGHT, MAX_VDI_HEIGHT); } if (w != WidthRequest || h != HeightRequest) { Log_Printf(LOG_WARN, "VDI screen: request = %dx%d@%d, result = %dx%d@%d\n", WidthRequest, HeightRequest, VDIPlanes, w, h, VDIPlanes); } else { Log_Printf(LOG_DEBUG, "VDI screen: %dx%d@%d\n", w, h, VDIPlanes); } VDIWidth = w; VDIHeight = h; if (bUseVDIRes) { /* INF file overriding so that (re-)boot uses correct bit-depth */ INF_SetVdiMode(VDIRes); } } /*-----------------------------------------------------------------------*/ /* AES opcode -> function name mapping */ static const char* AESName_10[] = { "appl_init", /* (0x0A) */ "appl_read", /* (0x0B) */ "appl_write", /* (0x0C) */ "appl_find", /* (0x0D) */ "appl_tplay", /* (0x0E) */ "appl_trecord", /* (0x0F) */ NULL, /* (0x10) */ NULL, /* (0x11) */ "appl_search", /* (0x12) */ "appl_exit", /* (0x13) */ "evnt_keybd", /* (0x14) */ "evnt_button", /* (0x15) */ "evnt_mesag", /* (0x16) */ "evnt_mesag", /* (0x17) */ "evnt_timer", /* (0x18) */ "evnt_multi", /* (0x19) */ "evnt_dclick", /* (0x1A) */ NULL, /* (0x1b) */ NULL, /* (0x1c) */ NULL, /* (0x1d) */ "menu_bar", /* (0x1E) */ "menu_icheck", /* (0x1F) */ "menu_ienable", /* (0x20) */ "menu_tnormal", /* (0x21) */ "menu_text", /* (0x22) */ "menu_register", /* (0x23) */ "menu_popup", /* (0x24) */ "menu_attach", /* (0x25) */ "menu_istart", /* (0x26) */ "menu_settings", /* (0x27) */ "objc_add", /* (0x28) */ "objc_delete", /* (0x29) */ "objc_draw", /* (0x2A) */ "objc_find", /* (0x2B) */ "objc_offset", /* (0x2C) */ "objc_order", /* (0x2D) */ "objc_edit", /* (0x2E) */ "objc_change", /* (0x2F) */ "objc_sysvar", /* (0x30) */ NULL, /* (0x31) */ "form_do", /* (0x32) */ "form_dial", /* (0x33) */ "form_alert", /* (0x34) */ "form_error", /* (0x35) */ "form_center", /* (0x36) */ "form_keybd", /* (0x37) */ "form_button", /* (0x38) */ NULL, /* (0x39) */ NULL, /* (0x3a) */ NULL, /* (0x3b) */ NULL, /* (0x3c) */ NULL, /* (0x3d) */ NULL, /* (0x3e) */ NULL, /* (0x3f) */ NULL, /* (0x40) */ NULL, /* (0x41) */ NULL, /* (0x42) */ NULL, /* (0x43) */ NULL, /* (0x44) */ NULL, /* (0x45) */ "graf_rubberbox", /* (0x46) */ "graf_dragbox", /* (0x47) */ "graf_movebox", /* (0x48) */ "graf_growbox", /* (0x49) */ "graf_shrinkbox", /* (0x4A) */ "graf_watchbox", /* (0x4B) */ "graf_slidebox", /* (0x4C) */ "graf_handle", /* (0x4D) */ "graf_mouse", /* (0x4E) */ "graf_mkstate", /* (0x4F) */ "scrp_read", /* (0x50) */ "scrp_write", /* (0x51) */ NULL, /* (0x52) */ NULL, /* (0x53) */ NULL, /* (0x54) */ NULL, /* (0x55) */ NULL, /* (0x56) */ NULL, /* (0x57) */ NULL, /* (0x58) */ NULL, /* (0x59) */ "fsel_input", /* (0x5A) */ "fsel_exinput", /* (0x5B) */ NULL, /* (0x5c) */ NULL, /* (0x5d) */ NULL, /* (0x5e) */ NULL, /* (0x5f) */ NULL, /* (0x60) */ NULL, /* (0x61) */ NULL, /* (0x62) */ NULL, /* (0x63) */ "wind_create", /* (0x64) */ "wind_open", /* (0x65) */ "wind_close", /* (0x66) */ "wind_delete", /* (0x67) */ "wind_get", /* (0x68) */ "wind_set", /* (0x69) */ "wind_find", /* (0x6A) */ "wind_update", /* (0x6B) */ "wind_calc", /* (0x6C) */ "wind_new", /* (0x6D) */ "rsrc_load", /* (0x6E) */ "rsrc_free", /* (0x6F) */ "rsrc_gaddr", /* (0x70) */ "rsrc_saddr", /* (0x71) */ "rsrc_obfix", /* (0x72) */ "rsrc_rcfix", /* (0x73) */ NULL, /* (0x74) */ NULL, /* (0x75) */ NULL, /* (0x76) */ NULL, /* (0x77) */ "shel_read", /* (0x78) */ "shel_write", /* (0x79) */ "shel_get", /* (0x7A) */ "shel_put", /* (0x7B) */ "shel_find", /* (0x7C) */ "shel_envrn", /* (0x7D) */ NULL, /* (0x7e) */ NULL, /* (0x7f) */ NULL, /* (0x80) */ NULL, /* (0x81) */ "appl_getinfo" /* (0x82) */ }; /** * Map AES call opcode to an AES function name */ static const char* AES_Opcode2Name(uint16_t opcode) { int code = opcode - 10; if (code >= 0 && code < ARRAY_SIZE(AESName_10) && AESName_10[code]) return AESName_10[code]; else return "???"; } #if ENABLE_TRACING /** * Output AES call info, including some of args */ static void AES_OpcodeInfo(FILE *fp, uint16_t opcode) { /* AES opcodes which have string args */ static const struct { int code; /* AES opcode */ int count; /* number of char * args _first_ in addrin[] */ } strings[] = { { 0x0D, 1 }, /* appl_find() */ { 0x12, 1 }, /* appl_search() */ { 0x23, 1 }, /* menu_register() */ { 0x34, 1 }, /* form_alert() */ { 0x51, 1 }, /* scrp_write() */ { 0x5A, 2 }, /* fsel_input() */ { 0x5B, 3 }, /* fsel_exinput() */ { 0x6E, 1 }, /* rsrc_load() */ { 0x7C, 1 } /* shell_find() */ }; int code = opcode - 10; if (code >= 0 && code < ARRAY_SIZE(AESName_10) && AESName_10[code]) { bool first = true; int i, items; fprintf(fp, "%s(", AESName_10[code]); items = 0; /* there are so few of these that linear search is fine */ for (i = 0; i < ARRAY_SIZE(strings); i++) { /* something that can be shown? */ if (strings[i].code == opcode) { items = strings[i].count; break; } } /* addrin array size in longs enough for items? */ if (items > 0 && items <= STMemory_ReadWord(AES.Control+SIZE_WORD*3)) { const char *str; fputs("addrin: ", fp); for (i = 0; i < items; i++) { if (first) first = false; else fputs(", ", fp); str = (const char *)STMemory_STAddrToPointer(STMemory_ReadLong(AES.Addrin+SIZE_LONG*i)); fprintf(fp, "\"%s\"", str); } } /* intin array size in words */ items = STMemory_ReadWord(AES.Control+SIZE_WORD*1); if (items > 0) { if (!first) { fputs(", ", fp); first = true; } fputs("intin: ", fp); for (i = 0; i < items; i++) { if (first) first = false; else fputs(",", fp); fprintf(fp, "0x%x", STMemory_ReadWord(AES.Intin+SIZE_WORD*i)); } } fputs(")\n", fp); } else fputs("???\n", fp); } #endif /** * Verify given VDI table pointer and store variables from * it for later use. Return true for success */ static bool AES_StoreVars(uint32_t TablePtr) { if (!STMemory_CheckAreaType(TablePtr, 24, ABFLAG_RAM)) { Log_Printf(LOG_WARN, "AES param store failed due to invalid parameter block address 0x%x+%i\n", TablePtr, 24); return false; } /* store values for debugger "info aes" command */ AES.Control = STMemory_ReadLong(TablePtr); AES.Global = STMemory_ReadLong(TablePtr+4); AES.Intin = STMemory_ReadLong(TablePtr+8); AES.Intout = STMemory_ReadLong(TablePtr+12); AES.Addrin = STMemory_ReadLong(TablePtr+16); AES.Addrout = STMemory_ReadLong(TablePtr+20); /* TODO: copy/convert also above array contents to AES struct */ AES.OpCode = STMemory_ReadWord(AES.Control); return true; } /** * If opcodes argument is set, show AES opcode/function name table, * otherwise AES vectors information. */ void AES_Info(FILE *fp, uint32_t bShowOpcodes) { uint16_t opcode; if (bShowOpcodes) { for (opcode = 10; opcode < 0x86; opcode++) { fprintf(fp, "%02x %-16s", opcode, AES_Opcode2Name(opcode)); if ((opcode-9) % 4 == 0) fputs("\n", fp); } return; } opcode = Vars_GetAesOpcode(); if (opcode != INVALID_OPCODE) { /* we're on AES trap -> store new values */ if (!AES_StoreVars(Regs[REG_D1])) return; } else { #if !ENABLE_TRACING fputs("Hatari build with ENABLE_TRACING required to retain AES call info!\n", fp); return; #else if (!bVdiAesIntercept) { fputs("VDI/AES interception isn't enabled!\n", fp); return; } if (!AES.Control) { fputs("No traced AES calls -> no AES call info!\n", fp); return; } opcode = STMemory_ReadWord(AES.Control); if (opcode != AES.OpCode) { fputs("AES parameter block contents changed since last call!\n", fp); return; } #endif } /* TODO: replace use of STMemory calls with getting the data * from already converted AES.* array members */ fputs("Latest AES Parameter block:\n", fp); #if ENABLE_TRACING fprintf(fp, "- Opcode: 0x%02hX ", opcode); AES_OpcodeInfo(fp, opcode); #else fprintf(fp, "- Opcode: 0x%02hX (%s)\n", opcode, AES_Opcode2Name(opcode)); #endif fprintf(fp, "- Control: 0x%08x\n", AES.Control); fprintf(fp, "- Global: 0x%08x, %d bytes\n", AES.Global, 2+2+2+4+4+4+4+4+4); fprintf(fp, "- Intin: 0x%08x, %d words\n", AES.Intin, STMemory_ReadWord(AES.Control+2*1)); fprintf(fp, "- Intout: 0x%08x, %d words\n", AES.Intout, STMemory_ReadWord(AES.Control+2*2)); fprintf(fp, "- Addrin: 0x%08x, %d longs\n", AES.Addrin, STMemory_ReadWord(AES.Control+2*3)); fprintf(fp, "- Addrout: 0x%08x, %d longs\n", AES.Addrout, STMemory_ReadWord(AES.Control+2*4)); fflush(fp); } /*-----------------------------------------------------------------------*/ /** * Map VDI call opcode/sub-opcode to a VDI function name */ static const char* VDI_Opcode2Name(uint16_t opcode, uint16_t subcode, uint16_t nintin, const char **extra_info) { unsigned int i; static const char* names_0[] = { "???", "v_opnwk", "v_clswk", "v_clrwk", "v_updwk", "", /* 5: lots of sub opcodes */ "v_pline", "v_pmarker", "v_gtext", "v_fillarea", /* sub-opcode 13: v_bez_fill with GDOS */ "v_cellarray", "", /* 11: lots of sub opcodes */ "vst_height", "vst_rotation", "vs_color", "vsl_type", "vsl_width", "vsl_color", "vsm_type", "vsm_height", "vsm_color", "vst_font", "vst_color", "vsf_interior", "vsf_style", "vsf_color", "vq_color", "vq_cellarray", "vrq/sm_locator", "vrq/sm_valuator", "vrq/sm_choice", "vrq/sm_string", "vswr_mode", "vsin_mode", "???", /* 34 */ "vql_attributes", "vqm_attributes", "vqf_attributes", "vqt_attributes", "vst_alignment" }; static const char* names_100[] = { "v_opnvwk", "v_clsvwk", "vq_extnd", "v_contourfill", "vsf_perimeter", "v_get_pixel", "vst_effects", "vst_point", "vsl_ends", "vro_cpyfm", "vr_trnfm", "vsc_form", "vsf_udpat", "vsl_udsty", "vr_recfl", "vqin_mode", "vqt_extent", "vqt_width", "vex_timv", "vst_load_fonts", "vst_unload_fonts", "vrt_cpyfm", "v_show_c", "v_hide_c", "vq_mouse", "vex_butv", "vex_motv", "vex_curv", "vq_key_s", "vs_clip", "vqt_name", "vqt_fontinfo" /* 139-169: no known opcodes * 170-255: NVDI/Speedo GDOS opcodes */ }; static const char* names_opcode5[] = { "", "vq_chcells", "v_exit_cur", "v_enter_cur", "v_curup", "v_curdown", "v_curright", "v_curleft", "v_curhome", "v_eeos", "v_eeol", "vs_curaddress", "v_curtext", "v_rvon", "v_rvoff", "vq_curaddress", "vq_tabstatus", "v_hardcopy", "v_dspcur", "v_rmcur", "v_form_adv", "v_output_window", "v_clear_disp_list", "v_bit_image", "vq_scan", "v_alpha_text" }; static const char* names_opcode11[] = { "", "v_bar", "v_arc", "v_pieslice", "v_circle", "v_ellipse", "v_ellarc", "v_ellpie", "v_rbox", "v_rfbox", "v_justified", "???", "v_bez_on/off", }; static struct { unsigned short opcode; unsigned short subcode; unsigned short nintin; const char *name; const char *extra_info; } const names_other[] = { { 5, 98, 0xffff, "v_meta_extents", "GDOS" }, { 5, 99, 0xffff, "v_write_meta", "GDOS" }, { 5, 100, 0xffff, "vm_filename", "GDOS" }, { 5, 101, 0xffff, "v_offset", "GDOS" }, { 5, 102, 0xffff, "v_fontinit", "GDOS" }, { 100, 1, 13, "v_opnbm", "EdDI" }, { 100, 2, 6, "v_resize_bm", "EdDI" }, { 100, 3, 4, "v_open_bm", "EdDI" }, { 100, 0xffff, 0xffff, "v_opnvwk", NULL }, { 132, 0xffff, 0xffff, "vqt_justified", "PC/GEM" }, { 133, 0xffff, 0xffff, "vs_grayoverride", "GEM/3" }, { 134, 0xffff, 1, "v_pat_rotate", "GEM/3" }, { 134, 0xffff, 0xffff, "vex_wheelv", "Milan" }, { 138, 0xffff, 0xffff, "v_setrgb", "NVDI" }, { 170, 0, 0xffff, "vr_transfer_bits" }, { 171, 0, 0xffff, "vr_clip_rects_by_dst", "NVDI" }, /* NVDI 5.02 */ { 171, 1, 0xffff, "vr_clip_rects_by_src", "NVDI" }, /* NVDI 5.02 */ { 171, 2, 0xffff, "vr_clip_rects32_by_dst", "NVDI" }, /* NVDI 5.02 */ { 171, 3, 0xffff, "vr_clip_rects32_by_src", "NVDI" }, /* NVDI 5.02 */ { 180, 0, 0xffff, "v_create_driver_info", "NVDI" }, /* NVDI 5.00 */ { 181, 0, 0xffff, "v_delete_driver_info", "NVDI" }, /* NVDI 5.00 */ { 182, 0, 0xffff, "v_read_default_settings", "NVDI" }, /* NVDI 5.00 */ { 182, 1, 0xffff, "v_write_default_settings", "NVDI" }, /* NVDI 5.00 */ { 190, 0, 0xffff, "vqt_char_index", "GDOS" }, /* NVDI 4.00 */ { 200, 0, 0xffff, "vst_fg_color", "GDOS" }, /* NVDI 5.00 */ { 200, 1, 0xffff, "vsf_fg_color", "GDOS" }, /* NVDI 5.00 */ { 200, 2, 0xffff, "vsl_fg_color", "GDOS" }, /* NVDI 5.00 */ { 200, 3, 0xffff, "vsm_fg_color", "GDOS" }, /* NVDI 5.00 */ { 200, 4, 0xffff, "vsr_fg_color", "GDOS" }, /* NVDI 5.00 */ { 201, 0, 0xffff, "vst_bg_color", "GDOS" }, /* NVDI 5.00 */ { 201, 1, 0xffff, "vsf_bg_color", "GDOS" }, /* NVDI 5.00 */ { 201, 2, 0xffff, "vsl_bg_color", "GDOS" }, /* NVDI 5.00 */ { 201, 3, 0xffff, "vsm_bg_color", "GDOS" }, /* NVDI 5.00 */ { 201, 4, 0xffff, "vsr_bg_color", "GDOS" }, /* NVDI 5.00 */ { 202, 0, 0xffff, "vqt_fg_color", "GDOS" }, /* NVDI 5.00 */ { 202, 1, 0xffff, "vqf_fg_color", "GDOS" }, /* NVDI 5.00 */ { 202, 2, 0xffff, "vql_fg_color", "GDOS" }, /* NVDI 5.00 */ { 202, 3, 0xffff, "vqm_fg_color", "GDOS" }, /* NVDI 5.00 */ { 202, 4, 0xffff, "vqr_fg_color", "GDOS" }, /* NVDI 5.00 */ { 203, 0, 0xffff, "vqt_bg_color", "GDOS" }, /* NVDI 5.00 */ { 203, 1, 0xffff, "vqf_bg_color", "GDOS" }, /* NVDI 5.00 */ { 203, 2, 0xffff, "vql_bg_color", "GDOS" }, /* NVDI 5.00 */ { 203, 3, 0xffff, "vqm_bg_color", "GDOS" }, /* NVDI 5.00 */ { 203, 4, 0xffff, "vqr_bg_color", "GDOS" }, /* NVDI 5.00 */ { 204, 0, 0xffff, "v_color2value", "NVDI" }, /* NVDI 5.00 */ { 204, 1, 0xffff, "v_value2color", "NVDI" }, /* NVDI 5.00 */ { 204, 2, 0xffff, "v_color2nearest", "NVDI" }, /* NVDI 5.00 */ { 204, 3, 0xffff, "vq_px_format", "NVDI" }, /* NVDI 5.00 */ { 205, 0, 0xffff, "vs_ctab", "NVDI" }, /* NVDI 5.00 */ { 205, 1, 0xffff, "vs_ctab_entry", "NVDI" }, /* NVDI 5.00 */ { 205, 2, 0xffff, "vs_dflt_ctab", "NVDI" }, /* NVDI 5.00 */ { 206, 0, 0xffff, "vq_ctab", "NVDI" }, /* NVDI 5.00 */ { 206, 1, 0xffff, "vq_ctab_entry", "NVDI" }, /* NVDI 5.00 */ { 206, 2, 0xffff, "vq_ctab_id", "NVDI" }, /* NVDI 5.00 */ { 206, 3, 0xffff, "v_ctab_idx2vdi", "NVDI" }, /* NVDI 5.00 */ { 206, 4, 0xffff, "v_ctab_vdi2idx", "NVDI" }, /* NVDI 5.00 */ { 206, 5, 0xffff, "v_ctab_idx2value", "NVDI" }, /* NVDI 5.00 */ { 206, 6, 0xffff, "v_get_ctab_id", "NVDI" }, /* NVDI 5.00 */ { 206, 7, 0xffff, "vq_dflt_ctab", "NVDI" }, /* NVDI 5.00 */ { 206, 8, 0xffff, "v_create_ctab", "NVDI" }, /* NVDI 5.00 */ { 206, 9, 0xffff, "v_delete_ctab", "NVDI" }, /* NVDI 5.00 */ { 207, 0, 0xffff, "vs_hilite_color", "NVDI" }, /* NVDI 5.00 */ { 207, 1, 0xffff, "vs_min_color", "NVDI" }, /* NVDI 5.00 */ { 207, 2, 0xffff, "vs_max_color", "NVDI" }, /* NVDI 5.00 */ { 207, 3, 0xffff, "vs_weight_color", "NVDI" }, /* NVDI 5.00 */ { 208, 0, 0xffff, "v_create_itab", "NVDI" }, /* NVDI 5.00 */ { 208, 1, 0xffff, "v_delete_itab", "NVDI" }, /* NVDI 5.00 */ { 209, 0, 0xffff, "vq_hilite_color", "NVDI" }, /* NVDI 5.00 */ { 209, 1, 0xffff, "vq_min_color", "NVDI" }, /* NVDI 5.00 */ { 209, 2, 0xffff, "vq_max_color", "NVDI" }, /* NVDI 5.00 */ { 209, 3, 0xffff, "vq_weight_color", "NVDI" }, /* NVDI 5.00 */ { 224, 100, 0xffff, "vs_backmap", "Speedo" }, /* SpeedoGDOS 5.1 */ { 224, 101, 0xffff, "vs_outmode", "Speedo" }, /* SpeedoGDOS 5.1 */ { 224, 105, 0xffff, "vs_use_fonts", "Speedo" }, /* SpeedoGDOS 5.1 */ { 225, 0, 0xffff, "vqt_drv_avail", "Speedo" }, /* SpeedoGDOS 5.1 */ { 226, 1, 0xffff, "v_set_cachedir", "Speedo" }, /* SpeedoGDOS 5.1 */ { 226, 2, 0xffff, "v_get_cachedir", "Speedo" }, /* SpeedoGDOS 5.1 */ { 226, 3, 0xffff, "v_def_cachedir", "Speedo" }, /* SpeedoGDOS 5.1 */ { 226, 4, 0xffff, "v_clr_cachedir", "Speedo" }, /* SpeedoGDOS 5.1 */ { 226, 5, 0xffff, "v_delete_cache", "Speedo" }, /* SpeedoGDOS 5.1 */ { 226, 6, 0xffff, "v_save_cache", "Speedo" }, /* SpeedoGDOS 5.1 */ { 229, 0, 0xffff, "vqt_xfntinfo", "GDOS" }, /* NVDI 3.02 */ { 230, 0, 0xffff, "vst_name", "GDOS" }, /* NVDI 3.02 */ { 230, 100, 0xffff, "vqt_name_and_id", "GDOS" }, /* NVDI 3.02 */ { 231, 0, 0xffff, "vst_width", "GDOS" }, /* NVDI 3.00 */ { 232, 0, 0xffff, "vqt_fontheader", "GDOS" }, /* NVDI 3.00 */ { 233, 0, 0xffff, "v_mono_ftext", "Speedo" }, /* SpeedoGDOS 5.1 */ { 234, 0, 0xffff, "vqt_trackkern", "GDOS" }, /* NVDI 3.00 */ { 235, 0, 0xffff, "vqt_pairkern", "GDOS" }, /* NVDI 3.00 */ { 236, 0, 0xffff, "vst_charmap", "GDOS" }, /* NVDI 3.00 */ { 236, 0, 0xffff, "vst_map_mode", "GDOS" }, /* NVDI 4.00 */ { 237, 0, 0xffff, "vst_kern", "GDOS" }, /* NVDI 3.00 */ { 237, 0, 0xffff, "vst_track_offset", "GDOS" }, /* NVDI 3.00 */ { 238, 0, 0xffff, "vq_ptsinsz", "GDOS" }, { 239, 0, 0xffff, "v_getbitmap_info", "GDOS" }, /* NVDI 3.00 */ { 240, 0, 0xffff, "vqt_f_extent", "GDOS" }, /* NVDI 3.00 */ { 240, 4200, 0xffff, "vqt_real_extent", "GDOS" }, /* NVDI 3.00 */ { 241, 0, 0xffff, "v_ftext", "GDOS" }, /* NVDI 3.00 */ { 242, 0, 0xffff, "v_killoutline", "GDOS" }, /* FSM */ { 243, 0, 0xffff, "v_getoutline", "GDOS" }, /* NVDI 3.00 */ { 243, 1, 0xffff, "v_get_outline", "GDOS" }, /* NVDI 5.00 */ { 243, 31, 0xffff, "v_fgetoutline", "Speedo" }, /* SpeedoGDOS 5.0d */ { 244, 0, 0xffff, "vst_scratch", "Speedo" }, { 245, 0, 0xffff, "vst_error", "Speedo" }, /* SpeedoGDOS 4.00 */ { 246, 0, 0xffff, "vst_arbpt", "GDOS" }, /* SpeedoGDOS 4.00 */ { 246, 0, 0xffff, "vst_arbpt32", "GDOS" }, /* NVDI 3.00 */ { 247, 0, 0xffff, "vqt_advance", "GDOS" }, /* SpeedoGDOS 4.00 */ { 247, 0, 0xffff, "vqt_advance32", "GDOS" }, /* NVDI 3.00 */ { 248, 0, 0xffff, "vq_devinfo", "GDOS" }, /* NVDI 3.00 */ { 248, 0, 0xffff, "vqt_devinfo", "GDOS" }, /* SpeedoGDOS 4.00 */ { 248, 4242, 0xffff, "vq_ext_devinfo", "GDOS" }, /* NVDI 3.00 */ { 249, 0, 0xffff, "v_savecache", "Speedo" }, { 250, 0, 0xffff, "v_loadcache", "Speedo" }, { 251, 0, 0xffff, "v_flushcache", "GDOS" }, /* NVDI */ { 252, 0, 0xffff, "vst_setsize32", "GDOS" }, /* NVDI 3.00 */ { 252, 0, 0xffff, "vst_setsize", "GDOS" }, /* SpeedoGDOS 4.00 */ { 253, 0, 0xffff, "vst_skew", "GDOS" }, /* NVDI 3.00 */ { 254, 0, 0xffff, "vqt_get_table", "GDOS" }, /* SpeedoGDOS 4.00 */ { 255, 0, 0xffff, "vqt_cachesize", "Speedo" }, /* SpeedoGDOS 4.00 */ { 255, 100, 0xffff, "vqt_cacheinfo", "Speedo" }, /* SpeedoGDOS 4.00 */ }; *extra_info = NULL; if (opcode == 5) { if (subcode < ARRAY_SIZE(names_opcode5)) { return names_opcode5[subcode]; } } else if (opcode == 11) { if (subcode < ARRAY_SIZE(names_opcode11)) { return names_opcode11[subcode]; } } else if (opcode < ARRAY_SIZE(names_0)) { if (opcode == 1 && nintin >= 16) return "v_opnprn"; if (opcode == 6 && subcode == 13) return "v_bez"; if (opcode == 9 && subcode == 13) return "v_bez_fill"; return names_0[opcode]; } else if (opcode > 100) { uint16_t idx = opcode - 100; if (idx < ARRAY_SIZE(names_100)) { return names_100[idx]; } } for (i = 0; i < ARRAY_SIZE(names_other); i++) if (names_other[i].opcode == opcode) { if ((names_other[i].subcode == subcode || names_other[i].subcode == 0xffff) && (nintin >= names_other[i].nintin || names_other[i].nintin == 0xffff)) { *extra_info = names_other[i].extra_info; return names_other[i].name; } } return "???"; } /** * Verify given VDI table pointer and store variables from * it for later use. Return true for success */ static bool VDI_StoreVars(uint32_t TablePtr) { if (!STMemory_CheckAreaType(TablePtr, 20, ABFLAG_RAM)) { Log_Printf(LOG_WARN, "VDI param store failed due to invalid parameter block address 0x%x+%i\n", TablePtr, 20); return false; } /* store values for extended VDI resolution handling * and debugger "info vdi" command */ VDI.Control = STMemory_ReadLong(TablePtr); VDI.Intin = STMemory_ReadLong(TablePtr+4); VDI.Ptsin = STMemory_ReadLong(TablePtr+8); VDI.Intout = STMemory_ReadLong(TablePtr+12); VDI.Ptsout = STMemory_ReadLong(TablePtr+16); /* TODO: copy/convert also above array contents to AES struct */ VDI.OpCode = STMemory_ReadWord(VDI.Control); return true; } /** * If opcodes argument is set, show VDI opcode/function name table, * otherwise VDI vectors information. */ void VDI_Info(FILE *fp, uint32_t bShowOpcodes) { uint16_t opcode; const char *extra_info; if (bShowOpcodes) { for (opcode = 0; opcode <= 0x84; ) { if (opcode == 0x28) { fputs("--- GDOS calls? ---\n", fp); opcode = 0x64; } fprintf(fp, "%02x %-16s", opcode, VDI_Opcode2Name(opcode, 0, 0, &extra_info)); if (++opcode % 4 == 0) fputs("\n", fp); } if (opcode % 4) fputs("\n", fp); return; } opcode = Vars_GetVdiOpcode(); if (opcode != INVALID_OPCODE) { /* we're on VDI trap -> store new values */ if (!VDI_StoreVars(Regs[REG_D1])) return; } else { #if !ENABLE_TRACING fputs("Hatari build with ENABLE_TRACING required to retain VDI call info!\n", fp); return; #else if (!bVdiAesIntercept) { fputs("VDI/AES interception isn't enabled!\n", fp); return; } if (!VDI.Control) { fputs("No traced VDI calls -> no VDI call info!\n", fp); return; } opcode = STMemory_ReadWord(VDI.Control); if (opcode != VDI.OpCode) { fputs("VDI parameter block contents changed since last call!\n", fp); return; } #endif } /* TODO: replace use of STMemory calls with getting the data * from already converted VDI.* array members */ fputs("Latest VDI Parameter block:\n", fp); uint16_t subcode = STMemory_ReadWord(VDI.Control+2*5); uint16_t nintin = STMemory_ReadWord(VDI.Control+2*3); const char *name = VDI_Opcode2Name(opcode, subcode, nintin, &extra_info); fprintf(fp, "- Opcode/Subcode: 0x%02hX/0x%02hX (%s%s%s)\n", opcode, subcode, name, extra_info ? ", " : "", extra_info ? extra_info : ""); fprintf(fp, "- Device handle: %d\n", STMemory_ReadWord(VDI.Control+2*6)); fprintf(fp, "- Control: 0x%08x\n", VDI.Control); fprintf(fp, "- Ptsin: 0x%08x, %d coordinate word pairs\n", VDI.Ptsin, STMemory_ReadWord(VDI.Control+2*1)); fprintf(fp, "- Ptsout: 0x%08x, %d coordinate word pairs\n", VDI.Ptsout, STMemory_ReadWord(VDI.Control+2*2)); fprintf(fp, "- Intin: 0x%08x, %d words\n", VDI.Intin, STMemory_ReadWord(VDI.Control+2*3)); fprintf(fp, "- Intout: 0x%08x, %d words\n", VDI.Intout, STMemory_ReadWord(VDI.Control+2*4)); fflush(fp); } /*-----------------------------------------------------------------------*/ /** * Return true for only VDI opcodes that need to be handled at Trap exit. */ static inline bool VDI_isWorkstationOpen(uint16_t opcode) { if (opcode == 1 || opcode == 100) return true; else return false; } /** * Check whether this is VDI/AES call and see if we need to re-direct * it to our own routines. Return true if VDI_Complete() function * needs to be called on OS call exit, otherwise return false. * * We enter here with Trap #2, so D0 tells which OS call it is (VDI/AES) * and D1 is pointer to VDI/AES vectors, i.e. Control, Intin, Ptsin etc... */ bool VDI_AES_Entry(void) { uint16_t call = Regs[REG_D0]; #if ENABLE_TRACING uint32_t TablePtr = Regs[REG_D1]; /* AES call? */ if (call == 0xC8) { if (!AES_StoreVars(TablePtr)) return false; if (LOG_TRACE_LEVEL(TRACE_OS_AES)) { LOG_TRACE_DIRECT_INIT(); LOG_TRACE_DIRECT("AES 0x%02hX ", AES.OpCode); AES_OpcodeInfo(TraceFile, AES.OpCode); fflush(TraceFile); } /* using same special opcode trick doesn't work for * both VDI & AES as AES functions can be called * recursively and VDI calls happen inside AES calls. */ return false; } /* VDI call? */ if (call == 0x73) { uint16_t subcode, nintin; const char *extra_info, *name; if (!VDI_StoreVars(TablePtr)) return false; subcode = STMemory_ReadWord(VDI.Control+2*5); nintin = STMemory_ReadWord(VDI.Control+2*3); name = VDI_Opcode2Name(VDI.OpCode, subcode, nintin, &extra_info); LOG_TRACE(TRACE_OS_VDI, "VDI 0x%02hX/0x%02hX (%s%s%s)\n", VDI.OpCode, subcode, name, extra_info ? ", " : "", extra_info ? extra_info : ""); } #endif if (call == 0x73) { /* Only workstation open needs to be handled at trap return */ return bUseVDIRes && VDI_isWorkstationOpen(VDI.OpCode); } LOG_TRACE((TRACE_OS_VDI|TRACE_OS_AES), "Trap #2 with D0 = 0x%hX\n", call); return false; } /*-----------------------------------------------------------------------*/ /** * Modify Line-A structure for our VDI resolutions */ void VDI_LineA(uint32_t linea, uint32_t fontbase) { uint32_t fontadr, font1, font2; LineABase = linea; FontBase = fontbase; LOG_TRACE(TRACE_OS_VDI, "VDI mode line-A variable init\n"); if (bUseVDIRes) { int cel_ht, cel_wd; fontadr = STMemory_ReadLong(linea-0x1cc); /* def_font */ if (fontadr == 0) { /* get 8x8 font header */ font1 = STMemory_ReadLong(fontbase + 4); /* get 8x16 font header */ font2 = STMemory_ReadLong(fontbase + 8); /* remove DEFAULT flag from 8x8 font */ STMemory_WriteWord(font1 + 66, STMemory_ReadWord(font1 + 66) & ~0x01); /* remove DEFAULT flag from 8x16 font */ STMemory_WriteWord(font2 + 66, STMemory_ReadWord(font2 + 66) & ~0x01); /* choose new font */ if (VDIHeight >= 400) { fontadr = font2; } else { fontadr = font1; } /* make this new default font */ STMemory_WriteLong(linea-0x1cc, fontadr); /* set DEFAULT flag for chosen font */ STMemory_WriteWord(fontadr + 66, STMemory_ReadWord(fontadr + 66) | 0x01); } cel_wd = STMemory_ReadWord(fontadr + 52); cel_ht = STMemory_ReadWord(fontadr + 82); if (cel_wd <= 0) { Log_Printf(LOG_WARN, "VDI Line-A init failed due to bad cell width!\n"); return; } if (cel_ht <= 0) { Log_Printf(LOG_WARN, "VDI Line-A init failed due to bad cell height!\n"); return; } STMemory_WriteWord(linea-46, cel_ht); /* v_cel_ht */ STMemory_WriteWord(linea-44, (VDIWidth/cel_wd)-1); /* v_cel_mx (cols-1) */ STMemory_WriteWord(linea-42, (VDIHeight/cel_ht)-1); /* v_cel_my (rows-1) */ STMemory_WriteWord(linea-40, cel_ht*((VDIWidth*VDIPlanes)/8)); /* v_cel_wr */ STMemory_WriteLong(linea-22, STMemory_ReadLong(fontadr + 76)); /* v_fnt_ad */ STMemory_WriteWord(linea-18, STMemory_ReadWord(fontadr + 38)); /* v_fnt_nd */ STMemory_WriteWord(linea-16, STMemory_ReadWord(fontadr + 36)); /* v_fnt_st */ STMemory_WriteWord(linea-14, STMemory_ReadWord(fontadr + 80)); /* v_fnt_wd */ STMemory_WriteWord(linea-12, VDIWidth); /* v_rez_hz */ STMemory_WriteLong(linea-10, STMemory_ReadLong(fontadr + 72)); /* v_off_ad */ STMemory_WriteWord(linea-4, VDIHeight); /* v_rez_vt */ STMemory_WriteWord(linea-2, (VDIWidth*VDIPlanes)/8); /* bytes_lin */ STMemory_WriteWord(linea+0, VDIPlanes); /* planes */ STMemory_WriteWord(linea+2, (VDIWidth*VDIPlanes)/8); /* width */ } } /*-----------------------------------------------------------------------*/ /** * This is called on completion of a VDI Trap workstation open, * to modify the return structure for extended resolutions. */ void VDI_Complete(void) { /* right opcode? */ assert(VDI_isWorkstationOpen(VDI.OpCode)); /* not changed between entry and completion? */ assert(VDI.OpCode == STMemory_ReadWord(VDI.Control)); STMemory_WriteWord(VDI.Intout, VDIWidth-1); /* IntOut[0] Width-1 */ STMemory_WriteWord(VDI.Intout+1*2, VDIHeight-1); /* IntOut[1] Height-1 */ STMemory_WriteWord(VDI.Intout+13*2, 1 << VDIPlanes); /* IntOut[13] #colors */ STMemory_WriteWord(VDI.Intout+39*2, 512); /* IntOut[39] #available colors */ STMemory_WriteWord(LineABase-0x15a*2, VDIWidth-1); /* WKXRez */ STMemory_WriteWord(LineABase-0x159*2, VDIHeight-1); /* WKYRez */ VDI_LineA(LineABase, FontBase); /* And modify Line-A structure accordingly */ LOG_TRACE(TRACE_OS_VDI, "VDI mode Workstation Open return values fix\n"); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/video.c000066400000000000000000007406201504763705000226130ustar00rootroot00000000000000/* Hatari - video.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Video hardware handling. This code handling all to do with the video chip. So, we handle VBLs, HBLs, copying the ST screen to a buffer to simulate the TV raster trace, border removal, palette changes per HBL, the 'video address pointer' etc... */ /* 2007/03/xx [NP] Support for cycle precise border removal / hardware scrolling by using */ /* Cycles_GetCounterOnWriteAccess (support left/right border and lines with*/ /* length of +26, +2, -2, +44, -106 bytes). */ /* Add support for 'Enchanted Lands' second removal of right border. */ /* More precise support for reading video counter $ff8205/07/09. */ /* 2007/04/14 [NP] Precise reloading of $ff8201/03 into $ff8205/07/09 at line 310 on cycle */ /* RESTART_VIDEO_COUNTER_CYCLE (ULM DSOTS Demo). */ /* 2007/04/16 [NP] Better Video_CalculateAddress. We must subtract a "magic" 12 cycles to */ /* Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) to get a correct */ /* value (No Cooper's video synchro protection is finally OK :) ). */ /* 2007/04/17 [NP] - Switch to 60 Hz to remove top border on line 33 should occur before */ /* LINE_REMOVE_TOP_CYCLE (a few cycles before the HBL) */ /* 2007/04/23 [NP] - Slight change in Video_StoreResolution to ignore hi res if the line */ /* has left/right border removed -> assume of lo res line. */ /* - Handle simultaneous removal of right border and bottom border with */ /* the same long switch to 60 Hz (Sync Screen in SNY II). */ /* 2007/05/06 [NP] More precise tests for top border's removal. */ /* 2007/05/11 [NP] Add support for med res overscan (No Cooper Greetings). */ /* 2007/05/12 [NP] - LastCycleSync50 and LastCycleSync60 for better top border's removal */ /* in Video_EndHBL. */ /* - Use VideoOffset in Video_CopyScreenLineColor to handle missing planes */ /* depending on line (med/lo and borders). */ /* 2007/09/25 [NP] Replace printf by calls to HATARI_TRACE. */ /* 2007/10/02 [NP] Use the new int.c to add interrupts with INT_CPU_CYCLE / INT_MFP_CYCLE. */ /* 2007/10/23 [NP] Add support for 0 byte line (60/50 switch at cycle 56). Allow 5 lines */ /* hardscroll (e.g. SHFORSTV.EXE by Paulo Simmoes). */ /* 2007/10/31 [NP] Use BORDERMASK_LEFT_OFF_MED when left border is removed with hi/med */ /* switch (ST CNX in PYM). */ /* 2007/11/02 [NP] Add support for 4 pixel hardware scrolling ("Let's Do The Twist" by */ /* ST CNX in Punish Your Machine). */ /* 2007/11/05 [NP] Depending on the position of the med res switch, the planes will be */ /* shifted when doing med res overscan (Best Part Of the Creation in PYM */ /* or No Cooper Greetings). */ /* 2007/11/30 [NP] A hi/med switch to remove the left border can be either used to initiate*/ /* a right hardware scrolling in low res (St Cnx) or a complete med res */ /* overscan line (Dragonnels Reset Part). */ /* Use bit 0-15, 16-19 and 20-23 in ScreenBorderMask[] to track border */ /* trick, STF hardware scrolling and plane shifting. */ /* 2007/12/22 [NP] Very precise values for VBL_VIDEO_CYCLE_OFFSET, HBL_VIDEO_CYCLE_OFFSET */ /* TIMERB_VIDEO_CYCLE_OFFSET and RESTART_VIDEO_COUNTER_CYCLE. These values */ /* were calculated using sttiming.s on a real STF and should give some very*/ /* accurate results (also uses 56 cycles instead of 44 to process an */ /* HBL/VBL/MFP exception). */ /* 2007/12/29 [NP] Better support for starting line 2 bytes earlier (if the line starts in */ /* 60 Hz and goes back to 50 Hz later), when combined with top border */ /* removal (Mindbomb Demo - D.I. No Shit). */ /* 2007/12/30 [NP] Slight improvement of VideoAdress in Video_CalculateAddress when reading*/ /* during the top border. */ /* Correct the case where removing top border on line 33 could also be */ /* interpreted as a right border removal (which is not possible since the */ /* display is still off at that point). */ /* 2008/01/03 [NP] Better handling of nStartHBL and nEndHBL when switching freq from */ /* 50 to 60 Hz. Allows emulation of a "short" 50 Hz screen of 171 lines */ /* and a more precise removal of bottom border in 50 and 60 Hz. */ /* 2008/01/04 [NP] More generic detection for removing 2 bytes to the right of the line */ /* when switching from 60 to 50 Hz (works even with a big number of cycles */ /* between the freq changes) (Phaleon's Menus). */ /* 2008/01/06 [NP] More generic detection for stopping the display in the middle of a line */ /* with a hi / lo res switch (-106 bytes per line). Although switch to */ /* hi res should occur at cycle 160, some demos use 164 (Phaleon's Menus). */ /* 2008/01/06 [NP] Better bottom border's removal in 50 Hz : switch to 60 Hz must occur */ /* before cycle LINE_REMOVE_BOTTOM_CYCLE on line 263 and switch back to 50 */ /* Hz must occur after LINE_REMOVE_BOTTOM_CYCLE on line 263 (this means */ /* we can already be in 50 Hz when Video_EndHBL is called and still remove */ /* the bottom border). This is similar to the tests used to remove the */ /* top border. */ /* 2008/01/12 [NP] In Video_SetHBLPaletteMaskPointers, consider that if a color's change */ /* occurs after cycle LINE_END_CYCLE_NO_RIGHT, then it's related to the */ /* next line. */ /* FIXME : it would be better to handle all color changes through spec512.c*/ /* and drop the 16 colors palette per line. */ /* FIXME : we should use Cycles_GetCounterOnWriteAccess, but it doesn't */ /* support multiple accesses like move.l or movem. */ /* 2008/01/12 [NP] Handle 60 Hz switch during the active display of the last line to remove*/ /* the bottom border : this should also shorten line by 2 bytes (F.N.I.L. */ /* Demo by TNT). */ /* 2008/01/15 [NP] Don't do 'left+2' if switch back to 50 Hz occurs when line is not active*/ /* (after cycle LINE_END_CYCLE_60) (XXX International Demos). */ /* 2008/01/31 [NP] Improve left border detection : allow switch to low res on cycle <= 28 */ /* instead of <= 20 (Vodka Demo Main Menu). */ /* 2008/02/02 [NP] Added 0 byte line detection when switching hi/lo res at position 28 */ /* (Lemmings screen in Nostalgic-o-demo). */ /* 2008/02/03 [NP] On STE, write to video counter $ff8205/07/09 should only be applied */ /* immediately if display has not started for the line (before cycle */ /* LINE_END_CYCLE_50). If write occurs after, the change to pVideoRaster */ /* should be delayed to the end of the line, after processing the current */ /* line with Video_CopyScreenLineColor (Stardust Tunnel Demo). */ /* 2008/02/04 [NP] The problem is similar when writing to hwscroll $ff8264, we must delay */ /* the change until the end of the line if display was already started */ /* (Mindrewind by Reservoir Gods). */ /* 2008/02/06 [NP] On STE, when left/right borders are off and hwscroll > 0, we must read */ /* 6 bytes less than the expected value (E605 by Light). */ /* 2008/02/17 [NP] In Video_CopyScreenLine, LineWidth*2 bytes should be added after */ /* pNewVideoRaster is copied to pVideoRaster (Braindamage Demo). */ /* When reading a byte at ff8205/07/09, all video address bytes should be */ /* updated in Video_ScreenCounter_ReadByte, not just the byte that was */ /* read. Fix programs that just modify one byte in the video address */ /* counter (e.g. sub #1,$ff8207 in Braindamage Demo). */ /* 2008/02/19 [NP] In Video_CalculateAddress, use pVideoRaster instead of VideoBase to */ /* determine the video address when display is off in the upper part of */ /* the screen (in case ff8205/07/09 were modified on STE). */ /* 2008/02/20 [NP] Better handling in Video_ScreenCounter_WriteByte by changing only one */ /* byte and keeping the other (Braindamage End Part). */ /* 2008/03/08 [NP] Use M68000_INT_VIDEO when calling M68000_Exception(). */ /* 2008/03/13 [NP] On STE, LineWidth value in $ff820f is added to the shifter counter just */ /* when display is turned off on a line (when right border is started, */ /* which is usually on cycle 376). */ /* This means a write to $ff820f should be applied immediately only if it */ /* occurs before cycle LineEndCycle. Else, it is stored in NewLineWidth */ /* and used after Video_CopyScreenLine has processed the current line */ /* (improve the bump mapping part in Pacemaker by Paradox). */ /* LineWidth should be added to pVideoRaster before checking the possible */ /* modification of $ff8205/07/09 in Video_CopyScreenLine. */ /* 2008/03/14 [NP] Rename ScanLineSkip to LineWidth (more consistent with STE docs). */ /* On STE, better support for writing to video counter, line width and */ /* hw scroll. If write to register occurs just at the start of a new line */ /* but before Video_EndHBL (because the move started just before cycle 512)*/ /* then the new value should not be set immediately but stored and set */ /* during Video_EndHBL (fix the bump mapping part in Pacemaker by Paradox).*/ /* 2008/03/25 [NP] On STE, when bSteBorderFlag is true, we should add 16 pixels to the left*/ /* border, not to the right one (Just Musix 2 Menu by DHS). */ /* 2008/03/26 [NP] Clear the rest of the border when using border tricks left+2, left+8 */ /* or right-106 (remove garbage pixels when hatari resolution changes). */ /* 2008/03/29 [NP] Function Video_SetSystemTimings to use different values depending on */ /* the machine type. On STE, top/bottom border removal can occur at cycle */ /* 500 instead of 504 on STF. */ /* 2008/04/02 [NP] Correct a rare case in Video_Sync_WriteByte at the end of line 33 : */ /* nStartHBL was set to 33 instead of 64, which gave a wrong address in */ /* Video_CalculateAddress. */ /* 2008/04/04 [NP] The value of RestartVideoCounterCycle is slightly different between */ /* an STF and an STE. */ /* 2008/04/05 [NP] The value of VblVideoCycleOffset is different of 4 cycles between */ /* STF and STE (fix end part in Pacemaker by Paradox). */ /* 2008/04/09 [NP] Preliminary support for lines using different frequencies in the same */ /* screen. In Video_InterruptHandler_EndLine, if the current freq is 50 Hz,*/ /* then next int should be scheduled in 512 cycles ; if freq is 60 Hz, */ /* next int should be in 508 cycles (used by timer B event count mode). */ /* 2008/04/10 [NP] Update LineEndCycle after changing freq to 50 or 60 Hz. */ /* Set EndLine interrupt to happen 28 cycles after LineEndCycle. This way */ /* Timer B occurs at cycle 404 in 50 Hz, or cycle 400 in 60 Hz (improve */ /* flickering bottom border in B.I.G. Demo screen 1). */ /* 2008/04/12 [NP] In the case of a 'right-2' line, we should not change the EndLine's int */ /* position when switching back to 50 Hz ; the int should happen at */ /* position LINE_END_CYCLE_60 + 28 (Anomaly Demo main menu). */ /* 2008/05/31 [NP] Ignore consecutives writes of the same value in the freq/res register. */ /* Only the 1st write matters, else this could confuse the code to remove */ /* top/bottom border (fix OSZI.PRG demo by ULM). */ /* 2008/06/07 [NP] In Video_SetHBLPaletteMaskPointers, use LineStartCycle instead of the */ /* 50 Hz constant SCREEN_START_CYCLE. */ /* Rename SCREEN_START_HBL_xxx to VIDEO_START_HBL_xxx. */ /* Rename SCREEN_END_HBL_xxx to VIDEO_END_HBL_xxx. */ /* Rename SCREEN_HEIGHT_HBL_xxx to VIDEO_HEIGHT_HBL_xxx. */ /* Use VIDEO_HEIGHT_BOTTOM_50HZ instead of OVERSCAN_BOTTOM. */ /* 2008/06/16 [NP] When Hatari is configured to display the screen's borders, 274 lines */ /* will be rendered on screen, but if the shifter is in 60 Hz, the last */ /* 16 lines will never be used, which can leave some bad pixels on */ /* screen. We clear the remaining lines before calling 'Screen_Draw'. */ /* (in FNIL by Delta Force, fix flickering gfx in the bottom border of the */ /* F2 screen : last 16 lines were the ones from the menu where bottom */ /* border was removed ). */ /* 2008/06/26 [NP] Improve STE scrolling : handle $ff8264 (no prefetch) and $ff8265 */ /* (prefetch). See Video_HorScroll_Write for details on both registers. */ /* More generic support for starting display 16 pixels earlier on STE */ /* by writing to $ff8265 and setting $ff8264=0 just after. */ /* (fix Digiworld 2 by ICE, which uses $ff8264 for horizontal scroll). */ /* 2008/07/07 [NP] Ignore other 50/60 Hz switches once the right border was removed, keep */ /* the timer B to occur at pos 460+28 (fix Oxygene screen in Transbeauce 2)*/ /* 2008/07/14 [NP] When removing only left border in 60Hz, line size is 26+158 bytes */ /* instead of 26+160 bytes in 50 Hz (HigResMode demo by Paradox). */ /* 2008/07/19 [NP] If $ff8260==3 (which is not a valid resolution mode), we use 0 instead */ /* (low res) (fix Omegakul screen in old Omega Demo from 1988). */ /* 2008/09/05 [NP] No need to test 60/50 switch if HblCounterVideo < nStartHBL (display */ /* has not started yet). */ /* 2008/09/25 [NP] Use nLastVisibleHbl to store the number of the last hbl line that should*/ /* be copied to the emulator's screen buffer. */ /* On STE, allow to change immediately video address, hw scroll and */ /* linewidth when nHBL>=nLastVisibleHbl instead of nHBL>=nEndHBL */ /* (fix Power Rise / Xtrem D demo). */ /* 2008/11/15 [NP] For STE registers, add in the TRACE call if the write is delayed or */ /* not (linewidth, hwscroll, video address). */ /* On STE, allow to change linewidth, hwscroll and video address with no */ /* delay as soon as nHBL >= nEndHBL (revert previous changes). Power Rise */ /* is still working due to NewHWScrollCount=-1 when setting immediate */ /* hwscroll. Fix regression in Braindamage. */ /* 2008/11/29 [NP] Increment jitter's index for HBL and VBL each time a possible interrupt */ /* occurs. Each interrupt can have a jitter between 0, 4 and 8 cycles ; the*/ /* jitter follows a predefined pattern of 5 values. The HBL and the VBL */ /* have their own pattern. See InterruptAddJitter() in cpu/newcpu.c */ /* (fix Fullscreen tunnel in Suretrip 49% by Checkpoint and digi sound in */ /* Swedish New Year's TCB screen). */ /* 2008/12/10 [NP] Enhance support for 0 byte line. The 60/50 Hz switch can happen at */ /* cycles 56/64, but also at 58/66 (because access to $ff820a doesn't */ /* require to be on a 4 cycles boundary). As hatari doesn't handle */ /* multiple of 2 cycles, we allow cycles 56/64 and 60/68 (fix nosync.tos */ /* that uses the STOP instruction to produce a 0 byte line on the first */ /* displayed line (found on atari-forum.com)). */ /* 2008/12/26 [NP] When reading $ff8260 on STF, set unused bits to 1 instead of 0 */ /* (fix wrong TOS resolution in Awesome Menu Disk 16). */ /* Set unused bit to 1 when reading $ff820a too. */ /* 2009/01/16 [NP] Handle special case when writing only in upper byte of a color reg. */ /* 2009/01/21 [NP] Implement STE horizontal scroll for medium res (fixes cool_ste.prg). */ /* Take the current res into account in Video_CopyScreenLineColor to */ /* allow mixing low/med res with horizontal scroll on STE. */ /* 2009/01/24 [NP] Better detection of 'right-2' when freq is changed to 60 Hz and */ /* restored to 50 after the end of the current line (fixes games menu on */ /* BBC compil 10). */ /* 2009/01/31 [NP] Handle a rare case where 'move.b #8,$fffa1f' to start the timer B is */ /* done just a few cycles before the actual signal for end of line. In that*/ /* case we must ensure that the write was really effective before the end */ /* of line (else no interrupt should be made) (fix Pompey Pirate Menu #57).*/ /* 2009/02/08 [NP] Handle special case for simultaneous HBL exceptions (fixes flickering in*/ /* Monster Business and Super Monaco GP). */ /* 2009/02/25 [NP] Ignore other 50/60 Hz switches after display was stopped in the middle */ /* of the line with a hi/lo switch. Correct missing end of line timer B */ /* interrupt in that case (fix flickering Dragon Ball part in Blood disk 2 */ /* by Holocaust). */ /* 2008/02/02 [NP] Added 0 byte line detection in STE mode when switching hi/lo res */ /* at position 32 (Lemmings screen in Nostalgic-o-demo). */ /* 2009/03/28 [NP] Depending on bit 3 of MFP's AER, timer B will count end of line events */ /* (bit=0) or start of line events (bit=1) (fix Seven Gates Of Jambala). */ /* 2009/04/02 [NP] Add another method to obtain a 0 byte line, by switching to hi/lo res */ /* at position 500/508 (fix the game No Buddies Land). */ /* 2009/04/xx [NP] Rewrite of many parts : add SHIFTER_FRAME structure, better accuracy */ /* when mixing 50/60 Hz lines and reading $ff8209, better emulation of */ /* HBL and Timer B position when changing freq/res, better emulation of */ /* freq changes for top/bottom/right borders. */ /* 2009/07/16 [NP] In Video_SetHBLPaletteMaskPointers, if LineCycle>460 we consider the */ /* color's change should be applied to next line (used when spec512 mode */ /* if off). */ /* 2009/10/31 [NP] Depending on the overscan mode, the displayed lines must be shifted */ /* left or right (fix Spec 512 images in the Overscan Demos, fix pixels */ /* alignment in screens mixing normal lines and overscan lines). */ /* 2009/12/02 [NP] If we switch hi/lo around position 464 (as in Enchanted Lands) and */ /* right border was not removed, then we get an empty line on the next */ /* HBL (fix Pax Plax Parralax in Beyond by Kruz). */ /* 2009/12/06 [NP] Add support for STE 224 bytes overscan without stabiliser by switching */ /* hi/lo at cycle 504/4 to remove left border (fix More Or Less Zero and */ /* Cernit Trandafir by DHS, as well as Save The Earth by Defence Force). */ /* 2009/12/13 [NP] Improve STE 224 bytes lines : correctly set leftmost 16 pixels to color */ /* 0 and correct small glitches when combined with hscroll ($ff8264). */ /* 2009/12/13 [NP] Line scrolling caused by hi/lo switch (STF_PixelScroll) should be */ /* applied after STE's hardware scrolling, else in overscan 4 color 0 */ /* pixels will appear in the right border (because overscan shift the */ /* whole displayed area 4 pixels to the left) (fix possible regression on */ /* STE introduced on 2009/10/31). */ /* 2010/01/10 [NP] In Video_CalculateAddress, take bSteBorderFlag into account (+16 pixels */ /* in left border on STE). */ /* 2010/01/10 [NP] In Video_CalculateAddress, take HWScrollPrefetch into account (shifter */ /* starts 16 pixels earlier) (fix EPSS demo by Unit 17). */ /* 2010/02/05 [NP] In Video_CalculateAddress, take STE's LineWidth into account when */ /* display is disabled in the right border (fix flickering in Utopos). */ /* 2010/02/07 [NP] Better support for modifying $ff8205/07/09 while display is on */ /* (fix EPSS demo by Unit 17). */ /* 2010/04/12 [NP] Improve timings when writing to $ff8205/07/09 when hscroll is used, */ /* using Video_GetMMUStartCycle (fix Pacemaker's Bump Part by Paradox). */ /* 2010/05/02 [NP] In Video_ConvertPosition, handle the case where we read the position */ /* between the last HBL and the start of the next VBL. During 64 cycles */ /* FrameCycles can be >= CYCLES_PER_FRAME (harmless fix, only useful when */ /* using --trace to get correct positions in the logs). */ /* 2010/05/04 [NP] Improve Video_ConvertPosition, use CyclesPerVBL instead of evaluating */ /* CYCLES_PER_FRAME (whose value could have changed this the start of the */ /* VBL). */ /* 2010/05/15 [NP] In Video_StartInterrupts() when running in monochrome (224 cycles per */ /* line), the VBL could sometimes be delayed by 160 cycles (divs) and */ /* hbl/timer B interrupts for line 0 were not called, which could cause an */ /* assert/crash in Hatari when setting timer B on line 2. */ /* If we detect VBL was delayed too much, we add hbl/timer b in the next */ /* 4 cycles. */ /* 2010/07/05 [NP] When removing left border, allow up to 32 cycles between hi and low */ /* res switching (fix Megabeer by Invizibles). */ /* 2010/11/01 [NP] On STE, the 224 bytes overscan will shift the screen 8 pixels to the */ /* left. */ /* For 230 bytes overscan, handle scrolling prefetching when computing */ /* pVideoRaster for the next line. */ /* 2010/12/12 [NP] In Video_CopyScreenLineColor, use pVideoRasterEndLine to improve */ /* STE's horizontal scrolling for any line's length (160, 224, 230, ...). */ /* Fix the last 16 pixels for 224 bytes overscan (More Or Less Zero and */ /* Cernit Trandafir by DHS, Save The Earth by Defence Force). */ /* 2011/04/03 [NP] Call DmaSnd_HBL_Update() on each HBL to handle programs that modify */ /* the samples data while those data are played by the DMA sound. */ /* (fixes the game Power Up Plus and the demo Mental Hangover). */ /* 2011/07/30 [NP] Add blank line detection in STF mode when switching 60/50 Hz at cycle */ /* 28. The shifter will still read bytes and border removal is possible, */ /* but the line will be blank (we use color 0 for now, but the line should */ /* be black). */ /* (fix spectrum 512 part in Overscan Demo and shforstv by Paulo Simoes */ /* by removing "parasite" pixels on the 1st line). */ /* 2011/11/17 [NP] Improve timings used for the 0 byte line when switching hi/lo at the */ /* end of the line. The hi/lo switch can be at 496/508 or 500/508 */ /* (fix NGC screen in Delirious Demo IV). */ /* 2011/11/18 [NP] Add support for another method to do 4 pixel hardware scrolling by doing*/ /* a med/lo switch after the hi/lo switch to remove left border */ /* (fix NGC screen in Delirious Demo IV). */ /* 2011/11/19 [NP] The 0 byte line obtained by switching hi/lo at the end of the line has */ /* no video signal at all (blank). In that case, the screen is shifted one */ /* line down, and bottom border removal will happen one line later too */ /* (fix NGC screen in Delirious Demo IV). */ /* 2012/01/11 [NP] Don't remove left border when the hi/lo switch is made at cycle >= 12 */ /* (fix 'Kill The Beast 2' in the Vodka Demo) */ /* 2012/05/19 [NP] Allow bottom border to be removed when switch back to 50 Hz is made at */ /* cycle 504 and more (instead of 508 and more). Same for top border */ /* (fix 'Musical Wonders 1990' by Offbeat). */ /* 2013/03/05 [NP] An extra 4 cycle delay is added by the MFP to set IRQ when the timer B */ /* expires in event count mode. Update TIMERB_VIDEO_CYCLE_OFFSET to 24 */ /* cycles instead of 28 to compensate for this and keep the same position. */ /* 2013/04/26 [NP] Cancel changes from 2012/05/19, 'Musical Wonders 1990' is really broken */ /* on a real STF and bottom border is not removed. */ /* 2013/05/03 [NP] Add support for IACK sequence when handling HBL/VBL exceptions. Allow */ /* to handle the case where interrupt pending bit is set twice (correct */ /* fix for Super Monaco GP, Super Hang On, Monster Business, European */ /* Demo's Intro, BBC Menu 52). */ /* 2013/07/17 [NP] Handle a special case when writing only in lower byte of a color reg. */ /* 2013/12/02 [NP] If $ff8260==3 (which is not a valid resolution mode), we use 2 instead */ /* (high res) (cancel wrong change from 2008/07/19 and fix 'The World Is */ /* My Oyster - Convention Report Part' by Aura). */ /* 2013/12/24 [NP] In Video_ColorReg_ReadWord, randomly return 0 or 1 for unused bits */ /* in STF's color registers (fix 'UMD 8730' by PHF in STF mode) */ /* 2013/12/28 [NP] For bottom border removal on a 60 Hz screen, max position to go back */ /* to 60 Hz should be 4 cycles earlier, as a 60 Hz line starts 4 cycles */ /* earlier (fix STE demo "It's a girl 2" by Paradox). */ /* 2014/02/22 [NP] In Video_ColorReg_ReadWord(), don't set unused STF bits to rand() if */ /* the PC is not executing from the RAM between 0 and 4MB (fix 'Union Demo'*/ /* protection code running at address $ff8240). */ /* 2014/03/21 [NP] For STE in med res overscan at 60 Hz, add a 3 pixels shift to have */ /* bitmaps and color changes synchronised (fix 'HighResMode' by Paradox). */ /* 2014/05/08 [NP] In case we're mixing 50 Hz and 60 Hz lines (512 or 508 cycles), we must */ /* update the position where the VBL interrupt will happen (fix "keyboard */ /* no jitter" test program by Nyh, with 4 lines at 60 Hz and 160240 cycles */ /* per VBL). */ /* 2014/05/31 [NP] Ensure pVideoRaster always points into a 24 bit space region. In case */ /* video address at $ff8201/03 is set into IO space $ffxxxx, the new value */ /* for video pointer should not be >= $1000000 (fix "Leavin' Teramis" */ /* which sets video address to $ffe100 to display "loading please wait". */ /* In that case, we must display $ffe100-$ffffff then $0-$5e00) */ /* 2015/06/19 [NP] In Video_CalculateAddress, handle a special/simplified case when reading*/ /* video pointer in hi res (fix protection in 'My Socks Are Weapons' demo */ /* by 'Legacy'). */ /* 2015/08/18 [NP] In Video_CalculateAddress, handle the case when reading overlaps end */ /* of line / start of next line and STE's linewidth at $FF820F != 0. */ /* 2015/09/28 [NP] In Video_ScreenCounter_ReadByte, take VideoCounterDelayedOffset into */ /* account to handle the case where ff8205/07/09 are modified when display */ /* is ON and read just after (this is sometimes used to detect if the */ /* machine is an STF or an STE) (fix STE detection in the Menu screen of */ /* the 'Place To Be Again' demo). */ /* 2015/09/29 [NP] Add different values for RestartVideoCounterCycle when using 60 Hz */ /* (fix 60 Hz spectrum 512 double buffer image in the intro of the */ /* 'Place To Be Again' demo) */ /* 2015/10/30 [NP] In Video_CopyScreenLineColor, correctly show the last 8 pixels on */ /* the right when displaying an STE 224 byte overscan line containing */ /* 416 usable pixels (eg 'Drone' by DHS, 'PhotoChrome Viewer' by DML) */ /* 2015/11/15 [NP] Call ShortCut_ActKey() earlier in Video_InterruptHandler_VBL, before */ /* acknowledging it to get more consistent state in memory snapshots */ /* created using shortcut keys. */ /* 2015/12/31 [NP] More accurate reloading of $ff8201/03 into $ff8205/07/09 at line 310 on */ /* cycle 48 (STF 50 Hz) and line 260 cycle 48 (STF 60 Hz) (used in Intro */ /* and Menu of 'Dark Side Of The Spoon' by ULM). */ /* 2016/01/15 [NP] Don't call Video_AddInterruptHBL if we're handling the special HBL to */ /* restart video counter (in case freq/res are modified between */ /* cycles 0 and 48) */ /* 2016/01/31 [NP] Video registers can be accessed at cycles 4n or 4n+2, except colors and */ /* resolution which must use M68000_SyncCpuBus_OnRead/WriteAccess() to be */ /* at 4n. This requires to use CPU in cycle exact mode. */ /* 2016/02/04 [NP] On STF, the switch back to low res to remove left border should be made */ /* at pos > 4. If made before, left border is not removed. */ /* 2016/02/04 [NP] Add support for left+2 at 50 Hz if switch back to 50 Hz is made at */ /* pos 54 (requires 2 cycle precision in CE mode) (wsdetect by Troed/Sync, */ /* report STF as WS1) */ /* 2016/02/19 [NP] Improve blank line detection when switching to 60 Hz at cycle 28 and */ /* switching back to 50 Hz before cycle 56 (allow combining blank line */ /* and left+2 in 'Closure' by Sync) */ /* 2016/02/22 [NP] Better accuracy for the number of cycles per VBL when mixing 50 Hz and */ /* 60 Hz line. Instead of starting next VBL from the current one and */ /* updating its delay on each freq change, we start it from the last HBL */ /* of the screen (which should be more similar to how real HW works) */ /* (eg 160240 cycles per VBL in "keyboard no jitter" test program by NyH). */ /* 2016/02/26 [NP] Add support for 'remove left' including a med res stabiliser, by doing */ /* hi/med/low switches at cycles 0/8/16 ('Closure' by Sync). */ /* 2016/03/15 [NP] Allow to have different video timings for all machines/wakeup states */ /* 2016/05/10 [NP] Rewrite freq/res registers handling into Video_Update_Glue_State(), */ /* taking all STF wakeup states into account ('Closure' by Sync and */ /* 'shforstv' by Paulo Simoes work in all 4 wakeup states, TCB screen in */ /* Swedish New Year has a centered logo in WS2, Nostalgic demo main menu */ /* by Oxygene is correctly broken in WS2/4). */ /* 2016/05/25 [NP] Rewrite top/bottom border handling into Video_Update_Glue_State() */ /* 2016/06/06 [NP] Add preliminary support for STE timings in Video_Update_Glue_State(). */ /* Similar to STF timings, with support for STE specific "left+20". */ /* (fix "We Were @" by Oxygene, "More or less 0", "Sea of colors" and more */ /* demos by DHS) */ /* 2016/06/07 [NP] In Video_Update_Glue_State(), add STE timings for "0 byte" and "left+2" */ /* lines (fix "LoSTE" and "Closure" by Sync in STE mode) */ /* 2017/02/24 [NP] If a line has right-2/left+2 on a 60 Hz screen, then it should be */ /* considered as a normal 60 Hz line (fix 60 Hz bottom border removal in */ /* 'Protracker 2.1' and 'Neochrome master' when running at 60 Hz) */ /* 2017/02/24 [NP] Don't change DE_end / right-2 unless DE_start has been set, else it */ /* could mean the freq change overlaps with the next HBL handler on line */ /* n+1 (eg: move at cycle 508 line n that changes freq to 60Hz at cycle 4 */ /* line n+1, before handler for new HBL n+1 was called) (fix wrong right-2 */ /* during 60 Hz bottom border removal in 'Protracker 2.1' and 'Neochrome */ /* master' when running at 60 Hz) */ /* 2017/05/09 [NP] Add support for 4 pixel hardscroll propagated on every line (fix */ /* beescrn4.prg by Paulo Simoes) */ /* 2017/10/30 [NP] In Video_ResetShifterTimings, use VIDEO_HEIGHT_HBL_MONO only if screen */ /* is in Mono mode and video resolution=high (fix 'Audio Sculpture' when */ /* running in Mono mode) */ /* 2018/07/10 [NP] Handle vblank/vsync signals to mask the 2 last displayed lines when */ /* removing bottom border (fix regression in B.I.G. Demo screen 2) */ /* When removed, 50 Hz bottom border has 47 extra lines but the last 2 are */ /* masked by vblank (and can displayed by disabling vblank at hbl 308) */ /* 2020/05/08 [NP] Handle screen with no vertical DE activated when switching to 60 Hz */ /* between lines 34 and 62 on a 50 Hz screen (fix fullscreen part in demo */ /* 'Hard As Ice' by ICE) */ /* 2024/09/21 [NP] Add Video_Set_Memcpy() to handle the case where physical RAM size */ /* doesn't match the MMU configuration at $FF8001 (fix intro part in demo */ /* 'Ika I Compofylla' by Newline when running in MegaST mode with 4MB RAM) */ /* 2025/07/17 [NP] Add support for STE 224 bytes overscan in med res (fix some parts of */ /* Double Rez Trouble by DHS) */ const char Video_fileid[] = "Hatari video.c"; #include #include "main.h" #include "configuration.h" #include "cycles.h" #include "fdc.h" #include "cycInt.h" #include "ioMem.h" #include "keymap.h" #include "m68000.h" #include "hatari-glue.h" #include "memorySnapShot.h" #include "mfp.h" #include "printer.h" #include "screen.h" #include "screenConvert.h" #include "screenSnapShot.h" #include "shortcut.h" #include "sound.h" #include "dmaSnd.h" #include "spec512.h" #include "stMemory.h" #include "vdi.h" #include "video.h" #include "ymFormat.h" #include "falcon/videl.h" #include "blitter.h" #include "avi_record.h" #include "ikbd.h" #include "floppy_ipf.h" #include "statusbar.h" #include "clocks_timings.h" #include "utils.h" /* The border's mask allows to keep track of all the border tricks */ /* applied to one video line. The masks for all lines are stored in the array */ /* ScreenBorderMask[]. */ /* - bits 0-19 are used to describe the border tricks. */ /* - bits 20-23 are used to store the bytes offset to apply for some particular */ /* tricks (for example med res overscan can shift display by 0 or 2 bytes */ /* depending on when the switch to med res is done after removing the left */ /* border). */ #define BORDERMASK_NONE 0x00 /* no effect on this line */ #define BORDERMASK_LEFT_OFF 0x01 /* removal of left border with hi/lo res switch -> +26 bytes */ #define BORDERMASK_LEFT_PLUS_2 0x02 /* line starts earlier in 60 Hz -> +2 bytes */ #define BORDERMASK_STOP_MIDDLE 0x04 /* line ends in hires at cycle 160 -> -106 bytes */ #define BORDERMASK_RIGHT_MINUS_2 0x08 /* line ends earlier in 60 Hz -> -2 bytes */ #define BORDERMASK_RIGHT_OFF 0x10 /* removal of right border -> +44 bytes */ #define BORDERMASK_RIGHT_OFF_FULL 0x20 /* full removal of right border and next left border -> +22 bytes */ #define BORDERMASK_OVERSCAN_MED_RES 0x40 /* some borders were removed and the line is in med res instead of low res */ #define BORDERMASK_EMPTY_LINE 0x80 /* 60/50 Hz switch prevents the line to start, video counter is not incremented */ #define BORDERMASK_LEFT_OFF_MED 0x100 /* removal of left border with hi/med res switch -> +26 bytes (for 4 pixels hardware scrolling) */ #define BORDERMASK_LEFT_OFF_2_STE 0x200 /* shorter removal of left border with hi/lo res switch -> +20 bytes (STE only)*/ #define BORDERMASK_BLANK_LINE 0x400 /* 60/50 Hz switch blanks the rest of the line, but video counter is still incremented */ #define BORDERMASK_NO_DE 0x800 #define BORDERMASK_BLANK 0x1000 #define BORDERMASK_NO_COUNT 0x2000 #define BORDERMASK_NO_SYNC 0x4000 #define BORDERMASK_SYNC_HIGH 0x8000 #define BORDERMASK_LEFT_OFF_2_STE_MED (1<<16) /* Same as BORDERMASK_LEFT_OFF_2_STE with line in med res */ //#define STF_SHORT_TOP int STRes = ST_LOW_RES; /* current ST resolution */ int TTRes; /* TT shifter resolution mode */ int nFrameSkips; /* speed up by skipping video frames */ bool bUseHighRes; /* Use hi-res (ie Mono monitor) */ int VerticalOverscan; /* V_OVERSCAN_xxxx for current display frame */ int nScreenRefreshRate = VIDEO_50HZ; /* 50 or 60 Hz in color, 71 Hz in mono */ uint32_t VideoBase; /* Base address in ST Ram for screen (read on each VBL) */ int nVBLs; /* VBL Counter */ int nHBL; /* HBL line */ int nStartHBL; /* Start HBL for visible screen */ int nEndHBL; /* End HBL for visible screen */ int nScanlinesPerFrame = 313; /* Number of scan lines per frame */ int nCyclesPerLine = 512; /* Cycles per horizontal line scan */ static int nFirstVisibleHbl = FIRST_VISIBLE_HBL_50HZ; /* The first line of the ST screen that is copied to the PC screen buffer */ static int nLastVisibleHbl = FIRST_VISIBLE_HBL_50HZ+NUM_VISIBLE_LINES; /* The last line of the ST screen that is copied to the PC screen buffer */ static int CyclesPerVBL = 313*512; /* Number of cycles per VBL */ static uint8_t HWScrollCount; /* HW scroll pixel offset, STE only (0...15) */ static int NewHWScrollCount = -1; /* Used in STE mode when writing to the scrolling registers $ff8264/65 */ static uint8_t HWScrollPrefetch; /* 0 when scrolling with $ff8264, 1 when scrolling with $ff8265 */ static int NewHWScrollPrefetch = -1; /* Used in STE mode when writing to the scrolling registers $ff8264/65 */ static uint8_t LineWidth; /* Scan line width add, STe only (words, minus 1) */ static int NewLineWidth = -1; /* Used in STE mode when writing to the line width register $ff820f */ static int VideoCounterDelayedOffset = 0; /* Used in STE mode when changing video counter while display is on */ static uint8_t *pVideoRasterDelayed = NULL; /* Used in STE mode when changing video counter while display is off in the right border */ static uint8_t *pVideoRaster; /* Pointer to Video raster, after VideoBase in PC address space. Use to copy data on HBL */ static bool bSteBorderFlag; /* true when screen width has been switched to 336 (e.g. in Obsession) */ static int NewSteBorderFlag = -1; /* New value for next line */ static bool bTTColorsSync; /* whether TT colors need conversion to SDL */ static int VideoRasterDelayedInc; /* Number of bytes to add at the end of the current video line */ int TTSpecialVideoMode; /* TT special video mode */ static int nPrevTTSpecialVideoMode; /* TT special video mode */ static int LastCycleScroll8264; /* value of Cycles_GetCounterOnWriteAccess last time ff8264 was set for the current VBL */ static int LastCycleScroll8265; /* value of Cycles_GetCounterOnWriteAccess last time ff8265 was set for the current VBL */ static bool RestartVideoCounter = false; /* true when reaching the HBL to restart video counter */ int LineTimerBPos = LINE_END_CYCLE_50 + TIMERB_VIDEO_CYCLE_OFFSET; /* position of the Timer B interrupt on active lines */ int TimerBEventCountCycleStart = -1; /* value of Cycles_GetCounterOnWriteAccess last time timer B was started for the current VBL */ static int BlankLines = 0; /* Number of empty line with no signal (by switching hi/lo near cycles 500) */ typedef struct { int VBL; /* VBL for this Pos (or -1 if Pos not defined for now) */ int FrameCycles; /* Number of cycles since this VBL */ int HBL; /* HBL in the VBL */ int LineCycles; /* cycles in the HBL */ } SHIFTER_POS; typedef struct { int StartCycle; /* first cycle of this line, as returned by Cycles_GetCounter */ uint32_t BorderMask; /* borders' states for this line */ int DisplayPixelShift; /* number of pixels to shift the whole line (<0 shift to the left, >0 shift to the right) */ /* On STF, this is obtained when switching hi/med for a variable number of cycles, */ /* but just removing left border will shift the line too. */ int DisplayStartCycle; /* cycle where display starts for this line (0-512) : 0, 52 or 56 */ int DisplayEndCycle; /* cycle where display ends for this line (0-512) : 0, 160, 372, 376, 460 or 512 */ int DisplayBytes; /* how many bytes to display for this line */ } SHIFTER_LINE; typedef struct { int HBL_CyclePos; /* cycle position for the HBL int (depends on freq/res) */ int TimerB_CyclePos; /* cycle position for the Timer B int (depends on freq/res) */ int Freq; /* value of ff820a & 2, or -1 if not set */ int Res; /* value of ff8260 & 3 (as seen by GLUE), or -1 if not set */ int Res_Shifter; /* value of ff8260/61 & 3 (as seen by Shifter), or -1 if not set */ SHIFTER_POS FreqPos50; /* position of latest freq change to 50 Hz*/ SHIFTER_POS FreqPos60; /* position of latest freq change to 60 Hz*/ SHIFTER_POS ResPosLo; /* position of latest change to low res in GLUE */ SHIFTER_POS ResPosMed; /* position of latest change to med res in GLUE */ SHIFTER_POS ResPosHi; /* position of latest change to high res in GLUE */ SHIFTER_POS ResPosLo_Shifter; /* position of latest change to low res in Shifter */ SHIFTER_POS ResPosMed_Shifter; /* position of latest change to med res in Shifter */ SHIFTER_POS ResPosHi_Shifter; /* position of latest change to high res in Shifter */ SHIFTER_POS ResPosStop_Shifter; /* position of latest change to stopped state in Shifter */ SHIFTER_POS Scroll8264Pos; /* position of latest write to $ff8264 */ SHIFTER_POS Scroll8265Pos; /* position of latest write to $ff8265 */ uint8_t VBlank_signal; /* 0=vblank off 1=vblank on */ int VBlank_Off_Line; /* First line number where VBlank_signal is OFF (next line after checking freq) */ int VBlank_On_Line; /* First line number where VBlank_signal is ON (next line after checking freq) */ int VBLank_Off_60_CheckFreq;/* Value of FreqHz at VBlank_CheckPos on line VBlank_Off_60_CheckLine */ int VBLank_Off_50_CheckFreq;/* Value of FreqHz at VBlank_CheckPos on line VBlank_Off_50_CheckLine */ int VBLank_On_60_CheckFreq; /* Value of FreqHz at VBlank_CheckPos on line VBlank_On_60_CheckLine */ int VBLank_On_50_CheckFreq; /* Value of FreqHz at VBlank_CheckPos on line VBlank_On_50_CheckLine */ uint8_t VSync_signal; /* 0=vsync off 1=vsync on */ SHIFTER_LINE ShifterLines[ MAX_SCANLINES_PER_FRAME+1 ]; } SHIFTER_FRAME; static SHIFTER_FRAME ShifterFrame; #define VIDEO_TIMING_STF_WS1 0 #define VIDEO_TIMING_STF_WS2 1 #define VIDEO_TIMING_STF_WS3 2 #define VIDEO_TIMING_STF_WS4 3 #define VIDEO_TIMING_STE 4 #define VIDEO_TIMING_TT 5 #define VIDEO_TIMING_MAX_NB 6 /* Number of different timings structs we need to store */ #define VIDEO_TIMING_DEFAULT VIDEO_TIMING_STF_WS3 typedef struct { const char *VideoTimingName; int Preload_Start_Hi; /* 0 (STE) */ int HDE_On_Hi; /* 4 */ int HBlank_Off_Low_60; /* 24 */ int HBlank_Off_Low_50; /* 28 */ int Preload_Start_Low_60; /* 36 (STE) */ int HDE_On_Low_60; /* 52 */ int Line_Set_Pal; /* 54 */ int Preload_Start_Low_50; /* 40 (STE) */ int HDE_On_Low_50; /* 56 */ int HDE_Off_Hi; /* 164 */ int HBlank_On_Hi; /* 184 */ int HDE_Off_Low_60; /* 372 */ int HDE_Off_Low_50; /* 376 */ int HBlank_On_Low; /* 450 */ int HSync_On_Offset_Low; /* 462 or 458 */ int HSync_Off_Offset_Low; /* 502 or 498 */ int RemoveTopBorder_Pos; /* 502 */ int RemoveBottomBorder_Pos; /* 502 */ int VDE_On_Line_50; /* 63 or 63-16 */ int VDE_On_Line_60; /* 34 */ int VDE_On_Line_Hi; /* 34 */ int VDE_Off_Line_50; /* 263 or 263-16 */ int VDE_Off_Line_60; /* 234 */ int VDE_Off_Line_Hi; /* 434 */ int VDE_Off_Line_NoBottom_50; /* 310 */ int VDE_Off_Line_NoBottom_60; /* 263 */ int VBlank_On_50_CheckLine; /* 307 */ int VBlank_On_60_CheckLine; /* 257 */ int VBlank_On_Hi_CheckLine; /* 501 (no blank in mono mode ?) */ int VBlank_Off_50_CheckLine; /* 24 */ int VBlank_Off_60_CheckLine; /* 15 */ int VBlank_Off_Hi_CheckLine; /* 0 (no blank in mono mode ?) */ int VBlank_CheckPos; /* 502 same for ON and OFF test */ int VSync_On_Line_50; /* 310 */ int VSync_On_Line_60; /* 260 */ int VSync_On_Line_Hi; /* 501 (not tested on real HW) */ int RestartVideoCounter_Line_60; /* 260 */ int RestartVideoCounter_Line_50; /* 310 */ int RestartVideoCounter_Pos; /* 56 */ int VblVideoCycleOffset; int Hbl_Int_Pos_Low_60; /* 508 */ int Hbl_Int_Pos_Low_50; /* 512 */ int Hbl_Int_Pos_Hi; /* 224 */ } VIDEO_TIMING; static VIDEO_TIMING VideoTimings[ VIDEO_TIMING_MAX_NB ]; static VIDEO_TIMING *pVideoTiming; static int VideoTiming; static uint64_t VBL_ClockCounter; /* Convert a horizontal video position measured at 8 MHz on STF/STE */ /* to the equivalent number of cycles when CPU runs at 8/16/32 MHz */ #define VIDEO_HPOS_TO_CYCLE( pos ) ( pos << nCpuFreqShift ) #define VIDEO_CYCLE_TO_HPOS( cyc ) ( cyc >> nCpuFreqShift ) /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ int Video_GetPosition_ForceInc = 0; /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ /*--------------------------------------------------------------*/ /* Local functions prototypes */ /*--------------------------------------------------------------*/ static void Video_InitTimings_Copy ( VIDEO_TIMING *pSrc , VIDEO_TIMING *pDest , int inc ); static uint32_t Video_CalculateAddress ( void ); static int Video_GetMMUStartCycle ( int DisplayStartCycle ); static void Video_WriteToGlueRes ( uint8_t Res ); static void Video_WriteToShifterRes ( uint8_t Res ); static void Video_Update_Glue_State ( int FrameCycles , int HblCounterVideo , int LineCycles , bool WriteToRes ); static int Video_HBL_GetDefaultPos ( void ); static int Video_HBL_GetCurrentPos ( void ); static int Video_TimerB_GetPosFromDE ( int DE_start , int DE_end ); static int Video_TimerB_GetDefaultPos ( void ); static void Video_EndHBL ( void ); static void Video_StartHBL ( void ); static void Video_StoreFirstLinePalette(void); static void Video_StoreResolution(int y , bool start); static void Video_CopyScreenLineMono(void); static void Video_CopyScreenLineColor(void); static void Video_SetHBLPaletteMaskPointers(void); static void Video_DrawScreen(void); static void Video_ResetShifterTimings(void); static void Video_InitShifterLines(void); static void Video_RestartVideoCounter(void); static void Video_ClearOnVBL(void); static void Video_AddInterrupt ( int Line , int PosCycles , interrupt_id Handler ); static void Video_AddInterruptHBL ( int Line , int Pos ); static void Video_Res_WriteByte(void); static void Video_Res_ReadByte(void); static void Video_ColorReg_WriteWord(void); static void Video_ColorReg_ReadWord(void); static void Video_TT_RasterHBL(void); /* * Special memcpy functions used by Video_CopyScreenLineColor and Video_CopyScreenLineMono * On STF/STE when the physical RAM size doesn't match the configuration in the MMU at $FF8001, * we must use some more complex memcpy function to get the correct address translation * See Video_Set_Memcpy() to set the correct memcpy function */ static void* video_memcpy_mmu ( uint8_t *dest , uint8_t *src , int n ); static void* video_memcpy_direct ( uint8_t *dest , uint8_t *src , int n ); static void* (*video_memcpy)( uint8_t *dest , uint8_t *src , int n ) = video_memcpy_direct; /** * Calculate the mask for the video address */ static uint32_t Video_GetAddrMask(void) { return (DMA_MaskAddressHigh() << 16) | 0xffff; } /** * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type) */ void Video_MemorySnapShot_Capture(bool bSave) { uint32_t addr; /* Save/Restore details */ MemorySnapShot_Store(&TTRes, sizeof(TTRes)); MemorySnapShot_Store(&bUseHighRes, sizeof(bUseHighRes)); MemorySnapShot_Store(&nScreenRefreshRate, sizeof(nScreenRefreshRate)); MemorySnapShot_Store(&nVBLs, sizeof(nVBLs)); MemorySnapShot_Store(&nHBL, sizeof(nHBL)); MemorySnapShot_Store(&nStartHBL, sizeof(nStartHBL)); MemorySnapShot_Store(&nEndHBL, sizeof(nEndHBL)); MemorySnapShot_Store(&VerticalOverscan, sizeof(VerticalOverscan)); MemorySnapShot_Store(HBLPalettes, sizeof(HBLPalettes)); MemorySnapShot_Store(HBLPaletteMasks, sizeof(HBLPaletteMasks)); MemorySnapShot_Store(&VideoBase, sizeof(VideoBase)); if ( bSave ) { addr = pVideoRaster - STRam; MemorySnapShot_Store(&addr, sizeof(addr)); } else { MemorySnapShot_Store(&addr, sizeof(addr)); pVideoRaster = &STRam[VideoBase & Video_GetAddrMask()]; } MemorySnapShot_Store(&LineWidth, sizeof(LineWidth)); MemorySnapShot_Store(&HWScrollCount, sizeof(HWScrollCount)); MemorySnapShot_Store(&nScanlinesPerFrame, sizeof(nScanlinesPerFrame)); MemorySnapShot_Store(&nCyclesPerLine, sizeof(nCyclesPerLine)); MemorySnapShot_Store(&nFirstVisibleHbl, sizeof(nFirstVisibleHbl)); MemorySnapShot_Store(&nLastVisibleHbl, sizeof(nLastVisibleHbl)); MemorySnapShot_Store(&bSteBorderFlag, sizeof(bSteBorderFlag)); MemorySnapShot_Store(&ShifterFrame, sizeof(ShifterFrame)); MemorySnapShot_Store(&TTSpecialVideoMode, sizeof(TTSpecialVideoMode)); MemorySnapShot_Store(&VBL_ClockCounter, sizeof(VBL_ClockCounter)); } /*-----------------------------------------------------------------------*/ /** * Reset video chip */ void Video_Reset(void) { /* NOTE! Must reset all of these register type things here!!!! */ Video_Reset_Glue(); /* Set system specific timings */ Video_SetTimings ( ConfigureParams.System.nMachineType , ConfigureParams.System.VideoTimingMode ); /* Reset VBL counter */ nVBLs = 0; /* Reset addresses */ VideoBase = 0L; /* Reset shifter's state variables */ ShifterFrame.Freq = -1; ShifterFrame.Res = -1; ShifterFrame.Res_Shifter = -1; ShifterFrame.FreqPos50.VBL = -1; ShifterFrame.FreqPos60.VBL = -1; ShifterFrame.ResPosLo.VBL = -1; ShifterFrame.ResPosMed.VBL = -1; ShifterFrame.ResPosHi.VBL = -1; ShifterFrame.ResPosLo_Shifter.VBL = -1; ShifterFrame.ResPosMed_Shifter.VBL = -1; ShifterFrame.ResPosHi_Shifter.VBL = -1; ShifterFrame.ResPosStop_Shifter.VBL = -1; ShifterFrame.Scroll8264Pos.VBL = -1; ShifterFrame.Scroll8265Pos.VBL = -1; ShifterFrame.VBlank_signal = VBLANK_SIGNAL_OFF; ShifterFrame.VSync_signal = VSYNC_SIGNAL_OFF; Video_InitShifterLines (); /* Reset STE screen variables */ LineWidth = 0; HWScrollCount = 0; bSteBorderFlag = false; NewLineWidth = -1; /* cancel pending modifications set before the reset */ NewHWScrollCount = -1; VideoCounterDelayedOffset = 0; pVideoRasterDelayed = NULL; TTSpecialVideoMode = nPrevTTSpecialVideoMode = 0; /* Clear framecycles counter at the time of the reset */ /* We must take into account if CyclesGlobalClockCounter is 4*n or 4*n+2 */ /* If not, further accesses to video registers will be made 2 cycles too late */ /* and spec512 like images or overscan will be broken */ if ( ( CyclesGlobalClockCounter & 3 ) == 2 ) { //fprintf ( stderr , "video reset desync %x %lx\n" , nCyclesMainCounter , CyclesGlobalClockCounter ); Cycles_SetCounter(CYCLES_COUNTER_VIDEO, 2); } else { Cycles_SetCounter(CYCLES_COUNTER_VIDEO, 0); } VBL_ClockCounter = CyclesGlobalClockCounter; /* shortcut keys are handled from Video_InterruptHandler_VBL, which can call * Video_Reset() from Video_InterruptHandler_VBL although the next VBL internal interrupt * is already set. This can create a 2 cycles desync that remains over all VBL, * which can break spec512 like images or overscan. * We compensate this desync if needed * TODO : shortcut keys should be handled in the main cpu loop, not directly in * Video_InterruptHandler_VBL. This should allow to remove this "resync" */ if ( ( CyclesGlobalClockCounter & 3 ) == 2 ) { // fprintf ( stderr , "video reset cycle counter desync %lu\n" , CyclesGlobalClockCounter ); VBL_ClockCounter -= 2; } else { // fprintf ( stderr , "video reset cycle counter in sync %lu\n" , CyclesGlobalClockCounter ); } /* Clear ready for new VBL */ Video_ClearOnVBL(); } /*-----------------------------------------------------------------------*/ /** * Reset the GLUE chip responsible for generating the H/V sync signals. * When the 68000 RESET instruction is called, frequency and resolution * should be reset to 0. */ void Video_Reset_Glue(void) { uint8_t VideoShifterByte; IoMem_WriteByte(0xff820a,0); /* Video frequency */ /* Are we in high-res? */ if (bUseHighRes) VideoShifterByte = ST_HIGH_RES; /* Mono monitor */ else VideoShifterByte = ST_LOW_RES; if (bUseVDIRes) VideoShifterByte = VDIRes; IoMem_WriteByte(0xff8260, VideoShifterByte); } /*-----------------------------------------------------------------------*/ /* * Init video timings values for all emulated machines. * This should be called only once, when emulator starts */ void Video_InitTimings(void) { VIDEO_TIMING *pVideoTiming1; VIDEO_TIMING *pVideoTiming2; /* Init all timings for STF machine */ pVideoTiming1 = &VideoTimings[ VIDEO_TIMING_STF_WS1 ]; /* Init all timings for WS1 mode */ pVideoTiming1->VideoTimingName = "WS1"; pVideoTiming1->HDE_On_Hi = 4; pVideoTiming1->HBlank_Off_Low_60 = 24; pVideoTiming1->HBlank_Off_Low_50 = 28; pVideoTiming1->HDE_On_Low_60 = 52; pVideoTiming1->Line_Set_Pal = 54; pVideoTiming1->HDE_On_Low_50 = 56; pVideoTiming1->HDE_Off_Hi = 164; pVideoTiming1->HBlank_On_Hi = 184; pVideoTiming1->HDE_Off_Low_60 = 372; pVideoTiming1->HDE_Off_Low_50 = 376; pVideoTiming1->HBlank_On_Low = 450; pVideoTiming1->HSync_On_Offset_Low = -50; /* 458/462 (line cycles-50) */ pVideoTiming1->HSync_Off_Offset_Low = -10; /* 498/502 (line cycles-10) */ pVideoTiming1->RemoveTopBorder_Pos = 502; pVideoTiming1->RemoveBottomBorder_Pos = 502; pVideoTiming1->VDE_On_Line_50 = VIDEO_START_HBL_50HZ; /* 63 or 63-16 */ pVideoTiming1->VDE_On_Line_60 = VIDEO_START_HBL_60HZ; /* 34 */ pVideoTiming1->VDE_On_Line_Hi = VIDEO_START_HBL_71HZ; /* 34 */ pVideoTiming1->VDE_Off_Line_50 = VIDEO_END_HBL_50HZ; /* 263 or 263-16 */ pVideoTiming1->VDE_Off_Line_60 = VIDEO_END_HBL_60HZ; /* 234 */ pVideoTiming1->VDE_Off_Line_Hi = VIDEO_END_HBL_71HZ; /* 434 */ pVideoTiming1->VDE_Off_Line_NoBottom_50 = pVideoTiming1->VDE_Off_Line_50 + VIDEO_HEIGHT_BOTTOM_50HZ; /* 310 (TODO:check on real HW) */ pVideoTiming1->VDE_Off_Line_NoBottom_60 = pVideoTiming1->VDE_Off_Line_60 + VIDEO_HEIGHT_BOTTOM_60HZ; /* 263 (TODO:check on real HW) */ pVideoTiming1->VBlank_On_50_CheckLine = 307; pVideoTiming1->VBlank_On_60_CheckLine = 257; pVideoTiming1->VBlank_On_Hi_CheckLine = 501; pVideoTiming1->VBlank_Off_50_CheckLine = 24; pVideoTiming1->VBlank_Off_60_CheckLine = 15; pVideoTiming1->VBlank_Off_Hi_CheckLine = 0; pVideoTiming1->VBlank_CheckPos = 502; pVideoTiming1->VSync_On_Line_50 = 310; pVideoTiming1->VSync_On_Line_60 = 260; pVideoTiming1->VSync_On_Line_Hi = 501; pVideoTiming1->RestartVideoCounter_Line_60 = RESTART_VIDEO_COUNTER_LINE_60HZ; /* 260 */ pVideoTiming1->RestartVideoCounter_Line_50 = RESTART_VIDEO_COUNTER_LINE_50HZ; /* 310 */ pVideoTiming1->RestartVideoCounter_Pos = RESTART_VIDEO_COUNTER_CYCLE_STF; /* 56 */ pVideoTiming1->VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STF - 4; pVideoTiming1->Hbl_Int_Pos_Low_60 = CYCLES_PER_LINE_60HZ - 4; pVideoTiming1->Hbl_Int_Pos_Low_50 = CYCLES_PER_LINE_50HZ - 4; pVideoTiming1->Hbl_Int_Pos_Hi = CYCLES_PER_LINE_71HZ - 4; #ifdef STF_SHORT_TOP pVideoTiming1->VDE_On_Line_50 -= 16; pVideoTiming1->VDE_Off_Line_50 -= 16; #endif /* WS2/WS3/WS4 timings are derived from WS1 with an increment */ pVideoTiming2 = &VideoTimings[ VIDEO_TIMING_STF_WS2 ]; pVideoTiming2->VideoTimingName = "WS2"; Video_InitTimings_Copy ( pVideoTiming1 , pVideoTiming2 , 3 ); pVideoTiming2->VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STF; pVideoTiming2->Hbl_Int_Pos_Low_60 = CYCLES_PER_LINE_60HZ; pVideoTiming2->Hbl_Int_Pos_Low_50 = CYCLES_PER_LINE_50HZ; pVideoTiming2->Hbl_Int_Pos_Hi = CYCLES_PER_LINE_71HZ; pVideoTiming2 = &VideoTimings[ VIDEO_TIMING_STF_WS3 ]; pVideoTiming2->VideoTimingName = "WS3"; Video_InitTimings_Copy ( pVideoTiming1 , pVideoTiming2 , 1 ); pVideoTiming2->VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STF; pVideoTiming2->Hbl_Int_Pos_Low_60 = CYCLES_PER_LINE_60HZ; pVideoTiming2->Hbl_Int_Pos_Low_50 = CYCLES_PER_LINE_50HZ; pVideoTiming2->Hbl_Int_Pos_Hi = CYCLES_PER_LINE_71HZ; pVideoTiming2 = &VideoTimings[ VIDEO_TIMING_STF_WS4 ]; pVideoTiming2->VideoTimingName = "WS4"; Video_InitTimings_Copy ( pVideoTiming1 , pVideoTiming2 , 2 ); pVideoTiming2->VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STF; pVideoTiming2->Hbl_Int_Pos_Low_60 = CYCLES_PER_LINE_60HZ; pVideoTiming2->Hbl_Int_Pos_Low_50 = CYCLES_PER_LINE_50HZ; pVideoTiming2->Hbl_Int_Pos_Hi = CYCLES_PER_LINE_71HZ; /* Init all timings for STE machine */ pVideoTiming1 = &VideoTimings[ VIDEO_TIMING_STE ]; pVideoTiming1->VideoTimingName = "STE"; pVideoTiming1->Preload_Start_Hi = 0; pVideoTiming1->HDE_On_Hi = 4; pVideoTiming1->HBlank_Off_Low_60 = 24; pVideoTiming1->HBlank_Off_Low_50 = 28; pVideoTiming1->Preload_Start_Low_60 = 36; pVideoTiming1->HDE_On_Low_60 = 52; pVideoTiming1->Line_Set_Pal = 56; pVideoTiming1->Preload_Start_Low_50 = 40; pVideoTiming1->HDE_On_Low_50 = 56; pVideoTiming1->HDE_Off_Hi = 164; pVideoTiming1->HBlank_On_Hi = 184; pVideoTiming1->HDE_Off_Low_60 = 372; pVideoTiming1->HDE_Off_Low_50 = 376; pVideoTiming1->HBlank_On_Low = 448; pVideoTiming1->HSync_On_Offset_Low = -52; /* 456/460 (line cycles-52) */ pVideoTiming1->HSync_Off_Offset_Low = -12; /* 496/500 (line cycles-12) */ pVideoTiming1->RemoveTopBorder_Pos = 500; pVideoTiming1->RemoveBottomBorder_Pos = 500; pVideoTiming1->VDE_On_Line_50 = VIDEO_START_HBL_50HZ; /* 63 */ pVideoTiming1->VDE_On_Line_60 = VIDEO_START_HBL_60HZ; /* 34 */ pVideoTiming1->VDE_On_Line_Hi = VIDEO_START_HBL_71HZ; /* 34 */ pVideoTiming1->VDE_Off_Line_50 = VIDEO_END_HBL_50HZ; /* 263 */ pVideoTiming1->VDE_Off_Line_60 = VIDEO_END_HBL_60HZ; /* 234 */ pVideoTiming1->VDE_Off_Line_Hi = VIDEO_END_HBL_71HZ; /* 434 */ pVideoTiming1->VDE_Off_Line_NoBottom_50 = pVideoTiming1->VDE_Off_Line_50 + VIDEO_HEIGHT_BOTTOM_50HZ; /* 310 */ pVideoTiming1->VDE_Off_Line_NoBottom_60 = pVideoTiming1->VDE_Off_Line_60 + VIDEO_HEIGHT_BOTTOM_60HZ; /* 263 */ pVideoTiming1->VBlank_On_50_CheckLine = 307; pVideoTiming1->VBlank_On_60_CheckLine = 257; pVideoTiming1->VBlank_On_Hi_CheckLine = 501; pVideoTiming1->VBlank_Off_50_CheckLine = 24; pVideoTiming1->VBlank_Off_60_CheckLine = 15; pVideoTiming1->VBlank_Off_Hi_CheckLine = 0; pVideoTiming1->VBlank_CheckPos = 502; pVideoTiming1->VSync_On_Line_50 = 310; pVideoTiming1->VSync_On_Line_60 = 260; pVideoTiming1->VSync_On_Line_Hi = 501; pVideoTiming1->RestartVideoCounter_Line_60 = RESTART_VIDEO_COUNTER_LINE_60HZ; /* 260 */ pVideoTiming1->RestartVideoCounter_Line_50 = RESTART_VIDEO_COUNTER_LINE_50HZ; /* 310 */ pVideoTiming1->RestartVideoCounter_Pos = RESTART_VIDEO_COUNTER_CYCLE_STE; /* 60 */ pVideoTiming1->VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STE; pVideoTiming1->Hbl_Int_Pos_Low_60 = CYCLES_PER_LINE_60HZ; pVideoTiming1->Hbl_Int_Pos_Low_50 = CYCLES_PER_LINE_50HZ; pVideoTiming1->Hbl_Int_Pos_Hi = CYCLES_PER_LINE_71HZ; /* Use the STE timings for TT */ pVideoTiming2 = &VideoTimings[ VIDEO_TIMING_TT ]; pVideoTiming2->VideoTimingName = "TT"; Video_InitTimings_Copy ( pVideoTiming1 , pVideoTiming2 , 0 ); /* Set timings to a default mode (this will be overridden later when choosing the emulated machine) */ pVideoTiming = &VideoTimings[ VIDEO_TIMING_DEFAULT ]; } /*-----------------------------------------------------------------------*/ /* * Copy some video timings, adding 'inc' to the values that depend on * wakeup state. */ static void Video_InitTimings_Copy ( VIDEO_TIMING *pSrc , VIDEO_TIMING *pDest , int inc ) { pDest->Preload_Start_Hi = pSrc->Preload_Start_Hi + inc; pDest->HDE_On_Hi = pSrc->HDE_On_Hi + inc; pDest->HBlank_Off_Low_60 = pSrc->HBlank_Off_Low_60 + inc; pDest->HBlank_Off_Low_50 = pSrc->HBlank_Off_Low_50 + inc; pDest->Preload_Start_Low_60 = pSrc->Preload_Start_Low_60 + inc; pDest->HDE_On_Low_60 = pSrc->HDE_On_Low_60 + inc; pDest->Line_Set_Pal = pSrc->Line_Set_Pal + inc; pDest->Preload_Start_Low_50 = pSrc->Preload_Start_Low_50 + inc; pDest->HDE_On_Low_50 = pSrc->HDE_On_Low_50 + inc; pDest->HDE_Off_Hi = pSrc->HDE_Off_Hi + inc; pDest->HBlank_On_Hi = pSrc->HBlank_On_Hi + inc; pDest->HDE_Off_Low_60 = pSrc->HDE_Off_Low_60 + inc; pDest->HDE_Off_Low_50 = pSrc->HDE_Off_Low_50 + inc; pDest->HBlank_On_Low = pSrc->HBlank_On_Low + inc; pDest->HSync_On_Offset_Low = pSrc->HSync_On_Offset_Low + inc; pDest->HSync_Off_Offset_Low = pSrc->HSync_Off_Offset_Low + inc; pDest->RemoveTopBorder_Pos = pSrc->RemoveTopBorder_Pos + inc; pDest->RemoveBottomBorder_Pos = pSrc->RemoveBottomBorder_Pos + inc; pDest->VDE_On_Line_50 = pSrc->VDE_On_Line_50; pDest->VDE_On_Line_60 = pSrc->VDE_On_Line_60; pDest->VDE_On_Line_Hi = pSrc->VDE_On_Line_Hi; pDest->VDE_Off_Line_50 = pSrc->VDE_Off_Line_50; pDest->VDE_Off_Line_60 = pSrc->VDE_Off_Line_60; pDest->VDE_Off_Line_Hi = pSrc->VDE_Off_Line_Hi; pDest->VDE_Off_Line_NoBottom_50 = pSrc->VDE_Off_Line_NoBottom_50; pDest->VDE_Off_Line_NoBottom_60 = pSrc->VDE_Off_Line_NoBottom_60; pDest->VBlank_On_50_CheckLine = pSrc->VBlank_On_50_CheckLine; pDest->VBlank_On_60_CheckLine = pSrc->VBlank_On_60_CheckLine; pDest->VBlank_On_Hi_CheckLine = pSrc->VBlank_On_Hi_CheckLine; pDest->VBlank_Off_50_CheckLine = pSrc->VBlank_Off_50_CheckLine; pDest->VBlank_Off_60_CheckLine = pSrc->VBlank_Off_60_CheckLine; pDest->VBlank_Off_Hi_CheckLine = pSrc->VBlank_Off_Hi_CheckLine; pDest->VBlank_CheckPos = pSrc->VBlank_CheckPos; pDest->VSync_On_Line_50 = pSrc->VSync_On_Line_50; pDest->VSync_On_Line_60 = pSrc->VSync_On_Line_60; pDest->VSync_On_Line_Hi = pSrc->VSync_On_Line_Hi; pDest->RestartVideoCounter_Line_60 = pSrc->RestartVideoCounter_Line_60; pDest->RestartVideoCounter_Line_50 = pSrc->RestartVideoCounter_Line_50; pDest->RestartVideoCounter_Pos = pSrc->RestartVideoCounter_Pos; pDest->VblVideoCycleOffset = pSrc->VblVideoCycleOffset; pDest->Hbl_Int_Pos_Low_60 = pSrc->Hbl_Int_Pos_Low_60; pDest->Hbl_Int_Pos_Low_50 = pSrc->Hbl_Int_Pos_Low_50; pDest->Hbl_Int_Pos_Hi = pSrc->Hbl_Int_Pos_Hi; } /*-----------------------------------------------------------------------*/ /* * Set specific video timings, depending on the system being emulated. * - STF is known to have 4 possible states depending on the MMU/GLUE synchronisation * - STE has MMU and GLUE merged in the same chip, so there's only 1 state * (other video differences are sometimes visible on STF/STE, so there might be * more states, affecting the shifter for example) */ void Video_SetTimings( MACHINETYPE MachineType , VIDEOTIMINGMODE Mode ) { /* Default timing for TT/Falcon (not really important */ /* as TT/Falcon don't use cycle precise video effects) */ VideoTiming = VIDEO_TIMING_DEFAULT; if ( ( MachineType == MACHINE_STE ) || ( MachineType == MACHINE_MEGA_STE ) ) VideoTiming = VIDEO_TIMING_STE; /* Only one choice for STE */ else if ( MachineType == MACHINE_TT ) VideoTiming = VIDEO_TIMING_TT; /* Only one choice for TT (same values as STE) */ else if ( ( MachineType == MACHINE_ST ) || ( MachineType == MACHINE_MEGA_ST ) ) /* 4 wakeup states are possible for STF */ { if ( Mode == VIDEO_TIMING_MODE_RANDOM ) Mode = VIDEO_TIMING_MODE_WS1 + Hatari_rand() % 4; /* random between the 4 modes WS1, WS2, WS3, WS4 */ if ( Mode == VIDEO_TIMING_MODE_WS1 ) VideoTiming = VIDEO_TIMING_STF_WS1; else if ( Mode == VIDEO_TIMING_MODE_WS2 ) VideoTiming = VIDEO_TIMING_STF_WS2; else if ( Mode == VIDEO_TIMING_MODE_WS3 ) VideoTiming = VIDEO_TIMING_STF_WS3; else VideoTiming = VIDEO_TIMING_STF_WS4; } pVideoTiming = &VideoTimings[ VideoTiming ]; Log_Printf(LOG_DEBUG, "Video_SetSystemTimings %d %d -> %d (%s) %d %d %d\n", MachineType, Mode, VideoTiming, pVideoTiming->VideoTimingName, pVideoTiming->RemoveTopBorder_Pos, pVideoTiming->RemoveBottomBorder_Pos, pVideoTiming->VblVideoCycleOffset); } const char *Video_GetTimings_Name ( void ) { return pVideoTiming->VideoTimingName; } /*-----------------------------------------------------------------------*/ /** * Return the value of the MONO output signal depending on the monitor : * 1=monochrome monitor 0=color monitor */ uint8_t Video_Get_MONO_Line ( void ) { if ( bUseHighRes ) return 1; else return 0; } /*-----------------------------------------------------------------------*/ /** * Convert the elapsed number of cycles since the start of the VBL * into the corresponding HBL number and the cycle position in the current * HBL. We use the starting cycle position of the closest HBL to compute * the cycle position on the line (this allows to mix lines with different * values for nCyclesPerLine). * We can have 2 cases on the limit where the real video line count can be * different from nHBL : * - when reading video address between cycle 0 and 12, LineCycle will be <0, * so we need to use the data from line nHBL-1 * - if LineCycle >= nCyclesPerLine, this means the HBL int was not processed * yet, so the video line number is in fact nHBL+1 */ void Video_ConvertPosition ( int FrameCycles , int *pHBL , int *pLineCycles ) { if ( ( nHBL == nScanlinesPerFrame ) /* rare case between end of last hbl and start of next VBL (during 64 cycles) */ && ( Config_IsMachineST() || Config_IsMachineSTE() ) ) /* for example, cycle 160336 will give HBL=0 and LineCycles=80 */ { *pHBL = 0; *pLineCycles = FrameCycles - ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle - nCyclesPerLine; if ( *pLineCycles < 0 ) /* reading before end of last hbl (possible in WS1) */ { *pHBL = nHBL-1; *pLineCycles = FrameCycles - ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle; } //fprintf ( stderr , "out of vbl FrameCycles %d CyclesPerVBL %d nHBL=%d %d %d\n" , FrameCycles , CyclesPerVBL, nHBL , *pHBL , *pLineCycles ); } else /* most common case */ { *pHBL = nHBL; *pLineCycles = FrameCycles - ShifterFrame.ShifterLines[ nHBL ].StartCycle; // fprintf ( stderr , "conv pos nHBL=%d %d %d %d\n" , nHBL , FrameCycles , *pLineCycles , ShifterFrame.ShifterLines[ nHBL ].StartCycle ); if ( *pLineCycles < 0 ) /* reading from the previous video line */ { *pHBL = nHBL-1; *pLineCycles = FrameCycles - ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle; } else if ( *pLineCycles >= nCyclesPerLine ) /* reading on the next line, but HBL int was delayed */ { *pHBL = nHBL+1; *pLineCycles -= nCyclesPerLine; } } if ( *pLineCycles < 0 ) fprintf ( stderr , "bug nHBL=%d %d %d %d\n" , nHBL , FrameCycles , *pHBL , *pLineCycles ); //if ( ( *pHBL != FrameCycles / nCyclesPerLine ) || ( *pLineCycles != FrameCycles % nCyclesPerLine ) ) // LOG_TRACE ( TRACE_VIDEO_ADDR , "conv pos %d %d - %d %d\n" , *pHBL , FrameCycles / nCyclesPerLine , *pLineCycles , FrameCycles % nCyclesPerLine ); // LOG_TRACE ( TRACE_VIDEO_ADDR , "conv pos %d %d %d\n" , FrameCycles , *pHBL , *pLineCycles ); } /*-----------------------------------------------------------------------*/ /** * Video_GetCyclesSinceVbl() is used to convert the global cycles counter * into another cycle counter relative to the current VBL as a "frame cycles" counter. * This frame cycles counter can then be used by Video_ConvertPosition() * to get a position inside the VBL, consisting of an HBL number and a number * of elapsed cycles into this HBL. * * For example, for a standard 50 Hz low resolution on STF/STE, a frame contains * 160256 cycles, giving 313 HBL and 512 cycles per HBL * * As a convention we consider that when global cycles counters is 0 (or after a reset), * then VBL 0 will also start at cycle 0. * All following VBL will considered to start at cycle VblVideoCycleOffset */ #undef OLD_GET_POS //#define OLD_GET_POS #define OLD_GET_POS_FORCE_INC int Video_GetCyclesSinceVbl ( void ) { int cycles_since_vbl; int cycles_old; cycles_old = Cycles_GetCounter(CYCLES_COUNTER_VIDEO); cycles_since_vbl = CyclesGlobalClockCounter - VBL_ClockCounter; if ( cycles_since_vbl != cycles_old ) fprintf ( stderr , "cyc since vbl old %d != new %d\n" , cycles_old , cycles_since_vbl ); return cycles_since_vbl; } int Video_GetCyclesSinceVbl_OnWriteAccess ( void ) { int cycles_since_vbl; int cycles_old; cycles_old = Cycles_GetCounterOnWriteAccess(CYCLES_COUNTER_VIDEO); cycles_since_vbl = Video_GetCyclesSinceVbl() + Cycles_GetInternalCycleOnWriteAccess(); if ( cycles_since_vbl != cycles_old ) fprintf ( stderr , "cyc since vbl write old %d != new %d\n" , cycles_old , cycles_since_vbl ); return cycles_since_vbl; } int Video_GetCyclesSinceVbl_OnReadAccess ( void ) { int cycles_since_vbl; int cycles_old; cycles_old = Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO); cycles_since_vbl = Video_GetCyclesSinceVbl() + Cycles_GetInternalCycleOnReadAccess(); if ( cycles_since_vbl != cycles_old ) fprintf ( stderr , "cyc since vbl read old %d != new %d\n" , cycles_old , cycles_since_vbl ); return cycles_since_vbl; } void Video_GetPosition ( int *pFrameCycles , int *pHBL , int *pLineCycles ) { #ifdef OLD_GET_POS *pFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO); #else *pFrameCycles = Video_GetCyclesSinceVbl(); #endif #ifdef OLD_GET_POS_FORCE_INC *pFrameCycles += Video_GetPosition_ForceInc; /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ #endif Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles ); } /* Same as Video_GetPosition combined with Video_GetPosition_ForceInc, except Video_GetPosition_CE */ /* is only used from Video_AddInterrupt(). */ /* TODO This will be merged later with different code when we use CyclesGlobalClockCounter instead of CYCLES_COUNTER_VIDEO */ static void Video_GetPosition_CE ( int *pFrameCycles , int *pHBL , int *pLineCycles ) { if ( !CpuRunCycleExact ) { Video_GetPosition ( pFrameCycles , pHBL , pLineCycles ); return; } #ifdef OLD_GET_POS *pFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO); *pFrameCycles += currcycle / 256; /* TEMP : to update CYCLES_COUNTER_VIDEO with new cycInt code */ #else *pFrameCycles = Video_GetCyclesSinceVbl(); *pFrameCycles += currcycle / 256; /* TEMP : to update CYCLES_COUNTER_VIDEO with new cycInt code */ #endif Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles ); } void Video_GetPosition_OnWriteAccess ( int *pFrameCycles , int *pHBL , int *pLineCycles ) { #ifdef OLD_GET_POS *pFrameCycles = Cycles_GetCounterOnWriteAccess(CYCLES_COUNTER_VIDEO); #else *pFrameCycles = Video_GetCyclesSinceVbl_OnWriteAccess(); #endif Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles ); } void Video_GetPosition_OnReadAccess ( int *pFrameCycles , int *pHBL , int *pLineCycles ) { #ifdef OLD_GET_POS *pFrameCycles = Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO); #else *pFrameCycles = Video_GetCyclesSinceVbl_OnReadAccess(); #endif Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles ); } /*-----------------------------------------------------------------------*/ /** * Calculate and return video address pointer. */ static uint32_t Video_CalculateAddress ( void ) { int FrameCycles, HblCounterVideo, LineCycles; int X, NbBytes; uint32_t VideoAddress; /* Address of video display in ST screen space */ int nSyncByte; int Res; int LineBorderMask; int PrevSize; int CurSize; int LineStartCycle , LineEndCycle; /* Find number of cycles passed during frame */ /* We need to subtract '8' for correct video address calculation */ #ifdef OLD_GET_POS FrameCycles = Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) - 8; #else FrameCycles = Video_GetCyclesSinceVbl_OnReadAccess() - 8; #endif /* Now find which pixel we are on (ignore left/right borders) */ Video_ConvertPosition ( FrameCycles , &HblCounterVideo , &LineCycles ); Res = IoMem_ReadByte ( 0xff8260 ) & 3; /* [FIXME] 'Delirious Demo IV' protection : reads FF8209 between a high/low switch */ /* on a low res screen. So far, Hatari doesn't handle mixed resolutions */ /* on the same line, so we ignore the hi switch in that case */ if ( ( M68000_InstrPC == 0x2110 ) && ( STMemory_ReadLong ( M68000_InstrPC ) == 0x14101280 ) ) /* move.b (a0),d2 + move.b d0,(a1) */ Res = 0; /* force to low res to pass the protection */ if ( Res & 2 ) /* hi res */ { LineStartCycle = LINE_START_CYCLE_71; LineEndCycle = LINE_END_CYCLE_71; HblCounterVideo = FrameCycles / nCyclesPerLine; LineCycles = FrameCycles % nCyclesPerLine; } else { nSyncByte = IoMem_ReadByte(0xff820a) & 2; /* only keep bit 1 */ if (nSyncByte) /* 50 Hz */ { LineStartCycle = LINE_START_CYCLE_50; LineEndCycle = LINE_END_CYCLE_50; } else /* 60 Hz */ { LineStartCycle = LINE_START_CYCLE_60; LineEndCycle = LINE_END_CYCLE_60; } } X = LineCycles; /* Top of screen is usually 63 lines from VBL in 50 Hz */ if ( HblCounterVideo < nStartHBL ) { /* pVideoRaster was set during Video_ClearOnVBL using VideoBase */ /* and it could also have been modified on STE by writing to ff8205/07/09 */ /* So, we should not use ff8201/ff8203 which are reloaded in ff8205/ff8207 only once per VBL */ /* but use pVideoRaster - STRam instead to get current shifter video address */ VideoAddress = pVideoRaster - STRam; } /* Special case when reading video counter in hi-res (used in the demo 'My Socks Are Weapons' by Legacy) */ /* This assumes a standard 640x400 resolution with no border removed, so code is simpler */ /* [NP] TODO : this should be handled in a more generic way with low/med cases */ /* even when Hatari is not started in monochrome mode */ else if ( Res & 2 ) /* Hi res */ { if ( X < LineStartCycle ) X = LineStartCycle; /* display is disabled in the left border */ else if ( X > LineEndCycle ) X = LineEndCycle; /* display is disabled in the right border */ NbBytes = ( (X-LineStartCycle)>>1 ) & (~1); /* 2 cycles per byte */ /* One line uses 80 bytes instead of the standard 160 bytes in low/med res */ if ( HblCounterVideo < nStartHBL + VIDEO_HEIGHT_HBL_MONO ) VideoAddress = VideoBase + ( HblCounterVideo - nStartHBL ) * ( BORDERBYTES_NORMAL / 2 ) + NbBytes; else VideoAddress = VideoBase + VIDEO_HEIGHT_HBL_MONO * ( BORDERBYTES_NORMAL / 2 ); } else { VideoAddress = pVideoRaster - STRam; /* pVideoRaster is updated by Video_CopyScreenLineColor */ /* Now find which pixel we are on (ignore left/right borders) */ // X = ( Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) - 12 ) % nCyclesPerLine; /* Get real video line count (can be different from nHBL) */ // HblCounterVideo = ( Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) - 12 ) / nCyclesPerLine; /* Correct the case when read overlaps end of line / start of next line */ /* Video_CopyScreenLineColor was not called yet to update VideoAddress */ /* so we need to determine the size of the previous line to get the */ /* correct value of VideoAddress. */ PrevSize = 0; if ( HblCounterVideo < nHBL ) X = 0; else if ( ( HblCounterVideo > nHBL ) /* HblCounterVideo = nHBL+1 */ && ( nHBL >= nStartHBL ) ) /* if nHBL was not visible, PrevSize = 0 */ { LineBorderMask = ShifterFrame.ShifterLines[ HblCounterVideo-1 ].BorderMask; /* get border mask for nHBL */ PrevSize = BORDERBYTES_NORMAL; /* normal line */ if (LineBorderMask & BORDERMASK_LEFT_OFF) PrevSize += BORDERBYTES_LEFT; else if (LineBorderMask & BORDERMASK_LEFT_PLUS_2) PrevSize += 2; if (LineBorderMask & BORDERMASK_STOP_MIDDLE) PrevSize -= 106; else if (LineBorderMask & BORDERMASK_RIGHT_MINUS_2) PrevSize -= 2; else if (LineBorderMask & BORDERMASK_RIGHT_OFF) PrevSize += BORDERBYTES_RIGHT; if (LineBorderMask & ( BORDERMASK_EMPTY_LINE | BORDERMASK_NO_DE ) ) PrevSize = 0; /* On STE, the Shifter skips the given amount of words as soon as display is disabled */ /* which is the case here when reading overlaps end/start of line (LineWidth is 0 on STF) */ PrevSize += LineWidth*2; } LineBorderMask = ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask; CurSize = BORDERBYTES_NORMAL; /* normal line */ if (LineBorderMask & BORDERMASK_LEFT_OFF) CurSize += BORDERBYTES_LEFT; else if (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE) CurSize += BORDERBYTES_LEFT_2_STE; else if (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE_MED) CurSize += BORDERBYTES_LEFT_2_STE; else if (LineBorderMask & BORDERMASK_LEFT_PLUS_2) CurSize += 2; else if (bSteBorderFlag) /* bigger line by 8 bytes on the left (STE specific) */ CurSize += 8; else if ( ( HWScrollCount > 0 ) && ( HWScrollPrefetch == 1 ) ) CurSize += 8; /* 8 more bytes are loaded when scrolling with prefetching */ if (LineBorderMask & BORDERMASK_STOP_MIDDLE) CurSize -= 106; else if (LineBorderMask & BORDERMASK_RIGHT_MINUS_2) CurSize -= 2; else if (LineBorderMask & BORDERMASK_RIGHT_OFF) CurSize += BORDERBYTES_RIGHT; if (LineBorderMask & BORDERMASK_RIGHT_OFF_FULL) CurSize += BORDERBYTES_RIGHT_FULL; if ( LineBorderMask & BORDERMASK_LEFT_PLUS_2) LineStartCycle = LINE_START_CYCLE_60; else if ( LineBorderMask & BORDERMASK_LEFT_OFF ) LineStartCycle = LINE_START_CYCLE_71; else if ( bSteBorderFlag ) LineStartCycle -= 16; /* display starts 16 pixels earlier */ else if ( ( HWScrollCount > 0 ) && ( HWScrollPrefetch == 1 ) ) LineStartCycle -= 16; /* shifter starts reading 16 pixels earlier when scrolling with prefetching */ LineEndCycle = LineStartCycle + CurSize*2; if ( X < LineStartCycle ) X = LineStartCycle; /* display is disabled in the left border */ else if ( X > LineEndCycle ) { X = LineEndCycle; /* display is disabled in the right border */ /* On STE, the Shifter skips the given amount of words as soon as display is disabled */ /* (LineWidth is 0 on STF) */ VideoAddress += LineWidth*2; } NbBytes = ( (X-LineStartCycle)>>1 ) & (~1); /* 2 cycles per byte */ /* when left border is open, we have 2 bytes less than theoretical value */ /* (26 bytes in left border, which is not a multiple of 4 cycles) */ if ( LineBorderMask & BORDERMASK_LEFT_OFF ) NbBytes -= 2; if ( LineBorderMask & ( BORDERMASK_EMPTY_LINE | BORDERMASK_NO_DE ) ) NbBytes = 0; /* Add line cycles if we have not reached end of screen yet */ if ( HblCounterVideo < nEndHBL + BlankLines ) VideoAddress += PrevSize + NbBytes; } #ifdef OLD_GET_POS LOG_TRACE(TRACE_VIDEO_ADDR , "video base=%x raster=%x addr=%x video_cyc=%d " "line_cyc=%d/X=%d @ nHBL=%d/video_hbl=%d %d<->%d pc=%x instr_cyc=%d\n", VideoBase, (int)(pVideoRaster - STRam), VideoAddress, Cycles_GetCounter(CYCLES_COUNTER_VIDEO), LineCycles, X, nHBL, HblCounterVideo, LineStartCycle, LineEndCycle, M68000_GetPC(), CurrentInstrCycles); #else LOG_TRACE(TRACE_VIDEO_ADDR , "video base=%x raster=%x addr=%x video_cyc=%d " "line_cyc=%d/X=%d @ nHBL=%d/video_hbl=%d %d<->%d pc=%x instr_cyc=%d\n", VideoBase, (int)(pVideoRaster - STRam), VideoAddress, Video_GetCyclesSinceVbl(), LineCycles, X, nHBL, HblCounterVideo, LineStartCycle, LineEndCycle, M68000_GetPC(), CurrentInstrCycles); #endif return VideoAddress; } /*-----------------------------------------------------------------------*/ /** * Calculate the cycle where the STF/STE's MMU starts reading * data to send them to the shifter. * On STE, if hscroll is used, prefetch will cause this position to * happen 16 cycles earlier. * This function should use the same logic as in Video_CalculateAddress. * NOTE : this function is not completely accurate, as even when there's * no hscroll (on STF) the mmu starts reading 16 cycles before display starts. * But it's good enough to emulate writing to ff8205/07/09 on STE. */ static int Video_GetMMUStartCycle ( int DisplayStartCycle ) { if ( bSteBorderFlag ) DisplayStartCycle -= 16; /* display starts 16 pixels earlier */ else if ( ( HWScrollCount > 0 ) && ( HWScrollPrefetch == 1 ) ) DisplayStartCycle -= 16; /* shifter starts reading 16 pixels earlier when scrolling with prefetching */ return DisplayStartCycle; } /*-----------------------------------------------------------------------*/ /** * Write to resolution register in the GLUE (0xff8260) * Depending on when the write is made, this will affect various video signals * and allow to change borders' position (see Video_Update_Glue_State() ) */ static void Video_WriteToGlueRes ( uint8_t Res ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); LOG_TRACE(TRACE_VIDEO_RES ,"res_glue=0x%2.2X video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n", Res, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); /* Ignore consecutive writes of the same value */ if ( Res == ShifterFrame.Res ) return; /* do nothing */ Video_Update_Glue_State ( FrameCycles , HblCounterVideo , LineCycles , true ); if ( ( ShifterFrame.Res == 0x02 ) && ( Res == 0x01 ) /* switched from hi res to med res */ && ( LineCycles <= (LINE_START_CYCLE_71+20) ) && ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF ) ) { ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= ~BORDERMASK_LEFT_OFF; /* cancel left off low res */ ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_LEFT_OFF_MED; /* a later switch to low res might gives right scrolling */ /* By default, this line will be in med res, except if we detect hardware scrolling later */ ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_OVERSCAN_MED_RES | ( 2 << 20 ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = pVideoTiming->HDE_On_Hi; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left med %d<->%d\n" , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ); } /* If left border is opened with hi/lo and we switch to medium resolution during the next cycles, */ /* then we assume a med res overscan line instead of a low res overscan line. */ /* Note that in that case, the switch to med res can shift the display by 0-3 words */ /* Used in 'No Cooper' greetings by 1984 and 'Punish Your Machine' by Delta Force */ if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF ) && ( Res == 0x01 ) ) { if ( ( LineCycles == LINE_LEFT_MED_CYCLE_1 ) /* 'No Cooper' timing */ || ( LineCycles == LINE_LEFT_MED_CYCLE_1+16 ) ) /* 'No Cooper' timing while removing bottom border */ { LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect med res overscan offset 0 byte\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_OVERSCAN_MED_RES | ( 0 << 20 ); } else if ( LineCycles == LINE_LEFT_MED_CYCLE_2 ) /* 'Best Part Of The Creation / PYM' timing */ { LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect med res overscan offset 2 bytes\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_OVERSCAN_MED_RES | ( 2 << 20 ); } } if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF_2_STE ) && ( Res == 0x01 ) && ( LineCycles == pVideoTiming->HDE_On_Hi ) ) { ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= (~BORDERMASK_LEFT_OFF_2_STE); ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_LEFT_OFF_2_STE_MED; /* TODO : we use DisplayPixelShift=-16 here to get the exepected result */ /* but it's more likely to be a 0 pixel shift and a 4 bytes compensation */ /* elsewhere when rendering line on screen */ ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = -16; /* screen is shifted 16 pixels to the left */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left 2 med ste\n" ); } /* If left border was opened with a hi/med res switch we need to check */ /* if the switch to low res can trigger a right hardware scrolling. */ /* We store the pixels count in DisplayPixelShift */ if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF_MED ) && ( Res == 0x00 ) && ( LineCycles <= LINE_SCROLL_1_CYCLE_50 ) ) { /* The hi/med switch was a switch to do low res hardware scrolling */ /* or to do a 'remove left' that includes a med res stabiliser, */ /* so we must cancel the med res overscan bit. */ ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= (~BORDERMASK_OVERSCAN_MED_RES); if ( LineCycles == LINE_LEFT_STAB_LOW ) /* cycle 16 */ { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect remove left with med stab\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 0; } else if ( LineCycles == LINE_SCROLL_13_CYCLE_50 ) /* cycle 20 */ { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 13 pixels right scroll\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 13; } else if ( LineCycles == LINE_SCROLL_9_CYCLE_50 ) /* cycle 24 */ { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 9 pixels right scroll\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 9; } else if ( LineCycles == LINE_SCROLL_5_CYCLE_50 ) /* cycle 28 */ { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 5 pixels right scroll\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 5; } else if ( LineCycles == LINE_SCROLL_1_CYCLE_50 ) /* cycle 32 */ { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 1 pixel right scroll\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 1; } } /* Left border was removed with a hi/lo switch, then a med res switch was made */ /* Depending on the low res switch, the screen will be shifted as a low res overscan line */ /* This is a different method than the one used by ST Connexion with only 3 res switches */ /* (so we must cancel the med res overscan bit) */ if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_OVERSCAN_MED_RES ) && ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & ( 0xf << 20 ) ) == 0 ) && ( Res == 0x00 ) && ( LineCycles <= 40 ) ) { ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= (~BORDERMASK_OVERSCAN_MED_RES); /* cancel med res */ if ( LineCycles == 28 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 13 pixels right scroll 2\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 13; } else if ( LineCycles == 32 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 9 pixels right scroll 2\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 9; } else if ( LineCycles == 36 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 5 pixels right scroll 2\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 5; } else if ( LineCycles == 40 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 1 pixel right scroll 2\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 1; } } /* Paulo Simoes' 4 pixel hardscroll on the whole screen, without removing left border */ /* All following lines will be shifted too, not just the one where the med/low switch happens */ if ( ( ShifterFrame.Res == 0x01 ) && ( Res == 0x00 ) /* switched from med res to low res */ && ( ShifterFrame.ResPosMed.LineCycles == 84 ) ) /* med res at cycle 84 */ { /* The med/low switch was a switch to do low res hardware scrolling */ if ( LineCycles == 100 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 13 pixels right scroll 3\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 13; } else if ( LineCycles == 104 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 9 pixels right scroll 3\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 9; pVideoRaster -= 2; } else if ( LineCycles == 92 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 5 pixels right scroll 3\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 5; pVideoRaster -= 4; } else if ( LineCycles == 96 ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 1 pixel right scroll 3\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 1; pVideoRaster -= 6; } /* Mark all the following lines as shifted too */ int i; for ( i=HblCounterVideo+1 ; i stay in hi res for 16 cycles to do the stab (hi/50/lo at 4/12/20) */ if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF ) && ( Res == 0x00 ) && ( LineCycles == 20 ) && ( ShifterFrame.ResPosHi.LineCycles == 4 ) && ( STMemory_ReadLong ( M68000_GetPC()-4 ) == 0x32883088 ) /* move.w a0,(a1) + move.w a0,(a0) */ ) { /* for now we simulate the same border as in ws1/3/4, but there's no med res in fact */ LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect remove left with no med stab closure ws2\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask = BORDERMASK_LEFT_OFF_MED; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 0; /* see special case in Video_CopyScreenLineColor() */ } /* TEMP for 'closure' in WS2 */ /* TEMP for 'closure' in STE mode */ /* -> stay in hi res for 16 cycles to do the stab (hi/50/lo at 0/8/16) */ if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF ) && ( Res == 0x00 ) && ( LineCycles == 16 ) && ( ShifterFrame.ResPosHi.LineCycles == 0 ) && ( STMemory_ReadLong ( M68000_GetPC()-4 ) == 0x32883088 ) /* move.w a0,(a1) + move.w a0,(a0) */ ) { /* for now we simulate the same border as in ws1/3/4, but there's no med res in fact */ LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect remove left with no med stab closure ste\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask = BORDERMASK_LEFT_OFF_MED; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 0; /* see special case in Video_CopyScreenLineColor() */ } /* TEMP for 'closure' in STE mode */ /* TEMP for 'death of the left border' by TNT */ /* -> stay in hi res for 16 cycles (hi/lo at 0/16) without stabiliser */ if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF ) && ( Res == 0x00 ) && ( LineCycles == 16 ) && ( ShifterFrame.ResPosHi.LineCycles == 0 ) && ( STMemory_ReadLong ( M68000_GetPC()-0x0c ) == 0x51c9fffc ) /* dbf d1,$fffc */ && ( M68000_GetPC() == 0x72c ) ) { /* we simulate a 13 px scroll, which compensates for 2 bytes in the video planes */ LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect remove left with no stab dolb\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 13; /* see special case in Video_CopyScreenLineColor() */ } /* TEMP for 'death of the left border' by TNT */ /* Store cycle position for this change of resolution */ ShifterFrame.Res = Res; if ( Res & 0x02 ) /* high res */ { ShifterFrame.ResPosHi.VBL = nVBLs; ShifterFrame.ResPosHi.FrameCycles = FrameCycles; ShifterFrame.ResPosHi.HBL = HblCounterVideo; ShifterFrame.ResPosHi.LineCycles = LineCycles; } else if ( Res == 0x01 ) /* med res */ { ShifterFrame.ResPosMed.VBL = nVBLs; ShifterFrame.ResPosMed.FrameCycles = FrameCycles; ShifterFrame.ResPosMed.HBL = HblCounterVideo; ShifterFrame.ResPosMed.LineCycles = LineCycles; } else /* low res */ { ShifterFrame.ResPosLo.VBL = nVBLs; ShifterFrame.ResPosLo.FrameCycles = FrameCycles; ShifterFrame.ResPosLo.HBL = HblCounterVideo; ShifterFrame.ResPosLo.LineCycles = LineCycles; } } /*-----------------------------------------------------------------------*/ /** * Write to resolution register in the Shifter (0xff8260 or 0xff8261) * Special case : when writing 3 to the Shifter's res, the shifter will stop processing incoming words sent by the MMU, * on the GLUE side this will be seen as hi res being selected */ static void Video_WriteToShifterRes ( uint8_t Res ) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); LOG_TRACE(TRACE_VIDEO_RES ,"res_shifter=0x%2.2X video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n", Res, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); if ( Res == 3 ) LOG_TRACE(TRACE_VIDEO_RES ,"res_shifter=0x%2.2X, shifter stopped video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n", Res, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); /* Ignore consecutive writes of the same value */ if ( Res == ShifterFrame.Res_Shifter ) return; /* do nothing */ /* Troed/Sync 4 pixels left hardscroll on the whole screen, without removing border */ /* Switch to res=3 to stop the shifter, then switch back to low/med res */ /* All following lines will be shifted but not the one where the switch to res=3 is done */ /* The switch is supposed to last less than 20 cycles to get all 4 positions */ /* Switch to res=3 is usually made at pos 68 to limit artifacts (all pixels will be black */ /* during the time when shifter is stopped), but it could be made anywhere on the line when DE is ON */ /* TODO : we shift the screen but we don't show the black pixels during stopped state */ /* TODO : shift should remain on all subsequent vbl's, not just the current one */ if ( ( ShifterFrame.Res_Shifter == 0x03 ) && ( ShifterFrame.ResPosStop_Shifter.LineCycles >= 64 ) /* switched to stopped state when DE ON */ && ( LineCycles >= 64+8 ) /* switch to res=3 during at least 8 cycles */ && ( LineCycles - ShifterFrame.ResPosStop_Shifter.LineCycles <= 32 ) ) /* stopped for max 32 cycles */ { int shifter_res_pos_old; int shifter_res_pos_new; int shifter_stopped_cycles; int Shift , AddressInc; /* Changes of resolution in the shifter are done on the next rounding to 4 cycles */ /* (also depending on the current bus access) */ /* TODO : use a common function like M68000_SyncCpuBus() */ shifter_res_pos_old = ( ShifterFrame.ResPosStop_Shifter.LineCycles + 3 ) & ~3; shifter_res_pos_new = ( LineCycles + 3 ) & ~3; shifter_stopped_cycles = shifter_res_pos_new - shifter_res_pos_old; Shift = 0; AddressInc = 0; if ( shifter_stopped_cycles % 16 == 4 ) // 88 + 16n { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 12 pixels left scroll with stopped shifter\n" ); AddressInc = -6; Shift = -12; } else if ( shifter_stopped_cycles % 16 == 0 ) // 84 + 16n { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 0 pixels left scroll with stopped shifter\n" ); AddressInc = 0; Shift = 0; } else if ( shifter_stopped_cycles % 16 == 12 ) // 80 + 16n { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 4 pixels left scroll with stopped shifter\n" ); AddressInc = -2; Shift = -4; } else if ( shifter_stopped_cycles % 16 == 8 ) // 76 + 16n { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 8 pixel left scroll with stopped shifter\n" ); AddressInc = -4; Shift = -8; } /* Offset to be added at the end of the current line */ if ( AddressInc != 0 ) VideoRasterDelayedInc = AddressInc; /* Mark all the following lines as shifted */ int i; for ( i=HblCounterVideo+1 ; i= 56, then the line will start 2 bytes earlier and will be 60 Hz (HBL at cycle 508) * - Empty line switching freq on STF : start the line in 50 Hz, change to 60 Hz at the exact place * where display is enabled in 50 Hz, then go back to 50 Hz. * This creates a 0 byte line (used in 'shforstv' by Paulo Simoes and 'Closure' by Sync) * - Remove 2 bytes to the right : start the line in 50 Hz (pos 0 or 56) and change to 60 Hz before the position * where display is disabled in 60 Hz (optionally go back to 50 Hz after this position, but not necessary) * - Remove right border : +44 bytes ; switch to 60 Hz at the position where the line ends in 50 Hz * Some programs don't switch back to 50 Hz immediately (Sync screen in 'SNY II', 'Closure' by Sync) */ /* In Hatari, the state machine is updated only on every write to freq/res registers (instead of doing it on every cycle as on real chip). The following pseudo code should be equivalent to a state machine updated on every cycle (but some rare cases/combinations could be missing) if ( freq == 71 & pos <= start_hi ) match_found; hbl = 224: if ( !no_de ) start = 4; end = 164; remove left; if ( left+2 ) cancel left+2; else if ( freq == 71 & pos <= blank_stop_50 ) match_found; hbl = 224: if ( !no_de ) end = 164; blank line no DE; if ( left+2 ) cancel left+2; else if ( freq == 71 & pos <= start_50 ) match_found; hbl = 224: if ( !no_de ) end = 164; line no DE; if ( left+2 ) cancel left+2; else if ( freq != 71 ) if ( pos <= start_hi & remove left ) cancel remove left; if ( pos <= blank_stop_50 & blank line no DE & !ignore line ) cancel blank line no DE; if ( pos <= start_50 & line no DE & !blank line & !ignore line ) cancel line no DE; if ( freq == 60 & pos < start_pal ) match_found; hbl = 508; end = 372; right-2; if ( !no_de ) if ( pos <= blank_stop_50 ) blank line with DE; if ( start == 56 ) start=52; left+2; else if ( freq == 50 & pos <= start_60 ) match_found; hbl = 512; if ( !no_de ) end = 376; if (right-2) cancel right-2 if ( start == 52 ) start=56; if ( left+2 ) cancel left+2; else if ( freq == 50 & pos <= start_pal ) match_found; hbl = 512; if ( !no_de ) end = 376 if (right-2) cancel right-2 left+2 50 Hz if ( STF & freq == 60 & pos > start_60 & pos <= start_50 & !no_de ) match_found; if ( start == 56 ) start=0; end=0; empty line no DE; else if ( STE & freq == 60 & pos > preload_start_60 & pos <= preload_start_50 ) match_found; if ( start == 56 ) start=0; end=0; empty line no DE; freq_test_next if ( match_found ) goto freq_test_done if ( freq == 71 & pos <= end & pos <= end_hi & !no_de ) end=164; stop middle; if (right-2) cancel right-2 else if ( freq == 71 & pos <= end & !no_de ) end=512; remove right / right full; else if ( freq == 71 & pos <= hsync_start_50/60 ) empty line res 3 no sync; else if ( freq == 71 & pos <= hsync_stop_50/60 ) empty line res 2 sync high; else if ( freq == 60 & pos <= end & pos <= end_60 & !no_de ) if ( end == 376 ) right-2; end=372; if ( stop middle ) cancel stop middle; if ( remove right | right full ) cancel remove right | right full; else if ( freq == 50 & pos <= end & pos <= end_50 & !no_de ) end=376; if ( right-2 ) cancel right-2; if ( stop middle ) cancel stop middle; if ( remove right | right full ) cancel remove right | right full; else if ( freq == 60 & pos <= end & pos > end_60 & pos <= end_50 & !no_de ) if ( end == 376 ) end=hsync_start_50/60; remove right; if ( right-2 ) cancel right-2; else if ( freq != 71 & pos <= hsync_start_50/60 ) if ( pos <= end ) end = hsync_start_50/60; if ( remove right full ) cancel remove right full; else if ( empty line res 3 no sync ) cancel empty line res 3 no sync; else if ( freq != 71 & pos <= hsync_stop_50/60 ) if ( empty line res 2 sync high ) cancel empty line res 2 sync high; freq_test_done */ static void Video_Update_Glue_State ( int FrameCycles , int HblCounterVideo , int LineCycles , bool WriteToRes ) { int FreqHz; int DE_start; int DE_end; int HBL_Pos; int nCyclesPerLine_new; int Freq_match_found; uint32_t BorderMask; int Top_Pos; int Bottom_Pos; /* FreqHz will be 50, 60 or 71 */ if ( IoMem[0xff8260] & 2 ) FreqHz = VIDEO_71HZ; else { /* We're only interested in bit 1 (50/60Hz) */ FreqHz = ( IoMem[0xff820a] & 2 ) ? VIDEO_50HZ : VIDEO_60HZ; } /* GLUE will latch freq register 1 cycle later than res register */ /* To take this into account in our case, we subtract 1 cycle to the res write position */ /* that will be used for all the comparison in the state machine */ /* (this is not the case for the STE GST MCU) */ if ( Config_IsMachineST() && WriteToRes ) LineCycles--; DE_start = ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle; DE_end = ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle; BorderMask = ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask; HBL_Pos = -1; nCyclesPerLine_new = -1; Freq_match_found = 0; //fprintf ( stderr , "pom %d %d-%d %d-%d\n" , nHBL , nStartHBL , nEndHBL , DE_start , DE_end ); /* * Check Freq's value before DE_start * We need 2 different cases for ST and STE */ /*************** STF ***************/ if ( Config_IsMachineST() ) { if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= pVideoTiming->HDE_On_Hi ) ) { Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Hi; /* 220/224 */ nCyclesPerLine_new = CYCLES_PER_LINE_71HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_start = pVideoTiming->HDE_On_Hi; /* 4 */ DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = -4; /* screen is shifted 4 pixels to the left */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left %d<->%d\n" , DE_start , DE_end ); if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= pVideoTiming->HBlank_Off_Low_50 ) ) { Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Hi; /* 220/224 */ nCyclesPerLine_new = CYCLES_PER_LINE_71HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= ( BORDERMASK_BLANK | BORDERMASK_NO_DE ); LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect blank line no DE res stf %d<->%d\n" , DE_start , DE_end ); } if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= pVideoTiming->HDE_On_Low_50 ) ) { Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Hi; /* 220/224 */ nCyclesPerLine_new = CYCLES_PER_LINE_71HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= BORDERMASK_NO_DE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect line no DE res stf %d<->%d\n" , DE_start , DE_end ); } if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } else if ( FreqHz != VIDEO_71HZ ) { if ( ( LineCycles <= pVideoTiming->HDE_On_Hi ) && ( BorderMask & BORDERMASK_LEFT_OFF ) ) { if ( FreqHz == VIDEO_50HZ ) DE_start = pVideoTiming->HDE_On_Low_50; /* 56 */ else { DE_start = pVideoTiming->HDE_On_Low_60; /* 52 */ BorderMask |= BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 60Hz %d<->%d\n" , DE_start , DE_end ); } BorderMask &= ~BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 0; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel remove left %d<->%d\n" , DE_start , DE_end ); } if ( ( LineCycles <= pVideoTiming->HBlank_Off_Low_50 ) && ( BorderMask & ( BORDERMASK_BLANK | BORDERMASK_NO_DE ) ) && !( BorderMask & BORDERMASK_NO_COUNT ) ) { BorderMask &= ~( BORDERMASK_BLANK | BORDERMASK_NO_DE ); LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel blank line no DE %d<->%d\n" , DE_start , DE_end ); } else if ( ( LineCycles <= pVideoTiming->HDE_On_Low_50 ) && ( BorderMask & BORDERMASK_NO_DE ) && !( BorderMask & BORDERMASK_BLANK ) && !( BorderMask & BORDERMASK_NO_COUNT ) ) { /* we can cancel a "line no de", but not a "blank line no de" */ BorderMask &= ~BORDERMASK_NO_DE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel line no DE %d<->%d\n" , DE_start , DE_end ); } } /* The line was in 50 Hz and continues in 60 Hz */ if ( ( FreqHz == VIDEO_60HZ ) && ( LineCycles < pVideoTiming->Line_Set_Pal ) ) { //fprintf ( stderr , "pom0a\n" ); Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Low_60; /* 504/508 */ nCyclesPerLine_new = CYCLES_PER_LINE_60HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { if ( DE_start > 0 ) /* ie only if StartHBL was called */ { DE_end = pVideoTiming->HDE_Off_Low_60; /* 372 */ BorderMask |= BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect right-2 %d<->%d\n" , DE_start , DE_end ); } if ( ( LineCycles > pVideoTiming->HBlank_Off_Low_60 ) && ( LineCycles <= pVideoTiming->HBlank_Off_Low_50 ) ) { BorderMask |= BORDERMASK_BLANK; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect blank line freq stf %d<->%d\n" , DE_start , DE_end ); } if ( DE_start == pVideoTiming->HDE_On_Low_50 ) /* 56 */ { DE_start = pVideoTiming->HDE_On_Low_60; /* 52 */ BorderMask |= BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 60Hz %d<->%d\n" , DE_start , DE_end ); } } } else if ( ( FreqHz == VIDEO_50HZ ) && ( LineCycles <= pVideoTiming->HDE_On_Low_60 ) ) { //fprintf ( stderr , "pom1a\n" ); Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Low_50; /* 508/512 */ nCyclesPerLine_new = CYCLES_PER_LINE_50HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Low_50; /* 376 */ if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } if ( DE_start == pVideoTiming->HDE_On_Low_60 ) /* 52 */ { DE_start = pVideoTiming->HDE_On_Low_50; /* 56 */ if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } } } else if ( ( FreqHz == VIDEO_50HZ ) && ( LineCycles <= pVideoTiming->Line_Set_Pal ) ) { //fprintf ( stderr , "pom2a\n" ); Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Low_50; /* 508/512 */ nCyclesPerLine_new = CYCLES_PER_LINE_50HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Low_50; /* 376 */ if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 50Hz %d<->%d\n" , DE_start , DE_end ); } } if ( ( FreqHz == VIDEO_60HZ ) && ( LineCycles > pVideoTiming->HDE_On_Low_60 ) && ( LineCycles <= pVideoTiming->HDE_On_Low_50 ) && !( BorderMask & BORDERMASK_NO_DE ) ) { Freq_match_found = 1; //fprintf ( stderr , "pom3a\n" ); if ( DE_start == pVideoTiming->HDE_On_Low_50 ) /* 56 */ { DE_start = 0 ; DE_end = 0; BorderMask |= BORDERMASK_NO_DE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect line no DE freq stf %d<->%d\n" , DE_start , DE_end ); } } } /*************** STE ***************/ else if ( Config_IsMachineSTE() ) { if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= pVideoTiming->HDE_On_Hi ) ) { Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Hi; /* 220/224 */ nCyclesPerLine_new = CYCLES_PER_LINE_71HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_start = pVideoTiming->HDE_On_Hi; /* 4 */ DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = -4; /* screen is shifted 4 pixels to the left */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left %d<->%d\n" , DE_start , DE_end ); if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= pVideoTiming->HBlank_Off_Low_50 ) ) { Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Hi; /* 220/224 */ nCyclesPerLine_new = CYCLES_PER_LINE_71HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= ( BORDERMASK_BLANK | BORDERMASK_NO_DE ); LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect blank line no DE res ste %d<->%d\n" , DE_start , DE_end ); } if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= pVideoTiming->Preload_Start_Low_50 ) ) { Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Hi; /* 220/224 */ nCyclesPerLine_new = CYCLES_PER_LINE_71HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= BORDERMASK_NO_DE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect line no DE res ste %d<->%d\n" , DE_start , DE_end ); } if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 %d<->%d\n" , DE_start , DE_end ); } } else if ( FreqHz != VIDEO_71HZ ) { if ( ( LineCycles < pVideoTiming->HDE_On_Hi ) && ( BorderMask & BORDERMASK_LEFT_OFF ) ) { if ( FreqHz == VIDEO_50HZ ) DE_start = pVideoTiming->HDE_On_Low_50; /* 56 */ else { DE_start = pVideoTiming->HDE_On_Low_60; /* 52 */ BorderMask |= BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 60Hz %d<->%d\n" , DE_start , DE_end ); } BorderMask &= ~BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 0; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel remove left %d<->%d\n" , DE_start , DE_end ); } else if ( ( LineCycles == pVideoTiming->HDE_On_Hi ) && ( BorderMask & BORDERMASK_LEFT_OFF ) ) { DE_start = pVideoTiming->Preload_Start_Hi + 16; BorderMask &= ~BORDERMASK_LEFT_OFF; BorderMask |= BORDERMASK_LEFT_OFF_2_STE; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = -8; /* screen is shifted 8 pixels to the left */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left 2 ste %d<->%d\n" , DE_start , DE_end ); } if ( ( LineCycles <= pVideoTiming->HBlank_Off_Low_50 ) && ( BorderMask & ( BORDERMASK_BLANK | BORDERMASK_NO_DE ) ) && !( BorderMask & BORDERMASK_NO_COUNT ) ) { BorderMask &= ~( BORDERMASK_BLANK | BORDERMASK_NO_DE ); LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel blank line no DE %d<->%d\n" , DE_start , DE_end ); } else if ( ( LineCycles <= pVideoTiming->Preload_Start_Low_50 ) && ( BorderMask & BORDERMASK_NO_DE ) && !( BorderMask & BORDERMASK_BLANK ) && !( BorderMask & BORDERMASK_NO_COUNT ) ) { /* we can cancel a "line no de", but not a "blank line no de" */ BorderMask &= ~BORDERMASK_NO_DE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel line no DE %d<->%d\n" , DE_start , DE_end ); } } /* The line was in 50 Hz and continues in 60 Hz */ if ( ( FreqHz == VIDEO_60HZ ) && ( LineCycles < pVideoTiming->Line_Set_Pal ) ) { //fprintf ( stderr , "pom0a\n" ); Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Low_60; /* 504/508 */ nCyclesPerLine_new = CYCLES_PER_LINE_60HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { if ( DE_start > 0 ) /* ie only if StartHBL was called */ { DE_end = pVideoTiming->HDE_Off_Low_60; /* 372 */ BorderMask |= BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect right-2 %d<->%d\n" , DE_start , DE_end ); } if ( ( LineCycles > pVideoTiming->HBlank_Off_Low_60 ) && ( LineCycles <= pVideoTiming->HBlank_Off_Low_50 ) ) { BorderMask |= BORDERMASK_BLANK; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect blank line freq ste %d<->%d\n" , DE_start , DE_end ); } if ( LineCycles <= pVideoTiming->Preload_Start_Low_60 ) { if ( DE_start == pVideoTiming->HDE_On_Low_50 ) /* 56 */ { DE_start = pVideoTiming->HDE_On_Low_60; /* 52 */ BorderMask |= BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 60Hz ste %d<->%d\n" , DE_start , DE_end ); } } else { /* normal line starting at 56 but running at 60 Hz ; we don't do anything special */ } } } else if ( ( FreqHz == VIDEO_50HZ ) && ( LineCycles <= pVideoTiming->Preload_Start_Low_60 ) ) { //fprintf ( stderr , "pom1a\n" ); Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Low_50; /* 508/512 */ nCyclesPerLine_new = CYCLES_PER_LINE_50HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Low_50; /* 376 */ if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } if ( DE_start == pVideoTiming->HDE_On_Low_60 ) /* 52 */ { DE_start = pVideoTiming->HDE_On_Low_50; /* 56 */ if ( BorderMask & BORDERMASK_LEFT_PLUS_2 ) { BorderMask &= ~BORDERMASK_LEFT_PLUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 ste %d<->%d\n" , DE_start , DE_end ); } } } } else if ( ( FreqHz == VIDEO_50HZ ) && ( LineCycles <= pVideoTiming->Line_Set_Pal ) ) { //fprintf ( stderr , "pom2a\n" ); Freq_match_found = 1; HBL_Pos = pVideoTiming->Hbl_Int_Pos_Low_50; /* 508/512 */ nCyclesPerLine_new = CYCLES_PER_LINE_50HZ; if ( !( BorderMask & BORDERMASK_NO_DE ) ) { DE_end = pVideoTiming->HDE_Off_Low_50; /* 376 */ if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 50Hz ste %d<->%d\n" , DE_start , DE_end ); } } if ( ( FreqHz == VIDEO_60HZ ) && ( LineCycles > pVideoTiming->Preload_Start_Low_60 ) && ( LineCycles <= pVideoTiming->Preload_Start_Low_50 ) && !( BorderMask & BORDERMASK_NO_DE ) ) { Freq_match_found = 1; //fprintf ( stderr , "pom3a\n" ); if ( DE_start == pVideoTiming->HDE_On_Low_50 ) /* 56 */ { DE_start = 0 ; DE_end = 0; BorderMask |= BORDERMASK_NO_DE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect line no DE freq ste %d<->%d\n" , DE_start , DE_end ); } } } //fprintf ( stderr , "pom0 %d-%d\n" , DE_start , DE_end ); /* If we found a match before DE_start, then there's no need */ /* to check between DE_start and DE_end */ if ( Freq_match_found ) goto Freq_Test_Done; //fprintf ( stderr , "pom1\n" ); /* * Check Freq's value between DE_start and DE_end */ if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= DE_end ) && ( LineCycles <= pVideoTiming->HDE_Off_Hi ) /* 160 */ && !( BorderMask & BORDERMASK_NO_DE ) ) { //fprintf ( stderr , "pom3\n" ); DE_end = pVideoTiming->HDE_Off_Hi; /* 164 */ BorderMask |= BORDERMASK_STOP_MIDDLE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect stop middle %d<->%d\n" , DE_start , DE_end ); if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= DE_end ) && !( BorderMask & BORDERMASK_NO_DE ) ) { //fprintf ( stderr , "pom3\n" ); DE_end = LINE_END_CYCLE_FULL; /* 512 */ BorderMask |= ( BORDERMASK_RIGHT_OFF | BORDERMASK_RIGHT_OFF_FULL ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask |= BORDERMASK_LEFT_OFF; /* no left border on next line */ ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = pVideoTiming->HDE_On_Hi; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove right/right full %d<->%d\n" , DE_start , DE_end ); } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= nCyclesPerLine + pVideoTiming->HSync_On_Offset_Low ) ) /* 458/462 depends on 50/60 freq (line cycles-50) */ { BorderMask |= BORDERMASK_NO_SYNC; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask |= ( BORDERMASK_BLANK | BORDERMASK_NO_DE | BORDERMASK_NO_COUNT ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = 0; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle = 0; BlankLines++; /* glue's line counter not incremented, display ends 1 line later */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line res 3 no sync %d<->%d\n" , DE_start , DE_end ); } else if ( ( FreqHz == VIDEO_71HZ ) && ( LineCycles <= nCyclesPerLine + pVideoTiming->HSync_Off_Offset_Low ) ) /* 498/502 depends on 50/60 freq (line cycles-10) */ { BorderMask |= BORDERMASK_SYNC_HIGH; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask |= ( BORDERMASK_BLANK | BORDERMASK_NO_DE | BORDERMASK_NO_COUNT ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = 0; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle = 0; BlankLines++; /* glue's line counter not incremented, display ends 1 line later */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line res 2 sync high %d<->%d\n" , DE_start , DE_end ); } else if ( FreqHz == VIDEO_71HZ ) /* rest of the line after HSync_Off_Offset_Low */ { /* Next line will start as "remove left" by default (eg E605 by Light or DHS demos on STE) */ ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask |= BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = pVideoTiming->HDE_On_Hi; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle = pVideoTiming->HDE_Off_Hi; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayPixelShift = -4; /* screen is shifted 4 pixels to the left */ LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left on next hbl %d<->%d\n" , DE_start , DE_end ); } if ( ( FreqHz == VIDEO_60HZ ) && ( LineCycles <= DE_end ) && ( LineCycles <= pVideoTiming->HDE_Off_Low_60 ) /* 372 */ && !( BorderMask & BORDERMASK_NO_DE ) ) { //fprintf ( stderr , "pom2\n" ); if ( DE_end == pVideoTiming->HDE_Off_Low_50 ) /* 376 */ { BorderMask |= BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect right-2 %d<->%d\n" , DE_start , DE_end ); } DE_end = pVideoTiming->HDE_Off_Low_60; /* 372 */ if ( BorderMask & BORDERMASK_STOP_MIDDLE ) { BorderMask &= ~BORDERMASK_STOP_MIDDLE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel stop middle %d<->%d\n" , DE_start , DE_end ); } else if ( BorderMask & ( BORDERMASK_RIGHT_OFF | BORDERMASK_RIGHT_OFF_FULL ) ) { BorderMask &= ~( BORDERMASK_RIGHT_OFF | BORDERMASK_RIGHT_OFF_FULL ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask &= ~BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = -1; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel remove right/right full %d<->%d\n" , DE_start , DE_end ); } } else if ( ( FreqHz == VIDEO_50HZ ) && ( LineCycles <= DE_end ) && ( LineCycles <= pVideoTiming->HDE_Off_Low_50 ) /* 376 */ && !( BorderMask & BORDERMASK_NO_DE ) ) { //fprintf ( stderr , "pom3\n" ); DE_end = pVideoTiming->HDE_Off_Low_50; /* 376 */ if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } else if ( BorderMask & BORDERMASK_STOP_MIDDLE ) { BorderMask &= ~BORDERMASK_STOP_MIDDLE; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel stop middle %d<->%d\n" , DE_start , DE_end ); } else if ( BorderMask & ( BORDERMASK_RIGHT_OFF | BORDERMASK_RIGHT_OFF_FULL ) ) { BorderMask &= ~( BORDERMASK_RIGHT_OFF | BORDERMASK_RIGHT_OFF_FULL ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask &= ~BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = -1; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel remove right/right full %d<->%d\n" , DE_start , DE_end ); } } else if ( ( FreqHz == VIDEO_60HZ ) && ( LineCycles <= DE_end ) && ( LineCycles > pVideoTiming->HDE_Off_Low_60 ) && ( LineCycles <= pVideoTiming->HDE_Off_Low_50 ) && !( BorderMask & BORDERMASK_NO_DE ) ) { //fprintf ( stderr , "pom4\n" ); if ( DE_end == pVideoTiming->HDE_Off_Low_50 ) /* 376 */ { DE_end = nCyclesPerLine + pVideoTiming->HSync_On_Offset_Low; /* 458/462 depends on 50/60 freq (line cycles-50) */ BorderMask |= BORDERMASK_RIGHT_OFF; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove right %d<->%d\n" , DE_start , DE_end ); if ( BorderMask & BORDERMASK_RIGHT_MINUS_2 ) { BorderMask &= ~BORDERMASK_RIGHT_MINUS_2; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel right-2 %d<->%d\n" , DE_start , DE_end ); } } } else if ( ( FreqHz != VIDEO_71HZ ) && ( LineCycles <= nCyclesPerLine + pVideoTiming->HSync_On_Offset_Low ) ) /* 458/462 depends on 50/60 freq (line cycles-50) */ { //fprintf ( stderr , "pom5\n" ); if ( LineCycles <= DE_end ) { DE_end = nCyclesPerLine + pVideoTiming->HSync_On_Offset_Low; /* 458/462 depends on 50/60 freq (line cycles-50) */ if ( BorderMask & BORDERMASK_RIGHT_OFF_FULL ) { BorderMask &= ~BORDERMASK_RIGHT_OFF_FULL; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask &= ~BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = -1; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel remove right full %d<->%d\n" , DE_start , DE_end ); } } else if ( BorderMask & BORDERMASK_NO_SYNC ) { BorderMask &= ~BORDERMASK_NO_SYNC; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask &= ~( BORDERMASK_BLANK | BORDERMASK_NO_DE | BORDERMASK_NO_COUNT ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = -1; BlankLines--; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel empty line res 3 no sync %d<->%d\n" , DE_start , DE_end ); } } else if ( ( FreqHz != VIDEO_71HZ ) && ( LineCycles <= nCyclesPerLine + pVideoTiming->HSync_Off_Offset_Low ) ) /* 498/502 depends on 50/60 freq (line cycles-10) */ { //fprintf ( stderr , "pom6\n" ); if ( BorderMask & BORDERMASK_SYNC_HIGH ) { BorderMask &= ~BORDERMASK_SYNC_HIGH; ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask &= ~( BORDERMASK_BLANK | BORDERMASK_NO_DE | BORDERMASK_NO_COUNT ); ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = -1; BlankLines--; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel empty line res 2 sync high %d<->%d\n" , DE_start , DE_end ); } } /* Go here directly if there's no more Freq/Res changes to test */ Freq_Test_Done: /* Update HBL's position only if display has not reached pos pVideoTiming->Line_Set_Pal */ /* and HBL interrupt was already handled at the beginning of this line. */ if ( ( HBL_Pos > 0 ) && ( HblCounterVideo == nHBL ) ) { /* Don't modify HBL's position now if we're handling the special HBL for video counter restart */ if ( RestartVideoCounter == false ) Video_AddInterruptHBL ( HblCounterVideo , HBL_Pos ); ShifterFrame.HBL_CyclePos = HBL_Pos; if ( nCyclesPerLine_new > 0 ) nCyclesPerLine_new <<= nCpuFreqShift; // TODO group the 3 if's into 1 /* In case we're mixing 50 Hz (512 cycles) and 60 Hz (508 cycles) lines on the same screen, */ /* we must update the position where the next VBL will happen (instead of the initial value in CyclesPerVBL) */ /* We check if number of cycles per line changes only on the last line, and if so, we update the VBL's position */ /* Since we setup VBL at the start of the last HBL, any change of number of cycles per line will */ /* in fact change the position of the next VBL */ if ( ( nCyclesPerLine_new > 0 ) && ( nHBL == nScanlinesPerFrame-1 ) && ( nCyclesPerLine != nCyclesPerLine_new ) ) { CyclesPerVBL += ( nCyclesPerLine_new - nCyclesPerLine ); /* +4 or -4 */ CycInt_ModifyInterrupt ( nCyclesPerLine_new - nCyclesPerLine , INT_CPU_CYCLE , INTERRUPT_VIDEO_VBL ); } /* If HBL_Pos changed, the cycles per line might change too */ if ( nCyclesPerLine_new > 0 ) nCyclesPerLine = nCyclesPerLine_new; } /* Update Timer B's position if DE start/end changed (and this is not an empty 0 byte line) */ /* In most cases Timer B will count end of line events, but it can also count start of line events */ /* (eg 'Seven Gates Of Jambala') so we need to check both start/end values */ if ( ( DE_end > 0 ) && ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle != DE_start ) || ( ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle != DE_end ) ) ) { LineTimerBPos = Video_TimerB_GetPosFromDE ( DE_start , DE_end ); Video_AddInterruptTimerB ( HblCounterVideo , LineCycles , LineTimerBPos ); } /* * Check if top/bottom borders' position should change */ /* If first displayed line is not reached yet, we can still change top border */ if ( ( HblCounterVideo < nStartHBL-1 ) || ( ( HblCounterVideo == nStartHBL-1 ) && ( LineCycles <= pVideoTiming->RemoveTopBorder_Pos ) ) ) { if ( FreqHz == VIDEO_71HZ ) Top_Pos = pVideoTiming->VDE_On_Line_Hi; else if ( FreqHz == VIDEO_60HZ ) Top_Pos = pVideoTiming->VDE_On_Line_60; else Top_Pos = pVideoTiming->VDE_On_Line_50; /* Change position if new position is not reached yet */ if ( ( Top_Pos != nStartHBL ) && ( ( HblCounterVideo < Top_Pos-1 ) || ( ( HblCounterVideo == Top_Pos-1 ) && ( LineCycles <= pVideoTiming->RemoveTopBorder_Pos ) ) ) ) { nStartHBL = Top_Pos; /* If 50 Hz screen and first line is before 50 Hz position -> top border removed */ if ( ( nScreenRefreshRate == VIDEO_50HZ ) && ( nStartHBL < pVideoTiming->VDE_On_Line_50 ) ) VerticalOverscan |= V_OVERSCAN_NO_TOP; else VerticalOverscan &= ~V_OVERSCAN_NO_TOP; VerticalOverscan &= ~V_OVERSCAN_NO_DE; } else { /* If freq is changed after new position top_pos and before nStartHBL on a 50Hz screen, then we get a screen */ /* where vertical DE is never activated (eg 60 Hz switch between line 34 and end of line 62) */ if ( ( nScreenRefreshRate == VIDEO_50HZ ) && ( FreqHz != VIDEO_50HZ ) ) VerticalOverscan |= V_OVERSCAN_NO_DE; else VerticalOverscan &= ~V_OVERSCAN_NO_DE; } } /* If last displayed line not reached yet, we can still change bottom border */ if ( ( HblCounterVideo < nEndHBL-1 ) || ( ( HblCounterVideo == nEndHBL-1 ) && ( LineCycles <= pVideoTiming->RemoveBottomBorder_Pos ) ) ) { if ( FreqHz == VIDEO_71HZ ) Bottom_Pos = pVideoTiming->VDE_Off_Line_Hi; else if ( FreqHz == VIDEO_60HZ ) Bottom_Pos = pVideoTiming->VDE_Off_Line_60; else Bottom_Pos = pVideoTiming->VDE_Off_Line_50; if ( ( HblCounterVideo < pVideoTiming->VDE_Off_Line_60-1 ) /* 234 */ || ( ( HblCounterVideo == pVideoTiming->VDE_Off_Line_60-1 ) && ( LineCycles <= pVideoTiming->RemoveBottomBorder_Pos ) ) ) { if ( ( nScreenRefreshRate == VIDEO_60HZ ) && ( FreqHz != VIDEO_60HZ ) ) { /* 60 Hz screen where freq != 60 Hz on last line -> bottom border removed */ nEndHBL = pVideoTiming->VDE_Off_Line_NoBottom_60; VerticalOverscan |= V_OVERSCAN_NO_BOTTOM_60; } else if ( ( nScreenRefreshRate == VIDEO_50HZ ) && ( FreqHz == VIDEO_60HZ ) ) { /* 50 Hz screen ending at 60 Hz screen's position -> short bottom border (-29 lines) */ nEndHBL = pVideoTiming->VDE_Off_Line_60; VerticalOverscan |= V_OVERSCAN_BOTTOM_SHORT_50; } else { nEndHBL = Bottom_Pos; VerticalOverscan &= ~( V_OVERSCAN_NO_BOTTOM_60 | V_OVERSCAN_BOTTOM_SHORT_50 ); } } else if ( ( HblCounterVideo < pVideoTiming->VDE_Off_Line_50-1 ) /* 263 */ || ( ( HblCounterVideo == pVideoTiming->VDE_Off_Line_50-1 ) && ( LineCycles <= pVideoTiming->RemoveBottomBorder_Pos ) ) ) { if ( VerticalOverscan & V_OVERSCAN_NO_BOTTOM_60 ) { /* Bottom border already removed above, can't be changed now */ } else if ( ( nScreenRefreshRate == VIDEO_50HZ ) && ( FreqHz != VIDEO_50HZ ) ) { /* 50 Hz screen where freq != 50 Hz on last line -> bottom border removed */ nEndHBL = pVideoTiming->VDE_Off_Line_NoBottom_50; VerticalOverscan |= V_OVERSCAN_NO_BOTTOM_50; } else { nEndHBL = Bottom_Pos; VerticalOverscan &= ~V_OVERSCAN_NO_BOTTOM_50; } } else if ( ( HblCounterVideo < pVideoTiming->VDE_Off_Line_Hi-1 ) /* 434 */ || ( ( HblCounterVideo == pVideoTiming->VDE_Off_Line_Hi-1 ) && ( LineCycles <= pVideoTiming->RemoveBottomBorder_Pos ) ) ) { if ( VerticalOverscan & V_OVERSCAN_NO_BOTTOM_50 ) { /* Bottom border already removed above, can't be changed now */ } else { nEndHBL = Bottom_Pos; } } } /* * Update the frequency's value at the different places where VBlank start/end position is checked * (check is done in Video_EndHBL) */ if ( ( HblCounterVideo < pVideoTiming->VBlank_Off_60_CheckLine ) || ( ( HblCounterVideo == pVideoTiming->VBlank_Off_60_CheckLine ) && ( LineCycles <= pVideoTiming->VBlank_CheckPos ) ) ) { ShifterFrame.VBLank_Off_60_CheckFreq = FreqHz; } if ( ( HblCounterVideo < pVideoTiming->VBlank_Off_50_CheckLine ) || ( ( HblCounterVideo == pVideoTiming->VBlank_Off_50_CheckLine ) && ( LineCycles <= pVideoTiming->VBlank_CheckPos ) ) ) { ShifterFrame.VBLank_Off_50_CheckFreq = FreqHz; } if ( ( HblCounterVideo < pVideoTiming->VBlank_On_60_CheckLine ) || ( ( HblCounterVideo == pVideoTiming->VBlank_On_60_CheckLine ) && ( LineCycles <= pVideoTiming->VBlank_CheckPos ) ) ) { ShifterFrame.VBLank_On_60_CheckFreq = FreqHz; } if ( ( HblCounterVideo < pVideoTiming->VBlank_On_50_CheckLine ) || ( ( HblCounterVideo == pVideoTiming->VBlank_On_50_CheckLine ) && ( LineCycles <= pVideoTiming->VBlank_CheckPos ) ) ) { ShifterFrame.VBLank_On_50_CheckFreq = FreqHz; } LOG_TRACE ( TRACE_VIDEO_BORDER_H , "video new V_DE %d<->%d VBlank_OFF %d<->%d H_DE %d<->%d shift=%d border=%x hbl_pos=%d cycles_line=%d video_hbl_w=%d\n" , nStartHBL , nEndHBL , ShifterFrame.VBlank_Off_Line , ShifterFrame.VBlank_On_Line , DE_start , DE_end , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift , BorderMask , HBL_Pos , nCyclesPerLine_new , HblCounterVideo ); /* Save new values */ ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = DE_start; ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = DE_end; ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask = BorderMask; } /*-----------------------------------------------------------------------*/ /** * Write to VideoSync (0xff820a), Hz setting */ void Video_Sync_WriteByte ( void ) { int FrameCycles, HblCounterVideo, LineCycles; uint8_t Freq; if ( bUseVDIRes ) return; /* no 50/60 Hz freq in VDI mode */ /* We're only interested in bit 1 (50/60Hz) */ Freq = IoMem[0xff820a] & 2; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); LOG_TRACE(TRACE_VIDEO_SYNC ,"sync=0x%2.2X video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n", Freq, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); /* Ignore consecutive writes of the same value */ if ( Freq == ShifterFrame.Freq ) return; /* do nothing */ /* Ignore freq changes if we are in high res */ /* 2009/04/26 : don't ignore for now (see ST Cnx in Punish Your Machine) */ // TODO in res part, update freq when switch to !high // if ( ShifterFrame.Res == 0x02 ) // return; /* do nothing */ Video_Update_Glue_State ( FrameCycles , HblCounterVideo , LineCycles , false ); /* TEMP for 'Gen4 Demo' by Ziggy / OVR in WS2,WS3,WS4 : */ /* top border is removed 4 cycles too late (due to double STOP instruction ?) and trigger a wrong "left+2" */ if ( ( STMemory_ReadLong ( 0xc000 ) == 0x69676779 ) /* "iggy" */ && ( M68000_GetPC() == 0x635e ) && ( STMemory_ReadLong ( M68000_GetPC() ) == 0x11fc0002 ) /* move.b #2 */ && ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_PLUS_2 ) ) { /* cancel a wrong left+2 */ LOG_TRACE(TRACE_VIDEO_BORDER_H , "cancel wrong left+2 gen4/ziggy\n" ); ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= ~BORDERMASK_LEFT_PLUS_2; ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = pVideoTiming->HDE_On_Low_50; ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = pVideoTiming->HDE_Off_Low_50; nCyclesPerLine = 512; } /* TEMP for 'Gen4 Demo' by Ziggy / OVR in WS2,WS3,WS4 : */ /* Store cycle position of freq 50/60 to check for top/bottom border removal in Video_EndHBL. */ ShifterFrame.Freq = Freq; if ( Freq == 0x02 ) /* 50 Hz */ { ShifterFrame.FreqPos50.VBL = nVBLs; ShifterFrame.FreqPos50.FrameCycles = FrameCycles; ShifterFrame.FreqPos50.HBL = HblCounterVideo; ShifterFrame.FreqPos50.LineCycles = LineCycles; } else /* 60 Hz */ { ShifterFrame.FreqPos60.VBL = nVBLs; ShifterFrame.FreqPos60.FrameCycles = FrameCycles; ShifterFrame.FreqPos60.HBL = HblCounterVideo; ShifterFrame.FreqPos60.LineCycles = LineCycles; } } /*-----------------------------------------------------------------------*/ /** * Compute the default cycle position where the HBL should happen on each line. * In low/med res, the position depends on the video frequency (50/60 Hz) * In high res, the position is always the same. * This position can change later when switching between hi res / 50 Hz / 60 Hz * * The position is measured for a CPU running at 8 MHz, it should be converted * to the correct number of cycles when using higher CPU speed (16 or 32 MHz) */ static int Video_HBL_GetDefaultPos ( void ) { int Pos; if ( ( IoMem_ReadByte ( 0xff8260 ) & 3 ) == 2 ) /* hi res */ Pos = pVideoTiming->Hbl_Int_Pos_Hi; else /* low res or med res */ { if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz, pos 512 */ Pos = pVideoTiming->Hbl_Int_Pos_Low_50; else /* 60 Hz, pos 508 */ Pos = pVideoTiming->Hbl_Int_Pos_Low_60; } return Pos; } /** * Return the current HBL position (depending on the latest switch * to hi res / 50 Hz / 60 Hz) */ static int Video_HBL_GetCurrentPos ( void ) { return ShifterFrame.HBL_CyclePos; } /*-----------------------------------------------------------------------*/ /** * Compute the cycle position where the timer B should happen on each * visible line. * We compute Timer B position for the given LineNumber, using start/end * display cycles from ShifterLines[ LineNumber ]. * The position depends on the start of line / end of line positions * (which depend on the current frequency / border tricks) and * on the value of the bit 3 in the MFP's AER. * If bit is 0, timer B will count end of line events (usual case), * but if bit is 1, timer B will count start of line events (eg Seven Gates Of Jambala) * * The position is measured for a CPU running at 8 MHz, it should be converted * to the correct number of cycles when using higher CPU speed (16 or 32 MHz) */ static int Video_TimerB_GetPosFromDE ( int DE_start , int DE_end ) { int Pos; if ( ( IoMem[0xfffa03] & ( 1 << 3 ) ) == 0 ) /* we're counting end of line events */ Pos = DE_end + TIMERB_VIDEO_CYCLE_OFFSET; else /* we're counting start of line events */ Pos = DE_start + TIMERB_VIDEO_CYCLE_OFFSET; return Pos; } int Video_TimerB_GetPos ( int LineNumber ) { int Pos; Pos = Video_TimerB_GetPosFromDE ( ShifterFrame.ShifterLines[ LineNumber ].DisplayStartCycle , ShifterFrame.ShifterLines[ LineNumber ].DisplayEndCycle ); //fprintf ( stderr , "timerb pos=%d\n" , Pos ); return Pos; } /*-----------------------------------------------------------------------*/ /** * Compute the default cycle position where the timer B should happen * on the next line, when restarting the INTERRUPT_VIDEO_ENDLINE handler. * In low/med res, the position depends on the video frequency (50/60 Hz) * In high res, the position is always the same. */ // TODO : use Video_TimerB_GetPosFromDE and Video_AddInterruptTimerB static int Video_TimerB_GetDefaultPos ( void ) { int Pos; if ( ( IoMem[0xfffa03] & ( 1 << 3 ) ) == 0 ) /* we're counting end of line events */ { if ( ( IoMem_ReadByte ( 0xff8260 ) & 3 ) == 2 ) /* hi res */ Pos = LINE_END_CYCLE_71; else /* low res or med res */ { if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz, pos 376 */ Pos = LINE_END_CYCLE_50; else /* 60 Hz, pos 372 */ Pos = LINE_END_CYCLE_60; } } else /* we're counting start of line events */ { if ( ( IoMem_ReadByte ( 0xff8260 ) & 3 ) == 2 ) /* hi res */ Pos = LINE_START_CYCLE_71; else /* low res or med res */ { if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz, pos 56 */ Pos = LINE_START_CYCLE_50; else /* 60 Hz, pos 52 */ Pos = LINE_START_CYCLE_60; } } Pos += TIMERB_VIDEO_CYCLE_OFFSET; //fprintf ( stderr , "timerb default pos=%d\n" , Pos ); return Pos; } /*-----------------------------------------------------------------------*/ /** * HBL interrupt : this occurs at the end of every line, on cycle 512 (in 50 Hz) * It takes 56 cycles to handle the 68000's exception. */ void Video_InterruptHandler_HBL ( void ) { int FrameCycles , HblCounterVideo , LineCycles; int PendingCyclesOver; int NewHBLPos; #ifdef OLD_GET_POS_FORCE_INC if ( CycInt_From_Opcode ) /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ { Video_GetPosition_ForceInc = currcycle / 256; // fprintf ( stderr , "Video_InterruptHandler_HBL from opcode currcycle=%d add=%d\n" , currcycle , Video_GetPosition_ForceInc ); } #endif Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* How many cycle was this HBL delayed (>= 0) */ PendingCyclesOver = -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); PendingCyclesOver <<= nCpuFreqShift; /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Handle the intermediate HBL interrupt used to set vsync and restart video counter on HBL 310 or 260 */ if ( RestartVideoCounter ) { // fprintf ( stderr , "restart video counter check nhbl=%d hbl=%d cyc=%d\n" , nHBL , HblCounterVideo , LineCycles); if ( ( ( ( IoMem_ReadByte ( 0xff820a ) & 2 ) == 2 ) && ( nHBL == pVideoTiming->RestartVideoCounter_Line_50 ) ) || ( ( ( IoMem_ReadByte ( 0xff820a ) & 2 ) == 0 ) && ( nHBL == pVideoTiming->RestartVideoCounter_Line_60 ) && ( nScanlinesPerFrame == SCANLINES_PER_FRAME_60HZ ) ) ) { ShifterFrame.VSync_signal = VSYNC_SIGNAL_ON; if ( ShifterFrame.VBlank_signal != VBLANK_SIGNAL_ON ) { ShifterFrame.VBlank_signal = VBLANK_SIGNAL_ON; /* On real HW, vsync also sets vblank */ ShifterFrame.VBlank_On_Line = nHBL; } LOG_TRACE ( TRACE_VIDEO_BORDER_V , "HBL %d cyc=%d detect vsync=on (force vblank=on)\n", nHBL, LineCycles ); Video_RestartVideoCounter(); LOG_TRACE(TRACE_VIDEO_HBL, "HBL %d cyc=%d restart video counter 0x%x\n", nHBL, LineCycles, VideoBase ); } /* Restore the normal HBL interrupt on the same line */ Video_AddInterruptHBL ( nHBL , Video_HBL_GetCurrentPos() ); RestartVideoCounter = false; return; } LOG_TRACE ( TRACE_VIDEO_HBL , "HBL %d video_cyc=%d pending_cyc=%d\n" , nHBL , FrameCycles , PendingCyclesOver ); /* Print traces if pending HBL bit changed just before IACK when HBL interrupt is allowed */ if ( ( CPU_IACK == true ) && ( regs.intmask < 2 ) ) { if ( pendingInterrupts & ( 1 << 2 ) ) { LOG_TRACE ( TRACE_VIDEO_HBL , "HBL %d, pending set again just before iack, skip one HBL interrupt VBL=%d video_cyc=%d pending_cyc=%d\n" , nHBL , nVBLs , FrameCycles , PendingCyclesOver ); } else { LOG_TRACE ( TRACE_VIDEO_HBL , "HBL %d, new pending HBL set just before iack VBL=%d video_cyc=%d pending_cyc=%d\n" , nHBL , nVBLs , FrameCycles , PendingCyclesOver ); } } /* Set pending bit for HBL interrupt in the CPU IPL */ M68000_Exception(EXCEPTION_NR_HBLANK , M68000_EXC_SRC_AUTOVEC); /* Horizontal blank interrupt, level 2 */ if (Config_IsMachineFalcon()) { VIDEL_VideoRasterHBL(); } else if (Config_IsMachineTT()) { Video_TT_RasterHBL(); } else { Video_EndHBL(); /* Check some borders removal and copy line to display buffer */ } DmaSnd_STE_HBL_Update(); /* Update STE/TT DMA sound if needed */ /* TEMP IPF */ IPF_Emulate(); /* TEMP IPF */ nHBL++; /* Increase HBL count */ if (nHBL < nScanlinesPerFrame) { /* Update start cycle for next HBL : start for previous HBL + number of cycles for previous line */ //fprintf ( stderr , "hbl %llu\n" , CyclesGlobalClockCounter ); //if ( FrameCycles - PendingCyclesOver != ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle + nCyclesPerLine ) // fprintf ( stderr , "mismatch StartCycle hbl %d : %d != %d\n" , nHBL , FrameCycles - PendingCyclesOver , ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle + nCyclesPerLine ); ShifterFrame.ShifterLines[ nHBL ].StartCycle = ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle + nCyclesPerLine; LOG_TRACE(TRACE_VIDEO_HBL, "HBL %d start=%d %x\n", nHBL, ShifterFrame.ShifterLines[nHBL].StartCycle, ShifterFrame.ShifterLines[nHBL].StartCycle); /* Setup next HBL */ Video_StartHBL(); /* Default cycle position for next HBL */ NewHBLPos = Video_HBL_GetDefaultPos(); ShifterFrame.HBL_CyclePos = NewHBLPos; //fprintf ( stderr , "NewHBLPos %d\n", NewHBLPos ); Video_AddInterruptHBL ( nHBL , NewHBLPos ); /* Add new VBL interrupt just after the last HBL (for example : VblVideoCycleOffset cycles after end of HBL 312 at 50 Hz) */ /* We setup VBL one HBL earlier (eg at nHBL=312 instead of 313) to be sure we don't miss it */ /* in case last HBL would be delayed too much (by more than VblVideoCycleOffset cycles) */ if (nHBL == nScanlinesPerFrame-1) { int CyclesToVbl = ShifterFrame.ShifterLines[nHBL].StartCycle + nCyclesPerLine + ( pVideoTiming->VblVideoCycleOffset << nCpuFreqShift ) - FrameCycles; CycInt_AddRelativeInterrupt( CyclesToVbl, INT_CPU_CYCLE, INTERRUPT_VIDEO_VBL); } } /* Check if video counter should be restarted on this HBL */ if ( RestartVideoCounter ) { //fprintf ( stderr , "restart video counter nhbl=%d hbl=%d cyc=%d restart_cyc=%d\n" , nHBL , HblCounterVideo , LineCycles, pVideoTiming->RestartVideoCounter_Pos); /* If HBL was delayed after RestartVideoCounter_Pos, we can restart immediately if we have */ /* the correct freq/hbl combination (we need to check nHBL == HblCounterVideo for the WS1 case where LineCycles can be 508) */ if ( ( nHBL == HblCounterVideo ) && ( LineCycles >= pVideoTiming->RestartVideoCounter_Pos ) ) { if ( ( ( ( IoMem_ReadByte ( 0xff820a ) & 2 ) == 2 ) && ( nHBL == pVideoTiming->RestartVideoCounter_Line_50 ) ) || ( ( ( IoMem_ReadByte ( 0xff820a ) & 2 ) == 0 ) && ( nHBL == pVideoTiming->RestartVideoCounter_Line_60 ) ) ) { Video_RestartVideoCounter(); LOG_TRACE(TRACE_VIDEO_HBL, "HBL %d cyc=%d restart video counter 0x%x (immediate)\n", nHBL, LineCycles, VideoBase ); } RestartVideoCounter = false; } /* HBL was not delayed after RestartVideoCounter_Pos, so we set an intermediate HBL interrupt */ /* This intermediate HBL interrupt will then set the real HBL interrupt at the end of the line */ else { Video_AddInterruptHBL ( nHBL , pVideoTiming->RestartVideoCounter_Pos ); } } #ifdef OLD_GET_POS_FORCE_INC Video_GetPosition_ForceInc = 0; /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ #endif } /*-----------------------------------------------------------------------*/ /** * Check at end of each HBL to see if any Shifter hardware tricks have been attempted * and copy the line to the screen buffer. * This is the place to check if top/bottom border were removed, as well as if some * left/right border changes were not validated before. * NOTE : the tests must be made with nHBL in ascending order. */ static void Video_EndHBL(void) { // // Handle top/bottom borders removal when switching freq // Detection is made in Video_Update_Glue_State() so we just print some logs // if ( ( nHBL == nStartHBL + BlankLines - 1 ) && ( VerticalOverscan & V_OVERSCAN_NO_TOP ) ) { /* 50 Hz screen where first line is before 50 Hz position -> top border removed */ LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect remove top\n" ); pHBLPaletteMasks -= OVERSCAN_TOP; // FIXME useless ? pHBLPalettes -= OVERSCAN_TOP; // FIXME useless ? } else if ( ( nHBL == pVideoTiming->VDE_On_Line_50 - 1 ) && ( VerticalOverscan & V_OVERSCAN_NO_DE ) ) { /* 50 Hz screen where freq != 50 Hz at HBL 62 cycle 502 -> vertical DE stays OFF for this screen */ LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect no V_DE screen\n" ); } else if ( ( nHBL == pVideoTiming->VDE_Off_Line_50 + BlankLines - 1 ) && ( VerticalOverscan & V_OVERSCAN_NO_BOTTOM_50 ) ) { /* 50 Hz screen where freq != 50 Hz on last line -> bottom border removed */ LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect remove bottom\n" ); } else if ( ( nHBL == pVideoTiming->VDE_Off_Line_60 + BlankLines - 1 ) && ( VerticalOverscan & V_OVERSCAN_NO_BOTTOM_60 ) ) { /* 60 Hz screen where freq != 60 Hz on last line -> bottom border removed */ LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect remove bottom 60Hz\n" ); } else if ( ( nHBL == pVideoTiming->VDE_Off_Line_60 + BlankLines - 1 ) && ( VerticalOverscan & V_OVERSCAN_BOTTOM_SHORT_50 ) ) { /* 50 Hz screen ending at 60 Hz screen's position -> short bottom border (-29 lines) */ LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect short bottom border\n" ); } /* Update VBlank signal */ if ( nHBL == pVideoTiming->VBlank_Off_60_CheckLine ) { if ( ShifterFrame.VBLank_Off_60_CheckFreq == VIDEO_60HZ ) { ShifterFrame.VBlank_signal = VBLANK_SIGNAL_OFF; ShifterFrame.VBlank_Off_Line = nHBL+1; LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect vblank=off 60Hz\n" ); } else if ( nScreenRefreshRate == VIDEO_60HZ ) LOG_TRACE ( TRACE_VIDEO_BORDER_V , "ignore vblank=off 60Hz\n" ); } else if ( nHBL == pVideoTiming->VBlank_Off_50_CheckLine ) { if ( ShifterFrame.VBLank_Off_50_CheckFreq == VIDEO_50HZ ) { ShifterFrame.VBlank_signal = VBLANK_SIGNAL_OFF; ShifterFrame.VBlank_Off_Line = nHBL+1; LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect vblank=off 50Hz\n" ); } else if ( nScreenRefreshRate == VIDEO_50HZ ) LOG_TRACE ( TRACE_VIDEO_BORDER_V , "ignore vblank=off 50Hz\n" ); } else if ( nHBL == pVideoTiming->VBlank_On_60_CheckLine ) { if ( ShifterFrame.VBLank_On_60_CheckFreq == VIDEO_60HZ ) { ShifterFrame.VBlank_signal = VBLANK_SIGNAL_ON; ShifterFrame.VBlank_On_Line = nHBL+1; LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect vblank=on 60Hz\n" ); } else if ( nScreenRefreshRate == VIDEO_60HZ ) LOG_TRACE ( TRACE_VIDEO_BORDER_V , "ignore vblank=on 60Hz\n" ); } else if ( nHBL == pVideoTiming->VBlank_On_50_CheckLine ) { if ( ShifterFrame.VBLank_On_50_CheckFreq == VIDEO_50HZ ) { ShifterFrame.VBlank_signal = VBLANK_SIGNAL_ON; ShifterFrame.VBlank_On_Line = nHBL+1; LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect vblank=on 50Hz\n" ); } else if ( nScreenRefreshRate == VIDEO_50HZ ) LOG_TRACE ( TRACE_VIDEO_BORDER_V , "ignore vblank=on 50Hz\n" ); } /* Store palette for very first line on screen - HBLPalettes[0] */ if (nHBL == nFirstVisibleHbl-1) { /* Store ALL palette for this line into raster table for datum */ Video_StoreFirstLinePalette(); } if (bUseHighRes) { /* Copy for hi-res (no overscan) */ if (nHBL >= nFirstVisibleHbl && nHBL < nLastVisibleHbl) Video_CopyScreenLineMono(); } /* Are we in possible visible color display (including borders)? */ else if (nHBL >= nFirstVisibleHbl && nHBL < nLastVisibleHbl) { /* Update resolution at the end of the line to check for mix low/med screens */ Video_StoreResolution(nHBL-nFirstVisibleHbl , false); /* Copy line of screen to buffer to simulate TV raster trace * - required for mouse cursor display/game updates * Eg, Lemmings and The Killing Game Show are good examples */ Video_CopyScreenLineColor(); } } /*-----------------------------------------------------------------------*/ /** * Set default values for the next HBL, depending on the current res/freq. * We set the number of cycles per line, as well as some default values * for display start/end cycle. */ static void Video_StartHBL(void) { RestartVideoCounter = false; if ((IoMem_ReadByte(0xff8260) & 3) == 2) /* hi res */ { nCyclesPerLine = CYCLES_PER_LINE_71HZ; if ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle == -1 ) /* start not set yet */ ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = pVideoTiming->HDE_On_Hi; ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = pVideoTiming->HDE_Off_Hi; /* If the whole screen is not in 71 Hz, then this HBL will default to "left off" */ if ( nScreenRefreshRate != VIDEO_71HZ ) { ShifterFrame.ShifterLines[ nHBL ].BorderMask |= BORDERMASK_LEFT_OFF; ShifterFrame.ShifterLines[ nHBL ].DisplayPixelShift = -4; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left %d<->%d\n" , ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle ); } } else { if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz */ { nCyclesPerLine = CYCLES_PER_LINE_50HZ; if ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle == -1 ) /* start not set yet */ ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = pVideoTiming->HDE_On_Low_50; ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = pVideoTiming->HDE_Off_Low_50; } else /* 60 Hz */ { nCyclesPerLine = CYCLES_PER_LINE_60HZ; if ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle == -1 ) /* start not set yet */ ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = pVideoTiming->HDE_On_Low_60; ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = pVideoTiming->HDE_Off_Low_60; /* If the whole screen is in 50 Hz, then this HBL will default to "left+2" + "right-2" (ie 60 Hz line) */ if ( nScreenRefreshRate == VIDEO_50HZ ) { ShifterFrame.ShifterLines[ nHBL ].BorderMask |= ( BORDERMASK_LEFT_PLUS_2 | BORDERMASK_RIGHT_MINUS_2 ) ; LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 / right-2 60Hz %d<->%d\n" , ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle ); } } /* Handle accurate restart of video counter only in low/med res */ if ( ( nHBL == pVideoTiming->RestartVideoCounter_Line_50 ) || ( nHBL == pVideoTiming->RestartVideoCounter_Line_60 ) ) RestartVideoCounter = true; } nCyclesPerLine <<= nCpuFreqShift; //fprintf (stderr , "Video_StartHBL %d %d %d\n", nHBL , ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle ); if (nHBL >= nFirstVisibleHbl && nHBL < nLastVisibleHbl) { /* Store resolution at the beginning of the line */ Video_StoreResolution(nHBL-nFirstVisibleHbl , true); } } /*-----------------------------------------------------------------------*/ /** * End Of Line interrupt * This interrupt is started on cycle position 404 in 50 Hz and on cycle * position 400 in 60 Hz. 50 Hz display ends at cycle 376 and 60 Hz displays * ends at cycle 372. This means the EndLine interrupt happens 24 cycles * after DisplayEndCycle. * Note that if bit 3 of MFP AER is 1, then timer B will count start of line * instead of end of line (at cycle 52+24 or 56+24) */ void Video_InterruptHandler_EndLine(void) { int FrameCycles, HblCounterVideo, LineCycles; int PendingCycles = -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); #ifdef OLD_GET_POS_FORCE_INC if ( CycInt_From_Opcode ) /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ { Video_GetPosition_ForceInc = currcycle / 256; // fprintf ( stderr , "Video_InterruptHandler_EndLine from opcode currcycle=%d add=%d\n" , currcycle , Video_GetPosition_ForceInc ); } #endif Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); LOG_TRACE ( TRACE_VIDEO_HBL , "EndLine TB %d video_cyc=%d line_cyc=%d pending_int_cnt=%d\n" , nHBL , FrameCycles , LineCycles , PendingCycles ); /* Remove this interrupt from list and re-order */ CycInt_AcknowledgeInterrupt(); /* Ignore HBLs in VDI mode */ if (bUseVDIRes) return; /* Generate new Endline, if need to - there are 313 HBLs per frame */ if (nHBL < nScanlinesPerFrame-1) { /* By default, next EndLine's int will be on line nHBL+1 at pos 376+24 or 372+24 */ if ( ( IoMem[0xfffa03] & ( 1 << 3 ) ) == 0 ) /* count end of line */ { /* If EndLine int is delayed too much (more than 100 cycles), nLineCycles will */ /* be in the range 0..xxx instead of 400..512. In that case, we need to add */ /* nCyclesPerLine to be in the range 512..x+512 */ /* Maximum possible delay should be around 160 cycles on STF (DIVS) */ /* In that case, HBL int will be delayed too, so we will have HblCounterVideo == nHBL+1 */ if ( HblCounterVideo == nHBL+1 ) /* int happened in fact on the next line nHBL+1 */ LineCycles += nCyclesPerLine; LineTimerBPos = Video_TimerB_GetDefaultPos (); } else /* count start of line, no possible delay to handle */ { LineTimerBPos = Video_TimerB_GetDefaultPos (); } // TODO : use Video_AddInterruptTimerB //fprintf ( stderr , "new tb %d %d %d\n" , LineTimerBPos , nCyclesPerLine , (LineTimerBPos<= nStartHBL ) && ( nHBL < nEndHBL + BlankLines ) && ( ( VerticalOverscan & V_OVERSCAN_NO_DE ) == 0 ) ) { /* Handle Timer B when using Event Count mode */ /* We must ensure that the write to fffa1b to activate timer B was */ /* completed before the point where the end of line signal was generated */ /* (in the case of a move.b #8,$fffa1b that would happen 4 cycles */ /* before end of line, the interrupt should not be generated) */ if ( ( TimerBEventCountCycleStart == -1 ) /* timer B was started during a previous VBL */ || ( TimerBEventCountCycleStart < FrameCycles-PendingCycles ) ) /* timer B was started before this possible interrupt */ { MFP_TimerB_EventCount ( pMFP_Main , PendingCycles ); /* Update events count / interrupt for timer B if needed */ if ( Config_IsMachineTT() ) MFP_TimerB_EventCount ( pMFP_TT , PendingCycles ); /* DE signal is also connected to timer B on the TT MFP */ } } #ifdef OLD_GET_POS_FORCE_INC Video_GetPosition_ForceInc = 0; /* TEMP : to update CYCLES_COUNTER_VIDEO during an opcode */ #endif } /*-----------------------------------------------------------------------*/ /** * Store whole palette on first line so have reference to work from */ static void Video_StoreFirstLinePalette(void) { uint16_t *pp2; int i; pp2 = (uint16_t *)&IoMem[0xff8240]; for (i = 0; i < 16; i++) { HBLPalettes[i] = SDL_SwapBE16(*pp2++); if (Config_IsMachineST()) HBLPalettes[i] &= 0x777; /* Force unused "random" bits to 0 */ } /* And set mask flag with palette and resolution */ // FIXME ; enlever PALETTEMASK_RESOLUTION // if ( ShifterFrame.ShifterLines[ nFirstVisibleHbl ].BorderMask == BORDERMASK_NONE ) // no border trick, store the current res HBLPaletteMasks[0] = (PALETTEMASK_RESOLUTION|PALETTEMASK_PALETTE) | (((uint32_t)IoMem_ReadByte(0xff8260)&0x3)<<16); // else // border removal, assume low res for the whole line // HBLPaletteMasks[0] = (PALETTEMASK_RESOLUTION|PALETTEMASK_PALETTE) | (0<<16); } /*-----------------------------------------------------------------------*/ /** * Store resolution on each line (used to test if mixed low/medium resolutions) * This functions is called twice per line : * - during Video_StartHBL (start=true) to set the default resolution when * starting a new line * - during Video_EndHBL (start=false) to update the resolution before rendering * the line on the screen (in case some border removals were used during this line) */ static void Video_StoreResolution(int y , bool start) { uint8_t res; int Mask; /* Clear resolution, and set with current value */ if (!(bUseHighRes || bUseVDIRes)) { if ( y >= HBL_PALETTE_MASKS ) /* we're above the limit (res was switched to mono for more than 1 VBL in color mode ?) */ { // fprintf ( stderr , "store res %d line %d hbl %d %x %x %d\n" , res , y , nHBL, Mask , HBLPaletteMasks[y] , sizeof(HBLPalettes) ); y = HBL_PALETTE_MASKS - 1; /* store in the last palette line */ } if ( start ) /* Just store current resolution */ res = IoMem_ReadByte(0xff8260)&0x3; else /* Check if resolution needs to be forced to low/med */ { res = ( HBLPaletteMasks[y] >> 16 ) & 0x3; Mask = ShifterFrame.ShifterLines[ y+nFirstVisibleHbl ].BorderMask; if ( ( Mask & BORDERMASK_OVERSCAN_MED_RES ) /* special case for med res to render the overscan line */ || ( Mask & BORDERMASK_LEFT_OFF_2_STE_MED ) ) res = 1; /* med res instead of low res */ else if ( Mask != BORDERMASK_NONE ) /* border removal : assume low res for the whole line */ res = 0; } HBLPaletteMasks[y] &= ~(0x3<<16); HBLPaletteMasks[y] |= PALETTEMASK_RESOLUTION|((uint32_t)res)<<16; // fprintf ( stderr , "store res %d line %d %x %x\n" , res , y , Mask , HBLPaletteMasks[y] ); } } /*-----------------------------------------------------------------------*/ /** * To copy shifter data to the emulated screen, some cases require * to use a more complex version than a simple memcpy. * * For example when the MMU configuration at $FF8001 doesn't match * the physical RAM size on STF/STE we need to copy 1 word at a time * after applying a translation on the address * * NOTE : these functions expect the number of bytes 'n' to copy to be even * NOTE : very few programs require to use video_memcpy_mmu(), so the performance impact * on emulation will be minimal in most cases. So far, only demo known to need this * mode is 'Ika I Compofylla' by Newline */ void Video_Set_Memcpy ( bool Force_MMU_Translation ) { //fprintf ( stderr , "Video_Set_Memcpy Force_MMU_Translation=%d\n" , Force_MMU_Translation ); if ( Force_MMU_Translation ) video_memcpy = video_memcpy_mmu; else video_memcpy = video_memcpy_direct; } /* Use a slower/more accurate rendering where each word is dynamically read from memory */ /* (this is used to apply MMU translation and when video address points after end of RAM) */ static void* video_memcpy_mmu ( uint8_t *dest , uint8_t *src , int n ) { int i; /* We must keep the src video address in a 22 or 24 bit space depending on the machine type */ uint32_t VideoMask = Video_GetAddrMask(); for ( i=0 ; i= 0x1000000) */ uint32_t VideoMask = Video_GetAddrMask(); /* Copy one line - 80 bytes in ST high resolution */ video_memcpy ( pSTScreen, pVideoRaster, SCREENBYTES_MONOLINE ); pVideoRaster += SCREENBYTES_MONOLINE; /* Handle STE fine scrolling (HWScrollCount is zero on ST). */ if (HWScrollCount) { uint16_t *pScrollAdj; int nNegScrollCnt; pScrollAdj = (uint16_t *)pSTScreen; nNegScrollCnt = 16 - HWScrollCount; /* Shift the whole line by the given scroll count */ while ((uint8_t*)pScrollAdj < pSTScreen + SCREENBYTES_MONOLINE-2) { do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount) | (do_get_mem_word(pScrollAdj+1) >> nNegScrollCnt)); ++pScrollAdj; } /* Handle the last 16 pixels of the line */ do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount) | (do_get_mem_word(pVideoRaster) >> nNegScrollCnt)); /* HW scrolling advances Shifter video counter by one */ pVideoRaster += 1 * 2; } /* LineWidth is zero on ST. */ /* On STE, the Shifter skips the given amount of words. */ pVideoRaster += LineWidth*2; /* On STE, handle modifications of the video counter address $ff8205/07/09 */ /* that occurred while the display was already ON */ if ( VideoCounterDelayedOffset != 0 ) { pVideoRaster += ( VideoCounterDelayedOffset & ~1 ); VideoCounterDelayedOffset = 0; } if ( pVideoRasterDelayed != NULL ) { pVideoRaster = pVideoRasterDelayed; pVideoRasterDelayed = NULL; } /* On STE, if we wrote to the hwscroll register, we set the */ /* new value here, once the current line was processed */ if ( NewHWScrollCount >= 0 ) { HWScrollCount = NewHWScrollCount; NewHWScrollCount = -1; } /* On STE, if we wrote to the linewidth register, we set the */ /* new value here, once the current line was processed */ if ( NewLineWidth >= 0 ) { LineWidth = NewLineWidth; NewLineWidth = -1; } /* Each screen line copied to buffer is always same length */ pSTScreen += SCREENBYTES_MONOLINE; /* We must keep the new video address in a 22 or 24 bit space depending on the machine type */ /* (for example in case it pointed to IO space and is now >= 0x1000000) */ pVideoRaster = ( ( pVideoRaster - STRam ) & VideoMask ) + STRam; //fprintf ( stderr , "video counter new=%x\n" , pVideoRaster-STRam ); } /*-----------------------------------------------------------------------*/ /** * Copy one line of color screen into buffer for conversion later. * Possible lines may be top/bottom border, and/or left/right borders. */ static void Video_CopyScreenLineColor(void) { int LineBorderMask; int VideoOffset = 0; int STF_PixelScroll = 0; int LineRes; uint8_t *pVideoRasterEndLine; /* addr of the last byte copied from pVideoRaster to pSTScreen (for HWScrollCount) */ int i; uint32_t VideoMask; LineBorderMask = ShifterFrame.ShifterLines[ nHBL ].BorderMask; STF_PixelScroll = ShifterFrame.ShifterLines[ nHBL ].DisplayPixelShift; /* We must keep the new video address in a 22 or 24 bit space depending on the machine type */ /* (for example in case it pointed to IO space and is now >= 0x1000000) */ VideoMask = Video_GetAddrMask(); /* Get resolution for this line (in case of mixed low/med screen) */ i = nHBL-nFirstVisibleHbl; if ( i >= HBL_PALETTE_MASKS ) i = HBL_PALETTE_MASKS - 1; LineRes = ( HBLPaletteMasks[i] >> 16 ) & 1; /* 0=low res 1=med res */ //fprintf(stderr , "copy line %d start %d end %d 0x%x 0x%x\n" , nHBL, nStartHBL, nEndHBL, LineBorderMask, pVideoRaster - STRam); /* If a line is left+2 / right-2 but the whole screen is in 60 Hz, then it's a normal 60 Hz line, */ /* not a 50 Hz line with different borders */ if ( ( nScreenRefreshRate == VIDEO_60HZ ) && ( ( LineBorderMask & ( BORDERMASK_LEFT_PLUS_2 | BORDERMASK_RIGHT_MINUS_2 ) ) == ( BORDERMASK_LEFT_PLUS_2 | BORDERMASK_RIGHT_MINUS_2 ) ) ) { LineBorderMask &= ~( BORDERMASK_LEFT_PLUS_2 | BORDERMASK_RIGHT_MINUS_2 ); LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel left+2 / right-2, normal 60Hz line on 60 Hz screen %d<->%d\n" , ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle ); } /* FIXME [NP] : when removing left border and displaying med res at 60 Hz on STE, we have a 3 pixel shift */ /* to correct to have bitmaps and color changes in sync. */ /* For now we only shift for med @ 60 Hz, but this should be measured for all */ /* freq and low / med res combinations on a real STE (fix "HighResMode" demo by Paradox). */ if ( Config_IsMachineSTE() && ( LineBorderMask & BORDERMASK_LEFT_OFF_MED ) && ( nCyclesPerLine == 508 ) ) { STF_PixelScroll = 3; } /* If left border is opened, we need to compensate one missing word in low res (1 plan) */ /* If overscan is in med res, the offset is variable */ if ( LineBorderMask & BORDERMASK_OVERSCAN_MED_RES ) VideoOffset = - ( ( LineBorderMask >> 20 ) & 0x0f ); /* No Cooper=0 PYM=-2 in med res overscan */ else if ( LineBorderMask & BORDERMASK_LEFT_OFF ) { int ShiftPixels = 0; if ( STF_PixelScroll == 13 ) { VideoOffset = 2; ShiftPixels = 8; } else if ( STF_PixelScroll == 9 ) { VideoOffset = 0; ShiftPixels = 8; } else if ( STF_PixelScroll == 5 ) { VideoOffset = -2; ShiftPixels = 8; } else if ( STF_PixelScroll == 1 ) { VideoOffset = -4; ShiftPixels = 8; } else VideoOffset = -2; /* Normal low res left border removal without 4 pixels scrolling */ STF_PixelScroll -= ShiftPixels; } else if ( ( LineBorderMask & BORDERMASK_LEFT_OFF_2_STE ) || ( LineBorderMask & BORDERMASK_LEFT_OFF_2_STE_MED ) ) VideoOffset = -4; /* 4 first bytes of the line are not shown */ /* Handle 4 pixels hardware scrolling ('ST Cnx' demo in 'Punish Your Machine') */ /* as well as 'remove left + med stab' ('Closure' demo Troed/Sync) */ /* Depending on the number of pixels, we need to compensate for some skipped words */ else if ( LineBorderMask & BORDERMASK_LEFT_OFF_MED ) { /* TEMP for 'Closure' in STE : planes are shifted and pixels are not aligned */ if ( Config_IsMachineSTE() && ( STF_PixelScroll == 0 ) ) { VideoOffset = -6; STF_PixelScroll -= 10; /* FIXME : should be measured on real STE */ } /* TEMP for 'Closure' in STE : planes are shifted and pixels are not aligned */ else { if ( STF_PixelScroll == 13 ) VideoOffset = 2; else if ( STF_PixelScroll == 9 ) VideoOffset = 0; else if ( STF_PixelScroll == 5 ) VideoOffset = -2; else if ( STF_PixelScroll == 1 ) VideoOffset = -4; else if ( STF_PixelScroll == 0 ) VideoOffset = -4; /* 'Closure' in STF : no 4 pixels scroll, but planes are shifted */ else VideoOffset = 0; /* never used ? */ STF_PixelScroll -= 8; /* removing left border in med res also shifts display to the left */ } // fprintf(stderr , "scr off %d %d\n" , STF_PixelScroll , VideoOffset); } /* Is total blank line? I.e. top/bottom border or V_DE is not activated */ /* TODO [NP] in that case we fill the line with byte 0x00, which will give a line with color 0, */ /* but this should be improved to really display a black line (requires changes in screen.c convert functions) */ if ((nHBL < nStartHBL) || (nHBL >= nEndHBL + BlankLines) || (LineBorderMask & ( BORDERMASK_EMPTY_LINE|BORDERMASK_NO_DE ) ) || ( ShifterFrame.VBlank_signal && ( nHBL >= ShifterFrame.VBlank_On_Line ) ) || ( VerticalOverscan & V_OVERSCAN_NO_DE ) ) { /* Clear line to color '0' */ memset(pSTScreen, 0, SCREENBYTES_LINE); } else { /* Does have left border ? */ if ( LineBorderMask & ( BORDERMASK_LEFT_OFF | BORDERMASK_LEFT_OFF_MED ) ) /* bigger line by 26 bytes on the left */ { pVideoRaster += BORDERBYTES_LEFT-SCREENBYTES_LEFT+VideoOffset; video_memcpy ( pSTScreen, pVideoRaster, SCREENBYTES_LEFT ); pVideoRaster += SCREENBYTES_LEFT; } else if ( ( LineBorderMask & BORDERMASK_LEFT_OFF_2_STE ) /* bigger line by 20 bytes on the left (STE specific) */ || ( LineBorderMask & BORDERMASK_LEFT_OFF_2_STE_MED ) ) { /* bytes 0-3 are not shown, only next 16 bytes (32 pixels, 4 bitplanes) */ if ( SCREENBYTES_LEFT > BORDERBYTES_LEFT_2_STE ) { memset ( pSTScreen, 0, SCREENBYTES_LEFT-BORDERBYTES_LEFT_2_STE+4 ); /* clear unused pixels + bytes 0-3 */ video_memcpy ( pSTScreen+SCREENBYTES_LEFT-BORDERBYTES_LEFT_2_STE+4, pVideoRaster+VideoOffset+4, BORDERBYTES_LEFT_2_STE-4 ); } else video_memcpy ( pSTScreen, pVideoRaster+BORDERBYTES_LEFT_2_STE-SCREENBYTES_LEFT+VideoOffset, SCREENBYTES_LEFT ); pVideoRaster += BORDERBYTES_LEFT_2_STE+VideoOffset; } else if (LineBorderMask & BORDERMASK_LEFT_PLUS_2) /* bigger line by 2 bytes on the left */ { if ( SCREENBYTES_LEFT > 2 ) { memset ( pSTScreen,0,SCREENBYTES_LEFT-2 ); /* clear unused pixels */ video_memcpy ( pSTScreen+SCREENBYTES_LEFT-2, pVideoRaster, 2 ); } else { /* nothing to copy, left border is not large enough */ } pVideoRaster += 2; } else if (bSteBorderFlag) /* bigger line by 8 bytes on the left (STE specific) */ { if ( SCREENBYTES_LEFT > 4*2 ) { memset ( pSTScreen,0,SCREENBYTES_LEFT-4*2 ); /* clear unused pixels */ video_memcpy ( pSTScreen+SCREENBYTES_LEFT-4*2, pVideoRaster, 4*2 ); } else { /* nothing to copy, left border is not large enough */ } pVideoRaster += 4*2; } else memset(pSTScreen,0,SCREENBYTES_LEFT); /* left border not removed, clear to color '0' */ /* Short line due to hires in the middle ? */ if (LineBorderMask & BORDERMASK_STOP_MIDDLE) { /* 106 bytes less in the line */ video_memcpy ( pSTScreen+SCREENBYTES_LEFT, pVideoRaster, SCREENBYTES_MIDDLE-106 ); memset ( pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE-106, 0, 106 ); /* clear unused pixels */ pVideoRaster += (SCREENBYTES_MIDDLE-106); } else { /* normal middle part (160 bytes) */ video_memcpy ( pSTScreen+SCREENBYTES_LEFT, pVideoRaster, SCREENBYTES_MIDDLE ); pVideoRaster += SCREENBYTES_MIDDLE; } /* Does have right border ? */ if (LineBorderMask & BORDERMASK_RIGHT_OFF) { video_memcpy ( pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE, pVideoRaster, SCREENBYTES_RIGHT ); pVideoRasterEndLine = pVideoRaster + SCREENBYTES_RIGHT; pVideoRaster += BORDERBYTES_RIGHT; } else if (LineBorderMask & BORDERMASK_RIGHT_MINUS_2) { /* Shortened line by 2 bytes */ memset(pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE-2, 0, SCREENBYTES_RIGHT+2); pVideoRaster -= 2; pVideoRasterEndLine = pVideoRaster; } else { /* Simply clear right border to '0' */ memset(pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE,0,SCREENBYTES_RIGHT); pVideoRasterEndLine = pVideoRaster; } /* Shifter read bytes and borders can change, but display is blank, so finally clear the line with color 0 */ if (LineBorderMask & ( BORDERMASK_BLANK_LINE | BORDERMASK_BLANK ) ) memset(pSTScreen, 0, SCREENBYTES_LINE); /* Full right border removal up to the end of the line (cycle 512) */ if (LineBorderMask & BORDERMASK_RIGHT_OFF_FULL) pVideoRaster += BORDERBYTES_RIGHT_FULL; /* Correct the offset for pVideoRaster from BORDERMASK_LEFT_OFF above if needed */ pVideoRaster -= VideoOffset; /* VideoOffset is 0 or -2 */ /* STE specific */ if (!bSteBorderFlag && HWScrollCount) /* Handle STE fine scrolling (HWScrollCount is zero on ST) */ { uint16_t *pScrollAdj; /* Pointer to actual position in line */ int nNegScrollCnt; uint16_t *pScrollEndAddr; /* Pointer to end of the line */ nNegScrollCnt = 16 - HWScrollCount; if (LineBorderMask & BORDERMASK_LEFT_OFF) pScrollAdj = (uint16_t *)pSTScreen; else if ( (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE) || (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE_MED) ) { if ( SCREENBYTES_LEFT > BORDERBYTES_LEFT_2_STE ) pScrollAdj = (uint16_t *)(pSTScreen+8); /* don't scroll the 8 first bytes (keep color 0)*/ else pScrollAdj = (uint16_t *)pSTScreen; /* we render less bytes on screen than a real ST, scroll the whole line */ } else pScrollAdj = (uint16_t *)(pSTScreen + SCREENBYTES_LEFT); /* When shifting the line to the left, we will have 'HWScrollCount' missing pixels at */ /* the end of the line. We must complete these last 16 pixels with pixels from the */ /* video counter last accessed value in pVideoRasterEndLine. */ /* There're 2 passes : */ /* - shift whole line except the last 16 pixels */ /* - shift/complete the last 16 pixels */ /* Addr of the last byte to shift in the 1st pass (excluding the last 16 pixels of the line) */ if (LineBorderMask & BORDERMASK_RIGHT_OFF) pScrollEndAddr = (uint16_t *)(pSTScreen + SCREENBYTES_LINE - 8); else pScrollEndAddr = (uint16_t *)(pSTScreen + SCREENBYTES_LEFT + SCREENBYTES_MIDDLE - 8); if ( LineRes == 1 ) /* med res */ { /* in med res, 16 pixels are 4 bytes, not 8 as in low res, so only the last 4 bytes need a special case */ pScrollEndAddr += 2; /* 2 uint16_t = 4 bytes = 16 pixels */ /* Shift the whole line to the left by the given scroll count (except the last 16 pixels) */ while (pScrollAdj < pScrollEndAddr) { do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount) | (do_get_mem_word(pScrollAdj+2) >> nNegScrollCnt)); ++pScrollAdj; } /* Handle the last 16 pixels of the line (complete the line with pixels from pVideoRasterEndLine) */ for ( i=0 ; i<2 ; i++ ) do_put_mem_word(pScrollAdj+i, (do_get_mem_word(pScrollAdj+i) << HWScrollCount) | (do_get_mem_word(pVideoRasterEndLine+i*2) >> nNegScrollCnt)); /* Depending on whether $ff8264 or $ff8265 was used to scroll, */ /* we prefetched 16 pixel (4 bytes) */ if ( HWScrollPrefetch == 1 ) /* $ff8265 prefetches 16 pixels */ pVideoRaster += 2 * 2; /* 2 bitplans */ /* If scrolling with $ff8264, there's no prefetch, which means display starts */ /* 16 pixels later but still stops at the normal point (eg we display */ /* (320-16) pixels in low res). We shift the whole line 4 bytes to the right to */ /* get the correct result (using memmove, as src/dest are overlapping). */ else { if (LineBorderMask & BORDERMASK_RIGHT_OFF) memmove ( pSTScreen+4 , pSTScreen , SCREENBYTES_LINE - 4 ); else memmove ( pSTScreen+4 , pSTScreen , SCREENBYTES_LEFT + SCREENBYTES_MIDDLE - 4 ); memset ( pSTScreen , 0 , 4 ); /* first 16 pixels are color '0' */ } } else /* low res */ { /* Shift the whole line to the left by the given scroll count (except the last 16 pixels) */ while (pScrollAdj < pScrollEndAddr) { do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount) | (do_get_mem_word(pScrollAdj+4) >> nNegScrollCnt)); ++pScrollAdj; } /* Handle the last 16 pixels of the line (complete the line with pixels from pVideoRasterEndLine) */ for ( i=0 ; i<4 ; i++ ) do_put_mem_word(pScrollAdj+i, (do_get_mem_word(pScrollAdj+i) << HWScrollCount) | (do_get_mem_word(pVideoRasterEndLine+i*2) >> nNegScrollCnt)); /* Depending on whether $ff8264 or $ff8265 was used to scroll, */ /* we prefetched 16 pixel (8 bytes) */ if ( HWScrollPrefetch == 1 ) /* $ff8265 prefetches 16 pixels */ pVideoRaster += 4 * 2; /* 4 bitplans */ /* If scrolling with $ff8264, there's no prefetch, which means display starts */ /* 16 pixels later but still stops at the normal point (eg we display */ /* (320-16) pixels in low res). We shift the whole line 8 bytes to the right to */ /* get the correct result (using memmove, as src/dest are overlapping). */ else { if (LineBorderMask & BORDERMASK_RIGHT_OFF) memmove ( pSTScreen+8 , pSTScreen , SCREENBYTES_LINE - 8 ); else memmove ( pSTScreen+8 , pSTScreen , SCREENBYTES_LEFT + SCREENBYTES_MIDDLE - 8 ); memset ( pSTScreen , 0 , 8 ); /* first 16 pixels are color '0' */ } /* On STE, when we have a 230 bytes overscan line and HWScrollCount > 0 */ /* we must read 6 bytes less than expected if scrolling is using prefetching ($ff8265) */ /* (this is not the case for the 224 bytes overscan which is a multiple of 8) */ if ( (LineBorderMask & BORDERMASK_LEFT_OFF) && (LineBorderMask & BORDERMASK_RIGHT_OFF) ) { if ( HWScrollPrefetch == 1 ) pVideoRaster -= 6; /* we don't add 8 bytes (see above), but 2 */ else pVideoRaster -= 0; } } } /* LineWidth is zero on ST. */ /* On STE, the Shifter skips the given amount of words. */ pVideoRaster += LineWidth*2; /* Handle 4 pixels hardware scrolling ('ST Cnx' demo in 'Punish Your Machine') */ /* as well as scrolling occurring when removing the left border. */ /* If >0, shift the line by STF_PixelScroll pixels to the right */ /* If <0, shift the line by -STF_PixelScroll pixels to the left */ /* This should be handled after the STE's hardware scrolling as it will scroll */ /* the whole displayed area (while the STE scrolls pixels inside the displayed area) */ if ( STF_PixelScroll > 0 ) { uint16_t *pScreenLineEnd; int count; pScreenLineEnd = (uint16_t *) ( pSTScreen + SCREENBYTES_LINE - 2 ); if ( LineRes == 0 ) /* low res */ { for ( count = 0 ; count < ( SCREENBYTES_LINE - 8 ) / 2 ; count++ , pScreenLineEnd-- ) do_put_mem_word ( pScreenLineEnd , ( ( do_get_mem_word ( pScreenLineEnd - 4 ) << 16 ) | ( do_get_mem_word ( pScreenLineEnd ) ) ) >> STF_PixelScroll ); /* Handle the first 16 pixels of the line (add color 0 pixels to the extreme left) */ do_put_mem_word ( pScreenLineEnd-0 , ( do_get_mem_word ( pScreenLineEnd-0 ) >> STF_PixelScroll ) ); do_put_mem_word ( pScreenLineEnd-1 , ( do_get_mem_word ( pScreenLineEnd-1 ) >> STF_PixelScroll ) ); do_put_mem_word ( pScreenLineEnd-2 , ( do_get_mem_word ( pScreenLineEnd-2 ) >> STF_PixelScroll ) ); do_put_mem_word ( pScreenLineEnd-3 , ( do_get_mem_word ( pScreenLineEnd-3 ) >> STF_PixelScroll ) ); } else /* med res */ { for ( count = 0 ; count < ( SCREENBYTES_LINE - 4 ) / 2 ; count++ , pScreenLineEnd-- ) do_put_mem_word ( pScreenLineEnd , ( ( do_get_mem_word ( pScreenLineEnd - 2 ) << 16 ) | ( do_get_mem_word ( pScreenLineEnd ) ) ) >> STF_PixelScroll ); /* Handle the first 16 pixels of the line (add color 0 pixels to the extreme left) */ do_put_mem_word ( pScreenLineEnd-0 , ( do_get_mem_word ( pScreenLineEnd-0 ) >> STF_PixelScroll ) ); do_put_mem_word ( pScreenLineEnd-1 , ( do_get_mem_word ( pScreenLineEnd-1 ) >> STF_PixelScroll ) ); } } else if ( STF_PixelScroll < 0 ) { uint16_t *pScreenLineStart; int count; int STE_HWScrollLeft; uint16_t extra_word; STF_PixelScroll = -STF_PixelScroll; pScreenLineStart = (uint16_t *)pSTScreen; STE_HWScrollLeft = 0; if ( !bSteBorderFlag && HWScrollCount ) STE_HWScrollLeft = HWScrollCount; if ( LineRes == 0 ) /* low res */ { for ( count = 0 ; count < ( SCREENBYTES_LINE - 8 ) / 2 ; count++ , pScreenLineStart++ ) do_put_mem_word ( pScreenLineStart , ( ( do_get_mem_word ( pScreenLineStart ) << STF_PixelScroll ) | ( do_get_mem_word ( pScreenLineStart + 4 ) >> (16-STF_PixelScroll) ) ) ); /* * Handle the last 16 pixels of the line after the shift to the left : * - if this is a 224 byte STE overscan line, then the last 8 pixels to the extreme right should be displayed * - for other cases (230 byte overscan), "entering" pixels to the extreme right should be set to color 0 */ if (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE) { /* This is one can be complicated, because we can have STE scroll to the left + the global */ /* 8 pixel left scroll added when using a 224 bytes overscan line. We use extra_word to fetch */ /* those missing pixels */ for ( i=0 ; i<4 ; i++ ) { if ( STE_HWScrollLeft == 0 ) extra_word = do_get_mem_word ( pVideoRasterEndLine + i*2 ); else extra_word = ( do_get_mem_word ( pVideoRasterEndLine + i*2 ) << STE_HWScrollLeft ) | ( do_get_mem_word ( pVideoRasterEndLine + 8 + i*2 ) >> (16-STE_HWScrollLeft) ); do_put_mem_word ( pScreenLineStart+i , ( ( do_get_mem_word ( pScreenLineStart+i ) << STF_PixelScroll ) | ( extra_word >> (16-STF_PixelScroll) ) ) ); } } else { for ( i=0 ; i<4 ; i++ ) do_put_mem_word ( pScreenLineStart+i , ( do_get_mem_word ( pScreenLineStart+i ) << STF_PixelScroll ) ); } } else /* med res */ { for ( count = 0 ; count < ( SCREENBYTES_LINE - 4 ) / 2 ; count++ , pScreenLineStart++ ) do_put_mem_word ( pScreenLineStart , ( ( do_get_mem_word ( pScreenLineStart ) << STF_PixelScroll ) | ( do_get_mem_word ( pScreenLineStart + 2 ) >> (16-STF_PixelScroll) ) ) ); /* Handle the last 16 pixels of the line */ if ( (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE) || (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE_MED) ) { for ( i=0 ; i<2 ; i++ ) { if ( STE_HWScrollLeft == 0 ) extra_word = do_get_mem_word ( pVideoRasterEndLine + i*2 ); else extra_word = ( do_get_mem_word ( pVideoRasterEndLine + i*2 ) << STE_HWScrollLeft ) | ( do_get_mem_word ( pVideoRasterEndLine + 8 + i*2 ) >> (16-STE_HWScrollLeft) ); do_put_mem_word ( pScreenLineStart+i , ( ( do_get_mem_word ( pScreenLineStart+i ) << STF_PixelScroll ) | ( extra_word >> (16-STF_PixelScroll) ) ) ); } } else { for ( i=0 ; i<2 ; i++ ) do_put_mem_word ( pScreenLineStart+i , ( do_get_mem_word ( pScreenLineStart+i ) << STF_PixelScroll ) ); } } } } /* On STE, handle some changes that needed to be delayed until the end of the visible line */ /* On STE, handle modifications of the video counter address $ff8205/07/09 */ /* that occurred while the display was already ON */ if ( VideoCounterDelayedOffset != 0 ) { // fprintf ( stderr , "adjust video counter offset=%d old video=%x\n" , VideoCounterDelayedOffset , pVideoRaster-STRam ); pVideoRaster += ( VideoCounterDelayedOffset & ~1 ); // fprintf ( stderr , "adjust video counter offset=%d new video=%x\n" , VideoCounterDelayedOffset , pVideoRaster-STRam ); VideoCounterDelayedOffset = 0; } if ( pVideoRasterDelayed != NULL ) { pVideoRaster = pVideoRasterDelayed; // fprintf ( stderr , "adjust video counter const new video=%x\n" , pVideoRaster-STRam ); pVideoRasterDelayed = NULL; } /* On STE, if we wrote to the hwscroll register, we set the */ /* new value here, once the current line was processed */ if ( NewHWScrollCount >= 0 ) { HWScrollCount = NewHWScrollCount; HWScrollPrefetch = NewHWScrollPrefetch; NewHWScrollCount = -1; NewHWScrollPrefetch = -1; } /* On STE, if we trigger the left border + 16 pixels trick, we set the */ /* new value here, once the current line was processed */ if ( NewSteBorderFlag >= 0 ) { if ( NewSteBorderFlag == 0 ) bSteBorderFlag = false; else bSteBorderFlag = true; NewSteBorderFlag = -1; } /* On STE, if we wrote to the linewidth register, we set the */ /* new value here, once the current line was processed */ if ( NewLineWidth >= 0 ) { LineWidth = NewLineWidth; NewLineWidth = -1; } /* When stopping shifter for 4 pixel hardscroll the video address needs to be */ /* adjusted on the next line */ if ( VideoRasterDelayedInc != 0 ) { pVideoRaster += VideoRasterDelayedInc; VideoRasterDelayedInc = 0; } /* Each screen line copied to buffer is always same length */ pSTScreen += SCREENBYTES_LINE; /* We must keep the new video address in a 22 or 24 bit space depending on the machine type */ /* (for example in case it pointed to IO space and is now >= 0x1000000) */ pVideoRaster = ( ( pVideoRaster - STRam ) & VideoMask ) + STRam; //fprintf ( stderr , "video counter new=%x\n" , pVideoRaster-STRam ); } /*-----------------------------------------------------------------------*/ /** * Clear raster line table to store changes in palette/resolution on a line * basic. Called once on VBL interrupt. */ void Video_SetScreenRasters(void) { pHBLPaletteMasks = HBLPaletteMasks; pHBLPalettes = HBLPalettes; memset(pHBLPaletteMasks, 0, sizeof(uint32_t)*NUM_VISIBLE_LINES); /* Clear array */ } /*-----------------------------------------------------------------------*/ /** * Set pointers to HBLPalette tables to store correct colours/resolutions */ static void Video_SetHBLPaletteMaskPointers(void) { int FrameCycles, HblCounterVideo, LineCycles; int Line; /* FIXME [NP] We should use Cycles_GetCounterOnWriteAccess, but it wouldn't */ /* work when using multiple accesses instructions like move.l or movem */ /* To correct this, we assume a delay of 8 cycles (should give a good approximation */ /* of a move.w or movem.l for example) */ // FrameCycles = Cycles_GetCounterOnWriteAccess(CYCLES_COUNTER_VIDEO); #ifdef OLD_GET_POS FrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO) + 8; #else FrameCycles = Video_GetCyclesSinceVbl() + 8; #endif /* Find 'line' into palette - screen starts 63 lines down, less 29 for top overscan */ Video_ConvertPosition ( FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); Line = HblCounterVideo - nFirstVisibleHbl; /* FIXME [NP] if the color change occurs after the last visible pixel of a line */ /* we consider the palette should be modified on the next line. This is quite */ /* a hack, we should handle all color changes through spec512.c to have cycle */ /* accuracy all the time. */ if ( LineCycles >= LINE_END_CYCLE_NO_RIGHT ) Line++; if (Line < 0) /* Limit to top/bottom of possible visible screen */ Line = 0; if (Line >= NUM_VISIBLE_LINES) Line = NUM_VISIBLE_LINES-1; /* Store pointers */ pHBLPaletteMasks = &HBLPaletteMasks[Line]; /* Next mask entry */ pHBLPalettes = &HBLPalettes[16*Line]; /* Next colour raster list x16 colours */ } /*-----------------------------------------------------------------------*/ /** * Set video shifter timing variables according to screen refresh rate. * Note: The following equation must be satisfied for correct timings: * * nCyclesPerLine * nScanlinesPerFrame * nScreenRefreshRate = 8 MHz */ static void Video_ResetShifterTimings(void) { uint8_t nSyncByte; int RefreshRate_prev; int RefreshRate_new; nSyncByte = IoMem_ReadByte(0xff820a); RefreshRate_prev = nScreenRefreshRate; if ( Config_IsMachineFalcon() ) { RefreshRate_new = VIDEL_Get_VFreq(); /* Due to rounding RefreshRate_new might not exactly 50, 60 or 71, so we add a small margin */ if ( ( RefreshRate_new >= VIDEO_60HZ-2 ) && ( RefreshRate_new <= VIDEO_60HZ+2 ) ) RefreshRate_new = VIDEO_60HZ; /* Not sure monochrome is set 71 Hz with Falcon, but we can check it anyway */ else if ( ( RefreshRate_new >= VIDEO_71HZ-2 ) && ( RefreshRate_new <= VIDEO_71HZ+2 ) ) RefreshRate_new = VIDEO_71HZ; /* 50 Hz or other freqs : we use 50 Hz by default */ else RefreshRate_new = VIDEO_50HZ; } else { if ( (IoMem_ReadByte(0xff8260) & 3) == 2 ) /* High res */ RefreshRate_new = VIDEO_71HZ; else /* Low or Medium res */ { if ( nSyncByte & 2 ) RefreshRate_new = VIDEO_50HZ; else RefreshRate_new = VIDEO_60HZ; } } if ( RefreshRate_new == VIDEO_71HZ ) { /* 71 Hz, monochrome */ nScreenRefreshRate = VIDEO_71HZ; nScanlinesPerFrame = SCANLINES_PER_FRAME_71HZ; nCyclesPerLine = CYCLES_PER_LINE_71HZ; nStartHBL = VIDEO_START_HBL_71HZ; nFirstVisibleHbl = FIRST_VISIBLE_HBL_71HZ; nLastVisibleHbl = FIRST_VISIBLE_HBL_71HZ + VIDEO_HEIGHT_HBL_MONO; ShifterFrame.VBlank_signal = VBLANK_SIGNAL_OFF; /* No blank in mono mode ? */ ShifterFrame.VBlank_Off_Line = pVideoTiming->VBlank_Off_Hi_CheckLine+1; ShifterFrame.VBlank_On_Line = pVideoTiming->VBlank_On_Hi_CheckLine+1; ShifterFrame.VBLank_Off_60_CheckFreq = VIDEO_71HZ; ShifterFrame.VBLank_Off_50_CheckFreq = VIDEO_71HZ; ShifterFrame.VBLank_On_60_CheckFreq = VIDEO_71HZ; ShifterFrame.VBLank_On_50_CheckFreq = VIDEO_71HZ; } else if ( RefreshRate_new == VIDEO_50HZ ) /* ST/STE 50 Hz or Falcon RGB/TV is set to 50 Hz */ { /* 50 Hz */ nScreenRefreshRate = VIDEO_50HZ; nScanlinesPerFrame = SCANLINES_PER_FRAME_50HZ; nCyclesPerLine = CYCLES_PER_LINE_50HZ; nStartHBL = VIDEO_START_HBL_50HZ; nFirstVisibleHbl = FIRST_VISIBLE_HBL_50HZ; nLastVisibleHbl = FIRST_VISIBLE_HBL_50HZ + NUM_VISIBLE_LINES; ShifterFrame.VBlank_signal = VBLANK_SIGNAL_ON; ShifterFrame.VBlank_Off_Line = pVideoTiming->VBlank_Off_50_CheckLine+1; ShifterFrame.VBlank_On_Line = pVideoTiming->VBlank_On_50_CheckLine+1; ShifterFrame.VBLank_Off_60_CheckFreq = VIDEO_50HZ; ShifterFrame.VBLank_Off_50_CheckFreq = VIDEO_50HZ; ShifterFrame.VBLank_On_60_CheckFreq = VIDEO_50HZ; ShifterFrame.VBLank_On_50_CheckFreq = VIDEO_50HZ; } else /* 60 Hz for ST/STE or Falcon VGA */ { nScreenRefreshRate = VIDEO_60HZ; nScanlinesPerFrame = SCANLINES_PER_FRAME_60HZ; nCyclesPerLine = CYCLES_PER_LINE_60HZ; nStartHBL = VIDEO_START_HBL_60HZ; nFirstVisibleHbl = FIRST_VISIBLE_HBL_60HZ; nLastVisibleHbl = FIRST_VISIBLE_HBL_60HZ + NUM_VISIBLE_LINES; ShifterFrame.VBlank_signal = VBLANK_SIGNAL_ON; ShifterFrame.VBlank_Off_Line = pVideoTiming->VBlank_Off_60_CheckLine+1; ShifterFrame.VBlank_On_Line = pVideoTiming->VBlank_On_60_CheckLine+1; ShifterFrame.VBLank_Off_60_CheckFreq = VIDEO_60HZ; ShifterFrame.VBLank_Off_50_CheckFreq = VIDEO_60HZ; ShifterFrame.VBLank_On_60_CheckFreq = VIDEO_60HZ; ShifterFrame.VBLank_On_50_CheckFreq = VIDEO_60HZ; } nCyclesPerLine <<= nCpuFreqShift; /* Use VIDEO_HEIGHT_HBL_MONO only when using Mono mode and video resolution = high */ /* For other cases (low/med res) in color or Mono mode, we use VIDEO_HEIGHT_HBL_COLOR */ /* (fix 'Audio Sculpture' which temporarily switches to low res even when started in Mono mode) */ if ( bUseHighRes && ( nScreenRefreshRate == VIDEO_71HZ ) ) { nEndHBL = nStartHBL + VIDEO_HEIGHT_HBL_MONO; } else { nEndHBL = nStartHBL + VIDEO_HEIGHT_HBL_COLOR; } /* Reset freq changes position for the next VBL to come */ LastCycleScroll8264 = -1; LastCycleScroll8265 = -1; TimerBEventCountCycleStart = -1; /* reset timer B activation cycle for this VBL */ BlankLines = 0; /* Update refresh rate in status bar if necessary */ if ( RefreshRate_prev != nScreenRefreshRate ) Statusbar_UpdateInfo (); } /*-----------------------------------------------------------------------*/ /** * Clear the array indicating the state of each video line. */ static void Video_InitShifterLines ( void ) { int i; for ( i=0 ; ipSTScreen; Video_SetScreenRasters(); Video_InitShifterLines(); Spec512_StartVBL(); Video_StartHBL(); /* Init ShifterFrame.ShifterLines[0] */ } /*-----------------------------------------------------------------------*/ /** * Get width, height and bpp according to TT-Resolution */ void Video_GetTTRes(int *width, int *height, int *bpp) { switch (TTRes) { case ST_LOW_RES: *width = 320; *height = 200; *bpp = 4; break; case ST_MEDIUM_RES:*width = 640; *height = 200; *bpp = 2; break; case ST_HIGH_RES: *width = 640; *height = 400; *bpp = 1; break; case TT_LOW_RES: *width = 320; *height = 480; *bpp = 8; break; case TT_MEDIUM_RES:*width = 640; *height = 480; *bpp = 4; break; case TT_HIGH_RES: *width = 1280; *height = 960; *bpp = 1; break; default: fprintf(stderr, "TT res error!\n"); *width = 320; *height = 200; *bpp = 4; break; } } /** * Set a TT palette color */ static void Video_SetTTPaletteColor(int idx, uint32_t addr) { uint8_t r,g,b, lowbyte, highbyte; lowbyte = IoMem_ReadByte(addr + 1); if (TTSpecialVideoMode & 0x10) /* TT Hyper-mono mode? */ { r = g = b = lowbyte; } else { highbyte = IoMem_ReadByte(addr); r = (highbyte << 4) | (highbyte & 0x0f); g = (lowbyte & 0xf0) | (lowbyte >> 4); b = (lowbyte << 4) | (lowbyte & 0x0f); } //printf("%d (%x): (%d,%d,%d)\n", idx, addr, r,g,b); Screen_SetPaletteColor(idx, r,g,b); } /** * Which 256-color TT palette 16-color "bank" is mapped to ST(e) palette */ static int TTPaletteSTBank(void) { return IoMem_ReadByte(0xff8263) & 0x0f; } /** * Convert TT palette to SDL palette */ static void Video_UpdateTTPalette(int bpp) { if (TTRes == TT_HIGH_RES || (bUseVDIRes && bpp == 1)) { /* Monochrome mode... palette is hardwired (?) */ Screen_SetPaletteColor(0, 255, 255, 255); Screen_SetPaletteColor(1, 0, 0, 0); } else if (bpp == 1) { /* Duochrome mode... palette is taken last two TT colors */ int base = (IoMem_ReadWord(0xff8400) & 0x2) >> 1; Video_SetTTPaletteColor(base, 0xff85fc); Video_SetTTPaletteColor(base ^ 1, 0xff85fe); } else { uint32_t ttpalette = 0xff8400; int i, colors = 1 << bpp; if (colors <= 16) { /* use correct ST palette bank */ ttpalette += TTPaletteSTBank() * 16*SIZE_WORD; } for (i = 0; i < colors; i++) { Video_SetTTPaletteColor(i, ttpalette); ttpalette += SIZE_WORD; } } bTTColorsSync = true; } /*-----------------------------------------------------------------------*/ /** * Update TT palette and blit TT screen using VIDEL code. * @return true if the screen contents changed */ bool Video_RenderTTScreen(void) { static int nPrevTTRes = -1; int width, height, bpp; Video_GetTTRes(&width, &height, &bpp); if (TTRes != nPrevTTRes) { Screen_SetGenConvSize(width, height, false); nPrevTTRes = TTRes; if (bpp == 1) /* Assert that mono palette will be used in mono mode */ bTTColorsSync = false; } /* colors need syncing? */ if (!bTTColorsSync || TTSpecialVideoMode != nPrevTTSpecialVideoMode) { Video_UpdateTTPalette(bpp); nPrevTTSpecialVideoMode = TTSpecialVideoMode; } return Screen_GenDraw(VideoBase, width, height, bpp, width * bpp / 16, 0, 0, 0, 0); } /*-----------------------------------------------------------------------*/ /** * Draw screen (either with ST/STE shifter drawing functions or with * Videl drawing functions) */ static void Video_DrawScreen(void) { /* Skip frame if need to */ if (nVBLs % (nFrameSkips+1)) return; /* Now draw the screen! */ if (bUseVDIRes) { if (Config_IsMachineTT() && !bTTColorsSync) { Video_UpdateTTPalette(VDIPlanes); } else if (Config_IsMachineFalcon()) { VIDEL_UpdateColors(); } Screen_GenDraw(VideoBase, VDIWidth, VDIHeight, VDIPlanes, VDIWidth * VDIPlanes / 16, 0, 0, 0, 0); } else if (Config_IsMachineFalcon()) { VIDEL_renderScreen(); } else if (Config_IsMachineTT()) { Video_RenderTTScreen(); } else { /* Before drawing the screen, ensure all unused lines are cleared to color 0 */ /* (this can happen in 60 Hz when hatari is displaying the screen's border) */ /* pSTScreen was set during Video_CopyScreenLineColor */ if (nHBL < nLastVisibleHbl) memset(pSTScreen, 0, SCREENBYTES_LINE * ( nLastVisibleHbl - nHBL ) ); Screen_Draw(); } } /*-----------------------------------------------------------------------*/ /** * Start HBL, Timer B and VBL interrupts. */ /** * Start HBL or Timer B interrupt at position Pos. If position Pos was * already reached, then the interrupt is set on the next line. */ static void Video_AddInterrupt ( int Line , int Pos , interrupt_id Handler ) { int FrameCycles , HblCounterVideo , LineCycles; int CyclesToPos; if ( nHBL >= nScanlinesPerFrame ) return; /* don't set a new hbl/timer B if we're on the last line, as the vbl will happen first */ Video_GetPosition_CE ( &FrameCycles , &HblCounterVideo , &LineCycles ); //fprintf ( stderr , "add int pos=%d handler=%d LineCycles=%d nCyclesPerLine=%d\n" , Pos , Handler , LineCycles , nCyclesPerLine ); Pos <<= nCpuFreqShift; /* convert Pos at 8 MHz into number of cycles at 8/16/32 MHz */ if ( Line <= nHBL ) CyclesToPos = Pos + ShifterFrame.ShifterLines[Line].StartCycle - FrameCycles; else /* Pos is on the next line (after the current hbl) */ CyclesToPos = Pos + ShifterFrame.ShifterLines[Line-1].StartCycle - FrameCycles + nCyclesPerLine; CycInt_AddRelativeInterrupt ( CyclesToPos , INT_CPU_CYCLE, Handler ); //fprintf ( stderr , "add int pos=%d handler=%d LineCycles=%d nCyclesPerLine=%d -> %d cycles\n" , Pos , Handler , LineCycles , nCyclesPerLine , CyclesToPos ); } static void Video_AddInterruptHBL ( int Line , int Pos ) { //fprintf ( stderr , "add hbl line=%d pos=%d\n" , Line , Pos ); if ( !bUseVDIRes ) { Video_AddInterrupt ( Line , Pos , INTERRUPT_VIDEO_HBL ); } } void Video_AddInterruptTimerB ( int LineVideo , int CycleVideo , int Pos ) { //fprintf ( stderr , "add timerb line=%d cycle=%d pos=%d\n" , LineVideo , CycleVideo , Pos ); if ( !bUseVDIRes ) { /* If new position is not reached yet, next interrupt should be on current line */ /* else it should be on next line */ if ( ( Pos << nCpuFreqShift ) > CycleVideo ) Video_AddInterrupt ( LineVideo , Pos , INTERRUPT_VIDEO_ENDLINE ); else Video_AddInterrupt ( LineVideo+1 , Pos , INTERRUPT_VIDEO_ENDLINE ); } } /** * Add some video interrupts to handle the first HBL and the first Timer B * in a new VBL. Also add an interrupt to trigger the next VBL. * This function is called from the VBL, so we use PendingCycleOver to take into account * the possible delay occurring when the VBL was executed. * In monochrome mode (71 Hz) a line is 224 cycles, which means if VBL is delayed * by a DIVS, FrameCycles can already be > 224 and we need to add an immediate * interrupt for hbl/timer in the next 4/8 cycles (else crash might happen as * line 0 processing would be skipped). */ void Video_StartInterrupts ( int PendingCyclesOver ) { int FrameCycles , HblCounterVideo , LineCycles; int Pos; /* HBL/Timer B are not emulated in VDI mode */ if (!bUseVDIRes) { ShifterFrame.ShifterLines[0].StartCycle = 0; /* 1st HBL always starts at cycle 0 */ Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles ); /* Set Timer B interrupt for line 0 */ Pos = Video_TimerB_GetPos ( 0 ); if ( ( Pos << nCpuFreqShift ) > FrameCycles ) /* check Pos for line 0 was not already reached */ Video_AddInterruptTimerB ( HblCounterVideo , LineCycles , Pos ); else /* the VBL was delayed by more than 1 HBL, add an immediate timer B */ { LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d delayed too much video_cyc=%d >= pos=%d for first timer B, add immediate timer B\n" , nVBLs , FrameCycles , Pos ); CycInt_AddRelativeInterrupt ( 4 , INT_CPU_CYCLE, INTERRUPT_VIDEO_ENDLINE ); } /* Set HBL interrupt for line 0 */ Pos = Video_HBL_GetDefaultPos(); ShifterFrame.HBL_CyclePos = Pos; if ( ( Pos << nCpuFreqShift ) > FrameCycles ) /* check Pos for line 0 was not already reached */ Video_AddInterruptHBL ( HblCounterVideo , Pos ); else /* the VBL was delayed by more than 1 HBL, add an immediate HBL */ { LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d delayed too much video_cyc=%d >= pos=%d for first HBL, add immediate HBL\n" , nVBLs , FrameCycles , Pos ); CycInt_AddRelativeInterrupt ( 8 , INT_CPU_CYCLE, INTERRUPT_VIDEO_HBL ); /* use 8 instead of 4 to happen after immediate timer b */ } } /* When using VDI, we setup the next VBL here ; else it will be setup at the start of the last HBL */ else { /* Add new VBL interrupt */ CyclesPerVBL = nScanlinesPerFrame * nCyclesPerLine; /* TODO [NP] use cpufreq / 50 instead ? */ CycInt_AddRelativeInterrupt( CyclesPerVBL - PendingCyclesOver , INT_CPU_CYCLE , INTERRUPT_VIDEO_VBL); } } /*-----------------------------------------------------------------------*/ /** * VBL interrupt : set new interrupts, draw screen, generate sound, * reset counters, ... */ void Video_InterruptHandler_VBL ( void ) { int PendingCyclesOver; int PendingInterruptCount_save; uint64_t VBL_ClockCounter_prev; PendingInterruptCount_save = PendingInterruptCount; /* In case we press a shortcut for reset, PendingInterruptCount will be changed to >0 and we will get */ /* some warnings "bug nHBL=...". To avoid this we restore the value (<= 0) saved at the start of the VBL */ if ( PendingInterruptCount > 0 ) PendingInterruptCount = PendingInterruptCount_save; /* Store cycles we went over for this frame(this is our initial count) */ PendingCyclesOver = -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); /* +ve */ /* Remove this interrupt from list and re-order */ /* NOTE [NP] : in case ShortCut_ActKey above was used to change some parameters that require */ /* a reset, then the current int won't be the VBL handler anymore. In that case we should not */ /* acknowledge else this will ack another int and this will break Falcon DMA sound for example */ /* TODO : see above to handle shortcut keys in the main CPU loop */ if ( CycInt_GetActiveInt() == INTERRUPT_VIDEO_VBL ) CycInt_AcknowledgeInterrupt(); /* Set frame cycles, used for Video Address */ Cycles_SetCounter(CYCLES_COUNTER_VIDEO, PendingCyclesOver + ( pVideoTiming->VblVideoCycleOffset << nCpuFreqShift ) ); VBL_ClockCounter_prev = VBL_ClockCounter; VBL_ClockCounter = CyclesGlobalClockCounter - PendingCyclesOver; /* The VBL interrupt starts at "VblVideoCycleOffset" cycles since the start of the VBL */ VBL_ClockCounter -= pVideoTiming->VblVideoCycleOffset << nCpuFreqShift; /* Clear any key presses which are due to be de-bounced (held for one ST frame) */ Keymap_DebounceAllKeys(); Video_DrawScreen(); /* Check printer status */ Printer_CheckIdleStatus(); /* Update counter for number of screen refreshes per second */ nVBLs++; /* Set video registers for frame */ Video_ClearOnVBL(); /* Since we don't execute HBL functions in VDI mode, we've got to * initialize the first HBL palette here when VDI mode is enabled. */ if (bUseVDIRes) Video_StoreFirstLinePalette(); /* Start VBL, HBL and Timer B interrupts (this must be done after resetting * video cycle counter setting default freq values in Video_ClearOnVBL) */ Video_StartInterrupts(PendingCyclesOver); /* Process shortcut keys */ ShortCut_ActKey(); /* Update the IKBD's internal clock */ IKBD_UpdateClockOnVBL (); /* Record video frame is necessary */ if ( Avi_AreWeRecording() ) Avi_RecordVideoStream (); /* Store off PSG registers for YM file, is enabled */ YMFormat_UpdateRecording(); /* Generate 1/50th second of sound sample data, to be played by sound thread */ Sound_Update_VBL(); /* Update the blitter's stats for the previous VBL */ Blitter_StatsUpdateRate ( (int)( VBL_ClockCounter - VBL_ClockCounter_prev ) ); #ifdef OLD_GET_POS LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d video_cyc=%d pending_cyc=%d vbl_cycles=%d\n" , nVBLs , Cycles_GetCounter(CYCLES_COUNTER_VIDEO) , PendingCyclesOver , (int)( CyclesGlobalClockCounter - PendingCyclesOver - VBL_ClockCounter ) ); #else LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d video_cyc=%d pending_cyc=%d vbl_cycles=%d\n" , nVBLs , Video_GetCyclesSinceVbl() , PendingCyclesOver , (int)( CyclesGlobalClockCounter - PendingCyclesOver - VBL_ClockCounter ) ); #endif // VBL_ClockCounter = CyclesGlobalClockCounter - PendingCyclesOver; /* Print traces if pending VBL bit changed just before IACK when VBL interrupt is allowed */ if ( ( CPU_IACK == true ) && ( regs.intmask < 4 ) ) { if ( pendingInterrupts & ( 1 << 4 ) ) { #ifdef OLD_GET_POS LOG_TRACE ( TRACE_VIDEO_VBL , "VBL %d, pending set again just before iack, skip one VBL interrupt video_cyc=%d pending_cyc=%d\n" , nVBLs , Cycles_GetCounter(CYCLES_COUNTER_VIDEO) , PendingCyclesOver ); #else LOG_TRACE ( TRACE_VIDEO_VBL , "VBL %d, pending set again just before iack, skip one VBL interrupt video_cyc=%d pending_cyc=%d\n" , nVBLs , Video_GetCyclesSinceVbl() , PendingCyclesOver ); #endif } else { #ifdef OLD_GET_POS LOG_TRACE ( TRACE_VIDEO_VBL , "VBL %d, new pending VBL set just before iack video_cyc=%d pending_cyc=%d\n" , nVBLs , Cycles_GetCounter(CYCLES_COUNTER_VIDEO) , PendingCyclesOver ); #else LOG_TRACE ( TRACE_VIDEO_VBL , "VBL %d, new pending VBL set just before iack video_cyc=%d pending_cyc=%d\n" , nVBLs , Video_GetCyclesSinceVbl() , PendingCyclesOver ); #endif } } /* Set pending bit for VBL interrupt in the CPU IPL */ /* [NP] We don't trigger a VBL interrupt if the ShortCut_ActKey() above asked */ /* for a warm or cold reset and the cpu is about to be reset at the end of the current instruction */ /* (else this could cause bus error / halt because at this point RAM was already cleared, but CPU core could */ /* try to access it to process the VBL interrupt and read the vector's address (especially when using MMU)) */ if ( quit_program == 0 ) M68000_Exception(EXCEPTION_NR_VBLANK, M68000_EXC_SRC_AUTOVEC); /* Vertical blank interrupt, level 4 */ Main_WaitOnVbl(); } /*-----------------------------------------------------------------------*/ /** * Get the video RAM base address, taking the differences for the low * byte into account for each machine type. */ uint32_t Video_GetScreenBaseAddr(void) { uint32_t nBase; nBase = (uint32_t)IoMem_ReadByte(0xff8201) << 16; nBase |= (uint32_t)IoMem_ReadByte(0xff8203) << 8; if (!Config_IsMachineST()) { /* On STe 2 aligned and on TT 8 aligned. On Falcon 4 aligned * in bitplane mode, and 2 aligned in hi-color */ uint32_t nLowByte = IoMem_ReadByte(0xff820d); if (Config_IsMachineTT()) nBase |= nLowByte & ~7; else if (Config_IsMachineFalcon() && !(IoMem_ReadWord(0xff8266) & 0x100)) nBase |= nLowByte & ~3; else nBase |= nLowByte & ~1; } return nBase; } /** * Write to video address base high, med and low register (0xff8201/03/0d). * On STE/TT, when a program writes to high or med registers, base low register * is reset to zero. */ void Video_ScreenBase_WriteByte(void) { /* On STF/STE machines with <= 4MB of RAM, video addresses are limited to $3fffff */ if ( IoAccessCurrentAddress == 0xff8201 ) IoMem[ 0xff8201 ] &= DMA_MaskAddressHigh(); /* On STE/TT, reset screen base low register */ if (!Config_IsMachineST() && (IoAccessCurrentAddress == 0xff8201 || IoAccessCurrentAddress == 0xff8203)) IoMem[0xff820d] = 0; if (LOG_TRACE_LEVEL(TRACE_VIDEO_STE)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); LOG_TRACE_PRINT ( "write ste video base=0x%x video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" , (IoMem[0xff8201]<<16)+(IoMem[0xff8203]<<8)+IoMem[0xff820d] , FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /*-----------------------------------------------------------------------*/ /** * Read video address counter and update ff8205/07/09 */ void Video_ScreenCounter_ReadByte(void) { uint32_t addr; addr = Video_CalculateAddress(); /* get current video address */ /* On STE, handle modifications of the video counter address $ff8205/07/09 */ /* that occurred while the display was already ON */ if ( VideoCounterDelayedOffset != 0 ) { addr += ( VideoCounterDelayedOffset & ~1 ); // fprintf ( stderr , "adjust video counter offset=%d new video=%x\n" , VideoCounterDelayedOffset , addr ); } IoMem[0xff8205] = ( addr >> 16 ) & 0xff; IoMem[0xff8207] = ( addr >> 8 ) & 0xff; IoMem[0xff8209] = addr & 0xff; } /*-----------------------------------------------------------------------*/ /** * Write to video address counter (0xff8205, 0xff8207 and 0xff8209). * Called on STE/TT only and like with base address, you cannot set lowest bit. * * As Hatari processes/converts one complete video line at a time, we have 3 cases : * - If display has not started yet for this line (left border), we can change pVideoRaster now. * We must take into account that the MMU starts 16 cycles earlier when hscroll is used. * - If display has stopped for this line (right border), we will change pVideoRaster * in Video_CopyScreenLineColor using pVideoRasterDelayed once the line has been processed. * - If the write is made while display is on, then we must compute an offset of what * the new address should have been, to correctly emulate the video address at the * end of the line while taking into account the fact that the video pointer is incrementing * during the active part of the line (this is the most "tricky" case) * * To compute the new address, we must change only the byte that was modified and keep the two others ones. */ void Video_ScreenCounter_WriteByte(void) { uint8_t AddrByte; uint32_t addr_cur; uint32_t addr_new = 0; int FrameCycles, HblCounterVideo, LineCycles; LOG_TRACE_VAR int Delayed; int MMUStartCycle; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); /* On STF/STE machines with <= 4MB of RAM, video addresses are limited to $3fffff */ if ( IoAccessCurrentAddress == 0xff8205 ) IoMem[ 0xff8205 ] &= DMA_MaskAddressHigh(); AddrByte = IoMem[ IoAccessCurrentAddress ]; /* Get current video address from the shifter */ addr_cur = Video_CalculateAddress(); /* Correct the address in case a modification of ff8205/07/09 was already delayed */ addr_new = addr_cur + VideoCounterDelayedOffset; /* Correct the address in case video counter was already modified in the right border */ if ( pVideoRasterDelayed != NULL ) addr_new = pVideoRasterDelayed - STRam; /* addr_new should now be the same as on a real STE */ /* Compute the new video address with one modified byte */ if ( IoAccessCurrentAddress == 0xff8205 ) addr_new = ( addr_new & 0x00ffff ) | ( AddrByte << 16 ); else if ( IoAccessCurrentAddress == 0xff8207 ) addr_new = ( addr_new & 0xff00ff ) | ( AddrByte << 8 ); else if ( IoAccessCurrentAddress == 0xff8209 ) addr_new = ( addr_new & 0xffff00 ) | ( AddrByte ); addr_new &= ~1; /* clear bit 0 */ MMUStartCycle = Video_GetMMUStartCycle ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle ); /* If display has not started, we can still modify pVideoRaster */ /* We must also check the write does not overlap the end of the line (to be sure Video_EndHBL is called first) */ if ( ( ( LineCycles <= MMUStartCycle ) && ( nHBL == HblCounterVideo ) ) || ( nHBL < nStartHBL ) || ( nHBL >= nEndHBL + BlankLines ) ) { pVideoRaster = &STRam[addr_new]; /* set new video address */ VideoCounterDelayedOffset = 0; pVideoRasterDelayed = NULL; Delayed = false; } /* Display is OFF (right border) but we can't change pVideoRaster now, we must process Video_CopyScreenLineColor first */ else if ( ( nHBL >= nStartHBL ) && ( nHBL < nEndHBL + BlankLines ) /* line should be active */ && ( ( LineCycles > ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle ) /* we're in the right border */ || ( HblCounterVideo == nHBL+1 ) ) ) /* or the write overlaps the next line and Video_EndHBL was not called yet */ { VideoCounterDelayedOffset = 0; pVideoRasterDelayed = &STRam[addr_new]; /* new value for pVideoRaster at the end of Video_CopyScreenLineColor */ Delayed = true; } /* Counter is modified while display is ON, store the bytes offset for Video_CopyScreenLineColor */ /* Even on a real STE, modifying video address in this case will cause artefacts */ else { VideoCounterDelayedOffset = addr_new - addr_cur; pVideoRasterDelayed = NULL; Delayed = true; /* [FIXME] 'E605' Earth part by Light : write to FF8209 on STE while display is on, */ /* in that case video counter is not correct */ if ( STMemory_ReadLong ( M68000_InstrPC ) == 0x01c9ffc3 ) /* movep.l d0,-$3d(a1) */ VideoCounterDelayedOffset += 6; /* or -2 ? */ /* [FIXME] 'Tekila' part in Delirious Demo IV : write to FF8209 on STE while display is on, */ /* in that case video counter is not correct */ else if ( ( STMemory_ReadLong ( M68000_InstrPC ) == 0x11c48209 ) /* move.b d4,$ff8209.w */ && ( STMemory_ReadLong ( M68000_InstrPC-4 ) == 0x11c28207 ) /* move.b d2,$ff8207.w */ && ( STMemory_ReadLong ( M68000_InstrPC-8 ) == 0x82054842 ) ) { VideoCounterDelayedOffset += 2; if ( VideoCounterDelayedOffset == 256 ) /* write sometimes happens at the same time */ VideoCounterDelayedOffset = 0; /* ff8207 increases */ /* partial fix, some errors remain for other cases where write happens at the same time ff8207 increases ... */ } } LOG_TRACE(TRACE_VIDEO_STE , "write ste video %x val=0x%x video_old=%x video_new=%x offset=%x delayed=%s" " video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" , IoAccessCurrentAddress, AddrByte, addr_cur , addr_new , VideoCounterDelayedOffset , Delayed ? "yes" : "no" , FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } /*-----------------------------------------------------------------------*/ /** * Read video sync register (0xff820a) */ void Video_Sync_ReadByte(void) { if (Config_IsMachineST() || Config_IsMachineSTE()) IoMem[0xff820a] |= 0xfc; /* set unused bits 2-7 to 1 */ } /*-----------------------------------------------------------------------*/ /** * Read video base address low byte (0xff820d). A plain ST can only store * screen addresses rounded to 256 bytes (i.e. no lower byte). */ void Video_BaseLow_ReadByte(void) { if (Config_IsMachineST()) IoMem[0xff820d] = 0; /* On ST this is always 0 */ /* Note that you should not do anything here for STe because * VideoBase address is set in an interrupt and would be wrong * here. It's fine like this. */ } /*-----------------------------------------------------------------------*/ /** * Read video line width register (0xff820f) */ void Video_LineWidth_ReadByte(void) { if (Config_IsMachineST()) IoMem[0xff820f] = 0; /* On ST this is always 0 */ /* If we're not in STF mode, we use the value already stored in $ff820f */ } /*-----------------------------------------------------------------------*/ /** * Read video resolution register (0xff8260 or 0xff8261) * NOTE : resolution register is stored in both the GLUE and the SHIFTER * As the value is read from the SHIFTER, we need to round memory access to 4 cycle */ void Video_Res_ReadByte(void) { uint32_t addr; addr = IoAccessCurrentAddress; /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnReadAccess(); if (bUseHighRes) IoMem[addr] = 2; /* If mono monitor, force to high resolution */ if (Config_IsMachineST()) IoMem[addr] |= 0xfc; /* On STF, set unused bits 2-7 to 1 */ else if (Config_IsMachineTT()) IoMem[addr] &= 0x07; /* Only use bits 0, 1 and 2 */ else IoMem[addr] &= 0x03; /* Only use bits 0 and 1, unused bits 2-7 are set to 0 */ } void Video_ResGlueShifter_ReadByte(void) /* Read from 0xff8260 */ { Video_Res_ReadByte(); } void Video_ResShifter_ReadByte(void) /* Read from 0xff8261 */ { Video_Res_ReadByte(); } /*-----------------------------------------------------------------------*/ /** * Read horizontal scroll register (0xff8265) */ void Video_HorScroll_Read(void) { IoMem[0xff8265] = HWScrollCount; } /*-----------------------------------------------------------------------*/ /** * Write video line width register (0xff820f) - STE only. * Content of LineWidth is added to the shifter counter when display is * turned off (start of the right border, usually at cycle 376) */ void Video_LineWidth_WriteByte(void) { uint8_t NewWidth; int FrameCycles, HblCounterVideo, LineCycles; LOG_TRACE_VAR int Delayed; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); NewWidth = IoMem_ReadByte(0xff820f); /* We must also check the write does not overlap the end of the line */ if ( ( ( nHBL == HblCounterVideo ) && ( LineCycles <= ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ) ) || ( nHBL < nStartHBL ) || ( nHBL >= nEndHBL + BlankLines ) ) { LineWidth = NewWidth; /* display is on, we can still change */ NewLineWidth = -1; /* cancel 'pending' change */ Delayed = false; } else { NewLineWidth = NewWidth; /* display is off, can't change LineWidth once in right border */ Delayed = true; } LOG_TRACE(TRACE_VIDEO_STE , "write ste linewidth=0x%x delayed=%s video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n", NewWidth, Delayed ? "yes" : "no" , FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } /*-----------------------------------------------------------------------*/ /** * Write to video shifter palette registers (0xff8240-0xff825e) * * Note that there's a special "strange" case when writing only to the upper byte * of the color reg (instead of writing 16 bits at once with .W/.L). * In that case, the byte written to address x is automatically written * to address x+1 too (but we shouldn't copy x in x+1 after masking x ; we apply the mask at the end) * Similarly, when writing a byte to address x+1, it's also written to address x. * So : *
 *	move.w #0,$ff8240	-> color 0 is now $000
 *	move.b #7,$ff8240	-> color 0 is now $707 !
 *	move.b #$55,$ff8241	-> color 0 is now $555 !
 *	move.b #$71,$ff8240	-> color 0 is now $171 (bytes are first copied, then masked)
 * 
*/ static void Video_ColorReg_WriteWord(void) { uint32_t addr; uint16_t col; int idx; addr = IoAccessCurrentAddress; /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnWriteAccess(); /* Handle special case when writing only to the upper byte of the color reg */ if (nIoMemAccessSize == SIZE_BYTE && (IoAccessCurrentAddress & 1) == 0) col = (IoMem_ReadByte(addr) << 8) + IoMem_ReadByte(addr); /* copy upper byte into lower byte */ /* Same when writing only to the lower byte of the color reg */ else if (nIoMemAccessSize == SIZE_BYTE && (IoAccessCurrentAddress & 1) == 1) col = (IoMem_ReadByte(addr) << 8) + IoMem_ReadByte(addr); /* copy lower byte into upper byte */ /* Usual case, writing a word or a long (2 words) */ else col = IoMem_ReadWord(addr); if (Config_IsMachineST()) col &= 0x777; /* Mask off to ST 512 palette */ else col &= 0xfff; /* Mask off to STe 4096 palette */ addr &= 0xfffffffe; /* Ensure addr is even to store the 16 bit color */ IoMem_WriteWord(addr, col); /* (some games write 0xFFFF and read back to see if STe) */ idx = (addr - 0xff8240) / 2; /* words */ if (bUseHighRes || (bUseVDIRes && VDIPlanes == 1)) { if (idx == 0) { Screen_SetPaletteColor(col & 1, 0, 0, 0); Screen_SetPaletteColor(!(col & 1), 255, 255, 255); } } else if (bUseVDIRes) { int r, g, b; r = (col >> 8) & 0x0f; r = ((r & 7) << 1) | (r >> 3); r |= r << 4; g = (col >> 4) & 0x0f; g = ((g & 7) << 1) | (g >> 3); g |= g << 4; b = col & 0x0f; b = ((b & 7) << 1) | (b >> 3); b |= b << 4; Screen_SetPaletteColor(idx, r, g, b); } else /* Don't store if hi-res or VDI resolution */ { Video_SetHBLPaletteMaskPointers(); /* Set 'pHBLPalettes' etc.. according cycles into frame */ Spec512_StoreCyclePalette(col, addr); /* Store colour into CyclePalettes[] */ pHBLPalettes[idx] = col; /* Set colour x */ *pHBLPaletteMasks |= 1 << idx; /* And mask */ if (LOG_TRACE_LEVEL(TRACE_VIDEO_COLOR)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); LOG_TRACE_PRINT ( "write col addr=%x col=%x video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" , IoAccessCurrentAddress, col, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } } /* * Read from video shifter palette registers (0xff8240-0xff825e) * * NOTE [NP] : On STF, only 3 bits are used for RGB (instead of 4 on STE) ; * the content of bits 3, 7 and 11 is not defined and will be 0 or 1 * depending on the latest activity on the BUS (last word access by the CPU or * the shifter). As precisely emulating these bits is quite complicated, * we use random values for now. * NOTE [NP] : When executing code from the IO addresses between 0xff8240-0xff825e * the unused bits on STF are set to '0' (used in "The Union Demo" protection). * So we use Hatari_rand() only if PC is located in RAM. */ static void Video_ColorReg_ReadWord(void) { uint16_t col; uint32_t addr; addr = IoAccessCurrentAddress; /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnReadAccess(); col = IoMem_ReadWord(addr); if (Config_IsMachineST() && M68000_GetPC() < 0x400000) /* PC in RAM < 4MB */ { col = ( col & 0x777 ) | ( Hatari_rand() & 0x888 ); IoMem_WriteWord ( addr , col ); } if (LOG_TRACE_LEVEL(TRACE_VIDEO_COLOR)) { int FrameCycles, HblCounterVideo, LineCycles; Video_GetPosition_OnReadAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); LOG_TRACE_PRINT ( "read col addr=%x col=%x video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" , IoAccessCurrentAddress, col, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } } /* * [NP] TODO : due to how .L accesses are handled in ioMem.c, we can't call directly * Video_ColorReg_WriteWord from ioMemTabST.c / ioMemTabSTE.c, we must use an intermediate * function, else .L accesses will not change 2 .W color regs, but only one. * This should be changed in ioMem.c to do 2 separate .W accesses, as would do a real 68000 */ void Video_Color0_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color1_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color2_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color3_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color4_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color5_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color6_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color7_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color8_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color9_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color10_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color11_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color12_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color13_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color14_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color15_WriteWord(void) { Video_ColorReg_WriteWord(); } void Video_Color0_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color1_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color2_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color3_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color4_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color5_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color6_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color7_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color8_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color9_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color10_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color11_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color12_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color13_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color14_ReadWord(void) { Video_ColorReg_ReadWord(); } void Video_Color15_ReadWord(void) { Video_ColorReg_ReadWord(); } /*-----------------------------------------------------------------------*/ /** * Write to video resolution register (0xff8260 or 0xff8261) * NOTE : resolution register is stored in both the GLUE and the SHIFTER * - writing to 0xff8260 will write to the GLUE and the Shifter * - writing to 0xff8261 will write only to the Shifter * * Depending on the emulated machine, this function can be called * for 0xff8260 only or also for 0xff8261 * * When writing to ff8260, the GLUE gets the new value immediately, before * rounding to 4 cycles. The rounding happens later, when the SHIFTER reads * the data from the bus, as explained by Ijor on atari-forum.com : * - CPU starts a bus cycle addressing shifter RES * - CPU drives the data bus with the new value * - GLUE reads the value from the CPU data bus * - Possible wait states inserted here by MMU * - MMU connects the CPU data bus to the RAM data bus * - SHIFTER reads the value from the RAM data bus using bits 8-9 * - CPU finishes the bus cycle * * Also value "3" is different for GLUE and SHIFTER * - GLUE will interpret "3" as high res (because bit 1 is set) * - SHIFTER will go to a stopped state and not process input words from MMU anymore ! * (this is used by Troed to create a 4 pixels hardscroll on STF) * * Data bus and writing to 0xff8261 (resolution in Shifter) : * As seen above, a write to 0xff8261 will write only to the Shifter * In that case the shifter always gets its value from data bus bits 8-9, whether * the access is byte or word (that's because shifter has no access to UDS and LDS signals) * * byte access : * move.b #xy,$ff8261 * the CPU will put #xyxy on the 16 bit databus, as shifter reads bits 8-9 we get the correct * value in Shifter res * * word access : * move.w #xyab,$ff8260 * the CPU will put #xyab on the 16 bit databus, as shifter reads bits 8-9 (and not 0-1 as with * normal RAM) we get the correct value in Shifter res. Value #xy is copied into GLUE and Shifter * and lower byte #ab is ignored */ void Video_Res_WriteByte(void) { uint8_t Res, Res_Shifter; uint32_t addr; if (Config_IsMachineTT()) { TTRes = IoMem_ReadByte(0xff8260) & 7; /* Copy to TT shifter mode register: */ IoMem_WriteByte(0xff8262, TTRes); } else if (!bUseVDIRes) /* ST and STE mode */ { /* Acces through 0xff8260 or 0xff8261 */ addr = IoAccessCurrentAddress; /* We only care for lower 2-bits */ Res = IoMem[addr] & 3; if ( addr == 0xff8260 ) /* Write to GLUE first and then to Shifter */ Video_WriteToGlueRes ( Res ); /* TODO : possible rounding to 4 cycles should be added here */ /* Special case : shifter always reads resolution from bits 8-9 on the databus */ /* - regs.db requires to run cpu in cycle exact mode */ /* - for non-CE mode and non-byte access we use the value of ff8260 for shifter res */ if ( CpuRunCycleExact ) Res_Shifter = ( regs.db >> 8 ) & 3; else if ( nIoMemAccessSize == SIZE_BYTE ) Res_Shifter = Res; else Res_Shifter = IoMem[0xff8260] & 3; Video_WriteToShifterRes ( Res_Shifter ); /* We update resolution for the current line only when writing to ff8260, */ /* not when writing to ff8261 */ if ( addr == 0xff8260 ) { Video_SetHBLPaletteMaskPointers(); *pHBLPaletteMasks &= 0xff00ffff; /* Store resolution after palette mask and set resolution write bit: */ *pHBLPaletteMasks |= (((uint32_t)Res|0x04)<<16); } } /* Access to shifter regs are on a 4 cycle boundary */ /* Rounding is added here, after the value was processed by Video_Update_Glue_State() */ /* TODO : this call should be made above */ M68000_SyncCpuBus_OnWriteAccess(); } void Video_ResGlueShifter_WriteByte(void) /* Write to 0xff8260 */ { Video_Res_WriteByte(); } void Video_ResShifter_WriteByte(void) /* Write to 0xff8261 */ { Video_Res_WriteByte(); } /* * Handle horizontal scrolling to the left. * On STE, there're 2 registers that can scroll the line : * - $ff8264 : scroll without prefetch * - $ff8265 : scroll with prefetch * Both registers will scroll the line to the left by skipping the amount * of pixels in $ff8264 or $ff8265 (from 0 to 15). * As some pixels will be skipped, this means the shifter needs to read * 16 other pixels in advance in some internal registers to have an uninterrupted flow of pixels. * * These 16 pixels can be prefetched before the display starts (on cycle 56 for example) when using * $ff8265 to scroll the line. In that case 8 more bytes per line (low res) will be read. Most programs * are using $ff8265 to scroll the line. * * When using $ff8264, the next 16 pixels will not be prefetched before the display * starts, they will be read when the display normally starts (cycle 56). While * reading these 16 pixels, the shifter won't be able to display anything, which will * result in 16 pixels having the color 0. So, reading the 16 pixels will in fact delay * the real start of the line, which will look as if it started 16 pixels later. As the * shifter will stop the display at cycle 56+320 anyway, this means the last 16 pixels * of each line won't be displayed and you get the equivalent of a shorter 304 pixels line. * As a consequence, this register is rarely used to scroll the line. * * By writing a value > 0 in $ff8265 (to start prefetching) and immediately after a value of 0 * in $ff8264 (no scroll and no prefetch), it's possible to fill the internal registers used * for the scrolling even if scrolling is set to 0. In that case, the shifter will start displaying * each line 16 pixels earlier (as the data are already available in the internal registers). * This allows to have 336 pixels per line (instead of 320) for all the remaining lines on the screen. * * Although some programs are using this sequence : * move.w #1,$ffff8264 ; Word access! * clr.b $ffff8264 ; Byte access! * It is also possible to add 16 pixels by doing : * move.b #X,$ff8265 ; with X > 0 * move.b #0,$ff8264 * Some games (Obsession, Skulls) and demos (Pacemaker by Paradox) use this * feature to increase the resolution, so we have to emulate this bug, too! * * So considering a low res line of 320 pixels (160 bytes) : * - if both $ff8264/65 are 0, no scrolling happens, the shifter reads 160 bytes and displays 320 pixels (same as STF) * - if $ff8265 > 0, line is scrolled, the shifter reads 168 bytes and displays 320 pixels. * - if $ff8264 > 0, line is scrolled, the shifter reads 160 bytes and displays 304 pixels, * the display starts 16 pixels later. * - if $ff8265 > 0 and then $ff8264 = 0, there's no scrolling, the shifter reads 168 bytes and displays 336 pixels, * the display starts 16 pixels earlier. */ void Video_HorScroll_Read_8264(void) { /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnReadAccess(); } void Video_HorScroll_Read_8265(void) { /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnReadAccess(); /* [NP] TODO : it seems ff8265 has some additional wait states */ } void Video_HorScroll_Write_8264(void) { /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnWriteAccess(); Video_HorScroll_Write(); } void Video_HorScroll_Write_8265(void) { /* Access to shifter regs are on a 4 cycle boundary */ M68000_SyncCpuBus_OnWriteAccess(); /* [NP] TODO : it seems ff8265 has some additional wait states */ Video_HorScroll_Write(); } void Video_HorScroll_Write(void) { uint32_t RegAddr; uint8_t ScrollCount; uint8_t Prefetch; int FrameCycles, HblCounterVideo, LineCycles; bool Add16px = false; static uint8_t LastVal8265 = 0; LOG_TRACE_VAR int Delayed; Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles ); LineCycles = VIDEO_CYCLE_TO_HPOS ( LineCycles ); RegAddr = IoAccessCurrentAddress; /* 0xff8264 or 0xff8265 */ ScrollCount = IoMem[ RegAddr ]; ScrollCount &= 0x0f; if ( RegAddr == 0xff8264 ) { Prefetch = 0; /* scroll without prefetch */ LastCycleScroll8264 = FrameCycles; ShifterFrame.Scroll8264Pos.VBL = nVBLs; ShifterFrame.Scroll8264Pos.FrameCycles = FrameCycles; ShifterFrame.Scroll8264Pos.HBL = HblCounterVideo; ShifterFrame.Scroll8264Pos.LineCycles = LineCycles; if ( ( ScrollCount == 0 ) && ( LastVal8265 > 0 ) && ( ShifterFrame.Scroll8265Pos.VBL > 0 ) /* a write to ff8265 has been made */ && ( ShifterFrame.Scroll8265Pos.VBL == ShifterFrame.Scroll8264Pos.VBL ) /* during the same VBL */ && ( ShifterFrame.Scroll8264Pos.FrameCycles - ShifterFrame.Scroll8265Pos.FrameCycles <= 40 ) ) { LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect ste left+16 pixels\n" ); Add16px = true; } } else { Prefetch = 1; /* scroll with prefetch */ LastCycleScroll8265 = FrameCycles; ShifterFrame.Scroll8265Pos.VBL = nVBLs; ShifterFrame.Scroll8265Pos.FrameCycles = FrameCycles; ShifterFrame.Scroll8265Pos.HBL = HblCounterVideo; ShifterFrame.Scroll8265Pos.LineCycles = LineCycles; LastVal8265 = ScrollCount; Add16px = false; } /* If the write was made before display starts on the current line, then */ /* we can still change the value now. Else, the new values will be used */ /* for line n+1. */ /* We must also check the write does not overlap the end of the line */ if ( ( ( LineCycles <= pVideoTiming->HDE_On_Low_50 ) && ( nHBL == HblCounterVideo ) ) || ( nHBL < nStartHBL ) || ( nHBL >= nEndHBL + BlankLines ) ) { HWScrollCount = ScrollCount; /* display has not started, we can still change */ HWScrollPrefetch = Prefetch; bSteBorderFlag = Add16px; NewHWScrollCount = -1; /* cancel 'pending' change */ Delayed = false; } else { NewHWScrollCount = ScrollCount; /* display has started, can't change HWScrollCount now */ NewHWScrollPrefetch = Prefetch; if ( Add16px ) NewSteBorderFlag = 1; else NewSteBorderFlag = 0; Delayed = true; } LOG_TRACE(TRACE_VIDEO_STE , "write ste %x hwscroll=%x delayed=%s video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" , RegAddr , ScrollCount, Delayed ? "yes" : "no" , FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles ); } /*-----------------------------------------------------------------------*/ /** * Helper for TT->ST color reg copies */ static void TT2STColor(uint32_t ttaddr, uint32_t staddr) { uint16_t stcolor, ttcolor; ttcolor = IoMem_ReadWord(ttaddr); stcolor = ((ttcolor & 0xeee) >> 1) | ((ttcolor&0x111) << 3); IoMem_WriteWord(staddr, stcolor); #if 0 fprintf(stderr, "0x%x: 0x%03x (TT) -> 0x%x: 0x%03x (ST)\n", ttaddr, ttcolor, staddr, stcolor); #endif } /*-----------------------------------------------------------------------*/ /** * Write to TT shifter mode register (0xff8262) */ void Video_TTShiftMode_WriteWord(void) { uint32_t stpalette = 0xff8240; uint32_t ttpalette = 0xff8400; int i; TTRes = IoMem_ReadByte(0xff8262) & 7; TTSpecialVideoMode = IoMem_ReadByte(0xff8262) & 0x90; /*fprintf(stderr, "Write to FF8262: %x, res=%i\n", IoMem_ReadWord(0xff8262), TTRes);*/ /* Is it an ST compatible resolution? */ if (TTRes <= 2) { IoMem_WriteByte(0xff8260, TTRes); Video_Res_WriteByte(); IoMem_WriteByte(0xff8262, TTRes | TTSpecialVideoMode); } /* ST palette needs to be updated in case there was a bank switch */ ttpalette += TTPaletteSTBank() * 16*SIZE_WORD; #if 0 fprintf(stderr, "TT ST Palette bank: %d\n", TTPaletteSTBank()); #endif for (i = 0; i < 16*SIZE_WORD; i += SIZE_WORD) { TT2STColor(ttpalette, stpalette); ttpalette += SIZE_WORD; stpalette += SIZE_WORD; } /* in case bank was switched and there are <= 16 colors */ bTTColorsSync = false; } /*-----------------------------------------------------------------------*/ /** * Write to TT color register area (at 0xff8400) * * Sync TT to ST color register * * Although registers themselves are word sized, writes to this area * can be of any size. Hatari IO-area handling doesn't feed them here * word sized, that would require separate handler for each palette * entry, and there are 256 of them. */ void Video_TTColorRegs_Write(void) { const uint32_t stpalette = 0xff8240; const uint32_t ttpalette = 0xff8400; int offset, i; uint32_t addr; /* ensure even address for byte accesses */ addr = IoAccessCurrentAddress & 0xfffffffe; offset = addr - (ttpalette + TTPaletteSTBank() * 16*SIZE_WORD); /* in case it was long access */ for (i = 0; i < nIoMemAccessSize; i += 2) { /* outside ST->TT color reg mapping bank? */ if (offset < 0 || offset >= 16*SIZE_WORD) continue; TT2STColor(addr, stpalette + offset); offset += 2; addr += 2; } bTTColorsSync = false; } /*-----------------------------------------------------------------------*/ /** * Write to ST color register area on TT (starting at 0xff8240) * * ST color register write on TT -> sync to TT color register * * Although registers themselves are word sized, writes to this area * can be of any size. Hatari IO-area handling doesn't feed them here * word sized, that would require separate handler for each palette * entry. */ void Video_TTColorRegs_STRegWrite(void) { const uint32_t stpalette = 0xff8240; const uint32_t ttpalette = 0xff8400; uint16_t ttcolor; uint16_t stcolor; int offset, i; uint32_t addr; /* byte writes don't have effect on TT */ if (nIoMemAccessSize < 2) return; addr = IoAccessCurrentAddress; offset = addr - stpalette; assert(offset >= 0 && offset < 16*SIZE_WORD); offset += TTPaletteSTBank() * 16*SIZE_WORD; /* in case it was long access */ for (i = 0; i < nIoMemAccessSize; i += 2) { /* program may write 0xFFFF and read it back * to check for STe palette so need to be masked */ stcolor = IoMem_ReadWord(addr) & 0xfff; IoMem_WriteWord(addr, stcolor); /* Sync ST(e) color to TT register */ ttcolor = ((stcolor & 0x777) << 1) | ((stcolor & 0x888) >> 3); IoMem_WriteWord(ttpalette + offset, ttcolor); #if 0 fprintf(stderr, "0x%x: 0x%03x (ST) -> 0x%x: 0x%03x (TT)\n", addr, stcolor, ttpalette + offset, ttcolor); #endif offset += 2; addr += 2; } bTTColorsSync = false; } /*-----------------------------------------------------------------------*/ /** * This function is called during Video_InterruptHandler_HBL. * In TT mode we don't call Video_EndHBL() which is STF/STE specific and * call Video_CopyScreenLineColor/Mono later, but we must still update pVideoRaster * in case a program reads video counter at $FF8205/07/09 * * This is a rather simple version that assumes the TT screen is using the * same display mode during the whole frame. * NOTE : This function will be called at the end of line nHBL, so the next displayed * line will be in fact nHBL+1 */ static void Video_TT_RasterHBL(void) { /* Only update video counter in TT mode */ int width, height, bpp, linebytes , lines; Video_GetTTRes(&width, &height, &bpp); linebytes = width * bpp / 8; if ( nHBL+1 < nStartHBL ) { /* pVideoRaster was set during Video_ClearOnVBL using VideoBase */ /* It's already the correct value */ } else { lines = nHBL+1 - nStartHBL; if ( lines >= height ) lines = height; pVideoRaster = ( ( Video_GetScreenBaseAddr() + lines * linebytes ) & 0xffffff ) + STRam; } } /*-----------------------------------------------------------------------*/ /** * display video related information (for debugger info command) */ void Video_Info(FILE *fp, uint32_t dummy) { const char *mode; switch (VerticalOverscan) { case V_OVERSCAN_NONE: mode = "none"; break; case V_OVERSCAN_NO_TOP: mode = "top"; break; case V_OVERSCAN_NO_BOTTOM_50: case V_OVERSCAN_NO_BOTTOM_60: mode = "bottom"; break; case V_OVERSCAN_NO_TOP|V_OVERSCAN_NO_BOTTOM_50: mode = "top+bottom"; break; case V_OVERSCAN_NO_DE: mode = "no V_DE"; break; default: mode = "unknown"; } fprintf(fp, "Video base : 0x%x\n", VideoBase); fprintf(fp, "VBL counter : %d\n", nVBLs); fprintf(fp, "HBL line : %d\n", nHBL); fprintf(fp, "V-overscan : %s\n", mode); fprintf(fp, "Refresh rate : %d Hz\n", nScreenRefreshRate); fprintf(fp, "Frame skips : %d\n", nFrameSkips); /* TODO: any other information that would be useful to show? */ } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/wavFormat.c000066400000000000000000000134641504763705000234520ustar00rootroot00000000000000/* Hatari - wavFormat.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. WAV File output As well as YM file output we also have output in .WAV format. These .WAV files can then be run through converters to any other format, such as MP3. We simply save out the WAVE format headers and then write the sample data (at the current rate of playback) as we build it up each frame. When we stop recording we complete the size information in the headers and close up. RIFF Chunk (12 bytes in length total) Byte Number 0 - 3 "RIFF" (ASCII Characters) 4 - 7 Total Length Of Package To Follow (Binary, little endian) 8 - 12 "WAVE" (ASCII Characters) FORMAT Chunk (24 bytes in length total) Byte Number 0 - 3 "fmt_" (ASCII Characters) 4 - 7 Length Of FORMAT Chunk (Binary, always 0x10) 8 - 9 Always 0x01 10 - 11 Channel Numbers (Always 0x01=Mono, 0x02=Stereo) 12 - 15 Sample Rate (Binary, in Hz) 16 - 19 Bytes Per Second 20 - 21 Bytes Per Sample: 1=8 bit Mono, 2=8 bit Stereo or 16 bit Mono, 4=16 bit Stereo 22 - 23 Bits Per Sample DATA Chunk Byte Number 0 - 3 "data" (ASCII Characters) 4 - 7 Length Of Data To Follow 8 - end Data (Samples) */ const char WAVFormat_fileid[] = "Hatari wavFormat.c"; #include #include "main.h" #include "audio.h" #include "configuration.h" #include "file.h" #include "log.h" #include "sound.h" #include "wavFormat.h" static FILE *WavFileHndl; static int nWavOutputBytes; /* Number of samples bytes saved */ bool bRecordingWav = false; /* Is a WAV file open and recording? */ static uint8_t WavHeader[] = { /* RIFF chunk */ 'R', 'I', 'F', 'F', /* "RIFF" (ASCII Characters) */ 0, 0, 0, 0, /* Total Length Of Package To Follow (patched when file is closed) */ 'W', 'A', 'V', 'E', /* "WAVE" (ASCII Characters) */ /* Format chunk */ 'f', 'm', 't', ' ', /* "fmt_" (ASCII Characters) */ 0x10, 0, 0, 0, /* Length Of FORMAT Chunk (always 0x10) */ 0x01, 0, /* Always 0x01 */ 0x02, 0, /* Number of channels (2 for stereo) */ 0, 0, 0, 0, /* Sample rate (patched when file header is written) */ 0, 0, 0, 0, /* Bytes per second (patched when file header is written) */ 0x04, 0, /* Bytes per sample (4 = 16 bit stereo) */ 0x10, 0, /* Bits per sample (16 bit) */ /* Data chunk */ 'd', 'a', 't', 'a', 0, 0, 0, 0, /* Length of data to follow (will be patched when file is closed) */ }; /** * Open WAV output file and write header. */ bool WAVFormat_OpenFile(char *pszWavFileName) { uint32_t nSampleFreq, nBytesPerSec; bRecordingWav = false; nWavOutputBytes = 0; /* Set frequency (11Khz, 22Khz or 44Khz) */ nSampleFreq = ConfigureParams.Sound.nPlaybackFreq; /* multiply by 4 for 16 bit stereo */ nBytesPerSec = nSampleFreq * 4; /* Create our file */ WavFileHndl = fopen(pszWavFileName, "wb"); if (!WavFileHndl) { perror("WAVFormat_OpenFile"); Log_AlertDlg(LOG_ERROR, "WAV recording: Failed to open file!"); return false; } /* Patch sample frequency in header structure */ WavHeader[24] = (uint8_t)nSampleFreq; WavHeader[25] = (uint8_t)(nSampleFreq >> 8); WavHeader[26] = (uint8_t)(nSampleFreq >> 16); WavHeader[27] = (uint8_t)(nSampleFreq >> 24); /* Patch bytes per second in header structure */ WavHeader[28] = (uint8_t)nBytesPerSec; WavHeader[29] = (uint8_t)(nBytesPerSec >> 8); WavHeader[30] = (uint8_t)(nBytesPerSec >> 16); WavHeader[31] = (uint8_t)(nBytesPerSec >> 24); /* Write header to file */ if (fwrite(&WavHeader, sizeof(WavHeader), 1, WavFileHndl) == 1) { bRecordingWav = true; Log_AlertDlg(LOG_INFO, "WAV sound data recording has been started."); } else { perror("WAVFormat_OpenFile"); Log_AlertDlg(LOG_ERROR, "WAV recording: Failed to write header!"); } /* Ok, or failed? */ return bRecordingWav; } /** * Write sizes to WAV header, then close the WAV file. */ void WAVFormat_CloseFile(void) { if (bRecordingWav) { uint32_t nWavFileBytes; uint32_t nWavLEOutBytes; bRecordingWav = false; /* Update headers with sizes */ nWavFileBytes = SDL_SwapLE32((12+24+8+nWavOutputBytes)-8); /* File length, less 8 bytes for 'RIFF' and length */ /* Seek to 'Total Length Of Package' element and * write total length of package in 'RIFF' chunk */ if (fseek(WavFileHndl, 4, SEEK_SET) != 0 || fwrite(&nWavFileBytes, sizeof(uint32_t), 1, WavFileHndl) != 1) { perror("WAVFormat_CloseFile"); fclose(WavFileHndl); WavFileHndl = NULL; return; } nWavLEOutBytes = SDL_SwapLE32(nWavOutputBytes); /* Seek to 'Length' element and write length of data in 'DATA' chunk */ if (fseek(WavFileHndl, 12+24+4, SEEK_SET) != 0 || fwrite(&nWavLEOutBytes, sizeof(uint32_t), 1, WavFileHndl) != 1) { perror("WAVFormat_CloseFile"); } /* Close file */ fclose(WavFileHndl); WavFileHndl = NULL; /* And inform user */ Log_AlertDlg(LOG_INFO, "WAV Sound data recording has been stopped."); } } /** * Update WAV file with current samples */ void WAVFormat_Update(int16_t pSamples[][2], int Index, int Length) { int16_t sample[2]; int i; int idx; if (bRecordingWav) { /* Output, better if did in two section if wrap */ idx = Index & AUDIOMIXBUFFER_SIZE_MASK; for(i = 0; i < Length; i++) { /* Convert sample to little endian */ sample[0] = SDL_SwapLE16(pSamples[idx][0]); sample[1] = SDL_SwapLE16(pSamples[idx][1]); idx = ( idx+1 ) & AUDIOMIXBUFFER_SIZE_MASK; /* And store */ if (fwrite(&sample, sizeof(sample), 1, WavFileHndl) != 1) { perror("WAVFormat_Update"); WAVFormat_CloseFile(); return; } } /* Add samples to wav file length counter */ nWavOutputBytes += Length * 4; } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/xbios.c000066400000000000000000000407021504763705000226230ustar00rootroot00000000000000/* Hatari - xbios.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. XBios Handler (Trap #14) - http://toshyp.atari.org/en/004014.html Intercept and direct XBios calls to allow saving screenshots in host format and to help with tracing/debugging. */ const char XBios_fileid[] = "Hatari xbios.c"; #include "main.h" #include "configuration.h" #include "control.h" #include "floppy.h" #include "log.h" #include "m68000.h" #include "rs232.h" #include "screenSnapShot.h" #include "stMemory.h" #include "debugui.h" #include "xbios.h" #define HATARI_CONTROL_OPCODE 255 /* whether to enable XBios(11/20/255) */ static bool bXBiosCommands; void XBios_EnableCommands(bool enable) { bXBiosCommands = enable; } /** * XBIOS Dbmsg * Call 11 * * Atari debugger API: * http://dev-docs.atariforge.org/files/Atari_Debugger_1-24-1990.pdf * http://toshyp.atari.org/en/004012.html#Dbmsg */ static bool XBios_Dbmsg(uint32_t Params) { /* Read details from stack */ const uint16_t reserved = STMemory_ReadWord(Params); const uint16_t msgnum = STMemory_ReadWord(Params+SIZE_WORD); const uint32_t addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_WORD); LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x0B Dbmsg(%d, 0x%04X, 0x%x) at PC 0x%X\n", reserved, msgnum, addr, M68000_GetPC()); if (reserved != 5 || !bXBiosCommands) return false; fprintf(stderr, "Dbmsg: 0x%04X, 0x%x\n", msgnum, addr); /* debugger message? */ if (msgnum >= 0xF000 && msgnum <= 0xF100) { const char *txt = (const char *)STMemory_STAddrToPointer(addr); char buffer[256]; /* between non-halting message and debugger command IDs, * are halting messages with message length encoded in ID */ if (msgnum > 0xF000 && msgnum < 0xF100) { const int len = (msgnum & 0xFF); memcpy(buffer, txt, len); buffer[len] = '\0'; txt = buffer; } fprintf(stderr, "-> \"%s\"\n", txt); } /* not just a message? */ if (msgnum != 0xF000) { fprintf(stderr, "-> HALT\n"); DebugUI(REASON_PROGRAM); } /* return value != function opcode, to indicate it's implemented */ Regs[REG_D0] = 0; return true; } /** * XBIOS Scrdmp * Call 20 */ static bool XBios_Scrdmp(uint32_t Params) { LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x14 Scrdmp() at PC 0x%X\n" , M68000_GetPC()); if (!bXBiosCommands) return false; ScreenSnapShot_SaveScreen(); /* Scrdmp() doesn't have return value, but return something else than * function number to indicate this XBios opcode was implemented */ Regs[REG_D0] = 0; return true; } /** * XBIOS remote control interface for Hatari * Call 255 */ static bool XBios_HatariControl(uint32_t Params) { const char *pText; pText = (const char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params)); LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02X HatariControl(%s) at PC 0x%X\n", HATARI_CONTROL_OPCODE, pText, M68000_GetPC()); if (!bXBiosCommands) return false; Control_ProcessBuffer(pText); /* return value != function opcode, to indicate it's implemented */ Regs[REG_D0] = 0; return true; } #if ENABLE_TRACING /** * XBIOS Floppy Read * Call 8 */ static bool XBios_Floprd(uint32_t Params) { uint32_t pBuffer; uint16_t Dev, Sector, Side, Track, Count; /* Read details from stack */ pBuffer = STMemory_ReadLong(Params); Dev = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG); /* skip reserved long */ Sector = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD); Track = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD+SIZE_WORD); Side = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD+SIZE_WORD+SIZE_WORD); Count = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD+SIZE_WORD+SIZE_WORD+SIZE_WORD); LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x08 Floprd(0x%x, %d, %d, %d, %d, %d) at PC 0x%X for: %s\n", pBuffer, Dev, Sector, Track, Side, Count, M68000_GetPC(), Dev < MAX_FLOPPYDRIVES ? EmulationDrives[Dev].sFileName : "n/a"); return false; } /** * XBIOS Floppy Write * Call 9 */ static bool XBios_Flopwr(uint32_t Params) { uint32_t pBuffer; uint16_t Dev, Sector, Side, Track, Count; /* Read details from stack */ pBuffer = STMemory_ReadLong(Params); Dev = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG); /* skip reserved long */ Sector = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD); Track = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD+SIZE_WORD); Side = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD+SIZE_WORD+SIZE_WORD); Count = STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD+SIZE_WORD+SIZE_WORD+SIZE_WORD); LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x09 Flopwr(0x%x, %d, %d, %d, %d, %d) at PC 0x%X for: %s\n", pBuffer, Dev, Sector, Track, Side, Count, M68000_GetPC(), Dev < MAX_FLOPPYDRIVES ? EmulationDrives[Dev].sFileName : "n/a"); return false; } /** * XBIOS RsConf * Call 15 */ static bool XBios_Rsconf(uint32_t Params) { int16_t Baud, Ctrl, Ucr, Rsr, Tsr, Scr; Baud = STMemory_ReadWord(Params); Ctrl = STMemory_ReadWord(Params+SIZE_WORD); Ucr = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD); Rsr = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD+SIZE_WORD); Tsr = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD+SIZE_WORD+SIZE_WORD); Scr = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD+SIZE_WORD+SIZE_WORD+SIZE_WORD); LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x0F Rsconf(%d, %d, %d, %d, %d, %d) at PC 0x%X\n", Baud, Ctrl, Ucr, Rsr, Tsr, Scr, M68000_GetPC()); return false; } /** * XBIOS Devconnect * Call 139 */ static bool XBios_Devconnect(uint32_t Params) { uint16_t src, dst, clk, prescale, protocol; /* Read details from stack */ src = STMemory_ReadWord(Params); dst = STMemory_ReadWord(Params+SIZE_WORD); clk = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD); prescale = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD+SIZE_WORD); protocol = STMemory_ReadWord(Params+SIZE_WORD+SIZE_WORD+SIZE_WORD+SIZE_WORD); LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x8B Devconnect(%hd, 0x%hx, %hd, %hd, %hd) at PC 0x%X\n", src, dst, clk, prescale, protocol , M68000_GetPC() ); return false; } /** * Map XBIOS call opcode to XBIOS function name * * Mapping is based on TOSHYP information: * http://toshyp.atari.org/en/004014.html */ static const char* XBios_Call2Name(uint16_t opcode) { static const char* names[] = { "Initmous", "Ssbrk", "Physbase", "Logbase", "Getrez", "Setscreen", "Setpalette", "Setcolor", "Floprd", "Flopwr", "Flopfmt", "Dbmsg", "Midiws", "Mfpint", "Iorec", "Rsconf", "Keytbl", "Random", "Protobt", "Flopver", "Scrdmp", "Cursconf", "Settime", "Gettime", "Bioskeys", "Ikbdws", "Jdisint", "Jenabint", "Giaccess", "Offgibit", "Ongibit", "Xbtimer", "Dosound", "Setprt", "Kbdvbase", "Kbrate", "Prtblk", "Vsync", "Supexec", "Puntaes", NULL, /* 40 */ "Floprate", "DMAread", "DMAwrite", "Bconmap", NULL, /* 45 */ "NVMaccess", "Waketime", /* TOS 2.06 */ "Metainit", NULL, /* 49: rest of MetaDOS calls */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 63 */ "Blitmode", NULL, /* 65: CENTScreen */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 79 */ "EsetShift", "EgetShift", "EsetBank", "EsetColor", "EsetPalette", "EgetPalette", "EsetGray", "EsetSmear", "VsetMode", "VgetMonitor", "VsetSync", "VgetSize", "VsetVars", /* TOS4 internal */ "VsetRGB", "VgetRGB", "VcheckMode", /* TOS4 internal (ValidMode()) */ "Dsp_DoBlock", "Dsp_BlkHandShake", "Dsp_BlkUnpacked", "Dsp_InStream", "Dsp_OutStream", "Dsp_IOStream", "Dsp_RemoveInterrupts", "Dsp_GetWordSize", "Dsp_Lock", "Dsp_Unlock", "Dsp_Available", "Dsp_Reserve", "Dsp_LoadProg", "Dsp_ExecProg", "Dsp_ExecBoot", "Dsp_LodToBinary", "Dsp_TriggerHC", "Dsp_RequestUniqueAbility", "Dsp_GetProgAbility", "Dsp_FlushSubroutines", "Dsp_LoadSubroutine", "Dsp_InqSubrAbility", "Dsp_RunSubroutine", "Dsp_Hf0", "Dsp_Hf1", "Dsp_Hf2", "Dsp_Hf3", "Dsp_BlkWords", "Dsp_BlkBytes", "Dsp_HStat", "Dsp_SetVectors", "Dsp_MultBlocks", "Locksnd", "Unlocksnd", "Soundcmd", "Setbuffer", "Setmode", "Settracks", "Setmontracks", "Setinterrupt", "Buffoper", "Dsptristate", "Gpio", "Devconnect", "Sndstatus", "Buffptr", NULL, /* 142 */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 149 */ "VsetMask", NULL, /* 151 */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* 164 */ "WavePlay" }; if (opcode < ARRAY_SIZE(names) && names[opcode]) { return names[opcode]; } return "???"; } void XBios_Info(FILE *fp, uint32_t dummy) { uint16_t opcode; for (opcode = 0; opcode < 168; ) { fprintf(fp, "%02x %-21s", opcode, XBios_Call2Name(opcode)); if (++opcode % 3 == 0) { fputs("\n", fp); } } } #else /* !ENABLE_TRACING */ #define XBios_Floprd(params) false #define XBios_Flopwr(params) false #define XBios_Rsconf(params) false #define XBios_Devconnect(params) false void XBios_Info(FILE *fp, uint32_t bShowOpcodes) { fputs("Hatari isn't configured with ENABLE_TRACING\n", fp); } #endif /* !ENABLE_TRACING */ /** * Check if we need to re-direct XBios call to our own routines */ bool XBios(void) { uint32_t Params; uint16_t XBiosCall; /* Find call */ Params = Regs[REG_A7]; XBiosCall = STMemory_ReadWord(Params); Params += SIZE_WORD; switch (XBiosCall) { /* commands with special handling */ case 8: return XBios_Floprd(Params); case 9: return XBios_Flopwr(Params); case 11: return XBios_Dbmsg(Params); case 15: return XBios_Rsconf(Params); case 20: return XBios_Scrdmp(Params); case 139: return XBios_Devconnect(Params); case HATARI_CONTROL_OPCODE: return XBios_HatariControl(Params); case 2: /* Physbase */ case 3: /* Logbase */ case 4: /* Getrez */ case 17: /* Random */ case 23: /* Gettime */ case 24: /* Bioskeys */ case 34: /* Kbdvbase */ case 37: /* Vsync */ case 39: /* Puntaes */ case 81: /* EgetShift */ case 89: /* VgetMonitor */ case 103: /* Dsp_GetWordSize */ case 104: /* Dsp_Lock */ case 105: /* Dsp_Unlock */ case 113: /* Dsp_RequestUniqueAbility */ case 114: /* Dsp_GetProgAbility */ case 115: /* Dsp_FlushSubroutines */ case 121: /* Dsp_Hf2 */ case 122: /* Dsp_Hf3 */ case 125: /* Dsp_Hstat */ case 128: /* Locksnd */ case 129: /* Unlocksnd */ /* commands with no args */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s() at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), M68000_GetPC()); return false; case 1: /* Ssbrk */ case 14: /* Iorec */ case 26: /* Jdisint */ case 27: /* Jenabint */ case 29: /* Offgibit */ case 30: /* Ongibit */ case 33: /* Setprt */ case 44: /* Bconmap */ case 64: /* Blitmode */ case 80: /* EsetShift */ case 82: /* EsetBank */ case 86: /* EsetGray */ case 87: /* EsetSmear */ case 88: /* VsetMode */ case 90: /* VsetSync */ case 91: /* VgetSize */ case 95: /* VcheckMode */ case 102: /* Dsp_RemoveInterrupts */ case 112: /* Dsp_TriggerHC */ case 117: /* Dsp_InqSubrAbility */ case 118: /* Dsp_RunSubroutine */ case 119: /* Dsp_Hf0 */ case 120: /* Dsp_Hf1 */ case 132: /* Setmode */ case 134: /* Setmontracks */ case 136: /* Buffoper */ case 140: /* Sndstatus */ /* ones taking single word */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%hX) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadWord(Params), M68000_GetPC()); return false; case 6: /* Setpalette */ case 22: /* Settime */ case 32: /* Dosound */ case 36: /* Ptrblt */ case 38: /* Supexec */ case 48: /* Metainit */ case 141: /* Buffptr */ /* ones taking long or pointer */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadLong(Params), M68000_GetPC()); return false; case 7: /* Setcolor */ case 21: /* Cursconf */ case 28: /* Giaccess */ case 35: /* Kbrate */ case 41: /* Floprate */ case 83: /* EsetColor */ case 130: /* Soundcmd */ case 133: /* Settracks */ case 137: /* Dsptristate */ case 135: /* Setinterrupt */ case 138: /* Gpio */ /* ones taking two words */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%hX, 0x%hX) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadWord(Params), STMemory_ReadWord(Params+SIZE_WORD), M68000_GetPC()); return false; case 12: /* Midiws */ case 13: /* Mfpint */ case 25: /* Ikbdws */ /* ones taking word length/index and pointer */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(%hd, 0x%X) at PC 0x %X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadWord(Params), STMemory_ReadLong(Params+SIZE_WORD), M68000_GetPC()); return false; case 84: /* EsetPalette */ case 85: /* EgetPalette */ case 93: /* VsetRGB */ case 94: /* VgetRGB */ /* ones taking word, word and long/pointer */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%hX, 0x%hX, 0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadWord(Params), STMemory_ReadWord(Params+SIZE_WORD), STMemory_ReadLong(Params+SIZE_WORD+SIZE_WORD), M68000_GetPC()); return false; case 131: /* Setbuffer */ /* ones taking word and two longs/pointers */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%hX, 0x%X, 0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadWord(Params), STMemory_ReadLong(Params+SIZE_WORD), STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG), M68000_GetPC()); return false; case 106: /* Dsp_Available */ case 107: /* Dsp_Reserve */ case 111: /* Dsp_LodToBinary */ case 126: /* Dsp_SetVectors */ /* ones taking two longs/pointers */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%X, 0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadLong(Params), STMemory_ReadLong(Params+SIZE_LONG), M68000_GetPC()); return false; case 108: /* Dsp_LoadProg */ /* ones taking long/pointer, word and long/pointer */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%X, 0x%hX, 0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadLong(Params), STMemory_ReadWord(Params+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_WORD), M68000_GetPC()); return false; case 96: /* Dsp_DoBlock */ case 97: /* Dsp_BlkHandShake */ case 98: /* Dsp_BlkUnpacked */ case 99: /* Dsp_InStream */ case 100: /* Dsp_OutStream */ case 123: /* Dsp_BlkWords */ case 124: /* Dsp_BlkBytes */ case 127: /* Dsp_MultBlocks */ /* ones taking four longs/pointers */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%X, 0x%X, 0x%X, 0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadLong(Params), STMemory_ReadLong(Params+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_LONG+SIZE_LONG), M68000_GetPC()); return false; case 101: /* Dsp_IOStream */ /* ones taking six longs/pointers */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%X, 0x%X, 0x%X, 0x%X, 0x%X, 0x%X) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadLong(Params), STMemory_ReadLong(Params+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_LONG+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_LONG+SIZE_LONG+SIZE_LONG), STMemory_ReadLong(Params+SIZE_LONG+SIZE_LONG+SIZE_LONG+SIZE_LONG+SIZE_LONG), M68000_GetPC()); return false; case 5: /* Setscreen */ if (STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG) == 3) { /* actually VSetscreen with extra parameter */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX VsetScreen(0x%X, 0x%X, 3, 0x%hX) at PC 0x%X\n", XBiosCall, STMemory_ReadLong(Params), STMemory_ReadLong(Params+SIZE_LONG), STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG+SIZE_WORD), M68000_GetPC()); return false; } /* fall through */ case 109: /* Dsp_ExecProg */ case 110: /* Dsp_ExecBoot */ case 116: /* Dsp_LoadSubroutine */ case 150: /* VsetMask */ /* ones taking two longs/pointers and a word */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX %s(0x%X, 0x%X, 0x%hX) at PC 0x%X\n", XBiosCall, XBios_Call2Name(XBiosCall), STMemory_ReadLong(Params), STMemory_ReadLong(Params+SIZE_LONG), STMemory_ReadWord(Params+SIZE_LONG+SIZE_LONG), M68000_GetPC()); return false; default: /* rest of XBios calls */ LOG_TRACE(TRACE_OS_XBIOS, "XBIOS 0x%02hX (%s)\n", XBiosCall, XBios_Call2Name(XBiosCall)); return false; } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/ymFormat.c000066400000000000000000000114001504763705000232660ustar00rootroot00000000000000/* Hatari - ymFormat.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. YM File output, for use with STSound etc... */ const char YMFormat_fileid[] = "Hatari ymFormat.c"; #include "main.h" #include "configuration.h" #include "file.h" #include "log.h" #include "psg.h" #include "sound.h" #include "ymFormat.h" #define YM_MAX_VBLS (50*60*8) /* 50=1 second, 50*60=1 minute, 50*60*8=8 minutes, or 24000 */ #define YM_RECORDSIZE (4+(YM_MAX_VBLS*NUM_PSG_SOUND_REGISTERS)) /* ~330k for 8 minutes */ bool bRecordingYM = false; static int nYMVBLS = 0; static uint8_t *pYMData, *pYMWorkspace = NULL; static char *pszYMFileName = NULL; /*-----------------------------------------------------------------------*/ /** * Start recording YM registers to workspace */ bool YMFormat_BeginRecording(const char *filename) { /* Free any previous data, don't save */ bRecordingYM = false; YMFormat_EndRecording(); /* Make sure we have a proper filename to use */ if (!filename || strlen(filename) <= 0) { return false; } pszYMFileName = strdup(filename); if (!pszYMFileName) { return false; } /* Create YM workspace */ pYMWorkspace = (uint8_t *)malloc(YM_RECORDSIZE); if (!pYMWorkspace) { /* Failed to allocate memory, cannot record */ free(pszYMFileName); pszYMFileName = NULL; return false; } /* Get workspace pointer and store 4 byte header */ pYMData = pYMWorkspace; *pYMData++ = 'Y'; *pYMData++ = 'M'; *pYMData++ = '3'; *pYMData++ = '!'; bRecordingYM = true; /* Ready to record */ nYMVBLS = 0; /* Number of VBLs of information */ /* And inform user */ Log_AlertDlg(LOG_INFO, "YM sound data recording has been started."); return true; } /*-----------------------------------------------------------------------*/ /** * Convert YM data to stream for output * * Data is: * 4 Byte header 'YM3!' * VBL Count x 14 PSG registers * BUT * We need data in a register stream, eg Reg 0, VBL 1, VBL 2, VBL n and then next register... * * Convert to new workspace and return true if all OK. */ static bool YMFormat_ConvertToStreams(void) { uint8_t *pNewYMWorkspace; uint8_t *pTmpYMData, *pNewYMData; uint8_t *pTmpYMStream, *pNewYMStream; int Reg, Count; /* Allocate new workspace to convert data to */ pNewYMWorkspace = (uint8_t *)malloc(YM_RECORDSIZE); if (pNewYMWorkspace) { /* Convert data, first copy over header */ pTmpYMData = pYMWorkspace; pNewYMData = pNewYMWorkspace; *pNewYMData++ = *pTmpYMData++; *pNewYMData++ = *pTmpYMData++; *pNewYMData++ = *pTmpYMData++; *pNewYMData++ = *pTmpYMData++; /* Now copy over each stream */ for(Reg=0; Reg=YM_MAX_VBLS) YMFormat_EndRecording(); } } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/src/zip.c000066400000000000000000000352401504763705000223020ustar00rootroot00000000000000/* Hatari - zip.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Zipped disk support, uses zlib */ const char ZIP_fileid[] = "Hatari zip.c"; #include "main.h" #include #include #include #if HAVE_ZLIB_H #include #endif #include "dim.h" #include "file.h" #include "floppy.h" #include "floppy_ipf.h" #include "floppy_stx.h" #include "log.h" #include "msa.h" #include "st.h" #include "str.h" #include "unzip.h" #include "zip.h" #ifdef QNX #include #define dirent direct #endif /* #define SAVE_TO_ZIP_IMAGES */ #define ZIP_PATH_MAX 256 #if HAVE_LIBZ /* Possible disk image extensions to scan for */ static const char * const pszDiskNameExts[] = { ".msa", ".st", ".dim", ".ipf", ".raw", ".ctr", ".stx", NULL }; /*-----------------------------------------------------------------------*/ /** * Does filename end with a .ZIP extension? If so, return true. */ bool ZIP_FileNameIsZIP(const char *pszFileName) { return File_DoesFileExtensionMatch(pszFileName,".zip"); } /*-----------------------------------------------------------------------*/ /** * Check if a file name contains a slash or backslash and return its position. */ static int Zip_FileNameHasSlash(const char *fn) { int i=0; while (fn[i] != '\0') { if (fn[i] == '\\' || fn[i] == '/') return i; i++; } return -1; } /*-----------------------------------------------------------------------*/ /** * Returns a list of files from a zip file. returns NULL on failure, * returns a pointer to an array of strings if successful. Sets nfiles * to the number of files. */ zip_dir *ZIP_GetFiles(const char *pszFileName) { int nfiles; unsigned int i; unz_global_info gi; int err; unzFile uf; char **filelist; unz_file_info file_info; char filename_inzip[ZIP_PATH_MAX]; zip_dir *zd = NULL; uf = unzOpen(pszFileName); if (uf == NULL) { Log_Printf(LOG_ERROR, "ZIP_GetFiles: Cannot open %s\n", pszFileName); return NULL; } err = unzGetGlobalInfo(uf,&gi); if (err != UNZ_OK) { Log_Printf(LOG_ERROR, "Error %d with zipfile in unzGetGlobalInfo \n",err); return NULL; } /* allocate a file list */ filelist = (char **)malloc(gi.number_entry*sizeof(char *)); if (!filelist) { perror("ZIP_GetFiles"); unzClose(uf); return NULL; } nfiles = gi.number_entry; /* set the number of files */ for (i = 0; i < gi.number_entry; i++) { err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, ZIP_PATH_MAX, NULL, 0, NULL, 0); if (err != UNZ_OK) { Log_Printf(LOG_ERROR, "ZIP_GetFiles: Error in ZIP-file\n"); goto cleanup; } filelist[i] = (char *)malloc(strlen(filename_inzip) + 1); if (!filelist[i]) { perror("ZIP_GetFiles"); goto cleanup; } strcpy(filelist[i], filename_inzip); if ((i+1) < gi.number_entry) { err = unzGoToNextFile(uf); if (err != UNZ_OK) { Log_Printf(LOG_ERROR, "ZIP_GetFiles: Error in ZIP-file\n"); goto cleanup; } } } zd = (zip_dir *)malloc(sizeof(zip_dir)); if (zd) { zd->names = filelist; zd->nfiles = nfiles; } else { perror("ZIP_GetFiles"); } cleanup: unzClose(uf); if (!zd && filelist) { /* deallocate memory */ for (; i > 0; i--) free(filelist[i]); free(filelist); } return zd; } /*-----------------------------------------------------------------------*/ /** * Free the memory that has been allocated for a zip_dir. */ void ZIP_FreeZipDir(zip_dir *f_zd) { while (f_zd->nfiles > 0) { f_zd->nfiles--; free(f_zd->names[f_zd->nfiles]); f_zd->names[f_zd->nfiles] = NULL; } free(f_zd->names); f_zd->names = NULL; free(f_zd); } /*-----------------------------------------------------------------------*/ /** * Free the memory that has been allocated for fentries. */ static void ZIP_FreeFentries(struct dirent **fentries, int entries) { while (entries > 0) { entries--; free(fentries[entries]); } free(fentries); } /*-----------------------------------------------------------------------*/ /** * Returns a list of files from the directory (dir) in a zip file list (zip) * sets entries to the number of entries and returns a dirent structure, or * NULL on failure. NOTE: only f_name is set in the dirent structures. */ struct dirent **ZIP_GetFilesDir(const zip_dir *zip, const char *dir, int *entries) { int i,j; zip_dir *files; char *temp; bool flag; int slash; struct dirent **fentries; files = (zip_dir *)malloc(sizeof(zip_dir)); if (!files) { perror("ZIP_GetFilesDir"); return NULL; } files->names = (char **)malloc((zip->nfiles + 1) * sizeof(char *)); if (!files->names) { perror("ZIP_GetFilesDir"); free(files); return NULL; } /* add ".." directory */ files->nfiles = 0; temp = (char *)malloc(4); if (!temp) { ZIP_FreeZipDir(files); return NULL; } temp[0] = temp[1] = '.'; temp[2] = '/'; temp[3] = '\0'; files->names[0] = temp; files->nfiles++; for (i = 0; i < zip->nfiles; i++) { if (strlen(zip->names[i]) > strlen(dir)) { if (strncasecmp(zip->names[i], dir, strlen(dir)) == 0) { temp = zip->names[i]; temp = (char *)(temp + strlen(dir)); if (temp[0] != '\0') { if ((slash=Zip_FileNameHasSlash(temp)) > 0) { /* file is in a subdirectory, add this subdirectory if it doesn't exist in the list */ flag = false; for (j = files->nfiles-1; j > 0; j--) { if (strncasecmp(temp, files->names[j], slash+1) == 0) flag = true; } if (flag == false) { char *subdir = malloc(slash+2); if (!subdir) { perror("ZIP_GetFilesDir"); ZIP_FreeZipDir(files); return NULL; } strncpy(subdir, temp, slash+1); subdir[slash+1] = '\0'; files->names[files->nfiles] = subdir; files->nfiles++; } } else { /* add a filename */ files->names[files->nfiles] = strdup(temp); if (!files->names[files->nfiles]) { perror("ZIP_GetFilesDir"); ZIP_FreeZipDir(files); return NULL; } files->nfiles++; } } } } } /* copy to a dirent structure */ *entries = files->nfiles; fentries = (struct dirent **)malloc(sizeof(struct dirent *)*files->nfiles); if (!fentries) { perror("ZIP_GetFilesDir"); ZIP_FreeZipDir(files); return NULL; } for (i = 0; i < files->nfiles; i++) { fentries[i] = (struct dirent *)malloc(sizeof(struct dirent)); if (!fentries[i]) { perror("ZIP_GetFilesDir"); ZIP_FreeFentries(fentries, i+1); ZIP_FreeZipDir(files); return NULL; } strncpy(fentries[i]->d_name, files->names[i], sizeof(fentries[i]->d_name)-1); fentries[i]->d_name[sizeof(fentries[i]->d_name) - 1] = 0; } ZIP_FreeZipDir(files); return fentries; } /*-----------------------------------------------------------------------*/ /** * Check an image file in the archive, return the uncompressed length */ static long ZIP_CheckImageFile(unzFile uf, char *filename, int namelen, int *pImageType) { unz_file_info file_info; if (unzLocateFile(uf,filename, 0) != UNZ_OK) { Log_Printf(LOG_ERROR, "File \"%s\" not found in the archive!\n", filename); return -1; } if (unzGetCurrentFileInfo(uf, &file_info, filename, namelen, NULL, 0, NULL, 0) != UNZ_OK) { Log_Printf(LOG_ERROR, "Error with zipfile in unzGetCurrentFileInfo\n"); return -1; } /* check for .stx, .ipf, .msa, .dim or .st extension */ if (STX_FileNameIsSTX(filename, false)) { *pImageType = FLOPPY_IMAGE_TYPE_STX; return file_info.uncompressed_size; } if (IPF_FileNameIsIPF(filename, false)) { *pImageType = FLOPPY_IMAGE_TYPE_IPF; return file_info.uncompressed_size; } if (MSA_FileNameIsMSA(filename, false)) { *pImageType = FLOPPY_IMAGE_TYPE_MSA; return file_info.uncompressed_size; } if (ST_FileNameIsST(filename, false)) { *pImageType = FLOPPY_IMAGE_TYPE_ST; return file_info.uncompressed_size; } if (DIM_FileNameIsDIM(filename, false)) { *pImageType = FLOPPY_IMAGE_TYPE_DIM; return file_info.uncompressed_size; } Log_Printf(LOG_ERROR, "Not an .ST, .MSA, .DIM, .IPF or .STX file.\n"); return 0; } /*-----------------------------------------------------------------------*/ /** * Return the first matching file in a zip, or NULL on failure. * String buffer size is ZIP_PATH_MAX */ static char *ZIP_FirstFile(const char *filename, const char * const ppsExts[]) { zip_dir *files; int i, j; char *name; files = ZIP_GetFiles(filename); if (files == NULL) return NULL; name = malloc(ZIP_PATH_MAX); if (!name) { perror("ZIP_FirstFile"); ZIP_FreeZipDir(files); return NULL; } name[0] = '\0'; /* Do we have to scan for a certain extension? */ if (ppsExts) { for(i = files->nfiles-1; i >= 0; i--) { for (j = 0; ppsExts[j] != NULL; j++) { if (File_DoesFileExtensionMatch(files->names[i], ppsExts[j]) && strlen(files->names[i]) < ZIP_PATH_MAX - 1) { strcpy(name, files->names[i]); break; } } } } else { /* There was no extension given -> use the very first name */ if (strlen(files->names[0]) < ZIP_PATH_MAX - 1) { strcpy(name, files->names[0]); } } /* free the files */ ZIP_FreeZipDir(files); if (name[0] == '\0') { free(name); return NULL; } return name; } /*-----------------------------------------------------------------------*/ /** * Extract a file (filename) from a ZIP-file (uf), the number of * bytes to uncompress is size. Returns a pointer to a buffer containing * the uncompressed data, or NULL. */ static void *ZIP_ExtractFile(unzFile uf, const char *filename, uLong size) { int err = UNZ_OK; char filename_inzip[ZIP_PATH_MAX]; void* buf; uInt size_buf; unz_file_info file_info; if (unzLocateFile(uf,filename, 0) != UNZ_OK) { Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not find file in archive\n"); return NULL; } err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0); if (err != UNZ_OK) { Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not get file info\n"); return NULL; } size_buf = size; buf = malloc(size_buf); if (!buf) { perror("ZIP_ExtractFile"); return NULL; } err = unzOpenCurrentFile(uf); if (err != UNZ_OK) { Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not open file\n"); free(buf); return NULL; } do { err = unzReadCurrentFile(uf,buf,size_buf); if (err < 0) { Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not read file\n"); return NULL; } } while (err > 0); return buf; } /*-----------------------------------------------------------------------*/ /** * Load disk image from a .ZIP archive into memory, set the number * of bytes loaded into pImageSize and return the data or NULL on error. */ uint8_t *ZIP_ReadDisk(int Drive, const char *pszFileName, const char *pszZipPath, long *pImageSize, int *pImageType) { uLong ImageSize=0; unzFile uf=NULL; uint8_t *buf; char *path; uint8_t *pDiskBuffer = NULL; *pImageSize = 0; *pImageType = FLOPPY_IMAGE_TYPE_NONE; uf = unzOpen(pszFileName); if (uf == NULL) { Log_Printf(LOG_ERROR, "Cannot open %s\n", pszFileName); return NULL; } if (pszZipPath == NULL || pszZipPath[0] == 0) { path = ZIP_FirstFile(pszFileName, pszDiskNameExts); if (path == NULL) { Log_Printf(LOG_ERROR, "Cannot open %s\n", pszFileName); unzClose(uf); return NULL; } } else { path = malloc(ZIP_PATH_MAX); if (path == NULL) { perror("ZIP_ReadDisk"); unzClose(uf); return NULL; } strncpy(path, pszZipPath, ZIP_PATH_MAX - 1); path[ZIP_PATH_MAX-1] = '\0'; } ImageSize = ZIP_CheckImageFile(uf, path, ZIP_PATH_MAX, pImageType); if (ImageSize <= 0) { unzClose(uf); free(path); return NULL; } /* extract to buf */ buf = ZIP_ExtractFile(uf, path, ImageSize); unzCloseCurrentFile(uf); unzClose(uf); free(path); path = NULL; if (buf == NULL) { return NULL; /* failed extraction, return error */ } switch(*pImageType) { case FLOPPY_IMAGE_TYPE_IPF: #ifndef HAVE_CAPSIMAGE Log_AlertDlg(LOG_ERROR, "This version of Hatari was not built with IPF support, this disk image can't be handled."); return NULL; #else /* return buffer */ pDiskBuffer = buf; break; #endif case FLOPPY_IMAGE_TYPE_STX: /* return buffer */ pDiskBuffer = buf; break; case FLOPPY_IMAGE_TYPE_MSA: /* uncompress the MSA file */ pDiskBuffer = MSA_UnCompress(buf, (long *)&ImageSize, ImageSize); free(buf); buf = NULL; break; case FLOPPY_IMAGE_TYPE_DIM: /* Skip DIM header */ ImageSize -= 32; memmove(buf, buf+32, ImageSize); /* return buffer */ pDiskBuffer = buf; break; case FLOPPY_IMAGE_TYPE_ST: /* ST image => return buffer directly */ pDiskBuffer = buf; break; } if (pDiskBuffer) { *pImageSize = ImageSize; } return pDiskBuffer; } /*-----------------------------------------------------------------------*/ /** * Load first file from a .ZIP archive into memory, and return the number * of bytes loaded. */ uint8_t *ZIP_ReadFirstFile(const char *pszFileName, long *pImageSize, const char * const ppszExts[]) { unzFile uf = NULL; uint8_t *pBuffer = NULL; char *pszZipPath; unz_file_info file_info; *pImageSize = 0; /* Open the ZIP file */ uf = unzOpen(pszFileName); if (uf == NULL) { Log_Printf(LOG_ERROR, "Cannot open '%s'\n", pszFileName); return NULL; } /* Locate the first file in the ZIP archive */ pszZipPath = ZIP_FirstFile(pszFileName, ppszExts); if (pszZipPath == NULL) { Log_Printf(LOG_ERROR, "Failed to locate first file in '%s'\n", pszFileName); unzClose(uf); return NULL; } if (unzLocateFile(uf, pszZipPath, 0) != UNZ_OK) { Log_Printf(LOG_ERROR, "Can not locate '%s' in the archive!\n", pszZipPath); goto cleanup; } /* Get file information (file size!) */ if (unzGetCurrentFileInfo(uf, &file_info, pszZipPath, ZIP_PATH_MAX, NULL, 0, NULL, 0) != UNZ_OK) { Log_Printf(LOG_ERROR, "Error with zipfile in unzGetCurrentFileInfo.\n"); goto cleanup; } /* Extract to buffer */ pBuffer = ZIP_ExtractFile(uf, pszZipPath, file_info.uncompressed_size); if (pBuffer) *pImageSize = file_info.uncompressed_size; /* And close the file */ unzCloseCurrentFile(uf); cleanup: unzClose(uf); free(pszZipPath); return pBuffer; } #else bool ZIP_FileNameIsZIP(const char *pszFileName) { return false; } uint8_t *ZIP_ReadDisk(int Drive, const char *name, const char *path, long *size , int *pImageType) { return NULL; } struct dirent **ZIP_GetFilesDir(const zip_dir *zip, const char *dir, int *entries) { return NULL; } zip_dir *ZIP_GetFiles(const char *pszFileName) { return NULL; } void ZIP_FreeZipDir(zip_dir *f_zd) { } #endif /* HAVE_LIBZ */ /** * Save .ZIP file from memory buffer. Returns true if all is OK. * * Not yet implemented. */ bool ZIP_WriteDisk(int Drive, const char *pszFileName,unsigned char *pBuffer,int ImageSize) { return false; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/000077500000000000000000000000001504763705000217035ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/CMakeLists.txt000066400000000000000000000010631504763705000244430ustar00rootroot00000000000000 add_subdirectory(debugger) add_subdirectory(unit) if(UNIX) add_test(NAME command-fifo COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/cmdfifo.sh $) add_test(NAME config-file COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/configfile.sh $) add_subdirectory(blitter) add_subdirectory(buserror) add_subdirectory(cpu) add_subdirectory(cycles) add_subdirectory(gemdos) add_subdirectory(mem_end) add_subdirectory(natfeats) add_subdirectory(screen) add_subdirectory(serial) add_subdirectory(xbios) endif(UNIX) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/autostart/000077500000000000000000000000001504763705000237315ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/autostart/2_secs.prg000066400000000000000000000073711504763705000256310ustar00rootroot00000000000000` (z*&H f&op`B@3## k # k# k### +,# 0# > k Nq Ј.@Jyg// ?<?<JNAO "@(I$k,+<=.NAXHra$_Nu/ //??<@NAO ra$_NuNVH<*H-I n(h&h g6)K g('Lg +@/.?<INA\L<N^Nu `*`+K`H0&i$ig('Jg%Kg !@L Nu ` `!J`/ $H!IB#PJg R!I$$_Nu%I`NV/ &H#n nJg n h!I n#h!I&_N^Nu&`/ $H!IB#hJg j!I%I$_Nu$`NV/ &H#n nJg n h!I n#h!I&_N^Nu'I`/ Jg"&i Бf ё&kg'I#K!I&_Nu!I`H8(H&I$hf$Tf L"KaLNuACd6gACd$j`f L"Ka`/ L"KaX`gACc$j`f L"Kan`/ L"KaX`H0&H$IaJg K"jaL NuH0&HJg kAfpL Nu$SgAfp`$j`B@`H8(H&I$hg JJg jAf JLNu L$Tg:& A,A(m" J В k Nq Ј.@Jyg// ?<?<JNAO "@(I$k,+<=.NAXHra$_Nu/ //??<@NAO ra$_NuNVH<*H-I n(h&h g6)K g('Lg +@/.?<INA\L<N^Nu `*`+K`H0&i$ig('Jg%Kg !@L Nu ` `!J`/ $H!IB#PJg R!I$$_Nu%I`NV/ &H#n nJg n h!I n#h!I&_N^Nu&`/ $H!IB#hJg j!I%I$_Nu$`NV/ &H#n nJg n h!I n#h!I&_N^Nu'I`/ Jg"&i Бf ё&kg'I#K!I&_Nu!I`H8(H&I$hf$Tf L"KaLNuACd6gACd$j`f L"Ka`/ L"KaX`gACc$j`f L"Kan`/ L"KaX`H0&H$IaJg K"jaL NuH0&HJg kAfpL Nu$SgAfp`$j`B@`H8(H&I$hg JJg jAf JLNu L$Tg:& A,A(m" J В #include int main() { Cconws("Sleeping "SECS" secs.\r\n"); sleep(SLEEP); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/autostart/sleep.prj000066400000000000000000000004571504763705000255640ustar00rootroot00000000000000; AHCC project file ; ; Note: program name is prefixed by seconds number, so that Hatari's ; alphabetical ordering of directory entries returns it before programs ; in AUTO/ that might require the sleep to work correctly .C [-iinclude -DSLEEP=4 -DSECS="4"] 4_secs.prg = ahcstart.o sleep.c ahccstdi.lib hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/blitter/000077500000000000000000000000001504763705000233505ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/blitter/CMakeLists.txt000066400000000000000000000002321504763705000261050ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) add_test(NAME blitter COMMAND ${testrunner} $ --machine ste) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/blitter/blitemu.c000066400000000000000000000053001504763705000251530ustar00rootroot00000000000000/* * Blitter test program for different combinations of src inc, dest inc, fxsr, nfsr * It requires an accurate emulation of the complex handling of nfsr * * Program by Christian Zietz, slightly modified to work with Hatari's test suite * */ #include #include #include #include typedef unsigned char BYTE; typedef unsigned int WORD; typedef unsigned long LONG; #define HALFTONE(x) (*((volatile WORD *)0xFF8A00L+(x<<1))) #define OP (*((volatile BYTE *)0xFF8A3BL)) #define ENDMASK1 (*((volatile WORD *)0xFF8A28L)) #define ENDMASK2 (*((volatile WORD *)0xFF8A2AL)) #define ENDMASK3 (*((volatile WORD *)0xFF8A2CL)) #define X_COUNT (*((volatile WORD *)0xFF8A36L)) #define Y_COUNT (*((volatile WORD *)0xFF8A38L)) #define DST_XINC (*((volatile WORD *)0xFF8A2EL)) #define DST_YINC (*((volatile WORD *)0xFF8A30L)) #define DST_ADDR (*((volatile LONG *)0xFF8A32L)) #define SRC_XINC (*((volatile WORD *)0xFF8A20L)) #define SRC_YINC (*((volatile WORD *)0xFF8A22L)) #define SRC_ADDR (*((volatile LONG *)0xFF8A24L)) #define HOP (*((volatile BYTE *)0xFF8A3AL)) #define LINE_NUM (*((volatile BYTE *)0xFF8A3CL)) #define SKEW (*((volatile BYTE *)0xFF8A3DL)) #define SRCLEN 64 #define DSTLEN 16 WORD srcbuf[SRCLEN]; WORD dstbuf[DSTLEN]; long do_copy(int stride, int fxsr, int nfsr, int op) { OP = op; /* D = S and D */ ENDMASK1 = ENDMASK2 = ENDMASK3 = 0xFFFF; X_COUNT = 1; Y_COUNT = 10; DST_XINC = stride; DST_YINC = stride; if (stride < 0) { DST_ADDR = (LONG)(&dstbuf[DSTLEN-1]); } else { DST_ADDR = (LONG)(&dstbuf[0]); } SRC_XINC = stride; SRC_YINC = stride; if (stride < 0) { SRC_ADDR = (LONG)(&srcbuf[SRCLEN-1]); } else { SRC_ADDR = (LONG)(&srcbuf[0]); } HOP = 2; /* use source data */ LINE_NUM = 0; SKEW = ((fxsr&1)<<7) + ((nfsr&1)<<6); /* FXSR NFSR */ LINE_NUM = 0x80; /* set busy bit: start blitter: no HOG mode */ while (LINE_NUM & 0x80) continue; return 0; } int main(void) { unsigned int i; int s,f,n,o; long oldsuper; int fh; char txt[128]; for (i=0; i>>>> OP = %d <<<<<", o); Fwrite(fh, strlen(txt), txt); for (s=2; s>=-2; s-=4) for (f=0; f<=1; f++) for (n=0; n<=1; n++) { sprintf(txt, "\r\nSRC_INC = DST_INC = %+d, FXSR = %d, NFSR = %d\r\n", s, f, n); Fwrite(fh, strlen(txt), txt); memset(dstbuf, -1, sizeof(dstbuf)); do_copy(s,f,n,o); for (i=0; iYE`.Tn`/.?< NA\??<>NAXB@`2pHA 00|Nu2pHA 00|NuH0&&H8$H2H N \A 2H N &bB KNL NuH0&&H8$HJl - D&  J2a KL NuNVH0$H&I nN6 n -f nR"n P n hg n ho n 6( n 0Cl n 0 n :0P n JPfJCgv n JhfF n JPg>JCg. n J( g$ n (0 f n ( B( SCp JN` n ( SP`JCg n J( f nR` n ( SP`0HL 8N^Nu n J( g0RC`*NVH>>>> OP = %d <<<<< SRC_INC = DST_INC = %+d, FXSR = %d, NFSR = %d %d                BBBBBBBBBB       DDDDDD      HHHHHH    0123456789ABCDEFdo_copy@mainisdigitisupperultoaltoa __prtfldL_printfX__sputcFsprintfRstrlen__uplwstrlwrstrrevmemset_uldiv L18934 $L18956 :L18972 JL18978 P_ulmod \L19014 tL19032 L19050 L19056 __73 __738 _ctype __745 __numstr __205 (__207 ~_prt_lmoH ~de__77 srcbuf dstbuf 4LN l@0n"H&6DH" exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; basedir=$(dirname "$0") testdir=$(mktemp -d) remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT export HATARI_TEST=blitter export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy cp "$basedir/blitemu.ttp" "$testdir" HOME="$testdir" $hatari --log-level fatal --fast-forward on --sound off \ --run-vbls 1000 --tos none "$@" "$testdir/blitemu.ttp" \ > "$testdir/log.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Test FAILED, Hatari returned error status ${exitstat}." cat "$testdir/log.txt" exit 1 fi if ! diff -q "$basedir/test_out.txt" "$testdir/BLITEMU.TXT"; then echo "Test FAILED, output differs:" diff -u "$basedir/test_out.txt" "$testdir/BLITEMU.TXT" exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/blitter/test_out.txt000066400000000000000000000027671504763705000257730ustar00rootroot00000000000000>>>>> OP = 1 <<<<< SRC_INC = DST_INC = +2, FXSR = 0, NFSR = 0 0 1 2 3 4 5 6 7 8 9 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = +2, FXSR = 0, NFSR = 1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = +2, FXSR = 1, NFSR = 0 1 3 5 7 9 11 13 15 17 19 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = +2, FXSR = 1, NFSR = 1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = -2, FXSR = 0, NFSR = 0 -1 -1 -1 -1 -1 -1 55 56 57 58 59 60 61 62 63 -1 SRC_INC = DST_INC = -2, FXSR = 0, NFSR = 1 -1 -1 -1 -1 -1 -1 54 55 56 57 58 59 60 61 62 63 SRC_INC = DST_INC = -2, FXSR = 1, NFSR = 0 -1 -1 -1 -1 -1 -1 45 47 49 51 53 55 57 59 61 63 SRC_INC = DST_INC = -2, FXSR = 1, NFSR = 1 -1 -1 -1 -1 -1 -1 44 46 48 50 52 54 56 58 60 62 >>>>> OP = 3 <<<<< SRC_INC = DST_INC = +2, FXSR = 0, NFSR = 0 0 1 2 3 4 5 6 7 8 9 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = +2, FXSR = 0, NFSR = 1 0 1 2 3 4 5 6 7 8 9 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = +2, FXSR = 1, NFSR = 0 1 3 5 7 9 11 13 15 17 19 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = +2, FXSR = 1, NFSR = 1 1 3 5 7 9 11 13 15 17 19 -1 -1 -1 -1 -1 -1 SRC_INC = DST_INC = -2, FXSR = 0, NFSR = 0 -1 -1 -1 -1 -1 -1 55 56 57 58 59 60 61 62 63 19 SRC_INC = DST_INC = -2, FXSR = 0, NFSR = 1 -1 -1 -1 -1 -1 -1 54 55 56 57 58 59 60 61 62 63 SRC_INC = DST_INC = -2, FXSR = 1, NFSR = 0 -1 -1 -1 -1 -1 -1 45 47 49 51 53 55 57 59 61 63 SRC_INC = DST_INC = -2, FXSR = 1, NFSR = 1 -1 -1 -1 -1 -1 -1 44 46 48 50 52 54 56 58 60 62 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/000077500000000000000000000000001504763705000235465ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/CMakeLists.txt000066400000000000000000000023671504763705000263160ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) foreach(machine st megast ste megaste tt falcon) add_test(NAME buserror-${machine}-b COMMAND ${testrunner} $ b ${machine}) add_test(NAME buserror-${machine}-w COMMAND ${testrunner} $ w ${machine}) endforeach(machine) foreach (lvl RANGE 0 4) add_test(NAME buserror-680${lvl}0-fast COMMAND ${testrunner} $ w st --cpulevel ${lvl} --compatible false --cpu-exact false) add_test(NAME buserror-680${lvl}0-compatible COMMAND ${testrunner} $ w st --cpulevel ${lvl} --compatible true --cpu-exact false) add_test(NAME buserror-680${lvl}0-cpuexact COMMAND ${testrunner} $ w st --cpulevel ${lvl} --compatible false --cpu-exact true) if (${lvl} GREATER 2) add_test(NAME buserror-680${lvl}0-mmu COMMAND ${testrunner} $ w tt --cpulevel ${lvl} --mmu true) endif() endforeach(lvl) add_test(NAME buserror-68060 COMMAND ${testrunner} $ w tt --cpulevel 6 --mmu false) add_test(NAME buserror-68060-mmu COMMAND ${testrunner} $ w tt --cpulevel 6 --mmu true) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/buserr_b.prg000066400000000000000000000012451504763705000260650ustar00rootroot00000000000000`Z *o - ЭЭм"ҍ¼.A// Bg?<JNAO B?< NA\#nBgHy?<NAX/9n?< NA\BgNA.NJylfx3lAWa`d" << l<0`<7A" << l<0`<7A" I<< l<0`<7A" 4< i<< l<0`<7A" 4<<< l<0`<7A" 4<<< l<0`<7ANuBUSERR_B.TXTBus Error testing results (byte access): EOF! $000000 - $000000 >     hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/buserr_b.s000066400000000000000000000132041504763705000255350ustar00rootroot00000000000000; Bus error tester - byte access ; (assemble with TurboAss) startaddr EQU $FF8000 endaddr EQU $FFFFFF movea.l 4(SP),A5 move.l $0C(A5),D0 add.l $14(A5),D0 add.l $1C(A5),D0 add.l #$0800,D0 move.l D0,D1 add.l A5,D1 and.l #-2,D1 movea.l D1,SP move.l D0,-(SP) move.l A5,-(SP) clr.w -(SP) move.w #$4A,-(SP) trap #1 ; Mshrink lea 12(SP),SP clr.l -(SP) move.w #$20,-(SP) trap #1 ; Super addq.l #6,SP move.l D0,old_ssp clr.w -(SP) pea filename move.w #$3C,-(SP) trap #1 ; Fcreate addq.l #8,SP move.w D0,fhndl pea welcometxt move.l #welcometxtend-welcometxt-1,-(SP) move.w D0,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite(welcometxt) lea 12(SP),SP move.l $08,oldbushandler move.l #buserrcatch,$08 movea.l #startaddr,A5 loop: movea.l SP,A6 move.b (A5),D0 ; *** Try to generate a bus error *** tst.w be_region beq.s cont lea hex_buf+10,A0 bsr write_hex clr.w be_region pea hex_buf move.l #19,-(SP) move.w fhndl,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite lea 12(SP),SP cont: addq.l #1,A5 cmpa.l #endaddr,A5 bne.s loop tst.w be_region beq.s nofinalwrite lea hex_buf+10,A0 bsr write_hex pea hex_buf move.l #19,-(SP) move.w fhndl,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite lea 12(SP),SP nofinalwrite: move.l oldbushandler,$08 pea eoftxt move.l #8,-(SP) move.w fhndl,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite(eoftxt) lea 12(SP),SP move.w fhndl,-(SP) move.w #$3E,-(SP) trap #1 ; Fclose addq.l #4,SP move.l old_ssp,-(SP) move.w #$20,-(SP) trap #1 ; Super addq.l #6,SP clr.w -(SP) trap #1 ; Pterm0 buserrcatch: movea.l A6,SP tst.w be_region bne cont move.w #-1,be_region lea hex_buf,A0 bsr.s write_hex bra cont write_hex: move.l A5,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b6_10 add.b #'0',D1 bra.s b6_ok b6_10: add.b #'A'-10,D1 b6_ok: move.b D1,6(A0) move.l A5,D1 lsr.b #4,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b5_10 add.b #'0',D1 bra.s b5_ok b5_10: add.b #'A'-10,D1 b5_ok: move.b D1,5(A0) move.l A5,D1 lsr.w #8,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b4_10 add.b #'0',D1 bra.s b4_ok b4_10: add.b #'A'-10,D1 b4_ok: move.b D1,4(A0) move.l A5,D1 move.w #12,D2 lsr.w D2,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b3_10 add.b #'0',D1 bra.s b3_ok b3_10: add.b #'A'-10,D1 b3_ok: move.b D1,3(A0) move.l A5,D1 move.w #16,D2 lsr.l D2,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b2_10 add.b #'0',D1 bra.s b2_ok b2_10: add.b #'A'-10,D1 b2_ok: move.b D1,2(A0) move.l A5,D1 move.w #20,D2 lsr.l D2,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b1_10 add.b #'0',D1 bra.s b1_ok b1_10: add.b #'A'-10,D1 b1_ok: move.b D1,1(A0) rts DATA filename: DC.B "BUSERR_B.TXT",0 welcometxt: DC.B "Bus Error testing results (byte access):",13,10,13,10,0 welcometxtend: eoftxt: DC.B 13,10,"EOF!",13,10,0 hex_buf: DC.B "$000000 - $000000",13,10,0 EVEN be_region: DC.W 0 BSS old_ssp: DS.L 1 oldbushandler: DS.L 1 fhndl: DS.W 1 END hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/buserr_w.prg000066400000000000000000000012451504763705000261120ustar00rootroot00000000000000`Z *o - ЭЭм"ҍ¼.A// Bg?<JNAO B?< NA\#nBgHy?<NAX/9n?< NA\BgNA.NJylfx3lAWa`d" << l<0`<7A" << l<0`<7A" I<< l<0`<7A" 4< i<< l<0`<7A" 4<<< l<0`<7A" 4<<< l<0`<7ANuBUSERR_W.TXTBus Error testing results (word access): EOF! $000000 - $000000 >     hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/buserr_w.s000066400000000000000000000132111504763705000255600ustar00rootroot00000000000000; Bus error tester - with word access ; (assemble with TurboAss) startaddr EQU $FF8000 endaddr EQU $FFFFFE movea.l 4(SP),A5 move.l $0C(A5),D0 add.l $14(A5),D0 add.l $1C(A5),D0 add.l #$0800,D0 move.l D0,D1 add.l A5,D1 and.l #-2,D1 movea.l D1,SP move.l D0,-(SP) move.l A5,-(SP) clr.w -(SP) move.w #$4A,-(SP) trap #1 ; Mshrink lea 12(SP),SP clr.l -(SP) move.w #$20,-(SP) trap #1 ; Super addq.l #6,SP move.l D0,old_ssp clr.w -(SP) pea filename move.w #$3C,-(SP) trap #1 ; Fcreate addq.l #8,SP move.w D0,fhndl pea welcometxt move.l #welcometxtend-welcometxt-1,-(SP) move.w D0,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite(welcometxt) lea 12(SP),SP move.l $08,oldbushandler move.l #buserrcatch,$08 movea.l #startaddr,A5 loop: movea.l SP,A6 move.w (A5),D0 ; *** Try to generate a bus error *** tst.w be_region beq.s cont lea hex_buf+10,A0 bsr write_hex clr.w be_region pea hex_buf move.l #19,-(SP) move.w fhndl,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite lea 12(SP),SP cont: addq.l #2,A5 cmpa.l #endaddr,A5 bne.s loop tst.w be_region beq.s nofinalwrite lea hex_buf+10,A0 bsr write_hex pea hex_buf move.l #19,-(SP) move.w fhndl,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite lea 12(SP),SP nofinalwrite: move.l oldbushandler,$08 pea eoftxt move.l #8,-(SP) move.w fhndl,-(SP) move.w #$40,-(SP) trap #1 ; Fwrite(eoftxt) lea 12(SP),SP move.w fhndl,-(SP) move.w #$3E,-(SP) trap #1 ; Fclose addq.l #4,SP move.l old_ssp,-(SP) move.w #$20,-(SP) trap #1 ; Super addq.l #6,SP clr.w -(SP) trap #1 ; Pterm0 buserrcatch: movea.l A6,SP tst.w be_region bne cont move.w #-1,be_region lea hex_buf,A0 bsr.s write_hex bra cont write_hex: move.l A5,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b6_10 add.b #'0',D1 bra.s b6_ok b6_10: add.b #'A'-10,D1 b6_ok: move.b D1,6(A0) move.l A5,D1 lsr.b #4,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b5_10 add.b #'0',D1 bra.s b5_ok b5_10: add.b #'A'-10,D1 b5_ok: move.b D1,5(A0) move.l A5,D1 lsr.w #8,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b4_10 add.b #'0',D1 bra.s b4_ok b4_10: add.b #'A'-10,D1 b4_ok: move.b D1,4(A0) move.l A5,D1 move.w #12,D2 lsr.w D2,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b3_10 add.b #'0',D1 bra.s b3_ok b3_10: add.b #'A'-10,D1 b3_ok: move.b D1,3(A0) move.l A5,D1 move.w #16,D2 lsr.l D2,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b2_10 add.b #'0',D1 bra.s b2_ok b2_10: add.b #'A'-10,D1 b2_ok: move.b D1,2(A0) move.l A5,D1 move.w #20,D2 lsr.l D2,D1 and.b #$0F,D1 cmp.b #10,D1 bge.s b1_10 add.b #'0',D1 bra.s b1_ok b1_10: add.b #'A'-10,D1 b1_ok: move.b D1,1(A0) rts DATA filename: DC.B "BUSERR_W.TXT",0 welcometxt: DC.B "Bus Error testing results (word access):",13,10,13,10,0 welcometxtend: eoftxt: DC.B 13,10,"EOF!",13,10,0 hex_buf: DC.B "$000000 - $000000",13,10,0 EVEN be_region: DC.W 0 BSS old_ssp: DS.L 1 oldbushandler: DS.L 1 fhndl: DS.W 1 END hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/readme.txt000066400000000000000000000011571504763705000255500ustar00rootroot00000000000000 Bus error test ============== The programs in this directory can be used to test the IO memory addresses which cause a bus error on a real machine. "buserr_b.prg" tests the IO memory with byte accesses, "buserr_w.prg" tests with word accesses. The results will be written to a file called "BUSERR_B.TXT" or "BUSERR_W.TXT". These can be used to compare the behaviour of the emulator with a real machine. In the "results" folder, there are sample files that have been taken on real machines. Note that the Falcon has two bus modes (STE compatible and normal), so there are two sets of results for the Falcon available. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/000077500000000000000000000000001504763705000252475ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/fal_c_b.txt000066400000000000000000000006701504763705000273600ustar00rootroot00000000000000Bus Error testing results (byte access): (Falcon, compatible mode) $FF8400 - $FF8560 $FF8561 - $FF8564 $FF8565 - $FF8604 $FF8610 - $FF8800 $FF8914 - $FF8920 $FF8926 - $FF8930 $FF8944 - $FF8960 $FF8970 - $FF8A00 $FF8A40 - $FF8C00 $FF8D00 - $FF9000 $FFA000 - $FFA200 $FFA208 - $FFC020 $FFC022 - $FFD020 $FFD021 - $FFD420 $FFD421 - $FFD425 $FFD426 - $FFFA00 $FFFA30 - $FFFC00 $FFFC08 - $FFFF82 $FFFF84 - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/fal_c_w.txt000066400000000000000000000005771504763705000274130ustar00rootroot00000000000000Bus Error testing results (word access): (Falcon, compatible mode) $FF8400 - $FF8604 $FF8610 - $FF8800 $FF8914 - $FF8920 $FF8926 - $FF8930 $FF8944 - $FF8960 $FF8970 - $FF8A00 $FF8A40 - $FF8C00 $FF8D00 - $FF9000 $FFA000 - $FFA200 $FFA208 - $FFD074 $FFD076 - $FFD520 $FFD522 - $FFD530 $FFD532 - $FFFA00 $FFFA30 - $FFFC00 $FFFC08 - $FFFF82 $FFFF84 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/fal_n_b.txt000066400000000000000000000007071504763705000273740ustar00rootroot00000000000000Bus Error testing results (byte access): (Falcon, normal mode) $FF8002 - $FF8006 $FF8008 - $FF800C $FF800E - $FF8200 $FF82C4 - $FF8604 $FF8610 - $FF8800 $FF8804 - $FF8900 $FF8914 - $FF8920 $FF8926 - $FF8930 $FF8944 - $FF8960 $FF8964 - $FF8A00 $FF8A40 - $FF8C80 $FF8C88 - $FF9200 $FF9204 - $FF9210 $FF9218 - $FF9220 $FF9224 - $FF9800 $FF9C00 - $FFA200 $FFA208 - $FFFA00 $FFFA30 - $FFFC00 $FFFC08 - $FFFF82 $FFFF84 - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/fal_n_w.txt000066400000000000000000000007071504763705000274210ustar00rootroot00000000000000Bus Error testing results (word access): (Falcon, normal mode) $FF8002 - $FF8006 $FF8008 - $FF800C $FF800E - $FF8200 $FF82C4 - $FF8604 $FF8610 - $FF8800 $FF8804 - $FF8900 $FF8914 - $FF8920 $FF8926 - $FF8930 $FF8944 - $FF8960 $FF8964 - $FF8A00 $FF8A40 - $FF8C80 $FF8C88 - $FF9200 $FF9204 - $FF9210 $FF9218 - $FF9220 $FF9224 - $FF9800 $FF9C00 - $FFA200 $FFA208 - $FFFA00 $FFFA30 - $FFFC00 $FFFC08 - $FFFF82 $FFFF84 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/mst4_b.txt000066400000000000000000000014761504763705000272100ustar00rootroot00000000000000Bus Error testing results (byte access): (Mega ST4, likely with IMP chipset) $FF800E - $FF8200 $FF820E - $FF8240 $FF8280 - $FF8608 $FF860E - $FF8800 $FF8900 - $FF8A00 $FF8A40 - $FFFA01 $FFFA02 - $FFFA03 $FFFA04 - $FFFA05 $FFFA06 - $FFFA07 $FFFA08 - $FFFA09 $FFFA0A - $FFFA0B $FFFA0C - $FFFA0D $FFFA0E - $FFFA0F $FFFA10 - $FFFA11 $FFFA12 - $FFFA13 $FFFA14 - $FFFA15 $FFFA16 - $FFFA17 $FFFA18 - $FFFA19 $FFFA1A - $FFFA1B $FFFA1C - $FFFA1D $FFFA1E - $FFFA1F $FFFA20 - $FFFA21 $FFFA22 - $FFFA23 $FFFA24 - $FFFA25 $FFFA26 - $FFFA27 $FFFA28 - $FFFA29 $FFFA2A - $FFFA2B $FFFA2C - $FFFA2D $FFFA2E - $FFFA2F $FFFA30 - $FFFA31 $FFFA32 - $FFFA33 $FFFA34 - $FFFA35 $FFFA36 - $FFFA37 $FFFA38 - $FFFA39 $FFFA3A - $FFFA3B $FFFA3C - $FFFA3D $FFFA3E - $FFFA3F $FFFA40 - $FFFC00 $FFFE00 - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/mst4_w.txt000066400000000000000000000003611504763705000272250ustar00rootroot00000000000000Bus Error testing results (word access): (Mega ST4, likely with IMP chipset) $FF800E - $FF8200 $FF820E - $FF8240 $FF8280 - $FF8604 $FF860E - $FF8800 $FF8900 - $FF8A00 $FF8A40 - $FFFA00 $FFFA40 - $FFFC00 $FFFE00 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/mste4_b.txt000066400000000000000000000022101504763705000273400ustar00rootroot00000000000000Bus Error testing results (byte access): (MegaSTE with TOS 2.06PL, 4MB ST-Ram and Crazy Dots VME card) $FF8010 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8608 $FF8610 - $FF8800 $FF8940 - $FF8A00 $FF8A40 - $FF8C80 $FF8C88 - $FF8E01 $FF8E02 - $FF8E03 $FF8E04 - $FF8E05 $FF8E06 - $FF8E07 $FF8E08 - $FF8E09 $FF8E0A - $FF8E0B $FF8E0C - $FF8E0D $FF8E0E - $FF8E0F $FF8E10 - $FF8E20 $FF8E24 - $FF9000 $FF9002 - $FF9201 $FF9204 - $FF9211 $FF9212 - $FF9213 $FF9214 - $FF9215 $FF9216 - $FF9217 $FF9218 - $FFFA01 $FFFA02 - $FFFA03 $FFFA04 - $FFFA05 $FFFA06 - $FFFA07 $FFFA08 - $FFFA09 $FFFA0A - $FFFA0B $FFFA0C - $FFFA0D $FFFA0E - $FFFA0F $FFFA10 - $FFFA11 $FFFA12 - $FFFA13 $FFFA14 - $FFFA15 $FFFA16 - $FFFA17 $FFFA18 - $FFFA19 $FFFA1A - $FFFA1B $FFFA1C - $FFFA1D $FFFA1E - $FFFA1F $FFFA20 - $FFFA21 $FFFA22 - $FFFA23 $FFFA24 - $FFFA25 $FFFA26 - $FFFA27 $FFFA28 - $FFFA29 $FFFA2A - $FFFA2B $FFFA2C - $FFFA2D $FFFA2E - $FFFA2F $FFFA30 - $FFFA31 $FFFA32 - $FFFA33 $FFFA34 - $FFFA35 $FFFA36 - $FFFA37 $FFFA38 - $FFFA39 $FFFA3A - $FFFA3B $FFFA3C - $FFFA3D $FFFA3E - $FFFA3F $FFFA40 - $FFFC00 $FFFE00 - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/mste4_w.txt000066400000000000000000000006201504763705000273700ustar00rootroot00000000000000Bus Error testing results (word access): (MegaSTE with TOS 2.06PL, 4MB ST-Ram and Crazy Dots VME card) $FF8010 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8604 $FF8610 - $FF8800 $FF8940 - $FF8A00 $FF8A40 - $FF8C80 $FF8C88 - $FF8E00 $FF8E10 - $FF8E20 $FF8E24 - $FF9000 $FF9002 - $FF9200 $FF9204 - $FF9210 $FF9218 - $FF9220 $FF9224 - $FFFA00 $FFFA40 - $FFFC00 $FFFE00 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/st_b.txt000066400000000000000000000017441504763705000267450ustar00rootroot00000000000000Bus Error testing results (byte access): (1040 STf, with Ricoh chipset) $FF8000 - $FF8001 $FF8002 - $FF8201 $FF8202 - $FF8203 $FF8204 - $FF8205 $FF8206 - $FF8207 $FF8208 - $FF8209 $FF820C - $FF820D $FF820E - $FF820F $FF8210 - $FF8240 $FF8280 - $FF8609 $FF860A - $FF860B $FF860C - $FF860D $FF860E - $FF860F $FF8610 - $FF8800 $FF8900 - $FFFA01 $FFFA02 - $FFFA03 $FFFA04 - $FFFA05 $FFFA06 - $FFFA07 $FFFA08 - $FFFA09 $FFFA0A - $FFFA0B $FFFA0C - $FFFA0D $FFFA0E - $FFFA0F $FFFA10 - $FFFA11 $FFFA12 - $FFFA13 $FFFA14 - $FFFA15 $FFFA16 - $FFFA17 $FFFA18 - $FFFA19 $FFFA1A - $FFFA1B $FFFA1C - $FFFA1D $FFFA1E - $FFFA1F $FFFA20 - $FFFA21 $FFFA22 - $FFFA23 $FFFA24 - $FFFA25 $FFFA26 - $FFFA27 $FFFA28 - $FFFA29 $FFFA2A - $FFFA2B $FFFA2C - $FFFA2D $FFFA2E - $FFFA2F $FFFA30 - $FFFA31 $FFFA32 - $FFFA33 $FFFA34 - $FFFA35 $FFFA36 - $FFFA37 $FFFA38 - $FFFA39 $FFFA3A - $FFFA3B $FFFA3C - $FFFA3D $FFFA3E - $FFFA3F $FFFA40 - $FFFC00 $FFFE00 - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/st_w.txt000066400000000000000000000003311504763705000267610ustar00rootroot00000000000000Bus Error testing results (word access): (1040 STf, with Ricoh chipset) $FF8002 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8604 $FF8610 - $FF8800 $FF8900 - $FFFA00 $FFFA40 - $FFFC00 $FFFE00 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/ste_b.txt000066400000000000000000000016621504763705000271110ustar00rootroot00000000000000Bus Error testing results (byte access): (on a STE Tos 1.62fr with 4mb memory) $FF8010 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8608 $FF8610 - $FF8800 $FF8940 - $FF8A00 $FF8A40 - $FF9000 $FF9002 - $FF9201 $FF9204 - $FF9211 $FF9212 - $FF9213 $FF9214 - $FF9215 $FF9216 - $FF9217 $FF9218 - $FFFA01 $FFFA02 - $FFFA03 $FFFA04 - $FFFA05 $FFFA06 - $FFFA07 $FFFA08 - $FFFA09 $FFFA0A - $FFFA0B $FFFA0C - $FFFA0D $FFFA0E - $FFFA0F $FFFA10 - $FFFA11 $FFFA12 - $FFFA13 $FFFA14 - $FFFA15 $FFFA16 - $FFFA17 $FFFA18 - $FFFA19 $FFFA1A - $FFFA1B $FFFA1C - $FFFA1D $FFFA1E - $FFFA1F $FFFA20 - $FFFA21 $FFFA22 - $FFFA23 $FFFA24 - $FFFA25 $FFFA26 - $FFFA27 $FFFA28 - $FFFA29 $FFFA2A - $FFFA2B $FFFA2C - $FFFA2D $FFFA2E - $FFFA2F $FFFA30 - $FFFA31 $FFFA32 - $FFFA33 $FFFA34 - $FFFA35 $FFFA36 - $FFFA37 $FFFA38 - $FFFA39 $FFFA3A - $FFFA3B $FFFA3C - $FFFA3D $FFFA3E - $FFFA3F $FFFA40 - $FFFC00 $FFFE00 - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/ste_w.txt000066400000000000000000000004771504763705000271410ustar00rootroot00000000000000Bus Error testing results (word access): (on a STE Tos 1.62fr with 4mb memory) $FF8010 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8604 $FF8610 - $FF8800 $FF8940 - $FF8A00 $FF8A40 - $FF9000 $FF9002 - $FF9200 $FF9204 - $FF9210 $FF9218 - $FF9220 $FF9224 - $FFFA00 $FFFA40 - $FFFC00 $FFFE00 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/tt_b.txt000066400000000000000000000103321504763705000267370ustar00rootroot00000000000000Bus Error testing results (byte access): (on a TT with TOS 3.06) $FF8002 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8400 $FF8600 - $FF8608 $FF8610 - $FF8700 $FF8720 - $FF8780 $FF8790 - $FF8800 $FF8801 - $FF8802 $FF8803 - $FF8804 $FF8805 - $FF8806 $FF8807 - $FF8808 $FF8809 - $FF880A $FF880B - $FF880C $FF880D - $FF880E $FF880F - $FF8810 $FF8811 - $FF8812 $FF8813 - $FF8814 $FF8815 - $FF8816 $FF8817 - $FF8818 $FF8819 - $FF881A $FF881B - $FF881C $FF881D - $FF881E $FF881F - $FF8820 $FF8821 - $FF8822 $FF8823 - $FF8824 $FF8825 - $FF8826 $FF8827 - $FF8828 $FF8829 - $FF882A $FF882B - $FF882C $FF882D - $FF882E $FF882F - $FF8830 $FF8831 - $FF8832 $FF8833 - $FF8834 $FF8835 - $FF8836 $FF8837 - $FF8838 $FF8839 - $FF883A $FF883B - $FF883C $FF883D - $FF883E $FF883F - $FF8840 $FF8841 - $FF8842 $FF8843 - $FF8844 $FF8845 - $FF8846 $FF8847 - $FF8848 $FF8849 - $FF884A $FF884B - $FF884C $FF884D - $FF884E $FF884F - $FF8850 $FF8851 - $FF8852 $FF8853 - $FF8854 $FF8855 - $FF8856 $FF8857 - $FF8858 $FF8859 - $FF885A $FF885B - $FF885C $FF885D - $FF885E $FF885F - $FF8860 $FF8861 - $FF8862 $FF8863 - $FF8864 $FF8865 - $FF8866 $FF8867 - $FF8868 $FF8869 - $FF886A $FF886B - $FF886C $FF886D - $FF886E $FF886F - $FF8870 $FF8871 - $FF8872 $FF8873 - $FF8874 $FF8875 - $FF8876 $FF8877 - $FF8878 $FF8879 - $FF887A $FF887B - $FF887C $FF887D - $FF887E $FF887F - $FF8880 $FF8881 - $FF8882 $FF8883 - $FF8884 $FF8885 - $FF8886 $FF8887 - $FF8888 $FF8889 - $FF888A $FF888B - $FF888C $FF888D - $FF888E $FF888F - $FF8890 $FF8891 - $FF8892 $FF8893 - $FF8894 $FF8895 - $FF8896 $FF8897 - $FF8898 $FF8899 - $FF889A $FF889B - $FF889C $FF889D - $FF889E $FF889F - $FF88A0 $FF88A1 - $FF88A2 $FF88A3 - $FF88A4 $FF88A5 - $FF88A6 $FF88A7 - $FF88A8 $FF88A9 - $FF88AA $FF88AB - $FF88AC $FF88AD - $FF88AE $FF88AF - $FF88B0 $FF88B1 - $FF88B2 $FF88B3 - $FF88B4 $FF88B5 - $FF88B6 $FF88B7 - $FF88B8 $FF88B9 - $FF88BA $FF88BB - $FF88BC $FF88BD - $FF88BE $FF88BF - $FF88C0 $FF88C1 - $FF88C2 $FF88C3 - $FF88C4 $FF88C5 - $FF88C6 $FF88C7 - $FF88C8 $FF88C9 - $FF88CA $FF88CB - $FF88CC $FF88CD - $FF88CE $FF88CF - $FF88D0 $FF88D1 - $FF88D2 $FF88D3 - $FF88D4 $FF88D5 - $FF88D6 $FF88D7 - $FF88D8 $FF88D9 - $FF88DA $FF88DB - $FF88DC $FF88DD - $FF88DE $FF88DF - $FF88E0 $FF88E1 - $FF88E2 $FF88E3 - $FF88E4 $FF88E5 - $FF88E6 $FF88E7 - $FF88E8 $FF88E9 - $FF88EA $FF88EB - $FF88EC $FF88ED - $FF88EE $FF88EF - $FF88F0 $FF88F1 - $FF88F2 $FF88F3 - $FF88F4 $FF88F5 - $FF88F6 $FF88F7 - $FF88F8 $FF88F9 - $FF88FA $FF88FB - $FF88FC $FF88FD - $FF88FE $FF88FF - $FF8900 $FF8914 - $FF8920 $FF8940 - $FF8961 $FF8962 - $FF8963 $FF8964 - $FF8C00 $FF8C20 - $FF8C80 $FF8C90 - $FF8E01 $FF8E02 - $FF8E03 $FF8E04 - $FF8E05 $FF8E06 - $FF8E07 $FF8E08 - $FF8E09 $FF8E0A - $FF8E0B $FF8E0C - $FF8E0D $FF8E0E - $FF8E0F $FF8E10 - $FF9000 $FF9002 - $FF9200 $FF9202 - $FFFA01 $FFFA02 - $FFFA03 $FFFA04 - $FFFA05 $FFFA06 - $FFFA07 $FFFA08 - $FFFA09 $FFFA0A - $FFFA0B $FFFA0C - $FFFA0D $FFFA0E - $FFFA0F $FFFA10 - $FFFA11 $FFFA12 - $FFFA13 $FFFA14 - $FFFA15 $FFFA16 - $FFFA17 $FFFA18 - $FFFA19 $FFFA1A - $FFFA1B $FFFA1C - $FFFA1D $FFFA1E - $FFFA1F $FFFA20 - $FFFA21 $FFFA22 - $FFFA23 $FFFA24 - $FFFA25 $FFFA26 - $FFFA27 $FFFA28 - $FFFA29 $FFFA2A - $FFFA2B $FFFA2C - $FFFA2D $FFFA2E - $FFFA2F $FFFA30 - $FFFA31 $FFFA32 - $FFFA33 $FFFA34 - $FFFA35 $FFFA36 - $FFFA37 $FFFA38 - $FFFA39 $FFFA3A - $FFFA3B $FFFA3C - $FFFA3D $FFFA3E - $FFFA3F $FFFA40 - $FFFA81 $FFFA82 - $FFFA83 $FFFA84 - $FFFA85 $FFFA86 - $FFFA87 $FFFA88 - $FFFA89 $FFFA8A - $FFFA8B $FFFA8C - $FFFA8D $FFFA8E - $FFFA8F $FFFA90 - $FFFA91 $FFFA92 - $FFFA93 $FFFA94 - $FFFA95 $FFFA96 - $FFFA97 $FFFA98 - $FFFA99 $FFFA9A - $FFFA9B $FFFA9C - $FFFA9D $FFFA9E - $FFFA9F $FFFAA0 - $FFFAA1 $FFFAA2 - $FFFAA3 $FFFAA4 - $FFFAA5 $FFFAA6 - $FFFAA7 $FFFAA8 - $FFFAA9 $FFFAAA - $FFFAAB $FFFAAC - $FFFAAD $FFFAAE - $FFFAAF $FFFAB0 - $FFFAB1 $FFFAB2 - $FFFAB3 $FFFAB4 - $FFFAB5 $FFFAB6 - $FFFAB7 $FFFAB8 - $FFFAB9 $FFFABA - $FFFABB $FFFABC - $FFFABD $FFFABE - $FFFABF $FFFAC0 - $FFFC00 $FFFC01 - $FFFC02 $FFFC03 - $FFFC04 $FFFC05 - $FFFC06 $FFFC07 - $FFFC08 $FFFC09 - $FFFC0A $FFFC0B - $FFFC0C $FFFC0D - $FFFC0E $FFFC0F - $FFFFFF EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/results/tt_w.txt000066400000000000000000000006431504763705000267700ustar00rootroot00000000000000Bus Error testing results (word access): (on a TT with TOS 3.06) $FF8002 - $FF8200 $FF8210 - $FF8240 $FF8280 - $FF8400 $FF8600 - $FF8604 $FF8610 - $FF8700 $FF8720 - $FF8780 $FF8790 - $FF8800 $FF8914 - $FF8920 $FF8940 - $FF8960 $FF8964 - $FF8C00 $FF8C20 - $FF8C80 $FF8C90 - $FF8E00 $FF8E10 - $FF9000 $FF9002 - $FF9200 $FF9202 - $FFFA00 $FFFA40 - $FFFA80 $FFFAC0 - $FFFC00 $FFFC10 - $FFFFFE EOF! hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/buserror/run_test.sh000077500000000000000000000027261504763705000257570ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 3 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 b|w " exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; width=$1 shift machine=$1 shift basedir=$(dirname "$0") testdir=$(mktemp -d) case "$machine" in st) reffile="st_$width.txt" ;; megast) reffile="mst4_$width.txt" ;; ste) reffile="ste_$width.txt" ;; megaste) reffile="mste4_$width.txt" ;; tt) reffile="tt_$width.txt" ;; falcon) reffile="fal_n_$width.txt" ;; *) echo "Unsupported machine type: $machine"; exit 1;; esac export HATARI_TEST=buserror export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy cp "$basedir/buserr_$width.prg" "$testdir" HOME="$testdir" $hatari --log-level fatal --fast-forward on --machine "$machine" \ --sound off --run-vbls 500 -t none "$@" "$testdir/buserr_$width.prg" \ 2> "$testdir"/stderr.txt > "$testdir"/stdout.txt exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari failed. Status=${exitstat}, output in ${testdir}" exit 1 fi grep -v '^(' "$basedir/results/$reffile" > "$testdir/expected.txt" if [ "$width" = "b" ]; then tester_output="$testdir/BUSERR_B.TXT" else tester_output="$testdir/BUSERR_W.TXT" fi if ! diff -q "$testdir/expected.txt" "$tester_output"; then echo "Test FAILED, output differs:" diff -u "$testdir/expected.txt" "$tester_output" rm -rf "$testdir" exit 1 fi echo "Test PASSED." rm -rf "$testdir" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/check-bashisms.sh000077500000000000000000000005561504763705000251340ustar00rootroot00000000000000#!/bin/sh cd "$(dirname "$0")/.." || exit 1 if [ -z "$(which checkbashisms)" ]; then echo "'checkbashisms' (from debian dev-scripts) missing" exit 1 fi errors=0 for i in $(git ls-files '*\.sh'); do echo "Checking $i ..." checkbashisms "$i" errors=$((errors + $?)) done if [ "$errors" -gt 0 ]; then echo "TEST FAILED" exit 1 fi echo "TEST SUCCESS" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cmdfifo.sh000077500000000000000000000044431504763705000236560ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 ..." exit 1 fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1 fi; testdir=$(mktemp -d) cmdfifo="$testdir/cmdfifo" remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT mkdir -p "$testdir/.config/hatari" cat > "$testdir/hatari.cfg" < "$testdir/out.txt" 2>&1 & hatari_pid=$! # Wait until the fifo has been created by Hatari while ! test -p "$cmdfifo" ; do sleep 0.1 done # Send some commands, check output later... echo "hatari-debug r" > "$cmdfifo" echo "hatari-path memsave $testdir/testmem.sav" > "$cmdfifo" echo "hatari-shortcut savemem" > "$cmdfifo" echo "hatari-shortcut screenshot" > "$cmdfifo" echo "hatari-shortcut recsound" > "$cmdfifo" echo "hatari-shortcut recanim" > "$cmdfifo" echo "hatari-option --spec512 256" > "$cmdfifo" echo "hatari-event doubleclick" > "$cmdfifo" echo "hatari-toggle printer" > "$cmdfifo" echo "hatari-embed-info" > "$cmdfifo" echo "hatari-shortcut quit" > "$cmdfifo" wait $hatari_pid exitstat=$? echo "--------------- Hatari output: -------------------" cat "$testdir/out.txt" echo "--------------------------------------------------" if [ $exitstat -ne 0 ]; then echo "ERROR: Running hatari FAILED. Status=${exitstat}" exit 1 fi if [ -e "$cmdfifo" ]; then echo "ERROR: FIFO removal FAILED" exit 1 fi if ! grep -q -i "D4.*00000000.*D5.*00000000" "$testdir/out.txt"; then echo "ERROR: Register dump FAILED" exit 1 fi if [ ! -e "$testdir/testmem.sav" ]; then echo "ERROR: Memory snapshot FAILED" exit 1 fi # shellcheck disable=SC2144 # there's only one match if [ ! -e "$testdir"/grab0001.* ]; then echo "ERROR: Screenshot FAILED" exit 1 fi if [ ! -e "$testdir/sndrec.wav" ]; then echo "ERROR: Sound recording FAILED" exit 1 fi if [ ! -e "$testdir/videorec.avi" ]; then echo "ERROR: Video recording FAILED" exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/configfile.sh000077500000000000000000000101201504763705000243410ustar00rootroot00000000000000#!/bin/sh # # Check whether loading and saving of config files works as expected if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 " exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; testdir=$(mktemp -d) if [ "$(uname -s)" = "Darwin" ]; then mkdir -p "${testdir}/Library/Application Support" cfgfile="${testdir}/Library/Application Support/Hatari/hatari.cfg" else cfgfile="$testdir"/.config/hatari/hatari.cfg fi remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT keymap="$testdir"/keymap.txt echo "# Temp file" > "$keymap" acsifile="$testdir"/acsi.img dd if=/dev/zero of="$acsifile" bs=512 count=1000 2> /dev/null scsifile="$testdir"/scsi.img dd if=/dev/zero of="$scsifile" bs=512 count=1000 2> /dev/null idefile="$testdir"/ide.img dd if=/dev/zero of="$idefile" bs=512 count=1000 2> /dev/null export HATARI_TEST=configfile export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy export HOME="$testdir" unset TERM $hatari --log-level fatal --sound off --tos none --confirm-quit false \ --machine tt --cpulevel 4 --fpu internal --cpuclock 16 --drive-led 0 \ --monitor tv --frameskips 3 --mousewarp off --statusbar FALSE \ --disasm uae --joy0 keys --keymap "$keymap" --crop 1 --fast-boot 1 \ --protect-floppy auto --gemdos-case upper --acsi 3="$acsifile" \ --scsi 5="$scsifile" --ide-master "$idefile" --patch-tos off \ --vdi 1 --rs232-out "$testdir"/serial-out.txt --rs232-in /dev/null \ --printer /dev/zero --avi-fps 60 --memsize 0 --alert-level fatal \ --saveconfig >"$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari with parameters FAILED. Status = ${exitstat}." cat "$testdir/out.txt" exit 1 fi echo "############################## Log file: ##############################" cat "$cfgfile" # Check whether the command line options have been included in the config file: echo "#################### Looking for expected settings: ###################" grep "nTextLogLevel = 0" "$cfgfile" || exit 1 grep "bEnableSound = FALSE" "$cfgfile" || exit 1 grep "bConfirmQuit = FALSE" "$cfgfile" || exit 1 grep "nModelType = 4" "$cfgfile" || exit 1 grep "nCpuLevel = 4" "$cfgfile" || exit 1 grep "nCpuFreq = 16" "$cfgfile" || exit 1 grep "bUseExtVdiResolutions = TRUE" "$cfgfile" || exit 1 grep "bShowDriveLed = FALSE" "$cfgfile" || exit 1 grep "nMonitorType = 3" "$cfgfile" || exit 1 grep "nFrameSkips = 3" "$cfgfile" || exit 1 grep "bMouseWarp = FALSE" "$cfgfile" || exit 1 grep "bShowStatusbar = FALSE" "$cfgfile" || exit 1 grep "bDisasmUAE = TRUE" "$cfgfile" || exit 1 grep "nJoystickMode = 2" "$cfgfile" || exit 1 grep "szMappingFileName = $keymap" "$cfgfile" || exit 1 grep "nMemorySize = 512" "$cfgfile" || exit 1 grep "nWriteProtection = 2" "$cfgfile" || exit 1 grep "nGemdosCase = 1" "$cfgfile" || exit 1 grep "sDeviceFile3 = $acsifile" "$cfgfile" || exit 1 grep "sDeviceFile5 = $scsifile" "$cfgfile" || exit 1 grep "sDeviceFile0 = $idefile" "$cfgfile" || exit 1 grep "bPatchTos = FALSE" "$cfgfile" || exit 1 grep "bEnableRS232 = TRUE" "$cfgfile" || exit 1 grep "szOutFileName = $testdir/serial-out.txt" "$cfgfile" || exit 1 grep "szInFileName = /dev/null" "$cfgfile" || exit 1 grep "bEnablePrinting = TRUE" "$cfgfile" || exit 1 grep "szPrintToFileName = /dev/zero" "$cfgfile" || exit 1 grep "bCrop = TRUE" "$cfgfile" || exit 1 grep "AviRecordFps = 60" "$cfgfile" || exit 1 grep "nAlertDlgLogLevel = 0" "$cfgfile" || exit 1 echo "######################################################################" # Now check that we can load and save the same config file again: touch "$testdir"/test.cfg cat > "$testdir"/commands.txt << EOF setopt -c $testdir/test.cfg setopt --saveconfig EOF $hatari --tos none --parse "$testdir"/commands.txt >"$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari -c ${testdir}/test.cfg FAILED. Status ${exitstat}." cat "$testdir"/out.txt exit 1 fi if ! diff -q "$cfgfile" "$testdir"/test.cfg ; then echo "Test FAILED, config files differs:" diff -u "$cfgfile" "$testdir"/test.cfg exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/000077500000000000000000000000001504763705000224725ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/CMakeLists.txt000066400000000000000000000004151504763705000252320ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) foreach (lvl 0 3 4) add_test(NAME cpu-integer-680${lvl}0 COMMAND ${testrunner} $ ${CMAKE_CURRENT_SOURCE_DIR}/int_test.tos --cpulevel ${lvl}) endforeach(lvl) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_abcd.s000066400000000000000000000007051504763705000244230ustar00rootroot00000000000000 /* test that ABCD clears Z only if result is nonzero */ .global tst_abcd_1 tst_abcd_1: clr.l d0 move.l #1,d1 abcd d0,d0 seq d0 rts .global tst_abcd_2 tst_abcd_2: move.l #1,d1 clr.l d0 abcd d1,d1 seq d0 rts .global tst_abcd_3 tst_abcd_3: move.l #0x033,d0 move.l #0x044,d1 abcd d1,d0 cmp.l #0x77,d0 sne d0 rts .global tst_abcd_4 tst_abcd_4: move.l #0x155,d0 abcd d0,d0 cmp.l #0x110,d0 sne d0 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_add.s000066400000000000000000000014361504763705000242640ustar00rootroot00000000000000 .global tst_add_1 tst_add_1: move.b #0x80,d0 add.b d0,d0 sne d0 rts .global tst_add_2 tst_add_2: move.w #0x8000,d0 add.w d0,d0 sne d0 rts .global tst_add_3 tst_add_3: move.l #0x80000000,d0 add.l d0,d0 sne d0 rts .global tst_add_4 tst_add_4: move.l #0x55667778,d1 move.l #0x11223388,d0 add.b d0,d1 sne d0 scc d2 cmp.l #0x55667700,d1 sne d1 or.b d1,d0 or.b d2,d0 rts .global tst_add_5 tst_add_5: move.l #0x55667788,d1 move.l #0x11223344,d0 add.w d0,d1 scs d0 cmp.l #0x5566aacc,d1 sne d1 or.b d1,d0 rts .global tst_add_6 tst_add_6: move.l #0x55667788,d1 move.l #0x11223344,d0 add.l d0,d1 scs d0 cmp.l #0x6688aacc,d1 sne d1 or.b d1,d0 rts .global tst_add_7 tst_add_7: move.l #0x80000000,d1 add.l d1,d1 scc d0 svc d2 sne d1 or.b d1,d0 or.b d2,d0 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_addi.s000066400000000000000000000011361504763705000244320ustar00rootroot00000000000000 .global tst_addi_1 tst_addi_1: sub.l d0,d0 addi.b #0,d0 sne d0 rts .global tst_addi_2 tst_addi_2: sub.l d0,d0 addi.w #0,d0 sne d0 rts .global tst_addi_3 tst_addi_3: sub.l d0,d0 addi.l #0,d0 sne d0 rts .global tst_addi_4 tst_addi_4: move.l #0x55667778,d1 addi.b #0x88,d1 sne d0 scc d2 cmp.l #0x55667700L,d1 sne d1 or.b d1,d0 or.b d2,d0 rts .global tst_addi_5 tst_addi_5: move.l #0x55667788,d1 addi.w #0x3344,d1 scs d0 cmp.l #0x5566aacc,d1 sne d1 or.b d1,d0 rts .global tst_addi_6 tst_addi_6: move.l #0x55667788,d1 addi.l #0x11223344,d1 cmp.l #0x6688aacc,d1 sne d0 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_addq.s000066400000000000000000000005261504763705000244440ustar00rootroot00000000000000 .global tst_addq_1 tst_addq_1: sub.l d0,d0 addq.b #1,d0 seq d0 rts .global tst_addq_2 tst_addq_2: sub.l d0,d0 addq.w #1,d0 seq d0 rts .global tst_addq_3 tst_addq_3: move.l d3,-(sp) move.l #0x55667778,d1 addq.b #8,d1 scs d0 spl d2 svc d3 cmp.l #0x55667780,d1 sne d1 or.b d1,d0 or.b d2,d0 or.b d3,d0 move.l (sp)+,d3 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_addx.s000066400000000000000000000003371504763705000244530ustar00rootroot00000000000000 /* test that ADDX does clears Z only if result is nonzero */ .global tst_addx_1 tst_addx_1: clr.l d0 move.l #1,d1 addx d0,d0 seq d0 rts .global tst_addx_2 tst_addx_2: move.l #1,d1 clr.l d0 addx d1,d1 seq d0 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_shft.s000066400000000000000000000046371504763705000245060ustar00rootroot00000000000000 /* ASL: test that both C & X flags are set to the last bit shifted out */ .global tst_shift_1 tst_shift_1: movem.l d3-d4,-(sp) move.l #0,d1 move.l #1,d2 move.l #1,d3 move.l #0,d4 lsr.l d2,d3 /* should set X & C */ scc d0 asl #1,d1 /* should clear C and X */ scs d1 addx.l d4,d4 tst.l d4 sne d4 or.b d1,d0 or.b d4,d0 movem.l (sp)+,d3-d4 rts /* test that lsl is modulo 64 */ .global tst_shift_2 tst_shift_2: move.l #33,d1 move.l #1,d2 lsl.l d1,d2 sne d0 rts .global tst_shift_3 tst_shift_3: move.l #65,d1 move.l #1,d2 lsl.l d1,d2 cmp.l #2,d2 sne d0 rts /* test that zero shift does not affect X flag */ .global tst_shift_4 tst_shift_4: movem.l d3-d4,-(sp) move.l #0,d1 move.l #1,d2 move.l #1,d3 move.l #0,d4 lsr.l d2,d3 /* should set X & C */ scc d0 lsr.l d1,d4 /* should not affect X & clear C */ scs d1 addx.l d4,d4 cmp.l #1,d4 sne d4 or.b d1,d0 or.b d4,d0 movem.l (sp)+,d3-d4 rts /* LSL: test that both C & X flags are set to the last bit shifted out */ .global tst_shift_5 tst_shift_5: movem.l d3-d4,-(sp) move.l #0x55555555,d1 move.l #0,d2 lsl.l #8,d1 scc d0 addx.l d2,d2 tst.l d2 seq d2 move.l #0,d3 lsl.l #1,d1 scs d4 addx.l d3,d3 tst.l d3 sne d3 cmp.l #0xaaaaaa00,d1 sne d1 or.b d1,d0 or.b d2,d0 or.b d3,d0 or.b d4,d0 movem.l (sp)+,d3-d4 rts /* same as above, for word shifts */ .global tst_shift_6 tst_shift_6: movem.l d3-d4,-(sp) move.l #0x55555555,d1 move.l #0,d2 lsl.w #8,d1 scc d0 addx.l d2,d2 cmp.l #1,d2 sne d2 move.l #0,d3 lsl.w #1,d1 scs d4 addx.l d3,d3 tst.l d3 sne d3 cmp.l #0x5555aa00,d1 sne d1 or.b d1,d0 or.b d2,d0 or.b d3,d0 or.b d4,d0 movem.l (sp)+,d3-d4 rts /* same as above, for byte shifts */ .global tst_shift_7 tst_shift_7: movem.l d3-d4,-(sp) move.l #0x55555555,d1 move.l #0,d2 lsl.b #8,d1 scc d0 addx.l d2,d2 cmp.l #1,d2 sne d2 moveq.l #0,d3 lsl.b #1,d1 scs d4 addx.l d3,d3 tst.l d3 sne d3 cmp.l #0x55555500,d1 sne d1 or.b d1,d0 or.b d2,d0 or.b d3,d0 or.b d4,d0 movem.l (sp)+,d3-d4 rts /* ROL: test that C is set to the last bit shifted out & that X is unaffected */ .global tst_shift_8 tst_shift_8: movem.l d3-d5,-(sp) moveq.l #0,d5 move.l #0x80000000,d0 add.l d0,d0 /* should set X, C and V, and clear d0 */ scc d1 svc d2 rol #1,d0 /* should clear C and V, and leave X unaffected */ scs d3 svs d4 addx.l d5,d5 cmp.l #1,d5 sne d0 or.b d1,d0 or.b d2,d0 or.b d3,d0 or.b d4,d0 movem.l (sp)+,d3-d5 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_test.c000066400000000000000000000046461504763705000245010ustar00rootroot00000000000000/* * CPU integer arithmetic tests. * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #include extern char tst_abcd_1(void); extern char tst_abcd_2(void); extern char tst_abcd_3(void); extern char tst_abcd_4(void); extern char tst_add_1(void); extern char tst_add_2(void); extern char tst_add_3(void); extern char tst_add_4(void); extern char tst_add_5(void); extern char tst_add_6(void); extern char tst_add_7(void); extern char tst_addi_1(void); extern char tst_addi_2(void); extern char tst_addi_3(void); extern char tst_addi_4(void); extern char tst_addi_5(void); extern char tst_addi_6(void); extern char tst_addq_1(void); extern char tst_addq_2(void); extern char tst_addq_3(void); extern char tst_addx_1(void); extern char tst_addx_2(void); extern char tst_shift_1(void); extern char tst_shift_2(void); extern char tst_shift_3(void); extern char tst_shift_4(void); extern char tst_shift_5(void); extern char tst_shift_6(void); extern char tst_shift_7(void); extern char tst_shift_8(void); struct test { char *name; char (*testfunc)(void); }; struct test tests[] = { { "abcd 1", tst_abcd_1 }, { "abcd 2", tst_abcd_2 }, { "abcd 3", tst_abcd_3 }, { "abcd 4", tst_abcd_4 }, { "add 1", tst_add_1 }, { "add 2", tst_add_2 }, { "add 3", tst_add_3 }, { "add 4", tst_add_4 }, { "add 5", tst_add_5 }, { "add 6", tst_add_6 }, { "add 7", tst_add_7 }, { "addi 1", tst_addi_1 }, { "addi 2", tst_addi_2 }, { "addi 3", tst_addi_3 }, { "addi 4", tst_addi_4 }, { "addi 5", tst_addi_5 }, { "addi 6", tst_addi_6 }, { "addq 1", tst_addq_1 }, { "addq 2", tst_addq_2 }, { "addq 3", tst_addq_3 }, { "addx 1", tst_addx_1 }, { "addx 2", tst_addx_2 }, { "shift 1", tst_shift_1 }, { "shift 2", tst_shift_2 }, { "shift 3", tst_shift_3 }, { "shift 4", tst_shift_4 }, { "shift 5", tst_shift_5 }, { "shift 6", tst_shift_6 }, { "shift 7", tst_shift_7 }, { "shift 8", tst_shift_8 }, { 0L, 0L } }; int main() { int failures = 0; int idx; for (idx = 0; tests[idx].name != 0L; idx++) { Cconws("Test '"); Cconws(tests[idx].name); Cconws("'\t: "); if (tests[idx].testfunc() != 0) { Cconws("FAILED\n"); failures++; } else { Cconws("OK\n"); } } // Crawcin(); return !(failures == 0); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_test.prj000066400000000000000000000002571504763705000250440ustar00rootroot00000000000000; AHCC project file for CPU integer tester int_test.tos .C [-iinclude] = ../startup.s int_test.c int_abcd.s int_add.s int_addi.s int_addq.s int_addx.s int_shft.s ahccstdi.lib hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cpu/int_test.tos000066400000000000000000000027051504763705000250560ustar00rootroot00000000000000`~*o - ЭЭ "ҍ.A// Bg?<JNAO N@??<LNAH8IjGQBDBC0HJgXHS?< NA\0H/4?< NA\Hk?< NA\0H tNJgHk ?< NA\RDRC`Hk?< NA\`JDV|LNuBrWNurBWNup3rD wVNu <U VNu<VNu0<@VNu <ЀVNu" ..." exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; testprg=$1 shift if [ ! -f "$testprg" ]; then echo "Second parameter must point to valid test PRG." exit 1; fi; testdir=$(mktemp -d) remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT export HATARI_TEST=cpu export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy HOME="$testdir" $hatari --log-level error --sound off --fast-forward on \ --tos none --run-vbls 500 "$@" "$testprg" > "$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari failed. Status=${exitstat}." cat "$testdir/out.txt" exit 1 fi # Now check for failure strings: if grep -qi fail "$testdir/out.txt"; then echo "Test FAILED:" cat "$testdir/out.txt" exit 1 fi if grep -qi error "$testdir/out.txt"; then echo "Test ERROR:" cat "$testdir/out.txt" exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cycles/000077500000000000000000000000001504763705000231655ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cycles/CMakeLists.txt000066400000000000000000000004631504763705000257300ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) add_test(NAME cycles-exact COMMAND ${testrunner} $ --compatible false --cpu-exact true) add_test(NAME cycles-compatible COMMAND ${testrunner} $ --compatible true --cpu-exact false) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cycles/cyccheck.prg000066400000000000000000000052551504763705000254620ustar00rootroot00000000000000` \BgHy?<NAXBgNA//?< NA\p oRJf///?9?<@NAO Nuone nopNqNutwo nopsNqNqNulsl #0pNulsl #6pNunop+exg+dbratNqAQNuexg+nop+dbratANqQNunop+exg+moveNqA Nuexg+nop+moveANq Nunop+asr+addpNqa`Nuasr+nop+addpaNq`Nunop+cmp+beqNq`(cmp+nop+beqNq`clr+sub+moveBB20 Nusub+clr+moveBB20 Numove ff820a8 Numove ff88008Nu ".8DNZbnv |JNu /H*@Hy?< NA\##1e@ #p#p3Nr#Jyg#pF'NR/?A pgF@h0<z@NNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq0 _N8 888 <g ZyygZ1e@F#`h1p@F#¼ļJA@N HBBz$z @|`0<'x#@Qp`F# 1w@AU~ H@|0B@H@Q#/9?< NA\LxNu3NsRESULTS.TXT : cycles filenamefhndltestsmain_looHpprintZseptext run_testZnumberteHxtstrlenloHlopouttext_noptest_noptext_2noHptest_2noHptext_lslH1test_lslH1text_lslH2test_lslH2text_exgH_dbra1test_exgH_dbra1text_exgH_dbra2test_exgH_dbra2text_exgH_move1test_exgH_move1text_exgH _move2test_exgH_move2text_asrH"_add1test_asrH._add1text_asrH8_add2test_asrHD_add2text_cmpHN_beq1test_cmpHZ_beq1text_cmpHb_beq2test_cmpHn_beq2text_subHv_move1test_subH_move1text_subH_move2test_subH_move2text_movHe_820atest_movHe_820atext_movHe_8800test_movHe_8800testinitRscratchPold_sspsaveilleHgalloopold_vblvblhandlHergot_vbl waitnopnojmpbasecontinuefound_vH(aluessomethinHrg_wrong_heregetouttaHherepvdecoutlHoop0R  F hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cycles/cyccheck.s000066400000000000000000000156111504763705000251310ustar00rootroot00000000000000; Check CPU cycles. Note that this test only runs in 8 MHz ST color modes! opt x+ clr.w -(sp) pea filename move.w #$3C,-(sp) trap #1 ; Fcreate addq.l #8,sp move.w d0,fhndl lea tests,a4 main_loop: move.l (a4)+,-(sp) bsr print addq.l #4,sp pea septext bsr print addq.l #4,sp move.l (a4)+,-(sp) bsr run_test addq.l #4,sp pea numbertext bsr print addq.l #4,sp tst.l (a4) bne.s main_loop move.w fhndl,-(sp) move.w #$3E,-(sp) trap #1 ; Fclose addq.l #4,sp ; move.w #7,-(sp) ; trap #1 ; Crawcin ; addq.l #2,sp clr.w -(sp) trap #1 print: move.l 4(sp),-(sp) move.w #9,-(sp) trap #1 ; Cconws addq.l #6,sp moveq #-1,d0 move.l 4(sp),a0 strlenloop: addq.l #1,d0 tst.b (a0)+ bne.s strlenloop move.l 4(sp),-(sp) move.l d0,-(sp) move.w fhndl,-(sp) move.w #$40,-(sp) trap #1 ; Fwrite lea 12(sp),sp out: rts ; *************** The tests ****************** text_nop: dc.b "one nop",0 even test_nop: nop ; 4 cycles rts text_2nop: dc.b "two nops",0 even test_2nop: nop ; 4 cycles nop ; 4 cycles rts text_lsl1: dc.b "lsl #0",0 even test_lsl1: moveq #0,d0 ; 4 cycles lsl.l d0,d1 ; 8 + 2 * 0 = 8 cycles rts text_lsl2: dc.b "lsl #6",0 even test_lsl2: moveq #6,d0 ; 4 cycles lsl.l d0,d1 ; 8 + 2 * 6 = 20 cycles rts text_exg_dbra1: dc.b "nop+exg+dbra",0 even test_exg_dbra1: moveq #0,d2 ; 4 cycles nop ; 4 cycles exg d0,d1 ; 6 cycles (pairing with the following instruction!) dbra d2,out ; 14 cycles rts text_exg_dbra2: dc.b "exg+nop+dbra",0 even test_exg_dbra2: moveq #0,d2 ; 4 cycles exg d0,d1 ; 6 cycles, rounded to 8 (no pairing!) nop ; 4 cycles dbra d2,out ; 14 cycles, rounded to 16 rts text_exg_move1: dc.b "nop+exg+move",0 even test_exg_move1: nop ; 4 cycles exg d0,d1 ; 6 cycles (pairing with the following instruction!) move.b -(a0),d1 ; 10 cycles rts text_exg_move2: dc.b "exg+nop+move",0 even test_exg_move2: exg d0,d1 ; 6 cycles, will be rounded to 8 cycles (no pairing) nop ; 4 cycles move.b -(a0),d1 ; 10 cycles, will be rounded to 12 rts text_asr_add1: dc.b "nop+asr+add",0 even test_asr_add1: moveq #2,d0 ; 4 cycles nop ; 4 cycles asr.w d0,d1 ; 6 + 2 * 2 = 10 cycles (not 12, thanks to pairing!) add.w -(a0),d1 ; 10 cycles rts text_asr_add2: dc.b "asr+nop+add",0 even test_asr_add2: moveq #2,d0 ; 4 cycles asr.w d0,d1 ; 6 + 2 * 2 = 10 cycles, rounded to 12 nop ; 4 cycles add.w -(a0),d1 ; 10 cycles, rounded to 12 rts text_cmp_beq1: dc.b "nop+cmp+beq",0 even test_cmp_beq1: nop ; 4 cycles cmp.l d0,d0 ; 6 cycles bra out ; 10 cycles text_cmp_beq2: dc.b "cmp+nop+beq",0 even test_cmp_beq2: cmp.l d0,d0 ; 6 cycles, rounded to 8 nop ; 4 cycles bra out ; 10 cycles, rounded to 12 text_sub_move1: dc.b "clr+sub+move",0 even test_sub_move1: clr.w d2 ; 4 cycles sub.l (a0),d0 ; 10 cycles move.w 0(a0,d2),d1 ; 14 cycles rts text_sub_move2: dc.b "sub+clr+move",0 even test_sub_move2: sub.l (a0),d0 ; 10 cycles, rounded to 12 clr.w d2 ; 4 cycles move.w 0(a0,d2),d1 ; 14 cycles, rounded to 16 rts text_move_820a: dc.b "move ff820a",0 even test_move_820a: move.b $ffff820a.w,d0 ; 12 cycles rts text_move_8800: dc.b "move ff8800",0 even test_move_8800: move.b $ffff8800.w,d0 ; 12 cycles + wait state = 16 cycles rts tests: dc.l text_nop, test_nop dc.l text_2nop, test_2nop dc.l text_lsl1, test_lsl1 dc.l text_lsl2, test_lsl2 dc.l text_exg_dbra1, test_exg_dbra1 dc.l text_exg_dbra2, test_exg_dbra2 dc.l text_exg_move1, test_exg_move1 dc.l text_exg_move2, test_exg_move2 dc.l text_asr_add1, test_asr_add1 dc.l text_asr_add2, test_asr_add2 dc.l text_cmp_beq1, test_cmp_beq1 dc.l text_cmp_beq2, test_cmp_beq2 dc.l text_sub_move1, test_sub_move1 dc.l text_sub_move2, test_sub_move2 dc.l text_move_820a, test_move_820a dc.l text_move_8800, test_move_8800 dc.l 0, 0 ********************************************** * Mini-program to determine the number of * * clockcycles a routine uses * * by Niclas Thisell * ********************************************** testinit: move.l #scratch,a0 rts ; Put routine to test on stack before calling this function run_test: move.l 4(sp),d0 movem.l d3-d7/a3-a6,-(sp) move.l d0,a5 ; ; pointer to test routine in a5 pea 0 move.w #$0020,-(sp) trap #1 addq.l #6,sp move.l d0,old_ssp move.l $00000010,saveillegal move.w #$0765,$ffff8240.w move.b #2,$ffff820a.w loop: move.l $70,old_vbl move.l #vblhandler,$70 move.w #0,got_vbl stop #$2300 ; Wait for VBL tst.w got_vbl beq.s loop move.l old_vbl,$70 move #$2700,sr jsr testinit move.l a0,-(sp) move.w d0,-(sp) lea $ffff8209.w,a0 moveq #0,d0 .wait: move.b (a0),d0 beq.s .wait not.w d0 lsr.w d0,d0 move.w #128,d0 sub.w nopno(pc),d0 add.w d0,d0 jmp .jmpbase(pc,d0.w) .jmpbase: REPT 128 nop ENDR move.w (sp)+,d0 movea.l (sp)+,a0 ;************************************ ;sprite-routine or whatever here! jsr (a5) continue: ;*********************************** move.b $ffff8209.w,d0 move.b $ffff8207.w,d1 move.b $ffff8205.w,d2 move.b $ffff8209.w,d3 sub.b d0,d3 cmp.b #18,d3 beq.s .found_values addq.w #5,nopno andi.w #127,nopno beq something_wrong_here move.w #$0765,$ffff8240.w move #$2300,sr bra loop .found_values: move.w #$0770,$ffff8240.w move #$2300,sr and.l #$000000ff,d0 and.l #$000000ff,d1 and.l #$000000ff,d2 lsl.w #8,d2 add.w d1,d2 lsl.l #8,d2 add.w d0,d2 sub.l $0000044e,d2 divu #160,d2 move.l d2,d0 mulu #256,d0 swap d2 add.w d2,d0 sub.w nopno(pc),d0 sub.w nopno(pc),d0 add.w d0,d0 sub.w #248,d0 bra getouttahere something_wrong_here: move.w #10000,d0 .p: addi.w #$0123,$ffff8240.w dbra d0,.p moveq #0,d0 bra getouttahere nopno: dc.w 0 getouttahere: move #$2300,sr move.b #2,$ffff820a.w move.w #$0777,$ffff8240.w lea numbertext+5(pc),a0 moveq #4,d7 .decoutloop: divu #10,d0 swap d0 add.w #"0",d0 move.b d0,-(a0) clr.w d0 swap d0 dbra d7,.decoutloop move.l saveillegal,$00000010 move.l old_ssp,-(sp) move.w #$20,-(sp) trap #1 ; Super addq.l #6,sp movem.l (sp)+,d3-d7/a3-a6 rts vblhandler: move.w #1,got_vbl rte data filename: dc.b "RESULTS.TXT",0 septext: dc.b " :",9,0 numbertext: dc.b " cycles",13,10,0 even bss old_ssp: ds.l 1 saveillegal: ds.l 1 old_vbl: ds.l 1 got_vbl: ds.w 1 fhndl: ds.w 1 ds.l 16 scratch: ds.l 16 end hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cycles/run_test.sh000077500000000000000000000017041504763705000253710ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 " exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; basedir=$(dirname "$0") testdir=$(mktemp -d) remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT export HATARI_TEST=cycles export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy cp "$basedir/cyccheck.prg" "$testdir" HOME="$testdir" $hatari --log-level fatal --fast-forward on --sound off \ --run-vbls 1000 --tos none "$@" "$testdir/cyccheck.prg" \ > "$testdir/log.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Test FAILED, Hatari returned error status ${exitstat}." cat "$testdir/log.txt" exit 1 fi if ! diff -q "$basedir/test_out.txt" "$testdir/RESULTS.TXT"; then echo "Test FAILED, output differs:" diff -u "$basedir/test_out.txt" "$testdir/RESULTS.TXT" exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/cycles/test_out.txt000066400000000000000000000006651504763705000256030ustar00rootroot00000000000000one nop : 00004 cycles two nops : 00008 cycles lsl #0 : 00012 cycles lsl #6 : 00024 cycles nop+exg+dbra : 00028 cycles exg+nop+dbra : 00032 cycles nop+exg+move : 00020 cycles exg+nop+move : 00024 cycles nop+asr+add : 00028 cycles asr+nop+add : 00032 cycles nop+cmp+beq : 00020 cycles cmp+nop+beq : 00024 cycles clr+sub+move : 00032 cycles sub+clr+move : 00036 cycles move ff820a : 00012 cycles move ff8800 : 00016 cycles hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/000077500000000000000000000000001504763705000234675ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/CMakeLists.txt000066400000000000000000000022371504763705000262330ustar00rootroot00000000000000 set(TEST_SOURCE_DIR ${CMAKE_SOURCE_DIR}/tests/debugger) include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/includes ${CMAKE_SOURCE_DIR}/src/debug ${CMAKE_SOURCE_DIR}/src/falcon ${CMAKE_SOURCE_DIR}/src/cpu) add_library(DebuggerTestLib test-dummies.c ${CMAKE_SOURCE_DIR}/src/str.c ${CMAKE_SOURCE_DIR}/src/debug/breakcond.c ${CMAKE_SOURCE_DIR}/src/debug/debugcpu.c ${CMAKE_SOURCE_DIR}/src/debug/history.c ${CMAKE_SOURCE_DIR}/src/debug/evaluate.c ${CMAKE_SOURCE_DIR}/src/debug/symbols.c ${CMAKE_SOURCE_DIR}/src/debug/vars.c) add_executable(test-breakcond test-breakcond.c) target_link_libraries(test-breakcond DebuggerTestLib) add_test(NAME debugger-breakcond WORKING_DIRECTORY ${TEST_SOURCE_DIR} COMMAND test-breakcond) add_executable(test-evaluate test-evaluate.c) target_link_libraries(test-evaluate DebuggerTestLib) add_test(NAME debugger-evaluate WORKING_DIRECTORY ${TEST_SOURCE_DIR} COMMAND test-evaluate) add_executable(test-symbols test-symbols.c) target_link_libraries(test-symbols DebuggerTestLib) add_test(NAME debugger-symbols WORKING_DIRECTORY ${TEST_SOURCE_DIR} COMMAND test-symbols) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/000077500000000000000000000000001504763705000244005ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/console.ini000066400000000000000000000000561504763705000265440ustar00rootroot00000000000000# debugger test help lock # and finally kill hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/debugger.ini000066400000000000000000000005301504763705000266630ustar00rootroot00000000000000# run Hatari with following options to test this: # --machine falcon --tos etos1024k.img --dsp emu --parse debugger.ini lock regaddr disasm d0 dspsymbols dsp-test.sym db (r0).x = data_300 db m0 = code_200 da test symbols os-header.sym b (a0) = os_magic b pc = ("reseth") symbols etos1024k.sym a _int_vbl dspsymbols free symbols free quit hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/dsp-test.sym000066400000000000000000000002321504763705000266720ustar00rootroot00000000000000# some DSP test symbols 0000 T _start 0050 T test 0051 T DSP_start_address 0100 T code_100 0200 T code_200 0300 D data_300 0400 B BSS_400 0500 T othersym hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/etos1024k.sym000066400000000000000000001265651504763705000266050ustar00rootroot000000000000000x00000380 D _proc_lives 0x00000384 D _proc_dregs 0x000003a4 D _proc_aregs 0x000003c4 D _proc_enum 0x000003c8 D _proc_usp 0x000003cc D _proc_stk 0x00000400 D _etv_timer 0x00000400 D sysvars_start 0x00000404 D _etv_critic 0x00000408 D _etv_term 0x0000040c D _etv_xtra 0x00000420 D memvalid 0x00000420 D _memvalid 0x00000424 D memctrl 0x00000426 D resvalid 0x0000042a D resvector 0x0000042e D _phystop 0x00000432 D _membot 0x00000436 D _memtop 0x0000043a D memval2 0x0000043a D _memval2 0x0000043e D _flock 0x00000440 D _seekrate 0x00000442 D _timer_ms 0x00000444 D _fverify 0x00000446 D _bootdev 0x00000448 D _palmode 0x0000044a D _defshiftmod 0x0000044c D _sshiftmod 0x0000044e D _v_bas_ad 0x00000452 D _vblsem 0x00000454 D _nvbls 0x00000456 D _vblqueue 0x0000045a D _colorptr 0x0000045e D _screenpt 0x00000462 D _vbclock 0x00000466 D _frclock 0x0000046a D _hdv_init 0x0000046e D _swv_vec 0x00000472 D _hdv_bpb 0x00000476 D _hdv_rw 0x0000047a D _hdv_boot 0x0000047e D _hdv_mediach 0x00000482 D _cmdload 0x00000484 D _conterm 0x00000486 D trp14ret 0x0000048a D criticret 0x0000048e D _themd 0x0000049e D ____md 0x000004a2 D _savptr 0x000004a6 D _nflops 0x000004a8 D _con_state 0x000004ac D _save_row 0x000004ae D sav_context 0x000004b2 D _bufl 0x000004ba D _hz_200 0x000004be D the_env 0x000004c2 D _drvbits 0x000004c6 D _dskbufp 0x000004ca D _autopath 0x000004ce D _vbl_list 0x000004ee D _dumpflg 0x000004f0 D _prtabt 0x000004f2 D _sysbase 0x000004f6 D _shell_p 0x000004fa D _end_os 0x000004fe D _exec_os 0x00000502 D _dump_vec 0x00000506 D _prt_stat 0x0000050a D _prt_vec 0x0000050e D _aux_stat 0x00000512 D _aux_vec 0x00000516 D _pun_ptr 0x0000051a D memval3 0x0000051a D _memval3 0x0000051e D _bconstat_vec 0x0000053e D _bconin_vec 0x0000055e D _bcostat_vec 0x0000057e D _bconout_vec 0x0000059e D _longframe 0x000005a0 D _p_cookies 0x000005a4 D _ramtop 0x000005a8 D _ramvalid 0x000005ac D _bell_hook 0x000005b0 D _kcl_hook 0x00000700 D _pmmutree 0x00000800 B _stkbot 0x00000800 D sysvars_end 0x00001000 B lowstram.o 0x00001000 B __low_stram_start 0x00001000 B _shifty 0x00001000 B _stktop 0x00001001 B _dskbuf_alignment 0x00001004 B _dskbuf 0x00001c04 B _phys_work 0x000020b8 B lineavars.o 0x000020bc B _CUR_FONT 0x000020ee B _mouse_cdb 0x000020ee B _m_pos_hx 0x000020f0 B _m_pos_hy 0x000020f4 B _m_cdb_bg 0x000020f6 B _m_cdb_fg 0x000020f8 B _mask_form 0x00002138 B _INQ_TAB 0x00002192 B _DEV_TAB 0x000021ec B _GCURX 0x000021ec B _mdata 0x000021ee B _GCURY 0x000021f0 B _HIDE_CNT 0x000021f2 B _MOUSE_BT 0x000021f4 B _REQ_COL 0x00002254 B _SIZ_TAB 0x00002272 B _TERM_CH 0x00002274 B _chc_mode 0x00002276 B _CUR_WORK 0x0000227a B _def_font 0x0000227e B _font_ring 0x0000228e B _font_count 0x00002290 B _line_cw 0x00002292 B _loc_mode 0x000022e4 B _num_qc_lines 0x000022e6 B _str_mode 0x000022e8 B _val_mode 0x000022ea B _cur_ms_stat 0x000022ec B _disab_cnt 0x000022ee B _newx 0x000022f0 B _newy 0x000022f2 B _draw_flag 0x000022f3 B _mouse_flag 0x000022f4 B _sav_cur_x 0x000022f6 B _sav_cur_y 0x000022f8 B retsav 0x000022fc B _mouse_cursor_save 0x00002404 B _tim_addr 0x00002408 B _tim_chain 0x0000240c B _user_but 0x00002410 B _user_cur 0x00002414 B _user_mot 0x00002418 B _v_cel_ht 0x0000241a B _v_cel_mx 0x0000241c B _v_cel_my 0x0000241e B _v_cel_wr 0x00002420 B _v_col_bg 0x00002422 B _v_col_fg 0x00002424 B _v_cur_ad 0x00002428 B _v_cur_of 0x0000242a B _v_cur_cx 0x0000242c B _v_cur_cy 0x0000242e B _v_period 0x0000242f B _v_cur_tim 0x00002430 B _v_fnt_ad 0x00002434 B _v_fnt_nd 0x00002436 B _v_fnt_st 0x00002438 B _v_fnt_wr 0x0000243a B _V_REZ_HZ 0x0000243c B _v_off_ad 0x00002440 B _v_stat_0 0x00002442 B _V_REZ_VT 0x00002444 B _BYTES_LIN 0x00002446 B line_a_vars 0x00002446 B _v_planes 0x00002448 B _v_lin_wr 0x0000244a B _CONTRL 0x0000244a B _local_pb 0x0000244e B _INTIN 0x00002452 B _PTSIN 0x00002456 B _INTOUT 0x0000245a B _PTSOUT 0x0000245e B _COLBIT0 0x00002460 B _COLBIT1 0x00002462 B _COLBIT2 0x00002464 B _COLBIT3 0x00002466 B _LSTLIN 0x00002468 B _LN_MASK 0x0000246a B _WRT_MODE 0x0000246c B _X1 0x0000246e B _Y1 0x00002470 B _X2 0x00002472 B _Y2 0x00002474 B _PATPTR 0x00002478 B _PATMSK 0x0000247a B _MFILL 0x0000247c B _CLIP 0x0000247e B _XMINCL 0x00002480 B _YMINCL 0x00002482 B _XMAXCL 0x00002484 B _YMAXCL 0x00002486 B _XDDA 0x00002488 B _DDAINC 0x0000248a B _SCALDIR 0x0000248c B _MONO 0x0000248e B _SOURCEX 0x00002490 B _SOURCEY 0x00002492 B _DESTX 0x00002494 B _DESTY 0x00002496 B _DELX 0x00002498 B _DELY 0x0000249a B _FBASE 0x0000249e B _FWIDTH 0x000024a0 B _STYLE 0x000024a2 B _LITEMASK 0x000024a4 B _SKEWMASK 0x000024a6 B _WEIGHT 0x000024a8 B _ROFF 0x000024aa B _LOFF 0x000024ac B _SCALE 0x000024ae B _CHUP 0x000024b0 B _TEXTFG 0x000024b2 B _SCRTCHP 0x000024b6 B _SCRPT2 0x000024ba B _COPYTRAN 0x000024bc B _SEEDABORT 0x000024f4 B _req_col2 0x00002aa4 B _ext_mouse_cursor_save 0x00002cac B __bss 0x00002cac B __low_stram_end 0x00002cac B _mcpu 0x00002cac B processor.o 0x00002cb2 B _fputype 0x00002cba B _is_apollo_68080 0x00002cc0 B vectors.o 0x00002f50 B aciavecs.o 0x00002f50 B _trap_save_area 0x000030d0 B _ikbdiorec 0x000030de B _midiiorec 0x000030ec B _mousexvec 0x000030f4 B _kbdvecs 0x00003128 B bios.o 0x00003128 B _bootflags 0x0000312a B _boot_status 0x00003140 B xbios.o 0x00003144 B acsi.o 0x0000314c B _blkdev 0x0000314c B blkdev.o 0x0000369c B clock.o 0x0000369c B _has_megartc 0x0000369e B _has_monster_rtc 0x000036a0 B _has_icdrtc 0x000036bc B _cookie_akp 0x000036bc B country.o 0x000036c0 B _cookie_idt 0x000036c4 B disk.o 0x000036c4 B _physsect2 0x000038c4 B _physsect 0x00003ac4 B _drvrem 0x00003ac8 B _ultrasatan_id 0x00003aca B _has_ultrasatan_clock 0x00003acc B _units 0x00003ca0 B dmasound.o 0x00003ca0 B _has_falcon_dmasound 0x00003ca2 B _has_microwire 0x00003ca4 B _has_dmasound 0x00003cac B floppy.o 0x00003cec B font.o 0x00003cec B _sysfonts 0x00003cfc B _fon6x6 0x00003d56 B _fon8x8 0x00003db0 B _fon8x16 0x00003e0c B ide.o 0x00004054 B ikbd.o 0x00004054 B _mouse_packet 0x00004094 B initinfo.o 0x00004098 B linea.o 0x000040a8 B lineainit.o 0x000040a8 B _v_planes_shift 0x000040ac B _is_aranym 0x000040ac B machine.o 0x000040ae B _has_dip_switches 0x000040b0 B _blitter_is_enabled 0x000040b2 B _has_blitter 0x000040b4 B _has_magnum 0x000040b6 B _has_monster 0x000040b8 B _has_vme 0x000040ba B _has_scc 0x000040bc B _has_tt_mfp 0x000040be B _has_modectl 0x000040c0 B _has_videl 0x000040c2 B _has_tt_shifter 0x000040c4 B _has_ste_shifter 0x000040c6 B _cookie_swi 0x000040ca B _cookie_mch 0x000040ce B _cookie_snd 0x000040d2 B _cookie_fdc 0x000040d6 B _cookie_vdo 0x000040da B _is_bus32 0x000040dc B mfp.o 0x000040dc B _timer_c_sieve 0x000040e0 B mouse.o 0x000040e0 B _rel_pblock 0x000040ec B natfeats.o 0x00004108 B _has_nvram 0x00004108 B nvram.o 0x0000410c B parport.o 0x00004118 B screen.o 0x0000411c B _recovery_loops 0x0000411c B serport.o 0x00004120 B _bconmap_root 0x00004128 B _rs232iorecptr 0x0000412c B _rsconfptr 0x00004a5c B sound.o 0x00004a64 B _current_video_mode 0x00004a64 B videl.o 0x00004a66 B _falcon_shadow_count 0x00004e8c B xhdi.o 0x00004e94 B delay.o 0x00004e94 B _loopcount_1_msec 0x00004e98 B delayasm.o 0x00004ea0 B _meminit_flags 0x00004ea0 B memory2.o 0x00004ea4 B scsi.o 0x00004ef4 B _has_nova 0x00004ef4 B nova.o 0x00004f04 B dsp.o 0x00004f04 B _has_dsp 0x00004fc4 B bdosmain.o 0x00004fc4 B _old_trap2 0x000050cc B console.o 0x000052f4 B fsdir.o 0x000052fc B _drvsel 0x000052fc B fsdrive.o 0x00005300 B _dirtbl 0x000053f0 B _errbuf 0x000053f0 B fsglob.o 0x00005424 B _errdrv 0x00005426 B _errcode 0x0000542a B _rwerr 0x0000542e B _sft 0x0000571c B _drvtbl 0x00005784 B osmem.o 0x00005784 B _root 0x00007624 B proc.o 0x00007624 B _run 0x00007e64 B rwa.o 0x00008638 B _current_date 0x00008638 B time.o 0x0000863a B _current_time 0x00008640 B _malloc_align_stram 0x00008640 B umem.o 0x00008644 B _has_alt_ram 0x00008646 B _pmdalt 0x00008652 B _pmd 0x00008668 B cookie.o 0x00008708 B miscasm.o 0x00008714 B _nls_hash 0x00008714 B nls.o 0x00008718 B nlsasm.o 0x0000a768 B string.o 0x0000a770 B _REV_MAP_COL 0x0000a770 B vdi_col.o 0x0000a970 B _MAP_COL 0x0000ab70 B _mcs_ptr 0x0000ab70 B vdi_control.o 0x0000ad80 B vdi_fill.o 0x0000ad80 B _vdishare 0x0000c590 B vdi_gdp.o 0x0000c5a0 B vdi_line.o 0x0000c600 B _flip_y 0x0000c600 B vdi_main.o 0x0000c604 B vdi_misc.o 0x0000c608 B _old_statvec 0x0000c608 B vdi_mouse.o 0x0000c60c B _user_wheel 0x0000c610 B vdi_raster.o 0x0000c660 B __endvdibss 0x0000c660 B endvdi.o 0x0000c660 B gemasm.o 0x0000c8e8 B gemstart.o 0x0000c9f0 B gemdosif.o 0x0000c9fa B _drwaddr 0x0000ca06 B _tiksav 0x0000ca0a B _NUM_TICK 0x0000ca0e B _CMP_TICK 0x0000ca12 B _enable_ceh 0x0000d0a4 B gemaplib.o 0x0000d0a4 B _gl_rbuf 0x0000d0a8 B _gl_rlen 0x0000d0aa B _gl_recd 0x0000d0ac B _gl_play 0x0000d0b0 B _appl_msg 0x0000d0b0 B gemctrl.o 0x0000d0c0 B _gl_ctmown 0x0000d0c2 B _gl_ctwait 0x0000d0d0 B gemevlib.o 0x0000d0d0 B _gl_ticktime 0x0000d0d2 B _gl_dclick 0x0000d0d8 B gemfmlib.o 0x0000d0d8 B _ml_ocnt 0x0000d1f0 B gemfslib.o 0x0000d208 B gemgraf.o 0x0000d208 B _gl_rmenu 0x0000d210 B _gl_rcenter 0x0000d218 B _gl_rzero 0x0000d220 B _gl_rfull 0x0000d228 B _gl_rscreen 0x0000d230 B _ptsin 0x0000d258 B _intin 0x0000d358 B _contrl 0x0000d370 B _gl_ws 0x0000d3e2 B _gl_dst 0x0000d3f6 B _gl_src 0x0000d40a B _gl_handle 0x0000d40c B _gl_nplanes 0x0000d40e B _gl_clip 0x0000d416 B _gl_hbox 0x0000d418 B _gl_wbox 0x0000d41a B _gl_hschar 0x0000d41c B _gl_wschar 0x0000d41e B _gl_hchar 0x0000d420 B _gl_wchar 0x0000d422 B _gl_height 0x0000d424 B _gl_width 0x0000d43c B gemgsxif.o 0x0000d43c B _gl_moff 0x0000d43e B _ptsout 0x0000d456 B _intout 0x0000d4dc B _D 0x0000d4dc B geminit.o 0x00010926 B _curpid 0x00010928 B _wind_spb 0x00010932 B _fpcnt 0x00010934 B _fph 0x00010936 B _fpt 0x00010938 B _indisp 0x0001093a B _zlr 0x0001093e B _dlr 0x00010942 B _eul 0x00010946 B _nrl 0x0001094a B _drl 0x0001094e B _rlr 0x00010952 B _gl_prevmouse 0x0001099c B _gl_mouse 0x000109e6 B _mouse_cursor 0x00010a06 B _ad_envrn 0x00010a0a B _num_accs 0x00010a0c B _totpds 0x00010bbc B geminput.o 0x00010bbc B _gl_bdely 0x00010bbe B _gl_btrue 0x00010bc0 B _gl_bdesired 0x00010bc2 B _gl_bclick 0x00010bc4 B _ctl_pd 0x00010bc8 B _gl_mowner 0x00010bcc B _pr_mclick 0x00010bce B _pr_yrat 0x00010bd0 B _pr_xrat 0x00010bd2 B _pr_button 0x00010bd4 B _mtrans 0x00010bd6 B _mclick 0x00010bd8 B _kstate 0x00010bda B _yrat 0x00010bdc B _xrat 0x00010bde B _button 0x00010bf4 B gemmnlib.o 0x00010bf4 B _gl_dabox 0x00010bf6 B _gl_mnppd 0x00010bfa B _gl_mntree 0x00010d04 B gemobed.o 0x00010d24 B gemrslib.o 0x00010dac B gemshlib.o 0x00010dac B _gl_nextrez 0x00010dae B _gl_changerez 0x00010db0 B _ad_stail 0x00010eb4 B gemsuper.o 0x00010eb4 B _gl_mnclick 0x00010ebc B gemwmlib.o 0x00010ebc B _gl_awind 0x00010ec0 B _gl_wtop 0x000111fc B gemwrect.o 0x00011214 B gsx2.o 0x00011214 B _vdipb 0x00011228 B gem_rsc.o 0x00011228 B _rs_obj 0x00011858 B _rs_tedinfo 0x00011ba0 B deskstart.o 0x000123a0 B deskmain.o 0x000123a0 B _menu_shortcuts 0x000123c2 B _gl_apid 0x000123c4 B _gl_pmstr 0x000123c8 B _gl_amstr 0x00012420 B gembind.o 0x00012420 B _global 0x000124a0 B deskapp.o 0x000124a8 B deskdir.o 0x000124b8 B deskfun.o 0x000124c4 B deskglob.o 0x000124c4 B _G 0x00012810 B deskinf.o 0x00012814 B deskins.o 0x0001283c B desksupp.o 0x00012844 B desk_rsc.o 0x00012844 B _desk_rs_trees 0x00012894 B _desk_rs_obj 0x00012898 B _desk_rs_tedinfo 0x00012dec B cmdasm.o 0x00012dec B _environment 0x00013dfc B cmdmain.o 0x00013dfc B _redir_handle 0x00013e00 B _user_path 0x00013e80 B _dta 0x00013e84 B _nflops_copy 0x00013e86 B _linewrap 0x00013e88 B _requested_res 0x00013e8a B _current_res 0x00013e8c B _screen_rows 0x00013e8e B _screen_cols 0x00013e90 B _idt_value 0x0001405c B cmdedit.o 0x0001410c B cmdexec.o 0x00014110 B cmdint.o 0x00014114 B __ebss 0x00014114 B endrom.o 0x00014114 D __end_os_stram 0x00e00000 T _os_header 0x00e00000 T startup.o 0x00e00000 T __text 0x00e00030 T _main 0x00e0004c T _osxhbootdelay 0x00e00122 T memdone 0x00e00128 T _run_cartridge_applications 0x00e00164 T meminit 0x00e00164 T memory.o 0x00e0040a T _detect_32bit_address_bus 0x00e00468 T processor.o 0x00e00584 T _processor_init 0x00e00660 T _instruction_cache_kludge 0x00e00660 T _invalidate_instruction_cache 0x00e0069e T _flush_data_cache 0x00e006a0 T _invalidate_data_cache 0x00e006d4 T _cache_exists 0x00e006e4 T _set_cache 0x00e00724 T _init_exc_vec 0x00e00724 T vectors.o 0x00e0073c T _init_user_vec 0x00e00836 T _int_hbl 0x00e0084c T _int_vbl 0x00e00910 T _int_timerc 0x00e00944 T _mfp_rs232_rx_interrupt 0x00e00954 T _mfp_rs232_tx_interrupt 0x00e00964 T _mfp_tt_rx_interrupt 0x00e00974 T _mfp_tt_tx_interrupt 0x00e00984 T _call_etv_critic 0x00e0099e T _default_etv_critic 0x00e009a6 T _int_illegal 0x00e009cc T _int_priv 0x00e00a2e T _int_unimpint 0x00e00c4a T _biostrap 0x00e00c58 T _xbiostrap 0x00e00cb2 T _just_rte 0x00e00cb4 T _xbios_unimpl 0x00e00cc0 T _supexec 0x00e00cc6 T _check_read_byte 0x00e00cf0 T _protect_v 0x00e00d00 T _protect_w 0x00e00d18 T _protect_ww 0x00e00d30 T _protect_wlwwwl 0x00e00d4e T _keyclick 0x00e00d64 T aciavecs.o 0x00e00d64 T _init_acia_vecs 0x00e00e0c T _int_acia 0x00e00e92 T ikbdraw 0x00e00f80 T _call_mousevec 0x00e00f98 T bios.o 0x00e00f98 T _setexc 0x00e00fb4 T _tickcal 0x00e00fb8 T _drvmap 0x00e00fbe T _mediach 0x00e00fd2 T _getbpb 0x00e00fe6 T _lrwabs 0x00e01010 T _bcostat 0x00e01074 T _bconin 0x00e010d8 T _bconstat 0x00e0113c T _bconout 0x00e011ac T _can_shutdown 0x00e011fa T _biosmain 0x00e019a6 T _is_text_pointer 0x00e019c4 T _bios_ent 0x00e019c6 T _bios_vecs 0x00e019f8 T xbios.o 0x00e01ab0 T _xbios_do_unimpl 0x00e01ab8 T _xbios_ent 0x00e01aba T _xbios_vecs 0x00e01cf4 T acsi.o 0x00e01edc T _acsi_init 0x00e01f0a T _acsi_rw 0x00e020f2 T _acsi_ioctl 0x00e0248c T biosmem.o 0x00e0248c T _bmem_init 0x00e024d2 T _balloc_stram 0x00e0251e T _getmpb 0x00e02554 T blkdev.o 0x00e02a6a T _blkdev_getbpb 0x00e02db4 T _compute_cksum 0x00e02dca T _blkdev_init 0x00e02f16 T _blkdev_boot 0x00e02f4a T _add_partition 0x00e03040 T _get_shift 0x00e03062 T _blkdev_drvmap 0x00e0306a T _blkdev_avail 0x00e0307c T chardev.o 0x00e0307c T _char_dummy 0x00e03080 T _charout_dummy 0x00e03084 T _bcostat2 0x00e03088 T _bconout5 0x00e0309c T _bconout2 0x00e030ac T _chardev_init 0x00e031b0 T clock.o 0x00e037a0 T _detect_icdrtc 0x00e03804 T _detect_monster_rtc 0x00e03874 T _detect_megartc 0x00e038fa T _clockvec 0x00e0391c T _clock_init 0x00e03976 T _settime 0x00e03e28 T _gettime 0x00e04328 T conout.o 0x00e043f2 T _invert_cell 0x00e04408 T _move_cursor 0x00e04518 T _blank_out 0x00e0465c T _scroll_up 0x00e046ae T _ascii_out 0x00e048b4 T _scroll_down 0x00e04900 T country.o 0x00e04996 T _get_lang_name 0x00e049ae T _detect_akp 0x00e04a28 T _detect_idt 0x00e04a88 T _get_keytbl 0x00e04aae T _get_fonts 0x00e06b8c T disk.o 0x00e06d10 T _disk_inquire 0x00e06d66 T _disk_get_capacity 0x00e06e7e T _disk_rw 0x00e078a8 T _disk_rescan 0x00e07900 T _disk_mediach 0x00e079b4 T _disk_init_all 0x00e07a74 T _DMAread 0x00e07ad2 T _DMAwrite 0x00e07b44 T dma.o 0x00e07b44 T _set_dma_addr 0x00e07b58 T _detect_dmasound 0x00e07b58 T dmasound.o 0x00e07ba2 T _locksnd 0x00e07bc8 T _unlocksnd 0x00e07bec T _soundcmd 0x00e07e32 T _setbuffer 0x00e07f24 T _setsndmode 0x00e07f74 T _settracks 0x00e07fc0 T _setmontracks 0x00e07ffc T _setinterrupt 0x00e08064 T _buffoper 0x00e080b6 T _dsptristate 0x00e0811a T _gpio 0x00e08164 T _devconnect 0x00e083f2 T _dmasound_init 0x00e084c6 T _sndstatus 0x00e08510 T _buffptr 0x00e08598 T floppy.o 0x00e08ccc T _flop_hdv_init 0x00e08de8 T _flop_boot_read 0x00e08e0c T _flop_checksum 0x00e08e4c T _flop_mediach 0x00e08f4e T _protobt 0x00e0907a T _floprd 0x00e090a0 T _flopwr 0x00e090c8 T _flopver 0x00e09362 T _floppy_rw 0x00e094d6 T _flopfmt 0x00e098ea T _floprate 0x00e09976 T _flopvbl 0x00e09b20 T _font_init 0x00e09b20 T font.o 0x00e09bb0 T _font_set_default 0x00e09ca8 T ide.o 0x00e0a144 T _detect_ide 0x00e0a1d4 T _ide_init 0x00e0a6b0 T _ide_rw 0x00e0aa80 T _ide_ioctl 0x00e0ab10 T ikbd.o 0x00e0b05e T _keytbl 0x00e0b09a T _bioskeys 0x00e0b0d4 T _kbshift 0x00e0b0ee T _bconstat2 0x00e0b104 T _bconin2 0x00e0b198 T _kbrate 0x00e0b1da T _kb_timerc_int 0x00e0b2f2 T _kbd_int 0x00e0b6f6 T _bcostat4 0x00e0b706 T _bconout4 0x00e0b720 T _ikbdws 0x00e0b74c T _ikbd_writeb 0x00e0b764 T _ikbd_writew 0x00e0b796 T _kbd_init 0x00e0b8b4 T initinfo.o 0x00e0bbf6 T _initinfo 0x00e0c0f6 T _display_startup_msg 0x00e0c1bc T kprint.o 0x00e0c26c T _cprintf 0x00e0c28a T _kprintf 0x00e0c2b8 T _kcprintf 0x00e0c4c6 T _dopanic 0x00e0cb7c T _int_linea 0x00e0cb7c T linea.o 0x00e0cc40 T lineainit.o 0x00e0cc40 T _set_screen_shift 0x00e0cc56 T _linea_init 0x00e0cc98 T _machine_detect 0x00e0cc98 T machine.o 0x00e0cfb4 T _machine_init 0x00e0cfb6 T _fill_cookie_jar 0x00e0d2f2 T _machine_name 0x00e0d370 T mfp.o 0x00e0d370 T _tt_mfp_init 0x00e0d38e T _tt_mfpint 0x00e0d43c T _mfp_init 0x00e0d45a T _jdisint 0x00e0d4ae T _jenabint 0x00e0d4f6 T _mfpint 0x00e0d524 T _setup_timer 0x00e0d5ba T _xbtimer 0x00e0d608 T _timeout_gpip 0x00e0d62e T _init_system_timer 0x00e0d660 T _bconstat3 0x00e0d660 T midi.o 0x00e0d676 T _bconin3 0x00e0d6d0 T _bcostat3 0x00e0d6e0 T _bconout3 0x00e0d6f8 T _midiws 0x00e0d724 T _midi_init 0x00e0d734 T _Initmous 0x00e0d734 T mouse.o 0x00e0d8ac T _detect_native_features 0x00e0d8ac T natfeat.o 0x00e0d8de T _natfeat_cookie 0x00e0d8f6 T _xhdi_vec 0x00e0d90c T natfeats.o 0x00e0d944 T _natfeat_init 0x00e0d9ea T _has_natfeats 0x00e0d9f2 T _nfGetFullName 0x00e0da24 T _is_nfStdErr 0x00e0da32 T _nfStdErr 0x00e0da50 T _get_xhdi_nfid 0x00e0da74 T _nf_shutdown 0x00e0da98 T _has_nf_shutdown 0x00e0dac8 T _nf_bootstrap 0x00e0db04 T _nf_getbootdrive 0x00e0db3a T _nf_getbootstrap_args 0x00e0db94 T _mmu_is_emulated 0x00e0dbd0 T _nf_setlinea 0x00e0dc0c T _detect_nvram 0x00e0dc0c T nvram.o 0x00e0dc28 T _get_nvram_rtc 0x00e0dc4c T _set_nvram_rtc 0x00e0dc6a T _nvmaccess 0x00e0ddf8 T _nvram_init 0x00e0de00 T _panic 0x00e0de00 T panicasm.o 0x00e0de2c T _halt 0x00e0de32 T _kill_program 0x00e0de3c T _warm_reset 0x00e0de48 T _cold_reset 0x00e0de60 T parport.o 0x00e0de60 T _setprt 0x00e0de78 T _parport_init 0x00e0deaa T _bconin0 0x00e0deae T _bcostat0 0x00e0debe T _bconout0 0x00e0df84 T screen.o 0x00e0dfbe T _egetshift 0x00e0dfd4 T _esetbank 0x00e0dffe T _esetcolor 0x00e0e02e T _esetpalette 0x00e0e0be T _egetpalette 0x00e0e14e T _esetgray 0x00e0e196 T _esetsmear 0x00e0e1d6 T _initialise_palette_registers 0x00e0e2ca T _screen_init_address 0x00e0e33a T _set_rez_hacked 0x00e0e34e T _rez_changeable 0x00e0e3b0 T _get_monitor_type 0x00e0e3ca T _initial_vram_size 0x00e0e404 T _screen_get_current_mode_info 0x00e0e454 T _get_palette 0x00e0e4b2 T _get_pixel_size 0x00e0e54a T _physbase 0x00e0e594 T _logbase 0x00e0e5be T _getrez 0x00e0e622 T _check_moderez 0x00e0e682 T _setpalette 0x00e0e68c T _setcolor 0x00e0e6d6 T _vsync 0x00e0e70c T _setscreen 0x00e0e7e8 T _screen_init_mode 0x00e0e990 T _esetshift 0x00e0e9c6 T _detect_monitor_change 0x00e0ecf8 T _bconstat1 0x00e0ecf8 T serport.o 0x00e0ed0e T _bconin1 0x00e0ed68 T _bcostat1 0x00e0ed90 T _bconout1 0x00e0f05e T _bconoutB 0x00e0f19e T _rsconf1 0x00e0f3ec T _push_serial_iorec 0x00e0f424 T _mfp_rs232_rx_interrupt_handler 0x00e0f472 T _mfp_rs232_tx_interrupt_handler 0x00e0f4bc T _mfp_tt_rx_interrupt_handler 0x00e0f50a T _mfp_tt_tx_interrupt_handler 0x00e0f554 T _scc_init 0x00e0f630 T _init_serport 0x00e0fb86 T _bconmap 0x00e0fd6c T sound.o 0x00e0fd98 T _snd_init 0x00e0fe16 T _giaccess 0x00e0fe48 T _ongibit 0x00e0fe70 T _offgibit 0x00e0fe98 T _dosound 0x00e0feb4 T _sndirq 0x00e0ff74 T _bell 0x00e0ffbc T videl.o 0x00e1007a T _lookup_videl_mode 0x00e100dc T _vmontype 0x00e10126 T _vsetmode 0x00e10444 T _vsetsync 0x00e104b2 T _vgetsize 0x00e1058c T _vsetrgb 0x00e10752 T _vgetrgb 0x00e108ae T _vfixmode 0x00e10938 T _videl_get_current_mode_info 0x00e1098a T _videl_setrez 0x00e109ea T _initialise_falcon_palette 0x00e10af4 T _get_videl_mode 0x00e10b0c T _videl_check_moderez 0x00e11f20 T vt52.o 0x00e12518 T _cputc 0x00e12534 T _blink 0x00e125ae T _cursconf 0x00e12616 T _vt52_init 0x00e12770 T xhdi.o 0x00e128da T _init_XHDI_drvmap 0x00e12902 T _xhdi_handler 0x00e1302c T pmmu030.o 0x00e1302c T _setup_68030_pmmu 0x00e13144 T delay.o 0x00e13144 T _init_delay 0x00e13194 T _calibrate_delay 0x00e13218 T delayasm.o 0x00e13218 T _run_calibration 0x00e13288 T memory2.o 0x00e13288 T _ttram_detect 0x00e1337c T _altram_init 0x00e134c8 T scsi.o 0x00e1446c T _detect_scsi 0x00e1449e T _scsi_init 0x00e14586 T _scsi_ioctl 0x00e146c6 T _build_rw_command 0x00e147ca T _scsi_rw 0x00e14998 T nova.o 0x00e149f4 T _detect_nova 0x00e14b60 T _init_nova 0x00e1554c T _get_novamembase 0x00e155a0 T dsp.o 0x00e1564e T _detect_dsp 0x00e15662 T _dsp_doblock 0x00e156e4 T _dsp_blkhandshake 0x00e15768 T _dsp_blkunpacked 0x00e157b6 T _dsp_instream 0x00e1580a T _dsp_outstream 0x00e1585e T _dsp_inout_handler 0x00e15936 T _dsp_iostream 0x00e159ca T _dsp_io_handler 0x00e15a6c T _dsp_removeinterrupts 0x00e15a8a T _dsp_getwordsize 0x00e15a9a T _dsp_blkwords 0x00e15afe T _dsp_blkbytes 0x00e15b60 T _dsp_lock 0x00e15b84 T _dsp_unlock 0x00e15b8c T _dsp_available 0x00e15baa T _dsp_reserve 0x00e15be4 T _dsp_execboot 0x00e15cce T _dsp_execprog 0x00e15d30 T _dsp_lodtobinary 0x00e1605c T _dsp_loadprog 0x00e16094 T _dsp_triggerhc 0x00e160aa T _dsp_requestuniqueability 0x00e160c6 T _dsp_getprogability 0x00e160da T _dsp_flushsubroutines 0x00e16108 T _dsp_init 0x00e16168 T _dsp_loadsubroutine 0x00e1636e T _dsp_inqsubrability 0x00e1639e T _dsp_runsubroutine 0x00e163fa T _dsp_hf0 0x00e1644e T _dsp_hf1 0x00e164a2 T _dsp_hf2 0x00e164be T _dsp_hf3 0x00e164da T _dsp_hstat 0x00e164ec T _dsp_setvectors 0x00e16552 T _dsp_sv_handler 0x00e16594 T _dsp_multblocks 0x00e16adc T dsp2.o 0x00e16adc T _dsp_inout_asm 0x00e16aec T _dsp_io_asm 0x00e16afc T _dsp_sv_asm 0x00e16b0c T bdosmain.o 0x00e16b7c T _osinit_before_xmaddalt 0x00e16bc0 T _osinit_after_xmaddalt 0x00e16be8 T _osif 0x00e1750c T console.o 0x00e17756 T _stdhdl_init 0x00e177b6 T _get_default_handle 0x00e177c4 T _xconstat 0x00e1781a T _xconostat 0x00e1783c T _xprtostat 0x00e1785e T _xauxistat 0x00e178b4 T _xauxostat 0x00e178d6 T _tabout 0x00e17976 T _xconout 0x00e17994 T _xauxout 0x00e179bc T _xprtout 0x00e179e4 T _xrawcin 0x00e179fc T _conin 0x00e17a26 T _xconin 0x00e17a58 T _xnecin 0x00e17a8a T _xauxin 0x00e17aac T _xrawio 0x00e17b20 T _xconws 0x00e17b56 T _cgets 0x00e17cba T _xconrs 0x00e17cf0 T fsbuf.o 0x00e17d5e T _bufl_init 0x00e17dbc T _flush 0x00e17ef4 T _getbcb 0x00e18100 T _getrec 0x00e18148 T fsdir.o 0x00e18538 T _xgsdtof 0x00e1859a T _builds 0x00e186f6 T _xsnext 0x00e18940 T _incr_curdir_usage 0x00e1899a T _decr_curdir_usage 0x00e189d2 T _xgetdir 0x00e18a62 T _dirinit 0x00e18b0e T _makofd 0x00e18b68 T _scan 0x00e18d00 T _findit 0x00e18ed2 T _xchdir 0x00e18f94 T _ixsfirst 0x00e190fc T _xsfirst 0x00e1914e T _xrename 0x00e19574 T _xchmod 0x00e19662 T _xrmdir 0x00e197a2 T _xmkdir 0x00e19a06 T _free_available_dnds 0x00e19a68 T fsdrive.o 0x00e19a68 T _log_media 0x00e19c48 T _ckdrv 0x00e19d54 T _cl2rec 0x00e19d54 T fsfat.o 0x00e19d6e T _clfix 0x00e19efe T _getrealcl 0x00e1a008 T _getclnum 0x00e1a028 T _nextcl 0x00e1a1e0 T _xgetfree 0x00e1a344 T fshand.o 0x00e1a344 T _ixforce 0x00e1a3c4 T _xforce 0x00e1a3da T _xdup 0x00e1a494 T fsio.o 0x00e1a944 T _eof 0x00e1a96c T _ixlseek 0x00e1aa5a T _xlseek 0x00e1aad2 T _ixread 0x00e1ab04 T _xread 0x00e1ab2c T _ixwrite 0x00e1ab46 T _xwrite 0x00e1ab70 T fsmain.o 0x00e1ab70 T _xgetdta 0x00e1ab7c T _xsetdta 0x00e1ab8a T _xsetdrv 0x00e1abb0 T _xgetdrv 0x00e1abbe T _getofd 0x00e1ac20 T fsopnclo.o 0x00e1ad58 T _xopen 0x00e1add4 T _ixclose 0x00e1af70 T _xclose 0x00e1b0a4 T _ixdel 0x00e1b1be T _xunlink 0x00e1b24a T _ixcreat 0x00e1b4e2 T _xcreat 0x00e1b4f4 T _contains_illegal_characters 0x00e1b52c T _ffit 0x00e1b52c T iumem.o 0x00e1b620 T _freeit 0x00e1b718 T _shrinkit 0x00e1b794 T _kpgmhdrld 0x00e1b794 T kpgmld.o 0x00e1b812 T _kpgmld 0x00e1b9fc T _kpgm_relocate 0x00e1bb50 T osmem.o 0x00e1bb50 T _xmfremd 0x00e1bc6e T _xmgetblk 0x00e1bd44 T _xmgetmd 0x00e1be32 T _xmfreblk 0x00e1be54 T _osmem_init 0x00e1be78 T proc.o 0x00e1c112 T _xexec 0x00e1c4bc T _xterm 0x00e1c5fa T _x0term 0x00e1c600 T _xtermres 0x00e1c654 T _bdos_trap2 0x00e1c654 T rwa.o 0x00e1c670 T _enter 0x00e1c6ee T _gouser 0x00e1c724 T _termuser 0x00e1c7cc T time.o 0x00e1c8b4 T _xgetdate 0x00e1c8be T _xsetdate 0x00e1c926 T _xgettime 0x00e1c930 T _xsettime 0x00e1c984 T _time_init 0x00e1c9cc T umem.o 0x00e1c9cc T _xmfree 0x00e1ca30 T _xsetblk 0x00e1caf0 T _xmxalloc 0x00e1cc22 T _xmalloc 0x00e1cc4e T _srealloc 0x00e1cc7a T _xmaddalt 0x00e1cd5c T _total_alt_ram 0x00e1cd90 T _umem_init 0x00e1ce0e T _set_owner 0x00e1ce64 T _cookie_init 0x00e1ce64 T cookie.o 0x00e1ce7c T _cookie_add 0x00e1ced0 T _cookie_get 0x00e1cf22 T _get_idt_cookie 0x00e1cf68 T _get_frb_cookie 0x00e1cfa4 T _get_floppy_type 0x00e1cfe8 T doprintf.o 0x00e1d0a2 T _doprintf 0x00e1d544 T intmath.o 0x00e1d544 T _Isqrt 0x00e1d5cc T _langs 0x00e1d5cc T langs.o 0x00e4f67c T _memcpy 0x00e4f67c T memmove.o 0x00e4f69e T _memmove 0x00e4f8f0 T _bzero_nobuiltin 0x00e4f8f0 T memset.o 0x00e4f8fe T _memset 0x00e4f9bc T miscasm.o 0x00e4f9bc T _trap1 0x00e4f9dc T _trap1_pexec 0x00e4fa00 T _stop_until_interrupt 0x00e4fa4c T _just_rts 0x00e4fa4e T _mul_div_round 0x00e4fa88 T _nls_init 0x00e4fa88 T nls.o 0x00e4fa94 T _nls_set_lang 0x00e4faec T _gettext_init 0x00e4faec T nlsasm.o 0x00e4fb06 T _gettext 0x00e4fbdc T _setjmp 0x00e4fbdc T setjmp.o 0x00e4fbea T _longjmp 0x00e4fc00 T string.o 0x00e4fc16 T _strlcpy 0x00e4fc66 T _strlen 0x00e4fc7e T _strcat 0x00e4fcb2 T _strcmp 0x00e4fcd6 T _memcmp 0x00e4fd06 T _strncmp 0x00e4fd3a T _strncasecmp 0x00e4fd98 T _toupper 0x00e4fdae T _sprintf 0x00e4fdec T gemdos.o 0x00e4fdee T _pgmld 0x00e4fe40 T _dos_rawcin 0x00e4fe4e T _dos_conws 0x00e4fe60 T _dos_conis 0x00e4fe6e T _dos_gdrv 0x00e4fe7c T _dos_sdta 0x00e4fe8e T _dos_gdta 0x00e4fe9c T _dos_sfirst 0x00e4feb2 T _dos_snext 0x00e4fec0 T _dos_open 0x00e4fed6 T _dos_close 0x00e4fee8 T _dos_read 0x00e4ff04 T _dos_write 0x00e4ff20 T _dos_lseek 0x00e4ff3c T _dos_chdir 0x00e4ff4e T _dos_gdir 0x00e4ff64 T _dos_sdrv 0x00e4ff76 T _dos_create 0x00e4ff8c T _dos_mkdir 0x00e4ff9e T _dos_chmod 0x00e4ffba T _dos_setdt 0x00e4fff0 T _dos_label 0x00e50062 T _dos_delete 0x00e50074 T _dos_space 0x00e500d6 T _dos_rename 0x00e500f0 T _dos_rmdir 0x00e50102 T _dos_alloc_stram 0x00e50116 T _dos_avail_stram 0x00e5012a T _dos_avail_altram 0x00e50140 T _dos_alloc_anyram 0x00e50156 T _dos_avail_anyram 0x00e5016c T _dos_free 0x00e5017e T _dos_shrink 0x00e50198 T _dos_load_file 0x00e501d0 T _fmt_str 0x00e501d0 T optimize.o 0x00e50262 T _unfmt_str 0x00e502b2 T _inf_sset 0x00e502de T _inf_sget 0x00e50300 T _inf_gindex 0x00e5034a T _inf_what 0x00e503a4 T _scan_2 0x00e50428 T _filename_start 0x00e50440 T _wildcmp 0x00e504dc T optimopt.o 0x00e504dc T _scasb 0x00e504f2 T _expand_string 0x00e5050c T _inside 0x00e5050c T rectfunc.o 0x00e50546 T _rc_constrain 0x00e505a2 T _rc_equal 0x00e505c4 T _rc_intersect 0x00e5063c T _rc_union 0x00e5069c T stringasm.o 0x00e5069c T _strlencpy 0x00e506b0 T _strchr 0x00e506cc D fnt_off_6x6.o 0x00e506cc D _off_6x6_table 0x00e508d0 D fnt_off_8x8.o 0x00e508d0 D _off_8x8_table 0x00e50ad4 T _fnt_st_6x6 0x00e50ad4 T fnt_st_6x6.o 0x00e50fb0 T _fnt_st_8x8 0x00e50fb0 T fnt_st_8x8.o 0x00e5180c T _fnt_st_8x16 0x00e5180c T fnt_st_8x16.o 0x00e52868 T _fnt_l2_6x6 0x00e52868 T fnt_l2_6x6.o 0x00e52d44 T _fnt_l2_8x8 0x00e52d44 T fnt_l2_8x8.o 0x00e535a0 T _fnt_l2_8x16 0x00e535a0 T fnt_l2_8x16.o 0x00e545fc T _fnt_gr_6x6 0x00e545fc T fnt_gr_6x6.o 0x00e54ad8 T _fnt_gr_8x8 0x00e54ad8 T fnt_gr_8x8.o 0x00e55334 T _fnt_gr_8x16 0x00e55334 T fnt_gr_8x16.o 0x00e56390 T _fnt_ru_6x6 0x00e56390 T fnt_ru_6x6.o 0x00e5686c T _fnt_ru_8x8 0x00e5686c T fnt_ru_8x8.o 0x00e570c8 T _fnt_ru_8x16 0x00e570c8 T fnt_ru_8x16.o 0x00e58124 T _fnt_tr_6x6 0x00e58124 T fnt_tr_6x6.o 0x00e58600 T _fnt_tr_8x8 0x00e58600 T fnt_tr_8x8.o 0x00e58e5c T _fnt_tr_8x16 0x00e58e5c T fnt_tr_8x16.o 0x00e59eb8 T _version 0x00e59eb8 T version.o 0x00e59ec0 T ___mulsi3 0x00e59ec0 T _mulsi3.o 0x00e59ee4 T ___udivsi3 0x00e59ee4 T _udivsi3.o 0x00e59f40 T ___divsi3 0x00e59f40 T _divsi3.o 0x00e59f70 T vdi_asm.o 0x00e59f70 T _vditrap 0x00e59f8c T _GSX_ENTRY 0x00e5a010 T _mouse_int 0x00e5a0d2 T _wheel_int 0x00e5a12a T _mov_cur 0x00e5a150 T _call_user_but 0x00e5a15c T _call_user_wheel 0x00e5a16c T vdi_col.o 0x00e5a49e T _vdi_vs_color 0x00e5a576 T _init_colors 0x00e5a718 T _vdi_vq_color 0x00e5b78c T vdi_control.o 0x00e5b964 T _get_vwk_by_handle 0x00e5b988 T _update_rez_dependent 0x00e5ba56 T _validate_color_index 0x00e5ba6e T _vdi_vs_clip 0x00e5bb1a T _vdi_vswr_mode 0x00e5bb60 T _vdi_v_opnvwk 0x00e5bc08 T _vdi_v_clsvwk 0x00e5bc6e T _vdi_v_opnwk 0x00e5bd7a T _vdi_v_clswk 0x00e5bdcc T _vdi_v_clrwk 0x00e5bdea T _vdi_vq_extnd 0x00e5bf50 T vdi_esc.o 0x00e5c12a T _vdi_v_escape 0x00e5c154 T _esc_init 0x00e5c160 T _esc_exit 0x00e5c1bc T vdi_fill.o 0x00e5c4a0 T _vdi_vsf_udpat 0x00e5c4fc T _vdi_vsf_color 0x00e5c52e T _vdi_vsf_perimeter 0x00e5c55c T _vdi_vr_recfl 0x00e5c60c T _vdi_vqf_attributes 0x00e5c650 T _st_fl_ptr 0x00e5c72a T _vdi_vsf_style 0x00e5c76c T _vdi_vsf_interior 0x00e5c792 T _clc_flit 0x00e5c942 T _polygon 0x00e5ca56 T _vdi_v_fillarea 0x00e5ca74 T _contourfill 0x00e5cd10 T _vdi_v_contourfill 0x00e5cd50 T _vdi_v_get_pixel 0x00e5cd80 T _get_pix 0x00e5cd94 T _put_pix 0x00e5ce28 T _SOLID 0x00e5ce2a T _HOLLOW 0x00e5ce2c T _ROM_UD_PATRN 0x00e5d0bc T vdi_gdp.o 0x00e5d444 T _vdi_v_gdp 0x00e5d880 T vdi_input.o 0x00e5d8c0 T _vdi_v_choice 0x00e5d8d4 T _vdi_v_string 0x00e5d9c0 T _vdi_vq_key_s 0x00e5d9e2 T _vdi_vsin_mode 0x00e5da36 T _vdi_vqin_mode 0x00e5da80 T vdi_line.o 0x00e5dcb0 T _vdi_vsl_udsty 0x00e5dcc0 T _vdi_vsl_type 0x00e5dcfa T _vdi_vsl_width 0x00e5dd48 T _vdi_vsl_ends 0x00e5dd86 T _vdi_vsl_color 0x00e5ddb8 T _vdi_vql_attributes 0x00e5ddfe T _draw_rect_common 0x00e5e556 T _Vwk2Attrib 0x00e5e582 T _draw_rect 0x00e5e5c0 T _linea_rect 0x00e5e6b2 T _linea_hline 0x00e5e6fe T _linea_polygon 0x00e5e792 T _linea_fill 0x00e5e820 T _clip_line 0x00e5ead0 T _arrow 0x00e5ebc2 T _wideline 0x00e5f0ee T _abline 0x00e5f93e T _polyline 0x00e5f9fa T _vdi_v_pline 0x00e5fa8e T _linea_line 0x00e5faf2 T _set_LN_MASK 0x00e5fb22 T _LINE_STYLE 0x00e5fb2e T _op_nodraw 0x00e5fb32 T _op_draw 0x00e5fb38 T _screen 0x00e5fb38 T vdi_main.o 0x00e5fe48 T vdi_marker.o 0x00e5fe48 T _vdi_vsm_height 0x00e5ff2c T _vdi_vsm_type 0x00e5ff66 T _vdi_vsm_color 0x00e5ff98 T _vdi_v_pmarker 0x00e600d6 T _vdi_vqm_attributes 0x00e601c0 T vdi_misc.o 0x00e6020a T _arb_corner 0x00e60234 T _arb_line 0x00e6025e T _vdi_vex_timv 0x00e60298 T _timer_init 0x00e602d4 T _timer_exit 0x00e602fc T _get_start_addr 0x00e60328 T vdi_mouse.o 0x00e6054c T _vdi_vq_mouse 0x00e6056e T _vdi_vex_butv 0x00e60586 T _vdi_vex_motv 0x00e6059e T _vdi_vex_curv 0x00e605b6 T _vdi_vex_wheelv 0x00e605ce T _vdimouse_init 0x00e606c6 T _vdimouse_exit 0x00e60730 T _cur_display 0x00e60a70 T _cur_replace 0x00e60b80 T _vdi_v_locator 0x00e60c38 T _vdi_v_hide_c 0x00e60c90 T _linea_show_mouse 0x00e60cae T _vdi_v_show_c 0x00e60cb0 T _linea_hide_mouse 0x00e60cb4 T _linea_transform_mouse 0x00e60cc2 T _vdi_vsc_form 0x00e60cc8 T vdi_raster.o 0x00e61310 T _vdi_vr_trnfm 0x00e6142c T _vdi_vro_cpyfm 0x00e61462 T _vdi_vrt_cpyfm 0x00e6149a T _linea_raster 0x00e614c4 T _linea_blit 0x00e61520 T vdi_text.o 0x00e61fac T _vdi_v_gtext 0x00e61fd0 T _text_init2 0x00e6201c T _text_init 0x00e6219a T _vdi_vst_height 0x00e6228c T _vdi_vst_point 0x00e623a8 T _vdi_vst_effects 0x00e623c8 T _vdi_vst_alignment 0x00e62406 T _vdi_vst_rotation 0x00e62444 T _vdi_vst_font 0x00e62556 T _vdi_vst_color 0x00e62588 T _vdi_vqt_attributes 0x00e625fa T _vdi_vqt_extent 0x00e626d8 T _vdi_vqt_width 0x00e62794 T _vdi_vqt_name 0x00e6280a T _vdi_vqt_fontinfo 0x00e628ba T _gdp_justified 0x00e62b14 T _vdi_vst_load_fonts 0x00e62bd8 T _vdi_vst_unload_fonts 0x00e62bf8 T vdi_textblit.o 0x00e62ca2 T _outline 0x00e62dac T _rotate 0x00e63016 T _scale 0x00e63232 T _direct_screen_blit 0x00e63428 T _text_blt 0x00e63bb8 T vdi_blit.o 0x00e63f00 T _fast_bit_blt 0x00e64304 T _normal_blit 0x00e64304 T vdi_tblit.o 0x00e64ab4 T gemasm.o 0x00e64ab4 T _psetup 0x00e64adc T _gotopgm 0x00e64b00 T _dsptch 0x00e64b4a T _switchto 0x00e64b78 T gemstart.o 0x00e64b78 T _ui_start 0x00e64c32 T _accdesk_start 0x00e64c76 T _dos_exec 0x00e64ce0 T _disable_interrupts 0x00e64ce0 T gemdosif.o 0x00e64cec T _enable_interrupts 0x00e64cf4 T _retake 0x00e64d12 T _giveerr 0x00e64d1a T _takeerr 0x00e64e34 T _unset_aestrap 0x00e64e3e T _set_aestrap 0x00e64e50 T _far_bcha 0x00e64e76 T _far_mcha 0x00e64ea4 T _aes_wheel 0x00e64ed2 T _drawrat 0x00e64ee2 T _tikcod 0x00e64f50 T _ap_init 0x00e64f50 T gemaplib.o 0x00e64f62 T _ap_rdwr 0x00e64fda T _ap_find 0x00e64ff8 T _ap_tplay 0x00e65180 T _ap_trecd 0x00e65254 T _ap_exit 0x00e652d0 T _azombie 0x00e652d0 T gemasync.o 0x00e6537e T _evinsert 0x00e653a6 T _mwait 0x00e653ee T _iasync 0x00e6551e T _apret 0x00e655a0 T _acancel 0x00e65644 T gemctrl.o 0x00e65722 T _ct_chgown 0x00e6574c T _ct_mouse 0x00e657ea T _ctlmgr 0x00e65f74 T _forkq 0x00e65f74 T gemdisp.o 0x00e65fda T _forker 0x00e660f4 T _chkkbd 0x00e6617c T _disp 0x00e66280 T gemevlib.o 0x00e662da T _ev_block 0x00e66302 T _ev_button 0x00e6633c T _ev_mesag 0x00e66398 T _ev_mouse 0x00e663b6 T _ev_timer 0x00e663d8 T _ev_multi 0x00e66718 T _ev_dclick 0x00e6675c T gemflag.o 0x00e6675c T _tchange 0x00e667ec T _tak_flag 0x00e66818 T _amutex 0x00e66856 T _unsync 0x00e6689c T gemfmalt.o 0x00e669aa T _fm_alert 0x00e66d50 T gemfmlib.o 0x00e66de4 T _fm_own 0x00e66e98 T _fm_keybd 0x00e66f52 T _fm_button 0x00e670ea T _fm_do 0x00e67292 T _fm_dial 0x00e67314 T _fm_show 0x00e6735e T _eralert 0x00e673d6 T _fm_error 0x00e6747c T gemfslib.o 0x00e6774e T _fs_start 0x00e6779c T _fs_input 0x00e684f0 T gemgraf.o 0x00e68634 T _gsx_sclip 0x00e686ac T _gsx_gclip 0x00e686c4 T _gsx_chkclip 0x00e68728 T _gsx_pline 0x00e6877c T _gsx_cline 0x00e687b8 T _gsx_attr 0x00e68862 T _gsx_xbox 0x00e68876 T _gsx_xcbox 0x00e6899e T _gsx_blt 0x00e68b22 T _bb_screen 0x00e68b52 T _gsx_trans 0x00e68bd2 T _gsx_start 0x00e68df4 T _bb_fill 0x00e68e8e T _gsx_tblt 0x00e68f9a T _gr_inside 0x00e68fb6 T _gr_rect 0x00e69034 T _gr_just 0x00e6910a T _gr_gtext 0x00e69160 T _gr_crack 0x00e691ae T _gr_gicon 0x00e694c4 T _gr_box 0x00e6956c T gemgrlib.o 0x00e696ee T _gr_stepcalc 0x00e697c4 T _gr_rubwind 0x00e6985c T _gr_rubbox 0x00e69884 T _gr_dragbox 0x00e69958 T _gr_2box 0x00e69a0a T _gr_movebox 0x00e69b08 T _gr_growbox 0x00e69b84 T _gr_shrinkbox 0x00e69c06 T _gr_watchbox 0x00e69cb8 T _gr_slidebox 0x00e69d5a T _gr_mouse 0x00e69e3a T _gr_mkstate 0x00e69e64 T gemgsxif.o 0x00e69e8c T _gsx_malloc 0x00e69f8a T _gsx_mfree 0x00e69f9a T _gsx_mret 0x00e69fb0 T _gsx_0code 0x00e6a002 T _gsx_1code 0x00e6a01c T _gsx_wsclose 0x00e6a028 T _gsx_wsclear 0x00e6a034 T _ratinit 0x00e6a048 T _gsx_graphic 0x00e6a0b4 T _gsx_tick 0x00e6a0d8 T _gsx_mxmy 0x00e6a0ee T _gsx_kstate 0x00e6a100 T _gsx_moff 0x00e6a12c T _ratexit 0x00e6a12e T _gsx_mon 0x00e6a150 T _gsx_mfset 0x00e6a1b6 T _gsx_char 0x00e6a20e T _gsx_setmousexy 0x00e6a254 T _gsx_nplanes 0x00e6a26a T _gsx_textsize 0x00e6a29e T _gsx_fix_screen 0x00e6a2ec T _gsx_fix 0x00e6a376 T _v_pline 0x00e6a39c T _vs_clip 0x00e6a3cc T _vst_height 0x00e6a414 T _vr_recfl 0x00e6a43a T _vro_cpyfm 0x00e6a6ea T _bb_restore 0x00e6a6f8 T _bb_save 0x00e6a708 T _vrt_cpyfm 0x00e6a758 T _vrn_trnfm 0x00e6a772 T _vsl_width 0x00e6a792 T _vex_wheelv 0x00e6a7b0 T _gsx_init 0x00e6a8d8 T geminit.o 0x00e6a956 T _size_theglo 0x00e6a95e T _init_p0_stkptr 0x00e6a970 T _default_mform 0x00e6a978 T _set_mouse_to_arrow 0x00e6a988 T _set_mouse_to_hourglass 0x00e6a998 T _all_run 0x00e6a9d0 T _wait_for_accs 0x00e6aa1e T _set_aes_background 0x00e6aa52 T _run_accs_and_desktop 0x00e6af6a T _gem_main 0x00e6b584 T geminput.o 0x00e6b584 T _in_mrect 0x00e6b5ae T _b_click 0x00e6b630 T _b_delay 0x00e6b6b8 T _set_ctrl 0x00e6b6d0 T _get_ctrl 0x00e6b6e8 T _get_mown 0x00e6b6f4 T _dq 0x00e6b726 T _fq 0x00e6b758 T _evremove 0x00e6b874 T _bchange 0x00e6ba14 T _set_mown 0x00e6ba74 T _post_keybd 0x00e6bac8 T _kchange 0x00e6baf2 T _downorup 0x00e6bb28 T _mchange 0x00e6bc34 T _wheel_change 0x00e6bc36 T _akbin 0x00e6bc9e T _adelay 0x00e6bd78 T _abutton 0x00e6bdf4 T _amouse 0x00e6bec8 T gemmnlib.o 0x00e6c120 T _do_chg 0x00e6c1ca T _mn_do 0x00e6c65c T _mn_bar 0x00e6c754 T _mn_clsda 0x00e6c794 T _mn_register 0x00e6c842 T _mn_unregister 0x00e6c8ca T _mn_init 0x00e6c8ea T _mn_getownid 0x00e6c920 T gemobed.o 0x00e6ca8c T _ob_center 0x00e6cb70 T _ins_char 0x00e6cc3c T _ob_edit 0x00e6d214 T gemobjop.o 0x00e6d214 T _ob_sst 0x00e6d31c T _everyobj 0x00e6d45a T _get_par 0x00e6d4c4 T gemoblib.o 0x00e6d4c4 T _ob_offset 0x00e6d54c T _ob_relxywh 0x00e6d584 T _ob_actxywh 0x00e6d5c6 T _ob_setxywh 0x00e6d5fe T _ob_format 0x00e6df54 T _ob_draw 0x00e6e00c T _ob_find 0x00e6e180 T _ob_add 0x00e6e210 T _ob_delete 0x00e6e2e2 T _ob_order 0x00e6e3ac T _ob_change 0x00e6e514 T _ob_fs 0x00e6e534 T gempd.o 0x00e6e534 T _pd_index 0x00e6e574 T _fpdnm 0x00e6e610 T _p_nameit 0x00e6e652 T _p_setappdir 0x00e6e694 T _pstart 0x00e6e774 T _insert_process 0x00e6e7a4 T gemqueue.o 0x00e6e8b8 T _aqueue 0x00e6e984 T gemrslib.o 0x00e6ed3c T _rs_obfix 0x00e6eda8 T _rs_free 0x00e6ee70 T _rs_gaddr 0x00e6eea2 T _rs_saddr 0x00e6eed6 T _rs_fixit 0x00e6efa0 T _rs_load 0x00e6f466 T _rs_str 0x00e6f486 T _xlate_obj_array 0x00e6f50c T gemsclib.o 0x00e6f50c T _sc_read 0x00e6f520 T _sc_write 0x00e6f53c T _sc_clear 0x00e6f5e4 T gemshlib.o 0x00e6f6e4 T _sh_read 0x00e6f716 T _sh_write 0x00e6f878 T _sh_get 0x00e6f894 T _sh_put 0x00e6f8b0 T _sh_tographic 0x00e6f8ec T _sh_name 0x00e6f912 T _sh_envrn 0x00e6f96e T _sh_find 0x00e6fb60 T _sh_rdef 0x00e6fb82 T _sh_wdef 0x00e6fba6 T _aes_run_rom_program 0x00e6fbd8 T _sh_main 0x00e70014 T gemsuper.o 0x00e70014 T _super 0x00e70df0 T gemwmlib.o 0x00e714ce T _w_nilit 0x00e71502 T _w_getsize 0x00e71552 T _w_drawdesk 0x00e7159a T _w_setactive 0x00e715ec T _w_bldactive 0x00e71b74 T _ap_sendmsg 0x00e71bc8 T _w_update 0x00e71d40 T _wm_init 0x00e71fdc T _wm_start 0x00e72014 T _wm_create 0x00e720e0 T _wm_delete 0x00e72158 T _wm_get 0x00e72332 T _wm_find 0x00e72352 T _wm_update 0x00e723a4 T _wm_calc 0x00e72974 T _wm_set 0x00e72d20 T _wm_close 0x00e72d34 T _wm_open 0x00e72d48 T _wm_new 0x00e72e48 T gemwrect.o 0x00e73042 T _or_start 0x00e73076 T _get_orect 0x00e73088 T _newrect 0x00e73148 T _gsx2 0x00e73148 T gsx2.o 0x00e73168 T _gem_rsc_init 0x00e73168 T gem_rsc.o 0x00e731ba T _gem_rsc_fixit 0x00e73614 T _rs_fstr 0x00e73650 T _rs_trees 0x00e7365c T _rs_bitblk 0x00e74038 T _mform_rs_data 0x00e74038 T mforms.o 0x00e742a8 T _deskstart 0x00e742a8 T deskstart.o 0x00e742e4 T deskmain.o 0x00e7503e T _hndl_msg 0x00e753ac T _centre_title 0x00e75400 T _install_shortcuts 0x00e75490 T _deskmain 0x00e7646e T _shortcut_mapping 0x00e7651c T gembind.o 0x00e76572 T _appl_init 0x00e765ba T _appl_exit 0x00e765c6 T _evnt_button 0x00e76618 T _evnt_timer 0x00e76632 T _evnt_multi 0x00e76732 T _evnt_dclick 0x00e7674c T _menu_bar 0x00e76766 T _menu_icheck 0x00e76788 T _menu_ienable 0x00e767aa T _menu_tnormal 0x00e767cc T _objc_add 0x00e767ee T _objc_draw 0x00e76830 T _objc_find 0x00e76862 T _objc_offset 0x00e76898 T _objc_order 0x00e768ba T _form_do 0x00e768d4 T _form_dial 0x00e76926 T _form_alert 0x00e76940 T _form_error 0x00e76952 T _form_center 0x00e76994 T _graf_rubbox 0x00e769da T _graf_growbox_grect 0x00e76a28 T _graf_shrinkbox_grect 0x00e76a76 T _graf_handle 0x00e76ab0 T _graf_mouse 0x00e76aca T _graf_mkstate 0x00e76afe T _fsel_exinput 0x00e76b32 T _wind_create_grect 0x00e76b66 T _wind_open_grect 0x00e76b9a T _wind_close 0x00e76bac T _wind_delete 0x00e76bbe T _wind_get 0x00e76c08 T _wind_get_grect 0x00e76c2c T _wind_set 0x00e76c68 T _wind_set_grect 0x00e76c8c T _wind_find 0x00e76ca6 T _wind_update 0x00e76cb8 T _wind_calc_grect 0x00e76d22 T _wind_new 0x00e76d2e T _rsrc_load 0x00e76d40 T _rsrc_free 0x00e76d4c T _rsrc_obfix 0x00e76d66 T _shel_write 0x00e76d98 T _shel_get 0x00e76db2 T _shel_put 0x00e76dcc T _shel_wdef 0x00e76de8 T deskact.o 0x00e77056 T _act_chg 0x00e7712c T _act_allchg 0x00e77308 T _act_bsclick 0x00e7743c T _act_bdown 0x00e77ccc T deskapp.o 0x00e77e3c T _nomem_alert 0x00e77e5e T _app_alloc 0x00e77e9a T _app_free 0x00e77f04 T _scan_str 0x00e77f92 T _app_tran 0x00e7803c T _app_start 0x00e78c74 T _app_save 0x00e79446 T _app_blddesk 0x00e796f6 T _app_afind_by_id 0x00e79724 T _app_afind_by_name 0x00e797fe T _app_afind_viewer 0x00e79828 T _app_read_inf 0x00e79abc T deskdir.o 0x00e79c24 T _draw_dial 0x00e79d2c T _draw_fld 0x00e79d90 T _add_path 0x00e79dce T _add_fname 0x00e79dec T _restore_path 0x00e79dfc T _del_fname 0x00e7a210 T _illegal_op_msg 0x00e7a27a T _d_doop 0x00e7a702 T _dir_op 0x00e7ad60 T deskfpd.o 0x00e7ad8a T _pn_close 0x00e7ad8c T _pn_open 0x00e7adec T _pn_sort 0x00e7b050 T _pn_active 0x00e7b224 T _pn_clear 0x00e7b240 T _pn_selected 0x00e7b260 T _pn_count 0x00e7b298 T deskfun.o 0x00e7b3d0 T _fun_alert 0x00e7b3ea T _fun_alert_merge 0x00e7b422 T _fun_msg 0x00e7b4d2 T _fun_mark_for_rebld 0x00e7b514 T _fun_rebld_marked 0x00e7b55c T _fun_rebld 0x00e7b5b6 T _add_one_level 0x00e7b82e T _fun_search 0x00e7bb0e T _fun_selectall 0x00e7bb9c T _fun_mask 0x00e7bc26 T _fun_mkdir 0x00e7bd6c T _fun_op 0x00e7bdee T _fun_close 0x00e7bfae T _wants_to_delete_files 0x00e7bfee T _fun_del 0x00e7c652 T _fun_drag 0x00e7ca14 T deskinf.o 0x00e7cea8 T _dr_code 0x00e7d23c T _start_dialog 0x00e7d2ac T _end_dialog 0x00e7d39c T _inf_show 0x00e7d3ca T _inf_numset 0x00e7d47c T _set_tedinfo_name 0x00e7d4ba T _inf_file_folder 0x00e7d842 T _inf_disk 0x00e7d95a T _inf_pref 0x00e7dba6 T _inf_backgrounds 0x00e7de34 T _inf_conf 0x00e7e3c6 T _opn_appl 0x00e7e434 T deskins.o 0x00e7e570 T _is_installed 0x00e7e5a0 T _is_viewer 0x00e7ec8a T _snap_icon 0x00e7f276 T _ins_devices 0x00e7f430 T _ins_app 0x00e7f4c2 T _is_executable 0x00e7f8f2 T _ins_icon 0x00e7fa16 T _rmv_icon 0x00e7fa84 T _ins_shortcut 0x00e7fc60 T deskobj.o 0x00e7fc60 T _obj_init 0x00e7fdec T _obj_walloc 0x00e7fe2e T _obj_wfree 0x00e7fea0 T _obj_ialloc 0x00e7ff14 T _obj_get_obid 0x00e7ff68 T deskpro.o 0x00e7ff68 T _pro_run 0x00e7fff0 T _change_resolution 0x00e7fff0 T deskrez.o 0x00e80224 T deskrsrc.o 0x00e80224 T _desktop_str_addr 0x00e80240 T desksupp.o 0x00e8032c T _deselect_all 0x00e80348 T _desk_clear 0x00e803e4 T _desk_clear_all 0x00e80422 T _desk_verify 0x00e804a6 T _do_wredraw 0x00e8059a T _do_xyfix 0x00e805ba T _do_wopen 0x00e806ba T _do_wfull 0x00e80754 T _remove_locate_shortcut 0x00e80856 T _build_root_path 0x00e80870 T _i_find 0x00e8094a T _set_default_path 0x00e8096e T _valid_drive 0x00e809cc T _malloc_fail_alert 0x00e809de T _desk_busy_on 0x00e809ee T _desk_busy_off 0x00e80a8a T _do_info 0x00e80c34 T _print_file 0x00e80dc0 T _do_aopen 0x00e814de T _do_diropen 0x00e815ec T _do_fopen 0x00e817aa T _refresh_window 0x00e81824 T _refresh_drive 0x00e8187e T _do_format 0x00e81f00 T _do_dopen 0x00e82024 T _do_open 0x00e8231c T deskwin.o 0x00e8231c T _win_view 0x00e8249e T _win_start 0x00e8250a T _win_free 0x00e8255c T _win_alloc 0x00e82644 T _win_find 0x00e8266e T _win_top 0x00e826b6 T _win_onbottom 0x00e826d8 T _win_ontop 0x00e82714 T _win_count 0x00e8271c T _win_bldview 0x00e82e12 T _win_dispfile 0x00e82eaa T _win_slide 0x00e82f50 T _win_arrow 0x00e8304e T _win_srtall 0x00e83092 T _win_bdall 0x00e830e6 T _win_shwall 0x00e8314c T _win_isel 0x00e831a0 T _win_sname 0x00e831d0 T _win_sinfo 0x00e83290 T desk_rsc.o 0x00e83290 T _desk_rs_init 0x00e83d12 T _desk_rs_fstr 0x00e83e0a T _desk_rs_bitblk 0x00e86dc0 T icons.o 0x00e86dc2 T _icon_rs_iconblk 0x00e87654 T cmdasm.o 0x00e87654 T _coma_start 0x00e87692 T _getwh 0x00e8769e T _getht 0x00e876ac T _jmp_gemdos 0x00e876d4 T _jmp_bios 0x00e876fc T _jmp_xbios 0x00e87724 T cmdmain.o 0x00e879f4 T _cmdmain 0x00e87d30 T _valid_res 0x00e87da4 T cmdedit.o 0x00e87efe T _insert_char 0x00e87f84 T _read_line 0x00e88526 T _init_screen 0x00e8855e T _init_cmdedit 0x00e885a6 T _save_history 0x00e8863c T cmdexec.o 0x00e88712 T _exec_program 0x00e889c4 T cmdint.o 0x00e8a588 T _lookup_builtin 0x00e8a688 T _get_path 0x00e8b1a4 T cmdparse.o 0x00e8b1f0 T _parse_line 0x00e8b340 T cmdutil.o 0x00e8b346 T _escape 0x00e8b37a T _message 0x00e8b3ae T _messagenl 0x00e8b4dc T _errmsg 0x00e8b77a T _strlower 0x00e8b7a4 T _strupper 0x00e8b7ce T _getword 0x00e8b860 T _decode_date_time 0x00e8b9bc T _get_path_component 0x00e8ba1e T _has_wildcard 0x00e8ba50 T _strequal 0x00e8baaa T _program_extension 0x00e8bb26 T _getcookie 0x00e8bb84 T endrom.o 0x00e8bb84 T _ui_mupb 0x00e8bb90 D __data 0x00e8bb90 D __edata 0x00e8bb90 D endrom.o 0x00e8bb90 T __etext hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/os-header.sym000066400000000000000000000025171504763705000270060ustar00rootroot00000000000000# example symbol/address file for OS Header # # The file contents are composed of a hexadecimal addresses followed # by space, address type, space and the corresponding symbol name. # Empty lines and lines which first char is '#' are ignored. # All TOS versions: # os_entry: BRA to reset handler (shadowed at $0). # os_version: TOS version number. The high byte is the major revision # number, and the low byte is the minor revision number. # reseth: Pointer to the system reset handler. # os_beg: Base address of the OS (same as _sysbase). # os_end: Address of the first byte of RAM not used by the operating system. # os_magic: Pointer to the GEM Memory Usage Parameter Block (MUPB). # os_date: Date of system build ($YYYYMMDD). # os_conf: OS Configuration Bits. # os_dosdate: GEMDOS format date of system build. # # TOS 1.2 or newer: # p_root: Pointer to a system variable containing the address # of the GEMDOS memory pool structure. # p_kbshift: Pointer to a system variable which contains the address # of the system keyboard shift state variable. # p_run: Pointer to a system variable which contains the address # of the currently executing GEMDOS process. # address, type & name of symbol 00 T os_entry 02 D os_version 04 D reseth 08 D os_beg 0C D os_end 14 D os_magic 18 D os_date 1C D os_conf 1E D os_dosdate 20 D p_root 24 D p_kbshift 28 D p_run hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/data/test.ini000066400000000000000000000000421504763705000260540ustar00rootroot00000000000000# test debugger input file disasm hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/test-breakcond.c000066400000000000000000000162531504763705000265470ustar00rootroot00000000000000/* * Code to test Hatari conditional breakpoints in src/debug/breakcond.c * (both matching and setting CPU and DSP breakpoints) */ #include "main.h" #include "dsp.h" #include "debugcpu.h" #include "breakcond.h" #include "stMemory.h" #include "newcpu.h" #define BITMASK(x) ((1<<(x))-1) /* BreakCond_Command() command strings */ #define CMD_LIST NULL #define CMD_REMOVE_ALL "all" static bool SetCpuRegister(const char *regname, uint32_t value) { uint32_t *addr; switch (DebugCpu_GetRegisterAddress(regname, &addr)) { case 32: *addr = value; break; case 16: *(uint16_t*)addr = value; break; default: fprintf(stderr, "SETUP ERROR: Register '%s' to set (to %x) is unrecognized!\n", regname, value); return false; } return true; } #if 0 static bool SetDspRegister(const char *regname, uint32_t value) { uint32_t *addr, mask; switch (DSP_GetRegisterAddress(regname, &addr, &mask)) { case 32: *addr = value & mask; break; case 16: *(uint16_t*)addr = value & mask; break; default: return false; } return true; } #endif int main(int argc, const char *argv[]) { const char *parser_fail[] = { /* syntax & register name errors */ "", " = ", " a0 d0 ", "gggg=a0", "=a=b=", "a0=d0=20", "a0=d || 0=20", "a0=d & 0=20", ".w&3=2", "d0 = %200", "d0 = \"ICE!BAR", "pc > $200 :foobar", "foo().w=bar()", "(a0.w=d0.l)", "(a0&3)=20", "20 = (a0.w)", "()&=d0", "d0=().w", "&& pc = 2", "pc = 2 &&", "255 & 3 = (d0) & && 2 = 2", /* missing options file */ "pc>pc :file no-such-file", "VdiOpcode = $8 :info no-info", /* size and mask mismatches with numbers */ "d0.w = $ffff0", "(a0).b & 3 < 100", NULL }; const char *parser_pass[] = { /* comparisons with normal numbers + indirect addressing */ " ($200).w > 200 ", " ($200).w < 200 ", " (200).w = $200 ", " (200).w ! $200 ", /* indirect addressing with registers */ "(a0)=(d0)", "(d0).w=(a0).b", /* sizes + multiple conditions + spacing */ "(a0).w&3=(d0)&&d0=1", " ( a 0 ) . w & 1 = ( d 0 ) & 1 && d 0 = 3 ", "a0=1 && (d0)&2=(a0).w && ($00ff00).w&1=1", " ($ff820a).b = 2", /* variables */ "hbl > 0 && vbl < 2000 && linecycles = 508", /* options */ "($200).w ! ($200).w :trace", "($200).w > ($200).w :4 :lock", "VdiOpcode = $8 :quiet :info vdi", "pc>pc :file data/test.ini :once", NULL }; /* address breakpoint + expression evaluation with register */ char addr_pass[] = "pc + ($200*16/2 & 0xffff)"; const char *nonmatching_tests[] = { "( $200 ) . b > 200", /* byte access to avoid endianness */ "pc < $50000 && pc > $60000", "pc > $50000 && pc < $54000", "d0 = a0", "a0 = pc :trace", /* matches, but :trace should hide that */ "a0 = pc :3", /* matches, but not yet */ NULL }; const char *matching_tests[] = { "a0 = pc", /* tested with all above */ "( $200 ) . b > ( 200 ) . b :once", "pc > $50000 && pc < $60000", "d0 = d1 :once :quiet", "a0 = pc", /* tested alone */ NULL }; const char *test; int total_tests = 0, total_errors = 0; int i, errors; bool use_dsp; /* first automated tests... */ use_dsp = false; fprintf(stderr, "\nShould FAIL for CPU:\n"); for (i = 0; (test = parser_fail[i]); i++) { fprintf(stderr, "-----------------\n- parsing '%s'\n", test); if (BreakCond_Command(test, use_dsp)) { fprintf(stderr, "***ERROR***: should have failed\n"); total_errors++; } } total_tests += i; fprintf(stderr, "-----------------\n\n"); BreakCond_Command(CMD_LIST, use_dsp); fprintf(stderr, "\nShould PASS for CPU:\n"); for (i = 0; (test = parser_pass[i]); i++) { fprintf(stderr, "-----------------\n- parsing '%s'\n", test); if (!BreakCond_Command(test, use_dsp)) { fprintf(stderr, "***ERROR***: should have passed\n"); total_errors++; } } total_tests += i; fprintf(stderr, "\nAddress PASS test for CPU:\n"); if (!BreakAddr_Command(addr_pass, use_dsp)) { fprintf(stderr, "***ERROR***: should have passed\n"); total_errors++; } total_tests += 1; fprintf(stderr, "-----------------\n\n"); BreakCond_Command(CMD_LIST, use_dsp); fprintf(stderr, "\n"); BreakCond_Command(CMD_REMOVE_ALL, use_dsp); BreakCond_Command(CMD_LIST, use_dsp); fprintf(stderr, "-----------------\n"); /* set up registers etc */ /* fail indirect equality checks with zeroed regs */ memset(STRam, 0, STRamEnd); STMemory_WriteByte(0, 1); /* !match: "( $200 ) . b > 200" * match: "( $200 ) . b > ( 200 ) . b" */ STMemory_WriteByte(0x200, 100); STMemory_WriteByte(200, 0x20); /* !match: "pc < $50000 && pc > $60000" * !match: "pc < $50000 && pc > $54000" * match: "pc > $50000 && pc < $60000" */ regs.pc = 0x58000; /* match: "d0 = d1" */ SetCpuRegister("d0", 4); SetCpuRegister("d1", 4); /* !match: "d0 = a0" * match: "pc = a0" */ SetCpuRegister("a0", 0x58000); /* add conditions */ fprintf(stderr, "\nBreakpoints that should NOT match:\n"); for (errors = i = 0; (test = nonmatching_tests[i]); i++) { fprintf(stderr, "-----------------\n- parsing '%s'\n", test); if (!BreakCond_Command(test, use_dsp)) { fprintf(stderr, "***ERROR***: should have passed\n"); total_errors++; } else { /* does it match? */ if (BreakCond_MatchCpu()) { fprintf(stderr, "***ERROR***: should NOT have matched\n"); errors++; /* remove */ BreakCond_Command("1", use_dsp); } } } fprintf(stderr, "-----------------\n\n"); BreakCond_Command(CMD_LIST, use_dsp); if (errors) { total_errors += errors; fprintf(stderr, "\nERROR: %d out of %d breakpoints matched!\n", errors, i); } total_tests += i; /* leave non-matching breakpoints, so that first matching * breakpoint is at after those, and test rest of matching * breakpoints as single breakpoints */ /* add conditions */ fprintf(stderr, "\nBreakpoints that should match:\n"); for (errors = i = 0; (test = matching_tests[i]); i++) { fprintf(stderr, "-----------------\n- parsing '%s'\n", test); if (!BreakCond_Command(test, use_dsp)) { fprintf(stderr, "***ERROR***: should have passed\n"); total_errors++; } else { /* does it match? */ if (!BreakCond_MatchCpu()) { fprintf(stderr, "***ERROR***: should have matched\n"); errors++; } /* remove all */ BreakCond_Command(CMD_REMOVE_ALL, use_dsp); } } fprintf(stderr, "-----------------\n\n"); if (errors) { total_errors += errors; fprintf(stderr, "ERROR: %d out of %d breakpoints didn't match!\n\n", errors, i); } total_tests += i; /* ...last parse cmd line args as DSP breakpoints */ if (argc > 1) { use_dsp = true; fprintf(stderr, "\nCommand line DSP breakpoints:\n"); for (argv++; --argc > 0; argv++) { fprintf(stderr, "-----------------\n- parsing '%s'\n", *argv); BreakCond_Command(*argv, use_dsp); } fprintf(stderr, "-----------------\n\n"); BreakCond_Command("", use_dsp); /* list */ if (BreakCond_MatchDsp()) { fprintf(stderr, "There were matching DSP breakpoint(s).\n"); } BreakCond_Command(CMD_REMOVE_ALL, use_dsp); BreakCond_Command(CMD_LIST, use_dsp); fprintf(stderr, "-----------------\n"); } if (total_errors) { fprintf(stderr, "\n***Detected %d ERRORs in %d automated tests!***\n\n", total_errors, total_tests); } else { fprintf(stderr, "\nFinished without any errors!\n\n"); } return total_errors; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/test-dummies.c000066400000000000000000000137211504763705000262570ustar00rootroot00000000000000/* * Dummy stuff needed to compile debugger related test code */ #include #include "config.h" #if HAVE_LIBREADLINE /* fake readline function */ #include char *rl_filename_completion_function(const char *text, int state) { return NULL; } #endif /* fake tracing flags */ #include "log.h" uint64_t LogTraceFlags = 0; FILE *TraceFile; void Log_Trace(const char *format, ...) { } void Log_ResetMsgRepeat(void) {} /* fake Hatari configuration variables for number parsing */ #include "configuration.h" CNF_PARAMS ConfigureParams; /* fake hatari-glue.c */ #include "hatari-glue.h" struct uae_prefs currprefs; /* fake options.c */ #include "options.h" bool Opt_IsAtariProgram(const char *path) { return false; } /* fake cycles stuff */ #include "cycles.h" uint64_t CyclesGlobalClockCounter; int Cycles_GetCounter(int nId) { return 0; } /* bring in gemdos defines (EMULATEDDRIVES) */ #include "gemdos.h" /* fake TOS variables */ uint32_t TosAddress = 0xe00000, TosSize = 256*1024; /* fake ST RAM, only 24-bit support */ #include "stMemory.h" uae_u8 *TTmemory = NULL; static uint8_t _STRam[16*1024*1024]; uint8_t *STRam = _STRam; uint32_t STRamEnd = 4*1024*1024; uint32_t STMemory_ReadLong(uint32_t addr) { uint32_t val; if (addr >= STRamEnd) return 0; val = (STRam[addr] << 24) | (STRam[addr+1] << 16) | (STRam[addr+2] << 8) | STRam[addr+3]; return val; } uint16_t STMemory_ReadWord(uint32_t addr) { uint16_t val; if (addr >= STRamEnd) return 0; val = (STRam[addr] << 8) | STRam[addr+1]; return val; } uint8_t STMemory_ReadByte(uint32_t addr) { if (addr >= STRamEnd) return 0; return STRam[addr]; } void STMemory_WriteByte(uint32_t addr, uint8_t val) { if (addr < STRamEnd) STRam[addr] = val; } void STMemory_WriteWord(uint32_t addr, uint16_t val) { if (addr < STRamEnd) { STRam[addr+0] = val >> 8; STRam[addr+1] = val & 0xff; } } void STMemory_WriteLong(uint32_t addr, uint32_t val) { if (addr < STRamEnd) { STRam[addr+0] = val >> 24; STRam[addr+1] = val >> 16 & 0xff; STRam[addr+2] = val >> 8 & 0xff; STRam[addr+3] = val & 0xff; } } bool STMemory_CheckAreaType(uint32_t addr, int size, int mem_type ) { if ((addr > STRamEnd && addr < 0xe00000) || (addr >= 0xff0000 && addr < 0xff8000)) { return false; } return true; } /* fake CPU wrapper stuff */ #include "m68000.h" uint16_t M68000_GetSR(void) { return 0x2700; } void M68000_SetSR(uint16_t v) { } void M68000_SetPC(uaecptr v) { } void M68000_SetDebugger(bool debug) { } /* fake UAE core registers */ #include "newcpu.h" struct regstruct regs; void m68k_dumpstate_file (FILE *f, uaecptr *nextpc, uaecptr prevpc) { } /* fake debugui.c stuff */ #include "debug_priv.h" #include "debugui.h" FILE *debugOutput; void DebugUI(debug_reason_t reason) { } void DebugUI_PrintBinary(FILE *fp, int minwidth, uint32_t value) { } int DebugUI_PrintCmdHelp(const char *psCmd) { return DEBUGGER_CMDDONE; } int DebugUI_GetPageLines(int config, int defvalue) { return 25; } bool DebugUI_DoQuitQuery(const char *info) { return false; } char *DebugUI_MatchHelper(const char **strings, int items, const char *text, int state) { return NULL; } /* fake vdi.c stuff */ #include "vdi.h" void VDI_Info(FILE *fp, uint32_t arg) { return; } /* fake debugInfo.c stuff */ #include "debugInfo.h" void DebugInfo_ShowSessionInfo(void) {} uint32_t DebugInfo_GetBASEPAGE(void) { return 0x1f34; } uint32_t DebugInfo_GetTEXT(void) { return 0x1234; } uint32_t DebugInfo_GetTEXTEnd(void) { return 0x1234; } uint32_t DebugInfo_GetDATA(void) { return 0x12f4; } uint32_t DebugInfo_GetBSS(void) { return 0x1f34; } info_func_t DebugInfo_GetInfoFunc(const char *name) { if (strcmp(name, "vdi") == 0) { return VDI_Info; } return NULL; } /* fake debugdsp.c stuff */ #ifdef ENABLE_DSP_EMU #include "debugdsp.h" void DebugDsp_InitSession(void) { } uint32_t DebugDsp_CallDepth(void) { return 0; } uint32_t DebugDsp_InstrCount(void) { return 0; } uint32_t DebugDsp_OpcodeType(void) { return 0; } #endif /* use fake dsp.c stuff in case config.h is configured with DSP emu */ #include "dsp.h" bool bDspEnabled; uint16_t DSP_DisasmAddress(FILE *f, uint16_t lowerAdr, uint16_t UpperAdr) { return 0; } uint16_t DSP_GetInstrCycles(void) { return 0; } uint16_t DSP_GetPC(void) { return 0; } int DSP_GetRegisterAddress(const char *arg, uint32_t **addr, uint32_t *mask) { *addr = NULL; /* find if this gets used */ *mask = 0; return 0; } uint32_t DSP_ReadMemory(uint16_t addr, char space, const char **mem_str) { *mem_str = NULL; /* find if this gets used */ return 0; } /* fake console redirection */ #include "console.h" int ConOutDevices; void Console_Check(void) { } /* fake profiler stuff */ #include "profile.h" const char Profile_Description[] = ""; int Profile_Command(int nArgc, char *psArgs[], bool bForDsp) { return DEBUGGER_CMDDONE; } char *Profile_Match(const char *text, int state) { return NULL; } bool Profile_CpuStart(void) { return false; } void Profile_CpuUpdate(void) { } void Profile_CpuStop(void) { } /* fake Hatari video variables */ #include "video.h" int nHBL = 20; int nVBLs = 71; /* fake video variables accessor */ void Video_GetPosition(int *pFrameCycles, int *pHBL, int *pLineCycles) { *pFrameCycles = 2048; *pHBL = nHBL; *pFrameCycles = 508; } /* only function needed from file.c */ #include #include "file.h" bool File_Exists(const char *filename) { struct stat buf; if (stat(filename, &buf) == 0 && (buf.st_mode & (S_IRUSR|S_IWUSR)) && !(buf.st_mode & S_IFDIR)) { /* file points to user readable regular file */ return true; } return false; } /* fake debugger file parsing */ #include "debugui.h" bool DebugUI_ParseFile(const char *path, bool reinit, bool verbose) { return File_Exists(path); } /* fake disassembly output */ #include "68kDisass.h" uint32_t Disasm_GetNextPC(uint32_t pc) { return pc+2; } void Disasm (FILE *f, uaecptr addr, uaecptr *nextpc, int count) {} void Disasm_GetColumns(int *columns) {} void Disasm_SetColumns(int *columns) {} void Disasm_DisableColumn(int column, const int *oldcols, int *newcols) {} hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/test-evaluate.c000066400000000000000000000045121504763705000264200ustar00rootroot00000000000000/* * Code to test Hatari expression evaluation in src/debug/evaluate.c * (including Hatari variable and CPU register values in expressions) */ #include #include #include "stMemory.h" #include "evaluate.h" #include "m68000.h" #include "configuration.h" #include "video.h" #define VBL_VALUE 21 int main(int argc, const char *argv[]) { /* expected to fail */ const char *failure[] = { "1+2*", "*1+2", "1+(2", "1)+2", "foo+1+bar", }; /* expected to succeed, with given result */ struct { const char *expression; uint32_t result; } success[] = { { "1+2*3", 7 }, { "(2+5)*3", 9 }, { "d0 + 2", 12 }, { "VBL+10", VBL_VALUE + 10 }, { "~%101 & $f0f0f ^ 0x21 * 0x200", 0xF4D0A }, }; int i, offset, tests = 0, errors = 0; const char *expression, *errstr; uint32_t result; /* set values needed by above successful calculations */ nVBLs = VBL_VALUE; memset(Regs, 0, sizeof(Regs)); Regs[REG_D0] = 10; memset(STRam, 0, STRamEnd); /* expressions use long access, 3*3 -> 9 */ STMemory_WriteLong(2+5, 3); fprintf(stderr, "\nExpressions that should FAIL:\n"); for (i = 0; i < ARRAY_SIZE(failure); i++) { expression = failure[i]; fprintf(stderr, "- '%s'\n", expression); errstr = Eval_Expression(expression, &result, &offset, false); if (errstr) { fprintf(stderr, "%*c-%s\n", 3+offset, '^', errstr); } else { fprintf(stderr, " => %x\n ***Unexpected SUCCESS from expression***\n", (uint32_t)result); errors++; } } tests += i; fprintf(stderr, "\nExpressions that should SUCCEED with given result:\n"); for (i = 0; i < ARRAY_SIZE(success); i++) { expression = success[i].expression; fprintf(stderr, "- '%s'\n", expression); errstr = Eval_Expression(expression, &result, &offset, false); if (errstr) { fprintf(stderr, "%*c-%s\n ***Unexpected ERROR in expression***\n", 3+offset, '^', errstr); errors++; } else if (result != success[i].result) { fprintf(stderr, " => %x (not %x)\n ***Wrong result from expression***\n", (uint32_t)result, (uint32_t)success[i].result); errors++; } else { fprintf(stderr, " => 0x%x\n", (uint32_t)result); } } tests += i; if (errors) { fprintf(stderr, "\n***Detected %d ERRORs in %d automated tests!***\n\n", errors, tests); } else { fprintf(stderr, "\nFinished without any errors!\n\n"); } return errors; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/test-scripting.sh000077500000000000000000000022131504763705000270030ustar00rootroot00000000000000#!/bin/sh # # script to test Hatari debugger and console script features if [ $# -ne 1 ]; then echo "usage: ${0##*/} " exit 1 fi etos=$1 if [ ! -f "$etos" ]; then echo "ERROR: given EmuTOS image file '$etos' doesn't exist!" exit 1 fi hpath=../../build/src if [ ! -d $hpath ]; then echo "ERROR: Hatari source directory '$hpath' missing!" exit 1 fi hatari=$hpath/hatari if [ ! -x $hatari ]; then echo "ERROR: Hatari binary '$hatari' missing!" exit 1 fi console=../../tools/hconsole/hconsole.py if [ ! -x $console ]; then echo "ERROR: Hatari console script '$console' missing!" exit 1 fi # Enable extra GCC/LLVM AddressSanitizer options in case Hatari's compiled with it: # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags export ASAN_OPTIONS="detect_stack_use_after_return=1,abort_on_error=1,debug=1" echo "TESTING: debugger input file" echo "============================" cmd="$hatari --sound off --machine falcon --tos $etos --dsp emu --parse data/debugger.ini" echo "$cmd" $cmd echo echo "TESTING: console input file" echo "===========================" PATH=$hpath:$PATH $console data/console.ini --exit -- hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/debugger/test-symbols.c000066400000000000000000000051551504763705000263060ustar00rootroot00000000000000/* * Code to test Hatari symbol/address (re-)loading in src/debug/symbols.c */ #include #include #include #include "debug_priv.h" #include "symbols.h" #include "main.h" #include "log.h" int main(int argc, const char *argv[]) { /* expected to fail */ const char *fail_name[] = { "afoo", "zbar", }; uint32_t fail_addr[] = { 0x10, 0x30, }; /* expected to succeed */ const char *success_name[] = { "os_magic", "p_root" }; uint32_t success_addr[] = { 0x14, 0x28, }; #define DO_CMD(cmd) Symbols_Command(ARRAY_SIZE(cmd), cmd) char symbols[] = "symbols"; char fname[] = "data/os-header.sym"; char sname[] = "name"; char scode[] = "code"; char sdata[] = "data"; char sfree[] = "free"; char *cmd_load[] = { symbols, fname }; char *cmd_free[] = { symbols, sfree }; char *cmd_show_byname[] = { symbols, sname }; char *cmd_show_bycode[] = { symbols, scode }; char *cmd_show_bydata[] = { symbols, sdata }; int i, tests = 0, errors = 0; const char *name; uint32_t addr; DO_CMD(cmd_load); DO_CMD(cmd_show_bycode); DO_CMD(cmd_show_bydata); DO_CMD(cmd_show_byname); DO_CMD(cmd_load); /* free + reload */ fprintf(stderr, "\nStuff that should FAIL:\n"); for (i = 0; i < ARRAY_SIZE(fail_name); i++) { name = fail_name[i]; if (Symbols_GetCpuAddress(SYMTYPE_ALL, name, &addr)) { fprintf(stderr, "*** Unexpected SUCCESS from '%s' (0x%08x) ***\n", name, addr); errors++; } else { fprintf(stderr, "- '%s'\n", name); } } tests += i; for (i = 0; i < ARRAY_SIZE(fail_addr); i++) { addr = fail_addr[i]; name = Symbols_GetByCpuAddress(addr, SYMTYPE_ALL); if (name) { fprintf(stderr, "*** Unexpected SUCCESS from 0x%08x (%s) ***\n", addr, name); errors++; } else { fprintf(stderr, "- 0x%08x\n", addr); } } tests += i; fprintf(stderr, "\nStuff that should SUCCEED:\n"); for (i = 0; i < ARRAY_SIZE(success_name); i++) { name = success_name[i]; if (Symbols_GetCpuAddress(SYMTYPE_ALL, name, &addr)) { fprintf(stderr, "- '%s'\n", name); } else { fprintf(stderr, "*** Unexpected FAIL from '%s' ***\n", name); errors++; } } tests += i; for (i = 0; i < ARRAY_SIZE(success_addr); i++) { addr = success_addr[i]; name = Symbols_GetByCpuAddress(addr, SYMTYPE_ALL); if (name) { fprintf(stderr, "- 0x%08x: %s\n", addr, name); } else { fprintf(stderr, "*** Unexpected FAIL from 0x%08x ***\n", addr); errors++; } } tests += i; DO_CMD(cmd_free); if (errors) { fprintf(stderr, "\n***Detected %d ERRORs in %d automated tests!***\n\n", errors, tests); } else { fprintf(stderr, "\nFinished without any errors!\n\n"); } return errors; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/000077500000000000000000000000001504763705000231615ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/CMakeLists.txt000066400000000000000000000003041504763705000257160ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) add_test(NAME gemdos COMMAND ${testrunner} $ ${CMAKE_CURRENT_SOURCE_DIR}/gmdostst.tos) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/get_sr.s000066400000000000000000000000511504763705000246240ustar00rootroot00000000000000 .global get_sr get_sr: move sr,d0 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/gmdostst.c000066400000000000000000000110561504763705000251740ustar00rootroot00000000000000/* * GEMDOS tests * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. */ #include #include extern unsigned short get_sr(void); static char buf1[512]; static char buf2[512]; static char buf3[512]; struct test { char *name; int (*testfunc)(void); }; static int tst_directories(void) { int ret; ret = Dgetpath(buf1, 0); if (ret) { Cconws("Dgetpath(buf1)"); return ret; } ret = strlen(buf1); if (ret > 0 && buf1[ret - 1] == '\\') { Cconws("buffer must not end with backslash"); return -1; } ret = Dcreate("TESTDIR"); if (ret) { Cconws("Dcreate(\"TESTDIR\")"); return ret; } ret = Dcreate("TESTDIR"); if (ret != -36) { Cconws("Dcreate(\"TESTDIR\" again)"); return -1; } ret = Dsetpath("TESTDIR\\"); if (ret) { Cconws("Dsetpath(\"TESTDIR\")"); return ret; } ret = Dgetpath(buf2, 0); if (ret) { Cconws("Dgetpath(buf2)"); return ret; } if (!strcmp(buf1, buf2)) { Cconws("buf1 vs. buf2"); return -1; } ret = Dsetpath("INVLDDIR"); if (ret != -34) { Cconws("Dsetpath(\"INVLDDIR\")"); return -1; } /* Empty string with Dsetpath should succeed but not change anything */ ret = Dsetpath(""); if (ret) { Cconws("Dsetpath(\"\")"); return ret; } ret = Dgetpath(buf3, 0); if (ret) { Cconws("Dgetpath(buf3)"); return ret; } if (strcmp(buf2, buf3)) { Cconws("path immutability"); return -1; } ret = Dsetpath(".."); if (ret) { Cconws("Dsetpath(\"..\")"); return ret; } ret = Dgetpath(buf3, 0); if (ret) { Cconws("Dgetpath(buf3)"); return ret; } if (strcmp(buf1, buf3)) { Cconws("buf1 vs. buf3"); return -1; } ret = Ddelete("TESTDIR"); if (ret) { Cconws("Ddelete(\"TESTDIR\")"); return ret; } ret = Ddelete("TESTDIR"); if (ret != -34) { Cconws("Ddelete(invalid directory)"); return -1; } /* After switching to the root dir, we should get an empty path */ ret = Dsetpath("\\"); if (ret) { Cconws("Dsetpath(\"\\\")"); return ret; } ret = Dgetpath(buf3, 0); if (ret || buf3[0] != '\0') { Cconws("empty Dgetpath()"); return -1; } ret = Dsetpath(buf1); if (ret) { Cconws("Dsetpath(buf1)"); return ret; } return 0; } static int tst_files(void) { int fh; long ret; const char testdata[] = "Hello World!\n1234567890"; fh = Fcreate("TESTFILE.DAT", 0); if (fh < 0) { Cconws("Fcreate(\"TESTFILE.DAT\")"); return fh; } ret = Fwrite(fh, sizeof(testdata), testdata); if (ret != sizeof(testdata)) { Cconws("Fwrite"); return -1; } ret = Fclose(fh); if (ret) { Cconws("Fclose on created file"); return ret; } fh = Fopen("TESTFILE.DAT", 0); if (fh < 0) { Cconws("Fopen(\"TESTFILE.DAT\")"); return fh; } ret = Fread(fh, sizeof(testdata), buf1); if (ret != sizeof(testdata) || memcmp(testdata, buf1, sizeof(testdata))) { Cconws("Fread"); return -1; } ret = Fread(fh, sizeof(testdata), buf2); if (ret) { Cconws("Fread EOF"); return -1; } ret = Fclose(fh); if (ret) { Cconws("Fclose on read-only file"); return ret; } ret = Fdelete("TESTFILE.DAT"); if (ret) { Cconws("Fdelete(\"TESTFILE.DAT\")"); return ret; } ret = Fdelete("TESTFILE.DAT"); if (ret == 0) { Cconws("Fdelete(\"TESTFILE.DAT\") again"); return -1; } return 0; } static int tst_sys(void) { long old_ssp, ret; /* Initially we should be in user mode */ ret = Super(1); if (ret) { Cconws("user mode by default"); return -1; } old_ssp = Super(0); if (old_ssp <= 0) { Cconws("switch to supervisor mode"); return -1; } ret = Super(1); if (ret != -1 || !(get_sr() & 0x2000)) { Cconws("supervisor mode"); return -1; } Super(old_ssp); /* Back to user mode - no valid return value here */ ret = Super(1); if (ret) { Cconws("user mode"); return -1; } return 0; } struct test tests[] = { { "paths", tst_directories }, { "files", tst_files }, { "sys", tst_sys }, { 0L, 0L } }; int main() { int failures = 0; int idx; for (idx = 0; tests[idx].name != 0L; idx++) { Cconws("Test '"); Cconws(tests[idx].name); Cconws("'\t: "); if (tests[idx].testfunc() != 0) { Cconws(" FAILED\r\n"); failures++; } else { Cconws(" OK\r\n"); } } // Crawcin(); return !(failures == 0); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/gmdostst.prj000066400000000000000000000001621504763705000255410ustar00rootroot00000000000000; AHCC project file for GEMDOS tester gmdostst.tos .C [-iinclude] = ../startup.s gmdostst.c get_sr.s ahccstdi.lib hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/gmdostst.tos000066400000000000000000000045641504763705000255650ustar00rootroot00000000000000` *o - ЭЭ "ҍ.A// Bg?<JNAO NNAXH&gHk,?< NA\0`BgHkC?<=NAP8lHkP?< NA\0`HT/<??<?NAO fA"LpNJ@gHkf?< NA\p`JHl/<??<?NAO &gHkl?< NA\p`??<>NAXH&gHkv?< NA\0`Hk?<ANA\H&gHk?< NA\0`Hk?<ANA\H&fHk?< NA\p`B@`H0G/<?< NA\&gHS?< NA\pL NuB?< NA\(nHk?< NA\p`/<?< NA\f N| fHk/?< NA\p`/?< NA\/<?< NA\&gHk??< NA\p`~B@`xH8I GBDBC0HJgXHS?< NA\0H/4?< NA\Hk?< NA\0H tNJ@gHk ?< NA\RDRC`Hk?< NA\`JDV|LNu@Nu/ &IfJfB@&_NuRR`H`BJgR` NuH0"$H&ISg fRR`HL NuDgetpath(buf1)buffer must not end with backslashTESTDIRDcreate("TESTDIR")TESTDIRDcreate("TESTDIR" again)TESTDIR\Dsetpath("TESTDIR")Dgetpath(buf2)buf1 vs. buf2INVLDDIRDsetpath("INVLDDIR")Dsetpath("")Dgetpath(buf3)path immutability..Dsetpath("..")Dgetpath(buf3)buf1 vs. buf3TESTDIRDdelete("TESTDIR")TESTDIRDdelete(invalid directory)\Dsetpath("\")empty Dgetpath()Dsetpath(buf1)TESTFILE.DATFcreate("TESTFILE.DAT")FwriteFclose on created fileTESTFILE.DATFopen("TESTFILE.DAT")FreadFread EOFFclose on read-only fileTESTFILE.DATFdelete("TESTFILE.DAT")TESTFILE.DATFdelete("TESTFILE.DAT") againuser mode by defaultswitch to supervisor modesupervisor modeuser modepathsfilessysTest '' : FAILED OK Hello World! 1234567890@4*h ȼ^Thatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/gemdos/run_test.sh000077500000000000000000000020461504763705000253650ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 ..." exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; testprg=$1 shift if [ ! -f "$testprg" ]; then echo "Second parameter must point to valid test PRG." exit 1; fi; testdir=$(mktemp -d) remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT export HATARI_TEST=gemdos export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy HOME="$testdir" $hatari --log-level error --sound off --fast-forward on \ --tos none --run-vbls 500 "$@" "$testprg" > "$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari failed. Status=${exitstat}." cat "$testdir/out.txt" exit 1 fi # Now check for failure strings: if grep -qi fail "$testdir/out.txt"; then echo "Test FAILED:" cat "$testdir/out.txt" exit 1 fi if grep -qi error "$testdir/out.txt"; then echo "Test ERROR:" cat "$testdir/out.txt" exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/000077500000000000000000000000001504763705000231715ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/checkkeys.c000066400000000000000000000062051504763705000253110ustar00rootroot00000000000000/* * Simple program: Loop, watching keystrokes * * Note that you need to call SDL_PollEvent() or SDL_WaitEvent() to * pump the event loop and catch keystrokes. */ #include #include #include #include "SDL.h" /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) { SDL_Quit(); exit(rc); } static void print_modifiers(void) { int mod; printf(" -"); mod = SDL_GetModState(); if(!mod) { printf(" (none)"); return; } if(mod & KMOD_LSHIFT) printf(" LSHIFT"); if(mod & KMOD_RSHIFT) printf(" RSHIFT"); if(mod & KMOD_LCTRL) printf(" LCTRL"); if(mod & KMOD_RCTRL) printf(" RCTRL"); if(mod & KMOD_LALT) printf(" LALT"); if(mod & KMOD_RALT) printf(" RALT"); if(mod & KMOD_LGUI) printf(" LGUI"); if(mod & KMOD_RGUI) printf(" RGUI"); if(mod & KMOD_NUM) printf(" NUMLOCK"); if(mod & KMOD_CAPS) printf(" CAPS"); if(mod & KMOD_MODE) printf(" MODE"); } static void PrintKey(SDL_Keysym *sym, int pressed) { /* Print the keycode, scancode, their names names and state */ if ( sym->sym ) { printf("Key %s: 0x%2x/0x%2x (%d/%d) - %s - %s", pressed ? "pressed " : "released", sym->sym, sym->scancode, sym->sym, sym->scancode, SDL_GetKeyName(sym->sym), SDL_GetScancodeName(sym->scancode)); } else { printf("Unknown Key, scancode = 0x%2x (%d) - %s", sym->scancode, sym->scancode, pressed ? "pressed" : "released"); } print_modifiers(); printf("\n"); } int main(int argc, char *argv[]) { SDL_Window *window; Uint32 videoflags = 0; SDL_Event event; int wd = 640; int ht = 480; int done; /* Initialize SDL */ if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); return(1); } while( argc > 1 ) { --argc; if ( argv[argc] && !strcmp(argv[argc], "-fullscreen") ) { videoflags = SDL_WINDOW_FULLSCREEN; } else { fprintf(stderr, "Usage: %s [-fullscreen]\n", argv[0]); quit(1); } } if (videoflags & SDL_WINDOW_FULLSCREEN) { SDL_DisplayMode dm; videoflags |= SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_BORDERLESS | SDL_WINDOW_INPUT_GRABBED; if (SDL_GetDesktopDisplayMode(0, &dm)) { fprintf(stderr, "SDL_GetDesktopDisplayMode failed: %s", SDL_GetError()); quit(1); } wd = dm.w; ht = dm.h; } else { videoflags = SDL_WINDOW_RESIZABLE; } window = SDL_CreateWindow("CheckKeys", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, wd, ht, videoflags); if (!window) { fprintf(stderr, "Failed to create %dx%d window: %s\n", wd, ht, SDL_GetError()); quit(2); } puts("Click to the window to quit.\n"); /* Watch keystrokes */ done = 0; puts("Status: hex sym/scan (dec) - sym - scan - modifiers\n"); while ( !done ) { /* Check for events */ SDL_WaitEvent(&event); switch (event.type) { case SDL_KEYDOWN: PrintKey(&event.key.keysym, 1); break; case SDL_KEYUP: PrintKey(&event.key.keysym, 0); break; case SDL_MOUSEBUTTONDOWN: /* Any button press quits the app... */ case SDL_QUIT: done = 1; break; default: break; } } SDL_Quit(); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/keytest.c000066400000000000000000000070141504763705000250270ustar00rootroot00000000000000/* ------------------------------------ * Program to show what keycodes evnt_multi & co. * return... */ #ifdef __AHCC__ # include # include #else # ifdef __SOZOBONX__ # include # include # include # endif #endif #include #include #ifndef FALSE #define FALSE 0 #define TRUE !FALSE #endif /* resource set indices for KEYTEST */ #define TREEMENU 0 /* menu */ #define MENUINFO 7 /* STRING in tree TREE001 */ #define MENUTABL 16 /* STRING in tree TREE001 */ #define MENUQUIT 18 /* STRING in tree TREE001 */ #define MY_MU (MU_MESAG | MU_KEYBD) OBJECT *tree_menu; /* ------------------------------------ * Function prototypes. */ static void prg_init (void); static void prg_exit (void); static void msg_handler (short *msgbuf); static void do_menu (short entry); static void ascii_table (void); static void show_key (short key, short state); /* ------------------------------------ * Standard GEM handling functions (main loop). */ int main() { short mbuf[8]; short evnt_m, m1fl, pmx1, pmy1, pm1w, pm1h, m2fl, pmx2, pmy2, pm2w, pm2h, pmx, pmy, pmstate, state, key, clicks, tlo_cnt, thi_cnt; m1fl = pmx1 = pmy1 = pm1w = pm1h = 0; m2fl = pmx2 = pmy2 = pm2w = pm2h = 0; tlo_cnt = thi_cnt = 0; prg_init (); graf_mouse(0, ARROW); while (TRUE) { /* mouse button, mouse coordinate 1, * mouse coordinate 2, message and timer events. * mouse coordinate, mouse button, special key and normal key states. * number of mouse clicks. */ evnt_m = evnt_multi(MY_MU, 259, 3, 0, m1fl, pmx1, pmy1, pm1w, pm1h, m2fl, pmx2, pmy2, pm2w, pm2h, mbuf, tlo_cnt, thi_cnt, &pmx, &pmy, &pmstate ,&state, &key, &clicks); if (evnt_m & MU_MESAG) msg_handler (mbuf); if (evnt_m & MU_KEYBD) show_key (key, state); } return 0; } /* ------------------------------------ * GEM startup. */ static void prg_init (void) { appl_init (); if (!rsrc_load ("keytest.rsc")) { appl_exit (); exit (1); } /* TREEMENU should be defines, generated by the rsc editor */ rsrc_gaddr (R_TREE, TREEMENU, &tree_menu); menu_bar (tree_menu, TRUE); /* show menubar */ } /* GEM exit. */ static void prg_exit (void) { menu_bar (tree_menu, FALSE); rsrc_free (); appl_exit (); exit (0); } /* message handling code. */ static void msg_handler (short *msgbuf) { switch (msgbuf[0]) { case MN_SELECTED: /* handle menu entry */ do_menu (msgbuf[4]); /* de-invert menu title */ menu_tnormal (tree_menu, msgbuf[3], 1); break; } } /* ------------------------------------ * Selection handling functions. */ static void do_menu (short entry) { switch (entry) { case MENUINFO: form_alert (1, "[1][|Show keycodes and status| of shift/alt/ctrl as|returned by evnt_multi()][ OK ]"); break; case MENUTABL: ascii_table (); break; case MENUQUIT: prg_exit (); } } static void ascii_table (void) { int x, y; Cconws ("\33Y# . | 0 1 2 3 4 5 6 7 8 9 a b c d e f \15\12"); Cconws ("----------------------------------------------------\15\12"); for (y = 2; y < 16; y ++) { Cconout(' '); Cconout('0' + y); Cconout(' '); Cconout('|'); for (x = 0; x < 16; x ++) { Cconout(' '); Cconout(x + y * 0x10); Cconout(' '); } Cconws("\15\12"); } } void show_key (short key, short state) { char buf[128]; sprintf (buf, "[1][Mod state: $%02x (%d)|Scan code: $%02x (%d)|ASCII code: $%02x (%d)][ OK ]", state, state, key>>8, key>>8, key&0xff, key&0xff); form_alert (1, buf); if ((key & 0xFF) == 27) prg_exit (); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/keytest.mak000066400000000000000000000001661504763705000253560ustar00rootroot00000000000000# A sozobon makefile for keytest CFLAGS = -e -v -O LDFLAGS = -s min_s.o LOADLIBES = xaesfast keytest.prg: keytest.c hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/keytest.prg000066400000000000000000000172261504763705000254030ustar00rootroot00000000000000`\ &H f&op`B@3## k # k# k### +,## k Nq Ј.@Jyg// ?<?<JNAO "@(I$k,+<=.NAXHra$_Nu/ //??<@NAO ra$_NuNVH<&n$n (H*I KN6 -f[ jg jo6*0Cl4:0RJRfJCgNJjf2JRg.JCg"J* g *0 f* B* SCp LN`* SR`JCg J* f`* SR`0HL<8N^NuJ* gxRC`rNVH<&n$n (H*IBJg< %f$ACrp Q0| =|=||0BDRHNlJ@g6<Hr @8Jng=D`JDf . f|0`=D` -f=|`  g +fS` *f8Jng=D`=D`z .f =|`j lfRn`\<g8<XgV<bg<cg<dgp<igh<og"<pgذ<sg<ugd<xgH LNHրR`JnfJyg8 -@ArN xf ANHnHn L"MaPր`p0`JnfJyg* -@Ar NHnHn L"MapPր`xp0`-ZB.| Hn/. L"MaDPր`L=||0=|=| -@ArNHnHn L"MaPր` JnfJyg* -@ArNHnHn L"MaPր`p0`JnfJyg* -@Ar NHnHn L"MaPր`0H`TjB.B.| HnHn L"MaXPր``JnfJyg* -@ArNHnHn L"Ma Pր`(p0`#H LNHր`H LNHր` L<8N^Nu"H PRpNuNVH-H&nHn/ AC arP6B300LN^NuNVH<*H-I n(h&h g6)K g('Lg +@/.?<INA\L<N^Nu `*`+K`H0&i$ig('Jg%Kg !@L Nu ` `!J`/ $H!IB#PJg R!I$$_Nu%I`NVH&n(H#KJg k!I#k'ILN^Nu(`/ $H!IB#hJg j!I%I$_Nu$`NVH&n(H#KJg k!I#k'ILN^Nu)I`/ Jg"&i Бf ё&kg'I#K!I&_Nu!I`H8(H&I$hf$Tf L"KaLNuACd6gACd$j`f L"Ka `/ L"KaX`gACc$j`f L"Kaz`/ L"KaX`H0&H$IaJg K"jaL NuH0&HJg kAfpL Nu$SgAfp`$j`B@`H8(H&I$hgJg jAf JLNu$Tg6& A,A(m В. <.*n&(n"-H&I=@=A=B=F=G=n =n=n=n=n=n=n=n=n=n=n A-HAD-HA-HA-HA-HBA0<"NBg6Ƹg8Ⱥg:J*g n*0J.g n.0J2g n200.L F (((hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/keytest.prj000066400000000000000000000001641504763705000253770ustar00rootroot00000000000000; AHCC project file for Atari key tester .C [-iinclude] keytest.prg = ahcstart.o keytest.c ahccstdi.lib ahccgem.lib hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/keytest.rsc000066400000000000000000000013101504763705000253650ustar00rootroot00000000000000$ Desk File Info...---------------------- Desk Accessory 1 Desk Accessory 2 Desk Accessory 3 Desk Accessory 4 Desk Accessory 5 Desk Accessory 6 ASCII-table--------------- Quit ESCPP  $ *P0 : Q f {  hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/keytest.rso000066400000000000000000000005111504763705000254030ustar00rootroot00000000000000RSOH @_h%jTREEMENUMENUINFOMENUTABLMENUQUIThatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/listkeys.c000066400000000000000000000027231504763705000252100ustar00rootroot00000000000000/* Print out all the keysyms we have, just to verify them */ #include #include "SDL.h" int main(int argc, char *argv[]) { const char *name; int count, empty; SDL_Keycode key, kmin, kmax; SDL_Scancode scan; /* key names are queried from host display subsystem */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); exit(1); } count = 0; printf("Available SDL scancodes (with corresponding keycode):\n"); for (scan = SDL_SCANCODE_UNKNOWN; scan < SDL_NUM_SCANCODES; scan++) { if (!SDL_GetKeyFromScancode(scan)) { continue; } printf("- 0x%03x (%3d): \"%s\"\n", scan, scan, SDL_GetScancodeName(scan)); count++; } printf("= %d scancodes.\n", count); kmin = 0x7fffffff; kmax = empty = count = 0; printf("\nNamed SDL keycodes (corresponding to above scancodes):\n"); for (scan = SDL_SCANCODE_UNKNOWN; scan < SDL_NUM_SCANCODES; scan++) { key = SDL_GetKeyFromScancode(scan); if (!key) { continue; } name = SDL_GetKeyName(key); /* skip empty names, as they *all* * seem to have same 0x4000000 code */ if (!(name && *name)) { if (key < kmin) { kmin = key; } if (key > kmax) { kmax = key; } empty++; continue; } printf("- 0x%08x: \"%s\"\n", key, name); count++; } printf("= %d keycodes", count); if (empty) { printf(" (+ %d no-name ones in range 0x%08x-0x%08x)", empty, kmin, kmax); } printf(".\n"); SDL_Quit(); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/makefile000066400000000000000000000004371504763705000246750ustar00rootroot00000000000000 CFLAGS := -O -Wall $$(pkg-config --cflags sdl2) LIBS := $$(pkg-config --libs sdl2) all: listkeys checkkeys checkkeys: checkkeys.c makefile $(CC) -o $@ $(CFLAGS) $< $(LIBS) listkeys: listkeys.c makefile $(CC) -o $@ $(CFLAGS) $< $(LIBS) clean: $(RM) listkeys checkkeys *.[oO] *~ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/keymap/readme.txt000066400000000000000000000006521504763705000251720ustar00rootroot00000000000000Programs to find out key codes needed for Hatari keymap files: keytest.prg -- Atari program to show Atari keycode for pressed key checkkeys -- SDL program to print pressed SDL keys codes listkeys -- SDL program to list SDL scancodes & keycodes Sources for first (keytest.*) can be compiled with AHCC from: https://docs.dev-docs.org/htm/search.php?find=AHCC Sources for last two can be compiled with make & gcc on PC. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/mem_end/000077500000000000000000000000001504763705000233075ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/mem_end/CMakeLists.txt000066400000000000000000000022741504763705000260540ustar00rootroot00000000000000 set(scr_runner ${CMAKE_CURRENT_SOURCE_DIR}/scr_run_test.sh) foreach(machine st ste tt falcon) add_test(NAME screen-end-${machine}-color COMMAND ${scr_runner} $ ${CMAKE_CURRENT_SOURCE_DIR}/scr_end.prg --machine ${machine}) add_test(NAME screen-end-${machine}-mono COMMAND ${scr_runner} $ ${CMAKE_CURRENT_SOURCE_DIR}/scr_end.prg --machine ${machine} --mono) add_test(NAME screen-end-${machine}-vdi COMMAND ${scr_runner} $ ${CMAKE_CURRENT_SOURCE_DIR}/scr_end.prg --machine ${machine} --vdi on) endforeach(machine) set(dsnd_runner ${CMAKE_CURRENT_SOURCE_DIR}/dsnd_run_test.sh) add_test(NAME dma-sound-end-ste COMMAND ${dsnd_runner} $ ${CMAKE_CURRENT_SOURCE_DIR}/dsnd_end.prg --machine ste) add_test(NAME dma-sound-end-falcon COMMAND ${dsnd_runner} $ ${CMAKE_CURRENT_SOURCE_DIR}/dsnd_end.prg --machine falcon --cpuclock 8) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/mem_end/dsnd_end.prg000066400000000000000000000003071504763705000255770ustar00rootroot00000000000000`B?< NA//8p!pNr#C!<<?a4<a.<a(<a"<a!p?< NA\Hz4?< NA\BgNAR@2< ..." exit 1 fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1 fi; prg=$1 shift testdir=$(mktemp -d) export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy unset TERM HATARI_TEST=$(basename "$prg") export HATARI_TEST HOME="$testdir" $hatari --log-level fatal --sound off --bios-intercept on \ --fast-forward on --run-vbls 500 --frameskips 0 --tos none \ "$@" "$prg" > "$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari FAILED. Status=${exitstat}. Hatari output:" cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi if ! grep -q "SUCCESS!" "$testdir/out.txt" ; then echo "Test FAILED: Program did not report SUCCESS!" cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi echo "Test PASSED." rm -rf "$testdir" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/mem_end/scr_end.prg000066400000000000000000000002511504763705000254340ustar00rootroot00000000000000`B?< NA//8p!p88<?a<<a6<a0<a*<a$<a<a߂߂!p?< NA\BgNANr# yf?<NNTNr#NuNshatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/mem_end/scr_end.s000066400000000000000000000016631504763705000251160ustar00rootroot00000000000000; Check whether we can crash Hatari by setting the screen to the end of memory TEXT clr.l -(sp) move.w #$20,-(sp) trap #1 ; Super move.l d0,-(sp) move.l $70.w,-(sp) move.l #vbl,$70.w move.b $ffff8201.w,-(sp) move.b $ffff8203.w,-(sp) move.b #$ff,$ffff8203.w move.b #$3f,d0 bsr.s teststep move.b #$80,d0 bsr.s teststep move.b #$df,d0 bsr.s teststep move.b #$e0,d0 bsr.s teststep move.b #$f0,d0 bsr.s teststep move.b #$fc,d0 bsr.s teststep move.b #$ff,d0 bsr.s teststep move.b (sp)+,$ffff8203.w move.b (sp)+,$ffff8201.w move.l (sp)+,$70.w move.w #$20,-(sp) trap #1 ; Super addq.l #6,sp clr.w -(sp) ; Pterm0 trap #1 teststep: move.b d0,$ffff8201.w stop #$2300 ; Wait for VBL cmp.w #$0001,2 ; Is it Hatari faketos? bne.s skipdump move.w #20,-(sp) trap #14 ; Scrdmp addq.l #2,sp skipdump: stop #$2300 rts vbl: rte hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/mem_end/scr_run_test.sh000077500000000000000000000020271504763705000263610ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 2 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 ..." exit 1 fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1 fi; prg=$1 shift testdir=$(mktemp -d) export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy unset TERM # export LD_PRELOAD=libefence.so HATARI_TEST=$(basename "$prg") export HATARI_TEST HOME="$testdir" $hatari --log-level fatal --sound off --bios-intercept on \ --fast-forward on --run-vbls 500 --frameskips 0 --tos none \ --screenshot-dir "$testdir" "$@" "$prg" > "$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari FAILED. Status=${exitstat}. Hatari output:" cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi # shellcheck disable=SC2144 # there's only one match if [ ! -e "$testdir"/grab0007.* ]; then echo "Test FAILED: Screenshot has not been taken." cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi echo "Test PASSED." rm -rf "$testdir" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/000077500000000000000000000000001504763705000235105ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/CMakeLists.txt000066400000000000000000000017131504763705000262520ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) foreach (lvl RANGE 0 4) add_test(NAME natfeats-680${lvl}0-fast COMMAND ${testrunner} $ --cpulevel ${lvl} --compatible false --cpu-exact false) add_test(NAME natfeats-680${lvl}0-compatible COMMAND ${testrunner} $ --cpulevel ${lvl} --compatible true --cpu-exact false) add_test(NAME natfeats-680${lvl}0-cpuexact COMMAND ${testrunner} $ --cpulevel ${lvl} --compatible false --cpu-exact true) if (${lvl} GREATER 2) add_test(NAME natfeats-680${lvl}0-mmu COMMAND ${testrunner} $ --cpulevel ${lvl} --mmu true) endif() endforeach(lvl) add_test(NAME natfeats-68060 COMMAND ${testrunner} $ --cpulevel 6 --mmu false) add_test(NAME natfeats-68060-mmu COMMAND ${testrunner} $ --cpulevel 6 --mmu true) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/Makefile.gcc000066400000000000000000000006571504763705000257130ustar00rootroot00000000000000# A GCC makefile for Native Features tester # # Building: # make -f Makefile.gcc CC=m68k-atari-mint-gcc NAME = nf_gcc.tos CC ?= gcc CFLAGS ?= -Wall -O2 -m68000 -mshort -DTEST LDFLAGS ?= -s OBS = nf_gcc.o nf_asmg.o all: $(NAME) $(NAME): $(OBS) $(CC) $(LDFLAGS) $(OBS) -o $@ nf_gcc.o: natfeats.c $(CC) $(CFLAGS) -c $< -o $@ nf_asmg.o: nf_asmg.s $(CC) $(CFLAGS) -c $< -o $@ veryclean: $(RM) $(OBS) *~ *.bak $(RM) $(NAME) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/Makefile.vbc000066400000000000000000000006141504763705000257220ustar00rootroot00000000000000# A VBCC makefile for Native Features tester NAME = nf_vbcc.tos # for m68000 CC ?= vc -v CFLAGS ?= -O1 -DTEST LDFLAGS ?= -g # leave symbols to binaries OBS = nf_vbcc.o nf_asmv.o all: $(NAME) $(NAME): $(OBS) $(CC) $(LDFLAGS) $(OBS) -o $@ nf_vbcc.o: natfeats.c $(CC) $(CFLAGS) -c $< -o $@ nf_asmv.o: nf_asmv.s $(CC) $(CFLAGS) -c $< -o $@ veryclean: $(RM) $(OBS) *~ *.bak $(RM) $(NAME) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/natfeats.c000066400000000000000000000100311504763705000254540ustar00rootroot00000000000000/* * natfeat.c - NatFeats API examples * * Copyright (c) 2014-2016, 2019 by Eero Tamminen * * NF initialization & calling is based on EmuTOS code, * Copyright (c) 2001-2003 The EmuTOS development team * * This file is distributed under the GPL, version 2 or at your * option any later version. See doc/license.txt for details. */ #if __GNUC__ # include #else /* VBCC/AHCC/Pure-C */ # include #endif #include "natfeats.h" #define MSG_NF_MISSING \ "\r\nStart Hatari with option:\r\n\t--natfeats yes\r\n" /* NatFeats available & initialized */ static int nf_ok; /* handles for NF features that may be used more frequently */ static long nfid_print, nfid_debugger, nfid_fastforward; /* API documentation is in natfeats.h header */ int nf_init(void) { void *sup = (void*)Super(0); nf_ok = detect_nf(); Super(sup); if (nf_ok) { /* initialize commonly used handles */ nfid_print = nf_id("NF_STDERR"); nfid_debugger = nf_id("NF_DEBUGGER"); nfid_fastforward = nf_id("NF_FASTFORWARD"); } else { (void)Cconws("Native Features initialization failed!\r\n"); } return nf_ok; } long nf_version(void) { long id; if(nf_ok && (id = nf_id("NF_VERSION"))) { return nf_call(id); } else { (void)Cconws("NF_VERSION unavailable!\r\n"); return 0; } } static long getname(char *buf, long len, int subid) { long id; if (nf_ok && (id = nf_id("NF_NAME"))) { return nf_call(id | subid, buf, (long)len); } else { (void)Cconws("NF_NAME unavailable!\r\n"); return 0; } } long nf_name(char *buf, long len) { return getname(buf, len, 0x0); } long nf_fullname(char *buf, long len) { return getname(buf, len, 0x1); } long nf_print(const char *text) { if (nfid_print) { return nf_call(nfid_print, text); } else { (void)Cconws("NF_STDERR unavailable!\r\n"); return 0; } } long nf_debugger(void) { if (nfid_debugger) { return nf_call(nfid_debugger); } else { (void)Cconws("NF_DEBUGGER unavailable!\r\n"); return 0; } } long nf_fastforward(long enabled) { if (nfid_fastforward) { return nf_call(nfid_fastforward, enabled); } else { (void)Cconws("NF_FASTFORWARD unavailable!\r\n"); return 0; } } static void halt_reset(int subid) { long id; if(nf_ok && (id = nf_id("NF_SHUTDOWN"))) { void *sup = (void*)Super(0); /* needs to be called in supervisor mode */ nf_call(id | subid); Super(sup); } else { (void)Cconws("NF_SHUTDOWN unavailable!\r\n"); } } void nf_shutdown(void) { halt_reset(0x0); } void nf_reset(void) { halt_reset(0x1); } void nf_reset_cold(void) { halt_reset(0x2); } void nf_poweroff(void) { halt_reset(0x3); } void nf_exit(long exitval) { long id; if(nf_ok && (id = nf_id("NF_EXIT"))) { nf_call(id, exitval); } else { /* NF_EXIT is Hatari specific, NF_SHUTDOWN isn't */ (void)Cconws("NF_EXIT unavailable, trying NF_SHUTDOWN...\r\n"); nf_shutdown(); } } #ifdef TEST /* show emulator name */ static void nf_showname(void) { long chars; char buffer[64]; chars = nf_fullname(buffer, (long)(sizeof(buffer)-2)); buffer[chars++] = '\n'; buffer[chars++] = '\0'; nf_print(buffer); } #if 1 # define nf_showversion(void) #else /* printf requires adding ahcstart.o & ahccstdi.lib to nf_ahcc.prj, * but those need features not supported by Hatari's dummy TOS * (used in automated tests). * * Seeing the output requires "--conout 2" Hatari option, as * emulated display doesn't have time to refresh before exit. */ #include static void nf_showversion(void) { long version = nf_version(); printf("NF API version: 0x%x\n", version); } #endif static int wait_key(void) { while (Cconis()) { Cconin(); } (void)Cconws("\r\n\r\n"); return Cconin(); } int main() { long old_ff; if (!nf_init()) { (void)Cconws(MSG_NF_MISSING); wait_key(); return 1; } old_ff = nf_fastforward(1); nf_print("Emulator name:\n"); nf_showname(); nf_print(""); /* check regression b2a81850 + its fix */ nf_print("Invoking debugger...\n"); nf_debugger(); nf_print("Restoring fastforward & shutting down...\n"); nf_fastforward(old_ff); nf_exit(0); wait_key(); return 0; } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/natfeats.h000066400000000000000000000042261504763705000254720ustar00rootroot00000000000000/* * natfeats.h - NatFeats API header file * * Copyright (c) 2014 by Eero Tamminen * * This file is distributed under the GPL, version 2 or at your * option any later version. See doc/license.txt for details. */ #ifndef _NATFEAT_H #define _NATFEAT_H /* AHCC uses registers to pass arguments, but * NatFeats calls expect arguments to be in stack. * "cdecl" can be used to declare that arguments * should be passed in stack. */ #if __AHCC__ #define CDECL cdecl #else #define CDECL #endif /* internal ASM helper interface for natfeats.c */ long CDECL nf_id(const char *); /* it's best not to use this directly as arguments are untyped */ long CDECL nf_call(long ID, ...); /* call only from Supervisor mode */ int CDECL detect_nf(void); /* natfeats.c public prototypes */ /** * detect & initialize native features * returns zero for fail */ extern int nf_init(void); /** * returns NatFeats version * (upper word = major number, lower word = minor number) */ extern long nf_version(void); /** * get emulator name * returns name length */ extern long nf_name(char *buf, long len); /** * get full emulator name * returns name length */ extern long nf_fullname(char *buf, long len); /** * print string to emulator console * returns number of chars output */ extern long nf_print(const char *text); /** * invoke emulator debugger * (Hatari specific, can be used e.g. in asserts) */ extern long nf_debugger(void); /** * set emulator fastforward mode on (1) or off (0) * (Hatari specific) * returns previous value */ extern long nf_fastforward(long enabled); /** * terminate the execution of the emulation if possible * (runs in supervisor mode) */ extern void nf_shutdown(void); /** * warm reset emulation * (runs in supervisor mode) */ extern void nf_reset(void); /** * cold reset emulation * (runs in supervisor mode) */ extern void nf_reset_cold(void); /** * power off emulation * (runs in supervisor mode) */ extern void nf_poweroff(void); /** * terminate the execution of the emulation with exit code * (Hatari specific, can be used e.g. for determining test-case success) */ extern void nf_exit(long exitval); #endif /* _NATFEAT_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/nf_ahcc.prj000066400000000000000000000001751504763705000256110ustar00rootroot00000000000000; AHCC project file for NatFeats tester .C [-iinclude -DTEST=1] nf_ahcc.tos = ../startup.s natfeats.c (natfeats.h) nf_asma.s hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/nf_ahcc.tos000066400000000000000000000025201504763705000256170ustar00rootroot00000000000000`N*o - ЭЭ "ҍ.A// Bg?<JNAO N??<LNAHHyNFX&g,B?< NA\&@0H/NJX/ ?< NA\L NuHy?< NA\`paNuH (Jy g$Hy8NFX&g//NJPLNuHy@?< NA\a`NV/Ap>a&R B68Aa&N^Nu/ ?< NATJ@g ?<NAT`Hyn?< NA\?<NAT$_NuH0G~aJ@fHS?< NA\apL Nupa&A/a(ahA?aA@aaFAVa afpaahB@`B"O x!>HyYsJgp.I!NusNusNuNF_STDERRNF_DEBUGGERNF_FASTFORWARDNative Features initialization failed! NF_NAMENF_NAME unavailable! NF_STDERR unavailable! NF_DEBUGGER unavailable! NF_FASTFORWARD unavailable! NF_SHUTDOWNNF_SHUTDOWN unavailable! NF_EXITNF_EXIT unavailable, trying NF_SHUTDOWN... Start Hatari with option: --natfeats yes Emulator name: Invoking debugger... Restoring fastforward & shutting down... NF_VERSION4,    Xphatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/nf_asma.s000066400000000000000000000025741504763705000253100ustar00rootroot00000000000000; nf_asma.s - AHCC NatFeats ASM detection code ; ; Copyright (c) 2014 by Eero Tamminen ; ; Mostly based on code from EmuTOS, ; Copyright (c) 2001-2013 by the EmuTOS development team ; ; This file is distributed under the GPL, version 2 or at your ; option any later version. See doc/license.txt for details. ;; ;; exported symbols ;; ;; (AHCC doesn't prepend C-symbols with _) ;; .global nf_id .global nf_call .global detect_nf ;; ;; variables ;; .data nf_version: dc.b "NF_VERSION\0" even ;; ;; code ;; .text ; NatFeats test ; ; Needs to be called from Supervisor mode, ; otherwise exception handler change bombs detect_nf: clr.l d0 ; assume no NatFeats available move.l sp, a1 move.l 0x10, a0 ; illegal vector move.l #fail_nf, 0x10 pea nf_version sub.l #4, sp ; Conflict with ColdFire instruction mvs.b d0,d1 dc.w 0x7300 ; Jump to NATFEAT_ID tst.l d0 beq.s fail_nf moveq #1, d0 ; NatFeats detected fail_nf: move.l a1, sp move.l a0, 0x10 ; illegal vector rts ; map native feature to its ID nf_id: dc.w 0x7300 ; Conflict with ColdFire instruction mvs.b d0,d1 rts ; call native feature by its ID nf_call: dc.w 0x7301 ; Conflict with ColdFire instruction mvs.b d1,d1 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/nf_asmg.s000066400000000000000000000027141504763705000253120ustar00rootroot00000000000000/* * nf_asm.s - GCC NatFeats ASM detection code * * Copyright (c) 2014 by Eero Tamminen * * Mostly based on code from EmuTOS, * Copyright (c) 2001-2013 by the EmuTOS development team * * This file is distributed under the GPL, version 2 or at your * option any later version. */ /* * public interface */ .globl _nf_id .globl _nf_call .globl _detect_nf /* illegal exception vector */ .equ vec_illegal, 0x10 /* * code */ .text /* * NatFeats test * * Needs to be called from Supervisor mode, * otherwise exception handler change bombs */ _detect_nf: clr.l d0 /* assume no NatFeats available */ move.l sp,a1 move.l vec_illegal,a0 move.l #fail_nf,vec_illegal pea nf_version_name sub.l #4,sp #ifdef __mcoldfire__ #error Conflict with instruction mvs.b d0,d1 #else .dc.w 0x7300 /* Jump to NATFEAT_ID */ #endif tst.l d0 beq.s fail_nf moveq #1,d0 /* NatFeats detected */ fail_nf: move.l a1,sp move.l a0,vec_illegal rts nf_version_name: .ascii "NF_VERSION\0" .even /* * map native features to NF ID */ _nf_id: .dc.w 0x7300 /* Conflict with ColdFire instruction mvs.b d0,d1 */ rts /* * call native feature by its ID */ _nf_call: .dc.w 0x7301 /* Conflict with ColdFire instruction mvs.b d1,d1 */ rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/nf_asmv.s000066400000000000000000000025701504763705000253310ustar00rootroot00000000000000; nf_asmv.s - VBCC/Vasm NatFeats ASM detection code ; ; Copyright (c) 2014 by Eero Tamminen ; ; Mostly based on code from EmuTOS, ; Copyright (c) 2001-2013 by the EmuTOS development team ; ; This file is distributed under the GPL, version 2 or at your ; option any later version. See doc/license.txt for details. ;; ;; exported symbols ;; PUBLIC _nf_id PUBLIC _nf_call PUBLIC _detect_nf ;; ;; variables ;; DATA nf_version: dc.b "NF_VERSION\0" even ;; ;; code ;; TEXT ; NatFeats test ; ; Needs to be called from Supervisor mode, ; otherwise exception handler change bombs _detect_nf: clr.l d0 ; assume no NatFeats available move.l sp, a1 move.l $10, a0 ; illegal vector move.l #fail_nf, $10 pea nf_version sub.l #4, sp ; Conflict with ColdFire instruction mvs.b d0,d1 dc.w $7300 ; Jump to NATFEAT_ID tst.l d0 beq.s fail_nf moveq #1, d0 ; NatFeats detected fail_nf: move.l a1, sp move.l a0, $10 ; illegal vector rts ; map native feature to its ID _nf_id: dc.w $7300 ; Conflict with ColdFire instruction mvs.b d0,d1 rts ; call native feature by its ID _nf_call: dc.w $7301 ; Conflict with ColdFire instruction mvs.b d1,d1 rts hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/readme.txt000066400000000000000000000027211504763705000255100ustar00rootroot00000000000000Contents: - What is NatFeats - Test / example files WHAT IS NATFEATS NatFeats is Native Features interface for Atari programs for calling emulator functionality. It originates from Aranym, and is specified here: https://github.com/aranym/aranym/wiki/natfeats-proposal These features range from simple things like: - Toggling Hatari fast-forward mode on/off - Outputting strings (NF_STDERR) and invoking debugger (NF_DEBUGGER) (which together can be used to implement asserts) - Exiting emulator with return value (NF_EXIT) To complex device drivers. Individual feature APIs are (partially) documented here: https://github.com/aranym/aranym/wiki/natfeats-nfapi What features are implemented by emulators can differ, so application needs to check for availability of each of the features before using them. Ready-to-use example code for accessing them is listed below. TEST / EXAMPLE FILES Here are examples of using the simpler Native Features: natfeats.c -- C-code for calling native features natfeats.h -- header for assembly- & C-code nf_asma.s -- assembly helper code for AHCC nf_asmg.s -- assembly helper code for GCC / Gas nf_asmv.s -- assembly helper code for VBCC / Vasm Makefile* -- Makefiles for GCC & VBCC nf_ahcc.prj -- AHCC project file They're also used in Hatari's automated tests. If TEST is defined, natfeats.c includes main() and few additional tests, with TEST undefined, you should be able to use these files as-is in your own programs. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/run_test.sh000077500000000000000000000020351504763705000257120ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 ..." exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; basedir=$(dirname "$0") testdir=$(mktemp -d) export HATARI_TEST=natfeats export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy unset TERM echo c | HOME="$testdir" $hatari --log-level fatal --sound off --natfeats on \ -t none --fast-forward on --run-vbls 500 "$@" "$basedir/nf_ahcc.tos" \ 2>&1 | sed -e 's/^Hatari v.*/Hatari v/' -e 's/^CPU=.*$/CPU=.../' \ -e 's/^00.*/00.../' -e '/^> c$/d' -e 's/^> //' \ > "$testdir/out.txt" exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari failed. Status=${exitstat}." cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi if ! diff -q "$basedir/test_out.txt" "$testdir/out.txt"; then echo "Test FAILED, output differs:" diff -u "$basedir/test_out.txt" "$testdir/out.txt" rm -rf "$testdir" exit 1 fi echo "Test PASSED." rm -rf "$testdir" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/natfeats/test_out.txt000066400000000000000000000004401504763705000261150ustar00rootroot00000000000000Emulator name: Hatari v Invoking debugger... ---------------------------------------------------------------------- You have entered debug mode. Type c to continue emulation, h for help. CPU=... 00... Returning to emulation... Restoring fastforward & shutting down... NatFeats: exit(0) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/readme.txt000066400000000000000000000053331504763705000237050ustar00rootroot00000000000000Tests ===== Here's code for testing Hatari. Content: * Running tests * Building tests * Tests files * Test subdirectories Running tests ------------- Most tests are run automatically with "make test". Individual tests can be run (with verbose output) like this: $ ctest -V -R command-fifo Or a group of test: $ ctest -V -R 'serial-scc-.*' To speed up test runs and avoid external dependencies, tests use "--tos none", which tells Hatari to use a "fake" TOS with no features. Building tests -------------- Easiest way to re-build modified C-code test binaries (when the code and AHCC are in same directory): $ hatari-prg-args -m --cpulevel 3 --trace os_base -- ahcc_p.ttp mfp_ser.prj Test files ---------- check-bashisms.sh -- "make test" tests for scripts POSIX shell syntax configfile.sh -- "make test" tests for Hatari config file load / save startup.s -- minimal startup code for tests built with AHCC (AHCC builds smallest / simplest test binaries, but its startup code still needs to be replaced to work with the --tos none option.) Test subdirectories ------------------- Test subdirectories for Hatari and the emulated Atari machines: autostart/ - trivial AUTO/-folder binaries for (manually) slowing down startup. Allows testing whether problems in autostarted programs are due to TOS starting them too fast for all TOS facilities to be present blitter/ - "make test" tests for different blitter combinations of xcount and nfsr buserror/ - "make test" tests for IO memory addresses which cause bus errors on real machines cpu/ - "make test" tests for few CPU instructions cycles/ - "make test" tests for CPU cycles debugger/ - "make test" test code & data for Hatari debugger. test-scripting.sh is script for manual testing of debugger scripting gemdos/ - "make test" test code for GEMDOS APIs used by GEMDOS HD emulation keymap/ - test programs for finding out Atari and SDL keycodes needed in Hatari keymap files natfeats/ - "make test" test for Native Features emulator interface, and example code for different compilers / assemblers on how to use it mem_end/ - "make test" tests handling of screen at end of RAM screen/ - "make test" tests for a fullscreen demo serial/ - "make test" tests for Hatari (MFP/SCC/MIDI) serial interfaces tosboot/ - Tester for automatically running all (specified) TOS versions with relevant Hatari configurations and for checking basic device and GEMDOS functionality. From screenshots saved at end of each test, one can manually verify that they all really booted up fine. There's also a script for comparing the screenshots against earlier reference screenshots unit/ - Few unit tests for file.c functionality xbios/ - "make test" tests for Hatari --bios-intercept facilities hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/000077500000000000000000000000001504763705000231625ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/CMakeLists.txt000066400000000000000000000011521504763705000257210ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) find_program(GM gm) find_program(IDENTIFY identify) if(GM OR IDENTIFY) add_test(NAME screen-fullscreen-st COMMAND ${testrunner} $ ${CMAKE_CURRENT_SOURCE_DIR}/flixfull.prg ${CMAKE_CURRENT_SOURCE_DIR}/flix_st.png --machine st) add_test(NAME screen-fullscreen-ste COMMAND ${testrunner} $ ${CMAKE_CURRENT_SOURCE_DIR}/flixfull.prg ${CMAKE_CURRENT_SOURCE_DIR}/flix_ste.png --machine ste) endif(GM OR IDENTIFY) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/flix_st.png000066400000000000000000000020341504763705000253370ustar00rootroot00000000000000PNG  IHDRumtEXtTitleHatari screenshotIDATxԱmPD5j]%@&pfݥ kvvs۷o/.S %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@O(1_IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/flix_ste.png000066400000000000000000000020341504763705000255040ustar00rootroot00000000000000PNG  IHDRumtEXtTitleHatari screenshotIDATxԱmPD5j]%@&pfݥ kvvs۷o/.S %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@Yd %p@O(1_IENDB`hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/flixfull.prg000066400000000000000000000051041504763705000255210ustar00rootroot00000000000000` "B?< NA\/a ?< NA\BgNAaa z A0<0UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU0330000000UU033000000AQZ!,p 89fatNuF' /` .L@H A3 " < 1B# AaF#pajax anB8`LH@Nu!paRL H@aB a: a2 `apa0:ANuAg@NuQ 0J9 0gNu# #p A & ' ( ) * + ,! -B(B( B(B(|@!p! NuF'! !pAzz zzzzzz!F#NuP 0NsNs/8p!paaWy $ y $m3 $!pNuB8!! !pB8 P 0NsB8P 0NsRy $ !$ NsRy $Ns!Lh3! F!P 0>!NsSy gNs!bhNr!F'\NqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq NqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq A C`p8 gF@hNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq>:@Nq2NqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq0NqNqNqNqNqNqNqNqNqNqNqNqNq2NqNqNqNqNqNqNqNqNqQ2NqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq0NqNqNqNqNqNqNqNqNqNq0Nq2NqNqNqNqNqNqNqNqNqNqNqNq20NqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq0NqNqNqNqNqNqNqNqNqNqNqNqNq2NqNqNqNqNqNqNqNqNqNq></Nq2NqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNqNq0NqNqNqNqNqNqNqNqNqNqNqNqNq2NqNqNqNqNqNqNqNqNqQ yg>#Ns!pNr#?<NNTBgNA'7GWgw: P  (T    phatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/flixfull.s000066400000000000000000000251211504763705000251740ustar00rootroot00000000000000******************************************************************************** ** ** ** Fullscreen-Routine ** ** ~~~~~~~~~~~~~~~~~~ ***** ** ** ** ** ** ** ** ** ** ** ** ** ** (c) by Felix Brandt **** ** ** *** ** ** ** ** ** ** ** ** ** ** ***** ** ** ** ** ** ** ** of Delta Force ** ** of The Union ** ** ** ******************************************************************************** ; Die Routine laeuft NICHT in monochrom und auf TTs. TEXT clr.l -(SP) move.w #$20,-(SP) trap #1 ; Super addq.l #6,SP move.l D0,-(SP) bsr.s start move.w #$20,-(SP) trap #1 ; Super addq.l #6,SP clr.w -(SP) ; Ende trap #1 ;----------------------------------------------------------------------------- start: bsr install_all ; alles initialisieren bsr get_lines ; Anzahl der Zeilen ermitteln movea.l screen(PC),A0 ; Graphikmuster zeichnen lea 160(A0),A0 move.w #276-1,D0 doit: REPT 26/2 move.w #%101010101010101,(A0)+ move.w #%11001100110011,(A0)+ move.w #%111100001111,(A0)+ move.w #%11111111,(A0)+ move.w #%1010101010101010,(A0)+ move.w #%1100110011001100,(A0)+ move.w #%1111000011110000,(A0)+ move.w #%1111111100000000,(A0)+ ENDR lea 2*8+6(A0),A0 dbra D0,doit move.l #fullvbl,$70.w ; Fullscreen-VBL-Routine waiter: cmpi.b #$39,$fffffc02.w ; Auf Space warten bne.s waiter ;--- end: bsr.s restore_all ; alles zuruecksetzen rts ;----------------------------------------------------------------------------- >PART 'Install_All' ; alles installieren install_all: ; Initialisiert alle Hardware-Register move #$2700,SR ; alle IRQs sperren move.b $ffff820a.w,oldsync ; Alte Synchronisation move.b $ffff8260.w,oldres ; Alte Aufloesung movem.l $ffff8240.w,D0-D7 ; Alte Palette movem.l D0-D7,oldpalette lea $ffff8201.w,A0 ; Screenadresse holen movep.w 0(A0),D0 move.w D0,oldscreen move.l #screen_base,D0 clr.b D0 ; untere 8 Bits weg move.l D0,screen lsr.l #8,D0 ; Bildschirmadresse setzen lea $ffff8201.w,A0 movep.w D0,0(A0) bsr init_mfp move #$2300,SR ; IRQs an moveq #$12,D0 ; Maus aus bsr.s send_ikbd bsr vsync move.b #2,$ffff820a.w ; 50 Hz bsr.s vsync clr.b $ffff8260.w ; Lores movem.l palette(PC),D0-D7 ; Neue Palette movem.l D0-D7,$ffff8240.w rts ENDPART >PART 'Restore_All' ; alles zuruecksetzen restore_all: ; alle Hardware-Register werden wieder zurueckgesetzt move.l #vbl,$70.w bsr.s vsync movem.l oldpalette,D0-D7 ; Alte Palette movem.l D0-D7,$ffff8240.w bsr.s vsync move.b #2,$ffff820a.w ; 50Hz bsr.s vsync move.b #0,$ffff820a.w ; 60Hz (um Syncerrors zu beheben) bsr.s vsync move.b oldsync(PC),$ffff820a.w ; Alte Synchronisation move.b oldres(PC),$ffff8260.w ; Alte Aufloesung bsr restore_mfp moveq #$08,D0 ; Maus wieder an bsr.s send_ikbd move.w oldscreen(PC),D0 lea $ffff8201.w,A0 movep.w D0,0(A0) ; alte Screenadresse setzen rts ENDPART >PART 'Send_IKBD' send_ikbd: ; sendet d0 an IKBD lea $fffffc00.w,A0 waitkeyready: btst #1,(A0) ; Tastaturprozessor bereit? beq.s waitkeyready move.b D0,2(A0) rts ENDPART >PART 'Vsync' vsync: sf vblflag waitforsync: tst.b vblflag beq.s waitforsync rts ENDPART >PART 'MFP-Install+DeInstall' init_mfp: ; rettet und setzt alle IRQ's move.l $0120.w,oldtimerb move.l $70.w,oldvbl lea $fffffa00.w,A0 ; MFP move.b $07(A0),oldmfp07 move.b $09(A0),oldmfp09 move.b $11(A0),oldmfp11 move.b $13(A0),oldmfp13 move.b $15(A0),oldmfp15 move.b $17(A0),oldmfp17 move.b $1b(A0),oldmfp1b move.b $21(A0),oldmfp21 clr.b $07(A0) ; alle IRQs aus clr.b $09(A0) clr.b $13(A0) clr.b $15(A0) bset #0,$07(A0) ; Timer B erlauben bset #0,$13(A0) move.b #$40,$17(A0) ; Automatic End Of Interrupt move.l #vbl,$70.w ; am Anfang (zum Initialisieren) move.l #timer_b,$0120.w rts restore_mfp: ; setzt alle MFP-Register wieder zurueck move #$2700,SR move.l oldtimerb(PC),$0120.w move.l oldvbl(PC),$70.w lea $fffffa00.w,A0 ; MFP move.b oldmfp07(PC),$07(A0) move.b oldmfp09(PC),$09(A0) move.b oldmfp11(PC),$11(A0) move.b oldmfp13(PC),$13(A0) move.b oldmfp15(PC),$15(A0) move.b oldmfp17(PC),$17(A0) move.b oldmfp1b(PC),$1b(A0) move.b oldmfp21(PC),$21(A0) move #$2300,SR rts ENDPART ;------------------------------------------------------------------------------ vbl: st vblflag ; VBL-Flag setzen rte timer_b: rte >PART 'Zeilen ermitteln' get_lines: ; Es wird ermittelt wieviel Zeilen, die MMU bei geoeffnetem oberen Border ; darstellt. move.l $70.w,-(SP) move.l #testvbl1,$70.w bsr vsync bsr vsync subq.w #3,scanlines ; Wert benutzbar machen cmpi.w #216,scanlines ; wegen bestimmten STEs notwendig blt.s itsok move.w #226,scanlines itsok: move.l (SP)+,$70.w rts testvbl1: clr.b $fffffa1b.w move.b #1,$fffffa21.w move.l #testb1,$0120.w move.b #8,$fffffa1b.w move.l #testvbl2,$70.w clr.b $ffff820a.w ; 60 Hz st vblflag rte testvbl2: clr.b $fffffa1b.w st vblflag rte testb1: addq.w #1,scanlines move.b #2,$ffff820a.w ; 50 Hz move.l #testb2,$0120.w rte testb2: addq.w #1,scanlines rte ENDPART >PART 'Fullscreen-Routinen' fullvbl: move.l #open_top,$68.w ; HBL fuer den oberen Border move.w #33,hblcount ; Zeilenzaehler setzen move #$2100,SR ; Los geht's st vblflag move.w #$2100,(SP) ; SR setzen rte open_top: subq.w #1,hblcount ; Zaehlen beq.s openit rte openit: move.l #open_top2,$68.w stop #$2100 ; Der Prozessor kann nur noch durch einen HBL wieder zum Leben erweckt werden. open_top2: move #$2700,SR ; [16] Keine Stoerung bitte addq.l #6,SP ; [8] Stack reparieren REPT 86 nop ENDR move.b #0,$ffff820a.w ; [16] 60 Hz REPT 17 nop ENDR move.b #2,$ffff820a.w ; [16] 50 Hz lea $ffff820a.w,A0 ; Sync-Register lea $ffff8260.w,A1 ; Aufloesungsregister moveq #0,D0 waitsync: move.b $ffff8209.w,D0 ; Screen-Low-Byte beq.s waitsync not.w D0 ; Wert negieren lsr.w D0,D0 ; Synchronisation mit dem Strahl ; Ab hier ist der Code synchron zum Rasterstrahl: REPT 70 ; Auf die richtige Stelle warten nop ENDR move.w scanlines(PC),D7 ; [8] Zeilen lines: nop move.w A1,(A1) ; [8] Linker Rand nop ; [4] move.b D0,(A1) ; [8] REPT 89 nop ENDR move.b D0,(A0) ; [8] Rechter Rand move.w A0,(A0) ; [8] REPT 13 nop ENDR move.w A1,(A1) ; [8] Stabilisator nop ; [4] move.b D0,(A1) ; [8] REPT 11-3 nop ENDR dbra D7,lines ; [12/16] move.w A1,(A1) ; [8] Linker Rand nop ; [4] move.b D0,(A1) ; [8] REPT 89 nop ENDR move.b D0,(A0) ; [8] Rechter Rand move.w A0,(A0) ; [8] REPT 10 nop ENDR move.w D0,(A0) ; [8] Unterer Rand 1 nop move.w A1,(A1) ; [8] Stabilisator nop ; [4] move.b D0,(A1) ; [8] REPT 11 nop ENDR move.w A1,(A1) ; [8] Linker Rand move.w A0,(A0) ; [8] Unterer Rand 2 move.b D0,(A1) ; [8] REPT 89 nop ENDR move.b D0,(A0) ; [8] Rechter Rand move.w A0,(A0) ; [8] REPT 13 nop ENDR move.w A1,(A1) ; [8] Stabilisator nop ; [4] move.b D0,(A1) ; [8] REPT 11-2 nop ENDR move.w #48-1,D7 ; [8] Zeilen lines2: nop move.w A1,(A1) ; [8] Linker Rand nop ; [4] move.b D0,(A1) ; [8] REPT 89 nop ENDR move.b D0,(A0) ; [8] Rechter Rand move.w A0,(A0) ; [8] REPT 13 nop ENDR move.w A1,(A1) ; [8] Stabilisator nop ; [4] move.b D0,(A1) ; [8] REPT 11-3 nop ENDR dbra D7,lines2 ; [12/16] cmp.w #$0001,2 ; Is it Hatari faketos? beq.s hataritest move.w #$2300,(SP) ; SR setzen rte ENDPART >PART 'Hatari-Screenshot' hataritest: move.l #vbl,$70.w stop #$2300 ; Wait for VBL move.w #20,-(sp) trap #14 ; Scrdmp addq.l #2,sp clr.w -(SP) trap #1 ; Pterm0 ENDPART ;------------------------------------------------------------------------------ DATA palette: DC.W $00,$01,$02,$03,$04,$05,$06,$07 DC.W $17,$0117,$0227,$0337,$0447,$0557,$0667,$0777 hblcount: DC.W -1 ;------------------------------------------------------------------------------ BSS screen: DS.L 1 oldvbl: DS.L 1 oldtimerb: DS.L 1 oldpalette: DS.W 16 oldscreen: DS.W 1 scanlines: DS.W 1 oldmfp07: DS.B 1 oldmfp09: DS.B 1 oldmfp11: DS.B 1 oldmfp13: DS.B 1 oldmfp15: DS.B 1 oldmfp17: DS.B 1 oldmfp1b: DS.B 1 oldmfp21: DS.B 1 oldres: DS.B 1 oldsync: DS.B 1 vblflag: DS.B 1 DS.B 256 ; wegen unteren 8 Bits des Screens screen_base: DS.B 230*277 ; Bildschirmspeicher END hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/screen/run_test.sh000077500000000000000000000030321504763705000253620ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 3 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 ..." exit 1 fi if command -v gm >/dev/null 2>&1; then identify="gm identify" elif command -v identify >/dev/null 2>&1; then identify=identify else echo "Need either 'gm' (GraphicsMagick) or 'identify' (ImageMagick)" exit 1 fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1 fi; prg=$1 shift refpng=$1 shift testdir=$(mktemp -d) export HATARI_TEST=screen export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy unset TERM HOME="$testdir" $hatari --log-level fatal --sound off -z 1 --max-width 416 \ --bios-intercept on --statusbar off --drive-led off --fast-forward on \ --run-vbls 500 --frameskips 0 --tos none --screenshot-dir "$testdir" \ "$@" "$prg" > "$testdir/out.txt" 2>&1 exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari FAILED. Status=${exitstat}. Hatari output:" cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi # shellcheck disable=SC2144 # there's only one match if [ ! -e "$testdir"/grab0001.* ]; then echo "Test FAILED: Screenshot has not been taken." cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi ref_signature=$($identify -format "%#" "$refpng") tst_signature=$($identify -format "%#" "$testdir"/grab0001.*) if [ "$ref_signature" != "$tst_signature" ]; then echo "Test FAILED, screenshot differs! Hatari output:" cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi echo "Test PASSED." rm -rf "$testdir" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/000077500000000000000000000000001504763705000231625ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/CMakeLists.txt000066400000000000000000000011751504763705000257260ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) foreach(machine st ste tt) add_test(NAME serial-mfp-${machine} COMMAND ${testrunner} $ mfp ${machine}) endforeach(machine) foreach(machine megaste tt falcon) add_test(NAME serial-scc-${machine} COMMAND ${testrunner} $ scc ${machine}) endforeach(machine) if(NOT PORTMIDI_FOUND) foreach(machine st ste tt falcon) add_test(NAME serial-midi-${machine} COMMAND ${testrunner} $ midi ${machine}) endforeach(machine) endif(NOT PORTMIDI_FOUND) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/expected.txt000066400000000000000000000000541504763705000255230ustar00rootroot00000000000000The quick brown fox jumps over the lazy dog hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/mfp_ser.c000066400000000000000000000062401504763705000247630ustar00rootroot00000000000000/* * Hatari serial port test * * Partly based on the file serport.c and mfp.h from EmuTOS: * Copyright (C) 2013-2018 The EmuTOS development team * * This file is distributed under the GPL, version 2 or at your * option any later version. See doc/license.txt for details. */ #include void sleep_vbl ( int count ) { volatile long vbl_nb; vbl_nb = *(long *)0x462; while ( *(long volatile *)0x462 < vbl_nb+count ) ; } typedef unsigned char UBYTE; /*==== MFP memory mapping =================================================*/ typedef struct { UBYTE dum1; volatile UBYTE gpip; /* general purpose .. register */ UBYTE dum2; volatile UBYTE aer; /* active edge register */ UBYTE dum3; volatile UBYTE ddr; /* data direction register */ UBYTE dum4; volatile UBYTE iera; /* interrupt enable register A */ UBYTE dum5; volatile UBYTE ierb; /* interrupt enable register B */ UBYTE dum6; volatile UBYTE ipra; /* interrupt pending register A */ UBYTE dum7; volatile UBYTE iprb; /* interrupt pending register B */ UBYTE dum8; volatile UBYTE isra; /* interrupt in-service register A */ UBYTE dum9; volatile UBYTE isrb; /* interrupt in-service register B */ UBYTE dum10; volatile UBYTE imra; /* interrupt mask register A */ UBYTE dum11; volatile UBYTE imrb; /* interrupt mask register B */ UBYTE dum12; volatile UBYTE vr; /* vector register */ UBYTE dum13; volatile UBYTE tacr; /* timer A control register */ UBYTE dum14; volatile UBYTE tbcr; /* timer B control register */ UBYTE dum15; volatile UBYTE tcdcr; /* timer C + D control register */ UBYTE dum16; volatile UBYTE tadr; /* timer A data register */ UBYTE dum17; volatile UBYTE tbdr; /* timer B data register */ UBYTE dum18; volatile UBYTE tcdr; /* timer C data register */ UBYTE dum19; volatile UBYTE tddr; /* timer D data register */ UBYTE dum20; volatile UBYTE scr; /* synchronous character register */ UBYTE dum21; volatile UBYTE ucr; /* USART control register */ UBYTE dum22; volatile UBYTE rsr; /* receiver status register */ UBYTE dum23; volatile UBYTE tsr; /* transmitter status register */ UBYTE dum24; volatile UBYTE udr; /* USART data register */ } MFP; #define MFP_BASE ((MFP *)(0xfffffa00L)) /* * MFP serial port i/o routines */ static long costat(void) { if (MFP_BASE->tsr & 0x80) return -1; else return 0; } static void conout(char b) { /* Wait for transmit buffer to become empty */ while(!costat()) ; /* Output to RS232 interface */ MFP_BASE->udr = (char)b; } int main(int argc, char *argv[]) { char text[] = "The quick brown fox\njumps over the lazy dog\n"; int i; void *sp = (void*)Super(0); MFP_BASE->scr = 0x00; MFP_BASE->ucr = 0x88; MFP_BASE->rsr = 0x01; MFP_BASE->tsr = 0x01; for (i = 0; text[i] != 0; i++) conout(text[i]); // wait a few VBL's to be sure all the bytes were transferred/received sleep_vbl(5); Super(sp); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/mfp_ser.prj000066400000000000000000000001361504763705000253320ustar00rootroot00000000000000; AHCC project file for MFP serial tester .C [-iinclude] mfp_ser.tos = ../startup.s mfp_ser.c hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/mfp_ser.tos000066400000000000000000000004741504763705000253510ustar00rootroot00000000000000`.*o - ЭЭ "ҍ.A// Bg?<JNAO N??<LNA/4(8b0HЄ"8bm(Nu8-<gpNuBNu/aJg/&NuNVH0ACp,QB?< NA\&@B8')+-BCJ60g60HaRC`paj/ ?< NA\B@L N^NuThe quick brown fox jumps over the lazy dog 4Zhatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/midi_ser.c000066400000000000000000000064331504763705000251270ustar00rootroot00000000000000/* * Hatari MIDI port test * * Based on the file midi.c from EmuTOS: * Copyright (C) 2001-2016 Martin Doering * * This file is distributed under the GPL, version 2 or at your * option any later version. See doc/license.txt for details. */ #include void sleep_vbl ( int count ) { volatile long vbl_nb; vbl_nb = *(long *)0x462; while ( *(long volatile *)0x462 < vbl_nb+count ) ; } /* constants for the ACIA registers */ /* baudrate selection and reset (Baudrate = clock/factor) */ #define ACIA_DIV1 0 #define ACIA_DIV16 1 #define ACIA_DIV64 2 #define ACIA_RESET 3 /* character format */ #define ACIA_D7E2S (0<<2) /* 7 data, even parity, 2 stop */ #define ACIA_D7O2S (1<<2) /* 7 data, odd parity, 2 stop */ #define ACIA_D7E1S (2<<2) /* 7 data, even parity, 1 stop */ #define ACIA_D7O1S (3<<2) /* 7 data, odd parity, 1 stop */ #define ACIA_D8N2S (4<<2) /* 8 data, no parity, 2 stop */ #define ACIA_D8N1S (5<<2) /* 8 data, no parity, 1 stop */ #define ACIA_D8E1S (6<<2) /* 8 data, even parity, 1 stop */ #define ACIA_D8O1S (7<<2) /* 8 data, odd parity, 1 stop */ /* transmit control */ #define ACIA_RLTID (0<<5) /* RTS low, TxINT disabled */ #define ACIA_RLTIE (1<<5) /* RTS low, TxINT enabled */ #define ACIA_RHTID (2<<5) /* RTS high, TxINT disabled */ #define ACIA_RLTIDSB (3<<5) /* RTS low, TxINT disabled, send break */ /* receive control */ #define ACIA_RID (0<<7) /* RxINT disabled */ #define ACIA_RIE (1<<7) /* RxINT enabled */ /* status fields of the ACIA */ #define ACIA_RDRF 1 /* Receive Data Register Full */ #define ACIA_TDRE (1<<1) /* Transmit Data Register Empty */ #define ACIA_DCD (1<<2) /* Data Carrier Detect */ #define ACIA_CTS (1<<3) /* Clear To Send */ #define ACIA_FE (1<<4) /* Framing Error */ #define ACIA_OVRN (1<<5) /* Receiver Overrun */ #define ACIA_PE (1<<6) /* Parity Error */ #define ACIA_IRQ (1<<7) /* Interrupt Request */ struct ACIA { unsigned char ctrl; unsigned char dummy1; unsigned char data; unsigned char dummy2; }; #define ACIA_MIDI_BASE (0xfffffc04L) #define midi_acia (*(volatile struct ACIA*)ACIA_MIDI_BASE) /* can we send a byte to the MIDI ACIA ? */ long bcostat3(void) { if (midi_acia.ctrl & ACIA_TDRE) { return -1; /* OK */ } else { /* Data register not empty */ return 0; /* not OK */ } } /* send a byte to the MIDI ACIA */ long bconout3(char c) { while(!bcostat3()) ; midi_acia.data = c; return 1L; } /*==== midi_init - initialize the MIDI acia ==================*/ /* * Enable receive interrupts, set the clock for 31.25 kbaud */ void midi_init(void) { /* initialize midi ACIA */ midi_acia.ctrl = ACIA_RESET; /* master reset */ midi_acia.ctrl = ACIA_RIE| /* enable RxINT */ ACIA_RLTID| /* RTS low, TxINT disabled */ ACIA_DIV16| /* clock/16 */ ACIA_D8N1S; /* 8 bit, 1 stop, no parity */ } int main(int argc, char *argv[]) { char text[] = "The quick brown fox\njumps over the lazy dog\n"; int i; void *sp = (void*)Super(0); midi_init(); for (i = 0; text[i] != 0; i++) bconout3(text[i]); // wait a few VBL's to be sure all the bytes were transferred/received sleep_vbl(5); Super(sp); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/midi_ser.prj000066400000000000000000000001411504763705000254660ustar00rootroot00000000000000; AHCC project file for MIDI serial tester .C [-iinclude] midi_ser.tos = ../startup.s midi_ser.c hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/midi_ser.tos000066400000000000000000000004721504763705000255070ustar00rootroot00000000000000`.*o - ЭЭ "ҍ.A// Bg?<JNAO N??<LNA/4(8b0HЄ"8bm(Nu8<gpNuBNu/aJgp&NuNuNVH0ACp,QB?< NA\&@aBCJ60g60HaRC`pal/ ?< NA\B@L N^NuThe quick brown fox jumps over the lazy dog 4jhatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/run_test.sh000077500000000000000000000027531504763705000253730ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 " exit 1 fi hatari=$1 if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1 fi sertype=$2 machine=$3 basedir=$(dirname "$0") testdir=$(mktemp -d) touch "$testdir"/empty.txt export HATARI_TEST=serial export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy unset TERM if [ "$sertype" = "mfp" ]; then serparams="--rs232-in $testdir/empty.txt --rs232-out $testdir/serial-out.txt" testprog=mfp_ser.tos elif [ "$sertype" = "midi" ]; then serparams="--midi-in $testdir/empty.txt --midi-out $testdir/serial-out.txt" testprog=midi_ser.tos elif [ "$sertype" = "scc" ]; then serparams="--scc-b-out $testdir/serial-out.txt" testprog=scc_ser.tos else echo "Unsupported serial type: $sertype" exit 1 fi # shellcheck disable=SC2086 # word splitting of $serparams is desired here HOME="$testdir" $hatari --log-level fatal --sound off --tos none \ --fast-forward on --run-vbls 500 $serparams --machine "$machine" \ "$basedir"/"$testprog" > "$testdir/out.txt" exitstat=$? if [ $exitstat -ne 0 ]; then echo "Test FAILED. Hatari exited with status ${exitstat}." cat "$testdir/out.txt" rm -rf "$testdir" exit 1 fi if ! diff -q "$basedir/expected.txt" "$testdir/serial-out.txt"; then echo "Test FAILED, output differs:" diff -u "$basedir/expected.txt" "$testdir/serial-out.txt" rm -rf "$testdir" exit 1 fi echo "Test PASSED." rm -rf "$testdir" exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/scc_ser.c000066400000000000000000000074621504763705000247600ustar00rootroot00000000000000/* * Hatari SCC serial port test * * Partly based on the file serport.c and mfp.h from EmuTOS: * Copyright (C) 2013-2018 The EmuTOS development team * * This file is distributed under the GPL, version 2 or at your * option any later version. See doc/license.txt for details. */ #include volatile int delay_cnt; static void delay_loop(int loops) { int i; for (i = 0; i < loops; i++) delay_cnt++; } void sleep_vbl ( int count ) { volatile long vbl_nb; vbl_nb = *(long *)0x462; while ( *(long volatile *)0x462 < vbl_nb+count ) ; } /* * defines */ #define RESET_RECOVERY_DELAY delay_loop(8) #define RECOVERY_DELAY delay_loop(4) #define SCC_BASE 0xffff8c80UL #define HIBYTE(x) ((unsigned char)((unsigned short)(x) >> 8)) #define LOBYTE(x) ((unsigned char)(unsigned short)(x)) /* * structures */ typedef struct { unsigned char dum1; volatile unsigned char ctl; unsigned char dum2; volatile unsigned char data; } PORT; typedef struct { PORT portA; PORT portB; } SCC; /* * SCC port B i/o routines */ static long bcostatB(void) { SCC *scc = (SCC *)SCC_BASE; long rc; rc = (scc->portB.ctl & 0x04) ? -1L : 0L; RECOVERY_DELAY; return rc; } long bconoutB(unsigned char b) { SCC *scc = (SCC *)SCC_BASE; while(!bcostatB()) ; scc->portB.data = b; RECOVERY_DELAY; return 1L; } static void write_scc(PORT *port, unsigned char reg, unsigned char data) { port->ctl = reg; RECOVERY_DELAY; port->ctl = data; RECOVERY_DELAY; } static const short SCC_init_string[] = { 0x0444, /* x16 clock mode, 1 stop bit, no parity */ 0x0104, /* 'parity is special condition' */ 0x0260, /* interrupt vector #s start at 0x60 (lowmem 0x180) */ 0x03c0, /* Rx 8 bits/char, disabled */ 0x05e2, /* Tx 8 bits/char, disabled, DTR, RTS */ 0x0600, /* SDLC (n/a) */ 0x0700, /* SDLC (n/a) */ 0x0901, /* status low, vector includes status */ 0x0a00, /* misc flags */ 0x0b50, /* Rx/Tx clocks from baudrate generator output */ 0x0c18, /* time const low = 24 | so rate = (24+2)*2/BR clock period */ 0x0d00, /* time const hi = 0 | = 52/(8053976/16) => 9680 bps */ 0x0e02, /* baudrate generator source = PCLK (8MHz) */ 0x0e03, /* ditto + enable baudrate generator */ 0x03c1, /* Rx 8 bits/char, enabled */ 0x05ea, /* Tx 8 bits/char, enabled, DTR, RTS */ 0x0f20, /* CTS interrupt enable */ 0x0010, /* reset external/status interrupts */ 0x0010, /* reset again (necessary, see manual) */ 0x0117, /* interrupts for Rx, Tx, special condition; parity is special */ 0x0901, /* status low, master interrupt disable */ /* NOTE: change above to 0x0909 to enable interrupts! */ 0xffff /* end of table marker */ }; /* * initialise the SCC */ static void init_scc(void) { SCC *scc = (SCC *)SCC_BASE; const short *p; /* issue hardware reset */ scc->portA.ctl = 0x09; RECOVERY_DELAY; scc->portA.ctl = 0xC0; RESET_RECOVERY_DELAY; /* initialise channel A */ for (p = SCC_init_string; *p >= 0; p++) write_scc(&scc->portA,HIBYTE(*p),LOBYTE(*p)); /* initialise channel B */ for (p = SCC_init_string; *p >= 0; p++) write_scc(&scc->portB,HIBYTE(*p),LOBYTE(*p)); /* * Enable routing of the SCC interrupt through the SCU like TOS does. * Even though interrupts are not used here, other programs might * install their own interrupt vectors and expect the interrupt * to be available to them. */ //if (HAS_VME) // *(volatile BYTE *)VME_INT_MASK |= VME_INT_SCC; } int main(int argc, char *argv[]) { char text[] = "The quick brown fox\njumps over the lazy dog\n"; int i; void *sp = (void*)Super(0); init_scc(); for (i = 0; text[i] != 0; i++) bconoutB(text[i]); // wait a few VBL's to be sure all the bytes were transferred/received sleep_vbl(5); Super(sp); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/scc_ser.prj000066400000000000000000000001361504763705000253200ustar00rootroot00000000000000; AHCC project file for SCC serial tester .C [-iinclude] scc_ser.tos = ../startup.s scc_ser.c hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/serial/scc_ser.tos000066400000000000000000000010511504763705000253270ustar00rootroot00000000000000`Z*o - ЭЭ "ҍ.A// Bg?<JNAO NP??<LNABA@l RyRA`Nu/4(8b0HЄ"8bm(NuH $|*<gp&pa LNup`H $|aJgCpapLNuH $H@patDpajLNuH0&|| paN|paBEJRmr*0H| KaT`EJRmr*0H|AaxT`L NuNVH0ACp,QB?< NA\&@afBCJ60g60Ha RC`pa/ ?< NA\B@L N^NuD`  P    The quick brown fox jumps over the lazy dog 4$8hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/startup.s000066400000000000000000000010541504763705000235710ustar00rootroot00000000000000; Start up code for AHCC based test programs .extern _main movea.l 4(SP),A5 ; Get pointer to basepage move.l $0C(A5),D0 ; Text segment length add.l $14(A5),D0 ; Data segment length add.l $1C(A5),D0 ; BSS segment length add.l #$2000,D0 ; Space for the stack move.l D0,D1 add.l A5,D1 and.l #-2,D1 movea.l D1,SP move.l D0,-(SP) move.l A5,-(SP) clr.w -(SP) move.w #$4A,-(SP) trap #1 ; Mshrink lea 12(SP),SP jsr main move.w D0,-(SP) move.w #$4C,-(SP) trap #1 ; Pterm .bss .global errno errno: ds.l 1 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/000077500000000000000000000000001504763705000233745ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/.gitignore000066400000000000000000000000341504763705000253610ustar00rootroot00000000000000dummy.cfg tos/etos1024k.img hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/blank-a.st.gz000066400000000000000000000016111504763705000256670ustar00rootroot00000000000000%Oblank-a.stԡNalQ-m2J @Xrt 熘n)-g;8uYʹ>H+U7.[Al_}flu=%Q%*dyvkRZ=mlv= =[@ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/bootauto.st.gz000066400000000000000000000123411504763705000262200ustar00rootroot00000000000000ebootauto.st[lGx?'|ͅ`DŽ) (/NpNNUH1ώY9M*m$;=ץ=UD{ ]Tt tr*C- DZՊc|giOX͛~ơMmmp?^Loa `.zPژϽajrw>\'_:[; _Q_`D Lݦ={vR_K= ɥVfR&efʬLʤXgӒv]Z'::S າimz&eR&eR&eR&eR&eR&eR&ͦE_Toj5orv +UO묿~eR&e@4Pcs zY0弾~ lFv4K^h h8nE{qSC[rLP@_"P7'cG?hIU~ߞɤLOJ4x @*W5̲= BD=G&V[>_Ͻlgy%j8% KtK,ҡiN7j(_y ͤM:gPZdSdrYWTumfTH,d.GmDq-l]f=]Et|걯௃y$=%˚=$g=?/o|_.=!C|E@iBc_GMmO∛$ڌ*z>=+6*]>0kP1:pS&ZGr[:nsGFS&e^ Ax2q-^~f"*'=[|ar_X#ZFg3/a%;lГZxi<!S4a0xhFc-s+lӁ3Go#4ehy&#N%FSk{fyu1'":2GH#JĀ.^Ҽj^TQKu& K|7x<7#\,}KGrUk'1Vp|=|Uk|S ҤM46(z\ȭ#W؈AiK}9Z,= {8ՋIL^уP!Qc(7{agR9Om{:O=cU|D$HKrίq,A )l9ԠIjz=$^Tωjn|=,7AZH e4f@-If{D'_$ B iol@ P`V`<38g!_E5|]S\Q^Ź#Y Azv ߿[%1s2ȭʒOGVU&m\k{M*! I94(V p>2QK[ÅYC3O<Fz-ߠH O@ [(+ץ^,w g&ۛ=_k@Y5ADsA;07oM&ыDOl}X$Gr4^2rj%a(o{P֫;8rMW݈ N;ڸ tBv( ()]-HN:If5|WOۂg?$|g7ji/cpPEt#%A0rߘG"(9ǖyu`29?#9󆧁ZBڒ$'x#z3U Sr"jɳj0_9mJ Uek~Ee y]dY:h3g\ 0wqsH܂-@20\EıcهPPĒKk8L1R,ǼG]{1?y1c<9`SA|T*Mi'}{s w_Vyҩ6(uQۥ5#[QENΠ|gZ[a>r.N8%,v"& {!NwN=3'ʜ+)2ك("- /=-Ґ^֋t.lWBߜM!E)ɓi>c-ůch 7̅9sLB7]GJ2 ښ?Zmay+;hJ`U5za8ԕCa%J~\vyvf5!WGW(O^]<Xs^$ׂ_:nV ibq!@O%U@4`/VaљMUL[ʡ6 [ke*a-d&J[);ξ; }qemmV[.3DN.nE65x<Ejr q>_RR/STsi x`- SΡ![-zC} cd>Ƥ"knh!Vk'qfYVRU-)b߆5[]StbdYXmfj{{u?8glY ĽK}VHjQ>;;e5MJIkWח}įI~qW=OYohl Pȁ%fgSy?z%i&qA(אo&eV2zo ؍[ =!;rJ $ncbr?_nQ_/' r+fhc6f3$b5Mۑc( ~ !JD$ VSY XXW`bq egnnթiS̲JIX q9})ܲm{[/ȃhatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/bootdesk.st.gz000066400000000000000000000130441504763705000261770ustar00rootroot00000000000000ebootdesk.st[ lG~3Ӟmƍi3l1ǃ1<3I{lU4Ӎ`ύm=YqB6Nh!6DZc/Y$8EKv#CV,@8x!88fN{j՚W^իWUmFІo f Q71"0LZTȂ S2>:W&IW7Mw ~K}z6qLh#G$/8,: \H i!Qߴ>XȚB3[V#7@Kmk K&'oP KM:Pd/Db!YU }$D,4d^ws筄x~%+xWq(ɥtr6q5) Zؿ*1#1YTp%9@ KcYzn@|)Yr-ɟZ]6&3+JBY:F2ukp$SsF$W0{GL)|(gfIF렿Y&AO6QWRUP*"ҺiRKGᠯep|U164^I,%;Rf.u0ʇ 9'gt/ b\D9M3aG(lD"b圝1jB^El\L.EJ@Utz:0]H+1oa%"8GT!@)}?>I+pi+JW_ѩ.Td)ߣLC?ȬsxW'G/<$=:y?޽k /⁶e#iaJE^ȋB*[:,Y'M4y/T4 Kl̨RjccFM0UCLNOhun`)>z{nvk/ɚp(]4#eۼ*_ͲKe٤{#XF+ wɩ )f!D{_ʹ% +.?5Hj5 "IX,5q` ьfV^98g폱V(Xc95 hyǢ$&ˆk{fiyX1G&E.RGfz=jAʏިT+R^u*7da-hU̠:jq/$Q?HbD<ݑ&Ud;{ TN$*3 J ڭ~AT>s*pjߋ܊`ov֬rQ=j&t7zd "Cɢ+#T/'3<9A=P&^H(T ~igBHmu*]h,G>"G$%sfpn &F⬞a uۋ#7{ n9Oߍoɏ:ϢN^x{tQWGAC#, zHxq5K ?Qs|$p$sWtxRޢـ^Ȧ@Ra<3_8g!˘E|]G\Ս^ r+V޳(4zҦ ;X([%1)vQkmd)R#k*qFu1赽:#M=汨/ n>T2!K #;rz{f-e܍F$ȿI@sk +*c1׍')K3]6fSqkY:ˁhIUECpv݄0̥z1ѭ`*Q#TGZcn"*Ф j}BVgp-$msv㯹GQGImMuMikZlfI)c* ~H7MEoT^>֋Y٤G 4ݜGE9(y:/z_C-!mYV<激sq%yo)9uTگ hTBȑX[̓,Ji#,1`fMS\9X H-pܼ2 r 8+l(Xhbs FKGpe`ލy`i-m:aގQ;0?y!̣x^;R[+eB<½Wq54JvywvGwSۃJ>fGfr4 ^G.f`řn6ٔ~Qd1Y8zR{1H4C{w_ Z {W⼺I݃s[ 09HO!ydeL=rbLb?f! %_k<_?~bN]ۏf3${k^.+y(l1֤Qw2n KigEךI'(FT֚1༾VU 9t+ QڙVE@HL%&LL,kqE B,TW?<99OnŅnN|;!S~;Eq@FMds؋eb~tf3~yE GȞZ(xKX1yߡ5]#{UHFf)VM& Oo]SKY_YOqoѵ$~O,vI F9Wzѻ%;VOhRwWԋ+7:pHj  N^J$=e^0㍂PNvMr} YG4-\f>X6JI+# [1w`Գg$)N$̡ͺ]nAFCqfa=} ;N+QF754m4CQVT2D&6^ !GtPY]RF s#HzĵKeA0.?,%I;´v^ ;?t!AwиE#/k,0Lrf/7޹՘ Y.wN<;7@_5cu>^z6C/aU+MNj#-qֽNuhZj=GG蔉a9ØgQW)R#a.w{drO~_dbv8+o fj^<ΰ}k j%MgLO_>5'KxLXi,Ag c iUcxY_Jջ;X yG5-xzps*+JQ=ETݦ 7N wtc7y 6@>%#1 /Uժ4lIIj VMn]ˠ5:Ns͈;Xn嘉kJ)M㻁-eVWEB%G#!d&ϯiM-d>Y݋=| -(ӉQs3 ?bZcuҬ̢Ol̨pS9 R׭U7k|ߌsZr3үSmy繇GۭK90l&/ f,ɯO0<r+<~ً~l3RBp3{~L}u(gp{.l;}};ÿt໙~iJy~m\KtW/ހ?¿ͺ n '_]dY}^vUEQx3>t~h7.;d|s%@%gYd[KuhD.:@q;Kq4C]?{M3YC֞r\I9M?N-~Jv)^?ze>2{{?bėuiDGQϜ08,9.>h/~]B4x\sG?HZr$==N?7]>5K_֯c;Xl+fֳ.̺#pu6h&NG YwdͭuDc=T~6k=ճkC+te݅DgdHELی4U ,U7Cb=To eHAV! =pRM! HӚT*Mul"pb[(! ]Mz6k7Ԡowc]l,u*pw7GNwo[2<C@(=80saL>=,k<\Y ʬGORONAXJ@gHy?< NA\#zLNu// ??/AN O#zNuNVH((IpaL(lLxN^Nu Lpa8*mHn/<@??<?NAO &f ab a\`Jm @oA0r@C$al`Hn/??<@NAO ,gA02C$a>`H0&H6??</?<CNAO 8Cg??/ AN P#zL NuH8I&H/ LN X?<NATJ@g KCaLNuHl?< NA\#z`H8I2&Ha/ LN X?<NATJ@g KCaLNuHl?< NA\#z`H8Il&H/ LN X?<NATJ@g KCaTLNuHl?< NA\#z`H0&H$I/ /AN P K"Ja JpaL NuHNAXHra$_Nu/ //??<@NAO ra$_NuNVH0$H&IBE nN6 n -f nR"n P n hg n ho n 6( n 0Cl n 0 n :0P n JPfJCgv n JhfF n JPg>JCg. n J( g$ n (0 f n ( B( SCp JN` n ( SP`JCg n J( f nR` n ( SP`0HL 8N^Nu n J( g0RC`*NVH?<&NN\ 9#$_Nu/ $Ha$$_Nu/ $HaҐ$_NuBJgR` NuH8$H&I(HJgHNJ@g  R` LLNuCaNuH(H&H"K IJgRJfSc  KR` LLNu$HBJBf($HBJBf H@B@H@NuB@H@HB0@0H@0H@Nu AH@r2B@tЀӁd҈рӁQF@Nu$HBJBf"$HBJBfB@H@NuB@H@HB0B@H@Nu A"B@H@HABAtҁрdЈӁрQNu$jD BJjD$HBJBf"$HBJBf $jDNuHBЂ$jDNuHBЂ$jDNuEmuTOSTOS >= v3 %s -> no extra sleeps needed Sleeping %d secs... (for TOS printing to work after boot) ERROR: Fopen(%c: '%s', %d) -> %ld ERROR: file close failed ERROR: %s(%d, %d, %p) -> %ld FreadFwriteERROR: Fattrib(%s, 1, %d) -> %d %s -> CON: (console) CON:ERROR: 'CON:' not ready! %s -> PRN: (printer) PRN:ERROR: 'PRN:' not ready! %s -> AUX: (serial) AUX:ERROR: 'AUX:' not ready! %s -> %s Truncate -> %s ERROR: truncate succeeded, Fcreate("%s", 0) -> %ld ERROR: truncate failed, Fcreate("%s", 0) -> %ld Result -> Midi (%s) EGEMDOS version = 0x%x successfailurertexttesttesttesttesttest               BBBBBBBBBB       DDDDDD      HHHHHH    0123456789ABCDEF       vdefaultlocal basefile baseglobal base@ " "@ " ("  &   4 " d.< 0 0 0" 8  D$ "DLdXbP$,LZ&6FHNAXJ@gHyz?< NA\#LNu// ??/ANO#NuNVH((IpaL(lLxN^Nu Lpa8*mHn/<@??<?NAO &f ab a\`Jm @oA0r@C$al`Hn/??<@NAO ,gA02C$a>`H8I&H/ LNX?<NATJ@g KCa4LNuHl?< NA\#`H8I&Ha/ LNX?<NATJ@g KCaLNuHl?< NA\#`H8I8&H/ LNX?<NATJ@g KCaLNuHl?< NA\#`/ /9ApNX?<%NNT/9?<?< NNPHyBg?< NNP$_Nu/ ?<0NAT?ANT$_Nur4|g BrfpNuB@Nu/ ?< NATJ@g ?<NAT`Hy?< NA\?<NATaJ@g$_Nu/ EN JNANBA NNNB@$_Nu2pHA00|Nu2pHA00|NuH0&&H8$H2H N2A2H N&bB KNL NuH0&&H8$HJl - D&  J2a KL NuH 6$H0(NJ@g* J0Cp"Q2LNu/ E6p JapAapA,apABapAXa$_Nu/ $Hp(|g JN$_NuH E6 JaAaA,aABaAXav C l0AnaRC`LNuH ??/?<BNAO &d3! LNuH $HBDfB@L8Nup*6|f0|fB@`Jg(0|g,0* j"*N పg*@x$B0` *D*0*"ta^Jdx`H $Hg JajLNuBC C l20A0||g0AAa.RC`B@`H $HfpLNup*6|fp` Ja*<@g jNBBB B*0|gB@`0*N 6g<H`p`/ $H (g <%@ N%HJf"|A%H%| $B$_Nu|@%| `H 6$HBF(p(8|g0|`gpLxNuJf Jap0|f C fv | RRR * e0B$:*2H0* jN 2Hg *@p`JFg C fv `0`/(0Hl< D $"ot3!<HH(Nu A00H$` `/ ??<>NAXHra$_Nu/ //??<@NAO ra$_NuNVH0$H&IBE nNn6 n -f nR"n P n hg n ho n 6( n 0Cl n 0 n :0P n JPfJCgv n JhfF n JPg>JCg. n J( g$ n (0 f n ( B( SCp JN` n ( SP`JCg n J( f nR` n ( SP`0HL 8N^Nu n J( g0RC`*NVH= v3 %s -> no extra sleeps needed Sleeping %d secs... (for TOS printing to work after boot) ERROR: Fopen(%c: '%s', %d) -> %ld ERROR: file close failed ERROR: %s(%d, %d, %p) -> %ld FreadFwrite %s -> CON: (console) CON:ERROR: 'CON:' not ready! %s -> PRN: (printer) PRN:ERROR: 'PRN:' not ready! %s -> AUX: (serial) AUX:ERROR: 'AUX:' not ready! Result -> Midi (%s) EGEMDOS version = 0x%x successfailuretexttexttext               BBBBBBBBBB       DDDDDD      HHHHHH    0123456789ABCDEF       ddefaultlocal basefile baseglobal base@ @  (   &   4 " d. 0 0 0  D$ "DLdXbP$,LZ&6FH10s) and show EmuCON output on console setopt --statusbar off --fast-boot on --fast-forward on --fastfdc on --conout 2 # wait for EmuTOS to boot sleep 5 # Invoke EmuCON with ^Z: Control down, press Z, Control up keydown 29 keypress Z keyup 29 # wait EmuCON to accept input sleep 1 # output command to re-build test.prg text ahcc_p.ttp gemdos.prj # press Return to execute it keypress 28 # wait build to finish sleep 5 # kill hatari kill quit hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/ahcc-minimal000066400000000000000000000004541504763705000265760ustar00rootroot00000000000000# hconsole input file to build disk/minimal.prg with AHCC # (see ahcc-gemdos file for more info on commands below) setopt --statusbar off --fast-boot on --fast-forward on --fastfdc on --conout 2 sleep 5 keydown 29 keypress Z keyup 29 sleep 1 text ahcc_p.ttp minimal.prj keypress 28 sleep 5 kill quit hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/common.c000066400000000000000000000140621504763705000257650ustar00rootroot00000000000000/* * Hatari - common.c * * Copyright (C) 2012 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Common functions for gemdos.c and minimal.c testers. */ #ifdef __PUREC__ /* or AHCC */ # include # include #else # include #endif #include #include #include "common.h" /*------- success / failure ----- */ /* anything failing changes 'msg' to failure, * write_midi() expects them to be of same length. */ static const char success[] = SUCCESS; static const char failure[] = FAILURE; static const char *msg = success; /* ------- sleeper functions ------ */ #define ETOS_STR 0x45544F53 /* "ETOS" */ /* how long sleep is needed to make sure all TOS * versions have started timer/counter after boot * so that printing will work? * * 2s is enough with floppies as they're slower, * but GEMDOS HD needs 4s with TOS v1.x */ static int sleep_secs = 4; /* return NULL if OS version needs a sleep, otherwise a version info string */ static long can_skip_sleep(void) { char *msg; long sysbase; OSHEADER* osheader; sysbase = *_sysbase; osheader = (OSHEADER*)sysbase; /* no extra boot sleeps needed with EmuTOS... */ if ((long)(osheader->p_rsv2) == ETOS_STR) { msg = "EmuTOS"; return (long)msg; } /* ...or newer (= slower to boot?) Atari TOS versions */ if (osheader->os_version >= 0x0300) { msg = "TOS >= v3"; return (long)msg; } return NULL; } /* do the sleeping */ static void sleep_if_needed(void) { char *info; /* already slept? */ if (!sleep_secs) { return; } info = (char *)Supexec(can_skip_sleep); if (info) { printf("\r\n%s -> no extra sleeps needed\r\n", info); } else { printf("\r\nSleeping %d secs...\r\n(for TOS printing to work after boot)\r\n", sleep_secs); sleep(sleep_secs); } sleep_secs = 0; } /* ------- helper functions ------ */ /* device/file opening with error handling */ static long open_device(const char *path, int attr) { long handle = Fopen(path, attr); if (handle < 0) { printf("ERROR: Fopen(%c: '%s', %d) -> %ld\r\n", 'A'+Dgetdrv(), path, attr, handle); msg = failure; } return handle; } /* device/file closing with error handling */ static void close_device(long handle) { if (Fclose(handle) != 0) { Cconws("ERROR: file close failed\r\n"); msg = failure; } } static void print_ioerror(const char *op, int handle, int bufsize, char *buffer, long count) { printf("ERROR: %s(%d, %d, %p) -> %ld\r\n", op, handle, bufsize, buffer, count); msg = failure; } /* write given file content to to given device/file with GEMDOS */ static void write_gemdos_device(const char *from, const char *to) { long handle1, handle2; long count1, count2; char buffer[64]; handle1 = open_device(from, FO_READ); if (handle1 < 0) { return; } handle2 = open_device(to, FO_WRITE); if (handle2 < 0) { return; } while (1) { /* copy file contents */ count1 = Fread(handle1, sizeof(buffer), buffer); if (!count1) { break; } if (count1 < 0 || count1 > (long)sizeof(buffer)) { print_ioerror("Fread", handle1, sizeof(buffer), buffer, count1); break; } count2 = Fwrite(handle2, count1, buffer); if (count2 != count1) { print_ioerror("Fwrite", handle2, count1, buffer, count2); break; } } close_device(handle1); close_device(handle2); } /* set given mode to given file */ static void set_mode(const char *path, int mode) { /* TODO: AHCC misses FA_SET (1) */ int result = Fattrib(path, 1, mode); if (result != mode) { printf("ERROR: Fattrib(%s, 1, %d) -> %d\r\n", path, mode, result); msg = failure; } } /* --------- public functions --------- * documented in the header */ void write2console(const char *input) { printf("\r\n%s -> CON: (console)\r\n", input); if (Cconos()) { write_gemdos_device(input, "CON:"); } else { Cconws("ERROR: 'CON:' not ready!\r\n"); msg = failure; } } void write2printer(const char *input) { sleep_if_needed(); printf("\r\n%s -> PRN: (printer)\r\n", input); if (Cprnos()) { write_gemdos_device(input, "PRN:"); } else { Cconws("ERROR: 'PRN:' not ready!\r\n"); msg = failure; } } void write2serial(const char *input) { printf("\r\n%s -> AUX: (serial)\r\n", input); if (Cauxos()) { write_gemdos_device(input, "AUX:"); } else { Cconws("ERROR: 'AUX:' not ready!\r\n"); msg = failure; } } void copy_file(const char *input, const char *output) { printf("\r\n%s -> %s\r\n", input, output); write_gemdos_device(input, output); set_mode(output, FA_READONLY); } void truncate_file(const char *readonly) { long handle; printf("\r\nTruncate -> %s\r\n", readonly); handle = Fcreate(readonly, 0); if (handle >= 0) { printf("ERROR: truncate succeeded, Fcreate(\"%s\", 0) -> %ld\r\n", readonly, handle); msg = failure; close_device(handle); } set_mode(readonly, 0); handle = Fcreate(readonly, 0); if (handle >= 0) { close_device(handle); } else { printf("ERROR: truncate failed, Fcreate(\"%s\", 0) -> %ld\r\n", readonly, handle); msg = failure; } } void write_midi(void) { printf("\r\nResult -> Midi (%s)\r\n", msg); Vsync(); /* sync screen output before sending result */ Midiws(sizeof(success)-2, msg); Midiws(0, "\n"); } void clear_screen(void) { printf("\033EGEMDOS version = 0x%x\r\n", Sversion()); } static int is_enter(long key) { int scancode = (key >> 16) & 0xff; /* return or enter? */ if (scancode == 28 || scancode == 114) { return 1; } return 0; } void wait_enter(void) { /* eat buffered keys */ while (Cconis()) { Cconin(); } Cconws("\r\n\r\n"); while (!is_enter(Cconin())); } /* ------- TODO ------ */ #if TEST_REDIRECTION static void stdin_to_printer(void) { long handle = open_device("PRN:", FO_WRITE); if (handle >= 0) { /* TODO: dup & force stdout to printer */ } } static void stdin_from_file(const char *path) { long handle = open_device(path, FO_WRITE); if (handle >= 0) { /* TODO: dup & force stdin from file */ } } static void stdin_stdout_reset(void) { /* TODO: force stdin & stdout back to normal */ } #endif hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/common.h000066400000000000000000000023531504763705000257720ustar00rootroot00000000000000/* * Hatari - common.c * * Copyright (C) 2012 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Common functions for gemdos.c and minimal.c testers. */ /* files which content is checked by tos_tester.py */ #define INPUT_FILE "text" #define OUTPUT_FILE "test" /* strings checked by tos_tester.py from midi output */ #define SUCCESS "success" #define FAILURE "failure" /* output either success or failure to midi at end of test */ extern void write_midi(void); /* output given file content to console */ extern void write2console(const char *input); /* output given file content to printer */ extern void write2printer(const char *input); /* output given file content to serial port */ extern void write2serial(const char *input); /* copy input file to output file and then sets it read-only */ extern void copy_file(const char *input, const char *output); /* try truncating given file which should be read-only * (=expected fail), then change it to read/write and * retry truncation (=expected success). */ extern void truncate_file(const char *readonly); extern void clear_screen(void); extern void wait_enter(void); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/gemdos.c000066400000000000000000000041721504763705000257540ustar00rootroot00000000000000/* * Hatari - gemdos.c * * Copyright (C) 2012 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Atari code for testing some basic Hatari GEMDOS device and file handle * operations in co-operations with tos_tester.py Hatari test driver. * * Full test should eventually be something like: * - clear screen * - open INPUT_FILE for reading * - write INPUT_FILE content to OUTPUT_FILE * - close OUTPUT_FILE and INPUT_FILE * - make OUTPUT_FILE read-only * - TODO: execute another instance of tester with option that * tell the second instance to do rest of tests * - TODO: dup stdin & stdout handles * - output OUTPUT_FILE to CON: * - open OUTPUT_FILE for reading * - TODO: force that to stdin * - open "CON:" (console) for writing * - TODO: force stdout there * - TODO: read stdin and write it to stdout * - for now, directly copy OUTPUT_FILE -> "CON:" * - TODO: restore stdin & stdout * - close OUTPUT_FILE & "CON:" * - output same file similarly to AUX: (serial) * - output same file similarly to PRN: (printer) * - NOTE: this fails for TOS v1.02 - v2.06, when done in a program * auto-started from the AUTO-folder or from DESKTOP.INF, unless * program waits long enough for OS to initialize 200hz timer * - truncate OUTPUT_FILE -> expected to fail * - make OUTPUT_FILE writable * - truncate OUTPUT_FILE * * As last step, output SUCCESS/FAILURE to MIDI to indicate whether * everything (except for the expected failure) succeeded. * * TOS tester will additionally verify that the pipeline worked fine * by checking that the device output file contents match what's * expected i.e. this whole pipeline works as expected: * INPUT_FILE --(OUTPUT_FILE)--> device * * And that OUTPUT_FILE is again empty after test. */ #include "common.h" int main() { clear_screen(); copy_file(INPUT_FILE, OUTPUT_FILE); write2console(OUTPUT_FILE); write2serial(OUTPUT_FILE); write2printer(OUTPUT_FILE); truncate_file(OUTPUT_FILE); write_midi(); wait_enter(); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/gemdos.prj000066400000000000000000000002211504763705000263140ustar00rootroot00000000000000; GEMDOS test program to signify that boot succeeded .C [-iinclude] gemdos.prg = ahcstart.o common.c (common.h) gemdos.c (common.h) ahccstdi.lib hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/minimal.c000066400000000000000000000013271504763705000261230ustar00rootroot00000000000000/* * Hatari - minimal.c * * Copyright (C) 2012 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Atari code for testing some basic Hatari device functionality * operations in co-operations with tos_tester.py Hatari test driver. * * This is a reduced version from gemdos.c which doesn't modify * the disk (image) contents. Unlike with GEMDOS HD emu, disk * image content modifications shouldn't need testing. */ #include "common.h" int main() { clear_screen(); write2console(INPUT_FILE); write2serial(INPUT_FILE); write2printer(INPUT_FILE); write_midi(); wait_enter(); return 0; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/disk/minimal.prj000066400000000000000000000002261504763705000264710ustar00rootroot00000000000000; trivial GEMDOS program to signify that boot succeeded .C [-iinclude] minimal.prg = ahcstart.o common.c (common.h) minimal.c (common.h) ahccstdi.lib hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/floppy/000077500000000000000000000000001504763705000247055ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/floppy/DESKTOP.INF000066400000000000000000000007631504763705000263620ustar00rootroot00000000000000#a000000 #b001000 #c7770007000600070055200505552220770557075055507703111302 #d #Z 01 A:\MINIMAL.PRG@ #E D8 11 #W 00 00 10 01 17 17 13 A:\*.*@ #W 00 00 08 0B 1D 0D 00 @ #W 00 00 0A 0F 1A 09 00 @ #W 00 00 0E 01 1A 09 00 @ #M 00 01 05 FF B FLOPPY@ @ #M 00 00 05 FF A FLOPPY@ @ #T 00 03 02 FF SHREDDER@ @ #F FF 04 @ *.*@ #D FF 01 @ *.*@ #P 03 04 @ *.*@ #G 03 FF *.APP@ @ #G 03 FF *.PRG@ @ #P 03 FF *.TTP@ @ #F 03 04 *.TOS@ @ #F 03 04 *.EXE@ @ #F 03 04 *.COM@ @ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/floppy/EMUDESK.INF000066400000000000000000000005241504763705000263410ustar00rootroot00000000000000#E 9A 07 #Z 01 A:\MINIMAL.PRG@ #W 00 00 02 06 26 0C 08 A:\*.*@ #W 00 00 02 08 26 0C 00 @ #W 00 00 02 0A 26 0C 00 @ #W 00 00 02 0D 26 0C 00 @ #M 00 00 01 FF A DISK A@ @ #M 01 00 01 FF B DISK B@ @ #F FF 28 @ *.*@ #D FF 02 @ *.*@ #G 08 FF *.APP@ @ #G 08 FF *.PRG@ @ #P 08 FF *.TTP@ @ #F 08 FF *.TOS@ @ #T 00 03 03 FF TRASH@ @ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/makefile000066400000000000000000000103401504763705000250720ustar00rootroot00000000000000# Makefile to build the Atari test program and other required files # for the TOS tester and to run it with minimal or fairly full # set of options. # default target is minimal 'test', everything else gets build as its deps all: test # targets without corresponding file .PHONY: clean pylint test test-all toolcheck # tos/ subdir should be either symlink to where you have your TOS # images, or real directory with symlinks to TOS images you want # to test with this. Or TOSDIR variable needs to point where they are: # TOSDIR=/path/to/toses/ make TOSDIR ?= tos # TOSDIR should contain at least this! BUILD_TOS = $(TOSDIR)/etos1024k.img # Hatari machine config for AHCC build BUILD_CONFIG = --layout uk --tos-res high --machine tt --tos $(BUILD_TOS) # where the code & test programs are DIR = disk # AHCC related files expected for building by disk/ahcc-* hconsole scripts AHCC_FILES = $(DIR)/ahcc_p.ttp $(DIR)/include $(DIR)/lib BUILD_TOOLS = $(AHCC_FILES) $(BUILD_TOS) # Building test programs requires: # - EmuTOS, AHCC and installed Hatari # # before running make: # - symlink etos1024k.img under $(TOSDIR)/ subdir # - symlink ahcc_p.ttp + its include & lib dirs under $(DIR)/ subdir # # -> otherwise toolcheck fails toolcheck: @which hatari @for i in $(BUILD_TOOLS); do \ if [ \! -e $$i ]; then \ echo "ERROR: required re-build file '$$i' missing!"; \ false; \ fi; \ done # build the full and minimal GEMDOS emu testers. GEMDOS_TEST = $(DIR)/GEMDOS.PRG GEMDOS_SCRIPT = $(DIR)/ahcc-gemdos GEMDOS_DEP = $(DIR)/gemdos.c $(DIR)/common.c $(DIR)/gemdos.prj # build the full GEMDOS tester $(GEMDOS_TEST): $(GEMDOS_DEP) $(MAKE) toolcheck $(RM) $(DIR)/*.O $(DIR)/*.MAP $(DIR)/*.tmp ../../tools/hconsole/hconsole.py $(GEMDOS_SCRIPT) -- $(BUILD_CONFIG) $(DIR) [ -f $(DIR)/GEMDOS.O ] && [ \! -f $(DIR)/ldfile.tmp ] # verify compiling & linking succeeded MINIMAL_TEST = $(DIR)/MINIMAL.PRG MINIMAL_SCRIPT = $(DIR)/ahcc-minimal MINIMAL_DEP = $(DIR)/minimal.c $(DIR)/common.c $(DIR)/minimal.prj # build the minimal GEMDOS tester $(MINIMAL_TEST): $(MINIMAL_DEP) $(MAKE) toolcheck $(RM) $(DIR)/*.O $(DIR)/*.MAP $(DIR)/*.tmp ../../tools/hconsole/hconsole.py $(MINIMAL_SCRIPT) -- $(BUILD_CONFIG) $(DIR) [ -f $(DIR)/MINIMAL.O ] && [ \! -f $(DIR)/ldfile.tmp ] # verify compiling & linking succeeded clean: $(RM) $(DIR)/*.O $(DIR)/*.MAP $(DIR)/*.tmp # create blank DD floppy image blank-a.st.gz: dd if=/dev/zero of=blank-a.st bs=1024 count=720 mformat -a -t 80 -h 2 -n 9 -i blank-a.st :: gzip blank-a.st # create 360KB (single side) test floppy that autoruns test program using *.INF file. # requires: # - mformat & mcopy from mtools bootdesk.st.gz: $(MINIMAL_TEST) $(DIR)/TEXT floppy/*.INF dd if=/dev/zero of=bootdesk.st bs=1024 count=360 mformat -a -t 80 -h 1 -n 9 -i bootdesk.st :: MTOOLS_NO_VFAT=1 mcopy -i bootdesk.st -spmv $+ :: $(RM) $@ gzip bootdesk.st # create 360KB (single side) test floppy that autoruns test program from auto/ # as very old TOS versions don't like the *.INF file autorun feature. # requires: # - mformat, mcopy & mmd from mtools bootauto.st.gz: $(MINIMAL_TEST) $(DIR)/TEXT dd if=/dev/zero of=bootauto.st bs=1024 count=360 mformat -a -t 80 -h 1 -n 9 -i bootauto.st :: MTOOLS_NO_VFAT=1 mmd -i bootauto.st ::AUTO MTOOLS_NO_VFAT=1 mcopy -i bootauto.st -pmv $(DIR)/TEXT :: MTOOLS_NO_VFAT=1 mcopy -i bootauto.st -pmv $(MINIMAL_TEST) ::AUTO $(RM) $@ gzip bootauto.st # optional 16MB HD image for EmuTOS/ACSI testing without HD drivers # converts floppy desktop infos for HD (A: -> C:) hd.img: $(MINIMAL_TEST) $(DIR)/TEXT floppy/*.INF mkdir tmp cp -a $(MINIMAL_TEST) $(DIR)/TEXT tmp/ for i in floppy/*.INF; do sed -e 's/A:/C:/g' < $$i > tmp/$${i##*/}; done ../../tools/atari-hd-image.sh 16 $@ LABEL tmp $(RM) -r tmp # requires: # - Building of floppies & GEMDOS_TEST to have succeeded # - Latest Hatari to be installed, or to run this with something like: # PATH=../../build/src:$PATH make test: blank-a.st.gz bootauto.st.gz bootdesk.st.gz $(GEMDOS_TEST) ./tos_tester.py --disks floppy,gemdos --graphics mono --memsizes 4 --machines ste $(BUILD_TOS) # run all default tests test-full: blank-a.st.gz bootauto.st.gz bootdesk.st.gz ./tos_tester.py $(TOSDIR)/*.img pylint: PYTHONPATH=../../tools/hconsole/ pylint tos_tester.py hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/readme.txt000066400000000000000000000152301504763705000253730ustar00rootroot00000000000000Files ----- Tools and data files: Makefile -- builds the test program and floppy & HD images containing it disk/* -- sources, binaries and input files for test program, directory is also used for GEMDOS HD emu testing floppy/* -- files to autostart test program from floppy tos_tester.py -- test driver, described below Generated test programs: GEMDOS.PRG -- test program GEMDOS HD MINIMAL.PRG -- test program for other drive types, i.e. ones where output file contents are not easily accessible Generated drive image files: bootauto.st.gz -- floppy image with the test files and test program run from AUTO/-folder, for TOS <1.04 bootdesk.st.gz -- floppy image with the test files and test program run from *.INF desktop file hd.img -- HD image with the the test program / files and a DOS partition table Other generated files: blank-a.st.gz -- blank floppy image to avoid TOS disk dialogs dummy.cfg -- Hatari config file generated by tos_tester.py output/* -- Test report and screenshots, temporary output files Other files and directories: readme.txt -- this text file tos/* -- TOSDIR in Makefile points here for your TOS images There's also "screenshot-report.sh" script to generate a HTML report out of the screenshots saved by TOS tester which will list missing tests and any differences in the produced screenshots. For that, you need "reference" directory to contain screenshots from an earlier, successful run of TOS tester. Usage ----- NOTE: TOS tester works *only* with Hatari versions that are built with raw MIDI device support, NOT with PortMidi support. This is because TOS tester communicates to the emulated test programs through FIFO file given to Hatari as MIDI input/output file, whereas PortMidi allows communication only to real MIDI devices. If you want to test Hatari version that isn't in your PATH, you need to give PATH for the Hatari binary you want to test, like this: PATH=../../build/src:$PATH make Before running that, tos/ subdirectory should have (symbolic links to) TOS images you want to test, at least EmuTOS etos1024k.img image. Or add this to above command: TOSDIR= Alternatively, you can call the TOS tester directly and specify the TOS images it should test: PATH=../../build/src:$PATH ./tos_tester.py To view the produced screenshots, either use ImageMagick: display output/*.png Or use the script that creates a HTML page with them and opens browser to view it. What TOS tester tests --------------------- These are the HW configuration combinations that TOS tester currently supports: * ST, MegaST, STE, MegaSTE, TT and Falcon machine types - EmuTOS 512k & 1024k are tested for all the machine types - EmuTOS 256k & TOS v2.x are tested with all machine types, except TT / Falcon - Rest of TOSes are tested only with a single machine type * TV, VGA, RGB and monochrome monitors and 1, 2 & 4 plane VDI modes * Different amounts of ST-RAM from 0 (0.5 MiB) to 14 MiB * Different amounts of TT-RAM from 0 to 1024 MiB * With and without GEMDOS harddisk directory emulation Test program is started either from a floppy or an emulated GEMDOS HD (directory) using *.INF file, or in case of TOS v1.00 - 1.02, from floppy auto/-folder. GEMDOS HD testing is done with more extensive gemdos.prg test program, floppy testing with minimal.prg test program which doesn't change the floppy content (to avoid its repository file update). * ACSI, IDE and SCSI interface testing with EmuTOS * Arbitrary boolean Hatari command line options specified with the "--bool" option You can use the command line options to specify which set of these is used and TOS tester will go through all combinations of them. See "tos_tester.py -h" output for examples. What to test ------------ For each Hatari release it would be good to test e.g. the following TOS versions: v1.00 de, v1.02 de, v1.04 de, v1.04 us, v1.62 de, v1.62 us, v2.06 de, v3.06 us, v4.04, etos192k, etos1024k[1] [1] Just the latest release of EmuTOS. And following monitor configurations: ST: tv, mono, vdi-1, vdi-4 STE: rgb, mono, vdi-1, vdi-4 TT: vga, mono, vdi-1, vdi-4 Falcon: rgb, mono, vga Memory configurations: ST: 0.5 & 2 MB STE: 1 & 4 MB TT: 2 & 10 MB ST-RAM, 0 & 32 MB TT-RAM, MMU on/off Falcon: 4 & 14 MB ST-RAM, 0 & 32 MB TT-RAM, MMU on/off And both with GEMDOS HD and just floppy. For EmuTOS, also ACSI (with ST/STe/TT), IDE (with Falcon) and SCSI (with Falcon/TT). Note that it's enough to give the whole set of HW configurations to TOS tester, it will automatically select a suitable subset of HW combinations to test, for each given TOS versions. Unless you're specifically testing FDC timings or memory detection compatibility, you can use --fast option to speed up TOS bootup a lot. Potential TODOs --------------- If all TOS versions support it, extend GEMDOS test program to test also: * starting another program * file redirection Testing a HD disk having also MiNT, with and without MMU. Current "screenshot-report.sh" script assumes that Hatari will always create identical screenshots for the same screen content. This might not be true if underlying libpng gets updated, so it would be better to have some e.g. SDL (pygame?) program that loads two images, compares their uncompressed content and either reports that, or shows the difference. Discarded ideas --------------- Add testing of ASCI, IDE and SCSI drives with normal TOS in addition to the GEMDOS HD and floppy tests. This isn't very straightforward because both need different drivers and therefore different disk images and the drivers either have issues with e.g. EmuTOS, or don't support all machines. Formatting and installing the drivers requires using interactive Atari programs, so these images cannot be automatically (re-)generated. (EmuTOS supports ACSI, IDE and SCSI directly, without any need for drivers. Both HDs with DOS (not Atari) partition table, and ones without partition table at all. That's why it can be already tested.) - Machine type specific test programs e.g. for: * ST color resolution overscan * STE blitter and overscan * TT FPU operations, could output e.g. speed * Falcon DSP operations Such programs also needs to have some static screen which doesn't automatically advance so that a screenshot can be taken of it. Alternatively, test program could be accompanied with debugger script(s) that stop the program at suitable point and take a screenshot. Tester for DMA sound output and comparison for the produced sound. IMHO these belong more to the regular Atari tests run with "make test". hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/screenshot-report.sh000077500000000000000000000077501504763705000274320ustar00rootroot00000000000000#!/bin/sh # # Script for screenshot report: # - compares images in current dir to references images # - reports images that are in reference subdir, # but not in current dir and images for which # there are no reference images. # # Copyright (C) 2012,2022 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. if [ -z "$(ls ./*.png ./*.bmp 2>/dev/null)" ]; then echo "ERROR: no screenshots!" echo echo "Run this from a directory with PNG/BMP screenshots" echo "to get HTML report showing them, which of them differ" echo "from the similarly named screenshots in 'reference'" echo "directory etc." exit 1 fi report="screenshot-report.html" refdir=reference difdir=difference title="Screenshot comparison report" cat > $report << EOF $title

$title

Contents:

EOF get_name () { name=$1 name=${name%.png} name=${name%.bmp} } missing="" mismatched="" matching="" new="" # images in reference dir, but not in current one for refimg in $(ls $refdir/*.png $refdir/*.bmp 2>/dev/null); do img=${refimg##*/} if [ ! -f "$img" ]; then missing="$missing $img" continue fi done echo "

Missing screenshots

" >> $report if [ -z "$missing" ]; then echo "None." >> $report else echo "
    " >> $report for img in $missing; do get_name "$img" echo "
  • $name" >> $report done echo "
" >> $report echo "Missing:" echo "$missing" fi # images that don't match the references ones for img in $(ls ./*.png ./*.bmp 2>/dev/null); do refimg=$refdir/$img if [ ! -f "$refimg" ]; then new="$new $img" continue fi if cmp "$img" "$refimg" > /dev/null; then matching="$matching $img" continue fi mismatched="$mismatched $img" done echo "

Mismatched screenshots

" >> $report if [ -z "$mismatched" ]; then echo "None." >> $report else echo "

Images which PNG/BMP screenshot file isn't exactly the same as the reference file" >> $report echo "(the image content could still be identical although the files aren't, due to changes e.g. in compression)." >> $report #echo "Left = new image, middle = difference, right = reference image:" >> $report echo "

Left = new image, right = reference image:" >> $report mkdir -p $difdir for img in $mismatched; do refimg=$refdir/$img get_name "$img" # disable compare usage, it gets stuck with TT mono images #diff=$difdir/$name.png #if compare "$img" "$refimg" "$diff"; then # echo "


$name
" >> $report #else echo "


$name
" >> $report #fi done fi echo "

Matching screenshots

" >> $report if [ -z "$matching" ]; then echo "None." >> $report else echo "
    " >> $report for img in $matching; do get_name "$img" echo "
  • $name" >> $report done echo "
" >> $report fi echo "

New screenshots

" >> $report if [ -z "$new" ]; then echo "None." >> $report else msg="

These are candidates to be moved to the '$refdir' directory:" echo "

$msg" >> $report for img in $new; do get_name "$img" echo "

$img
$name
" >> $report done echo "$msg" echo "$new" fi echo "" >> $report # show the report in the preferred HTMl viewer xdg-open $report hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/tosboot/tos_tester.py000077500000000000000000001072071504763705000261530ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2012-2025 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. """ Tester boots the given TOS versions under Hatari with all the possible combinations of the given machine HW configuration options, that are supported by the tested TOS version. Verification screenshot is taken at the end of each boot before proceeding to testing of the next combination. Screenshot name indicates the used combination, for example: etos1024k-falcon-rgb-gemdos-14M.png etos1024k-st-mono-floppy-1M.png NOTE: If you want to test the latest, uninstalled version of Hatari, you need to set PATH to point to your Hatari binary directory, like this: PATH=../../build/src:$PATH tos_tester.py If hconsole isn't installed to one of the standard locations (under /usr or /usr/local), or you don't run this from within Hatari sources, you also need to specify hconsole.py location with: export PYTHONPATH=/path/to/hconsole """ import getopt, os, signal, select, sys, time def add_hconsole_paths(): "add most likely hconsole locations to module import path" # prefer the devel version in Hatari sources, if it's found subdirs = len(os.path.abspath(os.curdir).split(os.path.sep))-1 for level in range(subdirs): f = level*(".." + os.path.sep) + "tools/hconsole/hconsole.py" if os.path.isfile(f): f = os.path.dirname(f) sys.path.append(f) print("Added local hconsole path: %s" % f) break sys.path += ["/usr/local/share/hatari/hconsole", "/usr/share/hatari/hconsole"] add_hconsole_paths() import hconsole def warning(msg): "output warning message" sys.stderr.write("WARNING: %s\n" % msg) def error_exit(msg): "output error and exit" sys.stderr.write("ERROR: %s\n" % msg) sys.exit(1) # ----------------------------------------------- class TOS: "class for TOS image information" # objects have members: # - path (string), given TOS image file path/name # - name (string), filename with path and extension stripped # - size (int), image file size, in kB # - etos (bool), is EmuTOS? # - version (int), TOS version # - memwait (int), how many secs to wait before memcheck key press # - fullwait (int), after which time safe to conclude boot to have failed # - machines (tuple of strings), which Atari machines this TOS supports def __init__(self, path): self.path, self.size, self.name = self._add_file(path) self.version, self.etos = self._add_version() self.memwait, self.fullwait, self.machines = self._add_info() def _add_file(self, img): "get TOS file size and basename for 'img'" if not os.path.isfile(img): raise AssertionError("'%s' given as TOS image isn't a file" % img) size = os.stat(img).st_size if size % 1024: raise AssertionError("image '%s' has invalid size of %g KB" % (img, size/1024.)) size /= 1024 if size not in (192, 256, 512, 1024): raise AssertionError("image '%s' size (%d KB) is not one of TOS sizes" % (img, size)) name = os.path.basename(img) name = name[:name.rfind('.')] return (img, size, name) def _add_version(self): "get TOS version and whether it's EmuTOS & supports GEMDOS HD" f = open(self.path, 'rb') f.seek(0x2, 0) version = (ord(f.read(1)) << 8) + ord(f.read(1)) # older TOS versions don't support autostarting # programs from GEMDOS HD dir with *.INF files f.seek(0x2C, 0) etos = (f.read(4) == b"ETOS") return (version, etos) def _add_info(self): "add TOS version specific info of supported machines etc, return timeouts & supported machines" name, size, version = self.name, self.size, self.version if self.etos: # EmuTOS 1024/512k, 256k and 192k versions have different machine support if size in (512, 1024): # startup screen on falcon 14MB is really slow info = (5, 10, ("st", "megast", "ste", "megaste", "tt", "falcon")) elif size == 256: info = (2, 8, ("st", "megast", "ste", "megaste")) elif size == 192: info = (0, 6, ("st", "megast")) else: raise AssertionError("'%s' image size %dkB isn't valid for EmuTOS" % (name, size)) # https://en.wikipedia.org/wiki/Atari_TOS elif version <= 0x100: # boots up really slow with 4MB info = (0, 20, ("st",)) elif version <= 0x104: info = (0, 12, ("st", "megast")) elif version <= 0x162: info = (0, 20, ("ste",)) elif version < 0x206: # TOS v2.x are slower with VDI mode than others info = (3, 14, ("ste", "megaste")) elif version == 0x206: # ST support added to TOS 2.x only after 2.05 info = (3, 14, ("st", "megast", "ste", "megaste")) elif version <= 0x306: # MMU slowdown is taken care of in prepare_test() info = (3, 20, ("tt",)) elif version <= 0x404: # no-IDE scan slowdown is taken care of in prepare_test() info = (3, 28, ("falcon",)) else: raise AssertionError("Unknown '%s' TOS version 0x%x" % (name, version)) if self.etos: print("'%s' is %dkB EmuTOS declaring itself TOS v%x (wait startup: %ds, rest: %ds)" % (name, size, version, info[0], info[1])) else: print("'%s' is normal TOS v%x (wait memcheck: %ds, rest: %ds)" % (name, version, info[0], info[1])) # 0: whether / how long to wait to dismiss memory test # 1: how long to wait until concluding test failed # 2: list of machines supported by this TOS version return info def supports_gemdos_hd(self): "whether TOS version supports Hatari's GEMDOS HD emulation" return self.version >= 0x0104 def supports_hdinterface(self, hdinterface): "whether TOS version supports monitor that is valid for given machine" # EmuTOS doesn't require drivers to access DOS formatted disks if self.etos: # IDE support is in EmuTOS since 0.9.0, SCSI since 0.9.10 if hdinterface != "acsi" and self.size < 512: return False return True # As ACSI (big endian) and IDE (little endian) images would require # different binary drivers on them and it's not possible to generate # such images automatically, testing ACSI & IDE images for normal # TOS isn't support. # # (And even with a driver, only TOS 4.x supports IDE.) print("NOTE: '%s' hard disk tests are supported only for EmuTOS" % hdinterface) return False def supports_monitor(self, monitortype, machine): "whether TOS version supports monitor that is valid for given machine" # other monitor types valid for the machine are # valid also for TOS that works on it if monitortype.startswith("vdi"): # VDI mode doesn't work properly until TOS v1.02 if self.version < 0x102: return False # sensible sized VDI modes don't work with TOS4 # (nor make sense with its Videl expander support) if self.version >= 0x400: return False if self.etos: # smallest EmuTOS image doesn't have any Falcon support if machine == "falcon" and self.size == 192: return False # 2-plane modes don't work properly with real TOS elif monitortype.endswith("2"): return False return True def supports_32bit_addressing(self, disk): "whether TOS version supports 32-bit addressing" if self.etos or (self.version >= 0x300 and self.version < 0x400): return True # Hatari patches TOS v4 for 32-bit support, but TOS v4 floppy access doesn't # work with TT-RAM as there's no _FRB cookie pointing to 64K ST-RAM DMA buffer if self.version >= 0x400 and disk != 'floppy': return True return False # ----------------------------------------------- def validate(args, full): "return set of members not in the full set and given args" return (set(args).difference(full), args) class Config: "Test configuration and validator class" # full set of possible options all_disks = ("floppy", "gemdos", "acsi", "ide", "scsi") all_graphics = ("mono", "rgb", "vga", "tv", "vdi1", "vdi2", "vdi4") all_machines = ("st", "megast", "ste", "megaste", "tt", "falcon") all_memsizes = (0, 1, 2, 4, 8, 10, 14) # bool options enabled by self.fast fast_opts = ("--fast-boot", "--fastfdc", "--timer-d") # defaults fast = False fixed = [] bools = [] disks = ("floppy", "gemdos", "scsi") graphics = ("mono", "rgb", "vga", "vdi1", "vdi4") machines = ("st", "ste", "megaste", "tt", "falcon") memsizes = (0, 4, 14) ttrams = (0, 32) def __init__(self, argv): longopts = ["bool=", "disks=", "fast", "graphics=", "help", "machines=", "memsizes=", "fixed=", "ttrams="] try: opts, paths = getopt.gnu_getopt(argv[1:], "b:d:fg:hm:s:o:t:", longopts) except getopt.GetoptError as error: self.usage(error) self.handle_options(opts) self.images = self.check_images(paths) print("\nTest configurations:") print("- machines = %s" % (self.machines,)) print("- graphics = %s" % (self.graphics,)) print("- disks = %s" % (self.disks,)) print("- RAM = %s" % (self.memsizes,)) print("- TTRAM = %s" % (self.ttrams,)) print("- bools = %s" % (self.bools,)) print("- fixed = '%s'\n" % ' '.join(self.fixed)) def check_images(self, paths): "validate given TOS images" images = [] for img in paths: try: images.append(TOS(img)) except AssertionError as msg: self.usage(msg) if not images: self.usage("no TOS image files given") return images def handle_options(self, opts): "parse command line options" unknown = None for opt, arg in opts: args = arg.split(",") if opt in ("-h", "--help"): self.usage() if opt in ("-f", "--fast"): self.fast = True elif opt in ("-b", "--bool"): self.bools = args elif opt in ("-o", "--fixed"): self.fixed = arg.split() elif opt in ("-d", "--disks"): unknown, self.disks = validate(args, self.all_disks) elif opt in ("-g", "--graphics"): unknown, self.graphics = validate(args, self.all_graphics) elif opt in ("-m", "--machines"): unknown, self.machines = validate(args, self.all_machines) elif opt in ("-s", "--memsizes"): try: args = [int(i) for i in args] except ValueError: self.usage("non-numeric memory sizes: %s" % arg) unknown, self.memsizes = validate(args, self.all_memsizes) elif opt in ("-t", "--ttrams"): try: args = [int(i) for i in args] except ValueError: self.usage("non-numeric TT-RAM sizes: %s" % arg) for ram in args: if ram < 0 or ram > 1024: self.usage("invalid TT-RAM (0-1024) size: %d" % ram) self.ttrams = args if unknown: self.usage("%s are invalid values for %s" % (list(unknown), opt)) def usage(self, msg=None): "output program usage information" name = os.path.basename(sys.argv[0]) # option value lists in directly CLI copy-pastable format disks, graphics, machines = [','.join(x) for x in (self.all_disks, self.all_graphics, self.all_machines)] memsizes = ','.join([str(x) for x in self.all_memsizes]) print(__doc__) print(""" Usage: %s [options] Options: \t-h, --help\tthis help \t-f, --fast\tspeed up boot with less accurate emulation: \t\t\t%s \t-d, --disks\t(%s) \t-g, --graphics\t(%s) \t-m, --machines\t(%s) \t-s, --memsizes\t(%s) \t-t, --ttrams\t(0-1024, in 4MiB steps) \t-b, --bool\t(boolean Hatari options to toggle in tests) \t-o, --fixed\t(hatari options to pass as-is) Multiple values for an option need to be comma separated. If option is given multiple times, last given value(s) are used. If some option isn't given, default list of values will be used for that. For example: %s \\ \t--disks gemdos \\ \t--machines st,falcon \\ \t--memsizes 0,4,14 \\ \t--ttrams 0,32 \\ \t--graphics mono,rgb \\ \t--bool --data-cache,--cpu-exact,--compatible,--mmu \\ \t--fixed "--cpulevel 4" """ % (name, self.fast_opts, disks, graphics, machines, memsizes, name)) if msg: print("ERROR: %s\n" % msg) sys.exit(1) def valid_disktype(self, machine, tos, disktype): "return whether given disk type is valid for given machine / TOS version" if disktype == "floppy": return True if disktype == "gemdos": return tos.supports_gemdos_hd() if machine in ("st", "megast", "ste", "megaste"): hdinterface = ("acsi",) elif machine == "tt": hdinterface = ("acsi", "scsi") elif machine == "falcon": hdinterface = ("ide", "scsi") else: raise AssertionError("unknown machine %s" % machine) if disktype in hdinterface: return tos.supports_hdinterface(disktype) return False def valid_monitortype(self, machine, tos, monitortype): "return whether given monitor type is valid for given machine / TOS version" if machine in ("st", "megast", "ste", "megaste"): monitors = ("mono", "rgb", "tv", "vdi1", "vdi2", "vdi4") elif machine == "tt": monitors = ("mono", "vga", "vdi1", "vdi2", "vdi4") elif machine == "falcon": monitors = ("mono", "rgb", "vga", "vdi1", "vdi2", "vdi4") else: raise AssertionError("unknown machine %s" % machine) if monitortype in monitors: return tos.supports_monitor(monitortype, machine) return False def valid_memsize(self, machine, memsize): "return True if given memory size is valid for given machine" # TT & MegaSTE can address only 10MB RAM due to VME, but # currently Hatari allows all RAM amounts on all HW if memsize > 10 and machine in ("megaste", "tt"): # TODO: return False when Hatari supports VME # (or disable VME when testing 14MB) return True # False return True def valid_ttram(self, machine, tos, ttram, disk): "return whether given TT-RAM size is valid for given machine" if ttram == 0: return True if machine in ("st", "megast", "ste", "megaste"): return False if machine in ("tt", "falcon"): if ttram < 0 or ttram > 1024: return False return tos.supports_32bit_addressing(disk) raise AssertionError("unknown machine %s" % machine) def validate_bools(self): "exit with error if given bool option is invalid" # Several bool options are left out of these lists, either because # they can be problematic for running of the tests themselves [1], # they should have no impact on emulation, only emulator [2], # or they are already part of other test options [3]. # # [1] --patch-tos, --midi # [2] --aspect, --borders, confirm-quit, --crop, --desktop, # --disable-video, --drive-led, --force-max, # --mousewarp, --resizable, --sound-sync # [3] --fast-forward, --vdi valid_opts = ( "--addr24", # TT/Falcon (or --cpulevel 3+) "--bios-intercept", "--blitter", # ST "--compatible", "--cpu-exact", "--data-cache", # TT/Falcon "--drive-a", "--drive-b", "--fast-boot", "--fastfdc", "--fpu-softfloat", # TT (or --fpu) "--gemdos-conv", # GEMDOS HD "--natfeats", "--mic", # Falcon "--mmu", # TT/Falcon (or --cpulevel 3+) "--timer-d", ) for option in self.bools: if option not in valid_opts: error_exit("bool option '%s' not in relevant options set:\n\t%s" % (option, valid_opts)) if self.fast and option in self.fast_opts: error_exit("bool option '%s' part of already enabled --fast options set:\n\t%s" % (option, self.fast_opts)) def valid_bool(self, machine, disk, ttram, option): "return True if given bool option is relevant for specified config" # assume "--cpulevel" would be used to raise ST/STE CPU to 030+ if machine in ("tt", "falcon") or "--cpulevel" in self.fixed: # TT-ram needs 32-bit addressing if ttram and option == "--addr24": return False elif option in ("--addr24", "--data-cache", "--mmu"): # 32-bit addressing, caches & MMU are relevant only for 030+ return False # invalid machine options if option == "--fpu-softfloat" and machine != "tt" and "--fpu" not in self.fixed: return False if option == "--blitter" and machine != "st": return False if option == "--mic" and machine != "falcon": return False # invalid disk options if option == "--gemdos-conv" and disk != "gemdos": return False if option == "--drive-a" and disk == "floppy": return False return True # ----------------------------------------------- def verify_file_match(srcfile, dstfile, identity): "return error string if sizes of given files don't match, and rename dstfile to identity" if not os.path.exists(dstfile): return "file '%s' missing" % dstfile dstsize = os.stat(dstfile).st_size srcsize = os.stat(srcfile).st_size if dstsize != srcsize: os.rename(dstfile, "%s.%s" % (dstfile, identity)) return "file '%s' size %d doesn't match file '%s' size %d" % (srcfile, srcsize, dstfile, dstsize) return None def verify_file_empty(filename, identity): "return error string if given file isn't empty, and rename file to identity" if not os.path.exists(filename): return "file '%s' missing" % filename size = os.stat(filename).st_size if size != 0: os.rename(filename, "%s.%s" % (filename, identity)) return "file '%s' isn't empty (%d bytes)" % (filename, size) return None def exit_if_missing(names): "exit if given (test input) file is missing" for name in names: if not os.path.exists(name): error_exit("test file '%s' missing") # how long to wait for invoked Hatari to open FIFO (= MIDI output file) FIFO_WAIT = 5 class Tester: "test driver class" output = "output" + os.path.sep report = output + "report.txt" # dummy Hatari config file to force suitable default options dummycfg = "dummy.cfg" defaults = [sys.argv[0], "--configfile", dummycfg] testprg = "disk" + os.path.sep + "GEMDOS.PRG" textinput = "disk" + os.path.sep + "TEXT" textoutput= "disk" + os.path.sep + "TEST" printout = output + "printer-out" serialout = output + "serial-out" fifofile = output + "midi-out" # oldest TOS versions don't support GEMDOS HD or auto-starting, # they need to use floppy and boot test program from AUTO/ bootauto = "bootauto.st.gz" bootdesk = "bootdesk.st.gz" floppyprg = "A:\\MINIMAL.PRG" # with EmuTOS, same image works for ACSI, SCSI and IDE testing, # whereas with real TOS, different images with different drivers # would be needed, potentially also for different machine types... hdimage = "hd.img" hdprg = "C:\\MINIMAL.PRG" results = None def __init__(self): "test setup initialization" self.cleanup_all_files() self.create_config() self.create_files() signal.signal(signal.SIGALRM, self.alarm_handler) hatari = hconsole.Hatari(["--confirm-quit", "no"]) if not hatari.winuae: error_exit("Hatari version available does not have WinAUE CPU core") hatari.kill_hatari() def alarm_handler(self, signum, dummy): "output error if (timer) signal came before passing current test stage" if signum == signal.SIGALRM: raise OSError("ERROR: timeout") else: print("ERROR: unknown signal %d received" % signum) raise AssertionError def create_config(self): "create Hatari default configuration file for testing" # override user's own config with default config to: # - get rid of the dialogs # - disable NatFeats # - disable fast boot options # - run as fast as possible (fast forward) # - use best CPU emulation options & 24-bit addressing # - disable ST blitter & (for now) MMU # - use dummy DSP & HW FPU types for speed # (OS doesn't use them, it just checks for their presence) # - enable TOS patching and disable cartridge # - limit Videl zooming to same sizes as ST screen zooming # - don't warp mouse on resolution changes # - get rid of borders in TOS screenshots # to make them smaller & more consistent # - disable GEMDOS emu by default # - disable GEMDOS HD write protection and host time use # - disable fast FDC & floppy write protection # - use empty floppy disk image to avoid TOS error when no disks # - enable both floppy drives, as double sided # - set printer output file # - disable serial in and set serial output file # - disable MIDI in, use MIDI out as fifo file to signify test completion dummy = open(self.dummycfg, "w") dummy.write(""" [Log] nAlertDlgLogLevel = 0 bConfirmQuit = FALSE bNatFeats = FALSE [System] bFastBoot = FALSE bPatchTimerD = FALSE bFastForward = TRUE bCompatibleCpu = TRUE bCpuDataCache = TRUE bCycleExactCpu = TRUE bAddressSpace24 = TRUE bBlitter = FALSE bMMU = FALSE nDSPType = 1 n_FPUType = 0 [ROM] bPatchTos = TRUE szCartridgeImageFileName = [Screen] nMaxWidth = 832 nMaxHeight = 576 bAllowOverscan = FALSE bMouseWarp = FALSE bCrop = TRUE [HardDisk] nGemdosDrive = 0 bUseHardDiskDirectory = FALSE bGemdosHostTime = FALSE nWriteProtection = 0 [Floppy] FastFloppy = FALSE nWriteProtection = 0 EnableDriveA = TRUE DriveA_NumberOfHeads = 2 EnableDriveB = TRUE DriveB_NumberOfHeads = 2 szDiskAFileName = blank-a.st.gz [Printer] bEnablePrinting = TRUE szPrintToFileName = %s [RS232] bEnableRS232 = TRUE szInFileName = szOutFileName = %s EnableSccB = TRUE SccBInFileName = SccBOutFileName = %s [Midi] bEnableMidi = TRUE sMidiInFileName = sMidiOutFileName = %s """ % (self.printout, self.serialout, self.serialout, self.fifofile)) dummy.close() def cleanup_all_files(self): "clean out any files left over from last run" for path in (self.fifofile, "grab0001.png", "grab0001.bmp"): if os.path.exists(path): os.remove(path) self.cleanup_test_files() def create_files(self): "create files needed during testing" if not os.path.exists(self.output): os.mkdir(self.output) if not os.path.exists(self.fifofile): os.mkfifo(self.fifofile) def get_screenshot(self, instance, identity): "save screenshot of test end result" instance.run("screenshot") if os.path.isfile("grab0001.png"): os.rename("grab0001.png", self.output + identity + ".png") elif os.path.isfile("grab0001.bmp"): os.rename("grab0001.bmp", self.output + identity + ".bmp") else: warning("failed to locate screenshot grab0001.{png,bmp}") def cleanup_test_files(self): "remove unnecessary files at end of test" for path in (self.serialout, self.printout): if os.path.exists(path): os.remove(path) def verify_output(self, identity): "do verification on all test output" ok = True # check file truncate if "gemdos" in identity: # file contents can be checked directly only with GEMDOS HD error = verify_file_empty(self.textoutput, identity) if error: print("ERROR: file wasn't truncated:\n\t%s" % error) ok = False # check serial output error = verify_file_match(self.textinput, self.serialout, identity) if error: print("ERROR: serial output doesn't match input:\n\t%s" % error) ok = False # check printer output error = verify_file_match(self.textinput, self.printout, identity) if error: print("ERROR: unexpected printer output:\n\t%s" % error) ok = False self.cleanup_test_files() return ok def wait_fifo(self, fifo, timeout): "wait_fifo(fifo) -> wait until fifo has input until given timeout" print("Waiting %ss for FIFO '%s' input..." % (timeout, self.fifofile)) sets = select.select([fifo], [], [], timeout) if sets[0]: print("...test program is READY, read its FIFO for test-case results:") try: # read can block, make sure it's eventually interrupted signal.alarm(timeout) line = fifo.readline().strip() signal.alarm(0) print("=> %s" % line) return (True, (line == "success")) except IOError: pass print("ERROR: TIMEOUT without FIFO input, BOOT FAILED") return (False, False) def open_fifo(self): "open FIFO for test program output" try: signal.alarm(FIFO_WAIT) # open returns after Hatari has opened the other # end of fifo, or when SIGALARM interrupts it fifo = open(self.fifofile, "r") # cancel signal signal.alarm(0) return fifo except IOError: print("ERROR: FIFO file open('%s') failed" % self.fifofile) print("(Hatari PortMidi support not disabled?)") return None def test(self, identity, testargs, memwait, testwait): "run single boot test with given args and waits" # Hatari command line options, don't exit if Hatari exits instance = hconsole.Main(self.defaults + testargs, False) fifo = self.open_fifo() if not fifo: print("ERROR: failed to get FIFO to Hatari!") self.get_screenshot(instance, identity) instance.run("kill") return (False, False, False, False) init_ok = True if memwait: print("Final start/memcheck wait: %ds" % memwait) # pass memory test time.sleep(memwait) instance.run("keypress %s" % hconsole.Scancode.Space) # wait until test program has been run and outputs something to fifo prog_ok, tests_ok = self.wait_fifo(fifo, testwait) # small extra wait to guarantee all test program output has # reached disk and screen (e.g. with frameskip) time.sleep(0.2) self.get_screenshot(instance, identity) if tests_ok: output_ok = self.verify_output(identity) else: output_ok = False # get rid of this Hatari instance instance.run("kill") return (init_ok, prog_ok, tests_ok, output_ok) def prepare_test(self, config, tos, machine, monitor, disk, memory, ttram, bools): "compose test ID and Hatari command line args, then call .test()" identity = "%s-%s-%s-%s-%dM-%dM" % (tos.name, machine, monitor, disk, memory, ttram) testargs = ["--tos", tos.path, "--machine", machine, "--memsize", str(memory)] if ttram: testargs += ["--addr24", "off", "--ttram", str(ttram)] else: testargs += ["--addr24", "on"] if monitor.startswith("vdi"): planes = monitor[-1] testargs += ["--vdi-planes", planes] if planes == "1": testargs += ["--vdi-width", "800", "--vdi-height", "600"] elif planes == "2": testargs += ["--vdi-width", "640", "--vdi-height", "480"] else: testargs += ["--vdi-width", "640", "--vdi-height", "400"] else: testargs += ["--monitor", monitor] memwait = tos.memwait testwait = tos.fullwait mmu = False for opt in bools: if opt[0] == '--mmu' and opt[1] == 'on': mmu = True identity += "-%s%s" % (opt[0].replace('-', ''), opt[1]) testargs += opt if mmu and machine in ("tt", "falcon"): # MMU doubles memory wait testwait += memwait memwait *= 2 if config.fixed: # pass-through Hatari options testargs += config.fixed if config.fast: for opt in config.fast_opts: testargs += [opt, "yes"] elif machine == "falcon" and disk != "ide": # Falcon IDE interface scanning when there's no IDE takes long testwait += 8 memwait += 8 if disk == "gemdos": exit_if_missing([self.testprg, self.textinput]) # use Hatari autostart, must be last thing added to testargs! testargs += [self.testprg] # HD supporting TOSes support also INF file autostart, so # with them test program can be run with the supplied INF # file. # # However, in case of VDI resolutions the VDI resolution # setting requires overriding the INF file... # # -> always specify directly which program to autostart # with --auto elif disk == "floppy": if tos.supports_gemdos_hd(): exit_if_missing([self.bootdesk]) testargs += ["--disk-a", self.bootdesk, "--auto", self.floppyprg] else: exit_if_missing([self.bootauto]) testargs += ["--disk-a", self.bootauto] # floppies are slower testwait += 3 elif disk == "acsi": exit_if_missing([self.hdimage]) testargs += ["--acsi", "0=%s" % self.hdimage, "--auto", self.hdprg] elif disk == "scsi": exit_if_missing([self.hdimage]) testargs += ["--scsi", "0=%s" % self.hdimage, "--auto", self.hdprg] elif disk == "ide": exit_if_missing([self.hdimage]) testargs += ["--ide-master", self.hdimage, "--auto", self.hdprg] else: raise AssertionError("unknown disk type '%s'" % disk) results = self.test(identity, testargs, memwait, testwait) self.results[tos.name].append((identity, results)) def run(self, config): "run all TOS boot test combinations" config.validate_bools() self.results = {} for tos in config.images: self.results[tos.name] = [] print("\n***** TESTING: %s *****\n" % tos.name) count = 0 for machine in config.machines: if machine not in tos.machines: continue for monitor in config.graphics: if not config.valid_monitortype(machine, tos, monitor): continue for memory in config.memsizes: if not config.valid_memsize(machine, memory): continue for disk in config.disks: if not config.valid_disktype(machine, tos, disk): continue for ttram in config.ttrams: if not config.valid_ttram(machine, tos, ttram, disk): continue bools = [b for b in config.bools if config.valid_bool(machine, disk, ttram, b)] bcount = len(bools) # from all bools off, to increasing bool enabling... for i in range(bcount): bool_mix = list(zip(bools, ("on",)*i + ("off",)*(bcount-i))) self.prepare_test(config, tos, machine, monitor, disk, memory, ttram, bool_mix) count += 1 # ...and finally all bools enabled (if any) bool_mix = list(zip(bools, ("on",)*bcount)) self.prepare_test(config, tos, machine, monitor, disk, memory, ttram, bool_mix) count += 1 if not count: warning("no matching configuration for TOS '%s'" % tos.name) self.cleanup_all_files() def summary(self): "summarize test results" cases = [0, 0, 0, 0] passed = [0, 0, 0, 0] tosnames = list(self.results.keys()) tosnames.sort() report = open(self.report, "w") report.write("\nTest report:\n------------\n") for tos in tosnames: configs = self.results[tos] if not configs: report.write("\n+ WARNING: no configurations for '%s' TOS!\n" % tos) continue report.write("\n+ %s:\n" % tos) for config, results in configs: # convert True/False bools to FAIL/pass strings values = [("FAIL", "pass")[int(r)] for r in results] report.write(" - %s: %s\n" % (config, values)) # update statistics for idx in range(len(results)): cases[idx] += 1 passed[idx] += results[idx] report.write("\nSummary of FAIL/pass values:\n") idx = 0 for line in ("Hatari init", "Test program running", "Test program test-cases", "Test program output"): passes, total = passed[idx], cases[idx] if passes < total: if not passes: result = "all %d FAILED" % total else: result = "%d/%d passed" % (passes, total) else: result = "all %d passed" % total report.write("- %s: %s\n" % (line, result)) idx += 1 report.write("\n") # print report out too print("\n--- %s ---" % self.report) report = open(self.report, "r") for line in report.readlines(): print(line.strip()) # ----------------------------------------------- def main(): "tester main function" info = "Hatari TOS bootup tester" print("\n%s\n%s\n" % (info, "-"*len(info))) # avoid global config file os.environ["HATARI_TEST"] = "boot" config = Config(sys.argv) tester = Tester() tester.run(config) tester.summary() if __name__ == "__main__": main() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/unit/000077500000000000000000000000001504763705000226625ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/unit/CMakeLists.txt000066400000000000000000000007101504763705000254200ustar00rootroot00000000000000 set(TEST_SOURCE_DIR ${CMAKE_SOURCE_DIR}/tests/debugger) include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/includes ${CMAKE_SOURCE_DIR}/src/debug) if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIR}) endif(ZLIB_FOUND) add_executable(test-file test-file.c ${CMAKE_SOURCE_DIR}/src/file.c) if(ZLIB_FOUND) target_link_libraries(test-file ${ZLIB_LIBRARY}) endif(ZLIB_FOUND) add_test(NAME unit-file COMMAND test-file) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/unit/test-file.c000066400000000000000000000061771504763705000247350ustar00rootroot00000000000000 #include #include #include #include #include #include "main.h" #include "file.h" #include "dialog.h" #include "zip.h" /* Fake functions that is required for linking */ bool DlgAlert_Query(const char *text) { return true; } uint8_t *ZIP_ReadFirstFile(const char *pName, long *pSize, const char * const pExts[]) { return NULL; } static void strcpy_path(char *dst, const char *src, int bufsize) { int i; for (i = 0; i < bufsize; i++) { if (src[i] == '/') dst[i] = PATHSEP; else dst[i] = src[i]; if (!dst[i]) break; } assert(i < bufsize); } static int strcmp_path(const char *ref, const char *expected) { char exbuf[256]; strcpy_path(exbuf, expected, sizeof(exbuf)); return strcmp(ref, exbuf); } static int Test_CleanFileName(const char *input, const char *expected) { char str[256]; printf("Testing File_CleanFileName(\"%s\")...\t", input); strcpy_path(str, input, sizeof(str)); File_CleanFileName(str); if (strcmp_path(str, expected) != 0) { puts("FAIL"); return 1; } puts("OK"); return 0; } static int Test_AddSlashToEndFileName(const char *input, const char *expected) { char str[256]; printf("Testing File_AddSlashToEndFileName(\"%s\")...\t", input); strcpy_path(str, input, sizeof(str)); File_AddSlashToEndFileName(str); if (strcmp_path(str, expected) != 0) { puts("FAIL"); return 1; } puts("OK"); return 0; } static int Test_DoesFileExtensionMatch(const char *input, const char *ext, bool bShouldMatch) { printf("Testing File_DoesFileExtensionMatch(\"%s\", \"%s\")...\t", input, ext); if (File_DoesFileExtensionMatch(input, ext) != bShouldMatch) { puts("FAIL"); return 1; } puts("OK"); return 0; } static int Test_MakeValidPathName(const char *input, const char *expected) { char str[256]; printf("Testing File_MakeValidPathName(\"%s\")...\t", input); strcpy_path(str, input, sizeof(str)); File_MakeValidPathName(str); if (strcmp_path(str, expected) != 0) { puts("FAIL"); return 1; } puts("OK"); return 0; } int main(int argc, char *argv[]) { int ret = 0; ret |= Test_CleanFileName("some-name/", "some-name"); ret |= Test_CleanFileName("/some-name", "/some-name"); ret |= Test_CleanFileName("/", "/"); ret |= Test_CleanFileName("", ""); ret |= Test_AddSlashToEndFileName("some-dir-name", "some-dir-name/"); ret |= Test_AddSlashToEndFileName("some-dir-name/", "some-dir-name/"); ret |= Test_AddSlashToEndFileName("/", "/"); ret |= Test_AddSlashToEndFileName("", ""); ret |= Test_DoesFileExtensionMatch("somedisk.msa", "MSA", true); ret |= Test_DoesFileExtensionMatch("somedisk.msa", ".MSA", true); ret |= Test_DoesFileExtensionMatch("somedisk.msa", ".MS", false); ret |= Test_DoesFileExtensionMatch("somedisk.msa", ".sa", false); ret |= Test_DoesFileExtensionMatch("somedisk.msa", "", true); ret |= Test_DoesFileExtensionMatch("", ".msa", false); ret |= Test_MakeValidPathName("/", "/"); ret |= Test_MakeValidPathName("/some-nonexisting-file-name", "/"); ret |= Test_MakeValidPathName("some-nonexisting-file-name/", "/"); ret |= Test_MakeValidPathName("", ""); return ret; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/xbios/000077500000000000000000000000001504763705000230275ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/xbios/CMakeLists.txt000066400000000000000000000010411504763705000255630ustar00rootroot00000000000000 set(testrunner ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh) foreach (lvl RANGE 0 4) add_test(NAME xbios-680${lvl}0 COMMAND ${testrunner} $ --cpulevel ${lvl}) endforeach(lvl) add_test(NAME xbios-68030-mmu COMMAND ${testrunner} $ --cpulevel 3 --mmu true) add_test(NAME xbios-68060 COMMAND ${testrunner} $ --cpulevel 6 --mmu false) add_test(NAME xbios-68060-mmu COMMAND ${testrunner} $ --cpulevel 6 --mmu true) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/xbios/run_test.sh000077500000000000000000000026631504763705000252400ustar00rootroot00000000000000#!/bin/sh if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then echo "Usage: $0 ..." exit 1; fi hatari=$1 shift if [ ! -x "$hatari" ]; then echo "First parameter must point to valid hatari executable." exit 1; fi; basedir=$(dirname "$0") testdir=$(mktemp -d) remove_temp() { rm -rf "$testdir" } trap remove_temp EXIT export HATARI_TEST=xbios export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy HOME="$testdir" $hatari --log-level fatal --sound off --cpuclock 32 --tos none \ --run-vbls 500 --bios-intercept on "$@" "$basedir/xbiostst.prg" \ 2> "$testdir/out.txt" << EOF c c EOF exitstat=$? if [ $exitstat -ne 0 ]; then echo "Running hatari failed. Status=${exitstat}." cat "$testdir/out.txt" exit 1 fi # Now check for expected strings: if ! grep -q "%101010.*#42.*2a" "$testdir/out.txt"; then echo "Test FAILED, missing '#42':" cat "$testdir/out.txt" exit 1 fi if ! grep -q "This is a Dbmsg test for a string with fixed size." \ "$testdir/out.txt"; then echo "Test FAILED, missing Dbmsg string with fixed size:" cat "$testdir/out.txt" exit 1 fi if ! grep -q "This is a Dbmsg test for a NUL-terminated string." \ "$testdir/out.txt"; then echo "Test FAILED, missing NUL-terminated Dbmsg string:" cat "$testdir/out.txt" exit 1 fi if ! grep -q "0x1234.*0xdeadc0de" "$testdir/out.txt"; then echo "Test FAILED, missing Dbmsg code 0x1234:" cat "$testdir/out.txt" exit 1 fi echo "Test PASSED." exit 0 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/xbios/xbiostst.prg000066400000000000000000000004501504763705000254170ustar00rootroot00000000000000`*o - ЭЭм"ҍ¼.A// Bg?<JNAO HzR?<NN\Hzd?<2?<?< NNO Hz?<?<?< NNO /<ޭ?<4?<?< NNO BgNAhatari-debug evaluate 23 + 19This is a Dbmsg test for a string with fixed size.This is a Dbmsg test for a NUL-terminated string.hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tests/xbios/xbiostst.s000066400000000000000000000032571504763705000251010ustar00rootroot00000000000000; Test Hatari's XBIOS functions (which can be enabled with --bios-intercept). ; Assemble this code with TurboAss. movea.l 4(SP),A5 ; Get pointer to basepage move.l $0C(A5),D0 ; Text segment length add.l $14(A5),D0 ; Data segment length add.l $1C(A5),D0 ; BSS segment length add.l #$0800,D0 ; Space for the stack move.l D0,D1 add.l A5,D1 and.l #-2,D1 movea.l D1,SP move.l D0,-(SP) move.l A5,-(SP) clr.w -(SP) move.w #$4A,-(SP) trap #1 ; Mshrink lea 12(SP),SP ; Test XBIOS 255 - Send Hatari control command pea hatcontrol(PC) move.w #255,-(SP) trap #14 addq.l #6,SP ; Test XBIOS 11 - the Atari debugger XBIOS call ; See: ; - http://dev-docs.atariforge.org/files/Atari_Debugger_1-24-1990.pdf ; - http://toshyp.atari.org/en/004013.html#Dbmsg ; Print string (with length encoded in msg_num), and invoke debugger pea dbmsg_start(PC) move.w #$F000+dbmsg_end-dbmsg_start,-(SP) move.w #5,-(SP) move.w #11,-(SP) trap #14 lea 10(SP),SP ; Print NUL-terminated string pea dbmsg_nul(PC) move.w #$F000,-(SP) move.w #5,-(SP) move.w #11,-(SP) trap #14 lea 10(SP),SP ; Print given value and invoke debugger move.l #$DEADC0DE,-(SP) move.w #$1234,-(SP) move.w #5,-(SP) move.w #11,-(SP) trap #14 lea 10(SP),SP clr.w -(SP) trap #1 ; Pterm0 DATA hatcontrol: DC.B "hatari-debug evaluate 23 + 19",0 dbmsg_start: DC.B "This is a Dbmsg test for a string with fixed size." dbmsg_end: dbmsg_nul: DC.B "This is a Dbmsg test for a NUL-terminated string.",0 EVEN END hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/000077500000000000000000000000001504763705000217015ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/CMakeLists.txt000066400000000000000000000041001504763705000244340ustar00rootroot00000000000000 add_subdirectory(hmsa) include(FindPython) if(Python_Interpreter_FOUND) add_subdirectory(hconsole) add_subdirectory(debugger) endif(Python_Interpreter_FOUND) install(PROGRAMS atari-convert-dir.py DESTINATION ${BINDIR} RENAME atari-convert-dir) install(PROGRAMS hatari-prg-args.sh DESTINATION ${BINDIR} RENAME hatari-prg-args) install(PROGRAMS atari-hd-image.sh DESTINATION ${BINDIR} RENAME atari-hd-image) install(PROGRAMS zip2st.sh DESTINATION ${BINDIR} RENAME zip2st) if(ENABLE_MAN_PAGES) add_custom_target(atari_convert_dir_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/atari-convert-dir.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/atari-convert-dir.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/atari-convert-dir.1 > ${CMAKE_CURRENT_BINARY_DIR}/atari-convert-dir.1.gz DEPENDS atari-convert-dir.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/atari-convert-dir.1.gz DESTINATION ${MANDIR}) add_custom_target(hatari_prg_args_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hatari-prg-args.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hatari-prg-args.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/hatari-prg-args.1 > ${CMAKE_CURRENT_BINARY_DIR}/hatari-prg-args.1.gz DEPENDS hatari-prg-args.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hatari-prg-args.1.gz DESTINATION ${MANDIR}) add_custom_target(atari_hd_image_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/atari-hd-image.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/atari-hd-image.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/atari-hd-image.1 > ${CMAKE_CURRENT_BINARY_DIR}/atari-hd-image.1.gz DEPENDS atari-hd-image.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/atari-hd-image.1.gz DESTINATION ${MANDIR}) add_custom_target(zip2st_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/zip2st.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zip2st.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/zip2st.1 > ${CMAKE_CURRENT_BINARY_DIR}/zip2st.1.gz DEPENDS zip2st.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/zip2st.1.gz DESTINATION ${MANDIR}) endif(ENABLE_MAN_PAGES) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/atari-convert-dir.1000066400000000000000000000025421504763705000253200ustar00rootroot00000000000000.TH "atari-convert-dir" "1" "2015-04-26" "Hatari" "Hatari utilities" .SH NAME atari\-convert\-dir \- helper for creating Atari compatible disk images .SH SYNOPSIS .B atari\-convert\-dir .RI sourcedir .RI targetdir .SH DESCRIPTION Copies directories and files from source directory to target directory so that their names are clipped to 8+3 characters in Atari GEMDOS compatible way. Files on a disk image that is created from the converted target directory, should work as well as with Hatari GEMDOS HD emulation. .PP This helper is used directly by \fIatari\-hd\-image\fP tool and its use is also recommended with \fIzip2st\fP tool content (because Mtools \fImcopy\fP doesn't convert long file names correctly for Atari). .SH SEE ALSO .IR atari\-hd\-image (1), .IR zip2st (1), .IR hatari (1), .IR mcopy (1) .SH "AUTHOR" Written by Eero Tamminen . .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/atari-convert-dir.py000077500000000000000000000065031504763705000256140ustar00rootroot00000000000000#!/usr/bin/python3 """ Script to copy Atari files with (potentially) too long names from source directory into target directory, using in latter file names clipped to 8+3 characters. Clipping is done like Atari GEMDOS functions would do it, so that those file names can then be moved/copied to Atari media from another OS. If original long names work with Hatari GEMDOS HD emulation, the clipped names should work with normal TOS on real harddisk (image)! """ import sys, os, shutil def debug(msg): sys.stderr.write("%s\n" % msg) def error_exit(msg): name = os.path.basename(sys.argv[0]) debug("\nUsage: %s " % name) debug(__doc__) debug("ERROR: %s!\n" % msg) sys.exit(1) newnames = {} def check_conflicts(srcdir, dstdir): # how much to clip from paths srcskip = len(srcdir)+1 dstskip = len(dstdir)+1 print("\nNames that aren't unique:") conflicts = False for key,names in newnames.items(): if len(names) > 1: conflicts = True print("- %s: %s" % (key[dstskip:], [name[srcskip:] for name in names])) if not conflicts: print("- none, all OK!") def hash_names(original, newname): if newname not in newnames: newnames[newname] = [] newnames[newname].append(original) def clip_name(name): dot = name.rfind('.') if dot >= 0: base = name[:dot] ext = name[dot+1:] name = "%s.%s" % (base[:8], ext[:3]) else: name = name[:8] # TODO: map non-ASCII characters return name.upper() def dirs_last(path): # order first by type, then (case-insensitively) by name return (os.path.isdir(path), path.upper()) def convert_dir(srcdir, dstdir): print("\n%s/ -> %s/:" % (srcdir, dstdir)) try: os.mkdir(dstdir) except OSError: debug("ERROR: directory creation failed, name conflict?") return # directory sorting requires full names dircontents = [os.path.join(srcdir, x) for x in os.listdir(srcdir)] for original in sorted(dircontents, key=dirs_last): origname = os.path.basename(original) clipname = clip_name(origname) newname = os.path.join(dstdir, clipname) hash_names(original, newname) if os.path.isdir(original): convert_dir(original, newname) else: print("- %s -> %s" % (origname, clipname)) try: shutil.copyfile(original, newname) except IOError: debug(" ERROR: file copy failed, non-readable file, or name conflict (with read-only file?)") continue shutil.copystat(original, newname) def main(args): if len(args) != 3: error_exit("too few arguments") srcdir = args[1] dstdir = args[2] if not os.path.isdir(srcdir): error_exit("source directory '%s' doesn't exist" % srcdir) if os.path.isdir(dstdir): error_exit("target directory '%s' exists, remove it to continue" % dstdir) if srcdir[-1] == os.path.sep: srcdir = srcdir[:-1] if dstdir[-1] == os.path.sep: dstdir = dstdir[:-1] if dstdir.startswith(srcdir+os.path.sep): error_exit("target directory '%s' is inside source directory '%s'" % (srcdir, dstdir)) convert_dir(srcdir, dstdir) check_conflicts(srcdir, dstdir) if __name__ == '__main__': main(sys.argv) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/atari-hd-image.1000066400000000000000000000037771504763705000245520ustar00rootroot00000000000000.TH "atari-hd-image" "1" "2015-04-26" "Hatari" "Hatari utilities" .SH NAME atari\-hd\-image \- tool for creating a harddisk image for use with Hatari .SH SYNOPSIS .B atari\-hd\-image .RI size .RI [filename] .RI "[partition name]" .RI [directory] .SH DESCRIPTION Create an ACSI/IDE harddisk image for Hatari with a single Atari compatible DOS FAT partition (using sfdisk, mkdosfs, atari\-convert\-dir, mcopy, dd and python). .SH OPTIONS .TP .B size Harddisk image size in megabytes, 8-512. 512MB is largest partition size supported by TOS versions before v4.x and by mkdosfs (for Atari compatible partition formatting). .TP .B filename Name for the harddisk image (default: hd.img) .TP .B partition name Name for the single partition (default: DOS) .TP .B directory directory for initial content copied to the image. \fIatari\-convert\-dir\fP tool is used to clip long file names to 8+3 size required by FAT and Atari TOS. If resulting file names aren't unique, you get warning(s), but script continues .SH EXAMPLES .TP 16MB 'hd.img' HD image: .B atari\-hd\-image 16 .TP 8MB image with partition named 'TEST', and files from content/: .B atari\-hd\-image 8 8mb-disk.img TEST content/ .SH SEE ALSO .IR atari\-convert\-dir (1), .IR hmsa (1), .IR zip2st (1), .IR hatari (1), .IR mkdosfs (1), .IR sfdisk (1), .IR dd (1) .SH "AUTHOR" Written by Eero Tamminen . .PP This manual page was written by Teemu Hukkanen for the Debian project and later updated by Eero Tamminen for the newer versions of Hatari. .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/atari-hd-image.sh000077500000000000000000000177161504763705000250250ustar00rootroot00000000000000#!/bin/sh # script for creating a compatible DOS HD image for Hatari with # a single FAT16 partition of given size, with given contents # defaults for disk attributes diskfile=hd.img # HD image filename partname=DOS # partition name # no args or first arg has non-digit characters? if [ $# -lt 1 ] || [ -n "$(echo "$1"|tr -d 0-9)" ]; then name=${0##*/} echo echo "usage: $name [filename] [partition name] [directory]" echo echo "Create an ACSI/IDE harddisk image for Hatari with a single Atari" echo "compatible DOS partition. Arguments are (defaults in parenthesis):" echo "- size: harddisk image size in megabytes, 8-512" echo "- filename: name for the harddisk image ($diskfile)" echo "- partition name: name for that single partition ($partname)" echo "- directory: directory for initial content copied to the image" echo echo "For example:" echo "- 16MB '$diskfile' HD image:" echo " $name 16" echo "- 8MB image with 'TEST' partition having files from content/:" echo " $name 8 8mb-disk.img TEST content/" echo exit 1 fi # sfdisk/mkdosfs reside in /sbin PATH=/sbin:$PATH export PATH # check tools if [ -z "$(which mkdosfs)" ] || [ -z "$(which python3)" ]; then echo "ERROR: either mkdosfs or python3 missing!" exit 1 fi # check disk size disksize=$1 if [ "$disksize" -lt 5 ]; then echo "ERROR: disk size needs to be at least 5 (MB) to work properly." exit 1 fi if [ "$disksize" -gt 512 ]; then echo "ERROR: mkdosfs supports Atari compatible partitions only up to 512 MB." exit 1 fi # check optional arguments if [ -n "$2" ]; then diskfile=$2 fi if [ -n "$3" ]; then partname=$3 fi # check content convertdir="" if [ -n "$4" ]; then contentdir=${4%/} if [ ! -d "$contentdir" ]; then echo "ERROR: given content directory doesn't exist!" exit 1 fi contentsize=$(du -ks "$contentdir" | awk '{printf("%d", $1/1024)}') if [ "$contentsize" -ge "$disksize" ]; then echo "ERROR: '$contentdir' directory contents ($contentsize MB) don't fit to given image size ($disksize MB)!" exit 1 fi # name conversion script should be in same dir as this script, or in PATH convert=${0%/*}/atari-convert-dir.py if [ ! -x "$convert" ]; then if [ -z "$(which atari-convert-dir)" ]; then echo "ERROR: $convert script for file name conversion missing!" exit 1 fi convert=atari-convert-dir fi convertdir=$contentdir.converted if [ -z "$(which mcopy)" ]; then echo "ERROR: mcopy (from Mtools) missing!" exit 1 fi fi # don't overwrite files by accident if [ -f "$diskfile" ]; then echo "ERROR: given harddisk image already exits. Give another name or remove it:" echo " rm $diskfile" exit 1 fi # disk geometry skip=0 # alignment / "padding" sectors between MBR before partition diskheads=16 tracksectors=32 # same as used by mkdosfs sectorsize=512 # partition size in sectors: # 16*32*512 is 1/4MB -> multiply by 4 to get number of required sectors partsectors=$((4*disksize*diskheads*tracksectors)) # temporary files tmppart=$diskfile.part # ------------------------------------------------------------------ error="premature script exit" # script exit/error handling exit_cleanup () { echo if [ -z "$error" ]; then echo "$step) Cleaning up..." else echo "ERROR: $error" echo echo "cleaning up..." if [ -f "$diskfile" ]; then echo "rm -f $diskfile" rm -f "$diskfile" fi fi if [ -f "$tmppart" ]; then echo "rm -f $tmppart" rm -f "$tmppart" fi if [ -n "$convertdir" ] && [ -d "$convertdir" ]; then echo "rm -rf $convertdir" rm -rf "$convertdir" fi echo "Done." } trap exit_cleanup EXIT # ------------------------------------------------------------------ echo step=1 echo "$step) Create DOS Master Boot Record / partition table..." # See: # - http://en.wikipedia.org/wiki/Master_boot_record # - http://en.wikipedia.org/wiki/Cylinder-head-sector # - http://en.wikipedia.org/wiki/File_Allocation_Table#Boot_Sector # For DOS MBR, the values are little endian. # ----------- python3 << EOF #!/usr/bin/python3 mbr = bytearray(512) def set_long(idx, value): mbr[idx+0] = value & 0xff mbr[idx+1] = value >> 8 & 0xff mbr[idx+2] = value >> 16 & 0xff mbr[idx+3] = value >> 24 def set_word(idx, value): mbr[idx] = value & 0xff mbr[idx+1] = value >> 8 & 0xff def set_CHS(idx, values): c, h, s = values print("CHS: %3d,%3d,%3d @ $%x" % (c,h,s,idx)) mbr[idx] = h mbr[idx+1] = (s & 0x3F) | ((c >> 2) & 0xC0) mbr[idx+2] = c & 0xFF def LBA2CHS(lba): c = lba // ($tracksectors * $diskheads) h = (lba // $tracksectors) % $diskheads s = (lba % $tracksectors) + 1 return (c,h,s) # disk size sectors = 1 + $skip + $partsectors if sectors < 65536: set_word(0x13, sectors) set_long(0x20, sectors) parttype=0x4 else: set_long(0x20, sectors) parttype=0x6 # reserved sectors = MBR mbr[0x0E] = 1 # CHS information set_word(0x0B, $sectorsize) mbr[0x0D] = 2 # sectors / cluster set_word(0x18, $tracksectors) set_word(0x1A, $diskheads) # non-removable disk mbr[0x15] = 0xF8 mbr[0x24] = 0x80 # partition size in sectors partsectors = $partsectors - 1 # first partition takes all offset = 0x1BE mbr[offset] = 0x80 # bootable mbr[offset+4] = parttype # partition start & sector count in LBA set_long(offset + 0x08, 1) set_long(offset + 0x0C, partsectors) # partition start & end in CHS set_CHS(offset + 1, LBA2CHS(1)) set_CHS(offset + 5, LBA2CHS(partsectors)) # 3 last partitions are empty nextpart = partsectors + 1 for i in (1,2,3): offset += 0x10 set_long(offset + 0x08, nextpart) set_long(offset + 0x0C, 0) set_CHS(offset + 1, LBA2CHS(nextpart)) set_CHS(offset + 5, LBA2CHS(nextpart)) # MBR signature mbr[0x1FE] = 0x55 mbr[0x1FF] = 0xAA open("$diskfile", "wb").write(bytes(mbr)) EOF # ----------- od -t x1 "$diskfile" # ------------------------------------------------------------------ echo step=$((step+1)) echo "$step) Create an Atari TOS compatible DOS partition..." # mkdosfs keeps the sector count below 32765 when -A is used by increasing # the logical sector size (this is for TOS compatibility, -A guarantees # also 2 sectors / cluster and Atari serial number etc). Mtools barfs # if partition size doesn't divide evenly with its track size. Determine # suitable cluster count & corresponding track size and align (decrease) # the file system sector count accordingly. tracksize=32 clustertmp=$((partsectors/2)) echo "Sectors: $partsectors, sectors/track: $tracksize, clusters: $clustertmp" while [ $clustertmp -gt 32765 ]; do clustertmp=$((clustertmp/2)) tracksize=$((tracksize*2)) echo "Doubling sector size as >32765 clusters -> $clustertmp clusters" done sectors=$((partsectors/tracksize)) sectors=$((sectors*tracksize)) kilobytes=$((sectors/2)) if [ $sectors -ne $partsectors ]; then echo "Align sector count with clusters/sectors/track: $partsectors -> $sectors ($kilobytes kB)" fi echo "mkdosfs -A -F 16 -n $partname -C $tmppart $kilobytes" mkdosfs -A -F 16 -n "$partname" -C "$tmppart" $kilobytes # ------------------------------------------------------------------ if [ -n "$contentdir" ]; then echo step=$((step+1)) echo "$step) Clip/convert long file names to Atari compatible 8+3 format..." echo "$convert $contentdir $convertdir" if ! $convert "$contentdir" "$convertdir"; then error="conversion failed." exit 2 fi echo step=$((step+1)) # copy contents of given directory to the new partition echo "$step) Copy the initial content to the partition..." echo "export MTOOLS_NO_VFAT=1" export MTOOLS_NO_VFAT=1 echo "mcopy -i $tmppart -spmv $convertdir/* ::" if ! mcopy -i "$tmppart" -spmv "$convertdir"/* ::; then error="mcopy failed." exit 2 fi echo "rm -rf $convertdir" rm -rf "$convertdir" fi # ------------------------------------------------------------------ echo step=$((step+1)) # copy the partition into disk echo "$step) Copy the partition to disk image..." echo "dd if=$tmppart of=$diskfile bs=512 seek=$((1+skip)) count=$sectors" dd if="$tmppart" of="$diskfile" bs=512 seek=$((1+skip)) count=$sectors step=$((step+1)) # cleanup is done by exit_cleanup() trap error="" hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/000077500000000000000000000000001504763705000234655ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/CMakeLists.txt000066400000000000000000000020301504763705000262200ustar00rootroot00000000000000 include_directories(${CMAKE_SOURCE_DIR}/src/cpu) add_executable(gst2ascii gst2ascii.c) install(TARGETS gst2ascii RUNTIME DESTINATION ${BINDIR}) install(PROGRAMS hatari_profile.py DESTINATION ${BINDIR} RENAME hatari_profile) if(ENABLE_MAN_PAGES) add_custom_target(gst2ascii_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gst2ascii.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gst2ascii.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/gst2ascii.1 > ${CMAKE_CURRENT_BINARY_DIR}/gst2ascii.1.gz DEPENDS gst2ascii.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/gst2ascii.1.gz DESTINATION ${MANDIR}) add_custom_target(hatari_profile_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hatari_profile.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hatari_profile.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/hatari_profile.1 > ${CMAKE_CURRENT_BINARY_DIR}/hatari_profile.1.gz DEPENDS hatari_profile.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hatari_profile.1.gz DESTINATION ${MANDIR}) endif(ENABLE_MAN_PAGES) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/README.txt000066400000000000000000000031431504763705000251640ustar00rootroot00000000000000This directory contains helper & post-processing tools and legacy scripts to be used with Hatari debugger symbol handling and profiling. They are... Scripts for converting different ASCII symbols (name/address) formats to a nm-style ASCII symbols format understood by the Hatari debugger: - ahcc-symbols-convert.sh - devpac3-symbols-convert.sh - dsp-lod-symbols-convert.sh NOTE: Normally you should set AHCC & DevPac to generate binaries with debug symbols (as explained in debugger documentation) instead of using these scripts. DSP LOD symbols you need only when profiling Falcon DSP LOD code. Tool for outputting binary symbols data from Atari programs as ASCII, to edit it before feeding it to Hatari debugger: - gst2ascii.1 - gst2ascii.c Typical use-cases for wanting this are profiling related: * Manual (or automated) removal of symbols that make profiler output messy, or slow it down too much, such as symbols for loop entry points * Binary is missing a symbol(s) / address(es) you're interested about, and you want to add it manually (e.g. old MiNTlib builds had stripped out symbols for time related functionality that took a lot of cycles) Hatari profiler output post-processing and call-graph generation: - hatari_profile.1 - hatari_profile.py Post-processing tool providing analysis data for optimizing I/O waits: - hatari_spinloop.py (= what you can do after you've optimized everything else profiler tells about CPU & DSP usage.) Legacy script for cleaning irrevant symbols out of 'nm' tool output (before gst2ascii and Hatari debugger supported binary symbols data): - nm-symbols-cleanup.sh hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/ahcc-symbols-convert.sh000077500000000000000000000016711504763705000300730ustar00rootroot00000000000000#!/bin/sh # # script to convert AHCC "linker -p" output for Hatari debugger. usage () { name=${0##*/} echo echo "usage: $name " echo echo "convert AHCC 'linker -p' symbol address map output to 'nm'" echo "format understood by the Hatari debugger 'symbols' command." echo echo "For example:" echo " $name etos1024k.map > etos1024k.sym" echo echo "ERROR: $1!" echo exit 1 } if [ $# -ne 1 ]; then usage "incorrect number of arguments" fi if [ ! -f "$1" ]; then usage "given '$1' address map file not found" fi # output only lines that have both address & symbol name, # remove "[ size]" stuff that confuses awk field parsing, # and convert those with awk to the "nm" format: #
grep -E ' (TEXT|DATA|BSS) ' "$1" |\ grep -E -v '(TEXT|DATA|BSS)[[:space:]]*$' |\ sed 's/\[[^]]*\]//' | awk ' /^ .* TEXT / { print $1, "T", $4 } /^ .* DATA / { print $1, "D", $4 } /^ .* BSS / { print $1, "B", $4 }' hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/devpac3-symbols-convert.sh000077500000000000000000000025541504763705000305230ustar00rootroot00000000000000#!/bin/sh # # script to convert Devpac v3 symbol table for Hatari debugger. # # 2013 (C) Eero Tamminen, licensed under GPL v2+ usage () { name=${0##*/} echo echo "Usage: $name " echo echo "Convert Devpac v3 symbol table listing (enabled" echo "in Listings options) to format understood by" echo "the Hatari debugger 'symbols' command." echo echo "For example:" echo " $name FOOBAR.SYM > foobar.syms" echo echo "ERROR: $1!" echo exit 1 } if [ $# -ne 1 ]; then usage "incorrect number of arguments" fi if [ ! -f "$1" ]; then usage "given '$1' symbol file not found" fi # parse symbol information at the end of the file awk ' /^[0-9A-F]+ [BDT] R / { print $1, $2, $4; next } /^[0-9A-F]+ F[DEF].R / { if ($2 == "FD.R") type = "D"; else if ($2 == "FE.R") type = "B"; else if ($2 == "FF.R") type = "T"; print $1, type, $3; next } ' "$1" | sort # parse code listing # - has problem that in Devpac output the offsets in # the code listing part don't take includes into # account i.e. they seem wrong # # works by: # - removing columns that confuse rest command line # - removing macro etc symbols at zero address # - removing (optional) ':' from symbol end # - matching addresses & symbol names and print # them in 'nm' order # #cut -b 7,9-17,39- $1 | grep -v 'T 00000000 ' | tr -d : |\ # awk '/^[TBD] [0-9A-F]+ [._a-zA-Z0-9]/ {print $2,$1,$3}' hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/dsp-lod-symbols-convert.sh000077500000000000000000000015201504763705000305300ustar00rootroot00000000000000#!/bin/sh # # script to convert DSP LOD file table for Hatari debugger. # # 2013 (C) Eero Tamminen, licensed under GPL v2+ usage () { name=${0##*/} echo echo "Usage: $name " echo echo "Convert P-memspace symbols in DSP LOD file" echo "(output by CLDLOD.TTP) to format understood" echo "by the Hatari debugger 'dspsymbols' command." echo echo "For example:" echo " $name FOOBAR.LOD > foobar.sym" echo echo "ERROR: $1!" echo exit 1 } if [ $# -ne 1 ]; then usage "incorrect number of arguments" fi if [ ! -f "$1" ]; then usage "given '$1' symbol file not found" fi # remove CR & parse symbol information at the end of the file tr -d '\r' < "$1" | awk ' BEGIN { show = 0 } /^_SYMBOL P/ { show = 1; next } /^_SYMBOL / { show = 0; next } /^[_0-9a-zA-Z]+ * I [0-9A-F]+/ { if (show) { printf("%s T %s\n", $3, $1); } }' hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/gst2ascii.1000066400000000000000000000060521504763705000254420ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "GST2ASCII" "1" "2023-09-03" "Hatari" "Hatari utilities" .SH "NAME" gst2ascii \- Filter and output Atari program symbol table as ASCII .SH "SYNOPSIS" .B gst2ascii .RI [options] .RI .SH "DESCRIPTION" \fIgst2ascii\fP reads symbol table from the given Atari program, and outputs it in the ASCII format understood by the Hatari debugger and its profiler data post-processor. Symbol tables in the traditional DRI/GST, GNU a.out and new GCC PRGELF formats are supported. .PP All symbol addresses output by the tool are TEXT relative, so when loading them in Hatari debugger, one needs to give just TEXT as offset for the 'symbols' command. .PP There are some options for filtering the symbol table content, and the resulting ASCII output is easy to edit also by hand, in case other symbols (e.g. loop labels) need to be removed from it, or missing function symbols need to be added to it. .SH "OPTIONS" .TP \fB-a\fP Filter absolute (= value, not address) symbols out from the output. .TP \fB-b\fP Filter BSS symbols out from the output. .TP \fB-d\fP Filter DATA symbols out from the output. .TP \fB-f\fP Filter (object) file/path name symbols out from the output. .TP \fB-g\fP Filter GCC internal (object) symbols out from the output. .TP \fB-l\fP Filter local (.L*) symbols out. Normally they are useless because they do not have names, just numbers. .TP \fB-n\fP Sort symbol output by symbol names, not by their addresses. .TP \fB-s\fP Filter out symbols with duplicate addresses from the output. .TP \fB-t\fP Filter TEXT symbols out from the output. .TP \fB-w\fP Filter weak symbols out from the output. .PP By default, same filter options are enabled as in Hatari debugger. Prefixing option letter with '+' instead of '-', keeps the indicated symbol type, if that type was disabled in the tool defaults (or by an option given earlier on the command line). .SH "NOTES" If you have problems, try with 'nm -n ' instead (Atari native or cross-compiler version). If \fInm\fP works, but \fIgst2ascii\fP does not, please report a bug on it. .SH "EXAMPLES" Save 'program.prg' symbol table in ASCII format with local symbols filtered out: .br gst2ascii -l program.prg > program.sym .PP Load generated ASCII symbols file in Hatari debugger: .br symbols program.sym TEXT DATA BSS .SH "SEE ALSO" .IR hatari (1), .IR hatari_profile (1) .SH "AUTHOR" Written by Eero Tamminen . .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/gst2ascii.c000066400000000000000000000161021504763705000255210ustar00rootroot00000000000000/* * Hatari - gst2ascii.c * * Copyright (C) 2013-2025 by Eero Tamminen * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * Convert DRI/GST and a.out format symbol table in a binary into ASCII symbols * file accepted by Hatari debugger and its profiler data post-processor. * This will also allow manual editing of the symbol table (removing irrelevant * labels or adding missing symbols for functions). */ #include #include #include #include #include #include #if defined(__MINT__) /* assume MiNT/lib is always big-endian */ # define be_swap16(x) ((uint16_t)(x)) # define be_swap32(x) ((uint32_t)(x)) #else # include "maccess.h" #endif #include #include "../../src/debug/a.out.h" #define ARRAY_SIZE(x) (int)(sizeof(x)/sizeof(x[0])) #include "../../src/debug/symbols-common.c" /* ------------------ options & usage ------------------ */ #ifdef WIN32 #define PATHSEP '\\' #else #define PATHSEP '/' #endif static const char *PrgPath; /* * Show program usage, given error message, and exit */ static void usage(const char *msg) { const struct { const char opt; const char *desc; } OptInfo[] = { { 'a', "absolute symbols (are values, not addresses)" }, { 'b', "BSS symbols" }, { 'd', "DATA symbols" }, { 'f', "file/path symbols" }, { 'g', "GCC internal (object) symbols" }, { 'l', "local (.L*) symbols" }, { 's', "symbols with duplicate addresses" }, { 't', "TEXT (code) symbols" }, { 'w', "weak (code) symbols" }, }; const char *name; int i; if ((name = strrchr(PrgPath, PATHSEP))) { name++; } else { name = PrgPath; } fprintf(stderr, "\n" "Usage: %s [options] \n" "\n" "Outputs given program symbol table content in ASCII format\n" "accepted by Hatari debugger and its profiler post-processor.\n" "\n" "All symbol addresses are output as TEXT relative, i.e. you need\n" "to give only that as section address for the Hatari debugger:\n" "\tsymbols TEXT\n" "\n" "Symbol type options:\n", name); for (i = 0; i < ARRAY_SIZE(OptInfo); i++) { fprintf(stderr, "\t-%c\tno %s\n", OptInfo[i].opt, OptInfo[i].desc); } fprintf(stderr, "\n" "Prefixing option letter with '+' instead of '-', keeps\n" "the indicated symbol type instead of dropping it.\n" "\n" "Output options:\n" "\t-n, +n\tSort by address (-n), or by name (+n)\n" "\n" "Defaults:\n" "* drop local (-l), GCC internal (-g) and duplicate (-s) symbols\n" "* sort symbols by address (-n)\n"); if (msg) { fprintf(stderr, "\nERROR: %s!\n", msg); } exit(msg != NULL); } /** * Sections just follow each other, so just add their sizes * (initially in .end fields) to successive fields and return true */ static bool update_sections(prg_section_t *sections) { uint32_t offset; sections[0].offset = 0; offset = sections[0].end; sections[1].offset = offset; offset += sections[1].end; sections[1].end = offset; sections[2].offset = offset; offset += sections[2].end; sections[2].end = offset; return true; } /** * Load symbols of given type and the symbol addresses from * the given file and add given offsets to the addresses. * Return symbols list or NULL for failure. */ static symbol_list_t* symbols_load(const char *filename, const symbol_opts_t *opts) { symbol_list_t *list; uint16_t magic; FILE *fp; int dups; fprintf(stderr, "Reading symbols from program '%s' symbol table...\n", filename); if (!(fp = fopen(filename, "rb"))) { usage("opening program file failed"); } if (fread(&magic, sizeof(magic), 1, fp) != 1) { usage("reading program file failed"); } if (be_swap16(magic) != ATARI_PROGRAM_MAGIC) { usage("file isn't an Atari program file"); } list = symbols_load_binary(fp, opts, update_sections); fclose(fp); if (!list || !list->namecount) { usage("no valid symbols in the program, or its symbol table loading failed"); } /* first sort symbols by address (with code symbols being first) */ qsort(list->names, list->namecount, sizeof(symbol_t), symbols_by_address); /* remove symbols with duplicate addresses? */ if (opts->no_dups) { if ((dups = symbols_trim_names(list))) { fprintf(stderr, "Removed %d symbols in same addresses as other symbols.\n", dups); } } /* copy name list to address list */ list->addresses = malloc(list->namecount * sizeof(symbol_t)); assert(list->addresses); memcpy(list->addresses, list->names, list->namecount * sizeof(symbol_t)); /* finally, sort name list by names */ qsort(list->names, list->namecount, sizeof(symbol_t), symbols_by_name); /* check for duplicate addresses? */ if (!opts->no_dups) { if ((dups = symbols_check_addresses(list->addresses, list->namecount))) { fprintf(stderr, "%d symbols in same addresses as other symbols.\n", dups); } } /* check for duplicate names */ if ((dups = symbols_check_names(list->names, list->namecount))) { fprintf(stderr, "%d symbol names that have multiple addresses.\n", dups); } return list; } /* ---------------- symbol showing & option parsing ------------------ */ /** * Show symbols sorted by selected option */ static int symbols_show(symbol_list_t* list, const symbol_opts_t *opts) { symbol_t *entry, *entries; char symchar; int i; if (!list) { fprintf(stderr, "No symbols!\n"); return 1; } if (opts->sort_name) { entries = list->names; } else { entries = list->addresses; } for (entry = entries, i = 0; i < list->namecount; i++, entry++) { symchar = symbol_char(entry->type); fprintf(stdout, "0x%08x %c %s\n", entry->address, symchar, entry->name); } fprintf(stderr, "%d (unignored) symbols processed.\n", list->namecount); fprintf(stderr, "\nLoad the listed symbols to Hatari debugger with 'symbols TEXT'.\n"); return 0; } /** * parse program options and then call symbol load+save */ int main(int argc, const char *argv[]) { symbol_opts_t opts; int i, notype; bool disable; PrgPath = *argv; memset(&opts, 0, sizeof(opts)); opts.no_gccint = true; opts.no_local = true; opts.no_dups = true; for (i = 1; i+1 < argc; i++) { if (argv[i][0] == '-') { disable = true; } else if (argv[i][0] == '+') { disable = false; } else { break; } notype = 0; switch(tolower((unsigned char)argv[i][1])) { /* symbol types */ case 'a': notype = SYMTYPE_ABS; break; case 'b': notype = SYMTYPE_BSS; break; case 'd': notype = SYMTYPE_DATA; break; case 't': notype = SYMTYPE_TEXT; break; case 'w': notype = SYMTYPE_WEAK; break; /* symbol flags */ case 'f': opts.no_files = disable; break; case 'g': opts.no_gccint = disable; break; case 'l': opts.no_local = disable; break; case 's': opts.no_dups = disable; break; /* other options */ case 'n': opts.sort_name = !disable; break; default: usage("unknown option"); } if (disable) { opts.notypes |= notype; } else { opts.notypes &= ~notype; } } if (i+1 != argc) { usage("incorrect number of arguments"); } return symbols_show(symbols_load(argv[i], &opts), &opts); } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/hatari_profile.1000066400000000000000000000152121504763705000265400ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "HATARI_PROFILE" "1" "2013-04-15" "Hatari" "Hatari utilities" .SH "NAME" hatari_profile \- Post-processor for Hatari profiler data .SH "SYNOPSIS" .B hatari_profile.py .IR [options] .IR .SH "DESCRIPTION" A Python script to post-process data produced by Hatari profiler "save" commands, both for CPU and DSP: .nf profile save dspprofile save .fi .PP It can provide function level (CPU and DSP) processor usage information for anything Hatari profiler records: instruction counts, processor cycles and depending on processor, also instruction cache misses or cycle differences. .PP This information can be provided as ASCII list of heaviest functions, as callgraphs (if profile data includes caller information), or as callgrind format export which can be viewed in (Linux) Kcachegrind GUI. .SH "OPTIONS" Invoking .I hatari_profile without arguments lists all of its options. .SH "USAGE EXAMPLES" Regardless of whether profile data contains some symbol information, you should always give script all relevant debug symbols, otherwise costs might not get correctly assigned to symbols preceding those costs. .PP This post-processes profile data for EmuTOS (ROM), with symbol file having fixed/absolute addresses (-a), shows statistics (-s) and top lists (-t), but limits list output to anything taking over 2% (-l 2) and includes "propagated" subroutine call costs (-p): .nf ------------------------------------------------------------- $ hatari_profile.py -a etos1024k.sym -st -l 2 -p etos-boot.txt Hatari profile data processor Parsing absolute symbol address information from etos1024k.sym... WARNING: replacing '_os_header' at 0xe00000 with '__text' WARNING: replacing '_instruction_cache_kludge' at 0xe00660 with '_invalidate_instruction_cache' 1806 lines with 1187 code symbols/addresses parsed, 0 unknown. Parsing profile information from etos-boot.txt... 18900 lines processed with 531 functions. ... Of all 38474 switches, ignored 3731 for type(s) ['r', 'u', 'x']. CPU profile information from 'etos-boot.txt': - Hatari v2.3.1, WinUAE CPU core Time spent in profile = 6.72122s. Visits/calls: - max = 1768, in _memcpy at 0xe46ea0, on line 7944 - 40800 in total Executed instructions: - max = 140992, in _timeout_gpip+22 at 0xe0b484, on line 4393 - 2566629 in total Used cycles: - max = 23997200, in _stop_until_interrupt+38 at 0xe4724a, on line 8213 - 53912568 in total Visits/calls: 4.33% 4.38% 1768 1786 _memcpy 3.55% 0.40% 1450 165 _gsx2 3.55% 24.78% 1449 10112 _GSX_ENTRY 3.55% 3.58% 1449 1459 _get_vwk_by_handle 3.55% 21.17% 1449 8636 _screen 3.45% 27.77% 1407 11329 gemgsxif.o 2.98% 3.00% 1214 1222 _bconstat2 2.95% 2.95% 1203 1203 _kbshift 2.78% 2.79% 1134 1137 _bcostat4 2.51% 1024 vdi_blit.o 2.35% 3.25% 959 1327 _cputc 2.35% 5.60% 957 2283 _bconout2 2.29% 936 _just_rte 2.27% 927 kprint.o 2.23% 7.48% 911 3052 _biostrap 2.10% 2.10% 858 858 gemdosif.o 2.10% 2.12% 858 867 _enable_interrupts Executed instructions: 32.96% 33.10% 33.19% 846043 849564 851784 _timeout_gpip 10.61% 10.66% 10.72% 272360 273633 275161 _blank_out 8.74% 8.81% 8.88% 224230 226006 227862 _draw_rect_common 5.94% 5.96% 5.96% 152525 152895 153039 _run_calibration 5.63% 144385 _ascii_out 3.52% 3.55% 22.77% 90398 91014 584547 _screen 2.62% 67293 ikbd.o Used cycles: 44.62% 44.62% 45.46% 24057296 24057392 24509580 _stop_until_interrupt 18.87% 19.01% 19.06% 10171140 10248940 10277724 _timeout_gpip 4.77% 4.82% 4.85% 2571980 2597744 2616824 _blank_out 3.83% 3.89% 3.93% 2065076 2096668 2121104 _draw_rect_common 2.82% 1521072 _ascii_out 2.58% 2.60% 2.60% 1391076 1400236 1402000 _run_calibration 2.19% 2.21% 11.51% 1181264 1193784 6203852 _screen ------------------------------------------------------------- .fi .PP This creates GraphViz callgraph files (-g) and Kcachegrind callgrind data file (-k) from Bad Mood (Doom BSP viewer) profile. Symbols for the program are TEXT section relative (-r), calls to some interrupt routines in the program are ignored as they aren't real calls, callgraph nodes using over 2% are highlighted, and there are some options to limit and simplify the graph: .nf ------------------------------------------------------------- $ hatari_profile.py -r bmsym.sym \\ -k -g -p --emph-limit 2.0 --limit 0.5 \\ --ignore-to framecounter,new_vbi,stabilizer_b \\ --compact --no-leafs --no-intermediate \\ badmood-profile.txt Hatari profile data processor Parsing TEXT relative symbol address information from bmsym.sym... 1023 lines with 392 code symbols/addresses parsed, 0 unknown. Parsing profile information from badmood-profile.txt... 1420 lines processed with 63 functions. Ignoring 29 switches to framecounter Ignoring 9 switches to stabilizer_b Ignoring 8 switches to new_vbi Of all 1562 switches, ignored 56 for type(s) ['r', 'u', 'x']. Generating callgrind file 'badmood-profile.cg'... Generating 'badmood-profile-0.dot' DOT callgraph file... Generating 'badmood-profile-1.dot' DOT callgraph file... Generating 'badmood-profile-2.dot' DOT callgraph file... Generating 'badmood-profile-3.dot' DOT callgraph file... CPU profile information from 'badmood-profile.txt': - Hatari v1.6.2+ (Apr 10 2013), WinUAE CPU core ------------------------------------------------------------- .fi .SH "SEE ALSO" .IR hatari (1), .IR gst2ascii (1), .IR hconsole (1) .IR hatariui (1) .SH "AUTHOR" Written by Eero Tamminen . .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/hatari_profile.py000077500000000000000000002341241504763705000270400ustar00rootroot00000000000000#!/usr/bin/python3 # # Hatari profile data processor # # 2013-2025 (C) Eero Tamminen, licensed under GPL v2+ # # TODO: # # Current way of handling symbol conflicts while they are being # read, causes unnecessary symbol/address conflict resolving / # renames, and as result outputs way too much noise about it. # # Instead, change that to work in multiple passes: # 1. load all symbols regardless of conflicts # 2. silently remove extra symbols for same address if they: # - are object/library file names, or # - look like weak symbols (e.g. extra _* prefix) # 3. remove rest of overlapping same-address symbols with warnings # (need to try whether using shorter or longer name is better) # 4. rename identical symbols pointing to different addresses # """ A tool for post-processing Hatari emulator HW profiling data with symbol information. In Hatari debugger you get (CPU) profiling data with the following commands (for Falcon DSP data, prefix commands with 'dsp'): profile on continue ... profile save Profiling information for code addresses is summed together and assigned to (function) symbols to which those addresses belong to. All costs for addresses between two symbol names (in profile file) or symbol addresses (read from symbols files) are assumed to belong to the function/symbol which address precedes them. Tool output will contain at least: - (deduced) call counts, - executed instruction counts, and - spent processor cycles. If profile data contains other information (e.g. cache misses), that is also shown. Provided symbol information should be in the same format as for the Hatari debugger 'symbols' command. Note that files containing absolute addresses and ones containing relatives addresses need to be given with different options! Usage: hatari-profile [options] Options: -a absolute symbol address information file -r TEXT (code section) relative symbols file -s output profile statistics -t list top functions for all profile items -i add address and time information to lists -f list at least first items -l list at least items which percentage >= limit -p show costs propagated up in the call hierarchy -o statistics output file name (default is stdout) -g write .dot callgraph files -k write .cg Callgrind format output (for Kcachegrind GUI) -v verbose output Long options for above are: --absolute --relative --stats --top --info --first --limit --propagate --output --graph --callgrind --verbose (Timing --info is shown only for cycles list.) For example: hatari-profile -a etos1024k.sym -st -f 10 prof1.txt prof2.txt For each given profile file, output is: - profile statistics - a sorted list of functions, for each of the profile data items (calls, instructions, cycles...) When both -l and -f options are specified, they're combined. Produced lists contain at least the number of items specified for the -f option, and more, if there are additional items which percentage of the total value is larger than the one given for the -l option. In callgraphs these options mainly affect which nodes are highlighted. If profiling was done after loading symbol information for the profile code, profile ddata includes "caller" information. Then -p option can be used to see: - costs also for everything else that a subroutine calls, in addition to - subroutine's "own" cost, which excludes costs for any further subroutine calls that it did (For more information on how these differ from normally shown costs, see Profiling section in Hatari manual.) With the -g option, callgraph are generated in DOT format for each of the profile data items, for each of the profile files, saved to -.dot file (prof1-0.dot, prof1-2.dot etc). Nodes with subroutine costs are shown as diamonds in the callgraphs. Call information filtering options: --no-calls <[bersux]+> remove calls of given types, default = 'rux' --ignore-to ignore calls to these symbols --compact leave only single connection between symbols which are connected through single node path (Give --no-calls option other char than [bersux] to see type descriptions.) is a comma separate list of symbol names, like this: --ignore-to _int_timerc,_int_vbl If (e.g. C++) symbol names contain commas, change list separator: --separator ';' These options change which calls are reported for functions, and can affect the shape & complexity of the graph a lot. If you e.g. want to see just nodes with costs specific to -p option, use "--no-calls berux" option. If default "--no-calls" type removal doesn't remove all interrupt handling switches [1], give handler names to "--ignore-to" option. In callgraphs, one can then investigate them separately using "--no-calls '' --only " options. [1] CPU being interrupted by an interrupt handler gets recorded as "a call" to that handler by the profiler, and because such "calls" can happen at any time, then can mess graphs badly NOTE: costs shown with "-p" option include costs of exception handling code that interrupts the function calls. Normally effect of this should be minimal, but by providing symbol addresses for interrupt handlers their costs will be visible in output. Callgraph filtering options to remove nodes and edges from the graph: --no-intermediate remove nodes with single parent & child --no-leafs remove nodes which have either: - one parent and no children, - one child and no parents, or - no children nor parents --no-limited remove _all_ nodes below -l limit --no-orphans remove unconnected nodes (regardless of their cost) --only-subroutines remove non-subroutine nodes below -l limit --ignore no nodes for these symbols [2] --ignore-from no arrows from these symbols [2] --only only these symbols and their callers [2] [2] symbol matching uses shell patterns: https://docs.python.org/3/library/fnmatch.html NOTE: leaf and intermediate node removal options remove only nodes which own costs fall below limit given with the "--limit" option. So remember specify limit with them, as it defaults to 0.0 on callgraphs! Functions which are called from everywhere (like malloc), may be good candidates for '--ignore' option when one wants a more readable graph. One can then investigate them separately with the '--only ' option. Callgraph visualization options: --full-symbols do not shorten (C++) symbols --mark color nodes which names contain any of the listed string(s) differently -e, --emph-limit percentage limit for highlighted nodes When -e limit is given, it is used for deciding which nodes to highlight, not -f & -l options. Normally it should be larger than -l option value. Nodes with costs that exceed the highlight limit have red outline. If node's own cost exceeds the limit, it has also gray background. To convert dot files e.g. to SVG and PDF, use: dot -Tsvg graph.dot > graph.svg dot -Tpdf graph.dot > graph.pdf ('dot' tool is in Graphviz package.) """ from copy import deepcopy from bisect import bisect_right from fnmatch import fnmatchcase import getopt, os, re, sys # PC address that was undefined during profiling, # signifies normally first called symbol in data PC_UNDEFINED = 0xffffff # call type identifiers and their information CALL_STARTUP = '0' # first called symbol, special case CALL_SUBROUTINE = 's' CALL_SUBRETURN = 'r' CALL_EXCEPTION = 'e' CALL_EXCRETURN = 'x' CALL_BRANCH = 'b' CALL_NEXT = 'n' CALL_UNKNOWN = 'u' CALLTYPES = { CALL_SUBROUTINE: "subroutine call", # is call CALL_SUBRETURN: "subroutine return", # shouldn't be call CALL_EXCEPTION: "exception", # is call CALL_EXCRETURN: "exception return", # shouldn't be call CALL_BRANCH: "branch/jump", # could be call CALL_NEXT: "next instruction", # most likely loop etc label CALL_UNKNOWN: "unknown" # shouldn't be call } # --------------------------------------------------------------------- class Output: "base class for error and file outputs" def __init__(self): self.error_write = sys.stderr.write self.write = sys.stdout.write self.verbose = False def enable_verbose(self): "enable verbose output" self.verbose = True def set_output(self, out): "set normal output data file" self.write = out.write def set_output_file_ext(self, fname, extension): "open output file name with extension replacement, on success return name" basename = os.path.splitext(fname)[0] fname = "%s%s" % (basename, extension) try: self.set_output(open(fname, "w")) return fname except IOError as err: self.warning(err) return None # rest are independent of output file def message(self, msg): "show message to user" self.error_write("%s\n" % msg) def warning(self, msg): "show warning to user" self.error_write("WARNING: %s\n" % msg) def error_exit(self, msg): "show error to user + exit" self.error_write("ERROR: %s!\n" % msg) sys.exit(1) # --------------------------------------------------------------------- class FunctionStats: """Function (instructions) state. State is updated while profile data is being parsed and when function changes, instance is stored for later use.""" def __init__(self, name, addr, line, items): "start collecting new 'name' function instructions information" self.name = name self.addr = addr # profile line on which function starts self.line = line # cost items summed from profile for given symbol # field 0 is ALWAYS calls count and field 1 instructions count! self.cost = [0] * items # if this object represents a function invoked with # a subroutine call, it has separately calculated # total and "own" costs self.subcost = None self.subtotal = None self.callflags = () # calltree linkage self.parent = {} # addr: Callinfo tuple self.child = {} # addr: True def is_subroutine(self): for flag in (CALL_SUBROUTINE, CALL_STARTUP): if flag in self.callflags: return True return False def has_cost(self): "return whether function has valid data yet" # other values are valid only if there are instructions return self.cost[1] != 0 def name_for_address(self, addr): "if name but no address, use given address and return name, otherwise None" if self.name and not self.addr: self.addr = addr return self.name return None def rename(self, name, offset): "rename function with given address offset" self.addr -= offset self.name = name def add(self, values): "add list of values to current state" # first instruction in a function? cost = self.cost if not cost[1]: # function call count is same as instruction # count for its first instruction cost[0] = values[1] for i in range(1, len(cost)): cost[i] += values[i] def __repr__(self): "return printable current function instruction state" ret = "0x%x = %s:" % (self.addr, self.name) if self.subtotal: ret = "%s %s, total: %s (subroutine)" % (ret, self.subcost, self.subtotal) else: ret = "%s %s" % (ret, self.cost) if self.parent or self.child: return "%s, %d parents, %d children" % (ret, len(self.parent), len(self.child)) return ret # --------------------------------------------------------------------- class InstructionStats: "statistics on all instructions" # not changeable, these are expectations about the data fields # in this, FunctionStats, ProfileCallers and ProfileGraph classes CALLS_FIELD = 0 INSTS_FIELD = 1 # unused CYCLES_FIELD = 2 def __init__(self, processor, clock, fields): "processor name, its speed and profile field names" # Calls item will be deducted from instruction values self.names = ["Visits/calls"] + fields self.items = len(self.names) self.max_line = [0] * self.items self.max_addr = [0] * self.items self.max_val = [0] * self.items self.totals = [0] * self.items self.processor = processor self.clock = clock # in Hz self.areas = {} # which memory area boundaries have been passed def change_area(self, function, name, addr): "switch to given area, if function not already in it, return True if switched" if addr > function.addr and name and name not in self.areas: self.areas[name] = True return True return False def add(self, addr, values, line): "add statistics for given list to current profile state" for i in range(1, self.items): value = values[i] if value > self.max_val[i]: self.max_val[i] = value self.max_addr[i] = addr self.max_line[i] = line def add_callcount(self, function): "add given function call count to statistics" value = function.cost[0] if value > self.max_val[0]: self.max_val[0] = value self.max_addr[0] = function.addr self.max_line[0] = function.line def get_time(self, cost): "return time (in seconds) spent by given cost item" return float(cost[self.CYCLES_FIELD])/self.clock def get_time_call(self, cost, callitem): "return time (in seconds) spent by given cost item per call" return float(cost[self.CYCLES_FIELD])/callitem[self.CALLS_FIELD]/self.clock def sum_values(self, functions): "calculate totals for given functions data" if functions: sums = [0] * self.items for fun in functions: for i in range(self.items): sums[i] += fun.cost[i] self.totals = sums # --------------------------------------------------------------------- class ProfileSymbols(Output): "class for handling parsing and matching symbols, memory areas and their addresses" # profile file field name for program text/code address range text_area = "PROGRAM_TEXT" # default emulation memory address range name default_area = "RAM?" def __init__(self): Output.__init__(self) self.renames = 0 # total of significant renames self.names = None self.symbols = None # (addr:symbol) dict for resolving self.absolute = {} # (addr:symbol) dict of absolute symbols self.relative = {} # (addr:symbol) dict of relative symbols self.aliases = {} # (name:addr) dict of replaced absolute symbols self.rel_aliases = {} # (name:addr) dict of replaced relative symbols self.symbols_need_sort = False self.symbols_sorted = None # sorted list of symbol addresses # memory areas that may be specified in profile file # (checked if instruction is before any of the symbol addresses) self.areas = {} # (name:(start,end)) # memory area format: # : 0x-0x # TOS: 0xe00000-0xe80000 self.r_area = re.compile("^([^:]+):[^0]*0x([0-9a-f]+)-0x([0-9a-f]+)$") # symbol file format: # [0x] [] # Note: C++ symbols contain almost any chars self.r_symbol = re.compile("^(0x)?([a-fA-F0-9]+) ([aAbBdDrRtTvVwW]) ([$]?[._a-zA-Z(][^$?@;]*)$") # weak C++ symbols can be data members, match them to ignore self.r_datasym = re.compile("^.*::[_a-zA-Z][_a-zA-Z0-9]*$") def parse_areas(self, fobj, parsed): "parse memory area lines from data and post-process earlier read symbols data" while True: parsed += 1 line = fobj.readline() if not line: break if line.startswith('#'): continue match = self.r_area.match(line.strip()) if not match: break name, start, end = match.groups() end = int(end, 16) start = int(start, 16) self.areas[name] = (start, end) if end < start: self.error_exit("invalid memory area '%s': 0x%x-0x%x on line %d" % (name, start, end, parsed)) elif self.verbose: self.message("memory area '%s': 0x%x-0x%x" % (name, start, end)) self._combine_symbols() self._clean_aliases() return line, parsed-1 def get_area(self, addr): "return memory area name + offset (used if no symbol matches)" # PROGRAM_TEXT may overlap with ST_RAM / TT_RAM, check it first for name in sorted(self.areas.keys()): value = self.areas[name] if value[1] and value[0] <= addr <= value[1]: return (name, addr - value[0]) return (self.default_area, addr) def _check_symbol(self, addr, name, symbols, aliases): "return True if symbol is OK for addition" # same address has already a symbol? if addr in symbols: # with same name? if name == symbols[addr]: return False # silently drop less accurate file path symbols if '/' in name or name.endswith('.o') or name.endswith('.a'): aliases[name] = addr return False oldname = symbols[addr] # silently override less accurate file path symbols if '/' in oldname or oldname.endswith('.o') or oldname.endswith('.a'): aliases[oldname] = addr return True # either of the names is C/C++ symbol? if (name.startswith('_') or '::' in name or ' ' in name or oldname.startswith('_') or '::' in oldname or ' ' in oldname): # prefer shorter names for C/C++ symbols if len(name) > len(oldname): aliases[name] = addr retval = False else: aliases[oldname] = addr retval = True else: # prefer longer names for asm symbols if len(name) < len(oldname): aliases[name] = addr retval = False else: aliases[oldname] = addr retval = True # count and warn only of significant renames lendiff = abs(len(name) - len(oldname)) minlen = min(len(name), len(oldname)) if not ( (lendiff < 3 and minlen > 3 and (name.endswith(oldname) or oldname.endswith(name) or name.startswith(oldname) or oldname.startswith(name)))): if self.verbose: if retval: self.message("0x%x: prefer '%s' over '%s'" % (addr, name, oldname)) else: self.message("0x%x: prefer '%s' over '%s'" % (addr, oldname, name)) self.renames += 1 return retval return True def _clean_aliases(self): "remove address aliases for names that already have symbols" to_remove = {} for name, addr in self.aliases.items(): if name in self.names: if addr == self.names[name]: to_remove[name] = True continue if not self.verbose: continue self.warning("multiple addresses (0x%x & 0x%x) for symbol '%s'" % (addr, self.names[name], name)) if addr in self.symbols: self.message("- 0x%x is also address for symbol '%s'" % (addr, self.symbols[addr])) for name in to_remove: del self.aliases[name] def parse_symbols(self, fobj, is_relative): "parse symbol file contents" if is_relative: symbols = self.relative aliases = self.rel_aliases else: symbols = self.absolute aliases = self.aliases old_aliases = len(self.aliases) old_renames = self.renames unknown = lines = cppdata = 0 for line in fobj.readlines(): lines += 1 line = line.strip() if line.startswith('#'): continue match = self.r_symbol.match(line) if match: dummy, addr, kind, name = match.groups() if kind not in ('t', 'T', 'W'): continue if (name.startswith("typeinfo ") or name.startswith("vtable ") or name.startswith("VTT ")): # C++ meta data, could be also in text section cppdata += 1 continue match = self.r_datasym.match(name) if match: # C++ data members cppdata += 1 continue addr = int(addr, 16) if self._check_symbol(addr, name, symbols, aliases): symbols[addr] = name else: self.warning("unrecognized symbol line %d:\n\t'%s'" % (lines, line)) unknown += 1 info = (lines, len(symbols), unknown) self.message("%d lines with %d code symbols/addresses parsed, %d unknown." % info) info = (len(aliases) - old_aliases, self.renames - old_renames) self.message("%d (new) symbols were aliased, with %d significant renames." % info) if cppdata: self.message("%d C++ data member symbols ignored." % cppdata) def _rename_symbol(self, addr, name): "return symbol name, potentially renamed if there were conflicts" if name in self.names: if addr == self.names[name]: return name # symbol with same name already exists at another address chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" maxid = len(chars) idx = 1 while True: # One cannot just blindly add random suffix to a mangled C++ symbol, # so use for most mangled symbols, and # ".%d" suffix for ones already using vendor extension if name.startswith("__Z") and '.' not in name: if idx >= maxid**2: self.warning("giving up on '%s' rename, at least %d duplicates" % (name, maxid)) return name newname = "%s2%c%c" % (name, chars[idx//maxid], chars[idx%maxid]) else: newname = "%s.%d" % (name, idx) if newname in self.names: idx += 1 continue if self.verbose: self.message("renaming '%s' at 0x%x as '%s' to avoid clash with same symbol at 0x%x" % (name, addr, newname, self.names[name])) name = newname break self.names[name] = addr return name def _combine_symbols(self): "combine absolute and relative symbols to single lookup" # renaming is done only at this point (after parsing memory areas) # to avoid addresses in names dict being messed by relative symbols self.names = {} self.symbols = {} self.symbols_need_sort = True for addr, name in self.absolute.items(): name = self._rename_symbol(addr, name) self.symbols[addr] = name if self.verbose: self.message("0x%x: %s (absolute)" % (addr, name)) if not self.relative: return if self.text_area not in self.areas: self.error_exit("'%s' area range missing from profile, needed for relative symbols" % self.text_area) area = self.areas[self.text_area] # relocate symbols used for resolving for addr, name in self.relative.items(): addr += area[0] # -1 used because compiler can add TEXT symbol right after end of TEXT section if addr < area[0] or addr-1 > area[1]: self.error_exit("relative symbol '%s' address 0x%x is outside of TEXT area: 0x%x-0x%x!\nDo symbols match the profiled binary?" % (name, addr, area[0], area[1])) if self._check_symbol(addr, name, self.symbols, self.rel_aliases): name = self._rename_symbol(addr, name) self.symbols[addr] = name if self.verbose: self.message("0x%x: %s (relative)" % (addr, name)) # relocate relative address aliases for name, addr in self.rel_aliases.items(): addr += area[0] # -1 used because compiler can add TEXT symbol right after end of TEXT section if addr < area[0] or addr-1 > area[1]: self.error_exit("relative symbol '%s' address 0x%x is outside of TEXT area: 0x%x-0x%x!\nDo symbols match the profiled binary?" % (name, addr, area[0], area[1])) if self.verbose: if name in self.aliases and self.aliases[name] != addr: self.message("conflicting addresses 0x%x & 0x%x for symbol '%s'" % (addr, self.aliases[name], name)) self.aliases[name] = addr def add_profile_symbol(self, addr, name): "add absolute symbol and return its name in case it got renamed" if self._check_symbol(addr, name, self.symbols, self.aliases): name = self._rename_symbol(addr, name) self.symbols[addr] = name self.symbols_need_sort = True return name def get_symbol(self, addr): "return symbol name for given address, or None" if addr in self.symbols: return self.symbols[addr] return None def get_addr(self, name): "return symbol address for given name, or None" if name in self.names: return self.names[name] if name in self.areas: return self.areas[name][0] return None def get_alias_addr(self, name): "return symbol address for given address alias, or None (for Callgrind)" if name in self.aliases: return self.aliases[name] return None def get_preceeding_symbol(self, addr): "resolve non-function addresses to preceding function name+offset" # should be called only after profile addresses has started if self.symbols: if self.symbols_need_sort: self.symbols_sorted = list(self.symbols.keys()) self.symbols_sorted.sort() self.symbols_need_sort = False idx = bisect_right(self.symbols_sorted, addr) - 1 if idx >= 0: saddr = self.symbols_sorted[idx] return (self.symbols[saddr], addr - saddr) return self.get_area(addr) # --------------------------------------------------------------------- class Callinfo: "class representing parsed profile data caller/callee linkage information" def __init__(self, addr): self.addr = addr self.calls = 0 self.flags = "-" self.total = [None, None] def set(self, calls, flags, intotal, extotal): "parse & set given string arguments" # how many times this caller called the destination self.calls = int(calls, 10) # what kind of "call" it was? if flags: self.flags = flags.strip() # cost of these calls including further function calls self._parse_totals(0, intotal) # costs excluding further function calls self._parse_totals(1, extotal) def _parse_totals(self, idx, totalstr): "parse '/' separate totals string into integer sequence, or leave it None" if totalstr: self.total[idx] = [int(x, 10) for x in totalstr.strip().split('/')] def _add_total(self, idx, newtotal): "add given totals to own totals" mytotal = self.total[idx] if mytotal and newtotal: self.total[idx] = [x + y for x, y in zip(mytotal, newtotal)] elif newtotal: self.total[idx] = newtotal def add(self, newinfo): "add counts and totals from other item to this one" self.calls += newinfo.calls for i in range(len(self.total)): self._add_total(i, newinfo.total[i]) def get_full_costs(self): return self.total[0] def get_own_costs(self): return self.total[1] # --------------------------------------------------------------------- class ProfileCallers(Output): "profile data callee/caller information parser & handler" def __init__(self): Output.__init__(self) # caller info in callee line: # 0x = [ inclusive/totals][ exclusive/totals] self.r_caller = re.compile("^0x([0-9a-f]+) = ([0-9]+)( [a-z]+)?( [0-9/]+)?( [0-9/]+)?$") # whether there is any caller info self.present = False # address dicts self.callinfo = None # callee:caller dict of callee:callinfo dicts # temporary during completion self.symbols = None # parsing options self.removable_calltypes = "rux" self.ignore_to = [] self.compact = False def set_ignore_to(self, lst): "set list of symbols to ignore calls to" self.ignore_to = lst def enable_compact(self): "enable: single entry between two functions" self.compact = True def remove_calls(self, types): "ignore calls of given type" for letter in types: if letter not in CALLTYPES: self.message("Valid call types are:") for item in CALLTYPES.items(): self.message(" %c -- %s" % item) self.error_exit("invalid call type") self.removable_calltypes = types def parse_callers(self, fobj, parsed, line): "parse callee: caller call count information" #0x: 0x = [ ][, [, ]]; N*[0x = ...;][ ] self.message("parsing call info...") self.callinfo = {} while True: parsed += 1 if not line: break if line.startswith('#'): line = fobj.readline() continue if not line.startswith("0x"): break callers = line.split(',') if len(callers) < 2: self.error_exit("caller info missing on callee line %d\n\t'%s'" % (parsed, line)) if ':' not in callers[0]: self.error_exit("callee/caller separator ':' missing on callee line %d\n\t'%s'" % (parsed, line)) caddr, callers[0] = callers[0].split(':') last = callers[-1].strip() if last.startswith('0x'): self.error_exit("last item isn't empty or symbol name on callee line %d\n\t'%s'" % (parsed, line)) callinfo = {} for callstr in callers[:-1]: callstr = callstr.strip() match = self.r_caller.match(callstr) if match: items = match.groups() paddr = int(items[0], 16) # caller/parent specific callinfo for this child/callee tmp = Callinfo(paddr) tmp.set(*items[1:]) callinfo[paddr] = tmp else: self.error_exit("unrecognized caller info '%s' on callee line %d\n\t'%s'" % (callstr, parsed, line)) self.callinfo[int(caddr, 16)] = callinfo line = fobj.readline() return line, parsed-1 def _complete_child(self, profile, linkage, child, caddr): "link parents with child based on caller info" flags = {} ignored = switches = 0 callinfo = Callinfo(caddr) for laddr, info in linkage.items(): callinfo.add(info) pname, offset = self.symbols.get_preceeding_symbol(laddr) if len(info.flags) > 1: self.warning("caller instruction change ('%s') detected for '%s', did its code change during profiling?" % (info.flags, pname)) elif info.flags in self.removable_calltypes: ignored += info.calls continue if laddr == PC_UNDEFINED: flags[CALL_STARTUP] = True switches += 1 continue flags[info.flags] = True # function address for the caller paddr = laddr - offset if paddr not in profile: # this can be either a mismatch in symbols, or few first instructions without symbol self.warning("parent caller 0x%x for '%s' not in profile!\nDo symbols match the profiled binary?" % (laddr, child.name)) continue parent = profile[paddr] if pname != parent.name: if not parent.name.endswith('.o'): self.warning("overriding parsed function 0x%x name '%s' with resolved caller 0x%x name '%s'" % (parent.addr, parent.name, paddr, pname)) parent.name = pname # link parent and child function together if paddr in child.parent: if self.compact: child.parent[paddr][0].add(info) else: child.parent[paddr] += (info,) else: child.parent[paddr] = (info,) parent.child[caddr] = True switches += info.calls if child.subcost: self.warning("cost already for child: %s" % child) child.subcost = callinfo.get_own_costs() child.subtotal = callinfo.get_full_costs() child.callflags = tuple(flags.keys()) return switches, ignored def complete(self, profile, symbols): "resolve caller functions and add child/parent info to profile data" if not self.callinfo: self.present = False return self.present = True self.symbols = symbols all_switches = all_ignored = 0 # go through called (child) functions... for caddr, callinfo in self.callinfo.items(): child = profile[caddr] if child.name in self.ignore_to: continue # ...and their callers (parents) switches, ignored = self._complete_child(profile, callinfo, child, caddr) all_switches += switches all_ignored += ignored # validate non-subroutine call counts if ignored: if self.verbose: self.message("Ignoring %d switches to %s" % (ignored, child.name)) #switches += ignored child.cost[0] -= ignored calls = child.cost[0] if calls != switches: info = (child.name, caddr, calls, switches, ignored) self.warning("call count mismatch for '%s' at 0x%x, %d != %d (%d ignored)" % info) self.callinfo = None self.symbols = None if all_ignored: all_switches += all_ignored info = (all_switches, all_ignored, list(self.removable_calltypes)) self.message("Of all %d switches, ignored %d for type(s) %s." % info) # --------------------------------------------------------------------- class ProfileCallgrind(Output): "profile output in callgrind format" # Output in Valgrind callgrind format: # http://valgrind.org/docs/manual/cl-format.html # for KCachegrind: # http://kcachegrind.sourceforge.net/ def __init__(self): Output.__init__(self) self.function = None self.profname = None self.tmpname = None self.missing = None def start_output(self, fname, emuinfo, names): "create callgraph file and write header to it" self.profname = fname self.tmpname = self.set_output_file_ext(fname, ".cgtmp") if not self.tmpname: self.error_exit("Temporary callgrind file '%s' creation failed" % self.tmpname) if emuinfo: self.write("creator: %s\n" % emuinfo) idx = 0 shortnames = [] for name in names: abr = "%s_%d" % (name.split()[-1], idx) self.write("event: %s %s\n" % (abr, name)) shortnames.append(abr) idx += 1 self.write("events: %s\n" % ' '.join(shortnames)) self.write("fl=%s\n" % fname) def output_line(self, function, counts, linenro): "output given function line information" # this needs to be done while data is parsed because # line specific data isn't stored after parsing if function != self.function: self.function = function # first line in function, output function info self.write("fn=%s\n" % function.name) # only first function line gets the call count counts = [function.cost[0]] + counts[1:] self.write("%d %s\n" % (linenro, ' '.join([str(x) for x in counts]))) def _output_calls(self, profile, paddr): # TODO: find the line in profile corresponding # to the caller and use that as line number, # instead of caller function beginning line number parent = profile[paddr] for caddr in parent.child: child = profile[caddr] if not child.subtotal: # costs missing, no sense in adding dep self.missing[caddr] = True continue total = 0 for item in child.parent[paddr]: total += item.calls self.write("cfn=%s\n" % child.name) self.write("calls=%d %d\n" % (total, child.line)) all_calls = child.cost[0] cost = [int(round(float(x)*total/all_calls)) for x in child.subtotal] counts = ' '.join([str(x) for x in cost]) self.write("%d %s\n" % (parent.line, counts)) if all_calls < total: self.warning("calls from %s to %s: %d > %d" % (parent.name, child.name, total, all_calls)) def output_callinfo(self, profile, symbols): "output function calling information" # this can be done only after profile is parsed because # before that we don't have function inclusive cost counts, # i.e. we need to post-process the generated callgrind file # to add the calls and their costs fname = self.set_output_file_ext(self.tmpname, ".cg") self.message("\nGenerating callgrind file '%s'..." % fname) tmpfile = open(self.tmpname, 'r') os.remove(self.tmpname) items = 0 self.missing = {} for line in tmpfile.readlines(): self.write(line) if not line.startswith("fn="): continue name = line.strip().split('=')[1] paddr = symbols.get_addr(name) if not paddr: self.warning("no resolved address for symbol '%s', trying address aliases instead" % name) paddr = symbols.get_alias_addr(name) if profile[paddr].child: self._output_calls(profile, paddr) items += 1 if self.missing: self.warning("%d / %d nodes missing caller information (for callgrind data)!" % (len(self.missing), items)) self.missing = None # --------------------------------------------------------------------- class EmulatorProfile(Output): "Emulator profile data file parsing and profile information" def __init__(self): Output.__init__(self) self.symbols = ProfileSymbols() self.callers = ProfileCallers() # profile data format # # emulator ID line: # profile [(info)] # self.emuinfo = None self.r_info = re.compile(r"^.* profile \((.*)\)$") # processor clock speed self.r_clock = re.compile(r"^Cycles/second:\t([0-9]+)$") # field names self.r_fields = re.compile(r"^Field names:\t(.*)$") # processor disassembly format regexp is gotten from profile file self.r_regexp = re.compile(r"Field regexp:\t(.*)$") self.r_address = None # memory address information is parsed by ProfileSymbols # # this class parses symbols from disassembly itself: # : (in disassembly) # Note: C++ symbols contain almost any chars self.r_function = re.compile(r"^([._a-zA-Z(][^$?@;]*):$") self.stats = None # InstructionStats instance self.callgrind = None # ProfileCallgrind instance self.profile = None # hash of profile (addr:data) self.linenro = 0 def enable_verbose(self): "set verbose output in this and member class instances" Output.enable_verbose(self) self.symbols.enable_verbose() self.callers.enable_verbose() def remove_calls(self, types): self.callers.remove_calls(types) def set_ignore_to(self, lst): self.callers.set_ignore_to(lst) def enable_compact(self): self.callers.enable_compact() def enable_callgrind(self): self.callgrind = ProfileCallgrind() def parse_symbols(self, fobj, is_relative): "parse symbols from given file object" self.symbols.parse_symbols(fobj, is_relative) def output_info(self, fname): "show profile file information" if self.emuinfo: info = "- %s\n" % self.emuinfo else: info = "" self.write("\n%s profile information from '%s':\n%s" % (self.stats.processor, fname, info)) def _get_profile_type(self, fobj): "get profile processor type and speed information or exit if it's unknown" line = fobj.readline() field = line.split() fields = len(field) if fields < 3 or field[2] != "profile": self.error_exit("unrecognized file, line 1\n\t%s\nnot in format:\n\t profile [(info)]" % line) processor = field[1] if fields > 3: match = self.r_info.match(line) if not match: self.error_exit("invalid (optional) emulator information format on line 1:\n\t%s" % line) self.emuinfo = match.group(1) else: self.emuinfo = None line = fobj.readline() match = self.r_clock.match(line) if not match: self.error_exit("invalid %s clock HZ information on line 2:\n\t%s" % (processor, line)) clock = int(match.group(1)) line = fobj.readline() match = self.r_fields.match(line) if not match: self.error_exit("invalid %s profile disassembly field descriptions on line 3:\n\t%s" % (processor, line)) fields = [x.strip() for x in match.group(1).split(',')] self.stats = InstructionStats(processor, clock, fields) line = fobj.readline() match = self.r_regexp.match(line) try: self.r_address = re.compile(match.group(1)) except (AttributeError, re.error) as error: self.error_exit("invalid %s profile disassembly regexp on line 4:\n\t%s\n%s" % (processor, line, error)) return 4 def _change_function(self, function, newname, addr): "store current function data and then reset to new function" if function.has_cost(): if not function.addr: name, offset = self.symbols.get_preceeding_symbol(function.addr) function.rename(name, offset) # addresses must increase in profile oldaddr = function.addr assert oldaddr not in self.profile self.stats.add_callcount(function) self.profile[oldaddr] = function if self.verbose: self.message(function) return FunctionStats(newname, addr, self.linenro, self.stats.items) def _check_symbols(self, function, addr): "if address is in new symbol (=function), change function" name = self.symbols.get_symbol(addr) if name: return self._change_function(function, name, addr) # as no better symbol, name it according to area where it moved to? name, offset = self.symbols.get_area(addr) addr -= offset if self.stats.change_area(function, name, addr): return self._change_function(function, name, addr) return function def _parse_line(self, function, addr, counts, discontinued): "parse given profile disassembly line match contents" newname = function.name_for_address(addr) if newname: # new symbol name finally got address on this profile line, # but it may need renaming due to symbol name clashes newname = self.symbols.add_profile_symbol(addr, newname) function.rename(newname, 0) elif discontinued: # continuation may skip to a function which name is not visible in profile file name, offset = self.symbols.get_preceeding_symbol(addr) symaddr = addr - offset # if changed area, preceding symbol can be before area start, # so need to check both address, and name having changed if symaddr > function.addr and name != function.name: addr = symaddr if self.verbose: self.message("DISCONTINUATION: %s at 0x%x -> %s at 0x%x" % (function.name, function.addr, name, addr)) #if newname: # self.warning("name_for_address() got name '%s' instead of '%s' from get_preceeding_symbol()" % (newname, name)) function = self._change_function(function, name, addr) newname = name if not newname: function = self._check_symbols(function, addr) self.stats.add(addr, counts, self.linenro) function.add(counts) return function def _parse_disassembly(self, fobj, line): "parse profile disassembly" self.message("parsing disassembly...") prev_addr = 0 discontinued = True function = FunctionStats(None, -1, 0, self.stats.items) while True: if not line: break self.linenro += 1 line = line.strip() if line.startswith('#'): pass if line == "[...]": # address discontinuation discontinued = True elif line.endswith(':'): # symbol match = self.r_function.match(line) if match: function = self._change_function(function, match.group(1), 0) else: self.error_exit("unrecognized function line %d:\n\t'%s'" % (self.linenro, line)) else: # disassembly line match = self.r_address.match(line) if not match: break addr = int(match.group(1), 16) if prev_addr > addr: self.error_exit("memory addresses are not in order on line %d" % self.linenro) prev_addr = addr # counts[0] will be inferred call count counts = [0] + [int(x) for x in match.group(2).split(',')] function = self._parse_line(function, addr, counts, discontinued) if self.callgrind: self.callgrind.output_line(function, counts, self.linenro) discontinued = False # next line line = fobj.readline() # finish self._change_function(function, None, 0) return line def parse_profile(self, fobj, fname): "parse profile data" self.profile = {} # header self.linenro = self._get_profile_type(fobj) if self.callgrind: self.callgrind.start_output(fname, self.emuinfo, self.stats.names) # memory areas line, self.linenro = self.symbols.parse_areas(fobj, self.linenro) # instructions / memory addresses line = self._parse_disassembly(fobj, line) # caller information line, self.linenro = self.callers.parse_callers(fobj, self.linenro, line) # unrecognized lines if line: self.error_exit("unrecognized line %d:\n\t'%s'" % (self.linenro, line)) # parsing info self.message("%d lines processed with %d functions." % (self.linenro, len(self.profile))) if len(self.profile) < 1: self.error_exit("no functions found!") # finish self.stats.sum_values(self.profile.values()) self.callers.complete(self.profile, self.symbols) if self.callgrind: self.callgrind.output_callinfo(self.profile, self.symbols) # --------------------------------------------------------------------- class ProfileSorter: "profile information sorting and list output class" def __init__(self, profile, stats, write, subcosts): self.profile = profile self.stats = stats self.write = write self.field = None self.show_subcosts = subcosts def _cmp_field(self, i): "return currently selected field in profile data" return self.profile[i].cost[self.field] def get_combined_limit(self, field, count, limit): "return percentage for given profile field that satisfies both count & limit constraint" if not count: return limit keys = list(self.profile.keys()) if len(keys) <= count: return 0.0 self.field = field keys.sort(key=self._cmp_field, reverse=True) total = self.stats.totals[field] function = self.profile[keys[count]] if self.show_subcosts and function.subtotal: value = function.subtotal[field] else: value = function.cost[field] percentage = value * 100.0 / total if percentage < limit or not limit: return percentage return limit def _output_list(self, keys, count, limit, show_info): "output list for currently selected field" field = self.field stats = self.stats total = stats.totals[field] self.write("\n%s:\n" % stats.names[field]) time = idx = 0 for addr in keys: function = self.profile[addr] value = function.cost[field] if not value: break percentage = 100.0 * value / total if count and limit: # if both list limits are given, both must be exceeded if percentage < limit and idx >= count: break elif limit and percentage < limit: break elif count and idx >= count: break idx += 1 mark = " " times = "" values = "" percentages = "" if self.show_subcosts: # show also subroutine call cost if field > 0: totals = (function.cost, function.subcost, function.subtotal) if function.subcost and ( len(function.subcost) > field and ( function.cost[field] > function.subcost[field])): mark = "*" else: # subfunction and symbol call counts are same totals = (function.cost, function.subtotal,) else: totals = (function.cost,) showtime = False if show_info and field == stats.CYCLES_FIELD: showtime = True for cost in totals: if cost and len(cost) > field: percentage = 100.0 * cost[field] / total percentages += "%7.2f%%" % percentage if showtime: time = stats.get_time(cost) times += "%9.5fs" % time values += "%10d" % cost[field] else: percentages += " " * 8 values += " " * 10 if showtime: times += " " * 10 self.write("%s %s %s %s" % (percentages, times, values, mark)) if show_info: costinfo = "" for cost in totals: if cost and len(cost) > field and function.cost[0]: if showtime: time = stats.get_time_call(cost, function.cost) costinfo += ",%8.5fs" % time elif field: costinfo += ", %d" % (cost[field] / function.cost[0]) if costinfo: costinfo += " / call" addrinfo = "0x%06x" % addr self.write(" %-23s (%s%s)\n" % (function.name, addrinfo, costinfo)) else: self.write(" %s\n" % function.name) def do_list(self, field, count, limit, show_info): "sort and show list for given profile data field" if self.stats.totals[field] == 0: return self.field = field keys = list(self.profile.keys()) keys.sort(key=self._cmp_field, reverse=True) self._output_list(keys, count, limit, show_info) # --------------------------------------------------------------------- class ProfileOutput(Output): "base class for profile output options" def __init__(self): Output.__init__(self) # both unset so that subclasses can set defaults reasonable for them self.show_subcosts = False self.limit = 0.0 self.count = 0 def enable_subcosts(self): "enable showing subroutine call costs instead of just own costs" self.show_subcosts = True def set_count(self, count): "set how many items to show or highlight at minimum, 0 = all/unset" if count < 0: self.error_exit("Invalid item count: %d" % count) self.count = count def set_limit(self, limit): "set percentage is shown or highlighted at minimum, 0.0 = all/unset" if limit < 0.0 or limit > 100.0: self.error_exit("Invalid percentage: %d" % limit) self.limit = limit # --------------------------------------------------------------------- class ProfileStats(ProfileOutput): "profile information statistics output" def __init__(self): ProfileOutput.__init__(self) self.limit = 1.0 self.sorter = None self.show_totals = False self.show_top = False self.show_info = False def enable_totals(self): "enable totals list" self.show_totals = True def enable_top(self): "enable showing listing for top items" self.show_top = True def enable_info(self): "enable showing extra info for list items" self.show_info = True def output_totals(self, profobj): "output profile statistics" stats = profobj.stats time = stats.get_time(stats.totals) self.write("\nTime spent in profile = %.5fs.\n" % time) self.write("\nProfile lines with highest costs + totals:\n") symbols = profobj.symbols items = len(stats.totals) for i in range(items): if not stats.totals[i]: continue addr = stats.max_addr[i] name, offset = symbols.get_preceeding_symbol(addr) if name: if offset: name = " in %s%+d" % (name, offset) else: name = " in %s" % name self.write("* %s:\n" % stats.names[i]) info = (stats.max_val[i], name, addr, stats.max_line[i]) self.write(" - max = %d,%s at 0x%x, on line %d\n" % info) self.write(" - %d in total\n" % stats.totals[i]) def do_output(self, profobj): "output enabled lists" if self.show_totals: self.output_totals(profobj) if self.show_top: sorter = ProfileSorter(profobj.profile, profobj.stats, self.write, self.show_subcosts) fields = range(profobj.stats.items) for field in fields: sorter.do_list(field, self.count, self.limit, self.show_info) # --------------------------------------------------------------------- def name_fnmatch(matches, name): "return True if 'name' fnmatch()es one of 'matches', False otherwise" if name in matches: return True for pattern in matches: if fnmatchcase(name, pattern): return True return False class ProfileGraph(ProfileOutput): "profile callgraph output" header = """ # Convert this to SVG with: # dot -Tsvg -o profile.svg digraph profile { center=1; ratio=compress; # page size A4 page="11.69,8.27"; size="9.69,6.27"; margin="1.0"; # set style options color="black"; bgcolor="white"; node [shape="ellipse"]; edge [dir="forward" arrowsize="2"]; labelloc="t"; label="%s"; """ footer = "}\n" def __init__(self): ProfileOutput.__init__(self) self.profile = None self.count = 8 self.nodes = None self.edges = None self.highlight = None self.output_enabled = False # callgraph node filtering self.compact = False self.remove_intermediate = False self.remove_leafs = False self.remove_limited = False self.remove_nonsubs = False self.remove_orphans = False self.full_symbols = False self.only = [] self.mark = [] self.ignore = [] self.ignore_from = [] self.emph_limit = 0 # for demangled (C++ etc) symbols self.re_thunk = re.compile(r"(non-)?virtual ") # ...thunk to self.re_clone = re.compile(r" \[clone[^]]+\]") # ...isra self.re_args = re.compile(r"[^(+]+::[^(+]+(\(.+\))") # class::method(args) def _get_short_name(self, name): # not C++ or other demangled symbol if self.full_symbols or not (" " in name or "::" in name): return name # remove args from method signatures m = self.re_args.search(name) if m: name = name[:m.start(1)] + "()" + name[m.end(1):] # shorten namespaces and template args name = name.replace("anonymous namespace", "anon ns") name = name.replace("unsigned ", "u") # remove virtual / clone infos from signatures for r in (self.re_thunk, self.re_clone): m = r.search(name) if m: name = name[:m.start()] + name[m.end():] return name def enable_full_symbols(self): "do not shorten (C++) symbols" self.full_symbols = True def enable_output(self): "enable output" self.output_enabled = True def enable_compact(self): "show just single edge between nodes" self.compact = True def disable_intermediate(self): "disable showing nodes with just single parent & child, with single edge" # TODO: move leaf/intermediate handling to ProfileCallers class? self.remove_intermediate = True def disable_leafs(self): "disable showing nodes which don't have children" self.remove_leafs = True def disable_limited(self): "disable showing all nodes which are below limit" self.remove_limited = True def disable_orphans(self): "disable showing above-limit nodes which have no connections" self.remove_orphans = True def only_subroutines(self): "disable showing nodes that aren't above limit or subroutines" self.remove_nonsubs = True def set_only(self, lst): "set list of only symbols to include" self.only = lst def set_marked(self, lst): "set list of substrings for symbols to mark in graphs" self.mark = lst def set_ignore(self, lst): "set list of symbols to ignore" self.ignore = lst def set_ignore_from(self, lst): "set list of symbols to ignore calls from" self.ignore_from = lst def set_emph_limit(self, limit): "set emphatize percentage limit" self.emph_limit = limit def _remove_from_profile(self, addr): "remove function with given address from profile" profile = self.profile function = profile[addr] if self.verbose: self.message("removing node %s" % function) parents = list(function.parent.keys()) children = list(function.child.keys()) # remove it from items linking it for paddr in parents: if paddr != addr: parent = profile[paddr] # link parent directly to its grandchildren for caddr in children: if caddr != addr: parent.child[caddr] = True # remove its parent's linkage del parent.child[addr] for caddr in children: if caddr != addr: child = profile[caddr] info = child.parent[addr] # link child directly to its grandparents for paddr in parents: if paddr != addr: #self.message("%s: %s" % (parent.name, info)) if paddr in child.parent: child.parent[paddr] += info else: child.parent[paddr] = info # remove its child's linkage del child.parent[addr] # remove it itself del profile[addr] def _set_reduced_profile(self, profobj, totals, field): "get relinked copy of profile data with requested items removed from it" # need our own copy so that it can be manipulated freely self.profile = deepcopy(profobj.profile) total = totals[field] to_remove = {} if self.ignore: for addr, function in self.profile.items(): if name_fnmatch(self.ignore, function.name): to_remove[addr] = True removed = 0 while True: for addr in to_remove: self._remove_from_profile(addr) removed += len(to_remove) to_remove = {} for addr, function in self.profile.items(): # remove everything except subroutines? if self.remove_nonsubs and not function.is_subroutine(): to_remove[addr] = True continue parents = len(function.parent) children = len(function.child) # remove orphans regardless of their cost? if self.remove_orphans and (parents + children) == 0: to_remove[addr] = True continue # don't remove symbols which own costs are over the limit if self.show_subcosts and function.subcost and len(function.subcost) > field: count = function.subcost[field] else: count = function.cost[field] percentage = 100.0 * count / total if percentage >= self.limit: continue if self.remove_limited: to_remove[addr] = True continue # remove leafs & intermediates if self.remove_leafs: if (parents + children) <= 1: to_remove[addr] = True continue # refers just to itself? if children == 1 and addr == tuple(function.child.keys())[0]: to_remove[addr] = True continue if parents == 1 and addr == tuple(function.parent.keys())[0]: to_remove[addr] = True continue if self.remove_intermediate: if parents == 1 and children == 1: to_remove[addr] = True continue # refers also to itself? if children == 2: for caddr in function.child: if caddr == addr: to_remove[addr] = True break if parents == 2: for paddr in function.parent: if paddr == addr: to_remove[addr] = True break if not to_remove: break return removed def _filter_profile(self): "filter remaining profile content to nodes and edges members based on only & ignore-from options" profile = self.profile self.nodes = {} self.edges = {} for caddr, child in profile.items(): if not child.parent: self.nodes[caddr] = True continue for paddr, info in child.parent.items(): parent = profile[paddr] # no recursion # if caddr == paddr: # continue if self.only: if not (name_fnmatch(self.only, child.name) or name_fnmatch(self.only, parent.name)): continue # child end for edges self.nodes[caddr] = True if name_fnmatch(self.ignore_from, parent.name): continue # parent end for edges self.nodes[paddr] = True # total calls count for child all_calls = profile[caddr].cost[0] if self.compact: edge = Callinfo(paddr) # TODO: "pick first" not good enough? edge.flags = [x.flags for x in info][0] edge.calls = sum([x.calls for x in info]) self.edges[(paddr, caddr)] = (paddr, all_calls, edge) else: # calls to child done from different locations in parent for edge in info: self.edges[(edge.addr, caddr)] = (paddr, all_calls, edge) return len(profile) - len(self.nodes) def _output_nodes(self, stats, field, limit): "output graph nodes from filtered nodes dict" self.highlight = {} total = stats.totals[field] for addr in self.nodes: shape = style = "" function = self.profile[addr] calls = function.cost[0] if self.show_subcosts and function.subtotal and len(function.subtotal) > field: shape = " shape=diamond" cost = function.subtotal if len(function.subcost) > field: owncount = function.subcost[field] else: owncount = 0 else: cost = function.cost owncount = cost[field] count = cost[field] percentage = 100.0 * count / total if percentage >= limit: self.highlight[addr] = True style = " color=red style=bold" ownpercentage = 100.0 * owncount / total if ownpercentage >= limit: style = "%s style=filled fillcolor=lightgray" % style for substr in self.mark: if substr in function.name: style = "%s style=filled fillcolor=green shape=square" % style break if CALL_STARTUP in function.callflags: style = "%s style=filled fillcolor=green shape=square" % style if count != owncount: coststr = "%.2f%%\\n(own: %.2f%%)" % (percentage, ownpercentage) else: coststr = "%.2f%%" % percentage name = self._get_short_name(function.name) if field == 0: self.write("N_%X [label=\"%s\\n%s\\n%d calls\"%s%s];\n" % (addr, coststr, name, count, style, shape)) elif field == stats.CYCLES_FIELD: time = stats.get_time(cost) self.write("N_%X [label=\"%s\\n%.5fs\\n%s\\n(%d calls)\"%s%s];\n" % (addr, coststr, time, name, calls, style, shape)) else: self.write("N_%X [label=\"%s\\n%d\\n%s\\n(%d calls)\"%s%s];\n" % (addr, coststr, count, name, calls, style, shape)) def _output_edges(self): "output graph edges from filtered edges dict, after nodes is called" for linkage, info in self.edges.items(): laddr, caddr = linkage paddr, calls, edge = info offset = laddr - paddr style = "" if caddr in self.highlight: style = " color=red style=bold" # arrowhead/tail styles: # none, normal, inv, dot, odot, invdot, invodot, tee, empty, # invempty, open, halfopen, diamond, odiamond, box, obox, crow flags = edge.flags if flags in (CALL_NEXT, CALL_BRANCH): style += " arrowhead=dot" elif flags == CALL_SUBROUTINE: pass # use default arrow elif flags == CALL_SUBRETURN: style += " arrowhead=inv" elif flags == CALL_EXCEPTION: style += " style=dashed" elif flags == CALL_EXCRETURN: style += " arrowhead=inv style=dashed" elif flags == CALL_UNKNOWN: style += " arrowhead=diamond style=dotted" pname = self._get_short_name(self.profile[paddr].name) if offset: label = "%s%+d\\n($%x)" % (pname, offset, laddr) else: label = pname if edge.calls != calls: percentage = 100.0 * edge.calls / calls label = "%s\\n%d calls\\n=%.2f%%" % (label, edge.calls, percentage) self.write("N_%X -> N_%X [label=\"%s\"%s];\n" % (paddr, caddr, label, style)) def do_output(self, profobj, fname): "output graphs for given profile data" if not (self.output_enabled and profobj.callers.present): if not profobj.callers.present: self.warning("callgraph output enabled, but caller info is missing!") return stats = profobj.stats for field in range(profobj.stats.items): if not stats.totals[field]: continue # get potentially reduced instance copy of profile data removed = self._set_reduced_profile(profobj, stats.totals, field) filtered = self._filter_profile() if not self.nodes: continue dotname = self.set_output_file_ext(fname, "-%d.dot" % field) if not dotname: continue self.message("\nGenerating '%s' DOT callgraph file..." % dotname) if self.emph_limit: limit = self.emph_limit else: # otherwise combine both "-f" and "-l" limits # limits are taken from full profile, not potentially reduced one sorter = ProfileSorter(profobj.profile, stats, None, False) limit = sorter.get_combined_limit(field, self.count, self.limit) name = stats.names[field] title = "%s\\nfor %s" % (name, fname) if profobj.emuinfo: title += "\\n(%s)" % profobj.emuinfo title += "\\n\\nown cost emphasis (gray bg) & total cost emphasis (red) limit = %.2f%%\\n" % limit if self.show_subcosts: title += "nodes which are subroutines and have accurate total costs, have diamond shape\\n" if removed: if self.remove_limited: title += "%d nodes below %.2f%% were removed\\n" % (removed, self.limit) else: title += "%d leaf and/or intermediate nodes below %.2f%% were removed\\n" % (removed, self.limit) if filtered: title += "%d nodes were filtered out\\n" % filtered self.write(self.header % title) self._output_nodes(stats, field, limit) self._output_edges() self.write(self.footer) # --------------------------------------------------------------------- class Main(Output): "program main loop & args parsing" longopts = [ "absolute=", "callgrind", "compact", "emph-limit=", "first", "full-symbols", "graph", "ignore=", "ignore-to=", "ignore-from=", "info", "limit=", "mark=", "no-calls=", "no-intermediate", "no-leafs", "no-limited", "no-orphans", "only=", "only-subroutines", "output=", "propagate", "relative=", "separator=", "stats", "top", "verbose" ] def __init__(self, argv): Output.__init__(self) self.name = os.path.basename(argv[0]) self.message("Hatari profile data processor") if len(argv) < 2: self.usage("argument(s) missing") self.args = argv[1:] def parse_args(self): "parse & handle program arguments" try: opts, rest = getopt.getopt(self.args, "a:e:f:gikl:o:pr:stv", self.longopts) except getopt.GetoptError as err: self.usage(err) prof = EmulatorProfile() graph = ProfileGraph() stats = ProfileStats() splitter = ',' for opt, arg in opts: #self.message("%s: %s" % (opt, arg)) # options for profile symbol parsing if opt == "--separator": splitter = arg elif opt in ("-a", "--absolute"): self.message("\nParsing absolute symbol address information from %s..." % arg) prof.parse_symbols(self.open_file(arg, "r"), False) elif opt in ("-r", "--relative"): self.message("\nParsing TEXT relative symbol address information from %s..." % arg) prof.parse_symbols(self.open_file(arg, "r"), True) # options for profile caller information parsing elif opt == "--no-calls": prof.remove_calls(arg) elif opt == "--ignore-to": prof.set_ignore_to(arg.split(splitter)) # options for profile Callgraph info generation elif opt in ("-k", "--callgrind"): prof.enable_callgrind() # options for both graphs & statistics elif opt in ("-f", "--first"): count = self.get_value(opt, arg, False) graph.set_count(count) stats.set_count(count) elif opt in ("-l", "--limit"): limit = self.get_value(opt, arg, True) graph.set_limit(limit) stats.set_limit(limit) elif opt in ("-p", "--propagate"): graph.enable_subcosts() stats.enable_subcosts() elif opt == "--compact": prof.enable_compact() graph.enable_compact() # options specific to graphs elif opt in ("-e", "--emph-limit"): graph.set_emph_limit(self.get_value(opt, arg, True)) elif opt == "--full-symbols": graph.enable_full_symbols() elif opt in ("-g", "--graph"): graph.enable_output() elif opt == "--ignore": graph.set_ignore(arg.split(splitter)) elif opt == "--ignore-from": graph.set_ignore_from(arg.split(splitter)) elif opt == "--only": graph.set_only(arg.split(splitter)) elif opt == "--mark": graph.set_marked(arg.split(splitter)) elif opt == "--no-intermediate": graph.disable_intermediate() elif opt == "--no-leafs": graph.disable_leafs() elif opt == "--no-limited": graph.disable_limited() elif opt == "--no-orphans": graph.disable_orphans() elif opt == "--only-subroutines": graph.only_subroutines() # options specific to statistics elif opt in ("-i", "--info"): stats.enable_info() elif opt in ("-s", "--stats"): stats.enable_totals() elif opt in ("-t", "--top"): stats.enable_top() # options for every class elif opt in ("-o", "--output"): out = self.open_file(arg, "w") self.message("\nSet output to go to '%s'." % arg) self.set_output(out) prof.set_output(out) stats.set_output(out) elif opt in ("-v", "--verbose"): prof.enable_verbose() stats.enable_verbose() graph.enable_verbose() else: self.usage("unknown option '%s' with value '%s'" % (opt, arg)) for arg in rest: self.message("\nParsing profile information from %s..." % arg) prof.parse_profile(self.open_file(arg, "r"), arg) graph.do_output(prof, arg) prof.output_info(arg) stats.do_output(prof) def open_file(self, path, mode): "open given path in given mode & return file object" try: return open(path, mode) except IOError as err: return self.usage("opening given '%s' file in mode '%s' failed:\n\t%s" % (path, mode, err)) def get_value(self, opt, arg, tofloat): "return numeric value for given string" try: if tofloat: return float(arg) return int(arg) except ValueError: return self.usage("invalid '%s' numeric value: '%s'" % (opt, arg)) def usage(self, msg): "show program usage + error message" self.message(__doc__) self.error_exit(msg) # --------------------------------------------------------------------- if __name__ == "__main__": Main(sys.argv).parse_args() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/hatari_spinloop.py000077500000000000000000000066011504763705000272400ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2013 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. """ Usage: hatari_spinloop.py Script for post-processing Hatari profiler looping information produced by "profile loops " command, after profiling is enabled with either "profile on" and/or "dspprofile on". That Hatari command saves spinloop data for any CPU and DSP code inner loop that spins for more than once. This script gives counts on how many times loops were executed, how many times they spinned at minimum and maximum, at which VBL those happened, and what was the standard deviation of that. Note: in some cases Hatari can list different sized spinloops for the same loop (start) address. This can be because code changed, or outer loop was sometimes repeated several times in succession without repeating the inner loop. """ import os, sys, math class LoopItem: def __init__(self, addr, size): self.addr = addr self.size = size self.loops = [] def add(self, count, vbl): self.loops.append((count, vbl)) def stats(self): "return max + its VBL, min + its VBL, count of separate loops, stddev for them, loop addr & size" self.loops.sort() count = len(self.loops) if count > 1: mean = sum([x for x,y in self.loops]) / float(count) mean2 = sum([(x-mean)**2 for x,y in self.loops]) / float(count-1) else: mean2 = 0.0 return (self.loops[-1][0], self.loops[-1][1], self.loops[0][0], self.loops[0][1], count, math.sqrt(mean2), self.addr, self.size) def output(processors, write): for name, data in processors.items(): write("\n%s loop statistics\n" % name) sorted = [] for item in data.values(): sorted.append(item.stats()) sorted.sort() sorted.reverse() write(" max:\tat VBL:\t min:\tat VBL:\ttimes:\tstddev:\taddr:\tsize:\n") for item in sorted: write("%7d\t%7d\t" % (item[0], item[1])) write("%7d\t%7d\t" % (item[2], item[3])) write("%7d\t%7.1f\t" % (item[4], item[5])) write(" %06x\t%6d\n" % (item[6], item[7])) def parse(fname): "parse looping information from given file object" processors = {} for line in open(fname).readlines(): if line[0] == '#': continue name, vbl, addr, size, count = line.split() if name not in processors: processors[name] = {} items = processors[name] addr = int(addr, 16) size = int(size) ident = (addr, size) if ident not in items: items[ident] = LoopItem(addr, size) items[ident].add(int(count), int(vbl)) return processors if __name__ == "__main__": if len(sys.argv) != 2 or not os.path.exists(sys.argv[1]): print(__doc__) sys.exit(1) data = parse(sys.argv[1]) output(data, sys.stdout.write) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/m68k-instructions.py000077500000000000000000000305541504763705000274000ustar00rootroot00000000000000#!/usr/bin/python3 # # code to generate m68k instruction opcode mask & value lookup table as C format # and a readline enabled console to get Hatari debugger breakpoint statements # matching the given instruction + some information about what the opcode bits # mean. # # m68k instruction information is based on HiSoft MC68000/68008/68010/68012 # Pocket Programming Guide. # # Copyright (C) 2011 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os import sys import readline class Instructions: # o, s are the important ones (in addition to 0/1 instruction specific bits) bittypes = { 's': "size (byte/word/long)", 'i': "dynamic, not immediate data", 'x': "source register", 'y': "destination register", 'r': "data / address register", 'v': "data value", 'o': "op-mode", 'm': "effective address mode", 'a': "effective address register", 't': "instruction type / direction", 'c': "condition / count", 'p': "displacement", 'b': "breakpoint / trap vector", } # if condition (!, <, >) isn't prefixed by bit type character (from above), # take the given value as-is, otherwise shift it so that it matches given # bit types (o or s) position and get mask for that bit type from the info. # # - 'and' may match also 'abcd' # - 'and' & 'anda' would need exact o values to not match # - 'exg' would need second o!10000 condition to avoid matching 'abcd' # - 'or' may match also 'sbcd' # # 5432109876543210, extra condition, instruction, condition notes: data = """ 1100yyy10000txxx - abcd 1101rrrooommmaaa - add 1101rrrooommmaaa o>010 adda 00000110ssmmmaaa - addi 0101rrr0ssmmmaaa s!11 addq 1101yyy1ss00txxx - addx 1100rrrooommmaaa !0xc0 and 00000010ssmmmaaa - andi 1110ccc1ssi00rrr - asl register 1110000111mmmaaa - asl memory 1110ccc0ssi00rrr - asr register 1110000011mmmaaa - asr memory 0110ccccpppppppp c>0001 bcc 0000rrr101mmmaaa - bchg dynamic 0000100001mmmaaa - bchg static 0000rrr110mmmaaa - bclr dynamic 0000100010mmmaaa - bclr static 0100100001001bbb - bkpt 01100000pppppppp - bra 0000rrr111mmmaaa - bset dynamic 0000100011mmmaaa - bset static 01100001pppppppp - bsr 0000rrr100mmmaaa - btst dynamic 0000100000mmmaaa - btst static 0100rrr110mmmaaa - chk 01000010ssmmmaaa - clr 1011rrrooommmaaa o<011 cmp 1011rrrooommmaaa o>010 cmpa 00001100ssmmmaaa - cmpi 1011yyy1ss001xxx - cmpm 0101cccc11001rrr - dbcc 1000rrr111mmmaaa - divs 1000rrr011mmmaaa - divu 1011rrrooommmaaa o>011 eor 00001010ssmmmaaa - eori 1100yyy1oooooxxx o<10111 exg 0100100ooo000rrr o>001 ext 0100101011111100 - illegal 0100111011mmmaaa - jmp 0100111010mmmaaa - jsr 0100rrr111mmmaaa - lea 0100111001010rrr - link 1110ccc1ssi01rrr - lsl register 1110001111mmmaaa - lsl memory 1110ccc0ssi01rrr - lsr register 1110001011mmmaaa - lsr memory 00ssyyymmmmmmxxx s>00 move 00ssyyy001mmmxxx s>01 movea 010011100110trrr - moveusp 010011100111101t - movec 01001t001smmmaaa - movem 0000rrrooo001rrr o>011 movep 0111rrr0vvvvvvvv - moveq 00001110ssmmmaaa - moves 1100rrr111mmmaaa - muls 1100rrr011mmmaaa - mulu 0100100000mmmaaa - nbcd 01000100ssmmmaaa - neg 01000000ssmmmaaa - negx 0100111001110001 - nop 01000110ssmmmaaa - not 1000rrrooommmaaa !0xc0 or 00000000ssmmmaaa - ori 0100100001mmmaaa - pea 0100111001110000 - reset 1110ccc1sst11rrr - rol register 1110011111mmmaaa m>001 rol memory 1110ccc0sst11rrr - ror register 1110011011mmmaaa m>001 ror memory 1110ccc1sst10rrr - roxl register 1110010111mmmaaa m>001 roxl memory 1110ccc0sst10rrr - roxr register 1110010011mmmaaa m>001 roxr memory 0100111001110100 - rtd 0100111001110011 - rte 0100111001110111 - rtr 0100111001110101 - rts 1000yyy10000txxx - sbcd 0101cccc11mmmaaa - scc 0100111001110010 - stop 1001rrrooommmaaa - sub 1001rrrooommmaaa o>010 suba 00000100ssmmmaaa - subi 0101rrr1ssmmmaaa - subq 1001yyy1ss00txxx - subx 0100100001000rrr - swap 0100101011mmmaaa - tas 010011100100bbbb - trap 0100111001110110 - trapv 01001010ssmmmaaa s<11 tst 0100111001011rrr - unlink """ def __init__(self): self.items = self.parse_data() def show(self, name): # TODO: handle dynamic/static & register/memory variants item = None for i in self.items: if i['name'] == name: item = i break if not item: print("ERROR: unknown instruction '%s'" % name) return print("\nInstruction:") print(" %(name)s - %(info)s" % item) specialbits = item['mask'] != 0xffff if specialbits: print("Bits:") oldchar = None for i in item['info']: if i in self.bittypes and i != oldchar: print(" - %c: %s" % (i, self.bittypes[i])) oldchar = i print("\nHatari breakpoint:") print(" b (pc).w", end=' ') if specialbits: print("& $%(mask)x" % item, end=' ') print("= $%(bits)x" % item, end=' ') if item['op'] != '-': print(" && (pc).w & $%(bmask)x %(op)c $%(bvalue)x" % item, end=' ') print() print() def get_names(self): return [x['name'] for x in self.items] def parse_data(self): items = [] for line in self.data.split(os.linesep): line = line.split('#')[0] # remove comment line.strip() if not line: continue splitted = line.split() info = splitted[0] bits, mask = self.parse_bits(info) bmask, op, bvalue = self.parse_cond(info, splitted[1]) name = " ".join(splitted[2:]) items.append({'bits':bits, 'mask':mask, 'info':info, 'bmask':bmask, 'op': op, 'bvalue':bvalue, 'name':name}) return items def parse_bits(self, bitstr): "bitstr = types of bits for instruction" if len(bitstr) != 16: print("ERROR: '%s' doesn't represent 16 bits" % bitstr) sys.exit(1) bits = 0 mask = 0 for bit in bitstr: if bit in ('0','1'): bits = bits << 1 | int(bit) mask = mask << 1 | 1 else: bits <<= 1 mask <<= 1 return bits, mask def parse_cond(self, bitstr, condstr): "bitstr = types of bits for instruction, condstr = " condmask = 0 condshift = 0 condtype = condstr[0] # mask & value are given directly? if condtype != '-' and condtype not in self.bittypes: condmask = int(condstr[1:], 16) return condmask, condtype, condmask for bit in bitstr: if bit in ('0','1'): condmask <<= 1 condshift += 1 else: if bit == condtype: condmask = condmask << 1 | 1 condshift = 0 else: condmask <<= 1 condshift += 1 if not condmask: return 0, "-", 0 # otherwise use mask & value position from bitstr string condbits = 0 if condstr != "-": condop = condstr[1] for bit in condstr[2:]: if bit not in ('0','1'): print("ERROR: '%s' value isn't a bitvector" % condstr) sys.exit(1) condbits = condbits << 1 | int(bit) return condmask, condop, condbits << condshift def check(self): # sanity check for matches between different entries write = sys.stderr.write for item in self.items: for test in self.items: if item == test: continue # no duplicate info? if test['info'] == item['info'] and test['bmask'] == item['bmask'] and test['op'] == item['op'] and test['bvalue'] == item['bvalue']: write("ERROR: '%s' duplicates info for '%s'\n" % (item['name'], test['name'])) # mask & bits is unique? if test['bits'] & item['mask'] == item['bits']: oper = item['op'] if oper == '!' and test['bits'] & item['bmask'] == item['bvalue']: continue if oper == '<' and test['bits'] & item['bmask'] >= item['bvalue']: continue if oper == '>' and test['bits'] & item['bmask'] <= item['bvalue']: continue write("WARNING: '%s' bits & mask may match '%s'\n" % (item['name'], test['name'])) for it in item, test: if it['bmask']: write("%(name)13s: bits & %(mask)04x = %(bits)04x, bits & %(bmask)04x %(op)s %(bvalue)04x\n" % it) else: write("%(name)13s: bits & %(mask)04x = %(bits)04x\n" % it) def print_items(self): print("""/* * instruction bitmask lookup table * generated by m68k-instructions.py * * instruction mask, bits, info string, comparison mask, comparison operator, value to compare against, instruction name */ instruction_t instructions = {""") for item in self.items: if item['op']: print("\t{ 0x%(mask)04x, 0x%(bits)04x, \"%(info)s\", 0x%(bmask)04x, '%(op)s', 0x%(bvalue)04x, \"%(name)s\" }," % item) else: print("\t{ 0x%(mask)04x, 0x%(bits)04x, \"%(info)s\", 0x%(bmask)x, '', 0, \"%(name)s\" }," % item) print("};") def print_help(self): print("help_txt =") print("\"\tInstruction bit type identifiers:\\n\"") for item in self.bittypes.items(): print("\"\t%s = %s\\n\"" % item) print(";") # main loop and command line parsing with readline class Main: prompt = "m68k-instruction: " historysize = 99 def __init__(self): self.commandlist = { "code": self.show_code, "check": self.show_checks, "help": self.show_help, "exit": sys.exit } self.instructions = Instructions() cmds = list(self.commandlist.keys()) + self.instructions.get_names() cmds.sort() self.commands = cmds self.init_readline() def loop(self): self.show_help() while 1: line = self.get_input().strip() if not line: continue if line in self.commandlist: self.commandlist[line]() else: self.instructions.show(line) def show_help(self): print(""" ************************************************************* * m68k instruction information, Hatari debugger breakpoints * * for them and C-code table for matching them. * * * * To see instructions, use the TAB key; to see instruction * * information, complete it and press Enter. "code" command * * shows the C-code, "check" shows potential instruction * * conflicts, "help" shows this help and "exit" exits. * ************************************************************* """) def show_code(self): self.instructions.print_items() print() self.instructions.print_help() def show_checks(self): self.instructions.check() def init_readline(self): readline.set_history_length(self.historysize) readline.parse_and_bind("tab: complete") readline.set_completer_delims(" \t\r\n") readline.set_completer(self.complete) def complete(self, text, state): idx = 0 #print "text: '%s', state '%d'" % (text, state) for cmd in self.commands: if cmd.startswith(text): idx += 1 if idx > state: return cmd return None def get_input(self): try: rawline = input(self.prompt) return rawline except EOFError: return "" if __name__ == "__main__": Main().loop() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/nm-symbols-cleanup.sh000077500000000000000000000020241504763705000275470ustar00rootroot00000000000000#!/bin/sh # # script to cleanup 'nm' output for Hatari debugger. # # 2013 (C) Eero Tamminen, licensed under GPL v2+ usage () { name=${0##*/} echo echo "Usage: $name " echo echo "Clean up from 'nm' symbol address output" echo "symbols that aren't useful with Hatari debugger" echo "e.g. because those (non-interesting) symbols" echo "are bound to multiple addresses:" echo "- GCC (2.x) internal symbols" echo "- Repeating local symbol names ('.L')" echo "- Assignments / defines which aren't addresses" echo "- Weak symbols that are just aliases for other symbols" echo "Paths are also remove from object file names." echo echo "For example:" echo " $name foobar.nm > foobar.sym" echo echo "ERROR: $1!" echo exit 1 } if [ $# -ne 1 ]; then usage "incorrect number of arguments" fi if [ ! -f "$1" ]; then usage "given '$1' nm output file not found" fi # clean symbol cruft & sort by hex address grep -v -e ' [aAwW] ' -e ' t \.L' -e gcc2_compiled -e _gnu_compiled_c "$1" |\ sed 's%/.*/%%' | sort hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/debugger/sym-clean-test.py000066400000000000000000000070151504763705000267070ustar00rootroot00000000000000# Tester for C++ symbol shortening in hatari_profile.py import re # few of the symbols used by ScummVm 2.6 funcs = [ "famisras10", "_d_number.isra.111", "operator delete(void*)", "operator new(unsigned long)", "operator new(unsigned long, std::nothrow_t const&)", "Common::operator+(int, Common::Rational const&)", "Common::operator-(int, Common::Rational const&)", "Common::operator*(int, Common::Rational const&)", "Common::operator/(int, Common::Rational const&)", "Common::operator==(int, Common::Rational const&)", "Common::operator!=(int, Common::Rational const&)", "Common::operator>(int, Common::Rational const&)", "Common::operator<(int, Common::Rational const&)", "Common::operator>=(int, Common::Rational const&)", "Common::operator<=(int, Common::Rational const&)", "virtual thunk to OSystem_Atari::initBackend()", "Sci::Script::isValidOffset(unsigned int) const", "(anonymous namespace)::pool::free(void*) [clone .constprop.4]", "Sci::MidiDriver_FMTowns::setTimerCallback(void*, void (*)(void*))", "non-virtual thunk to StdioStream::write(void const*, unsigned int)", "Scumm::AkosRenderer::byleRLEDecode(Scumm::BaseCostumeRenderer::ByleRLEData&)", "Common::setErrorOutputFormatter(void (*)(char*, char const*, unsigned long))", "Common::(anonymous namespace)::BufferedWriteStream::write(void const*, unsigned int)", "non-virtual thunk to Common::(anonymous namespace)::BufferedSeekableReadStream::seek(long long, int)", "Sci::GfxAnimate::processViewScaling(Sci::GfxView*, Common::ListInternal::Iterator)", "Common::SpanBase::validate(unsigned int, int, Common::SpanValidationMode) const [clone .part.167]", "Common::HashMap >::lookup(Sci::reg_t const&) const [clone .isra.42]", "Common::HashMap, Common::EqualTo >, Common::Hash, Common::EqualTo >::getVal(unsigned int const&) const [clone .isra.23]" "Common::sort(TwinE::Renderer::RenderCommand*, TwinE::Renderer::depthSortRenderCommands(int)::{lambda(TwinE::Renderer::RenderCommand const&, TwinE::Renderer::RenderCommand const&)#1}, TwinE::Renderer::depthSortRenderCommands(int)::{lambda(TwinE::Renderer::RenderCommand const&, TwinE::Renderer::RenderCommand const&)#1}) [clone .isra.26]", ] # C symbols re_isra = re.compile(r"\.isra\.[0-9]+$") # C++ symbols re_thunk = re.compile(r"(non-)?virtual ") # ...thunk to re_clone = re.compile(r" \[clone[^]]+\]") # ...isra re_args = re.compile(r"[^(+]+::[^(+]+(\(.+\))") # class::method(args) out = [] for f in funcs: n = f if not (" " in n or "::" in n): # C-symbols for r in (re_isra,): m = r.search(n) if m: n = n[:m.start()] + n[m.end():] print("'%s' => '%s'" % (f, n)) continue # remove args from method signatures m = re_args.search(n) if m: print(len(n), m.start(1), m.end(1)) n = n[:m.start(1)] + "()" + n[m.end(1):] # shorten namespaces and template args n = n.replace("anonymous namespace", "anon ns") n = n.replace("unsigned ", "u") # remove virtual and clone infos for r in (re_thunk, re_clone): m = r.search(n) if m: n = n[:m.start()] + n[m.end():] print("'%s' => '%s'" % (f, n)) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hatari-local-midi-ring.sh000077500000000000000000000025121504763705000264550ustar00rootroot00000000000000#!/bin/sh # argument checks if [ $# -lt 1 ] || [ -n "$(echo "$1"|tr -d 0-9)" ] || [ "$1" -lt 2 ] || [ "$1" -gt 16 ]; then echo "Usage: ${0##*/} [extra Hatari args]" echo echo "Example of running 4 Hatari instances in a MIDI ring, each using midi/ as HD:" echo " ${0##*/} 4 -d midi/" exit 1 fi count=$1 # rest of args go to Hatari shift # open fifos for i in $(seq "$count"); do if ! mkfifo "midi$i"; then echo "ERROR: creating FIFO 'midi$i' for MIDI communication failed!" exit 1 fi done # show full path hatari=$(which hatari) # run MIDI ring Hatari instances catpids="" for i in $(seq $((count-1))); do next=$((i+1)) echo "$hatari --midi-in midi$i --midi-out midi$next $* &" "$hatari" --midi-in "midi$i" --midi-out "midi$next" "$@" & # Without this Hataris would deadlock as fifos # block the process until both ends are opened. # Hatari opens midi output first (for writing), # so this needs to read it. cat "midi$next" >/dev/null & catpids="$catpids $!" done # and join the beginning and end of the MIDI ring echo "$hatari --midi-in midi$count --midi-out midi1 $* &" "$hatari" --midi-in "midi$count" --midi-out midi1 "$@" & cat midi1 >/dev/null & catpids="$catpids $!" # after connections are open, fifo files can be removed sleep 2 for i in $(seq "$count"); do rm "midi$i" done kill -9 $catpids hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hatari-local-rs232.sh000077500000000000000000000023501504763705000254510ustar00rootroot00000000000000#!/bin/sh # # Start two Hatari instances connected through emulated rs232 connection. # Given arguments (if any) are passed to the invoked Hatari instances. # Their rs232 connection goes through shared local fifo file. # # To run development version of Hatari, use something like: # PATH=../../build/src:$PATH hatari-local-rs232.sh # open fifos for i in 1 2; do if ! mkfifo rs232-$i; then echo "ERROR: creating FIFO 'rs232-$i' for RS232 communication failed!" exit 1 fi done # show full path hatari=$(which hatari) # connect the Hatari instances with fifos and pass all args to them echo "$hatari --rs232-in rs232-1 --rs232-out rs232-2 $* &" "$hatari" --rs232-in rs232-1 --rs232-out rs232-2 "$@" & echo "$hatari --rs232-in rs232-2 --rs232-out rs232-1 $* &" "$hatari" --rs232-in rs232-2 --rs232-out rs232-1 "$@" & # Without this Hataris would deadlock as fifos # block the process until both ends are opened. # Hatari opens rs232 output first (for writing), # so this needs to read it. catpids="" cat rs232-2 >/dev/null & catpids="$catpids $!" cat rs232-1 >/dev/null & catpids="$catpids $!" # after connections are open, fifo files can be removed sleep 2 for i in 1 2; do rm rs232-$i done # as can the 'cat' processes kill -9 $catpids hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hatari-prg-args.1000066400000000000000000000037251504763705000247620ustar00rootroot00000000000000.TH "hatari-prg-args" "1" "2018-05-01" "Hatari" "Hatari utilities" .SH NAME hatari\-prg\-args \- autorun Atari programs with arguments .SH SYNOPSIS .B hatari\-prg\-args [\fI\-q\fP] [\fIHatari args\fP] \fB--\fP <\fIAtari program\fP> <\fIprogram args\fP> .SH DESCRIPTION Utility for running Hatari so that it autostarts given Atari program and inserts given arguments to program (to its basepage, with builtin Hatari debugger facilities). .PP If Atari program arguments have same (host) path prefix as given Atari program, those prefixes are replaced with "C:\\". Unix path separators (/) are replaced with Atari ones (\\) and whole filename is upper-cased. .PP If program and its input files are on a disk image, all paths need to be given as on with TOS, and won't be translated. .PP Other program arguments are passed unmodified. .SH NOTES Maximum Atari program command line length that can be given (in basepage) is 126 characters. .PP On Unix, Atari/DOS path separators (\\) need to be quoted. .SH OPTIONS .TP .B \-q Make Hatari quit when the started Atari program terminates. If given, this needs to be the first argument. .SH EXAMPLES .TP Start SID player in mono and tell it to play given SID song: .B hatari\-prg\-args -m -- atari/sidplay.ttp atari/sids/test.sid .TP Run it from disk image instead of host directory: .B hatari\-prg\-args -m -- 'A:\\\\SIDPLAY.TTP' 'SIDS\\\\TEST.SID' .SH SEE ALSO .IR hatari (1), .IR hconsole (1) .SH "AUTHOR" Written by Eero Tamminen . .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hatari-prg-args.sh000077500000000000000000000105321504763705000252310ustar00rootroot00000000000000#!/bin/sh # # script to set Atari program args after TOS starts it. # # this is achieved by chaining breakpoints from Hatari startup # to program startup, and finally writing the arguments to # the program basepage. set -e # name (including path if necessary) of the hatari executable hatari=hatari usage () { name=${0##*/} echo "usage: $name [-q] [Hatari args] -- " echo echo "Script for running Hatari so that it autostarts given Atari program" echo "and inserts given arguments to program (to its basepage, with builtin" echo "Hatari debugger facilities)." echo echo "Options:" echo " -q Quit Hatari after Atari program exits" echo echo "If arguments have same (host) path prefix as given Atari program," echo "those prefixes are replaced with 'C:\', Unix path separators ('/')" echo "are replaced with Atari ones ('\\') and whole filename upper-cased." echo echo "Other arguments are passed unmodified." echo echo "Examples:" echo echo "* program in host folder and input files in same folder:" echo " $name -m -- atari/sidplay.ttp atari/sids/test.sid" echo echo "* program on a disk image:" echo " $name -m -- 'A:\\SIDPLAY.TTP' 'SIDS\\TEST.SID'" echo echo "Note: argument quoting is needed with '\\' chars." echo echo "ERROR: $1!" exit 1 } # --------- argument parsing -------- # generic argument checking if [ $# -lt 3 ]; then usage "not enough arguments" fi if ! echo " $* " | grep -q ' -- '; then usage "Separator missing for Hatari -- Atari options" fi # quiet option comes first quit=0 if [ "$1" = "-q" ]; then quit=1 shift fi # collect/remove hatari arguments hargs="" for arg in "$@"; do shift if [ "$arg" = '--' ]; then break fi hargs="$hargs $arg" done # check atari program prg=$1 shift if [ ! -f "$prg" ]; then if [ "${prg#[A-Z]:}" != "$prg" ]; then # program path inside disk image hargs="--auto $prg $hargs" prg="" else # non-existing *non-Atari* path given usage "given Atari program '$prg' does not exist" fi fi # builtin shell echo can be broken, so that it interprets backlashes by default, # i.e. concatenating '\' path separator with dirname 't', produces TAB, not '\t'... # -> use separate echo binary echo=$(which echo) # temporary dir for debugger scripts dir=$(mktemp -d) # collect/convert atari program arguments args=$dir/args rm -f "$args" touch "$args" prefix="" drive="C:\\" path="${prg%/*}/" for arg in "$@"; do if [ "${arg#$path}" != "$arg" ]; then # file path needing conversion if [ ! -f "$arg" ]; then usage "given file name '$arg' does not exist" fi # prefix with separator & drive letter & remove host path, # upper-case, convert path separators # # it gets 3 things wrong on same line: # shellcheck disable=SC2018 # shellcheck disable=SC2019 # shellcheck disable=SC1003 $echo -n "${prefix}${drive}${arg#$path}" | tr a-z A-Z | tr '/' '\\' >> "$args" else $echo -n "${prefix}$arg" >> "$args" fi prefix=" " done # calculate command line length and append zero just in case cmdlen=$(wc -c "$args" | awk '{print $1}') printf "\0" >> "$args" if [ "$cmdlen" -gt 126 ]; then usage "command line is too long, $cmdlen chars (basepage limit is 126)" fi # --------- debugger scripts -------- # until TOS has started, it's not possible to set breakpoint # to program startup, there's no valid basepage and therefore # no valid TEXT variable. So set breakpoint on Pexec(0,...) cat > "$dir/pexec.ini" << EOF b GemdosOpcode = 0x4B && OsCallParam = 0x0 :once :quiet :trace :file $dir/start.ini EOF # real work can be done when program starts, # i.e. PC is at TEXT section beginning cat > "$dir/start.ini" << EOF b pc = text :once :trace :quiet :file $dir/basepage.ini EOF # add command line args args to program basepage cat > "$dir/basepage.ini" << EOF setopt dec w "basepage+0x80" $cmdlen l $args "basepage+0x81" # info basepage EOF # quit Hatari after program exits if [ $quit -eq 1 ]; then # catch Pterm0() & Pterm() cat >> "$dir/basepage.ini" << EOF b GemdosOpcode = 0x00 :quiet :trace :file $dir/quit.ini b GemdosOpcode = 0x4C :quiet :trace :file $dir/quit.ini EOF echo "quit" > "$dir/quit.ini" fi # --------- ready, run ---------- # show args & debugger scripts head "$dir"/* # run Hatari echo "$hatari --parse $dir/pexec.ini $hargs $prg" # shellcheck disable=SC2086 # options must be split to work $hatari --parse "$dir/pexec.ini" $hargs "$prg" # cleanup rm -r "$dir" hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hatari-tos-register.sh000077500000000000000000000030771504763705000261440ustar00rootroot00000000000000#!/bin/sh # # minimal Linux init script for registering Hatari as binfmt_misc # handler for TOS binaries # # NOTE: newer Debian derived distributions have convenience functionality # for this. Just put something like this to /usr/share/binfmts/hatari: # package hatari # interpreter /usr/bin/hatari # magic \x60\x1a # And call these when Hatari is installed and removed: # update-binfmts --import hatari # update-binfmts --remove hatari /usr/bin/hatari # name given for binftm_misc name=TOS # first bytes for GEMDOS programs magic="\x60\x1a" # what is used to "interpret" (run) these programs runner=/usr/bin/hatari register_binfmt () { # Mount the binfmt_misc directory if not already mounted if [ ! -e /proc/sys/fs/binfmt_misc/register ]; then (set -e; mount none /proc/sys/fs/binfmt_misc -t binfmt_misc) fi if [ -e /proc/sys/fs/binfmt_misc/$name ] ; then echo "WARNING: $name binfmt_misc handler already registered." return fi # Register runner for files starting with specified magic regfile=/proc/sys/fs/binfmt_misc/register if ! echo ":$name:M::$magic::$runner:" > $regfile ; then echo "ERROR: registering $name binfmt_misc handler failed!" exit 1 fi echo "Registered $name binfmt_misc handler." } deregister_binfmt () { if [ -e /proc/sys/fs/binfmt_misc/$name ] ; then echo -1 > /proc/sys/fs/binfmt_misc/$name echo "De-registered $name binfmt_misc handler." else echo "WARNING: no binfmt_misc handler for $name." fi } case "$1" in start) register_binfmt ;; stop) deregister_binfmt ;; *) echo "Usage: ${0##*/} start|stop" >&2 exit 1 ;; esac hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/000077500000000000000000000000001504763705000235135ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/CMakeLists.txt000066400000000000000000000014171504763705000262560ustar00rootroot00000000000000 # example.py imports hconsole.py, so it needs to be in the same dir INSTALL(PROGRAMS hconsole.py example.py DESTINATION ${DATADIR}/hconsole/) # files imported by example.py, also need to be in the same dir INSTALL(FILES example-commands example-debugger DESTINATION ${DATADIR}/hconsole/) # documentation INSTALL(FILES release-notes.txt DESTINATION ${DOCDIR}/hconsole/) if(ENABLE_MAN_PAGES) add_custom_target(hconsole_man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hconsole.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hconsole.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/hconsole.1 > ${CMAKE_CURRENT_BINARY_DIR}/hconsole.1.gz DEPENDS hconsole.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hconsole.1.gz DESTINATION ${MANDIR}) endif(ENABLE_MAN_PAGES) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/example-commands000066400000000000000000000023631504763705000266740ustar00rootroot00000000000000# This is an example hconsole input file # # You can try it with something like this: # hconsole.py example-commands --exit -- --tos etos1024k.img # # I.e. specify EmuTOS image for Hatari and exit after the the input file. # # For more fun, use also Hatari's "--conout 2". :-) # without this EmuTOS boot takes 10s more setopt --fastfdc on --fast-forward on # wait for EmuTOS to boot sleep 3 # boot done, back to normal setopt --fastfdc off --fast-forward off # Note: keypress/down/up commands take either one character or # a multidigit key scancode. Scancodes need to be used for keys # that produce non-alphanumeric characters (white space would # even get stripped by command processing). # # Scancodes for special keys: # - Tab: 15 # - Return: 28 # - Enter: 114 # - Space: 57 # - Delete: 83 # - Backspace: 14 # - Escape: 0x1 # - Control: 29 # - Alternate: 56 # - Left Shift: 42 # - Right Shift: 54 # - Caps Lock: 53 # - Insert: 82 # - Home: 71 # - Help: 98 # - Undo: 97 # - Cursor Up: 72 # - Cursor Down: 80 # - Cursor Left: 75 # - Cursor Right: 77 # Invoke EmuCON with ^Z: Control down, press Z, Control up keydown 29 keypress Z keyup 29 # wait for EmuCON to start sleep 1 # and output some text to EmuTOS console text Welcome to 'hconsole' example... hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/example-debugger000066400000000000000000000012701504763705000266530ustar00rootroot00000000000000# Show disassembly from address in A0 when debugger is entered: lock regaddr disasm a0 # Track when GEMDOS is called with the Dgetdrv() opcode # (happens e.g. when EmuTOS console prints the prompt): breakpoint GemdosOpcode = 0x19 :lock # NOTE: in addition to information specified by "lock" command # being printed, ":lock" flag causes breakpoint hit to just to # be traced, instead of emulation dropping to debugger. # # This is important when using hconsole because entering the Hatari # debugger (and its line editing) will mess up the terminal from # which hconsole is run from. At least until user has blindly # typed "cont" to the Hatari debugger so that Hatari emulation # continues. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/example.py000077500000000000000000000025551504763705000255320ustar00rootroot00000000000000#!/usr/bin/python3 # # This is an example of scripting hatari using hconsole # # Run with (using correct EmuTOS path): # PATH=../../build/src/:$PATH ./example.py --tos etos1024k.img # Or if Hatari and hconsole are installed to the system: # /usr/share/hatari/hconsole/example.py --tos etos1024k.img import hconsole, os, sys # path for this script path = os.path.dirname(sys.argv[0]) # current work directory cwd = os.path.abspath(os.path.curdir) # shortcuts to hconsole stuff # # GEMDOS emulation dir is given because without # a disk, EmuTOS console invocation is ~8s main = hconsole.Main(sys.argv + ["."]) code = hconsole.Scancode # execute commands from external file in current directory main.script(path + "/example-commands") # define shortcut functions for entering specific key presses def backspace(): main.run("keypress %s" % code.Backspace) def enter(): main.run("keypress %s" % code.Return) # loop removing some of previously script output for i in range(25): backspace() enter() # output some text to EmuTOS console in a loop for i in range(3): main.run("text echo Welcome to 'hconsole'") enter() # ask Hatari debugger to parse breakpoint etc commands from file main.run("parse %s/example-debugger" % path) # hit a Getdrv() breakpoint when EmuTOS prompt is redrawn enter() # wait few secs and kill Hatari main.run("sleep 3") main.run("kill") hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/hconsole.1000066400000000000000000000046611504763705000254160ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "HCONSOLE" "1" "2010-10-10" "Hatari" "Hatari utilities" .SH "NAME" hconsole \- Interactive Python console for using Hatari's remote API .SH "SYNOPSIS" .B hconsole.py .IR [[console options] .IR [command file] .B \-\-] .IR [hatari options] .SH "DESCRIPTION" .I Hconsole is a Python script that invokes Hatari and then provides an interactive shell (with full readline editing capabilities) for executing Hatari remote API commands. .PP It can be useful for first exploring Hatari's remote API usage interactively and then writing scripts that somehow automate your Hatari usage. They can invoke hconsole functionality simply by doing "import hconsole" and calling appropriate methods (see example.py coming with hconsole). .SH "USAGE" Normally hconsole forwards all of its arguments to Hatari instance it invoked. If you want to give arguments for hconsole itself, you need to add '\-\-' argument after the hconsole arguments and before the arguments going to Hatari. .PP If you give a file name as hconsole argument, commands in it will be read and executed through Hatari's remote API before you get into hconsole interactive shell. .PP Hconsole accepts following options: .TP .B \-\-exit Exit after starting Hatari and parsing arguments. .TP .B \-\-help, \-h Show command line help. .SH "EXAMPLES" Start Hatari with all of the given arguments: .br hconsole.py \-\-monitor mono \-d test/ .PP Start Hatari without extra arguments, execute commands from the given commands.txt file, exit after the file ends: .br hconsole.py commands.txt \-\-exit \-\- .SH FILES By default hconsole and its examples are installed under the /usr/share/hatari/hconsole/ directory. .SH "SEE ALSO" .IR hatari (1), .IR hatariui (1) .SH "AUTHOR" Written by Eero Tamminen . .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/hconsole.py000077500000000000000000000452471504763705000257160ustar00rootroot00000000000000#!/usr/bin/python3 # # Hatari console: # Allows using Hatari shortcuts & debugger, changing paths, toggling # devices and changing Hatari command line options (even for things you # cannot change from the UI) from the console while Hatari is running. # # Copyright (C) 2008-2024 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import os import sys import time import signal import socket import readline class Scancode: "Atari scancodes for keys without alphanumeric characters" # US keyboard scancode mapping for characters which need shift Shifted = { '!': "0x2", '@': "0x3", '#': "0x4", '$': "0x5", '%': "0x6", '^': "0x7", '&': "0x8", '*': "0x9", '(': "10", ')': "11", '_': "12", '+': "13", '~': "41", '{': "26", '}': "27", ':': "39", '"': "40", '|': "43", '<': "51", '>': "52", '?': "53" } # US keyboard scancode mapping for characters which don't need shift UnShifted = { '-': "12", '=': "13", '[': "26", ']': "27", ';': "39", "'": "40", '\\': "43", '",': "51", '.': "52", '/': "53" } # special keys without corresponding character Tab = "15" Return = "28" Enter = "114" Space = "57" Delete = "83" Backspace = "14" Escape = "0x1" Control = "29" Alternate = "56" LeftShift = "42" RightShift = "54" CapsLock = "53" Insert = "82" Home = "71" Help = "98" Undo = "97" CursorUp = "72" CursorDown = "80" CursorLeft = "75" CursorRight = "77" # running Hatari instance class Hatari: controlpath = "/tmp/hatari-console-" + str(os.getpid()) + ".socket" hataribin = "hatari" def __init__(self, args): # member defaults self.pid = 0 self.interval = 0.2 self.shiftdown = False self.verbose = False self.control = None self.paused = False self.winuae = False # collect hatari process zombies without waitpid() signal.signal(signal.SIGCHLD, signal.SIG_IGN) self._assert_hatari_compatibility() self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) if os.path.exists(self.controlpath): os.unlink(self.controlpath) self.server.bind(self.controlpath) self.server.listen(1) if not self.run_hatari(args): print("ERROR: failed to run Hatari") sys.exit(1) def _assert_hatari_compatibility(self): "check Hatari compatibility and return error string if it's not" print("Using following Hatari binary:") os.system("which %s" % self.hataribin) error = "Hatari lacks '--control-socket' option (Windows?)" pipe = os.popen(self.hataribin + " -h") for line in pipe.readlines(): if line.find("--addr24") >= 0: self.winuae = True if line.find("--control-socket") >= 0: error = None break try: pipe.close() except IOError: pass if error: print("ERROR: %s" % error) sys.exit(-1) def is_running(self): if not self.pid: return False try: os.waitpid(self.pid, os.WNOHANG) except OSError as value: print("Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value)) self.pid = 0 if self.control: self.control.close() self.control = None return False return True def run_hatari(self, args): if self.control: print("ERROR: Hatari is already running, stop it first") return pid = os.fork() if pid < 0: print("ERROR: fork()ing Hatari failed!") return if pid: # in parent self.pid = pid print("WAIT hatari to connect to control socket...") (self.control, addr) = self.server.accept() print("connected!") return self.control else: # child runs Hatari allargs = [self.hataribin, "--control-socket", self.controlpath] + args print('RUN: "%s"' % ' '.join(allargs)) os.execvp(self.hataribin, allargs) def send_message(self, msg, fast = False): if self.control: if self.verbose: print("-> '%s'" % msg) self.control.sendall(bytes(msg + "\n", "ASCII")) # KLUDGE: wait so that Hatari output comes before next prompt if fast: interval = self.interval/4 else: interval = self.interval time.sleep(interval) return True else: print("ERROR: no Hatari (control socket)") return False def change_option(self, option): return self.send_message("hatari-option %s" % option) def trigger_shortcut(self, shortcut): return self.send_message("hatari-shortcut %s" % shortcut) def _shift_up(self): if self.shiftdown: self.shiftdown = False return self.send_message("hatari-event keyup %s" % Scancode.LeftShift, True) return True def _unshifted_keypress(self, key): self._shift_up() if key == ' ': # white space gets stripped, use scancode instead key = Scancode.Space return self.send_message("hatari-event keypress %s" % key, True) def _shifted_keypress(self, key): if not self.shiftdown: self.shiftdown = True self.send_message("hatari-event keydown %s" % Scancode.LeftShift, True) return self.send_message("hatari-event keypress %s" % key, True) def send_string(self, text): print("string:", text) for key in text: if key in Scancode.Shifted: ok = self._shifted_keypress(Scancode.Shifted[key]) elif key in Scancode.UnShifted: ok = self._unshifted_keypress(Scancode.UnShifted[key]) else: ok = self._unshifted_keypress(key) if not ok: return False return self._shift_up() def insert_event(self, event): if event.startswith("text "): cmd, value = event.split(None, 1) if value: return self.send_string(value) return self.send_message("hatari-event %s" % event, True) def debug_command(self, cmd): return self.send_message("hatari-debug %s" % cmd) def change_path(self, path): return self.send_message("hatari-path %s" % path) def toggle_device(self, device): return self.send_message("hatari-toggle %s" % device) def toggle_pause(self): self.paused = not self.paused if self.paused: return self.send_message("hatari-stop") else: return self.send_message("hatari-cont") def toggle_verbose(self): self.verbose = not self.verbose print("debug output", self.verbose) def kill_hatari(self): if self.is_running(): os.kill(self.pid, signal.SIGKILL) print("killed hatari with PID %d" % self.pid) self.pid = 0 if self.control: self.control.close() self.control = None # command line parsing with readline class CommandInput: prompt = "hatari-command: " historysize = 99 def __init__(self, commands): readline.set_history_length(self.historysize) readline.parse_and_bind("tab: complete") readline.set_completer_delims(" \t\r\n") readline.set_completer(self.complete) self.commands = commands def complete(self, text, state): idx = 0 #print "text: '%s', state '%d'" % (text, state) for cmd in self.commands: if cmd.startswith(text): idx += 1 if idx > state: return cmd def loop(self): try: rawline = input(self.prompt) return rawline except EOFError: return "" class Tokens: # update with: hatari -h|grep -- --|sed 's/^ *\(--[^ ]*\).*$/ "\1",/'|grep -v -e control-socket -e 'joy<' option_tokens = [ "--help", "--version", "--confirm-quit", "--configfile", "--keymap", "--country", "--layout", "--language", "--fast-forward", "--auto", "--mono", "--monitor", "--tos-res", "--fullscreen", "--window", "--grab", "--resizable", "--frameskips", "--slowdown", "--mousewarp", "--statusbar", "--drive-led", "--max-width", "--max-height", "--zoom", "--disable-video", "--borders", "--spec512", "--video-timing", "--desktop", "--force-max", "--aspect", "--vdi", "--vdi-planes", "--vdi-width", "--vdi-height", "--crop", "--avirecord", "--avi-vcodec", "--png-level", "--avi-fps", "--avi-file", "--screenshot-dir", "--screenshot-format", "--joystick", "--joy0", "--joy1", "--joy2", "--joy3", "--joy4", "--joy5", "--printer", "--midi-in", "--midi-out", "--midi", "--rs232-in", "--rs232-out", "--scc-a-in", "--scc-a-out", "--scc-a-lan-in", "--scc-a-lan-out", "--scc-b-in", "--scc-b-out", "--drive-a", "--drive-b", "--drive-a-heads", "--drive-b-heads", "--disk-a", "--disk-b", "--fastfdc", "--protect-floppy", "--harddrive", "--protect-hd", "--gemdos-case", "--gemdos-time", "--gemdos-conv", "--gemdos-drive", "--acsi", "--scsi", "--ide-master", "--ide-slave", "--ide-swap", "--memsize", "--ttram", "--memstate", "--tos", "--patch-tos", "--cartridge", "--cpulevel", "--cpuclock", "--compatible", "--data-cache", "--cpu-exact", "--addr24", "--fpu", "--fpu-softfloat", "--mmu", "--machine", "--blitter", "--dsp", "--rtc-year", "--timer-d", "--fast-boot", "--mic", "--sound", "--sound-buffer-size", "--sound-sync", "--ym-mixing", "--debug", "--debug-except", "--lilo", "--bios-intercept", "--conout", "--disasm", "--natfeats", "--trace", "--trace-file", "--msg-repeat", "--parse", "--saveconfig", "--cmd-fifo", "--log-file", "--log-level", "--alert-level", "--run-vbls", "--benchmark" ] shortcut_tokens = [ "mousegrab", "coldreset", "warmreset", "screenshot", "bosskey", "recanim", "recsound", "savemem" ] event_tokens = [ "doubleclick", "rightdown", "rightup", "keypress", "keydown", "keyup", "text" # simulated with keypresses ] device_tokens = [ "printer", "rs232", "scca", "sccalan", "sccb", "midi", ] path_tokens = [ "memauto", "memsave", "midiin", "midiout", "printout", "soundout", "rs232in", "rs232out", "sccain", "sccaout", "sccalanin", "sccalanout", "sccbin", "sccbout" ] # use the long variants of the commands for clarity debugger_tokens = [ "address", "breakpoint", "cd", "cont", "cpureg", "disasm", "dspaddress", "dspbreak", "dspcont", "dspdisasm", "dspmemdump", "dspreg", "dspsymbols", "echo", "evaluate", "help", "history", "info", "loadbin", "lock", "logfile", "memdump", "memwrite", "parse", "profile", "quit", "savebin", "setopt", "stateload", "statesave", "struct", "symbols", "trace" ] def __init__(self, hatari, do_exit = True): self.process_tokens = { "kill": hatari.kill_hatari, "pause": hatari.toggle_pause } self.script_tokens = { "script": self.do_script, "sleep": self.do_sleep } self.help_tokens = { "usage": self.show_help, "verbose": hatari.toggle_verbose } self.hatari = hatari # whether to exit when Hatari disappears self.do_exit = do_exit def get_tokens(self): tokens = [] for items in [self.option_tokens, self.shortcut_tokens, self.event_tokens, self.debugger_tokens, self.device_tokens, self.path_tokens, list(self.process_tokens.keys()), list(self.script_tokens.keys()), list(self.help_tokens.keys())]: for token in items: if token in tokens: print("ERROR: token '%s' already in tokens" % token) sys.exit(1) tokens += items return tokens def show_help(self): print(""" Hatari-console help ------------------- Hatari-console allows you to control Hatari through its control socket from the provided console prompt, while Hatari is running. All control commands support TAB completion on their names and options. The supported control facilities are:""") self.list_items("Command line options", self.option_tokens) self.list_items("Keyboard shortcuts", self.shortcut_tokens) self.list_items("Event invocation", self.event_tokens) self.list_items("Device toggling", self.device_tokens) self.list_items("Path setting", self.path_tokens) self.list_items("Debugger commands", self.debugger_tokens) print(""" "pause" toggles Hatari paused state on/off. "kill" will terminate Hatari. "script" command reads commands from the given file. "sleep" command can be used in script to wait given number of seconds. "verbose" command toggles commands debug output on/off. For command line options you can get further help with "--help" and for debugger commands with "help". Some of the other facilities give help when you give them invalid input. """) def list_items(self, title, items): print("\n%s:" % title) for item in items: print("*", item) def do_sleep(self, line): items = line.split()[1:] try: secs = float(items[0]) except: secs = -1.0 if secs > 0.0: print("Sleeping for %g secs..." % secs) time.sleep(secs) else: print("usage: sleep ") def do_script(self, line): try: filename = line.split()[1] f = open(filename) except: print("usage: script ") return for line in f.readlines(): line = line.strip() if not line or line[0] == '#': continue print(">", line) self.process_command(line) def process_command(self, line): if not self.hatari.is_running(): print("There's no Hatari (anymore)!") if not self.do_exit: return False print("Exiting...") sys.exit(0) if not line: return False first = line.split()[0] # multiple items if first in self.event_tokens: self.hatari.insert_event(line) elif first in self.debugger_tokens: self.hatari.debug_command(line) elif first in self.option_tokens: self.hatari.change_option(line) elif first in self.path_tokens: self.hatari.change_path(line) elif first in self.script_tokens: self.script_tokens[first](line) # single item elif line in self.device_tokens: self.hatari.toggle_device(line) elif line in self.shortcut_tokens: self.hatari.trigger_shortcut(line) elif line in self.process_tokens: self.process_tokens[line]() elif line in self.help_tokens: self.help_tokens[line]() else: print("ERROR: unknown hatari-console command:", line) return False return True class Main: def __init__(self, options, do_exit=True): args, self.file, self.exit = self.parse_args(options) hatari = Hatari(args) self.tokens = Tokens(hatari, do_exit) self.command = CommandInput(self.tokens.get_tokens()) def parse_args(self, args): if "-h" in args or "--help" in args: self.usage() file = [] exit = False if "--" not in args: return (args[1:], file, exit) for arg in args: if arg == "--": return (args[args.index("--")+1:], file, exit) if arg == "--exit": exit = True continue if os.path.exists(arg): file = arg else: self.usage("file '%s' not found" % arg) def usage(self, msg=None): name = os.path.basename(sys.argv[0]) print("\n%s" % name) print("=" * len(name)) print(""" Usage: %s [ --] [] Hatari console options/args: \t\t\tread commands from given file \t--exit\t\texit after executing the commands in the file \t-h, --help\t\tthis help Except for help, console options/args will be interpreted only if '--' is given as one of the arguments. Otherwise all arguments are given to Hatari. For example: %s --monitor mono test.prg %s commands.txt -- --monitor mono %s commands.txt --exit -- """ % (name, name, name, name)) if msg: print("ERROR: %s!\n" % msg) sys.exit(1) def loop(self): print(""" ********************************************************* * To see available commands, use the TAB key or 'usage' * ********************************************************* """) if self.file: self.script(self.file) if self.exit: sys.exit(0) while 1: line = self.command.loop().strip() self.tokens.process_command(line) def script(self, filename): self.tokens.do_script("script " + filename) def run(self, line): "helper method for running Hatari commands with hatari-console, returns False on error" return self.tokens.process_command(line) if __name__ == "__main__": Main(sys.argv).loop() hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hconsole/release-notes.txt000066400000000000000000000014211504763705000270200ustar00rootroot00000000000000User visible changes in Hatari (Python) console ----------------------------------------------- 2024-04: - Support new Hatari v2.5 options 2021-09: - Remove SDL1 specific options 2021-01: - Python v2 support removed 2011-02: - Support both Python v2 & v3 2010-10: - Split hatari-console.py from hatari UI and renamed it hconsole.py (no '-') so that it can be included from other Python scripts - Added manual page and hconsole example input file & extension program - Added "text", "script" & "verbose" commands - renamed some earlier commands (see 'usage' command) 2010-03: - New debugger and command line options commands support to hatari-console - hatari-console commands can be scripted (see its help) 2009-12: - Support new Hatari command line options in hatari-console hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/000077500000000000000000000000001504763705000226315ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/CMakeLists.txt000066400000000000000000000017021504763705000253710ustar00rootroot00000000000000 include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/includes ${CMAKE_SOURCE_DIR}/src/debug) set(HMSA_SOURCES hmsa.c floppy.c ../../src/file.c) if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIR}) set(HMSA_SOURCES ${HMSA_SOURCES} ../../src/unzip.c) endif(ZLIB_FOUND) add_executable(hmsa ${HMSA_SOURCES}) target_link_libraries(hmsa Floppy) if(Math_FOUND) target_link_libraries(hmsa ${MATH_LIBRARY}) endif(Math_FOUND) if(ZLIB_FOUND) target_link_libraries(hmsa ${ZLIB_LIBRARY}) endif(ZLIB_FOUND) install(TARGETS hmsa RUNTIME DESTINATION ${BINDIR}) if(ENABLE_MAN_PAGES) add_custom_target(hmsa_manpage ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hmsa.1.gz) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hmsa.1.gz COMMAND gzip -c -9 ${CMAKE_CURRENT_SOURCE_DIR}/hmsa.1 > ${CMAKE_CURRENT_BINARY_DIR}/hmsa.1.gz DEPENDS hmsa.1) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hmsa.1.gz DESTINATION ${MANDIR}) endif(ENABLE_MAN_PAGES) hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/floppy.c000066400000000000000000000054731504763705000243170ustar00rootroot00000000000000/* Hatari tool: Magic Shadow Archiver - floppy.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. Check for valid floppy disk geometry. */ #include #include "hmsa.h" #include "floppy.h" /** * Double-check information read from boot-sector as this is sometimes found to * be incorrect. The .ST image file should be divisible by the sector size and * sectors per track. * NOTE - Pass information from boot-sector to this function (if we can't * decide we leave it alone). */ static void Floppy_DoubleCheckFormat(long nDiskSize, uint16_t *pnSides, uint16_t *pnSectorsPerTrack) { int nSectorsPerTrack; long TotalSectors; /* Now guess at number of sides */ if (nDiskSize < (500*1024)) /* Is size is >500k assume 2 sides to disk! */ *pnSides = 1; else *pnSides = 2; /* And Sectors Per Track(always 512 bytes per sector) */ TotalSectors = nDiskSize/512; /* # Sectors on disk image */ /* Does this match up with what we've read from boot-sector? */ nSectorsPerTrack = *pnSectorsPerTrack; if (nSectorsPerTrack==0) /* Check valid, default to 9 */ nSectorsPerTrack = 9; if ((TotalSectors%nSectorsPerTrack)!=0) { /* No, we have an invalid boot-sector - re-calculate from disk size */ if ((TotalSectors%9)==0) /* Work in this order.... */ *pnSectorsPerTrack = 9; else if ((TotalSectors%10)==0) *pnSectorsPerTrack = 10; else if ((TotalSectors%11)==0) *pnSectorsPerTrack = 11; else if ((TotalSectors%12)==0) *pnSectorsPerTrack = 12; } /* else unknown, assume boot-sector is correct!!! */ } /** * Find details of disk image. We need to do this via a function as sometimes the boot-block * is not actually correct with the image - some demos/game disks have incorrect bytes in the * boot sector and this attempts to find the correct values. */ void Floppy_FindDiskDetails(const uint8_t *pBuffer, int nImageBytes, unsigned short *pnSectorsPerTrack, unsigned short *pnSides) { uint16_t nSectorsPerTrack, nSides, nSectors; /* First do check to find number of sectors and bytes per sector */ nSectorsPerTrack = pBuffer[24] | (pBuffer[25] << 8); /* SPT */ nSides = pBuffer[26] | (pBuffer[27] << 8); /* SIDE */ nSectors = pBuffer[19] | (pBuffer[20] << 8); /* total sectors */ /* If the number of sectors announced is incorrect, the boot-sector may * contain incorrect information, eg the 'Eat.st' demo, or wrongly imaged * single/double sided floppies... */ if (nSectors != nImageBytes/512) Floppy_DoubleCheckFormat(nImageBytes, &nSides, &nSectorsPerTrack); /* And set values */ if (pnSectorsPerTrack) *pnSectorsPerTrack = nSectorsPerTrack; if (pnSides) *pnSides = nSides; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/floppy.h000066400000000000000000000007011504763705000243110ustar00rootroot00000000000000/* Hatari tool: Magic Shadow Archiver - floppy.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #define NUMBYTESPERSECTOR 512 /* All disks are 512 bytes per sector */ void Floppy_FindDiskDetails(const uint8_t *pBuffer, int nImageBytes, unsigned short *pnSectorsPerTrack, unsigned short *pnSides); hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/hmsa.1000066400000000000000000000037401504763705000236470ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "HMSA" "1" "2025-07-12" "Hatari" "Hatari utilities" .SH "NAME" hmsa \- Atari MSA / ST disk image creator and converter .SH "SYNOPSIS" .B hmsa .RI diskimage .RI [disksize] .SH "DESCRIPTION" .I Hmsa is little program to create compressed Atari MSA (Magic Shadow Archiver) and uncompressed ST disk images and to convert disk images between these two formats. .PP MSA and ST image format saving code is same as in Hatari itself. .SH "USAGE" If you give only one parameter, the file name of an existing MSA or ST disk image, this image will be converted to the other disk image format under a suitable new file name. .PP If the file does not exist and you give also a disk size: .TP .B SS Single Sided (360KB) .TP .B DS Double Sided (720KB) .TP .B HD High Density (1.44MB) .TP .B ED Extended Density (2.88MB) .PP An empty disk of the given size will be created. .PP Disk image format is recognized based on the file name extension (either .msa or .st). .SH "EXAMPLES" Create a normal double sided empty ST disk image: .br hmsa blank.st DS .PP Convert an MSA format disk image to an ST format one: .br hmsa disk.msa .SH "SEE ALSO" .IR hatari (1), .IR zip2st (1), .IR atari\-hd\-image (1) .SH "AUTHOR" Written by Thomas Huth . This manual page added by Eero Tamminen . .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/hmsa.c000066400000000000000000000141751504763705000237350ustar00rootroot00000000000000/* Hatari tool: MSA and ST disk image creator and converter - hmsa.c This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #include #include #include #include #include "hmsa.h" #include "main.h" /* bool etc. */ #include "createBlankImage.h" #include "file.h" #include "msa.h" /* prototypes for dummy log/alert functions below */ #include "dialog.h" #include "log.h" /** * Print suitable output prefix based on log level */ static void print_prefix(LOGTYPE nType) { const char *sType; switch (nType) { case LOG_FATAL: case LOG_ERROR: sType = "ERROR: "; break; case LOG_WARN: sType = "WARNING: "; break; default: return; } fputs(sType, stdout); } /* output newline if it's missing from text */ static void do_newline(const char *text) { if (text[strlen(text)-1] != '\n') fputs("\n", stdout); } /** * Output Hatari log string. */ void Log_Printf(LOGTYPE nType, const char *psFormat, ...) { va_list argptr; print_prefix(nType); va_start(argptr, psFormat); vfprintf(stdout, psFormat, argptr); va_end(argptr); } /** * Output Hatari Alert dialog string. */ void Log_AlertDlg(LOGTYPE nType, const char *psFormat, ...) { va_list argptr; print_prefix(nType); va_start(argptr, psFormat); vfprintf(stdout, psFormat, argptr); va_end(argptr); do_newline(psFormat); } /** * Output Hatari Query dialog string. */ bool DlgAlert_Query(const char *text) { puts(text); do_newline(text); return true; } /** * ../src/file.c requires zip.c, which calls IPF_FileNameIsIPF * We create an empty function to replace it, as we don't use IPF here * and don't want to compile with all the IPF related files. * We do it also for STX. */ extern bool IPF_FileNameIsIPF(const char *pszFileName, bool bAllowGZ); /* function prototype */ bool IPF_FileNameIsIPF(const char *pszFileName, bool bAllowGZ) { return false; } extern bool STX_FileNameIsSTX(const char *pszFileName, bool bAllowGZ); /* function prototype */ bool STX_FileNameIsSTX(const char *pszFileName, bool bAllowGZ) { return false; } /** * Create MSA or ST image of requested size. * return error string or NULL for success. */ static const char* create_image(const char *filename, const char *sizeid) { int tracks, sectors, sides; tracks = 80; if (strcasecmp(sizeid, "ss") == 0) { sides = 1; sectors = 9; } else if (strcasecmp(sizeid, "ds") == 0) { sides = 2; sectors = 9; } else if (strcasecmp(sizeid, "hd") == 0) { sides = 2; sectors = 18; } else if (strcasecmp(sizeid, "ed") == 0) { sides = 2; sectors = 36; } else { return "ERROR: given disk size isn't one of supported ones!\n"; } if (CreateBlankImage_CreateFile(filename, tracks, sectors, sides, NULL)) { return NULL; } return "ERROR: Disk creation failed.\n"; } /** * Print program usage */ static void usage(const char *name) { printf("\n\ Hatari MSA (Magic Shadow Archiver) / ST disk image creator & converter v0.3.\n\ \n\ Usage: %s FILENAME [DISK SIZE]\n\ \n\ If you give only one parameter - the file name of an existing MSA\n\ or ST disk image, this image will be converted to the other disk image\n\ format under a suitable new file name. Disk image format is recognized\n\ based on the file name extension (.msa or .st).\n\ \n\ If the given file doesn't exist and you give also a disk size\n\ (SS, DS, HD, ED), an empty disk of the given size will be created.\n\ \n\ This software is distributed under the GNU General Public License, version 2\n\ or at your option any later version. Please read the file gpl.txt for details.\n\ \n", name); } /** * Command line argument parsing and new disk creation */ int main(int argc, char *argv[]) { bool isMsa; long disksize; unsigned char *diskbuf; const char *srcfile, *srcdot; char *dstfile, *dstdot; int ImageType; int drive; int retval = 0; if(argc < 2 || argv[1][0] == '-') { usage(argv[0]); return 0; } srcfile = argv[1]; srcdot = strrchr(srcfile, '.'); if(srcdot == NULL) { usage(argv[0]); fprintf(stderr, "ERROR: extension missing for file name %s!\n", argv[1]); return -1; } if (strcasecmp(srcdot, ".msa") == 0) { isMsa = true; } else if (strcasecmp(srcdot, ".st") == 0) { isMsa = false; } else { usage(argv[0]); fprintf(stderr, "ERROR: unrecognized file name extension %s (not .msa or .st)!\n", srcdot); return -1; } if (!File_Exists(srcfile)) { const char *errstr; if (argc != 3) { usage(argv[0]); fprintf(stderr, "ERROR: disk size for the new disk image not given!\n"); return -1; } errstr = create_image(srcfile, argv[2]); if (errstr) { usage(argv[0]); fputs(errstr, stderr); return -1; } return 0; } dstfile = malloc(strlen(srcfile) + 6); if (dstfile == NULL) { fprintf(stderr, "ERROR: No memory for new disk name!\n"); return -1; } strcpy(dstfile, srcfile); dstdot = strrchr(dstfile, '.'); if (isMsa) { /* Convert MSA to ST disk image */ strcpy(dstdot, ".st"); } else { /* Convert ST to MSA disk image */ strcpy(dstdot, ".msa"); } if (File_Exists(dstfile)) { fprintf(stderr, "ERROR: Destination disk image %s exists already!\n", dstfile); free(dstfile); return -1; } drive = 0; /* drive is not used for ST/MSA/DIM, set it to 0 */ if (isMsa) { /* Read the source disk image */ diskbuf = MSA_ReadDisk(drive, srcfile, &disksize, &ImageType); if (!diskbuf || disksize < 512*8) { fprintf(stderr, "ERROR: could not read MSA disk %s!\n", srcfile); retval = -1; } else { printf("Converting %s to %s (%li Bytes).\n", srcfile, dstfile, disksize); if (!File_Save(dstfile, diskbuf, disksize, false)) retval = -1; } } else { /* Just read disk image directly into buffer */ disksize = 0; diskbuf = File_Read(srcfile, &disksize, NULL); if (!diskbuf || disksize < 512*8) { fprintf(stderr, "ERROR: could not read ST disk %s!\n", srcfile); retval = -1; } else { printf("Converting %s to %s (%li Bytes).\n", srcfile, dstfile, disksize); if (!MSA_WriteDisk(drive, dstfile, diskbuf, disksize)) retval = -1; } } if (diskbuf) { free(diskbuf); } free(dstfile); return retval; } hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/hmsa/hmsa.h000066400000000000000000000005151504763705000237330ustar00rootroot00000000000000/* Hatari tool: Magic Shadow Archiver - hmsa.h This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. */ #ifndef HMSA_H #define HMSA_H #include #include #ifndef NULL #define NULL 0L #endif #endif /* HMSA_H */ hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/linux/000077500000000000000000000000001504763705000230405ustar00rootroot000000000000000001-m68k-Drop-Atari-EtherNAT-support-when-it-s-not-confi.patch000066400000000000000000000026411504763705000357350ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/linuxFrom 2023fe8fda5fb4e3ddf39ecc547594c4ab6a14ab Mon Sep 17 00:00:00 2001 From: Eero Tamminen Date: Sat, 25 Jun 2022 17:29:42 +0300 Subject: [PATCH 1/2] m68k: Drop Atari EtherNAT support when it's not configured in Not having it configured, but crash backtraces going through its IRQ handling code, is confusing (found while debugging issues in Atari emulation). Signed-off-by: Eero Tamminen --- arch/m68k/atari/ataints.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/m68k/atari/ataints.c b/arch/m68k/atari/ataints.c index 0465444ce..fa189eeb7 100644 --- a/arch/m68k/atari/ataints.c +++ b/arch/m68k/atari/ataints.c @@ -179,6 +179,7 @@ static struct irq_chip atari_mfptimer_chip = { }; +#ifdef CONFIG_ATARI_ETHERNAT /* * EtherNAT CPLD interrupt handling * CPLD interrupt register is at phys. 0x80000023 @@ -251,6 +252,7 @@ static struct irq_chip atari_ethernat_chip = { .irq_enable = atari_ethernat_enable, .irq_disable = atari_ethernat_disable, }; +#endif /* * void atari_init_IRQ (void) @@ -343,12 +345,13 @@ void __init atari_init_IRQ(void) stmfp_base.name, &stmfp_base)) pr_err("Couldn't register %s interrupt\n", stmfp_base.name); +#ifdef CONFIG_ATARI_ETHERNAT /* * EtherNAT ethernet / USB interrupt handlers */ - m68k_setup_irq_controller(&atari_ethernat_chip, handle_simple_irq, 139, 2); +#endif } -- 2.39.5 0002-m68k-Set-reasonable-PSG-port-A-default-value.patch000066400000000000000000000050141504763705000343330ustar00rootroot00000000000000hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/linuxFrom 08e43110a164a1b3f12f306342b63b9be88666e5 Mon Sep 17 00:00:00 2001 From: Eero Tamminen Date: Tue, 31 Mar 2020 22:41:40 +0300 Subject: [PATCH 2/2] m68k: Set reasonable PSG port-A default value Fixes continuous IDE & DSP reset requests on Atari Falcon from 2Hz floppy media change detection if those bits happen to be set (to a different value than ROM defaults) before Linux starts. All successive port-A requests preserve unrelated bits. If bits 4 & 6 are set, that means every PSG port-A request, such as floppy media change detection, being also Falcon DSP & IDE reset request. Signed-off-by: Eero Tamminen --- Documentation/arch/m68k/kernel-options.rst | 4 +++- arch/m68k/atari/config.c | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Documentation/arch/m68k/kernel-options.rst b/Documentation/arch/m68k/kernel-options.rst index 2008a20b4..cae270018 100644 --- a/Documentation/arch/m68k/kernel-options.rst +++ b/Documentation/arch/m68k/kernel-options.rst @@ -674,8 +674,10 @@ items: set RTS of the MIDI ACIA high snd6: set bit 6 of the PSG port A + (Falcon: internal speaker on/off, others: monitor jack GPO pin) snd7: - set bit 6 of the PSG port A + set bit 7 of the PSG port A + (Falcon: IDE drive on/off, TT: SCC-A LAN/serial2) It doesn't make sense to mention a switch more than once (no difference to only once), but you can give as many switches as you diff --git a/arch/m68k/atari/config.c b/arch/m68k/atari/config.c index b48a0606a..0e4bd90fe 100644 --- a/arch/m68k/atari/config.c +++ b/arch/m68k/atari/config.c @@ -188,6 +188,7 @@ early_param("switches", atari_switches_setup); void __init config_atari(void) { unsigned short tos_version; + unsigned char porta_init = 0x07; memset(&atari_hw_present, 0, sizeof(atari_hw_present)); @@ -212,11 +213,12 @@ void __init config_atari(void) if (atari_switches & ATARI_SWITCH_MIDI) acia.mid_ctrl = ACIA_DIV16 | ACIA_D8N1S | ACIA_RHTID; if (atari_switches & (ATARI_SWITCH_SND6|ATARI_SWITCH_SND7)) { - sound_ym.rd_data_reg_sel = 14; - sound_ym.wd_data = sound_ym.rd_data_reg_sel | - ((atari_switches&ATARI_SWITCH_SND6) ? 0x40 : 0) | - ((atari_switches&ATARI_SWITCH_SND7) ? 0x80 : 0); + porta_init |= ((atari_switches&ATARI_SWITCH_SND6) ? 0x40 : 0) | + ((atari_switches&ATARI_SWITCH_SND7) ? 0x80 : 0); } + /* Set PSG port-A defaults */ + sound_ym.rd_data_reg_sel = 14; + sound_ym.wd_data = porta_init; /* ++bjoern: * Determine hardware present -- 2.39.5 hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/linux/init.sh000077500000000000000000000014431504763705000243440ustar00rootroot00000000000000#!/bin/sh # # NOTE: this expects kernel to be configured with: # CONFIG_DEVTMPFS_MOUNT=y # so that /dev is automounted after root fs is mounted. # # Otherwise following is needed too: # mount -t devtmpfs udev /dev # mount special file systems mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t tmpfs tmpfs /run mount -t tmpfs tmpfs /tmp mkdir /dev/pts mount -t devpts devpts /dev/pts if [ -x /usr/bin/busybox ]; then echo "Boot took $(cut -d' ' -f1 /proc/uptime) seconds" # hack for running shell so that job control is enabled, see: # https://git.busybox.net/busybox/plain/shell/cttyhack.c # # without -c (controlling terminal) option, # hangs *if* 030 caches are enabled setsid -c cttyhack sh else # minimal klibc tools echo "uptime & idle seconds:" cat /proc/uptime exec sh fi hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/linux/kernel.config000066400000000000000000001070411504763705000255120ustar00rootroot00000000000000# # Automatically generated file; DO NOT EDIT. # Linux/m68k 6.15.0 Kernel Configuration # CONFIG_CC_VERSION_TEXT="m68k-linux-gnu-gcc (Debian 12.2.0-13) 12.2.0" CONFIG_CC_IS_GCC=y CONFIG_GCC_VERSION=120200 CONFIG_CLANG_VERSION=0 CONFIG_AS_IS_GNU=y CONFIG_AS_VERSION=24000 CONFIG_LD_IS_BFD=y CONFIG_LD_VERSION=24000 CONFIG_LLD_VERSION=0 CONFIG_RUSTC_VERSION=0 CONFIG_RUSTC_LLVM_VERSION=0 CONFIG_CC_CAN_LINK=y CONFIG_GCC_ASM_GOTO_OUTPUT_BROKEN=y CONFIG_CC_HAS_ASM_INLINE=y CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y CONFIG_LD_CAN_USE_KEEP_IN_OVERLAY=y CONFIG_PAHOLE_VERSION=0 CONFIG_IRQ_WORK=y # # General setup # CONFIG_BROKEN_ON_SMP=y CONFIG_INIT_ENV_ARG_LIMIT=32 # CONFIG_COMPILE_TEST is not set # CONFIG_WERROR is not set CONFIG_LOCALVERSION="hatari" CONFIG_LOCALVERSION_AUTO=y CONFIG_BUILD_SALT="" CONFIG_DEFAULT_INIT="" CONFIG_DEFAULT_HOSTNAME="atari" CONFIG_SYSVIPC=y CONFIG_SYSVIPC_SYSCTL=y CONFIG_POSIX_MQUEUE=y CONFIG_POSIX_MQUEUE_SYSCTL=y # CONFIG_WATCH_QUEUE is not set CONFIG_CROSS_MEMORY_ATTACH=y # CONFIG_USELIB is not set # CONFIG_AUDIT is not set # # IRQ subsystem # CONFIG_GENERIC_IRQ_SHOW=y # end of IRQ subsystem CONFIG_LEGACY_TIMER_TICK=y CONFIG_BPF=y # # BPF subsystem # # CONFIG_BPF_SYSCALL is not set # end of BPF subsystem CONFIG_PREEMPT_NONE_BUILD=y CONFIG_PREEMPT_NONE=y # # CPU/Task time and stats accounting # CONFIG_TICK_CPU_ACCOUNTING=y # CONFIG_BSD_PROCESS_ACCT is not set # CONFIG_TASKSTATS is not set # CONFIG_PSI is not set # end of CPU/Task time and stats accounting # # RCU Subsystem # CONFIG_TINY_RCU=y # CONFIG_RCU_EXPERT is not set CONFIG_TINY_SRCU=y # end of RCU Subsystem # CONFIG_IKCONFIG is not set # CONFIG_IKHEADERS is not set CONFIG_LOG_BUF_SHIFT=14 # # Scheduler features # # end of Scheduler features CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5" CONFIG_GCC10_NO_ARRAY_BOUNDS=y CONFIG_CC_NO_ARRAY_BOUNDS=y CONFIG_GCC_NO_STRINGOP_OVERFLOW=y CONFIG_CC_NO_STRINGOP_OVERFLOW=y # CONFIG_CGROUPS is not set # CONFIG_NAMESPACES is not set # CONFIG_CHECKPOINT_RESTORE is not set # CONFIG_SCHED_AUTOGROUP is not set # CONFIG_RELAY is not set CONFIG_BLK_DEV_INITRD=y CONFIG_INITRAMFS_SOURCE="" CONFIG_RD_GZIP=y # CONFIG_RD_BZIP2 is not set # CONFIG_RD_LZMA is not set # CONFIG_RD_XZ is not set CONFIG_RD_LZO=y # CONFIG_RD_LZ4 is not set CONFIG_RD_ZSTD=y # CONFIG_BOOT_CONFIG is not set CONFIG_INITRAMFS_PRESERVE_MTIME=y # CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE is not set CONFIG_CC_OPTIMIZE_FOR_SIZE=y CONFIG_SYSCTL=y CONFIG_HAVE_UID16=y # CONFIG_SYSFS_SYSCALL is not set CONFIG_EXPERT=y CONFIG_UID16=y CONFIG_MULTIUSER=y # CONFIG_SGETMASK_SYSCALL is not set CONFIG_FHANDLE=y CONFIG_POSIX_TIMERS=y CONFIG_PRINTK=y CONFIG_BUG=y CONFIG_ELF_CORE=y # CONFIG_BASE_SMALL is not set CONFIG_FUTEX=y CONFIG_FUTEX_PI=y CONFIG_EPOLL=y CONFIG_SIGNALFD=y CONFIG_TIMERFD=y CONFIG_EVENTFD=y CONFIG_SHMEM=y # CONFIG_AIO is not set CONFIG_IO_URING=y CONFIG_ADVISE_SYSCALLS=y CONFIG_MEMBARRIER=y # CONFIG_KCMP is not set CONFIG_CACHESTAT_SYSCALL=y # CONFIG_PC104 is not set CONFIG_KALLSYMS=y # CONFIG_KALLSYMS_SELFTEST is not set # CONFIG_KALLSYMS_ALL is not set # # Kernel Performance Events And Counters # # end of Kernel Performance Events And Counters # CONFIG_PROFILING is not set # # Kexec and crash features # # CONFIG_KEXEC is not set # end of Kexec and crash features # end of General setup CONFIG_M68K=y CONFIG_CPU_BIG_ENDIAN=y CONFIG_GENERIC_HWEIGHT=y CONFIG_GENERIC_CALIBRATE_DELAY=y CONFIG_TIME_LOW_RES=y CONFIG_NO_IOPORT_MAP=y CONFIG_HZ=100 CONFIG_PGTABLE_LEVELS=3 CONFIG_MMU=y CONFIG_MMU_MOTOROLA=y CONFIG_ARCH_SUPPORTS_KEXEC=y # # Platform setup # # # Processor Type # CONFIG_M68KCLASSIC=y # CONFIG_COLDFIRE is not set # CONFIG_SUN3 is not set # CONFIG_M68020 is not set CONFIG_M68030=y CONFIG_M68040=y CONFIG_M68060=y # # Processor Specific Options # CONFIG_M68KFPU_EMU=y # CONFIG_M68KFPU_EMU_EXTRAPREC is not set # CONFIG_M68KFPU_EMU_ONLY is not set CONFIG_ADVANCED=y # CONFIG_RMW_INSNS is not set # CONFIG_SINGLE_MEMORY_CHUNK is not set CONFIG_ARCH_FORCE_MAX_ORDER=11 # CONFIG_060_WRITETHROUGH is not set CONFIG_CPU_HAS_ADDRESS_SPACES=y CONFIG_FPU=y CONFIG_M68K_NONCOHERENT_DMA=y # # Machine Types # # CONFIG_AMIGA is not set CONFIG_ATARI=y CONFIG_ATARI_KBD_CORE=y # CONFIG_MAC is not set # CONFIG_APOLLO is not set # CONFIG_VME is not set # CONFIG_HP300 is not set # CONFIG_SUN3X is not set # CONFIG_Q40 is not set # CONFIG_VIRT is not set # # Bus Support # # CONFIG_ATARI_ROM_ISA is not set # end of Platform setup # # Kernel Features # # end of Kernel Features # # Platform devices # # CONFIG_HEARTBEAT is not set CONFIG_PROC_HARDWARE=y CONFIG_NATFEAT=y # CONFIG_NFBLOCK is not set CONFIG_NFCON=y # CONFIG_ATARI_ETHERNAT is not set # end of Platform devices # # Character devices # CONFIG_ATARI_DSP56K=y # end of Character devices CONFIG_CPU_MITIGATIONS=y # # General architecture-dependent options # CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y CONFIG_ARCH_HAS_CPU_FINALIZE_INIT=y CONFIG_ARCH_32BIT_OFF_T=y CONFIG_HAVE_ASM_MODVERSIONS=y CONFIG_MMU_GATHER_NO_RANGE=y CONFIG_MMU_GATHER_MERGE_VMAS=y CONFIG_MMU_LAZY_TLB_REFCOUNT=y CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y CONFIG_HAVE_ARCH_SECCOMP=y CONFIG_HAVE_ARCH_SECCOMP_FILTER=y CONFIG_SECCOMP=y CONFIG_SECCOMP_FILTER=y # CONFIG_SECCOMP_CACHE_DEBUG is not set CONFIG_LTO_NONE=y CONFIG_HAVE_MOD_ARCH_SPECIFIC=y CONFIG_MODULES_USE_ELF_RELA=y CONFIG_MODULES_USE_ELF_REL=y CONFIG_ALTERNATE_USER_ADDRESS_SPACE=y CONFIG_HAVE_PAGE_SIZE_4KB=y CONFIG_PAGE_SIZE_4KB=y CONFIG_PAGE_SIZE_LESS_THAN_64KB=y CONFIG_PAGE_SIZE_LESS_THAN_256KB=y CONFIG_PAGE_SHIFT=12 CONFIG_HAVE_ARCH_NVRAM_OPS=y CONFIG_OLD_SIGSUSPEND3=y CONFIG_OLD_SIGACTION=y CONFIG_COMPAT_32BIT_TIME=y CONFIG_ARCH_NO_PREEMPT=y CONFIG_HAVE_ARCH_LIBGCC_H=y CONFIG_HAVE_ARCH_PFN_VALID=y # # GCOV-based kernel profiling # # end of GCOV-based kernel profiling CONFIG_FUNCTION_ALIGNMENT=0 # end of General architecture-dependent options CONFIG_RT_MUTEXES=y # CONFIG_MODULES is not set CONFIG_BLOCK=y # CONFIG_BLOCK_LEGACY_AUTOLOAD is not set CONFIG_BLK_DEV_BSG_COMMON=y # CONFIG_BLK_DEV_BSGLIB is not set # CONFIG_BLK_DEV_INTEGRITY is not set CONFIG_BLK_DEV_WRITE_MOUNTED=y # CONFIG_BLK_DEV_ZONED is not set CONFIG_BLK_WBT=y CONFIG_BLK_WBT_MQ=y # CONFIG_BLK_INLINE_ENCRYPTION is not set # # Partition Types # CONFIG_PARTITION_ADVANCED=y # CONFIG_ACORN_PARTITION is not set # CONFIG_AIX_PARTITION is not set # CONFIG_OSF_PARTITION is not set # CONFIG_AMIGA_PARTITION is not set CONFIG_ATARI_PARTITION=y # CONFIG_MAC_PARTITION is not set CONFIG_MSDOS_PARTITION=y # CONFIG_BSD_DISKLABEL is not set # CONFIG_MINIX_SUBPARTITION is not set # CONFIG_SOLARIS_X86_PARTITION is not set # CONFIG_UNIXWARE_DISKLABEL is not set # CONFIG_LDM_PARTITION is not set # CONFIG_SGI_PARTITION is not set # CONFIG_ULTRIX_PARTITION is not set # CONFIG_SUN_PARTITION is not set # CONFIG_KARMA_PARTITION is not set # CONFIG_EFI_PARTITION is not set # CONFIG_SYSV68_PARTITION is not set CONFIG_CMDLINE_PARTITION=y # end of Partition Types # # IO Schedulers # CONFIG_MQ_IOSCHED_DEADLINE=y # CONFIG_MQ_IOSCHED_KYBER is not set # CONFIG_IOSCHED_BFQ is not set # end of IO Schedulers CONFIG_INLINE_SPIN_UNLOCK_IRQ=y CONFIG_INLINE_READ_UNLOCK=y CONFIG_INLINE_READ_UNLOCK_IRQ=y CONFIG_INLINE_WRITE_UNLOCK=y CONFIG_INLINE_WRITE_UNLOCK_IRQ=y # # Executable file formats # CONFIG_BINFMT_ELF=y CONFIG_ELFCORE=y CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y CONFIG_BINFMT_SCRIPT=y CONFIG_ARCH_HAS_BINFMT_FLAT=y # CONFIG_BINFMT_FLAT is not set CONFIG_BINFMT_FLAT_ARGVP_ENVP_ON_STACK=y CONFIG_BINFMT_MISC=y CONFIG_COREDUMP=y # end of Executable file formats # # Memory Management options # CONFIG_SWAP=y # CONFIG_ZSWAP is not set # # Slab allocator options # CONFIG_SLUB=y CONFIG_SLUB_TINY=y CONFIG_SLAB_MERGE_DEFAULT=y # end of Slab allocator options # CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set CONFIG_COMPAT_BRK=y CONFIG_FLATMEM=y CONFIG_COMPACTION=y CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1 # CONFIG_PAGE_REPORTING is not set CONFIG_MIGRATION=y CONFIG_PCP_BATCH_SCALE_MAX=2 # CONFIG_KSM is not set CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 CONFIG_PAGE_MAPCOUNT=y CONFIG_NEED_PER_CPU_KM=y # CONFIG_CMA is not set # CONFIG_IDLE_PAGE_TRACKING is not set CONFIG_ARCH_HAS_CPU_CACHE_ALIASING=y CONFIG_ARCH_HAS_CURRENT_STACK_POINTER=y CONFIG_ZONE_DMA=y # CONFIG_VM_EVENT_COUNTERS is not set # CONFIG_PERCPU_STATS is not set # # GUP_TEST needs to have DEBUG_FS enabled # # CONFIG_DMAPOOL_TEST is not set CONFIG_MEMFD_CREATE=y # CONFIG_ANON_VMA_NAME is not set # CONFIG_USERFAULTFD is not set # CONFIG_LRU_GEN is not set # # Data Access Monitoring # # CONFIG_DAMON is not set # end of Data Access Monitoring # end of Memory Management options CONFIG_NET=y # # Networking options # CONFIG_PACKET=y # CONFIG_PACKET_DIAG is not set CONFIG_UNIX=y CONFIG_AF_UNIX_OOB=y # CONFIG_UNIX_DIAG is not set # CONFIG_TLS is not set # CONFIG_XFRM_USER is not set # CONFIG_NET_KEY is not set CONFIG_INET=y # CONFIG_IP_MULTICAST is not set # CONFIG_IP_ADVANCED_ROUTER is not set # CONFIG_IP_PNP is not set # CONFIG_NET_IPIP is not set # CONFIG_NET_IPGRE_DEMUX is not set # CONFIG_SYN_COOKIES is not set # CONFIG_NET_IPVTI is not set # CONFIG_NET_FOU is not set # CONFIG_INET_AH is not set # CONFIG_INET_ESP is not set # CONFIG_INET_IPCOMP is not set CONFIG_INET_TABLE_PERTURB_ORDER=12 # CONFIG_INET_DIAG is not set # CONFIG_TCP_CONG_ADVANCED is not set CONFIG_TCP_CONG_CUBIC=y CONFIG_DEFAULT_TCP_CONG="cubic" # CONFIG_TCP_MD5SIG is not set # CONFIG_IPV6 is not set # CONFIG_MPTCP is not set # CONFIG_NETWORK_SECMARK is not set # CONFIG_NETWORK_PHY_TIMESTAMPING is not set # CONFIG_NETFILTER is not set # CONFIG_IP_DCCP is not set # CONFIG_IP_SCTP is not set # CONFIG_RDS is not set # CONFIG_TIPC is not set # CONFIG_ATM is not set # CONFIG_L2TP is not set # CONFIG_BRIDGE is not set # CONFIG_NET_DSA is not set # CONFIG_VLAN_8021Q is not set # CONFIG_LLC2 is not set # CONFIG_ATALK is not set # CONFIG_X25 is not set # CONFIG_LAPB is not set # CONFIG_PHONET is not set # CONFIG_IEEE802154 is not set # CONFIG_NET_SCHED is not set # CONFIG_DCB is not set # CONFIG_BATMAN_ADV is not set # CONFIG_OPENVSWITCH is not set # CONFIG_VSOCKETS is not set CONFIG_NETLINK_DIAG=y # CONFIG_MPLS is not set # CONFIG_NET_NSH is not set # CONFIG_HSR is not set # CONFIG_NET_SWITCHDEV is not set # CONFIG_NET_L3_MASTER_DEV is not set # CONFIG_QRTR is not set # CONFIG_NET_NCSI is not set CONFIG_MAX_SKB_FRAGS=17 CONFIG_NET_RX_BUSY_POLL=y CONFIG_BQL=y # # Network testing # # CONFIG_NET_PKTGEN is not set # end of Network testing # end of Networking options # CONFIG_HAMRADIO is not set # CONFIG_CAN is not set # CONFIG_BT is not set # CONFIG_AF_RXRPC is not set # CONFIG_AF_KCM is not set # CONFIG_MCTP is not set # CONFIG_WIRELESS is not set # CONFIG_RFKILL is not set # CONFIG_NET_9P is not set # CONFIG_CAIF is not set # CONFIG_CEPH_LIB is not set # CONFIG_NFC is not set # CONFIG_PSAMPLE is not set # CONFIG_NET_IFE is not set # CONFIG_LWTUNNEL is not set # CONFIG_FAILOVER is not set # CONFIG_ETHTOOL_NETLINK is not set # # Device Drivers # # CONFIG_PCCARD is not set # # Generic Driver Options # # CONFIG_UEVENT_HELPER is not set CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y # CONFIG_DEVTMPFS_SAFE is not set CONFIG_STANDALONE=y CONFIG_PREVENT_FIRMWARE_BUILD=y # # Firmware loader # # CONFIG_FW_LOADER is not set # end of Firmware loader # CONFIG_ALLOW_DEV_COREDUMP is not set # CONFIG_DEBUG_DRIVER is not set # CONFIG_DEBUG_DEVRES is not set # CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set CONFIG_GENERIC_CPU_DEVICES=y # CONFIG_FW_DEVLINK_SYNC_STATE_TIMEOUT is not set # end of Generic Driver Options # # Bus devices # # CONFIG_MHI_BUS is not set # CONFIG_MHI_BUS_EP is not set # end of Bus devices # # Cache Drivers # # end of Cache Drivers # CONFIG_CONNECTOR is not set # # Firmware Drivers # # # ARM System Control and Management Interface Protocol # # end of ARM System Control and Management Interface Protocol # CONFIG_FIRMWARE_MEMMAP is not set # CONFIG_GOOGLE_FIRMWARE is not set # # Qualcomm firmware drivers # # end of Qualcomm firmware drivers # # Tegra firmware driver # # end of Tegra firmware driver # end of Firmware Drivers # CONFIG_FWCTL is not set # CONFIG_GNSS is not set # CONFIG_MTD is not set # CONFIG_OF is not set CONFIG_PARPORT=y CONFIG_PARPORT_ATARI=y # CONFIG_PARPORT_1284 is not set CONFIG_PARPORT_NOT_PC=y CONFIG_BLK_DEV=y # CONFIG_BLK_DEV_NULL_BLK is not set CONFIG_ATARI_FLOPPY=y # CONFIG_ZRAM is not set CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_LOOP_MIN_COUNT=0 # CONFIG_BLK_DEV_DRBD is not set # CONFIG_BLK_DEV_NBD is not set CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_COUNT=1 CONFIG_BLK_DEV_RAM_SIZE=4096 # CONFIG_CDROM_PKTCDVD is not set # CONFIG_ATA_OVER_ETH is not set # CONFIG_BLK_DEV_RBD is not set # CONFIG_BLK_DEV_UBLK is not set # # NVME Support # # CONFIG_NVME_FC is not set # CONFIG_NVME_TCP is not set # end of NVME Support # # Misc devices # CONFIG_DUMMY_IRQ=y # CONFIG_ENCLOSURE_SERVICES is not set # CONFIG_SRAM is not set # CONFIG_XILINX_SDFEC is not set # CONFIG_NTSYNC is not set # CONFIG_C2PORT is not set # # EEPROM support # # CONFIG_EEPROM_93CX6 is not set # end of EEPROM support # # Altera FPGA firmware download module (requires I2C) # # CONFIG_ECHO is not set # CONFIG_PVPANIC is not set # end of Misc devices # # SCSI device support # CONFIG_SCSI_MOD=y # CONFIG_RAID_ATTRS is not set CONFIG_SCSI_COMMON=y CONFIG_SCSI=y CONFIG_SCSI_DMA=y CONFIG_SCSI_PROC_FS=y # # SCSI support type (disk, tape, CD-ROM) # CONFIG_BLK_DEV_SD=y # CONFIG_CHR_DEV_ST is not set # CONFIG_BLK_DEV_SR is not set # CONFIG_CHR_DEV_SG is not set CONFIG_BLK_DEV_BSG=y # CONFIG_CHR_DEV_SCH is not set CONFIG_SCSI_CONSTANTS=y CONFIG_SCSI_LOGGING=y CONFIG_SCSI_SCAN_ASYNC=y # # SCSI Transports # CONFIG_SCSI_SPI_ATTRS=y # CONFIG_SCSI_FC_ATTRS is not set # CONFIG_SCSI_ISCSI_ATTRS is not set # CONFIG_SCSI_SAS_ATTRS is not set # CONFIG_SCSI_SAS_LIBSAS is not set # CONFIG_SCSI_SRP_ATTRS is not set # end of SCSI Transports CONFIG_SCSI_LOWLEVEL=y # CONFIG_ISCSI_TCP is not set # CONFIG_ISCSI_BOOT_SYSFS is not set # CONFIG_SCSI_DEBUG is not set CONFIG_ATARI_SCSI=y # CONFIG_SCSI_DH is not set # end of SCSI device support CONFIG_ATA=y CONFIG_ATA_VERBOSE_ERROR=y # CONFIG_ATA_FORCE is not set # # Controllers with non-SFF native interface # # CONFIG_SATA_AHCI_PLATFORM is not set # CONFIG_AHCI_DWC is not set CONFIG_ATA_SFF=y # # SFF controllers with custom DMA interface # # CONFIG_ATA_BMDMA is not set # # PIO-only SFF controllers # CONFIG_PATA_FALCON=y # # Generic fallback / legacy drivers # # CONFIG_MD is not set # CONFIG_TARGET_CORE is not set CONFIG_NETDEVICES=y CONFIG_NET_CORE=y # CONFIG_BONDING is not set CONFIG_DUMMY=y # CONFIG_WIREGUARD is not set # CONFIG_EQUALIZER is not set # CONFIG_NET_TEAM is not set # CONFIG_MACVLAN is not set # CONFIG_IPVLAN is not set # CONFIG_VXLAN is not set # CONFIG_GENEVE is not set # CONFIG_BAREUDP is not set # CONFIG_GTP is not set # CONFIG_PFCP is not set # CONFIG_MACSEC is not set # CONFIG_NETCONSOLE is not set # CONFIG_TUN is not set # CONFIG_TUN_VNET_CROSS_LE is not set # CONFIG_VETH is not set # CONFIG_NLMON is not set # CONFIG_ETHERNET is not set # CONFIG_PHYLIB is not set # CONFIG_MDIO_DEVICE is not set # # PCS device drivers # # CONFIG_PCS_XPCS is not set # end of PCS device drivers CONFIG_PLIP=y CONFIG_PPP=y # CONFIG_PPP_BSDCOMP is not set CONFIG_PPP_DEFLATE=y # CONFIG_PPP_FILTER is not set # CONFIG_PPP_MPPE is not set # CONFIG_PPP_MULTILINK is not set # CONFIG_PPPOE is not set CONFIG_PPPOE_HASH_BITS=4 CONFIG_PPP_ASYNC=y # CONFIG_PPP_SYNC_TTY is not set # CONFIG_SLIP is not set CONFIG_SLHC=y # # Host-side USB support is needed for USB Network Adapter support # # CONFIG_WLAN is not set # CONFIG_WAN is not set # # Wireless WAN # # CONFIG_WWAN is not set # end of Wireless WAN # CONFIG_NET_FAILOVER is not set # CONFIG_ISDN is not set # # Input device support # CONFIG_INPUT=y # CONFIG_INPUT_FF_MEMLESS is not set # CONFIG_INPUT_SPARSEKMAP is not set # CONFIG_INPUT_MATRIXKMAP is not set # # Userland interfaces # CONFIG_INPUT_MOUSEDEV=y # CONFIG_INPUT_MOUSEDEV_PSAUX is not set CONFIG_INPUT_MOUSEDEV_SCREEN_X=640 CONFIG_INPUT_MOUSEDEV_SCREEN_Y=480 CONFIG_INPUT_JOYDEV=y CONFIG_INPUT_EVDEV=y # # Input Device Drivers # CONFIG_INPUT_KEYBOARD=y CONFIG_KEYBOARD_ATARI=y # CONFIG_KEYBOARD_ATKBD is not set # CONFIG_KEYBOARD_LKKBD is not set # CONFIG_KEYBOARD_NEWTON is not set # CONFIG_KEYBOARD_OPENCORES is not set # CONFIG_KEYBOARD_STOWAWAY is not set # CONFIG_KEYBOARD_SUNKBD is not set # CONFIG_KEYBOARD_XTKBD is not set CONFIG_INPUT_MOUSE=y # CONFIG_MOUSE_PS2 is not set # CONFIG_MOUSE_SERIAL is not set CONFIG_MOUSE_ATARI=y # CONFIG_MOUSE_VSXXXAA is not set # CONFIG_INPUT_JOYSTICK is not set # CONFIG_INPUT_TABLET is not set # CONFIG_INPUT_TOUCHSCREEN is not set CONFIG_INPUT_MISC=y # CONFIG_INPUT_AD714X is not set # CONFIG_INPUT_E3X0_BUTTON is not set CONFIG_INPUT_M68K_BEEP=y # CONFIG_INPUT_UINPUT is not set # CONFIG_INPUT_ADXL34X is not set # CONFIG_INPUT_CMA3000 is not set # CONFIG_RMI4_CORE is not set # # Hardware I/O ports # # CONFIG_SERIO is not set # CONFIG_GAMEPORT is not set # end of Hardware I/O ports # end of Input device support # # Character devices # CONFIG_TTY=y CONFIG_VT=y CONFIG_CONSOLE_TRANSLATIONS=y CONFIG_VT_CONSOLE=y CONFIG_VT_HW_CONSOLE_BINDING=y CONFIG_UNIX98_PTYS=y CONFIG_LEGACY_PTYS=y CONFIG_LEGACY_PTY_COUNT=64 CONFIG_LEGACY_TIOCSTI=y CONFIG_LDISC_AUTOLOAD=y # # Serial drivers # # CONFIG_SERIAL_8250 is not set # # Non-8250 serial port support # # CONFIG_SERIAL_UARTLITE is not set # CONFIG_SERIAL_SCCNXP is not set # CONFIG_SERIAL_ALTERA_JTAGUART is not set # CONFIG_SERIAL_ALTERA_UART is not set # CONFIG_SERIAL_ARC is not set # CONFIG_SERIAL_FSL_LPUART is not set # CONFIG_SERIAL_FSL_LINFLEXUART is not set # end of Serial drivers # CONFIG_SERIAL_NONSTANDARD is not set # CONFIG_N_GSM is not set # CONFIG_NULL_TTY is not set CONFIG_SERIAL_DEV_BUS=y CONFIG_SERIAL_DEV_CTRL_TTYPORT=y # CONFIG_TTY_PRINTK is not set CONFIG_PRINTER=y # CONFIG_LP_CONSOLE is not set # CONFIG_PPDEV is not set # CONFIG_VIRTIO_CONSOLE is not set # CONFIG_IPMI_HANDLER is not set # CONFIG_HW_RANDOM is not set CONFIG_DEVMEM=y CONFIG_NVRAM=y # CONFIG_TCG_TPM is not set # end of Character devices # # I2C support # # CONFIG_I2C is not set # end of I2C support # CONFIG_I3C is not set # CONFIG_SPI is not set # CONFIG_SPMI is not set # CONFIG_HSI is not set # CONFIG_PPS is not set # # PTP clock support # # CONFIG_PTP_1588_CLOCK is not set CONFIG_PTP_1588_CLOCK_OPTIONAL=y # # Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. # # end of PTP clock support # CONFIG_PINCTRL is not set # CONFIG_GPIOLIB is not set # CONFIG_W1 is not set # CONFIG_POWER_RESET is not set # CONFIG_POWER_SEQUENCING is not set # CONFIG_POWER_SUPPLY is not set # CONFIG_HWMON is not set # CONFIG_THERMAL is not set CONFIG_WATCHDOG=y CONFIG_WATCHDOG_CORE=y # CONFIG_WATCHDOG_NOWAYOUT is not set CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y CONFIG_WATCHDOG_OPEN_TIMEOUT=0 # CONFIG_WATCHDOG_SYSFS is not set # CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT is not set # # Watchdog Pretimeout Governors # # CONFIG_WATCHDOG_PRETIMEOUT_GOV is not set # # Watchdog Device Drivers # CONFIG_SOFT_WATCHDOG=y # CONFIG_XILINX_WATCHDOG is not set # CONFIG_CADENCE_WATCHDOG is not set # CONFIG_DW_WATCHDOG is not set # CONFIG_MAX63XX_WATCHDOG is not set CONFIG_SSB_POSSIBLE=y # CONFIG_SSB is not set CONFIG_BCMA_POSSIBLE=y # CONFIG_BCMA is not set # # Multifunction device drivers # # CONFIG_MFD_MADERA is not set # CONFIG_MFD_KEMPLD is not set # CONFIG_MFD_MT6397 is not set # CONFIG_MFD_SM501 is not set # CONFIG_MFD_SYSCON is not set # CONFIG_MFD_TQMX86 is not set # CONFIG_RAVE_SP_CORE is not set # CONFIG_MFD_QNAP_MCU is not set # end of Multifunction device drivers # CONFIG_REGULATOR is not set # CONFIG_RC_CORE is not set # # CEC support # # CONFIG_MEDIA_CEC_SUPPORT is not set # end of CEC support # CONFIG_MEDIA_SUPPORT is not set # # Graphics support # CONFIG_APERTURE_HELPERS=y CONFIG_VIDEO=y # CONFIG_AUXDISPLAY is not set # CONFIG_PANEL is not set # CONFIG_DRM is not set # # Frame buffer Devices # CONFIG_FB=y CONFIG_FB_ATARI=y # CONFIG_FB_OPENCORES is not set # CONFIG_FB_S1D13XXX is not set # CONFIG_FB_ATY is not set # CONFIG_FB_IBM_GXT4500 is not set # CONFIG_FB_VIRTUAL is not set # CONFIG_FB_METRONOME is not set CONFIG_FB_SIMPLE=y CONFIG_FB_CORE=y CONFIG_FB_NOTIFY=y # CONFIG_FIRMWARE_EDID is not set CONFIG_FB_DEVICE=y CONFIG_FB_CFB_FILLRECT=y CONFIG_FB_CFB_COPYAREA=y CONFIG_FB_CFB_IMAGEBLIT=y # CONFIG_FB_FOREIGN_ENDIAN is not set CONFIG_FB_IOMEM_FOPS=y CONFIG_FB_IOMEM_HELPERS=y # CONFIG_FB_MODE_HELPERS is not set # CONFIG_FB_TILEBLITTING is not set # end of Frame buffer Devices # # Backlight & LCD device support # # CONFIG_LCD_CLASS_DEVICE is not set # CONFIG_BACKLIGHT_CLASS_DEVICE is not set # end of Backlight & LCD device support # # Console display driver support # CONFIG_DUMMY_CONSOLE=y CONFIG_DUMMY_CONSOLE_COLUMNS=80 CONFIG_DUMMY_CONSOLE_ROWS=25 CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION=y CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y # CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set # CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER is not set # end of Console display driver support # CONFIG_LOGO is not set # end of Graphics support CONFIG_SOUND=y CONFIG_SOUND_OSS_CORE=y # CONFIG_SOUND_OSS_CORE_PRECLAIM is not set CONFIG_DMASOUND_ATARI=y CONFIG_DMASOUND=y # CONFIG_SND is not set # CONFIG_HID_SUPPORT is not set CONFIG_USB_OHCI_LITTLE_ENDIAN=y # CONFIG_USB_SUPPORT is not set # CONFIG_MMC is not set # CONFIG_SCSI_UFSHCD is not set # CONFIG_MEMSTICK is not set # CONFIG_NEW_LEDS is not set # CONFIG_ACCESSIBILITY is not set # CONFIG_INFINIBAND is not set CONFIG_RTC_LIB=y CONFIG_RTC_CLASS=y CONFIG_RTC_HCTOSYS=y CONFIG_RTC_HCTOSYS_DEVICE="rtc0" # CONFIG_RTC_SYSTOHC is not set # CONFIG_RTC_DEBUG is not set CONFIG_RTC_NVMEM=y # # RTC interfaces # CONFIG_RTC_INTF_SYSFS=y CONFIG_RTC_INTF_PROC=y CONFIG_RTC_INTF_DEV=y # CONFIG_RTC_INTF_DEV_UIE_EMUL is not set # CONFIG_RTC_DRV_TEST is not set # # I2C RTC drivers # # # SPI RTC drivers # # # SPI and I2C RTC drivers # # # Platform RTC drivers # # CONFIG_RTC_DRV_DS1286 is not set # CONFIG_RTC_DRV_DS1511 is not set # CONFIG_RTC_DRV_DS1553 is not set # CONFIG_RTC_DRV_DS1685_FAMILY is not set # CONFIG_RTC_DRV_DS1742 is not set # CONFIG_RTC_DRV_DS2404 is not set # CONFIG_RTC_DRV_STK17TA8 is not set # CONFIG_RTC_DRV_M48T86 is not set # CONFIG_RTC_DRV_M48T35 is not set # CONFIG_RTC_DRV_M48T59 is not set # CONFIG_RTC_DRV_MSM6242 is not set # CONFIG_RTC_DRV_RP5C01 is not set # # on-CPU RTC drivers # CONFIG_RTC_DRV_GENERIC=y # CONFIG_RTC_DRV_FTRTC010 is not set # # HID Sensor RTC drivers # # CONFIG_RTC_DRV_GOLDFISH is not set # CONFIG_DMADEVICES is not set # # DMABUF options # # CONFIG_SYNC_FILE is not set # CONFIG_DMABUF_HEAPS is not set # end of DMABUF options # CONFIG_UIO is not set # CONFIG_VFIO is not set # CONFIG_VIRT_DRIVERS is not set # CONFIG_VIRTIO_MENU is not set # CONFIG_VDPA is not set # CONFIG_VHOST_MENU is not set # # Microsoft Hyper-V guest support # # end of Microsoft Hyper-V guest support # CONFIG_GREYBUS is not set # CONFIG_COMEDI is not set # CONFIG_STAGING is not set # CONFIG_GOLDFISH is not set # CONFIG_COMMON_CLK is not set # CONFIG_HWSPINLOCK is not set # CONFIG_MAILBOX is not set # CONFIG_IOMMU_SUPPORT is not set # # Remoteproc drivers # # CONFIG_REMOTEPROC is not set # end of Remoteproc drivers # # Rpmsg drivers # # CONFIG_RPMSG_VIRTIO is not set # end of Rpmsg drivers # # SOC (System On Chip) specific Drivers # # # Amlogic SoC drivers # # end of Amlogic SoC drivers # # Broadcom SoC drivers # # end of Broadcom SoC drivers # # NXP/Freescale QorIQ SoC drivers # # end of NXP/Freescale QorIQ SoC drivers # # fujitsu SoC drivers # # end of fujitsu SoC drivers # # i.MX SoC drivers # # end of i.MX SoC drivers # # Enable LiteX SoC Builder specific drivers # # end of Enable LiteX SoC Builder specific drivers # CONFIG_WPCM450_SOC is not set # # Qualcomm SoC drivers # # end of Qualcomm SoC drivers # CONFIG_SOC_TI is not set # # Xilinx SoC drivers # # end of Xilinx SoC drivers # end of SOC (System On Chip) specific Drivers # # PM Domains # # # Amlogic PM Domains # # end of Amlogic PM Domains # # Broadcom PM Domains # # end of Broadcom PM Domains # # i.MX PM Domains # # end of i.MX PM Domains # # Qualcomm PM Domains # # end of Qualcomm PM Domains # end of PM Domains # CONFIG_PM_DEVFREQ is not set # CONFIG_EXTCON is not set # CONFIG_MEMORY is not set # CONFIG_IIO is not set # CONFIG_PWM is not set # # IRQ chip support # # end of IRQ chip support # CONFIG_IPACK_BUS is not set # CONFIG_RESET_CONTROLLER is not set # # PHY Subsystem # # CONFIG_GENERIC_PHY is not set # CONFIG_PHY_CAN_TRANSCEIVER is not set # # PHY drivers for Broadcom platforms # # CONFIG_BCM_KONA_USB2_PHY is not set # end of PHY drivers for Broadcom platforms # CONFIG_PHY_PXA_28NM_HSIC is not set # CONFIG_PHY_PXA_28NM_USB2 is not set # end of PHY Subsystem # CONFIG_POWERCAP is not set # CONFIG_MCB is not set # CONFIG_RAS is not set # # Android # # CONFIG_ANDROID_BINDER_IPC is not set # end of Android # CONFIG_DAX is not set CONFIG_NVMEM=y CONFIG_NVMEM_SYSFS=y # CONFIG_NVMEM_LAYOUTS is not set # CONFIG_NVMEM_RMEM is not set # # HW tracing support # # CONFIG_STM is not set # CONFIG_INTEL_TH is not set # end of HW tracing support # CONFIG_FPGA is not set # CONFIG_SIOX is not set # CONFIG_SLIMBUS is not set # CONFIG_INTERCONNECT is not set # CONFIG_COUNTER is not set # CONFIG_PECI is not set # CONFIG_HTE is not set # end of Device Drivers # # File systems # # CONFIG_VALIDATE_FS_PARSER is not set CONFIG_FS_IOMAP=y CONFIG_BUFFER_HEAD=y CONFIG_LEGACY_DIRECT_IO=y CONFIG_EXT2_FS=y # CONFIG_EXT2_FS_XATTR is not set # CONFIG_EXT3_FS is not set # CONFIG_EXT4_FS is not set # CONFIG_JFS_FS is not set # CONFIG_XFS_FS is not set # CONFIG_GFS2_FS is not set # CONFIG_BTRFS_FS is not set # CONFIG_NILFS2_FS is not set # CONFIG_F2FS_FS is not set # CONFIG_BCACHEFS_FS is not set CONFIG_FS_POSIX_ACL=y CONFIG_EXPORTFS=y # CONFIG_EXPORTFS_BLOCK_OPS is not set CONFIG_FILE_LOCKING=y # CONFIG_FS_ENCRYPTION is not set # CONFIG_FS_VERITY is not set CONFIG_FSNOTIFY=y # CONFIG_DNOTIFY is not set CONFIG_INOTIFY_USER=y CONFIG_FANOTIFY=y # CONFIG_FANOTIFY_ACCESS_PERMISSIONS is not set # CONFIG_QUOTA is not set # CONFIG_AUTOFS_FS is not set # CONFIG_FUSE_FS is not set # CONFIG_OVERLAY_FS is not set # # Caches # # end of Caches # # CD-ROM/DVD Filesystems # # CONFIG_ISO9660_FS is not set # CONFIG_UDF_FS is not set # end of CD-ROM/DVD Filesystems # # DOS/FAT/EXFAT/NT Filesystems # CONFIG_FAT_FS=y CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y CONFIG_FAT_DEFAULT_CODEPAGE=437 CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" CONFIG_FAT_DEFAULT_UTF8=y # CONFIG_EXFAT_FS is not set # CONFIG_NTFS3_FS is not set # CONFIG_NTFS_FS is not set # end of DOS/FAT/EXFAT/NT Filesystems # # Pseudo filesystems # CONFIG_PROC_FS=y # CONFIG_PROC_KCORE is not set CONFIG_PROC_SYSCTL=y # CONFIG_PROC_PAGE_MONITOR is not set CONFIG_PROC_CHILDREN=y CONFIG_KERNFS=y CONFIG_SYSFS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_TMPFS_XATTR=y # CONFIG_TMPFS_QUOTA is not set # CONFIG_CONFIGFS_FS is not set # end of Pseudo filesystems CONFIG_MISC_FILESYSTEMS=y # CONFIG_ORANGEFS_FS is not set # CONFIG_ADFS_FS is not set # CONFIG_AFFS_FS is not set # CONFIG_HFS_FS is not set # CONFIG_HFSPLUS_FS is not set # CONFIG_BEFS_FS is not set # CONFIG_BFS_FS is not set # CONFIG_EFS_FS is not set # CONFIG_CRAMFS is not set # CONFIG_SQUASHFS is not set # CONFIG_VXFS_FS is not set # CONFIG_MINIX_FS is not set # CONFIG_OMFS_FS is not set # CONFIG_HPFS_FS is not set # CONFIG_QNX4FS_FS is not set # CONFIG_QNX6FS_FS is not set CONFIG_ROMFS_FS=y CONFIG_ROMFS_BACKED_BY_BLOCK=y CONFIG_ROMFS_ON_BLOCK=y # CONFIG_PSTORE is not set # CONFIG_UFS_FS is not set # CONFIG_EROFS_FS is not set # CONFIG_NETWORK_FILESYSTEMS is not set CONFIG_NLS=y CONFIG_NLS_DEFAULT="iso8859-1" CONFIG_NLS_CODEPAGE_437=y # CONFIG_NLS_CODEPAGE_737 is not set # CONFIG_NLS_CODEPAGE_775 is not set CONFIG_NLS_CODEPAGE_850=y CONFIG_NLS_CODEPAGE_852=y # CONFIG_NLS_CODEPAGE_855 is not set # CONFIG_NLS_CODEPAGE_857 is not set # CONFIG_NLS_CODEPAGE_860 is not set # CONFIG_NLS_CODEPAGE_861 is not set # CONFIG_NLS_CODEPAGE_862 is not set # CONFIG_NLS_CODEPAGE_863 is not set # CONFIG_NLS_CODEPAGE_864 is not set CONFIG_NLS_CODEPAGE_865=y # CONFIG_NLS_CODEPAGE_866 is not set # CONFIG_NLS_CODEPAGE_869 is not set # CONFIG_NLS_CODEPAGE_936 is not set # CONFIG_NLS_CODEPAGE_950 is not set # CONFIG_NLS_CODEPAGE_932 is not set # CONFIG_NLS_CODEPAGE_949 is not set # CONFIG_NLS_CODEPAGE_874 is not set # CONFIG_NLS_ISO8859_8 is not set CONFIG_NLS_CODEPAGE_1250=y # CONFIG_NLS_CODEPAGE_1251 is not set CONFIG_NLS_ASCII=y CONFIG_NLS_ISO8859_1=y CONFIG_NLS_ISO8859_2=y # CONFIG_NLS_ISO8859_3 is not set # CONFIG_NLS_ISO8859_4 is not set # CONFIG_NLS_ISO8859_5 is not set # CONFIG_NLS_ISO8859_6 is not set # CONFIG_NLS_ISO8859_7 is not set # CONFIG_NLS_ISO8859_9 is not set # CONFIG_NLS_ISO8859_13 is not set # CONFIG_NLS_ISO8859_14 is not set CONFIG_NLS_ISO8859_15=y # CONFIG_NLS_KOI8_R is not set # CONFIG_NLS_KOI8_U is not set # CONFIG_NLS_MAC_ROMAN is not set # CONFIG_NLS_MAC_CELTIC is not set # CONFIG_NLS_MAC_CENTEURO is not set # CONFIG_NLS_MAC_CROATIAN is not set # CONFIG_NLS_MAC_CYRILLIC is not set # CONFIG_NLS_MAC_GAELIC is not set # CONFIG_NLS_MAC_GREEK is not set # CONFIG_NLS_MAC_ICELAND is not set # CONFIG_NLS_MAC_INUIT is not set # CONFIG_NLS_MAC_ROMANIAN is not set # CONFIG_NLS_MAC_TURKISH is not set CONFIG_NLS_UTF8=y # CONFIG_UNICODE is not set CONFIG_IO_WQ=y # end of File systems # # Security options # # CONFIG_KEYS is not set # CONFIG_SECURITY_DMESG_RESTRICT is not set CONFIG_PROC_MEM_ALWAYS_FORCE=y # CONFIG_PROC_MEM_FORCE_PTRACE is not set # CONFIG_PROC_MEM_NO_FORCE is not set # CONFIG_SECURITY is not set # CONFIG_SECURITYFS is not set # CONFIG_STATIC_USERMODEHELPER is not set CONFIG_DEFAULT_SECURITY_DAC=y CONFIG_LSM="yama,loadpin,safesetid,integrity" # # Kernel hardening options # # # Memory initialization # CONFIG_CC_HAS_AUTO_VAR_INIT_PATTERN=y CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO_BARE=y CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO=y CONFIG_INIT_STACK_NONE=y # CONFIG_INIT_STACK_ALL_PATTERN is not set # CONFIG_INIT_STACK_ALL_ZERO is not set # CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set # CONFIG_INIT_ON_FREE_DEFAULT_ON is not set CONFIG_CC_HAS_ZERO_CALL_USED_REGS=y # CONFIG_ZERO_CALL_USED_REGS is not set # end of Memory initialization # # Bounds checking # # CONFIG_HARDENED_USERCOPY is not set # end of Bounds checking # # Hardening of kernel data structures # CONFIG_LIST_HARDENED=y CONFIG_BUG_ON_DATA_CORRUPTION=y # end of Hardening of kernel data structures CONFIG_RANDSTRUCT_NONE=y # end of Kernel hardening options # end of Security options # CONFIG_CRYPTO is not set # # Library routines # # CONFIG_PACKING is not set CONFIG_BITREVERSE=y CONFIG_GENERIC_STRNCPY_FROM_USER=y CONFIG_GENERIC_STRNLEN_USER=y CONFIG_GENERIC_NET_UTILS=y # CONFIG_CORDIC is not set # CONFIG_PRIME_NUMBERS is not set # # Crypto library routines # CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y CONFIG_CRYPTO_LIB_POLY1305_RSIZE=1 CONFIG_CRYPTO_LIB_SHA1=y # end of Crypto library routines CONFIG_CRC_CCITT=y CONFIG_CRC32=y CONFIG_CRC_OPTIMIZATIONS=y CONFIG_XXHASH=y # CONFIG_RANDOM32_SELFTEST is not set CONFIG_ZLIB_INFLATE=y CONFIG_ZLIB_DEFLATE=y CONFIG_LZO_DECOMPRESS=y CONFIG_ZSTD_COMMON=y CONFIG_ZSTD_DECOMPRESS=y # CONFIG_XZ_DEC is not set CONFIG_DECOMPRESS_GZIP=y CONFIG_DECOMPRESS_LZO=y CONFIG_DECOMPRESS_ZSTD=y CONFIG_GENERIC_ALLOCATOR=y CONFIG_HAS_IOMEM=y CONFIG_HAS_DMA=y CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE=y CONFIG_ARCH_HAS_DMA_PREP_COHERENT=y CONFIG_DMA_NEED_SYNC=y CONFIG_DMA_NONCOHERENT_MMAP=y CONFIG_DMA_COHERENT_POOL=y CONFIG_DMA_DIRECT_REMAP=y # CONFIG_DMA_API_DEBUG is not set CONFIG_FORCE_NR_CPUS=y CONFIG_DQL=y CONFIG_GLOB=y # CONFIG_GLOB_SELFTEST is not set CONFIG_NLATTR=y CONFIG_GENERIC_ATOMIC64=y # CONFIG_IRQ_POLL is not set CONFIG_FONT_SUPPORT=y # CONFIG_FONTS is not set CONFIG_FONT_8x8=y CONFIG_FONT_8x16=y CONFIG_SG_POOL=y CONFIG_SBITMAP=y # CONFIG_LWQ_TEST is not set # end of Library routines CONFIG_GENERIC_LIB_ASHLDI3=y CONFIG_GENERIC_LIB_ASHRDI3=y CONFIG_GENERIC_LIB_LSHRDI3=y CONFIG_GENERIC_LIB_MULDI3=y # # Kernel hacking # # # printk and dmesg options # # CONFIG_PRINTK_TIME is not set # CONFIG_PRINTK_CALLER is not set # CONFIG_STACKTRACE_BUILD_ID is not set CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 CONFIG_CONSOLE_LOGLEVEL_QUIET=4 CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 # CONFIG_BOOT_PRINTK_DELAY is not set # CONFIG_DYNAMIC_DEBUG is not set # CONFIG_DYNAMIC_DEBUG_CORE is not set # CONFIG_SYMBOLIC_ERRNAME is not set CONFIG_DEBUG_BUGVERBOSE=y # end of printk and dmesg options CONFIG_DEBUG_KERNEL=y # CONFIG_DEBUG_MISC is not set # # Compile-time checks and compiler options # CONFIG_AS_HAS_NON_CONST_ULEB128=y CONFIG_DEBUG_INFO_NONE=y # CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT is not set # CONFIG_DEBUG_INFO_DWARF4 is not set # CONFIG_DEBUG_INFO_DWARF5 is not set CONFIG_FRAME_WARN=1024 CONFIG_STRIP_ASM_SYMS=y # CONFIG_READABLE_ASM is not set # CONFIG_HEADERS_INSTALL is not set # CONFIG_DEBUG_SECTION_MISMATCH is not set CONFIG_SECTION_MISMATCH_WARN_ONLY=y CONFIG_FRAME_POINTER=y CONFIG_VMLINUX_MAP=y # CONFIG_BUILTIN_MODULE_RANGES is not set # CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set # end of Compile-time checks and compiler options # # Generic Kernel Debugging Instruments # CONFIG_MAGIC_SYSRQ=y CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0xff CONFIG_MAGIC_SYSRQ_SERIAL=y CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE="" # CONFIG_DEBUG_FS is not set CONFIG_HAVE_KCSAN_COMPILER=y # end of Generic Kernel Debugging Instruments # # Networking Debugging # # CONFIG_DEBUG_NET is not set # end of Networking Debugging # # Memory Debugging # # CONFIG_PAGE_EXTENSION is not set # CONFIG_DEBUG_PAGEALLOC is not set # CONFIG_PAGE_POISONING is not set # CONFIG_DEBUG_OBJECTS is not set # CONFIG_DEBUG_STACK_USAGE is not set CONFIG_SCHED_STACK_END_CHECK=y # CONFIG_DEBUG_VFS is not set # CONFIG_DEBUG_VM is not set CONFIG_DEBUG_MEMORY_INIT=y # CONFIG_MEM_ALLOC_PROFILING is not set CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y # end of Memory Debugging CONFIG_DEBUG_SHIRQ=y # # Debug Oops, Lockups and Hangs # # CONFIG_PANIC_ON_OOPS is not set CONFIG_PANIC_ON_OOPS_VALUE=0 CONFIG_PANIC_TIMEOUT=0 CONFIG_LOCKUP_DETECTOR=y CONFIG_SOFTLOCKUP_DETECTOR=y # CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set CONFIG_DETECT_HUNG_TASK=y CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 # CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set CONFIG_DETECT_HUNG_TASK_BLOCKER=y CONFIG_WQ_WATCHDOG=y # CONFIG_WQ_CPU_INTENSIVE_REPORT is not set # end of Debug Oops, Lockups and Hangs # # Scheduler Debugging # # CONFIG_SCHEDSTATS is not set # end of Scheduler Debugging # # Lock Debugging (spinlocks, mutexes, etc...) # # CONFIG_DEBUG_RT_MUTEXES is not set # CONFIG_DEBUG_SPINLOCK is not set # CONFIG_DEBUG_MUTEXES is not set # CONFIG_DEBUG_RWSEMS is not set # CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set # CONFIG_LOCK_TORTURE_TEST is not set # CONFIG_WW_MUTEX_SELFTEST is not set # CONFIG_SCF_TORTURE_TEST is not set # end of Lock Debugging (spinlocks, mutexes, etc...) # CONFIG_DEBUG_IRQFLAGS is not set # CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set # CONFIG_DEBUG_KOBJECT is not set CONFIG_HAVE_DEBUG_BUGVERBOSE=y # # Debug kernel data structures # CONFIG_DEBUG_LIST=y # CONFIG_DEBUG_PLIST is not set # CONFIG_DEBUG_SG is not set # CONFIG_DEBUG_NOTIFIERS is not set # CONFIG_DEBUG_MAPLE_TREE is not set # end of Debug kernel data structures # # RCU Debugging # # CONFIG_RCU_SCALE_TEST is not set # CONFIG_RCU_TORTURE_TEST is not set # CONFIG_RCU_REF_SCALE_TEST is not set # CONFIG_RCU_TRACE is not set # CONFIG_RCU_EQS_DEBUG is not set # end of RCU Debugging # CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set # CONFIG_SAMPLES is not set # # m68k Debugging # # CONFIG_BOOTPARAM is not set # CONFIG_EARLY_PRINTK is not set # end of m68k Debugging # # Kernel Testing and Coverage # # CONFIG_KUNIT is not set # CONFIG_NOTIFIER_ERROR_INJECTION is not set # CONFIG_FAULT_INJECTION is not set CONFIG_CC_HAS_SANCOV_TRACE_PC=y # CONFIG_RUNTIME_TESTING_MENU is not set CONFIG_ARCH_USE_MEMTEST=y # CONFIG_MEMTEST is not set # end of Kernel Testing and Coverage # # Rust hacking # # end of Rust hacking # end of Kernel hacking hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/linux/symlink-busybox.sh000077500000000000000000000007431504763705000265620ustar00rootroot00000000000000#!/bin/sh # # script to generate m68k busybox links. # # Uses user-space Qemu to query from m68k busybox what # tools it provides. bb_path=usr/bin/busybox qemu=$(which qemu-m68k) if [ -z $qemu ]; then echo "ERROR: install 'qemu-m68k' first!" echo " sudo apt install qemu-user" exit 1 fi if [ ! -x $bb_path ]; then echo "ERROR: m68k BusyBox '$bb_path' missing!" exit 1 fi # symlink Busybox tools for tool in $($qemu $bb_path --list-full); do ln -sfv /$bb_path "$tool" done hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/zip2st.1000066400000000000000000000041441504763705000232210ustar00rootroot00000000000000.TH "ZIP2ST" "1" "2016-02-26" "Hatari" "Hatari utilities" .SH NAME zip2st \- convert directory or .zip archive to .st disk image .SH SYNOPSIS .B zip2st .RI .RI [dstname.st] .SH DESCRIPTION zip2st can be used to generate Atari .st disk image from directory and .zip archive contents. .PP If the destination filename is not given, it's based on the source name, with the .zip extension removed and .st prefix added. If \fIbasename\fP tool is present, file is saved to current directory, otherwise to the directory where the source directory/archive is. .PP Any single directories (other than 'auto') within the source zip / directory hierarchy are traversed down, to avoid adding redundant intermediate directories to the generated disk image. .PP The .st disk image is a raw disk image, and can be written to a floppy using dd(1), or converted to a .msa disk image with \fIhmsa\fP(1). .PP The contents of the uncompressed zip file needs to be smaller than 2.88MB (the largest floppy image size supported by Hatari). .PP If the source file names are longer than 8+3, use \fIatari\-convert\-dir\fP script to convert them into Atari compatible ones. If the files are within a .zip archive, you need to extract them before conversion. .PP .SH SEE ALSO .IR hmsa (1), .IR atari\-convert\-dir (1), .IR atari\-hd\-image (1), .IR hatari (1), .IR unzip (1), .IR mtools (1), .IR basename (1), .IR dd (1). .SH "AUTHOR" Written by Thomas Huth and Eero Tamminen. .PP This manual page was written by Teemu Hukkanen for the Debian project and later modified by Eero Tamminen to suit the latest version of Hatari. .SH "LICENSE" This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. .SH "NO WARRANTY" This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. hatari-v2.6.1-6a8e26d69fb32d61efd398dc1ad9a77122400034/tools/zip2st.sh000077500000000000000000000101161504763705000234720ustar00rootroot00000000000000#!/bin/sh # Script for converting .ZIP archives or directory contents # to Atari .ST disk images. # required tools are present? if [ -z "$(which mformat)" ] || [ -z "$(which mcopy)" ]; then echo "ERROR: 'mformat' or 'mcopy' (from 'mtools' package) missing." exit 2 fi usage () { name=${0##*/} echo "Convert a .zip archive file or directory" echo "contents to a .st disk image." echo echo "Single intermediate directories in the zip" echo "file are skipped (except AUTO/-folder)." echo echo "Usage:" echo " $name srcname.zip [destname.st]" echo " $name directory/ [destname.st]" echo echo "Example:" echo " for zip in *.zip; do $name \$zip; done" echo if [ -n "$1" ]; then echo "ERROR: $1!" fi exit 1 } # one ZIPFILE given? if [ $# -lt 1 ] || [ -z "$1" ] || [ $# -gt 2 ]; then usage "wrong number of argument(s)" fi ZIPFILE=$1 STFILE=$2 if [ ! -e "$ZIPFILE" ]; then usage "given ZIP file or directory '$ZIPFILE' is missing" fi if [ -z "$STFILE" ]; then # if no STFILE path given, target name based on ZIPFILE name # with extension removed and spaces converted to underscores BASENAME=${ZIPFILE%.zip} BASENAME=${BASENAME%.ZIP} if [ -z "$(which basename)" ]; then # goes to same place as source directory STFILE=${BASENAME}.st else # basename can reliably give last path component STFILE=$(basename "$BASENAME").st fi STFILE=$(echo "$STFILE" | tr ' ' '_') fi if [ -f "$STFILE" ]; then echo "ERROR: ST file '$STFILE' already exists, remove it first. Aborting..." exit 1 fi step=0 TEMPDIR="" # script exit/error handling exit_cleanup () { retval=$? if [ $retval -eq 0 ]; then echo "$step) Cleaning up temporary files..." else echo echo "ERROR (retval $retval), cleaning up..." fi if [ -n "$TEMPDIR" ]; then echo "rm -rv $TEMPDIR" rm -rv "$TEMPDIR" fi echo "Done." } if [ -d "$ZIPFILE" ]; then echo "Converting '$ZIPFILE/' -> '$STFILE'" ZIPDIR="$ZIPFILE" else if [ -z "$(which unzip)" ]; then echo "ERROR: 'unzip' tool missing." exit 2 fi TEMPDIR=$(mktemp -d) || exit 2 echo "Converting '$ZIPFILE' -> '$TEMPDIR/' -> '$STFILE'" trap exit_cleanup EXIT echo step=$((step+1)) echo "$step) Unzipping..." echo "unzip $ZIPFILE -d $TEMPDIR" unzip "$ZIPFILE" -d "$TEMPDIR" || exit 2 # .zip files created with STZip sometimes have wrong access rights... echo "chmod -R u+rw $TEMPDIR/*" chmod -R u+rw "$TEMPDIR"/* ZIPDIR=$TEMPDIR fi echo step=$((step+1)) echo "$step) Checking/skipping intermediate directories..." while true; do # shellcheck disable=SC2012 count=$(ls "$ZIPDIR"|wc -l) if [ "$count" -ne 1 ]; then if [ "$count" -eq 0 ]; then echo "ERROR: zip content is empty!" exit 1 fi # more than one dir/file break fi dir=$(ls "$ZIPDIR") if [ ! -d "$ZIPDIR/$dir" ]; then # not dir break fi if ! echo "$dir" | grep -q -v -i '^auto$'; then # don't skip AUTO dir break fi echo "- $dir" ZIPDIR="$ZIPDIR/$dir" done # size of reserved sectors, FATs & root dir + zip content size size=$((24 + $(du -ks "$ZIPDIR"|awk '{print $1}'))) # find a suitable disk size supported by mformat and Atari ST disksize=0 for i in 360 400 720 800 1440 2880; do if [ $i -gt $size ]; then disksize=$i break fi done if [ $disksize -gt 0 ]; then echo step=$((step+1)) echo "$step) Creating $disksize KB disk image..." echo "dd if=/dev/zero of=$STFILE bs=1024 count=$disksize" dd if=/dev/zero of="$STFILE" bs=1024 count=$disksize echo step=$((step+1)) echo "$step) Formatting disk image..." case $disksize in 360) format="-t 80 -h 1 -n 9" ;; 400) format="-t 80 -h 1 -n 10" ;; 800) format="-t 80 -h 2 -n 10" ;; *) format="-f $disksize" ;; esac echo "mformat -a $format -i $STFILE ::" # https://www.shellcheck.net/wiki/SC2086 # format options are supposed to be separate # shellcheck disable=SC2086 mformat -a $format -i "$STFILE" :: echo step=$((step+1)) echo "$step) Copying data to disk image..." echo "MTOOLS_NO_VFAT=1 mcopy -i $STFILE -spmv $ZIPDIR/* ::" MTOOLS_NO_VFAT=1 mcopy -i "$STFILE" -spmv "$ZIPDIR"/* :: else echo "ERROR: zip contents don't fit to a floppy image ($size > 2880 KB)." fi echo step=$((step+1)) # do cleanup in exit handler