diff --git a/.ci/AppImageBuilder.yml b/.ci/AppImageBuilder.yml index 9b2c7dd04..cadb3862d 100644 --- a/.ci/AppImageBuilder.yml +++ b/.ci/AppImageBuilder.yml @@ -51,45 +51,26 @@ AppDir: - libsndio7.0 - libwayland-client0 - libx11-6 + - libx11-xcb1 - libxcb1 + - libxcb-render0 + - libxcb-shape0 + - libxcb-shm0 + - libxcb-xfixes0 - zlib1g files: exclude: - etc - lib/udev - opt/libc/usr/share - - usr/bin - - usr/include + - usr/[a-km-rt-zA-Z]* - usr/lib/*/libasound.so.* + - usr/lib/*.a - usr/lib/cmake - usr/lib/pkgconfig - - usr/sbin - - usr/share/aclocal - - usr/share/alsa - - usr/share/apport - - usr/share/bug - - usr/share/color - - usr/share/doc - - usr/share/doc-base - - usr/share/fontconfig - - usr/share/fonts - - usr/share/ghostscript - - usr/share/glib-2.0 - - usr/share/info - - usr/share/libinput - - usr/share/libwacom - - usr/share/lintian - - usr/share/locale - - usr/share/man - - usr/share/metainfo - - usr/share/openal - - usr/share/pkgconfig - - usr/share/poppler - - usr/share/readline - - usr/share/rtmidi - - usr/share/sounds - - usr/share/X11 - - usr/share/xml + - usr/s[a-gi-zA-Z]* + - usr/share/[a-hj-zA-Z]* + - usr/share/i[a-bd-zA-Z]* - var AppImage: arch: !ENV '${arch_appimage}' diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index e20b0d363..a90e1d806 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -12,7 +12,7 @@ * * Authors: RichardG, * - * Copyright 2021 RichardG. + * Copyright 2021-2022 RichardG. */ def repository = 'https://github.com/86Box/86Box.git' @@ -84,8 +84,6 @@ def presetFlags = [ 'Dev': '--preset=experimental -D CMAKE_BUILD_TYPE=Debug -D VNC=OFF' ] -def anyFailure = false - def gitClone(repository, branch) { /* Clean workspace. */ cleanWs() @@ -105,9 +103,9 @@ def gitClone(repository, branch) { } else if (env.GIT_COMMIT != scmVars.GIT_COMMIT) { /* Checkout the commit read from the polling log. */ if (isUnix()) - sh "git checkout ${env.GIT_COMMIT} || exit 0" + sh returnStatus: true, script: "git checkout ${env.GIT_COMMIT}" else - bat "git checkout ${env.GIT_COMMIT} || exit /b 0" + bat returnStatus: true, script: "git checkout ${env.GIT_COMMIT}" } println "[-] Using git commit [${env.GIT_COMMIT}]" @@ -122,16 +120,23 @@ def gitClone(repository, branch) { def removeDir(dir) { if (isUnix()) - sh "rm -rf \"$dir\" || exit 0" + return sh(returnStatus: true, script: "rm -rf \"$dir\"") else - bat "if exist \"$dir\" rd /s /q \"$dir\" & exit /b 0" + return bat(returnStatus: true, script: "rd /s /q \"$dir\"") } def runBuild(args) { if (isUnix()) - sh "chmod u+x \"$WORKSPACE/.ci/build.sh\" && exec \"$WORKSPACE/.ci/build.sh\" $args" + return sh(returnStatus: true, script: "chmod u+x \"$WORKSPACE/.ci/build.sh\" && exec \"$WORKSPACE/.ci/build.sh\" $args") else - bat "C:\\msys64\\msys2_shell.cmd -msys2 -defterm -here -no-start -c 'exec \"\$(cygpath -u \\'%WORKSPACE%\\')/.ci/build.sh\" $args'" + return bat(returnStatus: true, script: "C:\\msys64\\msys2_shell.cmd -msys2 -defterm -here -no-start -c 'exec \"\$(cygpath -u \\'%WORKSPACE%\\')/.ci/build.sh\" $args'") +} + +def failStage() { + /* Force this stage to fail. */ + catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { + def x = 1 / 0 + } } pipeline { @@ -142,7 +147,6 @@ pipeline { } options { - disableConcurrentBuilds() quietPeriod(0) } @@ -155,6 +159,7 @@ pipeline { stages { stage('Source Tarball') { agent none + failFast false steps { script { @@ -178,78 +183,84 @@ pipeline { /* Adding to the above, run a git clone as soon as possible on any node to further avoid race conditions caused by busy node executor delays. */ - node('!Windows') { - /* Run git clone. */ - gitClone(repository, branch) - - /* Clean workspace, in case this is running in a non-build node. */ - cleanWs() - } - - /* Create source tarball. */ - node('Linux') { - try { + retry(10) { + node('citadel && !Windows') { /* Run git clone. */ gitClone(repository, branch) - /* Switch to temp directory. */ - dir("${env.WORKSPACE_TMP}/output") { - /* Run source tarball creation process. */ - def packageName = "${env.JOB_BASE_NAME}-Source-b${env.BUILD_NUMBER}" - runBuild("-s \"$packageName\"") + /* Clean workspace, in case this is running in a non-build node. */ + cleanWs() + } + } - /* Archive resulting artifacts. */ - archiveArtifacts artifacts: "$packageName*" - } + /* Create source tarball. */ + try { + retry(10) { + node('Linux') { + /* Run git clone. */ + gitClone(repository, branch) - /* Clean up. */ - removeDir("${env.WORKSPACE_TMP}/output") - } catch (e) { - /* Mark that a failure occurred. */ - anyFailure = true + /* Switch to temp directory. */ + dir("${env.WORKSPACE_TMP}/output") { + /* Run source tarball creation process. */ + def packageName = "${env.JOB_BASE_NAME}-Source-b${env.BUILD_NUMBER}" + if (runBuild("-s \"$packageName\"") == 0) { + /* Archive resulting artifacts. */ + archiveArtifacts artifacts: "$packageName*" + } else { + /* Fail this stage. */ + failStage() + } + } - /* Force this stage to fail. */ - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { - throw e; + /* Clean up. */ + removeDir("${env.WORKSPACE_TMP}/output") } } + } catch (e) { + /* Fail this stage. */ + failStage() } /* Build here to avoid creating a redundant parent stage on the stage view. */ osArchs.each { os, thisOsArchs -> + def combinations = [:] thisOsArchs.each { arch -> def thisArchDynarecs = dynarecArchs[arch] if (!thisArchDynarecs) thisArchDynarecs = ['NoDR'] thisArchDynarecs.each { dynarec -> presets.each { preset -> - node(os) { - stage("$os $arch $dynarec $preset") { - try { - /* Run git clone. */ - gitClone(repository, branch) + def combination = "$os $arch $dynarec $preset" + combinations[combination] = { + catchError(buildResult: 'FAILURE', stageResult: 'SUCCESS') { + retry(10) { + node(os) { + stage(combination) { + /* Run git clone. */ + gitClone(repository, branch) - /* Switch to output directory. */ - dir("${env.WORKSPACE_TMP}/output") { - /* Run build process. */ - def packageName = "${env.JOB_BASE_NAME}${dynarecSlugs[dynarec]}${presetSlugs[preset]}-$os-$arch-b${env.BUILD_NUMBER}" - dir("${dynarecNames[dynarec]}/$os - ${archNames[arch]}") { - runBuild("-b \"$packageName\" \"$arch\" ${presetFlags[preset]} ${dynarecFlags[dynarec]} ${osFlags[os]} -D \"BUILD_TYPE=$BUILD_TYPE\" -D \"EMU_BUILD=build ${env.BUILD_NUMBER}\" -D \"EMU_BUILD_NUM=${env.BUILD_NUMBER}\"") + /* Switch to output directory. */ + dir("${env.WORKSPACE_TMP}/output") { + /* Run build process. */ + def packageName = "${env.JOB_BASE_NAME}${dynarecSlugs[dynarec]}${presetSlugs[preset]}-$os-$arch-b${env.BUILD_NUMBER}" + def ret = -1 + dir("${dynarecNames[dynarec]}/$os - ${archNames[arch]}") { + ret = runBuild("-b \"$packageName\" \"$arch\" ${presetFlags[preset]} ${dynarecFlags[dynarec]} ${osFlags[os]} -D \"BUILD_TYPE=$BUILD_TYPE\" -D \"EMU_BUILD=build ${env.BUILD_NUMBER}\" -D \"EMU_BUILD_NUM=${env.BUILD_NUMBER}\"") + } + + if (ret == 0) { + /* Archive resulting artifacts. */ + archiveArtifacts artifacts: "**/**/$packageName*" + } else { + /* Fail this stage. */ + failStage() + } + } + + /* Clean up. */ + removeDir("${env.WORKSPACE_TMP}/output") } - - /* Archive resulting artifacts. */ - archiveArtifacts artifacts: "**/**/$packageName*" - } - - /* Clean up. */ - removeDir("${env.WORKSPACE_TMP}/output") - } catch (e) { - /* Mark that a failure occurred. */ - anyFailure = true - - /* Force this stage to fail. */ - catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { - throw e; } } } @@ -257,6 +268,7 @@ pipeline { } } } + parallel combinations } } } @@ -266,12 +278,6 @@ pipeline { post { always { script { - /* Mark build as failed if any step has failed. */ - if (anyFailure) { - println "[!] Failing build because a build stage failed" - currentBuild.result = 'FAILURE' - } - /* Send out build notifications. */ if (!env.JOB_BASE_NAME.contains('TestBuildPleaseIgnore')) { try { @@ -287,7 +293,7 @@ pipeline { scmWebUrl: commitBrowser /* Notify IRC, which needs a node for whatever reason. */ - node { + node('citadel') { ircNotify() } } catch (e) { diff --git a/.ci/build.sh b/.ci/build.sh index 493f42789..e62d037bd 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -34,7 +34,7 @@ # # Define common functions. -alias is_windows='[ ! -z "$MSYSTEM" ]' +alias is_windows='[ -n "$MSYSTEM" ]' alias is_mac='uname -s | grep -q Darwin' make_tar() { @@ -62,7 +62,7 @@ make_tar() { fi # Make tar verbose if requested. - [ ! -z "$VERBOSE" ] && local compression_flag="$compression_flag -v" + [ -n "$VERBOSE" ] && local compression_flag="$compression_flag -v" # tar is notorious for having many diverging implementations. For instance, # the flags we use to strip UID/GID metadata can be --owner/group (GNU), @@ -136,7 +136,7 @@ done cmake_flags_extra= # Check if mandatory arguments were specified. -if [ -z "$package_name" -a -z "$tarball_name" ] || [ ! -z "$package_name" -a -z "$arch" ] +if [ -z "$package_name" -a -z "$tarball_name" ] || [ -n "$package_name" -a -z "$arch" ] then echo '[!] Usage: build.sh -b {package_name} {architecture} [-t] [cmake_flags...]' echo ' build.sh -s {source_tarball_name}' @@ -147,7 +147,7 @@ fi cd "$(dirname "$0")/.." # Make source tarball if requested. -if [ ! -z "$tarball_name" ] +if [ -n "$tarball_name" ] then echo [-] Making source tarball [$tarball_name] @@ -211,6 +211,107 @@ then fi echo [-] Using MSYSTEM [$MSYSTEM] + # Update keyring, as the package signing keys sometimes change. + echo [-] Updating package databases and keyring + yes | pacman -Sy --needed msys2-keyring + + # Query installed packages. + pacman -Qe > pacman.txt + + # Download the specified versions of architecture-specific dependencies. + echo -n [-] Downloading dependencies: + pkg_dir="/var/cache/pacman/pkg" + repo_base="https://repo.msys2.org/mingw/$(echo $MSYSTEM | tr '[:upper:]' '[:lower:]')" + cat .ci/dependencies_msys.txt | tr -d '\r' > deps.txt + pkgs="" + while IFS=" " read pkg version + do + prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" + installed_version=$(grep -E "^$prefixed_pkg " pacman.txt | cut -d " " -f 2) + if [ "$installed_version" != "$version" ] # installed_version will be empty if not installed + then + echo -n " [$pkg" + + # Download package if not already present in the local cache. + pkg_tar="$prefixed_pkg-$version-any.pkg.tar" + if [ -s "$pkg_dir/$pkg_tar.xz" ] + then + pkg_fn="$pkg_tar.xz" + pkg_dest="$pkg_dir/$pkg_fn" + else + pkg_fn="$pkg_tar.zst" + pkg_dest="$pkg_dir/$pkg_fn" + if [ ! -s "$pkg_dest" ] + then + if ! wget -qO "$pkg_dest" "$repo_base/$pkg_fn" + then + rm -f "$pkg_dest" + pkg_fn="$pkg_tar.xz" + pkg_dest="$pkg_dir/$pkg_fn" + wget -qO "$pkg_dest" "$repo_base/$pkg_fn" || rm -f "$pkg_dest" + fi + if [ -s "$pkg_dest" ] + then + wget -qO "$pkg_dest.sig" "$repo_base/$pkg_fn.sig" || rm -f "$pkg_dest.sig" + [ ! -s "$pkg_dest.sig" ] && rm -f "$pkg_dest.sig" + fi + fi + fi + + # Check if the cached package is valid. + if [ -s "$pkg_dest" ] + then + # Add cached zst package. + pkgs="$pkgs $pkg_fn" + else + # Not valid, remove if it exists. + rm -f "$pkg_dest" "$pkg_dest.sig" + echo -n " FAIL" + fi + echo -n "]" + fi + done < deps.txt + [ -z "$pkgs" ] && echo -n ' none required' + echo + + # Install the downloaded architecture-specific dependencies. + echo [-] Installing dependencies through pacman + if [ -n "$pkgs" ] + then + pushd "$pkg_dir" + yes | pacman -U --needed $pkgs + if [ $? -ne 0 ] + then + # Install packages individually if installing them all together failed. + for pkg in $pkgs + do + yes | pacman -U --needed "$pkg" + done + fi + popd + + # Query installed packages again. + pacman -Qe > pacman.txt + fi + + # Install the latest versions for any missing packages (if the specified version couldn't be installed). + pkgs="make git" + while IFS=" " read pkg version + do + prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" + grep -qE "^$prefixed_pkg " pacman.txt || pkgs="$pkgs $prefixed_pkg" + done < deps.txt + rm -f pacman.txt deps.txt + yes | pacman -S --needed $pkgs + if [ $? -ne 0 ] + then + # Install packages individually if installing them all together failed. + for pkg in $pkgs + do + yes | pacman -S --needed "$pkg" + done + fi + # Point CMake to the toolchain file. cmake_flags_extra="$cmake_flags_extra -D \"CMAKE_TOOLCHAIN_FILE=cmake/$toolchain.cmake\"" elif is_mac @@ -227,7 +328,7 @@ else esac # Establish general dependencies. - pkgs="cmake pkg-config git imagemagick wget p7zip-full wayland-protocols tar gzip" + pkgs="cmake pkg-config git imagemagick wget p7zip-full wayland-protocols tar gzip file" if [ "$(dpkg --print-architecture)" = "$arch_deb" ] then pkgs="$pkgs build-essential" @@ -242,7 +343,7 @@ else # ...and the ones we do want listed. Non-dev packages fill missing spots on the list. libpkgs="" longest_libpkg=0 - for pkg in libc6-dev libstdc++6 libopenal-dev libfreetype6-dev libx11-dev libsdl2-dev libpng-dev librtmidi-dev qtdeclarative5-dev libwayland-dev libevdev-dev libglib2.0-dev libslirp-dev + for pkg in libc6-dev libstdc++6 libopenal-dev libfreetype6-dev libx11-dev libsdl2-dev libpng-dev librtmidi-dev qtdeclarative5-dev libwayland-dev libevdev-dev libglib2.0-dev libslirp-dev libfaudio-dev libaudio-dev libjack-jackd2-dev libpipewire-0.3-dev libsamplerate0-dev libsndio-dev do libpkgs="$libpkgs $pkg:$arch_deb" length=$(echo -n $pkg | sed 's/-dev$//' | sed "s/qtdeclarative/qt/" | wc -c) @@ -298,9 +399,6 @@ EOF # Link against the system libslirp instead of compiling ours. cmake_flags_extra="$cmake_flags_extra -D SLIRP_EXTERNAL=ON" - - # Use OpenAL for Linux builds before FAudio builds are set up. - cmake_flags_extra="$cmake_flags_extra -D OPENAL=ON" fi # Clean workspace. @@ -327,16 +425,16 @@ if [ "$CI" = "true" ] then # Backup strategy when running under Jenkins. [ -z "$git_hash" ] && git_hash=$(echo $GIT_COMMIT | cut -c 1-8) -elif [ ! -z "$git_hash" ] +elif [ -n "$git_hash" ] then # Append + to denote a dirty tree. git diff --quiet 2> /dev/null || git_hash="$git_hash+" fi -[ ! -z "$git_hash" ] && cmake_flags_extra="$cmake_flags_extra -D \"EMU_GIT_HASH=$git_hash\"" +[ -n "$git_hash" ] && cmake_flags_extra="$cmake_flags_extra -D \"EMU_GIT_HASH=$git_hash\"" # Add copyright year. year=$(date +%Y) -[ ! -z "$year" ] && cmake_flags_extra="$cmake_flags_extra -D \"EMU_COPYRIGHT_YEAR=$year\"" +[ -n "$year" ] && cmake_flags_extra="$cmake_flags_extra -D \"EMU_COPYRIGHT_YEAR=$year\"" # Run CMake. echo [-] Running CMake with flags [$cmake_flags $cmake_flags_extra] @@ -432,19 +530,42 @@ then else cwd_root=$(pwd) - # Build openal-soft 1.21.1 manually to fix audio issues. This is a temporary - # workaround until a newer version of openal-soft trickles down to Debian repos. - if [ -d "openal-soft-1.21.1" ] + if grep -q "OPENAL:BOOL=ON" build/CMakeCache.txt then - rm -rf openal-soft-1.21.1/build/* + # Build openal-soft 1.21.1 manually to fix audio issues. This is a temporary + # workaround until a newer version of openal-soft trickles down to Debian repos. + if [ -d "openal-soft-1.21.1" ] + then + rm -rf openal-soft-1.21.1/build/* + else + wget -qO - https://github.com/kcat/openal-soft/archive/refs/tags/1.21.1.tar.gz | tar zxf - + fi + cd openal-soft-1.21.1/build + [ -e Makefile ] && make clean + cmake -G "Unix Makefiles" -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" .. + make -j$(nproc) install || exit 99 + cd "$cwd_root" + + # Build SDL2 without sound systems. + sdl_ss=OFF else - wget -qO - https://github.com/kcat/openal-soft/archive/refs/tags/1.21.1.tar.gz | tar zxf - + # Build FAudio 22.03 manually to remove the dependency on GStreamer. This is a temporary + # workaround until a newer version of FAudio trickles down to Debian repos. + if [ -d "FAudio-22.03" ] + then + rm -rf FAudio-22.03/build + else + wget -qO - https://github.com/FNA-XNA/FAudio/archive/refs/tags/22.03.tar.gz | tar zxf - + fi + mkdir FAudio-22.03/build + cd FAudio-22.03/build + cmake -G "Unix Makefiles" -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" .. + make -j$(nproc) install || exit 99 + cd "$cwd_root" + + # Build SDL2 with sound systems. + sdl_ss=ON fi - cd openal-soft-1.21.1/build - [ -e Makefile ] && make clean - cmake -G "Unix Makefiles" -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" .. - make -j$(nproc) install || exit 99 - cd "$cwd_root" # Build rtmidi without JACK support to remove the dependency on libjack. if [ -d "rtmidi-4.0.0" ] @@ -468,10 +589,10 @@ else rm -rf sdlbuild mkdir sdlbuild cd sdlbuild - cmake -G "Unix Makefiles" -D SDL_DISKAUDIO=OFF -D SDL_DIRECTFB_SHARED=OFF -D SDL_OPENGL=OFF -D SDL_OPENGLES=OFF -D SDL_OSS=OFF -D SDL_ALSA=OFF \ - -D SDL_ALSA_SHARED=OFF -D SDL_JACK=OFF -D SDL_JACK_SHARED=OFF -D SDL_ESD=OFF -D SDL_ESD_SHARED=OFF -D SDL_PIPEWIRE=OFF -D SDL_PIPEWIRE_SHARED=OFF \ - -D SDL_PULSEAUDIO=OFF -D SDL_PULSEAUDIO_SHARED=OFF -D SDL_ARTS=OFF -D SDL_ARTS_SHARED=OFF -D SDL_NAS=OFF -D SDL_NAS_SHARED=OFF -D SDL_SNDIO=OFF \ - -D SDL_SNDIO_SHARED=OFF -D SDL_FUSIONSOUND=OFF -D SDL_FUSIONSOUND_SHARED=OFF -D SDL_LIBSAMPLERATE=OFF -D SDL_LIBSAMPLERATE_SHARED=OFF -D SDL_X11=OFF \ + cmake -G "Unix Makefiles" -D SDL_DISKAUDIO=OFF -D SDL_DIRECTFB_SHARED=OFF -D SDL_OPENGL=OFF -D SDL_OPENGLES=OFF -D SDL_OSS=OFF -D SDL_ALSA=$sdl_ss \ + -D SDL_ALSA_SHARED=$sdl_ss -D SDL_JACK=$sdl_ss -D SDL_JACK_SHARED=$sdl_ss -D SDL_ESD=OFF -D SDL_ESD_SHARED=OFF -D SDL_PIPEWIRE=$sdl_ss -D SDL_PIPEWIRE_SHARED=$sdl_ss \ + -D SDL_PULSEAUDIO=$sdl_ss -D SDL_PULSEAUDIO_SHARED=$sdl_ss -D SDL_ARTS=OFF -D SDL_ARTS_SHARED=OFF -D SDL_NAS=$sdl_ss -D SDL_NAS_SHARED=$sdl_ss -D SDL_SNDIO=$sdl_ss \ + -D SDL_SNDIO_SHARED=$sdl_ss -D SDL_FUSIONSOUND=OFF -D SDL_FUSIONSOUND_SHARED=OFF -D SDL_LIBSAMPLERATE=$sdl_ss -D SDL_LIBSAMPLERATE_SHARED=$sdl_ss -D SDL_X11=OFF \ -D SDL_X11_SHARED=OFF -D SDL_WAYLAND=OFF -D SDL_WAYLAND_SHARED=OFF -D SDL_WAYLAND_LIBDECOR=OFF -D SDL_WAYLAND_LIBDECOR_SHARED=OFF \ -D SDL_WAYLAND_QT_TOUCH=OFF -D SDL_RPI=OFF -D SDL_VIVANTE=OFF -D SDL_VULKAN=OFF -D SDL_KMSDRM=OFF -D SDL_KMSDRM_SHARED=OFF -D SDL_OFFSCREEN=OFF \ -D SDL_HIDAPI_JOYSTICK=ON -D SDL_VIRTUAL_JOYSTICK=ON -D SDL_SHARED=ON -D SDL_STATIC=OFF -S "$cwd_root/SDL2-2.0.20" \ @@ -544,7 +665,7 @@ else project_version=$(grep -oP '#define\s+EMU_VERSION\s+"\K([^"]+)' "build/src/include/$project_lower/version.h" 2> /dev/null) [ -z "$project_version" ] && project_version=unknown build_num=$(grep -oP '#define\s+EMU_BUILD_NUM\s+\K([0-9]+)' "build/src/include/$project_lower/version.h" 2> /dev/null) - [ ! -z "$build_num" -a "$build_num" != "0" ] && project_version="$project_version-b$build_num" + [ -n "$build_num" -a "$build_num" != "0" ] && project_version="$project_version-b$build_num" # Download appimage-builder if necessary. [ ! -e "appimage-builder.AppImage" ] && wget -qO appimage-builder.AppImage \ diff --git a/.ci/dependencies_msys.txt b/.ci/dependencies_msys.txt new file mode 100644 index 000000000..99b278fb3 --- /dev/null +++ b/.ci/dependencies_msys.txt @@ -0,0 +1,24 @@ +zlib 1.2.11-9 +binutils 2.37-4 +headers-git 9.0.0.6357.eac8c38c1-1 +crt-git 9.0.0.6357.eac8c38c1-2 +libwinpthread-git 9.0.0.6357.eac8c38c1-1 +winpthreads-git 9.0.0.6357.eac8c38c1-1 +winstorecompat-git 9.0.0.6357.eac8c38c1-1 +gcc-libs 11.2.0-4 +gcc-ada 11.2.0-4 +gcc-fortran 11.2.0-4 +gcc-libgfortran 11.2.0-4 +gcc-objc 11.2.0-4 +gcc 11.2.0-4 +libgccjit 11.2.0-4 +tools-git 9.0.0.6357.eac8c38c1-1 +make 4.3-1 +pkgconf 1.8.0-2 +openal 1.21.1-3 +libpng 1.6.37-6 +freetype 2.11.1-1 +SDL2 2.0.18-2 +rtmidi 4.0.0-1 +cmake 3.22.1-1 +qt5-static 5.15.2-4 diff --git a/.gitignore b/.gitignore index f54d57c5b..a38da5deb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ Makefile # Build scripts /archive_tmp /static2dll.* +/pacman.txt +/deps.txt /VERSION *.zip *.tar diff --git a/CMakeLists.txt b/CMakeLists.txt index 776af5fc9..1c7d22365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,8 +131,9 @@ option(DINPUT "DirectInput" option(CPPTHREADS "C++11 threads" ON) option(NEW_DYNAREC "Use the PCem v15 (\"new\") dynamic recompiler" OFF) option(MINITRACE "Enable Chrome tracing using the modified minitrace library" OFF) +option(GDBSTUB "Enable GDB stub server for debugging" OFF) option(DEV_BRANCH "Development branch" OFF) -option(QT "QT GUI" ON) +option(QT "Qt GUI" ON) # Development branch features # diff --git a/src/86box.c b/src/86box.c index 344dc8294..db0182d84 100644 --- a/src/86box.c +++ b/src/86box.c @@ -93,6 +93,7 @@ #include <86box/ui.h> #include <86box/plat.h> #include <86box/version.h> +#include <86box/gdbstub.h> /* Stuff that used to be globally declared in plat.h but is now extern there @@ -736,6 +737,8 @@ usage: if (lang_init) lang_id = lang_init; + gdbstub_init(); + /* All good! */ return(1); } @@ -1175,6 +1178,8 @@ pc_close(thread_t *ptr) mo_close(); scsi_disk_close(); + + gdbstub_close(); } @@ -1203,6 +1208,9 @@ pc_run(void) /* Run a block of code. */ startblit(); cpu_exec(cpu_s->rspeed / 100); +#ifdef USE_GDBSTUB /* avoid a KBC FIFO overflow when CPU emulation is stalled */ + if (gdbstub_step == GDBSTUB_EXEC) +#endif mouse_process(); joystick_process(); endblit(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d09fd551c..06cebf82b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,11 @@ if(CPPTHREADS) target_sources(86Box PRIVATE thread.cpp) endif() +if(GDBSTUB) + add_compile_definitions(USE_GDBSTUB) + target_sources(86Box PRIVATE gdbstub.c) +endif() + if(NEW_DYNAREC) add_compile_definitions(USE_NEW_DYNAREC) endif() diff --git a/src/codegen_new/codegen_ops.c b/src/codegen_new/codegen_ops.c index a1570fc31..d59e3f1b3 100644 --- a/src/codegen_new/codegen_ops.c +++ b/src/codegen_new/codegen_ops.c @@ -85,8 +85,13 @@ RecompOpFn recomp_opcodes_0f[512] = /*40*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /*50*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#if defined __ARM_EABI__ || defined _ARM_ || defined _M_ARM || defined __aarch64__ || defined _M_ARM64 +/*60*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +/*70*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#else /*60*/ ropPUNPCKLBW, ropPUNPCKLWD, ropPUNPCKLDQ, ropPACKSSWB, ropPCMPGTB, ropPCMPGTW, ropPCMPGTD, ropPACKUSWB, ropPUNPCKHBW, ropPUNPCKHWD, ropPUNPCKHDQ, ropPACKSSDW, NULL, NULL, ropMOVD_r_d, ropMOVQ_r_q, /*70*/ NULL, ropPSxxW_imm, ropPSxxD_imm, ropPSxxQ_imm, ropPCMPEQB, ropPCMPEQW, ropPCMPEQD, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ropMOVD_d_r, ropMOVQ_q_r, +#endif /*80*/ ropJO_16, ropJNO_16, ropJB_16, ropJNB_16, ropJE_16, ropJNE_16, ropJBE_16, ropJNBE_16, ropJS_16, ropJNS_16, ropJP_16, ropJNP_16, ropJL_16, ropJNL_16, ropJLE_16, ropJNLE_16, /*90*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -107,8 +112,13 @@ RecompOpFn recomp_opcodes_0f[512] = /*40*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /*50*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#if defined __ARM_EABI__ || defined _ARM_ || defined _M_ARM || defined __aarch64__ || defined _M_ARM64 +/*60*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +/*70*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#else /*60*/ ropPUNPCKLBW, ropPUNPCKLWD, ropPUNPCKLDQ, ropPACKSSWB, ropPCMPGTB, ropPCMPGTW, ropPCMPGTD, ropPACKUSWB, ropPUNPCKHBW, ropPUNPCKHWD, ropPUNPCKHDQ, ropPACKSSDW, NULL, NULL, ropMOVD_r_d, ropMOVQ_r_q, /*70*/ NULL, ropPSxxW_imm, ropPSxxD_imm, ropPSxxQ_imm, ropPCMPEQB, ropPCMPEQW, ropPCMPEQD, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ropMOVD_d_r, ropMOVQ_q_r, +#endif /*80*/ ropJO_32, ropJNO_32, ropJB_32, ropJNB_32, ropJE_32, ropJNE_32, ropJBE_32, ropJNBE_32, ropJS_32, ropJNS_32, ropJP_32, ropJNP_32, ropJL_32, ropJNL_32, ropJLE_32, ropJNLE_32, /*90*/ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, diff --git a/src/cpu/386.c b/src/cpu/386.c index 6af8173eb..2484cbb66 100644 --- a/src/cpu/386.c +++ b/src/cpu/386.c @@ -22,6 +22,7 @@ #include <86box/fdd.h> #include <86box/fdc.h> #include <86box/machine.h> +#include <86box/gdbstub.h> #include "386_common.h" #ifdef USE_NEW_DYNAREC #include "codegen.h" @@ -256,6 +257,11 @@ exec386(int cycs) if (TIMER_VAL_LESS_THAN_VAL(timer_target, (uint32_t) tsc)) timer_process_inline(); + +#ifdef USE_GDBSTUB + if (gdbstub_instruction()) + return; +#endif } } } diff --git a/src/cpu/386_dynarec.c b/src/cpu/386_dynarec.c index 6e70c03ea..e9a187216 100644 --- a/src/cpu/386_dynarec.c +++ b/src/cpu/386_dynarec.c @@ -26,6 +26,7 @@ #include <86box/fdd.h> #include <86box/fdc.h> #include <86box/machine.h> +#include <86box/gdbstub.h> #ifdef USE_DYNAREC #include "codegen.h" #ifdef USE_NEW_DYNAREC @@ -858,6 +859,11 @@ exec386_dynarec(int cycs) if (TIMER_VAL_LESS_THAN_VAL(timer_target, (uint32_t) tsc)) timer_process_inline(); } + +#ifdef USE_GDBSTUB + if (gdbstub_instruction()) + return; +#endif } cycles_main -= (cycles_start - cycles); diff --git a/src/cpu/386_dynarec_ops.c b/src/cpu/386_dynarec_ops.c index 2eb3a5f00..28fd3ec79 100644 --- a/src/cpu/386_dynarec_ops.c +++ b/src/cpu/386_dynarec_ops.c @@ -19,6 +19,7 @@ #include <86box/mem.h> #include <86box/nmi.h> #include <86box/pic.h> +#include <86box/gdbstub.h> #include "codegen.h" #define CPU_BLOCK_END() cpu_block_end = 1 diff --git a/src/cpu/808x.c b/src/cpu/808x.c index 239656361..a762fdae0 100644 --- a/src/cpu/808x.c +++ b/src/cpu/808x.c @@ -34,6 +34,7 @@ #include <86box/pic.h> #include <86box/ppi.h> #include <86box/timer.h> +#include <86box/gdbstub.h> /* Is the CPU 8088 or 8086. */ int is8086 = 0; @@ -2833,5 +2834,10 @@ execx86(int cycs) cpu_alu_op = 0; } + +#ifdef USE_GDBSTUB + if (gdbstub_instruction()) + return; +#endif } } diff --git a/src/cpu/cpu.c b/src/cpu/cpu.c index 380fb8a82..fa824c1e8 100644 --- a/src/cpu/cpu.c +++ b/src/cpu/cpu.c @@ -36,6 +36,7 @@ #include <86box/nmi.h> #include <86box/pic.h> #include <86box/pci.h> +#include <86box/gdbstub.h> #ifdef USE_DYNAREC # include "codegen.h" #endif @@ -1395,6 +1396,7 @@ cpu_set(void) cpu_exec = exec386; else cpu_exec = execx86; + gdbstub_cpu_init(); } diff --git a/src/cpu/x86_ops_int.h b/src/cpu/x86_ops_int.h index f35e526a6..0074aec29 100644 --- a/src/cpu/x86_ops_int.h +++ b/src/cpu/x86_ops_int.h @@ -1,6 +1,10 @@ static int opINT3(uint32_t fetchdat) { int cycles_old = cycles; UN_USED(cycles_old); +#ifdef USE_GDBSTUB + if (gdbstub_int3()) + return 1; +#endif if ((cr0 & 1) && (cpu_state.eflags & VM_FLAG) && (IOPL != 3)) { x86gpf(NULL,0); diff --git a/src/cpu/x87_ops.h b/src/cpu/x87_ops.h index 2f8927060..c41074a2c 100644 --- a/src/cpu/x87_ops.h +++ b/src/cpu/x87_ops.h @@ -239,89 +239,25 @@ static __inline int64_t x87_fround(double b) return 0LL; } -#define BIAS80 16383 -#define BIAS64 1023 + +#include "x87_ops_conv.h" static __inline double x87_ld80() { - int64_t exp64; - int64_t blah; - int64_t exp64final; - int64_t mant64; - int64_t sign; - struct { - int16_t begin; - union - { - double d; - uint64_t ll; - } eind; - } test; - test.eind.ll = readmeml(easeg,cpu_state.eaaddr); - test.eind.ll |= (uint64_t)readmeml(easeg,cpu_state.eaaddr+4)<<32; - test.begin = readmemw(easeg,cpu_state.eaaddr+8); - - exp64 = (((test.begin&0x7fff) - BIAS80)); - blah = ((exp64 >0)?exp64:-exp64)&0x3ff; - exp64final = ((exp64 >0)?blah:-blah) +BIAS64; - - mant64 = (test.eind.ll >> 11) & (0xfffffffffffffll); - sign = (test.begin&0x8000)?1:0; - - if ((test.begin & 0x7fff) == 0x7fff) - exp64final = 0x7ff; - if ((test.begin & 0x7fff) == 0) - exp64final = 0; - if (test.eind.ll & 0x400) - mant64++; - - test.eind.ll = (sign <<63)|(exp64final << 52)| mant64; - - return test.eind.d; + x87_conv_t test; + test.eind.ll = readmeml(easeg,cpu_state.eaaddr); + test.eind.ll |= (uint64_t)readmeml(easeg,cpu_state.eaaddr+4)<<32; + test.begin = readmemw(easeg,cpu_state.eaaddr+8); + return x87_from80(&test); } static __inline void x87_st80(double d) { - int64_t sign80; - int64_t exp80; - int64_t exp80final; - int64_t mant80; - int64_t mant80final; - - struct { - int16_t begin; - union - { - double d; - uint64_t ll; - } eind; - } test; - - test.eind.d=d; - - sign80 = (test.eind.ll&(0x8000000000000000ll))?1:0; - exp80 = test.eind.ll&(0x7ff0000000000000ll); - exp80final = (exp80>>52); - mant80 = test.eind.ll&(0x000fffffffffffffll); - mant80final = (mant80 << 11); - - if (exp80final == 0x7ff) /*Infinity / Nan*/ - { - exp80final = 0x7fff; - mant80final |= (0x8000000000000000ll); - } - else if (d != 0){ /* Zero is a special case */ - /* Elvira wants the 8 and tcalc doesn't */ - mant80final |= (0x8000000000000000ll); - /* Ca-cyber doesn't like this when result is zero. */ - exp80final += (BIAS80 - BIAS64); - } - test.begin = (((int16_t)sign80)<<15)| (int16_t)exp80final; - test.eind.ll = mant80final; - - writememl(easeg,cpu_state.eaaddr,test.eind.ll & 0xffffffff); - writememl(easeg,cpu_state.eaaddr+4,test.eind.ll>>32); - writememw(easeg,cpu_state.eaaddr+8,test.begin); + x87_conv_t test; + x87_to80(d, &test); + writememl(easeg,cpu_state.eaaddr,test.eind.ll & 0xffffffff); + writememl(easeg,cpu_state.eaaddr+4,test.eind.ll>>32); + writememw(easeg,cpu_state.eaaddr+8,test.begin); } static __inline void x87_st_fsave(int reg) diff --git a/src/cpu/x87_ops_conv.h b/src/cpu/x87_ops_conv.h new file mode 100644 index 000000000..44304c1bc --- /dev/null +++ b/src/cpu/x87_ops_conv.h @@ -0,0 +1,68 @@ +#define BIAS80 16383 +#define BIAS64 1023 + +typedef struct { + int16_t begin; + union { + double d; + uint64_t ll; + } eind; +} x87_conv_t; + +static __inline double x87_from80(x87_conv_t *test) +{ + int64_t exp64; + int64_t blah; + int64_t exp64final; + int64_t mant64; + int64_t sign; + + exp64 = (((test->begin&0x7fff) - BIAS80)); + blah = ((exp64 >0)?exp64:-exp64)&0x3ff; + exp64final = ((exp64 >0)?blah:-blah) +BIAS64; + + mant64 = (test->eind.ll >> 11) & (0xfffffffffffffll); + sign = (test->begin&0x8000)?1:0; + + if ((test->begin & 0x7fff) == 0x7fff) + exp64final = 0x7ff; + if ((test->begin & 0x7fff) == 0) + exp64final = 0; + if (test->eind.ll & 0x400) + mant64++; + + test->eind.ll = (sign <<63)|(exp64final << 52)| mant64; + + return test->eind.d; +} + +static __inline void x87_to80(double d, x87_conv_t *test) +{ + int64_t sign80; + int64_t exp80; + int64_t exp80final; + int64_t mant80; + int64_t mant80final; + + test->eind.d=d; + + sign80 = (test->eind.ll&(0x8000000000000000ll))?1:0; + exp80 = test->eind.ll&(0x7ff0000000000000ll); + exp80final = (exp80>>52); + mant80 = test->eind.ll&(0x000fffffffffffffll); + mant80final = (mant80 << 11); + + if (exp80final == 0x7ff) /*Infinity / Nan*/ + { + exp80final = 0x7fff; + mant80final |= (0x8000000000000000ll); + } + else if (d != 0){ /* Zero is a special case */ + /* Elvira wants the 8 and tcalc doesn't */ + mant80final |= (0x8000000000000000ll); + /* Ca-cyber doesn't like this when result is zero. */ + exp80final += (BIAS80 - BIAS64); + } + test->begin = (((int16_t)sign80)<<15)| (int16_t)exp80final; + test->eind.ll = mant80final; +} diff --git a/src/dma.c b/src/dma.c index 876c34356..a995660a5 100644 --- a/src/dma.c +++ b/src/dma.c @@ -35,11 +35,11 @@ dma_t dma[8]; uint8_t dma_e; +uint8_t dma_m; static uint8_t dmaregs[3][16]; static int dma_wp[2]; -static uint8_t dma_m; static uint8_t dma_stat; static uint8_t dma_stat_rq; static uint8_t dma_stat_rq_pc; diff --git a/src/gdbstub.c b/src/gdbstub.c new file mode 100644 index 000000000..20103c4cd --- /dev/null +++ b/src/gdbstub.c @@ -0,0 +1,1699 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * GDB stub server for remote debugging. + * + * + * + * Authors: RichardG, + * + * Copyright 2022 RichardG. + */ +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +# include +# include +#else +# include +# include +#endif +#define HAVE_STDARG_H +#include <86box/86box.h> +#include "cpu.h" +#include "x86seg.h" +#include "x87.h" +#include "x87_ops_conv.h" +#include <86box/io.h> +#include <86box/mem.h> +#include <86box/plat.h> +#include <86box/gdbstub.h> + +#define FAST_RESPONSE(s) \ + strcpy(client->response, s); \ + client->response_pos = sizeof(s); +#define FAST_RESPONSE_HEX(s) gdbstub_client_respond_hex(client, (uint8_t *) s, sizeof(s)); + +enum { + GDB_SIGINT = 2, + GDB_SIGTRAP = 5 +}; + +enum { + GDB_REG_EAX = 0, + GDB_REG_ECX, + GDB_REG_EDX, + GDB_REG_EBX, + GDB_REG_ESP, + GDB_REG_EBP, + GDB_REG_ESI, + GDB_REG_EDI, + GDB_REG_EIP, + GDB_REG_EFLAGS, + GDB_REG_CS, + GDB_REG_SS, + GDB_REG_DS, + GDB_REG_ES, + GDB_REG_FS, + GDB_REG_GS, + GDB_REG_FS_BASE, + GDB_REG_GS_BASE, + GDB_REG_CR0, + GDB_REG_CR2, + GDB_REG_CR3, + GDB_REG_CR4, + GDB_REG_EFER, + GDB_REG_ST0, + GDB_REG_ST1, + GDB_REG_ST2, + GDB_REG_ST3, + GDB_REG_ST4, + GDB_REG_ST5, + GDB_REG_ST6, + GDB_REG_ST7, + GDB_REG_FCTRL, + GDB_REG_FSTAT, + GDB_REG_FTAG, + GDB_REG_FISEG, + GDB_REG_FIOFF, + GDB_REG_FOSEG, + GDB_REG_FOOFF, + GDB_REG_FOP, + GDB_REG_MM0, + GDB_REG_MM1, + GDB_REG_MM2, + GDB_REG_MM3, + GDB_REG_MM4, + GDB_REG_MM5, + GDB_REG_MM6, + GDB_REG_MM7, + GDB_REG_MAX +}; + +enum { + GDB_MODE_BASE10 = 0, + GDB_MODE_HEX, + GDB_MODE_OCT, + GDB_MODE_BIN +}; + +typedef struct _gdbstub_client_ { + int socket; + struct sockaddr_in addr; + + char packet[16384], response[16384]; + int has_packet, waiting_stop, packet_pos, response_pos; + + event_t *processed_event, *response_event; + + uint16_t last_io_base, last_io_len; + + struct _gdbstub_client_ *next; +} gdbstub_client_t; + +typedef struct _gdbstub_breakpoint_ { + uint32_t addr; + union { + uint8_t orig_val; + uint32_t end; + }; + + struct _gdbstub_breakpoint_ *next; +} gdbstub_breakpoint_t; + +#define ENABLE_GDBSTUB_LOG 1 +#ifdef ENABLE_GDBSTUB_LOG +int gdbstub_do_log = ENABLE_GDBSTUB_LOG; + +static void +gdbstub_log(const char *fmt, ...) +{ + va_list ap; + + if (gdbstub_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define gdbstub_log(fmt, ...) +#endif + +static x86seg *segment_regs[] = { &cpu_state.seg_cs, &cpu_state.seg_ss, &cpu_state.seg_ds, &cpu_state.seg_es, &cpu_state.seg_fs, &cpu_state.seg_gs }; +static uint32_t *cr_regs[] = { &cpu_state.CR0.l, &cr2, &cr3, &cr4 }; +static void *fpu_regs[] = { &cpu_state.npxc, &cpu_state.npxs, NULL, &x87_pc_seg, &x87_pc_off, &x87_op_seg, &x87_op_off }; +static const char target_xml[] = /* based on qemu's i386-32bit.xml */ + // clang-format off + "" + "" + "" + "i8086" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; +// clang-format on + +#ifdef _WIN32 +static WSADATA wsa; +#endif +static int gdbstub_socket = -1, stop_reason_len = 0, in_gdbstub = 0; +static uint32_t watch_addr; +static char stop_reason[2048]; + +static gdbstub_client_t *first_client = NULL, *last_client = NULL; +static mutex_t *client_list_mutex; + +static void (*cpu_exec_shadow)(int cycs); +static gdbstub_breakpoint_t *first_swbreak = NULL, *first_hwbreak = NULL, + *first_rwatch = NULL, *first_wwatch = NULL, *first_awatch = NULL; + +int gdbstub_step = 0, gdbstub_next_asap = 0; +uint64_t gdbstub_watch_pages[(((uint32_t) -1) >> (MEM_GRANULARITY_BITS + 6)) + 1]; + +static void +gdbstub_break() +{ + /* Pause CPU execution as soon as possible. */ + if (gdbstub_step <= GDBSTUB_EXEC) + gdbstub_step = GDBSTUB_BREAK; +} + +static void +gdbstub_jump(uint32_t new_pc) +{ + /* Nasty hack; qemu always uses the full 32-bit EIP internally... */ + if (cpu_state.op32 || ((new_pc >= cs) && (new_pc < (cs + 65536)))) { + cpu_state.pc = new_pc - cs; + } else { + loadseg((new_pc >> 4) & 0xf000, &cpu_state.seg_cs); + cpu_state.pc = new_pc & 0xffff; + } + flushmmucache(); +} + +static inline int +gdbstub_hex_decode(int c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + else if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + else if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + else + return 0; +} + +static inline int +gdbstub_hex_encode(int c) +{ + if (c < 10) + return c + '0'; + else + return c - 10 + 'A'; +} + +static int +gdbstub_num_decode(char *p, int *dest, int mode) +{ + /* Stop if the pointer is invalid. */ + if (!p) + return 0; + + /* Read sign. */ + int sign = 1; + if ((p[0] == '+') || (p[0] == '-')) { + if (p[0] == '-') + sign = -1; + p++; + } + + /* Read type identifer if present (0x/0o/0b/0n) */ + if (p[0] == '0') { + switch (p[1]) { + case 'x': + mode = GDB_MODE_HEX; + break; + + case '0' ... '7': + p -= 1; + /* fall-through */ + + case 'o': + mode = GDB_MODE_OCT; + break; + + case 'b': + mode = GDB_MODE_BIN; + break; + + case 'n': + mode = GDB_MODE_BASE10; + break; + + default: + p -= 2; + break; + } + p += 2; + } + + /* Parse each character. */ + *dest = 0; + while (*p) { + switch (mode) { + case GDB_MODE_BASE10: + if ((*p >= '0') && (*p <= '9')) + *dest = ((*dest) * 10) + ((*p) - '0'); + else + return 0; + break; + + case GDB_MODE_HEX: + if (((*p >= '0') && (*p <= '9')) || ((*p >= 'A') && (*p <= 'F')) || ((*p >= 'a') && (*p <= 'f'))) + *dest = ((*dest) << 4) | gdbstub_hex_decode(*p); + else + return 0; + break; + + case GDB_MODE_OCT: + if ((*p >= '0') && (*p <= '7')) + *dest = ((*dest) << 3) | ((*p) - '0'); + else + return 0; + break; + + case GDB_MODE_BIN: + if ((*p == '0') || (*p == '1')) + *dest = ((*dest) << 1) | ((*p) - '0'); + else + return 0; + break; + } + p++; + } + + /* Apply sign. */ + if (sign < 0) + *dest = -(*dest); + + /* Return success. */ + return 1; +} + +static int +gdbstub_client_read_word(gdbstub_client_t *client, int *dest) +{ + char *p = &client->packet[client->packet_pos], *q = p; + while (((*p >= '0') && (*p <= '9')) || ((*p >= 'A') && (*p <= 'F')) || ((*p >= 'a') && (*p <= 'f'))) + *dest = ((*dest) << 4) | gdbstub_hex_decode(*p++); + return p - q; +} + +static int +gdbstub_client_read_hex(gdbstub_client_t *client, uint8_t *buf, int size) +{ + int pp = client->packet_pos; + while (size-- && (pp < (sizeof(client->packet) - 2))) { + *buf = gdbstub_hex_decode(client->packet[pp++]) << 4; + *buf++ |= gdbstub_hex_decode(client->packet[pp++]); + } + return pp - client->packet_pos; +} + +static int +gdbstub_client_read_string(gdbstub_client_t *client, char *buf, int size, char terminator) +{ + int pp = client->packet_pos; + char c; + while (size-- && (pp < (sizeof(client->packet) - 1))) { + c = client->packet[pp]; + if ((c == terminator) || (c == '\0')) { + *buf = '\0'; + break; + } + pp++; + *buf++ = c; + } + return pp - client->packet_pos; +} + +static int +gdbstub_client_write_reg(int index, uint8_t *buf) +{ + int width = 4; + switch (index) { + case GDB_REG_EAX ... GDB_REG_EDI: + cpu_state.regs[index - GDB_REG_EAX].l = *((uint32_t *) buf); + break; + + case GDB_REG_EIP: + gdbstub_jump(*((uint32_t *) buf)); + break; + + case GDB_REG_EFLAGS: + cpu_state.flags = *((uint16_t *) &buf[0]); + cpu_state.eflags = *((uint16_t *) &buf[2]); + break; + + case GDB_REG_CS ... GDB_REG_GS: + width = 2; + loadseg(*((uint16_t *) buf), segment_regs[index - GDB_REG_CS]); + flushmmucache(); + break; + + case GDB_REG_FS_BASE ... GDB_REG_GS_BASE: + /* Do what qemu does and just load the base. */ + segment_regs[(index - 16) + (GDB_REG_FS - GDB_REG_CS)]->base = *((uint32_t *) buf); + break; + + case GDB_REG_CR0 ... GDB_REG_CR4: + *cr_regs[index - GDB_REG_CR0] = *((uint32_t *) buf); + flushmmucache(); + break; + + case GDB_REG_EFER: + msr.amd_efer = *((uint64_t *) buf); + break; + + case GDB_REG_ST0 ... GDB_REG_ST7: + width = 10; + x87_conv_t conv = { + .eind = { .ll = *((uint64_t *) &buf[0]) }, + .begin = *((uint16_t *) &buf[8]) + }; + cpu_state.ST[(cpu_state.TOP + (index - GDB_REG_ST0)) & 7] = x87_from80(&conv); + break; + + case GDB_REG_FCTRL: + case GDB_REG_FISEG: + case GDB_REG_FOSEG: + width = 2; + *((uint16_t *) fpu_regs[index - GDB_REG_FCTRL]) = *((uint16_t *) buf); + if (index >= GDB_REG_FISEG) + flushmmucache(); + break; + + case GDB_REG_FSTAT: + case GDB_REG_FOP: + width = 2; + break; + + case GDB_REG_FTAG: + width = 2; + x87_settag(*((uint16_t *) buf)); + break; + + case GDB_REG_FIOFF: + case GDB_REG_FOOFF: + *((uint32_t *) fpu_regs[index - GDB_REG_FCTRL]) = *((uint32_t *) buf); + break; + + case GDB_REG_MM0 ... GDB_REG_MM7: + width = 8; + cpu_state.MM[index - GDB_REG_MM0].q = *((uint64_t *) &buf); + break; + + default: + width = 0; + } + + return width; +} + +static void +gdbstub_client_respond(gdbstub_client_t *client) +{ + /* Calculate checksum. */ + int checksum = 0, i; + for (i = 0; i < client->response_pos; i++) + checksum += client->response[i]; + + /* Send response packet. */ + client->response[client->response_pos] = '\0'; +#ifdef ENABLE_GDBSTUB_LOG + i = client->response[995]; /* pclog_ex buffer too small */ + client->response[995] = '\0'; + gdbstub_log("GDB Stub: Sending response: %s\n", client->response); + client->response[995] = i; +#endif + send(client->socket, "$", 1, 0); + send(client->socket, client->response, client->response_pos, 0); + char response_cksum[3] = { '#', gdbstub_hex_encode((checksum >> 4) & 0x0f), gdbstub_hex_encode(checksum & 0x0f) }; + send(client->socket, response_cksum, sizeof(response_cksum), 0); +} + +static void +gdbstub_client_respond_partial(gdbstub_client_t *client) +{ + /* Send response. */ + gdbstub_client_respond(client); + + /* Wait for the response to be acknowledged. */ + thread_wait_event(client->response_event, -1); + thread_reset_event(client->response_event); +} + +static void +gdbstub_client_respond_hex(gdbstub_client_t *client, uint8_t *buf, int size) +{ + while (size-- && (client->response_pos < (sizeof(client->response) - 2))) { + client->response[client->response_pos++] = gdbstub_hex_encode((*buf) >> 4); + client->response[client->response_pos++] = gdbstub_hex_encode((*buf++) & 0x0f); + } +} + +static int +gdbstub_client_read_reg(int index, uint8_t *buf) +{ + int width = 4; + switch (index) { + case GDB_REG_EAX ... GDB_REG_EDI: + *((uint32_t *) buf) = cpu_state.regs[index].l; + break; + + case GDB_REG_EIP: + *((uint32_t *) buf) = cs + cpu_state.pc; + break; + + case GDB_REG_EFLAGS: + *((uint16_t *) &buf[0]) = cpu_state.flags; + *((uint16_t *) &buf[2]) = cpu_state.eflags; + break; + + case GDB_REG_CS ... GDB_REG_GS: + *((uint16_t *) buf) = segment_regs[index - GDB_REG_CS]->seg; + break; + + case GDB_REG_FS_BASE ... GDB_REG_GS_BASE: + *((uint32_t *) buf) = segment_regs[(index - 16) + (GDB_REG_FS - GDB_REG_CS)]->base; + break; + + case GDB_REG_CR0 ... GDB_REG_CR4: + *((uint32_t *) buf) = *cr_regs[index - GDB_REG_CR0]; + break; + + case GDB_REG_EFER: + *((uint64_t *) buf) = msr.amd_efer; + break; + + case GDB_REG_ST0 ... GDB_REG_ST7: + width = 10; + x87_conv_t conv; + x87_to80(cpu_state.ST[(cpu_state.TOP + (index - GDB_REG_ST0)) & 7], &conv); + *((uint64_t *) &buf[0]) = conv.eind.ll; + *((uint16_t *) &buf[8]) = conv.begin; + break; + + case GDB_REG_FCTRL ... GDB_REG_FSTAT: + case GDB_REG_FISEG: + case GDB_REG_FOSEG: + width = 2; + *((uint16_t *) buf) = *((uint16_t *) fpu_regs[index - GDB_REG_FCTRL]); + break; + + case GDB_REG_FTAG: + width = 2; + *((uint16_t *) buf) = x87_gettag(); + break; + + case GDB_REG_FIOFF: + case GDB_REG_FOOFF: + *((uint32_t *) buf) = *((uint32_t *) fpu_regs[index - GDB_REG_FCTRL]); + break; + + case GDB_REG_FOP: + width = 2; + *((uint16_t *) buf) = 0; /* we don't store the FPU opcode */ + break; + + case GDB_REG_MM0 ... GDB_REG_MM7: + width = 8; + cpu_state.MM[index - GDB_REG_MM0].q = *((uint64_t *) &buf); + break; + + default: + width = 0; + } + + return width; +} + +static void +gdbstub_client_packet(gdbstub_client_t *client) +{ +#ifdef GDBSTUB_CHECK_CHECKSUM /* msys2 gdb 11.1 transmits qSupported and H with invalid checksum... */ + uint8_t rcv_checksum = 0, checksum = 0; +#endif + int i, j = 0, k = 0, l; + uint8_t buf[10] = { 0 }; + char *p; + + /* Validate checksum. */ + client->packet_pos -= 2; +#ifdef GDBSTUB_CHECK_CHECKSUM + gdbstub_client_read_hex(client, &rcv_checksum, 1); +#endif + *((uint16_t *) &client->packet[--client->packet_pos]) = 0; +#ifdef GDBSTUB_CHECK_CHECKSUM + for (i = 0; i < client->packet_pos; i++) + checksum += client->packet[i]; + + if (checksum != rcv_checksum) { + /* Send negative acknowledgement. */ +# ifdef ENABLE_GDBSTUB_LOG + i = client->packet[953]; /* pclog_ex buffer too small */ + client->packet[953] = '\0'; + gdbstub_log("GDB Stub: Received packet with invalid checksum (expected %02X got %02X): %s\n", checksum, rcv_checksum, client->packet); + client->packet[953] = i; +# endif + send(client->socket, "-", 1, 0); + return; + } +#endif + + /* Send positive acknowledgement. */ +#ifdef ENABLE_GDBSTUB_LOG + i = client->packet[996]; /* pclog_ex buffer too small */ + client->packet[996] = '\0'; + gdbstub_log("GDB Stub: Received packet: %s\n", client->packet); + client->packet[996] = i; +#endif + send(client->socket, "+", 1, 0); + + /* Block other responses from being written while this one (if any is produced) isn't acknowledged. */ + if ((client->packet[0] != 'c') && (client->packet[0] != 's')) { + thread_wait_event(client->response_event, -1); + thread_reset_event(client->response_event); + } + client->response_pos = 0; + client->packet_pos = 1; + + /* Parse command. */ + switch (client->packet[0]) { + case '?': /* stop reason */ + /* Respond with a stop reply packet if one is present. */ + if (stop_reason_len) { + strcpy(client->response, stop_reason); + client->response_pos = strlen(client->response); + } + break; + + case 'c': /* continue */ + case 's': /* step */ + /* Flag that the client is waiting for a stop reason. */ + client->waiting_stop = 1; + + /* Jump to address if specified. */ + if (client->packet[1] && gdbstub_client_read_word(client, &j)) + gdbstub_jump(j); + + /* Resume CPU. */ + gdbstub_step = gdbstub_next_asap = (client->packet[0] == 's') ? GDBSTUB_SSTEP : GDBSTUB_EXEC; + return; + + case 'D': /* detach */ + /* Resume emulation. */ + gdbstub_step = GDBSTUB_EXEC; + + /* Respond positively. */ +ok: + FAST_RESPONSE("OK"); + break; + + case 'g': /* read all registers */ + /* Output the values of all registers. */ + for (i = 0; i < GDB_REG_MAX; i++) + gdbstub_client_respond_hex(client, buf, gdbstub_client_read_reg(i, buf)); + break; + + case 'G': /* write all registers */ + /* Write the values of all registers. */ + for (i = 0; i < GDB_REG_MAX; i++) { + if (!gdbstub_client_read_hex(client, buf, sizeof(buf))) + break; + client->packet_pos += gdbstub_client_write_reg(i, buf); + } + break; + + case 'H': /* set thread */ + /* Read operation type and thread ID. */ + if ((client->packet[1] == '\0') || (client->packet[2] == '\0')) { +e22: + FAST_RESPONSE("E22"); + break; + } + + /* Respond positively only on thread 1. */ + if ((client->packet[2] == '1') && !client->packet[3]) + goto ok; + else + goto e22; + + case 'm': /* read memory */ + /* Read address and length. */ + if (!(i = gdbstub_client_read_word(client, &j))) + goto e22; + client->packet_pos += i + 1; + gdbstub_client_read_word(client, &k); + if (!k) + goto e22; + + /* Clamp length. */ + if (k >= (sizeof(client->response) >> 1)) + k = (sizeof(client->response) >> 1) - 1; + + /* Read by qwords, then by dwords, then by words, then by bytes. */ + i = 0; + if (is386) { + for (; i < (k & ~7); i += 8) { + *((uint64_t *) buf) = readmemql(j); + j += 8; + gdbstub_client_respond_hex(client, buf, 8); + } + for (; i < (k & ~3); i += 4) { + *((uint32_t *) buf) = readmemll(j); + j += 4; + gdbstub_client_respond_hex(client, buf, 4); + } + } + for (; i < (k & ~1); i += 2) { + *((uint16_t *) buf) = readmemwl(j); + j += 2; + gdbstub_client_respond_hex(client, buf, 2); + } + for (; i < k; i++) { + buf[0] = readmembl(j++); + gdbstub_client_respond_hex(client, buf, 1); + } + break; + + case 'M': /* write memory */ + case 'X': /* write memory binary */ + /* Read address and length. */ + if (!(i = gdbstub_client_read_word(client, &j))) + goto e22; + client->packet_pos += i + 1; + client->packet_pos += gdbstub_client_read_word(client, &k) + 1; + if (!k) + goto e22; + + /* Clamp length. */ + if (k >= ((sizeof(client->response) >> 1) - client->packet_pos)) + k = (sizeof(client->response) >> 1) - client->packet_pos - 1; + + /* Decode the data. */ + if (client->packet[0] == 'M') { /* hex encoded */ + gdbstub_client_read_hex(client, (uint8_t *) client->packet, k); + } else { /* binary encoded */ + i = 0; + while (i < k) { + if (client->packet[client->packet_pos] == '}') { + client->packet_pos++; + client->packet[i++] = client->packet[client->packet_pos++] ^ 0x20; + } else { + client->packet[i++] = client->packet[client->packet_pos++]; + } + } + } + + /* Write by qwords, then by dwords, then by words, then by bytes. */ + p = client->packet; + i = 0; + if (is386) { + for (; i < (k & ~7); i += 8) { + writememql(j, *((uint64_t *) p)); + j += 8; + p += 8; + } + for (; i < (k & ~3); i += 4) { + writememll(j, *((uint32_t *) p)); + j += 4; + p += 4; + } + } + for (; i < (k & ~1); i += 2) { + writememwl(j, *((uint16_t *) p)); + j += 2; + p += 2; + } + for (; i < k; i++) { + writemembl(j++, p[0]); + p++; + } + + /* Respond positively. */ + goto ok; + + case 'p': /* read register */ + /* Read register index. */ + if (!gdbstub_client_read_word(client, &j)) { +e14: + FAST_RESPONSE("E14"); + break; + } + + /* Read the register's value. */ + if (!(i = gdbstub_client_read_reg(j, buf))) + goto e14; + + /* Return value. */ + gdbstub_client_respond_hex(client, buf, i); + break; + + case 'P': /* write register */ + /* Read register index and value. */ + if (!(i = gdbstub_client_read_word(client, &j))) + goto e14; + client->packet_pos += i + 1; + if (!gdbstub_client_read_hex(client, buf, sizeof(buf))) + goto e14; + + /* Write the value to the register. */ + if (!gdbstub_client_write_reg(j, buf)) + goto e14; + + /* Respond positively. */ + goto ok; + + case 'q': /* query */ + /* Erase response, as we'll use it as a scratch buffer. */ + memset(client->response, 0, sizeof(client->response)); + + /* Read the query type. */ + client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, + (client->packet[1] == 'R') ? ',' : ':') + + 1; + + /* Perform the query. */ + if (!strcmp(client->response, "Supported")) { + /* Go through the feature list and negate ones we don't support. */ + while ((client->response_pos < (sizeof(client->response) - 1)) && (i = gdbstub_client_read_string(client, &client->response[client->response_pos], sizeof(client->response) - client->response_pos - 1, ';'))) { + client->packet_pos += i + 1; + if (strncmp(&client->response[client->response_pos], "PacketSize", 10) && strcmp(&client->response[client->response_pos], "swbreak") && strcmp(&client->response[client->response_pos], "hwbreak") && strncmp(&client->response[client->response_pos], "xmlRegisters", 12) && strcmp(&client->response[client->response_pos], "qXfer:features:read")) { + gdbstub_log("GDB Stub: Feature \"%s\" is not supported\n", &client->response[client->response_pos]); + client->response_pos += i; + client->response[client->response_pos++] = '-'; + client->response[client->response_pos++] = ';'; + } else { + gdbstub_log("GDB Stub: Feature \"%s\" is supported\n", &client->response[client->response_pos]); + } + } + + /* Add our supported features to the end. */ + if (client->response_pos < (sizeof(client->response) - 1)) + client->response_pos += snprintf(&client->response[client->response_pos], sizeof(client->response) - client->response_pos, + "PacketSize=%X;swbreak+;hwbreak+;qXfer:features:read+", sizeof(client->packet) - 1); + break; + } else if (!strcmp(client->response, "Xfer")) { + /* Read the transfer object. */ + client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, ':') + 1; + if (!strcmp(client->response, "features")) { + /* Read the transfer operation. */ + client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, ':') + 1; + if (!strcmp(client->response, "read")) { + /* Read the transfer annex. */ + client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, ':') + 1; + if (!strcmp(client->response, "target.xml")) + p = (char *) target_xml; + else + p = NULL; + + /* Stop if the file wasn't found. */ + if (!p) { +e00: + FAST_RESPONSE("E00"); + break; + } + + /* Read offset and length. */ + if (!(i = gdbstub_client_read_word(client, &j))) + goto e22; + client->packet_pos += i + 1; + client->packet_pos += gdbstub_client_read_word(client, &k) + 1; + if (!k) + goto e22; + + /* Check if the offset is valid. */ + l = strlen(p); + if (j > l) + goto e00; + p += j; + + /* Return the more/less flag while also clamping the length. */ + if (k >= ((sizeof(client->response) >> 1) - 2)) + k = (sizeof(client->response) >> 1) - 3; + if (k < (l - j)) { + client->response[client->response_pos++] = 'm'; + } else { + client->response[client->response_pos++] = 'l'; + k = l - j; + } + + /* Encode the data. */ + while (k--) { + i = *p++; + if ((i == '\0') || (i == '#') || (i == '$') || (i == '*') || (i == '}')) { + client->response[client->response_pos++] = '}'; + client->response[client->response_pos++] = i ^ 0x20; + } else { + client->response[client->response_pos++] = i; + } + } + break; + } + } + } else if (!strcmp(client->response, "Rcmd")) { + /* Read and decode command in-place. */ + i = gdbstub_client_read_hex(client, (uint8_t *) client->packet, strlen(client->packet) - client->packet_pos); + client->packet[i] = 0; + gdbstub_log("GDB Stub: Monitor command: %s\n", client->packet); + + /* Parse the command name. */ + char *strtok_save; + p = strtok_r(client->packet, " ", &strtok_save); + if (!p) + goto ok; + + /* Interpret the command. */ + if (p[0] == 'i') { + /* Read I/O operation width. */ + l = (p[1] == 'n') ? p[2] : p[1]; + + /* Read optional I/O port. */ + if (!(p = strtok_r(NULL, " ", &strtok_save)) || !gdbstub_num_decode(p, &j, GDB_MODE_HEX) || (j < 0) || (j >= 65536)) + j = client->last_io_base; + + /* Read optional length. */ + if (!(p = strtok_r(NULL, " ", &strtok_save)) || !gdbstub_num_decode(p, &k, GDB_MODE_BASE10)) + k = client->last_io_len; + + /* Clamp length. */ + if (k < 1) + k = 1; + if (k > (65536 - j)) + k = 65536 - j; + + /* Read ports. */ + i = 0; + while (i < k) { + if ((i % 16) == 0) { + if (i) { + client->packet[client->packet_pos++] = '\n'; + + /* Provide partial response with the last line. */ + client->response_pos = 0; + client->response[client->response_pos++] = 'O'; + gdbstub_client_respond_hex(client, (uint8_t *) client->packet, client->packet_pos); + gdbstub_client_respond_partial(client); + } + client->packet_pos = sprintf(client->packet, "%04X:", j + i); + } + /* Act according to I/O operation width. */ + switch (l) { + case 'd': + case 'l': + client->packet_pos += sprintf(&client->packet[client->packet_pos], " %08X", inl(j + i)); + i += 4; + break; + + case 'w': + client->packet_pos += sprintf(&client->packet[client->packet_pos], " %04X", inw(j + i)); + i += 2; + break; + + case 'b': + case '\0': + client->packet_pos += sprintf(&client->packet[client->packet_pos], " %02X", inb(j + i)); + i++; + break; + + default: + goto unknown; + } + } + client->packet[client->packet_pos++] = '\n'; + + /* Respond with the final line. */ + client->response_pos = 0; + gdbstub_client_respond_hex(client, (uint8_t *) &client->packet, client->packet_pos); + break; + } else { +unknown: + FAST_RESPONSE_HEX("Unknown command\n"); + break; + } + + goto ok; + } + break; + + case 'z': /* remove break/watchpoint */ + case 'Z': /* insert break/watchpoint */ + gdbstub_breakpoint_t *breakpoint, *prev_breakpoint = NULL, **first_breakpoint; + + /* Parse breakpoint type. */ + switch (client->packet[1]) { + case '0': /* software breakpoint */ + first_breakpoint = &first_swbreak; + break; + + case '1': /* hardware breakpoint */ + first_breakpoint = &first_hwbreak; + break; + + case '2': /* write watchpoint */ + first_breakpoint = &first_wwatch; + break; + + case '3': /* read watchpoint */ + first_breakpoint = &first_rwatch; + break; + + case '4': /* access watchpoint */ + first_breakpoint = &first_awatch; + break; + + default: /* unknown type */ + client->packet[2] = '\0'; /* force address check to fail */ + break; + } + + /* Read address. */ + if (client->packet[2] != ',') + break; + client->packet_pos = 3; + if (!(i = gdbstub_client_read_word(client, &j))) + break; + client->packet_pos += i; + if (client->packet[client->packet_pos++] == ',') + gdbstub_client_read_word(client, &k); + else + k = 1; + + /* Test writability of software breakpoint. */ + if (client->packet[1] == '0') { + buf[0] = readmembl(j); + writemembl(j, 0xcc); + buf[1] = readmembl(j); + writemembl(j, buf[0]); + if (buf[1] != 0xcc) + goto end; + } + + /* Find an existing breakpoint with this address. */ + breakpoint = *first_breakpoint; + while (breakpoint) { + if (breakpoint->addr == j) + break; + prev_breakpoint = breakpoint; + breakpoint = breakpoint->next; + } + + /* Check if the breakpoint is already present (when inserting) or not found (when removing). */ + if ((!!breakpoint) ^ (client->packet[0] == 'z')) + goto e22; + + /* Insert or remove the breakpoint. */ + if (client->packet[0] != 'z') { + /* Allocate a new breakpoint. */ + breakpoint = malloc(sizeof(gdbstub_breakpoint_t)); + breakpoint->addr = j; + breakpoint->end = j + k; + breakpoint->next = NULL; + + /* Add the new breakpoint to the list. */ + if (!(*first_breakpoint)) + *first_breakpoint = breakpoint; + else if (prev_breakpoint) + prev_breakpoint->next = breakpoint; + } else { + /* Remove breakpoint from the list. */ + if (breakpoint == *first_breakpoint) + *first_breakpoint = breakpoint->next; + else if (prev_breakpoint) + prev_breakpoint->next = breakpoint->next; + + /* De-allocate breakpoint. */ + free(breakpoint); + } + + /* Update the page watchpoint map if we're dealing with a watchpoint. */ + if (client->packet[1] >= '2') { + /* Clear this watchpoint's corresponding page map groups, + as everything is going to be recomputed soon anyway. */ + memset(&gdbstub_watch_pages[j >> (MEM_GRANULARITY_BITS + 6)], 0, + (((k - 1) >> (MEM_GRANULARITY_BITS + 6)) + 1) * sizeof(gdbstub_watch_pages[0])); + + /* Go through all watchpoint lists. */ + l = 0; + breakpoint = first_rwatch; + while (1) { + if (breakpoint) { + /* Flag this watchpoint's corresponding pages as having a watchpoint. */ + k = (breakpoint->end - 1) >> MEM_GRANULARITY_BITS; + for (i = breakpoint->addr >> MEM_GRANULARITY_BITS; i <= k; i++) + gdbstub_watch_pages[i >> 6] |= (1 << (i & 63)); + + breakpoint = breakpoint->next; + } else { + /* Jump from list to list as a shortcut. */ + if (l == 0) + breakpoint = first_wwatch; + else if (l == 1) + breakpoint = first_awatch; + else + break; + l++; + } + } + } + + /* Respond positively. */ + goto ok; + } +end: + /* Send response. */ + gdbstub_client_respond(client); +} + +static void +gdbstub_cpu_exec(int cycs) +{ + /* Flag that we're now in the debugger context to avoid triggering watchpoints. */ + in_gdbstub = 1; + + /* Handle CPU execution if it isn't paused. */ + if (gdbstub_step <= GDBSTUB_SSTEP) { + /* Swap in any software breakpoints. */ + gdbstub_breakpoint_t *swbreak = first_swbreak; + while (swbreak) { + /* Swap the INT 3 opcode into the address. */ + swbreak->orig_val = readmembl(swbreak->addr); + writemembl(swbreak->addr, 0xcc); + swbreak = swbreak->next; + } + + /* Call the original cpu_exec function outside the debugger context. */ + if ((gdbstub_step == GDBSTUB_SSTEP) && ((cycles + cycs) <= 0)) + cycs += -(cycles + cycs) + 1; + in_gdbstub = 0; + cpu_exec_shadow(cycs); + in_gdbstub = 1; + + /* Swap out any software breakpoints. */ + swbreak = first_swbreak; + while (swbreak) { + if (readmembl(swbreak->addr) == 0xcc) + writemembl(swbreak->addr, swbreak->orig_val); + swbreak = swbreak->next; + } + } + + /* Populate stop reason if we have stopped. */ + stop_reason_len = 0; + if (gdbstub_step > GDBSTUB_EXEC) { + /* Assemble stop reason manually (avoiding sprintf and friends) for performance. */ + stop_reason[stop_reason_len++] = 'T'; + stop_reason[stop_reason_len++] = '0'; + stop_reason[stop_reason_len++] = '0' + ((gdbstub_step == GDBSTUB_BREAK) ? GDB_SIGINT : GDB_SIGTRAP); + + /* Add extended break reason. */ + if (gdbstub_step >= GDBSTUB_BREAK_RWATCH) { + if (gdbstub_step != GDBSTUB_BREAK_WWATCH) + stop_reason[stop_reason_len++] = (gdbstub_step == GDBSTUB_BREAK_RWATCH) ? 'r' : 'a'; + stop_reason[stop_reason_len++] = 'w'; + stop_reason[stop_reason_len++] = 'a'; + stop_reason[stop_reason_len++] = 't'; + stop_reason[stop_reason_len++] = 'c'; + stop_reason[stop_reason_len++] = 'h'; + stop_reason[stop_reason_len++] = ':'; + stop_reason_len += sprintf(&stop_reason[stop_reason_len], "%X;", watch_addr); + } else if (gdbstub_step >= GDBSTUB_BREAK_SW) { + stop_reason[stop_reason_len++] = (gdbstub_step == GDBSTUB_BREAK_SW) ? 's' : 'h'; + stop_reason[stop_reason_len++] = 'w'; + stop_reason[stop_reason_len++] = 'b'; + stop_reason[stop_reason_len++] = 'r'; + stop_reason[stop_reason_len++] = 'e'; + stop_reason[stop_reason_len++] = 'a'; + stop_reason[stop_reason_len++] = 'k'; + stop_reason[stop_reason_len++] = ':'; + stop_reason[stop_reason_len++] = ';'; + } + + /* Add register dump. */ + uint8_t buf[10] = { 0 }; + int i, j, k; + for (i = 0; i < GDB_REG_MAX; i++) { + if (i >= 0x10) + stop_reason[stop_reason_len++] = gdbstub_hex_encode(i >> 4); + stop_reason[stop_reason_len++] = gdbstub_hex_encode(i & 0x0f); + stop_reason[stop_reason_len++] = ':'; + j = gdbstub_client_read_reg(i, buf); + for (k = 0; k < j; k++) { + stop_reason[stop_reason_len++] = gdbstub_hex_encode(buf[k] >> 4); + stop_reason[stop_reason_len++] = gdbstub_hex_encode(buf[k] & 0x0f); + } + stop_reason[stop_reason_len++] = ';'; + } + + /* Don't execute the CPU any further if single-stepping. */ + gdbstub_step = GDBSTUB_BREAK; + } + + /* Return the framerate to normal. */ + gdbstub_next_asap = 0; + + /* Process client packets. */ + thread_wait_mutex(client_list_mutex); + gdbstub_client_t *client = first_client; + while (client) { + /* Report stop reason if the client is waiting for one. */ + if (client->waiting_stop && stop_reason_len) { + client->waiting_stop = 0; + + /* Wait for any pending responses to be acknowledged. */ + if (!thread_wait_event(client->response_event, -1)) { + /* Block other responses from being written while this one isn't acknowledged. */ + thread_reset_event(client->response_event); + + /* Write stop reason response. */ + strcpy(client->response, stop_reason); + client->response_pos = stop_reason_len; + gdbstub_client_respond(client); + } else { + gdbstub_log("GDB Stub: Timed out waiting for client %s:%d\n", inet_ntoa(client->addr.sin_addr), client->addr.sin_port); + } + } + + if (client->has_packet) { + gdbstub_client_packet(client); + client->has_packet = client->packet_pos = 0; + thread_set_event(client->processed_event); + } + +#ifdef GDBSTUB_ALLOW_MULTI_CLIENTS + client = client->next; +#else + break; +#endif + } + thread_release_mutex(client_list_mutex); + + /* Flag that we're now out of the debugger context. */ + in_gdbstub = 0; +} + +static void +gdbstub_client_thread(void *priv) +{ + gdbstub_client_t *client = (gdbstub_client_t *) priv; + uint8_t buf[256]; + ssize_t bytes_read; + int i; + + gdbstub_log("GDB Stub: New connection from %s:%d\n", inet_ntoa(client->addr.sin_addr), client->addr.sin_port); + + /* Allow packets to be processed. */ + thread_set_event(client->processed_event); + + /* Read data from client. */ + while ((bytes_read = recv(client->socket, (char *) buf, sizeof(buf), 0)) > 0) { + for (i = 0; i < bytes_read; i++) { + switch (buf[i]) { + case '$': /* packet start */ + /* Wait for any existing packets to be processed. */ + thread_wait_event(client->processed_event, -1); + + client->packet_pos = 0; + break; + + case '-': /* negative acknowledgement */ + /* Retransmit the current response. */ + gdbstub_client_respond(client); + break; + + case '+': /* positive acknowledgement */ + /* Allow another response to be written. */ + thread_set_event(client->response_event); + break; + + case 0x03: /* break */ + /* Wait for any existing packets to be processed. */ + thread_wait_event(client->processed_event, -1); + + /* Break immediately. */ + gdbstub_log("GDB Stub: Break requested\n"); + gdbstub_break(); + break; + + default: + /* Wait for any existing packets to be processed, just in case. */ + thread_wait_event(client->processed_event, -1); + + if (client->packet_pos < (sizeof(client->packet) - 1)) { + /* Append byte to the packet. */ + client->packet[client->packet_pos++] = buf[i]; + + /* Check if we're at the end of a packet. */ + if ((client->packet_pos >= 3) && (client->packet[client->packet_pos - 3] == '#')) { /* packet checksum start */ + /* Small hack to speed up IDA instruction trace mode. */ + if (*((uint32_t *) client->packet) == ('H' | ('c' << 8) | ('1' << 16) | ('#' << 24))) { + /* Send pre-computed response. */ + send(client->socket, "+$OK#9A", 7, 0); + + /* Skip processing. */ + continue; + } + + /* Flag that a packet should be processed. */ + client->packet[client->packet_pos] = '\0'; + thread_reset_event(client->processed_event); + gdbstub_next_asap = client->has_packet = 1; + } + } + break; + } + } + } + + gdbstub_log("GDB Stub: Connection with %s:%d broken\n", inet_ntoa(client->addr.sin_addr), client->addr.sin_port); + + /* Close socket. */ + if (client->socket != -1) { + close(client->socket); + client->socket = -1; + } + + /* Unblock anyone waiting on the response event. */ + thread_set_event(client->response_event); + + /* Remove this client from the list. */ + thread_wait_mutex(client_list_mutex); +#ifdef GDBSTUB_ALLOW_MULTI_CLIENTS + if (client == first_client) { +#endif + first_client = client->next; + if (first_client == NULL) { + last_client = NULL; + gdbstub_step = GDBSTUB_EXEC; /* unpause CPU when all clients are disconnected */ + } +#ifdef GDBSTUB_ALLOW_MULTI_CLIENTS + } else { + other_client = first_client; + while (other_client) { + if (other_client->next == client) { + if (last_client == client) + last_client = other_client; + other_client->next = client->next; + break; + } + other_client = other_client->next; + } + } +#endif + + free(client); + thread_release_mutex(client_list_mutex); +} + +static void +gdbstub_server_thread(void *priv) +{ + /* Listen on GDB socket. */ + listen(gdbstub_socket, 1); + + /* Accept connections. */ + gdbstub_client_t *client; + socklen_t sl = sizeof(struct sockaddr_in); + while (1) { + /* Allocate client structure. */ + client = malloc(sizeof(gdbstub_client_t)); + memset(client, 0, sizeof(gdbstub_client_t)); + client->processed_event = thread_create_event(); + client->response_event = thread_create_event(); + + /* Accept connection. */ + client->socket = accept(gdbstub_socket, (struct sockaddr *) &client->addr, &sl); + if (client->socket < 0) + break; + + /* Add to client list. */ + thread_wait_mutex(client_list_mutex); + if (first_client) { +#ifdef GDBSTUB_ALLOW_MULTI_CLIENTS + last_client->next = client; + last_client = client; +#else + first_client->next = last_client = client; + close(first_client->socket); +#endif + } else { + first_client = last_client = client; + } + thread_release_mutex(client_list_mutex); + + /* Pause CPU execution. */ + gdbstub_break(); + + /* Start client thread. */ + thread_create(gdbstub_client_thread, client); + } + + /* Deallocate the redundant client structure. */ + thread_destroy_event(client->processed_event); + thread_destroy_event(client->response_event); + free(client); +} + +void +gdbstub_cpu_init() +{ + /* Replace cpu_exec with our own function if the GDB stub is active. */ + if ((gdbstub_socket != -1) && (cpu_exec != gdbstub_cpu_exec)) { + cpu_exec_shadow = cpu_exec; + cpu_exec = gdbstub_cpu_exec; + } +} + +int +gdbstub_instruction() +{ + /* Check hardware breakpoints if any are present. */ + gdbstub_breakpoint_t *breakpoint = first_hwbreak; + if (breakpoint) { + /* Calculate the current instruction's address. */ + uint32_t wanted_addr = cs + cpu_state.pc; + + /* Go through the list of software breakpoints. */ + do { + /* Check if the breakpoint coincides with this address. */ + if (breakpoint->addr == wanted_addr) { + gdbstub_log("GDB Stub: Hardware breakpoint at %08X\n", wanted_addr); + + /* Flag that we're in a hardware breakpoint. */ + gdbstub_step = GDBSTUB_BREAK_HW; + + /* Pause execution. */ + return 1; + } + + breakpoint = breakpoint->next; + } while (breakpoint); + } + + /* No breakpoint found, continue execution or stop if execution is paused. */ + return gdbstub_step - GDBSTUB_EXEC; +} + +int +gdbstub_int3() +{ + /* Check software breakpoints if any are present. */ + gdbstub_breakpoint_t *breakpoint = first_swbreak; + if (breakpoint) { + /* Calculate the breakpoint instruction's address. */ + uint32_t new_pc = cpu_state.pc - 1; + if (cpu_state.op32) + new_pc &= 0xffff; + uint32_t wanted_addr = cs + new_pc; + + /* Go through the list of software breakpoints. */ + do { + /* Check if the breakpoint coincides with this address. */ + if (breakpoint->addr == wanted_addr) { + gdbstub_log("GDB Stub: Software breakpoint at %08X\n", wanted_addr); + + /* Move EIP back to where the break instruction was. */ + cpu_state.pc = new_pc; + + /* Flag that we're in a software breakpoint. */ + gdbstub_step = GDBSTUB_BREAK_SW; + + /* Abort INT 3 execution. */ + return 1; + } + + breakpoint = breakpoint->next; + } while (breakpoint); + } + + /* No breakpoint found, continue INT 3 execution as normal. */ + return 0; +} + +void +gdbstub_mem_access(uint32_t *addrs, int access) +{ + /* Stop if we're in the debugger context. */ + if (in_gdbstub) + return; + + int width = access & (GDBSTUB_MEM_WRITE - 1), i; + + /* Go through the lists of watchpoints for this type of access. */ + gdbstub_breakpoint_t *watchpoint = (access & GDBSTUB_MEM_WRITE) ? first_wwatch : first_rwatch; + while (1) { + if (watchpoint) { + /* Check if any component of this address is within the breakpoint's range. */ + for (i = 0; i < width; i++) { + if ((addrs[i] >= watchpoint->addr) && (addrs[i] < watchpoint->end)) + break; + } + if (i < width) { + gdbstub_log("GDB Stub: %s watchpoint at %08X\n", (access & GDBSTUB_MEM_AWATCH) ? "Access" : ((access & GDBSTUB_MEM_WRITE) ? "Write" : "Read"), watch_addr); + + /* Flag that we're in a read/write watchpoint. */ + gdbstub_step = (access & GDBSTUB_MEM_AWATCH) ? GDBSTUB_BREAK_AWATCH : ((access & GDBSTUB_MEM_WRITE) ? GDBSTUB_BREAK_WWATCH : GDBSTUB_BREAK_RWATCH); + + /* Stop looking. */ + return; + } + + watchpoint = watchpoint->next; + } else { + /* Jump from list to list as a shortcut. */ + if (access & GDBSTUB_MEM_AWATCH) { + break; + } else { + watchpoint = first_awatch; + access |= GDBSTUB_MEM_AWATCH; + } + } + } +} + +void +gdbstub_init() +{ +#ifdef _WIN32 + WSAStartup(MAKEWORD(2, 2), &wsa); +#endif + + /* Create GDB server socket. */ + if ((gdbstub_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + pclog("GDB Stub: Failed to create socket\n"); + return; + } + + /* Bind GDB server socket. */ + int port = 12345; + struct sockaddr_in bind_addr = { + .sin_family = AF_INET, + .sin_addr = { .s_addr = INADDR_ANY }, + .sin_port = htons(port) + }; + if (bind(gdbstub_socket, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1) { + pclog("GDB Stub: Failed to bind on port %d (%d)\n", port, WSAGetLastError()); + gdbstub_socket = -1; + return; + } + + /* Create client list mutex. */ + client_list_mutex = thread_create_mutex(); + + /* Clear watchpoint page map. */ + memset(gdbstub_watch_pages, 0, sizeof(gdbstub_watch_pages)); + + /* Start server thread. */ + pclog("GDB Stub: Listening on port %d\n", port); + thread_create(gdbstub_server_thread, NULL); + + /* Start the CPU paused. */ + gdbstub_step = GDBSTUB_BREAK; +} + +void +gdbstub_close() +{ + /* Stop if the GDB server hasn't initialized. */ + if (gdbstub_socket < 0) + return; + + /* Close GDB server socket. */ + close(gdbstub_socket); + + /* Clear client list. */ + thread_wait_mutex(client_list_mutex); + gdbstub_client_t *client = first_client; + int socket; + while (client) { + socket = client->socket; + client->socket = -1; + close(socket); + client = client->next; + } + thread_release_mutex(client_list_mutex); + thread_close_mutex(client_list_mutex); +} diff --git a/src/include/86box/dma.h b/src/include/86box/dma.h index fc6a0b388..585d77e95 100644 --- a/src/include/86box/dma.h +++ b/src/include/86box/dma.h @@ -66,6 +66,7 @@ typedef struct { extern dma_t dma[8]; extern uint8_t dma_e; +extern uint8_t dma_m; extern void dma_init(void); diff --git a/src/include/86box/gdbstub.h b/src/include/86box/gdbstub.h new file mode 100644 index 000000000..49eed2c45 --- /dev/null +++ b/src/include/86box/gdbstub.h @@ -0,0 +1,78 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for the GDB stub server. + * + * + * + * Authors: RichardG, + * + * Copyright 2022 RichardG. + */ +#ifndef EMU_GDBSTUB_H +#define EMU_GDBSTUB_H +#include <86box/mem.h> + +#define GDBSTUB_MEM_READ 0 +#define GDBSTUB_MEM_WRITE 16 +#define GDBSTUB_MEM_AWATCH 32 + +enum { + GDBSTUB_EXEC = 0, + GDBSTUB_SSTEP, + GDBSTUB_BREAK, + GDBSTUB_BREAK_SW, + GDBSTUB_BREAK_HW, + GDBSTUB_BREAK_RWATCH, + GDBSTUB_BREAK_WWATCH, + GDBSTUB_BREAK_AWATCH +}; + +#ifdef USE_GDBSTUB + +# define GDBSTUB_MEM_ACCESS(addr, access, width) \ + uint32_t gdbstub_page = addr >> MEM_GRANULARITY_BITS; \ + if (gdbstub_watch_pages[gdbstub_page >> 6] & (1 << (gdbstub_page & 63))) { \ + uint32_t gdbstub_addrs[width]; \ + for (int gdbstub_i = 0; gdbstub_i < width; gdbstub_i++) \ + gdbstub_addrs[gdbstub_i] = addr + gdbstub_i; \ + gdbstub_mem_access(gdbstub_addrs, access | width); \ + } + +# define GDBSTUB_MEM_ACCESS_FAST(addrs, access, width) \ + uint32_t gdbstub_page = addr >> MEM_GRANULARITY_BITS; \ + if (gdbstub_watch_pages[gdbstub_page >> 6] & (1 << (gdbstub_page & 63))) \ + gdbstub_mem_access(addrs, access | width); + +extern int gdbstub_step, gdbstub_next_asap; +extern uint64_t gdbstub_watch_pages[(((uint32_t) -1) >> (MEM_GRANULARITY_BITS + 6)) + 1]; + +extern void gdbstub_cpu_init(); +extern int gdbstub_instruction(); +extern int gdbstub_int3(); +extern void gdbstub_mem_access(uint32_t *addrs, int access); +extern void gdbstub_init(); +extern void gdbstub_close(); + +#else + +# define GDBSTUB_MEM_ACCESS(addr, access, width) +# define GDBSTUB_MEM_ACCESS_FAST(addrs, access, width) + +# define gdbstub_step 0 +# define gdbstub_next_asap 0 + +# define gdbstub_cpu_init() +# define gdbstub_instruction() 0 +# define gdbstub_int3() 0 +# define gdbstub_init() +# define gdbstub_close() + +#endif + +#endif diff --git a/src/include/86box/machine.h b/src/include/86box/machine.h index b6abf8634..7c9e8a437 100644 --- a/src/include/86box/machine.h +++ b/src/include/86box/machine.h @@ -613,6 +613,9 @@ extern int machine_at_s370sba_init(const machine_t *); extern int machine_at_apas3_init(const machine_t *); extern int machine_at_gt694va_init(const machine_t *); extern int machine_at_cuv4xls_init(const machine_t *); +#ifdef EMU_DEVICE_H +extern const device_t *at_cuv4xls_get_device(void); +#endif extern int machine_at_6via90ap_init(const machine_t *); extern int machine_at_s1857_init(const machine_t *); extern int machine_at_p6bap_init(const machine_t *); diff --git a/src/include/86box/snd_ad1848.h b/src/include/86box/snd_ad1848.h index c74ba064c..f9f73a60c 100644 --- a/src/include/86box/snd_ad1848.h +++ b/src/include/86box/snd_ad1848.h @@ -41,8 +41,9 @@ typedef struct { int fm_vol_l, fm_vol_r; uint8_t fmt_mask, wave_vol_mask; - uint8_t enable : 1, irq : 4, dma : 3; - int freq; + uint8_t enable : 1, irq : 4, dma : 3, adpcm_ref; + int8_t adpcm_step; + int freq, adpcm_data, adpcm_pos; pc_timer_t timer_count; uint64_t timer_latch; diff --git a/src/include/86box/snd_mpu401.h b/src/include/86box/snd_mpu401.h index 97b0de5fd..96143b251 100644 --- a/src/include/86box/snd_mpu401.h +++ b/src/include/86box/snd_mpu401.h @@ -149,6 +149,8 @@ extern const device_t mpu401_device; extern const device_t mpu401_mca_device; extern uint8_t MPU401_ReadData(mpu_t *mpu); +extern void mpu401_write(uint16_t addr, uint8_t val, void *priv); +extern uint8_t mpu401_read(uint16_t addr, void *priv); extern void mpu401_setirq(mpu_t *mpu, int irq); extern void mpu401_change_addr(mpu_t *mpu, uint16_t addr); extern void mpu401_init(mpu_t *mpu, uint16_t addr, int irq, int mode, int receive_input); diff --git a/src/include/86box/snd_sb.h b/src/include/86box/snd_sb.h index d2498e363..bf0b437b0 100644 --- a/src/include/86box/snd_sb.h +++ b/src/include/86box/snd_sb.h @@ -120,6 +120,8 @@ typedef struct sb_ct1745_mixer_t { uint8_t index; uint8_t regs[256]; + + int output_filter; /* for clones */ } sb_ct1745_mixer_t; typedef struct sb_t { @@ -151,8 +153,13 @@ extern void sb_ct1345_mixer_write(uint16_t addr, uint8_t val, void *p); extern uint8_t sb_ct1345_mixer_read(uint16_t addr, void *p); extern void sb_ct1345_mixer_reset(sb_t *sb); +extern void sb_ct1745_mixer_write(uint16_t addr, uint8_t val, void *p); +extern uint8_t sb_ct1745_mixer_read(uint16_t addr, void *p); +extern void sb_ct1745_mixer_reset(sb_t* sb); + extern void sb_get_buffer_sbpro(int32_t *buffer, int len, void *p); extern void sbpro_filter_cd_audio(int channel, double *buffer, void *p); +extern void sb16_awe32_filter_cd_audio(int channel, double *buffer, void *p); extern void sb_close(void *p); extern void sb_speed_changed(void *p); diff --git a/src/include/86box/snd_sb_dsp.h b/src/include/86box/snd_sb_dsp.h index eff16373d..9dd184ac5 100644 --- a/src/include/86box/snd_sb_dsp.h +++ b/src/include/86box/snd_sb_dsp.h @@ -15,11 +15,16 @@ typedef struct sb_dsp_t { int sb_subtype; /* which clone */ void *parent; /* "sb_t *" if default subtype, "azt2316a_t *" if aztech. */ - int sb_8_length, sb_8_format, sb_8_autoinit, sb_8_pause, sb_8_enable, sb_8_autolen, sb_8_output; + int sb_8_length, sb_8_origlength, sb_8_format, sb_8_autoinit, sb_8_pause, sb_8_enable, sb_8_autolen, sb_8_output; int sb_8_dmanum; - int sb_16_length, sb_16_format, sb_16_autoinit, sb_16_pause, sb_16_enable, sb_16_autolen, sb_16_output; + int sb_16_length, sb_16_origlength, sb_16_format, sb_16_autoinit, sb_16_pause, sb_16_enable, sb_16_autolen, sb_16_output; int sb_16_dmanum; int sb_pausetime; + int (*dma_readb)(void *priv), + (*dma_readw)(void *priv), + (*dma_writeb)(void *priv, uint8_t val), + (*dma_writew)(void *priv, uint16_t val); + void *dma_priv; uint8_t sb_read_data[256]; int sb_read_wp, sb_read_rp; @@ -36,6 +41,8 @@ typedef struct sb_dsp_t { int midi_in_timestamp; int sb_irqnum; + void (*irq_update)(void *priv, int set), + *irq_priv; uint8_t sbe2; int sbe2count; @@ -53,7 +60,7 @@ typedef struct sb_dsp_t { int sbdacpos; - int sbleftright; + int sbleftright, sbleftright_default; int sbreset; uint8_t sbreaddat; @@ -123,4 +130,12 @@ void sb_dsp_set_stereo(sb_dsp_t *dsp, int stereo); void sb_dsp_update(sb_dsp_t *dsp); void sb_update_mask(sb_dsp_t *dsp, int irqm8, int irqm16, int irqm401); +void sb_dsp_irq_attach(sb_dsp_t *dsp, void (*irq_update)(void *priv, int set), void *priv); +void sb_dsp_dma_attach(sb_dsp_t *dsp, + int (*dma_readb)(void *priv), + int (*dma_readw)(void *priv), + int (*dma_writeb)(void *priv, uint8_t val), + int (*dma_writew)(void *priv, uint16_t val), + void *priv); + #endif /* SOUND_SND_SB_DSP_H */ diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index 558c472ed..2696954ef 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -118,6 +118,8 @@ extern const device_t sb_pro_mcv_device; extern const device_t sb_pro_compat_device; extern const device_t sb_16_device; extern const device_t sb_16_pnp_device; +extern const device_t sb_16_compat_device; +extern const device_t sb_16_compat_nompu_device; extern const device_t sb_32_pnp_device; extern const device_t sb_awe32_device; extern const device_t sb_awe32_pnp_device; @@ -138,6 +140,13 @@ extern const device_t cs4235_onboard_device; extern const device_t cs4236b_device; extern const device_t cs4237b_device; extern const device_t cs4238b_device; + +/* C-Media CMI8x38 */ +extern const device_t cmi8338_device; +extern const device_t cmi8338_onboard_device; +extern const device_t cmi8738_device; +extern const device_t cmi8738_onboard_device; +extern const device_t cmi8738_6ch_onboard_device; #endif #endif /*EMU_SOUND_H*/ diff --git a/src/io.c b/src/io.c index 9f9df62a4..268305e1a 100644 --- a/src/io.c +++ b/src/io.c @@ -673,7 +673,7 @@ io_trap_remap(void *handle, int enable, uint16_t addr, uint16_t size) trap->base, trap->base + trap->size - 1, trap->enable, addr, addr + size - 1, enable); /* Remove old I/O mapping. */ - if (trap->enable && trap->base && trap->size) { + if (trap->enable && trap->size) { io_removehandler(trap->base, trap->size, io_trap_readb, io_trap_readw, io_trap_readl, io_trap_writeb, io_trap_writew, io_trap_writel, @@ -686,8 +686,8 @@ io_trap_remap(void *handle, int enable, uint16_t addr, uint16_t size) trap->size = size; /* Add new I/O mapping. */ - if (trap->enable && trap->base && trap->size) { - io_sethandler(trap->base, trap->size, + if (trap->enable && trap->size) { + io_sethandler(trap->base, trap->size, io_trap_readb, io_trap_readw, io_trap_readl, io_trap_writeb, io_trap_writew, io_trap_writel, trap); diff --git a/src/machine/m_at_socket370.c b/src/machine/m_at_socket370.c index ae8754f80..89ed5c471 100644 --- a/src/machine/m_at_socket370.c +++ b/src/machine/m_at_socket370.c @@ -430,10 +430,20 @@ machine_at_cuv4xls_init(const machine_t *model) spd_register(SPD_TYPE_SDRAM, 0xF, 1024); device_add(&as99127f_device); /* fans: Chassis, CPU, Power; temperatures: MB, JTPWR, CPU */ + if (sound_card_current == SOUND_INTERNAL) + device_add(&cmi8738_onboard_device); + return ret; } +const device_t * +at_cuv4xls_get_device(void) +{ + return &cmi8738_onboard_device; +} + + int machine_at_6via90ap_init(const machine_t *model) { diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index ca4abfea2..982218e2d 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -914,7 +914,7 @@ const machine_t machines[] = { { "[VIA Apollo Pro 133A] Acorp 6VIA90AP", "6via90ap", MACHINE_TYPE_SOCKET370, CPU_PKG_SOCKET370, 0, 66666667, 150000000, 1300, 3500, MACHINE_MULTIPLIER_FIXED, MACHINE_AGP | MACHINE_BUS_PS2 | MACHINE_BUS_AC97 | MACHINE_IDE_DUAL | MACHINE_SOUND | MACHINE_GAMEPORT, 16384,3145728, 8192, 255, machine_at_6via90ap_init, NULL }, /* Has the VIA VT82C686B southbridge with on-chip KBC identical to the VIA VT82C42N. */ - { "[VIA Apollo Pro 133A] ASUS CUV4X-LS", "cuv4xls", MACHINE_TYPE_SOCKET370, CPU_PKG_SOCKET370, 0, 66666667, 150000000, 1300, 3500, 1.5, 8.0, (MACHINE_AGP & ~MACHINE_AT) | MACHINE_BUS_PS2 | MACHINE_BUS_AC97 | MACHINE_IDE_DUAL,16384,4194304, 8192, 255, machine_at_cuv4xls_init, NULL }, + { "[VIA Apollo Pro 133A] ASUS CUV4X-LS", "cuv4xls", MACHINE_TYPE_SOCKET370, CPU_PKG_SOCKET370, 0, 66666667, 150000000, 1300, 3500, 1.5, 8.0, (MACHINE_AGP & ~MACHINE_AT) | MACHINE_BUS_PS2 | MACHINE_BUS_AC97 | MACHINE_IDE_DUAL | MACHINE_SOUND, 16384,4194304, 8192, 255, machine_at_cuv4xls_init, at_cuv4xls_get_device }, /* Has a Winbond W83977EF Super I/O chip with on-chip KBC with AMIKey-2 KBC firmware. */ { "[VIA Apollo Pro 133A] BCM GT694VA", "gt694va", MACHINE_TYPE_SOCKET370, CPU_PKG_SOCKET370, 0, 66666667, 133333333, 1300, 3500, 1.5, 8.0, MACHINE_AGP | MACHINE_BUS_PS2 | MACHINE_IDE_DUAL | MACHINE_SOUND, 16384,3145728, 8192, 255, machine_at_gt694va_init, at_gt694va_get_device }, diff --git a/src/mem/mem.c b/src/mem/mem.c index fbc7d8f08..bdb0c4cdb 100644 --- a/src/mem/mem.c +++ b/src/mem/mem.c @@ -36,6 +36,7 @@ #include <86box/mem.h> #include <86box/plat.h> #include <86box/rom.h> +#include <86box/gdbstub.h> #ifdef USE_DYNAREC # include "codegen_public.h" #else @@ -783,6 +784,8 @@ readmembl(uint32_t addr) mem_mapping_t *map; uint64_t a; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 1); + addr64 = (uint64_t) addr; mem_logical_addr = addr; @@ -811,6 +814,8 @@ writemembl(uint32_t addr, uint8_t val) mem_mapping_t *map; uint64_t a; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 1); + addr64 = (uint64_t) addr; mem_logical_addr = addr; @@ -842,6 +847,8 @@ readmembl_no_mmut(uint32_t addr, uint32_t a64) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 1); + mem_logical_addr = addr; if (cr0 >> 31) { @@ -866,6 +873,8 @@ writemembl_no_mmut(uint32_t addr, uint32_t a64, uint8_t val) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 1); + mem_logical_addr = addr; if (page_lookup[addr >> 12] && page_lookup[addr >> 12]->write_b) { @@ -896,6 +905,7 @@ readmemwl(uint32_t addr) addr64a[0] = addr; addr64a[1] = addr + 1; + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_READ, 2); mem_logical_addr = addr; @@ -957,6 +967,7 @@ writememwl(uint32_t addr, uint16_t val) addr64a[0] = addr; addr64a[1] = addr + 1; + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_WRITE, 2); mem_logical_addr = addr; @@ -1029,6 +1040,8 @@ readmemwl_no_mmut(uint32_t addr, uint32_t *a64) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 2); + mem_logical_addr = addr; if (addr & 1) { @@ -1076,6 +1089,8 @@ writememwl_no_mmut(uint32_t addr, uint32_t *a64, uint16_t val) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 2); + mem_logical_addr = addr; if (addr & 1) { @@ -1135,6 +1150,7 @@ readmemll(uint32_t addr) for (i = 0; i < 4; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_READ, 4); mem_logical_addr = addr; @@ -1214,6 +1230,7 @@ writememll(uint32_t addr, uint32_t val) for (i = 0; i < 4; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_WRITE, 4); mem_logical_addr = addr; @@ -1305,6 +1322,8 @@ readmemll_no_mmut(uint32_t addr, uint32_t *a64) #ifndef NO_MMUT mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 4); + mem_logical_addr = addr; if (addr & 3) { @@ -1361,6 +1380,8 @@ writememll_no_mmut(uint32_t addr, uint32_t *a64, uint32_t val) #ifndef NO_MMUT mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 4); + mem_logical_addr = addr; if (addr & 3) { @@ -1429,6 +1450,7 @@ readmemql(uint32_t addr) for (i = 0; i < 8; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_READ, 8); mem_logical_addr = addr; @@ -1496,6 +1518,7 @@ writememql(uint32_t addr, uint64_t val) for (i = 0; i < 8; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_WRITE, 8); mem_logical_addr = addr; diff --git a/src/qt/qt_joystickconfiguration.cpp b/src/qt/qt_joystickconfiguration.cpp index 594a1ef17..54aec5c6d 100644 --- a/src/qt/qt_joystickconfiguration.cpp +++ b/src/qt/qt_joystickconfiguration.cpp @@ -43,7 +43,7 @@ JoystickConfiguration::JoystickConfiguration(int type, int joystick_nr, QWidget } ui->comboBoxDevice->setCurrentIndex(joystick_state[joystick_nr].plat_joystick_nr); - setFixedSize(minimumSizeHint()); + layout()->setSizeConstraint(QLayout::SetFixedSize); } JoystickConfiguration::~JoystickConfiguration() @@ -82,7 +82,9 @@ int JoystickConfiguration::selectedPov(int pov) { void JoystickConfiguration::on_comboBoxDevice_currentIndexChanged(int index) { for (auto w : widgets) { ui->ct->removeWidget(w); + w->deleteLater(); } + widgets.clear(); if (index == 0) { return; @@ -197,6 +199,4 @@ void JoystickConfiguration::on_comboBoxDevice_currentIndexChanged(int index) { ++row; } - - setFixedSize(minimumSizeHint()); } diff --git a/src/qt/qt_joystickconfiguration.ui b/src/qt/qt_joystickconfiguration.ui index abe17b5cc..7789b48c4 100644 --- a/src/qt/qt_joystickconfiguration.ui +++ b/src/qt/qt_joystickconfiguration.ui @@ -11,7 +11,7 @@ - Dialog + Joystick configuration diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 00bb1e077..d14b95ae0 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -54,6 +54,7 @@ extern "C" #include <86box/ui.h> #include <86box/video.h> #include <86box/discord.h> +#include <86box/gdbstub.h> } #include @@ -95,6 +96,11 @@ main_thread_fn() while (!is_quit && cpu_thread_run) { /* See if it is time to run a frame of code. */ new_time = elapsed_timer.elapsed(); +#ifdef USE_GDBSTUB + if (gdbstub_next_asap && (drawits <= 0)) + drawits = 10; + else +#endif drawits += (new_time - old_time); old_time = new_time; if (drawits > 0 && !dopause) { diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index b3d85ada4..ecfaef9f9 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -214,6 +214,11 @@ MainWindow::MainWindow(QWidget *parent) : } }); + connect(qApp, &QGuiApplication::applicationStateChanged, [this](Qt::ApplicationState state) { + if (mouse_capture && state != Qt::ApplicationState::ApplicationActive) + emit setMouseCapture(false); + }); + connect(this, &MainWindow::resizeContents, this, [this](int w, int h) { if (!QApplication::platformName().contains("eglfs") && vid_resize == 0) { w = qRound(w / (!dpi_scale ? util::screenOfWidget(this)->devicePixelRatio() : 1.)); @@ -243,7 +248,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(this, &MainWindow::updateStatusBarTip, status.get(), &MachineStatus::updateTip); connect(this, &MainWindow::updateStatusBarActivity, status.get(), &MachineStatus::setActivity); connect(this, &MainWindow::updateStatusBarEmpty, status.get(), &MachineStatus::setEmpty); - connect(this, &MainWindow::statusBarMessage, status.get(), &MachineStatus::message); + connect(this, &MainWindow::statusBarMessage, status.get(), &MachineStatus::message, Qt::QueuedConnection); ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); @@ -1265,6 +1270,8 @@ void MainWindow::on_actionFullscreen_triggered() { } else { if (video_fullscreen_first) { + bool wasCaptured = mouse_capture == 1; + QMessageBox questionbox(QMessageBox::Icon::Information, tr("Entering fullscreen mode"), tr("Press Ctrl+Alt+PgDn to return to windowed mode."), QMessageBox::Ok, this); QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again")); questionbox.setCheckBox(chkbox); @@ -1275,6 +1282,10 @@ void MainWindow::on_actionFullscreen_triggered() { }); questionbox.exec(); config_save(); + + /* (re-capture mouse after dialog. */ + if (wasCaptured) + emit setMouseCapture(true); } video_fullscreen = 1; setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); @@ -1305,22 +1316,27 @@ bool MainWindow::eventFilter(QObject* receiver, QEvent* event) if (this->keyboardGrabber() == this) { if (event->type() == QEvent::KeyPress) { event->accept(); - this->keyPressEvent((QKeyEvent*)event); + this->keyPressEvent((QKeyEvent *) event); return true; } if (event->type() == QEvent::KeyRelease) { event->accept(); - this->keyReleaseEvent((QKeyEvent*)event); + this->keyReleaseEvent((QKeyEvent *) event); return true; } } - if (receiver == this) - { + if (receiver == this) { static auto curdopause = dopause; - if (event->type() == QEvent::WindowBlocked) { curdopause = dopause; plat_pause(1); } - else if (event->type() == QEvent::WindowUnblocked) { plat_pause(curdopause); } + if (event->type() == QEvent::WindowBlocked) { + curdopause = dopause; + plat_pause(1); + emit setMouseCapture(false); + } else if (event->type() == QEvent::WindowUnblocked) { + plat_pause(curdopause); + } } + return QMainWindow::eventFilter(receiver, event); } diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 855e8a04c..508103169 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -533,9 +533,11 @@ void ProgSettings::reloadStrings() translatedstrings[IDS_2078] = QCoreApplication::translate("", "Press F8+F12 to release mouse").replace("F8+F12", MOUSE_CAPTURE_KEYSEQ).replace("CTRL-END", QLocale::system().name() == "de_DE" ? "Strg+Ende" : "CTRL-END").toStdWString(); translatedstrings[IDS_2079] = QCoreApplication::translate("", "Press F8+F12 or middle button to release mouse").replace("F8+F12", MOUSE_CAPTURE_KEYSEQ).replace("CTRL-END", QLocale::system().name() == "de_DE" ? "Strg+Ende" : "CTRL-END").toStdWString(); translatedstrings[IDS_2080] = QCoreApplication::translate("", "Failed to initialize FluidSynth").toStdWString(); + translatedstrings[IDS_2130] = QCoreApplication::translate("", "Invalid configuration").toStdWString(); translatedstrings[IDS_4099] = QCoreApplication::translate("", "MFM/RLL or ESDI CD-ROM drives never existed").toStdWString(); translatedstrings[IDS_2093] = QCoreApplication::translate("", "Failed to set up PCap").toStdWString(); translatedstrings[IDS_2094] = QCoreApplication::translate("", "No PCap devices found").toStdWString(); + translatedstrings[IDS_2095] = QCoreApplication::translate("", "Invalid PCap device").toStdWString(); translatedstrings[IDS_2110] = QCoreApplication::translate("", "Unable to initialize FreeType").toStdWString(); translatedstrings[IDS_2111] = QCoreApplication::translate("", "Unable to initialize SDL, libsdl2 is required").toStdWString(); translatedstrings[IDS_2129] = QCoreApplication::translate("", "Make sure libpcap is installed and that you are on a libpcap-compatible network connection.").toStdWString(); @@ -543,6 +545,7 @@ void ProgSettings::reloadStrings() translatedstrings[IDS_2063] = QCoreApplication::translate("", "Machine \"%hs\" is not available due to missing ROMs in the roms/machines directory. Switching to an available machine.").toStdWString(); translatedstrings[IDS_2064] = QCoreApplication::translate("", "Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card.").toStdWString(); translatedstrings[IDS_2128] = QCoreApplication::translate("", "Hardware not available").toStdWString(); + translatedstrings[IDS_2142] = QCoreApplication::translate("", "Monitor in sleep mode").toStdWString(); translatedstrings[IDS_2120] = QCoreApplication::translate("", "No ROMs found").toStdWString(); translatedstrings[IDS_2056] = QCoreApplication::translate("", "86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory.").replace("roms", ROMDIR).toStdWString(); diff --git a/src/qt/qt_progsettings.cpp b/src/qt/qt_progsettings.cpp index 9e39c4844..d5bfdaa2f 100644 --- a/src/qt/qt_progsettings.cpp +++ b/src/qt/qt_progsettings.cpp @@ -131,7 +131,7 @@ void ProgSettings::accept() connect(main_window, &MainWindow::updateStatusBarTip, main_window->status.get(), &MachineStatus::updateTip); connect(main_window, &MainWindow::updateStatusBarActivity, main_window->status.get(), &MachineStatus::setActivity); connect(main_window, &MainWindow::updateStatusBarEmpty, main_window->status.get(), &MachineStatus::setEmpty); - connect(main_window, &MainWindow::statusBarMessage, main_window->status.get(), &MachineStatus::message); + connect(main_window, &MainWindow::statusBarMessage, main_window->status.get(), &MachineStatus::message, Qt::QueuedConnection); QDialog::accept(); } diff --git a/src/qt/wl_mouse.cpp b/src/qt/wl_mouse.cpp index 420ddc486..75a03813c 100644 --- a/src/qt/wl_mouse.cpp +++ b/src/qt/wl_mouse.cpp @@ -98,8 +98,8 @@ void wl_mouse_capture(QWindow *window) void wl_mouse_uncapture() { - zwp_locked_pointer_v1_destroy(conf_pointer); - zwp_relative_pointer_v1_destroy(rel_pointer); + if (conf_pointer) zwp_locked_pointer_v1_destroy(conf_pointer); + if (rel_pointer) zwp_relative_pointer_v1_destroy(rel_pointer); rel_pointer = nullptr; conf_pointer = nullptr; } diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt index e6600a98d..db700670d 100644 --- a/src/sound/CMakeLists.txt +++ b/src/sound/CMakeLists.txt @@ -16,7 +16,7 @@ add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_resid.cc midi.c snd_speaker.c snd_pssj.c snd_lpt_dac.c snd_ac97_codec.c snd_ac97_via.c snd_lpt_dss.c snd_ps1.c snd_adlib.c snd_adlibgold.c snd_ad1848.c snd_audiopci.c - snd_azt2316a.c snd_cms.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c + snd_azt2316a.c snd_cms.c snd_cmi8x38.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c snd_emu8k.c snd_mpu401.c snd_sn76489.c snd_ssi2001.c snd_wss.c snd_ym7128.c) if(OPENAL) diff --git a/src/sound/snd_ad1848.c b/src/sound/snd_ad1848.c index a72e1472b..bf3858aa3 100644 --- a/src/sound/snd_ad1848.c +++ b/src/sound/snd_ad1848.c @@ -37,6 +37,10 @@ static int ad1848_vols_7bits[128]; static double ad1848_vols_5bits_aux_gain[32]; +/* Borrowed from snd_sb_dsp */ +extern int8_t scaleMap4[64]; +extern uint8_t adjustMap4[64]; + void ad1848_setirq(ad1848_t *ad1848, int irq) { @@ -232,6 +236,7 @@ ad1848_write(uint16_t addr, uint8_t val, void *priv) case 9: if (!ad1848->enable && (val & 0x41) == 0x01) { + ad1848->adpcm_pos = 0; if (ad1848->timer_latch) timer_set_delay_u64(&ad1848->timer_count, ad1848->timer_latch); else @@ -257,7 +262,8 @@ ad1848_write(uint16_t addr, uint8_t val, void *priv) break; case 17: - if (ad1848->type >= AD1848_TYPE_CS4231) /* enable additional data formats on modes 2 and 3 */ + /* Enable additional data formats on modes 2 and 3 where supported. */ + if ((ad1848->type == AD1848_TYPE_CS4231) || (ad1848->type == AD1848_TYPE_CS4236)) ad1848->fmt_mask = (val & 0x40) ? 0xf0 : 0x70; break; @@ -414,6 +420,64 @@ ad1848_update(ad1848_t *ad1848) } } +static int16_t +ad1848_process_mulaw(uint8_t byte) +{ + byte = ~byte; + int16_t dec = ((byte & 0x0f) << 3) + 0x84; + dec <<= (byte & 0x70) >> 4; + return (byte & 0x80) ? (0x84 - dec) : (dec - 0x84); +} + +static int16_t +ad1848_process_alaw(uint8_t byte) +{ + byte ^= 0x55; + int16_t dec = (byte & 0x0f) << 4; + int seg = (byte & 0x70) >> 4; + switch (seg) { + case 0: + dec |= 0x8; + break; + + case 1: + dec |= 0x108; + break; + + default: + dec |= 0x108; + dec <<= seg - 1; + break; + } + return (byte & 0x80) ? dec : -dec; +} + +static int16_t +ad1848_process_adpcm(ad1848_t *ad1848) +{ + int temp; + if (ad1848->adpcm_pos++ & 1) { + temp = (ad1848->adpcm_data & 0x0f) + ad1848->adpcm_step; + } else { + ad1848->adpcm_data = dma_channel_read(ad1848->dma); + temp = (ad1848->adpcm_data >> 4) + ad1848->adpcm_step; + } + if (temp < 0) + temp = 0; + else if (temp > 63) + temp = 63; + + ad1848->adpcm_ref += scaleMap4[temp]; + if (ad1848->adpcm_ref > 0xff) + ad1848->adpcm_ref = 0xff; + else if (ad1848->adpcm_ref < 0x00) + ad1848->adpcm_ref = 0x00; + + ad1848->adpcm_step = (ad1848->adpcm_step + adjustMap4[temp]) & 0xff; + + return (ad1848->adpcm_ref ^ 0x80) << 8; +} + static void ad1848_poll(void *priv) { @@ -431,12 +495,21 @@ ad1848_poll(void *priv) switch (ad1848->regs[8] & ad1848->fmt_mask) { case 0x00: /* Mono, 8-bit PCM */ - ad1848->out_l = ad1848->out_r = (dma_channel_read(ad1848->dma) ^ 0x80) * 256; + ad1848->out_l = ad1848->out_r = (dma_channel_read(ad1848->dma) ^ 0x80) << 8; break; case 0x10: /* Stereo, 8-bit PCM */ - ad1848->out_l = (dma_channel_read(ad1848->dma) ^ 0x80) * 256; - ad1848->out_r = (dma_channel_read(ad1848->dma) ^ 0x80) * 256; + ad1848->out_l = (dma_channel_read(ad1848->dma) ^ 0x80) << 8; + ad1848->out_r = (dma_channel_read(ad1848->dma) ^ 0x80) << 8; + break; + + case 0x20: /* Mono, 8-bit Mu-Law */ + ad1848->out_l = ad1848->out_r = ad1848_process_mulaw(dma_channel_read(ad1848->dma)); + break; + + case 0x30: /* Stereo, 8-bit Mu-Law */ + ad1848->out_l = ad1848_process_mulaw(dma_channel_read(ad1848->dma)); + ad1848->out_r = ad1848_process_mulaw(dma_channel_read(ad1848->dma)); break; case 0x40: /* Mono, 16-bit PCM little endian */ @@ -451,6 +524,26 @@ ad1848_poll(void *priv) ad1848->out_r = (dma_channel_read(ad1848->dma) << 8) | temp; break; + case 0x60: /* Mono, 8-bit A-Law */ + ad1848->out_l = ad1848->out_r = ad1848_process_alaw(dma_channel_read(ad1848->dma)); + break; + + case 0x70: /* Stereo, 8-bit A-Law */ + ad1848->out_l = ad1848_process_alaw(dma_channel_read(ad1848->dma)); + ad1848->out_r = ad1848_process_alaw(dma_channel_read(ad1848->dma)); + break; + + /* 0x80 and 0x90 reserved */ + + case 0xa0: /* Mono, 4-bit ADPCM */ + ad1848->out_l = ad1848->out_r = ad1848_process_adpcm(ad1848); + break; + + case 0xb0: /* Stereo, 4-bit ADPCM */ + ad1848->out_l = ad1848_process_adpcm(ad1848); + ad1848->out_r = ad1848_process_adpcm(ad1848); + break; + case 0xc0: /* Mono, 16-bit PCM big endian */ temp = dma_channel_read(ad1848->dma); ad1848->out_l = ad1848->out_r = dma_channel_read(ad1848->dma) | (temp << 8); @@ -462,6 +555,8 @@ ad1848_poll(void *priv) temp = dma_channel_read(ad1848->dma); ad1848->out_r = dma_channel_read(ad1848->dma) | (temp << 8); break; + + /* 0xe0 and 0xf0 reserved */ } if (ad1848->regs[6] & 0x80) @@ -475,7 +570,8 @@ ad1848_poll(void *priv) ad1848->out_r = (ad1848->out_r * ad1848_vols_7bits[ad1848->regs[7] & ad1848->wave_vol_mask]) >> 16; if (ad1848->count < 0) { - ad1848->count = ad1848->regs[15] | (ad1848->regs[14] << 8); + ad1848->count = ad1848->regs[15] | (ad1848->regs[14] << 8); + ad1848->adpcm_pos = 0; if (!(ad1848->status & 0x01)) { ad1848->status |= 0x01; ad1848->regs[24] |= 0x10; @@ -484,7 +580,8 @@ ad1848_poll(void *priv) } } - ad1848->count--; + if (!(ad1848->adpcm_pos & 7)) /* ADPCM counts down every 4 bytes */ + ad1848->count--; } else { ad1848->out_l = ad1848->out_r = 0; ad1848->cd_vol_l = ad1848->cd_vol_r = 0; @@ -563,7 +660,10 @@ ad1848_init(ad1848_t *ad1848, uint8_t type) ad1848->out_l = ad1848->out_r = 0; ad1848->fm_vol_l = ad1848->fm_vol_r = 65536; ad1848_updatevolmask(ad1848); - ad1848->fmt_mask = 0x70; + if (type == AD1848_TYPE_CS4235) + ad1848->fmt_mask = 0x50; + else + ad1848->fmt_mask = 0x70; for (c = 0; c < 128; c++) { attenuation = 0.0; diff --git a/src/sound/snd_audiopci.c b/src/sound/snd_audiopci.c index 7dbb1f1f2..5a64d3579 100644 --- a/src/sound/snd_audiopci.c +++ b/src/sound/snd_audiopci.c @@ -2087,6 +2087,17 @@ static const device_config_t es1371_config[] = { // clang-format on }; +static const device_config_t es1371_onboard_config[] = { +// clang-format off + { + "receive_input", "Receive input (MIDI)", CONFIG_BINARY, "", 1 + }, + { + "", "", -1 + } +// clang-format on +}; + const device_t es1371_device = { .name = "Ensoniq AudioPCI (ES1371)", .internal_name = "es1371", @@ -2112,5 +2123,5 @@ const device_t es1371_onboard_device = { { .available = NULL }, .speed_changed = es1371_speed_changed, .force_redraw = NULL, - .config = NULL + .config = es1371_onboard_config }; diff --git a/src/sound/snd_cmi8x38.c b/src/sound/snd_cmi8x38.c new file mode 100644 index 000000000..53ed17804 --- /dev/null +++ b/src/sound/snd_cmi8x38.c @@ -0,0 +1,1525 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * C-Media CMI8x38 PCI audio controller emulation. + * + * + * + * Authors: RichardG, + * + * Copyright 2022 RichardG. + */ +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/io.h> +#include <86box/mem.h> +#include <86box/pic.h> +#include <86box/timer.h> +#include <86box/dma.h> +#include <86box/pci.h> +#include <86box/sound.h> +#include <86box/snd_sb.h> +#include <86box/snd_sb_dsp.h> +#include <86box/gameport.h> +#include <86box/nmi.h> +#include <86box/ui.h> + +enum { + /* [23:16] = reg 0F [7:0] (reg 0C [31:24]) + [13] = onboard flag + [12:8] = reg 0B [4:0] (reg 08 [28:24]) + [7:0] = PCI device ID [7:0] */ + CMEDIA_CMI8338 = 0x000000, + CMEDIA_CMI8738_4CH = 0x040011, /* chip version 039 with 4-channel output */ + CMEDIA_CMI8738_6CH = 0x080011 /* chip version 055 with 6-channel output */ +}; + +enum { + TRAP_DMA = 0, + TRAP_PIC, + TRAP_OPL, + TRAP_MPU, + TRAP_MAX +}; + +typedef struct { + uint8_t id, reg, always_run, playback_enabled, channels; + struct _cmi8x38_ *dev; + + uint32_t sample_ptr, fifo_pos, fifo_end; + int32_t frame_count_dma, frame_count_fragment, sample_count_out; + uint8_t fifo[256], restart; + + int16_t out_fl, out_fr, out_rl, out_rr, out_c, out_lfe; + int vol_l, vol_r, pos; + int32_t buffer[SOUNDBUFLEN * 2]; + uint64_t timer_latch; + double dma_latch; + + pc_timer_t dma_timer, poll_timer; +} cmi8x38_dma_t; + +typedef struct _cmi8x38_ { + uint32_t type; + uint16_t io_base, sb_base, opl_base, mpu_base; + uint8_t pci_regs[256], io_regs[256]; + int slot; + + sb_t *sb; + void *gameport, *io_traps[TRAP_MAX]; + + cmi8x38_dma_t dma[2]; + uint16_t tdma_base_addr, tdma_base_count; + int tdma_8, tdma_16, tdma_mask, prev_mask, tdma_irq_mask; + + int master_vol_l, master_vol_r, cd_vol_l, cd_vol_r; +} cmi8x38_t; + +#ifdef ENABLE_CMI8X38_LOG +int cmi8x38_do_log = ENABLE_CMI8X38_LOG; + +static void +cmi8x38_log(const char *fmt, ...) +{ + va_list ap; + + if (cmi8x38_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define cmi8x38_log(fmt, ...) +#endif + +static const double freqs[] = { 5512.0, 11025.0, 22050.0, 44100.0, 8000.0, 16000.0, 32000.0, 48000.0 }; +static const uint16_t opl_ports_cmi8738[] = { 0x388, 0x3c8, 0x3e0, 0x3e8 }; + +static void cmi8x38_dma_process(void *priv); +static void cmi8x38_speed_changed(void *priv); + +static void +cmi8x38_update_irqs(cmi8x38_t *dev) +{ + /* Calculate and use the INTR flag. */ + if (*((uint32_t *) &dev->io_regs[0x10]) & 0x0401c003) { + dev->io_regs[0x13] |= 0x80; + pci_set_irq(dev->slot, PCI_INTA); + cmi8x38_log("CMI8x38: Raising IRQ\n"); + } else { + dev->io_regs[0x13] &= ~0x80; + pci_clear_irq(dev->slot, PCI_INTA); + } +} + +static void +cmi8x38_mpu_irq_update(void *priv, int set) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + if (set) + dev->io_regs[0x12] |= 0x01; + else + dev->io_regs[0x12] &= ~0x01; + cmi8x38_update_irqs(dev); +} + +static int +cmi8x38_mpu_irq_pending(void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + return dev->io_regs[0x12] & 0x01; +} + +static void +cmi8x38_sb_irq_update(void *priv, int set) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + /* Interrupt flag shared with the first DMA channel. Combining SB and + PCI DMA is undefined; the VxD driver disables SB if PCI is in use. */ + if (set) + dev->io_regs[0x10] |= 0x01; + else + dev->io_regs[0x10] &= ~0x01; + cmi8x38_update_irqs(dev); +} + +static int +cmi8x38_sb_dma_post(cmi8x38_t *dev, uint16_t *addr, uint16_t *count, int channel) +{ + /* Increment address and decrement count. */ + *addr += 1; + *count -= 1; + + /* Copy TDMA registers to DMA on CMI8738+. Everything so far suggests that + those chips use PCI bus mastering to directly write to the DMA registers. */ +#if 0 /* TSRs don't set ENWR8237, except for the patched C3DPCI - does that bit have no effect? */ + if ((dev->type != CMEDIA_CMI8338) && (dev->io_regs[0x17] & 0x10)) +#else + if (dev->type != CMEDIA_CMI8338) +#endif + { + if (channel & 4) + dma[channel].ab = (dma[channel].ab & 0xfffe0000) | ((*addr) << 1); + else + dma[channel].ab = (dma[channel].ab & 0xffff0000) | *addr; + dma[channel].ac = dma[channel].ab; + dma[channel].cc = dma[channel].cb = *count; + } + + /* Check TDMA position update interrupt if enabled. */ + if (dev->io_regs[0x0e] & 0x04) { + /* Nothing uses this; I assume it goes by the SB DSP sample counter (forwards instead of backwards). */ + int origlength = (channel & 4) ? dev->sb->dsp.sb_16_origlength : dev->sb->dsp.sb_8_origlength, + length = (channel & 4) ? dev->sb->dsp.sb_16_length : dev->sb->dsp.sb_8_length; + if ((origlength != length) && (((origlength - length) & dev->tdma_irq_mask) == 0)) { /* skip initial sample */ + /* Fire the interrupt. */ + dev->io_regs[0x11] |= (channel & 4) ? 0x40 : 0x80; + cmi8x38_update_irqs(dev); + } + } + + /* Handle end of DMA. */ + if (*count == 0xffff) { + if (dma[channel].mode & 0x10) { /* auto-init */ + /* Restart TDMA. */ + *addr = dev->tdma_base_addr; + *count = dev->tdma_base_count; + cmi8x38_log("CMI8x38: Restarting TDMA on DMA %d with addr %08X count %04X\n", + channel, + (channel & 4) ? ((dma[channel].ab & 0xfffe0000) | ((*addr) << 1)) : ((dma[channel].ab & 0xffff0000) | *addr), + *count); + } else { + /* Mask TDMA. */ + dev->tdma_mask |= 1 << channel; + } + + /* Set the mysterious LHBTOG bit, assuming it corresponds + to the 8237 channel status bit. Nothing reads it. */ + dev->io_regs[0x10] |= 0x40; + + return DMA_OVER; + } + return 0; +} + +static int +cmi8x38_sb_dma_readb(void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Stop if the DMA channel is invalid or if TDMA is masked. */ + int channel = dev->tdma_8; + if ((channel < 0) || (dev->tdma_mask & (1 << channel))) + return DMA_NODATA; + + /* Get 16-bit address and count registers. */ + uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c], + *count = (uint16_t *) &dev->io_regs[0x1e]; + + /* Read data. */ + int ret = mem_readb_phys((dma[channel].ab & 0xffff0000) | *addr); + + /* Handle address, count and end. */ + ret |= cmi8x38_sb_dma_post(dev, addr, count, channel); + + return ret; +} + +static int +cmi8x38_sb_dma_readw(void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Stop if the DMA channel is invalid or if TDMA is masked. */ + int channel = dev->tdma_16; + if ((channel < 0) || (dev->tdma_mask & (1 << channel))) + return DMA_NODATA; + + /* Get 16-bit address and count registers. */ + uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c], + *count = (uint16_t *) &dev->io_regs[0x1e]; + + /* Read data. */ + int ret = mem_readw_phys((dma[channel].ab & 0xfffe0000) | ((*addr) << 1)); + + /* Handle address, count and end. */ + ret |= cmi8x38_sb_dma_post(dev, addr, count, channel); + + return ret; +} + +static int +cmi8x38_sb_dma_writeb(void *priv, uint8_t val) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Stop if the DMA channel is invalid or if TDMA is masked. */ + int channel = dev->tdma_8; + if ((channel < 0) || (dev->tdma_mask & (1 << channel))) + return 1; + + /* Get 16-bit address and count registers. */ + uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c], + *count = (uint16_t *) &dev->io_regs[0x1e]; + + /* Write data. */ + mem_writeb_phys((dma[channel].ab & 0xffff0000) | *addr, val); + + /* Handle address, count and end. */ + cmi8x38_sb_dma_post(dev, addr, count, channel); + + return 0; +} + +static int +cmi8x38_sb_dma_writew(void *priv, uint16_t val) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Stop if the DMA channel is invalid or if TDMA is masked. */ + int channel = dev->tdma_16; + if ((channel < 0) || (dev->tdma_mask & (1 << channel))) + return 1; + + /* Get 16-bit address and count registers. */ + uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c], + *count = (uint16_t *) &dev->io_regs[0x1e]; + + /* Write data. */ + mem_writew_phys((dma[channel].ab & 0xfffe0000) | ((*addr) << 1), val); + + /* Handle address, count and end. */ + cmi8x38_sb_dma_post(dev, addr, count, channel); + + return 0; +} + +static void +cmi8x38_dma_write(uint16_t addr, uint8_t val, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Stop if DMA channel auto-detection is disabled. This is required for the CMI8338 TSR, + which disables auto-detection while copying the TDMA address/count to the SB DMA channel, + so that those writes don't loop back to the DMA register snoop mechanism implemented here. */ + if (!(dev->io_regs[0x27] & 0x01)) + return; + + /* Stop if this is not a TDMA channel. Also set or + clear the high channel flag while we're here. */ + int channel; + if (addr < 0x08) { + channel = addr >> 1; + if (channel != dev->tdma_8) + return; + dev->io_regs[0x10] &= ~0x20; + } else { + channel = 4 | ((addr >> 2) & 3); + if (channel != dev->tdma_16) + return; + dev->io_regs[0x10] |= 0x20; + } + + /* Write base address and count. */ + uint16_t *daddr = (uint16_t *) &dev->io_regs[0x1c], + *count = (uint16_t *) &dev->io_regs[0x1e]; + *daddr = dev->tdma_base_addr = dma[channel].ab >> !!(channel & 4); + *count = dev->tdma_base_count = dma[channel].cb; + cmi8x38_log("CMI8x38: Starting TDMA on DMA %d with addr %08X count %04X\n", + channel, + (channel & 4) ? ((dma[channel].ab & 0xfffe0000) | ((*daddr) << 1)) : ((dma[channel].ab & 0xffff0000) | *daddr), + *count); + + /* Clear the mysterious LHBTOG bit, assuming it corresponds + to the 8237 channel status bit. Nothing reads it. */ + dev->io_regs[0x10] &= ~0x40; +} + +static void +cmi8x38_dma_mask_write(uint16_t addr, uint8_t val, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* See comment on dma_write above. */ + if (!(dev->io_regs[0x27] & 0x01)) + return; + + /* Unmask TDMA on DMA unmasking edge. */ + if ((dev->tdma_8 >= 0) && (dev->prev_mask & (1 << dev->tdma_8)) && !(dma_m & (1 << dev->tdma_8))) + dev->tdma_mask &= ~(1 << dev->tdma_8); + else if ((dev->tdma_16 >= 0) && (dev->prev_mask & (1 << dev->tdma_16)) && !(dma_m & (1 << dev->tdma_16))) + dev->tdma_mask &= ~(1 << dev->tdma_16); + dev->prev_mask = dma_m; +} + +static void +cmi8338_io_trap(int size, uint16_t addr, uint8_t write, uint8_t val, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + +#ifdef ENABLE_CMI8X38_LOG + if (write) + cmi8x38_log("CMI8x38: cmi8338_io_trap(%04X, %02X)\n", addr, val); + else + cmi8x38_log("CMI8x38: cmi8338_io_trap(%04X)\n", addr); +#endif + + /* Weird offsets, it's best to just treat the register as a big dword. */ + uint32_t *lcs = (uint32_t *) &dev->io_regs[0x14]; + *lcs &= ~0x0003dff0; + *lcs |= (addr & 0x0f) << 14; + if (write) + *lcs |= 0x1000 | (val << 4); + + /* Raise NMI. */ + nmi = 1; +} + +static uint8_t +cmi8x38_sb_mixer_read(uint16_t addr, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16; + uint8_t ret = sb_ct1745_mixer_read(addr, dev->sb); + + if (addr & 1) { + if ((mixer->index == 0x0e) || (mixer->index >= 0xf0)) + ret = mixer->regs[mixer->index]; + cmi8x38_log("CMI8x38: sb_mixer_read(1, %02X) = %02X\n", mixer->index, ret); + } else { + cmi8x38_log("CMI8x38: sb_mixer_read(0) = %02X\n", ret); + } + + return ret; +} + +static void +cmi8x38_sb_mixer_write(uint16_t addr, uint8_t val, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16; + + /* Our clone mixer has a few differences. */ + if (addr & 1) { + cmi8x38_log("CMI8x38: sb_mixer_write(1, %02X, %02X)\n", mixer->index, val); + + switch (mixer->index) { + /* Reset interleaved stereo flag for SBPro mode. */ + case 0x00: + mixer->regs[0x0e] = 0x00; + break; + + /* No dynamic MPU port assignment. */ + case 0x84: + return; + + /* Some extended registers beyond those accepted by the CT1745. */ + case 0xf0: + if (dev->type == CMEDIA_CMI8338) + val &= 0xfe; + mixer->regs[mixer->index] = val; + return; + + case 0xf8 ... 0xff: + if (dev->type == CMEDIA_CMI8338) + mixer->regs[mixer->index] = val; + /* fall-through */ + + case 0xf1 ... 0xf7: + return; + } + + sb_ct1745_mixer_write(addr, val, dev->sb); + + /* No [3F:47] controls. */ + mixer->input_gain_L = 0; + mixer->input_gain_R = 0; + mixer->output_gain_L = (double) 1.0; + mixer->output_gain_R = (double) 1.0; + mixer->bass_l = 8; + mixer->bass_r = 8; + mixer->treble_l = 8; + mixer->treble_r = 8; + + /* Check interleaved stereo flag for SBPro mode. */ + if ((mixer->index == 0x00) || (mixer->index == 0x0e)) + sb_dsp_set_stereo(&dev->sb->dsp, mixer->regs[0x0e] & 2); + + /* Set TDMA channels if auto-detection is enabled. */ + if ((dev->io_regs[0x27] & 0x01) && (mixer->index == 0x81)) { + dev->tdma_8 = dev->sb->dsp.sb_8_dmanum; + if (dev->sb->dsp.sb_type >= SB16) + dev->tdma_16 = dev->sb->dsp.sb_16_dmanum; + } + } else { + cmi8x38_log("CMI8x38: sb_mixer_write(0, %02X)\n", val); + sb_ct1745_mixer_write(addr, val, dev->sb); + } +} + +static void +cmi8x38_remap_sb(cmi8x38_t *dev) +{ + if (dev->sb_base) { + io_removehandler(dev->sb_base, 0x0004, opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL, + cmi8x38_sb_mixer_write, NULL, NULL, dev); + + sb_dsp_setaddr(&dev->sb->dsp, 0); + } + + dev->sb_base = 0x220; + if (dev->type == CMEDIA_CMI8338) + dev->sb_base += (dev->io_regs[0x17] & 0x80) >> 2; + else + dev->sb_base += (dev->io_regs[0x17] & 0x0c) << 3; + if (!(dev->io_regs[0x04] & 0x08)) + dev->sb_base = 0; + cmi8x38_log("CMI8x38: remap_sb(%04X)\n", dev->sb_base); + + if (dev->sb_base) { + io_sethandler(dev->sb_base, 0x0004, opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL, + cmi8x38_sb_mixer_write, NULL, NULL, dev); + + sb_dsp_setaddr(&dev->sb->dsp, dev->sb_base); + } +} + +static void +cmi8x38_remap_opl(cmi8x38_t *dev) +{ + if (dev->opl_base) { + io_removehandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &dev->sb->opl); + } + + dev->opl_base = (dev->type == CMEDIA_CMI8338) ? 0x388 : opl_ports_cmi8738[dev->io_regs[0x17] & 0x03]; + io_trap_remap(dev->io_traps[TRAP_OPL], (dev->io_regs[0x04] & 0x01) && (dev->io_regs[0x16] & 0x80), dev->opl_base, 4); + if (!(dev->io_regs[0x1a] & 0x08)) + dev->opl_base = 0; + + cmi8x38_log("CMI8x38: remap_opl(%04X)\n", dev->opl_base); + + if (dev->opl_base) { + io_sethandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &dev->sb->opl); + } +} + +static void +cmi8x38_remap_mpu(cmi8x38_t *dev) +{ + if (dev->mpu_base) + mpu401_change_addr(dev->sb->mpu, 0); + + /* The CMI8338 datasheet's port range of [300:330] is + inaccurate. Drivers expect [330:300] like CMI8738. */ + dev->mpu_base = 0x330 - ((dev->io_regs[0x17] & 0x60) >> 1); + io_trap_remap(dev->io_traps[TRAP_MPU], (dev->io_regs[0x04] & 0x01) && (dev->io_regs[0x16] & 0x20), dev->mpu_base, 2); + if (!(dev->io_regs[0x04] & 0x04)) + dev->mpu_base = 0; + + cmi8x38_log("CMI8x38: remap_mpu(%04X)\n", dev->mpu_base); + + if (dev->mpu_base) + mpu401_change_addr(dev->sb->mpu, dev->mpu_base); +} + +static void +cmi8x38_remap_traps(cmi8x38_t *dev) +{ + cmi8x38_remap_opl(dev); + cmi8x38_remap_mpu(dev); + io_trap_remap(dev->io_traps[TRAP_DMA], (dev->io_regs[0x04] & 0x01) && (dev->io_regs[0x17] & 0x02), 0x0000, 16); + io_trap_remap(dev->io_traps[TRAP_PIC], (dev->io_regs[0x04] & 0x01) && (dev->io_regs[0x17] & 0x01), 0x0020, 2); +} + +static void +cmi8x38_start_playback(cmi8x38_t *dev) +{ + uint8_t i, val = dev->io_regs[0x00]; + + i = !(val & 0x01); + if (!dev->dma[0].playback_enabled && i) + timer_advance_u64(&dev->dma[0].poll_timer, dev->dma[0].timer_latch); + dev->dma[0].playback_enabled = i; + + i = !(val & 0x02); + if (!dev->dma[1].playback_enabled && i) + timer_advance_u64(&dev->dma[1].poll_timer, dev->dma[1].timer_latch); + dev->dma[1].playback_enabled = i; +} + +static uint8_t +cmi8x38_read(uint16_t addr, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + addr &= 0xff; + uint8_t ret; + + switch (addr) { + case 0x22: + case 0x23: + ret = cmi8x38_sb_mixer_read(addr ^ 1, dev); + break; + + case 0x40 ... 0x4f: + if (dev->type == CMEDIA_CMI8338) + goto io_reg; + else + ret = mpu401_read(addr, dev->sb->mpu); + break; + + case 0x50 ... 0x5f: + if (dev->type == CMEDIA_CMI8338) + goto io_reg; + else + ret = opl3_read(addr, &dev->sb->opl); + break; + + case 0x80: + case 0x88: + ret = dev->dma[(addr & 0x78) >> 3].sample_ptr; + break; + + case 0x81: + case 0x89: + ret = dev->dma[(addr & 0x78) >> 3].sample_ptr >> 8; + break; + + case 0x82: + case 0x8a: + ret = dev->dma[(addr & 0x78) >> 3].sample_ptr >> 16; + break; + + case 0x83: + case 0x8b: + ret = dev->dma[(addr & 0x78) >> 3].sample_ptr >> 24; + break; + + case 0x84: + case 0x8c: + ret = dev->dma[(addr & 0x78) >> 3].frame_count_dma; + break; + + case 0x85: + case 0x8d: + ret = dev->dma[(addr & 0x78) >> 3].frame_count_dma >> 8; + break; + + case 0x86: + case 0x8e: + ret = dev->dma[(addr & 0x78) >> 3].sample_count_out >> 2; + break; + + case 0x87: + case 0x8f: + ret = dev->dma[(addr & 0x78) >> 3].sample_count_out >> 10; + break; + + default: +io_reg: + ret = dev->io_regs[addr]; + break; + } + + cmi8x38_log("CMI8x38: read(%02X) = %02X\n", addr, ret); + return ret; +} + +static void +cmi8x38_write(uint16_t addr, uint8_t val, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + addr &= 0xff; + cmi8x38_log("CMI8x38: write(%02X, %02X)\n", addr, val); + + switch (addr) { + case 0x00: + val &= 0x0f; + + /* Don't care about recording DMA. */ + dev->dma[0].always_run = val & 0x01; + dev->dma[1].always_run = val & 0x02; + + /* Start playback if requested. */ + dev->io_regs[addr] = val; + cmi8x38_start_playback(dev); + break; + + case 0x02: + /* Reset or start DMA channels if requested. */ + dev->io_regs[addr] = val & 0x03; + for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) { + if (val & (0x04 << i)) { + /* Reset DMA channel. */ + val &= ~(0x01 << i); + dev->io_regs[0x10] &= ~(0x01 << i); /* clear interrupt */ + + /* Reset Sound Blaster as well when resetting channel 0. */ + if ((i == 0) && (dev->sb->dsp.sb_8_enable || dev->sb->dsp.sb_16_enable || dev->sb->dsp.sb_irq8 || dev->sb->dsp.sb_irq16)) + dev->sb->dsp.sb_8_enable = dev->sb->dsp.sb_16_enable = dev->sb->dsp.sb_irq8 = dev->sb->dsp.sb_irq16 = 0; + } else if (val & (0x01 << i)) { + /* Start DMA channel. */ + cmi8x38_log("CMI8x38: DMA %d trigger\n", i); + dev->dma[i].restart = 1; + cmi8x38_dma_process(&dev->dma[i]); + } + } + + /* Clear reset bits. */ + val &= 0x03; + + /* Start playback along with DMA channels. */ + if (val & 0x03) + cmi8x38_start_playback(dev); + + /* Update interrupts. */ + dev->io_regs[addr] = val; + cmi8x38_update_irqs(dev); + break; + + case 0x04: + /* Enable or disable the game port. */ + gameport_remap(dev->gameport, (val & 0x02) ? 0x200 : 0); + + /* Enable or disable the legacy devices. */ + dev->io_regs[addr] = val; + cmi8x38_remap_sb(dev); + /* remap_mpu called by remap_traps */ + + /* Enable or disable I/O traps. */ + cmi8x38_remap_traps(dev); + break; + + case 0x05: + dev->io_regs[addr] = val; + cmi8x38_speed_changed(dev); + break; + + case 0x08: + if (dev->type == CMEDIA_CMI8338) + val &= 0x0f; + break; + + case 0x09: +#if 0 /* actual CMI8338 behavior unconfirmed; this register is required for the Windows XP driver which outputs 96K */ + if (dev->type == CMEDIA_CMI8338) + return; +#endif + /* Update sample rate. */ + dev->io_regs[addr] = val; + cmi8x38_speed_changed(dev); + break; + + case 0x0a: + case 0x0b: + if (dev->type == CMEDIA_CMI8338) + return; + else + val &= 0xe0; + + if (addr == 0x0a) { + /* Set PCI latency timer if requested. */ + dev->pci_regs[0x0d] = (val & 0x80) ? 0x48 : 0x20; /* clearing SETLAT48 is undefined */ + } else { + /* Update channel count. */ + dev->io_regs[addr] = val; + cmi8x38_speed_changed(dev); + } + break; + + case 0x0e: + val &= 0x07; + + /* Clear interrupts. */ + dev->io_regs[0x10] &= val | 0xfc; + if (!(val & 0x04)) + dev->io_regs[0x11] &= ~0xc0; + cmi8x38_update_irqs(dev); + break; + + case 0x15: + if (dev->type == CMEDIA_CMI8338) + return; + else + val &= 0xf0; + + /* Update channel count. */ + dev->io_regs[addr] = val; + cmi8x38_speed_changed(dev); + break; + + case 0x16: + if (dev->type == CMEDIA_CMI8338) { + val &= 0xa0; + + /* Enable or disable I/O traps. */ + dev->io_regs[addr] = val; + cmi8x38_remap_traps(dev); + } + break; + + case 0x17: + if (dev->type == CMEDIA_CMI8338) { + val &= 0xf3; + + /* Force IRQ if requested. Clearing this bit is undefined. */ + if (val & 0x10) + pci_set_irq(dev->slot, PCI_INTA); + else if ((dev->io_regs[0x17] & 0x10) && !(val & 0x10)) + pci_clear_irq(dev->slot, PCI_INTA); + + /* Enable or disable I/O traps. */ + dev->io_regs[addr] = val; + cmi8x38_remap_traps(dev); + } + + /* Remap the legacy devices. */ + dev->io_regs[addr] = val; + cmi8x38_remap_sb(dev); + cmi8x38_remap_mpu(dev); + break; + + case 0x18: + if (dev->type == CMEDIA_CMI8338) + val &= 0x0f; + else + val &= 0xdf; + + /* Update the TDMA position update interrupt's sample interval. */ + dev->tdma_irq_mask = 2047 >> ((val >> 2) & 3); + break; + + case 0x19: + if (dev->type == CMEDIA_CMI8338) + return; + else + val &= 0xe0; + break; + + case 0x1a: + val &= 0xfd; + + /* Enable or disable the OPL. */ + dev->io_regs[addr] = val; + cmi8x38_remap_opl(dev); + break; + + case 0x1b: + if (dev->type == CMEDIA_CMI8338) + val &= 0xf0; + else + val &= 0xd7; + break; + + case 0x20: + /* ??? */ + break; + + case 0x21: + if (dev->type == CMEDIA_CMI8338) + val &= 0xf7; + else + val &= 0x07; + + /* Enable or disable SBPro channel swapping. */ + dev->sb->dsp.sbleftright_default = !!(val & 0x02); + + /* Enable or disable SB16 mode. */ + dev->sb->dsp.sb_type = (val & 0x01) ? SBPRO2 : SB16; + break; + + case 0x22: + case 0x23: + cmi8x38_sb_mixer_write(addr ^ 1, val, dev); + return; + + case 0x24: + if (dev->type == CMEDIA_CMI8338) + val &= 0xcf; + break; + + case 0x27: + if (dev->type == CMEDIA_CMI8338) + val &= 0x03; + else + val &= 0x27; + break; + + case 0x40 ... 0x4f: + if (dev->type != CMEDIA_CMI8338) + mpu401_write(addr, val, dev->sb->mpu); + return; + + case 0x50 ... 0x5f: + if (dev->type != CMEDIA_CMI8338) + opl3_write(addr, val, &dev->sb->opl); + return; + + case 0x92: + if (dev->type == CMEDIA_CMI8338) + return; + else + val &= 0x1f; + break; + + case 0x93: + if (dev->type == CMEDIA_CMI8338) + return; + else + val &= 0x10; + break; + + case 0x25: + case 0x26: + case 0x70: + case 0x71: + case 0x80 ... 0x8f: + break; + + default: + return; + } + + dev->io_regs[addr] = val; +} + +static void +cmi8x38_remap(cmi8x38_t *dev) +{ + if (dev->io_base) + io_removehandler(dev->io_base, 256, cmi8x38_read, NULL, NULL, cmi8x38_write, NULL, NULL, dev); + + dev->io_base = (dev->pci_regs[0x04] & 0x01) ? (dev->pci_regs[0x11] << 8) : 0; + cmi8x38_log("CMI8x38: remap(%04X)\n", dev->io_base); + + if (dev->io_base) + io_sethandler(dev->io_base, 256, cmi8x38_read, NULL, NULL, cmi8x38_write, NULL, NULL, dev); +} + +static uint8_t +cmi8x38_pci_read(int func, int addr, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + uint8_t ret = 0xff; + + if (!func) { + ret = dev->pci_regs[addr]; + cmi8x38_log("CMI8x38: pci_read(%02X) = %02X\n", addr, ret); + } + + return ret; +} + +static void +cmi8x38_pci_write(int func, int addr, uint8_t val, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + if (func) + return; + + cmi8x38_log("CMI8x38: pci_write(%02X, %02X)\n", addr, val); + + switch (addr) { + case 0x04: + val &= 0x05; + + /* Enable or disable the I/O BAR. */ + dev->pci_regs[addr] = val; + cmi8x38_remap(dev); + break; + + case 0x05: + val &= 0x01; + break; + + case 0x11: + /* Remap the I/O BAR. */ + dev->pci_regs[addr] = val; + cmi8x38_remap(dev); + break; + + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + if (!(dev->io_regs[0x1a] & 0x01)) + return; + break; + + case 0x40: + if (dev->type == CMEDIA_CMI8338) + val &= 0x0f; + else + return; + break; + + case 0x0c: + case 0x0d: + case 0x3c: + break; + + default: + return; + } + + dev->pci_regs[addr] = val; +} + +static void +cmi8x38_update(cmi8x38_t *dev, cmi8x38_dma_t *dma) +{ + sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16; + int32_t l = (dma->out_fl * mixer->voice_l) * mixer->master_l, + r = (dma->out_fr * mixer->voice_r) * mixer->master_r; + + for (; dma->pos < sound_pos_global; dma->pos++) { + dma->buffer[dma->pos * 2] = l; + dma->buffer[dma->pos * 2 + 1] = r; + } +} + +static void +cmi8x38_dma_process(void *priv) +{ + cmi8x38_dma_t *dma = (cmi8x38_dma_t *) priv; + cmi8x38_t *dev = dma->dev; + + /* Stop if this DMA channel is not active. */ + uint8_t dma_bit = 0x01 << dma->id; + if (!(dev->io_regs[0x02] & dma_bit)) { + cmi8x38_log("CMI8x38: Stopping DMA %d due to inactive channel (%02X)\n", dma->id, dev->io_regs[0x02]); + return; + } + + /* Schedule next run. */ + timer_on_auto(&dma->dma_timer, dma->dma_latch); + + /* Process DMA if it's active, and the FIFO has room or is disabled. */ + uint8_t dma_status = dev->io_regs[0x00] >> dma->id; + if (!(dma_status & 0x04) && (dma->always_run || ((dma->fifo_end - dma->fifo_pos) <= (sizeof(dma->fifo) - 4)))) { + /* Start DMA if requested. */ + if (dma->restart) { + /* Set up base address and counters. + Nothing reads sample_count_out; it's implemented as an assumption. */ + dma->restart = 0; + dma->sample_ptr = *((uint32_t *) &dev->io_regs[dma->reg]); + dma->frame_count_dma = dma->sample_count_out = *((uint16_t *) &dev->io_regs[dma->reg | 0x4]) + 1; + dma->frame_count_fragment = *((uint16_t *) &dev->io_regs[dma->reg | 0x6]) + 1; + + cmi8x38_log("CMI8x38: Starting DMA %d at %08X (count %04X fragment %04X)\n", dma->id, dma->sample_ptr, dma->frame_count_dma, dma->frame_count_fragment); + } + + if (dma_status & 0x01) { + /* Write channel: read data from FIFO. */ + mem_writel_phys(dma->sample_ptr, *((uint32_t *) &dma->fifo[dma->fifo_end & (sizeof(dma->fifo) - 1)])); + } else { + /* Read channel: write data to FIFO. */ + *((uint32_t *) &dma->fifo[dma->fifo_end & (sizeof(dma->fifo) - 1)]) = mem_readl_phys(dma->sample_ptr); + } + dma->fifo_end += 4; + dma->sample_ptr += 4; + + /* Check if the fragment size was reached. */ + if (--dma->frame_count_fragment <= 0) { + cmi8x38_log("CMI8x38: DMA %d fragment size reached at %04X frames left", dma->id, dma->frame_count_dma - 1); + + /* Reset fragment counter. */ + dma->frame_count_fragment = *((uint16_t *) &dev->io_regs[dma->reg | 0x6]) + 1; + + /* Fire interrupt if requested. */ + if (dev->io_regs[0x0e] & dma_bit) { + cmi8x38_log(", firing interrupt\n"); + + /* Set channel interrupt flag. */ + dev->io_regs[0x10] |= dma_bit; + + /* Fire interrupt. */ + cmi8x38_update_irqs(dev); + } else { + cmi8x38_log("\n"); + } + } + + /* Check if the buffer's end was reached. */ + if (--dma->frame_count_dma <= 0) { + dma->frame_count_dma = 0; + cmi8x38_log("CMI8x38: DMA %d end reached, restarting\n", dma->id); + + /* Restart DMA on the next run. */ + dma->restart = 1; + } + } +} + +static void +cmi8x38_poll(void *priv) +{ + cmi8x38_dma_t *dma = (cmi8x38_dma_t *) priv; + cmi8x38_t *dev = dma->dev; + int16_t *out_l, *out_r, *out_ol, *out_or; /* o = opposite */ + + /* Schedule next run if playback is enabled. */ + if (dma->playback_enabled) + timer_advance_u64(&dma->poll_timer, dma->timer_latch); + + /* Update audio buffer. */ + cmi8x38_update(dev, dma); + + /* Swap stereo pair if this is the rear DMA channel according to ENDBDAC and XCHGDAC. */ + if ((dev->io_regs[0x1a] & 0x80) && (!!(dev->io_regs[0x1a] & 0x40) ^ dma->id)) { + out_l = &dma->out_rl; + out_r = &dma->out_rr; + out_ol = &dma->out_fl; + out_or = &dma->out_fr; + } else { + out_l = &dma->out_fl; + out_r = &dma->out_fr; + out_ol = &dma->out_rl; + out_or = &dma->out_rr; + } + *out_ol = *out_or = dma->out_c = dma->out_lfe = 0; + + /* Feed next sample from the FIFO. */ + switch ((dev->io_regs[0x08] >> (dma->id << 1)) & 0x03) { + case 0x00: /* Mono, 8-bit PCM */ + if ((dma->fifo_end - dma->fifo_pos) >= 1) { + *out_l = *out_r = (dma->fifo[dma->fifo_pos++ & (sizeof(dma->fifo) - 1)] ^ 0x80) << 8; + dma->sample_count_out--; + goto n4spk3d; + } + break; + + case 0x01: /* Stereo, 8-bit PCM */ + if ((dma->fifo_end - dma->fifo_pos) >= 2) { + *out_l = (dma->fifo[dma->fifo_pos++ & (sizeof(dma->fifo) - 1)] ^ 0x80) << 8; + *out_r = (dma->fifo[dma->fifo_pos++ & (sizeof(dma->fifo) - 1)] ^ 0x80) << 8; + dma->sample_count_out -= 2; + goto n4spk3d; + } + break; + + case 0x02: /* Mono, 16-bit PCM */ + if ((dma->fifo_end - dma->fifo_pos) >= 2) { + *out_l = *out_r = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->sample_count_out -= 2; + goto n4spk3d; + } + break; + + case 0x03: /* Stereo, 16-bit PCM */ + switch (dma->channels) { /* multi-channel requires this data format */ + case 2: + if ((dma->fifo_end - dma->fifo_pos) >= 4) { + *out_l = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + *out_r = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->sample_count_out -= 4; + goto n4spk3d; + } + break; + + case 4: + if ((dma->fifo_end - dma->fifo_pos) >= 8) { + dma->out_fl = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_fr = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_rl = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_rr = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->sample_count_out -= 8; + return; + } + break; + + case 5: /* not supported by WDM and Linux drivers; channel layout assumed */ + if ((dma->fifo_end - dma->fifo_pos) >= 10) { + dma->out_fl = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_fr = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_rl = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_rr = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_c = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->sample_count_out -= 10; + return; + } + break; + + case 6: + if ((dma->fifo_end - dma->fifo_pos) >= 12) { + dma->out_fl = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_fr = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_rl = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_rr = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_c = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->out_lfe = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]); + dma->fifo_pos += 2; + dma->sample_count_out -= 12; + return; + } + break; + } + break; + } + + /* Feed silence if the FIFO is empty. */ + *out_l = *out_r = 0; + + /* Stop playback if DMA is disabled. */ + if ((*((uint32_t *) &dev->io_regs[0x00]) & (0x00010001 << dma->id)) != (0x00010000 << dma->id)) { + cmi8x38_log("CMI8x38: Stopping playback of DMA channel %d\n", dma->id); + dma->playback_enabled = 0; + } + + return; +n4spk3d: + /* Mirror front and rear channels if requested. */ + if (dev->io_regs[0x1b] & 0x04) { + *out_ol = *out_l; + *out_or = *out_r; + } +} + +static void +cmi8x38_get_buffer(int32_t *buffer, int len, void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Update wave playback channels. */ + cmi8x38_update(dev, &dev->dma[0]); + cmi8x38_update(dev, &dev->dma[1]); + + /* Apply wave mute. */ + if (!(dev->io_regs[0x24] & 0x40)) { + /* Fill buffer. */ + for (int c = 0; c < len * 2; c++) { + buffer[c] += dev->dma[0].buffer[c]; + buffer[c] += dev->dma[1].buffer[c]; + } + } + + dev->dma[0].pos = dev->dma[1].pos = 0; +} + +static void +cmi8x38_speed_changed(void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + double freq; + uint8_t dsr = dev->io_regs[0x09], freqreg = dev->io_regs[0x05] >> 2, + chfmt45 = dev->io_regs[0x0b], chfmt6 = dev->io_regs[0x15]; + +#ifdef ENABLE_CMI8X38_LOG + char buf[256]; + sprintf(buf, "%02X-%02X-%02X-%02X", dsr, freqreg, chfmt45, chfmt6); +#endif + + /* CMI8338 claims the frequency controls are for DAC (playback) and ADC (recording) + respectively, while CMI8738 claims they're for channel 0 and channel 1. The Linux + driver just assumes the latter definition, so that's what we're going to use here. */ + for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) { + /* More confusion. The Linux driver implies the sample rate doubling + bits take precedence over any configured sample rate. 128K with both + doubling bits set is also supported there, but that's for newer chips. */ + switch (dsr & 0x03) { + case 0x01: + freq = 88200.0; + break; + case 0x02: + freq = 96000.0; + break; +#if 0 + case 0x03: + freq = 128000.0; + break; +#endif + default: + freq = freqs[freqreg & 0x07]; + break; + } + + /* Set polling timer period. */ + freq = 1000000.0 / freq; + dev->dma[i].timer_latch = (uint64_t) ((double) TIMER_USEC * freq); + + /* Calculate channel count and set DMA timer period. */ + if ((dev->type == CMEDIA_CMI8338) || (i == 0)) { /* multi-channel requires channel 1 */ +stereo: + dev->dma[i].channels = 2; + } else { + if (chfmt45 & 0x80) + dev->dma[i].channels = (chfmt6 & 0x80) ? 6 : 5; + else if (chfmt45 & 0x20) + dev->dma[i].channels = 4; + else + goto stereo; + } + dev->dma[i].dma_latch = freq / dev->dma[i].channels; /* frequency / approximately(dwords * 2) */ + + /* Shift sample rate configuration registers. */ +#ifdef ENABLE_CMI8X38_LOG + sprintf(&buf[strlen(buf)], " %d:%X-%X-%.0f-%dC", i, dsr & 0x03, freqreg & 0x07, 1000000.0 / freq, dev->dma[i].channels); +#endif + dsr >>= 2; + freqreg >>= 3; + } + +#ifdef ENABLE_CMI8X38_LOG + if (cmi8x38_do_log) + ui_sb_bugui(buf); +#endif +} + +static void +cmi8x38_reset(void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + /* Reset PCI configuration registers. */ + memset(dev->pci_regs, 0, sizeof(dev->pci_regs)); + dev->pci_regs[0x00] = 0xf6; + dev->pci_regs[0x01] = 0x13; + dev->pci_regs[0x02] = dev->type; + dev->pci_regs[0x03] = 0x01; + dev->pci_regs[0x06] = (dev->type == CMEDIA_CMI8338) ? 0x80 : 0x10; + dev->pci_regs[0x07] = 0x02; + dev->pci_regs[0x08] = 0x10; + dev->pci_regs[0x0a] = 0x01; + dev->pci_regs[0x0b] = 0x04; + dev->pci_regs[0x0d] = 0x20; + dev->pci_regs[0x10] = 0x01; + dev->pci_regs[0x2c] = 0xf6; + dev->pci_regs[0x2d] = 0x13; + if (dev->type == CMEDIA_CMI8338) { + dev->pci_regs[0x2e] = 0xff; + dev->pci_regs[0x2f] = 0xff; + } else { + dev->pci_regs[0x2e] = dev->type; + dev->pci_regs[0x2f] = 0x01; + dev->pci_regs[0x34] = 0x40; + } + dev->pci_regs[0x3d] = 0x01; + dev->pci_regs[0x3e] = 0x02; + dev->pci_regs[0x3f] = 0x18; + + /* Reset I/O space registers. */ + memset(dev->io_regs, 0, sizeof(dev->io_regs)); + dev->io_regs[0x0b] = (dev->type >> 8) & 0x1f; + dev->io_regs[0x0f] = dev->type >> 16; + dev->tdma_irq_mask = 2047; + + /* Reset I/O mappings. */ + cmi8x38_remap(dev); + cmi8x38_remap_sb(dev); + /* remap_mpu and remap_opl called by remap_traps */ + cmi8x38_remap_traps(dev); + + /* Reset DMA channels. */ + for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) { + dev->dma[i].playback_enabled = 0; + dev->dma[i].fifo_pos = dev->dma[i].fifo_end = 0; + memset(dev->dma[i].fifo, 0, sizeof(dev->dma[i].fifo)); + } + + /* Reset TDMA channel. */ + dev->tdma_8 = 1; + dev->tdma_16 = 5; + dev->tdma_mask = 0; + + /* Reset Sound Blaster 16 mixer. */ + sb_ct1745_mixer_reset(dev->sb); +} + +static void * +cmi8x38_init(const device_t *info) +{ + cmi8x38_t *dev = malloc(sizeof(cmi8x38_t)); + memset(dev, 0, sizeof(cmi8x38_t)); + + /* Set the chip type. */ + if ((info->local == CMEDIA_CMI8738_6CH) && !device_get_config_int("six_channel")) + dev->type = CMEDIA_CMI8738_4CH; + else + dev->type = info->local; + cmi8x38_log("CMI8x38: init(%06X)\n", dev->type); + + /* Initialize Sound Blaster 16. */ + dev->sb = device_add_inst(device_get_config_int("receive_input") ? &sb_16_compat_device : &sb_16_compat_nompu_device, 1); + dev->sb->opl_enabled = 1; /* let snd_sb.c handle the OPL3 */ + dev->sb->mixer_sb16.output_filter = 0; /* no output filtering */ + + /* Initialize legacy interrupt and DMA handlers. */ + mpu401_irq_attach(dev->sb->mpu, cmi8x38_mpu_irq_update, cmi8x38_mpu_irq_pending, dev); + sb_dsp_irq_attach(&dev->sb->dsp, cmi8x38_sb_irq_update, dev); + sb_dsp_dma_attach(&dev->sb->dsp, cmi8x38_sb_dma_readb, cmi8x38_sb_dma_readw, cmi8x38_sb_dma_writeb, cmi8x38_sb_dma_writew, dev); + io_sethandler(0x00, 8, NULL, NULL, NULL, cmi8x38_dma_write, NULL, NULL, dev); + io_sethandler(0x08, 8, NULL, NULL, NULL, cmi8x38_dma_mask_write, NULL, NULL, dev); + io_sethandler(0xc0, 16, NULL, NULL, NULL, cmi8x38_dma_write, NULL, NULL, dev); + io_sethandler(0xd0, 16, NULL, NULL, NULL, cmi8x38_dma_mask_write, NULL, NULL, dev); + + /* Initialize DMA channels. */ + for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) { + dev->dma[i].id = i; + dev->dma[i].reg = 0x80 + (8 * i); + dev->dma[i].dev = dev; + + timer_add(&dev->dma[i].dma_timer, cmi8x38_dma_process, &dev->dma[i], 0); + timer_add(&dev->dma[i].poll_timer, cmi8x38_poll, &dev->dma[i], 0); + } + cmi8x38_speed_changed(dev); + + /* Initialize playback handler and CD audio filter. */ + sound_add_handler(cmi8x38_get_buffer, dev); + sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, dev->sb); + + /* Initialize game port. */ + dev->gameport = gameport_add(&gameport_pnp_device); + + /* Initialize I/O traps. */ + if (dev->type == CMEDIA_CMI8338) { + dev->io_traps[TRAP_DMA] = io_trap_add(cmi8338_io_trap, dev); + dev->io_traps[TRAP_PIC] = io_trap_add(cmi8338_io_trap, dev); + dev->io_traps[TRAP_OPL] = io_trap_add(cmi8338_io_trap, dev); + dev->io_traps[TRAP_MPU] = io_trap_add(cmi8338_io_trap, dev); + } + + /* Add PCI card. */ + dev->slot = pci_add_card((info->local & (1 << 13)) ? PCI_ADD_SOUND : PCI_ADD_NORMAL, cmi8x38_pci_read, cmi8x38_pci_write, dev); + + /* Perform initial reset. */ + cmi8x38_reset(dev); + + return dev; +} + +static void +cmi8x38_close(void *priv) +{ + cmi8x38_t *dev = (cmi8x38_t *) priv; + + cmi8x38_log("CMI8x38: close()\n"); + + for (int i = 0; i < TRAP_MAX; i++) + io_trap_remove(dev->io_traps[i]); + + free(dev); +} + +static const device_config_t cmi8x38_config[] = { + // clang-format off + { "receive_input", "Receive input (MPU-401)", CONFIG_BINARY, "", 1 }, + { "", "", -1 } + // clang-format on +}; + +static const device_config_t cmi8738_config[] = { + // clang-format off + { "six_channel", "6CH variant (6-channel)", CONFIG_BINARY, "", 1 }, + { "receive_input", "Receive input (MPU-401)", CONFIG_BINARY, "", 1 }, + { "", "", -1 } + // clang-format on +}; + +const device_t cmi8338_device = { + .name = "C-Media CMI8338", + .internal_name = "cmi8338", + .flags = DEVICE_PCI, + .local = CMEDIA_CMI8338, + .init = cmi8x38_init, + .close = cmi8x38_close, + .reset = cmi8x38_reset, + { .available = NULL }, + .speed_changed = cmi8x38_speed_changed, + .force_redraw = NULL, + .config = cmi8x38_config +}; + +const device_t cmi8338_onboard_device = { + .name = "C-Media CMI8338 (On-Board)", + .internal_name = "cmi8338_onboard", + .flags = DEVICE_PCI, + .local = CMEDIA_CMI8338 | (1 << 13), + .init = cmi8x38_init, + .close = cmi8x38_close, + .reset = cmi8x38_reset, + { .available = NULL }, + .speed_changed = cmi8x38_speed_changed, + .force_redraw = NULL, + .config = cmi8x38_config +}; + +const device_t cmi8738_device = { + .name = "C-Media CMI8738", + .internal_name = "cmi8738", + .flags = DEVICE_PCI, + .local = CMEDIA_CMI8738_6CH, + .init = cmi8x38_init, + .close = cmi8x38_close, + .reset = cmi8x38_reset, + { .available = NULL }, + .speed_changed = cmi8x38_speed_changed, + .force_redraw = NULL, + .config = cmi8738_config +}; + +const device_t cmi8738_onboard_device = { + .name = "C-Media CMI8738 (On-Board)", + .internal_name = "cmi8738_onboard", + .flags = DEVICE_PCI, + .local = CMEDIA_CMI8738_4CH | (1 << 13), + .init = cmi8x38_init, + .close = cmi8x38_close, + .reset = cmi8x38_reset, + { .available = NULL }, + .speed_changed = cmi8x38_speed_changed, + .force_redraw = NULL, + .config = cmi8x38_config +}; + +const device_t cmi8738_6ch_onboard_device = { + .name = "C-Media CMI8738-6CH (On-Board)", + .internal_name = "cmi8738_6ch_onboard", + .flags = DEVICE_PCI, + .local = CMEDIA_CMI8738_6CH | (1 << 13), + .init = cmi8x38_init, + .close = cmi8x38_close, + .reset = cmi8x38_reset, + { .available = NULL }, + .speed_changed = cmi8x38_speed_changed, + .force_redraw = NULL, + .config = cmi8x38_config +}; diff --git a/src/sound/snd_cs423x.c b/src/sound/snd_cs423x.c index c3c711f91..1f366e373 100644 --- a/src/sound/snd_cs423x.c +++ b/src/sound/snd_cs423x.c @@ -727,7 +727,7 @@ cs423x_init(const device_t *info) case CRYSTAL_CS4237B: case CRYSTAL_CS4238B: /* Same WSS codec and EEPROM structure. */ - dev->ad1848_type = AD1848_TYPE_CS4236; + dev->ad1848_type = (dev->type == CRYSTAL_CS4235) ? AD1848_TYPE_CS4235 : AD1848_TYPE_CS4236; dev->pnp_offset = 0x4013; /* Different Chip Version and ID registers, which shouldn't be reset by ad1848_init */ @@ -827,71 +827,71 @@ cs423x_speed_changed(void *priv) } const device_t cs4235_device = { - .name = "Crystal CS4235", + .name = "Crystal CS4235", .internal_name = "cs4235", - .flags = DEVICE_ISA | DEVICE_AT, - .local = CRYSTAL_CS4235, - .init = cs423x_init, - .close = cs423x_close, - .reset = cs423x_reset, + .flags = DEVICE_ISA | DEVICE_AT, + .local = CRYSTAL_CS4235, + .init = cs423x_init, + .close = cs423x_close, + .reset = cs423x_reset, { .available = NULL }, .speed_changed = cs423x_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; const device_t cs4235_onboard_device = { - .name = "Crystal CS4235 (On-Board)", + .name = "Crystal CS4235 (On-Board)", .internal_name = "cs4235_onboard", - .flags = DEVICE_ISA | DEVICE_AT, - .local = CRYSTAL_CS4235 | CRYSTAL_NOEEPROM, - .init = cs423x_init, - .close = cs423x_close, - .reset = cs423x_reset, + .flags = DEVICE_ISA | DEVICE_AT, + .local = CRYSTAL_CS4235 | CRYSTAL_NOEEPROM, + .init = cs423x_init, + .close = cs423x_close, + .reset = cs423x_reset, { .available = NULL }, .speed_changed = cs423x_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; const device_t cs4236b_device = { - .name = "Crystal CS4236B", + .name = "Crystal CS4236B", .internal_name = "cs4236b", - .flags = DEVICE_ISA | DEVICE_AT, - .local = CRYSTAL_CS4236B, - .init = cs423x_init, - .close = cs423x_close, - .reset = cs423x_reset, + .flags = DEVICE_ISA | DEVICE_AT, + .local = CRYSTAL_CS4236B, + .init = cs423x_init, + .close = cs423x_close, + .reset = cs423x_reset, { .available = NULL }, .speed_changed = cs423x_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; const device_t cs4237b_device = { - .name = "Crystal CS4237B", + .name = "Crystal CS4237B", .internal_name = "cs4237b", - .flags = DEVICE_ISA | DEVICE_AT, - .local = CRYSTAL_CS4237B, - .init = cs423x_init, - .close = cs423x_close, - .reset = cs423x_reset, + .flags = DEVICE_ISA | DEVICE_AT, + .local = CRYSTAL_CS4237B, + .init = cs423x_init, + .close = cs423x_close, + .reset = cs423x_reset, { .available = NULL }, .speed_changed = cs423x_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; const device_t cs4238b_device = { - .name = "Crystal CS4238B", + .name = "Crystal CS4238B", .internal_name = "cs4238b", - .flags = DEVICE_ISA | DEVICE_AT, - .local = CRYSTAL_CS4238B, - .init = cs423x_init, - .close = cs423x_close, - .reset = cs423x_reset, + .flags = DEVICE_ISA | DEVICE_AT, + .local = CRYSTAL_CS4238B, + .init = cs423x_init, + .close = cs423x_close, + .reset = cs423x_reset, { .available = NULL }, .speed_changed = cs423x_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; diff --git a/src/sound/snd_mpu401.c b/src/sound/snd_mpu401.c index c2cfa4caa..7b4a12aa3 100644 --- a/src/sound/snd_mpu401.c +++ b/src/sound/snd_mpu401.c @@ -1222,7 +1222,7 @@ MPU401_ReadData(mpu_t *mpu) return ret; } -static void +void mpu401_write(uint16_t addr, uint8_t val, void *priv) { mpu_t *mpu = (mpu_t *) priv; @@ -1241,7 +1241,7 @@ mpu401_write(uint16_t addr, uint8_t val, void *priv) } } -static uint8_t +uint8_t mpu401_read(uint16_t addr, void *priv) { mpu_t *mpu = (mpu_t *) priv; diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index 27c1c168c..9d80ef65c 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -376,9 +376,14 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) in_r = (mixer->input_selector_right & INPUT_MIDI_L) ? ((int32_t) out_l) : 0 + (mixer->input_selector_right & INPUT_MIDI_R) ? ((int32_t) out_r) : 0; - /* We divide by 3 to get the volume down to normal. */ - out_l += (low_fir_sb16(0, 0, (double) sb->dsp.buffer[c]) * mixer->voice_l) / 3.0; - out_r += (low_fir_sb16(0, 1, (double) sb->dsp.buffer[c + 1]) * mixer->voice_r) / 3.0; + if (mixer->output_filter) { + /* We divide by 3 to get the volume down to normal. */ + out_l += (low_fir_sb16(0, 0, (double) sb->dsp.buffer[c]) * mixer->voice_l) / 3.0; + out_r += (low_fir_sb16(0, 1, (double) sb->dsp.buffer[c + 1]) * mixer->voice_r) / 3.0; + } else { + out_l += (((double) sb->dsp.buffer[c]) * mixer->voice_l) / 3.0; + out_r += (((double) sb->dsp.buffer[c + 1]) * mixer->voice_r) / 3.0; + } out_l *= mixer->master_l; out_r *= mixer->master_r; @@ -459,7 +464,7 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) sb->emu8k.pos = 0; } -static void +void sb16_awe32_filter_cd_audio(int channel, double *buffer, void *p) { sb_t *sb = (sb_t *) p; @@ -472,7 +477,10 @@ sb16_awe32_filter_cd_audio(int channel, double *buffer, void *p) double bass_treble; double output_gain = (channel ? mixer->output_gain_R : mixer->output_gain_L); - c = (low_fir_sb16(1, channel, *buffer) * cd) / 3.0; + if (mixer->output_filter) + c = (low_fir_sb16(1, channel, *buffer) * cd) / 3.0; + else + c = ((*buffer) * cd) / 3.0; c *= master; /* This is not exactly how one does bass/treble controls, but the end result is like it. @@ -706,7 +714,7 @@ sb_ct1345_mixer_reset(sb_t *sb) sb_ct1345_mixer_write(5, 0, sb); } -static void +void sb_ct1745_mixer_write(uint16_t addr, uint8_t val, void *p) { sb_t *sb = (sb_t *) p; @@ -876,7 +884,7 @@ sb_ct1745_mixer_write(uint16_t addr, uint8_t val, void *p) } } -static uint8_t +uint8_t sb_ct1745_mixer_read(uint16_t addr, void *p) { sb_t *sb = (sb_t *) p; @@ -1046,7 +1054,7 @@ sb_ct1745_mixer_read(uint16_t addr, void *p) return ret; } -static void +void sb_ct1745_mixer_reset(sb_t *sb) { sb_ct1745_mixer_write(4, 0, sb); @@ -1756,11 +1764,10 @@ sb_16_init(const device_t *info) &sb->opl); } - sb->mixer_enabled = 1; - io_sethandler(addr + 4, 0x0002, - sb_ct1745_mixer_read, NULL, NULL, - sb_ct1745_mixer_write, NULL, NULL, - sb); + sb->mixer_enabled = 1; + sb->mixer_sb16.output_filter = 1; + io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, + sb_ct1745_mixer_write, NULL, NULL, sb); sound_add_handler(sb_get_buffer_sb16_awe32, sb); sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb); @@ -1790,7 +1797,8 @@ sb_16_pnp_init(const device_t *info) sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); - sb->mixer_enabled = 1; + sb->mixer_enabled = 1; + sb->mixer_sb16.output_filter = 1; sound_add_handler(sb_get_buffer_sb16_awe32, sb); sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb); @@ -1811,6 +1819,28 @@ sb_16_pnp_init(const device_t *info) return sb; } +static void * +sb_16_compat_init(const device_t *info) +{ + sb_t *sb = malloc(sizeof(sb_t)); + memset(sb, 0, sizeof(sb_t)); + + opl3_init(&sb->opl); + + sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); + sb_ct1745_mixer_reset(sb); + + sb->mixer_enabled = 1; + sound_add_handler(sb_get_buffer_sb16_awe32, sb); + + sb->mpu = (mpu_t *) malloc(sizeof(mpu_t)); + memset(sb->mpu, 0, sizeof(mpu_t)); + mpu401_init(sb->mpu, 0, 0, M_UART, info->local); + sb_dsp_set_mpu(&sb->dsp, sb->mpu); + + return sb; +} + static int sb_awe32_available() { @@ -1884,11 +1914,10 @@ sb_awe32_init(const device_t *info) &sb->opl); } - sb->mixer_enabled = 1; - io_sethandler(addr + 4, 0x0002, - sb_ct1745_mixer_read, NULL, NULL, - sb_ct1745_mixer_write, NULL, NULL, - sb); + sb->mixer_enabled = 1; + sb->mixer_sb16.output_filter = 1; + io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, + sb_ct1745_mixer_write, NULL, NULL, sb); sound_add_handler(sb_get_buffer_sb16_awe32, sb); sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb); @@ -1922,7 +1951,8 @@ sb_awe32_pnp_init(const device_t *info) sb_dsp_init(&sb->dsp, ((info->local == 2) || (info->local == 3) || (info->local == 4)) ? SBAWE64 : SBAWE32, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); - sb->mixer_enabled = 1; + sb->mixer_enabled = 1; + sb->mixer_sb16.output_filter = 1; sound_add_handler(sb_get_buffer_sb16_awe32, sb); sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb); @@ -2394,225 +2424,253 @@ static const device_config_t sb_awe64_gold_config[] = { // clang-format on const device_t sb_1_device = { - .name = "Sound Blaster v1.0", + .name = "Sound Blaster v1.0", .internal_name = "sb", - .flags = DEVICE_ISA, - .local = 0, - .init = sb_1_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA, + .local = 0, + .init = sb_1_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_config + .force_redraw = NULL, + .config = sb_config }; const device_t sb_15_device = { - .name = "Sound Blaster v1.5", + .name = "Sound Blaster v1.5", .internal_name = "sb1.5", - .flags = DEVICE_ISA, - .local = 0, - .init = sb_15_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA, + .local = 0, + .init = sb_15_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb15_config + .force_redraw = NULL, + .config = sb15_config }; const device_t sb_mcv_device = { - .name = "Sound Blaster MCV", + .name = "Sound Blaster MCV", .internal_name = "sbmcv", - .flags = DEVICE_MCA, - .local = 0, - .init = sb_mcv_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_MCA, + .local = 0, + .init = sb_mcv_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_mcv_config + .force_redraw = NULL, + .config = sb_mcv_config }; const device_t sb_2_device = { - .name = "Sound Blaster v2.0", + .name = "Sound Blaster v2.0", .internal_name = "sb2.0", - .flags = DEVICE_ISA, - .local = 0, - .init = sb_2_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA, + .local = 0, + .init = sb_2_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb2_config + .force_redraw = NULL, + .config = sb2_config }; const device_t sb_pro_v1_device = { - .name = "Sound Blaster Pro v1", + .name = "Sound Blaster Pro v1", .internal_name = "sbprov1", - .flags = DEVICE_ISA, - .local = 0, - .init = sb_pro_v1_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA, + .local = 0, + .init = sb_pro_v1_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_pro_config + .force_redraw = NULL, + .config = sb_pro_config }; const device_t sb_pro_v2_device = { - .name = "Sound Blaster Pro v2", + .name = "Sound Blaster Pro v2", .internal_name = "sbprov2", - .flags = DEVICE_ISA, - .local = 0, - .init = sb_pro_v2_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA, + .local = 0, + .init = sb_pro_v2_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_pro_config + .force_redraw = NULL, + .config = sb_pro_config }; const device_t sb_pro_mcv_device = { - .name = "Sound Blaster Pro MCV", + .name = "Sound Blaster Pro MCV", .internal_name = "sbpromcv", - .flags = DEVICE_MCA, - .local = 0, - .init = sb_pro_mcv_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_MCA, + .local = 0, + .init = sb_pro_mcv_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; const device_t sb_pro_compat_device = { - .name = "Sound Blaster Pro (Compatibility)", + .name = "Sound Blaster Pro (Compatibility)", .internal_name = "sbpro_compat", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 0, - .init = sb_pro_compat_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 0, + .init = sb_pro_compat_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = NULL + .force_redraw = NULL, + .config = NULL }; const device_t sb_16_device = { - .name = "Sound Blaster 16", + .name = "Sound Blaster 16", .internal_name = "sb16", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 0, - .init = sb_16_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 0, + .init = sb_16_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_16_config + .force_redraw = NULL, + .config = sb_16_config }; const device_t sb_16_pnp_device = { - .name = "Sound Blaster 16 PnP", + .name = "Sound Blaster 16 PnP", .internal_name = "sb16_pnp", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 0, - .init = sb_16_pnp_init, - .close = sb_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 0, + .init = sb_16_pnp_init, + .close = sb_close, + .reset = NULL, { .available = NULL }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_16_pnp_config + .force_redraw = NULL, + .config = sb_16_pnp_config +}; + +const device_t sb_16_compat_device = { + .name = "Sound Blaster 16 (Compatibility)", + .internal_name = "sb16_compat", + .flags = DEVICE_ISA | DEVICE_AT, + .local = 1, + .init = sb_16_compat_init, + .close = sb_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = sb_speed_changed, + .force_redraw = NULL, + .config = NULL +}; + +const device_t sb_16_compat_nompu_device = { + .name = "Sound Blaster 16 (Compatibility - MPU-401 Off)", + .internal_name = "sb16_compat", + .flags = DEVICE_ISA | DEVICE_AT, + .local = 0, + .init = sb_16_compat_init, + .close = sb_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = sb_speed_changed, + .force_redraw = NULL, + .config = NULL }; const device_t sb_32_pnp_device = { - .name = "Sound Blaster 32 PnP", + .name = "Sound Blaster 32 PnP", .internal_name = "sb32_pnp", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 0, - .init = sb_awe32_pnp_init, - .close = sb_awe32_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 0, + .init = sb_awe32_pnp_init, + .close = sb_awe32_close, + .reset = NULL, { .available = sb_32_pnp_available }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_32_pnp_config + .force_redraw = NULL, + .config = sb_32_pnp_config }; const device_t sb_awe32_device = { - .name = "Sound Blaster AWE32", + .name = "Sound Blaster AWE32", .internal_name = "sbawe32", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 0, - .init = sb_awe32_init, - .close = sb_awe32_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 0, + .init = sb_awe32_init, + .close = sb_awe32_close, + .reset = NULL, { .available = sb_awe32_available }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_awe32_config + .force_redraw = NULL, + .config = sb_awe32_config }; const device_t sb_awe32_pnp_device = { - .name = "Sound Blaster AWE32 PnP", + .name = "Sound Blaster AWE32 PnP", .internal_name = "sbawe32_pnp", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 1, - .init = sb_awe32_pnp_init, - .close = sb_awe32_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 1, + .init = sb_awe32_pnp_init, + .close = sb_awe32_close, + .reset = NULL, { .available = sb_awe32_pnp_available }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_awe32_pnp_config + .force_redraw = NULL, + .config = sb_awe32_pnp_config }; const device_t sb_awe64_value_device = { - .name = "Sound Blaster AWE64 Value", + .name = "Sound Blaster AWE64 Value", .internal_name = "sbawe64_value", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 2, - .init = sb_awe32_pnp_init, - .close = sb_awe32_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 2, + .init = sb_awe32_pnp_init, + .close = sb_awe32_close, + .reset = NULL, { .available = sb_awe64_value_available }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_awe64_value_config + .force_redraw = NULL, + .config = sb_awe64_value_config }; const device_t sb_awe64_device = { - .name = "Sound Blaster AWE64", + .name = "Sound Blaster AWE64", .internal_name = "sbawe64", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 3, - .init = sb_awe32_pnp_init, - .close = sb_awe32_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 3, + .init = sb_awe32_pnp_init, + .close = sb_awe32_close, + .reset = NULL, { .available = sb_awe64_available }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_awe64_config + .force_redraw = NULL, + .config = sb_awe64_config }; const device_t sb_awe64_gold_device = { - .name = "Sound Blaster AWE64 Gold", + .name = "Sound Blaster AWE64 Gold", .internal_name = "sbawe64_gold", - .flags = DEVICE_ISA | DEVICE_AT, - .local = 4, - .init = sb_awe32_pnp_init, - .close = sb_awe32_close, - .reset = NULL, + .flags = DEVICE_ISA | DEVICE_AT, + .local = 4, + .init = sb_awe32_pnp_init, + .close = sb_awe32_close, + .reset = NULL, { .available = sb_awe64_gold_available }, .speed_changed = sb_speed_changed, - .force_redraw = NULL, - .config = sb_awe64_gold_config + .force_redraw = NULL, + .config = sb_awe64_gold_config }; diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c index 59f384e2a..c87f45e2f 100644 --- a/src/sound/snd_sb_dsp.c +++ b/src/sound/snd_sb_dsp.c @@ -169,6 +169,16 @@ recalc_sb16_filter(int c, int playback_freq) low_fir_sb16_coef[c][n] /= gain; } +static void +sb_irq_update_pic(void *priv, int set) +{ + sb_dsp_t *dsp = (sb_dsp_t *) priv; + if (set) + picint(1 << dsp->sb_irqnum); + else + picintc(1 << dsp->sb_irqnum); +} + void sb_update_mask(sb_dsp_t *dsp, int irqm8, int irqm16, int irqm401) { @@ -185,7 +195,7 @@ sb_update_mask(sb_dsp_t *dsp, int irqm8, int irqm16, int irqm401) dsp->sb_irqm401 = irqm401; if (clear) - picintc(1 << dsp->sb_irqnum); + dsp->irq_update(dsp->irq_priv, 0); } void @@ -210,9 +220,9 @@ sb_update_status(sb_dsp_t *dsp, int bit, int set) } if (set && !masked) - picint(1 << dsp->sb_irqnum); + dsp->irq_update(dsp->irq_priv, 1); else if (!set) - picintc(1 << dsp->sb_irqnum); + dsp->irq_update(dsp->irq_priv, 0); } void @@ -281,7 +291,7 @@ sb_dsp_reset(sb_dsp_t *dsp) dsp->record_pos_read = 0; dsp->record_pos_write = SB_DSP_REC_SAFEFTY_MARGIN; - picintc(1 << dsp->sb_irqnum); + dsp->irq_update(dsp->irq_priv, 0); dsp->asp_data_len = 0; } @@ -339,7 +349,7 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_pausetime = -1; if (dma8) { - dsp->sb_8_length = len; + dsp->sb_8_length = dsp->sb_8_origlength = len; dsp->sb_8_format = format; dsp->sb_8_autoinit = autoinit; dsp->sb_8_pause = 0; @@ -350,10 +360,10 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_8_output = 1; if (!timer_is_enabled(&dsp->output_timer)) timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); - dsp->sbleftright = 0; + dsp->sbleftright = dsp->sbleftright_default; dsp->sbdacpos = 0; } else { - dsp->sb_16_length = len; + dsp->sb_16_length = dsp->sb_16_origlength = len; dsp->sb_16_format = format; dsp->sb_16_autoinit = autoinit; dsp->sb_16_pause = 0; @@ -370,7 +380,7 @@ void sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) { if (dma8) { - dsp->sb_8_length = len; + dsp->sb_8_length = dsp->sb_8_origlength = len; dsp->sb_8_format = format; dsp->sb_8_autoinit = autoinit; dsp->sb_8_pause = 0; @@ -381,7 +391,7 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) if (!timer_is_enabled(&dsp->input_timer)) timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } else { - dsp->sb_16_length = len; + dsp->sb_16_length = dsp->sb_16_origlength = len; dsp->sb_16_format = format; dsp->sb_16_autoinit = autoinit; dsp->sb_16_pause = 0; @@ -397,29 +407,31 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) } int -sb_8_read_dma(sb_dsp_t *dsp) +sb_8_read_dma(void *priv) { + sb_dsp_t *dsp = (sb_dsp_t *) priv; return dma_channel_read(dsp->sb_8_dmanum); } -void -sb_8_write_dma(sb_dsp_t *dsp, uint8_t val) +int +sb_8_write_dma(void *priv, uint8_t val) { - dma_channel_write(dsp->sb_8_dmanum, val); + sb_dsp_t *dsp = (sb_dsp_t *) priv; + return dma_channel_write(dsp->sb_8_dmanum, val) == DMA_NODATA; } int -sb_16_read_dma(sb_dsp_t *dsp) +sb_16_read_dma(void *priv) { + sb_dsp_t *dsp = (sb_dsp_t *) priv; return dma_channel_read(dsp->sb_16_dmanum); } int -sb_16_write_dma(sb_dsp_t *dsp, uint16_t val) +sb_16_write_dma(void *priv, uint16_t val) { - int ret = dma_channel_write(dsp->sb_16_dmanum, val); - - return (ret == DMA_NODATA); + sb_dsp_t *dsp = (sb_dsp_t *) priv; + return dma_channel_write(dsp->sb_16_dmanum, val) == DMA_NODATA; } void @@ -469,12 +481,12 @@ sb_exec_command(sb_dsp_t *dsp) sb_start_dma(dsp, 1, 0, 0, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); break; case 0x17: /* 2-bit ADPCM output with reference */ - dsp->sbref = sb_8_read_dma(dsp); + dsp->sbref = dsp->dma_readb(dsp->dma_priv); dsp->sbstep = 0; /* Fall through */ case 0x16: /* 2-bit ADPCM output */ sb_start_dma(dsp, 1, 0, ADPCM_2, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; if (dsp->sb_command == 0x17) dsp->sb_8_length--; @@ -486,7 +498,7 @@ sb_exec_command(sb_dsp_t *dsp) case 0x1F: /* 2-bit ADPCM autoinit output */ if (dsp->sb_type >= SB15) { sb_start_dma(dsp, 1, 1, ADPCM_2, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; } break; @@ -581,23 +593,23 @@ sb_exec_command(sb_dsp_t *dsp) dsp->sb_8_autolen = dsp->sb_data[0] + (dsp->sb_data[1] << 8); break; case 0x75: /* 4-bit ADPCM output with reference */ - dsp->sbref = sb_8_read_dma(dsp); + dsp->sbref = dsp->dma_readb(dsp->dma_priv); dsp->sbstep = 0; /* Fall through */ case 0x74: /* 4-bit ADPCM output */ sb_start_dma(dsp, 1, 0, ADPCM_4, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; if (dsp->sb_command == 0x75) dsp->sb_8_length--; break; case 0x77: /* 2.6-bit ADPCM output with reference */ - dsp->sbref = sb_8_read_dma(dsp); + dsp->sbref = dsp->dma_readb(dsp->dma_priv); dsp->sbstep = 0; /* Fall through */ case 0x76: /* 2.6-bit ADPCM output */ sb_start_dma(dsp, 1, 0, ADPCM_26, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; if (dsp->sb_command == 0x77) dsp->sb_8_length--; @@ -605,14 +617,14 @@ sb_exec_command(sb_dsp_t *dsp) case 0x7D: /* 4-bit ADPCM autoinit output */ if (dsp->sb_type >= SB15) { sb_start_dma(dsp, 1, 1, ADPCM_4, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; } break; case 0x7F: /* 2.6-bit ADPCM autoinit output */ if (dsp->sb_type >= SB15) { sb_start_dma(dsp, 1, 1, ADPCM_26, dsp->sb_data[0] + (dsp->sb_data[1] << 8)); - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; } break; @@ -757,7 +769,7 @@ sb_exec_command(sb_dsp_t *dsp) } dsp->sbe2 += sbe2dat[dsp->sbe2count & 3][8]; dsp->sbe2count++; - sb_8_write_dma(dsp, dsp->sbe2); + dsp->dma_writeb(dsp->dma_priv, dsp->sbe2); break; case 0xE3: /* DSP copyright */ if (dsp->sb_type >= SB16) { @@ -1016,7 +1028,7 @@ sb_read(uint16_t a, void *priv) } break; case 0xE: /* Read data ready */ - picintc(1 << dsp->sb_irqnum); + dsp->irq_update(dsp->irq_priv, 0); dsp->sb_irq8 = dsp->sb_irq16 = 0; /* Only bit 7 is defined but aztech diagnostics fail if the others are set. Keep the original behavior to not interfere with what's already working. */ if (IS_AZTECH(dsp)) { @@ -1030,7 +1042,7 @@ sb_read(uint16_t a, void *priv) case 0xF: /* 16-bit ack */ dsp->sb_irq16 = 0; if (!dsp->sb_irq8) - picintc(1 << dsp->sb_irqnum); + dsp->irq_update(dsp->irq_priv, 0); sb_dsp_log("SB 16-bit ACK read 0xFF\n"); ret = 0xff; break; @@ -1108,6 +1120,16 @@ sb_dsp_init(sb_dsp_t *dsp, int type, int subtype, void *parent) dsp->sb_16_dmanum = 5; dsp->mpu = NULL; + dsp->sbleftright_default = 0; + + dsp->irq_update = sb_irq_update_pic; + dsp->irq_priv = dsp; + dsp->dma_readb = sb_8_read_dma; + dsp->dma_readw = sb_16_read_dma; + dsp->dma_writeb = sb_8_write_dma; + dsp->dma_writew = sb_16_write_dma; + dsp->dma_priv = dsp; + sb_doreset(dsp); timer_add(&dsp->output_timer, pollsb, dsp, 0); @@ -1149,6 +1171,28 @@ sb_dsp_set_stereo(sb_dsp_t *dsp, int stereo) dsp->stereo = stereo; } +void +sb_dsp_irq_attach(sb_dsp_t *dsp, void (*irq_update)(void *priv, int set), void *priv) +{ + dsp->irq_update = irq_update; + dsp->irq_priv = priv; +} + +void +sb_dsp_dma_attach(sb_dsp_t *dsp, + int (*dma_readb)(void *priv), + int (*dma_readw)(void *priv), + int (*dma_writeb)(void *priv, uint8_t val), + int (*dma_writew)(void *priv, uint16_t val), + void *priv) +{ + dsp->dma_readb = dma_readb; + dsp->dma_readw = dma_readw; + dsp->dma_writeb = dma_writeb; + dsp->dma_writew = dma_writew; + dsp->dma_priv = priv; +} + void pollsb(void *p) { @@ -1162,13 +1206,13 @@ pollsb(void *p) switch (dsp->sb_8_format) { case 0x00: /* Mono unsigned */ - data[0] = sb_8_read_dma(dsp); + data[0] = dsp->dma_readb(dsp->dma_priv); /* Needed to prevent clicking in Worms, which programs the DSP to auto-init DMA but programs the DMA controller to single cycle */ if (data[0] == DMA_NODATA) break; dsp->sbdat = (data[0] ^ 0x80) << 8; - if ((dsp->sb_type >= SBPRO) && (dsp->sb_type < SB16) && dsp->stereo) { + if (dsp->stereo) { sb_dsp_log("pollsb: Mono unsigned, dsp->stereo, %s channel, %04X\n", dsp->sbleftright ? "left" : "right", dsp->sbdat); if (dsp->sbleftright) @@ -1181,11 +1225,11 @@ pollsb(void *p) dsp->sb_8_length--; break; case 0x10: /* Mono signed */ - data[0] = sb_8_read_dma(dsp); + data[0] = dsp->dma_readb(dsp->dma_priv); if (data[0] == DMA_NODATA) break; dsp->sbdat = data[0] << 8; - if ((dsp->sb_type >= SBPRO) && (dsp->sb_type < SB16) && dsp->stereo) { + if (dsp->stereo) { sb_dsp_log("pollsb: Mono signed, dsp->stereo, %s channel, %04X\n", dsp->sbleftright ? "left" : "right", data[0], dsp->sbdat); if (dsp->sbleftright) @@ -1198,8 +1242,8 @@ pollsb(void *p) dsp->sb_8_length--; break; case 0x20: /* Stereo unsigned */ - data[0] = sb_8_read_dma(dsp); - data[1] = sb_8_read_dma(dsp); + data[0] = dsp->dma_readb(dsp->dma_priv); + data[1] = dsp->dma_readb(dsp->dma_priv); if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA)) break; dsp->sbdatl = (data[0] ^ 0x80) << 8; @@ -1207,8 +1251,8 @@ pollsb(void *p) dsp->sb_8_length -= 2; break; case 0x30: /* Stereo signed */ - data[0] = sb_8_read_dma(dsp); - data[1] = sb_8_read_dma(dsp); + data[0] = dsp->dma_readb(dsp->dma_priv); + data[1] = dsp->dma_readb(dsp->dma_priv); if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA)) break; dsp->sbdatl = data[0] << 8; @@ -1241,11 +1285,11 @@ pollsb(void *p) if (dsp->sbdacpos >= 2) { dsp->sbdacpos = 0; - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; } - if ((dsp->sb_type >= SBPRO) && (dsp->sb_type < SB16) && dsp->stereo) { + if (dsp->stereo) { sb_dsp_log("pollsb: ADPCM 4, dsp->stereo, %s channel, %04X\n", dsp->sbleftright ? "left" : "right", dsp->sbdat); if (dsp->sbleftright) @@ -1284,11 +1328,11 @@ pollsb(void *p) dsp->sbdacpos++; if (dsp->sbdacpos >= 3) { dsp->sbdacpos = 0; - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); dsp->sb_8_length--; } - if ((dsp->sb_type >= SBPRO) && (dsp->sb_type < SB16) && dsp->stereo) { + if (dsp->stereo) { sb_dsp_log("pollsb: ADPCM 26, dsp->stereo, %s channel, %04X\n", dsp->sbleftright ? "left" : "right", dsp->sbdat); if (dsp->sbleftright) @@ -1321,10 +1365,10 @@ pollsb(void *p) dsp->sbdacpos++; if (dsp->sbdacpos >= 4) { dsp->sbdacpos = 0; - dsp->sbdat2 = sb_8_read_dma(dsp); + dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv); } - if ((dsp->sb_type >= SBPRO) && (dsp->sb_type < SB16) && dsp->stereo) { + if (dsp->stereo) { sb_dsp_log("pollsb: ADPCM 2, dsp->stereo, %s channel, %04X\n", dsp->sbleftright ? "left" : "right", dsp->sbdat); if (dsp->sbleftright) @@ -1339,7 +1383,7 @@ pollsb(void *p) if (dsp->sb_8_length < 0) { if (dsp->sb_8_autoinit) - dsp->sb_8_length = dsp->sb_8_autolen; + dsp->sb_8_length = dsp->sb_8_origlength = dsp->sb_8_autolen; else { dsp->sb_8_enable = 0; timer_disable(&dsp->output_timer); @@ -1352,22 +1396,22 @@ pollsb(void *p) switch (dsp->sb_16_format) { case 0x00: /* Mono unsigned */ - data[0] = sb_16_read_dma(dsp); + data[0] = dsp->dma_readw(dsp->dma_priv); if (data[0] == DMA_NODATA) break; dsp->sbdatl = dsp->sbdatr = data[0] ^ 0x8000; dsp->sb_16_length--; break; case 0x10: /* Mono signed */ - data[0] = sb_16_read_dma(dsp); + data[0] = dsp->dma_readw(dsp->dma_priv); if (data[0] == DMA_NODATA) break; dsp->sbdatl = dsp->sbdatr = data[0]; dsp->sb_16_length--; break; case 0x20: /* Stereo unsigned */ - data[0] = sb_16_read_dma(dsp); - data[1] = sb_16_read_dma(dsp); + data[0] = dsp->dma_readw(dsp->dma_priv); + data[1] = dsp->dma_readw(dsp->dma_priv); if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA)) break; dsp->sbdatl = data[0] ^ 0x8000; @@ -1375,8 +1419,8 @@ pollsb(void *p) dsp->sb_16_length -= 2; break; case 0x30: /* Stereo signed */ - data[0] = sb_16_read_dma(dsp); - data[1] = sb_16_read_dma(dsp); + data[0] = dsp->dma_readw(dsp->dma_priv); + data[1] = dsp->dma_readw(dsp->dma_priv); if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA)) break; dsp->sbdatl = data[0]; @@ -1388,7 +1432,7 @@ pollsb(void *p) if (dsp->sb_16_length < 0) { sb_dsp_log("16DMA over %i\n", dsp->sb_16_autoinit); if (dsp->sb_16_autoinit) - dsp->sb_16_length = dsp->sb_16_autolen; + dsp->sb_16_length = dsp->sb_16_origlength = dsp->sb_16_autolen; else { dsp->sb_16_enable = 0; timer_disable(&dsp->output_timer); @@ -1418,27 +1462,27 @@ sb_poll_i(void *p) if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && !dsp->sb_8_output) { switch (dsp->sb_8_format) { case 0x00: /* Mono unsigned As the manual says, only the left channel is recorded */ - sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80); + dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80); dsp->sb_8_length--; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; break; case 0x10: /* Mono signed As the manual says, only the left channel is recorded */ - sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8)); + dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8)); dsp->sb_8_length--; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; break; case 0x20: /* Stereo unsigned */ - sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80); - sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8) ^ 0x80); + dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80); + dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8) ^ 0x80); dsp->sb_8_length -= 2; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; break; case 0x30: /* Stereo signed */ - sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8)); - sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8)); + dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8)); + dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8)); dsp->sb_8_length -= 2; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; @@ -1447,7 +1491,7 @@ sb_poll_i(void *p) if (dsp->sb_8_length < 0) { if (dsp->sb_8_autoinit) - dsp->sb_8_length = dsp->sb_8_autolen; + dsp->sb_8_length = dsp->sb_8_origlength = dsp->sb_8_autolen; else { dsp->sb_8_enable = 0; timer_disable(&dsp->input_timer); @@ -1459,31 +1503,31 @@ sb_poll_i(void *p) if (dsp->sb_16_enable && !dsp->sb_16_pause && (dsp->sb_pausetime < 0LL) && !dsp->sb_16_output) { switch (dsp->sb_16_format) { case 0x00: /* Unsigned mono. As the manual says, only the left channel is recorded */ - if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000)) + if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000)) return; dsp->sb_16_length--; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; break; case 0x10: /* Signed mono. As the manual says, only the left channel is recorded */ - if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read])) + if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read])) return; dsp->sb_16_length--; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; break; case 0x20: /* Unsigned stereo */ - if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000)) + if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000)) return; - sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read + 1] ^ 0x8000); + dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read + 1] ^ 0x8000); dsp->sb_16_length -= 2; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; break; case 0x30: /* Signed stereo */ - if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read])) + if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read])) return; - sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read + 1]); + dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read + 1]); dsp->sb_16_length -= 2; dsp->record_pos_read += 2; dsp->record_pos_read &= 0xFFFF; @@ -1492,7 +1536,7 @@ sb_poll_i(void *p) if (dsp->sb_16_length < 0) { if (dsp->sb_16_autoinit) - dsp->sb_16_length = dsp->sb_16_autolen; + dsp->sb_16_length = dsp->sb_16_origlength = dsp->sb_16_autolen; else { dsp->sb_16_enable = 0; timer_disable(&dsp->input_timer); diff --git a/src/sound/sound.c b/src/sound/sound.c index 2783526d9..5a3e978f6 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -139,6 +139,8 @@ static const SOUND_CARD sound_cards[] = { { &ncr_business_audio_device }, { &sb_mcv_device }, { &sb_pro_mcv_device }, + { &cmi8338_device }, + { &cmi8738_device }, { &es1371_device }, { &ad1881_device }, { &cs4297a_device }, diff --git a/src/unix/unix.c b/src/unix/unix.c index 4e7e3602d..1f60ad565 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -32,6 +32,7 @@ #include <86box/timer.h> #include <86box/nvr.h> #include <86box/ui.h> +#include <86box/gdbstub.h> static int first_use = 1; static uint64_t StartingTime; @@ -229,19 +230,23 @@ wchar_t* plat_get_string(int i) switch (i) { case IDS_2077: - return L"Click to capture mouse."; + return L"Click to capture mouse"; case IDS_2078: return L"Press CTRL-END to release mouse"; case IDS_2079: return L"Press CTRL-END or middle button to release mouse"; case IDS_2080: return L"Failed to initialize FluidSynth"; + case IDS_2130: + return L"Invalid configuration"; case IDS_4099: return L"MFM/RLL or ESDI CD-ROM drives never existed"; case IDS_2093: return L"Failed to set up PCap"; case IDS_2094: return L"No PCap devices found"; + case IDS_2095: + return L"Invalid PCap device"; case IDS_2110: return L"Unable to initialize FreeType"; case IDS_2111: @@ -250,6 +255,8 @@ wchar_t* plat_get_string(int i) return L"libfreetype is required for ESC/P printer emulation."; case IDS_2132: return L"libgs is required for automatic conversion of PostScript files to PDF.\n\nAny documents sent to the generic PostScript printer will be saved as PostScript (.ps) files."; + case IDS_2133: + return L"libfluidsynth is required for FluidSynth MIDI output."; case IDS_2129: return L"Make sure libpcap is installed and that you are on a libpcap-compatible network connection."; case IDS_2114: @@ -260,6 +267,8 @@ wchar_t* plat_get_string(int i) return L"Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card."; case IDS_2128: return L"Hardware not available"; + case IDS_2142: + return L"Monitor in sleep mode"; } return L""; } @@ -519,6 +528,11 @@ main_thread(void *param) while (!is_quit && cpu_thread_run) { /* See if it is time to run a frame of code. */ new_time = SDL_GetTicks(); +#ifdef USE_GDBSTUB + if (gdbstub_next_asap && (drawits <= 0)) + drawits = 10; + else +#endif drawits += (new_time - old_time); old_time = new_time; if (drawits > 0 && !dopause) { diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index bbf5fd4b9..325c5d5dd 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -660,7 +660,7 @@ SNDOBJ := sound.o \ snd_lpt_dac.o snd_lpt_dss.o \ snd_adlib.o snd_adlibgold.o snd_ad1848.o snd_audiopci.o \ snd_ac97_codec.o snd_ac97_via.o \ - snd_azt2316a.o snd_cs423x.o \ + snd_azt2316a.o snd_cs423x.o snd_cmi8x38.o \ snd_cms.o \ snd_gus.o \ snd_sb.o snd_sb_dsp.o \ diff --git a/src/win/win.c b/src/win/win.c index 1490a1ce1..f4b906d7d 100644 --- a/src/win/win.c +++ b/src/win/win.c @@ -55,6 +55,7 @@ #include <86box/win_opengl.h> #include <86box/win.h> #include <86box/version.h> +#include <86box/gdbstub.h> #ifdef MTR_ENABLED #include #endif @@ -525,6 +526,11 @@ main_thread(void *param) while (!is_quit && cpu_thread_run) { /* See if it is time to run a frame of code. */ new_time = GetTickCount(); +#ifdef USE_GDBSTUB + if (gdbstub_next_asap && (drawits <= 0)) + drawits = 10; + else +#endif drawits += (new_time - old_time); old_time = new_time; if (drawits > 0 && !dopause) {